From 4500b6ce74ce613846ff2d581acaa60e8a3c1c80 Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Wed, 17 Feb 2021 09:33:03 +0100 Subject: [PATCH] [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 --- build.gradle.kts | 1 + buildSrc/src/main/kotlin/tasks.kt | 3 +- .../kotlin-gradle-plugin/build.gradle.kts | 1 + .../compilerRunner/GradleCliCommonizer.kt | 21 ++ ...kt => KotlinNativeCommonizerToolRunner.kt} | 2 +- .../targets/native/internal/CommonizerTask.kt | 5 +- native/commonizer-api/build.gradle.kts | 44 ++++ .../descriptors/commonizer/CliCommonizer.kt | 61 ++++++ .../descriptors/commonizer/Commonizer.kt | 21 ++ .../commonizer/CommonizerTarget.kt | 100 +++++++++ .../commonizer/TargetLibrariesLayout.kt | 30 +++ .../commonizer/parseCommonizerTarget.kt | 196 ++++++++++++++++++ .../commonizer/CliCommonizerTest.kt | 30 +++ .../commonizer/CommonizeLibcurlTest.kt | 80 +++++++ .../CommonizerTargetIdentityStringTest.kt | 137 ++++++++++++ .../CommonizerTargetPrettyNameTest.kt | 88 ++++++++ .../descriptors/commonizer/utils/konanHome.kt | 14 ++ .../testData/libcurl/linux_arm64/libcurl.klib | Bin 0 -> 40130 bytes .../testData/libcurl/linux_x64/libcurl.klib | Bin 0 -> 40048 bytes native/commonizer/build.gradle.kts | 3 + .../commonizer/CommonizerParameters.kt | 15 +- .../commonizer/CommonizerTarget.kt | 36 ---- .../commonizer/KonanDistribution.kt | 20 ++ .../commonizer/NativeLibraryLoader.kt | 44 ++++ .../descriptors/commonizer/ResultsConsumer.kt | 60 +++++- .../descriptors/commonizer/TargetProvider.kt | 2 +- .../cli/DependencyLibrariesOptionType.kt | 12 ++ .../cli/InputLibrariesOptionType.kt | 12 ++ .../commonizer/cli/LibrariesSetOptionType.kt | 25 +++ .../cli/OutputCommonizerTargetOptionType.kt | 23 ++ .../commonizer/cli/ProgressLogger.kt | 22 ++ .../commonizer/cli/StatsTypeOptionType.kt | 2 +- .../descriptors/commonizer/cli/TaskType.kt | 16 +- .../descriptors/commonizer/cli/nativeTasks.kt | 101 +++++++-- .../commonizer/core/RootCommonizer.kt | 12 +- .../kotlin/descriptors/commonizer/facade.kt | 6 +- ...iesFromKonanDistributionResultsConsumer.kt | 62 ++++++ .../CopyUnconsumedModulesAsIsConsumer.kt | 48 +++++ .../konan/LoggingResultsConsumer.kt | 20 ++ .../commonizer/konan/ModuleSerializer.kt | 56 +++++ .../konan/NativeDistributionCommonizer.kt | 141 ++++--------- .../NativeDistributionModulesProvider.kt | 2 - .../NativeDistributionResultsConsumer.kt | 172 --------------- .../commonizer/konan/NativeLibrary.kt | 23 +- .../konan/NativeSensitiveManifestData.kt | 2 +- .../commonizer/mergedtree/CirTreeMerger.kt | 6 +- .../mergedtree/classifierContainers.kt | 4 +- .../commonizer/repository/FilesRepository.kt | 34 +++ .../repository/KonanDistributionRepository.kt | 37 ++++ .../commonizer/repository/Repository.kt | 32 +++ .../commonizer/stats/StatsCollector.kt | 14 ++ .../AbstractCommonizationFromSourcesTest.kt | 59 +++--- .../commonizer/CommonizerFacadeTest.kt | 26 +-- .../commonizer/CommonizerTargetTest.kt | 70 ------- .../commonizer/core/RootCommonizerTest.kt | 116 ++++------- .../commonizer/core/TypeCommonizerTest.kt | 6 +- .../commonizer/utils/assertions.kt | 3 +- .../descriptors/commonizer/utils/mocks.kt | 29 ++- settings.gradle | 4 +- 59 files changed, 1646 insertions(+), 565 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCliCommonizer.kt rename libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/{KotlinNativeKlibCommonizerToolRunner.kt => KotlinNativeCommonizerToolRunner.kt} (93%) create mode 100644 native/commonizer-api/build.gradle.kts create mode 100644 native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/CliCommonizer.kt create mode 100644 native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/Commonizer.kt create mode 100644 native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTarget.kt create mode 100644 native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/TargetLibrariesLayout.kt create mode 100644 native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/parseCommonizerTarget.kt create mode 100644 native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CliCommonizerTest.kt create mode 100644 native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizeLibcurlTest.kt create mode 100644 native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetIdentityStringTest.kt create mode 100644 native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetPrettyNameTest.kt create mode 100644 native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/utils/konanHome.kt create mode 100644 native/commonizer-api/testData/libcurl/linux_arm64/libcurl.klib create mode 100644 native/commonizer-api/testData/libcurl/linux_x64/libcurl.klib delete mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTarget.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/KonanDistribution.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/NativeLibraryLoader.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/DependencyLibrariesOptionType.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/InputLibrariesOptionType.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/LibrariesSetOptionType.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/OutputCommonizerTargetOptionType.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/ProgressLogger.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/CopyLibrariesFromKonanDistributionResultsConsumer.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/CopyUnconsumedModulesAsIsConsumer.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/LoggingResultsConsumer.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/ModuleSerializer.kt delete mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionResultsConsumer.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/FilesRepository.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/KonanDistributionRepository.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/Repository.kt delete mode 100644 native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 53c91650853..86d145a1f06 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") { diff --git a/buildSrc/src/main/kotlin/tasks.kt b/buildSrc/src/main/kotlin/tasks.kt index 58b37c62099..51a47280ad4 100644 --- a/buildSrc/src/main/kotlin/tasks.kt +++ b/buildSrc/src/main/kotlin/tasks.kt @@ -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" ) } diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts index d0f46edc9ee..74be09e504c 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts @@ -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")) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCliCommonizer.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCliCommonizer.kt new file mode 100644 index 00000000000..0d6f6c9335d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCliCommonizer.kt @@ -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) + }) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinNativeKlibCommonizerToolRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinNativeCommonizerToolRunner.kt similarity index 93% rename from libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinNativeKlibCommonizerToolRunner.kt rename to libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinNativeCommonizerToolRunner.kt index c8a0bfa7309..dff26f69c67 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinNativeKlibCommonizerToolRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinNativeCommonizerToolRunner.kt @@ -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" diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTask.kt index 2da6a510bdb..984ec24de82 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTask.kt @@ -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) { if (commandLineArguments.isEmpty()) return - KotlinNativeKlibCommonizerToolRunner(project).run(commandLineArguments) + KotlinNativeCommonizerToolRunner(project).run(commandLineArguments) } private fun renameDirectory(source: File, destination: File) { diff --git a/native/commonizer-api/build.gradle.kts b/native/commonizer-api/build.gradle.kts new file mode 100644 index 00000000000..314a2b16402 --- /dev/null +++ b/native/commonizer-api/build.gradle.kts @@ -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() diff --git a/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/CliCommonizer.kt b/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/CliCommonizer.kt new file mode 100644 index 00000000000..bee9c28084f --- /dev/null +++ b/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/CliCommonizer.kt @@ -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): 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) + } + + override fun commonizeLibraries( + konanHome: File, + inputLibraries: Set, + dependencyLibraries: Set, + outputCommonizerTarget: SharedCommonizerTarget, + outputDirectory: File + ) { + val arguments = mutableListOf().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) { + 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()) + } +} diff --git a/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/Commonizer.kt b/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/Commonizer.kt new file mode 100644 index 00000000000..6795dad584f --- /dev/null +++ b/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/Commonizer.kt @@ -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, + dependencyLibraries: Set, + outputCommonizerTarget: SharedCommonizerTarget, + outputDirectory: File + ) +} diff --git a/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTarget.kt b/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTarget.kt new file mode 100644 index 00000000000..35328d0f057 --- /dev/null +++ b/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTarget.kt @@ -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() { + public constructor(vararg targets: CommonizerTarget) : this(targets.toSet()) + public constructor(vararg targets: KonanTarget) : this(targets.toSet()) + public constructor(targets: Iterable) : this(targets.map(::LeafCommonizerTarget).toSet()) + + init { + require(targets.isNotEmpty()) + } +} + +public fun CommonizerTarget(konanTargets: Iterable): 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(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 { 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 + 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 + } + } diff --git a/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/TargetLibrariesLayout.kt b/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/TargetLibrariesLayout.kt new file mode 100644 index 00000000000..63a862f9b8c --- /dev/null +++ b/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/TargetLibrariesLayout.kt @@ -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) + } +} diff --git a/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/parseCommonizerTarget.kt b/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/parseCommonizerTarget.kt new file mode 100644 index 00000000000..5c4e570b929 --- /dev/null +++ b/native/commonizer-api/src/org/jetbrains/kotlin/descriptors/commonizer/parseCommonizerTarget.kt @@ -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 { + var remainingString = identityString + val tokenizer = sharedTargetStartTokenizer + sharedTargetEndTokenizer + separatorTokenizer + wordTokenizer + return mutableListOf().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(val value: T, val remaining: List) + +private interface Parser { + operator fun invoke(tokens: List): ParserOutput? +} + + +private fun anyOf(vararg parser: Parser): Parser { + return AnyOfParser(parser.toList()) +} + +private data class AnyOfParser(val parsers: List>) : Parser { + override fun invoke(tokens: List): ParserOutput? { + return parsers.mapNotNull { parser -> parser(tokens) }.firstOrNull() + } +} + +private fun Parser.oneOrMore(): Parser> { + return OneOrMoreParser(this) +} + +private data class OneOrMoreParser(val parser: Parser) : Parser> { + override fun invoke(tokens: List): ParserOutput>? { + val outputs = mutableListOf() + 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 Parser.ignore(token: IdentityStringToken): Parser { + return IgnoreTokensParser(this, token) +} + +private data class IgnoreTokensParser(val parser: Parser, val ignoredToken: IdentityStringToken) : Parser { + override fun invoke(tokens: List): ParserOutput? { + return parser( + if (tokens.firstOrNull() == ignoredToken) tokens.drop(1) else tokens + ) + } +} + +private object LeafTargetParser : Parser { + override fun invoke(tokens: List): ParserOutput? { + val nextToken = tokens.firstOrNull() as? Word ?: return null + return ParserOutput(LeafTargetSyntaxNode(nextToken), tokens.drop(1)) + } +} + +private object SharedTargetParser : Parser { + override fun invoke(tokens: List): ParserOutput? { + 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() +} + +//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 diff --git a/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CliCommonizerTest.kt b/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CliCommonizerTest.kt new file mode 100644 index 00000000000..e51313d4a1b --- /dev/null +++ b/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CliCommonizerTest.kt @@ -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 + ) + } +} diff --git a/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizeLibcurlTest.kt b/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizeLibcurlTest.kt new file mode 100644 index 00000000000..8a3191e5bf9 --- /dev/null +++ b/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizeLibcurlTest.kt @@ -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") + } +} diff --git a/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetIdentityStringTest.kt b/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetIdentityStringTest.kt new file mode 100644 index 00000000000..7e06c4fd640 --- /dev/null +++ b/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetIdentityStringTest.kt @@ -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)") + } +} diff --git a/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetPrettyNameTest.kt b/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetPrettyNameTest.kt new file mode 100644 index 00000000000..3891818b2e4 --- /dev/null +++ b/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetPrettyNameTest.kt @@ -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()) + } + + 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)) + } +} diff --git a/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/utils/konanHome.kt b/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/utils/konanHome.kt new file mode 100644 index 00000000000..941a49a4ccb --- /dev/null +++ b/native/commonizer-api/test/org/jetbrains/kotlin/descriptors/commonizer/utils/konanHome.kt @@ -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) + } diff --git a/native/commonizer-api/testData/libcurl/linux_arm64/libcurl.klib b/native/commonizer-api/testData/libcurl/linux_arm64/libcurl.klib new file mode 100644 index 0000000000000000000000000000000000000000..a11828a1d123a783c9bf14a7e6fa8d1804b3a838 GIT binary patch literal 40130 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 literal 0 HcmV?d00001 diff --git a/native/commonizer-api/testData/libcurl/linux_x64/libcurl.klib b/native/commonizer-api/testData/libcurl/linux_x64/libcurl.klib new file mode 100644 index 0000000000000000000000000000000000000000..2d09a208ebc6ee9dda2927c7b218b644fb1b1173 GIT binary patch literal 40048 zcmb5V1$5+Ik1rTzPKT2YGcz+YGcz+YGc$8K%*;s#I~8VTW`+)K{@>f(nX~iF?0a>N zbuHVHb$^nsRW6l+G$&O8lZrXflN%z3|(zp=v7tVfgu0$S%v>r4Cz0K zxfnW`o4PpvpNM1s8*v*;J68{V4>p$n6Gepoqar&)7fUzOf3NQc5D*m9_hboywl&Cd zdyxJh92;N zte`8Rb)=>Gh0kPg5kj|^p22F&aDD|w{4A_g)H6Tb?3XCKn##Ian53AfnVBV6x|h}Y zUM~uL-zNC1kGd^>d|tGCdS8C&dFT2*aaveu9iP591VIr%5vki|@%WF0E7SWsPRD$$ zN`OBQdZ%O&d@3M|)U!k-5KU=3{Bn_9GdWM5R#iAEnM{IsOaWT-2=)=Ko#znG>g425 z{b_{IR_@{t%e2gH%HdT^_A8u?O}TY)?$EcAcXy=uEh`3Jdg2kj({grR%V^2tvtevy zIed?90a>IZgn%ecrspr@s{{p+0_pf-ek)ek`eV3m7^&2mo6T)YHPq*H2RWlKYhLaHgtQe{e&6ZSZaiEHIZRL%&P zZZR>WXKL=No%%tC8?Pe1=a3!8axGRfR@}l*5QQ$7Vi1 zB8ulU{>Y-5&Ox0c<*+6en#@!-uuJM!b<-=Ut9YK>GOj&)jtEmum|ga#NY^KBTUWK# zuj;uAlFRLhi>w6Mq56!KD8yHbYm)Vaoza}S^lMFhjbXVzw! z;t1d`9{_pDCSw3@9Bb_qXw9oiki#rocOPnA@r7#9J1rAv%|_PTE0dy%DM5Nu&*qmD z_X4zq{0YXKE1^WzkJ>-;4=jYnoV?c+CykxyT+X50RttHoiObw$`Vc?V>k`-@#&%_# zJOmgwgg!TfzI1HnK1~+%@D*~1n}&Z`noBi#cqz3_meQKL-sWj!8xLqLkwGof z(oM(SI%(h=O%6z>6c50YpJ)Vi|B%#WHV=n9S&?~`hIWS;9VMurD3mZmC%(-mMT8wZ z&l72FQo>uhluzXyq;HOaT1xs_B5RwB(U+0F73+2ip|>TZJN>cVOG0=cxppRHZ&zH2 zP}2nPX*&hmxMkCXSoPGXsBJ@He~@s@G*-3M;3p~rN|?X7Be$BD z#*%fg1u8Ip_@-~2q(!KqKhc(DgH^(0 z&Jv%~R_%chWK_hJD?3g>CGN4;Cw0ukr$eR&)?epq@<0|5Le7GVzsbS3`NOKe`Ir2J zu2*)!fP0KRqv8@|ZJBxP;;D)(sy=&L^Ph?@=*)!KHa%aGaHlCvr3&TKH8**capt~S z9`xRNZw`;!-tEzl3ES7b=NQp6{)oRKZ9-CCiqeNl;P;TeGi{0f6;39*h^!o>s8?r# zYi)Lutk~R<@cE%`h;lbns|*h^$_4_v<4`OV}Aqa4ee3K%P992Ah-U$ zUo*m-76>U~e3WXDW#XyhJ^Z>2WNwVrZ^nXr+!BynWWM@ZSu<`C^J{XoFw@>oEx-h@ zB&a>ZDAnOazRpyq9oqonidrgaUm6{{9PgFwgcu#~4bhOfcx#$uQ5tEfMh{fP*_Ub5+VwNESn~hbrMmc{>$b_A6&#LxyS|9nHJz5(hM~&4+45eK-%5|;{*cI= z_@mIlRSXGbAhJ|Q*TD^YaqdavH6SPsaRfD~rGm@Tmy(d&B6*P*)|RCLyO(^16CyK* z6VN_$m|}&LOK*WXD;g(r&8D=;OksXl>ZBJi%SIBSE}j$ z;T-w3AuTD4LA+fSM0+|45S7VQFA#-+Fw(+Zi#V*zorkqvgX3a>BxSIVU?Y$ z^n>r(blt0oA|U~ni_|Y8gDo2mb1Mx#mL8T8uW}U zd8$WGp1?hzBE+4FXoE=~9}>7k9yZQBngFet7`=0adzTGBECM*Q!e^y5m#W--S9;L*(IZPUNg(M$pGHXL6xT zrLp6bZ6(f}=L0zLQk+5597A$K!_kLUd)q{+M_Wt2xWligeKC)dM2G zGLsxJ&Ly3i;<5)fU~*~Fc4~(`&ou4Imocc-EpoOxgDNgNx=JyZiGVHN*IE#4b)5#Z zdA4@>Ncxshd+i<^&{V;XXI4;3skIuT{L6T&AJj^oh3P*U{@A*sDp3b^%_Z@*v%KVj zje<+YQ%@&qB;_PNNINA@_T$x7j(DbWZ}bjY+TVw1+pMrGP2<=V)b5yHIYjqeagy&kIH{SJIP9lbI7br1m8jce#boM8cG(|MxH*v4^c{II z1mK*x+WL{RFDwAKOZkT2UH93mjOf`_X6#!hHx%@9=8iOjI|_SyjQ5o6$}v9wsJv(6 zP%Wo*rDI6pblQ-yEB3V>q)2i^t~K)Mx<_>lITJ5`{L$Zwd3c8ENM_v#c0ONpMVTUF z=&srIU~p%~%J5k!Ki11OKPpAsRS+Xvhy#B-8{ANU=f>HSuq%I|6nF9{+{$AAf@4=X z8AM$aZ!g??dZOD4^>ULsCK!WPePWkpWlwoPK9wZP2b(fq37zQ%zMAWP9Z$g}bNR@Z!Mc#|bh4ng~eq=W@;y=4hsS&}7ZQ}(;+ zHP+S0+n{quS58+0t`z1P-M$UY@iqdBW=c0N)kPD@m4kGf@|Ed^0zQePokT}rTrD%+ z4izFdl3PmI`vWX`W=gTo-A9m%Y-YQqvcICC*`CgudS1U-&DE+$W)eW%bj>5U?)-KCV!oF=pXUcZv+~$zk zo`vYPm~RC13#e~0jjS6A+W0N-w?e$#zpMAr~7DcwGX;9F{$ zIQ$YY&X!hE$!OcA;F-TPr6p=Y`oj42OqDl|>;%21OZ8NpS_|sCMt%8EtB5dF)wgHI zwAw5EDxn<5I@o(Rag1UEH_&K)vYXMl2aRqu0rHMgy<$JUgZ%#E_p|)2-o@p3%zV3f znJ2r_qUOfhx;te7dy4n7zmFP#d1H5~_LQq%*^f|z`#V~vY?33nbvA3R$~u~~AJ*HC z{%)-5@@7za=W%b0xGl49?83BUwaeL+)6<}i@b$w!c8ETKu&11-h90jL=Jdd*Kfjd^ zq6}q))0hdLAKVgt*MFd8pO5uCw11e1t*>)}dj7I{}P8+$qjD)xAUCH7b$X@r>=)YsIKXE@%HM_awP4*t(` z&d5EW52?56*5@^Hg!xcA_PFcIiU-4A*bVEn15M0pN{)m#>$JpN4CXYGH~)d>4slLd zN5t!=zOWY?TXG&oYjPgq76EU5ue?|9Ir+Ly;cWTB^9hs@D?q&(P;renR|l0oVJFqZ z^1xEYmD<7zxLZ;^a}Gi86OUCb!|v=N;2(R|;gi_8#$d@b?eIV5e)e@P=QqyV?|bVeieE(4X`MVzOV(^%LweC@-$Y;KX75fb`pEa0cQi_&fIs9B%ePQH-Ljzziib_Y0UIiXrTvEai9G22#)d8q(8 zElf45&hgDD?u5ua)BJGwE6y_}8F*N(h7=AgNeGO68b^7(^SO%(N9S_V7Pga}3aBgU zASW3^d$p`$UyP`jO)%%P7WP5MC$L5&Yma2J`Q+`3i_*)6GL-YJBG<5F3sK3|?yBx# zE=*SsLtX!RiasT!j#R!k-VMy56q}Y>mO_JPl{oRQ+o;sBJZBvASYg1Rd6Zc0m)ZNh z0)}Jld0M)@6z*eSMM<@Du&gcjQ)2BO>}gi##E|NW*MuQah&ak=uxlzB>BeWX*l=iL z$!RSM%06>G9lFeh#L_o9$|Z8B_neyp=jj_#KPc(DlG}>GQzq@4%L;~VP7B>_yO6Kn z4ex5n)MbJd#eFeJ<9F7&EV@2}F;&xpjN4|PPOn3L-0pc?rnLzd4PRirf%{yyXTE{$ z8@+8QUgRlW*higDuhGuL|NVNnP1Wn7W!d}t5MTP)Y@W0SrJ65Hv9)I6qMWj#AzLdH zfz|)BYEi9R?$Kj9#4lggP-@DxyjUelwG_HXB~ileRd@A{0Qt>PlFeTZWz7%JS{=a* z!TecojpgkVcLb{&f5R$0@yD;}92p>6g>re1INMU^7*W^I^m$#tjx_Cik#mgb+ibMh zOG+L!G9^1@TReX=o#W`=6nXkEHJRMWqGe;}`zS4*yDspIqL@owBK_j|co*K)^x~7V zTnk>r>dWg2?2p&|6qW}maye;PyyAU@QL{;V^)~$2eiVJ4IE4|b&#dgD8|OZWdc2J_ zFx}Ed*4lO4U>$UxJc(L`QC-C?+^t87RkX}!`lBzcVqL6$aD~mwMt-_yw?{91W;y$b zyf@E#g>P$Z3VkuaZl#FTm6ugCg`Ywi`H;Pg6jVR7?%USYEV=`Z+)Z}iLk3w>FT|gUDAEhENs2J%%iSM zA?M8RmKV7Drou9dm$dSIj>=oyirhObWD!fF*TucAi!Q5TCOXnf$k^U5TaQFuBboMa=#Ukw>$$1D%b}BOxhz%BU%mEaPqAvZ5Cimc<;JSkEH?Y=rXVE4TLqTyvYRBG z6ubq}|8_YSAD~{!;<=7@NZz$9G5PJJIaeLTv|9R%+a5b7jjy5ZGk-bslKhpe$`+{5 z;U<+;Sqh=1TDDHAQ8@>bFF4mvayuIW6Hhp&9m`IkBVt}zB=pP&L@`wA+DpHdFGUEp zob!%3o{5NLkGLu@$y7Z2oLi**8wr@>Zmx|dX{%7{Q9XVYY2-Ty$-}(nqFO~SioU?T zeidqDCZTS$4LY}uBrjoQA`kNHN~|M`AZcbOY@~%uV`%BZ&y}3fC(5-nH>$+w6V)dl zaC}n^b4T~}#xw+Y3_Sl$`;DQ{gQ+3$MGXJic6VLa8g@f$t4`>RZ?!G>g8af?#3uPY zuw!n)|K#Vvyax*8XZ(lS8+)+*^ar+*CwYf@S1rdA`o8E)) z7LGmNa@4f7>H<4oe@izBczEqX~Y5a0W^s5R{y5Npb6 zM5K#zN*;fKhjnb(@(z3EjsJqY@$iww z?EM4|DsjM_(Wz+NHt8~6lv z@Xa6aj!${w*B-pV1$M$?2EY7)kY4-+FQLB!z#b<&Kl58`nnUL~px@sHx7J%=r|xI? zwfQ&ahTEGD4Dy8*_6!rhJ$U2g@m>sfO%d;(xT||`>yH(F{;elD!<p2ERZK3Tp!ziX6)ws`IVG2OL=?>*Uts?+YY+_4Vz#k9b)GlBF)&o{fUD5Ose;8x6qsX z4boTi+B`IWkZ%skC^(pR<3IF`skA4?+@10}lz)F#qSR3rTyq(%eek@gl79QEUS47M z9pv>%747GD)={f?FY@@(3K%Lj*MqiFEOB zjOU>u zs%YBigozuAI~=u$>6ir#Sy?e^hD+4c+(e0^siI|Vk3{$0qZ7E!pGo}LO_BcB;r-=S#eA=;{2IB}my`=28cw#Yn#)*cBD+0iT?=}0Q&j1Y|# zwC@VFSohJ44IG-zH6p=iwPf}PA7``zmt`wTiI6g{<>|prO-+re+szo2qf+b_X=4!Q zMd~3DWqm!x9+m;d0|j&ha|0xRzL=hlFj9A^n3QsAf~e3HRaQxdCc3$MPSJo~Q;(#P zaJ0d~^smt(I>vP20d(DSJJ{5NXcS{yt|`l}cb`Pd-*GycDvA_&$L%C+;I_7_#WK`e zLu6@(80l(<@qk!EXLTBkal!7)$%@u!i|BY$W!zsx%UV9T>;U7xL{&KW(~-ha8EYkuP&oe9 z39$FgNhKC?N;3dlGx{~bygfa4rH~CLh&ZUmVQ+NVxZ7Gwl-d!Xp^Zdlm0_FEPoz)2 zYscbRQ=z2%CY_;ti@UdByR%3}zdnsf@5r$K(GlYUCC~ig5oil`q*62x@&h$65Ao2Cl&o6+RZVKOc{qau$pICXpqo)}Rz z#1Igt#06wbv^iy#u+~RgZ+JI;>Nuf{Q^dfLTW_P8p}cbRfZ-W&nAsg^XfkK}lG%-D zK-w3K-0>Da35dJu*Ksh`X~>&}Ke@(WI7U4VmU-&8ffdGgEjGwK_sT_rq)Xs)T)cT{ zisZa^7R!J2B1qa$yMt{z2iWJc+~QV~^+HC2?Hn?&iJhR*dR${caAkti83qm*jx1BE zouJN=pP-ZP{fImA4Y+xXJmw7Xk^70-?v^HJh`uV1+za%rzaEkQ7Cms>-XM=QM9^m% zwUll7TMF8bi}+DHO&0Xie0O?piq_9)JAV;TpZy97R8z zBTqQw+F#)IF$%*q`r(kL>7|oAW&|m6$2aNrQRcu~#A{}nwHE>X`!ad{gO?Xco^Xrr zcLJmCpN!BtM#c`-Jbd5gpAJ{uc{I(CclJ(zCe`UzXQ-Eu`*%geqy7U+JkJcZ-pPQx zT(SGGpMFRPzu_weHJ{=F?OeL&!kud~l<$SYKy1`U#$j|4iG$c!M0zn8vj!NPo-66l9dFRjam@>kR@Hu!TuRivRW;ybS66b9`kH~BxHpdD z^HdJ({b`~fueEVt(e3^VTMxncbu>l|fGVjNYFKKs>iUD)`s}pPBl4qV4U|UI zJu{Ue@yEHdBrMTT(o|MvwKSEf%Et6bp*D)wYQ8Ee@1^3nwL7#$)CV8pqdWxCae9ZBm zzJM>#|Grt{e{Pbnws-k%z4-S{692wug!q4KlCZQhwsAEvHTgf$i1ME_I+;4#yE+-0 z{x=OJ|IxRx;4BON8w3a_84L*MKlh{98roT!nL4}tm+lN#W$E|>7Np)|^@Es{=553h zT%>xVUuY6g9E2@^!#kM_7ZhCcll=JPO!O`&gr`EjH*b~IX|TjQww=7P@rK9gNthQ9 zc@`OC4ysAMc>gPx++DFjTiq_lb+@yD#*`Wp4gW7MG`u+H!tUzL{W&;OpI_F2?;@sE zI`@8kTd`JKhzO+D3mfGEAt1}kRz^BUfzOr4@N{jiITZeCOS&b?aoX0ds85bm``xAT z-qPq<{~TUTES6tYyUX8ixy@rC47`LI&+_E^zAN~uo;GFb`%ZP+kY6{ZjW-j>KzhF+ zalt~mY;h;DXNuz>L>0biBwjL2aa<;1ch*VM;itW09K7J|f_0GT9&rJT6}0uun~YQW zGHTmy#V)^qsSob*cRKB-?SHrW?{EKnspI#z+WHv6-@;dlfNmBy7>Xnx4PE{nn5392 z3~oWAfpVgJqloMv4=hP$%TjDoY$aHLoF^R{0<|+6fIUMVNS*rwb^|8Rq^G2lnN+r%#TH(U&x)*_RVeYt9Hp$Jr-BM8 zsyZ&1|ssUmc3ok_$_6)4b$tH?kBfj_buuo*Ke2{4@v0<;D(y~@Y` zX60&+YKn-US$~m4sL+d;dpe9Xa$OINY0xRbh;f9Iu_UTx?X%gC{-pOBlmEb15ej>? zf2038+Q)c4Z;X9Dvj6W>9Qi*b(04Xln;5zn{`&;{kNlzU70zP$?ttR>-Z1{1ysf>7 ztBvV@Nu&L_<}q4|nMoQtI@v{8z%)H29i8?9>FcBrSk`W^znMijEf0plmc_ zRBsf12lU@t{+}18|JQN{Lt|@0b5ngAOCw`fC!7DjnS`Ak;6nfd0j+;;^#5)q|6hHK z4F8X6I%_-I|1v|{S$U};h93|&BzWA99TlKL75}m-EJ;IFgj})!rxP zwV}3M2cfkvdi5!=bW_yBtW|PKE3QPz=`wVl_B%0CRJqn!YLawd(k#=VK{b6I6?9Wl zhS&8SPJF+z20o>dPZ_#Qgw&;^#hAg9cLk+V*@)`MkQ0ys@e7c||4a-`kUS9m=-WS) zW9_WcW78(Et{$tDg2W6EpCvF%N?8MY{2GtylivdT=h19 z&tapzflRO$h~5SD|Ci7HBU+U4gEr40fPj`U{x>mZVEvyl&RF-G7efk19p$Kn5+RY> z<47r`{FS1FEyVGV)6(3~mPT5ryUNH51r-P}7b0XKJuSYbevoJh}wc%Cz%XdGdQToTxb1mjQR)ugd89Yy~T?SUJGkM#J;Uo^{7@FL&#)Jgzlq*-YKINYWIrmxc;parw>e>^4ghZhp6+6UQJ_1`d_Ko8Tliu0*Fkc^ zJVk+;ynPnmI8%fov7D~=f&`y+v2le2uDOfs8L^cRWPcNXM7%6-SDq4j$y_XrMlNnF zmr?Nr?#1>ztk02hJ%;U3>mF?h(e!Q+AI@WS+8e@VRNE;k8PT>gZrVn7;T|Lny{92c ztxwRUj+M->>79LzSCZH=@xmVyDo_^7=dO7f9H!32>c z2bX`&NgM&>*`2{Je&yy2PavnlmWE8x1l0sTB?u4FQmr+W?OH6;&tPd|3zdcSvB`!O zhRA39Ld0qEV0Qu?%Zr!ecR+@m>0(sADzh_S7G&$pFp;;zVO@DkeiJ}kQNkU@A=kQd2@H}9M=4#YZ9kl!9`U0q(1$3Mkb{$=-i_qcwlxrF3?b9YIY@sPzF zz5}U3nLZXl6iS@rOrO`bgF+P#XF3aZ_EjjR%i3@*FF1cS5YN3qKz#+ompd3s5Ij$e z&OPVIRvcsWHy|zligTnwq<~TZ8#=T!6z5n?;yh?OWvifld}q^zSv0XLt1pmr?N+`E zxxTu(#O3pCFQz7f?86jbu4M1`5o^SP!`Kx}w8qLs?6k@ru4cVtj0R{Lx;`KuBj*Su ztPhatT8$ePMCaNSnoF^XtltOeP!z5QVlXv#c5-*Uxg)ArRo-WIor(t84cGkoxt_AZ zk|4{Iy&UFSEs_m|Lh4MRTB2x6@jDX%{y3_VB@Y|mM52B;|MWW-?;{qlIy18^#BQf$ zHNKn>V2Sgk7O;l&XE{uFhiPi$h=IRzX!Rk8E)rFRnB6LDLQ&aD(mD-;tCiYd;5X^@ z>h_{U^&FZ;1r+j7=4emC!W~tnJQN&A5`fdt&d3UhD>6AB#HMruJ znj7W1ckp$?_wFNe^Q~pV7n{UkrJiMQ>MiHn59QL4F23qMhvIY!l$Ls#4X*myqo80)-y1qoAN6!8`hdGM<3V5Ox2)KgK6~m6 zzIjRIg|Ixcxh3ZA4f*&+mo@BNzWuD&hhNZ&U(||Y0SA^FAnO96bpqCW3oUUA-5x35 zkIo0Yz5-UA2`AZQ=!TQJ82R=qTbya$bOImpsHnOmBfPQEhz~#&r7Nzf+5lu4W`t|1 z90i?%r%of1S)qYw${h8msHSWKYkD6Aub`%W0Rg~{@t_iresTz%pc^M858GN-PQz8Ugw#y6BHcYB#)zU2eLv~x7CNalH^jbfhn z2@l{B?;FpwY0O`6QDVejbdh4jUuc81f)Zod$?sPmHn^RSZpJ-a1bLfh-{GU-cTSCk#r+1k+hIBif}pc`f@9ZrVw7YZu zyv+MtPUkE4sT?fX!MeLD>BA_G>Na~4ymafC?Yu%8wp1yzMyKH;6SkkyF@U9B4Wg>j zWJ*o9u*TEG-itL0tyV5jNt*QWRVH|pT=9{{HZ!kMNIMek`(7DteW=7@`sBMrn1h`~p!4qDhGvdOb`Md{O57A^R*W z!ESCsnk7Lt5|&Ji)kvCk0CR&{lp7dw`8zIM)FCaMc%Yvyg7No^kk{k*_3?OAq6ox^ z%3I)AH6v#X)DU8uv}Kh%(p=zsMgqqga|zyM;4B_TO^kKzAK7JZDdtMc?s6=0tTkz; zRRb>0B#^k`Q^9g{YFNiuF`nd$b==GA@E&#~ytv^}gTcU%5w_e3vbUrJX$jCvO=@NO zVSn#T$d`5OI-{6A)XAF)Jwx!3?CE@sHBZDIWQ-nR*th4~bAoh%w zxPgz2>N?t}v_MvLm#4g!GWpUxc@^q>3GVljMAI%aIx`lNUHm~*gc{TIiwbGWZ;x^m z%_%-E<&L{t?aB&M+zjnz$~G%yyVX*i9^0SpOf2UBlc`WMpKA3zpYvF9@7)0i%B57S zl3ayw9VWP9D%LElbF3(`*Zs5hF31fgig7aJvW6x7{Bh zEVz;8hu~%xGg%$9*y9Kf=YYPfQ0GOEqDG|3A%%t3J!%utEH}EJZNZjfc1FAxgcSr4 zXj;)G2Najf3`pu{O7Crh6T7^0XrsB5+;$HNygO+9?GO%SSRpq*Ymf-JM;q z+6YI5{Eo96-1|58p*fb{4l*G!2Txrf2kzj4XU?HQ1AR@>FyTv#2N_+wMTJE{XLuPx z=qLf58a#U2RZC@yy9a7$b}nc=#zUYvdJQF6a;RxMe(v%9@Q!eg#J#M1UtIZ3aRwT-OCc-dRyFpRds09dEBp752df=~x!5k;ItLE~Mm9jxK~(HqVfU3N z@Sx|3;?9GR)y{Z=A$h|Oir9P0z=44R!wwt7hE zFmItmfqr~AP(^qM(Xzg(@m*&}u488|56uE-uuJ5jr%;Gcgtlos7Z2|pZbB{&cOtbu7msWO` zmRi5a<)*%*-yZ#xu)9z#B6!s}&@1(Z6<1AM|;$yu+zC1T zc3(IH+cjV>jEo~Z+AY|&o(Z&J!JMUKOptwid_-%&wS>fH1{qHc$hgR`pCNqGcqoJ zXDX|Cw(C{sjB2&CIAse$sSC4Wvj9|A>gq^#&;c2Ckui|lh)i(7_~v0a=P_oBL+f+M zvS-difI$L1i0i#humm^8salpb5aIT_F7SkZF7l1nCZ5AJ!ETmqzfK-fr*M0Fi=oyIy>gwtD%aQr~iD+zYH{$1=RxtSg-2+GcQ_#Qhz0XmH$< zTYR!l(NBcJYhEjZ!8;Wg4!^);4uI+>NGWq)E_k?_f`e;)GaD(cx{bLgK{?O z6+S<0kCi=nTWP4_Z92_`fX_!^Gj*U?$BI( zNS~ZZt5xcB;FDleR8TOqx1}?AvfHxnxFIJ$&(KHB z(VzUX<+r0Z_X2I#1&m}{bYZoPXYdHFlh~;<{6+7S3v1Qfs5N*h)XSPim(|g8#F+}DC;w1Kg5|>e_7fTVpEph(u)vRS;i>D z(#*dg7BN;@#wf-rmfrS>7Q*IU;dOJ%YqWIEW5#wPDSMzTa!H}&tuqvNmf|On z8qJuK#&(lA=U_c)KdMP4%Ei9R3UeuH-KDK+8K7I#U$C*H=~3z|uUR9bhCKt|nAsbfR$5gpnzevHgnF*|?K7;@P`EbP zw6t3Bv{W{r#HbqwyD?;7AZMt>l%^U)8&x&2EU}~q6Iy`JpT*9f{VvWFy(^!!=tSnp zG94&h9I>qWUA!uKM`6H%2NZvlabr>JxF&XEVVB4wg(X`Sga_bciMQ(Lt~{Jr=O750T%Zf9^4ojRJwsU>;}=8!{Ppo#ZL&nOnJP# zYU6MU3_3r%Ap6VBvUrXMQsy`j7B2q)EkzJ+Iyb4R z1`TN@ny_FgG@YfoWlPa0A5_%DE_>LDlXSXeP4JDW+E)u?7;$wbRFRXEzA+DUaM)uD zC417Vqh?8Kkxcu1$2rReo%#oXHS@#ah_iM&n~!kNoeDLT{HBz2B9n{G7(lgmjLD_w z&ZNXrQy6U4MK7+DQy>}5VnEdLj3zf|yx2D{~rAVm7n2J#cK_u!li;DRV$?+GKiHS(^aJu1$1`Z(Iio%u8OsJgk)uKvW5SeLrPnjMqvde!j@Wct z8x!dDp(A#%Wr|7z#0hY_l{!3F({_=D3k)I>CiNdZ3q0|2z7-T~>eRvJ0tP=n!(n!| zGqM>61f*x_SMvqoXizcMIfm>wV|ShI%^L|^Vfm6{tKm@9w4$c~+7YQ}d)*SV+hFQv ze)u|npZ(bZ<5Q}l=EvRaz$xq(aMo74n0s3!OGrK24Ed07N1OX^W$Y$q8`t>{lO=nBrf~A z*|LAp1MnLfs-c_K{VFy;ukDjrrIbB9etQJa&aHZ->y~a%$sUe=JOXTo?<_aQFZPof zr7N|qw2ib)vQ2T^gMX8ny!V8qL@`nqk9sQVQ+`K6ULz!O$#g;@btZr8nWr(fP8Ba{auA1K5)7b?&3lhGef{Ah zLlmN`?K03C0(Bn&rCr6N7lVi|enNF98Tl!vQT#SpKXTwh%o$F>x%GZPp-g_vD{Qc&Q=Pg#)2En&p5R9!54yvO zP){HLvg3+CAsXLg*D=gSqftotE^f1NX(Dsh}r1_ z(;orTP6C`DQUPaMkri=si1AJ;&uGbKe!PpbAmwCZRZNwt^D6#&B9D^Di~2~U*Zo5= zl@D(!akIaTn7B&JRcHfd2hk#CF^yTwvS#TcF@2?nPSQok-rTkhz<2Nv>WP48KhY2p zkQE^M)j12Wfe(RvVnMBt6@dGfIRyj*&;slo0^osDfqard%?Je`cC>LBAc0kZe6m3= zpvob3bP>vh1>k|UcLVN`6kz))5i;TfPJw*VLC=s2fNppZECl-DfO@BaVUY|#cOY>c z7(vWHZiEpuA_6pl-XlQig!^rP{3d{-kr*HY@*Vn7z$Uwf^d0=?&erV;Mw0Qvm}u7Tn}44B7tAO$%BzL7@Ihz$4(^d1GOM!4exXPwj#{}p_?uYqLYh0`$krYTNrzm9zJtDrfFQP)-vHqo2g&hd&^?Qz3wvy6x{Cem#2{+Mx`=$tVzJ4uM0G z5BVoB;%D-O`9H|!?|w8;uoH&*KbXqfeG$V+nWtgQJ{J;ZHeVd5MQ7uo7Qz*>>31MZTnFT2-NaiBbBcHU{uo zOT_3l4e@*Qhf&L_ZJe-G4(RnS*99c>D%M-lrw>zKeL=HYf028|SFQZQK7ENN@rE++o{j#<^T(D!_Vvf&235XW zR`~VP{Wqju52>xXZQTxs1oc0&y48V^{p$STeH$U_e`qKXn)cNHjt%Noyr>aE4eoL& zFc6QT!0A0_0F&2Csaq3Fp0@I7T{FFx+x@T;-FOY}lT4m+gO?=4CWPq5uZH>k4&Q*p zn+1xU5ykGF(nLPJTrb_dF|S`@ufPw9d_?&l639m(!ebx1fJuVcRGwa@cRBeq@73N* zzkaEIeRvI!Q%vuPp=%`~lLW}54@~*-pOvLXe2fy*2o`nhqa*usKX=L?M2&!f67ei4 zPo3QG{^GC@=O%;pgG3eQrVAg0%@^-XWkUhNI{^Bp^nn6dTe6?R@s|g^ryuyT?1lY5 z`>1%m+2OiJ|Cz`6&;H8)%=i6gUK&A9c8KtQPPPBfFlYBaRuca+-2cze9O0Mj5WMOt zA*K!XeFEjP?7t}7<1hp3RN6-U?8HaD@?^|fT?CC;ACANd?U2cp>X~qt$rE9&{=LdTd=LbYpyaf9f2Z|z9F|xSl2YG_$2ex3ZKN(!vaoGr8V|9TQy$I5Qy$aM z?YRB|S7ywvg5!c`Yx&1*bdejb{L?HHk(-YEQ*T+cuA7Mb=MTUfSGmXy1z{IvI8GNP z{2Z5t+hg=mzU!pdqYp*+c8u`cdmV)bAF{{|kNl$#W%%}t@SLZ4{3-rIzH4O6_Jl~( zHdXK`J{7=Y|Q17{2LFsV-}z z1ud=#VQ?k9yGjeY^?x-vo)=~(mwd*35=MNA_$bC7FJ`md#Dl}qea_~Srs&o+8mnJ~ zSE%STtj`@gw76 z#CY2k+eO-y+ojh@w`i}DuVep-yGpuBxJr2zc@}yWy%&1Vf6n`y0iXSS%zcb~taXy} z5cW{=_@nXF`WX0F=p@cb%|psVgCCb2mz|WI5G^?)MJG-tIZ~KXm{Oz)II%wqd@Nsl zO@C~E41WCnzY9RDqn&F9{jnJh$dTb3lWfYD*tuJc$ ze#>b3F79kyPaiE`UfH;w%$=Zl8K)dN}T4yP1L$elICR|2H)~bGcRXv~>0Pps`j7DT8)9i@3UM=6^TJ22wE0 z4$G;QY9RC*gMI;)+PYQI=pWiba*5IiR2wU0A-z+v8q#_;u6m`xB7Sw%DcB}7V)e}U zt-Bp4jqRicrfB}1jh;7KZ65AwYG64KvTB)P74`{pR=spiN7*+n;iN@MU*3-eOS-B>Ev;K*+;7#%~Lc~da23)Jflf2J1A6^&&- zs~sE~tI7~6CSVO1Du#|DYf7E~+jcPX68N=Z>)kUxj zn6ZH>PV7UgUP_DHkV~rgjGUJewUpeTMPOEKJr zJj@_s=M{WVW5RV1GAzi7qL%9gOCw=<5eye(Tdg(*C6|#P$?9ZX-r9!2=6fEbby>0j z_-1e<7(Dx%JB#vfiA#G~gtE95I4tXT$zQ=YCFzy;p<^Jl={<$le#Q?k{N){XOB8@^ zfn5xkA#>vjZ=%q%)w}I9qK)-1K@9#^N#=D1szkP>L=;?zJx@Q4^JvrlHjp@YXK@29 zt`ocd{ETpUAv}Eve=djtvR7_7hzJJ>1e2>lClTt!DEhyi#V2MH>*#ta>gA&wBP7hC zZ5zO8oUw~rQqGDeM9_9teku_P8)Yua<4MsT5?Pve{!yc)gSN5%Xj^KaDALMJf1?^r z=W2KIpUt+e9%!2U>ORd+TRZcfyUu#xN-%}Dx-c@r-i%e9vkSpeiTX8Gnm=AV55Z+L z0iO_(XaMAbL}dgp6cx^{I|z{(=CaqmxI%J~k-bUFP~>6ygSs52A8BHQ94Z35k!6Ra zFJ8Hd%ZHaTSjo!M;Tf~3$nFC-5@sP6>vGtTyARId-m$U|89>|9+-tAJq6Hfz6@;x1 zdX#4*iNB4FzB-+b$$W^gNQ@hO(i?QH+^nb}_g=p25$ERP6`LzDP`^vdoC#Fj4Z#VI zt`@=?FwWqm8TePU89MXp#h>j^Q(xOt3B6u63cJ$6ou>pnoTIBGG2ll2W%{B> zLFOZxw5KC}$D5SnU-RM3M#2uHRNH+|#Y)uatAkIcsiMB4&AYx2oLo)yJ5TPj$)iBt zv+<*T9^LpUi6EWPWBe?p>0{<>1U3)dBPn*5?5Tz*9hA4ZC>=}}U%`*`5>df3toN@1 zL@00dl+H6FEmCiHNjm>+D-3XKz^H~mFqA{^uK_S@Oe1gsR%k}S*eU+;w{Zvk&XeR1 z_%7_836C@*_db=lO4-^a02Nd-nZ9@0)9@oIlNEGrNX8U_-%9+8Ttgb>3jl@ChSd{X zM{J)YRF>=xHB^@9E+JHw^iDBU_97S-Bu8ey#Qq=lR&Ay?Sy)crnyK@E!@o6v4~A*Z z0{q0nXE$zgwe_#N2&~m%8@1`~zk_spm#`iB9Ph*^ zJ&_G`WOr0*CmG@&l+)dTgEjgZw4`?zW*>|wJ$?;zNtrGi)^fVY@e8Q9BAISSKVSK~*8NcqxPu-ISbMjAE!}tc8 zYRK+R*gfM1b$_{te~|9ug!Kqs#=lrPO_AKOv2T#ywWKCj0luo&v;cPe`#1J3;`@Yl z)-wBq$SV>bsQbTBz9a2zN$%*}IIYO@VU+{MbD1<^@@L|4UUNm9f{9gEB-@;xz zVKF4p;vbmBM-V9AGzl8ZuJLpM0shkDR>*gfYtoG7j)-H2R7axmW2)oQ04f+4rZMKe zfAN*NwVL8T6tF-2Fv4Cf@qdO}a*P-G*X<00KL;G7#Qkvx`< z^u(!dA25GW4eyx0<}_~BE&ED*{SO5H{{TH|Ow!i%e-QM$g#TMV&-7pYd{^p*FX{;T zNZhRmL8N-k5p1(iq#zXXFqC}>^PQx|TEvmIaZ^7lk$Pa~FbEOHd^=V=2Aj1+#s7wZ3tHfo?d^0t>y;XAb(!<=+viPi%H2}g< zgHoH>(Pe$KCUt$YGiY`#t)aveWrXqExG^S!uEIos$*EDZqRp%|U_D}-bO23H8gsAH zpy*^yj(j;HqV#)eN@Jjmv#7Y?O~unOxw#aB+L$4xe@U5HP;HqW6McfrkR+ptrq()N zzOE`XcnqLBd34z?+1IRcZ_$@++O5&5SBR+u`@-A5#$04ndLT%Yq5yP;joWNePA_JN zADpOf@rKt7=c+bO6>cUV%86Ohc8!29un%@pi_xddG9Dsv77Qu+cD?_PVssIM0G`w?68-pD&j{$rt4w+>sm$$Ni;*yB%G z6qdjVl>VX0wqi(8&6d;a;|s3iJAzjyFF~7A$(g0?O0&3^#sL))th;WEYG(5=y|E7t^G?sRMb4$6OCiAP52~b)XQ1S*fFNkq z){quNq+_kC10k7zGstS9pvFQcta+5Ul}4U^OcXcCF@?|b55)NjwQBmQOgP; zG|g1gMvzTEEd+Rv-1bWJ-7A8;(9VeB{F%F_xn@nT+ZWM3m46Kf!6{OGT>=Db1)-X3 z=k|-Vkt^N=)zGd0PV5Q#F~xCAeM22T9Yu%4=XqVJX1PMGvNoB5Za?BPudGOUUCs`5 zs*WTN_@}!$iBnm_g2(CN0p+fx@uz!}f^djiSVDl$aE%t`Cz4MD@MvjJ?(f2=H@ zhOcm=FYXfCFF2VsFbVQ?Amm&bR6}&NmEPNc3} zGp}Euw@%835OA%_!nLqf*bKQ)wPcBIXkz}JKaBQw;bCRXxVUoWInp?5UJ&k*ZmCmu z$*4qAYeb;gV@|Jx>NZ~01Vk}3qMr{>DGPhIHaI; z*19M>Qsws2f{23{Lr?Jdj3O$ObtRnKQoaz9LKmO)+!CxN_(5AA1@A_tm-xfp>7S=v zL|r2d8a_2e^a?JrETyW~qy#>TuSS(hQ8EcvW_1m9fFJgrhwpzYbT+A|ozaX`U0dI< z(lN}z{qu~_z_!{sc1#;q2i<`k*9~f<)IOlHZ5-}t^+o&c6?1n}|LXm688mYfGOCyI z=o1=8c-t4#dIn6+Hyp4vFDqEebzlFe54Ed*RL;|00PMpM zI#9=8J|05ycV}lCVj|Ru-pyyDq1O;3`iTzwb?2R^KM$W+xF{uPiIci=nX2CcA6ex> zS-)xjXN0b5si9O=>hx#!Ox|2Hb;Mqu06H%ORS;1k?_Z&urzF-@Pzeuiu6(ZK-)Mo8Vvwx&nwdMQI1Cq6 zGX9po<-i{BW-6swu(Qg!55OMir5L#r{B+O_nn)_d@KQ9pCtLO zPN)za-Z?d-qraHpBl$(pN5i&EC#82xQ755-k7hRr`1C~M$sj{*i2W`79F8<^UL##& zJJ81nADo0MxcR{?>fuS@&EndA;S+Z=8r^}|UkE;+wuN7<%?D`;2zMVQQ6GF8z6&+wm_5PshvSM zDI)SgjL@yC$|c^W>)WmS!U;0_14e+me$0a7j`e0T?`}}IK;9{h)YxO&y8Ek?tkYBn z5clv+FJaO?D-ikDq&copj`Yh5B+-Ypgd!JMne|xp=3iR16xF=uZ>g?zStws+UH9>d z^=t4@yl+dWuSg6pP&d!~g_crsi|?JSk!SH9sGx5^)mP1G?qfv)l5sJeqnlQT;gU?EaoNuC z9mga4oO^b8pL1oO=_KeJMuEf}_axulaa7(T==_iViVqT1nU_TR2$HiOjUQPp_A^u& zls!*^y%jAt#yBIv57AyY1K%LS5CLq6qnx(;#F?Yt^%Emi+FzhgpQu-F#ZP=Y|6iPP zy6`{T_@6YD!VgyYe?_XIXZ^3?tMl5HE&dQXklhA!^jhHIFi}_?Jh;ZMS;WjiXg`JY zt`5f={7zSsE+)(OVsq9h)r8%38w)eR-$h=~M1kcndF6uWf~A5`M1epe%0Wo`$p1O` zl(%0u4!SRUn#RP`;pvDd+dO0IdOSIqmX?Iq*~5h8ZRYI#x+z)9a6)y_SA671F2n1k z&w&+jPtDQhaqcur-&03ysIOH^ouFPjAZk<%yGGewN!6f3zvusZLU5{xg88N%EBg+9 zv^XAm!D^?gPX^cgtWn&!I6`F<6$3IsnLXYe1y=iT{IL=OXRU{B_4*!0mja5s7lkp1!>!>8Nhbjve6xU{M z3;hJ>Y1Pq3aQaxO9`SJ<|B1$D3eHfh#HxA4TXd z2S*O|;!RDzaktG>Y!Ifu z@(((P#;4WZdh<*38`bIYu*PDB>l_f-K-{24MYX0)Mdl(&04+?tCX6uXUolm$n-lac zy!&3I``*CVVcTR1(Tge ze8u9dVlK0AFq0`+?#ZOohpHVjL%}x^cv*k&UL=?`)v7U;qh0%Swi7kP;v(GhQbjs^ z)l?JFBn0PbFW+OVEmaEm5uFwdg_p7ykUz@fcWU>VWRuP#@J5bLUq0XaUeg5?GP=)L zR$WEe+Qr1s`mLH%;7XO+YF%#TpY!WydKE-oFd2t@y{xWTp^= z5mw79_)ynT%J`I=b_tM)Hlh6TmYSX%E2#cSvpY%Q2ZCvIJX%E!0bONjH8QR>E$R8kB|3H{XRALWL`EAgX{Dt~bc`cXZYy~t5nhHfE5VR;YB8@q zR$08AII=5SZ0TXP0v&vo?GiEQTtHnzH*{67FckRnAUS4!UOmxY_GK!|inh(y!=C8a z@cN81P{DhfE@hbfyg#b%{S=V!Va!Da%3L0$wcNWbf6c|XzML}_TGsi%5lubz{Y>YU3<{Lst&Nq$yue- zoZa#Ci5pU2;p1As$-?#BklenD8x9O2eOl2IMP>);|jk@R!bg?+#|c6fZ@x(zkuN@zTbf1E4=TBaYJ_J z9>E0Y*0ZMzqtnXli zlq2}a?pPw86JFYfa$;XxBM@U>f`@YAUy6qY0Nut6fII65X_TEhthkrzq13@=YDN^k z+EwDa`XSb^@7h(OyBrLTFhUKl*ufa;>A07=p};}e;5%_71h#+p&_ZVMjmV9OjZtO~ zg;9CNh2Uo4jWlNIja7OJQQ)R4iL55&*sfzvhH;B4x^)M-IC2Zu6y0kiJ6;1#o`H*O zSznQhEtv$QPu3-Tqpf>pFO9b zMc=l#>UeCzLAI4s7U!$4^u=%XF8*_0?V27U&!(Kii^4V8`w)qUx)HHag71_2SDG@_ zOdY_JWl4(o=Wx`KJR|vg*eBQgCHX(YAJfa71QWixAK;9S!M9&yftwm1!=HVILB4r6 zygLaTSt4GM1MI;PbMA;2xTsr(PhIfPBxGddyHGs;(9oA(0>Z*Wkc1x{<;CSC?>i-vU%H>ZpHI5D`=7SM zxINa-*;!mJ?uP2EmN6{`C(#$Stq;*z*(avIH@x4o@jG1IEgyF5iH)$J(-|c>nSc4a zH7L82M0g<&$FVJ$ZpDK0N(8VX`%IL*R8earKA8ZjB*W$??5v7saN-ybN6!Ulj#iCc z8{-LBtDuuG-``E{XrA#u5k3pZ~L6B1i_OosU7i-WP7@H0iI&dLDndYX(Rn2si^E&#nV-!p%Pdu z2Ja4_7^scnMep4_bOu$hUSMU4~D z>g8flA@qa@XNv;g1ZgK{CIro_)#90T&YLKpcY#@{-)vuwE```hZ~*FAL9u7qu(B9I zQVJ!6lJLYFicZjsc&r{;NO4I7bH9E-L$OM&yrWN~e!KQ7^5X)^)Om=u(^8uly?+~z zwWQ5b0{h)jW+BE>>q!mxOlZfo3KJWRQ-Y_GDcDPgfTeSjU`aVCQWl5An1zW8R>>xx zT;Tiyz(U<>?QjDluqIpo9ErfnhTm@|*aOVdhszY_{ca_6B9zNuKHn5(tw2y<&a{)8 z4RoNAi`-q0pL`~NmHo*yZq4FqU{_r*EqADp%_CmUGbmzE7XUp{ODum_2ml)#uj?J! z3K~F6z<}e&5agE3e*JA37)hM1Tp}5-5H;bNm@#S+xR_TT$~P;$Evo6P4noG!(p31~ zFe~?aD_MZ)N2Y?ztUV7h`K8UHA6--gmbxivjJv1Vex05$z7s=BIiOHTO_X$k#&cv1 zS|GlV+&pF(+OU&dJ({MfT&bMQRnyic4b;N@4<_xXkO|kas6th=%CgyE0xi_N*zA6} zo|k@u1i;mGZJvrBcjwpPZFsAt6i#sq*m1HTQcGg537?eO9}sm&zI)m=8SNy~Fbkt5 zpkFTTf1(=)fWD?+_N4W{d(%%tOr)VpV^;w!f#Sz>iQ~#RtpkD3+jm8Dy?x37g07{~ z$>Is+FqD%sUMojKbv)FM0UF_DHIav|EW5Tka`RIZ7u3~=m8zv?G}JTK`GQTpmFe(O zqwb=LC5jd{Arqg-+u}tyR?Fn?bL-^mm{cd1H*&ABST_Tg+&q zBZVT81MU3na?|<^4MSmARuDL0jU|dT_q0>=!RV-IWgHZ`KP6B%U{qWGh0r_c*rh7w{aNmbIwN68Ts5NVxyirsgb(2lutY}-QC!zfv}rC`(G-Dju|g0iS-xT)(c zyN|c^)ytQU+bWWkXdU`)XR|W~`_YV4tg5b$4?9MACB1TZQ~v9z!Pt%SSvbcUZZPl~Re^lBy`WRz$xlOQz_aCC+s2tuy>e-Yxvq zHTC&*%J3G4(i2TpJAv}&K%gC{Bx3p`e!zSUJP$_E4puvI(GHFWUbfmp?!g?ScPXed z2n^r+tuUHTrI0;}$IqW_<`d+XWSh=^m!8WWvy|jNni{?^FWzFV?=8r`6a|~+-cnu7 zr@0wKNtxz7${x(GmG0Fv?K#RV^+cX?m%ml1X!9#SU$5|53!V+UH|bqG6_;M#5IVnCDH`j++eyjq0`LUM>x=oq}HsoK5n>|S_ zo~aIQm0y@y1v+tb8-`0PXDxS@cqPyXW_h(E=+q6LIhCG3t2}th-@4?xfOYFNoa5EIJZ zcTnClkbcXG@KzswSB;Vc(P_x^5IPdilLu1Mo|=)9j2^-&tH7Z_8Q8 z?>YvMe$3}6+Ciy+FE@in@{xan%i`CiVzp+75kU?oxw(m!jFs$i1-WPNP35QctS3)` zRrwaqZJTA3QOVajH5MLX=1UQ+yv7g7^HwaYVAp2J2#4)(POL>s;|>xu6SdqO3Q{+v zNiy2i73-?CoSG|%P0{)1S%Ou?%}0#XQ!~5@i_QHv9DK_1rrszkf%w4lO*UxhQ_bZ` zCv<=RSf45@n{&3{mr8Gyt-1F0H+|KU9`N9ws5JKbXsbB=+9Wy_Dz*Jn3G3w&v@#4f zH~QkY$2lWB2pU07#l_0Hl2`59H zwaUj+wx`FLiwny*s`{yvZvlhT7RS_;h?6ly`(V*ZGvVe}IfwA-IXq;+7?!{GL;od6 z7E3?H5<7GYY`TK-KfmTG8#Ye^6EPE8JN^B-y#y)Ka-3l^|D2%^Gnj@|)?ld=h6V7I zVLR^utm(~RyF+@D_9*OwMexsF)^N)bdA!Lehs|qC<+uvt&<4JdX!A%q^2rzq&A)pU zWiLSXnmWh#{T-n?Z$7{&P}t8*OBjSgn^+kFcO5(B6tytGb6xIu|V*|(DQQhBc}aoprr++P*L_cd(6UMFr5%* zWV)pD)L+A|>tNnf*%`|eQLAsxI;3w9HG$pct{xlJH|tN;TZZj3VlD2{Ydj0AvQsBF z59xI$Y*yL&EYB%9TF??ixNBqz{3s8C+dVYLLLJ1y;rznCg(Wm5Lz-S1ukmhP3BGXL zItBM-i3IpT1PhVw6m^AOK)ps$ZD<*3f9cvi038rJoF5Yne~w~qWztW-N!lPpLdMax zxkuh1-5ayX(i4dKOicYg0DBG3kct$Gt@)9FIQYwbc1#s17FV*#J>~}IzCN`OITBaX z_R4txeRyHh-S+}=ADLl_T{6nH$vyI7V8ewi5;5dur5cK6h%&3inW*bV?IYBrd|g?~ zwwHggDyhM8-%5v4CO8bL(i4nkM96`gdth)tQkMbrQk2j zz(lqB91TFZ6x<$QI8=4rp23f&@?{)D;Pm3>vtx!M4BRq3`hKq55lLQK2n^phX2zY$E|7qbk}ifoS;sNg*iDviPqBRq8Q6M#u&@^ z>f&8q7(RRIqmvD>?sL@Kx1;^pVi+(4>J%PEd53X9^@-zQ&)gMp_8j7d70Ggek6|F34=|0&q#iNAb}{zx3H zqyKL|e$4;%e0+3z z9)EM3AJ5U;QPb?wgZIQ_t<16^xwqHS*S}mnYW<_(C%emsTpY#OEUD1^3AJ(LavAS& zY8YHalbH}Zgg!H}+&n!FJLpm0U?V~>z_J=nVyWe)C*k2zElYtAcGG5US-4lD%Wi0W zl)k0c#QsabAyAEz^-0UJvV+!B`A-_lT*HWRKR0eZwTpVvJOP1}A=l%)DML@T1F822 zL36ADN=s%UCgcXPR&C3a&0MO-(s6g9Dawm=8ml=j4aM8O3i3 zEB4rC^v&d!EBlOjjIeQU0t+yQB8`5XkX1d7ylkBz-~`!4V~Or!G__&$jy)#JWJPn} z$r0rYcR(z|4Xfq-*g?3hDx@`L`lxecL8x%-D#;dJ=zg7dPU8zn?^Lk@_b<62>3aX7 z3j+8nD40H1%Vx|`2ytd}gzDWX`y#=7-Vh{t)RAiIBjK>{m2k^cXT@5fSkYF0d#I^i zyT!)OYDyd6EQo6bkt`sLrXx^*WeONIakl{|n{wuhOUbQq%urNsgEgfg2@&Tuz)X$2s3G2JbPjVGP`DzpD;BcoK8|7D=8C4` znJP%D%zF1cxuR;^Z#Jg)Y<8{>Xn(hCsAGs>)wVv}#g_vht8-70v&04+(} z+a?443k!r74TexG_$6oUwceFV!d)kVK=5m}y!0@^azJI@rKD-cb*UehWag7lH?A zy3+A2QjuGpbUgO7<7$#UVI08~R{Er=87{(Wvd5J7b(-4B$z97H9AfADXM(z=(eM9BVu-g zBLYg#dP)OWXlVyM%<=WHB9wnA#ryC)~V*m?MZ>KoRACjCC{XZLU~ z3Dn>V*1e$NshANTQ)*g(X3Rg{Z5eyTn7X!zW-lvoDOd=FDZc^1B(>M1Rgede*2Z zDgB4QU&v+Y%u6mYyfh9x|H|fJU!8GmRnHOSHsDNwc=H!<}<0JSR?K%EBQWP!Fz z6-@A-MBZJq4s+Q?y2cP~6MKkLwM+X%g+$R3FzCF>A0!bn>AczxBt@J`S_4+)Cs`2F? z10yv3XvAb5zxkKXP@fX%?zV?4I&8PbsX>1IV&FT6 zLh-4A)KGa6qkeXy05ty4KOi7}>46+bsS;}0IRfw^6ym`9Spw1p@!&r=LlCSd)kL^ zj{{Y@Tse5l_HwP|zrA2+@$xioxJsm+mo_i5EyJ7@tP z50ONBB2L;PUfsTV2i&{3oCI%MI9-hj#~db2q>xz&kTwQ|FH$5Qvt zd}+BxN$@8-i2R#1-1jG0=o`99Ap!^uloJ*f0R-+6q6kZ#5|SqFr*Zr+E4z7ojd%Wo za69(8j^Ax^?pxLN#Kd&Df|j9*=ZWYG57KVgcDdn!6+l8K-tv6jukP~nUwAUju3)57 zqF4VUa?_;$tto<07HHa+4xDDm5NAX~5JCy3f>@}Z*Q)n76>6A=#PNXxlTuAHtW~Kh zu&C~jOTFF}|E4@8TZwX&_eJCJuG#d3?EC%%S2ZjlF9-?Fi5Sxj zwq3zRKe*No;^_|P4qh}`%r~>dLwvGv=D2OtMtN)DE^E8@d?k#bdiqmLpNzF3vX*46O?wLoX2t+MCFmLYV}49PBpHs^{LiB>*}cxrb;mv zf&Mh+*r;)og!*U<+UV+o2LQ_szzOkL$Za4rpMWN-Ey4l->_!VO9L_=^44^>0oVA`; zPu_WICUm@MR7J6<#lejTF*s-zV9SOAw@Kv*4Mu61Qhaxg$l^Nr!ntdP;C;e)Oa@#C z*IqSa!BfUH64-i=rm;Of&WvVVgU8=<%3vmU+DbawU?iU;iUmR3k5ENiLjrMwP&`&= zLZf6;P7yLVLJi-w9x~z&j{3K}7KNb9sTC}$mDk3(G?&&b?zbt*#r)%?Rnf+@9___z zo?5Zwmbi~b6aJd?JtLwqq2Q&31nTE0fg(Num#G3lP+8g;st|Fi$?Y}EG$B)?WOmU* zADQg>2p+$YtKFP4OH-YT=-nPFZg3S^dD9er>K&toWb#l^U>4Fw;$o?ue2=K*5N>g; zTfH2qcQKD&W8J11wUU%6$%50yf?FgX+*hh~ zU@!a*3AO@)pMvoWx6Z?Z=~#v<`jEmFjS676C%gTjyI{04LhLJhG?GxX?py+Rjk;@; zP_&)YS0ASNGbB$#SKkGTwyF`9hnCL9dm?dn35uQCm=~(k@)~@wC#sA!$LVEv)ADnK zzXYrGDe~9VRJr%doTd!o&;2ycT$#E_7C4f>Fk_>CU+i5A>Iq(Zmq{rwK_`a_Csyq7 zKuQfJz>q$Tuj%x9{Al7SY}6W~(1FVp5k=z1wWeU{Nsjy(9!uwzMBO5bGulW&!dIhU zkyeq0D2R&FvpNztTswsFA_$r(h&oCjQ4BF*lV*|0z}e`udOn?+Xo=EhV{FmQ0_yCn zi>2FOUU=z4(uBU8@{Ppd6rDHy>V`X2G6XkwovTd(bj;Ke#$WmjfW-5db#i1q9)llD z{W%Z=@aUa79YhkfgPazWdXSa9)hqKvD1B1(LqO;7cN`RMtyFC(7oICZsV3Z-2U?21 zQ1>?hybyTOfAM&X1l!?DAZqxMOAOHEo_i=fm6jEp4WpI4p{!?-?M;}u1~+<+a&k+} zBUTNVCIst>wE6|s1M?N3TH=3UyI;MVy$C8iwJLXoD)9u~o&Y|Ou5|vae-5vIf|q$< zDYGS%@2rb!3C`b8Zwb!(Zu}@x)O>>N83R7h_Eb=Of>XZYJ0D*IQZpwcJjUA_qnJO? z`>Dj;(Dsr5+`+f10Pg5pN)(@{n6`AAm6>E*4|oZ_`bUj;g62ogcwTp)EEtKBZlRKN zryiVvg{Yrf2y-z+xgOZ0_i$L7Y!ZJP2YxJ*rbCe80(cMbvgWZtBJ5vCyukI-cBvp0 zKWYf9lI%S|F`<0HAM}p3co`QYE)0y!UjcYb%6^@7&(!GwBnVrS=7KB^bY~w%wl_*( zmrP6{u_IR)6dVCwpjXoi&KBP55;y3c7x0fdeVVp~&oKSE3!dP6(g5zzTELzslr7mF zAM&;!9eQ9({4HqYoV5{JcQZqKR^j`O$QIXWrN$PQgh&7IGh3me*zB#X#8Sy+Q^#qQ zLCu!*d_wZJ$o#ij3C3xczy1e4bPs>dzgf$n?e>(fZoRWDz-LcU5BXUE9dTEP%^Kz% zk8iFjYbAp*xQq#xLjUu-Or z0~Zm$Ong_vb5d7~>XP!-k|Ik%9Afw9-kEC6w(?Dv$5Z*E_j0z3SzMVGL&CI0+~<%W zCs!Ve$H9RHbJ8?MmLP472y|={lg7YqZq}GtR6wGdv*&J3(USEH1`7*MF-^{R@rcx# zS6%F6p_pQkAX)Zck3@@_T{V?o=<;o?zMB{Vn8*Wm2YWWvA4~Yx%naUYkQ~$XCNXqO7ZT=FxRwEsmw(3WpE(XRx(quSG@R^Yqp5`=3hVzh9#};2>qw$&SB25b zmXzR!QBs}FkS(k^#g)ShKLytLU=0v^(v9%y*x_YoMQVQ9Z_}bR*$4;{lJ^ndk|^O5 zmv6rhNr4*LvE!5hslDBH|>?T_KDopvS*~I z)AvFX0j!M=N)x~j?3=@;)h8HG8AEMDLdyvoBAPLVdf2M~DwPLd$otLpcajXy2gVcR zPVHc-vI<%xOl^1eqvyqac?zt`e@^N(Py^l?i>DeyPX21I!b?O5RK*d0T;eaRL%5z? zT}UEKn8@n()s4jeUQI7>v5<_|C&J2r34B%%T(K`^7vzRR{U;}wnTbb4fYQ&|K~E$R zl%358Nv2rf1;C;_PZqT=Zf6X+_nazOAZ4FPvLU+?OqcC9$nvjfLcjV=p2qdVYck@b zV~XF2imVb6Zu}K9dWP zbxe_B1`KMEIXUcd%kC$8qvwuHJ^4`hnotWjZAfibQ}S$Sw$j)TuTB4N2>bBW*Up5b zq`mAfz)U~%5Q#*XG}xn7-hGICef3;q0S&7ASvJI&jEkt{F(k^wDOon~kHpd(3XTZb z)g_i$SnGBes$V>#d}iXJKS6tleVZ(&&3N#-A6TSWmB=##so624JVl`niZHjh<#sgBy9Q)CLqITY3@=N5hX77rVl zq(u@9>5yJgiN2JE>l%tWo8^M7o-dulINU&qg2fsC1n?M^*qZ`pVBXSR;z)qN1v)8k zYWr7&aN>~n@We|R&+!OIYQ@`}BOsHiKXGO!DA{k;(IgUO+_oJSxhlqGwy+P76j_Z0 zC6Mjs0l1pku5WBhFA%ostQrLiO+VKQwR5!rWekE9A`)l~QiNA|q_xp)7xKlZ2x%SR- zp~aRi25HfZYYvQso`+g7Cgv)J-jy>`pwMRvMX#L^Lzz27W5Tj#_i@svlG5|S=RQlwbZBS; zrw)ogYIZVG)1pE9$J#yJYrW!SN?jd6*;hP02RTb~d=A}zCA$hAg?0h?Qrb`2!vCoF%5CEeF z9PM$->=Zy}aF4t)lqvWLNBqQz?aWI;_D1aUh?j*UDTfk(#HqYwKw!8Ym3|A$Zp~$N zIuvQ^Rr^_(l{k`!dk2TE1NqvOa@yi~eUW+EyIC>h;@F(}pLVrqRuO3EG_a4s1$AIT z{6V+dwB^4%Y?UscK65e;Ag8*vs6^A+EtqprNij7V#i9U8buIlm7+#!#a2S9Rh!nRJxa}1)J(0? z5;aTO7{$+X&hPm>iBr#!bMi;d$$h^*_kQnx-_N}-erd<|!pmo`7}jJny`wN~F-k`W z;?>9+cygJt2jyP9Tb6Amumjm^MjmD_9?a;f8wxBYqIMN6GPAfr>2Nhchaw>NxkM(1 zT;hLVpUQ&bT{rX?-|*`+K^*Kn-ZS29Aio0aTVtRb5|7F81$W-i$eyJF>+YvxORrRY z)!odojm(sb^Ek~dsBan|s`nM%J2Pakdi~des*v{edgSya7b%Km&>%#+GWfn}rD<;< z2fy}>Or&%3J;T&<4k5{UAKu-v7TzvFKC6m;)fJql7sbe>`7w%21ut<5mGes5 z!8!*z(}T%`(5%>7N# z%^L})^Mu2@LZ7KGa7?Ua_-4fVRJ6Ai`FvPyasnw+BzLB1WCa?aU*uAn3DyEcE$IQq zmD~AI->0h?hbS6>@~|&kRBQ|DxO7}~QhzlWl+}qkCY(9#l~y#aI`MWUC7Vk={rP1_ z+HYzR{+}@(D(cy{Oa23AsSTbPj$b|dCQG;O@r-odY!41j!P}G-s5r1H+})Hm;F2?2 z4D(m!bwf|7A>C4+GWGu$Lhdo|T4~Lf=0P$B-r~(4y9rFY8Pb#iCW>zNN;1?S$b8OE zyHeFBkp+Dv^TJI4Q8|S3(*ZGaS`3>s6hQlCkyC4wRQ_SK{#jjH92wg(ZS}3^OD|0G zajmI#?130O6Lo{VVImZhFD}fUdH|xBYzw9~6q{#d*g)x421VHb^0M(4Wb#~ zF=Vl_)3j6(&CXujd>8~w1NljnG6Pa+kb`*nGzpJ2Vw zl}3q0cUU1yQTdcmC9qB9r{CeRs3N&FWjki}f>Eo)xWyPz*b;K7qV{$qYd;s{1i4g# z-$N>iEH$I(SRQfAYW#E+YO!#sk0-_zPrbJig;FHH^-|;KPDh^?PEuF-nHQ~JqOBmz z075Awpa9;YV@Jpj18Q}YUpc+zoG9Q@#Ur+<0vZ~U<+A2*CjVhcS21UTdF7}1rUq@!x!9gkcwsKGLHMX%h^ zZ0CMtvudc>K1(JqF>lpwf>;rW=GeUQ-AA;89Y;Z@7BrHO1o>F%PwQ9O>QejxPL88` zE)H)Z4_ge4;-^Jw>;#ifbiFT2S}ZnphTdl8#ze>jTIRjZ^{3@)cLMRV3JnCFR-OOU zE0S_jM9~%~9~h~`JZ&QdKE$I}2GAcs6tr%53rIkCb5o77{!d zfhb4=yy{vxt2!iOmCqnX4Hx|=${4S-x_@^m$Ze%UY+hda2W@)|O~9*!%a4BRwAU~P zjOk&NwX<8R0xP46Z@qqdLLR#vV_T#JN%V7)vde1s27wbNAmA9bZSN~}<`~7+p3N7n z>268{m71c+4hYjILSIPQT4+kGO}8=BZf7^$ZbvQi38+YtB?zUFy~8R|%W~O`93#^D zYd=5z1*Z(5N2F&r!FD`2&?vr+cNb|C*~j+dwa2@UZBK-7oVrQjVc6xb z9e-xxS5u2=HFGdXc!v+Ut5e*)$0oiLJ)%99J?p&XA_OJhf{1k};1$_j%tQ7HjN5|{ z?E98|QTrTEJ@L1kznE*^;Xv*O6nF2l@UiV*cz@~A(nnRL$`o}!d7n?oV?RLUECFINkTKYuum^V52!Ed7YWStBCNEcT#k#J( zqiyCcI~J*0pj~hDvtcyK)u7ORd|3=;=JP74L=@L2cB8|^5N|%%|L}UguMxr$S1jL? z(CGPNeZ~08`Uc~WqreG*cN{D%=rhC5{DN|-1_L2#6eB_>)yiT+O>8`LN~p7vBllrX z{Mn6w0-5N^h@|B>(a@p*#jN@PvuIl{dTC+ z(ir=f&v_OzJ4R^EfG1(lmqGHy+{sL~K0$)LY!(JmtT<3il7Ibm+U;|(%oD$5hA#31 z=p*8Hx;j@jr3%A}I0}Q~$DRTW?Y?&cd$g9xzTcAl{(6R1_r_$`46T9IqTOZ|{eJM8 zY|MlE{_;YC4}}`B&R3fZY-{G4K^xq78uU01XbPL-7Fsw=wX0uaAzWbLNdRxU#H>d9 zkO{@#ZDdh<-cj$P?!PQ--l#w}JZGTTnyHy>cfXEq*xLk@&)4s>Fv&gd2QOj_8=)KM zt7F4EhPC_QXG)t!171#veY%qE5X~3f9=jm09WVQ{RxcA^*1N-oPvzFban#tz*W_Fg z*}9cym!Gz`zLImHNXy}ax@<&a^SC=;Tqc)qm}y~0<5XY^GCGxMI0z+N0yr`EjLy#j zQ5=5V_rqeIIrh857g)y9CGo|6}55*J~d2Awzvzu;X8Oqv2E^l!8X(H10 zHd0}Tiy87Tzrl=9oSrk3+6FrLD`O|AgtaMzW${tZcx0BLo!(C=ZB@%_{Dq=9&|xVy z@5z+38*a?rlfQx8;$PYps1Ax}$0NDT7eQakuRQiamGSKks>0?Zv}ay6x0}>kdAqG&jt%!M`YBS%G_xfq8#H!bU*}S^Z)u3aev`y=R$O5( zPed~h^(BmYzVK(fWLchj&g>MV?k!=&bzrN~SQ5->z!Yr1VN9up?x_DZ+ZR#&LOP|J zj`L2d%d$$tu*R>zXOf%gzV^}!RZmcD&@p=bUO7MG;<(0>FVY99Lo{(~ja^Gk-ay_- zn`F-)0)ZWHUO{teZCMA5r+>^Q97(^Hj`=3<@@dK zMhbehBB}UMlJB+ti+kNyjRBc+RxorcZ(Gj#)z~lc8>|e=Ere7T@i`0-w&_il@|MD- zu%8#!i&jOwiF2585N>Y~EH!(!o=qKDeuim#+#G3Az5lYJe_>knlipX!ms-WX^4nzQ zx0s6F7uLUAtJbMV09GV?W|PV(l)E5D$8+vElZ~wQ1d@^0xbB#uI72z|A^Ks2;W0t$+}jWP$T}AFNqAA zFjJ^+=nw1(`NJP7e(Ak`PxN2n8NAd4No|tSfMggYM(fs(H2v;Iz5{&3!A&WsI`QoG9p)#(EDn z7hFl%^mCsH^;drk895!`&lX4;0PTS|j`(8_y=&v;3_Gy3Q34KL5CRSiVn+{WCK;}H z@bABe!)!bpps>I6kH?7NCW-O>L(G9x&I=BKE65!$ke?*5>0bj~ZD9Ajp`^KNfPWf_ zrc8u0{m%dZ*7^W|7)dVEfvN8BeE%u*f4O#^FfUtADOj|CUSS{yawVNs{DnqGVzRC{Ymc+LGEoiMQf=qM(0VphPi8?PwN~dfp5DGt+}d z^S4rwD2KQlQkf!gIn=^`$oX5XNR)O|Riw}L(Vx-|>kUcv3vrdCqD0~(_Ly1MTO3h7lr z#1w}+$Dtg$IOqrp>4|P)im=6TD2L8>A3-5KM@CHHus;sv(5bQ`D5Q<*#1tOq<4_K@ ztsg-l?Fc2NaJe6ca;QJ_2nuOO4KYO>ejLi7{+c5wq_5?ODUjgfP!7GM`#**9pkwwg Rfh;HBEX{#T7Wvn&{{o3A=Tray literal 0 HcmV?d00001 diff --git a/native/commonizer/build.gradle.kts b/native/commonizer/build.gradle.kts index c9e124ef4b5..fcd6ca29fc2 100644 --- a/native/commonizer/build.gradle.kts +++ b/native/commonizer/build.gradle.kts @@ -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) { diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerParameters.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerParameters.kt index e19779bf7a4..f53a823693d 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerParameters.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerParameters.kt @@ -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() + private val _targetProviders = LinkedHashMap() val targetProviders: List 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 { if (_targetProviders.size < 2) return emptySet() // too few targets diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTarget.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTarget.kt deleted file mode 100644 index b91271767a6..00000000000 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTarget.kt +++ /dev/null @@ -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() { - init { - require(targets.isNotEmpty()) - } - - override val name get() = targets.joinToString(prefix = "[", postfix = "]") { it.name } - override val prettyName get() = name -} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/KonanDistribution.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/KonanDistribution.kt new file mode 100644 index 00000000000..21ccb614359 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/KonanDistribution.kt @@ -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) diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/NativeLibraryLoader.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/NativeLibraryLoader.kt new file mode 100644 index 00000000000..78f88095602 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/NativeLibraryLoader.kt @@ -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) + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/ResultsConsumer.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/ResultsConsumer.kt index cf9dfd5db2b..e3826298d8d 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/ResultsConsumer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/ResultsConsumer.kt @@ -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 { + 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) + } + } } diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/TargetProvider.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/TargetProvider.kt index 5f0b287a857..1a4343fd6d3 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/TargetProvider.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/TargetProvider.kt @@ -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? ) diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/DependencyLibrariesOptionType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/DependencyLibrariesOptionType.kt new file mode 100644 index 00000000000..6b8dfbc284a --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/DependencyLibrariesOptionType.kt @@ -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" +) diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/InputLibrariesOptionType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/InputLibrariesOptionType.kt new file mode 100644 index 00000000000..1860655a6aa --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/InputLibrariesOptionType.kt @@ -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" +) diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/LibrariesSetOptionType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/LibrariesSetOptionType.kt new file mode 100644 index 00000000000..015cace78b5 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/LibrariesSetOptionType.kt @@ -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>( + mandatory = mandatory, + alias = alias, + description = description +) { + override fun parse(rawValue: String, onError: (reason: String) -> Nothing): Option> { + if (rawValue.isBlank()) { + return Option(this, emptyList()) + } + return Option(this, rawValue.split(";").map(::File)) + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/OutputCommonizerTargetOptionType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/OutputCommonizerTargetOptionType.kt new file mode 100644 index 00000000000..55f052137a0 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/OutputCommonizerTargetOptionType.kt @@ -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( + 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 { + return try { + Option(this, parseCommonizerTarget(rawValue) as SharedCommonizerTarget) + } catch (t: Throwable) { + onError("Failed parsing output-commonizer-target ($rawValue): ${t.message}") + } + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/ProgressLogger.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/ProgressLogger.kt new file mode 100644 index 00000000000..90c130f6c39 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/ProgressLogger.kt @@ -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") + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/StatsTypeOptionType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/StatsTypeOptionType.kt index be731ff2d18..9cd3fb31477 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/StatsTypeOptionType.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/StatsTypeOptionType.kt @@ -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("log-stats", DESCRIPTION, mandatory = false) { override fun parse(rawValue: String, onError: (reason: String) -> Nothing): Option { diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/TaskType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/TaskType.kt index c2030f04cb3..d9420f7818c 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/TaskType.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/TaskType.kt @@ -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 } diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/nativeTasks.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/nativeTasks.kt index e0ff95e4642..4af1aef8a9f 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/nativeTasks.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/nativeTasks.kt @@ -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>) : T } } +internal class NativeKlibCommonize(options: Collection>) : Task(options) { + override val category: Category = Category.COMMONIZATION + + override fun execute(logPrefix: String) { + val distribution = KonanDistribution(getMandatory()) + val destination = getMandatory() + val targetLibraries = getMandatory, InputLibrariesOptionType>() + val dependencyLibraries = getMandatory, DependencyLibrariesOptionType>() + val outputCommonizerTarget = getMandatory() + val statsType = getOptional { 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>) : Task(options) { override val category get() = Category.COMMONIZATION override fun execute(logPrefix: String) { - val distribution = getMandatory() + val distribution = KonanDistribution(getMandatory()) val destination = getMandatory() val targets = getMandatory, NativeTargetsOptionType>() @@ -47,35 +100,43 @@ internal class NativeDistributionCommonize(options: Collection>) : Tas val copyEndorsedLibs = getOptional { it == "copy-endorsed-libs" } ?: false val statsType = getOptional { 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): 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): Int { + return targets.flatMap { repository.getLibraries(LeafCommonizerTarget(it)) }.count() } } } diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/core/RootCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/core/RootCommonizer.kt index 6a684c9272a..a7bdcb64079 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/core/RootCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/core/RootCommonizer.kt @@ -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() { - private val leafTargets = mutableSetOf() + private val leafTargets = mutableSetOf() 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 } } diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/facade.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/facade.kt index fcb7eca39a3..e85620ef3c0 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/facade.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/facade.kt @@ -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)) } diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/CopyLibrariesFromKonanDistributionResultsConsumer.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/CopyLibrariesFromKonanDistributionResultsConsumer.kt new file mode 100644 index 00000000000..23f4002c501 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/CopyLibrariesFromKonanDistributionResultsConsumer.kt @@ -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() + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/CopyUnconsumedModulesAsIsConsumer.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/CopyUnconsumedModulesAsIsConsumer.kt new file mode 100644 index 00000000000..ce804095e4d --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/CopyUnconsumedModulesAsIsConsumer.kt @@ -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, + private val outputLayout: CommonizerOutputLayout, + private val logger: Logger? = null +) : ResultsConsumer { + + private val consumedTargets = mutableSetOf() + + 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}") + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/LoggingResultsConsumer.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/LoggingResultsConsumer.kt new file mode 100644 index 00000000000..b8c800740a9 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/LoggingResultsConsumer.kt @@ -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)}") + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/ModuleSerializer.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/ModuleSerializer.kt new file mode 100644 index 00000000000..19ec275bdc3 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/ModuleSerializer.kt @@ -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() +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionCommonizer.kt index 76340070127..df23088222e 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionCommonizer.kt @@ -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, - 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()}") } diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionModulesProvider.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionModulesProvider.kt index 5dbd7c2546d..d977bb4c702 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionModulesProvider.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionModulesProvider.kt @@ -34,8 +34,6 @@ internal abstract class NativeDistributionModulesProvider(libraries: Collection< protected val moduleInfoMap: Map init { - check(libraries.isNotEmpty()) { "No libraries supplied" } - val libraryMap = mutableMapOf() val moduleInfoMap = mutableMapOf() diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionResultsConsumer.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionResultsConsumer.kt deleted file mode 100644 index 4bed4d2c4e9..00000000000 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionResultsConsumer.kt +++ /dev/null @@ -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() - - private val cachedManifestProviders = FactoryMap.create { target -> - when (target) { - is LeafTarget -> originalLibraries.librariesByTargets.getValue(target) - is SharedTarget -> CommonNativeManifestDataProvider(originalLibraries.librariesByTargets.values) - } - } - - private val cachedLibrariesDestination = FactoryMap.create { 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() - 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) -} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeLibrary.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeLibrary.kt index 557274101b2..d80d7ec9974 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeLibrary.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeLibrary.kt @@ -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 = 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) : internal class AllNativeLibraries( val stdlib: NativeLibrary, - val librariesByTargets: Map + val librariesByTargets: Map ) internal class CommonNativeManifestDataProvider( diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeSensitiveManifestData.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeSensitiveManifestData.kt index 3d3ee40a5ef..a479c9c05b2 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeSensitiveManifestData.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeSensitiveManifestData.kt @@ -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, diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/CirTreeMerger.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/CirTreeMerger.kt index 27274c344c5..27cbc668f33 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/CirTreeMerger.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/CirTreeMerger.kt @@ -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> + val missingModuleInfos: Map> ) private val size = parameters.targetProviders.size diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/classifierContainers.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/classifierContainers.kt index 2fe350c6502..0f386bee1ea 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/classifierContainers.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/classifierContainers.kt @@ -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 { diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/FilesRepository.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/FilesRepository.kt new file mode 100644 index 00000000000..9719dddfdde --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/FilesRepository.kt @@ -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, + private val libraryLoader: NativeLibraryLoader +) : Repository { + + private val librariesByTarget: Map> by lazy { + libraryFiles + .map(libraryLoader::invoke) + .groupBy { library -> CommonizerTarget(library.manifestData.nativeTargets.map(::konanTargetOrThrow)) } + .mapValues { (_, list) -> list.toSet() } + } + + override fun getLibraries(target: LeafCommonizerTarget): Set { + return librariesByTarget[target].orEmpty() + } +} + +private fun konanTargetOrThrow(value: String): KonanTarget { + return KonanTarget.predefinedTargets[value] ?: error("Unexpected KonanTarget $value") +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/KonanDistributionRepository.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/KonanDistributionRepository.kt new file mode 100644 index 00000000000..3aa005bd904 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/KonanDistributionRepository.kt @@ -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, + libraryLoader: NativeLibraryLoader, +) : Repository { + + private val librariesByTarget: Map>> = + 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 { + return librariesByTarget[target]?.value ?: error("Missing target $target") + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/Repository.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/Repository.kt new file mode 100644 index 00000000000..5f7074b0a2b --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/repository/Repository.kt @@ -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 +} + +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 { + override fun getLibraries(target: LeafCommonizerTarget): Set { + return repositories.map { it.getLibraries(target) }.flatten().toSet() + } +} + +internal object EmptyRepository : Repository { + override fun getLibraries(target: LeafCommonizerTarget): Set { + return emptySet() + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/StatsCollector.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/StatsCollector.kt index 6a67558db9d..8adede045ed 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/StatsCollector.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/StatsCollector.kt @@ -5,6 +5,20 @@ package org.jetbrains.kotlin.descriptors.commonizer.stats +import org.jetbrains.kotlin.konan.target.KonanTarget + +fun StatsCollector(type: StatsType, targets: List): 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, diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt index 95af8cb91d4..fcad9a3e61f 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt @@ -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 = analyzedModules.leafTargets + val leafTargets: Set = analyzedModules.leafTargets assertEquals(leafTargets, results.leafTargets) for (leafTarget in leafTargets) { @@ -116,18 +117,18 @@ private data class SourceModuleRoot( } private class SourceModuleRoots( - val originalRoots: Map, + val originalRoots: Map, val commonizedRoots: Map, val dependencyRoots: Map ) { - val leafTargets: Set = originalRoots.keys - val sharedTarget: SharedTarget + val leafTargets: Set = originalRoots.keys + val sharedTarget: SharedCommonizerTarget init { check(leafTargets.size >= 2) check(leafTargets.none { it.name == SHARED_TARGET_NAME }) - val sharedTargets = commonizedRoots.keys.filterIsInstance() + val sharedTargets = commonizedRoots.keys.filterIsInstance() 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, val dependencyModules: Map> ) { - val leafTargets: Set - val sharedTarget: SharedTarget + val leafTargets: Set + val sharedTarget: SharedCommonizerTarget init { originalModules.keys.let { targets -> check(targets.isNotEmpty()) - leafTargets = targets.filterIsInstance().toSet() + leafTargets = targets.filterIsInstance().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, parentDisposable: Disposable ): Pair>, AnalyzedModuleDependencies> { @@ -263,7 +264,7 @@ private class AnalyzedModules( } private fun createModules( - sharedTarget: SharedTarget, + sharedTarget: SharedCommonizerTarget, moduleRoots: Map, 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 { diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/CommonizerFacadeTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/CommonizerFacadeTest.kt index 0f4af9f10f1..106cd004c61 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/CommonizerFacadeTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/CommonizerFacadeTest.kt @@ -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>.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>.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>) { val results = MockResultsConsumer() diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetTest.kt deleted file mode 100644 index f56e791652f..00000000000 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/CommonizerTargetTest.kt +++ /dev/null @@ -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)) - } -} diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/RootCommonizerTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/RootCommonizerTest.kt index a78996b49fb..c0ec2144ae1 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/RootCommonizerTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/RootCommonizerTest.kt @@ -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() { @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() diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/TypeCommonizerTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/TypeCommonizerTest.kt index 7da9705f162..61a5a32e0be 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/TypeCommonizerTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/TypeCommonizerTest.kt @@ -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() { 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() diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/assertions.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/assertions.kt index 3d2eefde44e..72709bef6c6 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/assertions.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/assertions.kt @@ -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) } diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/mocks.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/mocks.kt index 03a7f70c316..9169e2b0fe6 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/mocks.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/mocks.kt @@ -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> get() = _modulesByTargets.mapValues { it.value.values } - val sharedTarget: SharedTarget by lazy { modulesByTargets.keys.filterIsInstance().single() } - val leafTargets: Set by lazy { modulesByTargets.keys.filterIsInstance().toSet() } + val sharedTarget: SharedCommonizerTarget by lazy { modulesByTargets.keys.filterIsInstance().single() } + val leafTargets: Set by lazy { modulesByTargets.keys.filterIsInstance().toSet() } private val finishedTargets = mutableSetOf() @@ -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 = emptyList(), + isInterop: Boolean = true, + packageFqName: String? = "mock", + exportForwardDeclarations: List = emptyList(), + nativeTargets: Collection = 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 + ) +} diff --git a/settings.gradle b/settings.gradle index 0d56a3fe779..0cf60573b00 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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")