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)
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 {
const val COMMON_MAIN_SOURCE_SET_NAME = "commonMain"
const val COMMON_TEST_SOURCE_SET_NAME = "commonTest"
@@ -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"
)
@@ -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
)
}
}
}
}
@@ -2,4 +2,4 @@ package com.example.lib
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')
include 'app-jvm', 'app-js'
include 'app-common', 'app-jvm', 'app-js'
@@ -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)
}
}
}
@@ -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"
}
}
@@ -45,7 +45,7 @@ abstract class KotlinOnlyTargetPreset<T : KotlinCompilation>(
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"
}
}
@@ -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")
}
@@ -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<T : KotlinSourceSet> 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 {