diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeWithСonfigurationCacheIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeWithСonfigurationCacheIT.kt new file mode 100644 index 00000000000..a53d1709ab0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeWithСonfigurationCacheIT.kt @@ -0,0 +1,38 @@ +/* + * 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.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.testbase.* +import org.junit.jupiter.api.DisplayName + +@DisplayName("Tests for K/N with enabled configuration cache") +@NativeGradlePluginTests +class NativeWithConfigurationCacheIT : KGPBaseTest() { + + override val defaultBuildOptions: BuildOptions = super.defaultBuildOptions.copy(configurationCache = true) + + @DisplayName( + "Configuration phase should be reused from configuration cache for the second build " + + "(downloading konan does not affect cache inputs)" + ) + @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) { + // 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") { + assertOutputContains("Configure project") + assertOutputContains("Unpack Kotlin/Native compiler to") + } + + build("help", "-Pkonan.data.dir=$localKonan") { + assertOutputContains("Reusing configuration cache.") + } + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-with-configuration-cache/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-with-configuration-cache/build.gradle.kts new file mode 100644 index 00000000000..a9df8ce407c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-with-configuration-cache/build.gradle.kts @@ -0,0 +1,22 @@ +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget + +plugins { + kotlin("multiplatform") +} + +repositories { + mavenLocal() + mavenCentral() +} + +kotlin { + val nativeMain by sourceSets.creating { + dependsOn(sourceSets["commonMain"]) + } + + targets.withType(KotlinNativeTarget::class.java).all { + compilations["main"].defaultSourceSet.dependsOn(nativeMain) + } + + () +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-with-configuration-cache/src/nativeMain/kotlin/main.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-with-configuration-cache/src/nativeMain/kotlin/main.kt new file mode 100644 index 00000000000..043d37542ad --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-with-configuration-cache/src/nativeMain/kotlin/main.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2020 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. + */ + +import platform.posix.* + +fun main() { + printf("Hello world %d", 42) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeCompilerDownloader.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeCompilerDownloader.kt index cfd40e77c8e..b78191bfe7c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeCompilerDownloader.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeCompilerDownloader.kt @@ -8,14 +8,19 @@ package org.jetbrains.kotlin.gradle.utils import org.gradle.api.Project import org.gradle.api.artifacts.repositories.ArtifactRepository +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileTree import org.gradle.api.logging.Logger +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters import org.jetbrains.kotlin.compilerRunner.KotlinNativeToolRunner import org.jetbrains.kotlin.compilerRunner.konanHome import org.jetbrains.kotlin.compilerRunner.konanVersion import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider +import org.jetbrains.kotlin.gradle.plugin.internal.configurationTimePropertiesAccessor +import org.jetbrains.kotlin.gradle.plugin.internal.usedAtConfigurationTime import org.jetbrains.kotlin.gradle.targets.native.internal.NativeDistributionType import org.jetbrains.kotlin.gradle.targets.native.internal.NativeDistributionTypeProvider import org.jetbrains.kotlin.gradle.targets.native.internal.PlatformLibrariesGenerator @@ -27,7 +32,7 @@ import java.nio.file.Files class NativeCompilerDownloader( val project: Project, - private val compilerVersion: String = project.konanVersion + private val compilerVersion: String = project.konanVersion, ) { companion object { @@ -35,6 +40,8 @@ class NativeCompilerDownloader( loadPropertyFromResources("project.properties", "kotlin.native.version") } + internal var NEED_TO_DOWNLOAD_FLAG: Boolean = true + internal const val BASE_DOWNLOAD_URL = "https://download.jetbrains.com/kotlin/native/builds" internal const val KOTLIN_GROUP_ID = "org.jetbrains.kotlin" } @@ -185,12 +192,29 @@ class NativeCompilerDownloader( } fun downloadIfNeeded() { - - val classpath = KotlinNativeToolRunner.Settings.fromProject(project).classpath - if (classpath.isEmpty() || classpath.any { !it.exists() }) { + checkClassPath() // This is workaround to avoid double execution configuration phase. See KT-61154 for more details + if (NEED_TO_DOWNLOAD_FLAG) { downloadAndExtract() } } + + private fun checkClassPath() { + project.providers.of(NativeCompilerDownloaderClassPathChecker::class.java) { + it.parameters.classPath.setFrom(KotlinNativeToolRunner.Settings.fromProject(project).classpath) + }.usedAtConfigurationTime(project.configurationTimePropertiesAccessor).get() + } + + internal abstract class NativeCompilerDownloaderClassPathChecker : ValueSource { + + interface Params : ValueSourceParameters { + val classPath: ConfigurableFileCollection + } + + override fun obtain(): Boolean { + NEED_TO_DOWNLOAD_FLAG = parameters.classPath.files.none { it.exists() } + return true + } + } } internal fun Project.setupNativeCompiler(konanTarget: KonanTarget) { @@ -204,6 +228,7 @@ internal fun Project.setupNativeCompiler(konanTarget: KonanTarget) { } downloader.downloadIfNeeded() + logger.info("Kotlin/Native distribution: $konanHome") } else { logger.info("User-provided Kotlin/Native distribution: $konanHome") @@ -214,3 +239,4 @@ internal fun Project.setupNativeCompiler(konanTarget: KonanTarget) { PlatformLibrariesGenerator(project, konanTarget).generatePlatformLibsIfNeeded() } } +