[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
@@ -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()
}