[Gradle] Migrate VariantAwareDependenciesMppIT to new test DSL

^KT-65528 In Progress
This commit is contained in:
Yahor Berdnikau
2024-02-14 12:34:59 +01:00
committed by Space Team
parent ab814e1600
commit 05359ba9e5
3 changed files with 236 additions and 196 deletions
@@ -28,7 +28,6 @@ import org.junit.runner.RunWith
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.DocumentBuilderFactory
@@ -36,7 +35,7 @@ import kotlin.io.path.absolutePathString
import kotlin.io.path.isDirectory import kotlin.io.path.isDirectory
import kotlin.test.* import kotlin.test.*
val SYSTEM_LINE_SEPARATOR: String = System.getProperty("line.separator") val SYSTEM_LINE_SEPARATOR: String = System.lineSeparator()
@RunWith(value = RunnerWithMuteInDatabase::class) @RunWith(value = RunnerWithMuteInDatabase::class)
abstract class BaseGradleIT { abstract class BaseGradleIT {
@@ -1153,3 +1152,24 @@ private object MavenLocalUrlProvider {
private val defaultM2RepoPath get() = File(homeDir, ".m2/repository").absolutePath private val defaultM2RepoPath get() = File(homeDir, ".m2/repository").absolutePath
} }
internal fun BaseGradleIT.Project.embedProject(other: BaseGradleIT.Project, renameTo: String? = null) {
setupWorkingDir()
other.setupWorkingDir(false)
val tempDir = createTempDir(if (isWindows) "" else "BaseGradleIT")
val embeddedModuleName = renameTo ?: other.projectName
try {
other.projectDir.copyRecursively(tempDir)
tempDir.copyRecursively(projectDir.resolve(embeddedModuleName))
} finally {
check(tempDir.deleteRecursively())
}
testCase.apply {
gradleSettingsScript().appendText("\ninclude(\"$embeddedModuleName\")")
val embeddedBuildScript = gradleBuildScript(embeddedModuleName)
if (embeddedBuildScript.extension == "kts") {
embeddedBuildScript.modify { it.replace(".version(\"$PLUGIN_MARKER_VERSION_PLACEHOLDER\")", "") }
}
}
}
@@ -6,274 +6,293 @@
package org.jetbrains.kotlin.gradle package org.jetbrains.kotlin.gradle
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.* import org.jetbrains.kotlin.gradle.util.*
import org.junit.Test import org.junit.jupiter.api.DisplayName
import kotlin.io.path.appendText
import kotlin.test.assertTrue import kotlin.test.assertTrue
class VariantAwareDependenciesMppIT : BaseGradleIT() { @DisplayName("Multiplatform variant aware dependency resolution")
private val gradleVersion = GradleVersionRequired.FOR_MPP_SUPPORT @MppGradlePluginTests
class VariantAwareDependenciesMppIT : KGPBaseTest() {
@Test override val defaultBuildOptions: BuildOptions = super.defaultBuildOptions.copy(
fun testJvmKtAppResolvesMppLib() { nativeOptions = super.defaultBuildOptions.nativeOptions.copy(
val outerProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app") // Use kotlin-native bundle version provided by default in KGP, because it will be pushed in one of the known IT repos for sure
val innerProject = Project("simpleProject") version = null
)
)
with(outerProject) { @DisplayName("JVM project could depend on multiplatform project")
embedProject(innerProject) @GradleTest
gradleBuildScript(innerProject.projectName).appendText("\ndependencies { implementation rootProject }") fun testJvmKtAppResolvesMppLib(gradleVersion: GradleVersion) {
testResolveAllConfigurations(innerProject.projectName) { project("new-mpp-lib-and-app/sample-lib", gradleVersion) {
assertContains(">> :${innerProject.projectName}:runtimeClasspath --> sample-lib-jvm6-1.0.jar") includeOtherProjectAsSubmodule("simpleProject")
subProject("simpleProject")
.buildGradle
.appendText(
"""
|
|dependencies { implementation project(":") }
|
""".trimMargin()
)
testResolveAllConfigurations("simpleProject") { _, buildResult ->
buildResult.assertOutputContains(">> :simpleProject:runtimeClasspath --> sample-lib-jvm6-1.0.jar")
} }
} }
} }
@Test @DisplayName("JS project could depend on multiplatform project")
fun testJsKtAppResolvesMppLib() { @GradleTest
val outerProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app") fun testJsKtAppResolvesMppLib(gradleVersion: GradleVersion) {
val innerProject = Project("kotlin2JsInternalTest") project("new-mpp-lib-and-app/sample-lib", gradleVersion) {
includeOtherProjectAsSubmodule("kotlin2JsInternalTest")
with(outerProject) { subProject("kotlin2JsInternalTest")
embedProject(innerProject) .buildGradle
gradleBuildScript(innerProject.projectName).appendText("\ndependencies { implementation rootProject }") .appendText(
"""
|
|dependencies { implementation rootProject }
|
""".trimMargin()
)
testResolveAllConfigurations(subproject = innerProject.projectName) { testResolveAllConfigurations("kotlin2JsInternalTest") { _, buildResult ->
assertContains(">> :${innerProject.projectName}:runtimeClasspath --> sample-lib-nodejs-1.0.klib") buildResult.assertOutputContains(">> :kotlin2JsInternalTest:runtimeClasspath --> sample-lib-nodejs-1.0.klib")
} }
} }
} }
@Test @DisplayName("Multiplatform project could depend on JVM project")
fun testMppLibResolvesJvmKtApp() { @GradleTest
val outerProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app") fun testMppLibResolvesJvmKtApp(gradleVersion: GradleVersion) {
val innerProject = Project("simpleProject") project("new-mpp-lib-and-app/sample-lib", gradleVersion) {
includeOtherProjectAsSubmodule("simpleProject")
buildGradle.appendText(
"""
|
|dependencies { jvm6MainImplementation project(':simpleProject') }
|
""".trimMargin()
)
with(outerProject) { testResolveAllConfigurations()
embedProject(innerProject)
gradleBuildScript().appendText("\ndependencies { jvm6MainImplementation project(':${innerProject.projectName}') }")
testResolveAllConfigurations(innerProject.projectName)
} }
} }
@Test @DisplayName("Multiplatform project could depend on JS project")
fun testMppLibResolvesJsKtApp() { @GradleTest
val outerProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app") fun testMppLibResolvesJsKtApp(gradleVersion: GradleVersion) {
val innerProject = Project("kotlin2JsInternalTest") project("new-mpp-lib-and-app/sample-lib", gradleVersion) {
includeOtherProjectAsSubmodule("kotlin2JsInternalTest")
with(outerProject) { buildGradle.appendText(
embedProject(innerProject) """
gradleBuildScript().appendText("\ndependencies { nodeJsMainImplementation project(':${innerProject.projectName}') }") |
|dependencies { nodeJsMainImplementation project(':kotlin2JsInternalTest') }
|
""".trimMargin()
)
testResolveAllConfigurations(innerProject.projectName) testResolveAllConfigurations()
} }
} }
@Test @DisplayName("Gradle JVM project could depend on multiplatform project")
fun testNonKotlinJvmAppResolvesMppLib() { @GradleTest
val outerProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app") fun testNonKotlinJvmAppResolvesMppLib(gradleVersion: GradleVersion) {
val innerProject = Project("simpleProject").apply { project("new-mpp-lib-and-app/sample-lib", gradleVersion) {
setupWorkingDir(false) includeOtherProjectAsSubmodule("simpleProject")
gradleBuildScript().modify { subProject("simpleProject").buildGradle.modify {
it.checkedReplace("id \"org.jetbrains.kotlin.jvm\"", "")
}
gradleBuildScript().appendText(
// In Gradle 5.3+, the variants of a Kotlin MPP can't be disambiguated in a pure Java project's deprecated // In Gradle 5.3+, the variants of a Kotlin MPP can't be disambiguated in a pure Java project's deprecated
// configurations that don't have a proper 'org.gradle.usage' attribute value, see KT-30378 // configurations that don't have a proper 'org.gradle.usage' attribute value, see KT-30378
"\n" + """ it.checkedReplace("id \"org.jetbrains.kotlin.jvm\"", "") +
configurations { """
configure([compile, runtime, testCompile, testRuntime, getByName('default')]) { |
canBeResolved = false |configurations {
} | configure([compile, runtime, deployCompile, deployCompileOnly, deployRuntime,
} | testCompile, testRuntime, getByName('default')]) {
""".trimIndent() | canBeResolved = false
) | }
} |}
|
|dependencies { implementation rootProject }
|
""".trimMargin()
}
with(outerProject) { testResolveAllConfigurations("simpleProject")
embedProject(innerProject)
gradleBuildScript(innerProject.projectName).appendText("\ndependencies { implementation rootProject }")
testResolveAllConfigurations(innerProject.projectName)
} }
} }
@Test @DisplayName("Kotlin JVM project could depend on another Kotlin JVM project")
fun testJvmKtAppResolvesJvmKtApp() { @GradleTest
val outerProject = Project("simpleProject", gradleVersion) fun testJvmKtAppResolvesJvmKtApp(gradleVersion: GradleVersion) {
val innerProject = Project("jvmTarget") // cannot use simpleApp again, should be another project project("simpleProject", gradleVersion) {
includeOtherProjectAsSubmodule("jvmTarget")
subProject("jvmTarget").buildGradle.modify {
it.replace("kotlinOptions.jvmTarget = \"1.7\"", "kotlinOptions.jvmTarget = \"11\"") +
"""
|
|dependencies { implementation rootProject }
|
""".trimMargin()
}
with(outerProject) { testResolveAllConfigurations("jvmTarget")
embedProject(innerProject) }
gradleBuildScript(innerProject.projectName).modify { }
@DisplayName("Multiplatform project could depend on Kotlin JVM and JS projects")
@GradleTest
fun testMppResolvesJvmAndJsKtLibs(gradleVersion: GradleVersion) {
project("new-mpp-lib-and-app/sample-lib", gradleVersion) {
includeOtherProjectAsSubmodule("simpleProject")
includeOtherProjectAsSubmodule("kotlin2JsInternalTest")
buildGradle.appendText(
""" """
${it.replace("kotlinOptions.jvmTarget = \"1.7\"", "kotlinOptions.jvmTarget = \"11\"")} |
|dependencies {
dependencies { implementation rootProject } | def jvmCompilations = kotlin.getTargets().getByName("jvm6").getCompilations()
""".trimIndent() | def jsCompilations = kotlin.getTargets().getByName("nodeJs").getCompilations()
} |
| def jvmMainImplConfigName = jvmCompilations.getByName("main").getImplementationConfigurationName()
| def jvmTestImplConfigName = jvmCompilations.getByName("test").getImplementationConfigurationName()
| def jsMainImplConfigName = jsCompilations.getByName("main").getImplementationConfigurationName()
| def jsTestImplConfigName = jsCompilations.getByName("test").getImplementationConfigurationName()
|
| add(jvmMainImplConfigName, project(':simpleProject'))
| add(jvmTestImplConfigName, project(':simpleProject'))
| add(jsMainImplConfigName, project(':kotlin2JsInternalTest'))
| add(jsTestImplConfigName, project(':kotlin2JsInternalTest'))
|}
""".trimMargin()
)
testResolveAllConfigurations(innerProject.projectName) testResolveAllConfigurations()
} }
} }
@Test @DisplayName("Kotlin JVM project could depend on multiplatform project in testRuntime configuration")
fun testMppResolvesJvmAndJsKtLibs() { @GradleTest
val outerProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app") fun testJvmKtAppDependsOnMppTestRuntime(gradleVersion: GradleVersion) {
val innerJvmProject = Project("simpleProject") project("new-mpp-lib-and-app/sample-lib", gradleVersion) {
val innerJsProject = Project("kotlin2JsInternalTest") includeOtherProjectAsSubmodule("simpleProject")
with(outerProject) { subProject("simpleProject").buildGradle.appendText(
embedProject(innerJvmProject) """
embedProject(innerJsProject) |
|dependencies { testImplementation project(path: ':', configuration: 'jvm6RuntimeElements') }
gradleBuildScript().appendText( |
"\n" + """ """.trimMargin()
dependencies {
def jvmCompilations = kotlin.getTargets().getByName("jvm6").getCompilations()
def jsCompilations = kotlin.getTargets().getByName("nodeJs").getCompilations()
def jvmMainImplConfigName = jvmCompilations.getByName("main").getImplementationConfigurationName()
def jvmTestImplConfigName = jvmCompilations.getByName("test").getImplementationConfigurationName()
def jsMainImplConfigName = jsCompilations.getByName("main").getImplementationConfigurationName()
def jsTestImplConfigName = jsCompilations.getByName("test").getImplementationConfigurationName()
add(jvmMainImplConfigName, project(':${innerJvmProject.projectName}'))
add(jvmTestImplConfigName, project(':${innerJvmProject.projectName}'))
add(jsMainImplConfigName, project(':${innerJsProject.projectName}'))
add(jsTestImplConfigName, project(':${innerJsProject.projectName}'))
}
""".trimIndent()
) )
testResolveAllConfigurations(innerJvmProject.projectName) testResolveAllConfigurations("simpleProject") { _, buildResult ->
} buildResult.assertOutputContains(">> :simpleProject:testCompileClasspath --> sample-lib-jvm6-1.0.jar")
} buildResult.assertOutputContains(">> :simpleProject:testRuntimeClasspath --> sample-lib-jvm6-1.0.jar")
@Test
fun testJvmKtAppDependsOnMppTestRuntime() {
val outerProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")
val innerProject = Project("simpleProject")
with(outerProject) {
embedProject(innerProject)
gradleBuildScript(innerProject.projectName).appendText(
"\ndependencies { testImplementation project(path: ':', configuration: 'jvm6RuntimeElements') }"
)
testResolveAllConfigurations(innerProject.projectName) {
assertContains(">> :${innerProject.projectName}:testCompileClasspath --> sample-lib-jvm6-1.0.jar")
assertContains(">> :${innerProject.projectName}:testRuntimeClasspath --> sample-lib-jvm6-1.0.jar")
} }
} }
} }
@Test @DisplayName("Multiplatform project with Java plugin applied could be resolved in all configurations")
fun testJvmWithJavaProjectCanBeResolvedInAllConfigurations() = @GradleTest
with(Project("new-mpp-jvm-with-java-multi-module", GradleVersionRequired.FOR_MPP_SUPPORT)) { fun testJvmWithJavaProjectCanBeResolvedInAllConfigurations(gradleVersion: GradleVersion) {
project("new-mpp-jvm-with-java-multi-module", gradleVersion) {
testResolveAllConfigurations("app") testResolveAllConfigurations("app")
} }
}
@Test @DisplayName("Configurations with no explicit usage could be resolved")
fun testConfigurationsWithNoExplicitUsageResolveRuntime() = @GradleTest
// Starting with Gradle 5.0, plain Maven dependencies are represented as two variants, and resolving them to the API one leads fun testConfigurationsWithNoExplicitUsageResolveRuntime(gradleVersion: GradleVersion) {
// to transitive dependencies left out of the resolution results. We need to ensure that our attributes schema does not lead to the API // Starting with Gradle 5.0, plain Maven dependencies are represented as two variants, and resolving them to the API one leads
// to transitive dependencies left out of the resolution results. We need to ensure that our attributes schema does not lead to the API
// variants chosen over the runtime ones when resolving a configuration with no required Usage: // variants chosen over the runtime ones when resolving a configuration with no required Usage:
with(Project("simpleProject")) { project("simpleProject", gradleVersion) {
setupWorkingDir() buildGradle.appendText(
gradleBuildScript().appendText( """
"\n" + """ |
dependencies { implementation 'org.jetbrains.kotlin:kotlin-compiler-embeddable' } |dependencies { implementation 'org.jetbrains.kotlin:kotlin-compiler-embeddable' }
|
configurations { |configurations {
customConfiguration.extendsFrom implementation | customConfiguration.extendsFrom implementation
customConfiguration.canBeResolved(true) | customConfiguration.canBeResolved(true)
} |}
""".trimIndent() |
""".trimMargin()
) )
testResolveAllConfigurations { testResolveAllConfigurations { _, buildResult ->
assertContains(">> :customConfiguration --> kotlin-compiler-embeddable-${defaultBuildOptions().kotlinVersion}.jar") buildResult.assertOutputContains(
">> :customConfiguration --> kotlin-compiler-embeddable-${defaultBuildOptions.kotlinVersion}.jar"
)
// Check that the transitive dependencies with 'runtime' scope are also available: // Check that the transitive dependencies with 'runtime' scope are also available:
assertContains(">> :customConfiguration --> kotlin-script-runtime-${defaultBuildOptions().kotlinVersion}.jar") buildResult.assertOutputContains(
">> :customConfiguration --> kotlin-script-runtime-${defaultBuildOptions.kotlinVersion}.jar"
)
} }
} }
}
@Test @DisplayName("Elements configurations could be resolved correctly")
fun testCompileAndRuntimeResolutionOfElementsConfigurations() = @GradleTest
with(Project("sample-app", gradleVersion, "new-mpp-lib-and-app")) { fun testCompileAndRuntimeResolutionOfElementsConfigurations(gradleVersion: GradleVersion) {
val libProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app") project("new-mpp-lib-and-app/sample-app", gradleVersion) {
embedProject(libProject) includeOtherProjectAsSubmodule(
gradleBuildScript().modify { otherProjectName = "sample-lib",
it.replace("'com.example:sample-lib:1.0'", "project('${libProject.projectName}')") pathPrefix = "new-mpp-lib-and-app"
} )
buildGradle.replaceText("'com.example:sample-lib:1.0'", "project(':sample-lib')")
val testGradleVersion = chooseWrapperVersionOrFinishTest() val isAtLeastGradle75 = gradleVersion >= GradleVersion.version("7.5")
val isAtLeastGradle75 = GradleVersion.version(testGradleVersion) >= GradleVersion.version("7.5")
listOf("jvm6" to "Classpath", "nodeJs" to "Classpath").forEach { (target, suffix) -> listOf("jvm6" to "Classpath", "nodeJs" to "Classpath").forEach { (target, suffix) ->
build("dependencyInsight", "--configuration", "${target}Compile$suffix", "--dependency", "sample-lib") { build("dependencyInsight", "--configuration", "${target}Compile$suffix", "--dependency", "sample-lib") {
assertSuccessful()
if (isAtLeastGradle75) { if (isAtLeastGradle75) {
assertContains("Variant ${target}ApiElements") assertOutputContains("Variant ${target}ApiElements")
} else { } else {
assertContains("variant \"${target}ApiElements\" [") assertOutputContains("variant \"${target}ApiElements\" [")
} }
} }
if (suffix == "Classpath") { if (suffix == "Classpath") {
build("dependencyInsight", "--configuration", "${target}Runtime$suffix", "--dependency", "sample-lib") { build("dependencyInsight", "--configuration", "${target}Runtime$suffix", "--dependency", "sample-lib") {
assertSuccessful()
if (isAtLeastGradle75) { if (isAtLeastGradle75) {
assertContains("Variant ${target}RuntimeElements") assertOutputContains("Variant ${target}RuntimeElements")
} else { } else {
assertContains("variant \"${target}RuntimeElements\" [") assertOutputContains("variant \"${target}RuntimeElements\" [")
} }
} }
} }
} }
} }
}
@Test @DisplayName("Custom configuration with dependency on multiplatform project could be resolved")
fun testResolveDependencyOnMppInCustomConfiguration() = with(Project("simpleProject", GradleVersionRequired.FOR_MPP_SUPPORT)) { @GradleTest
setupWorkingDir() fun testResolveDependencyOnMppInCustomConfiguration(gradleVersion: GradleVersion) {
project("simpleProject", gradleVersion) {
buildGradle.appendText(
"""
|
|configurations.create("custom")
|dependencies { custom("org.jetbrains.kotlinx:atomicfu:${TestVersions.ThirdPartyDependencies.KOTLINX_ATOMICFU}") }
|tasks.register("resolveCustom") { doLast { println("###" + configurations.custom.toList()) } }
|
""".trimMargin()
)
gradleBuildScript().appendText( build("resolveCustom") {
"\n" + """ val printedLine = output.lines().single { "###[" in it }.substringAfter("###")
configurations.create("custom") val items = printedLine.removeSurrounding("[", "]").split(", ")
dependencies { custom("org.jetbrains.kotlinx:atomicfu:0.15.2") } assertTrue(items.toString()) { items.any { "atomicfu-jvm" in it } }
tasks.register("resolveCustom") { doLast { println("###" + configurations.custom.toList()) } } }
""".trimIndent()
)
build("resolveCustom") {
assertSuccessful()
val printedLine = output.lines().single { "###[" in it }.substringAfter("###")
val items = printedLine.removeSurrounding("[", "]").split(", ")
assertTrue(items.toString()) { items.any { "atomicfu-jvm" in it } }
} }
} }
} }
internal fun BaseGradleIT.Project.embedProject(other: BaseGradleIT.Project, renameTo: String? = null) {
setupWorkingDir()
other.setupWorkingDir(false)
val tempDir = createTempDir(if (isWindows) "" else "BaseGradleIT")
val embeddedModuleName = renameTo ?: other.projectName
try {
other.projectDir.copyRecursively(tempDir)
tempDir.copyRecursively(projectDir.resolve(embeddedModuleName))
} finally {
check(tempDir.deleteRecursively())
}
testCase.apply {
gradleSettingsScript().appendText("\ninclude(\"$embeddedModuleName\")")
val embeddedBuildScript = gradleBuildScript(embeddedModuleName)
if (embeddedBuildScript.extension == "kts") {
embeddedBuildScript.modify { it.replace(".version(\"$PLUGIN_MARKER_VERSION_PLACEHOLDER\")", "") }
}
}
}
@@ -88,5 +88,6 @@ interface TestVersions {
object ThirdPartyDependencies { object ThirdPartyDependencies {
const val SHADOW_PLUGIN_VERSION = "8.1.1" const val SHADOW_PLUGIN_VERSION = "8.1.1"
const val GRADLE_ENTERPRISE_PLUGIN_VERSION = "3.13.4" const val GRADLE_ENTERPRISE_PLUGIN_VERSION = "3.13.4"
const val KOTLINX_ATOMICFU = "0.23.2"
} }
} }