diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt index 92c4ca95d63..77cd249901d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt @@ -27,11 +27,13 @@ import org.gradle.work.Incremental import org.gradle.work.NormalizeLineEndings import org.gradle.workers.WorkerExecutor import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsDefault import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsHelper import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerArgumentsProducer.CreateCompilerArgumentsContext import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerArgumentsProducer.CreateCompilerArgumentsContext.Companion.create import org.jetbrains.kotlin.gradle.report.BuildReportMode +import org.jetbrains.kotlin.gradle.tasks.K2MultiplatformStructure import org.jetbrains.kotlin.gradle.tasks.KaptGenerateStubs import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.toSingleCompilerPluginOptions @@ -59,6 +61,13 @@ abstract class KaptGenerateStubsTask @Inject constructor( @get:Internal abstract override val libraries: ConfigurableFileCollection + /** + * [K2MultiplatformStructure] is not required for Kapt stubs + */ + @InternalKotlinGradlePluginApi + @get:Internal + override val multiplatformStructure: K2MultiplatformStructure get() = super.multiplatformStructure + /* Used as input as empty kapt classpath should not trigger stub generation, but a non-empty one should. */ @Input fun getIfKaptClasspathIsPresent() = !kaptClasspath.isEmpty diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationK2MultiplatformConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationK2MultiplatformConfigurator.kt index dbaf531cc5b..3ef4f5011ef 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationK2MultiplatformConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationK2MultiplatformConfigurator.kt @@ -51,6 +51,8 @@ internal object KotlinCompilationK2MultiplatformConfigurator : KotlinCompilation K2MultiplatformStructure.Fragment(fragmentName, sourceFiles.reduce { acc, fileTree -> acc + fileTree }) } }) + + compileTask.multiplatformStructure.defaultFragmentName.set(compilation.defaultSourceSet.fragmentName()) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt index 7b23e4f2db9..bd8d8db3cfa 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt @@ -14,6 +14,7 @@ import org.gradle.api.tasks.* import org.gradle.work.NormalizeLineEndings import org.gradle.workers.WorkerExecutor import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompilerOptionsDefault import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerArgumentsProducer.ContributeCompilerArgumentsContext import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider @@ -21,6 +22,7 @@ import org.jetbrains.kotlin.gradle.plugin.statistics.CompileKotlinJsIrLinkMetric import org.jetbrains.kotlin.gradle.plugin.statistics.UsesBuildFusService import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode.DEVELOPMENT +import org.jetbrains.kotlin.gradle.tasks.K2MultiplatformStructure import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile import org.jetbrains.kotlin.gradle.utils.configureExperimentalTryNext import javax.inject.Inject @@ -44,6 +46,13 @@ abstract class KotlinJsIrLink @Inject constructor( @get:Internal override val sources: FileCollection = super.sources + /** + * [K2MultiplatformStructure] is not required for JS IR link + */ + @InternalKotlinGradlePluginApi + @get:Internal + override val multiplatformStructure: K2MultiplatformStructure get() = super.multiplatformStructure + override fun skipCondition(): Boolean { return !entryModule.get().asFile.exists() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTasks.kt index f983db5093d..205eb13bf12 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTasks.kt @@ -500,7 +500,7 @@ internal constructor( sources { args -> /* Shared native compilations in K2 still use -Xcommon-sources and klib dependencies */ if (compilerOptions.usesK2.get() && sharedCompilationData == null) { - args.fragmentSources = multiplatformStructure.fragmentSourcesCompilerArgs(sourceFileFilter) + args.fragmentSources = multiplatformStructure.fragmentSourcesCompilerArgs(sources.files, sourceFileFilter) } else { args.commonSources = commonSourcesTree.files.takeIf { it.isNotEmpty() }?.toPathsArray() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/K2MultiplatformStructure.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/K2MultiplatformStructure.kt index 3f1894087ba..0344b0f9dd7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/K2MultiplatformStructure.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/K2MultiplatformStructure.kt @@ -7,12 +7,14 @@ package org.jetbrains.kotlin.gradle.tasks import org.gradle.api.file.FileCollection import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* import org.gradle.api.tasks.util.PatternFilterable import org.gradle.work.Incremental import org.gradle.work.NormalizeLineEndings import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi +import java.io.File @InternalKotlinGradlePluginApi abstract class K2MultiplatformStructure { @@ -43,17 +45,48 @@ abstract class K2MultiplatformStructure { @get:Nested abstract val fragments: ListProperty + + /** + * If new sources were added to the Compile Task, + * and they weren't mapped to any of the fragments then [defaultFragmentName] will be used + * + * It is marked with @Optional as an extra protection measure for cases when some task extends + * a compile task but doesn't need K2 Structure for example [KotlinJsIrLink] + * + * @see KotlinCompileTool.source + */ + @get:Input + @get:Optional + abstract val defaultFragmentName: Property } internal val K2MultiplatformStructure.fragmentsCompilerArgs: Array get() = fragments.get().map { it.fragmentName }.toSet().toTypedArray() -internal fun K2MultiplatformStructure.fragmentSourcesCompilerArgs(sourceFileFilter: PatternFilterable? = null): Array = - fragments.get().flatMap { sourceSet -> +private fun fragmentSourceCompilerArg(sourceFile: File, fragmentName: String) = "$fragmentName:${sourceFile.absolutePath}" + +internal fun K2MultiplatformStructure.fragmentSourcesCompilerArgs( + allSources: Collection, + sourceFileFilter: PatternFilterable? = null +): Array { + val sourcesWithKnownFragment = mutableSetOf() + val fragmentSourcesCompilerArgs = fragments.get().flatMap { sourceSet -> sourceSet.sources .run { if (sourceFileFilter != null) asFileTree.matching(sourceFileFilter) else this } - .files.map { sourceFile -> "${sourceSet.fragmentName}:${sourceFile.absolutePath}" } - }.toTypedArray() + .files.map { sourceFile -> + sourcesWithKnownFragment.add(sourceFile) + fragmentSourceCompilerArg(sourceFile, sourceSet.fragmentName) + } + }.toMutableList() + + val sourcesWithUnknownFragment = allSources - sourcesWithKnownFragment + val defaultFragmentName = defaultFragmentName.orNull + if (defaultFragmentName != null) { + sourcesWithUnknownFragment.mapTo(fragmentSourcesCompilerArgs) { fragmentSourceCompilerArg(it, defaultFragmentName) } + } + + return fragmentSourcesCompilerArgs.toTypedArray() +} internal val K2MultiplatformStructure.fragmentRefinesCompilerArgs: Array get() = refinesEdges.get().map { edge -> diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/Kotlin2JsCompile.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/Kotlin2JsCompile.kt index b41dcad75ce..784c802fafa 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/Kotlin2JsCompile.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/Kotlin2JsCompile.kt @@ -178,7 +178,7 @@ abstract class Kotlin2JsCompile @Inject constructor( if (multiPlatformEnabled.get()) { if (compilerOptions.usesK2.get()) { - args.fragmentSources = multiplatformStructure.fragmentSourcesCompilerArgs(sourceFileFilter) + args.fragmentSources = multiplatformStructure.fragmentSourcesCompilerArgs(sources.files, sourceFileFilter) } else { args.commonSources = commonSourceSet.asFileTree.toPathsArray() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompile.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompile.kt index 467bac4bfd7..505c80e43c0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompile.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompile.kt @@ -263,18 +263,18 @@ abstract class KotlinCompile @Inject constructor( } sources { args -> + val sourcesFiles = sources.asFileTree.files.toList() + val javaSourcesFiles = javaSources.files.toList() + val scriptSourcesFiles = scriptSources.asFileTree.files.toList() + if (multiPlatformEnabled.get()) { if (compilerOptions.usesK2.get()) { - args.fragmentSources = multiplatformStructure.fragmentSourcesCompilerArgs(sourceFileFilter) + args.fragmentSources = multiplatformStructure.fragmentSourcesCompilerArgs(sourcesFiles, sourceFileFilter) } else { args.commonSources = commonSourceSet.asFileTree.toPathsArray() } } - val sourcesFiles = sources.asFileTree.files.toList() - val javaSourcesFiles = javaSources.files.toList() - val scriptSourcesFiles = scriptSources.asFileTree.files.toList() - if (logger.isInfoEnabled) { logger.info("Kotlin source files: ${sourcesFiles.joinToString()}") logger.info("Java source files: ${javaSourcesFiles.joinToString()}") diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/K2MultiplatformStructureTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/K2MultiplatformStructureTest.kt index 9ca08a2b7af..9cea261f8b0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/K2MultiplatformStructureTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/K2MultiplatformStructureTest.kt @@ -7,21 +7,25 @@ package org.jetbrains.kotlin.gradle.unitTests +import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.newInstance import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments import org.jetbrains.kotlin.compilerRunner.ArgumentUtils +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.internal.CompilerArgumentAware import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerArgumentsProducer.CreateCompilerArgumentsContext.Companion.lenient import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.IR +import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName import org.jetbrains.kotlin.gradle.tasks.* import org.jetbrains.kotlin.gradle.tasks.K2MultiplatformStructure.Fragment import org.jetbrains.kotlin.gradle.tasks.K2MultiplatformStructure.RefinesEdge import org.jetbrains.kotlin.gradle.util.* +import java.io.File import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull @@ -46,6 +50,7 @@ class K2MultiplatformStructureTest { Fragment("c", project.files()) ) ) + structure.defaultFragmentName.set("a") val sourceArguments = K2JVMCompilerArguments() sourceArguments.configureK2Multiplatform(structure) @@ -98,6 +103,53 @@ class K2MultiplatformStructureTest { } } + @Test + fun `test - extra sources that were added to compile task directly is included into default source set fragment`() { + val targets = listOf(kotlin.jvm(), kotlin.js(), kotlin.linuxX64()) + kotlin.writeSource("commonMain") + kotlin.writeSource("jvmMain") + kotlin.writeSource("linuxX64Main") + kotlin.writeSource("jsMain") + + // Add extra source file + targets.forEach { target -> + val compileTask = target.compilations.get("main").compileTaskProvider.get() as KotlinCompileTool + val extraSourceFile = project.file("${target.disambiguateName("generated")}.kt").also { + it.parentFile.mkdirs() + it.writeText("fun generated() {}") + } + compileTask.source(extraSourceFile) + } + + project.evaluate() + + val actualFragmentSources = targets.associate { target -> + val compileTask = target.compilations.get("main").compileTaskProvider.get() as K2MultiplatformCompilationTask + val args = compileTask.buildCompilerArguments() + + target.name to args.fragmentSources?.toList().orEmpty().map { + val fragment = it.substringBefore(":") + val filePath = it.substringAfter(":") + "$fragment:${File(filePath).name}" + } + } + + assertEquals( + mapOf( + "jvm" to listOf("jvmMain:jvmMain.kt", "commonMain:commonMain.kt", "jvmMain:jvmGenerated.kt"), + "js" to listOf("jsMain:jsMain.kt", "commonMain:commonMain.kt", "jsMain:jsGenerated.kt"), + "linuxX64" to listOf("linuxX64Main:linuxX64Main.kt", "commonMain:commonMain.kt", "linuxX64Main:linuxX64Generated.kt"), + ), + actualFragmentSources + ) + } + + private fun KotlinMultiplatformExtension.writeSource(sourceSet: String, fileName: String = "${sourceSet}.kt") { + val srcDir = sourceSets.maybeCreate(sourceSet).kotlin.srcDirs.first() + srcDir.mkdirs() + srcDir.resolve(fileName).writeText("""fun $sourceSet() {}""") + } + private fun `test compilations multiplatformStructure configuration`(compilation: KotlinCompilation<*>) { val defaultSourceSet = compilation.defaultSourceSet /* Create an additional intermediate source set for testing */ @@ -172,6 +224,6 @@ private fun K2MultiplatformCompilationTask.buildCompilerArguments(): CommonCompi internal fun CommonCompilerArguments.configureK2Multiplatform(multiplatformStructure: K2MultiplatformStructure) { fragments = multiplatformStructure.fragmentsCompilerArgs - fragmentSources = multiplatformStructure.fragmentSourcesCompilerArgs() + fragmentSources = multiplatformStructure.fragmentSourcesCompilerArgs(emptyList()) fragmentRefines = multiplatformStructure.fragmentRefinesCompilerArgs }