From cb99f430337c847784339353eb0fa702a5aadbf9 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Thu, 2 Aug 2018 02:25:51 +0300 Subject: [PATCH] Implement new MPP plugin --- .../tools/kotlin-gradle-plugin/build.gradle | 4 + .../dsl/KotlinMultiplatformExtension.kt | 18 + .../gradle/plugin/KotlinPluginWrapper.kt | 15 + .../gradle/plugin/KotlinTargetConfigurator.kt | 366 ++++++++++++++++++ .../plugin/mpp/KotlinMultiplatformPlugin.kt | 160 ++++++++ .../plugin/mpp/KotlinSoftwareComponent.kt | 70 ++++ .../gradle/plugin/mpp/kotlinTargetPresets.kt | 206 ++++++++++ .../jetbrains/kotlin/gradle/tasks/Tasks.kt | 2 +- .../kotlin-multiplatform.properties | 1 + ....jetbrains.kotlin.multiplatform.properties | 1 + 10 files changed, 842 insertions(+), 1 deletion(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-multiplatform.properties create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.multiplatform.properties diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle b/libraries/tools/kotlin-gradle-plugin/build.gradle index 8400b20299d..77ba615b9b4 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle @@ -130,6 +130,10 @@ pluginBundle { id = 'org.jetbrains.kotlin.jvm' description = displayName = 'Kotlin JVM plugin' } + kotlinMultiplatformPlugin { + id = 'org.jetbrains.kotlin.multiplatform' + description = displayName = 'Kotlin Multiplatform plugin' + } kotlinAndroidPlugin { id = 'org.jetbrains.kotlin.android' description = displayName = 'Kotlin Android plugin' diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt new file mode 100644 index 00000000000..bcdad6b052e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.dsl + +import org.gradle.api.NamedDomainObjectCollection +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetPreset + +open class KotlinMultiplatformExtension : KotlinProjectExtension() { + lateinit var presets: NamedDomainObjectCollection> + internal set + + lateinit var targets: NamedDomainObjectCollection + internal set +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index 4b7379dddc0..08c069a5dfd 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -121,6 +121,21 @@ open class Kotlin2JsPluginWrapper @Inject constructor( get() = KotlinSingleJavaTargetExtension::class } +open class KotlinMultiplatformPluginWrapper @Inject constructor( + fileResolver: FileResolver, + private val instantiator: Instantiator, + private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry +): KotlinBasePluginWrapper(fileResolver) { + override fun getPlugin(project: Project, kotlinGradleBuildServices: KotlinGradleBuildServices): Plugin = + KotlinMultiplatformPlugin( + buildOutputCleanupRegistry, fileResolver, + instantiator, kotlinPluginVersion + ) + + override val projectExtensionClass: KClass + get() = KotlinMultiplatformExtension::class +} + fun Project.getKotlinPluginVersion(): String? = plugins.asSequence().mapNotNull { (it as? KotlinBasePluginWrapper)?.kotlinPluginVersion }.firstOrNull() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt new file mode 100644 index 00000000000..a230dc551a3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt @@ -0,0 +1,366 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin + +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.PublishArtifact +import org.gradle.api.artifacts.type.ArtifactTypeDefinition +import org.gradle.api.attributes.Usage +import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.FileCollection +import org.gradle.api.internal.artifacts.ArtifactAttributes +import org.gradle.api.internal.plugins.DslObject +import org.gradle.api.plugins.BasePlugin +import org.gradle.api.tasks.bundling.Jar +import org.gradle.internal.cleanup.BuildOutputCleanupRegistry +import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.gradle.language.jvm.tasks.ProcessResources +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName +import org.jetbrains.kotlin.gradle.plugin.mpp.fullName +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile +import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast +import java.io.File +import java.util.concurrent.Callable + +open class KotlinTargetConfigurator( + private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry +) { + fun configureTarget( + project: Project, + target: KotlinOnlyTarget + ) { + configureCompilationDefaults(project, target) + configureCompilations(project, target) + defineConfigurationsForTarget(project, target) + configureArchivesAndComponent(project, target) + configureBuild(project, target) + + setCompatibilityOfAbstractCompileTasks(project) + } + + private fun configureCompilations( + project: Project, + platformTarget: KotlinOnlyTarget + ) { + val main = platformTarget.compilations.create(KotlinCompilation.MAIN_COMPILATION_NAME) + + platformTarget.compilations.create(KotlinCompilation.TEST_COMPILATION_NAME).apply { + compileDependencyFiles = project.files(main.output, project.configurations.maybeCreate(compileDependencyConfigurationName)) + + if (this is KotlinCompilationToRunnableFiles) { + runtimeDependencyFiles = project.files(output, main.output, project.configurations.maybeCreate(runtimeDependencyConfigurationName)) + } + } + + platformTarget.compilations.all { + buildOutputCleanupRegistry.registerOutputs(it.output) + } + } + + private fun configureCompilationDefaults(project: Project, target: KotlinOnlyTarget) { + target.compilations.all { compilation -> + defineConfigurationsForCompilation(compilation, target, project.configurations) + + project.kotlinExtension.sourceSets.maybeCreate(compilation.fullName).also { sourceSet -> + compilation.source(sourceSet) // also adds dependencies, requires the configurations for target and source set to exist at this point + } + + if (compilation is KotlinCompilationWithResources) { + configureResourceProcessing(project, compilation, project.files()) + } + + createLifecycleTask(compilation, project) + } + } + + private fun configureArchivesAndComponent(project: Project, target: KotlinOnlyTarget<*>) { + val mainCompilation = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + + val jar = project.tasks.create(target.artifactsTaskName, Jar::class.java) + jar.description = "Assembles a jar archive containing the main classes." + jar.group = BasePlugin.BUILD_GROUP + jar.from(mainCompilation.output) + + val jarArtifact = ArchivePublishArtifact(jar) + val apiElementsConfiguration = project.configurations.getByName(target.apiElementsConfigurationName) + + target.disambiguationClassifier?.let { jar.classifier = it } + + // Workaround: adding the artifact during configuration seems to interfere with the Java plugin, which results into missing + // task dependency 'assemble -> jar' if the Java plugin is applied after this steps + project.afterEvaluate { + project.artifacts.add(Dependency.ARCHIVES_CONFIGURATION, jar) { jarArtifact -> + jarArtifact.builtBy(jar) + jarArtifact.type = ArtifactTypeDefinition.JAR_TYPE + + addJar(apiElementsConfiguration, jarArtifact) + + if (mainCompilation is KotlinCompilationToRunnableFiles) { + val runtimeConfiguration = project.configurations.getByName(mainCompilation.deprecatedRuntimeConfigurationName) + val runtimeElementsConfiguration = project.configurations.getByName(target.runtimeElementsConfigurationName) + addJar(runtimeConfiguration, jarArtifact) + addJar(runtimeElementsConfiguration, jarArtifact) + // TODO Check Gradle's special split into variants for classes & resources -- do we need that too? + } + } + } + } + + private fun addJar(configuration: Configuration, jarArtifact: PublishArtifact) { + val publications = configuration.outgoing + + // Configure an implicit variant + publications.artifacts.add(jarArtifact) + publications.attributes.attribute(ArtifactAttributes.ARTIFACT_FORMAT, ArtifactTypeDefinition.JAR_TYPE) + } + + private fun configureResourceProcessing( + project: Project, + compilation: KotlinCompilationWithResources, + resourceSet: FileCollection + ) { + compilation.output.setResourcesDir(Callable { + val classesDirName = "resources/" + compilation.compilationName + File(project.buildDir, classesDirName) + }) + + val resourcesTask = project.tasks.maybeCreate(compilation.processResourcesTaskName, ProcessResources::class.java) + resourcesTask.description = "Processes $resourceSet." + DslObject(resourcesTask).conventionMapping.map("destinationDir") { compilation.output.resourcesDir } + resourcesTask.from(resourceSet) + } + + private fun createLifecycleTask(compilation: KotlinCompilation, project: Project) { + (compilation.output.classesDirs as ConfigurableFileCollection).from(project.files().builtBy(compilation.compileAllTaskName)) + + project.tasks.create(compilation.compileAllTaskName).apply { + group = LifecycleBasePlugin.BUILD_GROUP + description = "Assembles " + compilation.output + "" + dependsOn( + compilation.output.dirs, + compilation.compileKotlinTaskName + ) + if (compilation is KotlinCompilationWithResources) { + dependsOn(compilation.processResourcesTaskName) + } + } + } + + private fun defineConfigurationsForTarget(project: Project, target: KotlinOnlyTarget<*>) { + val configurations = project.configurations + + val defaultConfiguration = configurations.maybeCreate(target.defaultConfigurationName) + val mainCompilation = target.compilations.maybeCreate(KotlinCompilation.MAIN_COMPILATION_NAME) + val testCompilation = target.compilations.maybeCreate(KotlinCompilation.TEST_COMPILATION_NAME) + + val compileConfiguration = configurations.maybeCreate(mainCompilation.deprecatedCompileConfigurationName) + val implementationConfiguration = configurations.maybeCreate(mainCompilation.implementationConfigurationName) + + val runtimeOnlyConfiguration = configurations.maybeCreate(mainCompilation.runtimeOnlyConfigurationName) + val compileTestsConfiguration = configurations.maybeCreate(testCompilation.deprecatedCompileConfigurationName) + val testImplementationConfiguration = configurations.maybeCreate(testCompilation.implementationConfigurationName) + val testRuntimeOnlyConfiguration = configurations.maybeCreate(testCompilation.runtimeOnlyConfigurationName) + + compileTestsConfiguration.extendsFrom(compileConfiguration).usesPlatformOf(target) + testImplementationConfiguration.extendsFrom(implementationConfiguration).usesPlatformOf(target) + testRuntimeOnlyConfiguration.extendsFrom(runtimeOnlyConfiguration).usesPlatformOf(target) + + configurations.maybeCreate(target.apiElementsConfigurationName).apply { + description = "API elements for main." + isVisible = false + isCanBeResolved = false + isCanBeConsumed = true + attributes.attribute(USAGE_ATTRIBUTE, project.usageByName(Usage.JAVA_API)) + extendsFrom(configurations.maybeCreate(mainCompilation.apiConfigurationName)) + if (mainCompilation is KotlinCompilationToRunnableFiles) { + val runtimeConfiguration = configurations.maybeCreate(mainCompilation.deprecatedRuntimeConfigurationName) + extendsFrom(runtimeConfiguration) + } + usesPlatformOf(target) + } + + val runtimeElementsConfiguration = configurations.maybeCreate(target.runtimeElementsConfigurationName).apply { + description = "Elements of runtime for main." + isVisible = false + isCanBeConsumed = true + isCanBeResolved = false + attributes.attribute(USAGE_ATTRIBUTE, project.usageByName(Usage.JAVA_RUNTIME_JARS)) + if (mainCompilation is KotlinCompilationToRunnableFiles) { + val runtimeConfiguration = configurations.maybeCreate(mainCompilation.deprecatedRuntimeConfigurationName) + extendsFrom(implementationConfiguration, runtimeOnlyConfiguration, runtimeConfiguration) + } + usesPlatformOf(target) + } + + if (mainCompilation is KotlinCompilationToRunnableFiles && testCompilation is KotlinCompilationToRunnableFiles) { + val runtimeConfiguration = configurations.maybeCreate(mainCompilation.deprecatedRuntimeConfigurationName) + val testRuntimeConfiguration = configurations.maybeCreate(testCompilation.deprecatedRuntimeConfigurationName) + testRuntimeConfiguration.extendsFrom(runtimeConfiguration).usesPlatformOf(target) + } + + defaultConfiguration.extendsFrom(runtimeElementsConfiguration).usesPlatformOf(target) + } + + private fun configureBuild(project: Project, target: KotlinOnlyTarget<*>) { + val testCompilation = target.compilations.getByName(KotlinCompilation.TEST_COMPILATION_NAME) + project.tasks.maybeCreate(buildNeededTaskName, DefaultTask::class.java).apply { + description = "Assembles and tests this project and all projects it depends on." + group = "build" + dependsOn("build") + if (testCompilation is KotlinCompilationToRunnableFiles) { + addDependsOnTaskInOtherProjects(this@apply, true, name, testCompilation.deprecatedRuntimeConfigurationName) + } + } + + project.tasks.maybeCreate(buildDependentTaskName, DefaultTask::class.java).apply { + setDescription("Assembles and tests this project and all projects that depend on it.") + setGroup("build") + dependsOn("build") + doFirst { + if (!project.gradle.includedBuilds.isEmpty()) { + project.logger.warn("[composite-build] Warning: `" + path + "` task does not build included builds.") + } + } + if (testCompilation is KotlinCompilationToRunnableFiles) { + addDependsOnTaskInOtherProjects(this@apply, false, name, testCompilation.deprecatedRuntimeConfigurationName) + } + } + } + + private fun addDependsOnTaskInOtherProjects( + task: Task, useDependedOn: Boolean, otherProjectTaskName: String, + configurationName: String + ) { + val project = task.project + val configuration = project.configurations.getByName(configurationName) + task.dependsOn(configuration.getTaskDependencyFromProjectDependency(useDependedOn, otherProjectTaskName)) + } + + private fun setCompatibilityOfAbstractCompileTasks(project: Project) = with (project) { + tasks.withType(AbstractKotlinCompile::class.java).all { + // Workaround: these are input properties and should not hold null values: + it.targetCompatibility = "" + it.sourceCompatibility = "" + } + } + + companion object { + const val buildNeededTaskName = "buildAllNeeded" + const val buildDependentTaskName = "buildAllDependents" + + fun defineConfigurationsForCompilation( + compilation: KotlinCompilation, + target: KotlinTarget, + configurations: ConfigurationContainer + ) { + val compileConfiguration = configurations.maybeCreate(compilation.deprecatedCompileConfigurationName).apply { + usesPlatformOf(target) + isVisible = false + isCanBeConsumed = false + isCanBeResolved = false + description = "Dependencies for $compilation (deprecated, use '${compilation.implementationConfigurationName} ' instead)." + } + + val apiConfiguration = configurations.maybeCreate(compilation.apiConfigurationName).apply { + extendsFrom(compileConfiguration) + usesPlatformOf(target) + isVisible = false + isCanBeConsumed = false + isCanBeResolved = false + description = "API dependencies for $compilation." + } + + val implementationConfiguration = configurations.maybeCreate(compilation.implementationConfigurationName).apply { + extendsFrom(compileConfiguration, apiConfiguration) + usesPlatformOf(target) + isVisible = false + isCanBeConsumed = false + isCanBeResolved = false + description = "Implementation only dependencies for $compilation." + } + + val compileOnlyConfiguration = configurations.maybeCreate(compilation.compileOnlyConfigurationName).apply { + usesPlatformOf(target) + isVisible = false + isCanBeConsumed = false + isCanBeResolved = false + description = "Compile only dependencies for $compilation." + } + + val compileClasspathConfiguration = configurations.maybeCreate(compilation.compileDependencyConfigurationName).apply { + extendsFrom(compileOnlyConfiguration, implementationConfiguration) + usesPlatformOf(target) + isVisible = false + isCanBeConsumed = false + attributes.attribute(USAGE_ATTRIBUTE, compilation.target.project.usageByName(Usage.JAVA_API)) + description = "Compile classpath for $compilation." + } + + compilation.compileDependencyFiles = compileClasspathConfiguration + + if (compilation is KotlinCompilationToRunnableFiles) { + val runtimeConfiguration = configurations.maybeCreate(compilation.deprecatedRuntimeConfigurationName).apply { + extendsFrom(compileConfiguration) + usesPlatformOf(target) + isVisible = false + isCanBeConsumed = false + isCanBeResolved = false + description = + "Runtime dependencies for $compilation (deprecated, use '${compilation.runtimeOnlyConfigurationName} ' instead)." + } + + val runtimeOnlyConfiguration = configurations.maybeCreate(compilation.runtimeOnlyConfigurationName).apply { + usesPlatformOf(target) + isVisible = false + isCanBeConsumed = false + isCanBeResolved = false + description = "Runtime only dependencies for $compilation." + } + + val runtimeClasspathConfiguration = configurations.maybeCreate(compilation.runtimeDependencyConfigurationName).apply { + extendsFrom(runtimeOnlyConfiguration, runtimeConfiguration, implementationConfiguration) + usesPlatformOf(target) + isVisible = false + isCanBeConsumed = false + isCanBeResolved = true + attributes.attribute(USAGE_ATTRIBUTE, compilation.target.project.usageByName(Usage.JAVA_RUNTIME)) + description = "Runtime classpath of $compilation." + } + + compilation.runtimeDependencyFiles = compilation.output.plus(runtimeClasspathConfiguration) + } + } + + private val KotlinCompilation.deprecatedCompileConfigurationName: String + get() = disambiguateName("compile") + + private val KotlinCompilationToRunnableFiles.deprecatedRuntimeConfigurationName: String + get() = disambiguateName("runtime") + } +} + +internal fun Project.usageByName(usageName: String): Usage = + if (isGradleVersionAtLeast(4, 0)) { + // `project.objects` is an API introduced in Gradle 4.0 + project.objects.named(Usage::class.java, usageName) + } else { + val usagesClass = Class.forName("org.gradle.api.internal.attributes.Usages") + val usagesMethod = usagesClass.getMethod("usage", String::class.java) + usagesMethod(null, usageName) as Usage + } + +fun Configuration.usesPlatformOf(target: KotlinTarget): Configuration { + attributes.attribute(KotlinPlatformType.attribute, target.platformType) + return this +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt new file mode 100644 index 00000000000..6503aff880f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -0,0 +1,160 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp + +import groovy.lang.Closure +import org.gradle.api.NamedDomainObjectCollection +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.attributes.Attribute +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.internal.plugins.DslObject +import org.gradle.api.plugins.BasePlugin +import org.gradle.api.plugins.ReportingBasePlugin +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal +import org.gradle.internal.cleanup.BuildOutputCleanupRegistry +import org.gradle.internal.reflect.Instantiator +import org.gradle.util.ConfigureUtil +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.plugin.source.KotlinSourceSet + +internal val Project.multiplatformExtension get(): KotlinMultiplatformExtension? = + project.extensions.getByName("kotlin") as KotlinMultiplatformExtension + +internal class KotlinMultiplatformPlugin( + private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry, + private val fileResolver: FileResolver, + private val instantiator: Instantiator, + private val kotlinPluginVersion: String +) : Plugin { + + private class TargetFromPresetExtension(val targetsContainer: NamedDomainObjectCollection) { + fun fromPreset(preset: KotlinTargetPreset, name: String, configureClosure: Closure<*>): T = + fromPreset(preset, name, { ConfigureUtil.configure(configureClosure, this) }) + + @JvmOverloads + fun fromPreset(preset: KotlinTargetPreset, name: String, configureAction: T.() -> Unit = { }): T { + val target = preset.createTarget(name) + targetsContainer.add(target) + target.run(configureAction) + return target + } + } + + override fun apply(project: Project) { + project.plugins.apply(BasePlugin::class.java) + project.plugins.apply(ReportingBasePlugin::class.java) + + val targetsContainer = project.container(KotlinTarget::class.java) + val targetsFromPreset = TargetFromPresetExtension(targetsContainer) + + project.extensions.getByType(KotlinMultiplatformExtension::class.java).apply { + DslObject(targetsContainer).addConvention("fromPreset", targetsFromPreset) + + targets = targetsContainer + addExtension("targets", targets) + + presets = project.container(KotlinTargetPreset::class.java) + addExtension("presets", presets) + } + + setupDefaultPresets(project) + configureDefaultVersionsResolutionStrategy(project, kotlinPluginVersion) + configureSourceSets(project) + + setUpConfigurationAttributes(project) + configurePublishingWithMavenPublish(project) + } + + fun setupDefaultPresets(project: Project) { + with((project.kotlinExtension as KotlinMultiplatformExtension).presets) { + add(KotlinUniversalTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion)) + add(KotlinJvmTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion)) + add(KotlinJsTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion)) + add(KotlinAndroidTargetPreset(project, kotlinPluginVersion)) + add(KotlinJvmWithJavaTargetPreset(project, kotlinPluginVersion)) + } + } + + private fun configurePublishingWithMavenPublish(project: Project) = project.pluginManager.withPlugin("maven-publish") { + val targets = project.multiplatformExtension!!.targets + val kotlinSoftwareComponent = KotlinSoftwareComponent(project, "kotlin", targets) + + project.afterEvaluate { project -> // Use afterEvaluate because publications configuration is no more lazy since Gradle 4.9 + project.extensions.configure(PublishingExtension::class.java) { publishing -> + publishing.publications.create("kotlin", MavenPublication::class.java) { publication -> + publication.from(kotlinSoftwareComponent) + (publication as MavenPublicationInternal).publishWithOriginalFileName() + publication.artifactId = project.name + publication.groupId = project.group.toString() + } + } + } + + project.components.add(kotlinSoftwareComponent) + } + + private fun configureSourceSets(project: Project) = with (project.kotlinExtension as KotlinMultiplatformExtension) { + sourceSets.all { defineSourceSetConfigurations(project, it) } + val production = sourceSets.create(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + val test = sourceSets.create(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME) + + targets.all { + it.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)?.source(production) + it.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME)?.source(test) + } + } + + private fun setUpConfigurationAttributes(project: Project) { + val targets = (project.kotlinExtension as KotlinMultiplatformExtension).targets + + project.afterEvaluate { + targets.all { target -> + val mainCompilationAttributes = target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)?.attributes + ?: return@all + + fun copyAttribute(key: Attribute, from: AttributeContainer, to: AttributeContainer) { + to.attribute(key, from.getAttribute(key)!!) + } + + listOf( + target.apiElementsConfigurationName, + target.runtimeElementsConfigurationName, + target.defaultConfigurationName + ) + .mapNotNull { configurationName -> target.project.configurations.findByName(configurationName) } + .forEach { configuration -> + mainCompilationAttributes.keySet().forEach { key -> + copyAttribute(key, mainCompilationAttributes, configuration.attributes) + } + } + + target.compilations.all { compilation -> + val compilationAttributes = compilation.attributes + + compilation.relatedConfigurationNames + .mapNotNull { configurationName -> target.project.configurations.findByName(configurationName) } + .forEach { configuration -> + compilationAttributes.keySet().forEach { key -> + copyAttribute(key, compilationAttributes, configuration.attributes) + } + } + } + } + } + } + + private fun defineSourceSetConfigurations(project: Project, sourceSet: KotlinSourceSet) = with (project.configurations) { + sourceSet.relatedConfigurationNames.forEach { configurationName -> + maybeCreate(configurationName) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt new file mode 100644 index 00000000000..bf3bd4952ca --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp + +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.DependencyConstraint +import org.gradle.api.artifacts.ModuleDependency +import org.gradle.api.artifacts.PublishArtifact +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.attributes.Usage +import org.gradle.api.capabilities.Capability +import org.gradle.api.internal.component.SoftwareComponentInternal +import org.gradle.api.internal.component.UsageContext +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.usageByName + +class KotlinSoftwareComponent( + private val project: Project, + private val name: String, + private val kotlinTargets: Iterable +) : SoftwareComponentInternal { + override fun getUsages(): Set = kotlinTargets.flatMap { it.createUsageContexts() }.toSet() + + override fun getName(): String = name + + companion object { + fun kotlinApiUsage(project: Project) = project.usageByName(Usage.JAVA_API) + fun kotlinRuntimeUsage(project: Project) = project.usageByName(Usage.JAVA_RUNTIME) + } +} + +internal class KotlinPlatformUsageContext( + val project: Project, + val kotlinTarget: KotlinTarget, + private val usage: Usage, + val dependencyConfigurationName: String +) : UsageContext { + override fun getUsage(): Usage = usage + + override fun getName(): String = kotlinTarget.targetName + when (usage.name) { + Usage.JAVA_API -> "-api" + Usage.JAVA_RUNTIME -> "-runtime" + else -> error("unexpected usage") + } + + private val configuration: Configuration + get() = project.configurations.getByName(dependencyConfigurationName) + + override fun getDependencies(): MutableSet = + configuration.incoming.dependencies.withType(ModuleDependency::class.java) + + override fun getDependencyConstraints(): MutableSet = + configuration.incoming.dependencyConstraints + + override fun getArtifacts(): MutableSet = + // TODO Gradle Java plugin does that in a different way; check whether we can improve this + configuration.artifacts + + override fun getAttributes(): AttributeContainer = + configuration.outgoing.attributes + + override fun getCapabilities(): Set = emptySet() + + // FIXME this is a stub for a function that is not present in the Gradle API that we compile against + fun getGlobalExcludes(): Set = emptySet() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt new file mode 100644 index 00000000000..4b0e7c08311 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp + +import org.gradle.api.Project +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.plugins.JavaPlugin +import org.gradle.internal.cleanup.BuildOutputCleanupRegistry +import org.gradle.internal.reflect.Instantiator +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.tasks.AndroidTasksProvider +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsTasksProvider +import org.jetbrains.kotlin.gradle.tasks.KotlinCommonTasksProvider +import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider + +abstract class KotlinOnlyTargetPreset( + protected val project: Project, + private val instantiator: Instantiator, + private val fileResolver: FileResolver, + private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry, + protected val kotlinPluginVersion: String +) : KotlinTargetPreset> { + + override fun createTarget(name: String): KotlinOnlyTarget { + val result = KotlinOnlyTarget(project, platformType).apply { + targetName = name + disambiguationClassifier = name + + val compilationFactory = createCompilationFactory(this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + KotlinTargetConfigurator(buildOutputCleanupRegistry).configureTarget(project, result) + + result.compilations.all { compilation -> + buildCompilationProcessor(compilation).run() + } + + return result + } + + protected abstract fun createCompilationFactory(forTarget: KotlinOnlyTarget): KotlinCompilationFactory + protected abstract val platformType: KotlinPlatformType + internal abstract fun buildCompilationProcessor(compilation: T): KotlinSourceSetProcessor<*> +} + +class KotlinUniversalTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + buildOutputCleanupRegistry: BuildOutputCleanupRegistry, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + buildOutputCleanupRegistry, + kotlinPluginVersion +) { + override fun getName(): String = PRESET_NAME + + override fun createCompilationFactory( + forTarget: KotlinOnlyTarget + ): KotlinCompilationFactory = + KotlinCommonCompilationFactory(forTarget) + + override val platformType: KotlinPlatformType + get() = KotlinPlatformType.common + + override fun buildCompilationProcessor(compilation: KotlinCommonCompilation): KotlinSourceSetProcessor<*> = + KotlinCommonSourceSetProcessor( + project, + compilation, + KotlinCommonTasksProvider(), + kotlinPluginVersion + ) + + companion object { + const val PRESET_NAME = "universal" + } +} + +class KotlinJvmTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + buildOutputCleanupRegistry: BuildOutputCleanupRegistry, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + buildOutputCleanupRegistry, + kotlinPluginVersion +) { + override fun getName(): String = PRESET_NAME + + override fun createCompilationFactory(forTarget: KotlinOnlyTarget): KotlinCompilationFactory = + KotlinJvmCompilationFactory(forTarget) + + override val platformType: KotlinPlatformType + get() = KotlinPlatformType.jvm + + override fun buildCompilationProcessor(compilation: KotlinJvmCompilation): KotlinSourceSetProcessor<*> = + Kotlin2JvmSourceSetProcessor(project, KotlinTasksProvider(), compilation, kotlinPluginVersion) + + companion object { + const val PRESET_NAME = "jvm" + } +} + +class KotlinJsTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + buildOutputCleanupRegistry: BuildOutputCleanupRegistry, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + buildOutputCleanupRegistry, + kotlinPluginVersion +) { + override fun getName(): String = PRESET_NAME + + override fun createCompilationFactory(forTarget: KotlinOnlyTarget) = + KotlinJsCompilationFactory(project, forTarget) + + override val platformType: KotlinPlatformType + get() = KotlinPlatformType.js + + override fun buildCompilationProcessor(compilation: KotlinJsCompilation): KotlinSourceSetProcessor<*> = + Kotlin2JsSourceSetProcessor(project, Kotlin2JsTasksProvider(), compilation, kotlinPluginVersion) + + companion object { + const val PRESET_NAME = "js" + } +} + +class KotlinAndroidTargetPreset( + private val project: Project, + private val kotlinPluginVersion: String +) : KotlinTargetPreset { + + override fun getName(): String = PRESET_NAME + + override fun createTarget(name: String): KotlinAndroidTarget { + val result = KotlinAndroidTarget(name, project).apply { + disambiguationClassifier = name + } + + KotlinAndroidPlugin.applyToTarget( + project, result, AndroidTasksProvider(), + kotlinPluginVersion + ) + + + return result + } + + companion object { + const val PRESET_NAME = "android" + } +} + +class KotlinJvmWithJavaTargetPreset( + private val project: Project, + private val kotlinPluginVersion: String +): KotlinTargetPreset { + + override fun getName(): String = PRESET_NAME + + override fun createTarget(name: String): KotlinWithJavaTarget { + project.plugins.apply(JavaPlugin::class.java) + + val target = KotlinWithJavaTarget(project, KotlinPlatformType.jvm, name).apply { + disambiguationClassifier = name + } + + AbstractKotlinPlugin.configureTarget(target) { compilation -> + Kotlin2JvmSourceSetProcessor(project, KotlinTasksProvider(), compilation, kotlinPluginVersion) + } + + target.compilations.all { compilation -> + // Set up dependency resolution using platforms: + KotlinTargetConfigurator.defineConfigurationsForCompilation(compilation, target, project.configurations) + } + + target.compilations.getByName("test").run { + val main = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + + compileDependencyFiles = project.files(main.output, project.configurations.maybeCreate(compileDependencyConfigurationName)) + runtimeDependencyFiles = project.files(output, main.output, project.configurations.maybeCreate(runtimeDependencyConfigurationName)) + } + + return target + } + + companion object { + const val PRESET_NAME = "jvmWithJava" + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 71daf990df0..43b123583e2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -272,7 +272,7 @@ abstract class AbstractKotlinCompile() : AbstractKo args.verbose = true } - args.multiPlatform = project.plugins.any { it is KotlinPlatformPluginBase } + args.multiPlatform = project.plugins.any { it is KotlinPlatformPluginBase || it is KotlinMultiplatformPluginWrapper } setupPlugins(args) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-multiplatform.properties b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-multiplatform.properties new file mode 100644 index 00000000000..7e3d7f5489d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-multiplatform.properties @@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.multiplatform.properties b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.multiplatform.properties new file mode 100644 index 00000000000..7e3d7f5489d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.multiplatform.properties @@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper \ No newline at end of file