migrated NativeDownloadAndPlatformLibsIT to junit 5 and gradle TestKit

This commit is contained in:
Dmitrii Krasnov
2023-05-24 13:52:57 +02:00
committed by Space Team
parent 168076766f
commit 0191a15d96
18 changed files with 448 additions and 239 deletions
@@ -311,6 +311,10 @@ tasks.named<Task>("check") {
}
tasks.withType<Test> {
// Disable KONAN_DATA_DIR env variable for all integration tests
// because we are using `konan.data.dir` gradle property instead
environment.remove("KONAN_DATA_DIR")
val noTestProperty = project.providers.gradleProperty("noTest")
onlyIf { !noTestProperty.isPresent }
@@ -28,10 +28,13 @@ import org.junit.Before
import org.junit.runner.RunWith
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import java.util.regex.Pattern
import javax.xml.parsers.DocumentBuilderFactory
import kotlin.collections.HashSet
import kotlin.io.path.absolutePathString
import kotlin.io.path.isDirectory
import kotlin.test.*
@@ -273,6 +276,7 @@ abstract class BaseGradleIT {
val useParsableDiagnosticsFormatting: Boolean = true,
val showDiagnosticsStacktrace: Boolean? = false, // false by default to not clutter the testdata + stacktraces change often
val stacktraceMode: String? = StacktraceOption.FULL_STACKTRACE_LONG_OPTION,
val konanDataDir: Path = Paths.get("build/.konan"),
) {
val safeAndroidGradlePluginVersion: AGPVersion
get() = androidGradlePluginVersion ?: error("AGP version is expected to be set")
@@ -942,6 +946,8 @@ abstract class BaseGradleIT {
add("-Pkotlin.internal.suppressGradlePluginErrors=PreHMPPFlagsError")
}
add("-Pkonan.data.dir=${options.konanDataDir.absolutePathString()}")
// Workaround: override a console type set in the user machine gradle.properties (since Gradle 4.3):
add("--console=plain")
//The feature of failing the build on deprecation warnings is introduced in gradle 5.6
@@ -359,12 +359,14 @@ open class CommonizerIT : KGPBaseTest() {
nativeProject("commonize-kt-48118-c-interop-in-common-main", gradleVersion) {
reportSourceSetCommonizerDependencies {
val upperMain = getCommonizerDependencies("upperMain")
upperMain.withoutNativeDistributionDependencies().assertDependencyFilesMatches(".*cinterop-dummy")
upperMain.onlyNativeDistributionDependencies().assertNotEmpty()
val konanDataDirProperty = buildOptions.konanDataDir
?: error("konanDataDir must not be null in this test. Please set a custom konanDataDir property.")
upperMain.withoutNativeDistributionDependencies(konanDataDirProperty).assertDependencyFilesMatches(".*cinterop-dummy")
upperMain.onlyNativeDistributionDependencies(konanDataDirProperty).assertNotEmpty()
val commonMain = getCommonizerDependencies("commonMain")
commonMain.withoutNativeDistributionDependencies().assertDependencyFilesMatches(".*cinterop-dummy")
commonMain.onlyNativeDistributionDependencies().assertNotEmpty()
commonMain.withoutNativeDistributionDependencies(konanDataDirProperty).assertDependencyFilesMatches(".*cinterop-dummy")
commonMain.onlyNativeDistributionDependencies(konanDataDirProperty).assertNotEmpty()
}
build(":compileCommonMainKotlinMetadata")
@@ -407,13 +409,15 @@ open class CommonizerIT : KGPBaseTest() {
nativeProject("commonize-kt-48138-nativeMain-nativeTest-different-targets", gradleVersion) {
reportSourceSetCommonizerDependencies {
val nativeMain = getCommonizerDependencies("nativeMain")
nativeMain.withoutNativeDistributionDependencies().assertDependencyFilesMatches(".*cinterop-dummy")
nativeMain.onlyNativeDistributionDependencies().assertNotEmpty()
val konanDataDirProperty = buildOptions.konanDataDir
?: error("konanDataDir must not be null in this test. Please set a custom konanDataDir property.")
nativeMain.withoutNativeDistributionDependencies(konanDataDirProperty).assertDependencyFilesMatches(".*cinterop-dummy")
nativeMain.onlyNativeDistributionDependencies(konanDataDirProperty).assertNotEmpty()
nativeMain.assertTargetOnAllDependencies(CommonizerTarget(LINUX_X64, LINUX_ARM64, MINGW_X64))
val nativeTest = getCommonizerDependencies("nativeTest")
nativeTest.onlyNativeDistributionDependencies().assertNotEmpty()
nativeTest.withoutNativeDistributionDependencies().assertDependencyFilesMatches(".*cinterop-dummy")
nativeTest.onlyNativeDistributionDependencies(konanDataDirProperty).assertNotEmpty()
nativeTest.withoutNativeDistributionDependencies(konanDataDirProperty).assertDependencyFilesMatches(".*cinterop-dummy")
nativeTest.assertTargetOnAllDependencies(CommonizerTarget(LINUX_X64, LINUX_ARM64))
}
}
@@ -523,36 +527,38 @@ open class CommonizerIT : KGPBaseTest() {
getCommonizerDependencies("commonMain").assertEmpty()
getCommonizerDependencies("commonTest").assertEmpty()
getCommonizerDependencies("nativeMain").withoutNativeDistributionDependencies().apply {
val konanDataDirProperty = buildOptions.konanDataDir
?: error("konanDataDir must not be null in this test. Please set a custom konanDataDir property.")
getCommonizerDependencies("nativeMain").withoutNativeDistributionDependencies(konanDataDirProperty).apply {
assertDependencyFilesMatches(".*nativeHelper")
assertTargetOnAllDependencies(
CommonizerTarget(IOS_X64, IOS_ARM64, LINUX_X64, LINUX_ARM64, MACOS_X64, MINGW_X64)
)
}
getCommonizerDependencies("nativeTest").withoutNativeDistributionDependencies().apply {
getCommonizerDependencies("nativeTest").withoutNativeDistributionDependencies(konanDataDirProperty).apply {
assertDependencyFilesMatches(".*nativeHelper", ".*nativeTestHelper")
assertTargetOnAllDependencies(
CommonizerTarget(IOS_X64, IOS_ARM64, LINUX_X64, LINUX_ARM64, MACOS_X64, MINGW_X64)
)
}
getCommonizerDependencies("unixMain").withoutNativeDistributionDependencies().apply {
getCommonizerDependencies("unixMain").withoutNativeDistributionDependencies(konanDataDirProperty).apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper")
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64, LINUX_X64, LINUX_ARM64, MACOS_X64))
}
getCommonizerDependencies("unixTest").withoutNativeDistributionDependencies().apply {
getCommonizerDependencies("unixTest").withoutNativeDistributionDependencies(konanDataDirProperty).apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper", ".*nativeTestHelper")
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64, LINUX_X64, LINUX_ARM64, MACOS_X64))
}
getCommonizerDependencies("linuxMain").withoutNativeDistributionDependencies().apply {
getCommonizerDependencies("linuxMain").withoutNativeDistributionDependencies(konanDataDirProperty).apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper")
assertTargetOnAllDependencies(CommonizerTarget(LINUX_X64, LINUX_ARM64))
}
getCommonizerDependencies("linuxTest").withoutNativeDistributionDependencies().apply {
getCommonizerDependencies("linuxTest").withoutNativeDistributionDependencies(konanDataDirProperty).apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper", ".*nativeTestHelper")
assertTargetOnAllDependencies(CommonizerTarget(LINUX_X64, LINUX_ARM64))
}
@@ -563,22 +569,22 @@ open class CommonizerIT : KGPBaseTest() {
getCommonizerDependencies("linuxArm64Test").assertEmpty()
if (isMac) {
getCommonizerDependencies("appleMain").withoutNativeDistributionDependencies().apply {
getCommonizerDependencies("appleMain").withoutNativeDistributionDependencies(konanDataDirProperty).apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper", ".*appleHelper")
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64, MACOS_X64))
}
getCommonizerDependencies("appleTest").withoutNativeDistributionDependencies().apply {
getCommonizerDependencies("appleTest").withoutNativeDistributionDependencies(konanDataDirProperty).apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper", ".*appleHelper", ".*nativeTestHelper")
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64, MACOS_X64))
}
getCommonizerDependencies("iosMain").withoutNativeDistributionDependencies().apply {
getCommonizerDependencies("iosMain").withoutNativeDistributionDependencies(konanDataDirProperty).apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper", ".*appleHelper")
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64))
}
getCommonizerDependencies("iosTest").withoutNativeDistributionDependencies().apply {
getCommonizerDependencies("iosTest").withoutNativeDistributionDependencies(konanDataDirProperty).apply {
assertDependencyFilesMatches(
".*nativeHelper", ".*unixHelper", ".*appleHelper", ".*nativeTestHelper", ".*iosTestHelper"
)
@@ -531,7 +531,7 @@ class CompilerOptionsProjectIT : KGPBaseTest() {
assertCompilerArguments(":compileCommonMainKotlinMetadata", "-language-version 1.7", "-api-version 1.7")
}
build(":compileKotlinJvm", enableGradleDebug = true) {
build(":compileKotlinJvm") {
assertCompilerArguments(
":compileKotlinJvm",
"-language-version 1.8",
@@ -97,7 +97,8 @@ class ConfigurationCacheIT : AbstractConfigurationCacheIT() {
// Override kotlin native home location to be able to run clean native distribution commonization task
// since by default it is global location on host
val buildOptions = defaultBuildOptions.copy(
freeArgs = listOf("-Porg.jetbrains.kotlin.native.home=$konanHome")
freeArgs = listOf("-Porg.jetbrains.kotlin.native.home=$konanHome"),
konanDataDir = null
)
build(":cleanNativeDistributionCommonization", buildOptions = buildOptions) {
assertOutputContains("0 problems were found storing the configuration cache.")
@@ -136,7 +136,7 @@ abstract class MppCInteropDependencyTransformationIT : BaseGradleIT() {
private fun WithSourceSetCommonizerDependencies.assertP2SourceSetDependencies() {
listOf("nativeMain", "nativeTest").forEach { sourceSetName ->
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies()
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies(defaultBuildOptions().konanDataDir)
.assertDependencyFilesMatches(".*cinterop-simple.*", ".*cinterop-withPosix.*")
.assertTargetOnAllDependencies(
CommonizerTarget(LINUX_ARM64, LINUX_X64, IOS_ARM64, IOS_X64, MACOS_X64, MINGW_X64)
@@ -145,26 +145,26 @@ abstract class MppCInteropDependencyTransformationIT : BaseGradleIT() {
if (HostManager.hostIsMac) {
listOf("appleAndLinuxMain", "appleAndLinuxTest").forEach { sourceSetName ->
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies()
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies(defaultBuildOptions().konanDataDir)
.assertDependencyFilesMatches(".*cinterop-simple.*", ".*cinterop-withPosix.*")
.assertTargetOnAllDependencies(CommonizerTarget(LINUX_ARM64, LINUX_X64, IOS_ARM64, IOS_X64, MACOS_X64))
}
listOf("appleMain", "appleTest").forEach { sourceSetName ->
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies()
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies(defaultBuildOptions().konanDataDir)
.assertDependencyFilesMatches(".*cinterop-simple.*", ".*cinterop-withPosix.*")
.assertTargetOnAllDependencies(CommonizerTarget(IOS_ARM64, IOS_X64, MACOS_X64))
}
listOf("iosMain", "iosTest").forEach { sourceSetName ->
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies()
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies(defaultBuildOptions().konanDataDir)
.assertDependencyFilesMatches(".*cinterop-simple.*", ".*cinterop-withPosix.*")
.assertTargetOnAllDependencies(CommonizerTarget(IOS_ARM64, IOS_X64))
}
}
listOf("linuxMain", "linuxTest").forEach { sourceSetName ->
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies()
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies(defaultBuildOptions().konanDataDir)
.assertDependencyFilesMatches(".*cinterop-simple.*", ".*cinterop-withPosix.*")
.assertTargetOnAllDependencies(CommonizerTarget(LINUX_ARM64, LINUX_X64))
}
@@ -176,7 +176,7 @@ abstract class MppCInteropDependencyTransformationIT : BaseGradleIT() {
They will choose cinterops associated with 'nativeMain'
*/
listOf("nativeMain", "nativeTest", "windowsAndLinuxMain", "windowsAndLinuxTest").forEach { sourceSetName ->
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies()
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies(defaultBuildOptions().konanDataDir)
.assertDependencyFilesMatches(".*cinterop-simple.*", ".*cinterop-withPosix.*")
.assertTargetOnAllDependencies(
CommonizerTarget(LINUX_ARM64, LINUX_X64, IOS_ARM64, IOS_X64, MACOS_X64, MINGW_X64)
@@ -185,13 +185,13 @@ abstract class MppCInteropDependencyTransformationIT : BaseGradleIT() {
if (HostManager.hostIsMac) {
listOf("appleAndLinuxMain", "appleAndLinuxTest").forEach { sourceSetName ->
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies()
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies(defaultBuildOptions().konanDataDir)
.assertDependencyFilesMatches(".*cinterop-simple.*", ".*cinterop-withPosix.*")
.assertTargetOnAllDependencies(CommonizerTarget(LINUX_ARM64, LINUX_X64, IOS_ARM64, IOS_X64, MACOS_X64))
}
listOf("iosMain", "iosTest").forEach { sourceSetName ->
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies()
getCommonizerDependencies(sourceSetName).withoutNativeDistributionDependencies(defaultBuildOptions().konanDataDir)
.assertDependencyFilesMatches(".*cinterop-simple.*", ".*cinterop-withPosix.*")
.assertTargetOnAllDependencies(CommonizerTarget(IOS_ARM64, IOS_X64))
}
@@ -1121,7 +1121,7 @@ class GeneralNativeIT : BaseGradleIT() {
"build",
options = defaultBuildOptions().copy(
forceOutputToStdout = true,
customEnvironmentVariables = mapOf("KONAN_DATA_DIR" to Files.createTempDirectory("konan-data-dir").absolutePathString())
konanDataDir = Files.createTempDirectory("konan-data-dir").toAbsolutePath()
)
) {
assertFailed()
@@ -1,133 +1,176 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.native
import org.jetbrains.kotlin.gradle.*
import org.jetbrains.kotlin.gradle.embedProject
import org.jetbrains.kotlin.gradle.native.GeneralNativeIT.Companion.containsSequentially
import org.jetbrains.kotlin.gradle.native.GeneralNativeIT.Companion.withNativeCommandLineArguments
import org.jetbrains.kotlin.gradle.testbase.NativeToolKind
import org.jetbrains.kotlin.gradle.testbase.extractNativeCompilerCommandLineArguments
import org.jetbrains.kotlin.gradle.transformProjectWithPluginsDsl
import org.jetbrains.kotlin.gradle.util.modify
import org.gradle.api.logging.LogLevel
import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.replaceFirst
import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.Xcode
import org.jetbrains.kotlin.konan.target.XcodeVersion
import org.jetbrains.kotlin.konan.target.presetName
import org.jetbrains.kotlin.konan.util.DependencyDirectories
import org.junit.Assume
import org.junit.BeforeClass
import org.junit.Test
import kotlin.test.assertTrue
import kotlin.test.BeforeTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.OS
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.absolutePathString
import kotlin.io.path.appendText
import kotlin.io.path.deleteRecursively
class NativeDownloadAndPlatformLibsIT : BaseGradleIT() {
// We temporarily disable it for windows until a proper fix is found for this issue:
// https://youtrack.jetbrains.com/issue/KT-60138/NativeDownloadAndPlatformLibsIT-fails-on-Windows-OS
@OsCondition(
supportedOn = [OS.MAC, OS.LINUX], enabledOnCI = [OS.MAC, OS.LINUX]
)
@DisplayName("Tests for K/N builds with native downloading and platform libs")
@NativeGradlePluginTests
class NativeDownloadAndPlatformLibsIT : KGPBaseTest() {
companion object {
@BeforeClass
@JvmStatic
fun skipOnWindows() {
// This test class causes build timeouts on Windows CI machines.
// We temporarily disable it for windows until a proper fix is found.
Assume.assumeFalse(HostManager.hostIsMingw)
}
private const val KOTLIN_SPACE_DEV = "https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev"
private const val MAVEN_CENTRAL = "https://cache-redirector.jetbrains.com/maven-central"
}
override val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.FOR_MPP_SUPPORT
private val platformName: String = HostManager.platformName()
private val currentCompilerVersion = NativeCompilerDownloader.DEFAULT_KONAN_VERSION
private fun platformLibrariesProject(vararg targets: String): Project =
transformProjectWithPluginsDsl("native-platform-libraries").apply {
setupWorkingDir()
gradleBuildScript().appendText(
targets.joinToString(prefix = "\n", separator = "\n") {
"kotlin.$it()"
}
)
gradleProperties().apply {
configureJvmMemory()
override val defaultBuildOptions: BuildOptions
get() = super.defaultBuildOptions.copy(
// For each test in this class, we need to provide an isolated .konan directory,
// so we create it within each test project folder
konanDataDir = workingDir.resolve(".konan")
.toFile()
.apply { mkdirs() }.toPath()
)
@AfterEach
fun checkThatUserKonanDirIsEmptyAfterTest() {
val userHomeDir = System.getProperty("user.home")
assertFileNotExists(Paths.get("$userHomeDir/.konan/dependencies"))
assertFileNotExists(Paths.get("$userHomeDir/.konan/kotlin-native-prebuilt-$platformName-$currentCompilerVersion"))
}
@DisplayName("Downloading K/N distribution in default .konan dir")
@GradleTest
fun testLibrariesGenerationInDefaultKonanDir(gradleVersion: GradleVersion) {
checkThatUserKonanDirIsEmptyAfterTest()
val userHomeDir = System.getProperty("user.home")
platformLibrariesProject("linuxX64", gradleVersion = gradleVersion) {
build("assemble", buildOptions = defaultBuildOptions.copy(konanDataDir = null)) {
assertOutputContains("Kotlin/Native distribution: .*kotlin-native-prebuilt-$platformName".toRegex())
assertOutputDoesNotContain("Generate platform libraries for ")
// checking that konan was downloaded and native dependencies were not downloaded into ~/.konan dir
assertDirectoryExists(Paths.get("$userHomeDir/.konan/dependencies"))
assertDirectoryExists(Paths.get("$userHomeDir/.konan/kotlin-native-prebuilt-$platformName-$currentCompilerVersion"))
}
}
private val platformName: String = HostManager.platformName()
// clean ~/.konan after test it should not be with all inheritors of KGPBaseTest
Paths.get("$userHomeDir/.konan/dependencies").deleteRecursively()
Paths.get("$userHomeDir/.konan/kotlin-native-prebuilt-$platformName-$currentCompilerVersion").deleteRecursively()
@BeforeTest
fun deleteInstalledCompilers() {
val currentCompilerDir = DependencyDirectories.localKonanDir
.resolve("kotlin-native-$platformName-$currentCompilerVersion")
val prebuiltDistDir = DependencyDirectories.localKonanDir
.resolve("kotlin-native-prebuilt-$platformName-$currentCompilerVersion")
for (compilerDirectory in listOf(currentCompilerDir, prebuiltDistDir)) {
compilerDirectory.deleteRecursively()
}
}
private fun Project.buildWithLightDist(vararg tasks: String, check: CompiledProject.() -> Unit) =
build(*tasks, "-Pkotlin.native.distribution.type=light", check = check)
@Test
fun testNoGenerationByDefault() = with(platformLibrariesProject("linuxX64")) {
// Check that a prebuilt distribution is used by default.
build("assemble") {
assertSuccessful()
assertContainsRegex("Kotlin/Native distribution: .*kotlin-native-prebuilt-$platformName".toRegex())
assertNotContains("Generate platform libraries for ")
}
}
@Test
fun testLibrariesGeneration() {
val rootProject = Project("native-platform-libraries").apply {
embedProject(Project("native-platform-libraries"), renameTo = "subproject")
gradleProperties().apply {
configureJvmMemory()
@OptIn(EnvironmentalVariablesOverride::class)
@DisplayName("K/N Gradle project build (on Linux or Mac) with a dependency from a Maven")
@GradleTest
fun testSetupCommonOptionsForCaches(gradleVersion: GradleVersion, @TempDir tempDir: Path) {
val anotherKonanDataDir = tempDir.resolve(".konan2")
nativeProject(
"native-with-maven-dependencies",
gradleVersion = gradleVersion,
environmentVariables = EnvironmentalVariables(Pair("KONAN_DATA_DIR", anotherKonanDataDir.absolutePathString()))
) {
build(
"linkDebugExecutableNative",
buildOptions = defaultBuildOptions.copy(
nativeOptions = defaultBuildOptions.nativeOptions.copy(
cacheKind = null
)
)
) {
assertOutputDoesNotContain("w: Failed to build cache")
assertTasksExecuted(":linkDebugExecutableNative")
assertDirectoryDoesNotExist(anotherKonanDataDir)
}
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
gradleBuildScript().appendText("\nkotlin.linuxX64()\n")
gradleBuildScript("subproject").appendText("\nkotlin.linuxArm64()\n")
}
}
@DisplayName("Downloading K/N with custom konanDataDir property")
@GradleTest
fun testLibrariesGenerationInCustomKonanDir(gradleVersion: GradleVersion) {
platformLibrariesProject("linuxX64", gradleVersion = gradleVersion) {
build("assemble", buildOptions = defaultBuildOptions.copy(konanDataDir = workingDir.resolve(".konan"))) {
assertOutputContains("Kotlin/Native distribution: .*kotlin-native-prebuilt-$platformName".toRegex())
assertOutputDoesNotContain("Generate platform libraries for ")
// checking that konan was downloaded and native dependencies were not downloaded into ~/.konan dir
assertDirectoryExists(workingDir.resolve(".konan/dependencies"))
assertDirectoryExists(workingDir.resolve(".konan/kotlin-native-prebuilt-$platformName-$currentCompilerVersion"))
}
}
}
@DisplayName("K/N distribution without platform libraries generation")
@GradleTest
fun testNoGenerationByDefault(gradleVersion: GradleVersion) {
platformLibrariesProject("linuxX64", gradleVersion = gradleVersion) {
build("assemble") {
assertOutputContains("Kotlin/Native distribution: .*kotlin-native-prebuilt-$platformName".toRegex())
assertOutputDoesNotContain("Generate platform libraries for ")
}
}
}
@DisplayName("K/N distribution with platform libraries generation")
@GradleTest
fun testLibrariesGeneration(gradleVersion: GradleVersion) {
nativeProject("native-platform-libraries", gradleVersion = gradleVersion) {
includeOtherProjectAsSubmodule("native-platform-libraries", "", "subproject", true)
buildGradleKts.appendText("\nkotlin.linuxX64()\n")
subProject("subproject").buildGradleKts.appendText("\nkotlin.linuxArm64()\n")
with(rootProject) {
// Check that platform libraries are correctly generated for both root project and a subproject.
buildWithLightDist("assemble") {
assertSuccessful()
assertContainsRegex("Kotlin/Native distribution: .*kotlin-native-$platformName".toRegex())
assertContains("Generate platform libraries for linux_x64")
assertContains("Generate platform libraries for linux_arm64")
assertOutputContains("Kotlin/Native distribution: .*kotlin-native-$platformName".toRegex())
assertOutputContains("Generate platform libraries for linux_x64")
assertOutputContains("Generate platform libraries for linux_arm64")
}
// Check that we don't generate libraries during a second run. Don't clean to reduce execution time.
buildWithLightDist("assemble") {
assertSuccessful()
assertNotContains("Generate platform libraries for ")
assertOutputDoesNotContain("Generate platform libraries for ")
}
}
}
@Test
fun testLinkerArgsViaGradleProperties() {
with(Project("native-platform-libraries")) {
setupWorkingDir()
gradleProperties().apply {
configureJvmMemory()
appendText("\nkotlin.native.linkArgs=-Xfoo=bar -Xbaz=qux")
}
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
gradleBuildScript().appendText(
@DisplayName("Link with args via gradle properties")
@GradleTest
fun testLinkerArgsViaGradleProperties(gradleVersion: GradleVersion) {
nativeProject("native-platform-libraries", gradleVersion = gradleVersion) {
addPropertyToGradleProperties(
"kotlin.native.linkArgs",
mapOf(
"-Xfoo" to "-Xfoo=bar",
"-Xbaz" to "-Xbaz=qux"
)
)
buildGradleKts.appendText(
"""
|
|kotlin.linuxX64() {
@@ -137,58 +180,165 @@ class NativeDownloadAndPlatformLibsIT : BaseGradleIT() {
|}
""".trimMargin()
)
build("linkDebugSharedLinuxX64") {
assertSuccessful()
build("linkDebugSharedLinuxX64", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertTasksExecuted(
":compileKotlinLinuxX64",
":linkDebugSharedLinuxX64"
)
withNativeCommandLineArguments(":linkDebugSharedLinuxX64") {
assertTrue(it.contains("-Xfoo=bar"))
assertTrue(it.contains("-Xbaz=qux"))
assertTrue(it.contains("-Xmen=pool"))
extractNativeTasksCommandLineArgumentsFromOutput(":linkDebugSharedLinuxX64") {
assertCommandLineArgumentsContain("-Xfoo=bar", "-Xbaz=qux", "-Xmen=pool")
}
assertFileExists("/build/bin/linuxX64/debugShared/libnative_platform_libraries.so")
assertFileExists("/build/bin/linuxX64/debugShared/libnative_platform_libraries_api.h")
assertFileInProjectExists("build/bin/linuxX64/debugShared/libnative_platform_libraries.so")
assertFileInProjectExists("build/bin/linuxX64/debugShared/libnative_platform_libraries_api.h")
}
}
}
@Test
fun testNoGenerationForUnsupportedHost() {
hostHaveUnsupportedTarget()
platformLibrariesProject(KonanTarget.IOS_X64.presetName).buildWithLightDist("assemble") {
assertSuccessful()
assertNotContains("Generate platform libraries for ")
@OsCondition(supportedOn = [OS.LINUX], enabledOnCI = [OS.LINUX])
@DisplayName("Assembling project generates no platform libraries for unsupported host")
@GradleTest
fun testNoGenerationForUnsupportedHost(gradleVersion: GradleVersion) {
platformLibrariesProject(KonanTarget.IOS_X64.presetName, gradleVersion = gradleVersion) {
buildWithLightDist("assemble") {
assertOutputDoesNotContain("Generate platform libraries for ")
}
}
}
@Test
fun testCanUsePrebuiltDistribution() = with(platformLibrariesProject("linuxX64")) {
build("assemble", "-Pkotlin.native.distribution.type=prebuilt") {
assertSuccessful()
assertContainsRegex("Kotlin/Native distribution: .*kotlin-native-prebuilt-$platformName".toRegex())
assertNotContains("Generate platform libraries for ")
@DisplayName("Build K/N project with prebuild type")
@GradleTest
fun testCanUsePrebuiltDistribution(gradleVersion: GradleVersion) {
platformLibrariesProject("linuxX64", gradleVersion = gradleVersion) {
build(
"assemble", buildOptions = defaultBuildOptions.copy(
nativeOptions = defaultBuildOptions.nativeOptions.copy(
distributionType = "prebuilt"
)
)
) {
assertOutputContains("Kotlin/Native distribution: .*kotlin-native-prebuilt-$platformName".toRegex())
assertOutputDoesNotContain("Generate platform libraries for ")
}
}
}
@Test
fun testCompilerReinstallation() = with(platformLibrariesProject("linuxX64")) {
// Install the compiler at the first time. Don't build to reduce execution time.
buildWithLightDist("tasks") {
assertSuccessful()
assertContains("Generate platform libraries for linux_x64")
}
@DisplayName("Build K/N project with compiler reinstallation")
@GradleTest
fun testCompilerReinstallation(gradleVersion: GradleVersion) {
platformLibrariesProject("linuxX64", gradleVersion = gradleVersion) {
// Install the compiler at the first time. Don't build to reduce execution time.
buildWithLightDist("tasks") {
assertOutputContains("Generate platform libraries for linux_x64")
}
// Reinstall the compiler.
buildWithLightDist("tasks", "-Pkotlin.native.reinstall=true") {
assertSuccessful()
assertContains("Unpack Kotlin/Native compiler to ")
assertContains("Generate platform libraries for linux_x64")
// Reinstall the compiler.
buildWithLightDist(
"tasks",
buildOptions = defaultBuildOptions.copy(nativeOptions = defaultBuildOptions.nativeOptions.copy(reinstall = true))
) {
assertOutputContains("Unpack Kotlin/Native compiler to ")
assertOutputContains("Generate platform libraries for linux_x64")
}
}
}
@DisplayName("Download prebuilt Native bundle with maven")
@GradleTest
fun shouldDownloadPrebuiltNativeBundleWithMaven(gradleVersion: GradleVersion) {
val maven = mavenUrl()
Assumptions.assumeTrue(
maven != MAVEN_CENTRAL,
"Don't run this test for build that are not yet published to central.\n" +
" We won't public K/N into Maven central until this task is completed: KTI-1067"
)
nativeProject("native-download-maven", gradleVersion = gradleVersion) {
buildGradleKts.replaceFirst("// <MavenPlaceholder>", "maven(\"${maven}\")")
build(
"assemble",
buildOptions = defaultBuildOptions.copy(nativeOptions = defaultBuildOptions.nativeOptions.copy(distributionDownloadFromMaven = true))
) {
assertOutputContains("Unpack Kotlin/Native compiler to ")
assertOutputDoesNotContain("Generate platform libraries for ")
}
}
}
@DisplayName("Download light Native bundle with maven")
@RequiredXCodeVersion(minSupportedMajor = 14, minSupportedMinor = 1)
@GradleTest
fun shouldDownloadLightNativeBundleWithMaven(gradleVersion: GradleVersion) {
val maven = mavenUrl()
Assumptions.assumeTrue(
maven != MAVEN_CENTRAL,
"Don't run this test for build that are not yet published to central.\n" +
" We won't public K/N into Maven central until this task is completed: KTI-1067"
)
nativeProject("native-download-maven", gradleVersion = gradleVersion) {
buildGradleKts.replaceFirst("// <MavenPlaceholder>", "maven(\"${maven}\")")
val nativeOptions = defaultBuildOptions.nativeOptions.copy(
distributionType = "light",
distributionDownloadFromMaven = true
)
build(
"assemble",
buildOptions = defaultBuildOptions.copy(nativeOptions = nativeOptions)
) {
assertOutputContains("Unpack Kotlin/Native compiler to ")
assertOutputContains("Generate platform libraries for ")
}
}
}
@DisplayName("Download from maven should fail if there is no such build in the default repos")
@GradleTest
fun shouldFailDownloadWithNoBuildInDefaultRepos(gradleVersion: GradleVersion) {
nativeProject("native-download-maven", gradleVersion = gradleVersion) {
val nativeOptions = BuildOptions.NativeOptions(
version = "1.8.0-dev-1234",
distributionDownloadFromMaven = true
)
buildAndFail(
"assemble",
buildOptions = defaultBuildOptions.copy(nativeOptions = nativeOptions)
) {
assertOutputContains("Could not find org.jetbrains.kotlin:kotlin-native")
}
}
}
private fun platformLibrariesProject(
vararg targets: String,
gradleVersion: GradleVersion,
test: TestProject.() -> Unit = {},
) {
nativeProject("native-platform-libraries", gradleVersion) {
buildGradleKts.appendText(
targets.joinToString(prefix = "\n", separator = "\n") {
"kotlin.$it()"
}
)
test()
}
}
private fun TestProject.buildWithLightDist(
vararg tasks: String,
buildOptions: BuildOptions = defaultBuildOptions.copy(),
assertions: BuildResult.() -> Unit,
) =
build(
*tasks,
buildOptions = buildOptions.copy(
nativeOptions = buildOptions.nativeOptions.copy(distributionType = "light")
),
assertions = assertions
)
private fun mavenUrl(): String {
val versionPattern = "(\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:-(\\p{Alpha}*\\p{Alnum}|[\\p{Alpha}-]*))?(?:-(\\d+))?".toRegex()
val (_, _, _, metaString, build) = versionPattern.matchEntire(currentCompilerVersion)?.destructured
@@ -200,68 +350,4 @@ class NativeDownloadAndPlatformLibsIT : BaseGradleIT() {
}
}
@Test
fun `download prebuilt Native bundle with maven`() {
val maven = mavenUrl()
// Don't run this test for build that are not yet published to central
Assume.assumeTrue(maven != MAVEN_CENTRAL)
with(transformNativeTestProjectWithPluginDsl("native-download-maven")) {
gradleProperties().appendText(
"kotlin.native.distribution.downloadFromMaven=true"
)
gradleBuildScript().let {
val text = it.readText().replaceFirst("// <MavenPlaceholder>", "maven(\"${maven}\")")
it.writeText(text)
}
build("assemble") {
assertSuccessful()
assertContains("Unpack Kotlin/Native compiler to ")
assertNotContains("Generate platform libraries for ")
}
}
}
@Test
fun `download light Native bundle with maven`() {
val maven = mavenUrl()
// Don't run this test for build that are not yet published to central
Assume.assumeTrue(maven != MAVEN_CENTRAL)
if (HostManager.hostIsMac) {
// Building platform libs require Xcode 14.1
Assume.assumeTrue(Xcode.findCurrent().version >= XcodeVersion(14, 1))
}
with(transformNativeTestProjectWithPluginDsl("native-download-maven")) {
gradleProperties().appendText(
"kotlin.native.distribution.downloadFromMaven=true"
)
gradleBuildScript().let {
val text = it.readText().replaceFirst("// <MavenPlaceholder>", "maven(\"${maven}\")")
it.writeText(text)
}
build("assemble", "-Pkotlin.native.distribution.type=light") {
assertSuccessful()
assertContains("Unpack Kotlin/Native compiler to ")
assertContains("Generate platform libraries for ")
}
}
}
@Test
fun `download from maven should fail if there is no such build in the default repos`() {
with(transformNativeTestProjectWithPluginDsl("native-download-maven")) {
gradleProperties().appendText(
"""
kotlin.native.version=1.8.0-dev-1234
kotlin.native.distribution.downloadFromMaven=true
""".trimIndent()
)
build("assemble") {
assertContains("Could not find org.jetbrains.kotlin:kotlin-native")
assertFailed()
}
}
}
}
@@ -22,10 +22,14 @@ class NativeWithConfigurationCacheIT : KGPBaseTest() {
@GradleTestVersions(minVersion = TestVersions.Gradle.G_8_1) // Since 8.1 Gradle on configuration cache it detects when the build logic accesses the "outside world" more strict https://docs.gradle.org/8.1.1/release-notes.html#configuration-inputs-detection-improvements
@GradleTest
fun testConfigurationCacheReusedSecondTime(gradleVersion: GradleVersion) {
nativeProject("native-with-configuration-cache", gradleVersion, enableGradleDebug = true) {
nativeProject("native-with-configuration-cache", gradleVersion) {
// we need to download compiler on the first build, that is why we are setting custom konan home dir without any compiler inside
val localKonan = workingDir.resolve(".konan")
build("help", "-Pkonan.data.dir=$localKonan") {
build(
"help", buildOptions = defaultBuildOptions.copy(
konanDataDir = localKonan
)
) {
assertOutputContains("Configure project")
assertOutputContains("Unpack Kotlin/Native compiler to")
}
@@ -16,7 +16,10 @@ import org.jetbrains.kotlin.gradle.dsl.NativeCacheKind
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy
import org.jetbrains.kotlin.gradle.report.BuildReportType
import org.junit.jupiter.api.condition.OS
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import kotlin.io.path.absolutePathString
data class BuildOptions(
val logLevel: LogLevel = LogLevel.INFO,
@@ -51,6 +54,7 @@ data class BuildOptions(
val nativeOptions: NativeOptions = NativeOptions(),
val compilerExecutionStrategy: KotlinCompilerExecutionStrategy? = null,
val runViaBuildToolsApi: Boolean? = null,
val konanDataDir: Path? = konanDir,
) {
val isK2ByDefault
get() = KotlinVersion.DEFAULT >= KotlinVersion.KOTLIN_2_0
@@ -68,7 +72,7 @@ data class BuildOptions(
val verbose: Boolean = false,
val incrementalKapt: Boolean = false,
val includeCompileClasspath: Boolean = false,
val classLoadersCacheSize: Int? = null
val classLoadersCacheSize: Int? = null,
)
data class JsOptions(
@@ -78,7 +82,7 @@ data class BuildOptions(
)
data class NativeOptions(
val cacheKind: NativeCacheKind = NativeCacheKind.NONE,
val cacheKind: NativeCacheKind? = NativeCacheKind.NONE,
val cocoapodsGenerateWrapper: Boolean? = null,
val cocoapodsPlatform: String? = null,
val cocoapodsConfiguration: String? = null,
@@ -93,7 +97,7 @@ data class BuildOptions(
)
fun toArguments(
gradleVersion: GradleVersion
gradleVersion: GradleVersion,
): List<String> {
val arguments = mutableListOf<String>()
when (logLevel) {
@@ -209,6 +213,10 @@ data class BuildOptions(
arguments.add("--$stacktraceMode")
}
konanDataDir?.let {
arguments.add("-Pkonan.data.dir=${konanDataDir.absolutePathString()}")
}
arguments.addAll(freeArgs)
return arguments.toList()
@@ -218,7 +226,9 @@ data class BuildOptions(
arguments: MutableList<String>,
) {
arguments.add("-Pkotlin.native.cacheKind=${nativeOptions.cacheKind.name.lowercase()}")
nativeOptions.cacheKind?.let {
arguments.add("-Pkotlin.native.cacheKind=${nativeOptions.cacheKind.name.lowercase()}")
}
nativeOptions.cocoapodsGenerateWrapper?.let {
arguments.add("-Pkotlin.native.cocoapods.generate.wrapper=${it}")
@@ -260,7 +270,7 @@ data class BuildOptions(
fun BuildOptions.suppressDeprecationWarningsOn(
@Suppress("UNUSED_PARAMETER") reason: String, // just to require specifying a reason for suppressing
predicate: (BuildOptions) -> Boolean
predicate: (BuildOptions) -> Boolean,
) = if (predicate(this)) {
copy(warningMode = WarningMode.Summary)
} else {
@@ -270,7 +280,7 @@ fun BuildOptions.suppressDeprecationWarningsOn(
fun BuildOptions.suppressDeprecationWarningsSinceGradleVersion(
gradleVersion: String,
currentGradleVersion: GradleVersion,
reason: String
reason: String,
) = suppressDeprecationWarningsOn(reason) {
currentGradleVersion >= GradleVersion.version(gradleVersion)
}
}
@@ -12,7 +12,6 @@ import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.platform.commons.logging.LoggerFactory
import java.lang.reflect.AnnotatedElement
import java.util.*
/**
* An annotation that enables tests to be executed on a specific operating system within a specific environment.
@@ -45,9 +44,9 @@ internal class ExecutionOnOsCondition : ExecutionCondition {
private val isUnderTeamcity = System.getenv("TEAMCITY_VERSION") != null
private val enabledOnCurrentOs = "Enabled on operating system: " + System.getProperty("os.name")
private val notSupportedOnCurrentOs = "Test is not supported on operating system: " + System.getProperty("os.name")
private val disabledForCI = "Disabled for operating system: " + System.getProperty("os.name") + " on CI"
private val enabledOnCurrentOs = "Enabled on operating system: ${System.getProperty("os.name")}"
private val notSupportedOnCurrentOs = "Test is not supported on operating system: ${System.getProperty("os.name")}"
private val disabledForCI = "Disabled for operating system: ${System.getProperty("os.name")} on CI"
override fun evaluateExecutionCondition(context: ExtensionContext): ConditionEvaluationResult {
val annotation = findAnnotation<OsCondition>(context)
@@ -445,6 +445,8 @@ private fun TestProject.withBuildSummary(
}
}
val konanDir get() = Paths.get(".").resolve("build").resolve(".konan")
/**
* On changing test kit dir location update related location in 'cleanTestKitCache' task.
*/
@@ -0,0 +1,67 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.testbase
import org.jetbrains.kotlin.konan.target.Xcode
import org.jetbrains.kotlin.konan.target.XcodeVersion
import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.jupiter.api.extension.ConditionEvaluationResult
import org.junit.jupiter.api.extension.ExecutionCondition
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.platform.commons.logging.LoggerFactory
/**
* Represents a custom annotation that specifies the minimum required XCode version for executing annotated code.
* NOTE: If the target platform is not Mac OS this annotation will be ignored.
*
* This annotation can be applied to functions, annotation classes, and classes.
*
* @param minSupportedMajor min supported XCode version's major part. XCode versions pattern: <majorVersion>.<minorVersion>
* @param minSupportedMinor min supported XCode version's major part. XCode versions pattern: <majorVersion>.<minorVersion>
*/
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(RequiredXCodeExecutionCondition::class)
annotation class RequiredXCodeVersion(
val minSupportedMajor: Int,
val minSupportedMinor: Int,
)
/**
* An implementation of the ExecutionCondition interface that checks if the test should be executed
* based on the current XCode version.
*/
internal class RequiredXCodeExecutionCondition : ExecutionCondition {
private val logger = LoggerFactory.getLogger(ExecutionOnOsCondition::class.java)
override fun evaluateExecutionCondition(context: ExtensionContext): ConditionEvaluationResult {
val annotation = findAnnotation<RequiredXCodeVersion>(context)
return if (HostManager.hostIsMac) {
val minSupportedVersion = XcodeVersion(annotation.minSupportedMajor, annotation.minSupportedMinor)
val currentVersion = Xcode.findCurrent().version
if (currentVersion < minSupportedVersion) {
logger.info { generateUnsupportedVersionMessage(minSupportedVersion, currentVersion) }
ConditionEvaluationResult.disabled(
generateUnsupportedVersionMessage(minSupportedVersion, currentVersion)
)
} else {
ConditionEvaluationResult.enabled(enabledOnCurrentVersionMessage(currentVersion))
}
} else {
ConditionEvaluationResult.enabled("Test enabled because target OS is non MAC: ${System.getProperty("os.name")}")
}
}
private fun enabledOnCurrentVersionMessage(currentVersion: Any?) =
"Enabled on XCode version: $currentVersion"
private fun generateUnsupportedVersionMessage(minVersion: Any?, currentVersion: Any?) =
"Test is not supported with XCode older than $minVersion, but current is $currentVersion"
}
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.library.commonizerTarget
import org.jetbrains.kotlin.library.resolveSingleFileKlib
import org.jetbrains.kotlin.tooling.core.linearClosure
import java.io.File
import java.nio.file.Path
import javax.annotation.RegEx
import kotlin.io.path.appendText
import kotlin.test.fail
@@ -35,22 +36,22 @@ data class SourceSetCommonizerDependencies(
val dependencies: Set<SourceSetCommonizerDependency>,
) {
fun withoutNativeDistributionDependencies(): SourceSetCommonizerDependencies {
fun withoutNativeDistributionDependencies(konanDataDirProperty: Path): SourceSetCommonizerDependencies {
return SourceSetCommonizerDependencies(
sourceSetName,
dependencies.filter { dependency -> !dependency.isFromNativeDistribution() }.toSet()
dependencies.filter { dependency -> !dependency.isFromNativeDistribution(konanDataDirProperty) }.toSet()
)
}
fun onlyNativeDistributionDependencies(): SourceSetCommonizerDependencies {
fun onlyNativeDistributionDependencies(konanDataDirProperty: Path): SourceSetCommonizerDependencies {
return SourceSetCommonizerDependencies(
sourceSetName,
dependencies.filter { dependency -> dependency.isFromNativeDistribution() }.toSet()
dependencies.filter { dependency -> dependency.isFromNativeDistribution(konanDataDirProperty) }.toSet()
)
}
private fun SourceSetCommonizerDependency.isFromNativeDistribution(): Boolean {
val konanDataDir = System.getenv("KONAN_DATA_DIR")?.let(::File)
private fun SourceSetCommonizerDependency.isFromNativeDistribution(konanDataDirProperty: Path?): Boolean {
val konanDataDir = konanDataDirProperty?.toRealPath()?.toFile() ?: System.getenv("KONAN_DATA_DIR")?.let(::File)
if (konanDataDir != null) {
return file.startsWith(konanDataDir)
}
@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>")
kotlin("multiplatform")
}
repositories {
@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>")
kotlin("multiplatform")
}
repositories {
@@ -0,0 +1,20 @@
plugins {
kotlin("multiplatform")
}
repositories {
mavenLocal()
mavenCentral()
}
kotlin {
<SingleNativeTarget>("native") {
binaries.executable()
}
sourceSets.commonMain {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2")
}
}
}