[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:
committed by
Space
parent
6d019d9544
commit
4500b6ce74
@@ -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()
|
||||
+61
@@ -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
|
||||
)
|
||||
}
|
||||
+100
@@ -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
|
||||
}
|
||||
}
|
||||
+30
@@ -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)
|
||||
}
|
||||
}
|
||||
+196
@@ -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
|
||||
+30
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
+80
@@ -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")
|
||||
}
|
||||
}
|
||||
+137
@@ -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)")
|
||||
}
|
||||
}
|
||||
+88
@@ -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))
|
||||
}
|
||||
}
|
||||
+14
@@ -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.
Reference in New Issue
Block a user