[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
This commit is contained in:
Ilya Goncharov
2021-06-09 13:57:55 +03:00
committed by TeamCityServer
parent 03f85bea8f
commit 594cc29dd2
10 changed files with 235 additions and 11 deletions
@@ -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()
@@ -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")
@@ -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]) {
@@ -59,7 +59,7 @@ interface KotlinTargetConfigurator<KotlinTargetType : KotlinTarget> {
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<KotlinTargetType : KotlinTarget>
}
}
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<KotlinTargetType : KotlinTarget>
}
}
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))
@@ -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<KotlinSourceSet>()
private val jsOnlySourceSetsAttributes = mutableMapOf<KotlinSourceSet, KotlinJsCompilerAttribute>()
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<KotlinJsCompilation>()
.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)
}
@@ -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 =
@@ -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) {
@@ -119,6 +119,7 @@ open class KotlinJsTargetConfigurator :
isCanBeConsumed = true
attributes.attribute<Usage>(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(target))
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.common)
setupAsPublicConfigurationIfSupported(target)
}
}
}
@@ -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) {
@@ -143,6 +143,7 @@ open class KotlinJsIrTargetConfigurator() :
isCanBeConsumed = true
attributes.attribute<Usage>(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(target))
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.common)
setupAsPublicConfigurationIfSupported(target)
}
}
}