[Commonizer] Implement :native:kotlin-klib-commonizer:api with support for library commonization

- Implement new Gradle module ':native:kotlin-klib-commonizer'
- Implement new NativeKlibCommonize task
- Implement CommonizerTarget.identityString
This commit is contained in:
sebastian.sellmair
2021-02-17 09:33:03 +01:00
committed by Space
parent 6d019d9544
commit 4500b6ce74
59 changed files with 1646 additions and 565 deletions
+1
View File
@@ -768,6 +768,7 @@ tasks {
register("toolsTest") {
dependsOn(":tools:kotlinp:test")
dependsOn(":native:kotlin-klib-commonizer:test")
dependsOn(":native:kotlin-klib-commonizer-api:test")
}
register("examplesTest") {
+2 -1
View File
@@ -53,7 +53,8 @@ fun Task.dependsOnKotlinPluginInstall() {
":kotlin-scripting-compiler-embeddable:install",
":kotlin-scripting-compiler-impl-embeddable:install",
":kotlin-test-js-runner:install",
":native:kotlin-klib-commonizer-embeddable:install"
":native:kotlin-klib-commonizer-embeddable:install",
":native:kotlin-klib-commonizer-api:install"
)
}
@@ -42,6 +42,7 @@ dependencies {
implementation(kotlinStdlib())
implementation(project(":kotlin-util-klib"))
implementation(project(":native:kotlin-klib-commonizer-api"))
compileOnly(project(":kotlin-reflect-api"))
compileOnly(project(":kotlin-android-extensions"))
compileOnly(project(":kotlin-build-common"))
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2021 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.compilerRunner
import org.gradle.api.Project
import org.jetbrains.kotlin.descriptors.commonizer.CliCommonizer
/**
* Creates an instance of [CliCommonizer] that is backed by [KotlinNativeCommonizerToolRunner] to adhere to user defined settings
* when executing the commonizer (like jvm arguments, running in separate process, etc)
*/
internal fun GradleCliCommonizer(project: Project): CliCommonizer {
return CliCommonizer(CliCommonizer.Executor { arguments ->
KotlinNativeCommonizerToolRunner(project).run(arguments)
})
}
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import java.io.File
internal class KotlinNativeKlibCommonizerToolRunner(project: Project) : KotlinToolRunner(project) {
internal class KotlinNativeCommonizerToolRunner(project: Project) : KotlinToolRunner(project) {
override val displayName get() = "Kotlin/Native KLIB commonizer"
override val mainClass: String get() = "org.jetbrains.kotlin.descriptors.commonizer.cli.CommonizerCLI"
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.gradle.targets.native.internal
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.compilerRunner.KotlinNativeKlibCommonizerToolRunner
import org.jetbrains.kotlin.compilerRunner.KotlinNativeCommonizerToolRunner
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.targets.native.internal.SuccessMarker.Companion.getSuccessMarker
@@ -24,7 +24,6 @@ import java.nio.file.*
import java.nio.file.attribute.*
import java.time.*
import java.util.*
import javax.inject.Inject
internal const val COMMONIZER_TASK_NAME = "runCommonizer"
@@ -190,7 +189,7 @@ internal fun Project.createTempNativeDistributionCommonizerOutputDirectory(targe
fun callCommonizerCLI(project: Project, commandLineArguments: List<String>) {
if (commandLineArguments.isEmpty()) return
KotlinNativeKlibCommonizerToolRunner(project).run(commandLineArguments)
KotlinNativeCommonizerToolRunner(project).run(commandLineArguments)
}
private fun renameDirectory(source: File, destination: File) {
+44
View File
@@ -0,0 +1,44 @@
import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader
plugins {
kotlin("jvm")
id("jps-compatible")
}
kotlin {
explicitApi()
}
description = "Kotlin KLIB Library Commonizer API"
publish()
dependencies {
implementation(kotlinStdlib())
implementation(project(":native:kotlin-native-utils"))
testImplementation(project(":kotlin-test::kotlin-test-junit"))
testImplementation(commonDep("junit:junit"))
testImplementation(projectTests(":compiler:tests-common"))
testRuntimeOnly(project(":native:kotlin-klib-commonizer"))
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
tasks.register("downloadNativeCompiler") {
doFirst {
NativeCompilerDownloader(project).downloadIfNeeded()
}
}
projectTest(parallel = false) {
dependsOn(":dist")
dependsOn("downloadNativeCompiler")
workingDir = projectDir
environment("KONAN_HOME", NativeCompilerDownloader(project).compilerDirectory.absolutePath)
}
runtimeJar()
sourcesJar()
javadocJar()
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer
import java.io.File
import java.net.URLClassLoader
import kotlin.jvm.Throws
public fun CliCommonizer(classpath: Iterable<File>): CliCommonizer {
return CliCommonizer(URLClassLoader(classpath.map { it.absoluteFile.toURI().toURL() }.toTypedArray()))
}
public fun CliCommonizer(classLoader: ClassLoader): CliCommonizer {
return CliCommonizer(CommonizerClassLoaderExecutor(classLoader))
}
public class CliCommonizer(private val executor: Executor) : Commonizer {
public fun interface Executor {
public operator fun invoke(arguments: List<String>)
}
override fun commonizeLibraries(
konanHome: File,
inputLibraries: Set<File>,
dependencyLibraries: Set<File>,
outputCommonizerTarget: SharedCommonizerTarget,
outputDirectory: File
) {
val arguments = mutableListOf<String>().apply {
add("native-klib-commonize")
add("-distribution-path"); add(konanHome.absolutePath)
add("-input-libraries"); add(inputLibraries.joinToString(";") { it.absolutePath })
add("-dependency-libraries"); add(dependencyLibraries.joinToString(";") { it.absolutePath })
add("-output-commonizer-target"); add(outputCommonizerTarget.identityString)
add("-output-path"); add(outputDirectory.absolutePath)
}
executor(arguments)
}
}
private class CommonizerClassLoaderExecutor(private val commonizerClassLoader: ClassLoader) : CliCommonizer.Executor {
companion object {
private const val commonizerMainClass = "org.jetbrains.kotlin.descriptors.commonizer.cli.CommonizerCLI"
private const val commonizerMainFunction = "main"
}
@Throws(Throwable::class)
override fun invoke(arguments: List<String>) {
val commonizerMainClass = commonizerClassLoader.loadClass(commonizerMainClass)
val commonizerMainMethod = commonizerMainClass.methods.singleOrNull { it.name == commonizerMainFunction }
?: throw IllegalArgumentException(
"Missing or conflicting $commonizerMainFunction function in " +
"Class ${commonizerMainClass.name} from ClassLoader $commonizerClassLoader"
)
commonizerMainMethod.invoke(null, arguments.toTypedArray())
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer
import java.io.File
import java.io.Serializable
import kotlin.jvm.Throws
public interface Commonizer : Serializable {
@Throws(Throwable::class)
public fun commonizeLibraries(
konanHome: File,
inputLibraries: Set<File>,
dependencyLibraries: Set<File>,
outputCommonizerTarget: SharedCommonizerTarget,
outputDirectory: File
)
}
@@ -0,0 +1,100 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.Serializable
// N.B. TargetPlatform/SimplePlatform are non exhaustive enough to address both target platforms such as
// JVM, JS and concrete Kotlin/Native targets, e.g. macos_x64, ios_x64, linux_x64.
public sealed class CommonizerTarget : Serializable {
final override fun toString(): String = identityString
}
public data class LeafCommonizerTarget public constructor(val name: String) : CommonizerTarget() {
public constructor(konanTarget: KonanTarget) : this(konanTarget.name)
public val konanTargetOrNull: KonanTarget? = KonanTarget.predefinedTargets[name]
public val konanTarget: KonanTarget get() = konanTargetOrNull ?: error("Unknown KonanTarget: $name")
}
public data class SharedCommonizerTarget(val targets: Set<CommonizerTarget>) : CommonizerTarget() {
public constructor(vararg targets: CommonizerTarget) : this(targets.toSet())
public constructor(vararg targets: KonanTarget) : this(targets.toSet())
public constructor(targets: Iterable<KonanTarget>) : this(targets.map(::LeafCommonizerTarget).toSet())
init {
require(targets.isNotEmpty())
}
}
public fun CommonizerTarget(konanTargets: Iterable<KonanTarget>): CommonizerTarget {
val konanTargetsSet = konanTargets.toSet()
require(konanTargetsSet.isNotEmpty()) { "Empty set of of konanTargets" }
val leafTargets = konanTargetsSet.map(::LeafCommonizerTarget)
return leafTargets.singleOrNull() ?: SharedCommonizerTarget(leafTargets.toSet())
}
public fun CommonizerTarget(konanTarget: KonanTarget): LeafCommonizerTarget {
return LeafCommonizerTarget(konanTarget)
}
public fun CommonizerTarget(konanTarget: KonanTarget, vararg konanTargets: KonanTarget): SharedCommonizerTarget {
val targets = ArrayList<KonanTarget>(konanTargets.size + 1).apply {
add(konanTarget)
addAll(konanTargets)
}
return SharedCommonizerTarget(targets.map(::LeafCommonizerTarget).toSet())
}
public val CommonizerTarget.identityString: String
get() = when (this) {
is LeafCommonizerTarget -> name
is SharedCommonizerTarget -> identityString
}
private val SharedCommonizerTarget.identityString: String
get() {
val segments = targets.map(CommonizerTarget::identityString).sorted()
return segments.joinToString(
separator = ", ", prefix = "(", postfix = ")"
)
}
public val CommonizerTarget.prettyName: String
get() = when (this) {
is LeafCommonizerTarget -> "[$name]"
is SharedCommonizerTarget -> prettyName(null)
}
public fun SharedCommonizerTarget.prettyName(highlightedChild: CommonizerTarget?): String {
return targets
.sortedWith(compareBy<CommonizerTarget> { it.level }.thenBy { it.identityString }).joinToString(", ", "[", "]") { child ->
when (child) {
is LeafCommonizerTarget -> child.name
is SharedCommonizerTarget -> child.prettyName(highlightedChild)
} + if (child == highlightedChild) "(*)" else ""
}
}
public val CommonizerTarget.konanTargets: Set<KonanTarget>
get() {
return when (this) {
is LeafCommonizerTarget -> setOf(konanTarget)
is SharedCommonizerTarget -> targets.flatMap { it.konanTargets }.toSet()
}
}
public val CommonizerTarget.level: Int
get() {
return when (this) {
is LeafCommonizerTarget -> return 0
is SharedCommonizerTarget -> targets.maxOf { it.level } + 1
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer
import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_COMMON_LIBS_DIR
import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR
import java.io.File
public fun interface CommonizerOutputLayout {
public fun getTargetDirectory(root: File, target: CommonizerTarget): File
}
public object NativeDistributionCommonizerOutputLayout : CommonizerOutputLayout {
override fun getTargetDirectory(root: File, target: CommonizerTarget): File {
return when (target) {
is LeafCommonizerTarget -> root.resolve(KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR).resolve(target.name)
is SharedCommonizerTarget -> root.resolve(KONAN_DISTRIBUTION_COMMON_LIBS_DIR)
}
}
}
public object HierarchicalCommonizerOutputLayout : CommonizerOutputLayout {
override fun getTargetDirectory(root: File, target: CommonizerTarget): File {
return root.resolve(target.identityString)
}
}
@@ -0,0 +1,196 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer
import org.jetbrains.kotlin.descriptors.commonizer.IdentityStringSyntaxNode.LeafTargetSyntaxNode
import org.jetbrains.kotlin.descriptors.commonizer.IdentityStringSyntaxNode.SharedTargetSyntaxNode
import org.jetbrains.kotlin.descriptors.commonizer.IdentityStringToken.*
public fun parseCommonizerTarget(identityString: String): CommonizerTarget {
try {
val tokens = tokenizeIdentityString(identityString)
val syntaxTree = parser(tokens) ?: error("Failed building syntax tree. $identityString")
check(syntaxTree.remaining.isEmpty()) { "Failed building syntax tree. Unexpected remaining tokens ${syntaxTree.remaining}" }
return buildCommonizerTarget(syntaxTree.value)
} catch (e: Throwable) {
throw IllegalArgumentException("Failed parsing CommonizerTarget from \"$identityString\"", e)
}
}
//region Tokens
private fun tokenizeIdentityString(identityString: String): List<IdentityStringToken> {
var remainingString = identityString
val tokenizer = sharedTargetStartTokenizer + sharedTargetEndTokenizer + separatorTokenizer + wordTokenizer
return mutableListOf<IdentityStringToken>().apply {
while (remainingString.isNotEmpty()) {
val generatedToken = tokenizer.nextToken(remainingString)
?: error("Unexpected token at $remainingString")
remainingString = generatedToken.remaining
add(generatedToken.token)
}
}.toList()
}
private sealed class IdentityStringToken {
data class Word(val value: String) : IdentityStringToken()
object Separator : IdentityStringToken()
object SharedTargetStart : IdentityStringToken()
object SharedTargetEnd : IdentityStringToken()
final override fun toString(): String {
return when (this) {
is Word -> value
is Separator -> ", "
is SharedTargetStart -> "["
is SharedTargetEnd -> "]"
}
}
}
private data class GeneratedToken(val token: IdentityStringToken, val remaining: String)
private interface IdentityStringTokenizer {
fun nextToken(value: String): GeneratedToken?
}
private operator fun IdentityStringTokenizer.plus(other: IdentityStringTokenizer): IdentityStringTokenizer {
return CompositeIdentityStringTokenizer(this, other)
}
private data class CompositeIdentityStringTokenizer(
val first: IdentityStringTokenizer,
val second: IdentityStringTokenizer
) : IdentityStringTokenizer {
override fun nextToken(value: String): GeneratedToken? {
return first.nextToken(value) ?: second.nextToken(value)
}
}
private data class RegexIdentityStringTokenizer(
val regex: Regex,
val token: (String) -> IdentityStringToken
) : IdentityStringTokenizer {
override fun nextToken(value: String): GeneratedToken? {
val firstMatchResult = regex.findAll(value, 0).firstOrNull() ?: return null
val range = firstMatchResult.range
if (range.first != 0) return null
return GeneratedToken(token(firstMatchResult.value), value.drop(firstMatchResult.value.length))
}
}
private val sharedTargetStartTokenizer =
RegexIdentityStringTokenizer(Regex.fromLiteral("(")) { SharedTargetStart }
private val sharedTargetEndTokenizer =
RegexIdentityStringTokenizer(Regex.fromLiteral(")")) { SharedTargetEnd }
private val separatorTokenizer =
RegexIdentityStringTokenizer(Regex("""\s*,\s*""")) { Separator }
private val wordTokenizer =
RegexIdentityStringTokenizer(Regex("\\w+"), IdentityStringToken::Word)
//endregion
//region Syntax Tree
private val parser = anyOf(SharedTargetParser, LeafTargetParser)
private data class ParserOutput<out T : Any>(val value: T, val remaining: List<IdentityStringToken>)
private interface Parser<out T : Any> {
operator fun invoke(tokens: List<IdentityStringToken>): ParserOutput<T>?
}
private fun <T : Any> anyOf(vararg parser: Parser<T>): Parser<T> {
return AnyOfParser(parser.toList())
}
private data class AnyOfParser<T : Any>(val parsers: List<Parser<T>>) : Parser<T> {
override fun invoke(tokens: List<IdentityStringToken>): ParserOutput<T>? {
return parsers.mapNotNull { parser -> parser(tokens) }.firstOrNull()
}
}
private fun <T : Any> Parser<T>.oneOrMore(): Parser<List<T>> {
return OneOrMoreParser(this)
}
private data class OneOrMoreParser<T : Any>(val parser: Parser<T>) : Parser<List<T>> {
override fun invoke(tokens: List<IdentityStringToken>): ParserOutput<List<T>>? {
val outputs = mutableListOf<T>()
var remainingTokens = tokens
while (true) {
val output = parser(remainingTokens) ?: break
if (output.remaining == remainingTokens) break
outputs.add(output.value)
remainingTokens = output.remaining
}
if (outputs.isEmpty()) {
return null
}
return ParserOutput(outputs.toList(), remainingTokens)
}
}
private fun <T : Any> Parser<T>.ignore(token: IdentityStringToken): Parser<T> {
return IgnoreTokensParser(this, token)
}
private data class IgnoreTokensParser<T : Any>(val parser: Parser<T>, val ignoredToken: IdentityStringToken) : Parser<T> {
override fun invoke(tokens: List<IdentityStringToken>): ParserOutput<T>? {
return parser(
if (tokens.firstOrNull() == ignoredToken) tokens.drop(1) else tokens
)
}
}
private object LeafTargetParser : Parser<LeafTargetSyntaxNode> {
override fun invoke(tokens: List<IdentityStringToken>): ParserOutput<LeafTargetSyntaxNode>? {
val nextToken = tokens.firstOrNull() as? Word ?: return null
return ParserOutput(LeafTargetSyntaxNode(nextToken), tokens.drop(1))
}
}
private object SharedTargetParser : Parser<SharedTargetSyntaxNode> {
override fun invoke(tokens: List<IdentityStringToken>): ParserOutput<SharedTargetSyntaxNode>? {
if (tokens.firstOrNull() !is SharedTargetStart) return null
val innerParser = anyOf(LeafTargetParser, SharedTargetParser).ignore(Separator).oneOrMore()
val innerParserOutput = innerParser(tokens.drop(1)) ?: return null
val closingToken = innerParserOutput.remaining.firstOrNull()
if (closingToken != SharedTargetEnd) {
error("Missing ']' at ${tokens.joinToString("")}")
}
return ParserOutput(SharedTargetSyntaxNode(innerParserOutput.value), innerParserOutput.remaining.drop(1))
}
}
private sealed class IdentityStringSyntaxNode {
data class LeafTargetSyntaxNode(val token: Word) : IdentityStringSyntaxNode()
data class SharedTargetSyntaxNode(val children: List<IdentityStringSyntaxNode>) : IdentityStringSyntaxNode()
}
//endregion Tree
//region Build CommonizerTarget
private fun buildCommonizerTarget(node: IdentityStringSyntaxNode): CommonizerTarget {
return when (node) {
is LeafTargetSyntaxNode -> LeafCommonizerTarget(node.token.value)
is SharedTargetSyntaxNode -> SharedCommonizerTarget(
node.children.map { child -> buildCommonizerTarget(child) }.toSet()
)
}
}
//endregion
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer
import org.jetbrains.kotlin.descriptors.commonizer.utils.konanHome
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import kotlin.test.Test
class CliCommonizerTest {
@get:Rule
val temporaryOutputDirectory = TemporaryFolder()
@Test
fun invokeCliWithEmptyArguments() {
val commonizer = CliCommonizer(this::class.java.classLoader)
commonizer.commonizeLibraries(
konanHome = konanHome,
inputLibraries = emptySet(),
dependencyLibraries = emptySet(),
outputCommonizerTarget = CommonizerTarget(KonanTarget.LINUX_X64, KonanTarget.MACOS_X64),
outputDirectory = temporaryOutputDirectory.root
)
}
}
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer
import org.jetbrains.kotlin.descriptors.commonizer.utils.konanHome
import org.jetbrains.kotlin.konan.target.KonanTarget.LINUX_ARM64
import org.jetbrains.kotlin.konan.target.KonanTarget.LINUX_X64
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import java.io.File
import kotlin.test.Test
import kotlin.test.assertTrue
import kotlin.test.fail
class CommonizeLibcurlTest {
@get:Rule
val temporaryOutputDirectory = TemporaryFolder()
@Test
fun commonizeSuccessfully() {
val libraries = File("testData/libcurl").walkTopDown().filter { it.isFile && it.extension == "klib" }.toSet()
val commonizer = CliCommonizer(this::class.java.classLoader)
commonizer.commonizeLibraries(
konanHome = konanHome,
inputLibraries = libraries,
dependencyLibraries = emptySet(),
outputCommonizerTarget = CommonizerTarget(LINUX_ARM64, LINUX_X64),
outputDirectory = temporaryOutputDirectory.root
)
val x64OutputDirectory = temporaryOutputDirectory.root.resolve(CommonizerTarget(LINUX_X64).identityString)
val arm64OutputDirectory = temporaryOutputDirectory.root.resolve(CommonizerTarget(LINUX_ARM64).identityString)
val commonOutputDirectory = temporaryOutputDirectory.root.resolve(CommonizerTarget(LINUX_X64, LINUX_ARM64).identityString)
assertTrue(
x64OutputDirectory.exists(),
"Missing output directory for x64 target"
)
assertTrue(
arm64OutputDirectory.exists(),
"Missing output directory for arm64 target"
)
assertTrue(
commonOutputDirectory.exists(),
"Missing output directory for commonized x64&arm64 target"
)
fun assertContainsKnmFiles(file: File) {
assertTrue(
file.walkTopDown().any { it.extension == "knm" },
"Expected directory ${file.name} to contain at least one knm file"
)
}
assertContainsKnmFiles(x64OutputDirectory)
assertContainsKnmFiles(arm64OutputDirectory)
assertContainsKnmFiles(commonOutputDirectory)
fun assertContainsManifestWithContent(directory: File, content: String) {
val manifest = directory.walkTopDown().firstOrNull { it.name == "manifest" }
?: fail("${directory.name} does not contain any manifest")
assertTrue(
content in manifest.readText(),
"Expected manifest in ${directory.name} to contain $content\n${manifest.readText()}"
)
}
assertContainsManifestWithContent(x64OutputDirectory, "native_targets=linux_x64")
assertContainsManifestWithContent(arm64OutputDirectory, "native_targets=linux_arm64")
assertContainsManifestWithContent(commonOutputDirectory, "native_targets=linux_x64 linux_arm64")
}
}
@@ -0,0 +1,137 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.KonanTarget.*
import kotlin.test.Test
import kotlin.test.assertEquals
class CommonizerTargetIdentityStringTest {
@Test
fun leafTargets() {
KonanTarget.predefinedTargets.values.forEach { konanTarget ->
assertEquals(konanTarget.name, CommonizerTarget(konanTarget).identityString)
assertEquals(CommonizerTarget(konanTarget), parseCommonizerTarget(CommonizerTarget(konanTarget).identityString))
}
}
@Test
fun `simple shared targets are invariant under konanTarget order`() {
val macosFirst = CommonizerTarget(MACOS_X64, LINUX_X64)
val linuxFirst = CommonizerTarget(LINUX_X64, MACOS_X64)
assertEquals(macosFirst, linuxFirst)
assertEquals(macosFirst.identityString, linuxFirst.identityString)
assertEquals(linuxFirst, parseCommonizerTarget(linuxFirst.identityString))
assertEquals(macosFirst, parseCommonizerTarget(macosFirst.identityString))
assertEquals(macosFirst, parseCommonizerTarget(linuxFirst.identityString))
assertEquals(linuxFirst, parseCommonizerTarget(macosFirst.identityString))
}
@Test
fun `hierarchical commonizer targets`() {
val hierarchy = SharedCommonizerTarget(
CommonizerTarget(LINUX_X64, MACOS_X64),
CommonizerTarget(IOS_ARM64, IOS_X64)
)
assertEquals(setOf(LINUX_X64, MACOS_X64, IOS_ARM64, IOS_X64), hierarchy.konanTargets)
assertEquals(hierarchy, parseCommonizerTarget(hierarchy.identityString))
}
@Test
fun `multilevel hierarchical commonizer targets`() {
val hierarchy = SharedCommonizerTarget(
SharedCommonizerTarget(
SharedCommonizerTarget(
SharedCommonizerTarget(
CommonizerTarget(LINUX_X64, MACOS_X64),
CommonizerTarget(IOS_X64, IOS_ARM64)
),
CommonizerTarget(LINUX_ARM32_HFP)
),
CommonizerTarget(LINUX_MIPSEL32)
),
CommonizerTarget(WATCHOS_X86, WATCHOS_ARM64)
)
assertEquals(hierarchy, parseCommonizerTarget(hierarchy.identityString))
}
@Test
fun `parsing CommonizerTarget`() {
val target = parseCommonizerTarget("(x, (x, y, (a, b), (b, c)))")
assertEquals(
SharedCommonizerTarget(
LeafCommonizerTarget("x"),
SharedCommonizerTarget(
LeafCommonizerTarget("x"),
LeafCommonizerTarget("y"),
SharedCommonizerTarget(
LeafCommonizerTarget("a"),
LeafCommonizerTarget("b"),
),
SharedCommonizerTarget(
LeafCommonizerTarget("b"),
LeafCommonizerTarget("c")
)
)
),
target
)
}
@Test(expected = IllegalArgumentException::class)
fun `fail parsing CommonizerTarget 1`() {
parseCommonizerTarget("xxx,")
}
@Test(expected = IllegalArgumentException::class)
fun `fail parsing CommonizerTarget 2`() {
parseCommonizerTarget("")
}
@Test(expected = IllegalArgumentException::class)
fun `fail parsing CommonizerTarget 3`() {
parseCommonizerTarget("()")
}
@Test(expected = IllegalArgumentException::class)
fun `fail parsing CommonizerTarget 4`() {
parseCommonizerTarget("(xxx")
}
@Test(expected = IllegalArgumentException::class)
fun `fail parsing CommonizerTarget 5`() {
parseCommonizerTarget("xxx)")
}
@Test(expected = IllegalArgumentException::class)
fun `fail parsing CommonizerTarget 6`() {
parseCommonizerTarget("(xxx")
}
@Test(expected = IllegalArgumentException::class)
fun `fail parsing CommonizerTarget 7`() {
parseCommonizerTarget("(xxx yyy)")
}
@Test(expected = IllegalArgumentException::class)
fun `fail parsing CommonizerTarget 8`() {
parseCommonizerTarget(" ")
}
@Test(expected = IllegalArgumentException::class)
fun `fail parsing CommonizerTarget 9`() {
parseCommonizerTarget("xxx?")
}
@Test(expected = IllegalArgumentException::class)
fun `fail parsing CommonizerTarget 10`() {
parseCommonizerTarget("(x, (x, y)")
}
}
@@ -0,0 +1,88 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer
import org.junit.Test
import kotlin.test.assertEquals
class CommonizerTargetPrettyNameTest {
@Test
fun leafTargetNames() {
listOf(
Triple("foo", "[foo]", FOO),
Triple("bar", "[bar]", BAR),
Triple("baz_123", "[baz_123]", BAZ),
).forEach { (name, prettyName, target: LeafCommonizerTarget) ->
assertEquals(name, target.name)
assertEquals(prettyName, target.prettyName)
}
}
@Test
fun sharedTargetNames() {
listOf(
"[foo]" to SharedTarget(FOO),
"[bar, foo]" to SharedTarget(FOO, BAR),
"[bar, baz_123, foo]" to SharedTarget(FOO, BAR, BAZ),
"[bar, baz_123, foo, [bar, foo]]" to SharedTarget(FOO, BAR, BAZ, SharedTarget(FOO, BAR))
).forEach { (prettyName, target: SharedCommonizerTarget) ->
assertEquals(prettyName, target.prettyName)
}
}
@Test
fun prettyCommonizedName() {
val sharedTarget = SharedTarget(FOO, BAR, BAZ)
listOf(
"[bar, baz_123, foo(*)]" to FOO,
"[bar(*), baz_123, foo]" to BAR,
"[bar, baz_123(*), foo]" to BAZ,
"[bar, baz_123, foo]" to sharedTarget,
).forEach { (prettyCommonizerName, target: CommonizerTarget) ->
assertEquals(prettyCommonizerName, sharedTarget.prettyName(target))
}
}
@Test
fun prettyNestedName() {
val target = parseCommonizerTarget("(a, b, (c, (d, e)))") as SharedCommonizerTarget
assertEquals(
"[a, b, [c, [d, e]]]", target.prettyName
)
assertEquals(
"[a, b, [c, [d, e(*)]]]", target.prettyName(LeafCommonizerTarget("e"))
)
assertEquals(
"[a, b, [c, [d, e](*)]]", target.prettyName(parseCommonizerTarget("(d, e)"))
)
assertEquals(
"[a, b, [c, [d, e]](*)]", target.prettyName(parseCommonizerTarget("(c, (d, e))"))
)
assertEquals(
"[a, b(*), [c, [d, e]]]", target.prettyName(LeafCommonizerTarget("b"))
)
}
@Test(expected = IllegalArgumentException::class)
fun sharedTargetNoInnerTargets() {
SharedCommonizerTarget(emptySet<CommonizerTarget>())
}
private companion object {
val FOO = LeafCommonizerTarget("foo")
val BAR = LeafCommonizerTarget("bar")
val BAZ = LeafCommonizerTarget("baz_123")
@Suppress("TestFunctionName")
fun SharedTarget(vararg targets: CommonizerTarget) = SharedCommonizerTarget(linkedSetOf(*targets))
}
}
@@ -0,0 +1,14 @@
/*
* 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.descriptors.commonizer.utils
import java.io.File
internal val konanHome: File
get() {
val konanHomePath = System.getenv("KONAN_HOME")?.toString() ?: error("Missing KONAN_HOME environment variable")
return File(konanHomePath)
}
Binary file not shown.
Binary file not shown.
+3
View File
@@ -16,11 +16,13 @@ configurations {
dependencies {
embedded(project(":kotlinx-metadata-klib")) { isTransitive = false }
embedded(project(":kotlinx-metadata")) { isTransitive = false }
embedded(project(":native:kotlin-klib-commonizer-api")) { isTransitive = false }
// N.B. The order of "kotlinx-metadata*" dependencies makes sense for runtime classpath
// of the "runCommonizer" task. Please, don't mix them up.
compileOnly(project(":kotlinx-metadata-klib")) { isTransitive = false }
compileOnly(project(":kotlinx-metadata")) { isTransitive = false }
compileOnly(project(":native:kotlin-klib-commonizer-api")) { isTransitive = false }
compileOnly(project(":compiler:cli-common"))
compileOnly(project(":compiler:ir.serialization.common"))
compileOnly(project(":compiler:frontend"))
@@ -38,6 +40,7 @@ dependencies {
testImplementation(projectTests(":compiler:tests-common"))
testImplementation(project(":kotlinx-metadata-klib")) { isTransitive = false }
testImplementation(project(":kotlinx-metadata")) { isTransitive = false }
testImplementation(project(":native:kotlin-klib-commonizer-api"))
}
val runCommonizer by tasks.registering(JavaExec::class) {
@@ -5,16 +5,19 @@
package org.jetbrains.kotlin.descriptors.commonizer
import org.jetbrains.kotlin.descriptors.commonizer.konan.TargetedNativeManifestDataProvider
import org.jetbrains.kotlin.descriptors.commonizer.stats.StatsCollector
class CommonizerParameters(
val resultsConsumer: ResultsConsumer,
val manifestDataProvider: TargetedNativeManifestDataProvider,
val statsCollector: StatsCollector? = null,
val progressLogger: ((String) -> Unit)? = null
) {
// use linked hash map to preserve order
private val _targetProviders = LinkedHashMap<LeafTarget, TargetProvider>()
private val _targetProviders = LinkedHashMap<LeafCommonizerTarget, TargetProvider>()
val targetProviders: List<TargetProvider> get() = _targetProviders.values.toList()
val sharedTarget: SharedTarget get() = SharedTarget(_targetProviders.keys)
val sharedTarget: SharedCommonizerTarget get() = SharedCommonizerTarget(_targetProviders.keys)
// common module dependencies (ex: Kotlin stdlib)
var dependencyModulesProvider: ModulesProvider? = null
@@ -30,14 +33,6 @@ class CommonizerParameters(
return this
}
private var _resultsConsumer: ResultsConsumer? = null
var resultsConsumer: ResultsConsumer
get() = _resultsConsumer ?: error("Results consumer has not been set")
set(value) {
check(_resultsConsumer == null)
_resultsConsumer = value
}
fun getCommonModuleNames(): Set<String> {
if (_targetProviders.size < 2) return emptySet() // too few targets
@@ -1,36 +0,0 @@
/*
* Copyright 2010-2019 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.descriptors.commonizer
import org.jetbrains.kotlin.konan.target.KonanTarget
// N.B. TargetPlatform/SimplePlatform are non exhaustive enough to address both target platforms such as
// JVM, JS and concrete Kotlin/Native targets, e.g. macos_x64, ios_x64, linux_x64.
sealed class CommonizerTarget {
abstract val name: String
abstract val prettyName: String
fun prettyCommonizedName(sharedTarget: SharedTarget): String = when {
this == sharedTarget -> prettyName
this in sharedTarget.targets -> sharedTarget.targets.joinToString(prefix = "[", postfix = "]") {
if (it == this) "${it.name}(*)" else it.name
}
else -> error("Target $prettyName is not in ${sharedTarget.prettyName}")
}
}
data class LeafTarget(override val name: String, val konanTarget: KonanTarget? = null) : CommonizerTarget() {
override val prettyName get() = "[$name]"
}
data class SharedTarget(val targets: Set<CommonizerTarget>) : CommonizerTarget() {
init {
require(targets.isNotEmpty())
}
override val name get() = targets.joinToString(prefix = "[", postfix = "]") { it.name }
override val prettyName get() = name
}
@@ -0,0 +1,20 @@
/*
* 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.descriptors.commonizer
import org.jetbrains.kotlin.konan.library.*
import java.io.File
internal data class KonanDistribution(val root: File)
internal val KonanDistribution.stdlib: File
get() = root.resolve(konanCommonLibraryPath(KONAN_STDLIB_NAME))
internal val KonanDistribution.klibDir: File
get() = root.resolve(KONAN_DISTRIBUTION_KLIB_DIR)
internal val KonanDistribution.platformLibsDir: File
get() = klibDir.resolve(KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR)
@@ -0,0 +1,44 @@
/*
* 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.descriptors.commonizer
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.backend.common.serialization.metadata.metadataVersion
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeLibrary
import org.jetbrains.kotlin.library.ToolingSingleFileKlibResolveStrategy
import org.jetbrains.kotlin.library.resolveSingleFileKlib
import org.jetbrains.kotlin.util.Logger
import java.io.File
internal fun interface NativeLibraryLoader {
operator fun invoke(file: File): NativeLibrary
}
internal class DefaultNativeLibraryLoader(
private val logger: Logger
) : NativeLibraryLoader {
override fun invoke(file: File): NativeLibrary {
val library = resolveSingleFileKlib(
libraryFile = org.jetbrains.kotlin.konan.file.File(file.path),
logger = logger,
strategy = ToolingSingleFileKlibResolveStrategy
)
if (library.versions.metadataVersion == null)
logger.fatal("Library does not have metadata version specified in manifest: $file")
val metadataVersion = library.metadataVersion
if (metadataVersion?.isCompatible() != true)
logger.fatal(
"""
Library has incompatible metadata version ${metadataVersion ?: "\"unknown\""}: $file
Please make sure that all libraries passed to commonizer compatible metadata version ${KlibMetadataVersion.INSTANCE}
""".trimIndent()
)
return NativeLibrary(library)
}
}
@@ -5,9 +5,14 @@
package org.jetbrains.kotlin.descriptors.commonizer
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeSensitiveManifestData
import org.jetbrains.kotlin.library.SerializedMetadata
import java.io.File
internal fun buildResultsConsumer(init: ResultsConsumerBuilder.() -> Unit): ResultsConsumer {
return ResultsConsumerBuilder().apply(init).build()
}
interface ResultsConsumer {
enum class Status { NOTHING_TO_DO, DONE }
@@ -18,23 +23,70 @@ interface ResultsConsumer {
override val libraryName: String get() = originalLocation.name
}
class Commonized(override val libraryName: String, val metadata: SerializedMetadata) : ModuleResult()
class Commonized(
override val libraryName: String, val metadata: SerializedMetadata, val manifest: NativeSensitiveManifestData
) : ModuleResult()
}
/**
* Consume a single [ModuleResult] for the specified [CommonizerTarget].
*/
fun consume(target: CommonizerTarget, moduleResult: ModuleResult)
fun consume(target: CommonizerTarget, moduleResult: ModuleResult) = Unit
/**
* Mark the specified [CommonizerTarget] as fully consumed.
* It's forbidden to make subsequent [consume] calls for fully consumed targets.
*/
fun targetConsumed(target: CommonizerTarget)
fun targetConsumed(target: CommonizerTarget) = Unit
/**
* Notify that all results have been consumed.
* It's forbidden to make any subsequent [consume] and [targetConsumed] calls after this call.
*/
fun allConsumed(status: Status)
fun allConsumed(status: Status) = Unit
}
internal class ResultsConsumerBuilder {
private var resultsConsumer: ResultsConsumer? = null
infix fun add(consumer: ResultsConsumer) {
val resultsConsumer = this.resultsConsumer
if (resultsConsumer == null) {
this.resultsConsumer = consumer
return
}
this.resultsConsumer = resultsConsumer + consumer
}
fun build(): ResultsConsumer {
return resultsConsumer ?: object : ResultsConsumer {}
}
}
operator fun ResultsConsumer.plus(other: ResultsConsumer?): ResultsConsumer {
if (other == null) return this
if (this is CompositeResultsConsumer) {
return CompositeResultsConsumer(consumers + other)
}
return CompositeResultsConsumer(listOf(this, other))
}
private class CompositeResultsConsumer(val consumers: List<ResultsConsumer>) : ResultsConsumer {
override fun consume(target: CommonizerTarget, moduleResult: ResultsConsumer.ModuleResult) {
consumers.forEach { consumer ->
consumer.consume(target, moduleResult)
}
}
override fun targetConsumed(target: CommonizerTarget) {
consumers.forEach { consumer ->
consumer.targetConsumed(target)
}
}
override fun allConsumed(status: ResultsConsumer.Status) {
consumers.forEach { consumer ->
consumer.allConsumed(status)
}
}
}
@@ -10,7 +10,7 @@ import org.jetbrains.kotlin.library.SerializedMetadata
import java.io.File
class TargetProvider(
val target: LeafTarget,
val target: LeafCommonizerTarget,
val modulesProvider: ModulesProvider,
val dependencyModulesProvider: ModulesProvider?
)
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer.cli
internal object DependencyLibrariesOptionType : LibrariesSetOptionType(
mandatory = true,
alias = "dependency-libraries",
description = "';' separated list of klib file paths that can be used as dependency"
)
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer.cli
internal object InputLibrariesOptionType : LibrariesSetOptionType(
mandatory = true,
alias = "input-libraries",
description = "';' separated list of klib file paths that will get commonized"
)
@@ -0,0 +1,25 @@
/*
* 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.descriptors.commonizer.cli
import java.io.File
internal abstract class LibrariesSetOptionType(
mandatory: Boolean,
alias: String,
description: String
) : OptionType<List<File>>(
mandatory = mandatory,
alias = alias,
description = description
) {
override fun parse(rawValue: String, onError: (reason: String) -> Nothing): Option<List<File>> {
if (rawValue.isBlank()) {
return Option(this, emptyList())
}
return Option(this, rawValue.split(";").map(::File))
}
}
@@ -0,0 +1,23 @@
/*
* 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.descriptors.commonizer.cli
import org.jetbrains.kotlin.descriptors.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.parseCommonizerTarget
internal object OutputCommonizerTargetOptionType : OptionType<SharedCommonizerTarget>(
alias = "output-commonizer-target",
description = "Shared commonizer target representing the commonized output hierarchy",
mandatory = true
) {
override fun parse(rawValue: String, onError: (reason: String) -> Nothing): Option<SharedCommonizerTarget> {
return try {
Option(this, parseCommonizerTarget(rawValue) as SharedCommonizerTarget)
} catch (t: Throwable) {
onError("Failed parsing output-commonizer-target ($rawValue): ${t.message}")
}
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer.cli
import org.jetbrains.kotlin.util.Logger
fun Logger.toProgressLogger(): Logger {
return ProgressLogger(this)
}
private class ProgressLogger(private val logger: Logger) : Logger by logger {
override fun log(message: String) {
logger.log("* $message")
}
override fun warning(message: String) {
logger.log("* $message")
}
}
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.descriptors.commonizer.cli
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeDistributionCommonizer.StatsType
import org.jetbrains.kotlin.descriptors.commonizer.stats.StatsType
internal object StatsTypeOptionType : OptionType<StatsType>("log-stats", DESCRIPTION, mandatory = false) {
override fun parse(rawValue: String, onError: (reason: String) -> Nothing): Option<StatsType> {
@@ -40,7 +40,21 @@ internal enum class TaskType(
NativeDistributionOptionType
),
::NativeDistributionListTargets
);
),
NATIVE_KLIB_COMMONIZE(
"native-klib-commonize",
"Commonize any platform-specific libraries",
listOf(
NativeDistributionOptionType,
OutputOptionType,
InputLibrariesOptionType,
DependencyLibrariesOptionType,
OutputCommonizerTargetOptionType,
),
::NativeKlibCommonize
)
;
companion object {
fun getByAlias(alias: String) = values().firstOrNull { it.alias == alias }
@@ -5,8 +5,20 @@
package org.jetbrains.kotlin.descriptors.commonizer.cli
import org.jetbrains.kotlin.descriptors.commonizer.*
import org.jetbrains.kotlin.descriptors.commonizer.konan.*
import org.jetbrains.kotlin.descriptors.commonizer.konan.CopyUnconsumedModulesAsIsConsumer
import org.jetbrains.kotlin.descriptors.commonizer.konan.CopyStdlibResultsConsumer
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeDistributionCommonizer
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeDistributionCommonizer.StatsType
import org.jetbrains.kotlin.descriptors.commonizer.konan.ModuleSerializer
import org.jetbrains.kotlin.descriptors.commonizer.repository.*
import org.jetbrains.kotlin.descriptors.commonizer.repository.EmptyRepository
import org.jetbrains.kotlin.descriptors.commonizer.repository.FilesRepository
import org.jetbrains.kotlin.descriptors.commonizer.repository.KonanDistributionRepository
import org.jetbrains.kotlin.descriptors.commonizer.repository.Repository
import org.jetbrains.kotlin.descriptors.commonizer.stats.FileStatsOutput
import org.jetbrains.kotlin.descriptors.commonizer.stats.StatsCollector
import org.jetbrains.kotlin.descriptors.commonizer.stats.StatsType
import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_KLIB_DIR
import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -35,11 +47,52 @@ internal class NativeDistributionListTargets(options: Collection<Option<*>>) : T
}
}
internal class NativeKlibCommonize(options: Collection<Option<*>>) : Task(options) {
override val category: Category = Category.COMMONIZATION
override fun execute(logPrefix: String) {
val distribution = KonanDistribution(getMandatory<File, NativeDistributionOptionType>())
val destination = getMandatory<File, OutputOptionType>()
val targetLibraries = getMandatory<List<File>, InputLibrariesOptionType>()
val dependencyLibraries = getMandatory<List<File>, DependencyLibrariesOptionType>()
val outputCommonizerTarget = getMandatory<SharedCommonizerTarget, OutputCommonizerTargetOptionType>()
val statsType = getOptional<StatsType, StatsTypeOptionType> { it == "log-stats" } ?: StatsType.NONE
val konanTargets = outputCommonizerTarget.konanTargets
val logger = CliLoggerAdapter(2)
val libraryLoader = DefaultNativeLibraryLoader(logger)
val statsCollector = StatsCollector(statsType, outputCommonizerTarget.konanTargets.toList())
val repository = FilesRepository(targetLibraries.toSet(), libraryLoader)
val resultsConsumer = buildResultsConsumer {
this add ModuleSerializer(destination, HierarchicalCommonizerOutputLayout)
this add CopyUnconsumedModulesAsIsConsumer(
repository, destination, konanTargets, NativeDistributionCommonizerOutputLayout, logger.toProgressLogger()
)
this add LoggingResultsConsumer(outputCommonizerTarget, logger.toProgressLogger())
}
NativeDistributionCommonizer(
konanDistribution = distribution,
repository = repository,
dependencies = KonanDistributionRepository(distribution, outputCommonizerTarget.konanTargets, libraryLoader) +
FilesRepository(dependencyLibraries.toSet(), libraryLoader),
libraryLoader = libraryLoader,
targets = outputCommonizerTarget.konanTargets.toList(),
resultsConsumer = resultsConsumer,
statsCollector = statsCollector,
logger = logger
).run()
statsCollector?.writeTo(FileStatsOutput(destination, statsType.name.toLowerCase()))
}
}
internal class NativeDistributionCommonize(options: Collection<Option<*>>) : Task(options) {
override val category get() = Category.COMMONIZATION
override fun execute(logPrefix: String) {
val distribution = getMandatory<File, NativeDistributionOptionType>()
val distribution = KonanDistribution(getMandatory<File, NativeDistributionOptionType>())
val destination = getMandatory<File, OutputOptionType>()
val targets = getMandatory<List<KonanTarget>, NativeTargetsOptionType>()
@@ -47,35 +100,43 @@ internal class NativeDistributionCommonize(options: Collection<Option<*>>) : Tas
val copyEndorsedLibs = getOptional<Boolean, BooleanOptionType> { it == "copy-endorsed-libs" } ?: false
val statsType = getOptional<StatsType, StatsTypeOptionType> { it == "log-stats" } ?: StatsType.NONE
val targetNames = targets.joinToString { "[${it.name}]" }
val descriptionSuffix = estimateLibrariesCount(distribution, targets)?.let { " ($it items)" } ?: ""
val description = "${logPrefix}Preparing commonized Kotlin/Native libraries for targets $targetNames$descriptionSuffix"
val logger = CliLoggerAdapter(2)
val libraryLoader = DefaultNativeLibraryLoader(logger)
val repository = KonanDistributionRepository(distribution, targets.toSet(), libraryLoader)
val statsCollector = StatsCollector(statsType, targets.toList())
val resultsConsumer = buildResultsConsumer {
this add ModuleSerializer(destination, NativeDistributionCommonizerOutputLayout)
this add CopyUnconsumedModulesAsIsConsumer(
repository, destination, targets.toSet(), NativeDistributionCommonizerOutputLayout, logger.toProgressLogger()
)
if (copyStdlib) this add CopyStdlibResultsConsumer(distribution, destination, logger.toProgressLogger())
if (copyEndorsedLibs) this add CopyEndorsedLibrairesResultsConsumer(distribution, destination, logger.toProgressLogger())
this add LoggingResultsConsumer(SharedCommonizerTarget(targets), logger.toProgressLogger())
}
val targetNames = targets.joinToString { "[${it.name}]" }
val descriptionSuffix = estimateLibrariesCount(repository, targets).let { " ($it items)" }
val description = "${logPrefix}Preparing commonized Kotlin/Native libraries for targets $targetNames$descriptionSuffix"
println(description)
NativeDistributionCommonizer(
repository = distribution,
repository = repository,
konanDistribution = distribution,
dependencies = EmptyRepository,
libraryLoader = libraryLoader,
targets = targets,
destination = destination,
copyStdlib = copyStdlib,
copyEndorsedLibs = copyEndorsedLibs,
statsType = statsType,
logger = CliLoggerAdapter(2)
resultsConsumer = resultsConsumer,
statsCollector = statsCollector,
logger = logger
).run()
println("$description: Done")
}
companion object {
private fun estimateLibrariesCount(distribution: File, targets: List<KonanTarget>): Int? {
val targetNames = targets.map { it.name }
return distribution.resolve(KONAN_DISTRIBUTION_KLIB_DIR)
.resolve(KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR)
.listFiles()
?.filter { it.name in targetNames }
?.mapNotNull { it.listFiles() }
?.flatMap { it.toList() }
?.size
private fun estimateLibrariesCount(repository: Repository, targets: List<KonanTarget>): Int {
return targets.flatMap { repository.getLibraries(LeafCommonizerTarget(it)) }.count()
}
}
}
@@ -5,24 +5,24 @@
package org.jetbrains.kotlin.descriptors.commonizer.core
import org.jetbrains.kotlin.descriptors.commonizer.LeafTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedTarget
import org.jetbrains.kotlin.descriptors.commonizer.LeafCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirRoot
import org.jetbrains.kotlin.descriptors.commonizer.cir.factory.CirRootFactory
class RootCommonizer : AbstractStandardCommonizer<CirRoot, CirRoot>() {
private val leafTargets = mutableSetOf<LeafTarget>()
private val leafTargets = mutableSetOf<LeafCommonizerTarget>()
override fun commonizationResult() = CirRootFactory.create(
target = SharedTarget(leafTargets)
target = SharedCommonizerTarget(leafTargets)
)
override fun initialize(first: CirRoot) {
leafTargets += first.target as LeafTarget
leafTargets += first.target as LeafCommonizerTarget
}
override fun doCommonizeWith(next: CirRoot): Boolean {
leafTargets += next.target as LeafTarget
leafTargets += next.target as LeafCommonizerTarget
return true
}
}
@@ -67,11 +67,11 @@ private fun serializeTarget(mergeResult: CirTreeMergeResult, targetIndex: Int, p
val serializedMetadata = with(metadataModule.write(KLIB_FRAGMENT_WRITE_STRATEGY)) {
SerializedMetadata(header, fragments, fragmentNames)
}
parameters.resultsConsumer.consume(target, ModuleResult.Commonized(libraryName, serializedMetadata))
val manifestData = parameters.manifestDataProvider.getManifest(target, libraryName)
parameters.resultsConsumer.consume(target, ModuleResult.Commonized(libraryName, serializedMetadata, manifestData))
}
if (target is LeafTarget) {
if (target is LeafCommonizerTarget) {
mergeResult.missingModuleInfos.getValue(target).forEach {
parameters.resultsConsumer.consume(target, ModuleResult.Missing(it.originalLocation))
}
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer.konan
import org.jetbrains.kotlin.descriptors.commonizer.KonanDistribution
import org.jetbrains.kotlin.descriptors.commonizer.ResultsConsumer
import org.jetbrains.kotlin.descriptors.commonizer.klibDir
import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_COMMON_LIBS_DIR
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
import org.jetbrains.kotlin.util.Logger
import java.io.File
internal fun CopyStdlibResultsConsumer(
konanDistribution: KonanDistribution,
destination: File,
logger: Logger
): ResultsConsumer {
return CopyLibrariesFromKonanDistributionResultsConsumer(
konanDistribution,
destination,
invokeWhenCopied = { logger.log("Copied standard library") },
copyFileIf = { file -> file.endsWith(KONAN_STDLIB_NAME) }
)
}
internal fun CopyEndorsedLibrairesResultsConsumer(
konanDistribution: KonanDistribution,
destination: File,
logger: Logger
): ResultsConsumer {
return CopyLibrariesFromKonanDistributionResultsConsumer(
konanDistribution,
destination,
invokeWhenCopied = { logger.log("Copied endorsed libraries") },
copyFileIf = { file -> !file.endsWith(KONAN_STDLIB_NAME) }
)
}
private class CopyLibrariesFromKonanDistributionResultsConsumer(
private val konanDistribution: KonanDistribution,
private val destination: File,
private val invokeWhenCopied: () -> Unit = {},
private val copyFileIf: (File) -> Boolean = { true }
) : ResultsConsumer {
override fun allConsumed(status: ResultsConsumer.Status) {
konanDistribution.klibDir
.resolve(KONAN_DISTRIBUTION_COMMON_LIBS_DIR)
.listFiles().orEmpty()
.filter { it.isDirectory }
.filterNot(copyFileIf)
.forEach { libraryOrigin ->
val libraryDestination = destination.resolve(KONAN_DISTRIBUTION_COMMON_LIBS_DIR).resolve(libraryOrigin.name)
libraryOrigin.copyRecursively(libraryDestination)
}
invokeWhenCopied()
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer.konan
import org.jetbrains.kotlin.descriptors.commonizer.*
import org.jetbrains.kotlin.descriptors.commonizer.repository.Repository
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.util.Logger
import java.io.File
internal class CopyUnconsumedModulesAsIsConsumer(
private val repository: Repository,
private val destination: File,
private val targets: Set<KonanTarget>,
private val outputLayout: CommonizerOutputLayout,
private val logger: Logger? = null
) : ResultsConsumer {
private val consumedTargets = mutableSetOf<KonanTarget>()
override fun targetConsumed(target: CommonizerTarget) {
if (target is LeafCommonizerTarget) {
consumedTargets.add(target.konanTarget)
}
}
override fun allConsumed(status: ResultsConsumer.Status) {
when (status) {
ResultsConsumer.Status.NOTHING_TO_DO -> targets.forEach(::copyTargetAsIs)
ResultsConsumer.Status.DONE -> targets.minus(consumedTargets).forEach(::copyTargetAsIs)
}
}
private fun copyTargetAsIs(target: KonanTarget) {
val commonizerTarget = CommonizerTarget(target)
val libraries = repository.getLibraries(commonizerTarget)
val librariesDestination = outputLayout.getTargetDirectory(destination, commonizerTarget)
librariesDestination.mkdirs() // always create an empty directory even if there is nothing to copy
libraries.map { it.library.libraryFile.absolutePath }.map(::File).forEach { libraryFile ->
libraryFile.copyRecursively(destination.resolve(libraryFile.name))
}
logger?.log("Copied ${libraries.size} libraries for ${commonizerTarget.prettyName}")
}
}
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer.konan
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.ResultsConsumer
import org.jetbrains.kotlin.descriptors.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.prettyName
import org.jetbrains.kotlin.util.Logger
internal class LoggingResultsConsumer(
private val outputCommonizerTarget: SharedCommonizerTarget, private val logger: Logger
) : ResultsConsumer {
override fun targetConsumed(target: CommonizerTarget) {
logger.log("Written libraries for ${outputCommonizerTarget.prettyName(target)}")
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer.konan
import org.jetbrains.kotlin.descriptors.commonizer.*
import org.jetbrains.kotlin.library.SerializedMetadata
import org.jetbrains.kotlin.library.impl.BaseWriterImpl
import org.jetbrains.kotlin.library.impl.BuiltInsPlatform
import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutForWriter
import org.jetbrains.kotlin.library.impl.KotlinLibraryWriterImpl
import org.jetbrains.kotlin.util.Logger
import java.io.File
internal class ModuleSerializer(
private val destination: File,
private val outputLayout: CommonizerOutputLayout,
) : ResultsConsumer {
override fun consume(target: CommonizerTarget, moduleResult: ResultsConsumer.ModuleResult) {
val librariesDestination = outputLayout.getTargetDirectory(destination, target)
when (moduleResult) {
is ResultsConsumer.ModuleResult.Commonized -> {
val libraryName = moduleResult.libraryName
val libraryDestination = librariesDestination.resolve(libraryName)
writeLibrary(moduleResult.metadata, moduleResult.manifest, libraryDestination)
}
is ResultsConsumer.ModuleResult.Missing -> {
val libraryName = moduleResult.libraryName
val missingModuleLocation = moduleResult.originalLocation
missingModuleLocation.copyRecursively(librariesDestination.resolve(libraryName))
}
}
}
}
private fun writeLibrary(
metadata: SerializedMetadata,
manifestData: NativeSensitiveManifestData,
libraryDestination: File
) {
val layout = org.jetbrains.kotlin.konan.file.File(libraryDestination.path).let { KotlinLibraryLayoutForWriter(it, it) }
val library = KotlinLibraryWriterImpl(
moduleName = manifestData.uniqueName,
versions = manifestData.versions,
builtInsPlatform = BuiltInsPlatform.NATIVE,
nativeTargets = emptyList(), // will be overwritten with NativeSensitiveManifestData.applyTo() below
nopack = true,
shortName = manifestData.shortName,
layout = layout
)
library.addMetadata(metadata)
manifestData.applyTo(library.base as BaseWriterImpl)
library.commit()
}
@@ -5,160 +5,91 @@
package org.jetbrains.kotlin.descriptors.commonizer.konan
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.backend.common.serialization.metadata.metadataVersion
import org.jetbrains.kotlin.descriptors.commonizer.*
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeDistributionCommonizer.StatsType.*
import org.jetbrains.kotlin.descriptors.commonizer.stats.*
import org.jetbrains.kotlin.descriptors.commonizer.LeafCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.cli.toProgressLogger
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeDistributionCommonizer.*
import org.jetbrains.kotlin.descriptors.commonizer.repository.Repository
import org.jetbrains.kotlin.descriptors.commonizer.stats.StatsCollector
import org.jetbrains.kotlin.descriptors.commonizer.utils.ResettableClockMark
import org.jetbrains.kotlin.konan.library.*
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.ToolingSingleFileKlibResolveStrategy
import org.jetbrains.kotlin.library.resolveSingleFileKlib
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.util.Logger
import java.io.File
import org.jetbrains.kotlin.konan.file.File as KFile
class NativeDistributionCommonizer(
private val repository: File,
internal class NativeDistributionCommonizer internal constructor(
private val konanDistribution: KonanDistribution,
private val repository: Repository,
private val dependencies: Repository,
private val libraryLoader: NativeLibraryLoader,
private val targets: List<KonanTarget>,
private val destination: File,
private val copyStdlib: Boolean,
private val copyEndorsedLibs: Boolean,
private val statsType: StatsType,
private val resultsConsumer: ResultsConsumer,
private val statsCollector: StatsCollector?,
private val logger: Logger
) {
enum class StatsType { RAW, AGGREGATED, NONE }
private val clockMark = ResettableClockMark()
fun run() {
checkPreconditions()
clockMark.reset()
// 1. load libraries
val allLibraries = loadLibraries()
// 2. run commonization & write new libraries
commonizeAndSaveResults(allLibraries)
logTotal()
}
private fun checkPreconditions() {
if (!repository.isDirectory)
logger.fatal("Repository does not exist: $repository")
when (targets.size) {
0 -> logger.fatal("No targets specified")
1 -> logger.fatal("Too few targets specified: $targets")
}
when {
!destination.exists() -> destination.mkdirs()
!destination.isDirectory -> logger.fatal("Output already exists: $destination")
destination.walkTopDown().any { it != destination } -> logger.fatal("Output is not empty: $destination")
}
}
private fun logProgress(message: String) = logger.log("* $message in ${clockMark.elapsedSinceLast()}")
private fun logTotal() = logger.log("TOTAL: ${clockMark.elapsedSinceStart()}")
private fun loadLibraries(): AllNativeLibraries {
val stdlibPath = repository.resolve(konanCommonLibraryPath(KONAN_STDLIB_NAME))
val stdlib = NativeLibrary(loadLibrary(stdlibPath))
val stdlib = libraryLoader(konanDistribution.stdlib)
val librariesByTargets = targets.associate { target ->
val leafTarget = LeafTarget(target.name, target)
val platformLibs = leafTarget.platformLibrariesSource
.takeIf { it.isDirectory }
?.listFiles()
?.takeIf { it.isNotEmpty() }
?.map { NativeLibrary(loadLibrary(it)) }
.orEmpty()
if (platformLibs.isEmpty())
logger.warning("No platform libraries found for target ${leafTarget.prettyName}. This target will be excluded from commonization.")
leafTarget to NativeLibrariesToCommonize(platformLibs)
val librariesByTargets = targets.map(::LeafCommonizerTarget).associateWith { target ->
NativeLibrariesToCommonize(repository.getLibraries(target).toList())
}
librariesByTargets.forEach { (target, librariesToCommonize) ->
if (librariesToCommonize.libraries.isEmpty()) {
logger.warning("No platform libraries found for target $target. This target will be excluded from commonization.")
}
}
logProgress("Read lazy (uninitialized) libraries")
return AllNativeLibraries(stdlib, librariesByTargets)
}
private fun loadLibrary(location: File): KotlinLibrary {
if (!location.isDirectory)
logger.fatal("Library not found: $location")
val library = resolveSingleFileKlib(
libraryFile = KFile(location.path),
logger = logger,
strategy = ToolingSingleFileKlibResolveStrategy
)
if (library.versions.metadataVersion == null)
logger.fatal("Library does not have metadata version specified in manifest: $location")
val metadataVersion = library.metadataVersion
if (metadataVersion?.isCompatible() != true)
logger.fatal(
"""
Library has incompatible metadata version ${metadataVersion ?: "\"unknown\""}: $location
Please make sure that all libraries passed to commonizer compatible metadata version ${KlibMetadataVersion.INSTANCE}
""".trimIndent()
)
return library
}
private fun commonizeAndSaveResults(allLibraries: AllNativeLibraries) {
val statsCollector = when (statsType) {
RAW -> RawStatsCollector(targets)
AGGREGATED -> AggregatedStatsCollector(targets)
NONE -> null
}
val manifestProvider = TargetedNativeManifestDataProvider(allLibraries)
val parameters = CommonizerParameters(statsCollector, ::logProgress).apply {
val parameters = CommonizerParameters(resultsConsumer, manifestProvider, statsCollector, ::logProgress).apply {
val storageManager = LockBasedStorageManager("Commonized modules")
resultsConsumer = NativeDistributionResultsConsumer(
repository = repository,
originalLibraries = allLibraries,
destination = destination,
copyStdlib = copyStdlib,
copyEndorsedLibs = copyEndorsedLibs,
logProgress = ::logProgress
)
dependencyModulesProvider = NativeDistributionModulesProvider.forStandardLibrary(storageManager, allLibraries.stdlib)
allLibraries.librariesByTargets.forEach { (target, librariesToCommonize) ->
if (librariesToCommonize.libraries.isEmpty()) return@forEach
val modulesProvider = NativeDistributionModulesProvider.platformLibraries(storageManager, librariesToCommonize)
val dependencyModuleProvider = NativeDistributionModulesProvider.platformLibraries(
storageManager, NativeLibrariesToCommonize(dependencies.getLibraries(target).toList()),
)
addTarget(
TargetProvider(
target = target,
modulesProvider = modulesProvider,
dependencyModulesProvider = null // stdlib is already set as common dependency
dependencyModulesProvider = dependencyModuleProvider
)
)
}
}
runCommonization(parameters)
statsCollector?.writeTo(FileStatsOutput(destination, statsType.name.toLowerCase()))
}
private val LeafTarget.platformLibrariesSource: File
get() = repository.resolve(KONAN_DISTRIBUTION_KLIB_DIR)
.resolve(KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR)
.resolve(name)
private fun checkPreconditions() {
when (targets.size) {
0 -> logger.fatal("No targets specified")
1 -> logger.fatal("Too few targets specified: $targets")
}
}
private fun logProgress(message: String) = logger.toProgressLogger().log("$message in ${clockMark.elapsedSinceLast()}")
private fun logTotal() = logger.log("TOTAL: ${clockMark.elapsedSinceStart()}")
}
@@ -34,8 +34,6 @@ internal abstract class NativeDistributionModulesProvider(libraries: Collection<
protected val moduleInfoMap: Map<String, NativeModuleInfo>
init {
check(libraries.isNotEmpty()) { "No libraries supplied" }
val libraryMap = mutableMapOf<String, NativeLibrary>()
val moduleInfoMap = mutableMapOf<String, NativeModuleInfo>()
@@ -1,172 +0,0 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer.konan
import com.intellij.util.containers.FactoryMap
import org.jetbrains.kotlin.descriptors.commonizer.ResultsConsumer
import org.jetbrains.kotlin.descriptors.commonizer.ResultsConsumer.ModuleResult
import org.jetbrains.kotlin.descriptors.commonizer.ResultsConsumer.Status
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.LeafTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedTarget
import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_COMMON_LIBS_DIR
import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_KLIB_DIR
import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
import org.jetbrains.kotlin.library.SerializedMetadata
import org.jetbrains.kotlin.library.impl.BaseWriterImpl
import org.jetbrains.kotlin.library.impl.BuiltInsPlatform
import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutForWriter
import org.jetbrains.kotlin.library.impl.KotlinLibraryWriterImpl
import java.io.File
import org.jetbrains.kotlin.konan.file.File as KFile
internal class NativeDistributionResultsConsumer(
private val repository: File,
private val originalLibraries: AllNativeLibraries,
private val destination: File,
private val copyStdlib: Boolean,
private val copyEndorsedLibs: Boolean,
private val logProgress: (String) -> Unit
) : ResultsConsumer {
private val allLeafTargets = originalLibraries.librariesByTargets.keys
private val allLeafCommonizedTargets = originalLibraries.librariesByTargets.filterValues { it.libraries.isNotEmpty() }.keys
private val sharedTarget = SharedTarget(allLeafCommonizedTargets)
private val consumedTargets = LinkedHashSet<CommonizerTarget>()
private val cachedManifestProviders = FactoryMap.create<CommonizerTarget, NativeManifestDataProvider> { target ->
when (target) {
is LeafTarget -> originalLibraries.librariesByTargets.getValue(target)
is SharedTarget -> CommonNativeManifestDataProvider(originalLibraries.librariesByTargets.values)
}
}
private val cachedLibrariesDestination = FactoryMap.create<CommonizerTarget, File> { target ->
val librariesDestination = when (target) {
is LeafTarget -> destination.resolve(KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR).resolve(target.name)
is SharedTarget -> destination.resolve(KONAN_DISTRIBUTION_COMMON_LIBS_DIR)
}
librariesDestination.mkdirs() // always create an empty directory even if there is nothing to copy
librariesDestination
}
override fun consume(target: CommonizerTarget, moduleResult: ModuleResult) {
check(target in allLeafCommonizedTargets || target == sharedTarget)
consumedTargets += target
serializeModule(target, moduleResult)
}
override fun targetConsumed(target: CommonizerTarget) {
check(target in consumedTargets)
logProgress("Written libraries for ${target.prettyCommonizedName(sharedTarget)}")
}
override fun allConsumed(status: Status) {
// optimization: stdlib and endorsed libraries effectively remain the same across all Kotlin/Native targets,
// so they can be just copied to the new destination without running serializer
copyCommonStandardLibraries()
when (status) {
Status.NOTHING_TO_DO -> {
// It may happen that all targets to be commonized (or at least all but one target) miss platform libraries.
// In such case commonizer will do nothing and raise a special status value 'NOTHING_TO_DO'.
// So, let's just copy platform libraries from the target where they are to the new destination.
allLeafTargets.forEach(::copyTargetAsIs)
}
Status.DONE -> {
// 'targetsToCopy' are some leaf targets with empty set of platform libraries
val targetsToCopy = allLeafTargets - consumedTargets.filterIsInstance<LeafTarget>()
targetsToCopy.forEach(::copyTargetAsIs)
}
}
}
private fun copyCommonStandardLibraries() {
if (copyStdlib || copyEndorsedLibs) {
repository.resolve(KONAN_DISTRIBUTION_KLIB_DIR)
.resolve(KONAN_DISTRIBUTION_COMMON_LIBS_DIR)
.listFiles()
?.filter { it.isDirectory }
?.let {
if (copyStdlib) {
if (copyEndorsedLibs) it else it.filter { dir -> dir.endsWith(KONAN_STDLIB_NAME) }
} else
it.filter { dir -> !dir.endsWith(KONAN_STDLIB_NAME) }
}?.forEach { libraryOrigin ->
val libraryDestination = destination.resolve(KONAN_DISTRIBUTION_COMMON_LIBS_DIR).resolve(libraryOrigin.name)
libraryOrigin.copyRecursively(libraryDestination)
}
val what = listOfNotNull(
"standard library".takeIf { copyStdlib },
"endorsed libraries".takeIf { copyEndorsedLibs }
).joinToString(separator = " and ")
logProgress("Copied $what")
}
}
private fun copyTargetAsIs(leafTarget: LeafTarget) {
val librariesCount = originalLibraries.librariesByTargets.getValue(leafTarget).libraries.size
val librariesDestination = cachedLibrariesDestination.getValue(leafTarget)
val librariesSource = leafTarget.platformLibrariesSource
if (librariesSource.isDirectory) librariesSource.copyRecursively(librariesDestination)
logProgress("Copied $librariesCount libraries for ${leafTarget.prettyName}")
}
private fun serializeModule(target: CommonizerTarget, moduleResult: ModuleResult) {
val librariesDestination = cachedLibrariesDestination.getValue(target)
when (moduleResult) {
is ModuleResult.Commonized -> {
val libraryName = moduleResult.libraryName
val manifestData = cachedManifestProviders.getValue(target).getManifest(libraryName)
val libraryDestination = librariesDestination.resolve(libraryName)
writeLibrary(moduleResult.metadata, manifestData, libraryDestination)
}
is ModuleResult.Missing -> {
val libraryName = moduleResult.libraryName
val missingModuleLocation = moduleResult.originalLocation
missingModuleLocation.copyRecursively(librariesDestination.resolve(libraryName))
}
}
}
private fun writeLibrary(
metadata: SerializedMetadata,
manifestData: NativeSensitiveManifestData,
libraryDestination: File
) {
val layout = KFile(libraryDestination.path).let { KotlinLibraryLayoutForWriter(it, it) }
val library = KotlinLibraryWriterImpl(
moduleName = manifestData.uniqueName,
versions = manifestData.versions,
builtInsPlatform = BuiltInsPlatform.NATIVE,
nativeTargets = emptyList(), // will be overwritten with NativeSensitiveManifestData.applyTo() below
nopack = true,
shortName = manifestData.shortName,
layout = layout
)
library.addMetadata(metadata)
manifestData.applyTo(library.base as BaseWriterImpl)
library.commit()
}
private val LeafTarget.platformLibrariesSource: File
get() = repository.resolve(KONAN_DISTRIBUTION_KLIB_DIR)
.resolve(KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR)
.resolve(name)
}
@@ -5,14 +5,33 @@
package org.jetbrains.kotlin.descriptors.commonizer.konan
import com.intellij.util.containers.FactoryMap
import gnu.trove.THashMap
import org.jetbrains.kotlin.descriptors.commonizer.LeafTarget
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.LeafCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.library.KotlinLibrary
fun interface TargetedNativeManifestDataProvider {
fun getManifest(target: CommonizerTarget, libraryName: String): NativeSensitiveManifestData
}
internal interface NativeManifestDataProvider {
fun getManifest(libraryName: String): NativeSensitiveManifestData
}
internal fun TargetedNativeManifestDataProvider(libraries: AllNativeLibraries): TargetedNativeManifestDataProvider {
val cachedManifestProviders: Map<CommonizerTarget, NativeManifestDataProvider> = FactoryMap.create { target ->
when (target) {
is LeafCommonizerTarget -> libraries.librariesByTargets.getValue(target)
is SharedCommonizerTarget -> CommonNativeManifestDataProvider(libraries.librariesByTargets.values)
}
}
return TargetedNativeManifestDataProvider { target, libraryName ->
cachedManifestProviders.getValue(target).getManifest(libraryName)
}
}
/**
* A separate Kotlin/Native library.
*/
@@ -37,7 +56,7 @@ internal class NativeLibrariesToCommonize(val libraries: List<NativeLibrary>) :
internal class AllNativeLibraries(
val stdlib: NativeLibrary,
val librariesByTargets: Map<LeafTarget, NativeLibrariesToCommonize>
val librariesByTargets: Map<LeafCommonizerTarget, NativeLibrariesToCommonize>
)
internal class CommonNativeManifestDataProvider(
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.library.impl.BaseWriterImpl
* The set of properties in manifest of Kotlin/Native library that should be
* preserved in commonized libraries (both for "common" and platform-specific library parts).
*/
internal data class NativeSensitiveManifestData(
data class NativeSensitiveManifestData(
val uniqueName: String,
val versions: KotlinLibraryVersioning,
val dependencies: List<String>,
@@ -6,10 +6,8 @@
package org.jetbrains.kotlin.descriptors.commonizer.mergedtree
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.commonizer.LeafTarget
import org.jetbrains.kotlin.descriptors.commonizer.*
import org.jetbrains.kotlin.descriptors.commonizer.ModulesProvider.ModuleInfo
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerParameters
import org.jetbrains.kotlin.descriptors.commonizer.TargetProvider
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirEntityId
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirName
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirPackageName
@@ -49,7 +47,7 @@ class CirTreeMerger(
) {
class CirTreeMergeResult(
val root: CirRootNode,
val missingModuleInfos: Map<LeafTarget, Collection<ModuleInfo>>
val missingModuleInfos: Map<LeafCommonizerTarget, Collection<ModuleInfo>>
)
private val size = parameters.targetProviders.size
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.descriptors.commonizer.mergedtree
import gnu.trove.THashMap
import gnu.trove.THashSet
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.commonizer.SharedTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirEntityId
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirPackageName
@@ -25,7 +25,7 @@ class CirKnownClassifiers(
) {
// a shortcut for fast access
val commonDependencies: CirProvidedClassifiers =
dependencies.filterKeys { it is SharedTarget }.values.singleOrNull() ?: CirProvidedClassifiers.EMPTY
dependencies.filterKeys { it is SharedCommonizerTarget }.values.singleOrNull() ?: CirProvidedClassifiers.EMPTY
}
interface CirCommonizedClassifiers {
@@ -0,0 +1,34 @@
/*
* 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.descriptors.commonizer.repository
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.LeafCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.NativeLibraryLoader
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeLibrary
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
internal class FilesRepository(
private val libraryFiles: Set<File>,
private val libraryLoader: NativeLibraryLoader
) : Repository {
private val librariesByTarget: Map<CommonizerTarget, Set<NativeLibrary>> by lazy {
libraryFiles
.map(libraryLoader::invoke)
.groupBy { library -> CommonizerTarget(library.manifestData.nativeTargets.map(::konanTargetOrThrow)) }
.mapValues { (_, list) -> list.toSet() }
}
override fun getLibraries(target: LeafCommonizerTarget): Set<NativeLibrary> {
return librariesByTarget[target].orEmpty()
}
}
private fun konanTargetOrThrow(value: String): KonanTarget {
return KonanTarget.predefinedTargets[value] ?: error("Unexpected KonanTarget $value")
}
@@ -0,0 +1,37 @@
/*
* 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.descriptors.commonizer.repository
import org.jetbrains.kotlin.descriptors.commonizer.KonanDistribution
import org.jetbrains.kotlin.descriptors.commonizer.LeafCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.NativeLibraryLoader
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeLibrary
import org.jetbrains.kotlin.descriptors.commonizer.platformLibsDir
import org.jetbrains.kotlin.konan.target.KonanTarget
internal class KonanDistributionRepository(
konanDistribution: KonanDistribution,
targets: Set<KonanTarget>,
libraryLoader: NativeLibraryLoader,
) : Repository {
private val librariesByTarget: Map<LeafCommonizerTarget, Lazy<Set<NativeLibrary>>> =
targets.map(::LeafCommonizerTarget).associateWith { target ->
lazy {
konanDistribution.platformLibsDir
.resolve(target.name)
.takeIf { it.isDirectory }
?.listFiles()
.orEmpty().toList()
.map { libraryLoader(it) }
.toSet()
}
}
override fun getLibraries(target: LeafCommonizerTarget): Set<NativeLibrary> {
return librariesByTarget[target]?.value ?: error("Missing target $target")
}
}
@@ -0,0 +1,32 @@
/*
* 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.descriptors.commonizer.repository
import org.jetbrains.kotlin.descriptors.commonizer.LeafCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeLibrary
internal interface Repository {
fun getLibraries(target: LeafCommonizerTarget): Set<NativeLibrary>
}
internal operator fun Repository.plus(other: Repository): Repository {
if (this is CompositeRepository) {
return CompositeRepository(this.repositories + other)
}
return CompositeRepository(listOf(this, other))
}
private class CompositeRepository(val repositories: Iterable<Repository>) : Repository {
override fun getLibraries(target: LeafCommonizerTarget): Set<NativeLibrary> {
return repositories.map { it.getLibraries(target) }.flatten().toSet()
}
}
internal object EmptyRepository : Repository {
override fun getLibraries(target: LeafCommonizerTarget): Set<NativeLibrary> {
return emptySet()
}
}
@@ -5,6 +5,20 @@
package org.jetbrains.kotlin.descriptors.commonizer.stats
import org.jetbrains.kotlin.konan.target.KonanTarget
fun StatsCollector(type: StatsType, targets: List<KonanTarget>): StatsCollector? {
return when (type) {
StatsType.RAW -> RawStatsCollector(targets)
StatsType.AGGREGATED -> AggregatedStatsCollector(targets)
StatsType.NONE -> null
}
}
enum class StatsType {
RAW, AGGREGATED, NONE;
}
interface StatsCollector {
data class StatsKey(
val id: String,
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
import org.jetbrains.kotlin.descriptors.commonizer.ResultsConsumer.ModuleResult
import org.jetbrains.kotlin.descriptors.commonizer.ResultsConsumer.Status
import org.jetbrains.kotlin.descriptors.commonizer.SourceModuleRoot.Companion.SHARED_TARGET_NAME
import org.jetbrains.kotlin.descriptors.commonizer.konan.TargetedNativeManifestDataProvider
import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.ClassCollector
import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.FunctionCollector
import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.collectMembers
@@ -75,7 +76,7 @@ abstract class AbstractCommonizationFromSourcesTest : KtUsefulTestCase() {
runCommonization(analyzedModules.toCommonizerParameters(results))
assertEquals(Status.DONE, results.status)
val sharedTarget: SharedTarget = analyzedModules.sharedTarget
val sharedTarget: SharedCommonizerTarget = analyzedModules.sharedTarget
assertEquals(sharedTarget, results.sharedTarget)
val sharedModuleAsExpected: SerializedMetadata = analyzedModules.commonizedModules.getValue(sharedTarget)
@@ -84,7 +85,7 @@ abstract class AbstractCommonizationFromSourcesTest : KtUsefulTestCase() {
assertModulesAreEqual(sharedModuleAsExpected, sharedModuleByCommonizer, sharedTarget)
val leafTargets: Set<LeafTarget> = analyzedModules.leafTargets
val leafTargets: Set<LeafCommonizerTarget> = analyzedModules.leafTargets
assertEquals(leafTargets, results.leafTargets)
for (leafTarget in leafTargets) {
@@ -116,18 +117,18 @@ private data class SourceModuleRoot(
}
private class SourceModuleRoots(
val originalRoots: Map<LeafTarget, SourceModuleRoot>,
val originalRoots: Map<LeafCommonizerTarget, SourceModuleRoot>,
val commonizedRoots: Map<CommonizerTarget, SourceModuleRoot>,
val dependencyRoots: Map<CommonizerTarget, SourceModuleRoot>
) {
val leafTargets: Set<LeafTarget> = originalRoots.keys
val sharedTarget: SharedTarget
val leafTargets: Set<LeafCommonizerTarget> = originalRoots.keys
val sharedTarget: SharedCommonizerTarget
init {
check(leafTargets.size >= 2)
check(leafTargets.none { it.name == SHARED_TARGET_NAME })
val sharedTargets = commonizedRoots.keys.filterIsInstance<SharedTarget>()
val sharedTargets = commonizedRoots.keys.filterIsInstance<SharedCommonizerTarget>()
check(sharedTargets.size == 1)
sharedTarget = sharedTargets.single()
@@ -140,10 +141,10 @@ private class SourceModuleRoots(
companion object {
fun load(dataDir: File): SourceModuleRoots = try {
val originalRoots = listRoots(dataDir, ORIGINAL_ROOTS_DIR).mapKeys { LeafTarget(it.key) }
val originalRoots = listRoots(dataDir, ORIGINAL_ROOTS_DIR).mapKeys { LeafCommonizerTarget(it.key) }
val leafTargets = originalRoots.keys
val sharedTarget = SharedTarget(leafTargets)
val sharedTarget = SharedCommonizerTarget(leafTargets)
fun getTarget(targetName: String): CommonizerTarget =
if (targetName == SHARED_TARGET_NAME) sharedTarget else leafTargets.first { it.name == targetName }
@@ -185,41 +186,41 @@ private class AnalyzedModules(
val commonizedModules: Map<CommonizerTarget, SerializedMetadata>,
val dependencyModules: Map<CommonizerTarget, List<ModuleDescriptor>>
) {
val leafTargets: Set<LeafTarget>
val sharedTarget: SharedTarget
val leafTargets: Set<LeafCommonizerTarget>
val sharedTarget: SharedCommonizerTarget
init {
originalModules.keys.let { targets ->
check(targets.isNotEmpty())
leafTargets = targets.filterIsInstance<LeafTarget>().toSet()
leafTargets = targets.filterIsInstance<LeafCommonizerTarget>().toSet()
check(targets.size == leafTargets.size)
}
sharedTarget = SharedTarget(leafTargets)
sharedTarget = SharedCommonizerTarget(leafTargets)
val allTargets = leafTargets + sharedTarget
check(commonizedModules.keys == allTargets)
check(allTargets.containsAll(dependencyModules.keys))
}
fun toCommonizerParameters(resultsConsumer: ResultsConsumer) =
CommonizerParameters().also { parameters ->
parameters.resultsConsumer = resultsConsumer
parameters.dependencyModulesProvider = dependencyModules[sharedTarget]?.let(MockModulesProvider::create)
fun toCommonizerParameters(
resultsConsumer: ResultsConsumer, manifestDataProvider: TargetedNativeManifestDataProvider = MockNativeManifestDataProvider()
) = CommonizerParameters(resultsConsumer, manifestDataProvider).also { parameters ->
parameters.dependencyModulesProvider = dependencyModules[sharedTarget]?.let(MockModulesProvider::create)
leafTargets.forEach { leafTarget ->
val originalModule = originalModules.getValue(leafTarget)
leafTargets.forEach { leafTarget ->
val originalModule = originalModules.getValue(leafTarget)
parameters.addTarget(
TargetProvider(
target = leafTarget,
modulesProvider = MockModulesProvider.create(originalModule),
dependencyModulesProvider = dependencyModules[leafTarget]?.let(MockModulesProvider::create)
)
parameters.addTarget(
TargetProvider(
target = leafTarget,
modulesProvider = MockModulesProvider.create(originalModule),
dependencyModulesProvider = dependencyModules[leafTarget]?.let(MockModulesProvider::create)
)
}
)
}
}
companion object {
fun create(
@@ -242,7 +243,7 @@ private class AnalyzedModules(
}
private fun createDependencyModules(
sharedTarget: SharedTarget,
sharedTarget: SharedCommonizerTarget,
dependencyRoots: Map<out CommonizerTarget, SourceModuleRoot>,
parentDisposable: Disposable
): Pair<Map<CommonizerTarget, List<ModuleDescriptor>>, AnalyzedModuleDependencies> {
@@ -263,7 +264,7 @@ private class AnalyzedModules(
}
private fun createModules(
sharedTarget: SharedTarget,
sharedTarget: SharedCommonizerTarget,
moduleRoots: Map<out CommonizerTarget, SourceModuleRoot>,
dependencies: AnalyzedModuleDependencies,
parentDisposable: Disposable,
@@ -290,7 +291,7 @@ private class AnalyzedModules(
}
private fun createModule(
sharedTarget: SharedTarget,
sharedTarget: SharedCommonizerTarget,
currentTarget: CommonizerTarget,
moduleRoot: SourceModuleRoot,
dependencies: AnalyzedModuleDependencies,
@@ -338,7 +339,7 @@ private class AnalyzedModules(
}
private class DependenciesContainerImpl(
sharedTarget: SharedTarget,
sharedTarget: SharedCommonizerTarget,
currentTarget: CommonizerTarget,
dependencies: AnalyzedModuleDependencies
) : CommonDependenciesContainer {
@@ -7,8 +7,11 @@ package org.jetbrains.kotlin.descriptors.commonizer
import org.jetbrains.kotlin.descriptors.commonizer.ResultsConsumer.ModuleResult
import org.jetbrains.kotlin.descriptors.commonizer.ResultsConsumer.Status
import org.jetbrains.kotlin.descriptors.commonizer.konan.CommonNativeManifestDataProvider
import org.jetbrains.kotlin.descriptors.commonizer.konan.TargetedNativeManifestDataProvider
import org.jetbrains.kotlin.descriptors.commonizer.utils.MockResultsConsumer
import org.jetbrains.kotlin.descriptors.commonizer.utils.MockModulesProvider
import org.jetbrains.kotlin.descriptors.commonizer.utils.MockNativeManifestDataProvider
import org.junit.Test
import kotlin.contracts.ExperimentalContracts
import kotlin.test.assertEquals
@@ -62,20 +65,19 @@ class CommonizerFacadeTest {
)
companion object {
private fun Map<String, List<String>>.toCommonizerParameters(resultsConsumer: ResultsConsumer) =
CommonizerParameters().also { parameters ->
parameters.resultsConsumer = resultsConsumer
forEach { (targetName, moduleNames) ->
parameters.addTarget(
TargetProvider(
target = LeafTarget(targetName),
modulesProvider = MockModulesProvider.create(moduleNames),
dependencyModulesProvider = null
)
private fun Map<String, List<String>>.toCommonizerParameters(
resultsConsumer: ResultsConsumer, manifestDataProvider: TargetedNativeManifestDataProvider = MockNativeManifestDataProvider()
) = CommonizerParameters(resultsConsumer, manifestDataProvider).also { parameters ->
forEach { (targetName, moduleNames) ->
parameters.addTarget(
TargetProvider(
target = LeafCommonizerTarget(targetName),
modulesProvider = MockModulesProvider.create(moduleNames),
dependencyModulesProvider = null
)
}
)
}
}
private fun doTestNothingToCommonize(originalModules: Map<String, List<String>>) {
val results = MockResultsConsumer()
@@ -1,70 +0,0 @@
/*
* Copyright 2010-2021 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.descriptors.commonizer
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.junit.Test
import kotlin.test.assertEquals
class CommonizerTargetTest {
@Test
fun leafTargetNames() {
listOf(
Triple("foo", "[foo]", FOO),
Triple("bar", "[bar]", BAR),
Triple("baz_123", "[baz_123]", BAZ),
).forEach { (name, prettyName, target: LeafTarget) ->
assertEquals(name, target.name)
assertEquals(prettyName, target.prettyName)
}
}
@Test
fun sharedTargetNames() {
listOf(
"[foo]" to SharedTarget(FOO),
"[foo, bar]" to SharedTarget(FOO, BAR),
"[foo, bar, baz_123]" to SharedTarget(FOO, BAR, BAZ),
"[foo, bar, baz_123, [foo, bar]]" to SharedTarget(FOO, BAR, BAZ, SharedTarget(FOO, BAR))
).forEach { (prettyName, target: SharedTarget) ->
assertEquals(prettyName, target.prettyName)
assertEquals(prettyName, target.name)
}
}
@Test
fun prettyCommonizedName() {
val sharedTarget = SharedTarget(FOO, BAR, BAZ)
listOf(
"[foo(*), bar, baz_123]" to FOO,
"[foo, bar(*), baz_123]" to BAR,
"[foo, bar, baz_123(*)]" to BAZ,
"[foo, bar, baz_123]" to sharedTarget
).forEach { (prettyCommonizerName, target: CommonizerTarget) ->
assertEquals(prettyCommonizerName, target.prettyCommonizedName(sharedTarget))
}
}
@Test(expected = IllegalStateException::class)
fun prettyCommonizedNameFailure() {
FOO.prettyCommonizedName(SharedTarget(BAR, BAZ))
}
@Test(expected = IllegalArgumentException::class)
fun sharedTargetNoInnerTargets() {
SharedTarget(emptySet())
}
private companion object {
val FOO = LeafTarget("foo")
val BAR = LeafTarget("bar", KonanTarget.IOS_X64)
val BAZ = LeafTarget("baz_123", KonanTarget.MACOS_X64)
@Suppress("TestFunctionName")
fun SharedTarget(vararg targets: CommonizerTarget) = SharedTarget(linkedSetOf(*targets))
}
}
@@ -6,8 +6,8 @@
package org.jetbrains.kotlin.descriptors.commonizer.core
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.LeafTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedTarget
import org.jetbrains.kotlin.descriptors.commonizer.LeafCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirRoot
import org.jetbrains.kotlin.descriptors.commonizer.cir.factory.CirRootFactory
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -17,114 +17,86 @@ class RootCommonizerTest : AbstractCommonizerTest<CirRoot, CirRoot>() {
@Test
fun allAreNative() = doTestSuccess(
expected = SharedTarget(
expected = SharedCommonizerTarget(
setOf(
LeafTarget("ios_x64", KonanTarget.IOS_X64),
LeafTarget("ios_arm64", KonanTarget.IOS_ARM64),
LeafTarget("ios_arm32", KonanTarget.IOS_ARM32)
LeafCommonizerTarget(KonanTarget.IOS_X64),
LeafCommonizerTarget(KonanTarget.IOS_ARM64),
LeafCommonizerTarget(KonanTarget.IOS_ARM32)
)
).toMock(),
LeafTarget("ios_x64", KonanTarget.IOS_X64).toMock(),
LeafTarget("ios_arm64", KonanTarget.IOS_ARM64).toMock(),
LeafTarget("ios_arm32", KonanTarget.IOS_ARM32).toMock()
LeafCommonizerTarget(KonanTarget.IOS_X64).toMock(),
LeafCommonizerTarget(KonanTarget.IOS_ARM64).toMock(),
LeafCommonizerTarget(KonanTarget.IOS_ARM32).toMock()
)
@Test
fun jvmAndNative1() = doTestSuccess(
expected = SharedTarget(
expected = SharedCommonizerTarget(
setOf(
LeafTarget("jvm1"),
LeafTarget("ios_x64", KonanTarget.IOS_X64),
LeafTarget("jvm2")
LeafCommonizerTarget("jvm1"),
LeafCommonizerTarget(KonanTarget.IOS_X64),
LeafCommonizerTarget("jvm2")
)
).toMock(),
LeafTarget("jvm1").toMock(),
LeafTarget("ios_x64", KonanTarget.IOS_X64).toMock(),
LeafTarget("jvm2").toMock()
LeafCommonizerTarget("jvm1").toMock(),
LeafCommonizerTarget(KonanTarget.IOS_X64).toMock(),
LeafCommonizerTarget("jvm2").toMock()
)
@Test
fun jvmAndNative2() = doTestSuccess(
expected = SharedTarget(
expected = SharedCommonizerTarget(
setOf(
LeafTarget("ios_x64", KonanTarget.IOS_X64),
LeafTarget("jvm"),
LeafTarget("ios_arm64", KonanTarget.IOS_ARM64)
LeafCommonizerTarget(KonanTarget.IOS_X64),
LeafCommonizerTarget("jvm"),
LeafCommonizerTarget(KonanTarget.IOS_ARM64)
)
).toMock(),
LeafTarget("ios_x64", KonanTarget.IOS_X64).toMock(),
LeafTarget("jvm").toMock(),
LeafTarget("ios_arm64", KonanTarget.IOS_ARM64).toMock()
LeafCommonizerTarget(KonanTarget.IOS_X64).toMock(),
LeafCommonizerTarget("jvm").toMock(),
LeafCommonizerTarget(KonanTarget.IOS_ARM64).toMock()
)
@Test
fun noNative1() = doTestSuccess(
expected = SharedTarget(
expected = SharedCommonizerTarget(
setOf(
LeafTarget("default1"),
LeafTarget("default2"),
LeafTarget("default3")
LeafCommonizerTarget("default1"),
LeafCommonizerTarget("default2"),
LeafCommonizerTarget("default3")
)
).toMock(),
LeafTarget("default1").toMock(),
LeafTarget("default2").toMock(),
LeafTarget("default3").toMock()
LeafCommonizerTarget("default1").toMock(),
LeafCommonizerTarget("default2").toMock(),
LeafCommonizerTarget("default3").toMock()
)
@Test
fun noNative2() = doTestSuccess(
expected = SharedTarget(
expected = SharedCommonizerTarget(
setOf(
LeafTarget("jvm1"),
LeafTarget("default"),
LeafTarget("jvm2")
LeafCommonizerTarget("jvm1"),
LeafCommonizerTarget("default"),
LeafCommonizerTarget("jvm2")
)
).toMock(),
LeafTarget("jvm1").toMock(),
LeafTarget("default").toMock(),
LeafTarget("jvm2").toMock()
LeafCommonizerTarget("jvm1").toMock(),
LeafCommonizerTarget("default").toMock(),
LeafCommonizerTarget("jvm2").toMock()
)
@Test
fun noNative3() = doTestSuccess(
expected = SharedTarget(
expected = SharedCommonizerTarget(
setOf(
LeafTarget("jvm1"),
LeafTarget("jvm2"),
LeafTarget("jvm3")
LeafCommonizerTarget("jvm1"),
LeafCommonizerTarget("jvm2"),
LeafCommonizerTarget("jvm3")
)
).toMock(),
LeafTarget("jvm1").toMock(),
LeafTarget("jvm2").toMock(),
LeafTarget("jvm3").toMock()
)
@Test(expected = ObjectsNotEqual::class)
fun misconfiguration1() = doTestSuccess(
expected = SharedTarget(
setOf(
LeafTarget("ios_x64", KonanTarget.IOS_X64),
LeafTarget("ios_arm64", KonanTarget.IOS_ARM64),
LeafTarget("ios_arm32", KonanTarget.IOS_ARM32)
)
).toMock(),
LeafTarget("ios_x64").toMock(), // KonanTarget is missing here!
LeafTarget("ios_arm64", KonanTarget.IOS_ARM64).toMock(),
LeafTarget("ios_arm32", KonanTarget.IOS_ARM32).toMock()
)
@Test(expected = ObjectsNotEqual::class)
fun misconfiguration2() = doTestSuccess(
expected = SharedTarget(
setOf(
LeafTarget("jvm1"),
LeafTarget("jvm2"),
LeafTarget("jvm3")
)
).toMock(),
LeafTarget("jvm1", KonanTarget.IOS_X64).toMock(), // mistakenly specified KonanTarget!
LeafTarget("jvm2").toMock(),
LeafTarget("jvm3").toMock()
LeafCommonizerTarget("jvm1").toMock(),
LeafCommonizerTarget("jvm2").toMock(),
LeafCommonizerTarget("jvm3").toMock()
)
override fun createCommonizer() = RootCommonizer()
@@ -7,8 +7,8 @@ package org.jetbrains.kotlin.descriptors.commonizer.core
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
import org.jetbrains.kotlin.descriptors.commonizer.LeafTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedTarget
import org.jetbrains.kotlin.descriptors.commonizer.LeafCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirEntityId
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirType
import org.jetbrains.kotlin.descriptors.commonizer.cir.factory.CirClassFactory
@@ -537,7 +537,7 @@ class TypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
fun areEqual(classifiers: CirKnownClassifiers, a: CirType, b: CirType): Boolean =
TypeCommonizer(classifiers).run { commonizeWith(a) && commonizeWith(b) }
private val FAKE_SHARED_TARGET = SharedTarget(setOf(LeafTarget("a"), LeafTarget("b")))
private val FAKE_SHARED_TARGET = SharedCommonizerTarget(setOf(LeafCommonizerTarget("a"), LeafCommonizerTarget("b")))
private fun CirKnownClassifiers.classNode(classId: CirEntityId, computation: () -> CirClassNode) =
commonized.classNode(classId) ?: computation()
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.descriptors.commonizer.utils
import kotlinx.metadata.klib.KlibModuleMetadata
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.identityString
import org.jetbrains.kotlin.descriptors.commonizer.metadata.utils.MetadataDeclarationsComparator
import org.jetbrains.kotlin.descriptors.commonizer.metadata.utils.MetadataDeclarationsComparator.Mismatch
import org.jetbrains.kotlin.descriptors.commonizer.metadata.utils.MetadataDeclarationsComparator.Result
@@ -38,7 +39,7 @@ fun assertModulesAreEqual(reference: SerializedMetadata, generated: SerializedMe
val digitCount = mismatches.size.toString().length
val failureMessage = buildString {
appendLine("${mismatches.size} mismatches found while comparing reference module ${referenceModule.name} (A) and generated module ${generatedModule.name} (B) for target ${target.prettyName}:")
appendLine("${mismatches.size} mismatches found while comparing reference module ${referenceModule.name} (A) and generated module ${generatedModule.name} (B) for target ${target.identityString}:")
mismatches.forEachIndexed { index, mismatch ->
appendLine((index + 1).toString().padStart(digitCount, ' ') + ". " + mismatch)
}
@@ -16,9 +16,12 @@ import org.jetbrains.kotlin.descriptors.commonizer.ModulesProvider.ModuleInfo
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirEntityId
import org.jetbrains.kotlin.descriptors.commonizer.cir.CirName
import org.jetbrains.kotlin.descriptors.commonizer.cir.factory.CirClassFactory
import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeSensitiveManifestData
import org.jetbrains.kotlin.descriptors.commonizer.konan.TargetedNativeManifestDataProvider
import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.*
import org.jetbrains.kotlin.descriptors.impl.AbstractTypeAliasDescriptor
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
import org.jetbrains.kotlin.library.KotlinLibraryVersioning
import org.jetbrains.kotlin.library.SerializedMetadata
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.parentOrNull
@@ -204,8 +207,8 @@ internal class MockResultsConsumer : ResultsConsumer {
val modulesByTargets: Map<CommonizerTarget, Collection<ModuleResult>>
get() = _modulesByTargets.mapValues { it.value.values }
val sharedTarget: SharedTarget by lazy { modulesByTargets.keys.filterIsInstance<SharedTarget>().single() }
val leafTargets: Set<LeafTarget> by lazy { modulesByTargets.keys.filterIsInstance<LeafTarget>().toSet() }
val sharedTarget: SharedCommonizerTarget by lazy { modulesByTargets.keys.filterIsInstance<SharedCommonizerTarget>().single() }
val leafTargets: Set<LeafCommonizerTarget> by lazy { modulesByTargets.keys.filterIsInstance<LeafCommonizerTarget>().toSet() }
private val finishedTargets = mutableSetOf<CommonizerTarget>()
@@ -232,3 +235,25 @@ internal class MockResultsConsumer : ResultsConsumer {
this.status = status
}
}
fun MockNativeManifestDataProvider(
uniqueName: String = "mock",
versions: KotlinLibraryVersioning = KotlinLibraryVersioning(null, null, null, null, null),
dependencies: List<String> = emptyList(),
isInterop: Boolean = true,
packageFqName: String? = "mock",
exportForwardDeclarations: List<String> = emptyList(),
nativeTargets: Collection<String> = emptyList(),
shortName: String? = "mock"
): TargetedNativeManifestDataProvider = TargetedNativeManifestDataProvider { target, libraryName ->
NativeSensitiveManifestData(
uniqueName = uniqueName,
versions = versions,
dependencies = dependencies,
isInterop = isInterop,
packageFqName = packageFqName,
exportForwardDeclarations = exportForwardDeclarations,
nativeTargets = nativeTargets,
shortName = shortName
)
}
+3 -1
View File
@@ -136,6 +136,7 @@ include ":benchmarks",
":native:kotlin-native-utils",
":native:frontend.native",
":native:kotlin-klib-commonizer",
":native:kotlin-klib-commonizer-api",
":native:kotlin-klib-commonizer-embeddable",
":jps-plugin",
":kotlin-jps-plugin",
@@ -492,6 +493,7 @@ project(':kotlin-util-klib-metadata').projectDir = "$rootDir/compiler/util-klib-
project(':native:kotlin-native-utils').projectDir = "$rootDir/native/utils" as File
project(':native:frontend.native').projectDir = "$rootDir/native/frontend" as File
project(':native:kotlin-klib-commonizer').projectDir = "$rootDir/native/commonizer" as File
project(":native:kotlin-klib-commonizer-api").projectDir = "$rootDir/native/commonizer-api" as File
project(':native:kotlin-klib-commonizer-embeddable').projectDir = "$rootDir/native/commonizer-embeddable" as File
project(':kotlin-jps-plugin').projectDir = "$rootDir/prepare/jps-plugin" as File
project(':idea:idea-android-output-parser').projectDir = "$rootDir/idea/idea-android/idea-android-output-parser" as File
@@ -569,7 +571,7 @@ project(':plugins:jvm-abi-gen').projectDir = "$rootDir/plugins/jvm-abi-gen" as F
project(':plugins:jvm-abi-gen-embeddable').projectDir = "$rootDir/plugins/jvm-abi-gen/embeddable" as File
project(":dukat").projectDir = "$rootDir/libraries/tools/dukat" as File
project(':js:js.tests').projectDir = "$rootDir/js/js.tests" as File
project(':js:js.tests').projectDir = "$rootDir/js/js.tests" as File
project(':js:js.engines').projectDir = "$rootDir/js/js.engines" as File
project(':kotlinx-serialization-compiler-plugin').projectDir = file("$rootDir/plugins/kotlin-serialization/kotlin-serialization-compiler")