[Gradle, Native/JS] Specify FQ module name for klibs

By default, a unique name for a klibrary equals to a klib
file name. In the MPP plugin, the file name equals to a
Gradle project name. But the project name is not unique
enough. E.g. there is a situation when there are several projects
with the same simple name inside a single Gradle build. If
one of these projects depends on another, the compiler
will report a false cyclic dependency error.

This patch partially fixes this problem by specifying a custom
unique name for produced klibs based on project group and name.
We still can get a unique name clash in a case of equal groups
and project names. But in this case the cyclic dependency error
looks much more logical for user.

Also this patch specifies short names for native klibs to avoid
long declaration prefixes in framework headers.

Issue #KT-36721 Fixed
Issue #KT-38220 Fixed
This commit is contained in:
Ilya Matveev
2020-02-21 19:18:13 +07:00
parent e4f4b4eef0
commit edf2b18f4f
19 changed files with 223 additions and 11 deletions
@@ -51,6 +51,7 @@ class GeneralNativeIT : BaseGradleIT() {
assertTasksExecuted(":two:compileKotlinLinux")
assertNotContains("Parallel in-process execution of the Kotlin/Native compiler detected.")
}
}
// TODO: Move native specific tests from NewMultiplatformIT here.
}
@@ -21,10 +21,13 @@ import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
import org.jetbrains.kotlin.gradle.util.*
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.library.KLIB_PROPERTY_SHORT_NAME
import org.jetbrains.kotlin.library.KLIB_PROPERTY_UNIQUE_NAME
import org.junit.Assert
import org.junit.Ignore
import org.junit.Test
import java.io.File
import java.util.*
import java.util.jar.JarFile
import java.util.zip.ZipFile
import kotlin.test.assertEquals
@@ -2530,4 +2533,41 @@ class NewMultiplatformIT : BaseGradleIT() {
}
}
}
@Test
fun testKlibsWithTheSameProjectName() = with(transformProjectWithPluginsDsl("new-mpp-klibs-with-same-name")) {
// KT-36721.
build("assemble") {
assertSuccessful()
assertTasksExecuted(
":foo:foo:compileKotlinJs",
":foo:foo:compileKotlinLinux",
":foo:compileKotlinJs",
":foo:compileKotlinLinux",
":compileKotlinJs",
":compileKotlinLinux"
)
fun getManifest(relativePath: String): Properties =
with(ZipFile(projectDir.resolve(relativePath))) {
return this.getInputStream(getEntry("default/manifest")).use { stream ->
Properties().apply { load(stream) }
}
}
val interopManifest = getManifest("foo/build/classes/kotlin/linux/main/foo-cinterop-bar.klib")
assertEquals("org.sample.one.foo-cinterop-bar", interopManifest[KLIB_PROPERTY_UNIQUE_NAME])
val nativeManifest = getManifest("foo/build/classes/kotlin/linux/main/foo.klib")
assertEquals("org.sample.one.foo", nativeManifest[KLIB_PROPERTY_UNIQUE_NAME])
// Check the short name that is used as a prefix in generated ObjC headers.
assertEquals("foo", nativeManifest[KLIB_PROPERTY_SHORT_NAME])
val jsManifest = projectDir.resolve("foo/build/classes/kotlin/js/main/default/manifest")
.inputStream().use { stream ->
Properties().apply { load(stream) }
}
assertEquals("org.sample.one.foo", jsManifest[KLIB_PROPERTY_UNIQUE_NAME])
}
}
}
@@ -0,0 +1,28 @@
plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>")
}
group = "org.sample.root"
allprojects {
repositories {
mavenLocal()
jcenter()
}
}
kotlin {
linuxX64("linux")
js {
nodejs()
}
sourceSets["commonMain"].dependencies {
implementation(kotlin("stdlib-common"))
implementation(project(":foo"))
}
sourceSets["jsMain"].dependencies {
implementation(kotlin("stdlib-js"))
}
}
@@ -0,0 +1,25 @@
plugins {
kotlin("multiplatform")
}
group="org.sample.one"
kotlin {
linuxX64("linux") {
val bar by compilations["main"].cinterops.creating
}
js {
nodejs()
}
sourceSets["commonMain"].dependencies {
implementation(kotlin("stdlib-common"))
implementation(project(":foo:foo"))
}
sourceSets["jsMain"].dependencies {
implementation(kotlin("stdlib-js"))
}
}
@@ -0,0 +1,22 @@
plugins {
kotlin("multiplatform")
}
group="org.sample.two"
kotlin {
linuxX64("linux") {
val bar by compilations["main"].cinterops.creating
}
js {
nodejs()
}
sourceSets["commonMain"].dependencies {
implementation(kotlin("stdlib-common"))
}
sourceSets["jsMain"].dependencies {
implementation(kotlin("stdlib-js"))
}
}
@@ -0,0 +1,5 @@
import foo.*
fun foofoo() {
println("foofoo: ${answer()}")
}
@@ -0,0 +1,6 @@
import foo.*
actual fun foo() {
println("foo: ${question()}")
foofoo()
}
@@ -0,0 +1,8 @@
language = C
package = foo
---
const char* question() {
return "To be, or not to be";
}
@@ -0,0 +1,2 @@
kotlin.native.disableCompilerDaemon=true
kotlin.js.compiler=ir
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
mavenLocal()
jcenter()
gradlePluginPortal()
}
}
include("foo")
include("foo:foo")
@@ -17,4 +17,6 @@ internal const val ENABLE_DCE = "-Xir-dce"
internal const val GENERATE_D_TS = "-Xgenerate-dts"
internal const val PRODUCE_JS = "-Xir-produce-js"
internal const val PRODUCE_UNZIPPED_KLIB = "-Xir-produce-klib-dir"
internal const val PRODUCE_UNZIPPED_KLIB = "-Xir-produce-klib-dir"
internal const val MODULE_NAME = "-Xir-module-name"
@@ -7,14 +7,15 @@ package org.jetbrains.kotlin.gradle.targets.js.ir
import org.gradle.api.tasks.bundling.Zip
import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptions
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.KotlinJsIrSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.KotlinOnlyTargetConfigurator
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetWithTestsConfigurator
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsReportAggregatingTestRun
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.testing.internal.kotlinTestRegistry
import org.jetbrains.kotlin.gradle.testing.testTaskName
import org.jetbrains.kotlin.gradle.utils.klibModuleName
open class KotlinJsIrTargetConfigurator(kotlinPluginVersion: String) :
KotlinOnlyTargetConfigurator<KotlinJsIrCompilation, KotlinJsIrTarget>(true, true, kotlinPluginVersion),
@@ -63,17 +64,25 @@ open class KotlinJsIrTargetConfigurator(kotlinPluginVersion: String) :
override fun configureCompilations(target: KotlinJsIrTarget) {
super.configureCompilations(target)
target.compilations.all {
it.compileKotlinTask.kotlinOptions {
target.compilations.all { compilation ->
compilation.compileKotlinTask.kotlinOptions {
configureOptions()
freeCompilerArgs += listOf(
DISABLE_PRE_IR,
PRODUCE_UNZIPPED_KLIB
)
// Configure FQ module name to avoid cyclic dependencies in klib manifests (see KT-36721).
val baseName = if (compilation.name == KotlinCompilation.MAIN_COMPILATION_NAME) {
target.project.name
} else {
"${target.project.name}_${compilation.name}"
}
freeCompilerArgs += listOf("$MODULE_NAME=${target.project.klibModuleName(baseName)}")
}
it.binaries
compilation.binaries
.withType(JsIrBinary::class.java)
.all {
it.linkTask.configure { linkTask ->
@@ -24,6 +24,8 @@ import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.utils.klibModuleName
import org.jetbrains.kotlin.konan.library.KLIB_INTEROP_IR_PROVIDER_IDENTIFIER
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.CompilerOutputKind.*
@@ -297,6 +299,14 @@ open class KotlinNativeCompile : AbstractKotlinNativeCompile<KotlinCommonOptions
override val baseName: String
get() = if (compilation.isMainCompilation) project.name else "${project.name}_${compilation.name}"
@get:Input
val moduleName: String by project.provider {
project.klibModuleName(baseName)
}
@get:Input
val shortModuleName: String by project.provider { baseName }
// Inputs and outputs.
// region Sources.
@InputFiles
@@ -378,6 +388,9 @@ open class KotlinNativeCompile : AbstractKotlinNativeCompile<KotlinCommonOptions
override fun buildCompilerArgs(): List<String> = mutableListOf<String>().apply {
addAll(super.buildCompilerArgs())
// Configure FQ module name to avoid cyclic dependencies in klib manifests (see KT-36721).
addArg("-module-name", moduleName)
add("-Xshort-module-name=$shortModuleName")
val friends = friendModule?.files
if (friends != null && friends.isNotEmpty()) {
addArg("-friend-modules", friends.map { it.absolutePath }.joinToString(File.pathSeparator))
@@ -885,15 +898,22 @@ open class CInteropProcess : DefaultTask() {
val interopName: String
@Internal get() = settings.name
val outputFileName: String
@Internal get() = with(CompilerOutputKind.LIBRARY) {
val baseName = settings.compilation.let {
val baseKlibName: String
@Internal get() {
val compilationPrefix = settings.compilation.let {
if (it.isMainCompilation) project.name else it.name
}
val suffix = suffix(konanTarget)
return "$baseName-cinterop-$interopName$suffix"
return "$compilationPrefix-cinterop-$interopName"
}
val outputFileName: String
@Internal get() = with(CompilerOutputKind.LIBRARY) {
"$baseKlibName${suffix(konanTarget)}"
}
val moduleName: String
@Input get() = project.klibModuleName(baseKlibName)
@get:Internal
val outputFile: File
get() = outputFileProvider.get()
@@ -961,6 +981,10 @@ open class CInteropProcess : DefaultTask() {
addArgs("-compiler-option", allHeadersDirs.map { "-I${it.absolutePath}" })
addArgs("-headerFilterAdditionalSearchPrefix", headerFilterDirs.map { it.absolutePath })
if (project.konanVersion.isAtLeast(1, 4, 0)) {
addArg("-Xmodule-name", moduleName)
}
addAll(extraOpts)
}
@@ -0,0 +1,11 @@
/*
* Copyright 2010-2020 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.utils
import org.gradle.api.Project
internal fun Project.klibModuleName(baseName: String = project.name): String =
if (group.toString().isNotEmpty()) "$group.$baseName" else baseName