KT-38221: Stdlib by default; KT-40225 Single dependency on kotlin-test

* Add a DSL property `kotlin.coreLibrariesVersion` that is used as the
  default version for all org.jetbrains.kotlin dependencies instead of
  the plugin version used previously – the pluging version is the
  default now.

* Support omitting the `kotlin-stdlib-*` dependencies in all kinds of
  projects. For the JVM source sets, choose the stdlib module by
  checking the jvmTarget in the compilations – if all compilations
  target 8+ then use `kotlin-stdlib-jdk8`.

* Support adding a single dependency on the fake module
  `kotlin-test-multiplatform` that is later expanded into the
  appropriate dependencies on `kotlin-test-*` modules. For JVM source
  sets, detect the test framework by inspecting the test tasks.

* (minor) Fix _executionSource not updated in KotlinJvmTestRun

* (minor) Lazily query `kotlinUsageContext`'s dependency-related
  properties to avoid freezing the configurations

Issue #KT-38221 Fixed
Issue #KT-40225 Fixed
This commit is contained in:
Sergey Igushkin
2020-07-11 16:55:40 +04:00
parent 49c8d99f61
commit d83d3304a5
20 changed files with 678 additions and 27 deletions
@@ -28,7 +28,7 @@ abstract class BaseGradleIT {
protected var workingDir = File(".")
protected open fun defaultBuildOptions(): BuildOptions = BuildOptions(withDaemon = true)
internal open fun defaultBuildOptions(): BuildOptions = BuildOptions(withDaemon = true)
open val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.None
@@ -0,0 +1,307 @@
/*
* 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.
*/
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.internals.KOTLIN_TEST_MULTIPLATFORM_MODULE_NAME
import org.jetbrains.kotlin.gradle.util.AGPVersion
import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.test.KotlinTestUtils
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class KotlinSpecificDependenciesIT : BaseGradleIT() {
override val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.FOR_MPP_SUPPORT
override fun defaultBuildOptions(): BuildOptions =
super.defaultBuildOptions().copy(androidGradlePluginVersion = AGPVersion.v3_6_0, androidHome = KotlinTestUtils.findAndroidSdk())
private var testBuildRunId = 0
private fun Project.prepare() { // call this when reusing a project after a test, too, in order to remove any added dependencies
setupWorkingDir()
gradleSettingsScript().takeIf { it.exists() }?.modify(::transformBuildScriptWithPluginsDsl)
gradleBuildScript().modify {
transformBuildScriptWithPluginsDsl(it).lines().filter { line ->
"stdlib" !in line && "kotlin(\"test" !in line && "kotlin-test" !in line
}.joinToString("\n")
}
}
private fun jsProject() = Project("kotlin-js-plugin-project").apply {
setupWorkingDir()
gradleBuildScript().modify { it.lines().filter { "html" !in it }.joinToString("\n") }
projectFile("Main.kt").modify { "fun f() = listOf(1, 2, 3).joinToString()" }
prepare()
}
private fun androidProject() = Project("AndroidLibraryKotlinProject").apply { prepare() }
private fun mppProject() = Project("jvm-and-js-hmpp").apply {
setupWorkingDir()
prepare()
}
private fun jvmProject() = Project("simpleProject").apply {
prepare()
gradleBuildScript().modify { it.lines().filter { "testng" !in it }.joinToString("\n") }
}
@Test
fun testStdlibByDefault() {
listOf( // projects and tasks:
androidProject() to listOf("compileDebugKotlin"),
jvmProject() to listOf("compileKotlin"),
jsProject() to listOf("compileKotlinJs"),
mppProject() to listOf(
"compileKotlinJvm",
"compileKotlinJs",
"compileCommonMainKotlinMetadata",
"compileJvmAndJsMainKotlinMetadata"
)
).forEach { (project, tasks) ->
with(project) {
prepare()
tasks.forEach { task ->
project.checkTaskCompileClasspath(task, listOf("kotlin-stdlib" /*any of them*/))
}
projectDir.resolve("gradle.properties").appendText(
"\nkotlin.stdlib.default.dependency=false"
)
tasks.forEach { task ->
project.checkTaskCompileClasspath(task, listOf(), checkModulesNotInClasspath = listOf("kotlin-stdlib" /*any of them*/))
}
}
}
}
@Test
fun testStdlibBasedOnJdk() = with(jvmProject()) {
prepare()
gradleBuildScript().modify { "$it\nkotlin.target.compilations[\"main\"].kotlinOptions { jvmTarget = \"1.6\" }" }
val version = defaultBuildOptions().kotlinVersion
checkTaskCompileClasspath(
"compileKotlin",
listOf("kotlin-stdlib-$version"),
listOf("kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8")
)
gradleBuildScript().modify { "$it\nkotlin.target.compilations[\"main\"].kotlinOptions { jvmTarget = \"11\" }" }
checkTaskCompileClasspath(
"compileKotlin",
listOf("kotlin-stdlib", "kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8"),
)
}
@Test
fun testOverrideStdlib() = with(jvmProject().apply { prepare() }) {
gradleBuildScript().appendText(
"\n" + """
kotlin.target.compilations["main"].kotlinOptions.jvmTarget = "1.8"
dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib") }
""".trimIndent()
)
// Check that the explicit stdlib overrides the plugin's choice of stdlib-jdk8
checkTaskCompileClasspath(
"compileKotlin",
listOf("kotlin-stdlib-${defaultBuildOptions().kotlinVersion}"),
listOf("kotlin-stdlib-jdk8")
)
}
private val kotlinTestMultiplatformDependency = "org.jetbrains.kotlin:$KOTLIN_TEST_MULTIPLATFORM_MODULE_NAME"
@Test
fun testKotlinTestSingleDependency() {
data class TestCase(
val project: Project,
val configurationsToAddDependency: List<String>,
val classpathElementsExpectedByTask: Map<String, List<String>>,
val filesExpectedByConfiguration: Map<String, List<String>> = emptyMap()
)
listOf(
TestCase(
androidProject(),
listOf("testImplementation"),
mapOf(
"compileDebugUnitTestKotlin" to listOf("kotlin-test-junit"),
"compileReleaseUnitTestKotlin" to listOf("kotlin-test-junit")
)
),
TestCase(
androidProject(),
listOf("androidTestImplementation"),
mapOf("compileDebugAndroidTestKotlin" to listOf("kotlin-test-junit"))
),
TestCase(jvmProject(), listOf("testImplementation"), mapOf("compileTestKotlin" to listOf("kotlin-test-testng"))),
TestCase(jsProject(), listOf("testImplementation"), mapOf("compileTestKotlinJs" to listOf("kotlin-test-js"))),
TestCase(
mppProject(),
listOf("commonTestImplementation"),
mapOf(
"compileTestKotlinJvm" to listOf("kotlin-test-junit"),
"compileTestKotlinJs" to listOf("kotlin-test-js")
),
mapOf(
"commonTestImplementationDependenciesMetadata" to listOf("kotlin-test-common", "kotlin-test-annotations-common"),
"commonTestApiDependenciesMetadata" to listOf("!kotlin-test-common", "!kotlin-test-annotations-common"),
)
),
TestCase(
mppProject(),
listOf("jvmAndJsTestApi", "jvmAndJsTestCompileOnly"), // add to the intermediate source set, and to two scopes
mapOf(
"compileTestKotlinJvm" to listOf("kotlin-test-junit"),
"compileTestKotlinJs" to listOf("kotlin-test-js")
),
mapOf(
"commonTestApiDependenciesMetadata" to listOf("!kotlin-test-common"),
"commonTestCompileOnlyDependenciesMetadata" to listOf("!kotlin-test-common"),
"jvmAndJsTestApiDependenciesMetadata" to listOf("kotlin-test-common"),
"jvmAndJsTestCompileOnlyDependenciesMetadata" to listOf("kotlin-test-common"),
"jvmAndJsTestImplementationDependenciesMetadata" to listOf("!kotlin-test-common"),
)
)
).forEach { testCase ->
with(testCase) {
project.prepare()
project.gradleBuildScript().appendText(
configurationsToAddDependency.joinToString("\n", "\n") { configuration ->
"\ndependencies { \"$configuration\"(\"$kotlinTestMultiplatformDependency\") }"
}
)
classpathElementsExpectedByTask.forEach { (task, expected) ->
val (notInClasspath, inClasspath) = expected.partition { it.startsWith("!") }
project.checkTaskCompileClasspath(task, inClasspath, notInClasspath.map { it.removePrefix("!") })
}
filesExpectedByConfiguration.forEach { (configuration, expected) ->
val (notInItems, inItems) = expected.partition { it.startsWith("!") }
project.checkConfigurationContent(configuration, subproject = null, inItems, notInItems.map { it.removePrefix("!") })
}
}
}
}
@Test
fun testFrameworkSelection() {
data class TestCase(
val project: Project,
val testTaskName: String,
val compileTaskName: String,
val configurationToAddDependency: String
)
val frameworks = listOf("useJUnit()" to "junit", "useTestNG()" to "testng", "useJUnitPlatform()" to "junit5")
listOf(
TestCase(jvmProject(), "test", "compileTestKotlin", "testImplementation"),
TestCase(mppProject(), "jvmTest", "compileTestKotlinJvm", "commonTestImplementation"),
TestCase(mppProject(), "jvmTest", "compileTestKotlinJvm", "jvmAndJsTestImplementation")
).forEach { (project, testTaskName, compileTaskName, configuration) ->
project.prepare()
project.gradleBuildScript().appendText("""${'\n'}dependencies { "$configuration"("$kotlinTestMultiplatformDependency") }""")
frameworks.forEach { (setup, frameworkName) ->
with(project) {
gradleBuildScript().appendText("\n(tasks.getByName(\"$testTaskName\") as Test).$setup")
val expectedModule = "kotlin-test-$frameworkName-"
checkTaskCompileClasspath(
compileTaskName,
listOf(expectedModule),
frameworks.map { "kotlin-test-" + it.second + "-" } - expectedModule
)
}
}
}
}
@Test
fun testRemoveKotlinTestDependency() = with(jvmProject().apply { prepare() }) {
gradleBuildScript().appendText(
"\n" + """
dependencies { testImplementation("$kotlinTestMultiplatformDependency") }
configurations.getByName("testImplementation").dependencies.removeAll { it.name == "$KOTLIN_TEST_MULTIPLATFORM_MODULE_NAME" }
""".trimIndent()
)
checkTaskCompileClasspath("compileTestKotlin", checkModulesNotInClasspath = listOf("kotlin-test"))
// Add it back after removal:
gradleBuildScript().appendText(
"\n" + """
dependencies { testImplementation("$kotlinTestMultiplatformDependency") }
"""
)
checkTaskCompileClasspath("compileTestKotlin", checkModulesInClasspath = listOf("kotlin-test"))
}
@Test
fun testCoreLibraryVersionsDsl() = with(jvmProject().apply { prepare() }) {
val customVersion = "1.3.70"
gradleBuildScript().appendText(
"\n" + """
kotlin.coreLibrariesVersion = "$customVersion"
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.jetbrains.kotlin:kotlin-test-multiplatform")
}
test.useJUnit()
""".trimIndent()
)
checkTaskCompileClasspath(
"compileTestKotlin",
listOf("kotlin-stdlib-", "kotlin-reflect-", "kotlin-test-junit-").map { it + customVersion }
)
}
private fun Project.checkConfigurationContent(
configurationName: String,
subproject: String? = null,
checkModulesInResolutionResult: List<String> = emptyList(),
checkModulesNotInResolutionResult: List<String> = emptyList()
) {
val expression = """configurations["$configurationName"].toList()"""
checkPrintedItems(subproject, expression, checkModulesInResolutionResult, checkModulesNotInResolutionResult)
}
private fun Project.checkTaskCompileClasspath(
taskPath: String,
checkModulesInClasspath: List<String> = emptyList(),
checkModulesNotInClasspath: List<String> = emptyList()
) {
val subproject = taskPath.substringBeforeLast(":").takeIf { it.isNotEmpty() && it != taskPath }
val taskName = taskPath.removePrefix(subproject.orEmpty())
val expression = """(tasks.getByName("$taskName") as AbstractCompile).classpath.toList()"""
checkPrintedItems(subproject, expression, checkModulesInClasspath, checkModulesNotInClasspath)
}
private fun Project.checkPrintedItems(
subproject: String?,
itemsExpression: String,
checkAnyItemsContains: List<String>,
checkNoItemContains: List<String>
) {
setupWorkingDir()
val printingTaskName = "printItems${testBuildRunId++}"
gradleBuildScript(subproject).appendText(
"""
${'\n'}
tasks.create("$printingTaskName") {
doLast {
println("###$printingTaskName" + $itemsExpression)
}
}
""".trimIndent()
)
build("${subproject?.prependIndent(":").orEmpty()}:$printingTaskName") {
assertSuccessful()
val itemsLine = output.lines().single { "###$printingTaskName" in it }.substringAfter(printingTaskName)
val items = itemsLine.removeSurrounding("[", "]").split(", ").toSet()
checkAnyItemsContains.forEach { pattern -> assertTrue { items.any { pattern in it } } }
checkNoItemContains.forEach { pattern -> assertFalse { items.any { pattern in it } } }
}
}
}
@@ -2107,13 +2107,13 @@ class NewMultiplatformIT : BaseGradleIT() {
fun testDependencies() = testResolveAllConfigurations("app") {
assertContains(">> :app:testNonTransitiveStringNotationApiDependenciesMetadata --> junit-4.12.jar")
assertEquals(
1,
2, // the dependency above and stdlib
(Regex.escape(">> :app:testNonTransitiveStringNotationApiDependenciesMetadata") + " .*").toRegex().findAll(output).count()
)
assertContains(">> :app:testNonTransitiveDependencyNotationApiDependenciesMetadata --> kotlin-reflect-${defaultBuildOptions().kotlinVersion}.jar")
assertEquals(
1,
2, // the dependency above and stdlib
(Regex.escape(">> :app:testNonTransitiveStringNotationApiDependenciesMetadata") + " .*").toRegex().findAll(output).count()
)
@@ -0,0 +1,67 @@
plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>")
`maven-publish`
}
repositories {
mavenLocal()
jcenter()
}
group = "com.example.thirdparty"
version = "1.0"
kotlin {
jvm()
js()
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val jvmAndJsMain by creating {
dependsOn(commonMain)
}
val jvmAndJsTest by creating {
dependsOn(commonTest)
}
jvm().compilations["main"].defaultSourceSet {
dependsOn(jvmAndJsMain)
dependencies {
implementation(kotlin("stdlib"))
}
}
jvm().compilations["test"].defaultSourceSet {
dependsOn(jvmAndJsTest)
dependencies {
implementation(kotlin("test-junit"))
}
}
js().compilations["main"].defaultSourceSet {
dependsOn(jvmAndJsMain)
dependencies {
implementation(kotlin("stdlib-js"))
}
}
js().compilations["test"].defaultSourceSet {
dependsOn(jvmAndJsTest)
dependencies {
implementation(kotlin("test-js"))
}
}
}
}
publishing {
repositories {
maven("../repo")
}
}
@@ -0,0 +1 @@
kotlin.mpp.enableGranularSourceSetsMetadata=true
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
rootProject.name = "third-party-lib"
enableFeaturePreview("GRADLE_METADATA")
@@ -0,0 +1,5 @@
package com.example.thirdparty
expect fun thirdPartyFun(): String
private fun useStdlibInCommonMain() = listOf(1, 2, 3).joinToString()
@@ -0,0 +1,5 @@
package com.example.thirdparty
actual fun thirdPartyFun(): String = "thirdPartyFun @ js"
fun thirdPartyJsFun() { }
@@ -0,0 +1,3 @@
package com.example.thirdparty
private fun useStdlibInJvmAndJsMain() = listOf(1, 2, 3).joinToString()
@@ -0,0 +1,5 @@
package com.example.thirdparty
actual fun thirdPartyFun(): String = "thirdPartyFun @ jvm"
fun thirdPartyJvmFun() { }
@@ -47,6 +47,8 @@ open class KotlinProjectExtension : KotlinSourceSetContainer {
val experimental: ExperimentalExtension
get() = DslObject(this).extensions.getByType(ExperimentalExtension::class.java)
lateinit var coreLibrariesVersion: String
var explicitApi: ExplicitApiMode? = null
fun explicitApi() {
@@ -0,0 +1,246 @@
/*
* 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.
*/
package org.jetbrains.kotlin.gradle.internal
import com.android.build.gradle.api.TestVariant
import com.android.build.gradle.api.UnitTestVariant
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ExternalDependency
import org.gradle.api.tasks.testing.AbstractTestTask
import org.gradle.api.tasks.testing.Test
import org.gradle.api.tasks.testing.junit.JUnitOptions
import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions
import org.gradle.api.tasks.testing.testng.TestNGOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.execution.KotlinAggregateExecutionSource
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.CompilationSourceSetUtil
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.targets.jvm.JvmCompilationsTestRunSource
import org.jetbrains.kotlin.gradle.tasks.KOTLIN_MODULE_GROUP
import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.testing.KotlinTaskTestRun
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
internal fun customizeKotlinDependencies(project: Project) {
configureStdlibDefaultDependency(project)
configureKotlinTestDependencies(project)
configureDefaultVersionsResolutionStrategy(project)
}
private fun configureDefaultVersionsResolutionStrategy(project: Project) {
project.configurations.all { configuration ->
// Use the API introduced in Gradle 4.4 to modify the dependencies directly before they are resolved:
configuration.withDependencies { dependencySet ->
val coreLibrariesVersion = project.kotlinExtension.coreLibrariesVersion
dependencySet.filterIsInstance<ExternalDependency>()
.filter { it.group == KOTLIN_MODULE_GROUP && it.version.isNullOrEmpty() }
.forEach { it.version { constraint -> constraint.require(coreLibrariesVersion) } }
}
}
}
//region stdlib
internal fun configureStdlibDefaultDependency(project: Project) {
if (!PropertiesProvider(project).stdlibDefaultDependency)
return
project.kotlinExtension.sourceSets.all { kotlinSourceSet ->
val apiConfiguration = project.sourceSetDependencyConfigurationByScope(kotlinSourceSet, KotlinDependencyScope.API_SCOPE)
apiConfiguration.withDependencies { dependencies ->
val sourceSetDependencyConfigurations =
KotlinDependencyScope.values().map { project.sourceSetDependencyConfigurationByScope(kotlinSourceSet, it) }
val hierarchySourceSetsDependencyConfigurations = kotlinSourceSet.getSourceSetHierarchy().flatMap { hierarchySourceSet ->
if (hierarchySourceSet === kotlinSourceSet) emptyList() else
KotlinDependencyScope.values().map { scope ->
project.sourceSetDependencyConfigurationByScope(hierarchySourceSet, scope)
}
}
val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(kotlinSourceSet)
.filter { it.target !is KotlinMetadataTarget }
val platformTypes = compilations.mapTo(mutableSetOf()) { it.platformType }
val sourceSetStdlibPlatformType = when {
platformTypes.isEmpty() -> KotlinPlatformType.common
setOf(KotlinPlatformType.jvm, KotlinPlatformType.androidJvm).containsAll(platformTypes) -> KotlinPlatformType.jvm
platformTypes.size == 1 -> platformTypes.single()
else -> KotlinPlatformType.common
}
val isStdlibAddedByUser = sourceSetDependencyConfigurations
.flatMap { it.allDependencies }
.any { dependency -> dependency.group == KOTLIN_MODULE_GROUP && dependency.name in stdlibModules }
if (!isStdlibAddedByUser) {
val stdlibModuleName = when (sourceSetStdlibPlatformType) {
KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> jvmStdlibModuleForJvmCompilations(compilations)
KotlinPlatformType.js -> "kotlin-stdlib-js"
KotlinPlatformType.native -> null
KotlinPlatformType.common -> // there's no platform compilation that the source set is default for
"kotlin-stdlib-common"
}
// If the exact same module is added in the source sets hierarchy, possibly even with a different scope, we don't add it
val moduleAddedInHierarchy = hierarchySourceSetsDependencyConfigurations.any {
it.allDependencies.any { dependency -> dependency.group == KOTLIN_MODULE_GROUP && dependency.name == stdlibModuleName }
}
if (stdlibModuleName != null && !moduleAddedInHierarchy)
dependencies.add(project.kotlinDependency(stdlibModuleName, project.kotlinExtension.coreLibrariesVersion))
}
}
}
}
private val stdlibModules = setOf("kotlin-stdlib-common", "kotlin-stdlib", "kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8", "kotlin-stdlib-js")
private fun jvmStdlibModuleForJvmCompilations(compilations: Iterable<KotlinCompilation<*>>): String {
val jvmTargets = compilations.map { (it.kotlinOptions as KotlinJvmOptions).jvmTarget }
return if ("1.6" in jvmTargets) "kotlin-stdlib" else "kotlin-stdlib-jdk8"
}
//endregion
//region kotlin-test
internal fun configureKotlinTestDependencies(project: Project) {
fun isKotlinTestMultiplatformDependency(dependency: Dependency) =
dependency.group == KOTLIN_MODULE_GROUP && dependency.name == KOTLIN_TEST_MULTIPLATFORM_MODULE_NAME
KotlinDependencyScope.values().forEach { scope ->
val versionOrNullBySourceSet = mutableMapOf<KotlinSourceSet, String?>()
project.kotlinExtension.sourceSets.all { kotlinSourceSet ->
val configuration = project.sourceSetDependencyConfigurationByScope(kotlinSourceSet, scope)
var finalizingDependencies = false
configuration.dependencies.matching(::isKotlinTestMultiplatformDependency).apply {
firstOrNull()?.let { versionOrNullBySourceSet[kotlinSourceSet] = it.version }
whenObjectRemoved {
if (!finalizingDependencies && !any())
versionOrNullBySourceSet.remove(kotlinSourceSet)
}
whenObjectAdded { item ->
versionOrNullBySourceSet[kotlinSourceSet] = item.version
}
}
configuration.withDependencies { dependencies ->
val parentOrOwnVersions: List<String?> =
kotlinSourceSet.getSourceSetHierarchy().filter(versionOrNullBySourceSet::contains).map(versionOrNullBySourceSet::get)
finalizingDependencies = true
parentOrOwnVersions.distinct().forEach { version -> // add dependencies with each version and let Gradle disambiguate them
val dependenciesToAdd = kotlinTestDependenciesForSourceSet(project, kotlinSourceSet, version)
dependenciesToAdd.filterIsInstance<ExternalDependency>().forEach {
if (it.version == null) {
it.version { constraint -> constraint.require(project.kotlinExtension.coreLibrariesVersion) }
}
}
dependencies.addAll(dependenciesToAdd)
dependencies.removeIf(::isKotlinTestMultiplatformDependency)
}
}
}
}
}
private fun kotlinTestDependenciesForSourceSet(project: Project, kotlinSourceSet: KotlinSourceSet, version: String?): List<Dependency> {
val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(kotlinSourceSet)
.filter { it.target !is KotlinMetadataTarget }
val platformTypes = compilations.mapTo(mutableSetOf()) { it.platformType }
return when {
platformTypes.isEmpty() -> emptyList()
setOf(KotlinPlatformType.jvm, KotlinPlatformType.androidJvm).containsAll(platformTypes) -> listOf(
kotlinTestDependenciesForJvm(project, compilations, version)
)
platformTypes.singleOrNull() == KotlinPlatformType.js -> listOf(
project.kotlinDependency("kotlin-test-js", version)
)
platformTypes.singleOrNull() == KotlinPlatformType.native -> emptyList()
else -> listOf(
project.kotlinDependency("kotlin-test-common", version),
project.kotlinDependency("kotlin-test-annotations-common", version)
)
}
}
internal const val KOTLIN_TEST_MULTIPLATFORM_MODULE_NAME = "kotlin-test-multiplatform"
private fun Project.kotlinDependency(moduleName: String, versionOrNull: String?) =
project.dependencies.create("$KOTLIN_MODULE_GROUP:$moduleName${versionOrNull?.prependIndent(":").orEmpty()}")
private fun kotlinTestDependenciesForJvm(project: Project, compilations: Iterable<KotlinCompilation<*>>, version: String?): Dependency {
val testTaskLists: List<List<Task>?> = compilations.map { compilation ->
val target = compilation.target
when {
target is KotlinTargetWithTests<*, *> ->
target.findTestRunsByCompilation(compilation)?.filterIsInstance<KotlinTaskTestRun<*, *>>()?.map { it.executionTask.get() }
target is KotlinWithJavaTarget<*> ->
if (compilation.name == KotlinCompilation.TEST_COMPILATION_NAME)
project.locateTask<AbstractTestTask>(target.testTaskName)?.get()?.let(::listOf)
else null
compilation is KotlinJvmAndroidCompilation -> when (compilation.androidVariant) {
is UnitTestVariant ->
project.locateTask<AbstractTestTask>(lowerCamelCaseName("test", compilation.androidVariant.name))?.get()?.let(::listOf)
is TestVariant -> (compilation.androidVariant as TestVariant).connectedInstrumentTest?.let(::listOf)
else -> null
}
else -> null
}
}
if (null in testTaskLists) {
return project.kotlinDependency("kotlin-test", version)
}
val testTasks = testTaskLists.flatMap { checkNotNull(it) }
val frameworks = testTasks.mapTo(mutableSetOf()) { testTask ->
when (testTask) {
is Test -> testFrameworkOf(testTask)
else -> // Android connected test tasks don't inherit from Test, but we use JUnit for them
KotlinTestJvmFramework.junit
}
}
return when {
frameworks.size > 1 -> project.kotlinDependency("kotlin-test", version)
else -> project.kotlinDependency("kotlin-test-${frameworks.single().name}", version)
}
}
private enum class KotlinTestJvmFramework {
junit, testng, junit5
}
private fun testFrameworkOf(testTask: Test): KotlinTestJvmFramework = when (testTask.options) {
is JUnitOptions -> KotlinTestJvmFramework.junit
is JUnitPlatformOptions -> KotlinTestJvmFramework.junit5
is TestNGOptions -> KotlinTestJvmFramework.testng
else -> // failed to detect, fallback to junit
KotlinTestJvmFramework.junit
}
private fun KotlinTargetWithTests<*, *>.findTestRunsByCompilation(byCompilation: KotlinCompilation<*>): List<KotlinTargetTestRun<*>>? {
fun KotlinExecution.ExecutionSource.isProducedFromTheCompilation(): Boolean = when (this) {
is CompilationExecutionSource<*> -> compilation == byCompilation
is JvmCompilationsTestRunSource -> byCompilation in testCompilations
is KotlinAggregateExecutionSource<*> -> this.executionSources.any { it.isProducedFromTheCompilation() }
else -> false
}
return testRuns.filter { it.executionSource.isProducedFromTheCompilation() }.takeIf { it.isNotEmpty() }
}
//endregion
@@ -6,11 +6,8 @@
package org.jetbrains.kotlin.gradle.plugin
import com.android.build.gradle.*
import com.android.build.gradle.api.AndroidSourceSet
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.api.SourceKind
import com.android.build.gradle.api.*
import org.gradle.api.*
import org.gradle.api.artifacts.ExternalDependency
import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
import org.gradle.api.artifacts.maven.MavenResolver
import org.gradle.api.attributes.Usage
@@ -32,8 +29,9 @@ import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.jvm.tasks.Jar
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.internal.*
import org.jetbrains.kotlin.gradle.internal.checkAndroidAnnotationProcessorDependencyUsage
import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.model.builder.KotlinModelBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.*
@@ -412,7 +410,7 @@ internal abstract class AbstractKotlinPlugin(
rewriteMppDependenciesInPom(target)
configureProjectGlobalSettings(project, kotlinPluginVersion)
configureProjectGlobalSettings(project)
registry.register(KotlinModelBuilder(kotlinPluginVersion, null))
project.components.addAll(target.components)
@@ -452,8 +450,8 @@ internal abstract class AbstractKotlinPlugin(
}
companion object {
fun configureProjectGlobalSettings(project: Project, kotlinPluginVersion: String) {
configureDefaultVersionsResolutionStrategy(project, kotlinPluginVersion)
fun configureProjectGlobalSettings(project: Project) {
customizeKotlinDependencies(project)
configureClassInspectionForIC(project)
project.setupGeneralKotlinExtensionParameters()
}
@@ -590,17 +588,6 @@ internal abstract class AbstractKotlinPlugin(
}
}
internal fun configureDefaultVersionsResolutionStrategy(project: Project, kotlinPluginVersion: String) {
project.configurations.all { configuration ->
// Use the API introduced in Gradle 4.4 to modify the dependencies directly before they are resolved:
configuration.withDependencies { dependencySet ->
dependencySet.filterIsInstance<ExternalDependency>()
.filter { it.group == "org.jetbrains.kotlin" && it.version.isNullOrEmpty() }
.forEach { it.version { constraint -> constraint.require(kotlinPluginVersion) } }
}
}
}
internal open class KotlinPlugin(
kotlinPluginVersion: String,
registry: ToolingModelBuilderRegistry
@@ -703,7 +690,7 @@ internal open class KotlinAndroidPlugin(
applyUserDefinedAttributes(androidTarget)
configureDefaultVersionsResolutionStrategy(project, kotlinPluginVersion)
customizeKotlinDependencies(project)
registry.register(KotlinModelBuilder(kotlinPluginVersion, androidTarget))
@@ -78,6 +78,8 @@ abstract class KotlinBasePluginWrapper(
kotlinGradleBuildServices.detectKotlinPluginLoadedInMultipleProjects(project, kotlinPluginVersion)
project.createKotlinExtension(projectExtensionClass).apply {
coreLibrariesVersion = kotlinPluginVersion
fun kotlinSourceSetContainer(factory: NamedDomainObjectFactory<KotlinSourceSet>) =
project.container(KotlinSourceSet::class.java, factory)
@@ -202,6 +202,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
val jsCompiler: KotlinJsCompilerType
get() = property(jsCompilerProperty)?.let { KotlinJsCompilerType.byArgumentOrNull(it) } ?: KotlinJsCompilerType.LEGACY
val stdlibDefaultDependency: Boolean
get() = booleanProperty("kotlin.stdlib.default.dependency") ?: true
private fun propertyWithDeprecatedVariant(propName: String, deprecatedPropName: String): String? {
val deprecatedProperty = property(deprecatedPropName)
if (deprecatedProperty != null) {
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.configureOrCreate
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
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.sources.DefaultLanguageSettingsBuilder
@@ -84,7 +85,7 @@ class KotlinMultiplatformPlugin(
}
setupDefaultPresets(project)
configureDefaultVersionsResolutionStrategy(project, kotlinPluginVersion)
customizeKotlinDependencies(project)
configureSourceSets(project)
// set up metadata publishing
@@ -21,6 +21,7 @@ import org.gradle.api.component.SoftwareComponentFactory
import org.gradle.api.internal.component.SoftwareComponentInternal
import org.gradle.api.internal.component.UsageContext
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.provider.Provider
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.util.ConfigureUtil
import org.gradle.util.WrapUtil
@@ -98,7 +99,10 @@ abstract class AbstractKotlinTarget(
?: project.configurations.create(kotlinUsageContext.name).also { configuration ->
configuration.isCanBeConsumed = false
configuration.isCanBeResolved = false
configuration.dependencies.addAll(kotlinUsageContext.dependencies)
/** Add dependencies lazily to avoid freezing the content of the configuration held by [kotlinUsageContext] */
configuration.withDependencies { dependencies ->
dependencies.addAll(kotlinUsageContext.dependencies)
}
configuration.dependencyConstraints.addAll(kotlinUsageContext.dependencyConstraints)
configuration.artifacts.addAll(kotlinUsageContext.artifacts)
@@ -10,10 +10,10 @@ import org.gradle.api.Project
import org.gradle.api.plugins.JavaBasePlugin
import org.jetbrains.kotlin.gradle.dsl.KotlinJsProjectExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.configureDefaultVersionsResolutionStrategy
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsSingleTargetPreset
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrSingleTargetPreset
@@ -36,7 +36,7 @@ open class KotlinJsPlugin(
checkGradleCompatibility()
val kotlinExtension = project.kotlinExtension as KotlinJsProjectExtension
configureDefaultVersionsResolutionStrategy(project, kotlinPluginVersion)
customizeKotlinDependencies(project)
kotlinExtension.apply {
irPreset = KotlinJsIrSingleTargetPreset(project, kotlinPluginVersion)
@@ -64,6 +64,7 @@ open class KotlinJvmTestRun(testRunName: String, override val target: KotlinJvmT
get() = _executionSource
private set(value) {
setTestTaskClasspathAndClassesDirs(value.classpath, value.testClassesDirs)
_executionSource = value
}
private fun setTestTaskClasspathAndClassesDirs(classpath: FileCollection, testClassesDirs: FileCollection) {
@@ -26,3 +26,5 @@ const val NO_NATIVE_STDLIB_WARNING = NO_NATIVE_STDLIB_WARNING
const val NO_NATIVE_STDLIB_PROPERTY_WARNING = NO_NATIVE_STDLIB_PROPERTY_WARNING
val KOTLIN_12X_MPP_DEPRECATION_WARNING = KOTLIN_12X_MPP_DEPRECATION_WARNING
const val KOTLIN_TEST_MULTIPLATFORM_MODULE_NAME = org.jetbrains.kotlin.gradle.internal.KOTLIN_TEST_MULTIPLATFORM_MODULE_NAME