diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 78f1346b901..00000000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinNativeTaretConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinNativeTaretConfigurator.kt new file mode 100644 index 00000000000..eb37b6e9abb --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinNativeTaretConfigurator.kt @@ -0,0 +1,788 @@ +/* + * 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.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.* +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.FileCollection +import org.gradle.api.internal.artifacts.ArtifactAttributes +import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact +import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet +import org.gradle.api.internal.plugins.DslObject +import org.gradle.api.plugins.BasePlugin +import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.Delete +import org.gradle.api.tasks.Exec +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.testing.Test +import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.gradle.language.jvm.tasks.ProcessResources +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME +import org.jetbrains.kotlin.gradle.plugin.mpp.* +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile +import org.jetbrains.kotlin.gradle.tasks.CInteropProcess +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import java.util.* +import java.util.concurrent.Callable + +abstract class AbstractKotlinTargetConfigurator( + protected val createDefaultSourceSets: Boolean, + protected val createTestCompilation: Boolean +) { + open fun configureTarget( + target: KotlinTargetType + ) { + configureCompilationDefaults(target) + configureCompilations(target) + defineConfigurationsForTarget(target) + configureArchivesAndComponent(target) + configureTest(target) + configureBuild(target) + } + + + abstract protected fun configureArchivesAndComponent(target: KotlinTargetType) + abstract protected fun configureTest(target: KotlinTargetType) + + private fun Project.registerOutputsForStaleOutputCleanup(kotlinCompilation: KotlinCompilation<*>) { + val cleanTask = tasks.getByName(LifecycleBasePlugin.CLEAN_TASK_NAME) as Delete + cleanTask.delete(kotlinCompilation.output.allOutputs) + } + + protected fun configureCompilations(platformTarget: KotlinTargetType) { + val project = platformTarget.project + val main = platformTarget.compilations.create(KotlinCompilation.MAIN_COMPILATION_NAME) + + platformTarget.compilations.all { + project.registerOutputsForStaleOutputCleanup(it) + it.compileDependencyFiles = project.configurations.maybeCreate(it.compileDependencyConfigurationName) + if (it is KotlinCompilationToRunnableFiles) { + it.runtimeDependencyFiles = project.configurations.maybeCreate(it.runtimeDependencyConfigurationName) + } + } + + if (createTestCompilation) { + platformTarget.compilations.create(KotlinCompilation.TEST_COMPILATION_NAME).apply { + compileDependencyFiles = project.files( + main.output.allOutputs, + project.configurations.maybeCreate(compileDependencyConfigurationName) + ) + + if (this is KotlinCompilationToRunnableFiles) { + runtimeDependencyFiles = project.files( + output.allOutputs, + main.output.allOutputs, + project.configurations.maybeCreate(runtimeDependencyConfigurationName) + ) + } + } + } + } + + protected fun configureCompilationDefaults(target: KotlinTargetType) { + val project = target.project + + target.compilations.all { compilation -> + defineConfigurationsForCompilation(compilation) + + if (createDefaultSourceSets) { + project.kotlinExtension.sourceSets.maybeCreate(compilation.defaultSourceSetName).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(compilation, project.files(Callable { compilation.allKotlinSourceSets.map { it.resources } })) + } + + createLifecycleTask(compilation) + } + } + + protected fun configureResourceProcessing( + compilation: KotlinCompilationWithResources<*>, + resourceSet: FileCollection + ) { + val project = compilation.target.project + + val resourcesTask = project.tasks.maybeCreate(compilation.processResourcesTaskName, ProcessResources::class.java) + resourcesTask.description = "Processes $resourceSet." + DslObject(resourcesTask).conventionMapping.map("destinationDir") { project.file(compilation.output.resourcesDir) } + resourcesTask.from(resourceSet) + } + + protected fun createLifecycleTask(compilation: KotlinCompilation<*>) { + val project = compilation.target.project + + compilation.output.classesDirs.from(project.files().builtBy(compilation.compileAllTaskName)) + + project.tasks.create(compilation.compileAllTaskName).apply { + group = LifecycleBasePlugin.BUILD_GROUP + description = "Assembles outputs for compilation '${compilation.name}' of target '${compilation.target.name}'" + dependsOn(compilation.compileKotlinTaskName) + if (compilation is KotlinCompilationWithResources) { + dependsOn(compilation.processResourcesTaskName) + } + } + } + + protected open fun defineConfigurationsForTarget(target: KotlinTargetType) { + val project = target.project + + val configurations = project.configurations + + val defaultConfiguration = configurations.maybeCreate(target.defaultConfigurationName).apply { + setupAsLocalTargetSpecificConfigurationIfSupported(target) + } + + val mainCompilation = target.compilations.maybeCreate(KotlinCompilation.MAIN_COMPILATION_NAME) + + val compileConfiguration = configurations.maybeCreate(mainCompilation.deprecatedCompileConfigurationName) + val implementationConfiguration = configurations.maybeCreate(mainCompilation.implementationConfigurationName) + + val runtimeOnlyConfiguration = configurations.maybeCreate(mainCompilation.runtimeOnlyConfigurationName) + + val apiElementsConfiguration = configurations.maybeCreate(target.apiElementsConfigurationName).apply { + description = "API elements for main." + isVisible = false + isCanBeResolved = false + isCanBeConsumed = true + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(target)) + extendsFrom(configurations.maybeCreate(mainCompilation.apiConfigurationName)) + if (mainCompilation is KotlinCompilationToRunnableFiles) { + val runtimeConfiguration = configurations.maybeCreate(mainCompilation.deprecatedRuntimeConfigurationName) + extendsFrom(runtimeConfiguration) + } + usesPlatformOf(target) + setupAsPublicConfigurationIfSupported(target) + } + + if (mainCompilation is KotlinCompilationToRunnableFiles<*>) { + val runtimeElementsConfiguration = configurations.maybeCreate(target.runtimeElementsConfigurationName).apply { + description = "Elements of runtime for main." + isVisible = false + isCanBeConsumed = true + isCanBeResolved = false + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.producerRuntimeUsage(target)) + val runtimeConfiguration = configurations.maybeCreate(mainCompilation.deprecatedRuntimeConfigurationName) + extendsFrom(implementationConfiguration, runtimeOnlyConfiguration, runtimeConfiguration) + usesPlatformOf(target) + setupAsPublicConfigurationIfSupported(target) + } + defaultConfiguration.extendsFrom(runtimeElementsConfiguration) + } else { + defaultConfiguration.extendsFrom(apiElementsConfiguration) + } + + if (createTestCompilation) { + val testCompilation = target.compilations.getByName(KotlinCompilation.TEST_COMPILATION_NAME) + val compileTestsConfiguration = configurations.maybeCreate(testCompilation.deprecatedCompileConfigurationName) + val testImplementationConfiguration = configurations.maybeCreate(testCompilation.implementationConfigurationName) + val testRuntimeOnlyConfiguration = configurations.maybeCreate(testCompilation.runtimeOnlyConfigurationName) + + compileTestsConfiguration.extendsFrom(compileConfiguration) + testImplementationConfiguration.extendsFrom(implementationConfiguration) + testRuntimeOnlyConfiguration.extendsFrom(runtimeOnlyConfiguration) + + if (mainCompilation is KotlinCompilationToRunnableFiles && testCompilation is KotlinCompilationToRunnableFiles) { + val runtimeConfiguration = configurations.maybeCreate(mainCompilation.deprecatedRuntimeConfigurationName) + val testRuntimeConfiguration = configurations.maybeCreate(testCompilation.deprecatedRuntimeConfigurationName) + testRuntimeConfiguration.extendsFrom(runtimeConfiguration) + } + } + } + + protected fun configureBuild(target: KotlinTargetType) { + val project = target.project + + val buildNeeded = project.tasks.getByName(JavaBasePlugin.BUILD_NEEDED_TASK_NAME) + val buildDependent = project.tasks.getByName(JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME) + + if (createTestCompilation) { + val testCompilation = target.compilations.getByName(KotlinCompilation.TEST_COMPILATION_NAME) + if (testCompilation is KotlinCompilationToRunnableFiles) { + addDependsOnTaskInOtherProjects(buildNeeded, true, testCompilation.deprecatedRuntimeConfigurationName) + addDependsOnTaskInOtherProjects(buildDependent, false, testCompilation.deprecatedRuntimeConfigurationName) + } + } + } + + private fun addDependsOnTaskInOtherProjects(task: Task, useDependedOn: Boolean, configurationName: String) { + val project = task.project + val configuration = project.configurations.getByName(configurationName) + task.dependsOn(configuration.getTaskDependencyFromProjectDependency(useDependedOn, task.name)) + } + + companion object { + const val testTaskNameSuffix = "test" + + fun defineConfigurationsForCompilation( + compilation: KotlinCompilation<*> + ) { + val target = compilation.target + val configurations = target.project.configurations + + val compileConfiguration = configurations.maybeCreate(compilation.deprecatedCompileConfigurationName).apply { + setupAsLocalTargetSpecificConfigurationIfSupported(target) + isVisible = false + isCanBeResolved = true // Needed for IDE import + description = "Dependencies for $compilation (deprecated, use '${compilation.implementationConfigurationName} ' instead)." + } + + val apiConfiguration = configurations.maybeCreate(compilation.apiConfigurationName).apply { + extendsFrom(compileConfiguration) + isVisible = false + isCanBeConsumed = false + isCanBeResolved = false + description = "API dependencies for $compilation." + } + + val implementationConfiguration = configurations.maybeCreate(compilation.implementationConfigurationName).apply { + extendsFrom(compileConfiguration, apiConfiguration) + isVisible = false + isCanBeConsumed = false + isCanBeResolved = false + description = "Implementation only dependencies for $compilation." + } + + val compileOnlyConfiguration = configurations.maybeCreate(compilation.compileOnlyConfigurationName).apply { + setupAsLocalTargetSpecificConfigurationIfSupported(target) + isVisible = false + isCanBeResolved = true // Needed for IDE import + 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, KotlinUsages.consumerApiUsage(compilation.target)) + description = "Compile classpath for $compilation." + } + + if (compilation is KotlinCompilationToRunnableFiles) { + val runtimeConfiguration = configurations.maybeCreate(compilation.deprecatedRuntimeConfigurationName).apply { + setupAsLocalTargetSpecificConfigurationIfSupported(target) + extendsFrom(compileConfiguration) + isVisible = false + isCanBeResolved = true // Needed for IDE import + description = + "Runtime dependencies for $compilation (deprecated, use '${compilation.runtimeOnlyConfigurationName} ' instead)." + } + + val runtimeOnlyConfiguration = configurations.maybeCreate(compilation.runtimeOnlyConfigurationName).apply { + 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, KotlinUsages.consumerRuntimeUsage(compilation.target)) + description = "Runtime classpath of $compilation." + } + } + } + } +} + +internal val KotlinCompilation<*>.deprecatedCompileConfigurationName: String + get() = disambiguateName("compile") + +internal val KotlinCompilationToRunnableFiles<*>.deprecatedRuntimeConfigurationName: String + get() = disambiguateName("runtime") + +open class KotlinTargetConfigurator>( + createDefaultSourceSets: Boolean, + createTestCompilation: Boolean +) : AbstractKotlinTargetConfigurator>( + createDefaultSourceSets, + createTestCompilation +) { + + override fun configureArchivesAndComponent(target: KotlinOnlyTarget) { + val project = target.project + + 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.allOutputs) + + val apiElementsConfiguration = project.configurations.getByName(target.apiElementsConfigurationName) + + target.disambiguationClassifier?.let { jar.appendix = it.toLowerCase() } + + // 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? + } + } + } + } + + override fun configureTest(target: KotlinOnlyTarget) { + val testCompilation = target.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME) as? KotlinCompilationToRunnableFiles<*> + ?: return // Otherwise, there is no runtime classpath + + target.project.tasks.create(lowerCamelCaseName(target.targetName, testTaskNameSuffix), Test::class.java).apply { + project.afterEvaluate { + // use afterEvaluate to override the JavaPlugin defaults for Test tasks + conventionMapping.map("testClassesDirs") { testCompilation.output.classesDirs } + conventionMapping.map("classpath") { testCompilation.runtimeDependencyFiles } + description = "Runs the unit tests." + group = JavaBasePlugin.VERIFICATION_GROUP + target.project.tasks.findByName(JavaBasePlugin.CHECK_TASK_NAME)?.dependsOn(this@apply) + } + } + } + + 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) + } +} + + +open class KotlinNativeTargetConfigurator( + private val kotlinPluginVersion: String +) : AbstractKotlinTargetConfigurator( + createDefaultSourceSets = true, + createTestCompilation = true +) { + private fun Project.klibOutputDirectory( + compilation: KotlinNativeCompilation + ): File { + val targetSubDirectory = compilation.target.disambiguationClassifier?.let { "$it/" }.orEmpty() + return buildDir.resolve("classes/kotlin/$targetSubDirectory${compilation.name}") + } + + private fun AbstractKotlinNativeCompile.addCompilerPlugins() { + SubpluginEnvironment + .loadSubplugins(project, kotlinPluginVersion) + .addSubpluginOptions(project, this, compilerPluginOptions) + compilerPluginClasspath = project.configurations.getByName(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME) + } + + // region Artifact creation. + private fun Project.createKlibArtifact( + compilation: KotlinNativeCompilation, + artifactFile: File, + classifier: String?, + producingTask: Task, + copy: Boolean = false + ) { + if (!compilation.target.konanTarget.enabledOnCurrentHost) { + return + } + + val apiElements = configurations.getByName(compilation.target.apiElementsConfigurationName) + + val realProducingTask: Task + // TODO: Someone remove this HACK PLEASE! + val realArtifactFile = if (copy) { + realProducingTask = project.tasks.create("copy${producingTask.name.capitalize()}", Copy::class.java) { + val targetSubDirectory = compilation.target.disambiguationClassifier?.let { "$it/" }.orEmpty() + it.destinationDir = project.buildDir.resolve("libs/$targetSubDirectory${compilation.name}") + it.from(artifactFile) + it.dependsOn(producingTask) + } + realProducingTask.destinationDir.resolve(artifactFile.name) + } else { + realProducingTask = producingTask + artifactFile + } + + val klibArtifact = DefaultPublishArtifact( + compilation.name, + "klib", + "klib", + classifier, + Date(), + realArtifactFile, + realProducingTask + ) + project.extensions.getByType(DefaultArtifactPublicationSet::class.java).addCandidate(klibArtifact) + + with(apiElements.outgoing) { + artifacts.add(klibArtifact) + attributes.attribute(ArtifactAttributes.ARTIFACT_FORMAT, NativeArtifactFormat.KLIB) + } + } + + private fun Project.createRegularKlibArtifact( + compilation: KotlinNativeCompilation, + compileTask: KotlinNativeCompile + ) = createKlibArtifact(compilation, compileTask.outputFile.get(), null, compileTask) + + private fun Project.createCInteropKlibArtifact( + interop: DefaultCInteropSettings, + interopTask: CInteropProcess + ) = createKlibArtifact(interop.compilation, interopTask.outputFile, "cinterop-${interop.name}", interopTask, copy = true) + // endregion. + + // region Task creation. + private fun Project.createLinkTask(binary: NativeBinary) { + tasks.create( + binary.linkTaskName, + KotlinNativeLink::class.java + ).apply { + val target = binary.target + this.binary = binary + group = BasePlugin.BUILD_GROUP + description = "Links ${binary.outputKind.description} '${binary.name}' for a target '${target.name}'." + enabled = target.konanTarget.enabledOnCurrentHost + destinationDir = binary.outputDirectory + addCompilerPlugins() + + tasks.maybeCreate(target.artifactsTaskName).dependsOn(this) + tasks.maybeCreate(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).dependsOn(this) + } + } + + private fun Project.createRunTask(binary: Executable) { + val taskName = binary.runTaskName ?: return + // TODO provide a special exec task for tests. + tasks.create(taskName, Exec::class.java).apply { + if (binary.isDefaultTestExecutable) { + group = LifecycleBasePlugin.VERIFICATION_GROUP + description = "Executes Kotlin/Native unit tests for target ${binary.target.name}." + tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(this) + if (project.hasProperty("teamcity.version")) { + args("--ktest_logger=TEAMCITY") + } + } else { + group = RUN_GROUP + description = "Executes Kotlin/Native executable ${binary.name} for target ${binary.target.name}" + } + + enabled = binary.target.konanTarget.isCurrentHost + + executable = binary.outputFile.absolutePath + workingDir = project.projectDir + + onlyIf { binary.outputFile.exists() } + dependsOn(binary.linkTaskName) + } + } + + private fun Project.createKlibCompilationTask(compilation: KotlinNativeCompilation) { + val compileTask = tasks.create( + compilation.compileKotlinTaskName, + KotlinNativeCompile::class.java + ).apply { + this.compilation = compilation + group = BasePlugin.BUILD_GROUP + description = "Compiles a klibrary from the '${compilation.name}' " + + "compilation for target '${compilation.platformType.name}'." + enabled = compilation.target.konanTarget.enabledOnCurrentHost + + destinationDir = klibOutputDirectory(compilation) + addCompilerPlugins() + compilation.output.addClassesDir { + project.files(this.outputFile).builtBy(this) + } + } + + project.tasks.getByName(compilation.compileAllTaskName).dependsOn(compileTask) + + if (compilation.compilationName == KotlinCompilation.MAIN_COMPILATION_NAME) { + project.tasks.getByName(compilation.target.artifactsTaskName).apply { + dependsOn(compileTask) + } + project.tasks.getByName(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).apply { + dependsOn(compileTask) + } + createRegularKlibArtifact(compilation, compileTask) + } + } + + private fun Project.createCInteropTasks(compilation: KotlinNativeCompilation) { + compilation.cinterops.all { interop -> + val interopTask = tasks.create(interop.interopProcessingTaskName, CInteropProcess::class.java).apply { + settings = interop + destinationDir = provider { klibOutputDirectory(compilation) } + group = INTEROP_GROUP + description = "Generates Kotlin/Native interop library '${interop.name}' " + + "for compilation '${compilation.name}'" + + "of target '${konanTarget.name}'." + enabled = compilation.target.konanTarget.enabledOnCurrentHost + + val interopOutput = project.files(outputFileProvider).builtBy(this) + with(compilation) { + project.dependencies.add(compileDependencyConfigurationName, interopOutput) + if (isMainCompilation) { + target.compilations.findByName(TEST_COMPILATION_NAME)?.let { + project.dependencies.add(it.compileDependencyConfigurationName, interopOutput) + } + } + project.dependencies.add(target.apiElementsConfigurationName, interopOutput) + } + } + createCInteropKlibArtifact(interop, interopTask) + } + } + // endregion. + + // region Configuration. + override fun configureTarget(target: KotlinNativeTarget) { + super.configureTarget(target) + configureBinaries(target) + configureFrameworkExport(target) + configureCInterops(target) + warnAboutIncorrectDependencies(target) + } + + override fun configureArchivesAndComponent(target: KotlinNativeTarget): Unit = with(target.project) { + tasks.create(target.artifactsTaskName) + target.compilations.all { + createKlibCompilationTask(it) + } + + with(configurations.getByName(target.apiElementsConfigurationName)) { + outgoing.attributes.attribute(ArtifactAttributes.ARTIFACT_FORMAT, NativeArtifactFormat.KLIB) + } + } + + override fun configureTest(target: KotlinNativeTarget) { + target.binaries.defaultTestExecutable { + compilation = target.compilations.maybeCreate(KotlinCompilation.TEST_COMPILATION_NAME) + // Allow accessing the test binary using the old getters (e.g. compilations.test.getBinary('EXECUTABLE', 'DEBUG')) + compilation.binaries[NativeOutputKind.EXECUTABLE to KotlinNativeBinaryContainer.DEFAULT_TEST_BUILD_TYPE] = this + } + } + + protected fun configureCInterops(target: KotlinNativeTarget): Unit = with(target.project) { + target.compilations.all { compilation -> + createCInteropTasks(compilation) + compilation.cinterops.all { + defineConfigurationsForCInterop(compilation, it, target, configurations) + } + } + + if (createTestCompilation) { + val mainCompilation = target.compilations.getByName(MAIN_COMPILATION_NAME) + target.compilations.findByName(TEST_COMPILATION_NAME)?.apply { + cinterops.all { + it.dependencyFiles += mainCompilation.output.allOutputs + } + } + } + } + + protected fun configureBinaries(target: KotlinNativeTarget) { + val project = target.project + // Create link and run tasks. + target.binaries.all { + project.createLinkTask(it) + } + + target.binaries.withType(Executable::class.java).all { + project.createRunTask(it) + } + + target.binaries.prefixGroups.all { prefixGroup -> + val linkGroupTask = project.tasks.maybeCreate(prefixGroup.linkTaskName) + prefixGroup.binaries.all { + linkGroupTask.dependsOn(it.linkTaskName) + } + } + + // Create an aggregate link task for each compilation. + target.compilations.all { + project.tasks.create(it.binariesTaskName) + } + + project.whenEvaluated { + target.binaries.forEach { + project.tasks.getByName(it.compilation.binariesTaskName).dependsOn(it.linkTaskName) + } + } + + // Create binaries for output kinds declared using the old DSL. + project.whenEvaluated { + target.compilations.all { compilation -> + val binaries = target.binaries + val konanTarget = compilation.target.konanTarget + val name = compilation.name + val buildTypes = compilation.buildTypes + val availableOutputKinds = compilation.outputKinds.filter { it.availableFor(konanTarget) } + + val configure: NativeBinary.() -> Unit = { + this.compilation = compilation + linkerOpts.addAll(compilation.linkerOpts) + if (this is Executable) { + entryPoint = compilation.entryPoint + } + compilation.binaries[outputKind to buildType] = this + } + + for (kind in availableOutputKinds) { + when (kind) { + NativeOutputKind.EXECUTABLE -> binaries.executable(name, buildTypes, configure) + NativeOutputKind.DYNAMIC -> binaries.sharedLib(name, buildTypes, configure) + NativeOutputKind.STATIC -> binaries.staticLib(name, buildTypes, configure) + NativeOutputKind.FRAMEWORK -> binaries.framework(name, buildTypes, configure) + } + } + } + // Allow setting linker options for the default test executable using the + // corresponding properties of the test compilation. + target.binaries.getDefaultTestExecutable().apply { + linkerOpts.addAll(target.compilations.getByName(TEST_COMPILATION_NAME).linkerOpts) + } + } + } + + fun configureFrameworkExport(target: KotlinNativeTarget) { + val project = target.project + + target.compilations.all { + // Allow resolving api configurations directly to be able to check that + // all exported dependency are also added in the corresponding api configurations. + // The check is performed during a link task execution. + project.configurations.maybeCreate(it.apiConfigurationName).apply { + isCanBeResolved = true + usesPlatformOf(target) + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.consumerApiUsage(target)) + } + } + + target.binaries.withType(Framework::class.java).all { framework -> + val exportConfiguration = project.configurations.maybeCreate(framework.exportConfigurationName).apply { + isVisible = false + isTransitive = false + isCanBeConsumed = false + isCanBeResolved = true + usesPlatformOf(target) + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.consumerApiUsage(target)) + description = "Dependenceis to be exported in framework ${framework.name} for target ${target.targetName}" + } + } + } + + override fun defineConfigurationsForTarget(target: KotlinNativeTarget) { + super.defineConfigurationsForTarget(target) + val configurations = target.project.configurations + + // The configuration and the main compilation are created by the base class. + val mainCompilation = target.compilations.getByName(MAIN_COMPILATION_NAME) + configurations.getByName(target.apiElementsConfigurationName).apply { + // K/N compiler doesn't divide libraries into implementation and api ones. So we need to add implementation + // dependencies into the outgoing configuration. + extendsFrom(configurations.getByName(mainCompilation.implementationConfigurationName)) + } + } + + private fun warnAboutIncorrectDependencies(target: KotlinNativeTarget) = target.project.whenEvaluated { + + val compileOnlyDependencies = target.compilations.mapNotNull { + val dependencies = configurations.getByName(it.compileOnlyConfigurationName).allDependencies + if (dependencies.isNotEmpty()) { + it to dependencies + } else null + } + + fun Dependency.stringCoordinates(): String = buildString { + group?.let { append(it).append(':') } + append(name) + version?.let { append(':').append(it) } + } + + if (compileOnlyDependencies.isNotEmpty()) { + with(target.project.logger) { + warn("A compileOnly dependency is used in the Kotlin/Native target '${target.name}':") + compileOnlyDependencies.forEach { + warn( + """ + Compilation: ${it.first.name} + + Dependencies: + ${it.second.joinToString(separator = "\n") { it.stringCoordinates() }} + + """.trimIndent() + ) + } + warn("Such dependencies are not applicable for Kotlin/Native, consider changing the dependency type to 'implementation' or 'api'.") + } + } + } + // endregion. + + object NativeArtifactFormat { + const val KLIB = "org.jetbrains.kotlin.klib" + } + + companion object { + const val INTEROP_GROUP = "interop" + const val RUN_GROUP = "run" + + protected fun defineConfigurationsForCInterop( + compilation: KotlinNativeCompilation, + cinterop: CInteropSettings, + target: KotlinTarget, + configurations: ConfigurationContainer + ) { + val compileOnlyConfiguration = configurations.getByName(compilation.compileOnlyConfigurationName) + val implementationConfiguration = configurations.getByName(compilation.implementationConfigurationName) + + cinterop.dependencyFiles = configurations.maybeCreate(cinterop.dependencyConfigurationName).apply { + extendsFrom(compileOnlyConfiguration, implementationConfiguration) + usesPlatformOf(target) + isVisible = false + isCanBeConsumed = false + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.consumerApiUsage(target)) + description = "Dependencies for cinterop '${cinterop.name}' (compilation '${compilation.name}')." + } + } + } +} + +internal fun Project.usageByName(usageName: String): Usage = + project.objects.named(Usage::class.java, usageName) + +fun Configuration.usesPlatformOf(target: KotlinTarget): Configuration { + attributes.attribute(KotlinPlatformType.attribute, target.platformType) + // TODO: Provide an universal way to copy attributes from the target. + if (target is KotlinNativeTarget) { + attributes.attribute(KotlinNativeTarget.konanTargetAttribute, target.konanTarget.name) + } + 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/KotlinAndroidTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinAndroidTarget.kt new file mode 100644 index 00000000000..f4bbb5d60f9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinAndroidTarget.kt @@ -0,0 +1,437 @@ +/* + * 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.* +import org.gradle.api.artifacts.ConfigurablePublishArtifact +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.PublishArtifact +import org.gradle.api.attributes.Attribute +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.attributes.Usage.JAVA_API +import org.gradle.api.attributes.Usage.JAVA_RUNTIME_JARS +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.util.ConfigureUtil +import org.gradle.util.WrapUtil +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.utils.dashSeparatedName +import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.konan.target.KonanTarget + +abstract class AbstractKotlinTarget( + final override val project: Project +) : KotlinTarget { + private val attributeContainer = HierarchyAttributeContainer(parent = null) + + override fun getAttributes(): AttributeContainer = attributeContainer + + override val defaultConfigurationName: String + get() = disambiguateName("default") + + override val apiElementsConfigurationName: String + get() = disambiguateName("apiElements") + + override val runtimeElementsConfigurationName: String + get() = disambiguateName("runtimeElements") + + override val artifactsTaskName: String + get() = disambiguateName("jar") + + override fun toString(): String = "target $name ($platformType)" + + override val publishable: Boolean + get() = true + + override val components: Set by lazy { + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + val usageContexts = createUsageContexts(mainCompilation) + setOf( + if (isGradleVersionAtLeast(4, 7)) { + val componentName = mainCompilation.target.name + createKotlinVariant(componentName, mainCompilation, usageContexts).apply { + sourcesArtifacts = setOf( + sourcesJarArtifact( + mainCompilation, componentName, + dashSeparatedName(target.name.toLowerCase(), componentName.toLowerCase()) + ) + ) + } + } else { + KotlinVariant(mainCompilation, usageContexts) + } + ).also { project.components.addAll(it) } + } + + protected fun createKotlinVariant( + componentName: String, + compilation: KotlinCompilation<*>, + usageContexts: Set + ): KotlinVariant { + + val kotlinExtension = project.kotlinExtension as? KotlinMultiplatformExtension + ?: return KotlinVariantWithCoordinates(compilation, usageContexts) + + if (targetName == KotlinMultiplatformPlugin.METADATA_TARGET_NAME) + return KotlinVariantWithCoordinates(compilation, usageContexts) + + val separateMetadataTarget = kotlinExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME) + + val result = if (kotlinExtension.isGradleMetadataAvailable) { + KotlinVariantWithMetadataVariant(compilation, usageContexts, separateMetadataTarget) + } else { + // we should only add the Kotlin metadata dependency if we publish no Gradle metadata related to Kotlin MPP; + // with metadata, such a dependency would get invalid, since a platform module should only depend on modules for that + // same platform, not Kotlin metadata modules + KotlinVariantWithMetadataDependency(compilation, usageContexts, separateMetadataTarget) + } + + result.componentName = componentName + return result + } + + private fun createUsageContexts( + producingCompilation: KotlinCompilation<*> + ): Set { + // Here, `JAVA_API` and `JAVA_RUNTIME_JARS` are used intentionally as Gradle needs this for + // ordering of the usage contexts (prioritizing the dependencies) when merging them into the POM; + // These Java usages should not be replaced with the custom Kotlin usages. + return listOfNotNull( + JAVA_API to apiElementsConfigurationName, + (JAVA_RUNTIME_JARS to runtimeElementsConfigurationName).takeIf { producingCompilation is KotlinCompilationToRunnableFiles } + ).mapTo(mutableSetOf()) { (usageName, dependenciesConfigurationName) -> + DefaultKotlinUsageContext( + producingCompilation, + project.usageByName(usageName), + dependenciesConfigurationName + ) + } + } + + protected fun sourcesJarArtifact( + producingCompilation: KotlinCompilation<*>, + componentName: String, + artifactNameAppendix: String, + classifierPrefix: String? = null + ): PublishArtifact { + val sourcesJarTask = sourcesJarTask(producingCompilation, componentName, artifactNameAppendix) + val sourceArtifactConfigurationName = producingCompilation.disambiguateName("sourceArtifacts") + return producingCompilation.target.project.run { + (configurations.findByName(sourceArtifactConfigurationName) ?: run { + val configuration = configurations.create(sourceArtifactConfigurationName) { + it.isCanBeResolved = false + it.isCanBeConsumed = false + } + artifacts.add(sourceArtifactConfigurationName, sourcesJarTask) + configuration + }).artifacts.single().apply { + this as ConfigurablePublishArtifact + classifier = dashSeparatedName(classifierPrefix, "sources") + } + } + } + + @Suppress("UNCHECKED_CAST") + internal val publicationConfigureActions = + WrapUtil.toDomainObjectSet(Action::class.java) as DomainObjectSet> + + override fun mavenPublication(action: Action) { + publicationConfigureActions.add(action) + } + + override fun mavenPublication(action: Closure) = + mavenPublication(ConfigureUtil.configureUsing(action)) + + override var preset: KotlinTargetPreset? = null + internal set +} + +internal fun KotlinTarget.disambiguateName(simpleName: String) = + lowerCamelCaseName(targetName, simpleName) + +open class KotlinAndroidTarget( + override val targetName: String, + project: Project +) : AbstractKotlinTarget(project) { + + override var disambiguationClassifier: String? = null + internal set + + override val platformType: KotlinPlatformType + get() = KotlinPlatformType.androidJvm + + private val compilationFactory = KotlinJvmAndroidCompilationFactory(project, this) + + override val compilations: NamedDomainObjectContainer = + project.container(compilationFactory.itemClass, compilationFactory) + + /** Names of the Android library variants that should be published from the target's project within the default publications which are + * set up if the `maven-publish` Gradle plugin is applied. + * + * Item examples: + * * 'release' (in case no product flavors were defined) + * * 'fooRelease' (for the release build type of a flavor 'foo') + * * 'fooBarRelease' (for the release build type multi-dimensional flavors 'foo' and 'bar'). + * + * If set to null, which can also be done with [publishAllLibraryVariants], + * all library variants will be published, but not test or application variants. */ + var publishLibraryVariants: List? = listOf() + // Workaround for Groovy GString items in a list: + set(value) { field = value?.map(Any::toString) } + + /** Add Android library variant names to [publishLibraryVariants]. */ + fun publishLibraryVariants(vararg names: String) { + publishLibraryVariants = publishLibraryVariants.orEmpty() + names + } + + /** Set up all of the Android library variants to be published from this target's project within the default publications, which are + * set up if the `maven-publish` Gradle plugin is applied. This overrides the variants chosen with [publishLibraryVariants] */ + fun publishAllLibraryVariants() { + publishLibraryVariants = null + } + + /** If true, a publication will be created per merged product flavor, with the build types used as classifiers for the artifacts + * published within each publication. If set to false, each Android variant will have a separate publication. */ + var publishLibraryVariantsGroupedByFlavor = false + + private fun checkPublishLibraryVariantsExist() { + // Capture type parameter T + fun AbstractAndroidProjectHandler.getLibraryVariantNames() = + mutableSetOf().apply { + forEachVariant(project) { + if (getLibraryOutputTask(it) != null) + add(getVariantName(it)) + } + } + + val variantNames = + KotlinAndroidPlugin.androidTargetHandler(project.getKotlinPluginVersion()!!, this) + .getLibraryVariantNames() + + val missingVariants = + publishLibraryVariants?.minus(variantNames).orEmpty() + + if (missingVariants.isNotEmpty()) + throw InvalidUserDataException( + "Kotlin target '$targetName' tried to set up publishing for Android build variants that are not library variants " + + "or do not exist:\n" + missingVariants.joinToString("\n") { "* $it" } + + "\nCheck the 'publishLibraryVariants' property, it should point to existing Android library variants. Publishing " + + "of application and test variants is not supported." + ) + } + + override val components by lazy { + checkPublishLibraryVariantsExist() + + KotlinAndroidPlugin.androidTargetHandler(project.getKotlinPluginVersion()!!, this).doCreateComponents() + .also { project.components.addAll(it) } + } + + // Capture the type parameter T for `AbstractAndroidProjectHandler` + private fun AbstractAndroidProjectHandler.doCreateComponents(): Set { + if (!isGradleVersionAtLeast(4, 7)) + return emptySet() + + val publishableVariants = mutableListOf() + .apply { forEachVariant(project) { add(it) } } + .toList() // Defensive copy against unlikely modification by the lambda that captures the list above in forEachVariant { } + .filter { getLibraryOutputTask(it) != null && publishLibraryVariants?.contains(getVariantName(it)) ?: true } + + val publishableVariantGroups = publishableVariants.groupBy { variant -> + val flavorNames = getFlavorNames(variant) + if (publishLibraryVariantsGroupedByFlavor) { + // For each flavor, we group its variants (which differ only in the build type) in a single component in order to publish + // all of the build types of the flavor as a single module with the build type as the classifier of the artifacts + flavorNames + } else { + flavorNames + getBuildTypeName(variant) + } + } + + return publishableVariantGroups.map { (flavorGroupNameParts, androidVariants) -> + val nestedVariants = androidVariants.mapTo(mutableSetOf()) { androidVariant -> + val androidVariantName = getVariantName(androidVariant) + val compilation = compilations.getByName(androidVariantName) + + val flavorNames = getFlavorNames(androidVariant) + val buildTypeName = getBuildTypeName(androidVariant) + + val artifactClassifier = buildTypeName.takeIf { it != "release" && publishLibraryVariantsGroupedByFlavor } + + val usageContexts = createAndroidUsageContexts(androidVariant, compilation, artifactClassifier) + createKotlinVariant( + lowerCamelCaseName(compilation.target.name, *flavorGroupNameParts.toTypedArray()), + compilation, + usageContexts + ).apply { + sourcesArtifacts = setOf( + sourcesJarArtifact( + compilation, compilation.disambiguateName(""), + dashSeparatedName( + compilation.target.name.toLowerCase(), + *flavorNames.map { it.toLowerCase() }.toTypedArray(), + buildTypeName.takeIf { it != "release" }?.toLowerCase() + ), + classifierPrefix = artifactClassifier + ) + ) + + if (!publishLibraryVariantsGroupedByFlavor) { + defaultArtifactIdSuffix = + dashSeparatedName( + (getFlavorNames(androidVariant) + getBuildTypeName(androidVariant).takeIf { it != "release" }) + .map { it?.toLowerCase() } + ).takeIf { it.isNotEmpty() } + } + } + } + + if (publishLibraryVariantsGroupedByFlavor) { + JointAndroidKotlinTargetComponent( + this@KotlinAndroidTarget, + nestedVariants, + flavorGroupNameParts, + nestedVariants.flatMap { it.sourcesArtifacts }.toSet() + ) + } else { + nestedVariants.single() + } as KotlinTargetComponent + }.toSet() + } + + private fun AbstractAndroidProjectHandler.createAndroidUsageContexts( + variant: T, + compilation: KotlinCompilation<*>, + artifactClassifier: String? + ): Set { + val variantName = getVariantName(variant) + val outputTask = getLibraryOutputTask(variant) ?: return emptySet() + val artifact = run { + val archivesConfigurationName = lowerCamelCaseName(targetName, variantName, "archives") + project.configurations.maybeCreate(archivesConfigurationName).apply { + isCanBeConsumed = false + isCanBeResolved = false + } + project.artifacts.add(archivesConfigurationName, outputTask) { artifact -> + artifact.classifier = artifactClassifier + } + } + + val apiElementsConfigurationName = lowerCamelCaseName(variantName, "apiElements") + val runtimeElementsConfigurationName = lowerCamelCaseName(variantName, "runtimeElements") + + // Here, `JAVA_API` and `JAVA_RUNTIME_JARS` are used intentionally as Gradle needs this for + // ordering of the usage contexts (prioritizing the dependencies) when merging them into the POM; + // These Java usages should not be replaced with the custom Kotlin usages. + return listOf( + apiElementsConfigurationName to JAVA_API, + runtimeElementsConfigurationName to JAVA_RUNTIME_JARS + ).mapTo(mutableSetOf()) { (dependencyConfigurationName, usageName) -> + DefaultKotlinUsageContext( + compilation, + project.usageByName(usageName), + dependencyConfigurationName, + overrideConfigurationArtifacts = setOf(artifact) + ) + } + } +} + +open class KotlinWithJavaTarget( + project: Project, + override val platformType: KotlinPlatformType, + override val targetName: String +) : AbstractKotlinTarget(project) { + override var disambiguationClassifier: String? = null + internal set + + override val defaultConfigurationName: String + get() = Dependency.DEFAULT_CONFIGURATION + + override val apiElementsConfigurationName: String + get() = JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME + + override val runtimeElementsConfigurationName: String + get() = JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME + + override val artifactsTaskName: String + get() = JavaPlugin.JAR_TASK_NAME + + override val compilations: NamedDomainObjectContainer> = + @Suppress("UNCHECKED_CAST") + project.container( + KotlinWithJavaCompilation::class.java as Class>, + KotlinWithJavaCompilationFactory(project, this) + ) +} + +open class KotlinOnlyTarget>( + project: Project, + override val platformType: KotlinPlatformType +) : AbstractKotlinTarget(project) { + + override lateinit var compilations: NamedDomainObjectContainer + internal set + + override lateinit var targetName: String + internal set + + override var disambiguationClassifier: String? = null + internal set +} + +class KotlinNativeTarget( + project: Project, + val konanTarget: KonanTarget +) : KotlinOnlyTarget(project, KotlinPlatformType.native) { + + init { + attributes.attribute(konanTargetAttribute, konanTarget.name) + } + + val binaries = if(isGradleVersionAtLeast(4, 2)) { + // Use newInstance to allow accessing binaries by their names in Groovy using the extension mechanism. + project.objects.newInstance(KotlinNativeBinaryContainer::class.java, this, WrapUtil.toDomainObjectSet(NativeBinary::class.java)) + } else { + KotlinNativeBinaryContainer(this, WrapUtil.toDomainObjectSet(NativeBinary::class.java)) + } + + fun binaries(configure: KotlinNativeBinaryContainer.() -> Unit) { + binaries.configure() + } + + fun binaries(configure: Closure<*>) { + ConfigureUtil.configure(configure, binaries) + } + + override val artifactsTaskName: String + get() = disambiguateName("binaries") + + override val publishable: Boolean + get() = konanTarget.enabledOnCurrentHost + + // User-visible constants + val DEBUG = NativeBuildType.DEBUG + val RELEASE = NativeBuildType.RELEASE + + val EXECUTABLE = NativeOutputKind.EXECUTABLE + val FRAMEWORK = NativeOutputKind.FRAMEWORK + val DYNAMIC = NativeOutputKind.DYNAMIC + val STATIC = NativeOutputKind.STATIC + + companion object { + val konanTargetAttribute = Attribute.of( + "org.jetbrains.kotlin.native.target", + String::class.java + ) + } +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinAndroidTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinAndroidTargetPreset.kt new file mode 100644 index 00000000000..921736ad861 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinAndroidTargetPreset.kt @@ -0,0 +1,330 @@ +/* + * 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.Dependency +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.plugins.JavaPlugin +import org.gradle.internal.reflect.Instantiator +import org.jetbrains.kotlin.compilerRunner.KotlinNativeProjectProperty +import org.jetbrains.kotlin.compilerRunner.hasProperty +import org.jetbrains.kotlin.compilerRunner.konanHome +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.applyLanguageSettingsToKotlinTask +import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider +import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader +import org.jetbrains.kotlin.konan.KonanVersion +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget +import java.io.File +import java.util.* + +abstract class KotlinOnlyTargetPreset>( + protected val project: Project, + private val instantiator: Instantiator, + private val fileResolver: FileResolver, + protected val kotlinPluginVersion: String +) : KotlinTargetPreset> { + + protected open fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = true, createTestCompilation = true) + + override fun createTarget(name: String): KotlinOnlyTarget { + val result = KotlinOnlyTarget(project, platformType).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinOnlyTargetPreset + + val compilationFactory = createCompilationFactory(this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + createKotlinTargetConfigurator().configureTarget(result) + + result.compilations.all { compilation -> + buildCompilationProcessor(compilation).run() + if (compilation.name == KotlinCompilation.MAIN_COMPILATION_NAME) { + sourcesJarTask(compilation, result.targetName, result.targetName.toLowerCase()) + } + } + + return result + } + + protected abstract fun createCompilationFactory(forTarget: KotlinOnlyTarget): KotlinCompilationFactory + protected abstract val platformType: KotlinPlatformType + internal abstract fun buildCompilationProcessor(compilation: T): KotlinSourceSetProcessor<*> +} + +class KotlinMetadataTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return KotlinCommonSourceSetProcessor(project, compilation, tasksProvider, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "metadata" + } + + override fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = false, createTestCompilation = false) + + override fun createTarget(name: String): KotlinOnlyTarget = + super.createTarget(name).apply { + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + val commonMainSourceSet = project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + + mainCompilation.source(commonMainSourceSet) + + project.afterEvaluate { + // Since there's no default source set, apply language settings from commonMain: + val compileKotlinMetadata = project.tasks.getByName(mainCompilation.compileKotlinTaskName) as KotlinCompile<*> + applyLanguageSettingsToKotlinTask(commonMainSourceSet.languageSettings, compileKotlinMetadata) + } + } +} + +class KotlinJvmTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JvmSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "jvm" + } +} + +class KotlinJsTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JsSourceSetProcessor(project, tasksProvider, 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 + preset = this@KotlinAndroidTargetPreset + } + + KotlinAndroidPlugin.applyToTarget(kotlinPluginVersion, result) + 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 + preset = this@KotlinJvmWithJavaTargetPreset + } + + AbstractKotlinPlugin.configureTarget(target) { compilation -> + Kotlin2JvmSourceSetProcessor(project, KotlinTasksProvider(name), compilation, kotlinPluginVersion) + } + + target.compilations.getByName("test").run { + val main = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + + compileDependencyFiles = project.files( + main.output.allOutputs, + project.configurations.maybeCreate(compileDependencyConfigurationName) + ) + runtimeDependencyFiles = project.files( + output.allOutputs, + main.output.allOutputs, + project.configurations.maybeCreate(runtimeDependencyConfigurationName) + ) + } + + return target + } + + companion object { + const val PRESET_NAME = "jvmWithJava" + } +} + +class KotlinNativeTargetPreset( + private val name: String, + val project: Project, + val konanTarget: KonanTarget, + private val kotlinPluginVersion: String +) : KotlinTargetPreset { + + init { + // This is required to obtain Kotlin/Native home in CLion plugin: + setupNativeHomePrivateProperty() + } + + override fun getName(): String = name + + private fun setupNativeHomePrivateProperty() = with(project) { + if (!hasProperty(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY)) + extensions.extraProperties.set(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY, konanHome) + } + + private fun setupNativeCompiler() = with(project) { + if (!hasProperty(KotlinNativeProjectProperty.KONAN_HOME_OVERRIDE)) { + NativeCompilerDownloader(this).downloadIfNeeded() + logger.info("Kotlin/Native distribution: $konanHome") + } else { + logger.info("User-provided Kotlin/Native distribution: $konanHome") + } + } + + // We declare default K/N dependencies as files to avoid searching them in remote repos (see KT-28128). + private fun defaultLibs(target: KonanTarget? = null): List = with(project) { + + val relPath = if (target != null) "platform/${target.name}" else "common" + + file("$konanHome/klib/$relPath") + .listFiles { file -> file.isDirectory } + ?.sortedBy { dir -> dir.name.toLowerCase() } + ?.map { dir -> dependencies.create(files(dir)) } ?: emptyList() + } + + override fun createTarget(name: String): KotlinNativeTarget { + setupNativeCompiler() + + val result = KotlinNativeTarget(project, konanTarget).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinNativeTargetPreset + + val compilationFactory = KotlinNativeCompilationFactory(project, this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + KotlinNativeTargetConfigurator(kotlinPluginVersion).configureTarget(result) + + // Allow IDE to resolve the libraries provided by the compiler by adding them into dependencies. + result.compilations.all { compilation -> + val target = compilation.target.konanTarget + // First, put common libs: + defaultLibs().forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + // Then, platform-specific libs: + defaultLibs(target).forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + } + + if (!konanTarget.enabledOnCurrentHost) { + with(HostManager()) { + val supportedHosts = enabledTargetsByHost.filterValues { konanTarget in it }.keys + val supportedHostsString = + if (supportedHosts.size == 1) + "a ${supportedHosts.single()} host" else + "one of the host platforms: ${supportedHosts.joinToString(", ")}" + project.logger.warn( + "Target '$name' for platform ${konanTarget} is ignored during build on this ${HostManager.host} machine. " + + "You can build it with $supportedHostsString." + ) + } + } + + return result + } + + companion object { + private const val KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY = "konanHome" + } +} + +internal val KonanTarget.isCurrentHost: Boolean + get() = this == HostManager.host + +internal val KonanTarget.enabledOnCurrentHost + get() = HostManager().isEnabled(this) + +internal val KotlinNativeCompilation.isMainCompilation: Boolean + get() = name == KotlinCompilation.MAIN_COMPILATION_NAME diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCommonCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCommonCompilation.kt new file mode 100644 index 00000000000..1284fe446f3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCommonCompilation.kt @@ -0,0 +1,471 @@ + /* + * 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.Action +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.file.FileCollection +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.tasks.SourceSet +import org.gradle.util.ConfigureUtil +import org.jetbrains.kotlin.gradle.dsl.* +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker +import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy +import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import java.util.* +import java.util.concurrent.Callable + +internal fun KotlinCompilation<*>.composeName(prefix: String? = null, suffix: String? = null): String { + val compilationNamePart = compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME } + val targetNamePart = target.disambiguationClassifier + + return lowerCamelCaseName(prefix, targetNamePart, compilationNamePart, suffix) +} + +internal val KotlinCompilation<*>.defaultSourceSetName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName) + +abstract class AbstractKotlinCompilation( + target: KotlinTarget, + override val compilationName: String +) : KotlinCompilation, HasKotlinDependencies { + + override val kotlinOptions: T + get() = compileKotlinTask.kotlinOptions + + override fun kotlinOptions(configure: T.() -> Unit) = + configure(kotlinOptions) + + @Suppress("UNCHECKED_CAST") + override val compileKotlinTask: KotlinCompile + get() = (target.project.tasks.getByName(compileKotlinTaskName) as KotlinCompile) + + // Don't declare this property in the constructor to avoid NPE + // when an overriding property of a subclass is accessed instead. + override val target: KotlinTarget = target + + private val attributeContainer = HierarchyAttributeContainer(target.attributes) + + override fun getAttributes(): AttributeContainer = attributeContainer + + override val kotlinSourceSets: MutableSet = mutableSetOf() + + override val allKotlinSourceSets: Set + get() = kotlinSourceSets.flatMapTo(mutableSetOf()) { it.getSourceSetHierarchy() } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(defaultSourceSetName) + + override fun defaultSourceSet(configure: KotlinSourceSet.() -> Unit) = + configure(defaultSourceSet) + + override val output: KotlinCompilationOutput by lazy { + DefaultKotlinCompilationOutput( + target.project, + Callable { target.project.buildDir.resolve("processedResources/${target.targetName}/$name") }) + } + + open fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + fun AbstractKotlinCompile<*>.configureAction() { + source(sourceSet.kotlin) + sourceFilesExtensions(sourceSet.customSourceFilesExtensions) + if (addAsCommonSources) { + commonSourceSet += sourceSet.kotlin + } + } + + // Note! Invocation of withType-all results in preliminary task instantiation. + // After fix of this issue the following code should be uncommented: +// if (useLazyTaskConfiguration) { +// (target.project.tasks.named(compileKotlinTaskName) as TaskProvider>).configure { +// it.configureAction() +// } +// } + + target.project.tasks + // To configure a task that may have not yet been created at this point, use 'withType-matching-all`: + .withType(AbstractKotlinCompile::class.java) + .matching { it.name == compileKotlinTaskName } + .all { compileKotlinTask -> + compileKotlinTask.configureAction() + } + } + + override fun source(sourceSet: KotlinSourceSet) { + if (kotlinSourceSets.add(sourceSet)) { + //TODO possibly issue with forced instantiation + target.project.whenEvaluated { + sourceSet.getSourceSetHierarchy().forEach { sourceSet -> + val isCommonSource = + CompilationSourceSetUtil.sourceSetsInMultipleCompilations(project)?.contains(sourceSet) ?: false + + addSourcesToCompileTask(sourceSet, addAsCommonSources = isCommonSource) + + // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like + // old Java & Android projects: + addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) + addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) + addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) + + if (this@AbstractKotlinCompilation is KotlinCompilationToRunnableFiles<*>) { + addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false) + } + + if (sourceSet.name != defaultSourceSetName) { + kotlinExtension.sourceSets.findByName(defaultSourceSetName)?.let { defaultSourceSet -> + // Temporary solution for checking consistency across source sets participating in a compilation that may + // not be interconnected with the dependsOn relation: check the settings as if the default source set of + // the compilation depends on the one added to the compilation: + defaultSourceSetLanguageSettingsChecker.runAllChecks( + defaultSourceSet, + sourceSet + ) + } + } + } + } + } + } + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileClasspath" + ) + + override val compileKotlinTaskName: String + get() = lowerCamelCaseName( + "compile", + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "Kotlin", + target.targetName + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "classes") + + override lateinit var compileDependencyFiles: FileCollection + + override val apiConfigurationName: String + get() = disambiguateName("api") + + override val implementationConfigurationName: String + get() = disambiguateName("implementation") + + override val compileOnlyConfigurationName: String + get() = disambiguateName("compileOnly") + + override val runtimeOnlyConfigurationName: String + get() = disambiguateName("runtimeOnly") + + override fun dependencies(configure: KotlinDependencyHandler.() -> Unit): Unit = + DefaultKotlinDependencyHandler(this, target.project).run(configure) + + override fun dependencies(configureClosure: Closure) = + dependencies f@{ ConfigureUtil.configure(configureClosure, this@f) } + + override fun toString(): String = "compilation '$compilationName' ($target)" +} + +abstract class AbstractKotlinCompilationToRunnableFiles( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationToRunnableFiles { + override val runtimeDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "runtimeClasspath" + ) + + override lateinit var runtimeDependencyFiles: FileCollection +} + +internal fun KotlinCompilation<*>.disambiguateName(simpleName: String): String { + return lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + simpleName + ) +} + +open class KotlinJvmCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile +} + +class KotlinWithJavaCompilation( + target: KotlinWithJavaTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + lateinit var javaSourceSet: SourceSet + + override val output: KotlinCompilationOutput by lazy { KotlinWithJavaCompilationOutput(this) } + + override val processResourcesTaskName: String + get() = javaSourceSet.processResourcesTaskName + + override var runtimeDependencyFiles: FileCollection + get() = javaSourceSet.runtimeClasspath + set(value) { + javaSourceSet.runtimeClasspath = value + } + + override val runtimeDependencyConfigurationName: String + get() = javaSourceSet.runtimeClasspathConfigurationName + + override val compileDependencyConfigurationName: String + get() = javaSourceSet.compileClasspathConfigurationName + + override val runtimeOnlyConfigurationName: String + get() = javaSourceSet.runtimeOnlyConfigurationName + + override val implementationConfigurationName: String + get() = javaSourceSet.implementationConfigurationName + + override val apiConfigurationName: String + get() = javaSourceSet.apiConfigurationName + + override val compileOnlyConfigurationName: String + get() = javaSourceSet.compileOnlyConfigurationName + + override val compileAllTaskName: String + get() = javaSourceSet.classesTaskName + + override var compileDependencyFiles: FileCollection + get() = javaSourceSet.compileClasspath + set(value) { + javaSourceSet.compileClasspath = value + } + + fun source(javaSourceSet: SourceSet) { + with(target.project) { + afterEvaluate { + (tasks.getByName(compileKotlinTaskName) as AbstractKotlinCompile<*>).source(javaSourceSet.java) + } + } + } +} + +class KotlinJvmAndroidCompilation( + target: KotlinAndroidTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name) { + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + override val relatedConfigurationNames: List + get() = super.relatedConfigurationNames + listOf("${name}ApiElements", "${name}RuntimeElements") +} + +class KotlinJsCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: Kotlin2JsCompile + get() = super.compileKotlinTask as Kotlin2JsCompile +} + +class KotlinCommonCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name) { + override val compileKotlinTask: KotlinCompileCommon + get() = super.compileKotlinTask as KotlinCompileCommon + + // TODO once we properly compile metadata for each source set, the default source sets will likely become just the source sets + // which are transformed to metadata + private val commonSourceSetName = when (compilationName) { + KotlinCompilation.MAIN_COMPILATION_NAME -> KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME + else -> error("Custom metadata compilations are not supported yet") + } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(commonSourceSetName) +} + +class KotlinNativeCompilation( + override val target: KotlinNativeTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationWithResources { + + override val kotlinOptions: KotlinCommonOptions + get() = compileKotlinTask.kotlinOptions + + override val compileKotlinTask: KotlinNativeCompile + get() = super.compileKotlinTask as KotlinNativeCompile + + private val project: Project + get() = target.project + + // A collection containing all source sets used by this compilation + // (taking into account dependencies between source sets). Used by both compilation + // and linking tasks. Unlike kotlinSourceSets, includes dependency source sets. + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val allSources: MutableSet = mutableSetOf() + + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val commonSources: MutableSet = mutableSetOf() + + var isTestCompilation = false + + var friendCompilationName: String? = null + + internal val friendCompilation: KotlinNativeCompilation? + get() = friendCompilationName?.let { + target.compilations.getByName(it) + } + + // Used only to support the old APIs. TODO: Remove when the old APIs are removed. + internal val binaries = mutableMapOf, NativeBinary>() + + // Native-specific DSL. + var extraOpts = mutableListOf() + + fun extraOpts(vararg values: Any) = extraOpts(values.toList()) + fun extraOpts(values: List) { + extraOpts.addAll(values.map { it.toString() }) + } + + var buildTypes = mutableListOf() + var outputKinds = mutableListOf() + + fun outputKind(kind: NativeOutputKind) = outputKinds.add(kind) + + fun outputKinds(vararg kinds: NativeOutputKind) { + outputKinds = kinds.toMutableList() + } + + fun outputKinds(vararg kinds: String) { + outputKinds = kinds.map { NativeOutputKind.valueOf(it.toUpperCase()) }.toMutableList() + } + + fun outputKinds(kinds: List) { + outputKinds = kinds.map { + when (it) { + is NativeOutputKind -> it + is String -> NativeOutputKind.valueOf(it.toUpperCase()) + else -> error("Cannot use $it as an output kind") + } + }.toMutableList() + } + + var entryPoint: String? = null + fun entryPoint(value: String) { + entryPoint = value + } + + // Interop DSL. + val cinterops = project.container(DefaultCInteropSettings::class.java) { cinteropName -> + DefaultCInteropSettings(project, cinteropName, this) + } + + var linkerOpts = mutableListOf() + + fun cinterops(action: NamedDomainObjectContainer.() -> Unit) = cinterops.action() + fun cinterops(action: Closure) = cinterops(ConfigureUtil.configureUsing(action)) + fun cinterops(action: Action>) = action.execute(cinterops) + + fun linkerOpts(vararg values: String) = linkerOpts(values.toList()) + fun linkerOpts(values: List) { + linkerOpts.addAll(values) + } + + // Task accessors. + fun findLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink? = binaries[kind to buildType]?.linkTask + + fun getLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink = + findLinkTask(kind, buildType) + ?: throw IllegalArgumentException("Cannot find a link task for the binary kind '$kind' and the build type '$buildType'") + + fun findLinkTask(kind: String, buildType: String) = + findLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getLinkTask(kind: String, buildType: String) = + getLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun findBinary(kind: NativeOutputKind, buildType: NativeBuildType): File? = findLinkTask(kind, buildType)?.outputFile?.get() + + fun getBinary(kind: NativeOutputKind, buildType: NativeBuildType): File = getLinkTask(kind, buildType).outputFile.get() + + fun findBinary(kind: String, buildType: String) = + findBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getBinary(kind: String, buildType: String) = + getBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + // Naming + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + fun linkTaskName(kind: NativeOutputKind, buildType: NativeBuildType): String = + lowerCamelCaseName( + "link", + KotlinNativeBinaryContainer.generateBinaryName(compilationName, buildType, kind.taskNameClassifier), + target.targetName + ) + + fun linkTaskName(kind: String, buildType: String) = + linkTaskName(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileKlibraries" + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "klibrary") + + val binariesTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "binaries") + + override fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + allSources.add(sourceSet.kotlin) + if (addAsCommonSources) { + commonSources.add(sourceSet.kotlin) + } + } +} + +private object CompilationSourceSetUtil { + // Cache the results per project + private val projectSourceSetsInMultipleCompilationsCache = WeakHashMap>() + + fun sourceSetsInMultipleCompilations(project: Project) = + projectSourceSetsInMultipleCompilationsCache.computeIfAbsent(project) { _ -> + check(project.state.executed) { "Should only be computed after the project is evaluated" } + + val compilations = (project.kotlinExtension as? KotlinMultiplatformExtension)?.targets?.flatMap { it.compilations } + ?: return@computeIfAbsent null + + compilations + .flatMap { compilation -> compilation.allKotlinSourceSets.map { sourceSet -> compilation to sourceSet } } + .groupingBy { (_, sourceSet) -> sourceSet } + .eachCount() + .filterValues { it > 1 } + .keys + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJsCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJsCompilation.kt new file mode 100644 index 00000000000..1284fe446f3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJsCompilation.kt @@ -0,0 +1,471 @@ + /* + * 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.Action +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.file.FileCollection +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.tasks.SourceSet +import org.gradle.util.ConfigureUtil +import org.jetbrains.kotlin.gradle.dsl.* +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker +import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy +import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import java.util.* +import java.util.concurrent.Callable + +internal fun KotlinCompilation<*>.composeName(prefix: String? = null, suffix: String? = null): String { + val compilationNamePart = compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME } + val targetNamePart = target.disambiguationClassifier + + return lowerCamelCaseName(prefix, targetNamePart, compilationNamePart, suffix) +} + +internal val KotlinCompilation<*>.defaultSourceSetName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName) + +abstract class AbstractKotlinCompilation( + target: KotlinTarget, + override val compilationName: String +) : KotlinCompilation, HasKotlinDependencies { + + override val kotlinOptions: T + get() = compileKotlinTask.kotlinOptions + + override fun kotlinOptions(configure: T.() -> Unit) = + configure(kotlinOptions) + + @Suppress("UNCHECKED_CAST") + override val compileKotlinTask: KotlinCompile + get() = (target.project.tasks.getByName(compileKotlinTaskName) as KotlinCompile) + + // Don't declare this property in the constructor to avoid NPE + // when an overriding property of a subclass is accessed instead. + override val target: KotlinTarget = target + + private val attributeContainer = HierarchyAttributeContainer(target.attributes) + + override fun getAttributes(): AttributeContainer = attributeContainer + + override val kotlinSourceSets: MutableSet = mutableSetOf() + + override val allKotlinSourceSets: Set + get() = kotlinSourceSets.flatMapTo(mutableSetOf()) { it.getSourceSetHierarchy() } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(defaultSourceSetName) + + override fun defaultSourceSet(configure: KotlinSourceSet.() -> Unit) = + configure(defaultSourceSet) + + override val output: KotlinCompilationOutput by lazy { + DefaultKotlinCompilationOutput( + target.project, + Callable { target.project.buildDir.resolve("processedResources/${target.targetName}/$name") }) + } + + open fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + fun AbstractKotlinCompile<*>.configureAction() { + source(sourceSet.kotlin) + sourceFilesExtensions(sourceSet.customSourceFilesExtensions) + if (addAsCommonSources) { + commonSourceSet += sourceSet.kotlin + } + } + + // Note! Invocation of withType-all results in preliminary task instantiation. + // After fix of this issue the following code should be uncommented: +// if (useLazyTaskConfiguration) { +// (target.project.tasks.named(compileKotlinTaskName) as TaskProvider>).configure { +// it.configureAction() +// } +// } + + target.project.tasks + // To configure a task that may have not yet been created at this point, use 'withType-matching-all`: + .withType(AbstractKotlinCompile::class.java) + .matching { it.name == compileKotlinTaskName } + .all { compileKotlinTask -> + compileKotlinTask.configureAction() + } + } + + override fun source(sourceSet: KotlinSourceSet) { + if (kotlinSourceSets.add(sourceSet)) { + //TODO possibly issue with forced instantiation + target.project.whenEvaluated { + sourceSet.getSourceSetHierarchy().forEach { sourceSet -> + val isCommonSource = + CompilationSourceSetUtil.sourceSetsInMultipleCompilations(project)?.contains(sourceSet) ?: false + + addSourcesToCompileTask(sourceSet, addAsCommonSources = isCommonSource) + + // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like + // old Java & Android projects: + addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) + addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) + addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) + + if (this@AbstractKotlinCompilation is KotlinCompilationToRunnableFiles<*>) { + addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false) + } + + if (sourceSet.name != defaultSourceSetName) { + kotlinExtension.sourceSets.findByName(defaultSourceSetName)?.let { defaultSourceSet -> + // Temporary solution for checking consistency across source sets participating in a compilation that may + // not be interconnected with the dependsOn relation: check the settings as if the default source set of + // the compilation depends on the one added to the compilation: + defaultSourceSetLanguageSettingsChecker.runAllChecks( + defaultSourceSet, + sourceSet + ) + } + } + } + } + } + } + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileClasspath" + ) + + override val compileKotlinTaskName: String + get() = lowerCamelCaseName( + "compile", + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "Kotlin", + target.targetName + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "classes") + + override lateinit var compileDependencyFiles: FileCollection + + override val apiConfigurationName: String + get() = disambiguateName("api") + + override val implementationConfigurationName: String + get() = disambiguateName("implementation") + + override val compileOnlyConfigurationName: String + get() = disambiguateName("compileOnly") + + override val runtimeOnlyConfigurationName: String + get() = disambiguateName("runtimeOnly") + + override fun dependencies(configure: KotlinDependencyHandler.() -> Unit): Unit = + DefaultKotlinDependencyHandler(this, target.project).run(configure) + + override fun dependencies(configureClosure: Closure) = + dependencies f@{ ConfigureUtil.configure(configureClosure, this@f) } + + override fun toString(): String = "compilation '$compilationName' ($target)" +} + +abstract class AbstractKotlinCompilationToRunnableFiles( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationToRunnableFiles { + override val runtimeDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "runtimeClasspath" + ) + + override lateinit var runtimeDependencyFiles: FileCollection +} + +internal fun KotlinCompilation<*>.disambiguateName(simpleName: String): String { + return lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + simpleName + ) +} + +open class KotlinJvmCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile +} + +class KotlinWithJavaCompilation( + target: KotlinWithJavaTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + lateinit var javaSourceSet: SourceSet + + override val output: KotlinCompilationOutput by lazy { KotlinWithJavaCompilationOutput(this) } + + override val processResourcesTaskName: String + get() = javaSourceSet.processResourcesTaskName + + override var runtimeDependencyFiles: FileCollection + get() = javaSourceSet.runtimeClasspath + set(value) { + javaSourceSet.runtimeClasspath = value + } + + override val runtimeDependencyConfigurationName: String + get() = javaSourceSet.runtimeClasspathConfigurationName + + override val compileDependencyConfigurationName: String + get() = javaSourceSet.compileClasspathConfigurationName + + override val runtimeOnlyConfigurationName: String + get() = javaSourceSet.runtimeOnlyConfigurationName + + override val implementationConfigurationName: String + get() = javaSourceSet.implementationConfigurationName + + override val apiConfigurationName: String + get() = javaSourceSet.apiConfigurationName + + override val compileOnlyConfigurationName: String + get() = javaSourceSet.compileOnlyConfigurationName + + override val compileAllTaskName: String + get() = javaSourceSet.classesTaskName + + override var compileDependencyFiles: FileCollection + get() = javaSourceSet.compileClasspath + set(value) { + javaSourceSet.compileClasspath = value + } + + fun source(javaSourceSet: SourceSet) { + with(target.project) { + afterEvaluate { + (tasks.getByName(compileKotlinTaskName) as AbstractKotlinCompile<*>).source(javaSourceSet.java) + } + } + } +} + +class KotlinJvmAndroidCompilation( + target: KotlinAndroidTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name) { + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + override val relatedConfigurationNames: List + get() = super.relatedConfigurationNames + listOf("${name}ApiElements", "${name}RuntimeElements") +} + +class KotlinJsCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: Kotlin2JsCompile + get() = super.compileKotlinTask as Kotlin2JsCompile +} + +class KotlinCommonCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name) { + override val compileKotlinTask: KotlinCompileCommon + get() = super.compileKotlinTask as KotlinCompileCommon + + // TODO once we properly compile metadata for each source set, the default source sets will likely become just the source sets + // which are transformed to metadata + private val commonSourceSetName = when (compilationName) { + KotlinCompilation.MAIN_COMPILATION_NAME -> KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME + else -> error("Custom metadata compilations are not supported yet") + } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(commonSourceSetName) +} + +class KotlinNativeCompilation( + override val target: KotlinNativeTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationWithResources { + + override val kotlinOptions: KotlinCommonOptions + get() = compileKotlinTask.kotlinOptions + + override val compileKotlinTask: KotlinNativeCompile + get() = super.compileKotlinTask as KotlinNativeCompile + + private val project: Project + get() = target.project + + // A collection containing all source sets used by this compilation + // (taking into account dependencies between source sets). Used by both compilation + // and linking tasks. Unlike kotlinSourceSets, includes dependency source sets. + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val allSources: MutableSet = mutableSetOf() + + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val commonSources: MutableSet = mutableSetOf() + + var isTestCompilation = false + + var friendCompilationName: String? = null + + internal val friendCompilation: KotlinNativeCompilation? + get() = friendCompilationName?.let { + target.compilations.getByName(it) + } + + // Used only to support the old APIs. TODO: Remove when the old APIs are removed. + internal val binaries = mutableMapOf, NativeBinary>() + + // Native-specific DSL. + var extraOpts = mutableListOf() + + fun extraOpts(vararg values: Any) = extraOpts(values.toList()) + fun extraOpts(values: List) { + extraOpts.addAll(values.map { it.toString() }) + } + + var buildTypes = mutableListOf() + var outputKinds = mutableListOf() + + fun outputKind(kind: NativeOutputKind) = outputKinds.add(kind) + + fun outputKinds(vararg kinds: NativeOutputKind) { + outputKinds = kinds.toMutableList() + } + + fun outputKinds(vararg kinds: String) { + outputKinds = kinds.map { NativeOutputKind.valueOf(it.toUpperCase()) }.toMutableList() + } + + fun outputKinds(kinds: List) { + outputKinds = kinds.map { + when (it) { + is NativeOutputKind -> it + is String -> NativeOutputKind.valueOf(it.toUpperCase()) + else -> error("Cannot use $it as an output kind") + } + }.toMutableList() + } + + var entryPoint: String? = null + fun entryPoint(value: String) { + entryPoint = value + } + + // Interop DSL. + val cinterops = project.container(DefaultCInteropSettings::class.java) { cinteropName -> + DefaultCInteropSettings(project, cinteropName, this) + } + + var linkerOpts = mutableListOf() + + fun cinterops(action: NamedDomainObjectContainer.() -> Unit) = cinterops.action() + fun cinterops(action: Closure) = cinterops(ConfigureUtil.configureUsing(action)) + fun cinterops(action: Action>) = action.execute(cinterops) + + fun linkerOpts(vararg values: String) = linkerOpts(values.toList()) + fun linkerOpts(values: List) { + linkerOpts.addAll(values) + } + + // Task accessors. + fun findLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink? = binaries[kind to buildType]?.linkTask + + fun getLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink = + findLinkTask(kind, buildType) + ?: throw IllegalArgumentException("Cannot find a link task for the binary kind '$kind' and the build type '$buildType'") + + fun findLinkTask(kind: String, buildType: String) = + findLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getLinkTask(kind: String, buildType: String) = + getLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun findBinary(kind: NativeOutputKind, buildType: NativeBuildType): File? = findLinkTask(kind, buildType)?.outputFile?.get() + + fun getBinary(kind: NativeOutputKind, buildType: NativeBuildType): File = getLinkTask(kind, buildType).outputFile.get() + + fun findBinary(kind: String, buildType: String) = + findBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getBinary(kind: String, buildType: String) = + getBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + // Naming + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + fun linkTaskName(kind: NativeOutputKind, buildType: NativeBuildType): String = + lowerCamelCaseName( + "link", + KotlinNativeBinaryContainer.generateBinaryName(compilationName, buildType, kind.taskNameClassifier), + target.targetName + ) + + fun linkTaskName(kind: String, buildType: String) = + linkTaskName(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileKlibraries" + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "klibrary") + + val binariesTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "binaries") + + override fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + allSources.add(sourceSet.kotlin) + if (addAsCommonSources) { + commonSources.add(sourceSet.kotlin) + } + } +} + +private object CompilationSourceSetUtil { + // Cache the results per project + private val projectSourceSetsInMultipleCompilationsCache = WeakHashMap>() + + fun sourceSetsInMultipleCompilations(project: Project) = + projectSourceSetsInMultipleCompilationsCache.computeIfAbsent(project) { _ -> + check(project.state.executed) { "Should only be computed after the project is evaluated" } + + val compilations = (project.kotlinExtension as? KotlinMultiplatformExtension)?.targets?.flatMap { it.compilations } + ?: return@computeIfAbsent null + + compilations + .flatMap { compilation -> compilation.allKotlinSourceSets.map { sourceSet -> compilation to sourceSet } } + .groupingBy { (_, sourceSet) -> sourceSet } + .eachCount() + .filterValues { it > 1 } + .keys + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJsCompilationFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJsCompilationFactory.kt new file mode 100644 index 00000000000..e63b6e6573d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJsCompilationFactory.kt @@ -0,0 +1,93 @@ +/* + * 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.NamedDomainObjectFactory +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation + +interface KotlinCompilationFactory> : NamedDomainObjectFactory { + val itemClass: Class +} + +class KotlinCommonCompilationFactory( + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinCommonCompilation::class.java + + override fun create(name: String): KotlinCommonCompilation = + KotlinCommonCompilation(target, name) +} + +class KotlinJvmCompilationFactory( + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinJvmCompilation::class.java + + override fun create(name: String): KotlinJvmCompilation = + KotlinJvmCompilation(target, name) +} + +class KotlinWithJavaCompilationFactory( + val project: Project, + val target: KotlinWithJavaTarget +) : KotlinCompilationFactory> { + + override val itemClass: Class> + @Suppress("UNCHECKED_CAST") + get() = KotlinWithJavaCompilation::class.java as Class> + + override fun create(name: String): KotlinWithJavaCompilation { + val result = KotlinWithJavaCompilation(target, name) + return result + } +} + +class KotlinJvmAndroidCompilationFactory( + val project: Project, + val target: KotlinAndroidTarget +) : KotlinCompilationFactory { + + override val itemClass: Class + get() = KotlinJvmAndroidCompilation::class.java + + override fun create(name: String): KotlinJvmAndroidCompilation = + KotlinJvmAndroidCompilation(target, name) +} + +class KotlinJsCompilationFactory( + val project: Project, + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinJsCompilation::class.java + + override fun create(name: String): KotlinJsCompilation = + KotlinJsCompilation(target, name) +} + +class KotlinNativeCompilationFactory( + val project: Project, + val target: KotlinNativeTarget +) : KotlinCompilationFactory { + + override val itemClass: Class + get() = KotlinNativeCompilation::class.java + + override fun create(name: String): KotlinNativeCompilation = + KotlinNativeCompilation(target, name).apply { + if (name == KotlinCompilation.TEST_COMPILATION_NAME) { + friendCompilationName = KotlinCompilation.MAIN_COMPILATION_NAME + isTestCompilation = true + } + buildTypes = mutableListOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE) + } + +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJsTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJsTargetPreset.kt new file mode 100644 index 00000000000..921736ad861 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJsTargetPreset.kt @@ -0,0 +1,330 @@ +/* + * 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.Dependency +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.plugins.JavaPlugin +import org.gradle.internal.reflect.Instantiator +import org.jetbrains.kotlin.compilerRunner.KotlinNativeProjectProperty +import org.jetbrains.kotlin.compilerRunner.hasProperty +import org.jetbrains.kotlin.compilerRunner.konanHome +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.applyLanguageSettingsToKotlinTask +import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider +import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader +import org.jetbrains.kotlin.konan.KonanVersion +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget +import java.io.File +import java.util.* + +abstract class KotlinOnlyTargetPreset>( + protected val project: Project, + private val instantiator: Instantiator, + private val fileResolver: FileResolver, + protected val kotlinPluginVersion: String +) : KotlinTargetPreset> { + + protected open fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = true, createTestCompilation = true) + + override fun createTarget(name: String): KotlinOnlyTarget { + val result = KotlinOnlyTarget(project, platformType).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinOnlyTargetPreset + + val compilationFactory = createCompilationFactory(this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + createKotlinTargetConfigurator().configureTarget(result) + + result.compilations.all { compilation -> + buildCompilationProcessor(compilation).run() + if (compilation.name == KotlinCompilation.MAIN_COMPILATION_NAME) { + sourcesJarTask(compilation, result.targetName, result.targetName.toLowerCase()) + } + } + + return result + } + + protected abstract fun createCompilationFactory(forTarget: KotlinOnlyTarget): KotlinCompilationFactory + protected abstract val platformType: KotlinPlatformType + internal abstract fun buildCompilationProcessor(compilation: T): KotlinSourceSetProcessor<*> +} + +class KotlinMetadataTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return KotlinCommonSourceSetProcessor(project, compilation, tasksProvider, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "metadata" + } + + override fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = false, createTestCompilation = false) + + override fun createTarget(name: String): KotlinOnlyTarget = + super.createTarget(name).apply { + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + val commonMainSourceSet = project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + + mainCompilation.source(commonMainSourceSet) + + project.afterEvaluate { + // Since there's no default source set, apply language settings from commonMain: + val compileKotlinMetadata = project.tasks.getByName(mainCompilation.compileKotlinTaskName) as KotlinCompile<*> + applyLanguageSettingsToKotlinTask(commonMainSourceSet.languageSettings, compileKotlinMetadata) + } + } +} + +class KotlinJvmTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JvmSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "jvm" + } +} + +class KotlinJsTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JsSourceSetProcessor(project, tasksProvider, 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 + preset = this@KotlinAndroidTargetPreset + } + + KotlinAndroidPlugin.applyToTarget(kotlinPluginVersion, result) + 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 + preset = this@KotlinJvmWithJavaTargetPreset + } + + AbstractKotlinPlugin.configureTarget(target) { compilation -> + Kotlin2JvmSourceSetProcessor(project, KotlinTasksProvider(name), compilation, kotlinPluginVersion) + } + + target.compilations.getByName("test").run { + val main = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + + compileDependencyFiles = project.files( + main.output.allOutputs, + project.configurations.maybeCreate(compileDependencyConfigurationName) + ) + runtimeDependencyFiles = project.files( + output.allOutputs, + main.output.allOutputs, + project.configurations.maybeCreate(runtimeDependencyConfigurationName) + ) + } + + return target + } + + companion object { + const val PRESET_NAME = "jvmWithJava" + } +} + +class KotlinNativeTargetPreset( + private val name: String, + val project: Project, + val konanTarget: KonanTarget, + private val kotlinPluginVersion: String +) : KotlinTargetPreset { + + init { + // This is required to obtain Kotlin/Native home in CLion plugin: + setupNativeHomePrivateProperty() + } + + override fun getName(): String = name + + private fun setupNativeHomePrivateProperty() = with(project) { + if (!hasProperty(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY)) + extensions.extraProperties.set(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY, konanHome) + } + + private fun setupNativeCompiler() = with(project) { + if (!hasProperty(KotlinNativeProjectProperty.KONAN_HOME_OVERRIDE)) { + NativeCompilerDownloader(this).downloadIfNeeded() + logger.info("Kotlin/Native distribution: $konanHome") + } else { + logger.info("User-provided Kotlin/Native distribution: $konanHome") + } + } + + // We declare default K/N dependencies as files to avoid searching them in remote repos (see KT-28128). + private fun defaultLibs(target: KonanTarget? = null): List = with(project) { + + val relPath = if (target != null) "platform/${target.name}" else "common" + + file("$konanHome/klib/$relPath") + .listFiles { file -> file.isDirectory } + ?.sortedBy { dir -> dir.name.toLowerCase() } + ?.map { dir -> dependencies.create(files(dir)) } ?: emptyList() + } + + override fun createTarget(name: String): KotlinNativeTarget { + setupNativeCompiler() + + val result = KotlinNativeTarget(project, konanTarget).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinNativeTargetPreset + + val compilationFactory = KotlinNativeCompilationFactory(project, this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + KotlinNativeTargetConfigurator(kotlinPluginVersion).configureTarget(result) + + // Allow IDE to resolve the libraries provided by the compiler by adding them into dependencies. + result.compilations.all { compilation -> + val target = compilation.target.konanTarget + // First, put common libs: + defaultLibs().forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + // Then, platform-specific libs: + defaultLibs(target).forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + } + + if (!konanTarget.enabledOnCurrentHost) { + with(HostManager()) { + val supportedHosts = enabledTargetsByHost.filterValues { konanTarget in it }.keys + val supportedHostsString = + if (supportedHosts.size == 1) + "a ${supportedHosts.single()} host" else + "one of the host platforms: ${supportedHosts.joinToString(", ")}" + project.logger.warn( + "Target '$name' for platform ${konanTarget} is ignored during build on this ${HostManager.host} machine. " + + "You can build it with $supportedHostsString." + ) + } + } + + return result + } + + companion object { + private const val KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY = "konanHome" + } +} + +internal val KonanTarget.isCurrentHost: Boolean + get() = this == HostManager.host + +internal val KonanTarget.enabledOnCurrentHost + get() = HostManager().isEnabled(this) + +internal val KotlinNativeCompilation.isMainCompilation: Boolean + get() = name == KotlinCompilation.MAIN_COMPILATION_NAME diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmAndroidCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmAndroidCompilation.kt new file mode 100644 index 00000000000..1284fe446f3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmAndroidCompilation.kt @@ -0,0 +1,471 @@ + /* + * 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.Action +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.file.FileCollection +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.tasks.SourceSet +import org.gradle.util.ConfigureUtil +import org.jetbrains.kotlin.gradle.dsl.* +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker +import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy +import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import java.util.* +import java.util.concurrent.Callable + +internal fun KotlinCompilation<*>.composeName(prefix: String? = null, suffix: String? = null): String { + val compilationNamePart = compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME } + val targetNamePart = target.disambiguationClassifier + + return lowerCamelCaseName(prefix, targetNamePart, compilationNamePart, suffix) +} + +internal val KotlinCompilation<*>.defaultSourceSetName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName) + +abstract class AbstractKotlinCompilation( + target: KotlinTarget, + override val compilationName: String +) : KotlinCompilation, HasKotlinDependencies { + + override val kotlinOptions: T + get() = compileKotlinTask.kotlinOptions + + override fun kotlinOptions(configure: T.() -> Unit) = + configure(kotlinOptions) + + @Suppress("UNCHECKED_CAST") + override val compileKotlinTask: KotlinCompile + get() = (target.project.tasks.getByName(compileKotlinTaskName) as KotlinCompile) + + // Don't declare this property in the constructor to avoid NPE + // when an overriding property of a subclass is accessed instead. + override val target: KotlinTarget = target + + private val attributeContainer = HierarchyAttributeContainer(target.attributes) + + override fun getAttributes(): AttributeContainer = attributeContainer + + override val kotlinSourceSets: MutableSet = mutableSetOf() + + override val allKotlinSourceSets: Set + get() = kotlinSourceSets.flatMapTo(mutableSetOf()) { it.getSourceSetHierarchy() } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(defaultSourceSetName) + + override fun defaultSourceSet(configure: KotlinSourceSet.() -> Unit) = + configure(defaultSourceSet) + + override val output: KotlinCompilationOutput by lazy { + DefaultKotlinCompilationOutput( + target.project, + Callable { target.project.buildDir.resolve("processedResources/${target.targetName}/$name") }) + } + + open fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + fun AbstractKotlinCompile<*>.configureAction() { + source(sourceSet.kotlin) + sourceFilesExtensions(sourceSet.customSourceFilesExtensions) + if (addAsCommonSources) { + commonSourceSet += sourceSet.kotlin + } + } + + // Note! Invocation of withType-all results in preliminary task instantiation. + // After fix of this issue the following code should be uncommented: +// if (useLazyTaskConfiguration) { +// (target.project.tasks.named(compileKotlinTaskName) as TaskProvider>).configure { +// it.configureAction() +// } +// } + + target.project.tasks + // To configure a task that may have not yet been created at this point, use 'withType-matching-all`: + .withType(AbstractKotlinCompile::class.java) + .matching { it.name == compileKotlinTaskName } + .all { compileKotlinTask -> + compileKotlinTask.configureAction() + } + } + + override fun source(sourceSet: KotlinSourceSet) { + if (kotlinSourceSets.add(sourceSet)) { + //TODO possibly issue with forced instantiation + target.project.whenEvaluated { + sourceSet.getSourceSetHierarchy().forEach { sourceSet -> + val isCommonSource = + CompilationSourceSetUtil.sourceSetsInMultipleCompilations(project)?.contains(sourceSet) ?: false + + addSourcesToCompileTask(sourceSet, addAsCommonSources = isCommonSource) + + // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like + // old Java & Android projects: + addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) + addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) + addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) + + if (this@AbstractKotlinCompilation is KotlinCompilationToRunnableFiles<*>) { + addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false) + } + + if (sourceSet.name != defaultSourceSetName) { + kotlinExtension.sourceSets.findByName(defaultSourceSetName)?.let { defaultSourceSet -> + // Temporary solution for checking consistency across source sets participating in a compilation that may + // not be interconnected with the dependsOn relation: check the settings as if the default source set of + // the compilation depends on the one added to the compilation: + defaultSourceSetLanguageSettingsChecker.runAllChecks( + defaultSourceSet, + sourceSet + ) + } + } + } + } + } + } + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileClasspath" + ) + + override val compileKotlinTaskName: String + get() = lowerCamelCaseName( + "compile", + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "Kotlin", + target.targetName + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "classes") + + override lateinit var compileDependencyFiles: FileCollection + + override val apiConfigurationName: String + get() = disambiguateName("api") + + override val implementationConfigurationName: String + get() = disambiguateName("implementation") + + override val compileOnlyConfigurationName: String + get() = disambiguateName("compileOnly") + + override val runtimeOnlyConfigurationName: String + get() = disambiguateName("runtimeOnly") + + override fun dependencies(configure: KotlinDependencyHandler.() -> Unit): Unit = + DefaultKotlinDependencyHandler(this, target.project).run(configure) + + override fun dependencies(configureClosure: Closure) = + dependencies f@{ ConfigureUtil.configure(configureClosure, this@f) } + + override fun toString(): String = "compilation '$compilationName' ($target)" +} + +abstract class AbstractKotlinCompilationToRunnableFiles( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationToRunnableFiles { + override val runtimeDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "runtimeClasspath" + ) + + override lateinit var runtimeDependencyFiles: FileCollection +} + +internal fun KotlinCompilation<*>.disambiguateName(simpleName: String): String { + return lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + simpleName + ) +} + +open class KotlinJvmCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile +} + +class KotlinWithJavaCompilation( + target: KotlinWithJavaTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + lateinit var javaSourceSet: SourceSet + + override val output: KotlinCompilationOutput by lazy { KotlinWithJavaCompilationOutput(this) } + + override val processResourcesTaskName: String + get() = javaSourceSet.processResourcesTaskName + + override var runtimeDependencyFiles: FileCollection + get() = javaSourceSet.runtimeClasspath + set(value) { + javaSourceSet.runtimeClasspath = value + } + + override val runtimeDependencyConfigurationName: String + get() = javaSourceSet.runtimeClasspathConfigurationName + + override val compileDependencyConfigurationName: String + get() = javaSourceSet.compileClasspathConfigurationName + + override val runtimeOnlyConfigurationName: String + get() = javaSourceSet.runtimeOnlyConfigurationName + + override val implementationConfigurationName: String + get() = javaSourceSet.implementationConfigurationName + + override val apiConfigurationName: String + get() = javaSourceSet.apiConfigurationName + + override val compileOnlyConfigurationName: String + get() = javaSourceSet.compileOnlyConfigurationName + + override val compileAllTaskName: String + get() = javaSourceSet.classesTaskName + + override var compileDependencyFiles: FileCollection + get() = javaSourceSet.compileClasspath + set(value) { + javaSourceSet.compileClasspath = value + } + + fun source(javaSourceSet: SourceSet) { + with(target.project) { + afterEvaluate { + (tasks.getByName(compileKotlinTaskName) as AbstractKotlinCompile<*>).source(javaSourceSet.java) + } + } + } +} + +class KotlinJvmAndroidCompilation( + target: KotlinAndroidTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name) { + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + override val relatedConfigurationNames: List + get() = super.relatedConfigurationNames + listOf("${name}ApiElements", "${name}RuntimeElements") +} + +class KotlinJsCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: Kotlin2JsCompile + get() = super.compileKotlinTask as Kotlin2JsCompile +} + +class KotlinCommonCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name) { + override val compileKotlinTask: KotlinCompileCommon + get() = super.compileKotlinTask as KotlinCompileCommon + + // TODO once we properly compile metadata for each source set, the default source sets will likely become just the source sets + // which are transformed to metadata + private val commonSourceSetName = when (compilationName) { + KotlinCompilation.MAIN_COMPILATION_NAME -> KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME + else -> error("Custom metadata compilations are not supported yet") + } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(commonSourceSetName) +} + +class KotlinNativeCompilation( + override val target: KotlinNativeTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationWithResources { + + override val kotlinOptions: KotlinCommonOptions + get() = compileKotlinTask.kotlinOptions + + override val compileKotlinTask: KotlinNativeCompile + get() = super.compileKotlinTask as KotlinNativeCompile + + private val project: Project + get() = target.project + + // A collection containing all source sets used by this compilation + // (taking into account dependencies between source sets). Used by both compilation + // and linking tasks. Unlike kotlinSourceSets, includes dependency source sets. + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val allSources: MutableSet = mutableSetOf() + + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val commonSources: MutableSet = mutableSetOf() + + var isTestCompilation = false + + var friendCompilationName: String? = null + + internal val friendCompilation: KotlinNativeCompilation? + get() = friendCompilationName?.let { + target.compilations.getByName(it) + } + + // Used only to support the old APIs. TODO: Remove when the old APIs are removed. + internal val binaries = mutableMapOf, NativeBinary>() + + // Native-specific DSL. + var extraOpts = mutableListOf() + + fun extraOpts(vararg values: Any) = extraOpts(values.toList()) + fun extraOpts(values: List) { + extraOpts.addAll(values.map { it.toString() }) + } + + var buildTypes = mutableListOf() + var outputKinds = mutableListOf() + + fun outputKind(kind: NativeOutputKind) = outputKinds.add(kind) + + fun outputKinds(vararg kinds: NativeOutputKind) { + outputKinds = kinds.toMutableList() + } + + fun outputKinds(vararg kinds: String) { + outputKinds = kinds.map { NativeOutputKind.valueOf(it.toUpperCase()) }.toMutableList() + } + + fun outputKinds(kinds: List) { + outputKinds = kinds.map { + when (it) { + is NativeOutputKind -> it + is String -> NativeOutputKind.valueOf(it.toUpperCase()) + else -> error("Cannot use $it as an output kind") + } + }.toMutableList() + } + + var entryPoint: String? = null + fun entryPoint(value: String) { + entryPoint = value + } + + // Interop DSL. + val cinterops = project.container(DefaultCInteropSettings::class.java) { cinteropName -> + DefaultCInteropSettings(project, cinteropName, this) + } + + var linkerOpts = mutableListOf() + + fun cinterops(action: NamedDomainObjectContainer.() -> Unit) = cinterops.action() + fun cinterops(action: Closure) = cinterops(ConfigureUtil.configureUsing(action)) + fun cinterops(action: Action>) = action.execute(cinterops) + + fun linkerOpts(vararg values: String) = linkerOpts(values.toList()) + fun linkerOpts(values: List) { + linkerOpts.addAll(values) + } + + // Task accessors. + fun findLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink? = binaries[kind to buildType]?.linkTask + + fun getLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink = + findLinkTask(kind, buildType) + ?: throw IllegalArgumentException("Cannot find a link task for the binary kind '$kind' and the build type '$buildType'") + + fun findLinkTask(kind: String, buildType: String) = + findLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getLinkTask(kind: String, buildType: String) = + getLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun findBinary(kind: NativeOutputKind, buildType: NativeBuildType): File? = findLinkTask(kind, buildType)?.outputFile?.get() + + fun getBinary(kind: NativeOutputKind, buildType: NativeBuildType): File = getLinkTask(kind, buildType).outputFile.get() + + fun findBinary(kind: String, buildType: String) = + findBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getBinary(kind: String, buildType: String) = + getBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + // Naming + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + fun linkTaskName(kind: NativeOutputKind, buildType: NativeBuildType): String = + lowerCamelCaseName( + "link", + KotlinNativeBinaryContainer.generateBinaryName(compilationName, buildType, kind.taskNameClassifier), + target.targetName + ) + + fun linkTaskName(kind: String, buildType: String) = + linkTaskName(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileKlibraries" + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "klibrary") + + val binariesTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "binaries") + + override fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + allSources.add(sourceSet.kotlin) + if (addAsCommonSources) { + commonSources.add(sourceSet.kotlin) + } + } +} + +private object CompilationSourceSetUtil { + // Cache the results per project + private val projectSourceSetsInMultipleCompilationsCache = WeakHashMap>() + + fun sourceSetsInMultipleCompilations(project: Project) = + projectSourceSetsInMultipleCompilationsCache.computeIfAbsent(project) { _ -> + check(project.state.executed) { "Should only be computed after the project is evaluated" } + + val compilations = (project.kotlinExtension as? KotlinMultiplatformExtension)?.targets?.flatMap { it.compilations } + ?: return@computeIfAbsent null + + compilations + .flatMap { compilation -> compilation.allKotlinSourceSets.map { sourceSet -> compilation to sourceSet } } + .groupingBy { (_, sourceSet) -> sourceSet } + .eachCount() + .filterValues { it > 1 } + .keys + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmAndroidCompilationFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmAndroidCompilationFactory.kt new file mode 100644 index 00000000000..e63b6e6573d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmAndroidCompilationFactory.kt @@ -0,0 +1,93 @@ +/* + * 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.NamedDomainObjectFactory +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation + +interface KotlinCompilationFactory> : NamedDomainObjectFactory { + val itemClass: Class +} + +class KotlinCommonCompilationFactory( + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinCommonCompilation::class.java + + override fun create(name: String): KotlinCommonCompilation = + KotlinCommonCompilation(target, name) +} + +class KotlinJvmCompilationFactory( + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinJvmCompilation::class.java + + override fun create(name: String): KotlinJvmCompilation = + KotlinJvmCompilation(target, name) +} + +class KotlinWithJavaCompilationFactory( + val project: Project, + val target: KotlinWithJavaTarget +) : KotlinCompilationFactory> { + + override val itemClass: Class> + @Suppress("UNCHECKED_CAST") + get() = KotlinWithJavaCompilation::class.java as Class> + + override fun create(name: String): KotlinWithJavaCompilation { + val result = KotlinWithJavaCompilation(target, name) + return result + } +} + +class KotlinJvmAndroidCompilationFactory( + val project: Project, + val target: KotlinAndroidTarget +) : KotlinCompilationFactory { + + override val itemClass: Class + get() = KotlinJvmAndroidCompilation::class.java + + override fun create(name: String): KotlinJvmAndroidCompilation = + KotlinJvmAndroidCompilation(target, name) +} + +class KotlinJsCompilationFactory( + val project: Project, + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinJsCompilation::class.java + + override fun create(name: String): KotlinJsCompilation = + KotlinJsCompilation(target, name) +} + +class KotlinNativeCompilationFactory( + val project: Project, + val target: KotlinNativeTarget +) : KotlinCompilationFactory { + + override val itemClass: Class + get() = KotlinNativeCompilation::class.java + + override fun create(name: String): KotlinNativeCompilation = + KotlinNativeCompilation(target, name).apply { + if (name == KotlinCompilation.TEST_COMPILATION_NAME) { + friendCompilationName = KotlinCompilation.MAIN_COMPILATION_NAME + isTestCompilation = true + } + buildTypes = mutableListOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE) + } + +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmCompilation.kt new file mode 100644 index 00000000000..1284fe446f3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmCompilation.kt @@ -0,0 +1,471 @@ + /* + * 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.Action +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.file.FileCollection +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.tasks.SourceSet +import org.gradle.util.ConfigureUtil +import org.jetbrains.kotlin.gradle.dsl.* +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker +import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy +import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import java.util.* +import java.util.concurrent.Callable + +internal fun KotlinCompilation<*>.composeName(prefix: String? = null, suffix: String? = null): String { + val compilationNamePart = compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME } + val targetNamePart = target.disambiguationClassifier + + return lowerCamelCaseName(prefix, targetNamePart, compilationNamePart, suffix) +} + +internal val KotlinCompilation<*>.defaultSourceSetName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName) + +abstract class AbstractKotlinCompilation( + target: KotlinTarget, + override val compilationName: String +) : KotlinCompilation, HasKotlinDependencies { + + override val kotlinOptions: T + get() = compileKotlinTask.kotlinOptions + + override fun kotlinOptions(configure: T.() -> Unit) = + configure(kotlinOptions) + + @Suppress("UNCHECKED_CAST") + override val compileKotlinTask: KotlinCompile + get() = (target.project.tasks.getByName(compileKotlinTaskName) as KotlinCompile) + + // Don't declare this property in the constructor to avoid NPE + // when an overriding property of a subclass is accessed instead. + override val target: KotlinTarget = target + + private val attributeContainer = HierarchyAttributeContainer(target.attributes) + + override fun getAttributes(): AttributeContainer = attributeContainer + + override val kotlinSourceSets: MutableSet = mutableSetOf() + + override val allKotlinSourceSets: Set + get() = kotlinSourceSets.flatMapTo(mutableSetOf()) { it.getSourceSetHierarchy() } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(defaultSourceSetName) + + override fun defaultSourceSet(configure: KotlinSourceSet.() -> Unit) = + configure(defaultSourceSet) + + override val output: KotlinCompilationOutput by lazy { + DefaultKotlinCompilationOutput( + target.project, + Callable { target.project.buildDir.resolve("processedResources/${target.targetName}/$name") }) + } + + open fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + fun AbstractKotlinCompile<*>.configureAction() { + source(sourceSet.kotlin) + sourceFilesExtensions(sourceSet.customSourceFilesExtensions) + if (addAsCommonSources) { + commonSourceSet += sourceSet.kotlin + } + } + + // Note! Invocation of withType-all results in preliminary task instantiation. + // After fix of this issue the following code should be uncommented: +// if (useLazyTaskConfiguration) { +// (target.project.tasks.named(compileKotlinTaskName) as TaskProvider>).configure { +// it.configureAction() +// } +// } + + target.project.tasks + // To configure a task that may have not yet been created at this point, use 'withType-matching-all`: + .withType(AbstractKotlinCompile::class.java) + .matching { it.name == compileKotlinTaskName } + .all { compileKotlinTask -> + compileKotlinTask.configureAction() + } + } + + override fun source(sourceSet: KotlinSourceSet) { + if (kotlinSourceSets.add(sourceSet)) { + //TODO possibly issue with forced instantiation + target.project.whenEvaluated { + sourceSet.getSourceSetHierarchy().forEach { sourceSet -> + val isCommonSource = + CompilationSourceSetUtil.sourceSetsInMultipleCompilations(project)?.contains(sourceSet) ?: false + + addSourcesToCompileTask(sourceSet, addAsCommonSources = isCommonSource) + + // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like + // old Java & Android projects: + addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) + addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) + addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) + + if (this@AbstractKotlinCompilation is KotlinCompilationToRunnableFiles<*>) { + addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false) + } + + if (sourceSet.name != defaultSourceSetName) { + kotlinExtension.sourceSets.findByName(defaultSourceSetName)?.let { defaultSourceSet -> + // Temporary solution for checking consistency across source sets participating in a compilation that may + // not be interconnected with the dependsOn relation: check the settings as if the default source set of + // the compilation depends on the one added to the compilation: + defaultSourceSetLanguageSettingsChecker.runAllChecks( + defaultSourceSet, + sourceSet + ) + } + } + } + } + } + } + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileClasspath" + ) + + override val compileKotlinTaskName: String + get() = lowerCamelCaseName( + "compile", + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "Kotlin", + target.targetName + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "classes") + + override lateinit var compileDependencyFiles: FileCollection + + override val apiConfigurationName: String + get() = disambiguateName("api") + + override val implementationConfigurationName: String + get() = disambiguateName("implementation") + + override val compileOnlyConfigurationName: String + get() = disambiguateName("compileOnly") + + override val runtimeOnlyConfigurationName: String + get() = disambiguateName("runtimeOnly") + + override fun dependencies(configure: KotlinDependencyHandler.() -> Unit): Unit = + DefaultKotlinDependencyHandler(this, target.project).run(configure) + + override fun dependencies(configureClosure: Closure) = + dependencies f@{ ConfigureUtil.configure(configureClosure, this@f) } + + override fun toString(): String = "compilation '$compilationName' ($target)" +} + +abstract class AbstractKotlinCompilationToRunnableFiles( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationToRunnableFiles { + override val runtimeDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "runtimeClasspath" + ) + + override lateinit var runtimeDependencyFiles: FileCollection +} + +internal fun KotlinCompilation<*>.disambiguateName(simpleName: String): String { + return lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + simpleName + ) +} + +open class KotlinJvmCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile +} + +class KotlinWithJavaCompilation( + target: KotlinWithJavaTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + lateinit var javaSourceSet: SourceSet + + override val output: KotlinCompilationOutput by lazy { KotlinWithJavaCompilationOutput(this) } + + override val processResourcesTaskName: String + get() = javaSourceSet.processResourcesTaskName + + override var runtimeDependencyFiles: FileCollection + get() = javaSourceSet.runtimeClasspath + set(value) { + javaSourceSet.runtimeClasspath = value + } + + override val runtimeDependencyConfigurationName: String + get() = javaSourceSet.runtimeClasspathConfigurationName + + override val compileDependencyConfigurationName: String + get() = javaSourceSet.compileClasspathConfigurationName + + override val runtimeOnlyConfigurationName: String + get() = javaSourceSet.runtimeOnlyConfigurationName + + override val implementationConfigurationName: String + get() = javaSourceSet.implementationConfigurationName + + override val apiConfigurationName: String + get() = javaSourceSet.apiConfigurationName + + override val compileOnlyConfigurationName: String + get() = javaSourceSet.compileOnlyConfigurationName + + override val compileAllTaskName: String + get() = javaSourceSet.classesTaskName + + override var compileDependencyFiles: FileCollection + get() = javaSourceSet.compileClasspath + set(value) { + javaSourceSet.compileClasspath = value + } + + fun source(javaSourceSet: SourceSet) { + with(target.project) { + afterEvaluate { + (tasks.getByName(compileKotlinTaskName) as AbstractKotlinCompile<*>).source(javaSourceSet.java) + } + } + } +} + +class KotlinJvmAndroidCompilation( + target: KotlinAndroidTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name) { + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + override val relatedConfigurationNames: List + get() = super.relatedConfigurationNames + listOf("${name}ApiElements", "${name}RuntimeElements") +} + +class KotlinJsCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: Kotlin2JsCompile + get() = super.compileKotlinTask as Kotlin2JsCompile +} + +class KotlinCommonCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name) { + override val compileKotlinTask: KotlinCompileCommon + get() = super.compileKotlinTask as KotlinCompileCommon + + // TODO once we properly compile metadata for each source set, the default source sets will likely become just the source sets + // which are transformed to metadata + private val commonSourceSetName = when (compilationName) { + KotlinCompilation.MAIN_COMPILATION_NAME -> KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME + else -> error("Custom metadata compilations are not supported yet") + } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(commonSourceSetName) +} + +class KotlinNativeCompilation( + override val target: KotlinNativeTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationWithResources { + + override val kotlinOptions: KotlinCommonOptions + get() = compileKotlinTask.kotlinOptions + + override val compileKotlinTask: KotlinNativeCompile + get() = super.compileKotlinTask as KotlinNativeCompile + + private val project: Project + get() = target.project + + // A collection containing all source sets used by this compilation + // (taking into account dependencies between source sets). Used by both compilation + // and linking tasks. Unlike kotlinSourceSets, includes dependency source sets. + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val allSources: MutableSet = mutableSetOf() + + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val commonSources: MutableSet = mutableSetOf() + + var isTestCompilation = false + + var friendCompilationName: String? = null + + internal val friendCompilation: KotlinNativeCompilation? + get() = friendCompilationName?.let { + target.compilations.getByName(it) + } + + // Used only to support the old APIs. TODO: Remove when the old APIs are removed. + internal val binaries = mutableMapOf, NativeBinary>() + + // Native-specific DSL. + var extraOpts = mutableListOf() + + fun extraOpts(vararg values: Any) = extraOpts(values.toList()) + fun extraOpts(values: List) { + extraOpts.addAll(values.map { it.toString() }) + } + + var buildTypes = mutableListOf() + var outputKinds = mutableListOf() + + fun outputKind(kind: NativeOutputKind) = outputKinds.add(kind) + + fun outputKinds(vararg kinds: NativeOutputKind) { + outputKinds = kinds.toMutableList() + } + + fun outputKinds(vararg kinds: String) { + outputKinds = kinds.map { NativeOutputKind.valueOf(it.toUpperCase()) }.toMutableList() + } + + fun outputKinds(kinds: List) { + outputKinds = kinds.map { + when (it) { + is NativeOutputKind -> it + is String -> NativeOutputKind.valueOf(it.toUpperCase()) + else -> error("Cannot use $it as an output kind") + } + }.toMutableList() + } + + var entryPoint: String? = null + fun entryPoint(value: String) { + entryPoint = value + } + + // Interop DSL. + val cinterops = project.container(DefaultCInteropSettings::class.java) { cinteropName -> + DefaultCInteropSettings(project, cinteropName, this) + } + + var linkerOpts = mutableListOf() + + fun cinterops(action: NamedDomainObjectContainer.() -> Unit) = cinterops.action() + fun cinterops(action: Closure) = cinterops(ConfigureUtil.configureUsing(action)) + fun cinterops(action: Action>) = action.execute(cinterops) + + fun linkerOpts(vararg values: String) = linkerOpts(values.toList()) + fun linkerOpts(values: List) { + linkerOpts.addAll(values) + } + + // Task accessors. + fun findLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink? = binaries[kind to buildType]?.linkTask + + fun getLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink = + findLinkTask(kind, buildType) + ?: throw IllegalArgumentException("Cannot find a link task for the binary kind '$kind' and the build type '$buildType'") + + fun findLinkTask(kind: String, buildType: String) = + findLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getLinkTask(kind: String, buildType: String) = + getLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun findBinary(kind: NativeOutputKind, buildType: NativeBuildType): File? = findLinkTask(kind, buildType)?.outputFile?.get() + + fun getBinary(kind: NativeOutputKind, buildType: NativeBuildType): File = getLinkTask(kind, buildType).outputFile.get() + + fun findBinary(kind: String, buildType: String) = + findBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getBinary(kind: String, buildType: String) = + getBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + // Naming + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + fun linkTaskName(kind: NativeOutputKind, buildType: NativeBuildType): String = + lowerCamelCaseName( + "link", + KotlinNativeBinaryContainer.generateBinaryName(compilationName, buildType, kind.taskNameClassifier), + target.targetName + ) + + fun linkTaskName(kind: String, buildType: String) = + linkTaskName(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileKlibraries" + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "klibrary") + + val binariesTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "binaries") + + override fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + allSources.add(sourceSet.kotlin) + if (addAsCommonSources) { + commonSources.add(sourceSet.kotlin) + } + } +} + +private object CompilationSourceSetUtil { + // Cache the results per project + private val projectSourceSetsInMultipleCompilationsCache = WeakHashMap>() + + fun sourceSetsInMultipleCompilations(project: Project) = + projectSourceSetsInMultipleCompilationsCache.computeIfAbsent(project) { _ -> + check(project.state.executed) { "Should only be computed after the project is evaluated" } + + val compilations = (project.kotlinExtension as? KotlinMultiplatformExtension)?.targets?.flatMap { it.compilations } + ?: return@computeIfAbsent null + + compilations + .flatMap { compilation -> compilation.allKotlinSourceSets.map { sourceSet -> compilation to sourceSet } } + .groupingBy { (_, sourceSet) -> sourceSet } + .eachCount() + .filterValues { it > 1 } + .keys + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmCompilationFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmCompilationFactory.kt new file mode 100644 index 00000000000..e63b6e6573d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmCompilationFactory.kt @@ -0,0 +1,93 @@ +/* + * 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.NamedDomainObjectFactory +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation + +interface KotlinCompilationFactory> : NamedDomainObjectFactory { + val itemClass: Class +} + +class KotlinCommonCompilationFactory( + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinCommonCompilation::class.java + + override fun create(name: String): KotlinCommonCompilation = + KotlinCommonCompilation(target, name) +} + +class KotlinJvmCompilationFactory( + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinJvmCompilation::class.java + + override fun create(name: String): KotlinJvmCompilation = + KotlinJvmCompilation(target, name) +} + +class KotlinWithJavaCompilationFactory( + val project: Project, + val target: KotlinWithJavaTarget +) : KotlinCompilationFactory> { + + override val itemClass: Class> + @Suppress("UNCHECKED_CAST") + get() = KotlinWithJavaCompilation::class.java as Class> + + override fun create(name: String): KotlinWithJavaCompilation { + val result = KotlinWithJavaCompilation(target, name) + return result + } +} + +class KotlinJvmAndroidCompilationFactory( + val project: Project, + val target: KotlinAndroidTarget +) : KotlinCompilationFactory { + + override val itemClass: Class + get() = KotlinJvmAndroidCompilation::class.java + + override fun create(name: String): KotlinJvmAndroidCompilation = + KotlinJvmAndroidCompilation(target, name) +} + +class KotlinJsCompilationFactory( + val project: Project, + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinJsCompilation::class.java + + override fun create(name: String): KotlinJsCompilation = + KotlinJsCompilation(target, name) +} + +class KotlinNativeCompilationFactory( + val project: Project, + val target: KotlinNativeTarget +) : KotlinCompilationFactory { + + override val itemClass: Class + get() = KotlinNativeCompilation::class.java + + override fun create(name: String): KotlinNativeCompilation = + KotlinNativeCompilation(target, name).apply { + if (name == KotlinCompilation.TEST_COMPILATION_NAME) { + friendCompilationName = KotlinCompilation.MAIN_COMPILATION_NAME + isTestCompilation = true + } + buildTypes = mutableListOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE) + } + +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmTargetPreset.kt new file mode 100644 index 00000000000..921736ad861 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmTargetPreset.kt @@ -0,0 +1,330 @@ +/* + * 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.Dependency +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.plugins.JavaPlugin +import org.gradle.internal.reflect.Instantiator +import org.jetbrains.kotlin.compilerRunner.KotlinNativeProjectProperty +import org.jetbrains.kotlin.compilerRunner.hasProperty +import org.jetbrains.kotlin.compilerRunner.konanHome +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.applyLanguageSettingsToKotlinTask +import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider +import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader +import org.jetbrains.kotlin.konan.KonanVersion +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget +import java.io.File +import java.util.* + +abstract class KotlinOnlyTargetPreset>( + protected val project: Project, + private val instantiator: Instantiator, + private val fileResolver: FileResolver, + protected val kotlinPluginVersion: String +) : KotlinTargetPreset> { + + protected open fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = true, createTestCompilation = true) + + override fun createTarget(name: String): KotlinOnlyTarget { + val result = KotlinOnlyTarget(project, platformType).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinOnlyTargetPreset + + val compilationFactory = createCompilationFactory(this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + createKotlinTargetConfigurator().configureTarget(result) + + result.compilations.all { compilation -> + buildCompilationProcessor(compilation).run() + if (compilation.name == KotlinCompilation.MAIN_COMPILATION_NAME) { + sourcesJarTask(compilation, result.targetName, result.targetName.toLowerCase()) + } + } + + return result + } + + protected abstract fun createCompilationFactory(forTarget: KotlinOnlyTarget): KotlinCompilationFactory + protected abstract val platformType: KotlinPlatformType + internal abstract fun buildCompilationProcessor(compilation: T): KotlinSourceSetProcessor<*> +} + +class KotlinMetadataTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return KotlinCommonSourceSetProcessor(project, compilation, tasksProvider, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "metadata" + } + + override fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = false, createTestCompilation = false) + + override fun createTarget(name: String): KotlinOnlyTarget = + super.createTarget(name).apply { + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + val commonMainSourceSet = project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + + mainCompilation.source(commonMainSourceSet) + + project.afterEvaluate { + // Since there's no default source set, apply language settings from commonMain: + val compileKotlinMetadata = project.tasks.getByName(mainCompilation.compileKotlinTaskName) as KotlinCompile<*> + applyLanguageSettingsToKotlinTask(commonMainSourceSet.languageSettings, compileKotlinMetadata) + } + } +} + +class KotlinJvmTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JvmSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "jvm" + } +} + +class KotlinJsTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JsSourceSetProcessor(project, tasksProvider, 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 + preset = this@KotlinAndroidTargetPreset + } + + KotlinAndroidPlugin.applyToTarget(kotlinPluginVersion, result) + 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 + preset = this@KotlinJvmWithJavaTargetPreset + } + + AbstractKotlinPlugin.configureTarget(target) { compilation -> + Kotlin2JvmSourceSetProcessor(project, KotlinTasksProvider(name), compilation, kotlinPluginVersion) + } + + target.compilations.getByName("test").run { + val main = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + + compileDependencyFiles = project.files( + main.output.allOutputs, + project.configurations.maybeCreate(compileDependencyConfigurationName) + ) + runtimeDependencyFiles = project.files( + output.allOutputs, + main.output.allOutputs, + project.configurations.maybeCreate(runtimeDependencyConfigurationName) + ) + } + + return target + } + + companion object { + const val PRESET_NAME = "jvmWithJava" + } +} + +class KotlinNativeTargetPreset( + private val name: String, + val project: Project, + val konanTarget: KonanTarget, + private val kotlinPluginVersion: String +) : KotlinTargetPreset { + + init { + // This is required to obtain Kotlin/Native home in CLion plugin: + setupNativeHomePrivateProperty() + } + + override fun getName(): String = name + + private fun setupNativeHomePrivateProperty() = with(project) { + if (!hasProperty(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY)) + extensions.extraProperties.set(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY, konanHome) + } + + private fun setupNativeCompiler() = with(project) { + if (!hasProperty(KotlinNativeProjectProperty.KONAN_HOME_OVERRIDE)) { + NativeCompilerDownloader(this).downloadIfNeeded() + logger.info("Kotlin/Native distribution: $konanHome") + } else { + logger.info("User-provided Kotlin/Native distribution: $konanHome") + } + } + + // We declare default K/N dependencies as files to avoid searching them in remote repos (see KT-28128). + private fun defaultLibs(target: KonanTarget? = null): List = with(project) { + + val relPath = if (target != null) "platform/${target.name}" else "common" + + file("$konanHome/klib/$relPath") + .listFiles { file -> file.isDirectory } + ?.sortedBy { dir -> dir.name.toLowerCase() } + ?.map { dir -> dependencies.create(files(dir)) } ?: emptyList() + } + + override fun createTarget(name: String): KotlinNativeTarget { + setupNativeCompiler() + + val result = KotlinNativeTarget(project, konanTarget).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinNativeTargetPreset + + val compilationFactory = KotlinNativeCompilationFactory(project, this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + KotlinNativeTargetConfigurator(kotlinPluginVersion).configureTarget(result) + + // Allow IDE to resolve the libraries provided by the compiler by adding them into dependencies. + result.compilations.all { compilation -> + val target = compilation.target.konanTarget + // First, put common libs: + defaultLibs().forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + // Then, platform-specific libs: + defaultLibs(target).forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + } + + if (!konanTarget.enabledOnCurrentHost) { + with(HostManager()) { + val supportedHosts = enabledTargetsByHost.filterValues { konanTarget in it }.keys + val supportedHostsString = + if (supportedHosts.size == 1) + "a ${supportedHosts.single()} host" else + "one of the host platforms: ${supportedHosts.joinToString(", ")}" + project.logger.warn( + "Target '$name' for platform ${konanTarget} is ignored during build on this ${HostManager.host} machine. " + + "You can build it with $supportedHostsString." + ) + } + } + + return result + } + + companion object { + private const val KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY = "konanHome" + } +} + +internal val KonanTarget.isCurrentHost: Boolean + get() = this == HostManager.host + +internal val KonanTarget.enabledOnCurrentHost + get() = HostManager().isEnabled(this) + +internal val KotlinNativeCompilation.isMainCompilation: Boolean + get() = name == KotlinCompilation.MAIN_COMPILATION_NAME diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmWithJavaTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmWithJavaTargetPreset.kt new file mode 100644 index 00000000000..921736ad861 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinJvmWithJavaTargetPreset.kt @@ -0,0 +1,330 @@ +/* + * 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.Dependency +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.plugins.JavaPlugin +import org.gradle.internal.reflect.Instantiator +import org.jetbrains.kotlin.compilerRunner.KotlinNativeProjectProperty +import org.jetbrains.kotlin.compilerRunner.hasProperty +import org.jetbrains.kotlin.compilerRunner.konanHome +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.applyLanguageSettingsToKotlinTask +import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider +import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader +import org.jetbrains.kotlin.konan.KonanVersion +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget +import java.io.File +import java.util.* + +abstract class KotlinOnlyTargetPreset>( + protected val project: Project, + private val instantiator: Instantiator, + private val fileResolver: FileResolver, + protected val kotlinPluginVersion: String +) : KotlinTargetPreset> { + + protected open fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = true, createTestCompilation = true) + + override fun createTarget(name: String): KotlinOnlyTarget { + val result = KotlinOnlyTarget(project, platformType).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinOnlyTargetPreset + + val compilationFactory = createCompilationFactory(this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + createKotlinTargetConfigurator().configureTarget(result) + + result.compilations.all { compilation -> + buildCompilationProcessor(compilation).run() + if (compilation.name == KotlinCompilation.MAIN_COMPILATION_NAME) { + sourcesJarTask(compilation, result.targetName, result.targetName.toLowerCase()) + } + } + + return result + } + + protected abstract fun createCompilationFactory(forTarget: KotlinOnlyTarget): KotlinCompilationFactory + protected abstract val platformType: KotlinPlatformType + internal abstract fun buildCompilationProcessor(compilation: T): KotlinSourceSetProcessor<*> +} + +class KotlinMetadataTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return KotlinCommonSourceSetProcessor(project, compilation, tasksProvider, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "metadata" + } + + override fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = false, createTestCompilation = false) + + override fun createTarget(name: String): KotlinOnlyTarget = + super.createTarget(name).apply { + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + val commonMainSourceSet = project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + + mainCompilation.source(commonMainSourceSet) + + project.afterEvaluate { + // Since there's no default source set, apply language settings from commonMain: + val compileKotlinMetadata = project.tasks.getByName(mainCompilation.compileKotlinTaskName) as KotlinCompile<*> + applyLanguageSettingsToKotlinTask(commonMainSourceSet.languageSettings, compileKotlinMetadata) + } + } +} + +class KotlinJvmTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JvmSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "jvm" + } +} + +class KotlinJsTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JsSourceSetProcessor(project, tasksProvider, 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 + preset = this@KotlinAndroidTargetPreset + } + + KotlinAndroidPlugin.applyToTarget(kotlinPluginVersion, result) + 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 + preset = this@KotlinJvmWithJavaTargetPreset + } + + AbstractKotlinPlugin.configureTarget(target) { compilation -> + Kotlin2JvmSourceSetProcessor(project, KotlinTasksProvider(name), compilation, kotlinPluginVersion) + } + + target.compilations.getByName("test").run { + val main = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + + compileDependencyFiles = project.files( + main.output.allOutputs, + project.configurations.maybeCreate(compileDependencyConfigurationName) + ) + runtimeDependencyFiles = project.files( + output.allOutputs, + main.output.allOutputs, + project.configurations.maybeCreate(runtimeDependencyConfigurationName) + ) + } + + return target + } + + companion object { + const val PRESET_NAME = "jvmWithJava" + } +} + +class KotlinNativeTargetPreset( + private val name: String, + val project: Project, + val konanTarget: KonanTarget, + private val kotlinPluginVersion: String +) : KotlinTargetPreset { + + init { + // This is required to obtain Kotlin/Native home in CLion plugin: + setupNativeHomePrivateProperty() + } + + override fun getName(): String = name + + private fun setupNativeHomePrivateProperty() = with(project) { + if (!hasProperty(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY)) + extensions.extraProperties.set(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY, konanHome) + } + + private fun setupNativeCompiler() = with(project) { + if (!hasProperty(KotlinNativeProjectProperty.KONAN_HOME_OVERRIDE)) { + NativeCompilerDownloader(this).downloadIfNeeded() + logger.info("Kotlin/Native distribution: $konanHome") + } else { + logger.info("User-provided Kotlin/Native distribution: $konanHome") + } + } + + // We declare default K/N dependencies as files to avoid searching them in remote repos (see KT-28128). + private fun defaultLibs(target: KonanTarget? = null): List = with(project) { + + val relPath = if (target != null) "platform/${target.name}" else "common" + + file("$konanHome/klib/$relPath") + .listFiles { file -> file.isDirectory } + ?.sortedBy { dir -> dir.name.toLowerCase() } + ?.map { dir -> dependencies.create(files(dir)) } ?: emptyList() + } + + override fun createTarget(name: String): KotlinNativeTarget { + setupNativeCompiler() + + val result = KotlinNativeTarget(project, konanTarget).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinNativeTargetPreset + + val compilationFactory = KotlinNativeCompilationFactory(project, this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + KotlinNativeTargetConfigurator(kotlinPluginVersion).configureTarget(result) + + // Allow IDE to resolve the libraries provided by the compiler by adding them into dependencies. + result.compilations.all { compilation -> + val target = compilation.target.konanTarget + // First, put common libs: + defaultLibs().forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + // Then, platform-specific libs: + defaultLibs(target).forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + } + + if (!konanTarget.enabledOnCurrentHost) { + with(HostManager()) { + val supportedHosts = enabledTargetsByHost.filterValues { konanTarget in it }.keys + val supportedHostsString = + if (supportedHosts.size == 1) + "a ${supportedHosts.single()} host" else + "one of the host platforms: ${supportedHosts.joinToString(", ")}" + project.logger.warn( + "Target '$name' for platform ${konanTarget} is ignored during build on this ${HostManager.host} machine. " + + "You can build it with $supportedHostsString." + ) + } + } + + return result + } + + companion object { + private const val KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY = "konanHome" + } +} + +internal val KonanTarget.isCurrentHost: Boolean + get() = this == HostManager.host + +internal val KonanTarget.enabledOnCurrentHost + get() = HostManager().isEnabled(this) + +internal val KotlinNativeCompilation.isMainCompilation: Boolean + get() = name == KotlinCompilation.MAIN_COMPILATION_NAME diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMetadataTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMetadataTargetPreset.kt new file mode 100644 index 00000000000..921736ad861 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMetadataTargetPreset.kt @@ -0,0 +1,330 @@ +/* + * 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.Dependency +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.plugins.JavaPlugin +import org.gradle.internal.reflect.Instantiator +import org.jetbrains.kotlin.compilerRunner.KotlinNativeProjectProperty +import org.jetbrains.kotlin.compilerRunner.hasProperty +import org.jetbrains.kotlin.compilerRunner.konanHome +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.applyLanguageSettingsToKotlinTask +import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider +import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader +import org.jetbrains.kotlin.konan.KonanVersion +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget +import java.io.File +import java.util.* + +abstract class KotlinOnlyTargetPreset>( + protected val project: Project, + private val instantiator: Instantiator, + private val fileResolver: FileResolver, + protected val kotlinPluginVersion: String +) : KotlinTargetPreset> { + + protected open fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = true, createTestCompilation = true) + + override fun createTarget(name: String): KotlinOnlyTarget { + val result = KotlinOnlyTarget(project, platformType).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinOnlyTargetPreset + + val compilationFactory = createCompilationFactory(this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + createKotlinTargetConfigurator().configureTarget(result) + + result.compilations.all { compilation -> + buildCompilationProcessor(compilation).run() + if (compilation.name == KotlinCompilation.MAIN_COMPILATION_NAME) { + sourcesJarTask(compilation, result.targetName, result.targetName.toLowerCase()) + } + } + + return result + } + + protected abstract fun createCompilationFactory(forTarget: KotlinOnlyTarget): KotlinCompilationFactory + protected abstract val platformType: KotlinPlatformType + internal abstract fun buildCompilationProcessor(compilation: T): KotlinSourceSetProcessor<*> +} + +class KotlinMetadataTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return KotlinCommonSourceSetProcessor(project, compilation, tasksProvider, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "metadata" + } + + override fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = false, createTestCompilation = false) + + override fun createTarget(name: String): KotlinOnlyTarget = + super.createTarget(name).apply { + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + val commonMainSourceSet = project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + + mainCompilation.source(commonMainSourceSet) + + project.afterEvaluate { + // Since there's no default source set, apply language settings from commonMain: + val compileKotlinMetadata = project.tasks.getByName(mainCompilation.compileKotlinTaskName) as KotlinCompile<*> + applyLanguageSettingsToKotlinTask(commonMainSourceSet.languageSettings, compileKotlinMetadata) + } + } +} + +class KotlinJvmTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JvmSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "jvm" + } +} + +class KotlinJsTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JsSourceSetProcessor(project, tasksProvider, 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 + preset = this@KotlinAndroidTargetPreset + } + + KotlinAndroidPlugin.applyToTarget(kotlinPluginVersion, result) + 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 + preset = this@KotlinJvmWithJavaTargetPreset + } + + AbstractKotlinPlugin.configureTarget(target) { compilation -> + Kotlin2JvmSourceSetProcessor(project, KotlinTasksProvider(name), compilation, kotlinPluginVersion) + } + + target.compilations.getByName("test").run { + val main = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + + compileDependencyFiles = project.files( + main.output.allOutputs, + project.configurations.maybeCreate(compileDependencyConfigurationName) + ) + runtimeDependencyFiles = project.files( + output.allOutputs, + main.output.allOutputs, + project.configurations.maybeCreate(runtimeDependencyConfigurationName) + ) + } + + return target + } + + companion object { + const val PRESET_NAME = "jvmWithJava" + } +} + +class KotlinNativeTargetPreset( + private val name: String, + val project: Project, + val konanTarget: KonanTarget, + private val kotlinPluginVersion: String +) : KotlinTargetPreset { + + init { + // This is required to obtain Kotlin/Native home in CLion plugin: + setupNativeHomePrivateProperty() + } + + override fun getName(): String = name + + private fun setupNativeHomePrivateProperty() = with(project) { + if (!hasProperty(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY)) + extensions.extraProperties.set(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY, konanHome) + } + + private fun setupNativeCompiler() = with(project) { + if (!hasProperty(KotlinNativeProjectProperty.KONAN_HOME_OVERRIDE)) { + NativeCompilerDownloader(this).downloadIfNeeded() + logger.info("Kotlin/Native distribution: $konanHome") + } else { + logger.info("User-provided Kotlin/Native distribution: $konanHome") + } + } + + // We declare default K/N dependencies as files to avoid searching them in remote repos (see KT-28128). + private fun defaultLibs(target: KonanTarget? = null): List = with(project) { + + val relPath = if (target != null) "platform/${target.name}" else "common" + + file("$konanHome/klib/$relPath") + .listFiles { file -> file.isDirectory } + ?.sortedBy { dir -> dir.name.toLowerCase() } + ?.map { dir -> dependencies.create(files(dir)) } ?: emptyList() + } + + override fun createTarget(name: String): KotlinNativeTarget { + setupNativeCompiler() + + val result = KotlinNativeTarget(project, konanTarget).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinNativeTargetPreset + + val compilationFactory = KotlinNativeCompilationFactory(project, this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + KotlinNativeTargetConfigurator(kotlinPluginVersion).configureTarget(result) + + // Allow IDE to resolve the libraries provided by the compiler by adding them into dependencies. + result.compilations.all { compilation -> + val target = compilation.target.konanTarget + // First, put common libs: + defaultLibs().forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + // Then, platform-specific libs: + defaultLibs(target).forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + } + + if (!konanTarget.enabledOnCurrentHost) { + with(HostManager()) { + val supportedHosts = enabledTargetsByHost.filterValues { konanTarget in it }.keys + val supportedHostsString = + if (supportedHosts.size == 1) + "a ${supportedHosts.single()} host" else + "one of the host platforms: ${supportedHosts.joinToString(", ")}" + project.logger.warn( + "Target '$name' for platform ${konanTarget} is ignored during build on this ${HostManager.host} machine. " + + "You can build it with $supportedHostsString." + ) + } + } + + return result + } + + companion object { + private const val KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY = "konanHome" + } +} + +internal val KonanTarget.isCurrentHost: Boolean + get() = this == HostManager.host + +internal val KonanTarget.enabledOnCurrentHost + get() = HostManager().isEnabled(this) + +internal val KotlinNativeCompilation.isMainCompilation: Boolean + get() = name == KotlinCompilation.MAIN_COMPILATION_NAME diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeCompilation.kt new file mode 100644 index 00000000000..1284fe446f3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeCompilation.kt @@ -0,0 +1,471 @@ + /* + * 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.Action +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.file.FileCollection +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.tasks.SourceSet +import org.gradle.util.ConfigureUtil +import org.jetbrains.kotlin.gradle.dsl.* +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker +import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy +import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import java.util.* +import java.util.concurrent.Callable + +internal fun KotlinCompilation<*>.composeName(prefix: String? = null, suffix: String? = null): String { + val compilationNamePart = compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME } + val targetNamePart = target.disambiguationClassifier + + return lowerCamelCaseName(prefix, targetNamePart, compilationNamePart, suffix) +} + +internal val KotlinCompilation<*>.defaultSourceSetName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName) + +abstract class AbstractKotlinCompilation( + target: KotlinTarget, + override val compilationName: String +) : KotlinCompilation, HasKotlinDependencies { + + override val kotlinOptions: T + get() = compileKotlinTask.kotlinOptions + + override fun kotlinOptions(configure: T.() -> Unit) = + configure(kotlinOptions) + + @Suppress("UNCHECKED_CAST") + override val compileKotlinTask: KotlinCompile + get() = (target.project.tasks.getByName(compileKotlinTaskName) as KotlinCompile) + + // Don't declare this property in the constructor to avoid NPE + // when an overriding property of a subclass is accessed instead. + override val target: KotlinTarget = target + + private val attributeContainer = HierarchyAttributeContainer(target.attributes) + + override fun getAttributes(): AttributeContainer = attributeContainer + + override val kotlinSourceSets: MutableSet = mutableSetOf() + + override val allKotlinSourceSets: Set + get() = kotlinSourceSets.flatMapTo(mutableSetOf()) { it.getSourceSetHierarchy() } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(defaultSourceSetName) + + override fun defaultSourceSet(configure: KotlinSourceSet.() -> Unit) = + configure(defaultSourceSet) + + override val output: KotlinCompilationOutput by lazy { + DefaultKotlinCompilationOutput( + target.project, + Callable { target.project.buildDir.resolve("processedResources/${target.targetName}/$name") }) + } + + open fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + fun AbstractKotlinCompile<*>.configureAction() { + source(sourceSet.kotlin) + sourceFilesExtensions(sourceSet.customSourceFilesExtensions) + if (addAsCommonSources) { + commonSourceSet += sourceSet.kotlin + } + } + + // Note! Invocation of withType-all results in preliminary task instantiation. + // After fix of this issue the following code should be uncommented: +// if (useLazyTaskConfiguration) { +// (target.project.tasks.named(compileKotlinTaskName) as TaskProvider>).configure { +// it.configureAction() +// } +// } + + target.project.tasks + // To configure a task that may have not yet been created at this point, use 'withType-matching-all`: + .withType(AbstractKotlinCompile::class.java) + .matching { it.name == compileKotlinTaskName } + .all { compileKotlinTask -> + compileKotlinTask.configureAction() + } + } + + override fun source(sourceSet: KotlinSourceSet) { + if (kotlinSourceSets.add(sourceSet)) { + //TODO possibly issue with forced instantiation + target.project.whenEvaluated { + sourceSet.getSourceSetHierarchy().forEach { sourceSet -> + val isCommonSource = + CompilationSourceSetUtil.sourceSetsInMultipleCompilations(project)?.contains(sourceSet) ?: false + + addSourcesToCompileTask(sourceSet, addAsCommonSources = isCommonSource) + + // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like + // old Java & Android projects: + addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) + addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) + addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) + + if (this@AbstractKotlinCompilation is KotlinCompilationToRunnableFiles<*>) { + addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false) + } + + if (sourceSet.name != defaultSourceSetName) { + kotlinExtension.sourceSets.findByName(defaultSourceSetName)?.let { defaultSourceSet -> + // Temporary solution for checking consistency across source sets participating in a compilation that may + // not be interconnected with the dependsOn relation: check the settings as if the default source set of + // the compilation depends on the one added to the compilation: + defaultSourceSetLanguageSettingsChecker.runAllChecks( + defaultSourceSet, + sourceSet + ) + } + } + } + } + } + } + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileClasspath" + ) + + override val compileKotlinTaskName: String + get() = lowerCamelCaseName( + "compile", + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "Kotlin", + target.targetName + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "classes") + + override lateinit var compileDependencyFiles: FileCollection + + override val apiConfigurationName: String + get() = disambiguateName("api") + + override val implementationConfigurationName: String + get() = disambiguateName("implementation") + + override val compileOnlyConfigurationName: String + get() = disambiguateName("compileOnly") + + override val runtimeOnlyConfigurationName: String + get() = disambiguateName("runtimeOnly") + + override fun dependencies(configure: KotlinDependencyHandler.() -> Unit): Unit = + DefaultKotlinDependencyHandler(this, target.project).run(configure) + + override fun dependencies(configureClosure: Closure) = + dependencies f@{ ConfigureUtil.configure(configureClosure, this@f) } + + override fun toString(): String = "compilation '$compilationName' ($target)" +} + +abstract class AbstractKotlinCompilationToRunnableFiles( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationToRunnableFiles { + override val runtimeDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "runtimeClasspath" + ) + + override lateinit var runtimeDependencyFiles: FileCollection +} + +internal fun KotlinCompilation<*>.disambiguateName(simpleName: String): String { + return lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + simpleName + ) +} + +open class KotlinJvmCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile +} + +class KotlinWithJavaCompilation( + target: KotlinWithJavaTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + lateinit var javaSourceSet: SourceSet + + override val output: KotlinCompilationOutput by lazy { KotlinWithJavaCompilationOutput(this) } + + override val processResourcesTaskName: String + get() = javaSourceSet.processResourcesTaskName + + override var runtimeDependencyFiles: FileCollection + get() = javaSourceSet.runtimeClasspath + set(value) { + javaSourceSet.runtimeClasspath = value + } + + override val runtimeDependencyConfigurationName: String + get() = javaSourceSet.runtimeClasspathConfigurationName + + override val compileDependencyConfigurationName: String + get() = javaSourceSet.compileClasspathConfigurationName + + override val runtimeOnlyConfigurationName: String + get() = javaSourceSet.runtimeOnlyConfigurationName + + override val implementationConfigurationName: String + get() = javaSourceSet.implementationConfigurationName + + override val apiConfigurationName: String + get() = javaSourceSet.apiConfigurationName + + override val compileOnlyConfigurationName: String + get() = javaSourceSet.compileOnlyConfigurationName + + override val compileAllTaskName: String + get() = javaSourceSet.classesTaskName + + override var compileDependencyFiles: FileCollection + get() = javaSourceSet.compileClasspath + set(value) { + javaSourceSet.compileClasspath = value + } + + fun source(javaSourceSet: SourceSet) { + with(target.project) { + afterEvaluate { + (tasks.getByName(compileKotlinTaskName) as AbstractKotlinCompile<*>).source(javaSourceSet.java) + } + } + } +} + +class KotlinJvmAndroidCompilation( + target: KotlinAndroidTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name) { + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + override val relatedConfigurationNames: List + get() = super.relatedConfigurationNames + listOf("${name}ApiElements", "${name}RuntimeElements") +} + +class KotlinJsCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: Kotlin2JsCompile + get() = super.compileKotlinTask as Kotlin2JsCompile +} + +class KotlinCommonCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name) { + override val compileKotlinTask: KotlinCompileCommon + get() = super.compileKotlinTask as KotlinCompileCommon + + // TODO once we properly compile metadata for each source set, the default source sets will likely become just the source sets + // which are transformed to metadata + private val commonSourceSetName = when (compilationName) { + KotlinCompilation.MAIN_COMPILATION_NAME -> KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME + else -> error("Custom metadata compilations are not supported yet") + } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(commonSourceSetName) +} + +class KotlinNativeCompilation( + override val target: KotlinNativeTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationWithResources { + + override val kotlinOptions: KotlinCommonOptions + get() = compileKotlinTask.kotlinOptions + + override val compileKotlinTask: KotlinNativeCompile + get() = super.compileKotlinTask as KotlinNativeCompile + + private val project: Project + get() = target.project + + // A collection containing all source sets used by this compilation + // (taking into account dependencies between source sets). Used by both compilation + // and linking tasks. Unlike kotlinSourceSets, includes dependency source sets. + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val allSources: MutableSet = mutableSetOf() + + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val commonSources: MutableSet = mutableSetOf() + + var isTestCompilation = false + + var friendCompilationName: String? = null + + internal val friendCompilation: KotlinNativeCompilation? + get() = friendCompilationName?.let { + target.compilations.getByName(it) + } + + // Used only to support the old APIs. TODO: Remove when the old APIs are removed. + internal val binaries = mutableMapOf, NativeBinary>() + + // Native-specific DSL. + var extraOpts = mutableListOf() + + fun extraOpts(vararg values: Any) = extraOpts(values.toList()) + fun extraOpts(values: List) { + extraOpts.addAll(values.map { it.toString() }) + } + + var buildTypes = mutableListOf() + var outputKinds = mutableListOf() + + fun outputKind(kind: NativeOutputKind) = outputKinds.add(kind) + + fun outputKinds(vararg kinds: NativeOutputKind) { + outputKinds = kinds.toMutableList() + } + + fun outputKinds(vararg kinds: String) { + outputKinds = kinds.map { NativeOutputKind.valueOf(it.toUpperCase()) }.toMutableList() + } + + fun outputKinds(kinds: List) { + outputKinds = kinds.map { + when (it) { + is NativeOutputKind -> it + is String -> NativeOutputKind.valueOf(it.toUpperCase()) + else -> error("Cannot use $it as an output kind") + } + }.toMutableList() + } + + var entryPoint: String? = null + fun entryPoint(value: String) { + entryPoint = value + } + + // Interop DSL. + val cinterops = project.container(DefaultCInteropSettings::class.java) { cinteropName -> + DefaultCInteropSettings(project, cinteropName, this) + } + + var linkerOpts = mutableListOf() + + fun cinterops(action: NamedDomainObjectContainer.() -> Unit) = cinterops.action() + fun cinterops(action: Closure) = cinterops(ConfigureUtil.configureUsing(action)) + fun cinterops(action: Action>) = action.execute(cinterops) + + fun linkerOpts(vararg values: String) = linkerOpts(values.toList()) + fun linkerOpts(values: List) { + linkerOpts.addAll(values) + } + + // Task accessors. + fun findLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink? = binaries[kind to buildType]?.linkTask + + fun getLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink = + findLinkTask(kind, buildType) + ?: throw IllegalArgumentException("Cannot find a link task for the binary kind '$kind' and the build type '$buildType'") + + fun findLinkTask(kind: String, buildType: String) = + findLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getLinkTask(kind: String, buildType: String) = + getLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun findBinary(kind: NativeOutputKind, buildType: NativeBuildType): File? = findLinkTask(kind, buildType)?.outputFile?.get() + + fun getBinary(kind: NativeOutputKind, buildType: NativeBuildType): File = getLinkTask(kind, buildType).outputFile.get() + + fun findBinary(kind: String, buildType: String) = + findBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getBinary(kind: String, buildType: String) = + getBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + // Naming + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + fun linkTaskName(kind: NativeOutputKind, buildType: NativeBuildType): String = + lowerCamelCaseName( + "link", + KotlinNativeBinaryContainer.generateBinaryName(compilationName, buildType, kind.taskNameClassifier), + target.targetName + ) + + fun linkTaskName(kind: String, buildType: String) = + linkTaskName(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileKlibraries" + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "klibrary") + + val binariesTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "binaries") + + override fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + allSources.add(sourceSet.kotlin) + if (addAsCommonSources) { + commonSources.add(sourceSet.kotlin) + } + } +} + +private object CompilationSourceSetUtil { + // Cache the results per project + private val projectSourceSetsInMultipleCompilationsCache = WeakHashMap>() + + fun sourceSetsInMultipleCompilations(project: Project) = + projectSourceSetsInMultipleCompilationsCache.computeIfAbsent(project) { _ -> + check(project.state.executed) { "Should only be computed after the project is evaluated" } + + val compilations = (project.kotlinExtension as? KotlinMultiplatformExtension)?.targets?.flatMap { it.compilations } + ?: return@computeIfAbsent null + + compilations + .flatMap { compilation -> compilation.allKotlinSourceSets.map { sourceSet -> compilation to sourceSet } } + .groupingBy { (_, sourceSet) -> sourceSet } + .eachCount() + .filterValues { it > 1 } + .keys + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeCompilationFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeCompilationFactory.kt new file mode 100644 index 00000000000..e63b6e6573d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeCompilationFactory.kt @@ -0,0 +1,93 @@ +/* + * 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.NamedDomainObjectFactory +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation + +interface KotlinCompilationFactory> : NamedDomainObjectFactory { + val itemClass: Class +} + +class KotlinCommonCompilationFactory( + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinCommonCompilation::class.java + + override fun create(name: String): KotlinCommonCompilation = + KotlinCommonCompilation(target, name) +} + +class KotlinJvmCompilationFactory( + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinJvmCompilation::class.java + + override fun create(name: String): KotlinJvmCompilation = + KotlinJvmCompilation(target, name) +} + +class KotlinWithJavaCompilationFactory( + val project: Project, + val target: KotlinWithJavaTarget +) : KotlinCompilationFactory> { + + override val itemClass: Class> + @Suppress("UNCHECKED_CAST") + get() = KotlinWithJavaCompilation::class.java as Class> + + override fun create(name: String): KotlinWithJavaCompilation { + val result = KotlinWithJavaCompilation(target, name) + return result + } +} + +class KotlinJvmAndroidCompilationFactory( + val project: Project, + val target: KotlinAndroidTarget +) : KotlinCompilationFactory { + + override val itemClass: Class + get() = KotlinJvmAndroidCompilation::class.java + + override fun create(name: String): KotlinJvmAndroidCompilation = + KotlinJvmAndroidCompilation(target, name) +} + +class KotlinJsCompilationFactory( + val project: Project, + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinJsCompilation::class.java + + override fun create(name: String): KotlinJsCompilation = + KotlinJsCompilation(target, name) +} + +class KotlinNativeCompilationFactory( + val project: Project, + val target: KotlinNativeTarget +) : KotlinCompilationFactory { + + override val itemClass: Class + get() = KotlinNativeCompilation::class.java + + override fun create(name: String): KotlinNativeCompilation = + KotlinNativeCompilation(target, name).apply { + if (name == KotlinCompilation.TEST_COMPILATION_NAME) { + friendCompilationName = KotlinCompilation.MAIN_COMPILATION_NAME + isTestCompilation = true + } + buildTypes = mutableListOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE) + } + +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeTarget.kt new file mode 100644 index 00000000000..f4bbb5d60f9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeTarget.kt @@ -0,0 +1,437 @@ +/* + * 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.* +import org.gradle.api.artifacts.ConfigurablePublishArtifact +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.PublishArtifact +import org.gradle.api.attributes.Attribute +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.attributes.Usage.JAVA_API +import org.gradle.api.attributes.Usage.JAVA_RUNTIME_JARS +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.util.ConfigureUtil +import org.gradle.util.WrapUtil +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.utils.dashSeparatedName +import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.konan.target.KonanTarget + +abstract class AbstractKotlinTarget( + final override val project: Project +) : KotlinTarget { + private val attributeContainer = HierarchyAttributeContainer(parent = null) + + override fun getAttributes(): AttributeContainer = attributeContainer + + override val defaultConfigurationName: String + get() = disambiguateName("default") + + override val apiElementsConfigurationName: String + get() = disambiguateName("apiElements") + + override val runtimeElementsConfigurationName: String + get() = disambiguateName("runtimeElements") + + override val artifactsTaskName: String + get() = disambiguateName("jar") + + override fun toString(): String = "target $name ($platformType)" + + override val publishable: Boolean + get() = true + + override val components: Set by lazy { + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + val usageContexts = createUsageContexts(mainCompilation) + setOf( + if (isGradleVersionAtLeast(4, 7)) { + val componentName = mainCompilation.target.name + createKotlinVariant(componentName, mainCompilation, usageContexts).apply { + sourcesArtifacts = setOf( + sourcesJarArtifact( + mainCompilation, componentName, + dashSeparatedName(target.name.toLowerCase(), componentName.toLowerCase()) + ) + ) + } + } else { + KotlinVariant(mainCompilation, usageContexts) + } + ).also { project.components.addAll(it) } + } + + protected fun createKotlinVariant( + componentName: String, + compilation: KotlinCompilation<*>, + usageContexts: Set + ): KotlinVariant { + + val kotlinExtension = project.kotlinExtension as? KotlinMultiplatformExtension + ?: return KotlinVariantWithCoordinates(compilation, usageContexts) + + if (targetName == KotlinMultiplatformPlugin.METADATA_TARGET_NAME) + return KotlinVariantWithCoordinates(compilation, usageContexts) + + val separateMetadataTarget = kotlinExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME) + + val result = if (kotlinExtension.isGradleMetadataAvailable) { + KotlinVariantWithMetadataVariant(compilation, usageContexts, separateMetadataTarget) + } else { + // we should only add the Kotlin metadata dependency if we publish no Gradle metadata related to Kotlin MPP; + // with metadata, such a dependency would get invalid, since a platform module should only depend on modules for that + // same platform, not Kotlin metadata modules + KotlinVariantWithMetadataDependency(compilation, usageContexts, separateMetadataTarget) + } + + result.componentName = componentName + return result + } + + private fun createUsageContexts( + producingCompilation: KotlinCompilation<*> + ): Set { + // Here, `JAVA_API` and `JAVA_RUNTIME_JARS` are used intentionally as Gradle needs this for + // ordering of the usage contexts (prioritizing the dependencies) when merging them into the POM; + // These Java usages should not be replaced with the custom Kotlin usages. + return listOfNotNull( + JAVA_API to apiElementsConfigurationName, + (JAVA_RUNTIME_JARS to runtimeElementsConfigurationName).takeIf { producingCompilation is KotlinCompilationToRunnableFiles } + ).mapTo(mutableSetOf()) { (usageName, dependenciesConfigurationName) -> + DefaultKotlinUsageContext( + producingCompilation, + project.usageByName(usageName), + dependenciesConfigurationName + ) + } + } + + protected fun sourcesJarArtifact( + producingCompilation: KotlinCompilation<*>, + componentName: String, + artifactNameAppendix: String, + classifierPrefix: String? = null + ): PublishArtifact { + val sourcesJarTask = sourcesJarTask(producingCompilation, componentName, artifactNameAppendix) + val sourceArtifactConfigurationName = producingCompilation.disambiguateName("sourceArtifacts") + return producingCompilation.target.project.run { + (configurations.findByName(sourceArtifactConfigurationName) ?: run { + val configuration = configurations.create(sourceArtifactConfigurationName) { + it.isCanBeResolved = false + it.isCanBeConsumed = false + } + artifacts.add(sourceArtifactConfigurationName, sourcesJarTask) + configuration + }).artifacts.single().apply { + this as ConfigurablePublishArtifact + classifier = dashSeparatedName(classifierPrefix, "sources") + } + } + } + + @Suppress("UNCHECKED_CAST") + internal val publicationConfigureActions = + WrapUtil.toDomainObjectSet(Action::class.java) as DomainObjectSet> + + override fun mavenPublication(action: Action) { + publicationConfigureActions.add(action) + } + + override fun mavenPublication(action: Closure) = + mavenPublication(ConfigureUtil.configureUsing(action)) + + override var preset: KotlinTargetPreset? = null + internal set +} + +internal fun KotlinTarget.disambiguateName(simpleName: String) = + lowerCamelCaseName(targetName, simpleName) + +open class KotlinAndroidTarget( + override val targetName: String, + project: Project +) : AbstractKotlinTarget(project) { + + override var disambiguationClassifier: String? = null + internal set + + override val platformType: KotlinPlatformType + get() = KotlinPlatformType.androidJvm + + private val compilationFactory = KotlinJvmAndroidCompilationFactory(project, this) + + override val compilations: NamedDomainObjectContainer = + project.container(compilationFactory.itemClass, compilationFactory) + + /** Names of the Android library variants that should be published from the target's project within the default publications which are + * set up if the `maven-publish` Gradle plugin is applied. + * + * Item examples: + * * 'release' (in case no product flavors were defined) + * * 'fooRelease' (for the release build type of a flavor 'foo') + * * 'fooBarRelease' (for the release build type multi-dimensional flavors 'foo' and 'bar'). + * + * If set to null, which can also be done with [publishAllLibraryVariants], + * all library variants will be published, but not test or application variants. */ + var publishLibraryVariants: List? = listOf() + // Workaround for Groovy GString items in a list: + set(value) { field = value?.map(Any::toString) } + + /** Add Android library variant names to [publishLibraryVariants]. */ + fun publishLibraryVariants(vararg names: String) { + publishLibraryVariants = publishLibraryVariants.orEmpty() + names + } + + /** Set up all of the Android library variants to be published from this target's project within the default publications, which are + * set up if the `maven-publish` Gradle plugin is applied. This overrides the variants chosen with [publishLibraryVariants] */ + fun publishAllLibraryVariants() { + publishLibraryVariants = null + } + + /** If true, a publication will be created per merged product flavor, with the build types used as classifiers for the artifacts + * published within each publication. If set to false, each Android variant will have a separate publication. */ + var publishLibraryVariantsGroupedByFlavor = false + + private fun checkPublishLibraryVariantsExist() { + // Capture type parameter T + fun AbstractAndroidProjectHandler.getLibraryVariantNames() = + mutableSetOf().apply { + forEachVariant(project) { + if (getLibraryOutputTask(it) != null) + add(getVariantName(it)) + } + } + + val variantNames = + KotlinAndroidPlugin.androidTargetHandler(project.getKotlinPluginVersion()!!, this) + .getLibraryVariantNames() + + val missingVariants = + publishLibraryVariants?.minus(variantNames).orEmpty() + + if (missingVariants.isNotEmpty()) + throw InvalidUserDataException( + "Kotlin target '$targetName' tried to set up publishing for Android build variants that are not library variants " + + "or do not exist:\n" + missingVariants.joinToString("\n") { "* $it" } + + "\nCheck the 'publishLibraryVariants' property, it should point to existing Android library variants. Publishing " + + "of application and test variants is not supported." + ) + } + + override val components by lazy { + checkPublishLibraryVariantsExist() + + KotlinAndroidPlugin.androidTargetHandler(project.getKotlinPluginVersion()!!, this).doCreateComponents() + .also { project.components.addAll(it) } + } + + // Capture the type parameter T for `AbstractAndroidProjectHandler` + private fun AbstractAndroidProjectHandler.doCreateComponents(): Set { + if (!isGradleVersionAtLeast(4, 7)) + return emptySet() + + val publishableVariants = mutableListOf() + .apply { forEachVariant(project) { add(it) } } + .toList() // Defensive copy against unlikely modification by the lambda that captures the list above in forEachVariant { } + .filter { getLibraryOutputTask(it) != null && publishLibraryVariants?.contains(getVariantName(it)) ?: true } + + val publishableVariantGroups = publishableVariants.groupBy { variant -> + val flavorNames = getFlavorNames(variant) + if (publishLibraryVariantsGroupedByFlavor) { + // For each flavor, we group its variants (which differ only in the build type) in a single component in order to publish + // all of the build types of the flavor as a single module with the build type as the classifier of the artifacts + flavorNames + } else { + flavorNames + getBuildTypeName(variant) + } + } + + return publishableVariantGroups.map { (flavorGroupNameParts, androidVariants) -> + val nestedVariants = androidVariants.mapTo(mutableSetOf()) { androidVariant -> + val androidVariantName = getVariantName(androidVariant) + val compilation = compilations.getByName(androidVariantName) + + val flavorNames = getFlavorNames(androidVariant) + val buildTypeName = getBuildTypeName(androidVariant) + + val artifactClassifier = buildTypeName.takeIf { it != "release" && publishLibraryVariantsGroupedByFlavor } + + val usageContexts = createAndroidUsageContexts(androidVariant, compilation, artifactClassifier) + createKotlinVariant( + lowerCamelCaseName(compilation.target.name, *flavorGroupNameParts.toTypedArray()), + compilation, + usageContexts + ).apply { + sourcesArtifacts = setOf( + sourcesJarArtifact( + compilation, compilation.disambiguateName(""), + dashSeparatedName( + compilation.target.name.toLowerCase(), + *flavorNames.map { it.toLowerCase() }.toTypedArray(), + buildTypeName.takeIf { it != "release" }?.toLowerCase() + ), + classifierPrefix = artifactClassifier + ) + ) + + if (!publishLibraryVariantsGroupedByFlavor) { + defaultArtifactIdSuffix = + dashSeparatedName( + (getFlavorNames(androidVariant) + getBuildTypeName(androidVariant).takeIf { it != "release" }) + .map { it?.toLowerCase() } + ).takeIf { it.isNotEmpty() } + } + } + } + + if (publishLibraryVariantsGroupedByFlavor) { + JointAndroidKotlinTargetComponent( + this@KotlinAndroidTarget, + nestedVariants, + flavorGroupNameParts, + nestedVariants.flatMap { it.sourcesArtifacts }.toSet() + ) + } else { + nestedVariants.single() + } as KotlinTargetComponent + }.toSet() + } + + private fun AbstractAndroidProjectHandler.createAndroidUsageContexts( + variant: T, + compilation: KotlinCompilation<*>, + artifactClassifier: String? + ): Set { + val variantName = getVariantName(variant) + val outputTask = getLibraryOutputTask(variant) ?: return emptySet() + val artifact = run { + val archivesConfigurationName = lowerCamelCaseName(targetName, variantName, "archives") + project.configurations.maybeCreate(archivesConfigurationName).apply { + isCanBeConsumed = false + isCanBeResolved = false + } + project.artifacts.add(archivesConfigurationName, outputTask) { artifact -> + artifact.classifier = artifactClassifier + } + } + + val apiElementsConfigurationName = lowerCamelCaseName(variantName, "apiElements") + val runtimeElementsConfigurationName = lowerCamelCaseName(variantName, "runtimeElements") + + // Here, `JAVA_API` and `JAVA_RUNTIME_JARS` are used intentionally as Gradle needs this for + // ordering of the usage contexts (prioritizing the dependencies) when merging them into the POM; + // These Java usages should not be replaced with the custom Kotlin usages. + return listOf( + apiElementsConfigurationName to JAVA_API, + runtimeElementsConfigurationName to JAVA_RUNTIME_JARS + ).mapTo(mutableSetOf()) { (dependencyConfigurationName, usageName) -> + DefaultKotlinUsageContext( + compilation, + project.usageByName(usageName), + dependencyConfigurationName, + overrideConfigurationArtifacts = setOf(artifact) + ) + } + } +} + +open class KotlinWithJavaTarget( + project: Project, + override val platformType: KotlinPlatformType, + override val targetName: String +) : AbstractKotlinTarget(project) { + override var disambiguationClassifier: String? = null + internal set + + override val defaultConfigurationName: String + get() = Dependency.DEFAULT_CONFIGURATION + + override val apiElementsConfigurationName: String + get() = JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME + + override val runtimeElementsConfigurationName: String + get() = JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME + + override val artifactsTaskName: String + get() = JavaPlugin.JAR_TASK_NAME + + override val compilations: NamedDomainObjectContainer> = + @Suppress("UNCHECKED_CAST") + project.container( + KotlinWithJavaCompilation::class.java as Class>, + KotlinWithJavaCompilationFactory(project, this) + ) +} + +open class KotlinOnlyTarget>( + project: Project, + override val platformType: KotlinPlatformType +) : AbstractKotlinTarget(project) { + + override lateinit var compilations: NamedDomainObjectContainer + internal set + + override lateinit var targetName: String + internal set + + override var disambiguationClassifier: String? = null + internal set +} + +class KotlinNativeTarget( + project: Project, + val konanTarget: KonanTarget +) : KotlinOnlyTarget(project, KotlinPlatformType.native) { + + init { + attributes.attribute(konanTargetAttribute, konanTarget.name) + } + + val binaries = if(isGradleVersionAtLeast(4, 2)) { + // Use newInstance to allow accessing binaries by their names in Groovy using the extension mechanism. + project.objects.newInstance(KotlinNativeBinaryContainer::class.java, this, WrapUtil.toDomainObjectSet(NativeBinary::class.java)) + } else { + KotlinNativeBinaryContainer(this, WrapUtil.toDomainObjectSet(NativeBinary::class.java)) + } + + fun binaries(configure: KotlinNativeBinaryContainer.() -> Unit) { + binaries.configure() + } + + fun binaries(configure: Closure<*>) { + ConfigureUtil.configure(configure, binaries) + } + + override val artifactsTaskName: String + get() = disambiguateName("binaries") + + override val publishable: Boolean + get() = konanTarget.enabledOnCurrentHost + + // User-visible constants + val DEBUG = NativeBuildType.DEBUG + val RELEASE = NativeBuildType.RELEASE + + val EXECUTABLE = NativeOutputKind.EXECUTABLE + val FRAMEWORK = NativeOutputKind.FRAMEWORK + val DYNAMIC = NativeOutputKind.DYNAMIC + val STATIC = NativeOutputKind.STATIC + + companion object { + val konanTargetAttribute = Attribute.of( + "org.jetbrains.kotlin.native.target", + String::class.java + ) + } +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeTargetPreset.kt new file mode 100644 index 00000000000..921736ad861 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinNativeTargetPreset.kt @@ -0,0 +1,330 @@ +/* + * 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.Dependency +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.plugins.JavaPlugin +import org.gradle.internal.reflect.Instantiator +import org.jetbrains.kotlin.compilerRunner.KotlinNativeProjectProperty +import org.jetbrains.kotlin.compilerRunner.hasProperty +import org.jetbrains.kotlin.compilerRunner.konanHome +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.applyLanguageSettingsToKotlinTask +import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider +import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader +import org.jetbrains.kotlin.konan.KonanVersion +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget +import java.io.File +import java.util.* + +abstract class KotlinOnlyTargetPreset>( + protected val project: Project, + private val instantiator: Instantiator, + private val fileResolver: FileResolver, + protected val kotlinPluginVersion: String +) : KotlinTargetPreset> { + + protected open fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = true, createTestCompilation = true) + + override fun createTarget(name: String): KotlinOnlyTarget { + val result = KotlinOnlyTarget(project, platformType).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinOnlyTargetPreset + + val compilationFactory = createCompilationFactory(this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + createKotlinTargetConfigurator().configureTarget(result) + + result.compilations.all { compilation -> + buildCompilationProcessor(compilation).run() + if (compilation.name == KotlinCompilation.MAIN_COMPILATION_NAME) { + sourcesJarTask(compilation, result.targetName, result.targetName.toLowerCase()) + } + } + + return result + } + + protected abstract fun createCompilationFactory(forTarget: KotlinOnlyTarget): KotlinCompilationFactory + protected abstract val platformType: KotlinPlatformType + internal abstract fun buildCompilationProcessor(compilation: T): KotlinSourceSetProcessor<*> +} + +class KotlinMetadataTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return KotlinCommonSourceSetProcessor(project, compilation, tasksProvider, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "metadata" + } + + override fun createKotlinTargetConfigurator(): KotlinTargetConfigurator = + KotlinTargetConfigurator(createDefaultSourceSets = false, createTestCompilation = false) + + override fun createTarget(name: String): KotlinOnlyTarget = + super.createTarget(name).apply { + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + val commonMainSourceSet = project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + + mainCompilation.source(commonMainSourceSet) + + project.afterEvaluate { + // Since there's no default source set, apply language settings from commonMain: + val compileKotlinMetadata = project.tasks.getByName(mainCompilation.compileKotlinTaskName) as KotlinCompile<*> + applyLanguageSettingsToKotlinTask(commonMainSourceSet.languageSettings, compileKotlinMetadata) + } + } +} + +class KotlinJvmTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JvmSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion) + } + + companion object { + const val PRESET_NAME = "jvm" + } +} + +class KotlinJsTargetPreset( + project: Project, + instantiator: Instantiator, + fileResolver: FileResolver, + kotlinPluginVersion: String +) : KotlinOnlyTargetPreset( + project, + instantiator, + fileResolver, + 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<*> { + val tasksProvider = KotlinTasksProvider(compilation.target.targetName) + return Kotlin2JsSourceSetProcessor(project, tasksProvider, 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 + preset = this@KotlinAndroidTargetPreset + } + + KotlinAndroidPlugin.applyToTarget(kotlinPluginVersion, result) + 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 + preset = this@KotlinJvmWithJavaTargetPreset + } + + AbstractKotlinPlugin.configureTarget(target) { compilation -> + Kotlin2JvmSourceSetProcessor(project, KotlinTasksProvider(name), compilation, kotlinPluginVersion) + } + + target.compilations.getByName("test").run { + val main = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + + compileDependencyFiles = project.files( + main.output.allOutputs, + project.configurations.maybeCreate(compileDependencyConfigurationName) + ) + runtimeDependencyFiles = project.files( + output.allOutputs, + main.output.allOutputs, + project.configurations.maybeCreate(runtimeDependencyConfigurationName) + ) + } + + return target + } + + companion object { + const val PRESET_NAME = "jvmWithJava" + } +} + +class KotlinNativeTargetPreset( + private val name: String, + val project: Project, + val konanTarget: KonanTarget, + private val kotlinPluginVersion: String +) : KotlinTargetPreset { + + init { + // This is required to obtain Kotlin/Native home in CLion plugin: + setupNativeHomePrivateProperty() + } + + override fun getName(): String = name + + private fun setupNativeHomePrivateProperty() = with(project) { + if (!hasProperty(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY)) + extensions.extraProperties.set(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY, konanHome) + } + + private fun setupNativeCompiler() = with(project) { + if (!hasProperty(KotlinNativeProjectProperty.KONAN_HOME_OVERRIDE)) { + NativeCompilerDownloader(this).downloadIfNeeded() + logger.info("Kotlin/Native distribution: $konanHome") + } else { + logger.info("User-provided Kotlin/Native distribution: $konanHome") + } + } + + // We declare default K/N dependencies as files to avoid searching them in remote repos (see KT-28128). + private fun defaultLibs(target: KonanTarget? = null): List = with(project) { + + val relPath = if (target != null) "platform/${target.name}" else "common" + + file("$konanHome/klib/$relPath") + .listFiles { file -> file.isDirectory } + ?.sortedBy { dir -> dir.name.toLowerCase() } + ?.map { dir -> dependencies.create(files(dir)) } ?: emptyList() + } + + override fun createTarget(name: String): KotlinNativeTarget { + setupNativeCompiler() + + val result = KotlinNativeTarget(project, konanTarget).apply { + targetName = name + disambiguationClassifier = name + preset = this@KotlinNativeTargetPreset + + val compilationFactory = KotlinNativeCompilationFactory(project, this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + KotlinNativeTargetConfigurator(kotlinPluginVersion).configureTarget(result) + + // Allow IDE to resolve the libraries provided by the compiler by adding them into dependencies. + result.compilations.all { compilation -> + val target = compilation.target.konanTarget + // First, put common libs: + defaultLibs().forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + // Then, platform-specific libs: + defaultLibs(target).forEach { + project.dependencies.add(compilation.compileDependencyConfigurationName, it) + } + } + + if (!konanTarget.enabledOnCurrentHost) { + with(HostManager()) { + val supportedHosts = enabledTargetsByHost.filterValues { konanTarget in it }.keys + val supportedHostsString = + if (supportedHosts.size == 1) + "a ${supportedHosts.single()} host" else + "one of the host platforms: ${supportedHosts.joinToString(", ")}" + project.logger.warn( + "Target '$name' for platform ${konanTarget} is ignored during build on this ${HostManager.host} machine. " + + "You can build it with $supportedHostsString." + ) + } + } + + return result + } + + companion object { + private const val KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY = "konanHome" + } +} + +internal val KonanTarget.isCurrentHost: Boolean + get() = this == HostManager.host + +internal val KonanTarget.enabledOnCurrentHost + get() = HostManager().isEnabled(this) + +internal val KotlinNativeCompilation.isMainCompilation: Boolean + get() = name == KotlinCompilation.MAIN_COMPILATION_NAME diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaCompilation.kt new file mode 100644 index 00000000000..1284fe446f3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaCompilation.kt @@ -0,0 +1,471 @@ + /* + * 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.Action +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.file.FileCollection +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.tasks.SourceSet +import org.gradle.util.ConfigureUtil +import org.jetbrains.kotlin.gradle.dsl.* +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker +import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy +import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import java.util.* +import java.util.concurrent.Callable + +internal fun KotlinCompilation<*>.composeName(prefix: String? = null, suffix: String? = null): String { + val compilationNamePart = compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME } + val targetNamePart = target.disambiguationClassifier + + return lowerCamelCaseName(prefix, targetNamePart, compilationNamePart, suffix) +} + +internal val KotlinCompilation<*>.defaultSourceSetName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName) + +abstract class AbstractKotlinCompilation( + target: KotlinTarget, + override val compilationName: String +) : KotlinCompilation, HasKotlinDependencies { + + override val kotlinOptions: T + get() = compileKotlinTask.kotlinOptions + + override fun kotlinOptions(configure: T.() -> Unit) = + configure(kotlinOptions) + + @Suppress("UNCHECKED_CAST") + override val compileKotlinTask: KotlinCompile + get() = (target.project.tasks.getByName(compileKotlinTaskName) as KotlinCompile) + + // Don't declare this property in the constructor to avoid NPE + // when an overriding property of a subclass is accessed instead. + override val target: KotlinTarget = target + + private val attributeContainer = HierarchyAttributeContainer(target.attributes) + + override fun getAttributes(): AttributeContainer = attributeContainer + + override val kotlinSourceSets: MutableSet = mutableSetOf() + + override val allKotlinSourceSets: Set + get() = kotlinSourceSets.flatMapTo(mutableSetOf()) { it.getSourceSetHierarchy() } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(defaultSourceSetName) + + override fun defaultSourceSet(configure: KotlinSourceSet.() -> Unit) = + configure(defaultSourceSet) + + override val output: KotlinCompilationOutput by lazy { + DefaultKotlinCompilationOutput( + target.project, + Callable { target.project.buildDir.resolve("processedResources/${target.targetName}/$name") }) + } + + open fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + fun AbstractKotlinCompile<*>.configureAction() { + source(sourceSet.kotlin) + sourceFilesExtensions(sourceSet.customSourceFilesExtensions) + if (addAsCommonSources) { + commonSourceSet += sourceSet.kotlin + } + } + + // Note! Invocation of withType-all results in preliminary task instantiation. + // After fix of this issue the following code should be uncommented: +// if (useLazyTaskConfiguration) { +// (target.project.tasks.named(compileKotlinTaskName) as TaskProvider>).configure { +// it.configureAction() +// } +// } + + target.project.tasks + // To configure a task that may have not yet been created at this point, use 'withType-matching-all`: + .withType(AbstractKotlinCompile::class.java) + .matching { it.name == compileKotlinTaskName } + .all { compileKotlinTask -> + compileKotlinTask.configureAction() + } + } + + override fun source(sourceSet: KotlinSourceSet) { + if (kotlinSourceSets.add(sourceSet)) { + //TODO possibly issue with forced instantiation + target.project.whenEvaluated { + sourceSet.getSourceSetHierarchy().forEach { sourceSet -> + val isCommonSource = + CompilationSourceSetUtil.sourceSetsInMultipleCompilations(project)?.contains(sourceSet) ?: false + + addSourcesToCompileTask(sourceSet, addAsCommonSources = isCommonSource) + + // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like + // old Java & Android projects: + addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) + addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) + addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) + + if (this@AbstractKotlinCompilation is KotlinCompilationToRunnableFiles<*>) { + addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false) + } + + if (sourceSet.name != defaultSourceSetName) { + kotlinExtension.sourceSets.findByName(defaultSourceSetName)?.let { defaultSourceSet -> + // Temporary solution for checking consistency across source sets participating in a compilation that may + // not be interconnected with the dependsOn relation: check the settings as if the default source set of + // the compilation depends on the one added to the compilation: + defaultSourceSetLanguageSettingsChecker.runAllChecks( + defaultSourceSet, + sourceSet + ) + } + } + } + } + } + } + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileClasspath" + ) + + override val compileKotlinTaskName: String + get() = lowerCamelCaseName( + "compile", + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "Kotlin", + target.targetName + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "classes") + + override lateinit var compileDependencyFiles: FileCollection + + override val apiConfigurationName: String + get() = disambiguateName("api") + + override val implementationConfigurationName: String + get() = disambiguateName("implementation") + + override val compileOnlyConfigurationName: String + get() = disambiguateName("compileOnly") + + override val runtimeOnlyConfigurationName: String + get() = disambiguateName("runtimeOnly") + + override fun dependencies(configure: KotlinDependencyHandler.() -> Unit): Unit = + DefaultKotlinDependencyHandler(this, target.project).run(configure) + + override fun dependencies(configureClosure: Closure) = + dependencies f@{ ConfigureUtil.configure(configureClosure, this@f) } + + override fun toString(): String = "compilation '$compilationName' ($target)" +} + +abstract class AbstractKotlinCompilationToRunnableFiles( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationToRunnableFiles { + override val runtimeDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + "runtimeClasspath" + ) + + override lateinit var runtimeDependencyFiles: FileCollection +} + +internal fun KotlinCompilation<*>.disambiguateName(simpleName: String): String { + return lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }, + simpleName + ) +} + +open class KotlinJvmCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile +} + +class KotlinWithJavaCompilation( + target: KotlinWithJavaTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + lateinit var javaSourceSet: SourceSet + + override val output: KotlinCompilationOutput by lazy { KotlinWithJavaCompilationOutput(this) } + + override val processResourcesTaskName: String + get() = javaSourceSet.processResourcesTaskName + + override var runtimeDependencyFiles: FileCollection + get() = javaSourceSet.runtimeClasspath + set(value) { + javaSourceSet.runtimeClasspath = value + } + + override val runtimeDependencyConfigurationName: String + get() = javaSourceSet.runtimeClasspathConfigurationName + + override val compileDependencyConfigurationName: String + get() = javaSourceSet.compileClasspathConfigurationName + + override val runtimeOnlyConfigurationName: String + get() = javaSourceSet.runtimeOnlyConfigurationName + + override val implementationConfigurationName: String + get() = javaSourceSet.implementationConfigurationName + + override val apiConfigurationName: String + get() = javaSourceSet.apiConfigurationName + + override val compileOnlyConfigurationName: String + get() = javaSourceSet.compileOnlyConfigurationName + + override val compileAllTaskName: String + get() = javaSourceSet.classesTaskName + + override var compileDependencyFiles: FileCollection + get() = javaSourceSet.compileClasspath + set(value) { + javaSourceSet.compileClasspath = value + } + + fun source(javaSourceSet: SourceSet) { + with(target.project) { + afterEvaluate { + (tasks.getByName(compileKotlinTaskName) as AbstractKotlinCompile<*>).source(javaSourceSet.java) + } + } + } +} + +class KotlinJvmAndroidCompilation( + target: KotlinAndroidTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name) { + override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile + get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + override val relatedConfigurationNames: List + get() = super.relatedConfigurationNames + listOf("${name}ApiElements", "${name}RuntimeElements") +} + +class KotlinJsCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + override val compileKotlinTask: Kotlin2JsCompile + get() = super.compileKotlinTask as Kotlin2JsCompile +} + +class KotlinCommonCompilation( + target: KotlinTarget, + name: String +) : AbstractKotlinCompilation(target, name) { + override val compileKotlinTask: KotlinCompileCommon + get() = super.compileKotlinTask as KotlinCompileCommon + + // TODO once we properly compile metadata for each source set, the default source sets will likely become just the source sets + // which are transformed to metadata + private val commonSourceSetName = when (compilationName) { + KotlinCompilation.MAIN_COMPILATION_NAME -> KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME + else -> error("Custom metadata compilations are not supported yet") + } + + override val defaultSourceSet: KotlinSourceSet + get() = target.project.kotlinExtension.sourceSets.getByName(commonSourceSetName) +} + +class KotlinNativeCompilation( + override val target: KotlinNativeTarget, + name: String +) : AbstractKotlinCompilation(target, name), KotlinCompilationWithResources { + + override val kotlinOptions: KotlinCommonOptions + get() = compileKotlinTask.kotlinOptions + + override val compileKotlinTask: KotlinNativeCompile + get() = super.compileKotlinTask as KotlinNativeCompile + + private val project: Project + get() = target.project + + // A collection containing all source sets used by this compilation + // (taking into account dependencies between source sets). Used by both compilation + // and linking tasks. Unlike kotlinSourceSets, includes dependency source sets. + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val allSources: MutableSet = mutableSetOf() + + // TODO: Move into the compilation task when the linking task does klib linking instead of compilation. + internal val commonSources: MutableSet = mutableSetOf() + + var isTestCompilation = false + + var friendCompilationName: String? = null + + internal val friendCompilation: KotlinNativeCompilation? + get() = friendCompilationName?.let { + target.compilations.getByName(it) + } + + // Used only to support the old APIs. TODO: Remove when the old APIs are removed. + internal val binaries = mutableMapOf, NativeBinary>() + + // Native-specific DSL. + var extraOpts = mutableListOf() + + fun extraOpts(vararg values: Any) = extraOpts(values.toList()) + fun extraOpts(values: List) { + extraOpts.addAll(values.map { it.toString() }) + } + + var buildTypes = mutableListOf() + var outputKinds = mutableListOf() + + fun outputKind(kind: NativeOutputKind) = outputKinds.add(kind) + + fun outputKinds(vararg kinds: NativeOutputKind) { + outputKinds = kinds.toMutableList() + } + + fun outputKinds(vararg kinds: String) { + outputKinds = kinds.map { NativeOutputKind.valueOf(it.toUpperCase()) }.toMutableList() + } + + fun outputKinds(kinds: List) { + outputKinds = kinds.map { + when (it) { + is NativeOutputKind -> it + is String -> NativeOutputKind.valueOf(it.toUpperCase()) + else -> error("Cannot use $it as an output kind") + } + }.toMutableList() + } + + var entryPoint: String? = null + fun entryPoint(value: String) { + entryPoint = value + } + + // Interop DSL. + val cinterops = project.container(DefaultCInteropSettings::class.java) { cinteropName -> + DefaultCInteropSettings(project, cinteropName, this) + } + + var linkerOpts = mutableListOf() + + fun cinterops(action: NamedDomainObjectContainer.() -> Unit) = cinterops.action() + fun cinterops(action: Closure) = cinterops(ConfigureUtil.configureUsing(action)) + fun cinterops(action: Action>) = action.execute(cinterops) + + fun linkerOpts(vararg values: String) = linkerOpts(values.toList()) + fun linkerOpts(values: List) { + linkerOpts.addAll(values) + } + + // Task accessors. + fun findLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink? = binaries[kind to buildType]?.linkTask + + fun getLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink = + findLinkTask(kind, buildType) + ?: throw IllegalArgumentException("Cannot find a link task for the binary kind '$kind' and the build type '$buildType'") + + fun findLinkTask(kind: String, buildType: String) = + findLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getLinkTask(kind: String, buildType: String) = + getLinkTask(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun findBinary(kind: NativeOutputKind, buildType: NativeBuildType): File? = findLinkTask(kind, buildType)?.outputFile?.get() + + fun getBinary(kind: NativeOutputKind, buildType: NativeBuildType): File = getLinkTask(kind, buildType).outputFile.get() + + fun findBinary(kind: String, buildType: String) = + findBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + fun getBinary(kind: String, buildType: String) = + getBinary(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + // Naming + override val processResourcesTaskName: String + get() = disambiguateName("processResources") + + fun linkTaskName(kind: NativeOutputKind, buildType: NativeBuildType): String = + lowerCamelCaseName( + "link", + KotlinNativeBinaryContainer.generateBinaryName(compilationName, buildType, kind.taskNameClassifier), + target.targetName + ) + + fun linkTaskName(kind: String, buildType: String) = + linkTaskName(NativeOutputKind.valueOf(kind.toUpperCase()), NativeBuildType.valueOf(buildType.toUpperCase())) + + override val compileDependencyConfigurationName: String + get() = lowerCamelCaseName( + target.disambiguationClassifier, + compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }.orEmpty(), + "compileKlibraries" + ) + + override val compileAllTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "klibrary") + + val binariesTaskName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName, "binaries") + + override fun addSourcesToCompileTask(sourceSet: KotlinSourceSet, addAsCommonSources: Boolean) { + allSources.add(sourceSet.kotlin) + if (addAsCommonSources) { + commonSources.add(sourceSet.kotlin) + } + } +} + +private object CompilationSourceSetUtil { + // Cache the results per project + private val projectSourceSetsInMultipleCompilationsCache = WeakHashMap>() + + fun sourceSetsInMultipleCompilations(project: Project) = + projectSourceSetsInMultipleCompilationsCache.computeIfAbsent(project) { _ -> + check(project.state.executed) { "Should only be computed after the project is evaluated" } + + val compilations = (project.kotlinExtension as? KotlinMultiplatformExtension)?.targets?.flatMap { it.compilations } + ?: return@computeIfAbsent null + + compilations + .flatMap { compilation -> compilation.allKotlinSourceSets.map { sourceSet -> compilation to sourceSet } } + .groupingBy { (_, sourceSet) -> sourceSet } + .eachCount() + .filterValues { it > 1 } + .keys + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaCompilationFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaCompilationFactory.kt new file mode 100644 index 00000000000..e63b6e6573d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaCompilationFactory.kt @@ -0,0 +1,93 @@ +/* + * 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.NamedDomainObjectFactory +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation + +interface KotlinCompilationFactory> : NamedDomainObjectFactory { + val itemClass: Class +} + +class KotlinCommonCompilationFactory( + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinCommonCompilation::class.java + + override fun create(name: String): KotlinCommonCompilation = + KotlinCommonCompilation(target, name) +} + +class KotlinJvmCompilationFactory( + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinJvmCompilation::class.java + + override fun create(name: String): KotlinJvmCompilation = + KotlinJvmCompilation(target, name) +} + +class KotlinWithJavaCompilationFactory( + val project: Project, + val target: KotlinWithJavaTarget +) : KotlinCompilationFactory> { + + override val itemClass: Class> + @Suppress("UNCHECKED_CAST") + get() = KotlinWithJavaCompilation::class.java as Class> + + override fun create(name: String): KotlinWithJavaCompilation { + val result = KotlinWithJavaCompilation(target, name) + return result + } +} + +class KotlinJvmAndroidCompilationFactory( + val project: Project, + val target: KotlinAndroidTarget +) : KotlinCompilationFactory { + + override val itemClass: Class + get() = KotlinJvmAndroidCompilation::class.java + + override fun create(name: String): KotlinJvmAndroidCompilation = + KotlinJvmAndroidCompilation(target, name) +} + +class KotlinJsCompilationFactory( + val project: Project, + val target: KotlinOnlyTarget +) : KotlinCompilationFactory { + override val itemClass: Class + get() = KotlinJsCompilation::class.java + + override fun create(name: String): KotlinJsCompilation = + KotlinJsCompilation(target, name) +} + +class KotlinNativeCompilationFactory( + val project: Project, + val target: KotlinNativeTarget +) : KotlinCompilationFactory { + + override val itemClass: Class + get() = KotlinNativeCompilation::class.java + + override fun create(name: String): KotlinNativeCompilation = + KotlinNativeCompilation(target, name).apply { + if (name == KotlinCompilation.TEST_COMPILATION_NAME) { + friendCompilationName = KotlinCompilation.MAIN_COMPILATION_NAME + isTestCompilation = true + } + buildTypes = mutableListOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE) + } + +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaCompilationOutput.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaCompilationOutput.kt new file mode 100644 index 00000000000..4fe268181d2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaCompilationOutput.kt @@ -0,0 +1,56 @@ +/* + * 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.file.ConfigurableFileCollection +import org.gradle.api.file.FileCollection +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationOutput +import java.io.File +import java.util.concurrent.Callable + +class DefaultKotlinCompilationOutput( + private val project: Project, + override var resourcesDirProvider: Any +) : KotlinCompilationOutput, Callable { + + override val classesDirs: ConfigurableFileCollection = project.files() + + override val allOutputs = project.files().apply { + from(classesDirs) + from(Callable { resourcesDir }) + } + + override val resourcesDir: File + get() = project.file(resourcesDirProvider) + + override fun call(): FileCollection = allOutputs +} + +class KotlinWithJavaCompilationOutput( + internal val compilation: KotlinWithJavaCompilation<*> +) : KotlinCompilationOutput, Callable { + + private val javaSourceSetOutput + get() = compilation.javaSourceSet.output + + override val resourcesDir: File + get() = javaSourceSetOutput.resourcesDir!! + + override var resourcesDirProvider: Any + get() = javaSourceSetOutput.resourcesDir!! + set(value) { + javaSourceSetOutput.setResourcesDir(value) + } + + override val classesDirs: ConfigurableFileCollection = + javaSourceSetOutput.classesDirs as ConfigurableFileCollection + + override val allOutputs: FileCollection + get() = javaSourceSetOutput + + override fun call(): FileCollection = allOutputs +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaTarget.kt new file mode 100644 index 00000000000..f4bbb5d60f9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinWithJavaTarget.kt @@ -0,0 +1,437 @@ +/* + * 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.* +import org.gradle.api.artifacts.ConfigurablePublishArtifact +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.PublishArtifact +import org.gradle.api.attributes.Attribute +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.attributes.Usage.JAVA_API +import org.gradle.api.attributes.Usage.JAVA_RUNTIME_JARS +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.util.ConfigureUtil +import org.gradle.util.WrapUtil +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.utils.dashSeparatedName +import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.konan.target.KonanTarget + +abstract class AbstractKotlinTarget( + final override val project: Project +) : KotlinTarget { + private val attributeContainer = HierarchyAttributeContainer(parent = null) + + override fun getAttributes(): AttributeContainer = attributeContainer + + override val defaultConfigurationName: String + get() = disambiguateName("default") + + override val apiElementsConfigurationName: String + get() = disambiguateName("apiElements") + + override val runtimeElementsConfigurationName: String + get() = disambiguateName("runtimeElements") + + override val artifactsTaskName: String + get() = disambiguateName("jar") + + override fun toString(): String = "target $name ($platformType)" + + override val publishable: Boolean + get() = true + + override val components: Set by lazy { + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + val usageContexts = createUsageContexts(mainCompilation) + setOf( + if (isGradleVersionAtLeast(4, 7)) { + val componentName = mainCompilation.target.name + createKotlinVariant(componentName, mainCompilation, usageContexts).apply { + sourcesArtifacts = setOf( + sourcesJarArtifact( + mainCompilation, componentName, + dashSeparatedName(target.name.toLowerCase(), componentName.toLowerCase()) + ) + ) + } + } else { + KotlinVariant(mainCompilation, usageContexts) + } + ).also { project.components.addAll(it) } + } + + protected fun createKotlinVariant( + componentName: String, + compilation: KotlinCompilation<*>, + usageContexts: Set + ): KotlinVariant { + + val kotlinExtension = project.kotlinExtension as? KotlinMultiplatformExtension + ?: return KotlinVariantWithCoordinates(compilation, usageContexts) + + if (targetName == KotlinMultiplatformPlugin.METADATA_TARGET_NAME) + return KotlinVariantWithCoordinates(compilation, usageContexts) + + val separateMetadataTarget = kotlinExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME) + + val result = if (kotlinExtension.isGradleMetadataAvailable) { + KotlinVariantWithMetadataVariant(compilation, usageContexts, separateMetadataTarget) + } else { + // we should only add the Kotlin metadata dependency if we publish no Gradle metadata related to Kotlin MPP; + // with metadata, such a dependency would get invalid, since a platform module should only depend on modules for that + // same platform, not Kotlin metadata modules + KotlinVariantWithMetadataDependency(compilation, usageContexts, separateMetadataTarget) + } + + result.componentName = componentName + return result + } + + private fun createUsageContexts( + producingCompilation: KotlinCompilation<*> + ): Set { + // Here, `JAVA_API` and `JAVA_RUNTIME_JARS` are used intentionally as Gradle needs this for + // ordering of the usage contexts (prioritizing the dependencies) when merging them into the POM; + // These Java usages should not be replaced with the custom Kotlin usages. + return listOfNotNull( + JAVA_API to apiElementsConfigurationName, + (JAVA_RUNTIME_JARS to runtimeElementsConfigurationName).takeIf { producingCompilation is KotlinCompilationToRunnableFiles } + ).mapTo(mutableSetOf()) { (usageName, dependenciesConfigurationName) -> + DefaultKotlinUsageContext( + producingCompilation, + project.usageByName(usageName), + dependenciesConfigurationName + ) + } + } + + protected fun sourcesJarArtifact( + producingCompilation: KotlinCompilation<*>, + componentName: String, + artifactNameAppendix: String, + classifierPrefix: String? = null + ): PublishArtifact { + val sourcesJarTask = sourcesJarTask(producingCompilation, componentName, artifactNameAppendix) + val sourceArtifactConfigurationName = producingCompilation.disambiguateName("sourceArtifacts") + return producingCompilation.target.project.run { + (configurations.findByName(sourceArtifactConfigurationName) ?: run { + val configuration = configurations.create(sourceArtifactConfigurationName) { + it.isCanBeResolved = false + it.isCanBeConsumed = false + } + artifacts.add(sourceArtifactConfigurationName, sourcesJarTask) + configuration + }).artifacts.single().apply { + this as ConfigurablePublishArtifact + classifier = dashSeparatedName(classifierPrefix, "sources") + } + } + } + + @Suppress("UNCHECKED_CAST") + internal val publicationConfigureActions = + WrapUtil.toDomainObjectSet(Action::class.java) as DomainObjectSet> + + override fun mavenPublication(action: Action) { + publicationConfigureActions.add(action) + } + + override fun mavenPublication(action: Closure) = + mavenPublication(ConfigureUtil.configureUsing(action)) + + override var preset: KotlinTargetPreset? = null + internal set +} + +internal fun KotlinTarget.disambiguateName(simpleName: String) = + lowerCamelCaseName(targetName, simpleName) + +open class KotlinAndroidTarget( + override val targetName: String, + project: Project +) : AbstractKotlinTarget(project) { + + override var disambiguationClassifier: String? = null + internal set + + override val platformType: KotlinPlatformType + get() = KotlinPlatformType.androidJvm + + private val compilationFactory = KotlinJvmAndroidCompilationFactory(project, this) + + override val compilations: NamedDomainObjectContainer = + project.container(compilationFactory.itemClass, compilationFactory) + + /** Names of the Android library variants that should be published from the target's project within the default publications which are + * set up if the `maven-publish` Gradle plugin is applied. + * + * Item examples: + * * 'release' (in case no product flavors were defined) + * * 'fooRelease' (for the release build type of a flavor 'foo') + * * 'fooBarRelease' (for the release build type multi-dimensional flavors 'foo' and 'bar'). + * + * If set to null, which can also be done with [publishAllLibraryVariants], + * all library variants will be published, but not test or application variants. */ + var publishLibraryVariants: List? = listOf() + // Workaround for Groovy GString items in a list: + set(value) { field = value?.map(Any::toString) } + + /** Add Android library variant names to [publishLibraryVariants]. */ + fun publishLibraryVariants(vararg names: String) { + publishLibraryVariants = publishLibraryVariants.orEmpty() + names + } + + /** Set up all of the Android library variants to be published from this target's project within the default publications, which are + * set up if the `maven-publish` Gradle plugin is applied. This overrides the variants chosen with [publishLibraryVariants] */ + fun publishAllLibraryVariants() { + publishLibraryVariants = null + } + + /** If true, a publication will be created per merged product flavor, with the build types used as classifiers for the artifacts + * published within each publication. If set to false, each Android variant will have a separate publication. */ + var publishLibraryVariantsGroupedByFlavor = false + + private fun checkPublishLibraryVariantsExist() { + // Capture type parameter T + fun AbstractAndroidProjectHandler.getLibraryVariantNames() = + mutableSetOf().apply { + forEachVariant(project) { + if (getLibraryOutputTask(it) != null) + add(getVariantName(it)) + } + } + + val variantNames = + KotlinAndroidPlugin.androidTargetHandler(project.getKotlinPluginVersion()!!, this) + .getLibraryVariantNames() + + val missingVariants = + publishLibraryVariants?.minus(variantNames).orEmpty() + + if (missingVariants.isNotEmpty()) + throw InvalidUserDataException( + "Kotlin target '$targetName' tried to set up publishing for Android build variants that are not library variants " + + "or do not exist:\n" + missingVariants.joinToString("\n") { "* $it" } + + "\nCheck the 'publishLibraryVariants' property, it should point to existing Android library variants. Publishing " + + "of application and test variants is not supported." + ) + } + + override val components by lazy { + checkPublishLibraryVariantsExist() + + KotlinAndroidPlugin.androidTargetHandler(project.getKotlinPluginVersion()!!, this).doCreateComponents() + .also { project.components.addAll(it) } + } + + // Capture the type parameter T for `AbstractAndroidProjectHandler` + private fun AbstractAndroidProjectHandler.doCreateComponents(): Set { + if (!isGradleVersionAtLeast(4, 7)) + return emptySet() + + val publishableVariants = mutableListOf() + .apply { forEachVariant(project) { add(it) } } + .toList() // Defensive copy against unlikely modification by the lambda that captures the list above in forEachVariant { } + .filter { getLibraryOutputTask(it) != null && publishLibraryVariants?.contains(getVariantName(it)) ?: true } + + val publishableVariantGroups = publishableVariants.groupBy { variant -> + val flavorNames = getFlavorNames(variant) + if (publishLibraryVariantsGroupedByFlavor) { + // For each flavor, we group its variants (which differ only in the build type) in a single component in order to publish + // all of the build types of the flavor as a single module with the build type as the classifier of the artifacts + flavorNames + } else { + flavorNames + getBuildTypeName(variant) + } + } + + return publishableVariantGroups.map { (flavorGroupNameParts, androidVariants) -> + val nestedVariants = androidVariants.mapTo(mutableSetOf()) { androidVariant -> + val androidVariantName = getVariantName(androidVariant) + val compilation = compilations.getByName(androidVariantName) + + val flavorNames = getFlavorNames(androidVariant) + val buildTypeName = getBuildTypeName(androidVariant) + + val artifactClassifier = buildTypeName.takeIf { it != "release" && publishLibraryVariantsGroupedByFlavor } + + val usageContexts = createAndroidUsageContexts(androidVariant, compilation, artifactClassifier) + createKotlinVariant( + lowerCamelCaseName(compilation.target.name, *flavorGroupNameParts.toTypedArray()), + compilation, + usageContexts + ).apply { + sourcesArtifacts = setOf( + sourcesJarArtifact( + compilation, compilation.disambiguateName(""), + dashSeparatedName( + compilation.target.name.toLowerCase(), + *flavorNames.map { it.toLowerCase() }.toTypedArray(), + buildTypeName.takeIf { it != "release" }?.toLowerCase() + ), + classifierPrefix = artifactClassifier + ) + ) + + if (!publishLibraryVariantsGroupedByFlavor) { + defaultArtifactIdSuffix = + dashSeparatedName( + (getFlavorNames(androidVariant) + getBuildTypeName(androidVariant).takeIf { it != "release" }) + .map { it?.toLowerCase() } + ).takeIf { it.isNotEmpty() } + } + } + } + + if (publishLibraryVariantsGroupedByFlavor) { + JointAndroidKotlinTargetComponent( + this@KotlinAndroidTarget, + nestedVariants, + flavorGroupNameParts, + nestedVariants.flatMap { it.sourcesArtifacts }.toSet() + ) + } else { + nestedVariants.single() + } as KotlinTargetComponent + }.toSet() + } + + private fun AbstractAndroidProjectHandler.createAndroidUsageContexts( + variant: T, + compilation: KotlinCompilation<*>, + artifactClassifier: String? + ): Set { + val variantName = getVariantName(variant) + val outputTask = getLibraryOutputTask(variant) ?: return emptySet() + val artifact = run { + val archivesConfigurationName = lowerCamelCaseName(targetName, variantName, "archives") + project.configurations.maybeCreate(archivesConfigurationName).apply { + isCanBeConsumed = false + isCanBeResolved = false + } + project.artifacts.add(archivesConfigurationName, outputTask) { artifact -> + artifact.classifier = artifactClassifier + } + } + + val apiElementsConfigurationName = lowerCamelCaseName(variantName, "apiElements") + val runtimeElementsConfigurationName = lowerCamelCaseName(variantName, "runtimeElements") + + // Here, `JAVA_API` and `JAVA_RUNTIME_JARS` are used intentionally as Gradle needs this for + // ordering of the usage contexts (prioritizing the dependencies) when merging them into the POM; + // These Java usages should not be replaced with the custom Kotlin usages. + return listOf( + apiElementsConfigurationName to JAVA_API, + runtimeElementsConfigurationName to JAVA_RUNTIME_JARS + ).mapTo(mutableSetOf()) { (dependencyConfigurationName, usageName) -> + DefaultKotlinUsageContext( + compilation, + project.usageByName(usageName), + dependencyConfigurationName, + overrideConfigurationArtifacts = setOf(artifact) + ) + } + } +} + +open class KotlinWithJavaTarget( + project: Project, + override val platformType: KotlinPlatformType, + override val targetName: String +) : AbstractKotlinTarget(project) { + override var disambiguationClassifier: String? = null + internal set + + override val defaultConfigurationName: String + get() = Dependency.DEFAULT_CONFIGURATION + + override val apiElementsConfigurationName: String + get() = JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME + + override val runtimeElementsConfigurationName: String + get() = JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME + + override val artifactsTaskName: String + get() = JavaPlugin.JAR_TASK_NAME + + override val compilations: NamedDomainObjectContainer> = + @Suppress("UNCHECKED_CAST") + project.container( + KotlinWithJavaCompilation::class.java as Class>, + KotlinWithJavaCompilationFactory(project, this) + ) +} + +open class KotlinOnlyTarget>( + project: Project, + override val platformType: KotlinPlatformType +) : AbstractKotlinTarget(project) { + + override lateinit var compilations: NamedDomainObjectContainer + internal set + + override lateinit var targetName: String + internal set + + override var disambiguationClassifier: String? = null + internal set +} + +class KotlinNativeTarget( + project: Project, + val konanTarget: KonanTarget +) : KotlinOnlyTarget(project, KotlinPlatformType.native) { + + init { + attributes.attribute(konanTargetAttribute, konanTarget.name) + } + + val binaries = if(isGradleVersionAtLeast(4, 2)) { + // Use newInstance to allow accessing binaries by their names in Groovy using the extension mechanism. + project.objects.newInstance(KotlinNativeBinaryContainer::class.java, this, WrapUtil.toDomainObjectSet(NativeBinary::class.java)) + } else { + KotlinNativeBinaryContainer(this, WrapUtil.toDomainObjectSet(NativeBinary::class.java)) + } + + fun binaries(configure: KotlinNativeBinaryContainer.() -> Unit) { + binaries.configure() + } + + fun binaries(configure: Closure<*>) { + ConfigureUtil.configure(configure, binaries) + } + + override val artifactsTaskName: String + get() = disambiguateName("binaries") + + override val publishable: Boolean + get() = konanTarget.enabledOnCurrentHost + + // User-visible constants + val DEBUG = NativeBuildType.DEBUG + val RELEASE = NativeBuildType.RELEASE + + val EXECUTABLE = NativeOutputKind.EXECUTABLE + val FRAMEWORK = NativeOutputKind.FRAMEWORK + val DYNAMIC = NativeOutputKind.DYNAMIC + val STATIC = NativeOutputKind.STATIC + + companion object { + val konanTargetAttribute = Attribute.of( + "org.jetbrains.kotlin.native.target", + String::class.java + ) + } +} +