From a6d145eb4f61406651976d24846a59c1c41fd626 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Wed, 22 Feb 2023 14:02:49 +0100 Subject: [PATCH] [Gradle] Implement K2MultiplatformStructureTest ^KT-56210 Verification Pending --- .../jetbrains/kotlin/gradle/BaseGradleIT.kt | 5 + .../kotlin/gradle/testbase/BuildOptions.kt | 7 +- .../gradle/plugin/PropertiesProvider.kt | 3 + ...nCompilationLanguageVersionConfigurator.kt | 18 ++ .../factory/KotlinCompilationImplFactory.kt | 5 +- .../unitTests/K2MultiplatformStructureTest.kt | 172 ++++++++++++++++++ 6 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationLanguageVersionConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/K2MultiplatformStructureTest.kt diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt index 6a652752f4c..6fb6239baa1 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt @@ -275,6 +275,7 @@ abstract class BaseGradleIT { val configurationCacheProblems: ConfigurationCacheProblems = ConfigurationCacheProblems.FAIL, val warningMode: WarningMode = WarningMode.Fail, val useFir: Boolean = false, + val languageVersion: String? = null, val customEnvironmentVariables: Map = mapOf(), val dryRun: Boolean = false, val abiSnapshot: Boolean = false, @@ -940,6 +941,10 @@ abstract class BaseGradleIT { add("-Pkotlin.useK2=true") } + if(options.languageVersion != null) { + add("-Pkotlin.internal.languageVersion=${options.languageVersion}") + } + if (options.dryRun) { add("--dry-run") } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt index b97a7ff4f67..bd646e6dc35 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt @@ -12,8 +12,6 @@ import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMEN import org.jetbrains.kotlin.gradle.BaseGradleIT import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType import org.jetbrains.kotlin.gradle.report.BuildReportType -import org.jetbrains.kotlin.gradle.suppressDeprecationWarningsOn -import org.jetbrains.kotlin.gradle.util.AGPVersion import org.junit.jupiter.api.condition.OS import java.util.* @@ -38,6 +36,7 @@ data class BuildOptions( val buildReport: List = emptyList(), val useFir: Boolean = false, val usePreciseJavaTracking: Boolean? = null, + val languageVersion: String? = null, val freeArgs: List = emptyList(), val statisticsForceValidation: Boolean = true, val usePreciseOutputsBackup: Boolean? = null, @@ -144,6 +143,10 @@ data class BuildOptions( arguments.add("-Pkotlin.useK2=true") } + if (languageVersion != null) { + arguments.add("-Pkotlin.internal.languageVersion=$languageVersion") + } + if (usePreciseJavaTracking != null) { arguments.add("-Pkotlin.incremental.usePreciseJavaTracking=$usePreciseJavaTracking") } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt index fdab76c1e51..22d4d29f13b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt @@ -195,6 +195,9 @@ internal class PropertiesProvider private constructor(private val project: Proje val useK2: Boolean? get() = booleanProperty("kotlin.useK2") + val languageVersion: String? + get() = property("kotlin.internal.languageVersion") + val keepMppDependenciesIntactInPoms: Boolean? get() = booleanProperty("kotlin.mpp.keepMppDependenciesIntactInPoms") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationLanguageVersionConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationLanguageVersionConfigurator.kt new file mode 100644 index 00000000000..8bf297c55be --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationLanguageVersionConfigurator.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl + +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion +import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider +import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.factory.KotlinCompilationImplFactory + +internal object KotlinCompilationLanguageVersionConfigurator : KotlinCompilationImplFactory.PreConfigure { + override fun configure(compilation: KotlinCompilationImpl) { + compilation.project.kotlinPropertiesProvider.languageVersion?.let { languageVersionString -> + compilation.compilerOptions.options.languageVersion.convention(KotlinVersion.fromVersion(languageVersionString)) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/factory/KotlinCompilationImplFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/factory/KotlinCompilationImplFactory.kt index 66c9b576b72..db23efa035e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/factory/KotlinCompilationImplFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/factory/KotlinCompilationImplFactory.kt @@ -42,7 +42,10 @@ internal class KotlinCompilationImplFactory( DefaultProcessResourcesTaskNameFactory, private val preConfigureAction: PreConfigure = - KotlinCompilationK2MultiplatformConfigurator, + PreConfigure.composite( + KotlinCompilationK2MultiplatformConfigurator, + KotlinCompilationLanguageVersionConfigurator + ), private val postConfigureAction: PostConfigure = DefaultKotlinCompilationPostConfigure 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 new file mode 100644 index 00000000000..8cbd5a5f076 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/K2MultiplatformStructureTest.kt @@ -0,0 +1,172 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("FunctionName") + +package org.jetbrains.kotlin.gradle.unitTests + +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.K2NativeCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments +import org.jetbrains.kotlin.compilerRunner.ArgumentUtils +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.KotlinJsCompilerType.IR +import org.jetbrains.kotlin.gradle.tasks.K2Compile +import org.jetbrains.kotlin.gradle.tasks.K2MultiplatformStructure +import org.jetbrains.kotlin.gradle.tasks.K2MultiplatformStructure.DependsOnEdge +import org.jetbrains.kotlin.gradle.tasks.K2MultiplatformStructure.Fragment +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile +import org.jetbrains.kotlin.gradle.tasks.configureK2Multiplatform +import org.jetbrains.kotlin.gradle.util.applyMultiplatformPlugin +import org.jetbrains.kotlin.gradle.util.buildProject +import org.jetbrains.kotlin.gradle.util.enableDefaultStdlibDependency +import org.jetbrains.kotlin.gradle.util.main +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.fail + +class K2MultiplatformStructureTest { + private val project = buildProject { + /* Resolving dependencies is necessary for creating compiler arguments */ + enableDefaultStdlibDependency(false) + applyMultiplatformPlugin() + } + private val kotlin = project.multiplatformExtension + + @Test + fun `test - configureK2Multiplatform - then parse arguments`() { + val structure = project.objects.newInstance() + structure.dependsOnEdges.set(listOf(DependsOnEdge("a", "b"), DependsOnEdge("b", "c"))) + structure.fragments.set( + listOf( + Fragment("a", project.files("a.kt")), + Fragment("b", project.files("b.kt")), + Fragment("c", project.files()) + ) + ) + + val sourceArguments = K2JVMCompilerArguments() + sourceArguments.configureK2Multiplatform(structure) + + val parsedArguments = K2JVMCompilerArguments().apply { + parseCommandLineArguments(ArgumentUtils.convertArgumentsToStringList(sourceArguments), this) + } + + val fragments = parsedArguments.fragments ?: fail("Missing ${CommonCompilerArguments::fragments.name}") + assertEquals(listOf("a", "b", "c"), fragments.toList()) + + val fragmentSources = parsedArguments.fragmentSources ?: fail("Missing ${CommonCompilerArguments::fragmentSources.name}") + assertEquals( + listOf( + "a;${project.file("a.kt").absolutePath}", + "b;${project.file("b.kt").absolutePath}" + ), + fragmentSources.toList() + ) + + val dependsOn = parsedArguments.dependsOnDependencies ?: fail("Missing ${CommonCompilerArguments::dependsOnDependencies.name}") + assertEquals(listOf("a:b", "b:c"), dependsOn.toList()) + } + + @Test + fun `test - configure jvm compilation`() { + `test compilations multiplatformStructure configuration`(kotlin.jvm().compilations.main) + } + + @Test + fun `test - configure native compilation`() { + `test compilations multiplatformStructure configuration`(kotlin.linuxX64().compilations.main) + } + + @Test + fun `test - configure js compilation`() { + `test compilations multiplatformStructure configuration`(kotlin.js(IR).compilations.main) + } + + private fun `test compilations multiplatformStructure configuration`(compilation: KotlinCompilation<*>) { + val defaultSourceSet = compilation.defaultSourceSet + /* Create an additional intermediate source set for testing */ + kotlin.sourceSets.create("intermediateMain") { intermediateMain -> + intermediateMain.dependsOn(kotlin.sourceSets.getByName("commonMain")) + compilation.defaultSourceSet.dependsOn(intermediateMain) + } + + /* Create dummy source files */ + kotlin.sourceSets.all { sourceSet -> + sourceSet.kotlin.srcDirs.first().resolve("${sourceSet.name}.kt").apply { + parentFile.mkdirs() + writeText("object ${sourceSet.name}") + } + } + + /* Enable K2 if necessary */ + if (KotlinVersion.DEFAULT < KotlinVersion.KOTLIN_2_0) { + compilation.compilerOptions.options.languageVersion.set(KotlinVersion.KOTLIN_2_0) + } + + val compileTask = compilation.compileTaskProvider.get() as K2Compile + + /* check dependsOnEdges */ + assertEquals( + setOf( + DependsOnEdge(defaultSourceSet.name, "commonMain"), + DependsOnEdge(defaultSourceSet.name, "intermediateMain"), + DependsOnEdge("intermediateMain", "commonMain") + ), + compileTask.multiplatformStructure.dependsOnEdges.get().toSet() + ) + + /* check source files */ + assertEquals( + mapOf( + "commonMain" to project.files("src/commonMain/kotlin/commonMain.kt").toSet(), + "intermediateMain" to project.files("src/intermediateMain/kotlin/intermediateMain.kt").toSet(), + defaultSourceSet.name to project.files("src/${defaultSourceSet.name}/kotlin/${defaultSourceSet.name}.kt").toSet() + ), + compileTask.multiplatformStructure.fragments.get().associate { fragment -> + fragment.fragmentName to fragment.sources.files + } + ) + + val args = compileTask.buildCompilerArguments() + + if (args.commonSources != null) { + fail("Unexpected ${CommonCompilerArguments::commonSources.name} in K2 compilation: ${args.commonSources}") + } + + if (args.fragments == null) { + fail("Missing ${CommonCompilerArguments::fragments.name} in K2 compilation") + } + + if (args.fragmentSources == null) { + fail("Missing ${CommonCompilerArguments::fragmentSources.name} in K2 compilation") + } + + if (args.dependsOnDependencies == null) { + fail("Missing ${CommonCompilerArguments::dependsOnDependencies.name} in K2 compilation") + } + } +} + +private fun K2Compile.buildCompilerArguments(): CommonCompilerArguments { + /* KotlinNative implements CompilerArgumentAware, but does not adhere to its contract */ + + if (this is KotlinNativeCompile) { + val arguments = K2NativeCompilerArguments() + val argsList = this.buildKotlinNativeKlibCompilerArgs() + parseCommandLineArguments(argsList, arguments) + return arguments + } + @Suppress("UNCHECKED_CAST") + this as CompilerArgumentAware + val args = createCompilerArgs() + setupCompilerArgs(args) + return args +}