[Gradle] Initial 'externalTargetApi' w/ compilable Android prototype

^KT-54766 Verification Pending
This commit is contained in:
Sebastian Sellmair
2022-09-26 16:19:31 +02:00
committed by Space Team
parent 33adebe5bf
commit 9599c16d95
22 changed files with 855 additions and 75 deletions
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2022 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
@RequiresOptIn(
message = "This API is intended to be used by Google to maintain KotlinTargets outside of kotlin.git",
level = RequiresOptIn.Level.ERROR
)
annotation class ExternalKotlinTargetApi
@@ -9,9 +9,16 @@ dependencies {
compileOnly(project(":kotlin-gradle-plugin"))
}
/* This module is just for local development / prototyping and demos */
if (!kotlinBuildProperties.isTeamcityBuild) {
tasks.register("install") {
dependsOn(tasks.named("publishToMavenLocal"))
configureKotlinCompileTasksGradleCompatibility()
kotlin {
sourceSets.all {
languageSettings.optIn("org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi")
}
}
/* This module is just for local development / prototyping and demos */
if (!kotlinBuildProperties.isTeamcityBuild) {
publish()
standardPublicJars()
}
@@ -0,0 +1,161 @@
/*
* Copyright 2010-2022 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.
*/
@file:Suppress("DEPRECATION", "DuplicatedCode")
package org.jetbrains.kotlin.gradle.android
import com.android.build.gradle.AppExtension
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import org.gradle.api.attributes.Usage
import org.gradle.api.attributes.java.TargetJvmEnvironment
import org.gradle.api.attributes.java.TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.named
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.external.project
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.external.*
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinTargetDescriptor.DecoratedExternalTargetFactory
@OptIn(ExternalVariantApi::class)
fun KotlinMultiplatformExtension.androidTargetPrototype(): PrototypeAndroidTarget {
val project = this.project
val androidExtension = project.extensions.getByType<AppExtension>()
/*
Set a variant filter and only allow 'debug'.
Reason: This prototype will not deal w/ buildTypes or flavors.
Only 'debug' will be supported. As of agreed w/ AGP team, this is the initial goal
for the APIs.
*/
androidExtension.variantFilter { variant ->
if (variant.name != "debug") {
variant.ignore = true
}
}
/*
Create our 'AndroidTarget':
This uses the 'KotlinPlatformType.jvm' instead of androidJvm, since from the perspective of
Kotlin, this is just another 'jvm' like target (using the jvm compiler)
*/
val androidTarget = createExternalKotlinTarget<PrototypeAndroidTarget> {
targetName = "android"
platformType = KotlinPlatformType.jvm
decoratedExternalTargetFactory = DecoratedExternalTargetFactory { externalTarget ->
PrototypeAndroidTarget(externalTarget, PrototypeAndroidDsl(31))
}
}
/*
Whilst using the .all hook, we only expect the single 'debug' variant to be available through this API.
*/
androidExtension.applicationVariants.all { applicationVariant ->
project.logger.quiet("Setting up applicationVariant: ${applicationVariant.name}")
/*
Create Compilations: main, unitTest and instrumentedTest
(as proposed in the new Multiplatform/Android SourceSetLayout v2)
*/
val mainCompilation = androidTarget.createAndroidCompilation("main")
val unitTestCompilation = androidTarget.createAndroidCompilation("unitTest")
val instrumentedTestCompilation = androidTarget.createAndroidCompilation("instrumentedTest")
/*
Associate unitTest/instrumentedTest compilations with main
*/
unitTestCompilation.associateWith(mainCompilation)
instrumentedTestCompilation.associateWith(mainCompilation)
/*
Setup dependsOn edges as in Multiplatform/Android SourceSetLayout v2:
android/main dependsOn commonMain
android/unitTest dependsOn commonTest
android/instrumentedTest *does not depend on a common SourceSet*
*/
mainCompilation.defaultSourceSet.dependsOn(sourceSets.getByName("commonMain"))
unitTestCompilation.defaultSourceSet.dependsOn(sourceSets.getByName("commonTest"))
/*
Wire the Kotlin Compilations output (.class files) into the Android artifacts
by using the 'registerPreJavacGeneratedBytecode' function
*/
applicationVariant.registerPreJavacGeneratedBytecode(mainCompilation.output.classesDirs)
applicationVariant.unitTestVariant.registerPreJavacGeneratedBytecode(unitTestCompilation.output.classesDirs)
applicationVariant.testVariant.registerPreJavacGeneratedBytecode(instrumentedTestCompilation.output.classesDirs)
/*
Add dependencies coming from Kotlin to Android by adding all dependencies from Kotlin to the variants
compileConfiguration or runtimeConfiguration
*/
applicationVariant.compileConfiguration.extendsFrom(mainCompilation.configurations.compileDependencyConfiguration)
applicationVariant.runtimeConfiguration.extendsFrom(mainCompilation.configurations.runtimeDependencyConfiguration)
applicationVariant.unitTestVariant.compileConfiguration.extendsFrom(unitTestCompilation.configurations.compileDependencyConfiguration)
applicationVariant.unitTestVariant.runtimeConfiguration.extendsFrom(unitTestCompilation.configurations.runtimeDependencyConfiguration)
applicationVariant.testVariant.compileConfiguration.extendsFrom(instrumentedTestCompilation.configurations.compileDependencyConfiguration)
applicationVariant.testVariant.runtimeConfiguration.extendsFrom(instrumentedTestCompilation.configurations.runtimeDependencyConfiguration)
/*
Add the 'android boot classpath' to the compilation dependencies to compile against
*/
mainCompilation.configurations.compileDependencyConfiguration.dependencies.add(
project.dependencies.create(project.androidBootClasspath())
)
/*
Setup apiElements configuration:
Usage: JAVA_API
jvmEnvironment: Android
variants:
- classes (provides access to the compiled .class files)
artifactType: CLASSES_JAR
*/
project.configurations.getByName(androidTarget.apiElementsConfigurationName).apply {
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_API))
attributes.attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID))
outgoing.variants.create("classes").let { variant ->
variant.attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type)
variant.artifact(mainCompilation.output.classesDirs.singleFile) {
it.builtBy(mainCompilation.output.classesDirs)
}
}
}
/*
Setup runtimeElements configuration:
Usage: JAVA_RUNTIME
jvmEnvironment: Android
variants:
- classes (provides access to the compiled .class files)
artifactType: CLASSES_JAR
*/
project.configurations.getByName(androidTarget.runtimeElementsConfigurationName).apply {
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_RUNTIME))
attributes.attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID))
outgoing.variants.create("classes").let { variant ->
variant.attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type)
variant.artifact(mainCompilation.output.classesDirs.singleFile) {
it.builtBy(mainCompilation.output.classesDirs)
}
}
}
/*
"Disable" configurations from plain Android plugin
This hack will not be necessary in the final implementation
*/
project.configurations.findByName("${applicationVariant.name}ApiElements")?.isCanBeConsumed = false
project.configurations.findByName("${applicationVariant.name}RuntimeElements")?.isCanBeConsumed = false
}
return androidTarget
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2022 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.
*/
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
package org.jetbrains.kotlin.gradle.android
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import org.jetbrains.kotlin.gradle.plugin.HasCompilerOptions
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilation
class PrototypeAndroidCompilation(delegate: Delegate) : ExternalKotlinCompilation(delegate) {
override val kotlinOptions: KotlinCommonOptions
get() = super.kotlinOptions as KotlinJvmOptions
@Suppress("UNCHECKED_CAST")
override val compilerOptions: HasCompilerOptions<KotlinJvmCompilerOptions>
get() = super.compilerOptions as HasCompilerOptions<KotlinJvmCompilerOptions>
var androidCompilationSpecificStuff = 10
}
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2022 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.android
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.kotlin.dsl.getByType
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.external.DecoratedExternalKotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinTarget
data class PrototypeAndroidDsl(
var compileSdk: Int
)
class PrototypeAndroidTarget(
private val target: ExternalKotlinTarget,
val androidDsl: PrototypeAndroidDsl
) : DecoratedExternalKotlinTarget(target) {
internal val kotlin = target.project.extensions.getByType<KotlinMultiplatformExtension>()
@Suppress("unchecked_cast")
override val compilations: NamedDomainObjectContainer<PrototypeAndroidCompilation>
get() = target.compilations as NamedDomainObjectContainer<PrototypeAndroidCompilation>
}
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2022 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.android
import com.android.build.gradle.BaseExtension
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.kotlin.dsl.getByType
import java.util.concurrent.Callable
internal fun Project.androidBootClasspath(): FileCollection {
return project.files(Callable { project.extensions.getByType<BaseExtension>().bootClasspath })
}
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2022 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.android
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import org.gradle.api.attributes.java.TargetJvmEnvironment
import org.gradle.kotlin.dsl.named
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor.DecoratedKotlinCompilationFactory
import org.jetbrains.kotlin.gradle.plugin.mpp.external.createCompilation
internal fun PrototypeAndroidTarget.createAndroidCompilation(name: String): PrototypeAndroidCompilation {
return createCompilation {
compilationName = name
defaultSourceSet = kotlin.sourceSets.maybeCreate(camelCase("prototype", targetName, name))
decoratedKotlinCompilationFactory = DecoratedKotlinCompilationFactory(::PrototypeAndroidCompilation)
compileTaskName = camelCase("prototype", "compile", targetName, name)
/*
Replace Kotlin's compilation association (main <-> test) with noop,
since Android goes through adding a dependency on the project itself
*/
compilationAssociator = ExternalKotlinCompilationDescriptor.CompilationAssociator { first, second ->
first.configurations.compileDependencyConfiguration.extendsFrom(
second.configurations.apiConfiguration,
second.configurations.implementationConfiguration,
second.configurations.compileOnlyConfiguration
)
}
/* Configure the compilation before it is accessible for user code */
configure { compilation ->
/* Setup attributes for the compile dependencies */
compilation.configurations.compileDependencyConfiguration.apply {
attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type)
attributes.attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID))
}
/* Setup attributes for the runtime dependencies */
compilation.configurations.runtimeDependencyConfiguration?.apply {
attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type)
attributes.attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID))
}
}
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2022 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.
*/
@file:Suppress("DEPRECATION")
package org.jetbrains.kotlin.gradle.android
fun camelCase(vararg parts: String): String {
if (parts.isEmpty()) return ""
return parts.joinToString("") { it.capitalize() }.decapitalize()
}
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.*
@@ -143,7 +144,7 @@ abstract class AbstractKotlinTargetConfigurator<KotlinTargetType : KotlinTarget>
compilation.compileKotlinTaskProvider.map { it.outputs.files }
})
if (compilation is KotlinJvmCompilation && compilation.target.withJavaEnabled) {
if (compilation is KotlinJvmCompilation && (compilation.target as? KotlinJvmTarget)?.withJavaEnabled == true) {
it.inputs.files({ compilation.compileJavaTaskProvider?.map { it.outputs.files } })
}
@@ -20,16 +20,9 @@ import org.gradle.api.internal.component.SoftwareComponentInternal
import org.gradle.api.internal.component.UsageContext
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetContainerDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.tasks.dependsOn
import org.jetbrains.kotlin.gradle.utils.dashSeparatedName
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
@@ -84,61 +77,7 @@ abstract class AbstractKotlinTarget(
}
override val components: Set<SoftwareComponent> by lazy {
buildAdhocComponentsFromKotlinVariants(kotlinComponents)
}
private fun buildAdhocComponentsFromKotlinVariants(kotlinVariants: Set<KotlinTargetComponent>): Set<SoftwareComponent> {
val softwareComponentFactoryClass = SoftwareComponentFactory::class.java
// TODO replace internal API access with injection (not possible until we have this class on the compile classpath)
val softwareComponentFactory = (project as ProjectInternal).services.get(softwareComponentFactoryClass)
return kotlinVariants.map { kotlinVariant ->
val adhocVariant = softwareComponentFactory.adhoc(kotlinVariant.name)
project.whenEvaluated {
(kotlinVariant as SoftwareComponentInternal).usages.filterIsInstance<KotlinUsageContext>().forEach { kotlinUsageContext ->
val publishedConfigurationName = publishedConfigurationName(kotlinUsageContext.name)
val configuration = project.configurations.findByName(publishedConfigurationName)
?: project.configurations.create(publishedConfigurationName).also { configuration ->
configuration.isCanBeConsumed = false
configuration.isCanBeResolved = false
configuration.extendsFrom(project.configurations.getByName(kotlinUsageContext.dependencyConfigurationName))
configuration.artifacts.addAll(kotlinUsageContext.artifacts)
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, attributes, configuration.attributes)
}
}
adhocVariant.addVariantsFromConfiguration(configuration) { configurationVariantDetails ->
val mavenScope = when (kotlinUsageContext.usage.name) {
"java-api-jars" -> "compile"
"java-runtime-jars" -> "runtime"
else -> error("unexpected usage value '${kotlinUsageContext.usage.name}'")
}
configurationVariantDetails.mapToMavenScope(mavenScope)
}
}
}
adhocVariant as SoftwareComponent
object : ComponentWithVariants, ComponentWithCoordinates, SoftwareComponentInternal {
override fun getCoordinates() =
(kotlinVariant as? ComponentWithCoordinates)?.coordinates ?: error("kotlinVariant is not ComponentWithCoordinates")
override fun getVariants(): Set<SoftwareComponent> =
(kotlinVariant as? KotlinVariantWithMetadataVariant)?.variants.orEmpty()
override fun getName(): String = adhocVariant.name
override fun getUsages(): MutableSet<out UsageContext> = (adhocVariant as SoftwareComponentInternal).usages
}
}.toSet()
project.buildAdhocComponentsFromKotlinVariants(kotlinComponents)
}
protected open fun createKotlinVariant(
@@ -228,3 +167,56 @@ internal fun KotlinTarget.disambiguateName(simpleName: String) =
internal fun javaApiUsageForMavenScoping() = "java-api-jars"
internal fun Project.buildAdhocComponentsFromKotlinVariants(kotlinVariants: Set<KotlinTargetComponent>): Set<SoftwareComponent> {
val softwareComponentFactoryClass = SoftwareComponentFactory::class.java
// TODO replace internal API access with injection (not possible until we have this class on the compile classpath)
val softwareComponentFactory = (project as ProjectInternal).services.get(softwareComponentFactoryClass)
return kotlinVariants.map { kotlinVariant ->
val adhocVariant = softwareComponentFactory.adhoc(kotlinVariant.name)
project.whenEvaluated {
(kotlinVariant as SoftwareComponentInternal).usages.filterIsInstance<KotlinUsageContext>().forEach { kotlinUsageContext ->
val publishedConfigurationName = publishedConfigurationName(kotlinUsageContext.name)
val configuration = project.configurations.findByName(publishedConfigurationName)
?: project.configurations.create(publishedConfigurationName).also { configuration ->
configuration.isCanBeConsumed = false
configuration.isCanBeResolved = false
configuration.extendsFrom(project.configurations.getByName(kotlinUsageContext.dependencyConfigurationName))
configuration.artifacts.addAll(kotlinUsageContext.artifacts)
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, attributes, configuration.attributes)
}
}
adhocVariant.addVariantsFromConfiguration(configuration) { configurationVariantDetails ->
val mavenScope = when (kotlinUsageContext.usage.name) {
"java-api-jars" -> "compile"
"java-runtime-jars" -> "runtime"
else -> error("unexpected usage value '${kotlinUsageContext.usage.name}'")
}
configurationVariantDetails.mapToMavenScope(mavenScope)
}
}
}
adhocVariant as SoftwareComponent
object : ComponentWithVariants, ComponentWithCoordinates, SoftwareComponentInternal {
override fun getCoordinates() =
(kotlinVariant as? ComponentWithCoordinates)?.coordinates ?: error("kotlinVariant is not ComponentWithCoordinates")
override fun getVariants(): Set<SoftwareComponent> =
(kotlinVariant as? KotlinVariantWithMetadataVariant)?.variants.orEmpty()
override fun getName(): String = adhocVariant.name
override fun getUsages(): MutableSet<out UsageContext> = (adhocVariant as SoftwareComponentInternal).usages
}
}.toSet()
}
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler
internal interface KotlinCompilationConfigurationsContainer {
interface KotlinCompilationConfigurationsContainer {
val deprecatedCompileConfiguration: Configuration?
val deprecatedRuntimeConfiguration: Configuration?
val apiConfiguration: Configuration
@@ -32,7 +32,7 @@ internal class DefaultKotlinCompilationFriendPathsResolver(
/* Resolution of friend artifacts */
interface FriendArtifactResolver {
fun interface FriendArtifactResolver {
fun resolveFriendArtifacts(compilation: InternalKotlinCompilation<*>): FileCollection
companion object {
@@ -25,10 +25,8 @@ import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.utils.ObservableSet
import org.jetbrains.kotlin.tooling.core.MutableExtras
import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
import javax.inject.Inject
internal class KotlinCompilationImpl @Inject constructor(
internal class KotlinCompilationImpl constructor(
private val params: Params
) : InternalKotlinCompilation<KotlinCommonOptions> {
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2022 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.plugin.mpp.external
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
@ExternalKotlinTargetApi
abstract class DecoratedExternalKotlinTarget(
internal val delegate: ExternalKotlinTarget
) : KotlinTarget by delegate
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2022 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.
*/
@file:Suppress("DEPRECATION")
package org.jetbrains.kotlin.gradle.plugin.mpp.external
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.plugin.mpp.DecoratedKotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationImpl
@ExternalKotlinTargetApi
abstract class ExternalKotlinCompilation(delegate: Delegate) :
DecoratedKotlinCompilation<KotlinCommonOptions>(delegate.compilation) {
class Delegate internal constructor(internal val compilation: KotlinCompilationImpl)
}
@@ -0,0 +1,98 @@
/*
* Copyright 2010-2022 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.plugin.mpp.external
import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.DecoratedKotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor.*
import kotlin.properties.Delegates
@ExternalKotlinTargetApi
interface ExternalKotlinCompilationDescriptor<T : ExternalKotlinCompilation> {
fun interface DecoratedKotlinCompilationFactory<T : DecoratedKotlinCompilation<*>> {
fun create(delegate: ExternalKotlinCompilation.Delegate): T
}
fun interface FriendArtifactResolver<T : ExternalKotlinCompilation> {
fun resolveFriendPaths(compilation: T): FileCollection
}
fun interface CompilationAssociator<T : ExternalKotlinCompilation> {
fun associate(compilation: T, main: ExternalKotlinCompilation)
}
val compilationName: String
val compileTaskName: String?
val compileAllTaskName: String?
val defaultSourceSet: KotlinSourceSet
val decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory<T>
val friendArtifactResolver: FriendArtifactResolver<T>?
val compilationAssociator: CompilationAssociator<T>?
val configure: ((T) -> Unit)?
}
@ExternalKotlinTargetApi
fun <T : ExternalKotlinCompilation> ExternalKotlinCompilationDescriptor(
configure: ExternalKotlinCompilationDescriptorBuilder<T>.() -> Unit
): ExternalKotlinCompilationDescriptor<T> {
return ExternalKotlinCompilationDescriptorBuilderImpl<T>().also(configure).run {
ExternalKotlinCompilationDescriptorImpl(
compilationName = compilationName,
compileTaskName = compileTaskName,
compileAllTaskName = compileAllTaskName,
defaultSourceSet = defaultSourceSet,
decoratedKotlinCompilationFactory = decoratedKotlinCompilationFactory,
friendArtifactResolver = friendArtifactResolver,
compilationAssociator = compilationAssociator,
configure = this.configure
)
}
}
@ExternalKotlinTargetApi
interface ExternalKotlinCompilationDescriptorBuilder<T : ExternalKotlinCompilation> {
var compilationName: String
var compileTaskName: String?
var compileAllTaskName: String?
var defaultSourceSet: KotlinSourceSet
var decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory<T>
var friendArtifactResolver: FriendArtifactResolver<T>?
var compilationAssociator: CompilationAssociator<T>?
var configure: ((T) -> Unit)?
fun configure(action: (T) -> Unit) = apply {
val configure = this.configure
if (configure == null) this.configure = action
else this.configure = { configure(it); action(it) }
}
}
@ExternalKotlinTargetApi
private class ExternalKotlinCompilationDescriptorBuilderImpl<T : ExternalKotlinCompilation> :
ExternalKotlinCompilationDescriptorBuilder<T> {
override var compilationName: String by Delegates.notNull()
override var compileTaskName: String? = null
override var compileAllTaskName: String? = null
override var defaultSourceSet: KotlinSourceSet by Delegates.notNull()
override var decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory<T> by Delegates.notNull()
override var friendArtifactResolver: FriendArtifactResolver<T>? = null
override var compilationAssociator: CompilationAssociator<T>? = null
override var configure: ((T) -> Unit)? = null
}
@ExternalKotlinTargetApi
private data class ExternalKotlinCompilationDescriptorImpl<T : ExternalKotlinCompilation>(
override val compilationName: String,
override val compileTaskName: String?,
override val compileAllTaskName: String?,
override val defaultSourceSet: KotlinSourceSet,
override val decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory<T>,
override val friendArtifactResolver: FriendArtifactResolver<T>?,
override val compilationAssociator: CompilationAssociator<T>?,
override val configure: ((T) -> Unit)?
) : ExternalKotlinCompilationDescriptor<T>
@@ -0,0 +1,85 @@
/*
* Copyright 2010-2022 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.plugin.mpp.external
import org.gradle.api.Action
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.component.SoftwareComponent
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent
import org.jetbrains.kotlin.gradle.plugin.mpp.buildAdhocComponentsFromKotlinVariants
@ExternalKotlinTargetApi
class ExternalKotlinTarget internal constructor(
override val project: Project,
override val targetName: String,
override val platformType: KotlinPlatformType,
val defaultConfiguration: Configuration,
val apiElementsConfiguration: Configuration,
val runtimeElementsConfiguration: Configuration,
override val publishable: Boolean,
internal val kotlinComponents: Set<KotlinTargetComponent>,
private val artifactsTaskLocator: ArtifactsTaskLocator,
) : KotlinTarget {
fun interface ArtifactsTaskLocator {
fun locate(target: ExternalKotlinTarget): TaskProvider<out Task>
}
val kotlin = project.multiplatformExtension
override val useDisambiguationClassifierAsSourceSetNamePrefix: Boolean = true
override val overrideDisambiguationClassifierOnIdeImport: String? = null
val artifactsTask: TaskProvider<out Task> by lazy {
artifactsTaskLocator.locate(this)
}
override val artifactsTaskName: String
get() = artifactsTask.name
override val defaultConfigurationName: String
get() = defaultConfiguration.name
override val apiElementsConfigurationName: String
get() = apiElementsConfiguration.name
override val runtimeElementsConfigurationName: String
get() = runtimeElementsConfiguration.name
override val components: Set<SoftwareComponent> by lazy {
project.buildAdhocComponentsFromKotlinVariants(kotlinComponents)
}
override val compilations: NamedDomainObjectContainer<ExternalKotlinCompilation> by lazy {
project.container(ExternalKotlinCompilation::class.java)
}
override fun mavenPublication(action: Action<MavenPublication>) {
TODO("Not yet implemented")
}
override val preset: Nothing? = null
override fun getAttributes(): AttributeContainer {
TODO("Not yet implemented")
}
internal fun onCreated() {
artifactsTask
components
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2022 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.plugin.mpp.external
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinTargetDescriptor.DecoratedExternalTargetFactory
import kotlin.properties.Delegates
@ExternalKotlinTargetApi
interface ExternalKotlinTargetDescriptor<T : DecoratedExternalKotlinTarget> {
fun interface DecoratedExternalTargetFactory<T : DecoratedExternalKotlinTarget> {
fun create(target: ExternalKotlinTarget): T
}
val targetName: String
val platformType: KotlinPlatformType
val decoratedExternalTargetFactory: DecoratedExternalTargetFactory<T>
val configure: ((T) -> Unit)?
}
@ExternalKotlinTargetApi
interface ExternalKotlinTargetDescriptorBuilder<T : DecoratedExternalKotlinTarget> {
var targetName: String
var platformType: KotlinPlatformType
var decoratedExternalTargetFactory: DecoratedExternalTargetFactory<T>
var configure: ((T) -> Unit)?
fun configure(action: (T) -> Unit) = apply {
val configure = this.configure
if (configure == null) this.configure = action
else this.configure = { configure(it); action(it) }
}
}
@ExternalKotlinTargetApi
fun <T : DecoratedExternalKotlinTarget> ExternalKotlinTargetDescriptor(
configure: ExternalKotlinTargetDescriptorBuilder<T>.() -> Unit
): ExternalKotlinTargetDescriptor<T> {
return ExternalKotlinTargetDescriptorBuilderImpl<T>().also(configure).build()
}
@ExternalKotlinTargetApi
private class ExternalKotlinTargetDescriptorBuilderImpl<T : DecoratedExternalKotlinTarget> : ExternalKotlinTargetDescriptorBuilder<T> {
override var targetName: String by Delegates.notNull()
override var platformType: KotlinPlatformType by Delegates.notNull()
override var decoratedExternalTargetFactory: DecoratedExternalTargetFactory<T> by Delegates.notNull()
override var configure: ((T) -> Unit)? = null
fun build(): ExternalKotlinTargetDescriptorImpl<T> = ExternalKotlinTargetDescriptorImpl(
targetName = targetName,
platformType = platformType,
decoratedExternalTargetFactory = decoratedExternalTargetFactory,
configure = configure
)
}
@ExternalKotlinTargetApi
private data class ExternalKotlinTargetDescriptorImpl<T : DecoratedExternalKotlinTarget>(
override val targetName: String,
override val platformType: KotlinPlatformType,
override val decoratedExternalTargetFactory: DecoratedExternalTargetFactory<T>,
override val configure: ((T) -> Unit)?
) : ExternalKotlinTargetDescriptor<T>
@@ -0,0 +1,89 @@
/*
* Copyright 2010-2022 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.plugin.mpp.external
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.plugin.Kotlin2JvmSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationInfo
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.DefaultKotlinCompilationAssociator
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.DefaultKotlinCompilationFriendPathsResolver
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationAssociator
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationSourceSetsContainer
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.factory.*
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.factory.KotlinCompilationImplFactory.KotlinCompilationTaskNamesContainerFactory
import org.jetbrains.kotlin.gradle.plugin.mpp.decoratedInstance
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilation.Delegate
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileConfig
@ExternalKotlinTargetApi
fun <T : ExternalKotlinCompilation> DecoratedExternalKotlinTarget.createCompilation(
descriptor: ExternalKotlinCompilationDescriptor<T>
): T {
val compilationImplFactory = KotlinCompilationImplFactory(
compilerOptionsFactory = when (platformType) {
KotlinPlatformType.common -> KotlinMultiplatformCommonCompilerOptionsFactory
KotlinPlatformType.jvm -> KotlinJvmCompilerOptionsFactory
KotlinPlatformType.androidJvm -> KotlinJvmCompilerOptionsFactory
KotlinPlatformType.js -> KotlinJsCompilerOptionsFactory
KotlinPlatformType.native -> KotlinNativeCompilerOptionsFactory
KotlinPlatformType.wasm -> KotlinMultiplatformCommonCompilerOptionsFactory
},
compilationSourceSetsContainerFactory = { _, _ -> KotlinCompilationSourceSetsContainer(descriptor.defaultSourceSet) },
compilationTaskNamesContainerFactory = KotlinCompilationTaskNamesContainerFactory { target, compilationName ->
val default = DefaultKotlinCompilationTaskNamesContainerFactory.create(target, compilationName)
default.copy(
compileTaskName = descriptor.compileTaskName ?: default.compileTaskName,
compileAllTaskName = descriptor.compileAllTaskName ?: default.compileAllTaskName
)
},
compilationAssociator = descriptor.compilationAssociator?.let { declaredAssociator ->
@Suppress("unchecked_cast")
KotlinCompilationAssociator { _, first, second ->
declaredAssociator.associate(first.decoratedInstance as T, second.decoratedInstance as ExternalKotlinCompilation)
}
} ?: DefaultKotlinCompilationAssociator,
compilationFriendPathsResolver = DefaultKotlinCompilationFriendPathsResolver(
DefaultKotlinCompilationFriendPathsResolver.FriendArtifactResolver.composite(
DefaultKotlinCompilationFriendPathsResolver.DefaultFriendArtifactResolver,
descriptor.friendArtifactResolver?.let { declaredResolver ->
DefaultKotlinCompilationFriendPathsResolver.FriendArtifactResolver { compilation ->
@Suppress("unchecked_cast")
declaredResolver.resolveFriendPaths(compilation as T)
}
}
)
)
)
val compilationImpl = compilationImplFactory.create(this, descriptor.compilationName)
val decoratedCompilation = descriptor.decoratedKotlinCompilationFactory.create(Delegate(compilationImpl))
descriptor.configure?.invoke(decoratedCompilation)
this.delegate.compilations.add(decoratedCompilation)
val tasksProvider = KotlinTasksProvider()
val compilationInfo = KotlinCompilationInfo(decoratedCompilation)
val config = KotlinCompileConfig(compilationInfo)
config.configureTask { task ->
task.useModuleDetection.value(true).disallowChanges()
task.destinationDirectory.set(project.layout.buildDirectory.dir("tmp/kotlin-classes/debug"))
}
Kotlin2JvmSourceSetProcessor(tasksProvider, compilationInfo).run()
project.logger.quiet("Registered: ${compilationInfo.compileKotlinTaskName}")
return decoratedCompilation
}
@ExternalKotlinTargetApi
fun <T : ExternalKotlinCompilation> DecoratedExternalKotlinTarget.createCompilation(
descriptor: ExternalKotlinCompilationDescriptorBuilder<T>.() -> Unit
): T {
return createCompilation(ExternalKotlinCompilationDescriptor(descriptor))
}
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2022 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.
*/
@file:OptIn(ExternalKotlinTargetApi::class)
package org.jetbrains.kotlin.gradle.plugin.mpp.external
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
@ExternalKotlinTargetApi
fun <T : DecoratedExternalKotlinTarget> KotlinMultiplatformExtension.createExternalKotlinTarget(
descriptor: ExternalKotlinTargetDescriptor<T>
): T {
val defaultConfiguration = project.configurations.maybeCreate(lowerCamelCaseName(descriptor.targetName, "default"))
val apiElementsConfiguration = project.configurations.maybeCreate(lowerCamelCaseName(descriptor.targetName, "apiElements"))
val runtimeElementsConfiguration = project.configurations.maybeCreate(lowerCamelCaseName(descriptor.targetName, "runtimeElements"))
val artifactsTaskLocator = ExternalKotlinTarget.ArtifactsTaskLocator { target ->
target.project.locateOrRegisterTask<Jar>(lowerCamelCaseName(descriptor.targetName, "jar"))
}
val target = ExternalKotlinTarget(
project = project,
targetName = descriptor.targetName,
platformType = descriptor.platformType,
defaultConfiguration = defaultConfiguration,
apiElementsConfiguration = apiElementsConfiguration,
runtimeElementsConfiguration = runtimeElementsConfiguration,
publishable = true,
kotlinComponents = emptySet(),
artifactsTaskLocator = artifactsTaskLocator
)
val decorated = descriptor.decoratedExternalTargetFactory.create(target)
target.onCreated()
descriptor.configure?.invoke(decorated)
targets.add(decorated)
return decorated
}
@ExternalKotlinTargetApi
fun <T : DecoratedExternalKotlinTarget> KotlinMultiplatformExtension.createExternalKotlinTarget(
descriptor: ExternalKotlinTargetDescriptorBuilder<T>.() -> Unit
): T {
return createExternalKotlinTarget(ExternalKotlinTargetDescriptor(descriptor))
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.gradle
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.internal.project.ProjectInternal
@@ -13,7 +14,6 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.kpm.applyKpmPlugin
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJvmPlugin
import org.jetbrains.kotlin.gradle.plugin.extraProperties
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
fun buildProject(
@@ -51,8 +51,13 @@ fun Project.kotlin(code: KotlinMultiplatformExtension.() -> Unit) {
fun Project.androidLibrary(code: LibraryExtension.() -> Unit) {
plugins.findPlugin("com.android.library") ?: plugins.apply("com.android.library")
val androidExtension = project.extensions.findByName("android") as? LibraryExtension
?: throw IllegalStateException("Android library extension is missing in project")
val androidExtension = project.extensions.getByName("android") as LibraryExtension
androidExtension.code()
}
fun Project.androidApplication(code: ApplicationExtension.() -> Unit) {
plugins.findPlugin("com.android.application") ?: plugins.apply("com.android.application")
val androidExtension = project.extensions.getByName("android") as ApplicationExtension
androidExtension.code()
}
@@ -0,0 +1,48 @@
/*
* Copyright 2010-2022 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.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle.externalTargetApi
import org.jetbrains.kotlin.gradle.android.androidTargetPrototype
import org.jetbrains.kotlin.gradle.androidApplication
import org.jetbrains.kotlin.gradle.buildProjectWithMPP
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.kpm.idea.mavenCentralCacheRedirector
import org.junit.Test
import kotlin.test.assertEquals
class ExternalAndroidTargetPrototypeSmokeTest {
@Test
fun `apply prototype - evaluate - compilations exist`() {
val project = buildProjectWithMPP()
project.androidApplication { compileSdk = 31 }
val androidTargetPrototype = project.multiplatformExtension.androidTargetPrototype()
project.evaluate()
assertEquals(
setOf("main", "unitTest", "instrumentedTest"),
androidTargetPrototype.compilations.map { it.name }.toSet()
)
}
@Test
fun `apply prototype - evaluate - configurations can be resolved`() {
val project = buildProjectWithMPP()
project.androidApplication { compileSdk = 31 }
val androidTargetPrototype = project.multiplatformExtension.androidTargetPrototype()
project.repositories.mavenLocal()
project.repositories.mavenCentralCacheRedirector()
project.evaluate()
androidTargetPrototype.compilations.all { compilation ->
compilation.compileDependencyFiles.files
compilation.runtimeDependencyFiles?.files
}
}
}