Enable metadata publishing for new MPP and consumption from source sets

* Add configurations for consuming source sets metadata (extending the
  source set's dependency configurations). These configurations will be
  used by the IDE to pick up the dependencies metadata.

* Remove the universal preset from the default presets, create a
  metadata target by default for compiling and publishing the metadata
  of `commonMain` (in the future, we will publish and consume the
  metadata of the other source sets as well).

* Make the classpath configurations of common modules consume the
  metadata and thus ensure compatibility of 1.2.x MPP with the new MPP
  model.
This commit is contained in:
Sergey Igushkin
2018-08-12 00:41:57 +03:00
parent add323a486
commit 1a3dd67176
12 changed files with 172 additions and 17 deletions
@@ -23,6 +23,20 @@ interface KotlinSourceSet : Named, HasKotlinDependencies {
fun dependsOn(other: KotlinSourceSet) fun dependsOn(other: KotlinSourceSet)
val dependsOn: Set<KotlinSourceSet> val dependsOn: Set<KotlinSourceSet>
val apiMetadataConfigurationName: String
val implementationMetadataConfigurationName: String
val compileOnlyMetadataConfigurationName: String
val runtimeOnlyMetadataConfigurationName: String
override val relatedConfigurationNames: List<String>
get() = super.relatedConfigurationNames +
listOf(
apiMetadataConfigurationName,
implementationMetadataConfigurationName,
compileOnlyMetadataConfigurationName,
runtimeOnlyMetadataConfigurationName
)
companion object { companion object {
const val COMMON_MAIN_SOURCE_SET_NAME = "commonMain" const val COMMON_MAIN_SOURCE_SET_NAME = "commonMain"
const val COMMON_TEST_SOURCE_SET_NAME = "commonTest" const val COMMON_TEST_SOURCE_SET_NAME = "commonTest"
@@ -30,10 +30,12 @@ class KotlinAndroid32GradleIT : KotlinAndroid3GradleIT(androidGradlePluginVersio
":lib:compileReleaseKotlinAndroidLib", ":lib:compileReleaseKotlinAndroidLib",
":lib:compileKotlinJvmLib", ":lib:compileKotlinJvmLib",
":lib:compileKotlinJsLib", ":lib:compileKotlinJsLib",
":lib:compileKotlinMetadata",
":app:compileDebugKotlinAndroidApp", ":app:compileDebugKotlinAndroidApp",
":app:compileReleaseKotlinAndroidApp", ":app:compileReleaseKotlinAndroidApp",
":app:compileKotlinJvmApp", ":app:compileKotlinJvmApp",
":app:compileKotlinJsApp", ":app:compileKotlinJsApp",
":app:compileKotlinMetadata",
":lib:compileDebugUnitTestJavaWithJavac", ":lib:compileDebugUnitTestJavaWithJavac",
":app:compileDebugUnitTestJavaWithJavac" ":app:compileDebugUnitTestJavaWithJavac"
) )
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.gradle 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.plugin.sources.SourceSetConsistencyChecks
import org.jetbrains.kotlin.gradle.util.checkBytecodeContains import org.jetbrains.kotlin.gradle.util.checkBytecodeContains
import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.modify
@@ -27,12 +29,16 @@ class NewMultiplatformIT : BaseGradleIT() {
with(libProject) { with(libProject) {
build("publish") { build("publish") {
assertSuccessful() assertSuccessful()
assertTasksExecuted(":compileKotlinJvm6", ":compileKotlinNodeJs", ":jvm6Jar", ":nodeJsJar") assertTasksExecuted(
val repoDir = projectDir.resolve("repo") ":compileKotlinJvm6", ":compileKotlinNodeJs", ":compileKotlinMetadata",
val moduleDir = repoDir.resolve("com/example/sample-lib/1.0") ":jvm6Jar", ":nodeJsJar", ":metadataJar"
)
val moduleDir = projectDir.resolve("repo/com/example/sample-lib/1.0")
val jvmJarName = "sample-lib-1.0-jvm6.jar" val jvmJarName = "sample-lib-1.0-jvm6.jar"
val jsJarName = "sample-lib-1.0-nodeJs.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()) Assert.assertTrue(moduleDir.resolve(it).exists())
} }
@@ -46,6 +52,9 @@ class NewMultiplatformIT : BaseGradleIT() {
Assert.assertTrue("function idUsage(" in compiledJs) Assert.assertTrue("function idUsage(" in compiledJs)
Assert.assertTrue("function expectedFun(" in compiledJs) Assert.assertTrue("function expectedFun(" in compiledJs)
Assert.assertTrue("function main(" 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() { fun CompiledProject.checkAppBuild() {
assertSuccessful() assertSuccessful()
assertTasksExecuted(":compileKotlinJvm6", ":compileKotlinJvm8", ":compileKotlinNodeJs") assertTasksExecuted(":compileKotlinJvm6", ":compileKotlinJvm8", ":compileKotlinNodeJs", ":compileKotlinMetadata")
projectDir.resolve(targetClassesDir("jvm6")).run { projectDir.resolve(targetClassesDir("jvm6")).run {
Assert.assertTrue(resolve("com/example/app/AKt.class").exists()) 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 { projectDir.resolve(targetClassesDir("jvm8")).run {
@@ -70,6 +79,10 @@ class NewMultiplatformIT : BaseGradleIT() {
Assert.assertTrue(resolve("com/example/app/Jdk8ApiUsageKt.class").exists()) 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 { projectDir.resolve(targetClassesDir("nodeJs")).resolve("sample-app.js").readText().run {
Assert.assertTrue(contains("console.info")) Assert.assertTrue(contains("console.info"))
Assert.assertTrue(contains("function nodeJsMain(")) Assert.assertTrue(contains("function nodeJsMain("))
@@ -96,7 +109,9 @@ class NewMultiplatformIT : BaseGradleIT() {
build("assemble") { build("assemble") {
assertSuccessful() 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") val jvmClassFile = projectDir.resolve(kotlinClassesDir("app-jvm") + "com/example/app/JvmAppKt.class")
checkBytecodeContains(jvmClassFile, "CommonKt.id", "MainKt.expectedFun") checkBytecodeContains(jvmClassFile, "CommonKt.id", "MainKt.expectedFun")
@@ -326,4 +341,66 @@ class NewMultiplatformIT : BaseGradleIT() {
assertContains("allJava:foo.java") 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
)
}
}
}
} }
@@ -2,4 +2,4 @@ package com.example.lib
class CommonLibClass class CommonLibClass
expect class ExpectedLibClass expect class ExpectedLibClass()
@@ -0,0 +1,5 @@
apply plugin: 'kotlin-platform-common'
dependencies {
compile 'com.example:sample-lib:1.0'
}
@@ -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<String>) {
expectedFun()
}
@@ -1,3 +1,3 @@
enableFeaturePreview('GRADLE_METADATA') enableFeaturePreview('GRADLE_METADATA')
include 'app-jvm', 'app-js' include 'app-common', 'app-jvm', 'app-js'
@@ -433,15 +433,18 @@ internal abstract class AbstractKotlinPlugin(
) { ) {
val project = kotlinTarget.project 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 // Don't set the attributes for common module; otherwise their 'common' platform won't be compatible with the one in
// platform-specific modules // platform-specific modules
if (kotlinTarget.platformType != KotlinPlatformType.common) { if (kotlinTarget.platformType != KotlinPlatformType.common) {
project.dependencies.attributesSchema.attribute(KotlinPlatformType.attribute)
project.configurations.getByName(kotlinTarget.apiElementsConfigurationName).usesPlatformOf(kotlinTarget) project.configurations.getByName(kotlinTarget.apiElementsConfigurationName).usesPlatformOf(kotlinTarget)
project.configurations.getByName(kotlinTarget.runtimeElementsConfigurationName).usesPlatformOf(kotlinTarget) project.configurations.getByName(kotlinTarget.runtimeElementsConfigurationName).usesPlatformOf(kotlinTarget)
kotlinTarget.compilations.all { compilation ->
KotlinTargetConfigurator.defineConfigurationsForCompilation(compilation, kotlinTarget, project.configurations)
}
} }
} }
@@ -29,7 +29,7 @@ import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet
internal val Project.multiplatformExtension get(): KotlinMultiplatformExtension? = internal val Project.multiplatformExtension get(): KotlinMultiplatformExtension? =
project.extensions.getByName("kotlin") as KotlinMultiplatformExtension project.extensions.getByName("kotlin") as KotlinMultiplatformExtension
internal class KotlinMultiplatformPlugin( class KotlinMultiplatformPlugin(
private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry, private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry,
private val fileResolver: FileResolver, private val fileResolver: FileResolver,
private val instantiator: Instantiator, private val instantiator: Instantiator,
@@ -72,11 +72,16 @@ internal class KotlinMultiplatformPlugin(
setUpConfigurationAttributes(project) setUpConfigurationAttributes(project)
configurePublishingWithMavenPublish(project) configurePublishingWithMavenPublish(project)
// set up metadata publishing
targetsFromPreset.fromPreset(
KotlinMetadataTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion),
METADATA_TARGET_NAME
)
} }
fun setupDefaultPresets(project: Project) { fun setupDefaultPresets(project: Project) {
with((project.kotlinExtension as KotlinMultiplatformExtension).presets) { with((project.kotlinExtension as KotlinMultiplatformExtension).presets) {
add(KotlinUniversalTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion))
add(KotlinJvmTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion)) add(KotlinJvmTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion))
add(KotlinJsTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion)) add(KotlinJsTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion))
add(KotlinAndroidTargetPreset(project, kotlinPluginVersion)) add(KotlinAndroidTargetPreset(project, kotlinPluginVersion))
@@ -155,4 +160,8 @@ internal class KotlinMultiplatformPlugin(
} }
} }
} }
companion object {
const val METADATA_TARGET_NAME = "metadata"
}
} }
@@ -45,7 +45,7 @@ abstract class KotlinOnlyTargetPreset<T : KotlinCompilation>(
internal abstract fun buildCompilationProcessor(compilation: T): KotlinSourceSetProcessor<*> internal abstract fun buildCompilationProcessor(compilation: T): KotlinSourceSetProcessor<*>
} }
class KotlinUniversalTargetPreset( class KotlinMetadataTargetPreset(
project: Project, project: Project,
instantiator: Instantiator, instantiator: Instantiator,
fileResolver: FileResolver, fileResolver: FileResolver,
@@ -77,7 +77,7 @@ class KotlinUniversalTargetPreset(
) )
companion object { companion object {
const val PRESET_NAME = "universal" const val PRESET_NAME = "metadata"
} }
} }
@@ -20,6 +20,8 @@ import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.lang.reflect.Constructor import java.lang.reflect.Constructor
import java.util.* import java.util.*
const val METADATA_CONFIGURATION_NAME_SUFFIX = "DependenciesMetadata"
class DefaultKotlinSourceSet( class DefaultKotlinSourceSet(
private val project: Project, private val project: Project,
val displayName: String, val displayName: String,
@@ -38,6 +40,18 @@ class DefaultKotlinSourceSet(
override val runtimeOnlyConfigurationName: String override val runtimeOnlyConfigurationName: String
get() = disambiguateName("runtimeOnly") 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 { override val kotlin: SourceDirectorySet = createDefaultSourceDirectorySet(name + " Kotlin source", fileResolver).apply {
filter.include("**/*.java", "**/*.kt", "**/*.kts") filter.include("**/*.java", "**/*.kt", "**/*.kts")
} }
@@ -7,8 +7,11 @@ package org.jetbrains.kotlin.gradle.plugin.sources
import org.gradle.api.NamedDomainObjectFactory import org.gradle.api.NamedDomainObjectFactory
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.attributes.Usage
import org.gradle.api.internal.file.FileResolver 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.source.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.usageByName
import java.io.File import java.io.File
internal abstract class KotlinSourceSetFactory<T : KotlinSourceSet> internal constructor( internal abstract class KotlinSourceSetFactory<T : KotlinSourceSet> internal constructor(
@@ -52,6 +55,24 @@ internal class DefaultKotlinSourceSetFactory(
override fun setUpSourceSetDefaults(sourceSet: DefaultKotlinSourceSet) { override fun setUpSourceSetDefaults(sourceSet: DefaultKotlinSourceSet) {
super.setUpSourceSetDefaults(sourceSet) super.setUpSourceSetDefaults(sourceSet)
sourceSet.resources.srcDir(File(defaultSourceLocation(sourceSet.name), "resources")) 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 { override fun doCreateSourceSet(name: String): DefaultKotlinSourceSet {