[IC] Baseline fix for common sources getting access to platform declarations
K2-only issue. In an incremental build, sourceSet boundary isn't preserved in certain conditions: SourceSet A depends on SourceSet B A and B overload a function, for example: A -> fun foo(p: Parent) // (1) B -> fun foo(c: Child) // (2) If some source file in A is calling foo(..), only (1) is supposed to be visible for that call site. However, if a) it is an incremental build, b) the declaration of (1) is not a part of the compilation set, and c) call to foo(c: Child) is applicable, then (2) would be called from the generated code. So, the build result is not consistent between the full build and an incremental build. As a workaround, we fallback to a non-incremental build, if any source from A needs to be compiled. To enable "risky" incremental builds, use Gradle property kotlin.internal.incremental.enableUnsafeOptimizationsForMultiplatform=true ^KT-62686 ^KT-63837 Fixed Merge-request: KT-MR-13695 Merged-by: Evgenii Mazhukin <evgenii.mazhukin@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
ad6513e247
commit
de4953adf6
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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.incremental
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
||||||
|
import org.jetbrains.kotlin.config.LanguageVersion
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds current mapping from source files to fragments, in the K2 KMP context
|
||||||
|
*
|
||||||
|
* Note: for KT-62686, we don't really care about removed files: if a source isn't recompiled,
|
||||||
|
* it can't get illegal lookups.
|
||||||
|
*
|
||||||
|
* So there's no need to store previous source-to-fragment mapping
|
||||||
|
*/
|
||||||
|
class FragmentContext(
|
||||||
|
/**
|
||||||
|
* Map from path to source to this source's fragment
|
||||||
|
*/
|
||||||
|
private val fileToFragment: Map<String, String>,
|
||||||
|
/**
|
||||||
|
* If a fragment isn't refined by any other fragments, it's allowed to have incremental compilation.
|
||||||
|
* Otherwise, issues from KT-62686 are applicable.
|
||||||
|
*/
|
||||||
|
private val leafFragments: Set<String>
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Returns true, if any file from dirtySet is a part of refined fragment (for example: common, apple, linux).
|
||||||
|
* It is relevant to KT-62686, because in k2 "refined" fragments aren't supposed to see symbols from "refining" fragments,
|
||||||
|
* but they do.
|
||||||
|
*
|
||||||
|
* Use of `absolutePath` is coordinated with K2MultiplatformStructure.fragmentSourcesCompilerArgs
|
||||||
|
*/
|
||||||
|
fun dirtySetTouchesNonLeafFragments(dirtySet: Iterable<File>): Boolean {
|
||||||
|
return dirtySet.any { file ->
|
||||||
|
!leafFragments.contains(fileToFragment[file.absolutePath])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun canCreateFragmentContext(args: CommonCompilerArguments): Boolean {
|
||||||
|
// fragmentContext solves k2-only issues; also, in k1 mode we don't even expect the fragment-related args
|
||||||
|
val isCorrectLanguageVersion = (LanguageVersion.fromVersionString(args.languageVersion) ?: LanguageVersion.LATEST_STABLE).usesK2
|
||||||
|
|
||||||
|
val hasAllRequiredArguments = listOf(args.fragments, args.fragmentRefines, args.fragmentSources).none {
|
||||||
|
it.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
return isCorrectLanguageVersion && hasAllRequiredArguments
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromCompilerArguments(args: CommonCompilerArguments): FragmentContext? {
|
||||||
|
if (!canCreateFragmentContext(args)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileToFragment = args.fragmentSources!!.associate {
|
||||||
|
// expected format: -Xfragment-sources=jvmMain:/tmp/<..>/kotlin/main.kt,<...>
|
||||||
|
val fragmentSource = it.split(":", limit=2)
|
||||||
|
Pair(fragmentSource.last(), fragmentSource.first())
|
||||||
|
}
|
||||||
|
val refinedFragments = args.fragmentRefines!!.map { fragmentRefine ->
|
||||||
|
// expected format: -Xfragment-refines=jvmMain:commonMain,<...>
|
||||||
|
fragmentRefine.split(":", limit = 2).last()
|
||||||
|
}.toSet()
|
||||||
|
val leafFragments = args.fragments!!.toSet() - refinedFragments
|
||||||
|
|
||||||
|
return FragmentContext(fileToFragment, leafFragments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ class IncrementalCompilationContext(
|
|||||||
*/
|
*/
|
||||||
val trackChangesInLookupCache: Boolean = false,
|
val trackChangesInLookupCache: Boolean = false,
|
||||||
val icFeatures: IncrementalCompilationFeatures = IncrementalCompilationFeatures.DEFAULT_CONFIGURATION,
|
val icFeatures: IncrementalCompilationFeatures = IncrementalCompilationFeatures.DEFAULT_CONFIGURATION,
|
||||||
|
val fragmentContext: FragmentContext? = null,
|
||||||
) {
|
) {
|
||||||
@Deprecated("This constructor is scheduled to be removed. KSP is using it")
|
@Deprecated("This constructor is scheduled to be removed. KSP is using it")
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import java.io.Serializable
|
|||||||
*
|
*
|
||||||
* To add a new feature toggle, consider this checklist:
|
* To add a new feature toggle, consider this checklist:
|
||||||
*
|
*
|
||||||
* 1. Gradle input: see [AbstractKotlinCompile.getIncrementalCompilationFeatures].
|
* 1. Gradle input: see [AbstractKotlinCompile.makeIncrementalCompilationFeatures].
|
||||||
* Platform-agnostic properties should be the default, but you can extend one of the subclasses, if needed.
|
* Platform-agnostic properties should be the default, but you can extend one of the subclasses, if needed.
|
||||||
* If new property might affect the result of compilation, annotate it with @Input.
|
* If new property might affect the result of compilation, annotate it with @Input.
|
||||||
* 2. Gradle.properties support: declare the property in [org.jetbrains.kotlin.gradle.plugin.PropertiesProvider].
|
* 2. Gradle.properties support: declare the property in [org.jetbrains.kotlin.gradle.plugin.PropertiesProvider].
|
||||||
@@ -48,11 +48,17 @@ data class IncrementalCompilationFeatures(
|
|||||||
* Requires [preciseCompilationResultsBackup]
|
* Requires [preciseCompilationResultsBackup]
|
||||||
*/
|
*/
|
||||||
val keepIncrementalCompilationCachesInMemory: Boolean = false,
|
val keepIncrementalCompilationCachesInMemory: Boolean = false,
|
||||||
|
/**
|
||||||
|
* By default, with k2 KMP, we recompile the whole module, if any common sources are recompiled.
|
||||||
|
* It provides consistent builds at the cost of compilation speed. (See KT-62686 for the underlying issue.)
|
||||||
|
* You can enable "unsafeIC" to use pre-2.0 behavior with potentially incorrect incremental builds.
|
||||||
|
*/
|
||||||
|
val enableUnsafeIncrementalCompilationForMultiplatform: Boolean = false,
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DEFAULT_CONFIGURATION = IncrementalCompilationFeatures()
|
val DEFAULT_CONFIGURATION = IncrementalCompilationFeatures()
|
||||||
|
|
||||||
const val serialVersionUID: Long = 0
|
const val serialVersionUID: Long = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* 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.incremental
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normally we can RequireRebuild based on input's change,
|
||||||
|
* but the decision can also come later, between incremental compilation steps.
|
||||||
|
*
|
||||||
|
* This exception is used for KT-63837 where common sources need to be rebuilt for consistency.
|
||||||
|
*/
|
||||||
|
class RequireRebuildForCorrectnessInKMPException() : Exception()
|
||||||
+3
-2
@@ -36,9 +36,10 @@ enum class BuildAttribute(val kind: BuildAttributeKind, val readableString: Stri
|
|||||||
DEP_CHANGE_NON_INCREMENTAL_BUILD_IN_DEP(BuildAttributeKind.REBUILD_REASON, "Non incremental build in history"),
|
DEP_CHANGE_NON_INCREMENTAL_BUILD_IN_DEP(BuildAttributeKind.REBUILD_REASON, "Non incremental build in history"),
|
||||||
IN_PROCESS_EXECUTION(BuildAttributeKind.REBUILD_REASON, "In-process execution"),
|
IN_PROCESS_EXECUTION(BuildAttributeKind.REBUILD_REASON, "In-process execution"),
|
||||||
OUT_OF_PROCESS_EXECUTION(BuildAttributeKind.REBUILD_REASON, "Out of process execution"),
|
OUT_OF_PROCESS_EXECUTION(BuildAttributeKind.REBUILD_REASON, "Out of process execution"),
|
||||||
IC_IS_NOT_ENABLED(BuildAttributeKind.REBUILD_REASON, "Incremental compilation is not enabled");
|
IC_IS_NOT_ENABLED(BuildAttributeKind.REBUILD_REASON, "Incremental compilation is not enabled"),
|
||||||
|
UNSAFE_INCREMENTAL_CHANGE_KT_62686(BuildAttributeKind.REBUILD_REASON, "Incremental compilation might be incorrect (KT-62686)");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val serialVersionUID = 0L
|
const val serialVersionUID = 1L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-5
@@ -91,6 +91,7 @@ abstract class IncrementalCompilerRunner<
|
|||||||
private fun createIncrementalCompilationContext(
|
private fun createIncrementalCompilationContext(
|
||||||
fileLocations: FileLocations?,
|
fileLocations: FileLocations?,
|
||||||
transaction: CompilationTransaction,
|
transaction: CompilationTransaction,
|
||||||
|
fragmentContext: FragmentContext? = null,
|
||||||
) = IncrementalCompilationContext(
|
) = IncrementalCompilationContext(
|
||||||
pathConverterForSourceFiles = fileLocations?.let { it.getRelocatablePathConverterForSourceFiles() } ?: BasicFileToPathConverter,
|
pathConverterForSourceFiles = fileLocations?.let { it.getRelocatablePathConverterForSourceFiles() } ?: BasicFileToPathConverter,
|
||||||
pathConverterForOutputFiles = fileLocations?.let { it.getRelocatablePathConverterForOutputFiles() } ?: BasicFileToPathConverter,
|
pathConverterForOutputFiles = fileLocations?.let { it.getRelocatablePathConverterForOutputFiles() } ?: BasicFileToPathConverter,
|
||||||
@@ -99,6 +100,7 @@ abstract class IncrementalCompilerRunner<
|
|||||||
trackChangesInLookupCache = shouldTrackChangesInLookupCache,
|
trackChangesInLookupCache = shouldTrackChangesInLookupCache,
|
||||||
storeFullFqNamesInLookupCache = shouldStoreFullFqNamesInLookupCache,
|
storeFullFqNamesInLookupCache = shouldStoreFullFqNamesInLookupCache,
|
||||||
icFeatures = icFeatures,
|
icFeatures = icFeatures,
|
||||||
|
fragmentContext = fragmentContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
protected abstract val shouldTrackChangesInLookupCache: Boolean
|
protected abstract val shouldTrackChangesInLookupCache: Boolean
|
||||||
@@ -165,6 +167,9 @@ abstract class IncrementalCompilerRunner<
|
|||||||
class Failed(val reason: BuildAttribute, val cause: Throwable) : ICResult
|
class Failed(val reason: BuildAttribute, val cause: Throwable) : ICResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Be aware that [tryCompileIncrementally] catches a lot of exceptions internally.
|
||||||
|
// So this transformer should be used for very specific things, like cache closing, that are
|
||||||
|
// related to the transaction as a whole rather than any compilation step.
|
||||||
private fun incrementalCompilationExceptionTransformer(t: Throwable): ICResult = when (t) {
|
private fun incrementalCompilationExceptionTransformer(t: Throwable): ICResult = when (t) {
|
||||||
is CachesManagerCloseException -> ICResult.Failed(IC_FAILED_TO_CLOSE_CACHES, t)
|
is CachesManagerCloseException -> ICResult.Failed(IC_FAILED_TO_CLOSE_CACHES, t)
|
||||||
else -> throw t
|
else -> throw t
|
||||||
@@ -186,10 +191,19 @@ abstract class IncrementalCompilerRunner<
|
|||||||
if (changedFiles is ChangedFiles.Unknown) {
|
if (changedFiles is ChangedFiles.Unknown) {
|
||||||
return ICResult.RequiresRebuild(UNKNOWN_CHANGES_IN_GRADLE_INPUTS)
|
return ICResult.RequiresRebuild(UNKNOWN_CHANGES_IN_GRADLE_INPUTS)
|
||||||
}
|
}
|
||||||
changedFiles as ChangedFiles.Known?
|
|
||||||
|
val fragmentContext = if (!icFeatures.enableUnsafeIncrementalCompilationForMultiplatform) { //see KT-62686
|
||||||
|
FragmentContext.fromCompilerArguments(args)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
return createTransaction().runWithin(::incrementalCompilationExceptionTransformer) { transaction ->
|
return createTransaction().runWithin(::incrementalCompilationExceptionTransformer) { transaction ->
|
||||||
val icContext = createIncrementalCompilationContext(fileLocations, transaction)
|
val icContext = createIncrementalCompilationContext(
|
||||||
|
fileLocations,
|
||||||
|
transaction,
|
||||||
|
fragmentContext
|
||||||
|
)
|
||||||
val caches = createCacheManager(icContext, args).also {
|
val caches = createCacheManager(icContext, args).also {
|
||||||
// this way we make the transaction to be responsible for closing the caches manager
|
// this way we make the transaction to be responsible for closing the caches manager
|
||||||
transaction.cachesManager = it
|
transaction.cachesManager = it
|
||||||
@@ -198,7 +212,7 @@ abstract class IncrementalCompilerRunner<
|
|||||||
fun compile(): ICResult {
|
fun compile(): ICResult {
|
||||||
// Step 1: Get changed files
|
// Step 1: Get changed files
|
||||||
val knownChangedFiles: ChangedFiles.Known = try {
|
val knownChangedFiles: ChangedFiles.Known = try {
|
||||||
getChangedFiles(changedFiles, allSourceFiles, caches)
|
getChangedFiles(changedFiles as ChangedFiles.Known?, allSourceFiles, caches)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
return ICResult.Failed(IC_FAILED_TO_GET_CHANGED_FILES, e)
|
return ICResult.Failed(IC_FAILED_TO_GET_CHANGED_FILES, e)
|
||||||
}
|
}
|
||||||
@@ -241,6 +255,8 @@ abstract class IncrementalCompilerRunner<
|
|||||||
abiSnapshotData,
|
abiSnapshotData,
|
||||||
messageCollector,
|
messageCollector,
|
||||||
)
|
)
|
||||||
|
} catch (e: RequireRebuildForCorrectnessInKMPException) {
|
||||||
|
return ICResult.RequiresRebuild(UNSAFE_INCREMENTAL_CHANGE_KT_62686)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
return ICResult.Failed(IC_FAILED_TO_COMPILE_INCREMENTALLY, e)
|
return ICResult.Failed(IC_FAILED_TO_COMPILE_INCREMENTALLY, e)
|
||||||
}
|
}
|
||||||
@@ -463,9 +479,14 @@ abstract class IncrementalCompilerRunner<
|
|||||||
|
|
||||||
val lookupTracker = LookupTrackerImpl(getLookupTrackerDelegate())
|
val lookupTracker = LookupTrackerImpl(getLookupTrackerDelegate())
|
||||||
val expectActualTracker = ExpectActualTrackerImpl()
|
val expectActualTracker = ExpectActualTrackerImpl()
|
||||||
//TODO(valtman) sourceToCompile calculate based on abiSnapshot
|
|
||||||
val (sourcesToCompile, removedKotlinSources) = dirtySources.partition { it.exists() && allKotlinSources.contains(it) }
|
val (sourcesToCompile, removedKotlinSources) = dirtySources.partition { it.exists() && allKotlinSources.contains(it) }
|
||||||
|
|
||||||
|
icContext.fragmentContext?.let {
|
||||||
|
if (it.dirtySetTouchesNonLeafFragments(sourcesToCompile)) {
|
||||||
|
throw RequireRebuildForCorrectnessInKMPException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val services = makeServices(
|
val services = makeServices(
|
||||||
args, lookupTracker, expectActualTracker, caches,
|
args, lookupTracker, expectActualTracker, caches,
|
||||||
dirtySources.toSet(), compilationMode is CompilationMode.Incremental
|
dirtySources.toSet(), compilationMode is CompilationMode.Incremental
|
||||||
@@ -686,4 +707,4 @@ fun extractKotlinSourcesFromFreeCompilerArguments(
|
|||||||
}
|
}
|
||||||
compilerArguments.freeArgs = freeArgs
|
compilerArguments.freeArgs = freeArgs
|
||||||
return allKotlinFiles
|
return allKotlinFiles
|
||||||
}
|
}
|
||||||
|
|||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* 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.mpp
|
||||||
|
|
||||||
|
import org.gradle.api.logging.LogLevel
|
||||||
|
import org.gradle.util.GradleVersion
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.BuildAttribute
|
||||||
|
import org.jetbrains.kotlin.gradle.testbase.*
|
||||||
|
import org.jetbrains.kotlin.gradle.util.replaceWithVersion
|
||||||
|
import org.jetbrains.kotlin.test.TestMetadata
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normal behavior is tested by smokes: [org.jetbrains.kotlin.gradle.mpp.smoke.MultiModuleIncrementalCompilationIT]
|
||||||
|
*
|
||||||
|
* [CommonCodeWithPlatformSymbolsIT] should be removed with the IC option when the underlying compiler issue is fixed
|
||||||
|
*/
|
||||||
|
|
||||||
|
open class CommonCodeWithPlatformSymbolsITBase(val platformSourceSet: String, val taskToExecute: String) : KGPBaseTest() {
|
||||||
|
override val defaultBuildOptions: BuildOptions
|
||||||
|
get() = super.defaultBuildOptions.copy(
|
||||||
|
logLevel = LogLevel.DEBUG,
|
||||||
|
languageVersion = "2.0",
|
||||||
|
enableUnsafeIncrementalCompilationForMultiplatform = false
|
||||||
|
)
|
||||||
|
|
||||||
|
@GradleTest
|
||||||
|
@DisplayName("Baseline - compilation failure without hotfix")
|
||||||
|
@TestMetadata("kt-62686-mpp-source-set-boundary")
|
||||||
|
fun testBaselineForCommonSourceSetIC(gradleVersion: GradleVersion) {
|
||||||
|
project(
|
||||||
|
"kt-62686-mpp-source-set-boundary",
|
||||||
|
gradleVersion,
|
||||||
|
buildOptions = defaultBuildOptions.copy(enableUnsafeIncrementalCompilationForMultiplatform = true)
|
||||||
|
) {
|
||||||
|
// initial build is good
|
||||||
|
build(taskToExecute) {
|
||||||
|
assertTasksExecuted(taskToExecute)
|
||||||
|
}
|
||||||
|
|
||||||
|
projectPath.resolve("src/commonMain/kotlin/riskyCode.kt").replaceWithVersion("useMemberFunctionFromExpectClass")
|
||||||
|
|
||||||
|
// common source file is recompiled, and the incorrect overload resolution happens:
|
||||||
|
buildAndFail(taskToExecute) {
|
||||||
|
assertTasksFailed(taskToExecute)
|
||||||
|
assertOutputContains("Return type mismatch: expected 'kotlin.String', actual '${platformSourceSet}OnlyType'.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// but changed code is valid for full compilation:
|
||||||
|
build("clean", taskToExecute) {
|
||||||
|
assertTasksExecuted(taskToExecute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GradleTest
|
||||||
|
@DisplayName("Use member function of an expect class that is overloaded in the actual class")
|
||||||
|
@TestMetadata("kt-62686-mpp-source-set-boundary")
|
||||||
|
fun testIncrementalBuildWithOverloadedMemberFunction(gradleVersion: GradleVersion) {
|
||||||
|
project(
|
||||||
|
"kt-62686-mpp-source-set-boundary", gradleVersion
|
||||||
|
) {
|
||||||
|
build(taskToExecute) {
|
||||||
|
assertTasksExecuted(taskToExecute)
|
||||||
|
}
|
||||||
|
|
||||||
|
projectPath.resolve("src/commonMain/kotlin/riskyCode.kt").replaceWithVersion("useMemberFunctionFromExpectClass")
|
||||||
|
|
||||||
|
build(taskToExecute) {
|
||||||
|
assertTasksExecuted(taskToExecute)
|
||||||
|
assertNonIncrementalCompilation(BuildAttribute.UNSAFE_INCREMENTAL_CHANGE_KT_62686)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GradleTest
|
||||||
|
@DisplayName("Use top level function that is overloaded in platform sourceSet")
|
||||||
|
@TestMetadata("kt-62686-mpp-source-set-boundary")
|
||||||
|
fun testIncrementalBuildWithOverloadedTopLevelFunction(gradleVersion: GradleVersion) {
|
||||||
|
project(
|
||||||
|
"kt-62686-mpp-source-set-boundary", gradleVersion
|
||||||
|
) {
|
||||||
|
build(taskToExecute) {
|
||||||
|
assertTasksExecuted(taskToExecute)
|
||||||
|
}
|
||||||
|
|
||||||
|
projectPath.resolve("src/commonMain/kotlin/riskyCode.kt").replaceWithVersion("useTopLevelFunction")
|
||||||
|
|
||||||
|
build(taskToExecute) {
|
||||||
|
assertTasksExecuted(taskToExecute)
|
||||||
|
assertNonIncrementalCompilation(BuildAttribute.UNSAFE_INCREMENTAL_CHANGE_KT_62686)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GradleTest
|
||||||
|
@DisplayName("Touch risky code by change in common sourceSet")
|
||||||
|
@TestMetadata("kt-62686-mpp-source-set-boundary")
|
||||||
|
fun testIncrementalBuildWhereRiskyCodeIsTouchedIndirectly(gradleVersion: GradleVersion) {
|
||||||
|
project(
|
||||||
|
"kt-62686-mpp-source-set-boundary", gradleVersion
|
||||||
|
) {
|
||||||
|
projectPath.resolve("src/commonMain/kotlin/riskyCode.kt").replaceWithVersion("useMemberFunctionFromExpectClass")
|
||||||
|
|
||||||
|
build(taskToExecute) {
|
||||||
|
assertTasksExecuted(taskToExecute)
|
||||||
|
}
|
||||||
|
|
||||||
|
projectPath.resolve("src/commonMain/kotlin/dependedOnByRiskyCode.kt")
|
||||||
|
.replaceWithVersion("changeType")
|
||||||
|
|
||||||
|
build(taskToExecute) {
|
||||||
|
assertTasksExecuted(taskToExecute)
|
||||||
|
assertNonIncrementalCompilation(BuildAttribute.UNSAFE_INCREMENTAL_CHANGE_KT_62686)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GradleTest
|
||||||
|
@DisplayName("Touch risky code by change in platform sourceSet")
|
||||||
|
@TestMetadata("kt-62686-mpp-source-set-boundary")
|
||||||
|
fun testIncrementalBuildWhereRiskyCodeIsTouchedByChangeInPlatformSourceSet(gradleVersion: GradleVersion) {
|
||||||
|
project(
|
||||||
|
"kt-62686-mpp-source-set-boundary", gradleVersion
|
||||||
|
) {
|
||||||
|
projectPath.resolve("src/commonMain/kotlin/riskyCode.kt").replaceWithVersion("useMemberFunctionFromExpectClass")
|
||||||
|
|
||||||
|
build(taskToExecute) {
|
||||||
|
assertTasksExecuted(taskToExecute)
|
||||||
|
}
|
||||||
|
|
||||||
|
projectPath.resolve("src/$platformSourceSet/kotlin/dependedOnByActualDeclaration.kt")
|
||||||
|
.replaceWithVersion("changeType")
|
||||||
|
|
||||||
|
build(taskToExecute) {
|
||||||
|
assertTasksExecuted(taskToExecute)
|
||||||
|
assertNonIncrementalCompilation(BuildAttribute.UNSAFE_INCREMENTAL_CHANGE_KT_62686)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MppGradlePluginTests
|
||||||
|
@DisplayName("Tests for IC with compatible overloads in common and platform sourceSets - Jvm")
|
||||||
|
class CommonCodeWithPlatformSymbolsJvmIT() : CommonCodeWithPlatformSymbolsITBase(
|
||||||
|
platformSourceSet = "jvmMain", taskToExecute = ":compileKotlinJvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
@MppGradlePluginTests
|
||||||
|
@DisplayName("Tests for IC with compatible overloads in common and platform sourceSets - Js")
|
||||||
|
class CommonCodeWithPlatformSymbolsJsIT() : CommonCodeWithPlatformSymbolsITBase(
|
||||||
|
platformSourceSet = "jsMain", taskToExecute = ":compileKotlinJs"
|
||||||
|
)
|
||||||
+6
-1
@@ -20,7 +20,12 @@ import java.nio.file.Path
|
|||||||
abstract class KmpIncrementalITBase : KGPBaseTest() {
|
abstract class KmpIncrementalITBase : KGPBaseTest() {
|
||||||
|
|
||||||
override val defaultBuildOptions: BuildOptions
|
override val defaultBuildOptions: BuildOptions
|
||||||
get() = super.defaultBuildOptions.copyEnsuringK2()
|
get() = super.defaultBuildOptions.copyEnsuringK2().copy(
|
||||||
|
/**
|
||||||
|
* disable IC-breaking feature; it's tested separately in [org.jetbrains.kotlin.gradle.mpp.CommonCodeWithPlatformSymbolsITBase]
|
||||||
|
*/
|
||||||
|
enableUnsafeIncrementalCompilationForMultiplatform = true
|
||||||
|
)
|
||||||
|
|
||||||
protected open val gradleTask = "assemble"
|
protected open val gradleTask = "assemble"
|
||||||
protected open val projectName = "generic-kmp-app-plus-lib-with-tests"
|
protected open val projectName = "generic-kmp-app-plus-lib-with-tests"
|
||||||
|
|||||||
+4
-1
@@ -22,7 +22,10 @@ import org.junit.jupiter.api.DisplayName
|
|||||||
@MppGradlePluginTests
|
@MppGradlePluginTests
|
||||||
open class ExpectActualIncrementalCompilationIT : KGPBaseTest() {
|
open class ExpectActualIncrementalCompilationIT : KGPBaseTest() {
|
||||||
override val defaultBuildOptions: BuildOptions
|
override val defaultBuildOptions: BuildOptions
|
||||||
get() = super.defaultBuildOptions.copyEnsuringK2()
|
get() = super.defaultBuildOptions.copyEnsuringK2().copy(
|
||||||
|
// disable IC-breaking feature; it's tested separately in [org.jetbrains.kotlin.gradle.mpp.CommonCodeWithPlatformSymbolsITBase]
|
||||||
|
enableUnsafeIncrementalCompilationForMultiplatform = true
|
||||||
|
)
|
||||||
|
|
||||||
@DisplayName("File with actual declaration needs recompiling")
|
@DisplayName("File with actual declaration needs recompiling")
|
||||||
@GradleTest
|
@GradleTest
|
||||||
|
|||||||
+5
@@ -46,6 +46,7 @@ data class BuildOptions(
|
|||||||
val statisticsForceValidation: Boolean = true,
|
val statisticsForceValidation: Boolean = true,
|
||||||
val usePreciseOutputsBackup: Boolean? = null,
|
val usePreciseOutputsBackup: Boolean? = null,
|
||||||
val keepIncrementalCompilationCachesInMemory: Boolean? = null,
|
val keepIncrementalCompilationCachesInMemory: Boolean? = null,
|
||||||
|
val enableUnsafeIncrementalCompilationForMultiplatform: Boolean? = null,
|
||||||
val useDaemonFallbackStrategy: Boolean = false,
|
val useDaemonFallbackStrategy: Boolean = false,
|
||||||
val useParsableDiagnosticsFormatting: Boolean = true,
|
val useParsableDiagnosticsFormatting: Boolean = true,
|
||||||
val showDiagnosticsStacktrace: Boolean? = false, // false by default to not clutter the testdata + stacktraces change often
|
val showDiagnosticsStacktrace: Boolean? = false, // false by default to not clutter the testdata + stacktraces change often
|
||||||
@@ -195,6 +196,10 @@ data class BuildOptions(
|
|||||||
arguments.add("-Pkotlin.compiler.keepIncrementalCompilationCachesInMemory=$keepIncrementalCompilationCachesInMemory")
|
arguments.add("-Pkotlin.compiler.keepIncrementalCompilationCachesInMemory=$keepIncrementalCompilationCachesInMemory")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enableUnsafeIncrementalCompilationForMultiplatform != null) {
|
||||||
|
arguments.add("-Pkotlin.internal.incremental.enableUnsafeOptimizationsForMultiplatform=$enableUnsafeIncrementalCompilationForMultiplatform")
|
||||||
|
}
|
||||||
|
|
||||||
arguments.add("-Pkotlin.daemon.useFallbackStrategy=$useDaemonFallbackStrategy")
|
arguments.add("-Pkotlin.daemon.useFallbackStrategy=$useDaemonFallbackStrategy")
|
||||||
|
|
||||||
if (useParsableDiagnosticsFormatting) {
|
if (useParsableDiagnosticsFormatting) {
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("multiplatform")
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm().compilations.all {
|
||||||
|
compilerOptions.configure {
|
||||||
|
// log level isn't properly used to set `verbose` in the default configuration, fix is WIP in KT-64698
|
||||||
|
verbose.convention(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
js(IR)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
val dependedOnByRiskyCode = 1
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
val dependedOnByRiskyCode = "2"
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
fun test(): String = "!" //A().foo(Child())
|
||||||
|
|
||||||
|
fun test2(): String = "?" //foo(Child())
|
||||||
|
|
||||||
|
private val dependency = dependedOnByRiskyCode
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
fun test(): String = A().foo(Child())
|
||||||
|
|
||||||
|
fun test2(): String = "?" //foo(Child())
|
||||||
|
|
||||||
|
private val dependency = dependedOnByRiskyCode
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
fun test(): String = "!" //A().foo(Child())
|
||||||
|
|
||||||
|
fun test2(): String = foo(Child())
|
||||||
|
|
||||||
|
private val dependency = dependedOnByRiskyCode
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
open class Base
|
||||||
|
class Child: Base()
|
||||||
|
|
||||||
|
fun foo(x: Base): String = ""
|
||||||
|
|
||||||
|
expect class A() {
|
||||||
|
fun foo(x: Base): String
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
class jsMainOnlyType()
|
||||||
|
|
||||||
|
actual class A {
|
||||||
|
actual fun foo(x: Base) = "aloha"
|
||||||
|
fun foo(x: Child) = jsMainOnlyType()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(x: Child) = jsMainOnlyType()
|
||||||
|
|
||||||
|
fun testPlatform() =
|
||||||
|
A().foo(Child())
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
println(test())
|
||||||
|
println(testPlatform())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dependency = dependedOnByActualDeclaration
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
val dependedOnByActualDeclaration = 1
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
val dependedOnByActualDeclaration = "2"
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
class jvmMainOnlyType
|
||||||
|
|
||||||
|
actual class A {
|
||||||
|
actual fun foo(x: Base) = "aloha"
|
||||||
|
fun foo(x: Child) = jvmMainOnlyType()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(x: Child) = jvmMainOnlyType()
|
||||||
|
|
||||||
|
fun testPlatform() =
|
||||||
|
A().foo(Child())
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
println(test())
|
||||||
|
println(testPlatform())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dependency = dependedOnByActualDeclaration
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
val dependedOnByActualDeclaration = 1
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
val dependedOnByActualDeclaration = "2"
|
||||||
+8
@@ -540,6 +540,13 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
|||||||
.map { KotlinCompilerArgumentsLogLevel.fromPropertyValue(it) }
|
.map { KotlinCompilerArgumentsLogLevel.fromPropertyValue(it) }
|
||||||
.orElse(KotlinCompilerArgumentsLogLevel.DEFAULT)
|
.orElse(KotlinCompilerArgumentsLogLevel.DEFAULT)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Without unsafe optimization: in k2, if common source is dirty, module will be rebuilt.
|
||||||
|
* With unsafe optimization: regular IC logic is used. Common sources might see declarations from platform sources. See KT-62686
|
||||||
|
*/
|
||||||
|
val enableUnsafeOptimizationsForMultiplatform: Boolean
|
||||||
|
get() = booleanProperty(PropertyNames.KOTLIN_UNSAFE_MULTIPLATFORM_INCREMENTAL_COMPILATION) ?: false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a comma-separated list of browsers to use when running karma tests for [target]
|
* Retrieves a comma-separated list of browsers to use when running karma tests for [target]
|
||||||
* @see KOTLIN_JS_KARMA_BROWSERS
|
* @see KOTLIN_JS_KARMA_BROWSERS
|
||||||
@@ -648,6 +655,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
|||||||
val KOTLIN_CREATE_ARCHIVE_TASKS_FOR_CUSTOM_COMPILATIONS =
|
val KOTLIN_CREATE_ARCHIVE_TASKS_FOR_CUSTOM_COMPILATIONS =
|
||||||
property("$KOTLIN_INTERNAL_NAMESPACE.mpp.createArchiveTasksForCustomCompilations")
|
property("$KOTLIN_INTERNAL_NAMESPACE.mpp.createArchiveTasksForCustomCompilations")
|
||||||
val KOTLIN_COMPILER_ARGUMENTS_LOG_LEVEL = property("$KOTLIN_INTERNAL_NAMESPACE.compiler.arguments.log.level")
|
val KOTLIN_COMPILER_ARGUMENTS_LOG_LEVEL = property("$KOTLIN_INTERNAL_NAMESPACE.compiler.arguments.log.level")
|
||||||
|
val KOTLIN_UNSAFE_MULTIPLATFORM_INCREMENTAL_COMPILATION = property("$KOTLIN_INTERNAL_NAMESPACE.incremental.enableUnsafeOptimizationsForMultiplatform")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
+5
@@ -218,6 +218,10 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constr
|
|||||||
@get:Internal
|
@get:Internal
|
||||||
internal abstract val keepIncrementalCompilationCachesInMemory: Property<Boolean>
|
internal abstract val keepIncrementalCompilationCachesInMemory: Property<Boolean>
|
||||||
|
|
||||||
|
/** See [org.jetbrains.kotlin.incremental.IncrementalCompilationFeatures.enableUnsafeIncrementalCompilationForMultiplatform] */
|
||||||
|
@get:Internal
|
||||||
|
internal abstract val enableUnsafeIncrementalCompilationForMultiplatform: Property<Boolean>
|
||||||
|
|
||||||
/** Task outputs that we don't want to include in [TaskOutputsBackup] (see [TaskOutputsBackup.outputsToRestore] for more info). */
|
/** Task outputs that we don't want to include in [TaskOutputsBackup] (see [TaskOutputsBackup.outputsToRestore] for more info). */
|
||||||
@get:Internal
|
@get:Internal
|
||||||
internal abstract val taskOutputsBackupExcludes: SetProperty<File>
|
internal abstract val taskOutputsBackupExcludes: SetProperty<File>
|
||||||
@@ -302,6 +306,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constr
|
|||||||
return IncrementalCompilationFeatures(
|
return IncrementalCompilationFeatures(
|
||||||
preciseCompilationResultsBackup = preciseCompilationResultsBackup.get(),
|
preciseCompilationResultsBackup = preciseCompilationResultsBackup.get(),
|
||||||
keepIncrementalCompilationCachesInMemory = keepIncrementalCompilationCachesInMemory.get(),
|
keepIncrementalCompilationCachesInMemory = keepIncrementalCompilationCachesInMemory.get(),
|
||||||
|
enableUnsafeIncrementalCompilationForMultiplatform = enableUnsafeIncrementalCompilationForMultiplatform.get(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -80,6 +80,9 @@ internal abstract class AbstractKotlinCompileConfig<TASK : AbstractKotlinCompile
|
|||||||
task.taskOutputsBackupExcludes.addAll(task.keepIncrementalCompilationCachesInMemory.map {
|
task.taskOutputsBackupExcludes.addAll(task.keepIncrementalCompilationCachesInMemory.map {
|
||||||
if (it) listOf(task.taskBuildCacheableOutputDirectory.get().asFile) else emptyList()
|
if (it) listOf(task.taskBuildCacheableOutputDirectory.get().asFile) else emptyList()
|
||||||
})
|
})
|
||||||
|
task.enableUnsafeIncrementalCompilationForMultiplatform
|
||||||
|
.convention(propertiesProvider.enableUnsafeOptimizationsForMultiplatform)
|
||||||
|
.finalizeValueOnRead()
|
||||||
task.buildFinishedListenerService.value(buildFinishedListenerService).disallowChanges()
|
task.buildFinishedListenerService.value(buildFinishedListenerService).disallowChanges()
|
||||||
task.buildIdService.value(buildIdService).disallowChanges()
|
task.buildIdService.value(buildIdService).disallowChanges()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user