From 0c9d962f9f474479fd6328df9e13e539e25a31b5 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Fri, 12 Apr 2019 18:40:21 +0300 Subject: [PATCH] MPP granular source set metadata support --- .../kotlin/gradle/HierarchicalMppIT.kt | 318 +++++++++++++++ .../build.gradle.kts | 11 + .../gradle.properties | 1 + .../my-app/build.gradle.kts | 88 ++++ .../my-app/src/commonMain/kotlin/App.kt | 11 + .../my-app/src/commonTest/kotlin/AppTest.kt | 20 + .../src/jvmAndJsMain/kotlin/AppJvmAndJs.kt | 17 + .../jvmAndJsTest/kotlin/AppJvmAndJsTest.kt | 21 + .../my-app/src/jvmMain/kotlin/AppJvm.kt | 19 + .../linuxAndJsMain/kotlin/AppLinuxAndJs.kt | 18 + .../kotlin/AppLinuxAndJsTest.kt | 20 + .../src/linuxX64Main/kotlin/AppLinux.kt | 8 + .../my-lib-bar/build.gradle.kts | 95 +++++ .../my-lib-bar/src/commonMain/kotlin/Bar.kt | 11 + .../src/commonTest/kotlin/BarTest.kt | 17 + .../src/jvmAndJsMain/kotlin/BarJvmAndJs.kt | 13 + .../jvmAndJsTest/kotlin/BarJvmAndJsTest.kt | 19 + .../linuxAndJsMain/kotlin/BarLinuxAndJs.kt | 10 + .../kotlin/BarLinuxAndJsTest.kt | 16 + .../src/linuxX64Main/kotlin/BarLinux.kt | 10 + .../my-lib-foo/build.gradle.kts | 100 +++++ .../my-lib-foo/src/commonMain/kotlin/Foo.kt | 5 + .../src/commonTest/kotlin/FooTest.kt | 11 + .../src/jvmAndJsMain/kotlin/FooJvmAndJs.kt | 10 + .../jvmAndJsTest/kotlin/FooJvmAndJsTest.kt | 17 + .../linuxAndJsMain/kotlin/FooLinuxAndJs.kt | 3 + .../kotlin/FooLinuxAndJsTest.kt | 11 + .../src/linuxX64Main/kotlin/FooLinux.kt | 3 + .../settings.gradle.kts | 12 + .../third-party-lib/build.gradle.kts | 57 +++ .../third-party-lib/settings.gradle.kts | 10 + .../src/commonMain/kotlin/ThirdParty.kt | 3 + .../src/jsMain/kotlin/ThirdPartyJs.kt | 5 + .../src/jvmMain/kotlin/ThirdPartyJvm.kt | 5 + .../my-app/build.gradle.kts | 89 +++++ .../my-app/gradle.properties | 1 + .../my-app/settings.gradle.kts | 10 + .../my-app/src/commonMain/kotlin/App.kt | 11 + .../my-app/src/commonTest/kotlin/AppTest.kt | 20 + .../src/jvmAndJsMain/kotlin/AppJvmAndJs.kt | 17 + .../jvmAndJsTest/kotlin/AppJvmAndJsTest.kt | 21 + .../my-app/src/jvmMain/kotlin/AppJvm.kt | 19 + .../linuxAndJsMain/kotlin/AppLinuxAndJs.kt | 18 + .../kotlin/AppLinuxAndJsTest.kt | 20 + .../src/linuxX64Main/kotlin/AppLinux.kt | 8 + .../my-lib-bar/build.gradle.kts | 96 +++++ .../my-lib-bar/gradle.properties | 1 + .../my-lib-bar/settings.gradle.kts | 10 + .../my-lib-bar/src/commonMain/kotlin/Bar.kt | 11 + .../src/commonTest/kotlin/BarTest.kt | 17 + .../src/jvmAndJsMain/kotlin/BarJvmAndJs.kt | 13 + .../jvmAndJsTest/kotlin/BarJvmAndJsTest.kt | 19 + .../linuxAndJsMain/kotlin/BarLinuxAndJs.kt | 10 + .../kotlin/BarLinuxAndJsTest.kt | 16 + .../src/linuxX64Main/kotlin/BarLinux.kt | 10 + .../my-lib-foo/build.gradle.kts | 101 +++++ .../my-lib-foo/gradle.properties | 1 + .../my-lib-foo/settings.gradle.kts | 10 + .../my-lib-foo/src/commonMain/kotlin/Foo.kt | 5 + .../src/commonTest/kotlin/FooTest.kt | 11 + .../src/jvmAndJsMain/kotlin/FooJvmAndJs.kt | 10 + .../jvmAndJsTest/kotlin/FooJvmAndJsTest.kt | 17 + .../linuxAndJsMain/kotlin/FooLinuxAndJs.kt | 3 + .../kotlin/FooLinuxAndJsTest.kt | 9 + .../src/linuxX64Main/kotlin/FooLinux.kt | 3 + .../third-party-lib/build.gradle.kts | 57 +++ .../third-party-lib/settings.gradle.kts | 10 + .../src/commonMain/kotlin/ThirdParty.kt | 3 + .../src/jsMain/kotlin/ThirdPartyJs.kt | 5 + .../src/jvmMain/kotlin/ThirdPartyJvm.kt | 5 + .../gradle/plugin/KotlinPluginWrapper.kt | 2 +- .../kotlin/gradle/plugin/KotlinProperties.kt | 3 + .../gradle/plugin/KotlinTargetConfigurator.kt | 41 +- .../mpp/GenerateProjectStructureMetadata.kt | 44 ++ .../mpp/GranularMetadataTransformation.kt | 375 ++++++++++++++++++ .../plugin/mpp/KotlinCommonCompilation.kt | 12 +- .../gradle/plugin/mpp/KotlinMetadataTarget.kt | 85 ++++ .../plugin/mpp/KotlinMultiplatformPlugin.kt | 61 ++- .../mpp/KotlinProjectStructureMetadata.kt | 130 ++++++ .../plugin/mpp/KotlinSoftwareComponent.kt | 5 +- .../kotlin/gradle/plugin/mpp/KotlinUsages.kt | 40 +- .../plugin/mpp/SourceSetVisibilityProvider.kt | 103 +++++ .../mpp/TransformKotlinGranularMetadata.kt | 95 +++++ .../plugin/mpp/UnusedSourceSetsChecker.kt | 2 +- .../gradle/plugin/mpp/kotlinCompilations.kt | 75 ++-- .../kotlin/gradle/plugin/mpp/kotlinTargets.kt | 5 +- .../gradle/plugin/mpp/kotlinVariants.kt | 6 +- .../plugin/mpp/mppDependencyRewritingUtils.kt | 19 +- .../plugin/sources/DefaultKotlinSourceSet.kt | 9 +- .../plugin/sources/KotlinDependencyScope.kt | 38 ++ .../plugin/sources/KotlinSourceSetFactory.kt | 5 + .../KotlinMetadataTargetConfigurator.kt | 333 +++++++++++++++- .../metadata/KotlinMetadataTargetPreset.kt | 4 +- .../targets/native/KotlinNativeCompilation.kt | 10 +- .../jetbrains/kotlin/gradle/tasks/Tasks.kt | 2 +- .../internals/ExposedForIntegrationTests.kt | 12 + 96 files changed, 3089 insertions(+), 109 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/gradle.properties create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/commonMain/kotlin/App.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/commonTest/kotlin/AppTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmAndJsMain/kotlin/AppJvmAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmAndJsTest/kotlin/AppJvmAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmMain/kotlin/AppJvm.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxAndJsMain/kotlin/AppLinuxAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxAndJsTest/kotlin/AppLinuxAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxX64Main/kotlin/AppLinux.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/commonMain/kotlin/Bar.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/commonTest/kotlin/BarTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxX64Main/kotlin/BarLinux.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/commonMain/kotlin/Foo.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/commonTest/kotlin/FooTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/jvmAndJsMain/kotlin/FooJvmAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/jvmAndJsTest/kotlin/FooJvmAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxAndJsMain/kotlin/FooLinuxAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxAndJsTest/kotlin/FooLinuxAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxX64Main/kotlin/FooLinux.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/settings.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/settings.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/commonMain/kotlin/ThirdParty.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/jsMain/kotlin/ThirdPartyJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/jvmMain/kotlin/ThirdPartyJvm.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/gradle.properties create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/settings.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/commonMain/kotlin/App.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/commonTest/kotlin/AppTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmAndJsMain/kotlin/AppJvmAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmAndJsTest/kotlin/AppJvmAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmMain/kotlin/AppJvm.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxAndJsMain/kotlin/AppLinuxAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxAndJsTest/kotlin/AppLinuxAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxX64Main/kotlin/AppLinux.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/gradle.properties create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/settings.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/commonMain/kotlin/Bar.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/commonTest/kotlin/BarTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxX64Main/kotlin/BarLinux.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/gradle.properties create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/settings.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/commonMain/kotlin/Foo.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/commonTest/kotlin/FooTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/jvmAndJsMain/kotlin/FooJvmAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/jvmAndJsTest/kotlin/FooJvmAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxAndJsMain/kotlin/FooLinuxAndJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxAndJsTest/kotlin/FooLinuxAndJsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxX64Main/kotlin/FooLinux.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/settings.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/commonMain/kotlin/ThirdParty.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/jsMain/kotlin/ThirdPartyJs.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/jvmMain/kotlin/ThirdPartyJvm.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GenerateProjectStructureMetadata.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMetadataTarget.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinDependencyScope.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt new file mode 100644 index 00000000000..3055338e7b1 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt @@ -0,0 +1,318 @@ +/* + * Copyright 2010-2019 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.parseKotlinSourceSetMetadataFromXml +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata +import org.jetbrains.kotlin.gradle.plugin.mpp.MULTIPLATFORM_PROJECT_METADATA_FILE_NAME +import org.jetbrains.kotlin.gradle.util.modify +import org.jetbrains.kotlin.konan.file.File +import java.util.zip.ZipFile +import javax.xml.parsers.DocumentBuilderFactory +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class HierarchicalMppIT : BaseGradleIT() { + companion object { + private val gradleVersion = GradleVersionRequired.AtLeast("5.0") + } + + @Test + fun testPublishedModules() { + Project("third-party-lib", gradleVersion, "hierarchical-mpp-published-modules").run { + setupWorkingDir() + gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl) + build("publish") { + assertSuccessful() + } + } + + Project("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run { + setupWorkingDir() + gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl) + build("publish") { + checkMyLibFoo(this, subprojectPrefix = null) + } + } + + Project("my-lib-bar", gradleVersion, "hierarchical-mpp-published-modules").run { + setupWorkingDir() + gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl) + build("publish") { + checkMyLibBar(this, subprojectPrefix = null) + } + } + + Project("my-app", gradleVersion, "hierarchical-mpp-published-modules").run { + setupWorkingDir() + gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl) + build("assemble") { + checkMyApp(this, subprojectPrefix = null) + } + } + } + + @Test + fun testProjectDependencies() { + Project("third-party-lib", gradleVersion, "hierarchical-mpp-project-dependency").run { + setupWorkingDir() + gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl) + build("publish") { + assertSuccessful() + } + } + + with(Project("hierarchical-mpp-project-dependency", gradleVersion)) { + setupWorkingDir() + gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl) + + build("publish", "assemble") { + checkMyLibFoo(this, subprojectPrefix = "my-lib-foo") + checkMyLibBar(this, subprojectPrefix = "my-lib-bar") + checkMyApp(this, subprojectPrefix = "my-app") + } + } + } + + private fun checkMyLibFoo(compiledProject: CompiledProject, subprojectPrefix: String? = null) = with(compiledProject) { + val taskPrefix = subprojectPrefix?.let { ":$it" }.orEmpty() + val directoryPrefix = subprojectPrefix?.let { "$it/" }.orEmpty() + + assertSuccessful() + assertTasksExecuted(expectedTasks(subprojectPrefix)) + + ZipFile( + project.projectDir.parentFile.resolve( + "repo/com/example/foo/my-lib-foo-metadata/1.0/my-lib-foo-metadata-1.0-all.jar" + ) + ).use { publishedMetadataJar -> + publishedMetadataJar.checkAllEntryNamesArePresent( + "META-INF/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME", + + "commonMain/META-INF/my-lib-foo.kotlin_module", + "commonMain/com/example/foo/FooKt.kotlin_metadata", + + "jvmAndJsMain/META-INF/my-lib-foo_jvmAndJsMain.kotlin_module", + "jvmAndJsMain/com/example/foo/FooJvmAndJsKt.kotlin_metadata", + + "linuxAndJsMain/META-INF/my-lib-foo_linuxAndJsMain.kotlin_module", + "linuxAndJsMain/com/example/foo/FooLinuxAndJsKt.kotlin_metadata" + ) + + val parsedProjectStructureMetadata: KotlinProjectStructureMetadata = publishedMetadataJar.getProjectStructureMetadata() + + val expectedProjectStructureMetadata = expectedProjectStructureMetadata( + sourceSetModuleDependencies = mapOf( + "jvmAndJsMain" to setOf("com.example.thirdparty" to "third-party-lib"), + "linuxAndJsMain" to emptySet(), + "commonMain" to emptySet() + ) + ) + + assertEquals(expectedProjectStructureMetadata, parsedProjectStructureMetadata) + } + } + + private fun checkMyLibBar(compiledProject: CompiledProject, subprojectPrefix: String?) = with(compiledProject) { + val taskPrefix = subprojectPrefix?.let { ":$it" }.orEmpty() + + assertSuccessful() + assertTasksExecuted(expectedTasks(subprojectPrefix)) + + ZipFile( + project.projectDir.parentFile.resolve( + "repo/com/example/bar/my-lib-bar-metadata/1.0/my-lib-bar-metadata-1.0-all.jar" + ) + ).use { publishedMetadataJar -> + publishedMetadataJar.checkAllEntryNamesArePresent( + "META-INF/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME", + + "commonMain/META-INF/my-lib-bar.kotlin_module", + "commonMain/com/example/bar/BarKt.kotlin_metadata", + + "jvmAndJsMain/META-INF/my-lib-bar_jvmAndJsMain.kotlin_module", + "jvmAndJsMain/com/example/bar/BarJvmAndJsKt.kotlin_metadata", + + "linuxAndJsMain/META-INF/my-lib-bar_linuxAndJsMain.kotlin_module", + "linuxAndJsMain/com/example/bar/BarLinuxAndJsKt.kotlin_metadata" + ) + + val parsedProjectStructureMetadata: KotlinProjectStructureMetadata = publishedMetadataJar.getProjectStructureMetadata() + + val expectedProjectStructureMetadata = expectedProjectStructureMetadata( + sourceSetModuleDependencies = mapOf( + "jvmAndJsMain" to setOf(), + "linuxAndJsMain" to emptySet(), + "commonMain" to setOf("com.example.foo" to "my-lib-foo") + ) + ) + + assertEquals(expectedProjectStructureMetadata, parsedProjectStructureMetadata) + } + + checkNamesOnCompileClasspath( + "$taskPrefix:compileKotlinMetadata", + shouldInclude = listOf( + "my-lib-foo" to "main" + ), + shouldNotInclude = listOf( + "my-lib-foo" to "jvmAndJsMain", + "my-lib-foo" to "linuxAndJsMain", + "third-party-lib-metadata-1.0" to "" + ) + ) + + checkNamesOnCompileClasspath( + "$taskPrefix:compileJvmAndJsMainKotlinMetadata", + shouldInclude = listOf( + "my-lib-foo" to "main", + "my-lib-foo" to "jvmAndJsMain", + "third-party-lib-metadata-1.0" to "" + ), + shouldNotInclude = listOf( + "my-lib-foo" to "linuxAndJsMain" + ) + ) + + checkNamesOnCompileClasspath( + "$taskPrefix:compileLinuxAndJsMainKotlinMetadata", + shouldInclude = listOf( + "my-lib-foo" to "linuxAndJsMain", + "my-lib-foo" to "main" + ), + shouldNotInclude = listOf( + "my-lib-foo" to "jvmAndJsMain", + "third-party-lib-metadata-1.0" to "" + ) + ) + } + + private fun checkMyApp(compiledProject: CompiledProject, subprojectPrefix: String?) = with(compiledProject) { + val taskPrefix = subprojectPrefix?.let { ":$it" }.orEmpty() + + assertSuccessful() + assertTasksExecuted(expectedTasks(subprojectPrefix)) + + checkNamesOnCompileClasspath( + "$taskPrefix:compileKotlinMetadata", + shouldInclude = listOf( + "my-lib-bar" to "main", + "my-lib-foo" to "main" + ), + shouldNotInclude = listOf( + "my-lib-bar" to "jvmAndJsMain", + "my-lib-bar" to "linuxAndJsMain", + "my-lib-foo" to "jvmAndJsMain", + "my-lib-foo" to "linuxAndJsMain", + "third-party-lib-metadata-1.0" to "" + ) + ) + + checkNamesOnCompileClasspath( + "$taskPrefix:compileJvmAndJsMainKotlinMetadata", + shouldInclude = listOf( + "my-lib-bar" to "main", + "my-lib-bar" to "jvmAndJsMain", + "my-lib-foo" to "main", + "my-lib-foo" to "jvmAndJsMain", + "third-party-lib-metadata-1.0" to "" + ), + shouldNotInclude = listOf( + "my-lib-bar" to "linuxAndJsMain", + "my-lib-foo" to "linuxAndJsMain" + ) + ) + + checkNamesOnCompileClasspath( + "$taskPrefix:compileLinuxAndJsMainKotlinMetadata", + shouldInclude = listOf( + "my-lib-bar" to "main", + "my-lib-bar" to "linuxAndJsMain", + "my-lib-foo" to "main", + "my-lib-foo" to "linuxAndJsMain" + ), + shouldNotInclude = listOf( + "my-lib-bar" to "jvmAndJsMain", + "my-lib-foo" to "jvmAndJsMain", + "third-party-lib-metadata-1.0" to "" + ) + ) + + checkNamesOnCompileClasspath("$taskPrefix:compileLinuxAndJsMainKotlinMetadata") + } + + private fun CompiledProject.checkNamesOnCompileClasspath( + taskPath: String, + shouldInclude: Iterable> = emptyList(), + shouldNotInclude: Iterable> = emptyList() + ) { + val taskOutput = getOutputForTask(taskPath.removePrefix(":")) + val compilerArgsLine = taskOutput.lines().single { "Kotlin compiler args:" in it } + val classpathItems = compilerArgsLine.substringAfter("-classpath").substringBefore(" -").split(File.pathSeparator) + + shouldInclude.forEach { (module, sourceSet) -> + assertTrue("expected module '$module' source set '$sourceSet' on the classpath of task $taskPath") { + classpathItems.any { module in it && it.contains(sourceSet, ignoreCase = true) } + } + } + + shouldNotInclude.forEach { (module, sourceSet) -> + assertTrue("not expected module '$module' source set '$sourceSet' on the compile classpath of task $taskPath") { + classpathItems.none { module in it && it.contains(sourceSet, ignoreCase = true) } + } + } + } + + private fun expectedTasks(subprojectPrefix: String?) = listOf( + "generateProjectStructureMetadata", + "transformCommonMainDependenciesMetadata", + "transformJvmAndJsMainDependenciesMetadata", + "transformLinuxAndJsMainDependenciesMetadata", + "compileKotlinMetadata", + "compileJvmAndJsMainKotlinMetadata", + "compileLinuxAndJsMainKotlinMetadata" + ).map { subprojectPrefix?.let { ":$it" }.orEmpty() + ":" + it } + + // the projects used in these tests are similar and only the dependencies differ: + private fun expectedProjectStructureMetadata( + sourceSetModuleDependencies: Map>> + ): KotlinProjectStructureMetadata { + + val jvmSourceSets = setOf("commonMain", "jvmAndJsMain") + val jsSourceSets = setOf("commonMain", "jvmAndJsMain", "linuxAndJsMain") + return KotlinProjectStructureMetadata( + sourceSetNamesByVariantName = mapOf( + "js-api" to jsSourceSets, + "js-runtime" to jsSourceSets, + "jvm-api" to jvmSourceSets, + "jvm-runtime" to jvmSourceSets, + "linuxX64-api" to setOf("commonMain", "linuxAndJsMain") + ), + sourceSetsDependsOnRelation = mapOf( + "jvmAndJsMain" to setOf("commonMain"), + "linuxAndJsMain" to setOf("commonMain"), + "commonMain" to emptySet() + ), + sourceSetModuleDependencies = sourceSetModuleDependencies + ) + } + + private fun ZipFile.checkAllEntryNamesArePresent(vararg expectedEntryNames: String) { + val entryNames = entries().asSequence().map { it.name }.toSet() + val entryNamesString = entryNames.joinToString() + expectedEntryNames.forEach { + assertTrue("expecting entry $it in entry names $entryNamesString") { it in entryNames } + } + } + + private fun ZipFile.getProjectStructureMetadata(): KotlinProjectStructureMetadata { + val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val document = getInputStream(getEntry("META-INF/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME")) + .use { inputStream -> documentBuilder.parse(inputStream) } + return checkNotNull(parseKotlinSourceSetMetadataFromXml(document)) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/build.gradle.kts new file mode 100644 index 00000000000..d30075788ce --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + kotlin("multiplatform").version("").apply(false) +} + +allprojects { + repositories { + mavenLocal() + maven("$rootDir/../repo") + jcenter() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/gradle.properties new file mode 100644 index 00000000000..2a391fe78b0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/gradle.properties @@ -0,0 +1 @@ +kotlin.mpp.enableGranularSourceSetsMetadata=true \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/build.gradle.kts new file mode 100644 index 00000000000..d277f176998 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/build.gradle.kts @@ -0,0 +1,88 @@ +plugins { + kotlin("multiplatform") +} + +repositories { + mavenLocal() + jcenter() +} + +group = "com.example.app" +version = "1.0" + +kotlin { + jvm() + js() + + linuxX64() + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":my-lib-bar")) + 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) + } + + val linuxAndJsMain by creating { + dependsOn(commonMain) + } + + val linuxAndJsTest 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) + dependsOn(linuxAndJsMain) + dependencies { + implementation(kotlin("stdlib-js")) + } + } + + js().compilations["test"].defaultSourceSet { + dependsOn(jvmAndJsTest) + dependsOn(linuxAndJsTest) + dependencies { + implementation(kotlin("test-js")) + } + } + + linuxX64().compilations["main"].defaultSourceSet { + dependsOn(linuxAndJsMain) + } + + linuxX64().compilations["test"].defaultSourceSet { + dependsOn(linuxAndJsTest) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/commonMain/kotlin/App.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/commonMain/kotlin/App.kt new file mode 100644 index 00000000000..deaca4eb24f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/commonMain/kotlin/App.kt @@ -0,0 +1,11 @@ +package com.example.app + +import com.example.foo.* +import com.example.bar.* + +fun appCommon() { + foo() + fooCommon() + bar() + barCommon() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/commonTest/kotlin/AppTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/commonTest/kotlin/AppTest.kt new file mode 100644 index 00000000000..397474b4d1a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/commonTest/kotlin/AppTest.kt @@ -0,0 +1,20 @@ +package com.example.app + +import com.example.bar.bar +import com.example.bar.barCommon +import com.example.foo.foo +import com.example.foo.fooCommon +import kotlin.test.Test +import kotlin.test.assertEquals + +class AppTest { + @Test + fun testApp() { + appCommon() + assertEquals(foo(), fooCommon()) + assertEquals(bar(), barCommon()) + // fooJvmAndJs() // unresolved + // fooLinuxAndJs() // unresolved + // barJvmAndJs // unresolved + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmAndJsMain/kotlin/AppJvmAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmAndJsMain/kotlin/AppJvmAndJs.kt new file mode 100644 index 00000000000..7d8a7d875ff --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmAndJsMain/kotlin/AppJvmAndJs.kt @@ -0,0 +1,17 @@ +package com.example.app + +import com.example.foo.* +import com.example.bar.* +import com.example.thirdparty.thirdPartyFun + +fun appJvmAndJs() { + fooCommon() + fooJvmAndJs() + // fooLinuxAndJs() //unresolved + + barCommon() + barJvmAndJs() + // barLinuxAndJs() // unresolved + + thirdPartyFun() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmAndJsTest/kotlin/AppJvmAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmAndJsTest/kotlin/AppJvmAndJsTest.kt new file mode 100644 index 00000000000..ac097bccc6c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmAndJsTest/kotlin/AppJvmAndJsTest.kt @@ -0,0 +1,21 @@ +package com.example.app + +import com.example.bar.barCommon +import com.example.bar.barJvmAndJs +import com.example.foo.fooCommon +import com.example.foo.fooJvmAndJs +import com.example.thirdparty.thirdPartyFun + +fun testAppJvmAndJs() { + fooCommon() + fooJvmAndJs() + // fooLinuxAndJs() //unresolved + + barCommon() + barJvmAndJs() + // barLinuxAndJs() // unresolved + + thirdPartyFun() + + appJvmAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmMain/kotlin/AppJvm.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmMain/kotlin/AppJvm.kt new file mode 100644 index 00000000000..4c5712235ce --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/jvmMain/kotlin/AppJvm.kt @@ -0,0 +1,19 @@ +package com.example.app + +import com.example.bar.barCommon +import com.example.bar.barJvmAndJs +import com.example.foo.fooCommon +import com.example.foo.fooJvmAndJs +import com.example.thirdparty.thirdPartyFun +import com.example.thirdparty.thirdPartyJvmFun + +fun main() { + thirdPartyFun() + thirdPartyJvmFun() + + fooCommon() + fooJvmAndJs() + + barCommon() + barJvmAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxAndJsMain/kotlin/AppLinuxAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxAndJsMain/kotlin/AppLinuxAndJs.kt new file mode 100644 index 00000000000..37bfa3e4128 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxAndJsMain/kotlin/AppLinuxAndJs.kt @@ -0,0 +1,18 @@ +package com.example.app + +import com.example.bar.barCommon +import com.example.bar.barLinuxAndJs +import com.example.foo.fooCommon +import com.example.foo.fooLinuxAndJs + +fun appLinuxAndJs() { + fooCommon() + fooLinuxAndJs() + // fooJvmAndJs() // unresolved + + barCommon() + barLinuxAndJs() + // barJvmAndJs() // unresolved + + // thirdPartyFun() // unresolved +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxAndJsTest/kotlin/AppLinuxAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxAndJsTest/kotlin/AppLinuxAndJsTest.kt new file mode 100644 index 00000000000..455ae2d2d20 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxAndJsTest/kotlin/AppLinuxAndJsTest.kt @@ -0,0 +1,20 @@ +package com.example.app + +import com.example.bar.barCommon +import com.example.bar.barLinuxAndJs +import com.example.foo.fooCommon +import com.example.foo.fooLinuxAndJs + +fun testAppLinuxAndJs() { + fooCommon() + fooLinuxAndJs() + // fooJvmAndJs() // unresolved + + barCommon() + barLinuxAndJs() + // barJvmAndJs() // unresolved + + // thirdPartyFun() // unresolved + + appLinuxAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxX64Main/kotlin/AppLinux.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxX64Main/kotlin/AppLinux.kt new file mode 100644 index 00000000000..9f99d558228 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-app/src/linuxX64Main/kotlin/AppLinux.kt @@ -0,0 +1,8 @@ +package com.example.bar + +import com.example.foo.* + +fun appLinux() { + fooLinuxAndJs() + barLinuxAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/build.gradle.kts new file mode 100644 index 00000000000..2bc509d15ea --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/build.gradle.kts @@ -0,0 +1,95 @@ +plugins { + kotlin("multiplatform") + `maven-publish` +} + +repositories { + mavenLocal() + jcenter() +} + +group = "com.example.bar" +version = "1.0" + +kotlin { + jvm() + js() + + linuxX64() + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":my-lib-foo")) + 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) + } + + val linuxAndJsMain by creating { + dependsOn(commonMain) + } + + val linuxAndJsTest 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) + dependsOn(linuxAndJsMain) + dependencies { + implementation(kotlin("stdlib-js")) + } + } + + js().compilations["test"].defaultSourceSet { + dependsOn(jvmAndJsTest) + dependsOn(linuxAndJsTest) + dependencies { + implementation(kotlin("test-js")) + } + } + + linuxX64().compilations["main"].defaultSourceSet { + dependsOn(linuxAndJsMain) + } + + linuxX64().compilations["test"].defaultSourceSet { + dependsOn(linuxAndJsTest) + } + } +} + +publishing { + repositories { + maven("$rootDir/../repo") + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/commonMain/kotlin/Bar.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/commonMain/kotlin/Bar.kt new file mode 100644 index 00000000000..f3984fcad04 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/commonMain/kotlin/Bar.kt @@ -0,0 +1,11 @@ +package com.example.bar + +import com.example.foo.* + +expect fun bar(): String + +fun barCommon(): String { + foo() + // thirdPartyFun() //unresolved + return fooCommon() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/commonTest/kotlin/BarTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/commonTest/kotlin/BarTest.kt new file mode 100644 index 00000000000..c91c810b8a7 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/commonTest/kotlin/BarTest.kt @@ -0,0 +1,17 @@ +package com.example.bar + +import com.example.foo.foo +import com.example.foo.fooCommon +import kotlin.test.Test +import kotlin.test.assertEquals + +class BarTest { + @Test + fun testBar() { + // thirdPartyFun() // unresolved + // fooJvmAndJs() // unresolved + // fooLinuxAndJs() // unresolved + assertEquals(foo(), fooCommon()) + assertEquals(bar(), barCommon()) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt new file mode 100644 index 00000000000..ce87255cbb2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt @@ -0,0 +1,13 @@ +package com.example.bar + +import com.example.foo.* +import com.example.thirdparty.thirdPartyFun + +actual fun bar(): String = barJvmAndJs() + +fun barJvmAndJs(): String { + thirdPartyFun() + fooCommon() + // fooLinuxAndJs() //unresolved + return fooJvmAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt new file mode 100644 index 00000000000..70e56d8d349 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt @@ -0,0 +1,19 @@ +package com.example.bar + +import com.example.foo.foo +import com.example.thirdparty.thirdPartyFun +import kotlin.test.Test +import kotlin.test.assertEquals + +class BarJvmAndJsTest { + @Test + fun testBarJvmAndJs() { + // barLinuxAndJs() // unresolved + assertEquals(foo(), barJvmAndJs()) + } + + @Test + fun testThirdParty() { + thirdPartyFun() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt new file mode 100644 index 00000000000..c5c6e6a7de2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt @@ -0,0 +1,10 @@ +package com.example.bar + +import com.example.foo.* + +fun barLinuxAndJs(): String { + fooCommon() + // thirdPartyFun() // unresolved + // fooJvmAndJs() // unresolved + return fooLinuxAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt new file mode 100644 index 00000000000..fc738e0b218 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt @@ -0,0 +1,16 @@ +package com.example.bar + +import com.example.foo.fooCommon +import com.example.foo.fooLinuxAndJs +import kotlin.test.Test + +class BarLinuxAndJsTest { + @Test + fun testBarLinuxAndJs() { + fooCommon() + fooLinuxAndJs() + // fooJvmAndJs() // unresolved + // thirdPartyFun() // unresolved + barLinuxAndJs() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxX64Main/kotlin/BarLinux.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxX64Main/kotlin/BarLinux.kt new file mode 100644 index 00000000000..9756984eab8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-bar/src/linuxX64Main/kotlin/BarLinux.kt @@ -0,0 +1,10 @@ +package com.example.bar + +import com.example.foo.* + +actual fun bar(): String { + fooCommon() + fooLinuxAndJs() + // fooJvmAndJs() // unresolved + return barLinuxAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/build.gradle.kts new file mode 100644 index 00000000000..a760293b6e3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/build.gradle.kts @@ -0,0 +1,100 @@ +plugins { + kotlin("multiplatform") + `maven-publish` +} + +repositories { + mavenLocal() + jcenter() +} + +group = "com.example.foo" +version = "1.0" + +kotlin { + jvm() + js() + + // Add a target that the third-party-lib does not have: + linuxX64() + + 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) + dependencies { + // Add the third-party-lib dependency only to these two platforms, + // as an API dependency, so that it is seen as transitive: + api("com.example.thirdparty:third-party-lib:1.0") + } + } + + val jvmAndJsTest by creating { + dependsOn(commonTest) + } + + val linuxAndJsMain by creating { + dependsOn(commonMain) + } + + val linuxAndJsTest 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) + dependsOn(linuxAndJsMain) + dependencies { + implementation(kotlin("stdlib-js")) + } + } + + js().compilations["test"].defaultSourceSet { + dependsOn(jvmAndJsTest) + dependsOn(linuxAndJsTest) + dependencies { + implementation(kotlin("test-js")) + } + } + + linuxX64().compilations["main"].defaultSourceSet { + dependsOn(linuxAndJsMain) + } + + linuxX64().compilations["test"].defaultSourceSet { + dependsOn(linuxAndJsTest) + } + } +} + +publishing { + repositories { + maven("$rootDir/../repo") + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/commonMain/kotlin/Foo.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/commonMain/kotlin/Foo.kt new file mode 100644 index 00000000000..f39097eafd2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/commonMain/kotlin/Foo.kt @@ -0,0 +1,5 @@ +package com.example.foo + +expect fun foo(): String + +fun fooCommon() = "foo" \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/commonTest/kotlin/FooTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/commonTest/kotlin/FooTest.kt new file mode 100644 index 00000000000..5dc747175a6 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/commonTest/kotlin/FooTest.kt @@ -0,0 +1,11 @@ +package com.example.foo + +import kotlin.test.Test +import kotlin.test.assertEquals + +class FooTest { + @Test + fun testFoo() { + assertEquals(foo(), fooCommon()) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/jvmAndJsMain/kotlin/FooJvmAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/jvmAndJsMain/kotlin/FooJvmAndJs.kt new file mode 100644 index 00000000000..a7c110e4c7d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/jvmAndJsMain/kotlin/FooJvmAndJs.kt @@ -0,0 +1,10 @@ +package com.example.foo + +import com.example.thirdparty.thirdPartyFun + +actual fun foo() = fooJvmAndJs() + +fun fooJvmAndJs(): String { + thirdPartyFun() + return fooCommon() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/jvmAndJsTest/kotlin/FooJvmAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/jvmAndJsTest/kotlin/FooJvmAndJsTest.kt new file mode 100644 index 00000000000..be330a4252f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/jvmAndJsTest/kotlin/FooJvmAndJsTest.kt @@ -0,0 +1,17 @@ +package com.example.foo + +import com.example.thirdparty.thirdPartyFun +import kotlin.test.Test +import kotlin.test.assertEquals + +class FooJvmAndJsTest { + @Test + fun testFooJvmAndJs() { + assertEquals(fooJvmAndJs(), foo()) + } + + @Test + fun testThirdParty() { + thirdPartyFun() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxAndJsMain/kotlin/FooLinuxAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxAndJsMain/kotlin/FooLinuxAndJs.kt new file mode 100644 index 00000000000..b245a54ef5b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxAndJsMain/kotlin/FooLinuxAndJs.kt @@ -0,0 +1,3 @@ +package com.example.foo + +fun fooLinuxAndJs() = fooCommon() \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxAndJsTest/kotlin/FooLinuxAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxAndJsTest/kotlin/FooLinuxAndJsTest.kt new file mode 100644 index 00000000000..6e200b7e71a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxAndJsTest/kotlin/FooLinuxAndJsTest.kt @@ -0,0 +1,11 @@ +package com.example.foo + +import kotlin.test.* + +class FooLinuxAndJsTest { + @Test + fun testFooJvmAndJs() { + assertEquals(foo(), fooCommon()) + fooLinuxAndJs() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxX64Main/kotlin/FooLinux.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxX64Main/kotlin/FooLinux.kt new file mode 100644 index 00000000000..a0a6ed53a35 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/my-lib-foo/src/linuxX64Main/kotlin/FooLinux.kt @@ -0,0 +1,3 @@ +package com.example.foo + +actual fun foo() = fooLinuxAndJs() \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/settings.gradle.kts new file mode 100644 index 00000000000..239f39c62a9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/settings.gradle.kts @@ -0,0 +1,12 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +rootProject.name = "mpp-granular-metadata-demo" + +include("my-lib-foo", "my-lib-bar", "my-app") + +enableFeaturePreview("GRADLE_METADATA") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/build.gradle.kts new file mode 100644 index 00000000000..0d8b7682fd2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/build.gradle.kts @@ -0,0 +1,57 @@ +plugins { + kotlin("multiplatform").version("") + `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")) + } + } + jvm().compilations["main"].defaultSourceSet { + dependencies { + implementation(kotlin("stdlib")) + } + } + jvm().compilations["test"].defaultSourceSet { + dependencies { + implementation(kotlin("test-junit")) + } + } + js().compilations["main"].defaultSourceSet { + dependencies { + implementation(kotlin("stdlib-js")) + } + } + js().compilations["test"].defaultSourceSet { + dependencies { + implementation(kotlin("test-js")) + } + } + } +} + +publishing { + repositories { + maven("../repo") + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/settings.gradle.kts new file mode 100644 index 00000000000..055148414fb --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +rootProject.name = "third-party-lib" + +enableFeaturePreview("GRADLE_METADATA") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/commonMain/kotlin/ThirdParty.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/commonMain/kotlin/ThirdParty.kt new file mode 100644 index 00000000000..7f0e5c2b5d4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/commonMain/kotlin/ThirdParty.kt @@ -0,0 +1,3 @@ +package com.example.thirdparty + +expect fun thirdPartyFun(): String \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/jsMain/kotlin/ThirdPartyJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/jsMain/kotlin/ThirdPartyJs.kt new file mode 100644 index 00000000000..dba00046373 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/jsMain/kotlin/ThirdPartyJs.kt @@ -0,0 +1,5 @@ +package com.example.thirdparty + +actual fun thirdPartyFun(): String = "thirdPartyFun @ js" + +fun thirdPartyJsFun() { } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/jvmMain/kotlin/ThirdPartyJvm.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/jvmMain/kotlin/ThirdPartyJvm.kt new file mode 100644 index 00000000000..ad7e11903d8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-project-dependency/third-party-lib/src/jvmMain/kotlin/ThirdPartyJvm.kt @@ -0,0 +1,5 @@ +package com.example.thirdparty + +actual fun thirdPartyFun(): String = "thirdPartyFun @ jvm" + +fun thirdPartyJvmFun() { } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/build.gradle.kts new file mode 100644 index 00000000000..4efc0f9f3ac --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/build.gradle.kts @@ -0,0 +1,89 @@ +plugins { + kotlin("multiplatform").version("") +} + +repositories { + mavenLocal() + maven("../repo") + jcenter() +} + +group = "com.example.app" +version = "1.0" + +kotlin { + jvm() + js() + + linuxX64() + + sourceSets { + val commonMain by getting { + dependencies { + api("com.example.bar:my-lib-bar:1.0") + 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) + } + + val linuxAndJsMain by creating { + dependsOn(commonMain) + } + + val linuxAndJsTest 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) + dependsOn(linuxAndJsMain) + dependencies { + implementation(kotlin("stdlib-js")) + } + } + + js().compilations["test"].defaultSourceSet { + dependsOn(jvmAndJsTest) + dependsOn(linuxAndJsTest) + dependencies { + implementation(kotlin("test-js")) + } + } + + linuxX64().compilations["main"].defaultSourceSet { + dependsOn(linuxAndJsMain) + } + + linuxX64().compilations["test"].defaultSourceSet { + dependsOn(linuxAndJsTest) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/gradle.properties new file mode 100644 index 00000000000..2a391fe78b0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/gradle.properties @@ -0,0 +1 @@ +kotlin.mpp.enableGranularSourceSetsMetadata=true \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/settings.gradle.kts new file mode 100644 index 00000000000..290414f38b4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +rootProject.name = "my-app" + +enableFeaturePreview("GRADLE_METADATA") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/commonMain/kotlin/App.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/commonMain/kotlin/App.kt new file mode 100644 index 00000000000..deaca4eb24f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/commonMain/kotlin/App.kt @@ -0,0 +1,11 @@ +package com.example.app + +import com.example.foo.* +import com.example.bar.* + +fun appCommon() { + foo() + fooCommon() + bar() + barCommon() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/commonTest/kotlin/AppTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/commonTest/kotlin/AppTest.kt new file mode 100644 index 00000000000..397474b4d1a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/commonTest/kotlin/AppTest.kt @@ -0,0 +1,20 @@ +package com.example.app + +import com.example.bar.bar +import com.example.bar.barCommon +import com.example.foo.foo +import com.example.foo.fooCommon +import kotlin.test.Test +import kotlin.test.assertEquals + +class AppTest { + @Test + fun testApp() { + appCommon() + assertEquals(foo(), fooCommon()) + assertEquals(bar(), barCommon()) + // fooJvmAndJs() // unresolved + // fooLinuxAndJs() // unresolved + // barJvmAndJs // unresolved + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmAndJsMain/kotlin/AppJvmAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmAndJsMain/kotlin/AppJvmAndJs.kt new file mode 100644 index 00000000000..7d8a7d875ff --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmAndJsMain/kotlin/AppJvmAndJs.kt @@ -0,0 +1,17 @@ +package com.example.app + +import com.example.foo.* +import com.example.bar.* +import com.example.thirdparty.thirdPartyFun + +fun appJvmAndJs() { + fooCommon() + fooJvmAndJs() + // fooLinuxAndJs() //unresolved + + barCommon() + barJvmAndJs() + // barLinuxAndJs() // unresolved + + thirdPartyFun() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmAndJsTest/kotlin/AppJvmAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmAndJsTest/kotlin/AppJvmAndJsTest.kt new file mode 100644 index 00000000000..ac097bccc6c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmAndJsTest/kotlin/AppJvmAndJsTest.kt @@ -0,0 +1,21 @@ +package com.example.app + +import com.example.bar.barCommon +import com.example.bar.barJvmAndJs +import com.example.foo.fooCommon +import com.example.foo.fooJvmAndJs +import com.example.thirdparty.thirdPartyFun + +fun testAppJvmAndJs() { + fooCommon() + fooJvmAndJs() + // fooLinuxAndJs() //unresolved + + barCommon() + barJvmAndJs() + // barLinuxAndJs() // unresolved + + thirdPartyFun() + + appJvmAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmMain/kotlin/AppJvm.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmMain/kotlin/AppJvm.kt new file mode 100644 index 00000000000..4c5712235ce --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/jvmMain/kotlin/AppJvm.kt @@ -0,0 +1,19 @@ +package com.example.app + +import com.example.bar.barCommon +import com.example.bar.barJvmAndJs +import com.example.foo.fooCommon +import com.example.foo.fooJvmAndJs +import com.example.thirdparty.thirdPartyFun +import com.example.thirdparty.thirdPartyJvmFun + +fun main() { + thirdPartyFun() + thirdPartyJvmFun() + + fooCommon() + fooJvmAndJs() + + barCommon() + barJvmAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxAndJsMain/kotlin/AppLinuxAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxAndJsMain/kotlin/AppLinuxAndJs.kt new file mode 100644 index 00000000000..37bfa3e4128 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxAndJsMain/kotlin/AppLinuxAndJs.kt @@ -0,0 +1,18 @@ +package com.example.app + +import com.example.bar.barCommon +import com.example.bar.barLinuxAndJs +import com.example.foo.fooCommon +import com.example.foo.fooLinuxAndJs + +fun appLinuxAndJs() { + fooCommon() + fooLinuxAndJs() + // fooJvmAndJs() // unresolved + + barCommon() + barLinuxAndJs() + // barJvmAndJs() // unresolved + + // thirdPartyFun() // unresolved +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxAndJsTest/kotlin/AppLinuxAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxAndJsTest/kotlin/AppLinuxAndJsTest.kt new file mode 100644 index 00000000000..455ae2d2d20 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxAndJsTest/kotlin/AppLinuxAndJsTest.kt @@ -0,0 +1,20 @@ +package com.example.app + +import com.example.bar.barCommon +import com.example.bar.barLinuxAndJs +import com.example.foo.fooCommon +import com.example.foo.fooLinuxAndJs + +fun testAppLinuxAndJs() { + fooCommon() + fooLinuxAndJs() + // fooJvmAndJs() // unresolved + + barCommon() + barLinuxAndJs() + // barJvmAndJs() // unresolved + + // thirdPartyFun() // unresolved + + appLinuxAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxX64Main/kotlin/AppLinux.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxX64Main/kotlin/AppLinux.kt new file mode 100644 index 00000000000..9f99d558228 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-app/src/linuxX64Main/kotlin/AppLinux.kt @@ -0,0 +1,8 @@ +package com.example.bar + +import com.example.foo.* + +fun appLinux() { + fooLinuxAndJs() + barLinuxAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/build.gradle.kts new file mode 100644 index 00000000000..84cde994842 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/build.gradle.kts @@ -0,0 +1,96 @@ +plugins { + kotlin("multiplatform").version("") + `maven-publish` +} + +repositories { + mavenLocal() + maven("../repo") + jcenter() +} + +group = "com.example.bar" +version = "1.0" + +kotlin { + jvm() + js() + + linuxX64() + + sourceSets { + val commonMain by getting { + dependencies { + api("com.example.foo:my-lib-foo:1.0") + 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) + } + + val linuxAndJsMain by creating { + dependsOn(commonMain) + } + + val linuxAndJsTest 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) + dependsOn(linuxAndJsMain) + dependencies { + implementation(kotlin("stdlib-js")) + } + } + + js().compilations["test"].defaultSourceSet { + dependsOn(jvmAndJsTest) + dependsOn(linuxAndJsTest) + dependencies { + implementation(kotlin("test-js")) + } + } + + linuxX64().compilations["main"].defaultSourceSet { + dependsOn(linuxAndJsMain) + } + + linuxX64().compilations["test"].defaultSourceSet { + dependsOn(linuxAndJsTest) + } + } +} + +publishing { + repositories { + maven("../repo") + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/gradle.properties new file mode 100644 index 00000000000..2a391fe78b0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/gradle.properties @@ -0,0 +1 @@ +kotlin.mpp.enableGranularSourceSetsMetadata=true \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/settings.gradle.kts new file mode 100644 index 00000000000..17a7c7b3b1d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +rootProject.name = "my-lib-bar" + +enableFeaturePreview("GRADLE_METADATA") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/commonMain/kotlin/Bar.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/commonMain/kotlin/Bar.kt new file mode 100644 index 00000000000..f3984fcad04 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/commonMain/kotlin/Bar.kt @@ -0,0 +1,11 @@ +package com.example.bar + +import com.example.foo.* + +expect fun bar(): String + +fun barCommon(): String { + foo() + // thirdPartyFun() //unresolved + return fooCommon() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/commonTest/kotlin/BarTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/commonTest/kotlin/BarTest.kt new file mode 100644 index 00000000000..c91c810b8a7 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/commonTest/kotlin/BarTest.kt @@ -0,0 +1,17 @@ +package com.example.bar + +import com.example.foo.foo +import com.example.foo.fooCommon +import kotlin.test.Test +import kotlin.test.assertEquals + +class BarTest { + @Test + fun testBar() { + // thirdPartyFun() // unresolved + // fooJvmAndJs() // unresolved + // fooLinuxAndJs() // unresolved + assertEquals(foo(), fooCommon()) + assertEquals(bar(), barCommon()) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt new file mode 100644 index 00000000000..ce87255cbb2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt @@ -0,0 +1,13 @@ +package com.example.bar + +import com.example.foo.* +import com.example.thirdparty.thirdPartyFun + +actual fun bar(): String = barJvmAndJs() + +fun barJvmAndJs(): String { + thirdPartyFun() + fooCommon() + // fooLinuxAndJs() //unresolved + return fooJvmAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt new file mode 100644 index 00000000000..70e56d8d349 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt @@ -0,0 +1,19 @@ +package com.example.bar + +import com.example.foo.foo +import com.example.thirdparty.thirdPartyFun +import kotlin.test.Test +import kotlin.test.assertEquals + +class BarJvmAndJsTest { + @Test + fun testBarJvmAndJs() { + // barLinuxAndJs() // unresolved + assertEquals(foo(), barJvmAndJs()) + } + + @Test + fun testThirdParty() { + thirdPartyFun() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt new file mode 100644 index 00000000000..c5c6e6a7de2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt @@ -0,0 +1,10 @@ +package com.example.bar + +import com.example.foo.* + +fun barLinuxAndJs(): String { + fooCommon() + // thirdPartyFun() // unresolved + // fooJvmAndJs() // unresolved + return fooLinuxAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt new file mode 100644 index 00000000000..fc738e0b218 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt @@ -0,0 +1,16 @@ +package com.example.bar + +import com.example.foo.fooCommon +import com.example.foo.fooLinuxAndJs +import kotlin.test.Test + +class BarLinuxAndJsTest { + @Test + fun testBarLinuxAndJs() { + fooCommon() + fooLinuxAndJs() + // fooJvmAndJs() // unresolved + // thirdPartyFun() // unresolved + barLinuxAndJs() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxX64Main/kotlin/BarLinux.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxX64Main/kotlin/BarLinux.kt new file mode 100644 index 00000000000..9756984eab8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-bar/src/linuxX64Main/kotlin/BarLinux.kt @@ -0,0 +1,10 @@ +package com.example.bar + +import com.example.foo.* + +actual fun bar(): String { + fooCommon() + fooLinuxAndJs() + // fooJvmAndJs() // unresolved + return barLinuxAndJs() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/build.gradle.kts new file mode 100644 index 00000000000..e8dfda51a79 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/build.gradle.kts @@ -0,0 +1,101 @@ +plugins { + kotlin("multiplatform").version("") + `maven-publish` +} + +repositories { + mavenLocal() + maven("../repo") + jcenter() +} + +group = "com.example.foo" +version = "1.0" + +kotlin { + jvm() + js() + + // Add a target that the third-party-lib does not have: + linuxX64() + + 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) + dependencies { + // Add the third-party-lib dependency only to these two platforms, + // as an API dependency, so that it is seen as transitive: + api("com.example.thirdparty:third-party-lib:1.0") + } + } + + val jvmAndJsTest by creating { + dependsOn(commonTest) + } + + val linuxAndJsMain by creating { + dependsOn(commonMain) + } + + val linuxAndJsTest 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) + dependsOn(linuxAndJsMain) + dependencies { + implementation(kotlin("stdlib-js")) + } + } + + js().compilations["test"].defaultSourceSet { + dependsOn(jvmAndJsTest) + dependsOn(linuxAndJsTest) + dependencies { + implementation(kotlin("test-js")) + } + } + + linuxX64().compilations["main"].defaultSourceSet { + dependsOn(linuxAndJsMain) + } + + linuxX64().compilations["test"].defaultSourceSet { + dependsOn(linuxAndJsTest) + } + } +} + +publishing { + repositories { + maven("../repo") + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/gradle.properties new file mode 100644 index 00000000000..2a391fe78b0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/gradle.properties @@ -0,0 +1 @@ +kotlin.mpp.enableGranularSourceSetsMetadata=true \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/settings.gradle.kts new file mode 100644 index 00000000000..e0ae6817795 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +rootProject.name = "my-lib-foo" + +enableFeaturePreview("GRADLE_METADATA") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/commonMain/kotlin/Foo.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/commonMain/kotlin/Foo.kt new file mode 100644 index 00000000000..f39097eafd2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/commonMain/kotlin/Foo.kt @@ -0,0 +1,5 @@ +package com.example.foo + +expect fun foo(): String + +fun fooCommon() = "foo" \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/commonTest/kotlin/FooTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/commonTest/kotlin/FooTest.kt new file mode 100644 index 00000000000..5dc747175a6 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/commonTest/kotlin/FooTest.kt @@ -0,0 +1,11 @@ +package com.example.foo + +import kotlin.test.Test +import kotlin.test.assertEquals + +class FooTest { + @Test + fun testFoo() { + assertEquals(foo(), fooCommon()) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/jvmAndJsMain/kotlin/FooJvmAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/jvmAndJsMain/kotlin/FooJvmAndJs.kt new file mode 100644 index 00000000000..a7c110e4c7d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/jvmAndJsMain/kotlin/FooJvmAndJs.kt @@ -0,0 +1,10 @@ +package com.example.foo + +import com.example.thirdparty.thirdPartyFun + +actual fun foo() = fooJvmAndJs() + +fun fooJvmAndJs(): String { + thirdPartyFun() + return fooCommon() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/jvmAndJsTest/kotlin/FooJvmAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/jvmAndJsTest/kotlin/FooJvmAndJsTest.kt new file mode 100644 index 00000000000..be330a4252f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/jvmAndJsTest/kotlin/FooJvmAndJsTest.kt @@ -0,0 +1,17 @@ +package com.example.foo + +import com.example.thirdparty.thirdPartyFun +import kotlin.test.Test +import kotlin.test.assertEquals + +class FooJvmAndJsTest { + @Test + fun testFooJvmAndJs() { + assertEquals(fooJvmAndJs(), foo()) + } + + @Test + fun testThirdParty() { + thirdPartyFun() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxAndJsMain/kotlin/FooLinuxAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxAndJsMain/kotlin/FooLinuxAndJs.kt new file mode 100644 index 00000000000..b245a54ef5b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxAndJsMain/kotlin/FooLinuxAndJs.kt @@ -0,0 +1,3 @@ +package com.example.foo + +fun fooLinuxAndJs() = fooCommon() \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxAndJsTest/kotlin/FooLinuxAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxAndJsTest/kotlin/FooLinuxAndJsTest.kt new file mode 100644 index 00000000000..c62b0de41c6 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxAndJsTest/kotlin/FooLinuxAndJsTest.kt @@ -0,0 +1,9 @@ +package com.example.foo + +class FooLinuxAndJsTest { + @Test + fun testFooJvmAndJs() { + assertEquals(foo(), fooCommon()) + fooLinuxAndJs() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxX64Main/kotlin/FooLinux.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxX64Main/kotlin/FooLinux.kt new file mode 100644 index 00000000000..a0a6ed53a35 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/my-lib-foo/src/linuxX64Main/kotlin/FooLinux.kt @@ -0,0 +1,3 @@ +package com.example.foo + +actual fun foo() = fooLinuxAndJs() \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/build.gradle.kts new file mode 100644 index 00000000000..0d8b7682fd2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/build.gradle.kts @@ -0,0 +1,57 @@ +plugins { + kotlin("multiplatform").version("") + `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")) + } + } + jvm().compilations["main"].defaultSourceSet { + dependencies { + implementation(kotlin("stdlib")) + } + } + jvm().compilations["test"].defaultSourceSet { + dependencies { + implementation(kotlin("test-junit")) + } + } + js().compilations["main"].defaultSourceSet { + dependencies { + implementation(kotlin("stdlib-js")) + } + } + js().compilations["test"].defaultSourceSet { + dependencies { + implementation(kotlin("test-js")) + } + } + } +} + +publishing { + repositories { + maven("../repo") + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/settings.gradle.kts new file mode 100644 index 00000000000..055148414fb --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +rootProject.name = "third-party-lib" + +enableFeaturePreview("GRADLE_METADATA") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/commonMain/kotlin/ThirdParty.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/commonMain/kotlin/ThirdParty.kt new file mode 100644 index 00000000000..7f0e5c2b5d4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/commonMain/kotlin/ThirdParty.kt @@ -0,0 +1,3 @@ +package com.example.thirdparty + +expect fun thirdPartyFun(): String \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/jsMain/kotlin/ThirdPartyJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/jsMain/kotlin/ThirdPartyJs.kt new file mode 100644 index 00000000000..dba00046373 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/jsMain/kotlin/ThirdPartyJs.kt @@ -0,0 +1,5 @@ +package com.example.thirdparty + +actual fun thirdPartyFun(): String = "thirdPartyFun @ js" + +fun thirdPartyJsFun() { } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/jvmMain/kotlin/ThirdPartyJvm.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/jvmMain/kotlin/ThirdPartyJvm.kt new file mode 100644 index 00000000000..ad7e11903d8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/hierarchical-mpp-published-modules/third-party-lib/src/jvmMain/kotlin/ThirdPartyJvm.kt @@ -0,0 +1,5 @@ +package com.example.thirdparty + +actual fun thirdPartyFun(): String = "thirdPartyFun @ jvm" + +fun thirdPartyJvmFun() { } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index d77d4d45d69..00e562827aa 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -86,7 +86,7 @@ abstract class KotlinBasePluginWrapper( private fun setupAttributeMatchingStrategy(project: Project) = with(project.dependencies.attributesSchema) { KotlinPlatformType.setupAttributesMatchingStrategy(this) - KotlinUsages.setupAttributesMatchingStrategy(this) + KotlinUsages.setupAttributesMatchingStrategy(project, this) ProjectLocalConfigurations.setupAttributesMatchingStrategy(this) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt index 7aaf2d3192e..c0bb0832832 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt @@ -88,6 +88,9 @@ internal class PropertiesProvider(private val project: Project) { val setJvmTargetFromAndroidCompileOptions: Boolean? get() = booleanProperty("kotlin.setJvmTargetFromAndroidCompileOptions") + val enableGranularSourceSetsMetadata: Boolean? + get() = booleanProperty("kotlin.mpp.enableGranularSourceSetsMetadata") + /** * Enables parallel tasks execution within a project with Workers API. * Does not enable using actual worker proccesses diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt index 12ec60f5e58..1248c7e51c1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt @@ -22,6 +22,7 @@ import org.gradle.api.tasks.Delete import org.gradle.api.tasks.bundling.Jar import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.language.jvm.tasks.ProcessResources +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest @@ -54,31 +55,28 @@ abstract class AbstractKotlinTargetConfigurator cleanTask.delete(kotlinCompilation.output.allOutputs) } + protected open fun setupCompilationDependencyFiles(project: Project, compilation: KotlinCompilation) { + compilation.compileDependencyFiles = project.configurations.maybeCreate(compilation.compileDependencyConfigurationName) + if (compilation is KotlinCompilationToRunnableFiles) { + compilation.runtimeDependencyFiles = project.configurations.maybeCreate(compilation.runtimeDependencyConfigurationName) + } + } + protected open fun configureCompilations(platformTarget: KotlinTargetType) { val project = platformTarget.project val main = platformTarget.compilations.create(KotlinCompilation.MAIN_COMPILATION_NAME) platformTarget.compilations.all { project.registerOutputsForStaleOutputCleanup(it) - it.compileDependencyFiles = project.configurations.maybeCreate(it.compileDependencyConfigurationName) - if (it is KotlinCompilationToRunnableFiles) { - it.runtimeDependencyFiles = project.configurations.maybeCreate(it.runtimeDependencyConfigurationName) - } + setupCompilationDependencyFiles(project, it) } if (createTestCompilation) { platformTarget.compilations.create(KotlinCompilation.TEST_COMPILATION_NAME).apply { - compileDependencyFiles = project.files( - main.output.allOutputs, - project.configurations.maybeCreate(compileDependencyConfigurationName) - ) + compileDependencyFiles += main.output.allOutputs if (this is KotlinCompilationToRunnableFiles) { - runtimeDependencyFiles = project.files( - output.allOutputs, - main.output.allOutputs, - project.configurations.maybeCreate(runtimeDependencyConfigurationName) - ) + runtimeDependencyFiles += project.files(output.allOutputs, main.output.allOutputs) } } } @@ -328,17 +326,21 @@ abstract class KotlinTargetConfigurator) { + val result = target.project.tasks.create(target.artifactsTaskName, Jar::class.java) + result.description = "Assembles a jar archive containing the main classes." + result.group = BasePlugin.BUILD_GROUP + result.from(target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).output.allOutputs) + } + override fun configureArchivesAndComponent(target: KotlinOnlyTarget) { val project = target.project val mainCompilation = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) - val jar = project.tasks.create(target.artifactsTaskName, Jar::class.java) - jar.description = "Assembles a jar archive containing the main classes." - jar.group = BasePlugin.BUILD_GROUP - jar.from(mainCompilation.output.allOutputs) - - val apiElementsConfiguration = project.configurations.getByName(target.apiElementsConfigurationName) + createJarTasks(target) + val jar = project.tasks.getByName(target.artifactsTaskName) as Jar target.disambiguationClassifier?.let { jar.appendix = it.toLowerCase() } @@ -349,6 +351,7 @@ abstract class KotlinTargetConfigurator) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GenerateProjectStructureMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GenerateProjectStructureMetadata.kt new file mode 100644 index 00000000000..7d091ef352f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GenerateProjectStructureMetadata.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import java.io.File +import javax.xml.transform.OutputKeys +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + +open class GenerateProjectStructureMetadata : DefaultTask() { + @get:Internal + internal lateinit var lazyKotlinProjectStructureMetadata: Lazy + + @get:Nested + internal val kotlinProjectStructureMetadata: KotlinProjectStructureMetadata + get() = lazyKotlinProjectStructureMetadata.value + + @get:OutputFile + val resultXmlFile: File + get() = project.buildDir.resolve("kotlinProjectStructureMetadata/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME") + + @TaskAction + fun generateMetadataXml() { + resultXmlFile.parentFile.mkdirs() + + val document = kotlinProjectStructureMetadata.toXmlDocument() + + TransformerFactory.newInstance().newTransformer().apply { + setOutputProperty(OutputKeys.INDENT, "yes") + setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4") + }.transform(DOMSource(document), StreamResult(resultXmlFile)) + } +} + +const val MULTIPLATFORM_PROJECT_METADATA_FILE_NAME = "kotlin-project-structure-metadata.xml" \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt new file mode 100644 index 00000000000..92ed1aa8556 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt @@ -0,0 +1,375 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp + +import org.gradle.api.Project +import org.gradle.api.artifacts.* +import org.gradle.api.file.FileCollection +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +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 java.io.File +import java.util.* +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream +import javax.xml.parsers.DocumentBuilderFactory + +internal sealed class MetadataDependencyResolution( + val dependency: ResolvedDependency, + val projectDependency: ProjectDependency? +) { + override fun toString(): String { + val verb = when (this) { + is KeepOriginalDependency -> "keep" + is ExcludeAsUnrequested -> "exclude" + is ChooseVisibleSourceSets -> "choose" + } + return "$verb, dependency = $dependency" + } + + class KeepOriginalDependency( + dependency: ResolvedDependency, + projectDependency: ProjectDependency? + ) : MetadataDependencyResolution(dependency, projectDependency) + + class ExcludeAsUnrequested( + dependency: ResolvedDependency, + projectDependency: ProjectDependency? + ) : MetadataDependencyResolution(dependency, projectDependency) + + abstract class ChooseVisibleSourceSets( + dependency: ResolvedDependency, + projectDependency: ProjectDependency?, + val projectStructureMetadata: KotlinProjectStructureMetadata, + val allVisibleSourceSetNames: Set, + val visibleSourceSetNamesExcludingDependsOn: Set, + val visibleTransitiveDependencies: Set + ) : MetadataDependencyResolution(dependency, projectDependency) { + /** Returns the mapping of source set names to files which should be used as the [dependency] parts representing the source sets. + * If any temporary files need to be created, their paths are built from the [baseDir]. + * If [doProcessFiles] is true, these temporary files are actually re-created during the call, + * otherwise only their paths are returned, while the files might be missing. + */ + abstract fun getMetadataFilesBySourceSet(baseDir: File, doProcessFiles: Boolean): Map + + override fun toString(): String = + super.toString() + ", sourceSets = " + allVisibleSourceSetNames.joinToString(", ", "[", "]") { + (if (it in visibleSourceSetNamesExcludingDependsOn) "*" else "") + it + } + } +} + +private typealias ModuleId = Pair // group ID, artifact ID + +private val ResolvedDependency.moduleId: ModuleId + get() = moduleGroup to moduleName + +internal class GranularMetadataTransformation( + val project: Project, + val kotlinSourceSet: KotlinSourceSet, + /** A list of scopes that the dependencies from [kotlinSourceSet] are treated as requested dependencies. */ + val sourceSetRequestedScopes: List, + /** A configuration that holds the dependencies of the appropriate scope for all Kotlin source sets in the project */ + val allSourceSetsConfigurations: Iterable +) { + val metadataDependencyResolutions: Iterable by lazy { doTransform() } + + // Keep parents of each dependency, too. We need a dependency's parent when it's an MPP's metadata module dependency: + // in this case, the parent is the MPP's root module. + private data class ResolvedDependencyWithParent( + val dependency: ResolvedDependency, + val parent: ResolvedDependency? + ) + + private fun collectProjectDependencies( + requestedDependencies: Iterable, + resolvedDependencies: Iterable + ): Map { + val result = mutableMapOf() + + val resolvedDependenciesMap: Map = resolvedDependencies.associateBy { it.moduleId } + + fun visitProjectDependency(projectDependency: ProjectDependency) { + val moduleId = projectDependency.group to projectDependency.name + + if (moduleId in result) return + result[moduleId] = projectDependency + + val resolvedDependency = resolvedDependenciesMap[moduleId] ?: return + + projectDependency.dependencyProject.configurations.getByName(resolvedDependency.configuration) + .allDependencies + .withType(ProjectDependency::class.java) + .forEach(::visitProjectDependency) + } + + requestedDependencies.forEach(::visitProjectDependency) + + return result + } + + private fun getRequestedDependencies(kotlinSourceSet: KotlinSourceSet): Set { + val hierarchy = kotlinSourceSet.getSourceSetHierarchy().toMutableSet() + + // This is an ad-hoc mechanism for exposing the commonMain dependencies to test source sets as well: + // TODO once a general production-test visibility mechanism is implemented, replace this workaround with the general solution + if (hierarchy.any { it.name == KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME }) { + hierarchy += project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME).getSourceSetHierarchy() + } + + return hierarchy.flatMapTo(mutableSetOf()) { sourceSet -> + sourceSetRequestedScopes.flatMap { scope -> + project.sourceSetDependencyConfigurationByScope(sourceSet, scope).allDependencies + } + } + } + + private fun doTransform(): Iterable { + val result = mutableListOf() + + val resolvedDependenciesFromAllSourceSets = allSourceSetsConfigurations.map { it.resolvedConfiguration.lenientConfiguration } + + val visitedDependencies = mutableSetOf() + + val directRequestedDependencies = getRequestedDependencies(kotlinSourceSet) + + val directRequestedModules: Set = directRequestedDependencies.mapTo(mutableSetOf()) { it.group to it.name } + + val allModuleDependencies = resolvedDependenciesFromAllSourceSets.flatMapTo(mutableSetOf()) { it.allModuleDependencies } + + val knownProjectDependencies = collectProjectDependencies( + directRequestedDependencies.filterIsInstance(), + allModuleDependencies + ) + + val resolvedDependencyQueue: Queue = ArrayDeque().apply { + addAll( + resolvedDependenciesFromAllSourceSets.flatMap { it.firstLevelModuleDependencies } + .filter { it.moduleId in directRequestedModules } + .map { ResolvedDependencyWithParent(it, null) } + ) + } + + while (resolvedDependencyQueue.isNotEmpty()) { + val (resolvedDependency, parent: ResolvedDependency?) = resolvedDependencyQueue.poll() + + val projectDependency: ProjectDependency? = knownProjectDependencies[resolvedDependency.moduleId] + + visitedDependencies.add(resolvedDependency) + + val dependencyResult = processDependency(resolvedDependency, parent, projectDependency) + result.add(dependencyResult) + + val transitiveDependenciesToVisit = when (dependencyResult) { + is MetadataDependencyResolution.KeepOriginalDependency -> resolvedDependency.children + is MetadataDependencyResolution.ChooseVisibleSourceSets -> dependencyResult.visibleTransitiveDependencies + is MetadataDependencyResolution.ExcludeAsUnrequested -> error("a visited dependency is erroneously considered unrequested") + } + + resolvedDependencyQueue.addAll( + transitiveDependenciesToVisit.filter { it !in visitedDependencies } + .map { ResolvedDependencyWithParent(it, resolvedDependency) } + ) + } + + allModuleDependencies.forEach { resolvedDependency -> + if (resolvedDependency !in visitedDependencies) { + result.add( + MetadataDependencyResolution.ExcludeAsUnrequested( + resolvedDependency, + knownProjectDependencies[resolvedDependency.moduleGroup to resolvedDependency.moduleName] + ) + ) + } + } + + return result + } + + /** + * If the [module] is an MPP metadata module, we extract [KotlinProjectStructureMetadata] and do the following: + * + * * get the [KotlinProjectStructureMetadata] from the dependency (either deserialize from the artifact or build from the project) + * + * * determine the set *S* of source sets that should be seen in the [kotlinSourceSet] by finding which variants the [parent] + * dependency got resolved for the compilations where [kotlinSourceSet] participates: + * + * * transform the single Kotlin metadata artifact into a set of Kotlin metadata artifacts for the particular source sets in + * *S* and add the results as [MetadataDependencyResolution.ChooseVisibleSourceSets] + * + * * based on the project structure metadata, determine which of the module's dependencies are requested by the + * source sets in *S*, then consider only these transitive dependencies, ignore the others; + */ + private fun processDependency( + module: ResolvedDependency, + parent: ResolvedDependency?, + projectDependency: ProjectDependency? + ): MetadataDependencyResolution { + + val mppDependencyMetadataExtractor = when { + projectDependency != null -> ProjectMppDependencyMetadataExtractor(project, module, projectDependency.dependencyProject) + parent != null -> JarArtifactMppDependencyMetadataExtractor(project, module) + else -> null + } + + val projectStructureMetadata = mppDependencyMetadataExtractor?.getProjectStructureMetadata() + + if (projectStructureMetadata == null) { + return MetadataDependencyResolution.KeepOriginalDependency(module, projectDependency) + } + + val (allVisibleSourceSets, visibleByParents) = + SourceSetVisibilityProvider(project).getVisibleSourceSets( + kotlinSourceSet, + sourceSetRequestedScopes, + parent ?: module, + projectStructureMetadata, + projectDependency?.dependencyProject + ) + + // Keep only the transitive dependencies requested by the visible source sets: + // Visit the transitive dependencies visible by parents, too (i.e. allVisibleSourceSets), as this source set might get a more + // concrete view on them: + val requestedTransitiveDependencies: Set = + mutableSetOf().apply { + projectStructureMetadata.sourceSetModuleDependencies + .filterKeys { it in allVisibleSourceSets } + .forEach { addAll(it.value) } + } + + val transitiveDependenciesToVisit = module.children.filterTo(mutableSetOf()) { + (it.moduleId) in requestedTransitiveDependencies + } + + val visibleSourceSetsExcludingDependsOn = allVisibleSourceSets.filterTo(mutableSetOf()) { it !in visibleByParents } + + return object : MetadataDependencyResolution.ChooseVisibleSourceSets( + module, + projectDependency, + projectStructureMetadata, + allVisibleSourceSets, + visibleSourceSetsExcludingDependsOn, + transitiveDependenciesToVisit + ) { + override fun getMetadataFilesBySourceSet(baseDir: File, doProcessFiles: Boolean): Map = + mppDependencyMetadataExtractor.getVisibleSourceSetsMetadata(visibleSourceSetsExcludingDependsOn, baseDir, doProcessFiles) + } + } +} + +private abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedDependency) { + abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? + abstract fun getVisibleSourceSetsMetadata( + visibleSourceSetNames: Set, + baseDir: File, + doProcessFiles: Boolean + ): Map +} + +private class ProjectMppDependencyMetadataExtractor( + project: Project, + dependency: ResolvedDependency, + private val dependencyProject: Project +) : MppDependencyMetadataExtractor(project, dependency) { + override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? = + buildKotlinProjectStructureMetadata(dependencyProject) + + override fun getVisibleSourceSetsMetadata( + visibleSourceSetNames: Set, + baseDir: File, + doProcessFiles: Boolean + ): Map = + dependencyProject.multiplatformExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME).compilations + .filter { it.defaultSourceSet.name in visibleSourceSetNames } + .associate { it.defaultSourceSet.name to it.output.classesDirs } +} + +private class JarArtifactMppDependencyMetadataExtractor( + project: Project, + dependency: ResolvedDependency +) : MppDependencyMetadataExtractor(project, dependency) { + + private val artifact: ResolvedArtifact? + get() = dependency.moduleArtifacts.singleOrNull { it.extension == "jar" } + + override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? { + val artifactFile = artifact?.file ?: return null + + return ZipFile(artifactFile).use { zip -> + val metadata = zip.getEntry("META-INF/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME") + ?: return null + + val metadataXmlDocument = zip.getInputStream(metadata).use { inputStream -> + DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream) + } + + parseKotlinSourceSetMetadataFromXml(metadataXmlDocument) + } + } + + override fun getVisibleSourceSetsMetadata( + visibleSourceSetNames: Set, + baseDir: File, + doProcessFiles: Boolean + ): Map { + val jarArtifact = artifact ?: return emptyMap() + val artifactFile = jarArtifact.file + val moduleId = jarArtifact.moduleVersion.id + + return extractSourceSetMetadataFromJar(moduleId, visibleSourceSetNames, artifactFile, baseDir, doProcessFiles) + } + + private fun extractSourceSetMetadataFromJar( + module: ModuleVersionIdentifier, + chooseSourceSetsByNames: Set, + artifactJar: File, + baseDir: File, + doProcessFiles: Boolean + ): Map { + val moduleString = "${module.group}-${module.name}-${module.version}" + val transformedModuleRoot = run { baseDir.resolve(moduleString).also { it.mkdirs() } } + + val resultFiles = mutableMapOf() + + ZipFile(artifactJar).use { zip -> + val entriesBySourceSet = zip.entries().asSequence() + .groupBy { it.name.substringBefore("/") } + .filterKeys { it in chooseSourceSetsByNames } + + entriesBySourceSet.forEach { (sourceSetName, entries) -> + // TODO: once IJ supports non-JAR metadata dependencies, extraact to a directory, not a JAR + // Also, if both IJ and the CLI compiler can read metadata from a path inside a JAR, then no operations will be needed + val extractToJarFile = transformedModuleRoot.resolve("$moduleString-$sourceSetName.jar") + + resultFiles[sourceSetName] = project.files(extractToJarFile) + + if (doProcessFiles) { + ZipOutputStream(extractToJarFile.outputStream()).use { resultZipOutput -> + for (entry in entries) { + if (entry.isDirectory) + continue + + // Drop the source set name from the entry path + val resultEntry = ZipEntry(entry.name.substringAfter("/")) + + zip.getInputStream(entry).use { inputStream -> + resultZipOutput.putNextEntry(resultEntry) + inputStream.copyTo(resultZipOutput) + resultZipOutput.closeEntry() + } + } + } + } + } + } + + return resultFiles + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCommonCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCommonCompilation.kt index a5474d91fd1..8fc03bc03dc 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCommonCompilation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCommonCompilation.kt @@ -19,11 +19,13 @@ class KotlinCommonCompilation( override val compileKotlinTask: KotlinCompileCommon get() = super.compileKotlinTask as KotlinCompileCommon - // TODO once we properly compile metadata for each source set, the default source sets will likely become just the source sets - // which are transformed to metadata - private val commonSourceSetName = when (compilationName) { - KotlinCompilation.MAIN_COMPILATION_NAME -> KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME - else -> error("Custom metadata compilations are not supported yet") + private val commonSourceSetName by lazy { + when (compilationName) { + // Historically, a metadata target has a main compilation. We keep using it to compile just the commonMain source set: + KotlinCompilation.MAIN_COMPILATION_NAME -> KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME + // All other common source sets are compiled by compilations named according to the source sets: + else -> compilationName + } } override val defaultSourceSet: KotlinSourceSet diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMetadataTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMetadataTarget.kt new file mode 100644 index 00000000000..a909772fd48 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMetadataTarget.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp + +import org.gradle.api.Project +import org.gradle.api.artifacts.ConfigurablePublishArtifact +import org.gradle.api.artifacts.Dependency +import org.gradle.api.attributes.Usage.JAVA_API +import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope +import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope +import org.jetbrains.kotlin.gradle.targets.metadata.KotlinMetadataTargetConfigurator +import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled +import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast + +internal const val COMMON_MAIN_ELEMENTS_CONFIGURATION_NAME = "commonMainMetadataElements" + +open class KotlinMetadataTarget(project: Project) : KotlinOnlyTarget(project, KotlinPlatformType.common) { + override val kotlinComponents: Set by lazy { + if (!project.isKotlinGranularMetadataEnabled) + super.kotlinComponents + else { + val usageContexts = mutableSetOf() + + // This usage value is only needed for Maven scope mapping. Don't replace it with a custom Kotlin Usage value + val javaApiUsage = project.usageByName(if (isGradleVersionAtLeast(5, 3)) "java-api-jars" else JAVA_API) + + usageContexts += run { + val allMetadataJar = project.tasks.getByName(KotlinMetadataTargetConfigurator.ALL_METADATA_JAR_NAME) + val allMetadataArtifact = project.artifacts.add(Dependency.ARCHIVES_CONFIGURATION, allMetadataJar) { it.classifier = "all" } + + DefaultKotlinUsageContext( + compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME), + javaApiUsage, + apiElementsConfigurationName, + overrideConfigurationArtifacts = setOf(allMetadataArtifact) + ) + } + + // Ensure that consumers who expect Kotlin 1.2.x metadata package can still get one: publish the old metadata artifact as well: + usageContexts += run { + project.configurations.create(COMMON_MAIN_ELEMENTS_CONFIGURATION_NAME).apply { + isCanBeConsumed = false + isCanBeResolved = false + usesPlatformOf(this@KotlinMetadataTarget) + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(this@KotlinMetadataTarget)) // 'kotlin-api' usage + + val commonMainApiConfiguration = project.sourceSetDependencyConfigurationByScope( + project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME), + KotlinDependencyScope.API_SCOPE + ) + extendsFrom(commonMainApiConfiguration) + + project.artifacts.add(name, project.tasks.getByName(artifactsTaskName)) + } + + DefaultKotlinUsageContext( + compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME), + javaApiUsage, + COMMON_MAIN_ELEMENTS_CONFIGURATION_NAME + ) + } + + val component = + createKotlinVariant(targetName, compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME), usageContexts) + + val sourcesJarTask = + sourcesJarTask(project, lazy { project.kotlinExtension.sourceSets.toSet() }, null, targetName.toLowerCase()) + + component.sourcesArtifacts = setOf( + project.artifacts.add(Dependency.ARCHIVES_CONFIGURATION, sourcesJarTask).apply { + this as ConfigurablePublishArtifact + classifier = "sources" + } + ) + + setOf(component) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index b139a42fa47..69f2d49d2f0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder import org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubplugin +import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName @@ -98,12 +99,12 @@ class KotlinMultiplatformPlugin( private fun setupCompilerPluginOptions(project: Project) { // common source sets use the compiler options from the metadata compilation: val metadataCompilation = - project.multiplatformExtension.targets - .getByName(METADATA_TARGET_NAME) - .compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + project.multiplatformExtension.metadata().compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) val primaryCompilationsBySourceSet by lazy { // don't evaluate eagerly: Android targets are not created at this point - val allCompilationsForSourceSets = compilationsBySourceSet(project) + val allCompilationsForSourceSets = CompilationSourceSetUtil.compilationsBySourceSets(project).mapValues { (_, compilations) -> + compilations.filter { it.target.platformType != KotlinPlatformType.common } + } allCompilationsForSourceSets.mapValues { (_, compilations) -> // choose one primary compilation when (compilations.size) { @@ -175,8 +176,9 @@ class KotlinMultiplatformPlugin( targets .withType(AbstractKotlinTarget::class.java).matching { it.publishable && it.name != METADATA_TARGET_NAME } .all { - if (it is KotlinAndroidTarget) + if (it is KotlinAndroidTarget || it is KotlinMetadataTarget) // Android targets have their variants created in afterEvaluate; TODO handle this better? + // Kotlin Metadata targets rely on complete source sets hierearchy and cannot be inspected for publication earlier project.whenEvaluated { it.createMavenPublications(publishing.publications) } else it.createMavenPublications(publishing.publications) @@ -204,7 +206,9 @@ class KotlinMultiplatformPlugin( pom.withXml { xml -> if (PropertiesProvider(project).keepMppDependenciesIntactInPoms != true) - project.rewritePomMppDependenciesToActualTargetModules(xml, kotlinComponent) + project.rewritePomMppDependenciesToActualTargetModules(xml, kotlinComponent) { id -> + filterMetadataDependencies(this@createMavenPublications, id) + } } } @@ -213,6 +217,25 @@ class KotlinMultiplatformPlugin( } } + /** + * The metadata targets need their POMs to only include the dependencies from the commonMain API configuration. + * The actual apiElements configurations of metadata targets now contain dependencies from all source sets, but, as the consumers who + * can't read Gradle module metadata won't resolve a dependency on an MPP to the granular metadata variant and won't then choose the + * right dependencies for each source set, we put only the dependencies of the legacy common variant into the POM, i.e. commonMain API. + */ + private fun filterMetadataDependencies(target: AbstractKotlinTarget, groupNameVersion: Triple): Boolean { + if (target !is KotlinMetadataTarget || !target.project.isKotlinGranularMetadataEnabled) { + return true + } + + val (group, name, _) = groupNameVersion + + val project = target.project + val metadataApiLegacyElements = project.configurations.getByName(COMMON_MAIN_ELEMENTS_CONFIGURATION_NAME) + + return metadataApiLegacyElements.allDependencies.any { it.group == group && it.name == name } + } + private fun configureSourceSets(project: Project) = with(project.multiplatformExtension) { val production = sourceSets.create(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) val test = sourceSets.create(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME) @@ -286,8 +309,15 @@ internal fun applyUserDefinedAttributes(target: AbstractKotlinTarget) { } } -internal fun sourcesJarTask(compilation: KotlinCompilation<*>, componentName: String?, artifactNameAppendix: String): Jar { - val project = compilation.target.project +internal fun sourcesJarTask(compilation: KotlinCompilation<*>, componentName: String?, artifactNameAppendix: String): Jar = + sourcesJarTask(compilation.target.project, lazy { compilation.allKotlinSourceSets }, componentName, artifactNameAppendix) + +internal fun sourcesJarTask( + project: Project, + sourceSets: Lazy>, + componentName: String?, + artifactNameAppendix: String +): Jar { val taskName = lowerCamelCaseName(componentName, "sourcesJar") (project.tasks.findByName(taskName) as? Jar)?.let { return it } @@ -298,7 +328,7 @@ internal fun sourcesJarTask(compilation: KotlinCompilation<*>, componentName: St } project.whenEvaluated { - compilation.allKotlinSourceSets.forEach { sourceSet -> + sourceSets.value.forEach { sourceSet -> result.from(sourceSet.kotlin) { copySpec -> copySpec.into(sourceSet.name) } @@ -306,15 +336,4 @@ internal fun sourcesJarTask(compilation: KotlinCompilation<*>, componentName: St } return result -} - -internal fun compilationsBySourceSet(project: Project): Map>> = - HashMap>>().also { result -> - for (target in project.multiplatformExtension.targets) { - for (compilation in target.compilations) { - for (sourceSet in compilation.allKotlinSourceSets) { - result.getOrPut(sourceSet) { mutableSetOf() }.add(compilation) - } - } - } - } \ No newline at end of file +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt new file mode 100644 index 00000000000..9ba32c6b3bf --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp + +import org.gradle.api.Project +import org.gradle.api.tasks.Input +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull +import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedPlatformCompilations +import org.w3c.dom.Document +import org.w3c.dom.Element +import org.w3c.dom.Node +import org.w3c.dom.NodeList +import javax.xml.parsers.DocumentBuilderFactory + +data class KotlinProjectStructureMetadata( + @Input + val sourceSetNamesByVariantName: Map>, + + @Input + val sourceSetsDependsOnRelation: Map>, + + @Input + val sourceSetModuleDependencies: Map>> +) + +internal fun buildKotlinProjectStructureMetadata(project: Project): KotlinProjectStructureMetadata? { + val sourceSetsWithMetadataCompilations = + project.multiplatformExtensionOrNull?.targets?.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME)?.compilations?.associate { + it.defaultSourceSet to it + } ?: return null + + val publishedVariantsNamesWithCompilation = getPublishedPlatformCompilations(project).mapKeys { it.key.name } + + return KotlinProjectStructureMetadata( + sourceSetNamesByVariantName = publishedVariantsNamesWithCompilation.mapValues { (_, compilation) -> + compilation.allKotlinSourceSets.filter { it in sourceSetsWithMetadataCompilations }.map { it.name }.toSet() + }, + sourceSetsDependsOnRelation = sourceSetsWithMetadataCompilations.keys.associate { sourceSet -> + sourceSet.name to sourceSet.dependsOn.filter { it in sourceSetsWithMetadataCompilations }.map { it.name }.toSet() + }, + sourceSetModuleDependencies = sourceSetsWithMetadataCompilations.keys.associate { sourceSet -> + sourceSet.name to project.configurations.getByName(sourceSet.apiConfigurationName).allDependencies.map { + it.group.orEmpty() to it.name + }.toSet() + } + ) +} + +internal fun KotlinProjectStructureMetadata.toXmlDocument(): Document { + val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().apply { + fun Node.node(name: String, action: Element.() -> Unit) = appendChild(createElement(name).apply(action)) + fun Node.textNode(name: String, value: String) = + appendChild(createElement(name).apply { appendChild(createTextNode(value)) }) + + node("projectStructure") { + node("variants") { + sourceSetNamesByVariantName.forEach { (variantName, sourceSets) -> + node("variant") { + textNode("name", variantName) + sourceSets.forEach { sourceSetName -> textNode("sourceSet", sourceSetName) } + } + } + } + + node("sourceSets") { + val keys = sourceSetsDependsOnRelation.keys + sourceSetModuleDependencies.keys + for (sourceSet in keys) { + node("sourceSet") { + textNode("name", sourceSet) + sourceSetsDependsOnRelation[sourceSet].orEmpty().forEach { dependsOn -> + textNode("dependsOn", dependsOn) + } + sourceSetModuleDependencies[sourceSet].orEmpty().forEach { moduleDependency -> + textNode("moduleDependency", moduleDependency.first + ":" + moduleDependency.second) + } + } + } + } + } + } + return document +} + +private val NodeList.elements: Iterable get() = (0 until length).map { this@elements.item(it) }.filterIsInstance() + +internal fun parseKotlinSourceSetMetadataFromXml(document: Document): KotlinProjectStructureMetadata? { + val projectStructureNode = document.getElementsByTagName("projectStructure").elements.single() + + val variantsNode = projectStructureNode.getElementsByTagName("variants").item(0) ?: return null + + val sourceSetsByVariant = mutableMapOf>() + + variantsNode.childNodes.elements.filter { it.tagName == "variant" }.forEach { variantNode -> + val variantName = variantNode.getElementsByTagName("name").elements.single().textContent + val sourceSets = variantNode.childNodes.elements.filter { it.tagName == "sourceSet" }.mapTo(mutableSetOf()) { it.textContent } + + sourceSetsByVariant[variantName] = sourceSets + } + + val sourceSetDependsOnRelation = mutableMapOf>() + val sourceSetModuleDependencies = mutableMapOf>>() + + val sourceSetsNode = projectStructureNode.getElementsByTagName("sourceSets").item(0) ?: return null + + sourceSetsNode.childNodes.elements.filter { it.tagName == "sourceSet" }.forEach { sourceSetNode -> + val sourceSetName = sourceSetNode.getElementsByTagName("name").elements.single().textContent + + val dependsOn = mutableSetOf() + val moduleDependencies = mutableSetOf>() + + sourceSetNode.childNodes.elements.forEach { + when (it.tagName) { + "dependsOn" -> dependsOn.add(it.textContent) + "moduleDependency" -> moduleDependencies.add(it.textContent.split(":").let { (first, second) -> first to second }) + } + } + + sourceSetDependsOnRelation[sourceSetName] = dependsOn + sourceSetModuleDependencies[sourceSetName] = moduleDependencies + } + + return KotlinProjectStructureMetadata( + sourceSetsByVariant, + sourceSetDependsOnRelation, + sourceSetModuleDependencies + ) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt index 5bf1818e3ec..aca85f74842 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt @@ -61,7 +61,8 @@ class DefaultKotlinUsageContext( override val compilation: KotlinCompilation<*>, private val usage: Usage, override val dependencyConfigurationName: String, - private val overrideConfigurationArtifacts: Set? = null + private val overrideConfigurationArtifacts: Set? = null, + private val filterConfigurationAttributes: (Attribute<*>) -> Boolean = { true } ) : KotlinUsageContext { private val kotlinTarget: KotlinTarget get() = compilation.target @@ -106,7 +107,7 @@ class DefaultKotlinUsageContext( } configurationAttributes.keySet() - .filter { it != ProjectLocalConfigurations.ATTRIBUTE } + .filter { filterConfigurationAttributes(it) && it != ProjectLocalConfigurations.ATTRIBUTE } .forEach { copyAttribute(it, configurationAttributes, result) } return result diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt index b08768a18a5..413fadb5d60 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.gradle.plugin.mpp +import org.gradle.api.Project import org.gradle.api.attributes.* import org.gradle.api.attributes.Usage.* import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType @@ -12,11 +13,14 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.androidJvm import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.usageByName +import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast object KotlinUsages { const val KOTLIN_API = "kotlin-api" const val KOTLIN_RUNTIME = "kotlin-runtime" + const val KOTLIN_METADATA = "kotlin-metadata" + val values = setOf(KOTLIN_API, KOTLIN_RUNTIME) private val jvmPlatformTypes: Set = setOf(jvm, androidJvm) @@ -63,6 +67,33 @@ object KotlinUsages { } } + private val javaUsagesForKotlinMetadataConsumers = listOf("java-api-jars", JAVA_API, JAVA_RUNTIME_JARS, JAVA_RUNTIME) + + private class KotlinMetadataCompatibility : AttributeCompatibilityRule { + override fun execute(details: CompatibilityCheckDetails) = with(details) { + // ensure that a consumer that requests 'kotlin-metadata' can also consumer 'kotlin-api' artifacts or the + // 'java-*' ones (these are how Gradle represents a module that is published with no Gradle module metadata) + if ( + consumerValue?.name == KOTLIN_METADATA && + (producerValue?.name == KOTLIN_API || producerValue?.name in javaUsagesForKotlinMetadataConsumers) + ) { + compatible() + } + } + } + + private class KotlinMetadataDisambiguation : AttributeDisambiguationRule { + override fun execute(details: MultipleCandidatesDetails) = details.run { + if (consumerValue?.name == KOTLIN_METADATA) { + // Prefer Kotlin metadata, but if there's no such variant then accept 'kotlin-api' or the Java usages + // (see the compatibility rule): + val acceptedProducerValues = listOf(KOTLIN_METADATA, KOTLIN_API, *javaUsagesForKotlinMetadataConsumers.toTypedArray()) + val candidatesMap = candidateValues.associateBy { it.name } + acceptedProducerValues.firstOrNull { it in candidatesMap }?.let { closestMatch(candidatesMap.getValue(it)) } + } + } + } + private class KotlinUsagesDisambiguation : AttributeDisambiguationRule { override fun execute(details: MultipleCandidatesDetails) = with(details) { val candidateNames = candidateValues.map { it?.name }.toSet() @@ -96,10 +127,15 @@ object KotlinUsages { } } - internal fun setupAttributesMatchingStrategy(attributesSchema: AttributesSchema) { - attributesSchema.attribute(Usage.USAGE_ATTRIBUTE) { strategy -> + internal fun setupAttributesMatchingStrategy(project: Project, attributesSchema: AttributesSchema) { + attributesSchema.attribute(USAGE_ATTRIBUTE) { strategy -> strategy.compatibilityRules.add(KotlinJavaRuntimeJarsCompatibility::class.java) strategy.disambiguationRules.add(KotlinUsagesDisambiguation::class.java) + + if (project.isKotlinGranularMetadataEnabled) { + strategy.compatibilityRules.add(KotlinMetadataCompatibility::class.java) + strategy.disambiguationRules.add(KotlinMetadataDisambiguation::class.java) + } } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt new file mode 100644 index 00000000000..9e12def63e2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp + +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ResolvedDependency +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope +import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedPlatformCompilations + +internal data class DependencySourceSetVisibilityResult( + val sourceSetsVisibleByThisSourceSet: Set, + val sourceSetsVisibleThroughDependsOn: Set +) + +internal class SourceSetVisibilityProvider( + private val project: Project +) { + fun getVisibleSourceSets( + visibleFrom: KotlinSourceSet, + dependencyScopes: Iterable, + mppDependency: ResolvedDependency, + dependencyProjectStructureMetadata: KotlinProjectStructureMetadata, + otherProject: Project? + ): DependencySourceSetVisibilityResult { + val visibleByThisSourceSet = + getVisibleSourceSetsImpl(visibleFrom, dependencyScopes, mppDependency, dependencyProjectStructureMetadata, otherProject) + + val visibleByParents = visibleFrom.dependsOn + .flatMapTo(mutableSetOf()) { + getVisibleSourceSetsImpl(it, dependencyScopes, mppDependency, dependencyProjectStructureMetadata, otherProject) + } + + return DependencySourceSetVisibilityResult(visibleByThisSourceSet, visibleByParents) + } + + @Suppress("UnstableApiUsage") + private fun getVisibleSourceSetsImpl( + visibleFrom: KotlinSourceSet, + dependencyScopes: Iterable, + mppDependency: ResolvedDependency, + dependencyProjectMetadata: KotlinProjectStructureMetadata, + otherProject: Project? + ): Set { + val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(visibleFrom) + + var visiblePlatformVariantNames: Set = + compilations + .filter { it.target.platformType != KotlinPlatformType.common } + .flatMapTo(mutableSetOf()) { compilation -> + val configurations = dependencyScopes.mapNotNullTo(mutableSetOf()) { scope -> + project.resolvableConfigurationFromCompilationByScope(compilation, scope) + } + configurations.mapNotNull { configuration -> + // Resolve the configuration but don't trigger artifacts download, only download component metadata: + configuration.incoming.resolutionResult.allComponents + .find { + it.moduleVersion?.group == mppDependency.moduleGroup && it.moduleVersion?.name == mppDependency.moduleName + } + ?.variant?.displayName + } + } + + if (visiblePlatformVariantNames.isEmpty()) { + return emptySet() + } + + if (otherProject != null) { + val publishedVariants = getPublishedPlatformCompilations(otherProject).keys + + visiblePlatformVariantNames = visiblePlatformVariantNames.mapTo(mutableSetOf()) { configurationName -> + publishedVariants.first { it.dependencyConfigurationName == configurationName }.name + } + } + + return dependencyProjectMetadata.sourceSetNamesByVariantName + .filterKeys { it in visiblePlatformVariantNames } + .values.let { if (it.isEmpty()) emptySet() else it.reduce { acc, item -> acc intersect item } } + } +} + +internal fun Project.resolvableConfigurationFromCompilationByScope( + compilation: KotlinCompilation<*>, + scope: KotlinDependencyScope +): Configuration? { + val configurationName = when (scope) { + KotlinDependencyScope.API_SCOPE, KotlinDependencyScope.IMPLEMENTATION_SCOPE, KotlinDependencyScope.COMPILE_ONLY_SCOPE -> compilation.compileDependencyConfigurationName + + KotlinDependencyScope.RUNTIME_ONLY_SCOPE -> + (compilation as? KotlinCompilationToRunnableFiles<*>)?.runtimeDependencyConfigurationName + ?: return null + } + + return project.configurations.getByName(configurationName) +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt new file mode 100644 index 00000000000..aeb912ecc81 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp + +import org.gradle.api.DefaultTask +import org.gradle.api.file.FileCollection +import org.gradle.api.tasks.* +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.API_SCOPE +import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.IMPLEMENTATION_SCOPE +import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy +import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME +import java.io.File +import javax.inject.Inject + +open class TransformKotlinGranularMetadata +@Inject constructor( + @get:Internal + val kotlinSourceSet: KotlinSourceSet +) : DefaultTask() { + + @get:OutputDirectory + val outputsDir: File = project.buildDir.resolve("kotlinSourceSetMetadata/${kotlinSourceSet.name}") + + @Suppress("unused") // Gradle input + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + internal val allSourceSetsMetadataConfiguration = project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME) + + private val participatingSourceSets: Set + get() = transformation.kotlinSourceSet.getSourceSetHierarchy().toMutableSet().apply { + if (any { it.name == KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME }) + add(project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)) + } + + @Suppress("unused") // Gradle input + @get:Input + internal val inputSourceSetsAndCompilations: Map> + get() { + val sourceSets = participatingSourceSets + return CompilationSourceSetUtil.compilationsBySourceSets(project) + .filterKeys { it in sourceSets } + .entries.associate { (sourceSet, compilations) -> + sourceSet.name to compilations.map { it.name }.sorted() + } + } + + private val participatingCompilations: Iterable> + get() { + val sourceSets = participatingSourceSets + return CompilationSourceSetUtil.compilationsBySourceSets(project).filterKeys { it in sourceSets }.values.flatten() + } + + @Suppress("unused") // Gradle input + @get:Input + internal val inputCompilationDependencies: Map>> + get() = participatingCompilations.associate { + it.name to project.configurations.getByName(it.compileDependencyConfigurationName) + .allDependencies.map { listOf(it.group, it.name, it.version) }.toSet() + } + + private val transformation = + GranularMetadataTransformation( + project, + kotlinSourceSet, + listOf(API_SCOPE, IMPLEMENTATION_SCOPE), + listOf(project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME)) + ) + + @get:Internal + internal val metadataDependencyResolutions: Iterable + get() = transformation.metadataDependencyResolutions + + @get:Internal + internal val filesByResolution: Map + get() = metadataDependencyResolutions.filterIsInstance() + .associate { it to project.files(it.getMetadataFilesBySourceSet(outputsDir, doProcessFiles = false).values) } + + @TaskAction + fun transformMetadata() { + if (outputsDir.isDirectory) { + outputsDir.deleteRecursively() + } + outputsDir.mkdirs() + + metadataDependencyResolutions + .filterIsInstance() + .forEach { it.getMetadataFilesBySourceSet(outputsDir, doProcessFiles = true) } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/UnusedSourceSetsChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/UnusedSourceSetsChecker.kt index 6c472d8dbe4..15a2cb5c1e3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/UnusedSourceSetsChecker.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/UnusedSourceSetsChecker.kt @@ -37,7 +37,7 @@ object UnusedSourceSetsChecker { fun checkSourceSets(project: Project) { // TODO once Android compilations are configured eagerly, move this to afterEvaluate { ... } instead of taskGraph.whenReady { ... } project.gradle.taskGraph.whenReady { _ -> - val compilationsBySourceSet = compilationsBySourceSet(project) + val compilationsBySourceSet = CompilationSourceSetUtil.compilationsBySourceSets(project) val unusedSourceSets = project.kotlinExtension.sourceSets.filter { compilationsBySourceSet[it]?.isEmpty() ?: true } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt index 2f1ed80d899..526dc664319 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt @@ -10,10 +10,7 @@ import org.gradle.api.Project import org.gradle.api.attributes.AttributeContainer import org.gradle.api.file.FileCollection import org.gradle.util.ConfigureUtil -import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions -import org.jetbrains.kotlin.gradle.dsl.KotlinCompile -import org.jetbrains.kotlin.gradle.dsl.kotlinExtension -import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull +import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy @@ -77,13 +74,13 @@ abstract class AbstractKotlinCompilation( Callable { target.project.buildDir.resolve("processedResources/${target.targetName}/$name") }) } - open fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + open fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Lazy) { fun AbstractKotlinCompile<*>.configureAction() { source(sourceSet.kotlin) sourceFilesExtensions(sourceSet.customSourceFilesExtensions) - if (addAsCommonSources) { - commonSourceSet += sourceSet.kotlin - } + commonSourceSet += project.files(Callable { + if (addAsCommonSources.value) sourceSet.kotlin else emptyList() + }) } // Note! Invocation of withType-all results in preliminary task instantiation. @@ -103,42 +100,50 @@ abstract class AbstractKotlinCompilation( } } - override fun source(sourceSet: KotlinSourceSet) { - if (kotlinSourceSets.add(sourceSet)) { + internal fun addExactSourceSetsEagerly(sourceSets: Set) { + with(target.project) { //TODO possibly issue with forced instantiation - target.project.whenEvaluated { - sourceSet.getSourceSetHierarchy().forEach { sourceSet -> - val isCommonSource = - CompilationSourceSetUtil.sourceSetsInMultipleCompilations(project)?.contains(sourceSet.name) ?: false - - addSourcesToCompileTask(sourceSet, addAsCommonSources = isCommonSource) - - // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like - // old Java & Android projects: - addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) - addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) - addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) - - if (this@AbstractKotlinCompilation is KotlinCompilationToRunnableFiles<*>) { - addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false) + sourceSets.forEach { sourceSet -> + addSourcesToCompileTask( + sourceSet, + addAsCommonSources = lazy { + CompilationSourceSetUtil.sourceSetsInMultipleCompilations(project).contains(sourceSet.name) } + ) - if (sourceSet.name != defaultSourceSetName) { - kotlinExtension.sourceSets.findByName(defaultSourceSetName)?.let { defaultSourceSet -> - // Temporary solution for checking consistency across source sets participating in a compilation that may - // not be interconnected with the dependsOn relation: check the settings as if the default source set of - // the compilation depends on the one added to the compilation: - defaultSourceSetLanguageSettingsChecker.runAllChecks( - defaultSourceSet, - sourceSet - ) - } + // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like + // old Java & Android projects: + addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) + addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) + addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) + + if (this@AbstractKotlinCompilation is KotlinCompilationToRunnableFiles<*>) { + addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false) + } + + if (sourceSet.name != defaultSourceSetName) { + kotlinExtension.sourceSets.findByName(defaultSourceSetName)?.let { defaultSourceSet -> + // Temporary solution for checking consistency across source sets participating in a compilation that may + // not be interconnected with the dependsOn relation: check the settings as if the default source set of + // the compilation depends on the one added to the compilation: + defaultSourceSetLanguageSettingsChecker.runAllChecks( + defaultSourceSet, + sourceSet + ) } } } } } + final override fun source(sourceSet: KotlinSourceSet) { + if (kotlinSourceSets.add(sourceSet)) { + target.project.whenEvaluated { + addExactSourceSetsEagerly(sourceSet.getSourceSetHierarchy()) + } + } + } + override val compileDependencyConfigurationName: String get() = lowerCamelCaseName( target.disambiguationClassifier, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt index 9efc90ab4ef..86ef00bf7be 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt @@ -118,12 +118,13 @@ abstract class AbstractKotlinTarget( configuration.dependencyConstraints.addAll(kotlinUsageContext.dependencyConstraints) configuration.artifacts.addAll(kotlinUsageContext.artifacts) - kotlinUsageContext.attributes.keySet().forEach { + val attributes = kotlinUsageContext.attributes + attributes.keySet().forEach { // capture type parameter T fun copyAttribute(key: Attribute, from: AttributeContainer, to: AttributeContainer) { to.attribute(key, from.getAttribute(key)!!) } - copyAttribute(it, kotlinUsageContext.attributes, configuration.attributes) + copyAttribute(it, attributes, configuration.attributes) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt index 1738f476346..359d4f46db2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt @@ -64,7 +64,7 @@ open class KotlinVariant( final override val target: KotlinTarget get() = producingCompilation.target - override fun getUsages(): Set = usages + override fun getUsages(): Set = usages override fun getName(): String = componentName ?: producingCompilation.target.targetName @@ -101,7 +101,7 @@ class KotlinVariantWithMetadataDependency( val originalUsages: Set, private val metadataTarget: AbstractKotlinTarget ) : KotlinVariantWithCoordinates(producingCompilation, originalUsages) { - override fun getUsages(): Set = originalUsages.mapTo(mutableSetOf()) { usageContext -> + override fun getUsages(): Set = originalUsages.mapTo(mutableSetOf()) { usageContext -> KotlinUsageContextWithAdditionalDependencies(usageContext, setOf(metadataDependency())) } @@ -135,7 +135,7 @@ class JointAndroidKotlinTargetComponent( override val sourcesArtifacts: Set ) : KotlinTargetComponentWithCoordinatesAndPublication, SoftwareComponentInternal { - override fun getUsages(): Set = nestedVariants.flatMap { it.usages }.toSet() + override fun getUsages(): Set = nestedVariants.flatMap { it.usages }.toSet() override fun getName(): String = lowerCamelCaseName(target.targetName, *flavorNames.toTypedArray()) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/mppDependencyRewritingUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/mppDependencyRewritingUtils.kt index d1c5b703143..f6f6419abe4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/mppDependencyRewritingUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/mppDependencyRewritingUtils.kt @@ -20,15 +20,15 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent internal fun Project.rewritePomMppDependenciesToActualTargetModules( pomXml: XmlProvider, - component: KotlinTargetComponent + component: KotlinTargetComponent, + filterDependencies: (groupNameVersion: Triple) -> Boolean = { true } ) { if (component !is SoftwareComponentInternal) return - val dependenciesNodeList = pomXml.asNode().get("dependencies") as NodeList - val dependencyNodes = dependenciesNodeList.filterIsInstance().flatMap { - (it.get("dependency") as? NodeList).orEmpty() - }.filterIsInstance() + val dependenciesNode = (pomXml.asNode().get("dependencies") as NodeList).filterIsInstance().singleOrNull() ?: return + + val dependencyNodes = (dependenciesNode.get("dependency") as? NodeList).orEmpty().filterIsInstance() val dependencyByNode = mutableMapOf() @@ -57,6 +57,15 @@ internal fun Project.rewritePomMppDependenciesToActualTargetModules( // Rewrite the dependency nodes according to the mapping: dependencyNodes.forEach { dependencyNode -> val moduleDependency = dependencyByNode[dependencyNode] + + if (moduleDependency != null) { + val groupNameVersion = Triple(moduleDependency.group, moduleDependency.name, moduleDependency.version) + if (!filterDependencies(groupNameVersion)) { + dependenciesNode.remove(dependencyNode) + return@forEach + } + } + val mapDependencyTo = resultDependenciesForEachUsageContext.find { moduleDependency in it }?.get(moduleDependency) if (mapDependencyTo != null) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt index dd069c05016..315c85a7ef7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt @@ -17,8 +17,12 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler +import org.jetbrains.kotlin.gradle.plugin.mpp.GranularMetadataTransformation +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata +import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File import java.lang.reflect.Constructor import java.util.* @@ -112,9 +116,11 @@ class DefaultKotlinSourceSet( override fun addCustomSourceFilesExtensions(extensions: List) { explicitlyAddedCustomSourceFilesExtensions.addAll(extensions) } + + internal val dependencyTransformations: MutableMap = mutableMapOf() } -private fun KotlinSourceSet.checkForCircularDependencies(): Unit { +private fun KotlinSourceSet.checkForCircularDependencies() { // If adding an edge creates a cycle, than the source node of the edge belongs to the cycle, so run DFS from that node // to check whether it became reachable from itself val visited = hashSetOf() @@ -148,6 +154,7 @@ internal fun KotlinSourceSet.disambiguateName(simpleName: String): String { private fun createDefaultSourceDirectorySet(project: Project, name: String?, resolver: FileResolver?): SourceDirectorySet { if (isGradleVersionAtLeast(5, 0)) { + @Suppress("UnstableApiUsage") val objects = project.objects val sourceDirectorySetMethod = objects.javaClass.methods.single { it.name == "sourceDirectorySet" && it.parameterCount == 2 } return sourceDirectorySetMethod(objects, name, name) as SourceDirectorySet diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinDependencyScope.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinDependencyScope.kt new file mode 100644 index 00000000000..406c34054a0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinDependencyScope.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.sources + +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.* + +internal enum class KotlinDependencyScope(val scopeName: String) { + API_SCOPE("api"), + IMPLEMENTATION_SCOPE("implementation"), + COMPILE_ONLY_SCOPE("compileOnly"), + RUNTIME_ONLY_SCOPE("runtimeOnly") +} + +internal fun Project.sourceSetDependencyConfigurationByScope(sourceSet: KotlinSourceSet, scope: KotlinDependencyScope): Configuration = + project.configurations.getByName( + when (scope) { + API_SCOPE -> sourceSet.apiConfigurationName + IMPLEMENTATION_SCOPE -> sourceSet.implementationConfigurationName + COMPILE_ONLY_SCOPE -> sourceSet.compileOnlyConfigurationName + RUNTIME_ONLY_SCOPE -> sourceSet.runtimeOnlyConfigurationName + } + ) + +internal fun Project.sourceSetMetadataConfigurationByScope(sourceSet: KotlinSourceSet, scope: KotlinDependencyScope): Configuration = + project.configurations.getByName( + when (scope) { + API_SCOPE -> sourceSet.apiMetadataConfigurationName + IMPLEMENTATION_SCOPE -> sourceSet.implementationMetadataConfigurationName + COMPILE_ONLY_SCOPE -> sourceSet.compileOnlyMetadataConfigurationName + RUNTIME_ONLY_SCOPE -> sourceSet.runtimeOnlyMetadataConfigurationName + } + ) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt index 857285d9817..45e8eec9c54 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages import org.jetbrains.kotlin.gradle.plugin.usageByName +import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import java.io.File internal abstract class KotlinSourceSetFactory internal constructor( @@ -78,6 +79,10 @@ internal class DefaultKotlinSourceSetFactory( isVisible = false isCanBeConsumed = false extendsFrom(project.configurations.maybeCreate(configurationName)) + + if (project.isKotlinGranularMetadataEnabled) { + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA)) + } } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt index a5b63186106..5d7dd8e4a60 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt @@ -5,11 +5,31 @@ package org.jetbrains.kotlin.gradle.targets.metadata -import org.jetbrains.kotlin.gradle.plugin.KotlinCommonSourceSetProcessor -import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetProcessor -import org.jetbrains.kotlin.gradle.plugin.KotlinTargetConfigurator -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinCommonCompilation +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.attributes.Usage +import org.gradle.api.file.FileCollection +import org.gradle.api.internal.component.SoftwareComponentInternal +import org.gradle.api.plugins.BasePlugin +import org.gradle.api.tasks.bundling.Jar +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.mpp.* +import org.jetbrains.kotlin.gradle.plugin.sources.* import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider +import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation +import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import java.util.concurrent.Callable + +internal const val ALL_COMPILE_METADATA_CONFIGURATION_NAME = "allSourceSetsCompileDependenciesMetadata" +internal const val ALL_RUNTIME_METADATA_CONFIGURATION_NAME = "allSourceSetsRuntimeDependenciesMetadata" + +internal val Project.isKotlinGranularMetadataEnabled: Boolean + get() = PropertiesProvider(rootProject).enableGranularSourceSetsMetadata == true class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) : KotlinTargetConfigurator( @@ -18,8 +38,313 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) : kotlinPluginVersion = kotlinPluginVersion ) { + companion object { + internal const val ALL_METADATA_JAR_NAME = "allMetadataJar" + } + + private val KotlinOnlyTarget.apiElementsConfiguration: Configuration + get() = project.configurations.getByName(apiElementsConfigurationName) + + override fun configureTarget(target: KotlinOnlyTarget) { + super.configureTarget(target) + + if (target.project.isKotlinGranularMetadataEnabled) { + target as KotlinMetadataTarget + + createMergedAllSourceSetsConfigurations(target) + + val allMetadataJar = target.project.tasks.getByName(ALL_METADATA_JAR_NAME) as Jar + createMetadataCompilationsForCommonSourceSets(target, allMetadataJar) + + setupDependencyTransformationForCommonSourceSets(target) + + target.project.configurations.getByName(target.apiElementsConfigurationName).attributes + .attribute(Usage.USAGE_ATTRIBUTE, target.project.usageByName(KotlinUsages.KOTLIN_METADATA)) + } + } + + override fun setupCompilationDependencyFiles(project: Project, compilation: KotlinCompilation) { + /** See [createTransformedMetadataClasspath] and its usage. */ + if (project.isKotlinGranularMetadataEnabled) + compilation.compileDependencyFiles = project.files() + else + super.setupCompilationDependencyFiles(project, compilation) + } + override fun buildCompilationProcessor(compilation: KotlinCommonCompilation): KotlinSourceSetProcessor<*> { val tasksProvider = KotlinTasksProvider(compilation.target.targetName) return KotlinCommonSourceSetProcessor(compilation.target.project, compilation, tasksProvider, kotlinPluginVersion) } + + override fun createJarTasks(target: KotlinOnlyTarget) { + super.createJarTasks(target) + + if (target.project.isKotlinGranularMetadataEnabled) { + /** This JAR is created in addition to the main one, published with a classifier, but is by default used + * for project dependencies (as the Kotlin Granular metadata is enabled across all projects in a build, this is OK). + * See also [KotlinMetadataTarget.kotlinComponents] + */ + target.project.tasks.create(ALL_METADATA_JAR_NAME, Jar::class.java).apply { + description = "Assembles a jar archive containing the metadata for all Kotlin source sets." + group = BasePlugin.BUILD_GROUP + + @Suppress("DEPRECATION") + if (isGradleVersionAtLeast(5, 2)) { + archiveAppendix.convention(target.name.toLowerCase()) + archiveClassifier.set("all") + } else { + appendix = target.name.toLowerCase() + classifier = "all" + } + } + } + } + + private fun transformGranularMetadataTaskName(sourceSet: KotlinSourceSet) = + lowerCamelCaseName("transform", sourceSet.name, "DependenciesMetadata") + + private fun setupDependencyTransformationForCommonSourceSets(target: KotlinMetadataTarget) { + target.project.kotlinExtension.sourceSets.all { + setupDependencyTransformationForSourceSet(target.project, it) + } + } + + private fun createMetadataCompilationsForCommonSourceSets( + target: KotlinMetadataTarget, + allMetadataJar: Jar + ) = target.project.whenEvaluated { + // Do this after all targets are configured by the user build script + + val publishedCommonSourceSets: Set = getPublishedCommonSourceSets(project) + + val sourceSetsWithMetadataCompilations: Map = + publishedCommonSourceSets.associate { sourceSet -> + sourceSet to configureMetadataCompilation(target, sourceSet, allMetadataJar) + } + + sourceSetsWithMetadataCompilations.forEach { (sourceSet, metadataCompilation) -> + val compileMetadataTransformationTasksForHierarchy = mutableSetOf() + + // Adjust metadata compilation to support source set hierarchies, i.e. use both the outputs of dependsOn source set compilation + // and their dependencies metadata transformed for compilation: + sourceSet.getSourceSetHierarchy().forEach { hierarchySourceSet -> + if (hierarchySourceSet != sourceSet) { + val dependencyCompilation = sourceSetsWithMetadataCompilations.getValue(hierarchySourceSet as DefaultKotlinSourceSet) + metadataCompilation.compileDependencyFiles += dependencyCompilation.output.classesDirs.filter { it.exists() } + } + + project.tasks.withType(TransformKotlinGranularMetadata::class.java) + .findByName(transformGranularMetadataTaskName(hierarchySourceSet)) + ?.let(compileMetadataTransformationTasksForHierarchy::add) + } + + metadataCompilation.compileDependencyFiles += createTransformedMetadataClasspath( + project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME), + project, + compileMetadataTransformationTasksForHierarchy + ) + } + + val generateMetadata = + createGenerateProjectStructureMetadataTask() + + allMetadataJar.from(project.files(Callable { generateMetadata.resultXmlFile }).builtBy(generateMetadata)) { spec -> + spec.into("META-INF").rename { MULTIPLATFORM_PROJECT_METADATA_FILE_NAME } + } + } + + private fun createMergedAllSourceSetsConfigurations(target: KotlinMetadataTarget): Unit = with(target.project) { + listOf(ALL_COMPILE_METADATA_CONFIGURATION_NAME, ALL_RUNTIME_METADATA_CONFIGURATION_NAME).forEach { configurationName -> + project.configurations.create(configurationName).apply { + isCanBeConsumed = false + isCanBeResolved = true + + usesPlatformOf(target) + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA)) + } + } + } + + private fun configureMetadataCompilation( + target: KotlinMetadataTarget, + sourceSet: KotlinSourceSet, + allMetadataJar: Jar + ): KotlinCommonCompilation { + val project = target.project + + // With the metadata target, we publish all API dependencies of all the published source sets together: + target.apiElementsConfiguration.extendsFrom(project.configurations.getByName(sourceSet.apiConfigurationName)) + + val metadataCompilation = when (sourceSet.name) { + // Historically, we already had a 'main' compilation in metadata targets; TODO consider removing it instead + KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME -> target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + else -> target.compilations.create(lowerCamelCaseName(sourceSet.name)) { compilation -> + compilation.addExactSourceSetsEagerly(setOf(sourceSet)) + } + } + + project.addExtendsFromRelation(metadataCompilation.compileDependencyConfigurationName, ALL_COMPILE_METADATA_CONFIGURATION_NAME) + + allMetadataJar.from(metadataCompilation.output.allOutputs) { spec -> + spec.into(metadataCompilation.defaultSourceSet.name) + } + + @Suppress("UnstableApiUsage") + project.tasks.create( + transformGranularMetadataTaskName(sourceSet), + TransformKotlinGranularMetadata::class.java, + sourceSet + ) + + return metadataCompilation + } + + private fun setupDependencyTransformationForSourceSet( + project: Project, + sourceSet: KotlinSourceSet + ) { + KotlinDependencyScope.values().forEach { scope -> + val allMetadataConfigurations = mutableListOf().apply { + if (scope != KotlinDependencyScope.COMPILE_ONLY_SCOPE) + add(project.configurations.getByName(ALL_RUNTIME_METADATA_CONFIGURATION_NAME)) + if (scope != KotlinDependencyScope.RUNTIME_ONLY_SCOPE) + add(project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME)) + } + + val granularMetadataTransformation = GranularMetadataTransformation( + project, + sourceSet, + listOf(scope), + allMetadataConfigurations + ) + + (sourceSet as DefaultKotlinSourceSet).dependencyTransformations[scope] = granularMetadataTransformation + + val sourceSetMetadataConfigurationByScope = project.sourceSetMetadataConfigurationByScope(sourceSet, scope) + + granularMetadataTransformation.applyToConfiguration(sourceSetMetadataConfigurationByScope) + + val sourceSetDependencyConfigurationByScope = project.sourceSetDependencyConfigurationByScope(sourceSet, scope) + + // All source set dependencies except for compileOnly take part and agree in versions with all other runtime dependencies: + if (scope != KotlinDependencyScope.COMPILE_ONLY_SCOPE) { + project.addExtendsFromRelation( + ALL_RUNTIME_METADATA_CONFIGURATION_NAME, + sourceSetDependencyConfigurationByScope.name + ) + project.addExtendsFromRelation( + sourceSetMetadataConfigurationByScope.name, + ALL_RUNTIME_METADATA_CONFIGURATION_NAME + ) + } + + // All source set dependencies except for runtimeOnly take part and agree in versions with all other compile dependencies: + if (scope != KotlinDependencyScope.RUNTIME_ONLY_SCOPE) { + project.addExtendsFromRelation( + ALL_COMPILE_METADATA_CONFIGURATION_NAME, + sourceSetDependencyConfigurationByScope.name + ) + project.addExtendsFromRelation( + sourceSetMetadataConfigurationByScope.name, + ALL_COMPILE_METADATA_CONFIGURATION_NAME + ) + } + } + } + + /** Ensure that the [configuration] excludes the dependencies that are classified by this [GranularMetadataTransformation] as + * [MetadataDependencyResolution.ExcludeAsUnrequested], and uses exactly the same versions as were resolved for the requested + * dependencies during the transformation. */ + private fun GranularMetadataTransformation.applyToConfiguration(configuration: Configuration) { + // Run this action immediately before the configuration first takes part in dependency resolution: + @Suppress("UnstableApiUsage") + configuration.withDependencies { + val (unrequested, requested) = metadataDependencyResolutions + .partition { it is MetadataDependencyResolution.ExcludeAsUnrequested } + + unrequested.forEach { configuration.exclude(mapOf("group" to it.dependency.moduleGroup, "module" to it.dependency.moduleName)) } + + requested.forEach { + val notation = listOf(it.dependency.moduleGroup, it.dependency.moduleName, it.dependency.moduleVersion).joinToString(":") + configuration.resolutionStrategy.force(notation) + } + } + } + + private fun createTransformedMetadataClasspath( + fromFiles: Iterable, + project: Project, + granularMetadataTransformationTasks: Set + ): FileCollection { + return project.files(Callable { + val resolutionsByArtifactFile = granularMetadataTransformationTasks + .flatMap { it.metadataDependencyResolutions } + .groupBy { it.dependency } + .filterKeys { it.moduleArtifacts.size == 1 } // TODO do we have modules that resolve to more than one artifact? use sets? + .mapKeys { (dependency, _) -> dependency.moduleArtifacts.single().file } + + val transformedFiles = granularMetadataTransformationTasks.flatMap { it.filesByResolution.toList() }.toMap() + + mutableSetOf().apply { + fromFiles.forEach { file -> + val resolutions = resolutionsByArtifactFile[file] + if (resolutions == null) { + add(file) + } else { + val chooseVisibleSourceSets = + resolutions.filterIsInstance() + + if (chooseVisibleSourceSets.isNotEmpty()) { + add(chooseVisibleSourceSets.map { transformedFiles.getValue(it) }) + } else if (resolutions.any { it is MetadataDependencyResolution.KeepOriginalDependency }) { + add(file) + } + } + } + } + }).builtBy(granularMetadataTransformationTasks) + } + + private fun getPublishedCommonSourceSets(project: Project): Set { + val compilationsBySourceSet: Map>> = + CompilationSourceSetUtil.compilationsBySourceSets(project) + + // For now, we will only compile metadata from source sets used by multiple platforms + // TODO once the compiler is able to analyze common code with platform-specific features and dependencies, lift this restriction + val sourceSetsUsedInMultipleTargets = compilationsBySourceSet.filterValues { compilations -> + compilations.map { it.target.platformType }.distinct().size > 1 + } + + // We don't want to publish source set metadata from source sets that don't participate in any compilation that is published, + // such as test or benchmark sources; find all published compilations: + val publishedCompilations = getPublishedPlatformCompilations(project).values + + return sourceSetsUsedInMultipleTargets + .filterValues { compilations -> compilations.any { it in publishedCompilations } } + .keys + } + + private fun Project.createGenerateProjectStructureMetadataTask(): GenerateProjectStructureMetadata = + tasks.create("generateProjectStructureMetadata", GenerateProjectStructureMetadata::class.java) { task -> + task.lazyKotlinProjectStructureMetadata = lazy { checkNotNull(buildKotlinProjectStructureMetadata(project)) } + } +} + +internal fun getPublishedPlatformCompilations(project: Project): Map> { + val result = mutableMapOf>() + + project.multiplatformExtension.targets.withType(AbstractKotlinTarget::class.java).forEach { target -> + if (target.platformType == KotlinPlatformType.common) + return@forEach + + target.kotlinComponents + .filterIsInstance() + .forEach { component -> + component.usages + .filterIsInstance() + .forEach { usage -> result[usage] = usage.compilation } + } + } + + return result } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetPreset.kt index 2dfee29df80..648e8dac671 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetPreset.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetPreset.kt @@ -23,8 +23,6 @@ class KotlinMetadataTargetPreset( project, kotlinPluginVersion ) { - override fun instantiateTarget() = KotlinOnlyTarget(project, KotlinPlatformType.common) - override fun getName(): String = PRESET_NAME override fun createCompilationFactory( @@ -42,6 +40,8 @@ class KotlinMetadataTargetPreset( override fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = KotlinMetadataTargetConfigurator(kotlinPluginVersion) + override fun instantiateTarget(): KotlinOnlyTarget = KotlinMetadataTarget(project) + override fun createTarget(name: String): KotlinOnlyTarget = super.createTarget(name).apply { val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeCompilation.kt index af1a40b3c1c..0a76d1d4e7e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeCompilation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeCompilation.kt @@ -10,6 +10,7 @@ import groovy.lang.Closure import org.gradle.api.Action import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.SourceDirectorySet import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions @@ -18,6 +19,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationWithResources import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.util.concurrent.Callable class KotlinNativeCompilation( override val target: KotlinNativeTarget, @@ -40,7 +42,7 @@ class KotlinNativeCompilation( internal val allSources: MutableSet = mutableSetOf() // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. - internal val commonSources: MutableSet = mutableSetOf() + internal val commonSources: ConfigurableFileCollection = project.files() var friendCompilationName: String? = null @@ -82,10 +84,8 @@ class KotlinNativeCompilation( val binariesTaskName: String get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "binaries") - override fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + override fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Lazy) { allSources.add(sourceSet.kotlin) - if (addAsCommonSources) { - commonSources.add(sourceSet.kotlin) - } + commonSources.from(project.files(Callable { if (addAsCommonSources.value) sourceSet.kotlin else emptyList() })) } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 94c80b331d2..396e028724d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -218,7 +218,7 @@ abstract class AbstractKotlinCompile() : AbstractKo @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) - internal var commonSourceSet: Iterable = emptyList() + internal var commonSourceSet: FileCollection = project.files() @get:Input internal val moduleName: String diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt new file mode 100644 index 00000000000..73ff31a8e80 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2010-2019 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.internals + +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata +import org.w3c.dom.Document + +fun parseKotlinSourceSetMetadataFromXml(document: Document): KotlinProjectStructureMetadata? = + org.jetbrains.kotlin.gradle.plugin.mpp.parseKotlinSourceSetMetadataFromXml(document) \ No newline at end of file