MPP granular source set metadata support

This commit is contained in:
Sergey Igushkin
2019-04-12 18:40:21 +03:00
parent fcb006b42a
commit 0c9d962f9f
96 changed files with 3089 additions and 109 deletions
@@ -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))
}
}
@@ -0,0 +1,11 @@
plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>").apply(false)
}
allprojects {
repositories {
mavenLocal()
maven("$rootDir/../repo")
jcenter()
}
}
@@ -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)
}
}
}
@@ -0,0 +1,11 @@
package com.example.app
import com.example.foo.*
import com.example.bar.*
fun appCommon() {
foo()
fooCommon()
bar()
barCommon()
}
@@ -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
}
}
@@ -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()
}
@@ -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()
}
@@ -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()
}
@@ -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
}
@@ -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()
}
@@ -0,0 +1,8 @@
package com.example.bar
import com.example.foo.*
fun appLinux() {
fooLinuxAndJs()
barLinuxAndJs()
}
@@ -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")
}
}
@@ -0,0 +1,11 @@
package com.example.bar
import com.example.foo.*
expect fun bar(): String
fun barCommon(): String {
foo()
// thirdPartyFun() //unresolved
return fooCommon()
}
@@ -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())
}
}
@@ -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()
}
@@ -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()
}
}
@@ -0,0 +1,10 @@
package com.example.bar
import com.example.foo.*
fun barLinuxAndJs(): String {
fooCommon()
// thirdPartyFun() // unresolved
// fooJvmAndJs() // unresolved
return fooLinuxAndJs()
}
@@ -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()
}
}
@@ -0,0 +1,10 @@
package com.example.bar
import com.example.foo.*
actual fun bar(): String {
fooCommon()
fooLinuxAndJs()
// fooJvmAndJs() // unresolved
return barLinuxAndJs()
}
@@ -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")
}
}
@@ -0,0 +1,5 @@
package com.example.foo
expect fun foo(): String
fun fooCommon() = "foo"
@@ -0,0 +1,11 @@
package com.example.foo
import kotlin.test.Test
import kotlin.test.assertEquals
class FooTest {
@Test
fun testFoo() {
assertEquals(foo(), fooCommon())
}
}
@@ -0,0 +1,10 @@
package com.example.foo
import com.example.thirdparty.thirdPartyFun
actual fun foo() = fooJvmAndJs()
fun fooJvmAndJs(): String {
thirdPartyFun()
return fooCommon()
}
@@ -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()
}
}
@@ -0,0 +1,11 @@
package com.example.foo
import kotlin.test.*
class FooLinuxAndJsTest {
@Test
fun testFooJvmAndJs() {
assertEquals(foo(), fooCommon())
fooLinuxAndJs()
}
}
@@ -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")
@@ -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")
}
}
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
rootProject.name = "third-party-lib"
enableFeaturePreview("GRADLE_METADATA")
@@ -0,0 +1,5 @@
package com.example.thirdparty
actual fun thirdPartyFun(): String = "thirdPartyFun @ js"
fun thirdPartyJsFun() { }
@@ -0,0 +1,5 @@
package com.example.thirdparty
actual fun thirdPartyFun(): String = "thirdPartyFun @ jvm"
fun thirdPartyJvmFun() { }
@@ -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)
}
}
}
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
rootProject.name = "my-app"
enableFeaturePreview("GRADLE_METADATA")
@@ -0,0 +1,11 @@
package com.example.app
import com.example.foo.*
import com.example.bar.*
fun appCommon() {
foo()
fooCommon()
bar()
barCommon()
}
@@ -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
}
}
@@ -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()
}
@@ -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()
}
@@ -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()
}
@@ -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
}
@@ -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()
}
@@ -0,0 +1,8 @@
package com.example.bar
import com.example.foo.*
fun appLinux() {
fooLinuxAndJs()
barLinuxAndJs()
}
@@ -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")
}
}
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
rootProject.name = "my-lib-bar"
enableFeaturePreview("GRADLE_METADATA")
@@ -0,0 +1,11 @@
package com.example.bar
import com.example.foo.*
expect fun bar(): String
fun barCommon(): String {
foo()
// thirdPartyFun() //unresolved
return fooCommon()
}
@@ -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())
}
}
@@ -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()
}
@@ -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()
}
}
@@ -0,0 +1,10 @@
package com.example.bar
import com.example.foo.*
fun barLinuxAndJs(): String {
fooCommon()
// thirdPartyFun() // unresolved
// fooJvmAndJs() // unresolved
return fooLinuxAndJs()
}
@@ -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()
}
}
@@ -0,0 +1,10 @@
package com.example.bar
import com.example.foo.*
actual fun bar(): String {
fooCommon()
fooLinuxAndJs()
// fooJvmAndJs() // unresolved
return barLinuxAndJs()
}
@@ -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")
}
}
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
rootProject.name = "my-lib-foo"
enableFeaturePreview("GRADLE_METADATA")
@@ -0,0 +1,5 @@
package com.example.foo
expect fun foo(): String
fun fooCommon() = "foo"
@@ -0,0 +1,11 @@
package com.example.foo
import kotlin.test.Test
import kotlin.test.assertEquals
class FooTest {
@Test
fun testFoo() {
assertEquals(foo(), fooCommon())
}
}
@@ -0,0 +1,10 @@
package com.example.foo
import com.example.thirdparty.thirdPartyFun
actual fun foo() = fooJvmAndJs()
fun fooJvmAndJs(): String {
thirdPartyFun()
return fooCommon()
}
@@ -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()
}
}
@@ -0,0 +1,9 @@
package com.example.foo
class FooLinuxAndJsTest {
@Test
fun testFooJvmAndJs() {
assertEquals(foo(), fooCommon())
fooLinuxAndJs()
}
}
@@ -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")
}
}
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
rootProject.name = "third-party-lib"
enableFeaturePreview("GRADLE_METADATA")
@@ -0,0 +1,5 @@
package com.example.thirdparty
actual fun thirdPartyFun(): String = "thirdPartyFun @ js"
fun thirdPartyJsFun() { }
@@ -0,0 +1,5 @@
package com.example.thirdparty
actual fun thirdPartyFun(): String = "thirdPartyFun @ jvm"
fun thirdPartyJvmFun() { }
@@ -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)
}
@@ -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,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<*>) {
@@ -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"
@@ -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
}
}
@@ -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
@@ -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)
}
}
}
@@ -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)
}
}
}
}
}
@@ -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
)
}
@@ -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
@@ -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)
}
}
}
}
@@ -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)
}
@@ -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) }
}
}
@@ -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
}
@@ -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,
@@ -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)
}
}
@@ -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())
@@ -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) {
@@ -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
@@ -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
}
)
@@ -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))
}
}
}
}
@@ -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
}
@@ -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)
@@ -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>() }))
}
}
@@ -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
@@ -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)