Migration HierarchicalMppIT tests to new DSL

^KT-50912
This commit is contained in:
Alexander Dudinsky
2022-03-31 13:24:33 +00:00
committed by Space
parent c56f06f942
commit 86d8ef7e45
15 changed files with 333 additions and 192 deletions
@@ -5,62 +5,91 @@
package org.jetbrains.kotlin.gradle package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode import org.gradle.api.logging.configuration.WarningMode
import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.internals.MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME import org.jetbrains.kotlin.gradle.internals.MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME
import org.jetbrains.kotlin.gradle.internals.parseKotlinSourceSetMetadataFromJson import org.jetbrains.kotlin.gradle.internals.parseKotlinSourceSetMetadataFromJson
import org.jetbrains.kotlin.gradle.native.transformNativeTestProjectWithPluginDsl
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata
import org.jetbrains.kotlin.gradle.plugin.mpp.ModuleDependencyIdentifier import org.jetbrains.kotlin.gradle.plugin.mpp.ModuleDependencyIdentifier
import org.jetbrains.kotlin.gradle.plugin.mpp.SourceSetMetadataLayout import org.jetbrains.kotlin.gradle.plugin.mpp.SourceSetMetadataLayout
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.checkedReplace import org.jetbrains.kotlin.gradle.util.checkedReplace
import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.modify
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.io.TempDir
import java.io.File import java.io.File
import java.nio.file.Path
import java.util.zip.ZipFile import java.util.zip.ZipFile
import kotlin.test.Test import kotlin.io.path.absolutePathString
import kotlin.io.path.appendText
import kotlin.io.path.writeText
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
class HierarchicalMppIT : BaseGradleIT() { @MppGradlePluginTests
override val defaultGradleVersion: GradleVersionRequired @DisplayName("Hierarchical multiplatform")
get() = gradleVersion class HierarchicalMppIT : KGPBaseTest() {
companion object { private val String.withPrefix get() = "hierarchical-mpp-published-modules/$this"
private val gradleVersion = GradleVersionRequired.FOR_MPP_SUPPORT
}
@Test @GradleTest
fun testPublishedModules() { @DisplayName("Check build with published third-party library")
publishThirdPartyLib(withGranularMetadata = false) fun testPublishedModules(gradleVersion: GradleVersion, @TempDir tempDir: Path) {
val buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)
transformNativeTestProjectWithPluginDsl("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run { publishThirdPartyLib(withGranularMetadata = false, gradleVersion = gradleVersion, localRepoDir = tempDir)
nativeProject(
"my-lib-foo".withPrefix,
gradleVersion,
localRepoDir = tempDir,
buildOptions = buildOptions
).run {
build("publish") { build("publish") {
checkMyLibFoo(this, subprojectPrefix = null) checkMyLibFoo(localRepoDir = tempDir)
} }
} }
transformNativeTestProjectWithPluginDsl("my-lib-bar", gradleVersion, "hierarchical-mpp-published-modules").run { nativeProject(
"my-lib-bar".withPrefix,
gradleVersion,
localRepoDir = tempDir,
buildOptions = buildOptions
).run {
build("publish") { build("publish") {
checkMyLibBar(this, subprojectPrefix = null) checkMyLibBar(localRepoDir = tempDir)
} }
} }
transformNativeTestProjectWithPluginDsl("my-app", gradleVersion, "hierarchical-mpp-published-modules").run { nativeProject(
"my-app".withPrefix,
gradleVersion,
localRepoDir = tempDir,
buildOptions = buildOptions
).run {
build("assemble") { build("assemble") {
checkMyApp(this, subprojectPrefix = null) checkMyApp()
} }
} }
} }
@Test @GradleTest
fun testNoSourceSetsVisibleIfNoVariantMatched() { @DisplayName("Check no sourceSets visible if no variant matched")
publishThirdPartyLib(withGranularMetadata = true) fun testNoSourceSetsVisibleIfNoVariantMatched(gradleVersion: GradleVersion, @TempDir tempDir: Path) {
publishThirdPartyLib(withGranularMetadata = true, gradleVersion = gradleVersion, localRepoDir = tempDir)
transformNativeTestProjectWithPluginDsl("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run { nativeProject(
projectName = "my-lib-foo".withPrefix,
gradleVersion = gradleVersion,
localRepoDir = tempDir
).run {
// --- Move the dependency from jvmAndJsMain to commonMain, where there's a linuxX64 target missing in the lib // --- Move the dependency from jvmAndJsMain to commonMain, where there's a linuxX64 target missing in the lib
gradleBuildScript().modify { buildGradleKts.modify {
it.checkedReplace("api(\"com.example.thirdparty:third-party-lib:1.0\")", "//") + "\n" + """ it.checkedReplace("api(\"com.example.thirdparty:third-party-lib:1.0\")", "//") + "\n" + """
dependencies { dependencies {
"commonMainApi"("com.example.thirdparty:third-party-lib:1.0") "commonMainApi"("com.example.thirdparty:third-party-lib:1.0")
@@ -81,14 +110,19 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
} }
@Test @GradleTest
fun testDependenciesInTests() { @DisplayName("Dependencies in tests should be correct with third-party library")
publishThirdPartyLib(withGranularMetadata = true) { fun testDependenciesInTests(gradleVersion: GradleVersion, @TempDir tempDir: Path) {
projectDir.resolve("src/jvmMain").copyRecursively(projectDir.resolve("src/linuxX64Main")) publishThirdPartyLib(withGranularMetadata = true, gradleVersion = gradleVersion, localRepoDir = tempDir) {
gradleBuildScript().appendText("\nkotlin.linuxX64()") kotlinSourcesDir("jvmMain").copyRecursively(kotlinSourcesDir("linuxX64Main"))
buildGradleKts.appendText("kotlin.linuxX64()")
} }
transformNativeTestProjectWithPluginDsl("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run { nativeProject(
projectName = "my-lib-foo".withPrefix,
gradleVersion = gradleVersion,
localRepoDir = tempDir
).run {
testDependencyTransformations { reports -> testDependencyTransformations { reports ->
val testApiTransformationReports = val testApiTransformationReports =
reports.filter { report -> reports.filter { report ->
@@ -110,13 +144,12 @@ class HierarchicalMppIT : BaseGradleIT() {
val existingFilesFromReports = reports.flatMap { it.useFiles }.filter { it.isFile } val existingFilesFromReports = reports.flatMap { it.useFiles }.filter { it.isFile }
assertTrue { existingFilesFromReports.isNotEmpty() } assertTrue { existingFilesFromReports.isNotEmpty() }
build("clean") { build("clean") {
assertSuccessful()
existingFilesFromReports.forEach { assertTrue("Expected that $it exists after clean build.") { it.isFile } } existingFilesFromReports.forEach { assertTrue("Expected that $it exists after clean build.") { it.isFile } }
} }
} }
// --- Move the dependency from jvmAndJsMain to commonMain, expect that it is now propagated to commonTest: // --- Move the dependency from jvmAndJsMain to commonMain, expect that it is now propagated to commonTest:
gradleBuildScript().modify { buildGradleKts.modify {
it.checkedReplace("api(\"com.example.thirdparty:third-party-lib:1.0\")", "//") + "\n" + """ it.checkedReplace("api(\"com.example.thirdparty:third-party-lib:1.0\")", "//") + "\n" + """
dependencies { dependencies {
"commonMainApi"("com.example.thirdparty:third-party-lib:1.0") "commonMainApi"("com.example.thirdparty:third-party-lib:1.0")
@@ -140,7 +173,7 @@ class HierarchicalMppIT : BaseGradleIT() {
// --- Remove the dependency from commonMain, add it to commonTest to check that it is correctly picked from a non-published // --- Remove the dependency from commonMain, add it to commonTest to check that it is correctly picked from a non-published
// source set: // source set:
gradleBuildScript().modify { buildGradleKts.modify {
it.checkedReplace("\"commonMainApi\"(\"com.example.thirdparty:third-party-lib:1.0\")", "//") + "\n" + """ it.checkedReplace("\"commonMainApi\"(\"com.example.thirdparty:third-party-lib:1.0\")", "//") + "\n" + """
dependencies { dependencies {
"commonTestApi"("com.example.thirdparty:third-party-lib:1.0") "commonTestApi"("com.example.thirdparty:third-party-lib:1.0")
@@ -166,93 +199,108 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
} }
@Test @GradleTest
fun testProjectDependencies() { @DisplayName("Dependencies in project should be correct with third-party library")
publishThirdPartyLib(withGranularMetadata = false) fun testProjectDependencies(gradleVersion: GradleVersion, @TempDir tempDir: Path) {
publishThirdPartyLib(withGranularMetadata = false, gradleVersion = gradleVersion, localRepoDir = tempDir)
with(transformNativeTestProjectWithPluginDsl("hierarchical-mpp-project-dependency", gradleVersion)) { with(
build("publish", "assemble") { nativeProject(
checkMyLibFoo(this, subprojectPrefix = "my-lib-foo") "hierarchical-mpp-project-dependency",
checkMyLibBar(this, subprojectPrefix = "my-lib-bar") gradleVersion,
checkMyApp(this, subprojectPrefix = "my-app") localRepoDir = tempDir,
} buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)
}
}
@Test
fun testHmppWithPublishedJsBothDependency() {
val directoryPrefix = "hierarchical-mpp-with-js-published-modules"
publishThirdPartyLib(
projectName = "third-party-lib",
directoryPrefix = directoryPrefix,
withGranularMetadata = true,
jsCompilerType = KotlinJsCompilerType.BOTH
)
with(transformNativeTestProjectWithPluginDsl("my-lib-foo", gradleVersion, directoryPrefix)) {
build(
"publish",
"assemble",
options = defaultBuildOptions().copy(jsCompilerType = KotlinJsCompilerType.IR)
) {
assertSuccessful()
}
}
}
@Test
fun testHmppWithProjectJsIrDependency() {
with(transformNativeTestProjectWithPluginDsl("hierarchical-mpp-with-js-project-dependency", gradleVersion)) {
build(
"assemble",
options = defaultBuildOptions().copy(jsCompilerType = KotlinJsCompilerType.IR)
) {
assertSuccessful()
}
}
}
@Test
fun testMultiModulesHmppKt48370() = with(Project("hierarchical-mpp-multi-modules", GradleVersionRequired.FOR_MPP_SUPPORT)) {
build(
"assemble", options = defaultBuildOptions().copy(
parallelTasksInProject = true,
warningMode = WarningMode.Summary
) )
) { ) {
assertSuccessful() build("publish", "assemble") {
checkMyLibFoo(subprojectPrefix = "my-lib-foo", tempDir)
checkMyLibBar(subprojectPrefix = "my-lib-bar", tempDir)
checkMyApp(subprojectPrefix = "my-app")
}
} }
} }
@GradleTest
@DisplayName("Works with published JS library")
fun testHmppWithPublishedJsBothDependency(gradleVersion: GradleVersion, @TempDir tempDir: Path) {
publishThirdPartyLib(
projectName = "hierarchical-mpp-with-js-published-modules/third-party-lib",
withGranularMetadata = true,
jsCompilerType = KotlinJsCompilerType.BOTH,
gradleVersion = gradleVersion,
localRepoDir = tempDir
)
with(
nativeProject(
"hierarchical-mpp-with-js-published-modules/my-lib-foo",
gradleVersion,
localRepoDir = tempDir,
buildOptions = defaultBuildOptions.copy(jsOptions = BuildOptions.JsOptions(jsCompilerType = KotlinJsCompilerType.IR))
)
) {
build("publish", "assemble")
}
}
@GradleTest
@DisplayName("Works with project dependency on JS library")
fun testHmppWithProjectJsIrDependency(gradleVersion: GradleVersion) {
with(
nativeProject(
projectName = "hierarchical-mpp-with-js-project-dependency",
gradleVersion = gradleVersion,
buildOptions = defaultBuildOptions.copy(jsOptions = BuildOptions.JsOptions(jsCompilerType = KotlinJsCompilerType.IR))
)
) {
build("assemble")
}
}
@GradleTest
@DisplayName("KT-48370: Multiplatform Gradle build fails for Native targets with \"we cannot choose between the following variants of project\"")
fun testMultiModulesHmppKt48370(gradleVersion: GradleVersion) = with(
project(
projectName = "hierarchical-mpp-multi-modules",
gradleVersion = gradleVersion,
buildOptions = defaultBuildOptions.copy(
warningMode = WarningMode.Summary
)
)
) {
build(
"assemble",
)
}
private fun publishThirdPartyLib( private fun publishThirdPartyLib(
projectName: String = "third-party-lib", projectName: String = "third-party-lib".withPrefix,
directoryPrefix: String = "hierarchical-mpp-published-modules",
withGranularMetadata: Boolean, withGranularMetadata: Boolean,
jsCompilerType: KotlinJsCompilerType = KotlinJsCompilerType.LEGACY, jsCompilerType: KotlinJsCompilerType = KotlinJsCompilerType.LEGACY,
beforePublishing: Project.() -> Unit = { } gradleVersion: GradleVersion,
): Project = localRepoDir: Path,
transformNativeTestProjectWithPluginDsl(projectName, gradleVersion, directoryPrefix).apply { beforePublishing: TestProject.() -> Unit = { }
): TestProject =
nativeProject(
projectName = projectName,
gradleVersion = gradleVersion,
localRepoDir = localRepoDir,
buildOptions = defaultBuildOptions.copy(jsOptions = BuildOptions.JsOptions(jsCompilerType = jsCompilerType))
).apply {
beforePublishing() beforePublishing()
if (!withGranularMetadata) { if (!withGranularMetadata) {
projectDir.resolve("gradle.properties").appendText("kotlin.internal.mpp.hierarchicalStructureByDefault=false") projectPath.toFile().resolve("gradle.properties").appendText("kotlin.internal.mpp.hierarchicalStructureByDefault=false")
}
build(
"publish",
options = defaultBuildOptions().copy(jsCompilerType = jsCompilerType)
) {
assertSuccessful()
} }
build("publish")
} }
private fun checkMyLibFoo(compiledProject: CompiledProject, subprojectPrefix: String? = null) = with(compiledProject) { private fun BuildResult.checkMyLibFoo(subprojectPrefix: String? = null, localRepoDir: Path) {
assertSuccessful()
assertTasksExecuted(expectedTasks(subprojectPrefix)) assertTasksExecuted(expectedTasks(subprojectPrefix))
ZipFile( ZipFile(
project.projectDir.parentFile.resolve( localRepoDir.toFile().resolve(
"repo/com/example/foo/my-lib-foo/1.0/my-lib-foo-1.0-all.jar" "com/example/foo/my-lib-foo/1.0/my-lib-foo-1.0-all.jar"
) )
).use { publishedMetadataJar -> ).use { publishedMetadataJar ->
publishedMetadataJar.checkAllEntryNamesArePresent( publishedMetadataJar.checkAllEntryNamesArePresent(
@@ -282,8 +330,8 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
ZipFile( ZipFile(
project.projectDir.parentFile.resolve( localRepoDir.toFile().resolve(
"repo/com/example/foo/my-lib-foo/1.0/my-lib-foo-1.0-sources.jar" "com/example/foo/my-lib-foo/1.0/my-lib-foo-1.0-sources.jar"
) )
).use { publishedSourcesJar -> ).use { publishedSourcesJar ->
publishedSourcesJar.checkAllEntryNamesArePresent( publishedSourcesJar.checkAllEntryNamesArePresent(
@@ -295,15 +343,14 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
} }
private fun checkMyLibBar(compiledProject: CompiledProject, subprojectPrefix: String?) = with(compiledProject) { private fun BuildResult.checkMyLibBar(subprojectPrefix: String? = null, localRepoDir: Path) {
val taskPrefix = subprojectPrefix?.let { ":$it" }.orEmpty() val taskPrefix = subprojectPrefix?.let { ":$it" }.orEmpty()
assertSuccessful()
assertTasksExecuted(expectedTasks(subprojectPrefix)) assertTasksExecuted(expectedTasks(subprojectPrefix))
ZipFile( ZipFile(
project.projectDir.parentFile.resolve( localRepoDir.toFile().resolve(
"repo/com/example/bar/my-lib-bar/1.0/my-lib-bar-1.0-all.jar" "com/example/bar/my-lib-bar/1.0/my-lib-bar-1.0-all.jar"
) )
).use { publishedMetadataJar -> ).use { publishedMetadataJar ->
publishedMetadataJar.checkAllEntryNamesArePresent( publishedMetadataJar.checkAllEntryNamesArePresent(
@@ -333,8 +380,8 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
ZipFile( ZipFile(
project.projectDir.parentFile.resolve( localRepoDir.toFile().resolve(
"repo/com/example/bar/my-lib-bar/1.0/my-lib-bar-1.0-sources.jar" "com/example/bar/my-lib-bar/1.0/my-lib-bar-1.0-sources.jar"
) )
).use { publishedSourcesJar -> ).use { publishedSourcesJar ->
publishedSourcesJar.checkAllEntryNamesArePresent( publishedSourcesJar.checkAllEntryNamesArePresent(
@@ -382,10 +429,8 @@ class HierarchicalMppIT : BaseGradleIT() {
) )
} }
private fun checkMyApp(compiledProject: CompiledProject, subprojectPrefix: String?) = with(compiledProject) { private fun BuildResult.checkMyApp(subprojectPrefix: String? = null) {
val taskPrefix = subprojectPrefix?.let { ":$it" }.orEmpty() val taskPrefix = subprojectPrefix?.let { ":$it" }.orEmpty()
assertSuccessful()
assertTasksExecuted(expectedTasks(subprojectPrefix)) assertTasksExecuted(expectedTasks(subprojectPrefix))
checkNamesOnCompileClasspath( checkNamesOnCompileClasspath(
@@ -436,7 +481,7 @@ class HierarchicalMppIT : BaseGradleIT() {
checkNamesOnCompileClasspath("$taskPrefix:compileLinuxAndJsMainKotlinMetadata") checkNamesOnCompileClasspath("$taskPrefix:compileLinuxAndJsMainKotlinMetadata")
} }
private fun CompiledProject.checkNamesOnCompileClasspath( private fun BuildResult.checkNamesOnCompileClasspath(
taskPath: String, taskPath: String,
shouldInclude: Iterable<Pair<String, String>> = emptyList(), shouldInclude: Iterable<Pair<String, String>> = emptyList(),
shouldNotInclude: Iterable<Pair<String, String>> = emptyList() shouldNotInclude: Iterable<Pair<String, String>> = emptyList()
@@ -519,13 +564,15 @@ class HierarchicalMppIT : BaseGradleIT() {
return checkNotNull(parseKotlinSourceSetMetadataFromJson(json)) return checkNotNull(parseKotlinSourceSetMetadataFromJson(json))
} }
@Test @GradleTest
fun testCompileOnlyDependencyProcessingForMetadataCompilations() = @DisplayName("Compile only dependency processing for metadata compilations")
with(transformNativeTestProjectWithPluginDsl("hierarchical-mpp-project-dependency")) { fun testCompileOnlyDependencyProcessingForMetadataCompilations(gradleVersion: GradleVersion, @TempDir tempDir: Path) =
publishThirdPartyLib(withGranularMetadata = true) with(nativeProject("hierarchical-mpp-project-dependency", gradleVersion, localRepoDir = tempDir)) {
publishThirdPartyLib(withGranularMetadata = true, gradleVersion = gradleVersion, localRepoDir = tempDir)
gradleBuildScript("my-lib-foo").appendText("\ndependencies { \"jvmAndJsMainCompileOnly\"(kotlin(\"test-annotations-common\")) }") subProject("my-lib-foo").buildGradleKts
projectDir.resolve("my-lib-foo/src/jvmAndJsMain/kotlin/UseCompileOnlyDependency.kt").writeText( .appendText("\ndependencies { \"jvmAndJsMainCompileOnly\"(kotlin(\"test-annotations-common\")) }")
projectPath.resolve("my-lib-foo/src/jvmAndJsMain/kotlin/UseCompileOnlyDependency.kt").writeText(
""" """
import kotlin.test.Test import kotlin.test.Test
@@ -536,33 +583,39 @@ class HierarchicalMppIT : BaseGradleIT() {
""".trimIndent() """.trimIndent()
) )
build(":my-lib-foo:compileJvmAndJsMainKotlinMetadata") { build(":my-lib-foo:compileJvmAndJsMainKotlinMetadata")
assertSuccessful()
}
} }
@Test @GradleTest
fun testHmppDependenciesInJsTests() { @DisplayName("HMPP dependencies in js tests")
val thirdPartyRepo = publishThirdPartyLib(withGranularMetadata = true).projectDir.parentFile.resolve("repo") fun testHmppDependenciesInJsTests(gradleVersion: GradleVersion, @TempDir tempDir: Path) {
with(Project("hierarchical-mpp-js-test")) { publishThirdPartyLib(
withGranularMetadata = true,
gradleVersion = gradleVersion,
localRepoDir = tempDir
)
with(project("hierarchical-mpp-js-test", gradleVersion)) {
val taskToExecute = ":jsNodeTest" val taskToExecute = ":jsNodeTest"
build(taskToExecute, "-PthirdPartyRepo=$thirdPartyRepo") { build(taskToExecute, "-PthirdPartyRepo=${tempDir.absolutePathString()}") {
assertSuccessful()
assertTasksExecuted(taskToExecute) assertTasksExecuted(taskToExecute)
} }
} }
} }
@Test @GradleTest
fun testProcessingDependencyDeclaredInNonRootSourceSet() { @DisplayName("Processing dependency declared in non root sourceSet")
publishThirdPartyLib(withGranularMetadata = true) fun testProcessingDependencyDeclaredInNonRootSourceSet(gradleVersion: GradleVersion, @TempDir tempDir: Path) {
publishThirdPartyLib(withGranularMetadata = true, gradleVersion = gradleVersion, localRepoDir = tempDir)
transformNativeTestProjectWithPluginDsl("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run { nativeProject(
"my-lib-foo".withPrefix,
gradleVersion = gradleVersion,
localRepoDir = tempDir,
buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)
).run {
val intermediateMetadataCompileTask = ":compileJvmAndJsMainKotlinMetadata" val intermediateMetadataCompileTask = ":compileJvmAndJsMainKotlinMetadata"
build(intermediateMetadataCompileTask) { build(intermediateMetadataCompileTask) {
assertSuccessful()
checkNamesOnCompileClasspath( checkNamesOnCompileClasspath(
intermediateMetadataCompileTask, intermediateMetadataCompileTask,
shouldInclude = listOf( shouldInclude = listOf(
@@ -573,11 +626,16 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
} }
@Test @GradleTest
fun testDependenciesInNonPublishedSourceSets() { @DisplayName("Check dependencies in non published sourceSets")
publishThirdPartyLib(withGranularMetadata = true) fun testDependenciesInNonPublishedSourceSets(gradleVersion: GradleVersion, @TempDir tempDir: Path) {
publishThirdPartyLib(withGranularMetadata = true, gradleVersion = gradleVersion, localRepoDir = tempDir)
transformNativeTestProjectWithPluginDsl("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run { nativeProject(
"my-lib-foo".withPrefix,
gradleVersion = gradleVersion,
localRepoDir = tempDir
).run {
testDependencyTransformations { reports -> testDependencyTransformations { reports ->
reports.single { reports.single {
it.sourceSetName == "jvmAndJsMain" && it.scope == "api" && it.groupAndModule.startsWith("com.example") it.sourceSetName == "jvmAndJsMain" && it.scope == "api" && it.groupAndModule.startsWith("com.example")
@@ -589,23 +647,30 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
} }
@Test @GradleTest
fun testTransitiveDependencyOnSelf() = with(Project("transitive-dep-on-self-hmpp")) { @DisplayName("Check transitive dependency on self")
testDependencyTransformations(subproject = "lib") { reports -> fun testTransitiveDependencyOnSelf(gradleVersion: GradleVersion) =
reports.single { with(project("transitive-dep-on-self-hmpp", gradleVersion = gradleVersion)) {
it.sourceSetName == "commonTest" && it.scope == "implementation" && "libtests" in it.groupAndModule testDependencyTransformations(subproject = "lib") { reports ->
}.let { reports.single {
assertEquals(setOf("commonMain", "jvmAndJsMain"), it.allVisibleSourceSets) it.sourceSetName == "commonTest" && it.scope == "implementation" && "libtests" in it.groupAndModule
}.let {
assertEquals(setOf("commonMain", "jvmAndJsMain"), it.allVisibleSourceSets)
}
} }
} }
}
@Test @GradleTest
fun testMixedScopesFilesExistKt44845() { @DisplayName("KT-44845: all external dependencies is unresolved in IDE with kotlin.mpp.enableGranularSourceSetsMetadata=true")
publishThirdPartyLib(withGranularMetadata = true) fun testMixedScopesFilesExistKt44845(gradleVersion: GradleVersion, @TempDir tempDir: Path) {
publishThirdPartyLib(withGranularMetadata = true, gradleVersion = gradleVersion, localRepoDir = tempDir)
transformNativeTestProjectWithPluginDsl("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run { nativeProject(
gradleBuildScript().appendText( "my-lib-foo".withPrefix,
gradleVersion,
localRepoDir = tempDir
).run {
buildGradleKts.appendText(
""" """
${"\n"} ${"\n"}
dependencies { dependencies {
@@ -617,15 +682,19 @@ class HierarchicalMppIT : BaseGradleIT() {
testDependencyTransformations { reports -> testDependencyTransformations { reports ->
val reportsForJvmAndJsMain = reports.filter { it.sourceSetName == "jvmAndJsMain" } val reportsForJvmAndJsMain = reports.filter { it.sourceSetName == "jvmAndJsMain" }
val thirdPartyLib = reportsForJvmAndJsMain.single { val thirdPartyLib = reportsForJvmAndJsMain.singleOrNull {
it.scope == "api" && it.groupAndModule.startsWith("com.example") it.scope == "api" && it.groupAndModule.startsWith("com.example")
} }
val coroutinesCore = reportsForJvmAndJsMain.single { val coroutinesCore = reportsForJvmAndJsMain.singleOrNull {
it.scope == "implementation" && it.groupAndModule.contains("kotlinx-coroutines-core") it.scope == "implementation" && it.groupAndModule.contains("kotlinx-coroutines-core")
} }
val serialization = reportsForJvmAndJsMain.single { val serialization = reportsForJvmAndJsMain.singleOrNull {
it.scope == "compileOnly" && it.groupAndModule.contains("kotlinx-serialization-json") it.scope == "compileOnly" && it.groupAndModule.contains("kotlinx-serialization-json")
} }
assertNotNull(thirdPartyLib, "Expected report for third-party-lib")
assertNotNull(coroutinesCore, "Expected report for kotlinx-coroutines-core")
assertNotNull(serialization, "Expected report for kotlinx-serialization-json")
listOf(thirdPartyLib, coroutinesCore, serialization).forEach { report -> listOf(thirdPartyLib, coroutinesCore, serialization).forEach { report ->
assertTrue(report.newVisibleSourceSets.isNotEmpty(), "Expected visible source sets for $report") assertTrue(report.newVisibleSourceSets.isNotEmpty(), "Expected visible source sets for $report")
assertTrue(report.useFiles.isNotEmpty(), "Expected non-empty useFiles for $report") assertTrue(report.useFiles.isNotEmpty(), "Expected non-empty useFiles for $report")
@@ -635,9 +704,10 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
} }
@Test @GradleTest
fun testNativeLeafTestSourceSetsKt46417() { @DisplayName("KT-46417: [UNRESOLVED_REFERENCE] For project to project dependencies of native platform test source sets")
with(Project("kt-46417-ios-test-source-sets")) { fun testNativeLeafTestSourceSetsKt46417(gradleVersion: GradleVersion) {
with(project("kt-46417-ios-test-source-sets", gradleVersion = gradleVersion)) {
testDependencyTransformations("p2") { reports -> testDependencyTransformations("p2") { reports ->
val report = reports.singleOrNull { it.sourceSetName == "iosArm64Test" && it.scope == "implementation" } val report = reports.singleOrNull { it.sourceSetName == "iosArm64Test" && it.scope == "implementation" }
assertNotNull(report, "No single report for 'iosArm64' and implementation scope") assertNotNull(report, "No single report for 'iosArm64' and implementation scope")
@@ -647,12 +717,12 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
} }
private fun Project.testDependencyTransformations( private fun TestProject.testDependencyTransformations(
subproject: String? = null, subproject: String? = null,
check: CompiledProject.(reports: Iterable<DependencyTransformationReport>) -> Unit check: BuildResult.(reports: Iterable<DependencyTransformationReport>) -> Unit
) { ) {
setupWorkingDir() val buildGradleKts = (subproject?.let { subProject(subproject).buildGradleKts } ?: buildGradleKts).toFile()
val buildGradleKts = gradleBuildScript(subproject) assert(buildGradleKts.exists()) { "Kotlin scripts are not found." }
assert(buildGradleKts.extension == "kts") { "Only Kotlin scripts are supported." } assert(buildGradleKts.extension == "kts") { "Only Kotlin scripts are supported." }
val testTaskName = "reportDependencyTransformationsForTest" val testTaskName = "reportDependencyTransformationsForTest"
@@ -695,8 +765,6 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
build(":${subproject?.plus(":").orEmpty()}$testTaskName") { build(":${subproject?.plus(":").orEmpty()}$testTaskName") {
assertSuccessful()
val reports = output.lines() val reports = output.lines()
.filter { DependencyTransformationReport.TEST_OUTPUT_MARKER in it } .filter { DependencyTransformationReport.TEST_OUTPUT_MARKER in it }
.map { DependencyTransformationReport.parseTestOutputLine(it) } .map { DependencyTransformationReport.parseTestOutputLine(it) }
@@ -705,7 +773,7 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
} }
private data class DependencyTransformationReport( internal data class DependencyTransformationReport(
val sourceSetName: String, val sourceSetName: String,
val scope: String, val scope: String,
val groupAndModule: String, val groupAndModule: String,
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.gradle.util.modify
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.io.path.createFile
import kotlin.io.path.exists import kotlin.io.path.exists
internal fun Path.applyAndroidTestFixes() { internal fun Path.applyAndroidTestFixes() {
@@ -17,7 +18,7 @@ internal fun Path.applyAndroidTestFixes() {
assert(keystoreFile.exists()) { assert(keystoreFile.exists()) {
"Common 'debug.keystore' file does not exists in ${keystoreFile.toAbsolutePath()} location!" "Common 'debug.keystore' file does not exists in ${keystoreFile.toAbsolutePath()} location!"
} }
resolve("gradle.properties").append( resolve("gradle.properties").also { if (!it.exists()) it.createFile() }.append(
""" """
|test.fixes.android.debugKeystore=${keystoreFile.toAbsolutePath().toString().normalizePath()} |test.fixes.android.debugKeystore=${keystoreFile.toAbsolutePath().toString().normalizePath()}
| |
@@ -20,6 +20,13 @@ fun BuildResult.assertTasksExecuted(vararg tasks: String) {
} }
} }
/**
* Asserts given [tasks] have 'SUCCESS' execution state.
*/
fun BuildResult.assertTasksExecuted(tasks: Collection<String>) {
assertTasksExecuted(*tasks.toTypedArray())
}
/** /**
* Asserts given [tasks] have 'FAILED' execution state. * Asserts given [tasks] have 'FAILED' execution state.
*/ */
@@ -12,7 +12,11 @@ import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.BaseGradleIT.Companion.acceptAndroidSdkLicenses import org.jetbrains.kotlin.gradle.BaseGradleIT.Companion.acceptAndroidSdkLicenses
import org.jetbrains.kotlin.gradle.model.ModelContainer import org.jetbrains.kotlin.gradle.model.ModelContainer
import org.jetbrains.kotlin.gradle.model.ModelFetcherBuildAction import org.jetbrains.kotlin.gradle.model.ModelFetcherBuildAction
import org.jetbrains.kotlin.gradle.native.disableKotlinNativeCaches
import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.gradle.report.BuildReportType import org.jetbrains.kotlin.gradle.report.BuildReportType
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.presetName
import org.jetbrains.kotlin.test.util.KtTestUtil import org.jetbrains.kotlin.test.util.KtTestUtil
import java.io.File import java.io.File
import java.nio.file.* import java.nio.file.*
@@ -36,17 +40,19 @@ fun KGPBaseTest.project(
enableGradleDebug: Boolean = false, enableGradleDebug: Boolean = false,
projectPathAdditionalSuffix: String = "", projectPathAdditionalSuffix: String = "",
buildJdk: File? = null, buildJdk: File? = null,
test: TestProject.() -> Unit localRepoDir: Path? = null,
test: TestProject.() -> Unit = {}
): TestProject { ): TestProject {
val projectPath = setupProjectFromTestResources( val projectPath = setupProjectFromTestResources(
projectName, projectName,
gradleVersion, gradleVersion,
workingDir, workingDir,
projectPathAdditionalSuffix projectPathAdditionalSuffix,
) )
projectPath.addDefaultBuildFiles() projectPath.addDefaultBuildFiles()
projectPath.enableCacheRedirector() projectPath.enableCacheRedirector()
projectPath.enableAndroidSdk() projectPath.enableAndroidSdk()
if (addHeapDumpOptions) projectPath.addHeapDumpOptions() if (addHeapDumpOptions) projectPath.addHeapDumpOptions()
val gradleRunner = GradleRunner val gradleRunner = GradleRunner
@@ -65,13 +71,51 @@ fun KGPBaseTest.project(
forceOutput, forceOutput,
enableBuildScan enableBuildScan
) )
localRepoDir?.let { testProject.configureLocalRepository(localRepoDir) }
if (buildJdk != null) testProject.setupNonDefaultJdk(buildJdk) if (buildJdk != null) testProject.setupNonDefaultJdk(buildJdk)
testProject.test() testProject.test()
return testProject return testProject
} }
/**
* Create new test project with configuring single native target.
*
* @param [projectName] test project name in 'src/test/resources/testProject` directory.
* @param [buildOptions] common Gradle build options
* @param [buildJdk] path to JDK build should run with. *Note* Only append to 'gradle.properties'!
*/
fun KGPBaseTest.nativeProject(
projectName: String,
gradleVersion: GradleVersion,
buildOptions: BuildOptions = defaultBuildOptions,
forceOutput: Boolean = false,
enableBuildScan: Boolean = false,
addHeapDumpOptions: Boolean = true,
enableGradleDebug: Boolean = false,
projectPathAdditionalSuffix: String = "",
buildJdk: File? = null,
localRepoDir: Path? = null,
test: TestProject.() -> Unit = {}
): TestProject {
val project = project(
projectName = projectName,
gradleVersion = gradleVersion,
buildOptions = buildOptions,
forceOutput = forceOutput,
enableBuildScan = enableBuildScan,
addHeapDumpOptions = addHeapDumpOptions,
enableGradleDebug = enableGradleDebug,
projectPathAdditionalSuffix = projectPathAdditionalSuffix,
buildJdk = buildJdk,
localRepoDir = localRepoDir,
)
project.configureSingleNativeTarget()
project.disableKotlinNativeCaches()
project.test()
return project
}
/** /**
* Trigger test project build with given [buildArguments] and assert build is successful. * Trigger test project build with given [buildArguments] and assert build is successful.
*/ */
@@ -204,6 +248,7 @@ open class GradleProject(
val settingsGradle: Path get() = projectPath.resolve("settings.gradle") val settingsGradle: Path get() = projectPath.resolve("settings.gradle")
val settingsGradleKts: Path get() = projectPath.resolve("settings.gradle.kts") val settingsGradleKts: Path get() = projectPath.resolve("settings.gradle.kts")
val gradleProperties: Path get() = projectPath.resolve("gradle.properties") val gradleProperties: Path get() = projectPath.resolve("gradle.properties")
val buildFileNames: Set<String> get() = setOf("build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts")
fun classesDir( fun classesDir(
sourceSet: String = "main", sourceSet: String = "main",
@@ -324,11 +369,11 @@ private fun setupProjectFromTestResources(
projectName: String, projectName: String,
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
tempDir: Path, tempDir: Path,
optionalSubDir: String optionalSubDir: String,
): Path { ): Path {
val testProjectPath = projectName.testProjectPath val testProjectPath = projectName.testProjectPath
assertTrue("Test project exists") { Files.exists(testProjectPath) } assertTrue("Test project doesn't exists") { Files.exists(testProjectPath) }
assertTrue("Test project path is a directory") { Files.isDirectory(testProjectPath) } assertTrue("Test project path isn't a directory") { Files.isDirectory(testProjectPath) }
return tempDir return tempDir
.resolve(gradleVersion.version) .resolve(gradleVersion.version)
@@ -535,3 +580,23 @@ private fun Path.addHeapDumpOptions() {
} }
} }
} }
private const val SINGLE_NATIVE_TARGET_PLACEHOLDER = "<SingleNativeTarget>"
private const val LOCAL_REPOSITORY_PLACEHOLDER = "<localRepo>"
private fun TestProject.configureSingleNativeTarget(preset: String = HostManager.host.presetName) {
val buildScript = if (buildGradle.exists()) buildGradle else buildGradleKts
buildScript.modify {
it.replace(SINGLE_NATIVE_TARGET_PLACEHOLDER, preset)
}
}
private fun TestProject.configureLocalRepository(localRepoDir: Path) {
projectPath.toFile().walkTopDown()
.filter { it.isFile && it.name in buildFileNames }
.forEach { file ->
file.modify { it.replace(LOCAL_REPOSITORY_PLACEHOLDER, localRepoDir.absolutePathString().replace("\\", "\\\\")) }
}
}
internal fun TestProject.disableKotlinNativeCaches() = gradleProperties.toFile().disableKotlinNativeCaches()
@@ -1,11 +1,11 @@
plugins { plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>").apply(false) kotlin("multiplatform").apply(false)
} }
allprojects { allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
maven("$rootDir/../repo") maven("<localRepo>")
mavenCentral() mavenCentral()
} }
} }
@@ -90,6 +90,6 @@ kotlin {
publishing { publishing {
repositories { repositories {
maven("$rootDir/../repo") maven("<localRepo>")
} }
} }
@@ -95,6 +95,6 @@ kotlin {
publishing { publishing {
repositories { repositories {
maven("$rootDir/../repo") maven("<localRepo>")
} }
} }
@@ -1,5 +1,5 @@
plugins { plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>") kotlin("multiplatform")
`maven-publish` `maven-publish`
} }
@@ -52,6 +52,6 @@ kotlin {
publishing { publishing {
repositories { repositories {
maven("../repo") maven("<localRepo>")
} }
} }
@@ -1,10 +1,10 @@
plugins { plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>") kotlin("multiplatform")
} }
repositories { repositories {
mavenLocal() mavenLocal()
maven("../repo") maven("<localRepo>")
mavenCentral() mavenCentral()
} }
@@ -1,11 +1,11 @@
plugins { plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>") kotlin("multiplatform")
`maven-publish` `maven-publish`
} }
repositories { repositories {
mavenLocal() mavenLocal()
maven("../repo") maven("<localRepo>")
mavenCentral() mavenCentral()
} }
@@ -91,6 +91,6 @@ kotlin {
publishing { publishing {
repositories { repositories {
maven("../repo") maven("<localRepo>")
} }
} }
@@ -1,11 +1,11 @@
plugins { plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>") kotlin("multiplatform")
`maven-publish` `maven-publish`
} }
repositories { repositories {
mavenLocal() mavenLocal()
maven("../repo") maven("<localRepo>")
mavenCentral() mavenCentral()
} }
@@ -103,6 +103,6 @@ kotlin {
publishing { publishing {
repositories { repositories {
maven("../repo") maven("<localRepo>")
} }
} }
@@ -1,5 +1,5 @@
plugins { plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>") kotlin("multiplatform")
`maven-publish` `maven-publish`
} }
@@ -52,6 +52,6 @@ kotlin {
publishing { publishing {
repositories { repositories {
maven("../repo") maven("<localRepo>")
} }
} }
@@ -1,5 +1,5 @@
plugins { plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>").apply(false) kotlin("multiplatform").apply(false)
} }
allprojects { allprojects {
@@ -1,11 +1,11 @@
plugins { plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>") kotlin("multiplatform")
`maven-publish` `maven-publish`
} }
repositories { repositories {
mavenLocal() mavenLocal()
maven("../repo") maven("<localRepo>")
mavenCentral() mavenCentral()
} }
@@ -33,6 +33,6 @@ kotlin {
publishing { publishing {
repositories { repositories {
maven("../repo") maven("<localRepo>")
} }
} }
@@ -1,5 +1,5 @@
plugins { plugins {
kotlin("js").version("<pluginMarkerVersion>") kotlin("js")
`maven-publish` `maven-publish`
} }
@@ -30,7 +30,7 @@ kotlin {
publishing { publishing {
repositories { repositories {
maven("../repo") maven("<localRepo>")
} }
publications { publications {
create<MavenPublication>("maven") { create<MavenPublication>("maven") {