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