WIP: Basic support for Kotlin/Native * Target presets * Compilation into a klib * Compilation into native binraies: framework and executable * Basic dependencies between projects
diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlatformType.kt b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlatformType.kt index 8656d43..e060fb2 100644 --- a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlatformType.kt +++ b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlatformType.kt
@@ -7,19 +7,33 @@ import org.gradle.api.Named import org.gradle.api.attributes.Attribute +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget import java.io.Serializable -enum class KotlinPlatformType: Named, Serializable { - common, jvm, js, - native; // TODO: split native into separate entries here or transform the enum to interface and implement entries in K/N +// TODO: Do we really need a separate platform type for each native target? +open class KotlinPlatformType(private val name: String) : Named, Serializable { override fun toString(): String = name override fun getName(): String = name companion object { + + val COMMON = KotlinPlatformType("common") + val JVM = KotlinPlatformType("JVM") + val JS = KotlinPlatformType("JS") + val attribute = Attribute.of( "org.jetbrains.kotlin.platform.type", KotlinPlatformType::class.java ) } -} \ No newline at end of file +} + +// TODO: Make KonanTarget serializable +data class KotlinNativePlatformType(val konanTargetName: String) : KotlinPlatformType(konanTargetName) { + val konanTarget: KonanTarget + get() = HostManager().targetByName(konanTargetName) +} + +fun KonanTarget.toKotlinPlatformType(): KotlinNativePlatformType = KotlinNativePlatformType(name) \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinNativeToolRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinNativeToolRunner.kt new file mode 100644 index 0000000..366e37f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinNativeToolRunner.kt
@@ -0,0 +1,143 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.compilerRunner + +import org.gradle.api.Named +import org.gradle.api.Project +import org.gradle.api.file.FileCollection +import org.jetbrains.kotlin.konan.KonanVersion +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.konan.util.DependencyProcessor + +internal enum class KotlinNativeProjectProperty(val propertyName: String) { + KONAN_HOME ("konan.home"), + KONAN_JVM_ARGS ("konan.jvmArgs"), + KONAN_USE_ENVIRONMENT_VARIABLES("konan.useEnvironmentVariables"), + DOWNLOAD_COMPILER ("download.compiler"), + + // Properties used instead of env vars until https://github.com/gradle/gradle/issues/3468 is fixed. + // TODO: Remove them when an API for env vars is provided. + KONAN_CONFIGURATION_BUILD_DIR ("konan.configuration.build.dir"), + KONAN_DEBUGGING_SYMBOLS ("konan.debugging.symbols"), + KONAN_OPTIMIZATIONS_ENABLE ("konan.optimizations.enable"), + KONAN_PUBLICATION_ENABLED ("konan.publication.enabled") +} + +internal fun Project.hasProperty(property: KotlinNativeProjectProperty) = hasProperty(property.propertyName) +internal fun Project.findProperty(property: KotlinNativeProjectProperty): Any? = findProperty(property.propertyName) + +internal fun Project.getProperty(property: KotlinNativeProjectProperty) = findProperty(property) + ?: throw IllegalArgumentException("No such property in the project: ${property.propertyName}") + +internal val Project.jvmArgs + get() = (findProperty(KotlinNativeProjectProperty.KONAN_JVM_ARGS) as String?)?.split("\\s+".toRegex()).orEmpty() + +// konanHome extension is set by downloadKonanCompiler task. +internal val Project.konanHome: String + get() { + assert(hasProperty(KotlinNativeProjectProperty.KONAN_HOME)) + return project.file(getProperty(KotlinNativeProjectProperty.KONAN_HOME)).canonicalPath + } + +internal interface KonanToolRunner: Named { + val mainClass: String + val classpath: FileCollection + val jvmArgs: List<String> + val environment: Map<String, Any> + val additionalSystemProperties: Map<String, String> + + fun run(args: List<String>) + fun run(vararg args: String) = run(args.toList()) +} + +internal abstract class KonanCliRunner( + val toolName: String, + val fullName: String, + val project: Project, + private val additionalJvmArgs: List<String> +): KonanToolRunner { + override val mainClass = "org.jetbrains.kotlin.cli.utilities.MainKt" + + override fun getName() = toolName + + // We need to unset some environment variables which are set by XCode and may potentially affect the tool executed. + protected val blacklistEnvironment: List<String> by lazy { + KonanToolRunner::class.java.getResourceAsStream("/env_blacklist")?.let { stream -> + stream.reader().use { it.readLines() } + } ?: emptyList<String>() + } + + override val classpath: FileCollection = + project.fileTree("${project.konanHome}/konan/lib/") + .apply { include("*.jar") } + + override val jvmArgs = mutableListOf("-ea").apply { + if (additionalJvmArgs.none { it.startsWith("-Xmx") } && + project.jvmArgs.none { it.startsWith("-Xmx") }) { + add("-Xmx3G") + } + addAll(additionalJvmArgs) + addAll(project.jvmArgs) + } + + override val additionalSystemProperties = mutableMapOf( + "konan.home" to project.konanHome, + "java.library.path" to "${project.konanHome}/konan/nativelib" + ) + + override val environment = mutableMapOf("LIBCLANG_DISABLE_CRASH_RECOVERY" to "1") + + override fun run(args: List<String>) { + project.logger.info("Run tool: $toolName with args: ${args.joinToString(separator = " ")}") + if (classpath.isEmpty) { + throw IllegalStateException("Classpath of the tool is empty: $toolName\n" + + "Probably the 'konan.home' project property contains an incorrect path.\n" + + "Please change it to the compiler root directory and rerun the build.") + } + + project.javaexec { spec -> + spec.main = mainClass + spec.classpath = classpath + spec.jvmArgs(jvmArgs) + spec.systemProperties(System.getProperties().map { (k, v) -> k.toString() to v }.toMap()) + spec.systemProperties(additionalSystemProperties) + spec.args(listOf(toolName) + args) + blacklistEnvironment.forEach { spec.environment.remove(it) } + spec.environment(environment) + } + } +} + +internal class KonanInteropRunner(project: Project, additionalJvmArgs: List<String> = emptyList()) + : KonanCliRunner("cinterop", "Kotlin/Native cinterop tool", project, additionalJvmArgs) +{ + init { + if (HostManager.host == KonanTarget.MINGW_X64) { + //TODO: Oh-ho-ho fix it in more convinient way. + environment.put("PATH", DependencyProcessor.defaultDependenciesRoot.absolutePath + + "\\msys2-mingw-w64-x86_64-gcc-7.2.0-clang-llvm-5.0.0-windows-x86-64" + + "\\bin;${environment.get("PATH")}") + } + } +} + +internal class KonanCompilerRunner(project: Project, additionalJvmArgs: List<String> = emptyList()) + : KonanCliRunner("konanc", "Kotlin/Native compiler", project, additionalJvmArgs) + +internal class KonanKlibRunner(project: Project, additionalJvmArgs: List<String> = emptyList()) + : KonanCliRunner("klib", "Klib management tool", project, additionalJvmArgs)
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index 8cd9bd0..4d6df79 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt
@@ -455,13 +455,13 @@ kotlinPluginVersion: String ) : AbstractKotlinPlugin(tasksProvider, kotlinPluginVersion) { override val platformType: KotlinPlatformType - get() = KotlinPlatformType.jvm + get() = KotlinPlatformType.JVM override fun buildSourceSetProcessor(project: Project, compilation: KotlinCompilation, kotlinPluginVersion: String) = Kotlin2JvmSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion) override fun apply(project: Project) { - val target = KotlinWithJavaTarget(project, KotlinPlatformType.js, "kotlin").apply { + val target = KotlinWithJavaTarget(project, KotlinPlatformType.JS, "kotlin").apply { disambiguationClassifier = null // don't add anything to the task names } (project.kotlinExtension as KotlinSingleTargetProjectExtension).target = target @@ -477,7 +477,7 @@ ) : AbstractKotlinPlugin(tasksProvider, kotlinPluginVersion) { override val platformType: KotlinPlatformType - get() = KotlinPlatformType.common + get() = KotlinPlatformType.COMMON override fun buildSourceSetProcessor( project: Project, @@ -487,7 +487,7 @@ KotlinCommonSourceSetProcessor(project, compilation, tasksProvider, kotlinPluginVersion) override fun apply(project: Project) { - val target = KotlinWithJavaTarget(project, KotlinPlatformType.js, "common").apply { + val target = KotlinWithJavaTarget(project, KotlinPlatformType.JS, "common").apply { disambiguationClassifier = "common" } (project.kotlinExtension as KotlinSingleTargetProjectExtension).target = target @@ -501,7 +501,7 @@ kotlinPluginVersion: String ) : AbstractKotlinPlugin(tasksProvider, kotlinPluginVersion) { override val platformType: KotlinPlatformType - get() = KotlinPlatformType.js + get() = KotlinPlatformType.JS override fun buildSourceSetProcessor( project: Project, @@ -513,7 +513,7 @@ ) override fun apply(project: Project) { - val target = KotlinWithJavaTarget(project, KotlinPlatformType.js, "2Js").apply { + val target = KotlinWithJavaTarget(project, KotlinPlatformType.JS, "2Js").apply { disambiguationClassifier = "2Js" }
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/base/KotlinCompilationFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/base/KotlinCompilationFactory.kt index d7cf742..756c658 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/base/KotlinCompilationFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/base/KotlinCompilationFactory.kt
@@ -76,8 +76,6 @@ } } -class Kotlin - class KotlinJsCompilationFactory( val project: Project, val target: KotlinOnlyTarget<KotlinJsCompilation> @@ -87,4 +85,25 @@ override fun create(name: String): KotlinJsCompilation = KotlinJsCompilation(target, name, project.createSourceSetOutput(name)) +} + +class KotlinNativeCompilationFactory( + val project: Project, + val target: KotlinNativeTarget +) : KotlinCompilationFactory<KotlinNativeCompilation> { + + override val itemClass: Class<KotlinNativeCompilation> + get() = KotlinNativeCompilation::class.java + + override fun create(name: String): KotlinNativeCompilation = + KotlinNativeCompilation(target, name, project.createSourceSetOutput(name)).apply { + if (name == KotlinCompilation.TEST_COMPILATION_NAME) { + outputKinds = mutableListOf(NativeOutputKind.EXECUTABLE) + buildTypes = mutableListOf(NativeBuildType.DEBUG) + isTestCompilation = true + } else { + buildTypes = mutableListOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE) + } + } + } \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCompilation.kt index 4ef18fee..00be67c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCompilation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinCompilation.kt
@@ -43,8 +43,8 @@ } } -abstract class AbstractKotlinCompilation( - final override val target: KotlinTarget, +abstract class AbstractKotlinCompilation<T : KotlinTarget>( + final override val target: T, override val compilationName: String ) : KotlinCompilation, HasKotlinDependencies { private val attributeContainer = HierarchyAttributeContainer(target.attributes) @@ -108,8 +108,8 @@ override fun toString(): String = "compilation '$compilationName' ($target)" } -abstract class AbstractKotlinCompilationToRunnableFiles(target: KotlinTarget, name: String) - : AbstractKotlinCompilation(target, name), KotlinCompilationToRunnableFiles { +abstract class AbstractKotlinCompilationToRunnableFiles<T : KotlinTarget>(target: T, name: String) + : AbstractKotlinCompilation<T>(target, name), KotlinCompilationToRunnableFiles { override val runtimeDependencyConfigurationName: String get() = lowerCamelCaseName(compilationName, target.disambiguationClassifier, "runtimeClasspath") @@ -128,7 +128,7 @@ target: KotlinTarget, name: String, override val output: SourceSetOutput -) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { +) : AbstractKotlinCompilationToRunnableFiles<KotlinTarget>(target, name), KotlinCompilationWithResources { override val processResourcesTaskName: String get() = disambiguateName("processResources") } @@ -137,7 +137,7 @@ target: KotlinWithJavaTarget, name: String, val javaSourceSet: SourceSet -) : AbstractKotlinCompilationToRunnableFiles(target, name), KotlinCompilationWithResources { +) : AbstractKotlinCompilationToRunnableFiles<KotlinWithJavaTarget>(target, name), KotlinCompilationWithResources { override val output: SourceSetOutput get() = javaSourceSet.output @@ -186,16 +186,63 @@ target: KotlinAndroidTarget, name: String, override val output: SourceSetOutput -): AbstractKotlinCompilation(target, name) +): AbstractKotlinCompilation<KotlinAndroidTarget>(target, name) class KotlinJsCompilation( target: KotlinTarget, name: String, override val output: SourceSetOutput -) : AbstractKotlinCompilation(target, name) +) : AbstractKotlinCompilation<KotlinTarget>(target, name) class KotlinCommonCompilation( target: KotlinTarget, name: String, override val output: SourceSetOutput -) : AbstractKotlinCompilation(target, name) \ No newline at end of file +) : AbstractKotlinCompilation<KotlinTarget>(target, name) + +class KotlinNativeCompilation( + target: KotlinNativeTarget, + name: String, + override val output: SourceSetOutput +) : AbstractKotlinCompilationToRunnableFiles<KotlinNativeTarget>(target, name) { + + val linkAllTaskName: String + get() = lowerCamelCaseName("link", compilationName.takeIf { it != "main" }.orEmpty(), target.disambiguationClassifier) + + var isTestCompilation = false + + // Native-specific DSL. + val extraOpts = mutableListOf<String>() + + fun extraOpts(vararg values: Any) = extraOpts(values.toList()) + fun extraOpts(values: List<Any>) { + extraOpts.addAll(values.map { it.toString() }) + } + + var buildTypes = mutableListOf<NativeBuildType>() + var outputKinds = mutableListOf<NativeOutputKind>() + + // TODO: Integrate with Big Kotlin tasks and runners and remove this method. + override fun source(sourceSet: KotlinSourceSet) { + kotlinSourceSets += sourceSet + + with(target.project) { + addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName) + addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName) + addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName) + if (this is KotlinCompilationToRunnableFiles) { + addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName) + } + } + } + + // TODO: Can we do it better? + companion object { + val DEBUG = NativeBuildType.DEBUG + val RELEASE = NativeBuildType.RELEASE + + val EXECUTABLE = NativeOutputKind.EXECUTABLE + val FRAMEWORK = NativeOutputKind.FRAMEWORK + } + +} \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index 3909da3..90814c1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt
@@ -23,6 +23,8 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget internal val Project.multiplatformExtension get(): KotlinMultiplatformExtension? = project.extensions.getByName("kotlin") as KotlinMultiplatformExtension @@ -78,6 +80,9 @@ add(KotlinJsTargetPreset(project, instantiator, fileResolver, buildOutputCleanupRegistry, kotlinPluginVersion)) add(KotlinAndroidTargetPreset(project, kotlinPluginVersion, buildOutputCleanupRegistry)) add(KotlinJvmWithJavaTargetPreset(project, kotlinPluginVersion)) + HostManager().targets.forEach { name, target -> + add(KotlinNativeTargetPreset(name, project, target.toKotlinPlatformType(), buildOutputCleanupRegistry)) + } } }
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinPresets.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinPresets.kt index 1a9784c..0ecd612 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinPresets.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinPresets.kt
@@ -6,18 +6,27 @@ package org.jetbrains.kotlin.gradle.plugin.mpp import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact import org.gradle.api.internal.file.FileResolver +import org.gradle.api.plugins.BasePlugin import org.gradle.api.plugins.JavaPlugin import org.gradle.internal.cleanup.BuildOutputCleanupRegistry import org.gradle.internal.reflect.Instantiator +import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.gradle.nativeplatform.test.tasks.RunTestExecutable +import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.dsl.sourceSetProvider import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.base.* -import org.jetbrains.kotlin.gradle.tasks.AndroidTasksProvider -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsTasksProvider -import org.jetbrains.kotlin.gradle.tasks.KotlinCommonTasksProvider -import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider +import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.gradle.utils.setClassesDirCompatible +import org.jetbrains.kotlin.konan.target.CompilerOutputKind +import org.jetbrains.kotlin.konan.target.HostManager +import java.io.File +import java.util.* abstract class KotlinOnlyTargetPreset<T : KotlinCompilation>( protected val project: Project, @@ -70,7 +79,7 @@ KotlinCommonCompilationFactory(project, forTarget) override val platformType: KotlinPlatformType - get() = KotlinPlatformType.common + get() = KotlinPlatformType.COMMON override fun buildCompilationProcessor(compilation: KotlinCommonCompilation): KotlinSourceSetProcessor<*> = KotlinCommonSourceSetProcessor( @@ -104,7 +113,7 @@ KotlinJvmCompilationFactory(project, forTarget) override val platformType: KotlinPlatformType - get() = KotlinPlatformType.jvm + get() = KotlinPlatformType.JVM override fun buildCompilationProcessor(compilation: KotlinJvmCompilation): KotlinSourceSetProcessor<*> = Kotlin2JvmSourceSetProcessor(project, KotlinTasksProvider(), compilation, kotlinPluginVersion) @@ -133,7 +142,7 @@ KotlinJsCompilationFactory(project, forTarget) override val platformType: KotlinPlatformType - get() = KotlinPlatformType.js + get() = KotlinPlatformType.JS override fun buildCompilationProcessor(compilation: KotlinJsCompilation): KotlinSourceSetProcessor<*> = Kotlin2JsSourceSetProcessor(project, Kotlin2JsTasksProvider(), compilation, kotlinPluginVersion) @@ -185,7 +194,7 @@ override fun createTarget(name: String): KotlinWithJavaTarget { project.plugins.apply(JavaPlugin::class.java) - val target = KotlinWithJavaTarget(project, KotlinPlatformType.jvm, name) + val target = KotlinWithJavaTarget(project, KotlinPlatformType.JVM, name) AbstractKotlinPlugin.configureTarget(target) { compilation -> Kotlin2JvmSourceSetProcessor( @@ -202,4 +211,170 @@ companion object { const val PRESET_NAME = "jvmWithJava" } +} + +class KotlinNativeTargetPreset( + private val name: String, + val project: Project, + val platformType: KotlinNativePlatformType, + private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry +) : KotlinTargetPreset<KotlinNativeTarget> { + + override fun getName(): String = name + + private val Collection<*>.isDimensionVisible: Boolean + get() = size > 1 + + private fun createDimensionSuffix(dimensionName: String, multivalueProperty: Collection<*>): String = + if (multivalueProperty.isDimensionVisible) { + dimensionName.toLowerCase().capitalize() + } else { + "" + } + + private fun Task.dependsOnCompilerDownloading() { + val checkCompilerTask = project.tasks.maybeCreate( + KonanCompilerDownloadTask.KONAN_DOWNLOAD_TASK_NAME, + KonanCompilerDownloadTask::class.java + ) + dependsOn(checkCompilerTask) + } + + private fun createKlibCompilationTask(compilation: KotlinNativeCompilation) { + val compileTask = project.tasks.create( + compilation.compileKotlinTaskName, + KotlinNativeCompile::class.java + ).apply { + this.compilation = compilation + outputKind = CompilerOutputKind.LIBRARY + group = BasePlugin.BUILD_GROUP + description = "Compiles a klibrary from the '${compilation.name}' " + + "compilation for target '${compilation.platformType.name}'" + + outputFile.set { + val targetSubDirectory = compilation.target.disambiguationClassifier?.let { "$it/" }.orEmpty() + val compilationName = compilation.compilationName + val klibName = if (compilation.name == KotlinCompilation.MAIN_COMPILATION_NAME) + project.name + else + compilationName + File(project.buildDir, "classes/kotlin/$targetSubDirectory$compilationName/$klibName.klib") + } + + dependsOnCompilerDownloading() + } + + compilation.output.tryAddClassesDir { project.files(compileTask.outputFile.asFile.get()) } + + project.tasks.getByName(compilation.compileAllTaskName).dependsOn(compileTask) + if (compilation.compilationName == KotlinCompilation.MAIN_COMPILATION_NAME) { + project.tasks.findByName(compilation.target.artifactsTaskName)?.dependsOn(compileTask) + val apiElements = project.configurations.getByName(compilation.target.apiElementsConfigurationName) + apiElements.artifacts.add( + DefaultPublishArtifact( + compilation.name, + "klib", + "klib", + "klib", + Date(), + compileTask.outputFile.asFile.get(), + compileTask + ) + ) + } + } + + private fun createTestTask(compilation: KotlinNativeCompilation, testExecutableLinkTask: KotlinNativeCompile) { + val taskName = lowerCamelCaseName("run", compilation.name, compilation.platformType.name) + val testTask = project.tasks.create(taskName, RunTestExecutable::class.java).apply { + group = LifecycleBasePlugin.VERIFICATION_GROUP + description = "Executes Kotlin/Native unit tests from the '${compilation.name}' compilation " + + "for target '${compilation.platformType.name}'" + + val testExecutableProperty = testExecutableLinkTask.outputFile + executable = testExecutableProperty.asFile.get().absolutePath + // TODO: Provide a normal test path! + outputDir = project.layout.buildDirectory.dir("test-results").get().asFile + + onlyIf { testExecutableProperty.asFile.get().exists() } + inputs.file(testExecutableProperty) + dependsOn(testExecutableLinkTask) + dependsOnCompilerDownloading() + } + } + + // Called in whenEvaluated block. + private fun createBinaryLinkTasks(compilation: KotlinNativeCompilation) { + val konanTarget = compilation.target.konanTarget + val buildTypes = compilation.buildTypes + val availableOutputKinds = compilation.outputKinds.filter { it.availableFor(konanTarget) } + + // TODO: Consider using lockable set property. + // TODO: Provide outgoing configurations somehow. + for (buildType in compilation.buildTypes) { + for (kind in availableOutputKinds) { + val compilerOutputKind = kind.compilerOutputKind + + val compilationSuffix = compilation.name.takeIf { it != "main" }.orEmpty() + val buildTypeSuffix = createDimensionSuffix(buildType.name, buildTypes) + val targetSuffix = compilation.target.name + val taskName = lowerCamelCaseName("link", compilationSuffix, buildTypeSuffix, kind.taskNameClassifier, targetSuffix) + + val linkTask = project.tasks.create( + taskName, + KotlinNativeCompile::class.java + ).apply { + this.compilation = compilation + outputKind = compilerOutputKind + group = BasePlugin.BUILD_GROUP + description = "Links ${kind.description} from the '${compilation.name}' " + + "compilation for target '${compilation.platformType.name}'" + + optimized = buildType.optimized + debuggable = buildType.debuggable + + outputFile.set { + val targetSubDirectory = compilation.target.disambiguationClassifier?.let { "$it/" }.orEmpty() + val compilationName = compilation.compilationName + val prefix = compilerOutputKind.prefix(konanTarget) + val suffix = compilerOutputKind.suffix(konanTarget) + File(project.buildDir, "bin/$targetSubDirectory$compilationName/$prefix$compilationName$suffix") + } + + dependsOnCompilerDownloading() + } + + project.tasks.maybeCreate(compilation.linkAllTaskName).dependsOn(linkTask) + + if (compilation.isTestCompilation && + buildType == NativeBuildType.DEBUG && + konanTarget == HostManager.host + ) { + createTestTask(compilation, linkTask) + } + } + } + } + + override fun createTarget(name: String): KotlinNativeTarget { + val result = KotlinNativeTarget(project, platformType).apply { + targetName = name + disambiguationClassifier = name + + val compilationFactory = KotlinNativeCompilationFactory(project, this) + compilations = project.container(compilationFactory.itemClass, compilationFactory) + } + + KotlinOnlyTargetConfigurator(buildOutputCleanupRegistry).configureTarget(project, result) + + // TODO: Move into KotlinNativeTargetConfigurator + result.compilations.all { compilation -> + createKlibCompilationTask(compilation) + project.whenEvaluated { + createBinaryLinkTasks(compilation) + } + } + + return result + } } \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinTarget.kt index e9e86ae..59d84f1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinTarget.kt
@@ -11,11 +11,13 @@ import org.gradle.api.internal.component.UsageContext import org.gradle.api.plugins.JavaPlugin import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinNativePlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.base.KotlinJvmAndroidCompilationFactory import org.jetbrains.kotlin.gradle.plugin.base.KotlinWithJavaCompilationFactory import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.konan.target.KonanTarget abstract class AbstractKotlinTarget ( override val project: Project @@ -55,7 +57,7 @@ internal set override val platformType: KotlinPlatformType - get() = KotlinPlatformType.jvm + get() = KotlinPlatformType.JVM private val compilationFactory = KotlinJvmAndroidCompilationFactory(project, this) @@ -106,3 +108,13 @@ override var disambiguationClassifier: String? = null internal set } + +class KotlinNativeTarget( + project: Project, + override val platformType: KotlinNativePlatformType +) : KotlinOnlyTarget<KotlinNativeCompilation>(project, platformType) { + + val konanTarget: KonanTarget + get() = platformType.konanTarget +} +
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/NativeBinaryTypes.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/NativeBinaryTypes.kt new file mode 100644 index 0000000..df25947 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/NativeBinaryTypes.kt
@@ -0,0 +1,48 @@ +package org.jetbrains.kotlin.gradle.plugin.mpp + +import org.gradle.api.Named +import org.gradle.api.attributes.Usage +import org.jetbrains.kotlin.konan.target.CompilerOutputKind +import org.jetbrains.kotlin.konan.target.Family +import org.jetbrains.kotlin.konan.target.KonanTarget + +object KotlinNativeUsage { + const val KLIB = "kotlin-native-klib" + const val FRAMEWORK = "kotlin-native-framework" +} + +enum class NativeBuildType(val optimized: Boolean, val debuggable: Boolean): Named { + RELEASE(true, false), + DEBUG(false, true); + + override fun getName(): String = name.toLowerCase() +} + +enum class NativeOutputKind( + val compilerOutputKind: CompilerOutputKind, + val taskNameClassifier: String, + val description: String = taskNameClassifier, + val additionalCompilerFlags: List<String> = emptyList(), + val runtimeUsageName: String? = null, + val linkUsageName: String? = null, + val publishable: Boolean = true +) { + EXECUTABLE( + CompilerOutputKind.PROGRAM, + "executable", + description = "an executable", + runtimeUsageName = Usage.NATIVE_RUNTIME + ), + FRAMEWORK( + CompilerOutputKind.FRAMEWORK, + "framework", + description = "an Objective C framework", + linkUsageName = KotlinNativeUsage.FRAMEWORK, + publishable = false + ) { + override fun availableFor(target: KonanTarget) = + target.family == Family.OSX || target.family == Family.IOS + }; + + open fun availableFor(target: KonanTarget) = true +} \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinNativeTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinNativeTasks.kt new file mode 100644 index 0000000..df8a9b0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinNativeTasks.kt
@@ -0,0 +1,183 @@ +package org.jetbrains.kotlin.gradle.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleScriptException +import org.gradle.api.Project +import org.gradle.api.file.FileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.* +import org.jetbrains.kotlin.compilerRunner.KonanCompilerRunner +import org.jetbrains.kotlin.compilerRunner.KotlinNativeProjectProperty +import org.jetbrains.kotlin.compilerRunner.getProperty +import org.jetbrains.kotlin.compilerRunner.hasProperty +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation +import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet +import org.jetbrains.kotlin.konan.KonanVersion +import org.jetbrains.kotlin.konan.MetaVersion +import org.jetbrains.kotlin.konan.target.CompilerOutputKind +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.util.DependencyProcessor +import org.jetbrains.kotlin.konan.util.DependencySource +import java.io.IOException + +// TODO: It's just a temporary task used while KN isn't integrated with Big Kotlin compilation infrastructure. + +open class KotlinNativeCompile: DefaultTask() { + + init { + super.dependsOn(KonanCompilerDownloadTask.KONAN_DOWNLOAD_TASK_NAME) + } + + @Internal + lateinit var compilation: KotlinNativeCompilation + + // region inputs/outputs + @Input + lateinit var outputKind: CompilerOutputKind + + // Inputs and outputs + + val sources: Collection<FileCollection> + @InputFiles get() = compilation.kotlinSourceSets.map { it.kotlin } + + val libraries: FileCollection + @InputFiles get() = compilation.compileDependencyFiles.filter { + it.extension == "klib" + } + + @Input var optimized = false + @Input var debuggable = true + + val processTests + @Input get() = compilation.isTestCompilation + + val target: String + @Input get() = compilation.target.konanTarget.name + + val additionalCompilerOptions: Collection<String> + @Input get() = compilation.extraOpts + + + @OutputFile val outputFile: RegularFileProperty = newOutputFile() + // endregion + + // region Useful extensions + internal fun MutableList<String>.addArg(parameter: String, value: String) { + add(parameter) + add(value) + } + + internal fun MutableList<String>.addArgs(parameter: String, values: Iterable<String>) { + values.forEach { + addArg(parameter, it) + } + } + + internal fun MutableList<String>.addArgIfNotNull(parameter: String, value: String?) { + if (value != null) { + addArg(parameter, value) + } + } + + internal fun MutableList<String>.addKey(key: String, enabled: Boolean) { + if (enabled) { + add(key) + } + } + + internal fun MutableList<String>.addFileArgs(parameter: String, values: FileCollection) { + values.files.forEach { + addArg(parameter, it.canonicalPath) + } + } + + internal fun MutableList<String>.addFileArgs(parameter: String, values: Collection<FileCollection>) { + values.forEach { + addFileArgs(parameter, it) + } + } + + internal fun MutableList<String>.addListArg(parameter: String, values: List<String>) { + if (values.isNotEmpty()) { + addArg(parameter, values.joinToString(separator = " ")) + } + } + // endregion + + @TaskAction + fun compile() { + val output = outputFile.asFile.get() + output.parentFile.mkdirs() + + val args = mutableListOf<String>().apply { + addArg("-o", outputFile.get().asFile.absolutePath) + addKey("-opt", optimized) + addKey("-g", debuggable) + addKey("-ea", debuggable) + addKey("-tr", processTests) + + addArg("-target", target) + addArg("-p", outputKind.name.toLowerCase()) + + add("-Xmulti-platform") + + addAll(additionalCompilerOptions) + + libraries.files.forEach {library -> + library.parent?.let { addArg("-r", it) } + addArg("-l", library.nameWithoutExtension) + } + + // TODO: Filter only kt files? + addAll(sources.flatMap { it.files }.map { it.absolutePath }) + } + + KonanCompilerRunner(project).run(args) + } +} + + +open class KonanCompilerDownloadTask : DefaultTask() { + + internal companion object { + internal const val BASE_DOWNLOAD_URL = "https://download.jetbrains.com/kotlin/native/builds" + const val KONAN_DOWNLOAD_TASK_NAME = "checkKonanCompiler" + } + + private val simpleOsName: String = HostManager.simpleOsName() + + private val konanCompilerName: String = + "kotlin-native-$simpleOsName-${KonanVersion.CURRENT}" + + @TaskAction + fun downloadAndExtract() { + if (!project.hasProperty(KotlinNativeProjectProperty.DOWNLOAD_COMPILER)) { + val konanHome = project.getProperty(KotlinNativeProjectProperty.KONAN_HOME) + logger.info("Use a user-defined compiler path: $konanHome") + } else { + try { + val downloadUrlDirectory = buildString { + append("$BASE_DOWNLOAD_URL/") + val version = KonanVersion.CURRENT + when (version.meta) { + MetaVersion.DEV -> append("dev/") + else -> append("releases/") + } + append("$version/") + append(simpleOsName) + } + val konanCompiler = konanCompilerName + val parentDir = DependencyProcessor.localKonanDir + logger.info("Downloading Kotlin/Native compiler from $downloadUrlDirectory/$konanCompiler into $parentDir") + DependencyProcessor( + parentDir, + downloadUrlDirectory, + mapOf(konanCompiler to listOf(DependencySource.Remote.Public)) + ).run() + } catch (e: IOException) { + throw GradleScriptException("Cannot download Kotlin/Native compiler", e) + } + } + } +} \ No newline at end of file