From 9599c16d95fc248fa5215e143247d50f0bd74020 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Mon, 26 Sep 2022 16:19:31 +0200 Subject: [PATCH] [Gradle] Initial 'externalTargetApi' w/ compilable Android prototype ^KT-54766 Verification Pending --- .../kotlin/gradle/ExternalKotlinTargetApi.kt | 12 ++ .../build.gradle.kts | 15 +- .../gradle/android/AndroidTargetPrototype.kt | 161 ++++++++++++++++++ .../android/PrototypeAndroidCompilation.kt | 26 +++ .../gradle/android/PrototypeAndroidTarget.kt | 27 +++ .../gradle/android/androidBootClasspath.kt | 16 ++ .../android/createAndroidCompilation.kt | 49 ++++++ .../jetbrains/kotlin/gradle/android/utils.kt | 13 ++ .../gradle/plugin/KotlinTargetConfigurator.kt | 3 +- .../gradle/plugin/mpp/AbstractKotlinTarget.kt | 116 ++++++------- ...otlinCompilationConfigurationsContainer.kt | 2 +- .../KotlinCompilationFriendPathsResolver.kt | 2 +- .../compilationImpl/KotlinCompilationImpl.kt | 4 +- .../external/DecoratedExternalKotlinTarget.kt | 14 ++ .../mpp/external/ExternalKotlinCompilation.kt | 19 +++ .../ExternalKotlinCompilationDescriptor.kt | 98 +++++++++++ .../mpp/external/ExternalKotlinTarget.kt | 85 +++++++++ .../ExternalKotlinTargetDescriptor.kt | 69 ++++++++ .../createExternalKotlinCompilation.kt | 89 ++++++++++ .../external/createExternalKotlinTarget.kt | 51 ++++++ .../jetbrains/kotlin/gradle/buildProject.kt | 11 +- ...ExternalAndroidTargetPrototypeSmokeTest.kt | 48 ++++++ 22 files changed, 855 insertions(+), 75 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-annotations/src/main/kotlin/org/jetbrains/kotlin/gradle/ExternalKotlinTargetApi.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/PrototypeAndroidCompilation.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/PrototypeAndroidTarget.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidBootClasspath.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/createAndroidCompilation.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/utils.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilation.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTarget.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetDescriptor.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt diff --git a/libraries/tools/kotlin-gradle-plugin-annotations/src/main/kotlin/org/jetbrains/kotlin/gradle/ExternalKotlinTargetApi.kt b/libraries/tools/kotlin-gradle-plugin-annotations/src/main/kotlin/org/jetbrains/kotlin/gradle/ExternalKotlinTargetApi.kt new file mode 100644 index 00000000000..d2339db1f12 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-annotations/src/main/kotlin/org/jetbrains/kotlin/gradle/ExternalKotlinTargetApi.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts index 7012a3ddbe4..4212e162549 100644 --- a/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts @@ -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() +} diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt new file mode 100644 index 00000000000..640f3b6ad57 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt @@ -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() + + /* + 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 { + 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 +} diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/PrototypeAndroidCompilation.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/PrototypeAndroidCompilation.kt new file mode 100644 index 00000000000..a690372801c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/PrototypeAndroidCompilation.kt @@ -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 + get() = super.compilerOptions as HasCompilerOptions + + var androidCompilationSpecificStuff = 10 +} + diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/PrototypeAndroidTarget.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/PrototypeAndroidTarget.kt new file mode 100644 index 00000000000..ea1020d8b02 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/PrototypeAndroidTarget.kt @@ -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() + + @Suppress("unchecked_cast") + override val compilations: NamedDomainObjectContainer + get() = target.compilations as NamedDomainObjectContainer +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidBootClasspath.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidBootClasspath.kt new file mode 100644 index 00000000000..14d4d1dd8b2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidBootClasspath.kt @@ -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().bootClasspath }) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/createAndroidCompilation.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/createAndroidCompilation.kt new file mode 100644 index 00000000000..65a8de036e5 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/createAndroidCompilation.kt @@ -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)) + } + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/utils.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/utils.kt new file mode 100644 index 00000000000..c9cb725356e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/utils.kt @@ -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() +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt index 4af920e34bf..2f03dcb688d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt @@ -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 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 } }) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt index 5b3be3f1606..bc148b93a2d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt @@ -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 by lazy { - buildAdhocComponentsFromKotlinVariants(kotlinComponents) - } - - private fun buildAdhocComponentsFromKotlinVariants(kotlinVariants: Set): Set { - 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().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 copyAttribute(key: Attribute, 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 = - (kotlinVariant as? KotlinVariantWithMetadataVariant)?.variants.orEmpty() - - override fun getName(): String = adhocVariant.name - override fun getUsages(): MutableSet = (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): Set { + 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().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 copyAttribute(key: Attribute, 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 = + (kotlinVariant as? KotlinVariantWithMetadataVariant)?.variants.orEmpty() + + override fun getName(): String = adhocVariant.name + override fun getUsages(): MutableSet = (adhocVariant as SoftwareComponentInternal).usages + } + }.toSet() +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationConfigurationsContainer.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationConfigurationsContainer.kt index e2ce160e380..8389969071c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationConfigurationsContainer.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationConfigurationsContainer.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt index dee1b2734ee..e3c091d42e3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt @@ -32,7 +32,7 @@ internal class DefaultKotlinCompilationFriendPathsResolver( /* Resolution of friend artifacts */ - interface FriendArtifactResolver { + fun interface FriendArtifactResolver { fun resolveFriendArtifacts(compilation: InternalKotlinCompilation<*>): FileCollection companion object { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt index bef36624706..0b798b83a1d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt @@ -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 { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt new file mode 100644 index 00000000000..ca58188a7ed --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt @@ -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 \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilation.kt new file mode 100644 index 00000000000..4d9f90a8d44 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilation.kt @@ -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(delegate.compilation) { + class Delegate internal constructor(internal val compilation: KotlinCompilationImpl) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt new file mode 100644 index 00000000000..38292eb7dc4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt @@ -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 { + fun interface DecoratedKotlinCompilationFactory> { + fun create(delegate: ExternalKotlinCompilation.Delegate): T + } + + fun interface FriendArtifactResolver { + fun resolveFriendPaths(compilation: T): FileCollection + } + + fun interface CompilationAssociator { + fun associate(compilation: T, main: ExternalKotlinCompilation) + } + + val compilationName: String + val compileTaskName: String? + val compileAllTaskName: String? + val defaultSourceSet: KotlinSourceSet + val decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory + val friendArtifactResolver: FriendArtifactResolver? + val compilationAssociator: CompilationAssociator? + val configure: ((T) -> Unit)? +} + +@ExternalKotlinTargetApi +fun ExternalKotlinCompilationDescriptor( + configure: ExternalKotlinCompilationDescriptorBuilder.() -> Unit +): ExternalKotlinCompilationDescriptor { + return ExternalKotlinCompilationDescriptorBuilderImpl().also(configure).run { + ExternalKotlinCompilationDescriptorImpl( + compilationName = compilationName, + compileTaskName = compileTaskName, + compileAllTaskName = compileAllTaskName, + defaultSourceSet = defaultSourceSet, + decoratedKotlinCompilationFactory = decoratedKotlinCompilationFactory, + friendArtifactResolver = friendArtifactResolver, + compilationAssociator = compilationAssociator, + configure = this.configure + ) + } +} + +@ExternalKotlinTargetApi +interface ExternalKotlinCompilationDescriptorBuilder { + var compilationName: String + var compileTaskName: String? + var compileAllTaskName: String? + var defaultSourceSet: KotlinSourceSet + var decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory + var friendArtifactResolver: FriendArtifactResolver? + var compilationAssociator: CompilationAssociator? + 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 : + ExternalKotlinCompilationDescriptorBuilder { + 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 by Delegates.notNull() + override var friendArtifactResolver: FriendArtifactResolver? = null + override var compilationAssociator: CompilationAssociator? = null + override var configure: ((T) -> Unit)? = null +} + +@ExternalKotlinTargetApi +private data class ExternalKotlinCompilationDescriptorImpl( + override val compilationName: String, + override val compileTaskName: String?, + override val compileAllTaskName: String?, + override val defaultSourceSet: KotlinSourceSet, + override val decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory, + override val friendArtifactResolver: FriendArtifactResolver?, + override val compilationAssociator: CompilationAssociator?, + override val configure: ((T) -> Unit)? +) : ExternalKotlinCompilationDescriptor \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTarget.kt new file mode 100644 index 00000000000..7b4d7105f7d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTarget.kt @@ -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, + private val artifactsTaskLocator: ArtifactsTaskLocator, +) : KotlinTarget { + + fun interface ArtifactsTaskLocator { + fun locate(target: ExternalKotlinTarget): TaskProvider + } + + val kotlin = project.multiplatformExtension + + override val useDisambiguationClassifierAsSourceSetNamePrefix: Boolean = true + + override val overrideDisambiguationClassifierOnIdeImport: String? = null + + val artifactsTask: TaskProvider 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 by lazy { + project.buildAdhocComponentsFromKotlinVariants(kotlinComponents) + } + + override val compilations: NamedDomainObjectContainer by lazy { + project.container(ExternalKotlinCompilation::class.java) + } + + override fun mavenPublication(action: Action) { + TODO("Not yet implemented") + } + + override val preset: Nothing? = null + + override fun getAttributes(): AttributeContainer { + TODO("Not yet implemented") + } + + internal fun onCreated() { + artifactsTask + components + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetDescriptor.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetDescriptor.kt new file mode 100644 index 00000000000..47707de099d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetDescriptor.kt @@ -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 { + + fun interface DecoratedExternalTargetFactory { + fun create(target: ExternalKotlinTarget): T + } + + val targetName: String + val platformType: KotlinPlatformType + val decoratedExternalTargetFactory: DecoratedExternalTargetFactory + val configure: ((T) -> Unit)? +} + +@ExternalKotlinTargetApi +interface ExternalKotlinTargetDescriptorBuilder { + var targetName: String + var platformType: KotlinPlatformType + var decoratedExternalTargetFactory: DecoratedExternalTargetFactory + 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 ExternalKotlinTargetDescriptor( + configure: ExternalKotlinTargetDescriptorBuilder.() -> Unit +): ExternalKotlinTargetDescriptor { + return ExternalKotlinTargetDescriptorBuilderImpl().also(configure).build() +} + +@ExternalKotlinTargetApi +private class ExternalKotlinTargetDescriptorBuilderImpl : ExternalKotlinTargetDescriptorBuilder { + override var targetName: String by Delegates.notNull() + override var platformType: KotlinPlatformType by Delegates.notNull() + override var decoratedExternalTargetFactory: DecoratedExternalTargetFactory by Delegates.notNull() + override var configure: ((T) -> Unit)? = null + + fun build(): ExternalKotlinTargetDescriptorImpl = ExternalKotlinTargetDescriptorImpl( + targetName = targetName, + platformType = platformType, + decoratedExternalTargetFactory = decoratedExternalTargetFactory, + configure = configure + ) +} + +@ExternalKotlinTargetApi +private data class ExternalKotlinTargetDescriptorImpl( + override val targetName: String, + override val platformType: KotlinPlatformType, + override val decoratedExternalTargetFactory: DecoratedExternalTargetFactory, + override val configure: ((T) -> Unit)? +) : ExternalKotlinTargetDescriptor + + diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt new file mode 100644 index 00000000000..d11f31bbc9b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt @@ -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 DecoratedExternalKotlinTarget.createCompilation( + descriptor: ExternalKotlinCompilationDescriptor +): 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 DecoratedExternalKotlinTarget.createCompilation( + descriptor: ExternalKotlinCompilationDescriptorBuilder.() -> Unit +): T { + return createCompilation(ExternalKotlinCompilationDescriptor(descriptor)) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt new file mode 100644 index 00000000000..0c556462ef6 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt @@ -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 KotlinMultiplatformExtension.createExternalKotlinTarget( + descriptor: ExternalKotlinTargetDescriptor +): 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(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 KotlinMultiplatformExtension.createExternalKotlinTarget( + descriptor: ExternalKotlinTargetDescriptorBuilder.() -> Unit +): T { + return createExternalKotlinTarget(ExternalKotlinTargetDescriptor(descriptor)) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/buildProject.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/buildProject.kt index dac5e8f001f..a6a1189d00a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/buildProject.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/buildProject.kt @@ -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() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt new file mode 100644 index 00000000000..68458f8c67d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt @@ -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 + } + } +} \ No newline at end of file