Prepare for enabling HMPP by default and migrating the flags (KT-40245)

* Add a new single flag for enabling HMPP:
  kotlin.mpp.hierarchicalStructureSupport

  When this flag is enabled:

  * The old HMPP flags gets enabled, too, for old consumers
  * The commonizer gets enabled as well
  * An internal flag is set to indicate that the old flags were set by
    the plugin

* Add an internal flag that we should flip when HMPP becomes the
  default: kotlin.internal.mpp.hierarchicalStructureByDefault

  With this flag is enabled:

  * All MPP projects will use the composite artifact and
    compile metadata with the KLIB compiler
  * The compatibility metadata artifact will be gone
  * The new HMPP flag is enabled by default

* Add consistency checks for new VS old flags

Issue #KT-40245
This commit is contained in:
Sergey Igushkin
2021-07-27 19:44:11 +04:00
committed by teamcityserver
parent 352c624601
commit d0f207071c
8 changed files with 373 additions and 12 deletions
@@ -0,0 +1,68 @@
/*
* Copyright 2010-2021 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
import org.jetbrains.kotlin.gradle.HierarchicalStructureOptInMigrationArtifactContentIT.Mode.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.util.zip.ZipFile
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@RunWith(Parameterized::class)
internal class HierarchicalStructureOptInMigrationArtifactContentIT : BaseGradleIT() {
enum class Mode {
FLIPPED_DEFAULT, FLIPPED_DISABLE, NON_FLIPPED_DEFAULT
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun params() = Mode.values().map { arrayOf(it) }
}
@Parameterized.Parameter(0)
lateinit var mode: Mode
@ExperimentalStdlibApi
@Test
@Suppress("NON_EXHAUSTIVE_WHEN")
fun testArtifactFormatAndContent() = with(transformProjectWithPluginsDsl("new-mpp-published")) {
projectDir.resolve("gradle.properties").delete()
build(
*buildList {
add("clean")
add("publish")
when (mode) {
FLIPPED_DISABLE, FLIPPED_DEFAULT -> add("-Pkotlin.internal.mpp.hierarchicalStructureByDefault=true")
}
when (mode) {
FLIPPED_DISABLE -> add("-Pkotlin.mpp.hierarchicalStructureSupport=false")
}
}.toTypedArray(),
) {
assertSuccessful()
val metadataJarEntries = ZipFile(
projectDir.resolve("../repo/com/example/bar/my-lib-bar/1.0/my-lib-bar-1.0.jar")
).use { zip ->
zip.entries().asSequence().toList().map { it.name }
}
if (mode != NON_FLIPPED_DEFAULT) {
assertTrue { metadataJarEntries.any { "commonMain" in it } }
}
val hasJvmAndJsMainEntries = metadataJarEntries.any { "jvmAndJsMain" in it }
val shouldHaveJvmAndJsMainEntries = when (mode) {
FLIPPED_DISABLE, NON_FLIPPED_DEFAULT -> false
FLIPPED_DEFAULT -> true
}
assertEquals(shouldHaveJvmAndJsMainEntries, hasJvmAndJsMainEntries)
}
}
}
@@ -0,0 +1,133 @@
/*
* Copyright 2010-2021 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
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@RunWith(Parameterized::class)
internal class MppFlagsMigrationIT : BaseGradleIT() {
internal data class TestCase(
var hierarchiesByDefault: Boolean = false,
var hierarchiesSupportFlag: Boolean? = null,
var granularMetadataFlag: Boolean? = null,
var dependencyPropagationFlag: Boolean? = null,
var expectedToPass: Boolean = true,
var expectedPhraseInOutput: String? = null,
) {
constructor(configure: TestCase.() -> Unit) : this() {
configure()
}
}
@Parameterized.Parameter(0)
lateinit var testCase: TestCase
companion object {
@OptIn(ExperimentalStdlibApi::class)
private val testCases = buildList {
add(TestCase {
hierarchiesByDefault = true
granularMetadataFlag = true
expectedToPass = true
expectedPhraseInOutput = "It is safe to remove the property."
})
add(TestCase {
hierarchiesByDefault = true
granularMetadataFlag = false
expectedToPass = false
expectedPhraseInOutput = "Multiplatform Hierarchical Structures support is now enabled by default"
})
add(TestCase {
hierarchiesByDefault = true
dependencyPropagationFlag = false
expectedToPass = true
expectedPhraseInOutput = "It is safe to remove the property"
})
add(TestCase {
hierarchiesByDefault = true
dependencyPropagationFlag = true
expectedToPass = false
expectedPhraseInOutput = "Kotlin/Native dependencies commonization is now enabled by default"
})
add(TestCase {
hierarchiesByDefault = true
hierarchiesSupportFlag = false
granularMetadataFlag = true
expectedToPass = false
expectedPhraseInOutput = "Conflicting properties"
})
add(TestCase {
hierarchiesByDefault = true
hierarchiesSupportFlag = false
granularMetadataFlag = false
expectedToPass = true
expectedPhraseInOutput = "is redundant"
})
add(TestCase {
hierarchiesByDefault = true
hierarchiesSupportFlag = false
dependencyPropagationFlag = false
expectedToPass = false
expectedPhraseInOutput = "Conflicting properties"
})
add(TestCase {
hierarchiesByDefault = false
hierarchiesSupportFlag = false
expectedToPass = false
expectedPhraseInOutput = "not yet supported"
})
add(TestCase {
hierarchiesByDefault = false
hierarchiesSupportFlag = true
expectedToPass = false
expectedPhraseInOutput = "not yet supported"
})
}
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun testCases() = testCases.map { arrayOf(it) }
}
val testProject by lazy {
Project("new-mpp-published").apply {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
}
}
@OptIn(ExperimentalStdlibApi::class)
@Test
fun doTest() {
val args = buildList {
with(testCase) {
if (hierarchiesByDefault)
add("-Pkotlin.internal.mpp.hierarchicalStructureByDefault=true")
if (hierarchiesSupportFlag != null)
add("-Pkotlin.mpp.hierarchicalStructureSupport=$hierarchiesSupportFlag")
if (granularMetadataFlag != null)
add("-Pkotlin.mpp.enableGranularSourceSetsMetadata=$granularMetadataFlag")
if (dependencyPropagationFlag != null) {
add("-Pkotlin.native.enableDependencyPropagation=$dependencyPropagationFlag")
}
}
}
// just run the configuration phase:
testProject.build(*args.toTypedArray()) {
if (testCase.expectedToPass)
assertSuccessful()
else
assertFailed()
testCase.expectedPhraseInOutput?.let { assertContains(it, ignoreCase = true) }
}
}
}
@@ -11,6 +11,9 @@ import org.jetbrains.kotlin.gradle.dsl.NativeCacheKind
import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessageOutputStreamHandler.Companion.IGNORE_TCSM_OVERFLOW
import org.jetbrains.kotlin.gradle.plugin.Kotlin2JsPlugin.Companion.NOWARN_2JS_FLAG
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.Companion.jsCompilerProperty
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_DEPENDENCY_PROPAGATION
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.js.dukat.ExternalsOutputFormat
@@ -146,10 +149,23 @@ internal class PropertiesProvider private constructor(private val project: Proje
get() = booleanProperty("kotlin.android.buildTypeAttribute.keep") ?: false
val enableGranularSourceSetsMetadata: Boolean?
get() = booleanProperty("kotlin.mpp.enableGranularSourceSetsMetadata")
get() = booleanProperty(KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA)
val hierarchicalStructureSupport: Boolean
get() = booleanProperty(KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT) ?: mppHierarchicalStructureByDefault
val nativeDependencyPropagation: Boolean?
get() = booleanProperty(KOTLIN_NATIVE_DEPENDENCY_PROPAGATION)
var mpp13XFlagsSetByPlugin: Boolean
get() = booleanProperty("kotlin.internal.mpp.13X.flags.setByPlugin") ?: false
set(value) { project.extensions.extraProperties.set("kotlin.internal.mpp.13X.flags.setByPlugin") { "$value" } }
val mppHierarchicalStructureByDefault: Boolean
get() = booleanProperty("kotlin.internal.mpp.hierarchicalStructureByDefault") ?: false
val enableCompatibilityMetadataVariant: Boolean
get() = booleanProperty("kotlin.mpp.enableCompatibilityMetadataVariant") ?: true
get() = booleanProperty("kotlin.mpp.enableCompatibilityMetadataVariant") ?: !mppHierarchicalStructureByDefault
val enableKotlinToolingMetadataArtifact: Boolean
get() = booleanProperty("kotlin.mpp.enableKotlinToolingMetadataArtifact") ?: true
@@ -381,6 +397,12 @@ internal class PropertiesProvider private constructor(private val project: Proje
localProperties.getProperty(propName)
}
internal object PropertyNames {
internal const val KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA = "kotlin.mpp.enableGranularSourceSetsMetadata"
internal const val KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT = "kotlin.mpp.hierarchicalStructureSupport"
internal const val KOTLIN_NATIVE_DEPENDENCY_PROPAGATION = "kotlin.native.enableDependencyPropagation"
}
companion object {
internal const val KOTLIN_NATIVE_HOME = "kotlin.native.home"
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.sources.getVisibleSourceSetsFromAssociateCompilations
import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon
interface KotlinMetadataCompilation<T : KotlinCommonOptions> : KotlinCompilation<T>
@@ -28,7 +29,7 @@ class KotlinCommonCompilation(
get() = super.compileKotlinTask as KotlinCompileCommon
internal val isKlibCompilation: Boolean
get() = PropertiesProvider(target.project).enableGranularSourceSetsMetadata == true && !forceCompilationToKotlinMetadata
get() = target.project.isKotlinGranularMetadataEnabled && !forceCompilationToKotlinMetadata
internal var forceCompilationToKotlinMetadata: Boolean = false
@@ -11,7 +11,6 @@ import org.gradle.api.Project
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.plugins.DslObject
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.tasks.SourceTask
@@ -26,6 +25,7 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin.Companion.sourceSetFreeCompilerArgsPropertyName
import org.jetbrains.kotlin.gradle.plugin.mpp.internal.handleHierarchicalStructureFlagsMigration
import org.jetbrains.kotlin.gradle.plugin.sources.*
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
@@ -58,8 +58,6 @@ class KotlinMultiplatformPlugin : Plugin<Project> {
override fun apply(project: Project) {
checkGradleCompatibility("the Kotlin Multiplatform plugin", GradleVersion.version("6.0"))
project.plugins.apply(JavaBasePlugin::class.java)
if (PropertiesProvider(project).mppStabilityNoWarn != true) {
SingleWarningPerBuild.show(
project,
@@ -69,6 +67,10 @@ class KotlinMultiplatformPlugin : Plugin<Project> {
)
}
handleHierarchicalStructureFlagsMigration(project)
project.plugins.apply(JavaBasePlugin::class.java)
val targetsContainer = project.container(KotlinTarget::class.java)
val kotlinMultiplatformExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
val targetsFromPreset = TargetFromPresetExtension(kotlinMultiplatformExtension)
@@ -0,0 +1,122 @@
/*
* Copyright 2010-2021 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.plugin.mpp.internal
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames
import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild
import org.jetbrains.kotlin.gradle.utils.getOrPut
internal fun handleHierarchicalStructureFlagsMigration(project: Project) {
with(PropertiesProvider(project)) {
checkHmppFeatureFlagsForConsistency(project)
if (hierarchicalStructureSupport) {
if (project === project.rootProject)
project.extensions.extraProperties.getOrPut(PropertyNames.KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA) { "true" }
project.extensions.extraProperties.getOrPut(PropertyNames.KOTLIN_NATIVE_DEPENDENCY_PROPAGATION) { "false" }
PropertiesProvider(project).mpp13XFlagsSetByPlugin = true
}
}
}
private fun PropertiesProvider.checkHmppFeatureFlagsForConsistency(project: Project) {
if (mppHierarchicalStructureByDefault) {
if (hierarchicalStructureSupport) {
if (project === project.rootProject) {
when (enableGranularSourceSetsMetadata) {
true -> warnGranularMetadataTrueHasNoEffect(project)
false -> errorGranularMetadataFalseUnsupported()
null -> Unit
}
}
when (nativeDependencyPropagation) {
false -> if (!mpp13XFlagsSetByPlugin) warningDependencyPropagationFalseHasNoEffect(project)
true -> errorDependencyPropagationTrueUnsupported()
null -> Unit
}
} else { // hierarchicalStructureSupport is false
if (project === project.rootProject) {
when (enableGranularSourceSetsMetadata) {
true -> errorGranularMetadataTrueConflictsWithNewFlag()
false -> warningGranularMetadataFalseRedundantWithNewFlag(project)
null -> Unit
}
}
if (!mpp13XFlagsSetByPlugin && nativeDependencyPropagation == false)
errorDependencyPropagationFalseConflictsWithNewFlag()
}
} else { // mppHierarchicalStructureByDefault is false
if (project.findProperty(PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT) != null)
throw GradleException("The property '${PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT}' is not yet supported.")
}
}
private fun errorDependencyPropagationFalseConflictsWithNewFlag() {
throw GradleException(
"Conflicting properties: '${PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT}=false' (enabling Kotlin/Native " +
"dependencies commonization) " +
"and '${PropertyNames.KOTLIN_NATIVE_DEPENDENCY_PROPAGATION}=false'"
)
}
private fun warningGranularMetadataFalseRedundantWithNewFlag(project: Project) {
SingleWarningPerBuild.show(
project,
"The property '${PropertyNames.KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA}=false' is redundant with " +
"'${PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT}=false'\n"
)
}
private fun errorGranularMetadataTrueConflictsWithNewFlag() {
throw GradleException(
"Conflicting properties: '${PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT}=false' and " +
"'${PropertyNames.KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA}=true'."
)
}
private fun warningDependencyPropagationFalseHasNoEffect(project: Project) {
SingleWarningPerBuild.show(
project,
"The property '${PropertyNames.KOTLIN_NATIVE_DEPENDENCY_PROPAGATION}=false' has no effect in this and future " +
"Kotlin versions, as Kotlin/Native dependency commonization is now enabled by default. " +
"It is safe to remove the property.\n"
)
}
private fun errorDependencyPropagationTrueUnsupported() {
throw GradleException(
"Kotlin/Native dependencies commonization is now enabled by default for all projects with Native-shared code, " +
"and the property '${PropertyNames.KOTLIN_NATIVE_DEPENDENCY_PROPAGATION}=true' is not supported anymore. " +
"It is possible to temporarily disable Hierarchical Structures support altogether with the following Gradle " +
"property:\n\n" +
" ${PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT}=false\n\n" +
"If you are facing any issues with shared code and Hierarchical Structures support, please report them " +
"at https://kotl.in/issue\n"
)
}
private fun errorGranularMetadataFalseUnsupported() {
throw GradleException(
"Kotlin Multiplatform Hierarchical Structures support is now enabled by default for all projects, and the " +
"property '${PropertyNames.KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA}=false' is not supported anymore. " +
"If you need to temporarily revert to the old behavior, please use the following Gradle property:\n\n" +
" ${PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT}=false\n\n" +
"If you are facing any issues with shared code and Hierarchical Structures support, please report them " +
"at https://kotl.in/issue"
)
}
private fun warnGranularMetadataTrueHasNoEffect(project: Project) {
SingleWarningPerBuild.show(
project,
"The property '${PropertyNames.KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA}=true' has no effect in this and future " +
"Kotlin versions, as Hierarchical Structures support is now enabled by default. " +
"It is safe to remove the property.\n"
)
}
@@ -41,8 +41,19 @@ internal const val ALL_COMPILE_METADATA_CONFIGURATION_NAME = "allSourceSetsCompi
internal const val ALL_RUNTIME_METADATA_CONFIGURATION_NAME = "allSourceSetsRuntimeDependenciesMetadata"
internal val Project.isKotlinGranularMetadataEnabled: Boolean
get() = project.topLevelExtension is KotlinPm20ProjectExtension ||
PropertiesProvider(rootProject).enableGranularSourceSetsMetadata == true
get() = project.topLevelExtension is KotlinPm20ProjectExtension || with(PropertiesProvider(rootProject)) {
mppHierarchicalStructureByDefault || // then we want to use KLIB granular compilation & artifacts even if it's just commonMain
hierarchicalStructureSupport ||
enableGranularSourceSetsMetadata == true
}
internal val Project.shouldCompileIntermediateSourceSetsToMetadata: Boolean
get() = project.topLevelExtension is KotlinPm20ProjectExtension || with(PropertiesProvider(rootProject)) {
when {
!hierarchicalStructureSupport && mppHierarchicalStructureByDefault -> false
else -> true
}
}
internal val Project.isCompatibilityMetadataVariantEnabled: Boolean
get() = PropertiesProvider(this).enableCompatibilityMetadataVariant == true
@@ -569,6 +580,9 @@ internal fun dependsOnClosureWithInterCompilationDependencies(project: Project,
* Those compilations will be created but the corresponding tasks will be disabled.
*/
internal fun getCommonSourceSetsForMetadataCompilation(project: Project): Set<KotlinSourceSet> {
if (!project.shouldCompileIntermediateSourceSetsToMetadata)
return setOf(project.multiplatformExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME))
val compilationsBySourceSet: Map<KotlinSourceSet, Set<KotlinCompilation<*>>> =
compilationsBySourceSets(project)
@@ -11,10 +11,9 @@ import org.jetbrains.kotlin.commonizer.*
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.compareVersionNumbers
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.gradle.targets.metadata.getMetadataCompilationForSourceSet
import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled
@@ -76,7 +75,7 @@ private fun File.listLibraryFiles(): List<File> = listFiles().orEmpty()
private val Project.isNativeDependencyPropagationEnabled: Boolean
get() = (findProperty("kotlin.native.enableDependencyPropagation") as? String)?.toBoolean() ?: true
get() = PropertiesProvider(this).nativeDependencyPropagation ?: true
//for reflection call from KotlinCommonizerModelBuilder
// DO NOT REFACTOR THIS FUNCTION!