Skip to content

Commit 24debfd

Browse files
sellmairSpace Team
authored and
Space Team
committed
[Gradle] ExternalTargetApi: Implement ExternalKotlinTargetSourcesJarUtils
This utils are intended to be used external target authors exclusively and shall help to create sources.jar files using regular Kotlin Multiplatform conventions. ^KT-58109 Verification Pending
1 parent 3f1faa2 commit 24debfd

File tree

8 files changed

+326
-86
lines changed

8 files changed

+326
-86
lines changed

libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,13 @@ public final class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotli
831831
public static final fun ExternalKotlinTargetDescriptor (Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetDescriptor;
832832
}
833833

834+
public final class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetSourcesJarUtilsKt {
835+
public static final fun includeSources (Lorg/gradle/jvm/tasks/Jar;Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinCompilation;)V
836+
public static final fun publishSources (Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget;Lorg/gradle/api/tasks/TaskProvider;)V
837+
public static final fun publishSources (Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget;Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinCompilation;)V
838+
public static final fun sourcesJarTask (Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget;Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinCompilation;)Lorg/gradle/api/tasks/TaskProvider;
839+
}
840+
834841
public abstract class org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier {
835842
}
836843

libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/InternalKotlinCompilation.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ package org.jetbrains.kotlin.gradle.plugin.mpp
88
import org.gradle.api.file.FileCollection
99
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
1010
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
11+
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle
1112
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
13+
import org.jetbrains.kotlin.gradle.plugin.await
1214
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationConfigurationsContainer
1315
import org.jetbrains.kotlin.gradle.utils.ObservableSet
1416
import org.jetbrains.kotlin.tooling.core.HasMutableExtras
@@ -25,4 +27,9 @@ internal interface InternalKotlinCompilation<out T : KotlinCommonOptions> : Kotl
2527
internal val <T : KotlinCommonOptions> KotlinCompilation<T>.internal: InternalKotlinCompilation<T>
2628
get() = (this as? InternalKotlinCompilation<T>) ?: throw IllegalArgumentException(
2729
"KotlinCompilation($name) ${this::class} does not implement ${InternalKotlinCompilation::class}"
28-
)
30+
)
31+
32+
internal suspend fun InternalKotlinCompilation<*>.awaitAllKotlinSourceSets(): Set<KotlinSourceSet> {
33+
KotlinPluginLifecycle.Stage.AfterFinaliseCompilations.await()
34+
return allKotlinSourceSets
35+
}

libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt

Lines changed: 4 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@ package org.jetbrains.kotlin.gradle.plugin.mpp
77

88
import org.gradle.api.Plugin
99
import org.gradle.api.Project
10-
import org.gradle.api.file.DuplicatesStrategy
11-
import org.gradle.api.plugins.BasePlugin
1210
import org.gradle.api.plugins.JavaBasePlugin
13-
import org.gradle.api.tasks.TaskProvider
14-
import org.gradle.jvm.tasks.Jar
1511
import org.gradle.util.GradleVersion
16-
import org.jetbrains.kotlin.gradle.dsl.*
12+
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
1713
import org.jetbrains.kotlin.gradle.dsl.explicitApiModeAsCompilerArg
14+
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
1815
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
1916
import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
2017
import org.jetbrains.kotlin.gradle.plugin.*
@@ -37,14 +34,12 @@ import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinWasmTargetPreset
3734
import org.jetbrains.kotlin.gradle.targets.native.createFatFrameworks
3835
import org.jetbrains.kotlin.gradle.targets.native.tasks.artifact.registerKotlinArtifactsExtension
3936
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool
40-
import org.jetbrains.kotlin.gradle.tasks.locateTask
41-
import org.jetbrains.kotlin.gradle.tasks.registerTask
42-
import org.jetbrains.kotlin.gradle.utils.*
37+
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
38+
import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheck
4339
import org.jetbrains.kotlin.konan.target.HostManager
4440
import org.jetbrains.kotlin.konan.target.KonanTarget.*
4541
import org.jetbrains.kotlin.konan.target.presetName
4642
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
47-
import java.io.File
4843

4944
class KotlinMultiplatformPlugin : Plugin<Project> {
5045

@@ -259,62 +254,7 @@ internal fun applyUserDefinedAttributes(target: AbstractKotlinTarget) {
259254
}
260255
}
261256

262-
internal fun sourcesJarTask(compilation: KotlinCompilation<*>, componentName: String, artifactNameAppendix: String): TaskProvider<Jar> =
263-
sourcesJarTask(
264-
compilation.target.project,
265-
compilation.target.project.future {
266-
KotlinPluginLifecycle.Stage.AfterFinaliseCompilations.await()
267-
compilation.allKotlinSourceSets.associate { it.name to it.kotlin }
268-
},
269-
componentName,
270-
artifactNameAppendix
271-
)
272-
273-
private fun sourcesJarTask(
274-
project: Project,
275-
sourceSets: Future<Map<String, Iterable<File>>>,
276-
taskNamePrefix: String,
277-
artifactNameAppendix: String,
278-
): TaskProvider<Jar> =
279-
sourcesJarTaskNamed(lowerCamelCaseName(taskNamePrefix, "sourcesJar"), taskNamePrefix, project, sourceSets, artifactNameAppendix)
280-
281-
internal fun sourcesJarTaskNamed(
282-
taskName: String,
283-
componentName: String,
284-
project: Project,
285-
sourceSets: Future<Map<String, Iterable<File>>>,
286-
artifactNameAppendix: String,
287-
componentTypeName: String = "target",
288-
): TaskProvider<Jar> {
289-
project.locateTask<Jar>(taskName)?.let {
290-
return it
291-
}
292-
293-
val result = project.registerTask<Jar>(taskName) { sourcesJar ->
294-
sourcesJar.archiveAppendix.set(artifactNameAppendix)
295-
sourcesJar.archiveClassifier.set("sources")
296-
sourcesJar.isPreserveFileTimestamps = false
297-
sourcesJar.isReproducibleFileOrder = true
298-
sourcesJar.group = BasePlugin.BUILD_GROUP
299-
sourcesJar.description = "Assembles a jar archive containing the sources of $componentTypeName '$componentName'."
300-
}
301-
302-
result.configure {
303-
project.launch {
304-
sourceSets.await().forEach { (sourceSetName, sourceSetFiles) ->
305-
it.from(sourceSetFiles) { copySpec ->
306-
copySpec.into(sourceSetName)
307-
// Duplicates are coming from `SourceSets` that `sourceSet` depends on.
308-
// Such dependency was added by Kotlin compilation.
309-
// TODO: rethink approach for adding dependent `SourceSets` to Kotlin compilation `SourceSet`
310-
copySpec.duplicatesStrategy = DuplicatesStrategy.WARN
311-
}
312-
}
313-
}
314-
}
315257

316-
return result
317-
}
318258

319259
internal fun Project.setupGeneralKotlinExtensionParameters() {
320260
project.launch {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.gradle.plugin.mpp.external
7+
8+
import org.gradle.api.tasks.TaskProvider
9+
import org.gradle.jvm.tasks.Jar
10+
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
11+
import org.jetbrains.kotlin.gradle.plugin.mpp.includeSources
12+
import org.jetbrains.kotlin.gradle.plugin.mpp.internal
13+
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
14+
import org.jetbrains.kotlin.gradle.plugin.mpp.sourcesJarTask
15+
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
16+
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
17+
18+
/**
19+
*
20+
* Will add all sources (including transitive dependsOn edges) from the compilation into this jar task given
21+
* the multiplatform convention.
22+
*
23+
* e.g.
24+
* ```
25+
* src/commonMain/kotlin/CommonMain.kt
26+
* src/jvmMain/kotlin/JvmMain.kt
27+
* ```
28+
*
29+
* will be packaged like
30+
* ```
31+
* /commonMain/CommonMain.kt
32+
* /jvmMain/JvmMain.kt
33+
* ```
34+
*
35+
* @since 1.9.20
36+
*/
37+
@ExternalKotlinTargetApi
38+
fun Jar.includeSources(compilation: DecoratedExternalKotlinCompilation) {
39+
includeSources(compilation.internal)
40+
}
41+
42+
/**
43+
* Registers, or returns if already existing, a sources jar task that contains
44+
* all sources of the given compilation (see [includeSources])
45+
*
46+
* @since 1.9.20
47+
*/
48+
@ExternalKotlinTargetApi
49+
fun DecoratedExternalKotlinTarget.sourcesJarTask(compilation: DecoratedExternalKotlinCompilation): TaskProvider<Jar> {
50+
return sourcesJarTask(
51+
compilation,
52+
componentName = lowerCamelCaseName(targetName, compilation.name.takeUnless { compilation.isMain() }),
53+
artifactNameAppendix = targetName.toLowerCaseAsciiOnly()
54+
)
55+
}
56+
57+
/**
58+
* Publishes the sources packaged by the given [jarTask] in this targets' publication.
59+
*
60+
* @since 1.9.20
61+
*/
62+
@ExternalKotlinTargetApi
63+
fun DecoratedExternalKotlinTarget.publishSources(jarTask: TaskProvider<Jar>) {
64+
delegate.sourcesElementsPublishedConfiguration.outgoing.artifact(jarTask) { artifact ->
65+
artifact.classifier = "sources"
66+
}
67+
}
68+
69+
/**
70+
* Publishes the sources associated with the given [compilation] in this targets' publication.
71+
* Will register the corresponding [sourcesJarTask] if necessary.
72+
*
73+
* @since 1.9.20
74+
*/
75+
@ExternalKotlinTargetApi
76+
fun DecoratedExternalKotlinTarget.publishSources(compilation: DecoratedExternalKotlinCompilation) {
77+
publishSources(sourcesJarTask(compilation))
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.gradle.plugin.mpp
7+
8+
import org.gradle.api.Project
9+
import org.gradle.api.file.DuplicatesStrategy
10+
import org.gradle.api.plugins.BasePlugin
11+
import org.gradle.api.tasks.TaskProvider
12+
import org.gradle.jvm.tasks.Jar
13+
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
14+
import org.jetbrains.kotlin.gradle.plugin.launch
15+
import org.jetbrains.kotlin.gradle.tasks.locateTask
16+
import org.jetbrains.kotlin.gradle.tasks.registerTask
17+
import org.jetbrains.kotlin.gradle.utils.Future
18+
import org.jetbrains.kotlin.gradle.utils.future
19+
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
20+
import java.io.File
21+
22+
23+
internal fun sourcesJarTask(compilation: KotlinCompilation<*>, componentName: String, artifactNameAppendix: String): TaskProvider<Jar> =
24+
sourcesJarTask(
25+
project = compilation.target.project,
26+
sourceSets = compilation.target.project.future {
27+
compilation.internal.awaitAllKotlinSourceSets().associate { it.name to it.kotlin }
28+
},
29+
componentName = componentName,
30+
artifactNameAppendix = artifactNameAppendix
31+
)
32+
33+
private fun sourcesJarTask(
34+
project: Project,
35+
sourceSets: Future<Map<String, Iterable<File>>>,
36+
componentName: String,
37+
artifactNameAppendix: String,
38+
): TaskProvider<Jar> =
39+
sourcesJarTaskNamed(
40+
taskName = lowerCamelCaseName(componentName, "sourcesJar"),
41+
componentName = componentName,
42+
project = project,
43+
sourceSets = sourceSets,
44+
artifactNameAppendix = artifactNameAppendix
45+
)
46+
47+
internal fun sourcesJarTaskNamed(
48+
taskName: String,
49+
componentName: String,
50+
project: Project,
51+
sourceSets: Future<Map<String, Iterable<File>>>,
52+
artifactNameAppendix: String,
53+
componentTypeName: String = "target",
54+
): TaskProvider<Jar> {
55+
project.locateTask<Jar>(taskName)?.let {
56+
return it
57+
}
58+
59+
val result = project.registerTask<Jar>(taskName) { sourcesJar ->
60+
sourcesJar.archiveAppendix.set(artifactNameAppendix)
61+
sourcesJar.archiveClassifier.set("sources")
62+
sourcesJar.isPreserveFileTimestamps = false
63+
sourcesJar.isReproducibleFileOrder = true
64+
sourcesJar.group = BasePlugin.BUILD_GROUP
65+
sourcesJar.description = "Assembles a jar archive containing the sources of $componentTypeName '$componentName'."
66+
project.launch {
67+
sourcesJar.includeSources(sourceSets.await())
68+
}
69+
}
70+
71+
return result
72+
}
73+
74+
internal fun Jar.includeSources(compilation: KotlinCompilation<*>) {
75+
compilation.internal.allKotlinSourceSets.forAll { sourceSet ->
76+
includeSources(sourceSet.name, sourceSet.kotlin)
77+
}
78+
}
79+
80+
internal fun Jar.includeSources(sourceSets: Map<String, Iterable<File>>) {
81+
sourceSets.forEach { (name, sources) -> includeSources(name, sources) }
82+
}
83+
84+
internal fun Jar.includeSources(name: String, sources: Iterable<File>) {
85+
from(sources) { spec ->
86+
spec.into(name)
87+
// Duplicates are coming from `SourceSets` that `sourceSet` depends on.
88+
// Such dependency was added by Kotlin compilation.
89+
// TODO: rethink approach for adding dependent `SourceSets` to Kotlin compilation `SourceSet`
90+
spec.duplicatesStrategy = DuplicatesStrategy.WARN
91+
}
92+
}

libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiTests.kt

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,24 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree
1717
import org.jetbrains.kotlin.gradle.plugin.hierarchy.KotlinSourceSetTreeClassifier
1818
import org.jetbrains.kotlin.gradle.plugin.hierarchy.orNull
1919
import org.jetbrains.kotlin.gradle.plugin.launchInStage
20-
import org.jetbrains.kotlin.gradle.plugin.mpp.external.*
2120
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor.CompilationFactory
22-
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinTargetDescriptor.TargetFactory
23-
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
24-
import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest
21+
import org.jetbrains.kotlin.gradle.plugin.mpp.external.createCompilation
22+
import org.jetbrains.kotlin.gradle.plugin.mpp.external.createExternalKotlinTarget
23+
import org.jetbrains.kotlin.gradle.util.*
2524
import org.jetbrains.kotlin.gradle.utils.property
2625
import org.jetbrains.kotlin.gradle.utils.toMap
2726
import kotlin.test.*
2827

2928
class ExternalKotlinTargetApiTests {
3029

31-
class FakeTarget(delegate: Delegate) : DecoratedExternalKotlinTarget(delegate)
32-
class FakeCompilation(delegate: Delegate) : DecoratedExternalKotlinCompilation(delegate)
33-
3430
val project = buildProjectWithMPP()
3531
val kotlin = project.multiplatformExtension
3632

37-
private fun ExternalKotlinTargetDescriptorBuilder<FakeTarget>.defaults() {
38-
targetName = "fake"
39-
platformType = KotlinPlatformType.jvm
40-
targetFactory = TargetFactory(::FakeTarget)
41-
}
42-
43-
private fun ExternalKotlinCompilationDescriptorBuilder<FakeCompilation>.defaults() {
44-
compilationName = "fake"
45-
compilationFactory = CompilationFactory(::FakeCompilation)
46-
defaultSourceSet = kotlin.sourceSets.maybeCreate("fake")
47-
}
4833

4934
@Test
5035
fun `test - sourceSetClassifier - default`() = buildProjectWithMPP().runLifecycleAwareTest {
5136
val target = kotlin.createExternalKotlinTarget<FakeTarget> { defaults() }
52-
val compilation = target.createCompilation<FakeCompilation> { defaults() }
37+
val compilation = target.createCompilation<FakeCompilation> { defaults(kotlin) }
5338

5439
assertEquals(KotlinSourceSetTree("fake"), KotlinSourceSetTree.orNull(compilation))
5540
}
@@ -58,7 +43,7 @@ class ExternalKotlinTargetApiTests {
5843
fun `test - sourceSetClassifier - custom name`() = buildProjectWithMPP().runLifecycleAwareTest {
5944
val target = kotlin.createExternalKotlinTarget<FakeTarget> { defaults() }
6045
val compilation = target.createCompilation<FakeCompilation> {
61-
defaults()
46+
defaults(kotlin)
6247
sourceSetTreeClassifierV2 = KotlinSourceSetTreeClassifier.Name("mySourceSetTree")
6348
}
6449

@@ -73,7 +58,7 @@ class ExternalKotlinTargetApiTests {
7358
val target = kotlin.createExternalKotlinTarget<FakeTarget> { defaults() }
7459

7560
val mainCompilation = target.createCompilation<FakeCompilation> {
76-
defaults()
61+
defaults(kotlin)
7762
sourceSetTreeClassifierV2 = KotlinSourceSetTreeClassifier.Property(myProperty)
7863
}
7964

0 commit comments

Comments
 (0)