From 594cc29dd2637c36dcdb18efc018923dfaf5216e Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Wed, 9 Jun 2021 13:57:55 +0300 Subject: [PATCH] [Gradle, JS] Necessary to use approach with traversing all source sets Because common source sets included into JS compilation, it can be detected as JS associated compilations. So necessary to set attribute about JS compiler attribute only (at least at the beginning) to JS only source sets (source sets which are not included into any non js compilations) [Gradle, JS] Add test with resolving JS variant of MPP library [Gradle, JS] Add additional filtering of JS targets [Gradle, JS] Add test on local resolve ^KT-47163 fixed ^KT-47114 fixed --- .../kotlin/gradle/Kotlin2JsGradlePluginIT.kt | 69 ++++++++++++++++++ .../kotlin/gradle/NewMultiplatformIT.kt | 70 +++++++++++++++++++ .../sample-app/build.gradle | 4 +- .../gradle/plugin/KotlinTargetConfigurator.kt | 11 ++- .../plugin/sources/KotlinSourceSetFactory.kt | 68 +++++++++++++++++- .../targets/js/KotlinJsCompilerAttribute.kt | 7 +- .../gradle/targets/js/KotlinJsTarget.kt | 7 +- .../targets/js/KotlinJsTargetConfigurator.kt | 1 + .../gradle/targets/js/ir/KotlinJsIrTarget.kt | 8 ++- .../js/ir/KotlinJsIrTargetConfigurator.kt | 1 + 10 files changed, 235 insertions(+), 11 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt index 3871fa3b7e1..2e8bee54c56 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt @@ -10,11 +10,13 @@ import org.gradle.api.logging.LogLevel import org.gradle.api.logging.configuration.WarningMode import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType +import org.jetbrains.kotlin.gradle.plugin.sources.METADATA_CONFIGURATION_NAME_SUFFIX import org.jetbrains.kotlin.gradle.targets.js.ir.KLIB_TYPE import org.jetbrains.kotlin.gradle.targets.js.npm.* import org.jetbrains.kotlin.gradle.tasks.USING_JS_INCREMENTAL_COMPILATION_MESSAGE import org.jetbrains.kotlin.gradle.tasks.USING_JS_IR_BACKEND_MESSAGE import org.jetbrains.kotlin.gradle.util.* +import org.junit.Assert import org.junit.Assume.assumeFalse import org.junit.Test import java.io.File @@ -743,6 +745,73 @@ abstract class AbstractKotlin2JsGradlePluginIT(val irBackend: Boolean) : BaseGra } } + @Test + fun testResolveJsProjectDependencyToMetadata() = with(Project("kotlin-js-browser-project")) { + setupWorkingDir() + gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl) + gradleSettingsScript().modify(::transformBuildScriptWithPluginsDsl) + gradleProperties().appendText("kotlin.js.compiler=both") + + val compiler = if (irBackend) "IR" else "LEGACY" + + val pathPrefix = "metadataDependency: " + + val appBuild = projectDir.resolve("app/build.gradle.kts") + appBuild.modify { + it.replace("target {", "js($compiler) {") + } + appBuild.appendText( + "\n" + """ + kotlin.sourceSets { + val main by getting { + dependencies { + // add these dependencies to check that they are resolved to metadata + api(project(":base")) + implementation(project(":base")) + compileOnly(project(":base")) + runtimeOnly(project(":base")) + } + } + } + + task("printMetadataFiles") { + doFirst { + listOf("api", "implementation", "compileOnly", "runtimeOnly").forEach { kind -> + val configuration = configurations.getByName(kind + "DependenciesMetadata") + configuration.files.forEach { println("$pathPrefix" + configuration.name + "->" + it.name) } + } + } + } + """.trimIndent() + ) + + val metadataDependencyRegex = "$pathPrefix(.*?)->(.*)".toRegex() + + build( + "printMetadataFiles", + options = defaultBuildOptions().copy(jsCompilerType = if (irBackend) KotlinJsCompilerType.IR else KotlinJsCompilerType.LEGACY) + ) { + assertSuccessful() + + val suffix = if (irBackend) "ir" else "legacy" + val ext = if (irBackend) "klib" else "jar" + + val expectedFileName = "base-$suffix.$ext" + + val paths = metadataDependencyRegex + .findAll(output).map { it.groupValues[1] to it.groupValues[2] } + .filter { (_, f) -> "base" in f } + .toSet() + + Assert.assertEquals( + listOf("api", "implementation", "compileOnly", "runtimeOnly").map { + "$it$METADATA_CONFIGURATION_NAME_SUFFIX" to expectedFileName + }.toSet(), + paths + ) + } + } + @Test fun testNoUnintendedDevDependencies() = with(Project("kotlin-js-browser-project")) { setupWorkingDir() diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt index efa557ce9ed..b1bc9b82db0 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt @@ -905,6 +905,76 @@ class NewMultiplatformIT : BaseGradleIT() { } } + @Test + fun testResolveJsPartOfMppLibDependencyToMetadata() { + val libProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app") + val appProject = Project("sample-app", gradleVersion, "new-mpp-lib-and-app") + + libProject.build( + "publish", + options = defaultBuildOptions().copy(jsCompilerType = BOTH) + ) { + assertSuccessful() + } + val localRepo = libProject.projectDir.resolve("repo") + val localRepoUri = localRepo.toURI() + + with(appProject) { + setupWorkingDir() + + val pathPrefix = "metadataDependency: " + + gradleBuildScript().appendText( + "\n" + """ + repositories { maven { url '$localRepoUri' } } + + kotlin.sourceSets { + nodeJsMain { + dependencies { + // add these dependencies to check that they are resolved to metadata + api 'com.example:sample-lib-nodejs:1.0' + implementation 'com.example:sample-lib-nodejs:1.0' + compileOnly 'com.example:sample-lib-nodejs:1.0' + runtimeOnly 'com.example:sample-lib-nodejs:1.0' + } + } + } + + task('printMetadataFiles') { + doFirst { + ['Api', 'Implementation', 'CompileOnly', 'RuntimeOnly'].each { kind -> + def configuration = configurations.getByName("nodeJsMain${'$'}kind" + '$METADATA_CONFIGURATION_NAME_SUFFIX') + configuration.files.each { println '$pathPrefix' + configuration.name + '->' + it.name } + } + } + } + """.trimIndent() + ) + val metadataDependencyRegex = "$pathPrefix(.*?)->(.*)".toRegex() + + build( + "printMetadataFiles", + options = defaultBuildOptions().copy(jsCompilerType = IR) + ) { + assertSuccessful() + + val expectedFileName = "sample-lib-nodejsir-1.0.klib" + + val paths = metadataDependencyRegex + .findAll(output).map { it.groupValues[1] to it.groupValues[2] } + .filter { (_, f) -> "sample-lib" in f } + .toSet() + + Assert.assertEquals( + listOf("Api", "Implementation", "CompileOnly", "RuntimeOnly").map { + "nodeJsMain$it$METADATA_CONFIGURATION_NAME_SUFFIX" to expectedFileName + }.toSet(), + paths + ) + } + } + } + @Test fun testResolveMppProjectDependencyToMetadata() { val libProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app/build.gradle index f6b212bc047..3a6a2078c1e 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app/build.gradle @@ -46,6 +46,8 @@ kotlin { } } + js('nodeJs') + targets { fromPreset(presets.jvm, 'jvm6') { attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 6) @@ -61,7 +63,7 @@ kotlin { } } } - fromPreset(presets.js, 'nodeJs') + fromPreset(presets.linuxX64, 'linux64') configure([linux64]) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt index ff22c33ffc4..5641c4d7514 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt @@ -59,7 +59,7 @@ interface KotlinTargetConfigurator { fun defineConfigurationsForTarget(target: KotlinTargetType) fun configureArchivesAndComponent(target: KotlinTargetType) fun configureBuild(target: KotlinTargetType) - fun configureSourceSet(target: KotlinTarget) + fun configureSourceSet(target: KotlinTargetType) fun configurePlatformSpecificModel(target: KotlinTargetType) = Unit } @@ -105,7 +105,7 @@ abstract class AbstractKotlinTargetConfigurator } } - override fun configureSourceSet(target: KotlinTarget) { + override fun configureSourceSet(target: KotlinTargetType) { val project = target.project target.compilations.all { compilation -> @@ -254,7 +254,12 @@ abstract class AbstractKotlinTargetConfigurator } } - private fun addDependsOnTaskInOtherProjects(project: Project, taskProvider: TaskProvider<*>, useDependedOn: Boolean, configurationName: String) { + private fun addDependsOnTaskInOtherProjects( + project: Project, + taskProvider: TaskProvider<*>, + useDependedOn: Boolean, + configurationName: String + ) { val configuration = project.configurations.getByName(configurationName) taskProvider.configure { task -> task.dependsOn(configuration.getTaskDependencyFromProjectDependency(useDependedOn, taskProvider.name)) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt index 88273013b1e..5bc9ba1663a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt @@ -7,11 +7,16 @@ package org.jetbrains.kotlin.gradle.plugin.sources import org.gradle.api.NamedDomainObjectFactory import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration import org.gradle.api.attributes.Usage -import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType -import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages -import org.jetbrains.kotlin.gradle.plugin.usageByName +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.targets +import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute +import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget +import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import java.io.File @@ -92,10 +97,67 @@ internal class DefaultKotlinSourceSetFactory( if (project.isKotlinGranularMetadataEnabled) { attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA)) } + + project.whenEvaluated { + setJsCompilerIfNecessary(sourceSet, this@apply) + } } } } + // KT-47163 + // It is necessary to set jsCompilerAttribute to configurations which associated with ONLY js source sets + // Otherwise configuration cannot be resolved because ambiguity between IR and Legacy variants inside one module + private val notOnlyJsSourceSets = mutableSetOf() + + private val jsOnlySourceSetsAttributes = mutableMapOf() + + private fun setJsCompilerIfNecessary(sourceSet: KotlinSourceSet, configuration: Configuration) { + if (sourceSet in notOnlyJsSourceSets) return + + if (sourceSet in jsOnlySourceSetsAttributes) { + configuration.attributes.attribute( + KotlinJsCompilerAttribute.jsCompilerAttribute, + jsOnlySourceSetsAttributes.getValue(sourceSet) + ) + return + } + + project.kotlinExtension.targets + .filter { it !is KotlinJsIrTarget && it !is KotlinJsTarget } + .forEach { target -> + target.compilations.forEach { compilation -> + notOnlyJsSourceSets.addAll(compilation.allKotlinSourceSets) + } + } + + if (sourceSet in notOnlyJsSourceSets) return + + fun chooseCompilerAttribute(target: KotlinTarget): KotlinJsCompilerAttribute { + if (target is KotlinJsIrTarget) { + return KotlinJsCompilerAttribute.ir + } + + target as KotlinJsTarget + return if (target.irTarget != null) KotlinJsCompilerAttribute.ir else KotlinJsCompilerAttribute.legacy + } + + project.kotlinExtension.targets + .filter { it is KotlinJsTarget || it is KotlinJsIrTarget } + .forEach { target -> + target.compilations + .filterIsInstance() + .forEach { compilation -> + if (sourceSet in compilation.allKotlinSourceSets) { + val compilerAttribute = chooseCompilerAttribute(target) + jsOnlySourceSetsAttributes[sourceSet] = compilerAttribute + configuration.attributes.attribute(KotlinJsCompilerAttribute.jsCompilerAttribute, compilerAttribute) + return + } + } + } + } + override fun doCreateSourceSet(name: String): DefaultKotlinSourceSet { return DefaultKotlinSourceSet(project, name) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilerAttribute.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilerAttribute.kt index f7026ba5954..86266c2dc89 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilerAttribute.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilerAttribute.kt @@ -6,8 +6,9 @@ package org.jetbrains.kotlin.gradle.targets.js import org.gradle.api.Named -import org.gradle.api.attributes.Attribute -import org.gradle.api.attributes.AttributesSchema +import org.gradle.api.attributes.* +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages import java.io.Serializable // For Gradle attributes @@ -15,6 +16,8 @@ import java.io.Serializable enum class KotlinJsCompilerAttribute : Named, Serializable { legacy, ir, + + @Deprecated("This value is not used in Gradle plugin. You don't need to use it. It will be removed in next major.") both; override fun getName(): String = diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt index ff803f0458d..c7834ae075d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt @@ -52,7 +52,12 @@ constructor( } internal val commonFakeApiElementsConfigurationName: String - get() = disambiguateName("commonFakeApiElements") + get() = lowerCamelCaseName( + irTarget?.let { + this.disambiguationClassifierInPlatform + } ?: disambiguationClassifier, + "commonFakeApiElements" + ) val disambiguationClassifierInPlatform: String? get() = if (irTarget != null) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt index c70e206a3c8..031428b3390 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt @@ -119,6 +119,7 @@ open class KotlinJsTargetConfigurator : isCanBeConsumed = true attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(target)) attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.common) + setupAsPublicConfigurationIfSupported(target) } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt index dd73f65ca68..c2a57d917b1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt @@ -72,7 +72,13 @@ constructor( } internal val commonFakeApiElementsConfigurationName: String - get() = disambiguateName("commonFakeApiElements") + get() = lowerCamelCaseName( + if (mixedMode) + disambiguationClassifierInPlatform + else + disambiguationClassifier, + "commonFakeApiElements" + ) val disambiguationClassifierInPlatform: String? get() = if (mixedMode) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTargetConfigurator.kt index 1c140b17b5b..6017c3ff82a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTargetConfigurator.kt @@ -143,6 +143,7 @@ open class KotlinJsIrTargetConfigurator() : isCanBeConsumed = true attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(target)) attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.common) + setupAsPublicConfigurationIfSupported(target) } } } \ No newline at end of file