MPP granular source set metadata support
This commit is contained in:
+318
@@ -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<Pair<String, String>> = emptyList(),
|
||||
shouldNotInclude: Iterable<Pair<String, String>> = 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<String, Set<Pair<String, String>>>
|
||||
): 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))
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
plugins {
|
||||
kotlin("multiplatform").version("<pluginMarkerVersion>").apply(false)
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven("$rootDir/../repo")
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
kotlin.mpp.enableGranularSourceSetsMetadata=true
|
||||
+88
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.example.app
|
||||
|
||||
import com.example.foo.*
|
||||
import com.example.bar.*
|
||||
|
||||
fun appCommon() {
|
||||
foo()
|
||||
fooCommon()
|
||||
bar()
|
||||
barCommon()
|
||||
}
|
||||
+20
@@ -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
|
||||
}
|
||||
}
|
||||
+17
@@ -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()
|
||||
}
|
||||
+21
@@ -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()
|
||||
}
|
||||
+19
@@ -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()
|
||||
}
|
||||
+18
@@ -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
|
||||
}
|
||||
+20
@@ -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()
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.example.bar
|
||||
|
||||
import com.example.foo.*
|
||||
|
||||
fun appLinux() {
|
||||
fooLinuxAndJs()
|
||||
barLinuxAndJs()
|
||||
}
|
||||
+95
@@ -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")
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.example.bar
|
||||
|
||||
import com.example.foo.*
|
||||
|
||||
expect fun bar(): String
|
||||
|
||||
fun barCommon(): String {
|
||||
foo()
|
||||
// thirdPartyFun() //unresolved
|
||||
return fooCommon()
|
||||
}
|
||||
+17
@@ -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())
|
||||
}
|
||||
}
|
||||
+13
@@ -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()
|
||||
}
|
||||
+19
@@ -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()
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example.bar
|
||||
|
||||
import com.example.foo.*
|
||||
|
||||
fun barLinuxAndJs(): String {
|
||||
fooCommon()
|
||||
// thirdPartyFun() // unresolved
|
||||
// fooJvmAndJs() // unresolved
|
||||
return fooLinuxAndJs()
|
||||
}
|
||||
+16
@@ -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()
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example.bar
|
||||
|
||||
import com.example.foo.*
|
||||
|
||||
actual fun bar(): String {
|
||||
fooCommon()
|
||||
fooLinuxAndJs()
|
||||
// fooJvmAndJs() // unresolved
|
||||
return barLinuxAndJs()
|
||||
}
|
||||
+100
@@ -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")
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.example.foo
|
||||
|
||||
expect fun foo(): String
|
||||
|
||||
fun fooCommon() = "foo"
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.example.foo
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class FooTest {
|
||||
@Test
|
||||
fun testFoo() {
|
||||
assertEquals(foo(), fooCommon())
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example.foo
|
||||
|
||||
import com.example.thirdparty.thirdPartyFun
|
||||
|
||||
actual fun foo() = fooJvmAndJs()
|
||||
|
||||
fun fooJvmAndJs(): String {
|
||||
thirdPartyFun()
|
||||
return fooCommon()
|
||||
}
|
||||
+17
@@ -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()
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example.foo
|
||||
|
||||
fun fooLinuxAndJs() = fooCommon()
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.example.foo
|
||||
|
||||
import kotlin.test.*
|
||||
|
||||
class FooLinuxAndJsTest {
|
||||
@Test
|
||||
fun testFooJvmAndJs() {
|
||||
assertEquals(foo(), fooCommon())
|
||||
fooLinuxAndJs()
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example.foo
|
||||
|
||||
actual fun foo() = fooLinuxAndJs()
|
||||
+12
@@ -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")
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
plugins {
|
||||
kotlin("multiplatform").version("<pluginMarkerVersion>")
|
||||
`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")
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "third-party-lib"
|
||||
|
||||
enableFeaturePreview("GRADLE_METADATA")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example.thirdparty
|
||||
|
||||
expect fun thirdPartyFun(): String
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.example.thirdparty
|
||||
|
||||
actual fun thirdPartyFun(): String = "thirdPartyFun @ js"
|
||||
|
||||
fun thirdPartyJsFun() { }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.example.thirdparty
|
||||
|
||||
actual fun thirdPartyFun(): String = "thirdPartyFun @ jvm"
|
||||
|
||||
fun thirdPartyJvmFun() { }
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
plugins {
|
||||
kotlin("multiplatform").version("<pluginMarkerVersion>")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
kotlin.mpp.enableGranularSourceSetsMetadata=true
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "my-app"
|
||||
|
||||
enableFeaturePreview("GRADLE_METADATA")
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.example.app
|
||||
|
||||
import com.example.foo.*
|
||||
import com.example.bar.*
|
||||
|
||||
fun appCommon() {
|
||||
foo()
|
||||
fooCommon()
|
||||
bar()
|
||||
barCommon()
|
||||
}
|
||||
+20
@@ -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
|
||||
}
|
||||
}
|
||||
+17
@@ -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()
|
||||
}
|
||||
+21
@@ -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()
|
||||
}
|
||||
+19
@@ -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()
|
||||
}
|
||||
+18
@@ -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
|
||||
}
|
||||
+20
@@ -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()
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.example.bar
|
||||
|
||||
import com.example.foo.*
|
||||
|
||||
fun appLinux() {
|
||||
fooLinuxAndJs()
|
||||
barLinuxAndJs()
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
plugins {
|
||||
kotlin("multiplatform").version("<pluginMarkerVersion>")
|
||||
`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")
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
kotlin.mpp.enableGranularSourceSetsMetadata=true
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "my-lib-bar"
|
||||
|
||||
enableFeaturePreview("GRADLE_METADATA")
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.example.bar
|
||||
|
||||
import com.example.foo.*
|
||||
|
||||
expect fun bar(): String
|
||||
|
||||
fun barCommon(): String {
|
||||
foo()
|
||||
// thirdPartyFun() //unresolved
|
||||
return fooCommon()
|
||||
}
|
||||
+17
@@ -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())
|
||||
}
|
||||
}
|
||||
+13
@@ -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()
|
||||
}
|
||||
+19
@@ -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()
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example.bar
|
||||
|
||||
import com.example.foo.*
|
||||
|
||||
fun barLinuxAndJs(): String {
|
||||
fooCommon()
|
||||
// thirdPartyFun() // unresolved
|
||||
// fooJvmAndJs() // unresolved
|
||||
return fooLinuxAndJs()
|
||||
}
|
||||
+16
@@ -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()
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example.bar
|
||||
|
||||
import com.example.foo.*
|
||||
|
||||
actual fun bar(): String {
|
||||
fooCommon()
|
||||
fooLinuxAndJs()
|
||||
// fooJvmAndJs() // unresolved
|
||||
return barLinuxAndJs()
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
plugins {
|
||||
kotlin("multiplatform").version("<pluginMarkerVersion>")
|
||||
`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")
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
kotlin.mpp.enableGranularSourceSetsMetadata=true
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "my-lib-foo"
|
||||
|
||||
enableFeaturePreview("GRADLE_METADATA")
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.example.foo
|
||||
|
||||
expect fun foo(): String
|
||||
|
||||
fun fooCommon() = "foo"
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.example.foo
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class FooTest {
|
||||
@Test
|
||||
fun testFoo() {
|
||||
assertEquals(foo(), fooCommon())
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example.foo
|
||||
|
||||
import com.example.thirdparty.thirdPartyFun
|
||||
|
||||
actual fun foo() = fooJvmAndJs()
|
||||
|
||||
fun fooJvmAndJs(): String {
|
||||
thirdPartyFun()
|
||||
return fooCommon()
|
||||
}
|
||||
+17
@@ -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()
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example.foo
|
||||
|
||||
fun fooLinuxAndJs() = fooCommon()
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.example.foo
|
||||
|
||||
class FooLinuxAndJsTest {
|
||||
@Test
|
||||
fun testFooJvmAndJs() {
|
||||
assertEquals(foo(), fooCommon())
|
||||
fooLinuxAndJs()
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example.foo
|
||||
|
||||
actual fun foo() = fooLinuxAndJs()
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
plugins {
|
||||
kotlin("multiplatform").version("<pluginMarkerVersion>")
|
||||
`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")
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "third-party-lib"
|
||||
|
||||
enableFeaturePreview("GRADLE_METADATA")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example.thirdparty
|
||||
|
||||
expect fun thirdPartyFun(): String
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.example.thirdparty
|
||||
|
||||
actual fun thirdPartyFun(): String = "thirdPartyFun @ js"
|
||||
|
||||
fun thirdPartyJsFun() { }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.example.thirdparty
|
||||
|
||||
actual fun thirdPartyFun(): String = "thirdPartyFun @ jvm"
|
||||
|
||||
fun thirdPartyJvmFun() { }
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -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
|
||||
|
||||
+22
-19
@@ -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<KotlinTargetType : KotlinTarget>
|
||||
cleanTask.delete(kotlinCompilation.output.allOutputs)
|
||||
}
|
||||
|
||||
protected open fun setupCompilationDependencyFiles(project: Project, compilation: KotlinCompilation<KotlinCommonOptions>) {
|
||||
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<KotlinCompilationType : KotlinCompilatio
|
||||
}
|
||||
}
|
||||
|
||||
/** The implementations are expected to create a [Jar] task under the name [KotlinTarget.artifactsTaskName] of the [target]. */
|
||||
protected open fun createJarTasks(target: KotlinOnlyTarget<KotlinCompilationType>) {
|
||||
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<KotlinCompilationType>) {
|
||||
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<KotlinCompilationType : KotlinCompilatio
|
||||
jarArtifact.builtBy(jar)
|
||||
jarArtifact.type = ArtifactTypeDefinition.JAR_TYPE
|
||||
|
||||
val apiElementsConfiguration = project.configurations.getByName(target.apiElementsConfigurationName)
|
||||
addJar(apiElementsConfiguration, jarArtifact)
|
||||
|
||||
if (mainCompilation is KotlinCompilationToRunnableFiles<*>) {
|
||||
|
||||
+44
@@ -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<KotlinProjectStructureMetadata>
|
||||
|
||||
@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"
|
||||
+375
@@ -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<String>,
|
||||
val visibleSourceSetNamesExcludingDependsOn: Set<String>,
|
||||
val visibleTransitiveDependencies: Set<ResolvedDependency>
|
||||
) : 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<String, FileCollection>
|
||||
|
||||
override fun toString(): String =
|
||||
super.toString() + ", sourceSets = " + allVisibleSourceSetNames.joinToString(", ", "[", "]") {
|
||||
(if (it in visibleSourceSetNamesExcludingDependsOn) "*" else "") + it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private typealias ModuleId = Pair<String?, String> // 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<KotlinDependencyScope>,
|
||||
/** A configuration that holds the dependencies of the appropriate scope for all Kotlin source sets in the project */
|
||||
val allSourceSetsConfigurations: Iterable<Configuration>
|
||||
) {
|
||||
val metadataDependencyResolutions: Iterable<MetadataDependencyResolution> 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<ProjectDependency>,
|
||||
resolvedDependencies: Iterable<ResolvedDependency>
|
||||
): Map<ModuleId, ProjectDependency> {
|
||||
val result = mutableMapOf<ModuleId, ProjectDependency>()
|
||||
|
||||
val resolvedDependenciesMap: Map<ModuleId, ResolvedDependency> = 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<Dependency> {
|
||||
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<MetadataDependencyResolution> {
|
||||
val result = mutableListOf<MetadataDependencyResolution>()
|
||||
|
||||
val resolvedDependenciesFromAllSourceSets = allSourceSetsConfigurations.map { it.resolvedConfiguration.lenientConfiguration }
|
||||
|
||||
val visitedDependencies = mutableSetOf<ResolvedDependency>()
|
||||
|
||||
val directRequestedDependencies = getRequestedDependencies(kotlinSourceSet)
|
||||
|
||||
val directRequestedModules: Set<ModuleId> = directRequestedDependencies.mapTo(mutableSetOf()) { it.group to it.name }
|
||||
|
||||
val allModuleDependencies = resolvedDependenciesFromAllSourceSets.flatMapTo(mutableSetOf()) { it.allModuleDependencies }
|
||||
|
||||
val knownProjectDependencies = collectProjectDependencies(
|
||||
directRequestedDependencies.filterIsInstance<ProjectDependency>(),
|
||||
allModuleDependencies
|
||||
)
|
||||
|
||||
val resolvedDependencyQueue: Queue<ResolvedDependencyWithParent> = ArrayDeque<ResolvedDependencyWithParent>().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<ModuleId> =
|
||||
mutableSetOf<ModuleId>().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<String, FileCollection> =
|
||||
mppDependencyMetadataExtractor.getVisibleSourceSetsMetadata(visibleSourceSetsExcludingDependsOn, baseDir, doProcessFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedDependency) {
|
||||
abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata?
|
||||
abstract fun getVisibleSourceSetsMetadata(
|
||||
visibleSourceSetNames: Set<String>,
|
||||
baseDir: File,
|
||||
doProcessFiles: Boolean
|
||||
): Map<String, FileCollection>
|
||||
}
|
||||
|
||||
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<String>,
|
||||
baseDir: File,
|
||||
doProcessFiles: Boolean
|
||||
): Map<String, FileCollection> =
|
||||
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<String>,
|
||||
baseDir: File,
|
||||
doProcessFiles: Boolean
|
||||
): Map<String, FileCollection> {
|
||||
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<String>,
|
||||
artifactJar: File,
|
||||
baseDir: File,
|
||||
doProcessFiles: Boolean
|
||||
): Map<String, FileCollection> {
|
||||
val moduleString = "${module.group}-${module.name}-${module.version}"
|
||||
val transformedModuleRoot = run { baseDir.resolve(moduleString).also { it.mkdirs() } }
|
||||
|
||||
val resultFiles = mutableMapOf<String, FileCollection>()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
+7
-5
@@ -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
|
||||
|
||||
+85
@@ -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<KotlinCommonCompilation>(project, KotlinPlatformType.common) {
|
||||
override val kotlinComponents: Set<KotlinTargetComponent> by lazy {
|
||||
if (!project.isKotlinGranularMetadataEnabled)
|
||||
super.kotlinComponents
|
||||
else {
|
||||
val usageContexts = mutableSetOf<DefaultKotlinUsageContext>()
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
-21
@@ -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<String?, String, String?>): 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<Set<KotlinSourceSet>>,
|
||||
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<KotlinSourceSet, Set<KotlinCompilation<*>>> =
|
||||
HashMap<KotlinSourceSet, MutableSet<KotlinCompilation<*>>>().also { result ->
|
||||
for (target in project.multiplatformExtension.targets) {
|
||||
for (compilation in target.compilations) {
|
||||
for (sourceSet in compilation.allKotlinSourceSets) {
|
||||
result.getOrPut(sourceSet) { mutableSetOf() }.add(compilation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
@@ -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<String, Set<String>>,
|
||||
|
||||
@Input
|
||||
val sourceSetsDependsOnRelation: Map<String, Set<String>>,
|
||||
|
||||
@Input
|
||||
val sourceSetModuleDependencies: Map<String, Set<Pair<String, String>>>
|
||||
)
|
||||
|
||||
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<Element> get() = (0 until length).map { this@elements.item(it) }.filterIsInstance<Element>()
|
||||
|
||||
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<String, Set<String>>()
|
||||
|
||||
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<String, Set<String>>()
|
||||
val sourceSetModuleDependencies = mutableMapOf<String, Set<Pair<String, String>>>()
|
||||
|
||||
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<String>()
|
||||
val moduleDependencies = mutableSetOf<Pair<String, String>>()
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
+3
-2
@@ -61,7 +61,8 @@ class DefaultKotlinUsageContext(
|
||||
override val compilation: KotlinCompilation<*>,
|
||||
private val usage: Usage,
|
||||
override val dependencyConfigurationName: String,
|
||||
private val overrideConfigurationArtifacts: Set<PublishArtifact>? = null
|
||||
private val overrideConfigurationArtifacts: Set<PublishArtifact>? = 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
|
||||
|
||||
+38
-2
@@ -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<KotlinPlatformType> = 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<Usage> {
|
||||
override fun execute(details: CompatibilityCheckDetails<Usage>) = 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<Usage> {
|
||||
override fun execute(details: MultipleCandidatesDetails<Usage>) = 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<Usage> {
|
||||
override fun execute(details: MultipleCandidatesDetails<Usage?>) = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+103
@@ -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<String>,
|
||||
val sourceSetsVisibleThroughDependsOn: Set<String>
|
||||
)
|
||||
|
||||
internal class SourceSetVisibilityProvider(
|
||||
private val project: Project
|
||||
) {
|
||||
fun getVisibleSourceSets(
|
||||
visibleFrom: KotlinSourceSet,
|
||||
dependencyScopes: Iterable<KotlinDependencyScope>,
|
||||
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<KotlinDependencyScope>,
|
||||
mppDependency: ResolvedDependency,
|
||||
dependencyProjectMetadata: KotlinProjectStructureMetadata,
|
||||
otherProject: Project?
|
||||
): Set<String> {
|
||||
val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(visibleFrom)
|
||||
|
||||
var visiblePlatformVariantNames: Set<String> =
|
||||
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)
|
||||
}
|
||||
|
||||
+95
@@ -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<KotlinSourceSet>
|
||||
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<String, Iterable<String>>
|
||||
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<KotlinCompilation<*>>
|
||||
get() {
|
||||
val sourceSets = participatingSourceSets
|
||||
return CompilationSourceSetUtil.compilationsBySourceSets(project).filterKeys { it in sourceSets }.values.flatten()
|
||||
}
|
||||
|
||||
@Suppress("unused") // Gradle input
|
||||
@get:Input
|
||||
internal val inputCompilationDependencies: Map<String, Set<List<String?>>>
|
||||
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<MetadataDependencyResolution>
|
||||
get() = transformation.metadataDependencyResolutions
|
||||
|
||||
@get:Internal
|
||||
internal val filesByResolution: Map<out MetadataDependencyResolution, FileCollection>
|
||||
get() = metadataDependencyResolutions.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
|
||||
.associate { it to project.files(it.getMetadataFilesBySourceSet(outputsDir, doProcessFiles = false).values) }
|
||||
|
||||
@TaskAction
|
||||
fun transformMetadata() {
|
||||
if (outputsDir.isDirectory) {
|
||||
outputsDir.deleteRecursively()
|
||||
}
|
||||
outputsDir.mkdirs()
|
||||
|
||||
metadataDependencyResolutions
|
||||
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
|
||||
.forEach { it.getMetadataFilesBySourceSet(outputsDir, doProcessFiles = true) }
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
+40
-35
@@ -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<T : KotlinCommonOptions>(
|
||||
Callable { target.project.buildDir.resolve("processedResources/${target.targetName}/$name") })
|
||||
}
|
||||
|
||||
open fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) {
|
||||
open fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Lazy<Boolean>) {
|
||||
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<Any>()
|
||||
})
|
||||
}
|
||||
|
||||
// Note! Invocation of withType-all results in preliminary task instantiation.
|
||||
@@ -103,42 +100,50 @@ abstract class AbstractKotlinCompilation<T : KotlinCommonOptions>(
|
||||
}
|
||||
}
|
||||
|
||||
override fun source(sourceSet: KotlinSourceSet) {
|
||||
if (kotlinSourceSets.add(sourceSet)) {
|
||||
internal fun addExactSourceSetsEagerly(sourceSets: Set<KotlinSourceSet>) {
|
||||
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,
|
||||
|
||||
+3
-2
@@ -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 <T> copyAttribute(key: Attribute<T>, from: AttributeContainer, to: AttributeContainer) {
|
||||
to.attribute(key, from.getAttribute(key)!!)
|
||||
}
|
||||
copyAttribute(it, kotlinUsageContext.attributes, configuration.attributes)
|
||||
copyAttribute(it, attributes, configuration.attributes)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -64,7 +64,7 @@ open class KotlinVariant(
|
||||
final override val target: KotlinTarget
|
||||
get() = producingCompilation.target
|
||||
|
||||
override fun getUsages(): Set<UsageContext> = usages
|
||||
override fun getUsages(): Set<KotlinUsageContext> = usages
|
||||
|
||||
override fun getName(): String = componentName ?: producingCompilation.target.targetName
|
||||
|
||||
@@ -101,7 +101,7 @@ class KotlinVariantWithMetadataDependency(
|
||||
val originalUsages: Set<DefaultKotlinUsageContext>,
|
||||
private val metadataTarget: AbstractKotlinTarget
|
||||
) : KotlinVariantWithCoordinates(producingCompilation, originalUsages) {
|
||||
override fun getUsages(): Set<UsageContext> = originalUsages.mapTo(mutableSetOf()) { usageContext ->
|
||||
override fun getUsages(): Set<KotlinUsageContext> = originalUsages.mapTo(mutableSetOf()) { usageContext ->
|
||||
KotlinUsageContextWithAdditionalDependencies(usageContext, setOf(metadataDependency()))
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class JointAndroidKotlinTargetComponent(
|
||||
override val sourcesArtifacts: Set<PublishArtifact>
|
||||
) : KotlinTargetComponentWithCoordinatesAndPublication, SoftwareComponentInternal {
|
||||
|
||||
override fun getUsages(): Set<UsageContext> = nestedVariants.flatMap { it.usages }.toSet()
|
||||
override fun getUsages(): Set<KotlinUsageContext> = nestedVariants.flatMap { it.usages }.toSet()
|
||||
|
||||
override fun getName(): String = lowerCamelCaseName(target.targetName, *flavorNames.toTypedArray())
|
||||
|
||||
|
||||
+14
-5
@@ -20,15 +20,15 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent
|
||||
|
||||
internal fun Project.rewritePomMppDependenciesToActualTargetModules(
|
||||
pomXml: XmlProvider,
|
||||
component: KotlinTargetComponent
|
||||
component: KotlinTargetComponent,
|
||||
filterDependencies: (groupNameVersion: Triple<String?, String, String?>) -> Boolean = { true }
|
||||
) {
|
||||
if (component !is SoftwareComponentInternal)
|
||||
return
|
||||
|
||||
val dependenciesNodeList = pomXml.asNode().get("dependencies") as NodeList
|
||||
val dependencyNodes = dependenciesNodeList.filterIsInstance<Node>().flatMap {
|
||||
(it.get("dependency") as? NodeList).orEmpty()
|
||||
}.filterIsInstance<Node>()
|
||||
val dependenciesNode = (pomXml.asNode().get("dependencies") as NodeList).filterIsInstance<Node>().singleOrNull() ?: return
|
||||
|
||||
val dependencyNodes = (dependenciesNode.get("dependency") as? NodeList).orEmpty().filterIsInstance<Node>()
|
||||
|
||||
val dependencyByNode = mutableMapOf<Node, ModuleDependency>()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+8
-1
@@ -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<String>) {
|
||||
explicitlyAddedCustomSourceFilesExtensions.addAll(extensions)
|
||||
}
|
||||
|
||||
internal val dependencyTransformations: MutableMap<KotlinDependencyScope, GranularMetadataTransformation> = 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<KotlinSourceSet>()
|
||||
@@ -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
|
||||
|
||||
+38
@@ -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
|
||||
}
|
||||
)
|
||||
+5
@@ -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<T : KotlinSourceSet> 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+329
-4
@@ -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<KotlinCommonCompilation>(
|
||||
@@ -18,8 +38,313 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
|
||||
kotlinPluginVersion = kotlinPluginVersion
|
||||
) {
|
||||
|
||||
companion object {
|
||||
internal const val ALL_METADATA_JAR_NAME = "allMetadataJar"
|
||||
}
|
||||
|
||||
private val KotlinOnlyTarget<KotlinCommonCompilation>.apiElementsConfiguration: Configuration
|
||||
get() = project.configurations.getByName(apiElementsConfigurationName)
|
||||
|
||||
override fun configureTarget(target: KotlinOnlyTarget<KotlinCommonCompilation>) {
|
||||
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<KotlinCommonOptions>) {
|
||||
/** 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<KotlinCommonCompilation>) {
|
||||
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<KotlinSourceSet> = getPublishedCommonSourceSets(project)
|
||||
|
||||
val sourceSetsWithMetadataCompilations: Map<KotlinSourceSet, KotlinCommonCompilation> =
|
||||
publishedCommonSourceSets.associate { sourceSet ->
|
||||
sourceSet to configureMetadataCompilation(target, sourceSet, allMetadataJar)
|
||||
}
|
||||
|
||||
sourceSetsWithMetadataCompilations.forEach { (sourceSet, metadataCompilation) ->
|
||||
val compileMetadataTransformationTasksForHierarchy = mutableSetOf<TransformKotlinGranularMetadata>()
|
||||
|
||||
// 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<Configuration>().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<File>,
|
||||
project: Project,
|
||||
granularMetadataTransformationTasks: Set<TransformKotlinGranularMetadata>
|
||||
): 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<Any /* File | FileCollection */>().apply {
|
||||
fromFiles.forEach { file ->
|
||||
val resolutions = resolutionsByArtifactFile[file]
|
||||
if (resolutions == null) {
|
||||
add(file)
|
||||
} else {
|
||||
val chooseVisibleSourceSets =
|
||||
resolutions.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
|
||||
|
||||
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<KotlinSourceSet> {
|
||||
val compilationsBySourceSet: Map<KotlinSourceSet, Set<KotlinCompilation<*>>> =
|
||||
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<KotlinUsageContext, KotlinCompilation<*>> {
|
||||
val result = mutableMapOf<KotlinUsageContext, KotlinCompilation<*>>()
|
||||
|
||||
project.multiplatformExtension.targets.withType(AbstractKotlinTarget::class.java).forEach { target ->
|
||||
if (target.platformType == KotlinPlatformType.common)
|
||||
return@forEach
|
||||
|
||||
target.kotlinComponents
|
||||
.filterIsInstance<SoftwareComponentInternal>()
|
||||
.forEach { component ->
|
||||
component.usages
|
||||
.filterIsInstance<KotlinUsageContext>()
|
||||
.forEach { usage -> result[usage] = usage.compilation }
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
+2
-2
@@ -23,8 +23,6 @@ class KotlinMetadataTargetPreset(
|
||||
project,
|
||||
kotlinPluginVersion
|
||||
) {
|
||||
override fun instantiateTarget() = KotlinOnlyTarget<KotlinCommonCompilation>(project, KotlinPlatformType.common)
|
||||
|
||||
override fun getName(): String = PRESET_NAME
|
||||
|
||||
override fun createCompilationFactory(
|
||||
@@ -42,6 +40,8 @@ class KotlinMetadataTargetPreset(
|
||||
override fun createKotlinTargetConfigurator(): KotlinTargetConfigurator<KotlinCommonCompilation> =
|
||||
KotlinMetadataTargetConfigurator(kotlinPluginVersion)
|
||||
|
||||
override fun instantiateTarget(): KotlinOnlyTarget<KotlinCommonCompilation> = KotlinMetadataTarget(project)
|
||||
|
||||
override fun createTarget(name: String): KotlinOnlyTarget<KotlinCommonCompilation> =
|
||||
super.createTarget(name).apply {
|
||||
val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
|
||||
|
||||
+5
-5
@@ -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<SourceDirectorySet> = mutableSetOf()
|
||||
|
||||
// TODO: Move into the compilation task when the linking task does klib linking instead of compilation.
|
||||
internal val commonSources: MutableSet<SourceDirectorySet> = 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<Boolean>) {
|
||||
allSources.add(sourceSet.kotlin)
|
||||
if (addAsCommonSources) {
|
||||
commonSources.add(sourceSet.kotlin)
|
||||
}
|
||||
commonSources.from(project.files(Callable { if (addAsCommonSources.value) sourceSet.kotlin else emptyList<Any>() }))
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -218,7 +218,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
|
||||
|
||||
@get:InputFiles
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
internal var commonSourceSet: Iterable<File> = emptyList()
|
||||
internal var commonSourceSet: FileCollection = project.files()
|
||||
|
||||
@get:Input
|
||||
internal val moduleName: String
|
||||
|
||||
+12
@@ -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)
|
||||
Reference in New Issue
Block a user