diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/source/KotlinSourceSet.kt b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/source/KotlinSourceSet.kt index e22f1258ba1..9aa3f171aea 100644 --- a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/source/KotlinSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/source/KotlinSourceSet.kt @@ -23,6 +23,20 @@ interface KotlinSourceSet : Named, HasKotlinDependencies { fun dependsOn(other: KotlinSourceSet) val dependsOn: Set + val apiMetadataConfigurationName: String + val implementationMetadataConfigurationName: String + val compileOnlyMetadataConfigurationName: String + val runtimeOnlyMetadataConfigurationName: String + + override val relatedConfigurationNames: List + get() = super.relatedConfigurationNames + + listOf( + apiMetadataConfigurationName, + implementationMetadataConfigurationName, + compileOnlyMetadataConfigurationName, + runtimeOnlyMetadataConfigurationName + ) + companion object { const val COMMON_MAIN_SOURCE_SET_NAME = "commonMain" const val COMMON_TEST_SOURCE_SET_NAME = "commonTest" diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt index f15aef2e813..ccfab330fa6 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt @@ -30,10 +30,12 @@ class KotlinAndroid32GradleIT : KotlinAndroid3GradleIT(androidGradlePluginVersio ":lib:compileReleaseKotlinAndroidLib", ":lib:compileKotlinJvmLib", ":lib:compileKotlinJsLib", + ":lib:compileKotlinMetadata", ":app:compileDebugKotlinAndroidApp", ":app:compileReleaseKotlinAndroidApp", ":app:compileKotlinJvmApp", ":app:compileKotlinJsApp", + ":app:compileKotlinMetadata", ":lib:compileDebugUnitTestJavaWithJavac", ":app:compileDebugUnitTestJavaWithJavac" ) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt index f042e8b10b7..4403f406741 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt @@ -5,6 +5,8 @@ package org.jetbrains.kotlin.gradle +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin +import org.jetbrains.kotlin.gradle.plugin.sources.METADATA_CONFIGURATION_NAME_SUFFIX import org.jetbrains.kotlin.gradle.plugin.sources.SourceSetConsistencyChecks import org.jetbrains.kotlin.gradle.util.checkBytecodeContains import org.jetbrains.kotlin.gradle.util.modify @@ -27,12 +29,16 @@ class NewMultiplatformIT : BaseGradleIT() { with(libProject) { build("publish") { assertSuccessful() - assertTasksExecuted(":compileKotlinJvm6", ":compileKotlinNodeJs", ":jvm6Jar", ":nodeJsJar") - val repoDir = projectDir.resolve("repo") - val moduleDir = repoDir.resolve("com/example/sample-lib/1.0") + assertTasksExecuted( + ":compileKotlinJvm6", ":compileKotlinNodeJs", ":compileKotlinMetadata", + ":jvm6Jar", ":nodeJsJar", ":metadataJar" + ) + val moduleDir = projectDir.resolve("repo/com/example/sample-lib/1.0") val jvmJarName = "sample-lib-1.0-jvm6.jar" val jsJarName = "sample-lib-1.0-nodeJs.jar" - listOf(jvmJarName, jsJarName, "sample-lib-1.0.module").forEach { + val metadataJarName = "sample-lib-1.0-metadata.jar" + + listOf(jvmJarName, jsJarName, metadataJarName, "sample-lib-1.0.module").forEach { Assert.assertTrue(moduleDir.resolve(it).exists()) } @@ -46,6 +52,9 @@ class NewMultiplatformIT : BaseGradleIT() { Assert.assertTrue("function idUsage(" in compiledJs) Assert.assertTrue("function expectedFun(" in compiledJs) Assert.assertTrue("function main(" in compiledJs) + + val metadataJarEntries = ZipFile(moduleDir.resolve(metadataJarName)).entries().asSequence().map { it.name }.toSet() + Assert.assertTrue("com/example/lib/CommonKt.kotlin_metadata" in metadataJarEntries) } } @@ -57,11 +66,11 @@ class NewMultiplatformIT : BaseGradleIT() { fun CompiledProject.checkAppBuild() { assertSuccessful() - assertTasksExecuted(":compileKotlinJvm6", ":compileKotlinJvm8", ":compileKotlinNodeJs") + assertTasksExecuted(":compileKotlinJvm6", ":compileKotlinJvm8", ":compileKotlinNodeJs", ":compileKotlinMetadata") projectDir.resolve(targetClassesDir("jvm6")).run { Assert.assertTrue(resolve("com/example/app/AKt.class").exists()) - Assert.assertTrue(this.resolve("com/example/app/UseBothIdsKt.class").exists()) + Assert.assertTrue(resolve("com/example/app/UseBothIdsKt.class").exists()) } projectDir.resolve(targetClassesDir("jvm8")).run { @@ -70,6 +79,10 @@ class NewMultiplatformIT : BaseGradleIT() { Assert.assertTrue(resolve("com/example/app/Jdk8ApiUsageKt.class").exists()) } + projectDir.resolve(targetClassesDir("metadata")).run { + Assert.assertTrue(resolve("com/example/app/AKt.kotlin_metadata").exists()) + } + projectDir.resolve(targetClassesDir("nodeJs")).resolve("sample-app.js").readText().run { Assert.assertTrue(contains("console.info")) Assert.assertTrue(contains("function nodeJsMain(")) @@ -96,7 +109,9 @@ class NewMultiplatformIT : BaseGradleIT() { build("assemble") { assertSuccessful() - assertTasksExecuted(":app-js:compileKotlin2Js", ":app-jvm:compileKotlin") + assertTasksExecuted(":app-js:compileKotlin2Js", ":app-jvm:compileKotlin", ":app-common:compileKotlinCommon") + + assertFileExists(kotlinClassesDir("app-common") + "com/example/app/CommonAppKt.kotlin_metadata") val jvmClassFile = projectDir.resolve(kotlinClassesDir("app-jvm") + "com/example/app/JvmAppKt.class") checkBytecodeContains(jvmClassFile, "CommonKt.id", "MainKt.expectedFun") @@ -326,4 +341,66 @@ class NewMultiplatformIT : BaseGradleIT() { assertContains("allJava:foo.java") } } + + + @Test + fun testResolveMppLibDependencyToMetadata() { + val libProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app") + val appProject = Project("sample-app", gradleVersion, "new-mpp-lib-and-app") + + libProject.build("publish") { assertSuccessful() } + val localRepo = libProject.projectDir.resolve("repo") + val localRepoUri = localRepo.toURI() + + with(appProject) { + setupWorkingDir() + + val pathPrefix = "metadataDependency: " + + gradleBuildScript().appendText( + "\n" + """ + repositories { maven { url '$localRepoUri' } } + + kotlin.sourceSets { + commonMain { + dependencies { + // add these dependencies to check that they are resolved to metadata + api 'com.example:sample-lib:1.0' + compileOnly 'com.example:sample-lib:1.0' + runtimeOnly 'com.example:sample-lib:1.0' + } + } + } + + task('printMetadataFiles') { + doFirst { + ['Api', 'Implementation', 'CompileOnly', 'RuntimeOnly'].each { kind -> + def configuration = configurations.getByName("commonMain${'$'}kind" + '$METADATA_CONFIGURATION_NAME_SUFFIX') + configuration.files.each { println '$pathPrefix' + configuration.name + '->' + it.absolutePath } + } + } + } + """.trimIndent() + ) + val metadataDependencyRegex = "$pathPrefix(.*?)->(.*)".toRegex() + + build("printMetadataFiles") { + assertSuccessful() + + val expectedPath = + localRepo.resolve( + "com/example/sample-lib/1.0/sample-lib-1.0-${KotlinMultiplatformPlugin.METADATA_TARGET_NAME}.jar" + ).absolutePath + + val paths = metadataDependencyRegex.findAll(output).map { it.groupValues[1] to it.groupValues[2] }.toSet() + + Assert.assertEquals( + listOf("Api", "Implementation", "CompileOnly", "RuntimeOnly").map { + "commonMain$it$METADATA_CONFIGURATION_NAME_SUFFIX" to expectedPath + }.toSet(), + paths + ) + } + } + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/lib/src/commonMain/kotlin/CommonLibClass.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/lib/src/commonMain/kotlin/CommonLibClass.kt index b89e78a13cb..20732ce7026 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/lib/src/commonMain/kotlin/CommonLibClass.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/lib/src/commonMain/kotlin/CommonLibClass.kt @@ -2,4 +2,4 @@ package com.example.lib class CommonLibClass -expect class ExpectedLibClass \ No newline at end of file +expect class ExpectedLibClass() \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/app-common/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/app-common/build.gradle new file mode 100644 index 00000000000..56e29c9786f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/app-common/build.gradle @@ -0,0 +1,5 @@ +apply plugin: 'kotlin-platform-common' + +dependencies { + compile 'com.example:sample-lib:1.0' +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/app-common/src/main/kotlin/commonApp.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/app-common/src/main/kotlin/commonApp.kt new file mode 100644 index 00000000000..47ef85a09bf --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/app-common/src/main/kotlin/commonApp.kt @@ -0,0 +1,10 @@ +package com.example.app + +import com.example.lib.id +import com.example.lib.expectedFun + +fun idUsage(x: Int): Int = id(x) + +fun main(args: Array) { + expectedFun() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/settings.gradle index 5d139c22763..50965ce7e5d 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/settings.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/settings.gradle @@ -1,3 +1,3 @@ enableFeaturePreview('GRADLE_METADATA') -include 'app-jvm', 'app-js' \ No newline at end of file +include 'app-common', 'app-jvm', 'app-js' \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index 82038462375..a47be84cb8d 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -433,15 +433,18 @@ internal abstract class AbstractKotlinPlugin( ) { val project = kotlinTarget.project + // Setup the consuming configurations: + project.dependencies.attributesSchema.attribute(KotlinPlatformType.attribute) + kotlinTarget.compilations.all { compilation -> + KotlinTargetConfigurator.defineConfigurationsForCompilation(compilation, kotlinTarget, project.configurations) + } + + // Setup the published configurations: // Don't set the attributes for common module; otherwise their 'common' platform won't be compatible with the one in // platform-specific modules if (kotlinTarget.platformType != KotlinPlatformType.common) { - project.dependencies.attributesSchema.attribute(KotlinPlatformType.attribute) project.configurations.getByName(kotlinTarget.apiElementsConfigurationName).usesPlatformOf(kotlinTarget) project.configurations.getByName(kotlinTarget.runtimeElementsConfigurationName).usesPlatformOf(kotlinTarget) - kotlinTarget.compilations.all { compilation -> - KotlinTargetConfigurator.defineConfigurationsForCompilation(compilation, kotlinTarget, project.configurations) - } } } 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 ebb21e341ef..d892be86645 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 @@ -29,7 +29,7 @@ import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet internal val Project.multiplatformExtension get(): KotlinMultiplatformExtension? = project.extensions.getByName("kotlin") as KotlinMultiplatformExtension -internal class KotlinMultiplatformPlugin( +class KotlinMultiplatformPlugin( private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry, private val fileResolver: FileResolver, private val instantiator: Instantiator, @@ -72,11 +72,16 @@ internal class KotlinMultiplatformPlugin( setUpConfigurationAttributes(project) configurePublishingWithMavenPublish(project) + + // set up metadata publishing + targetsFromPreset.fromPreset( + KotlinMetadataTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion), + METADATA_TARGET_NAME + ) } fun setupDefaultPresets(project: Project) { with((project.kotlinExtension as KotlinMultiplatformExtension).presets) { - add(KotlinUniversalTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion)) add(KotlinJvmTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion)) add(KotlinJsTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion)) add(KotlinAndroidTargetPreset(project, kotlinPluginVersion)) @@ -155,4 +160,8 @@ internal class KotlinMultiplatformPlugin( } } } + + companion object { + const val METADATA_TARGET_NAME = "metadata" + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt index e80b99cce90..a7701887eb6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt @@ -45,7 +45,7 @@ abstract class KotlinOnlyTargetPreset( internal abstract fun buildCompilationProcessor(compilation: T): KotlinSourceSetProcessor<*> } -class KotlinUniversalTargetPreset( +class KotlinMetadataTargetPreset( project: Project, instantiator: Instantiator, fileResolver: FileResolver, @@ -77,7 +77,7 @@ class KotlinUniversalTargetPreset( ) companion object { - const val PRESET_NAME = "universal" + const val PRESET_NAME = "metadata" } } 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 11a0254c086..8d96c02c02f 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 @@ -20,6 +20,8 @@ import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import java.lang.reflect.Constructor import java.util.* +const val METADATA_CONFIGURATION_NAME_SUFFIX = "DependenciesMetadata" + class DefaultKotlinSourceSet( private val project: Project, val displayName: String, @@ -38,6 +40,18 @@ class DefaultKotlinSourceSet( override val runtimeOnlyConfigurationName: String get() = disambiguateName("runtimeOnly") + override val apiMetadataConfigurationName: String + get() = lowerCamelCaseName(apiConfigurationName, METADATA_CONFIGURATION_NAME_SUFFIX) + + override val implementationMetadataConfigurationName: String + get() = lowerCamelCaseName(implementationConfigurationName, METADATA_CONFIGURATION_NAME_SUFFIX) + + override val compileOnlyMetadataConfigurationName: String + get() = lowerCamelCaseName(compileOnlyConfigurationName, METADATA_CONFIGURATION_NAME_SUFFIX) + + override val runtimeOnlyMetadataConfigurationName: String + get() = lowerCamelCaseName(runtimeOnlyConfigurationName, METADATA_CONFIGURATION_NAME_SUFFIX) + override val kotlin: SourceDirectorySet = createDefaultSourceDirectorySet(name + " Kotlin source", fileResolver).apply { filter.include("**/*.java", "**/*.kt", "**/*.kts") } 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 aea545bd090..f7acc0b88fc 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 @@ -7,8 +7,11 @@ package org.jetbrains.kotlin.gradle.plugin.sources import org.gradle.api.NamedDomainObjectFactory import org.gradle.api.Project +import org.gradle.api.attributes.Usage import org.gradle.api.internal.file.FileResolver +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.usageByName import java.io.File internal abstract class KotlinSourceSetFactory internal constructor( @@ -52,6 +55,24 @@ internal class DefaultKotlinSourceSetFactory( override fun setUpSourceSetDefaults(sourceSet: DefaultKotlinSourceSet) { super.setUpSourceSetDefaults(sourceSet) sourceSet.resources.srcDir(File(defaultSourceLocation(sourceSet.name), "resources")) + + val dependencyConfigurationWithMetadata = with(sourceSet) { + listOf( + apiConfigurationName to apiMetadataConfigurationName, + implementationConfigurationName to implementationMetadataConfigurationName, + compileOnlyConfigurationName to compileOnlyMetadataConfigurationName, + runtimeOnlyConfigurationName to runtimeOnlyMetadataConfigurationName + ) + } + + dependencyConfigurationWithMetadata.forEach { (configurationName, metadataName) -> + project.configurations.maybeCreate(metadataName).apply { + attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.common) + isVisible = false + isCanBeConsumed = false + extendsFrom(project.configurations.maybeCreate(configurationName)) + } + } } override fun doCreateSourceSet(name: String): DefaultKotlinSourceSet {