Provide an extended DSL for final native binaries

In 1.3.0 only basic settings for final native binaries (e.g.
executables or frameworks) are available. They allow a user to
specify what kinds of binaries should be produced from one or another
compilation but they don't allow specifying different options for
different binaries or creating more than 2 (debug and release)
binaries of the same kind (e.g. executable) from the same
compilation. Also link tasks for these binaries are created after
project evaluation thus they are inaccessible for a user outside
of an afterEvaluate block.

This patch adds an extended binary DSL allowing a user to declare
binaries independently from compilations and to specify different
options (e.g. linker arguments) for different binaries. Also
link tasks for binaries declared in this DSL are created at a
configuration time so user can access them outside of an
afterEvaluate block.

Also this patch adds creating run tasks for all executables
declared in the buildscript (KT-28106)

Initial DSL methods for binaries declaration are still supported.

Kotlin DSL example is the following:

kotlin {
    macosX64 {
        binaries {
            // Create debug and release executable with
            // a name prefix 'Foo'.
            // Two domain objects will be created:
            // fooDebugExecutable and fooReleaseExecutable
            executable("Foo", listOf(RELEASE, DEBUG)) {
                compilation = compilations["foo"]
                entryPoint = "foo.main"
                linkerOpts.add("-Llib/path")
                println(runTask.name)
            }

            // Name prefix and build types are optional.
            // debugFramework and releaseFramework are created here.
            framework()
        }
    }
}
This commit is contained in:
Ilya Matveev
2018-12-05 17:45:25 +07:00
parent 44af5b783f
commit ea21662bed
15 changed files with 1036 additions and 196 deletions
@@ -15,8 +15,13 @@ val generateMppTargetContainerWithPresets by generator(
sourceSets["main"]
)
generateMppTargetContainerWithPresets.run {
systemProperty(
val generateAbstractBinaryContainer by generator(
"org.jetbrains.kotlin.generators.gradle.dsl.MppNativeBinaryDSLCodegenKt",
sourceSets["main"]
)
listOf(generateMppTargetContainerWithPresets, generateAbstractBinaryContainer).forEach {
it.systemProperty(
"org.jetbrains.kotlin.generators.gradle.dsl.outputSourceRoot",
project(":kotlin-gradle-plugin").projectDir.resolve("src/main/kotlin").absolutePath
)
@@ -25,4 +30,4 @@ generateMppTargetContainerWithPresets.run {
// Workaround: 'java -jar' refuses to read the original dotted filename on Windows, 'Unable to access jarFile org.jetbrains.kotlin....jar'
tasks.named<Jar>(generateMppTargetContainerWithPresets.name + "WriteClassPath").configure {
archiveName = generateMppTargetContainerWithPresets.name
}
}
@@ -32,3 +32,9 @@ internal fun TypeName.renderErased(): String =
internal fun TypeName.collectFqNames(): Set<String> =
setOf(fqName) + typeArguments.flatMap { it.collectFqNames() }.toSet()
internal fun String.indented(nSpaces: Int = 4): String {
val spaces = String(CharArray(nSpaces) { ' ' })
return lines().joinToString("\n") {
if (it.isNotBlank()) "$spaces$it" else it
}
}
@@ -0,0 +1,191 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. 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.generators.gradle.dsl
import java.io.File
fun main() {
generateAbstractKotlinNativeBinaryContainer()
}
internal data class BinaryType(
val description: String,
val className: TypeName,
val nativeOutputKind: TypeName,
val factoryMethod: String,
val getMethod: String,
val findMethod: String
)
private fun binaryType(description: String, className: String, outputKind: String, baseMethodName: String) =
BinaryType(
description,
typeName("$MPP_PACKAGE.$className"),
typeName("${nativeOutputKindClass.fqName}.$outputKind"),
baseMethodName,
"get${baseMethodName.capitalize()}",
"find${baseMethodName.capitalize()}"
)
private val nativeBuildTypeClass = typeName("$MPP_PACKAGE.NativeBuildType")
private val nativeOutputKindClass = typeName("$MPP_PACKAGE.NativeOutputKind")
private val nativeBinaryBaseClass = typeName("$MPP_PACKAGE.NativeBinary")
private fun generateFactoryMethods(binaryType: BinaryType): String {
val className = binaryType.className.renderShort()
val outputKind = binaryType.nativeOutputKind.renderShort()
val outputKindClass = nativeOutputKindClass.renderShort()
val nativeBuildType = nativeBuildTypeClass.renderShort()
val methodName = binaryType.factoryMethod
val binaryDescription = binaryType.description
return """
/** Creates $binaryDescription with the given [namePrefix] for each build type and configures it. */
@JvmOverloads
fun $methodName(
namePrefix: String,
buildTypes: Collection<$nativeBuildType> = $nativeBuildType.DEFAULT_BUILD_TYPES,
configure: $className.() -> Unit = {}
) = createBinaries(namePrefix, namePrefix, $outputKindClass.$outputKind, buildTypes, ::$className, configure)
/** Creates $binaryDescription with the empty name prefix for each build type and configures it. */
@JvmOverloads
fun $methodName(
buildTypes: Collection<$nativeBuildType> = $nativeBuildType.DEFAULT_BUILD_TYPES,
configure: $className.() -> Unit = {}
) = createBinaries("", project.name, $outputKindClass.$outputKind, buildTypes, ::$className, configure)
/** Creates $binaryDescription with the given [namePrefix] for each build type and configures it. */
@JvmOverloads
fun $methodName(
namePrefix: String,
buildTypes: Collection<$nativeBuildType> = $nativeBuildType.DEFAULT_BUILD_TYPES,
configureClosure: Closure<*>
) = $methodName(namePrefix, buildTypes) { ConfigureUtil.configure(configureClosure, this) }
/** Creates $binaryDescription with the default name prefix for each build type and configures it. */
@JvmOverloads
fun $methodName(
buildTypes: Collection<$nativeBuildType> = $nativeBuildType.DEFAULT_BUILD_TYPES,
configureClosure: Closure<*>
) = $methodName(buildTypes) { ConfigureUtil.configure(configureClosure, this) }
""".trimIndent()
}
private fun generateTypedGetters(binaryType: BinaryType): String = with(binaryType) {
val className = className.renderShort()
val buildType = nativeBuildTypeClass.renderShort()
val nativeBuildType = nativeBuildTypeClass.renderShort()
return """
/** Returns $description with the given [namePrefix] and the given build type. Throws an exception if there is no such binary.*/
abstract fun $getMethod(namePrefix: String, buildType: $buildType): $className
/** Returns $description with the given [namePrefix] and the given build type. Throws an exception if there is no such binary.*/
fun $getMethod(namePrefix: String, buildType: String): $className =
$getMethod(namePrefix, $nativeBuildType.valueOf(buildType.toUpperCase()))
/** Returns $description with the empty name prefix and the given build type. Throws an exception if there is no such binary.*/
fun $getMethod(buildType: $buildType): $className = $getMethod("", buildType)
/** Returns $description with the empty name prefix and the given build type. Throws an exception if there is no such binary.*/
fun $getMethod(buildType: String): $className = $getMethod("", buildType)
/** Returns $description with the given [namePrefix] and the given build type. Returns null if there is no such binary. */
abstract fun $findMethod(namePrefix: String, buildType: $buildType): $className?
/** Returns $description with the given [namePrefix] and the given build type. Returns null if there is no such binary. */
fun $findMethod(namePrefix: String, buildType: String): $className? =
$findMethod(namePrefix, $nativeBuildType.valueOf(buildType.toUpperCase()))
/** Returns $description with the empty name prefix and the given build type. Returns null if there is no such binary. */
fun $findMethod(buildType: $buildType): $className? = $findMethod("", buildType)
/** Returns $description with the empty name prefix and the given build type. Returns null if there is no such binary. */
fun $findMethod(buildType: String): $className? = $findMethod("", buildType)
""".trimIndent()
}
fun generateAbstractKotlinNativeBinaryContainer() {
val binaryTypes = listOf(
binaryType("an executable","Executable", "EXECUTABLE", "executable"),
binaryType("a static library","StaticLibrary", "STATIC", "staticLib"),
binaryType("a shared library","SharedLibrary", "DYNAMIC", "sharedLib"),
binaryType("an Objective-C framework","Framework", "FRAMEWORK", "framework")
)
val className = typeName("org.jetbrains.kotlin.gradle.dsl.AbstractKotlinNativeBinaryContainer")
val superClassName = typeName("org.gradle.api.DomainObjectSet", nativeBinaryBaseClass.fqName)
val imports = """
import groovy.lang.Closure
import org.gradle.api.DomainObjectSet
import org.gradle.api.Project
import org.gradle.util.ConfigureUtil
import $MPP_PACKAGE.*
""".trimIndent()
val generatedCodeWarning = "// DO NOT EDIT MANUALLY! Generated by ${object {}.javaClass.enclosingClass.name}"
val classProperties = listOf(
"abstract val project: Project",
"abstract val target: ${typeName(KOTLIN_NATIVE_TARGET_CLASS_FQNAME).shortName()}"
).joinToString(separator = "\n") { it.indented(4) }
val nativeBinary = nativeBinaryBaseClass.renderShort()
val nativeOutputKind = nativeOutputKindClass.renderShort()
val nativeBuildType = nativeBuildTypeClass.renderShort()
val buildTypeConstants = listOf(
"// User-visible constants.",
"val DEBUG = $nativeBuildType.DEBUG",
"val RELEASE = $nativeBuildType.RELEASE"
).joinToString(separator = "\n") { it.indented(4) }
val baseFactoryFunction = """
protected abstract fun <T : $nativeBinary> createBinaries(
namePrefix: String,
baseName: String,
outputKind: $nativeOutputKind,
buildTypes: Collection<$nativeBuildType>,
create: (name: String, baseName: String, buildType: $nativeBuildType, compilation: KotlinNativeCompilation) -> T,
configure: T.() -> Unit
)
""".trimIndent().indented(4)
val namedGetters = """
/** Provide an access to binaries using the [] operator in Groovy DSL. */
fun getAt(name: String): NativeBinary = getByName(name)
/** Provide an access to binaries using the [] operator in Kotlin DSL. */
operator fun get(name: String): NativeBinary = getByName(name)
/** Returns a binary with the given [name]. Throws an exception if there is no such binary. */
abstract fun getByName(name: String): NativeBinary
/** Returns a binary with the given [name]. Returns null if there is no such binary. */
abstract fun findByName(name: String): NativeBinary?
""".trimIndent().indented(4)
val code = listOf(
"package ${className.packageName()}",
imports,
generatedCodeWarning,
"abstract class ${className.renderShort()} : ${superClassName.renderShort()} {",
classProperties,
buildTypeConstants,
baseFactoryFunction,
namedGetters,
binaryTypes.joinToString(separator = "\n\n") { generateTypedGetters(it).indented(4) },
binaryTypes.joinToString(separator = "\n\n") { generateFactoryMethods(it).indented(4) },
"}"
).joinToString(separator = "\n\n")
val outputSourceRoot = System.getProperties()["org.jetbrains.kotlin.generators.gradle.dsl.outputSourceRoot"]
val targetFile = File("$outputSourceRoot/${className.fqName.replace(".", "/")}.kt")
targetFile.writeText(code)
}
@@ -19,7 +19,7 @@ private val parentInterface = KotlinTargetsContainerWithPresets::class
private val presetsProperty = KotlinTargetsContainerWithPresets::presets.name
private fun generateKotlinTargetContainerWithPresetFunctionsInterface() {
// Generate KotlinMutliplatformExtension subclass with member functions for the presets:
// Generate KotlinMultiplatformExtension subclass with member functions for the presets:
val functions = allPresetEntries.map {
generatePresetFunctions(it, presetsProperty, "configureOrCreate")
}
@@ -79,8 +79,3 @@ private fun generatePresetFunctions(
fun $presetName(configure: Closure<*>) = $presetName { ConfigureUtil.configure(configure, this) }
""".trimIndent()
}
private fun String.indented(nSpaces: Int = 4): String {
val spaces = String(CharArray(nSpaces) { ' ' })
return lines().joinToString("\n") { "$spaces$it" }
}
@@ -16,10 +16,10 @@ internal class KotlinPresetEntry(
internal fun KotlinPresetEntry.typeNames(): Set<TypeName> = setOf(presetType, targetType)
private const val MPP_PACKAGE = "org.jetbrains.kotlin.gradle.plugin.mpp"
internal const val MPP_PACKAGE = "org.jetbrains.kotlin.gradle.plugin.mpp"
private const val KOTLIN_NATIVE_TARGET_PRESET_CLASS_FQNAME = "$MPP_PACKAGE.KotlinNativeTargetPreset"
private const val KOTLIN_NATIVE_TARGET_CLASS_FQNAME = "$MPP_PACKAGE.KotlinNativeTarget"
internal const val KOTLIN_NATIVE_TARGET_PRESET_CLASS_FQNAME = "$MPP_PACKAGE.KotlinNativeTargetPreset"
internal const val KOTLIN_NATIVE_TARGET_CLASS_FQNAME = "$MPP_PACKAGE.KotlinNativeTarget"
private const val KOTLIN_ONLY_TARGET_CLASS_FQNAME = "$MPP_PACKAGE.KotlinOnlyTarget"
@@ -0,0 +1,265 @@
package org.jetbrains.kotlin.gradle.dsl
import groovy.lang.Closure
import org.gradle.api.DomainObjectSet
import org.gradle.api.Project
import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.plugin.mpp.*
// DO NOT EDIT MANUALLY! Generated by org.jetbrains.kotlin.generators.gradle.dsl.MppNativeBinaryDSLCodegenKt
abstract class AbstractKotlinNativeBinaryContainer : DomainObjectSet<NativeBinary> {
abstract val project: Project
abstract val target: KotlinNativeTarget
// User-visible constants.
val DEBUG = NativeBuildType.DEBUG
val RELEASE = NativeBuildType.RELEASE
protected abstract fun <T : NativeBinary> createBinaries(
namePrefix: String,
baseName: String,
outputKind: NativeOutputKind,
buildTypes: Collection<NativeBuildType>,
create: (name: String, baseName: String, buildType: NativeBuildType, compilation: KotlinNativeCompilation) -> T,
configure: T.() -> Unit
)
/** Provide an access to binaries using the [] operator in Groovy DSL. */
fun getAt(name: String): NativeBinary = getByName(name)
/** Provide an access to binaries using the [] operator in Kotlin DSL. */
operator fun get(name: String): NativeBinary = getByName(name)
/** Returns a binary with the given [name]. Throws an exception if there is no such binary. */
abstract fun getByName(name: String): NativeBinary
/** Returns a binary with the given [name]. Returns null if there is no such binary. */
abstract fun findByName(name: String): NativeBinary?
/** Returns an executable with the given [namePrefix] and the given build type. Throws an exception if there is no such binary.*/
abstract fun getExecutable(namePrefix: String, buildType: NativeBuildType): Executable
/** Returns an executable with the given [namePrefix] and the given build type. Throws an exception if there is no such binary.*/
fun getExecutable(namePrefix: String, buildType: String): Executable =
getExecutable(namePrefix, NativeBuildType.valueOf(buildType.toUpperCase()))
/** Returns an executable with the empty name prefix and the given build type. Throws an exception if there is no such binary.*/
fun getExecutable(buildType: NativeBuildType): Executable = getExecutable("", buildType)
/** Returns an executable with the empty name prefix and the given build type. Throws an exception if there is no such binary.*/
fun getExecutable(buildType: String): Executable = getExecutable("", buildType)
/** Returns an executable with the given [namePrefix] and the given build type. Returns null if there is no such binary. */
abstract fun findExecutable(namePrefix: String, buildType: NativeBuildType): Executable?
/** Returns an executable with the given [namePrefix] and the given build type. Returns null if there is no such binary. */
fun findExecutable(namePrefix: String, buildType: String): Executable? =
findExecutable(namePrefix, NativeBuildType.valueOf(buildType.toUpperCase()))
/** Returns an executable with the empty name prefix and the given build type. Returns null if there is no such binary. */
fun findExecutable(buildType: NativeBuildType): Executable? = findExecutable("", buildType)
/** Returns an executable with the empty name prefix and the given build type. Returns null if there is no such binary. */
fun findExecutable(buildType: String): Executable? = findExecutable("", buildType)
/** Returns a static library with the given [namePrefix] and the given build type. Throws an exception if there is no such binary.*/
abstract fun getStaticLib(namePrefix: String, buildType: NativeBuildType): StaticLibrary
/** Returns a static library with the given [namePrefix] and the given build type. Throws an exception if there is no such binary.*/
fun getStaticLib(namePrefix: String, buildType: String): StaticLibrary =
getStaticLib(namePrefix, NativeBuildType.valueOf(buildType.toUpperCase()))
/** Returns a static library with the empty name prefix and the given build type. Throws an exception if there is no such binary.*/
fun getStaticLib(buildType: NativeBuildType): StaticLibrary = getStaticLib("", buildType)
/** Returns a static library with the empty name prefix and the given build type. Throws an exception if there is no such binary.*/
fun getStaticLib(buildType: String): StaticLibrary = getStaticLib("", buildType)
/** Returns a static library with the given [namePrefix] and the given build type. Returns null if there is no such binary. */
abstract fun findStaticLib(namePrefix: String, buildType: NativeBuildType): StaticLibrary?
/** Returns a static library with the given [namePrefix] and the given build type. Returns null if there is no such binary. */
fun findStaticLib(namePrefix: String, buildType: String): StaticLibrary? =
findStaticLib(namePrefix, NativeBuildType.valueOf(buildType.toUpperCase()))
/** Returns a static library with the empty name prefix and the given build type. Returns null if there is no such binary. */
fun findStaticLib(buildType: NativeBuildType): StaticLibrary? = findStaticLib("", buildType)
/** Returns a static library with the empty name prefix and the given build type. Returns null if there is no such binary. */
fun findStaticLib(buildType: String): StaticLibrary? = findStaticLib("", buildType)
/** Returns a shared library with the given [namePrefix] and the given build type. Throws an exception if there is no such binary.*/
abstract fun getSharedLib(namePrefix: String, buildType: NativeBuildType): SharedLibrary
/** Returns a shared library with the given [namePrefix] and the given build type. Throws an exception if there is no such binary.*/
fun getSharedLib(namePrefix: String, buildType: String): SharedLibrary =
getSharedLib(namePrefix, NativeBuildType.valueOf(buildType.toUpperCase()))
/** Returns a shared library with the empty name prefix and the given build type. Throws an exception if there is no such binary.*/
fun getSharedLib(buildType: NativeBuildType): SharedLibrary = getSharedLib("", buildType)
/** Returns a shared library with the empty name prefix and the given build type. Throws an exception if there is no such binary.*/
fun getSharedLib(buildType: String): SharedLibrary = getSharedLib("", buildType)
/** Returns a shared library with the given [namePrefix] and the given build type. Returns null if there is no such binary. */
abstract fun findSharedLib(namePrefix: String, buildType: NativeBuildType): SharedLibrary?
/** Returns a shared library with the given [namePrefix] and the given build type. Returns null if there is no such binary. */
fun findSharedLib(namePrefix: String, buildType: String): SharedLibrary? =
findSharedLib(namePrefix, NativeBuildType.valueOf(buildType.toUpperCase()))
/** Returns a shared library with the empty name prefix and the given build type. Returns null if there is no such binary. */
fun findSharedLib(buildType: NativeBuildType): SharedLibrary? = findSharedLib("", buildType)
/** Returns a shared library with the empty name prefix and the given build type. Returns null if there is no such binary. */
fun findSharedLib(buildType: String): SharedLibrary? = findSharedLib("", buildType)
/** Returns an Objective-C framework with the given [namePrefix] and the given build type. Throws an exception if there is no such binary.*/
abstract fun getFramework(namePrefix: String, buildType: NativeBuildType): Framework
/** Returns an Objective-C framework with the given [namePrefix] and the given build type. Throws an exception if there is no such binary.*/
fun getFramework(namePrefix: String, buildType: String): Framework =
getFramework(namePrefix, NativeBuildType.valueOf(buildType.toUpperCase()))
/** Returns an Objective-C framework with the empty name prefix and the given build type. Throws an exception if there is no such binary.*/
fun getFramework(buildType: NativeBuildType): Framework = getFramework("", buildType)
/** Returns an Objective-C framework with the empty name prefix and the given build type. Throws an exception if there is no such binary.*/
fun getFramework(buildType: String): Framework = getFramework("", buildType)
/** Returns an Objective-C framework with the given [namePrefix] and the given build type. Returns null if there is no such binary. */
abstract fun findFramework(namePrefix: String, buildType: NativeBuildType): Framework?
/** Returns an Objective-C framework with the given [namePrefix] and the given build type. Returns null if there is no such binary. */
fun findFramework(namePrefix: String, buildType: String): Framework? =
findFramework(namePrefix, NativeBuildType.valueOf(buildType.toUpperCase()))
/** Returns an Objective-C framework with the empty name prefix and the given build type. Returns null if there is no such binary. */
fun findFramework(buildType: NativeBuildType): Framework? = findFramework("", buildType)
/** Returns an Objective-C framework with the empty name prefix and the given build type. Returns null if there is no such binary. */
fun findFramework(buildType: String): Framework? = findFramework("", buildType)
/** Creates an executable with the given [namePrefix] for each build type and configures it. */
@JvmOverloads
fun executable(
namePrefix: String,
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configure: Executable.() -> Unit = {}
) = createBinaries(namePrefix, namePrefix, NativeOutputKind.EXECUTABLE, buildTypes, ::Executable, configure)
/** Creates an executable with the empty name prefix for each build type and configures it. */
@JvmOverloads
fun executable(
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configure: Executable.() -> Unit = {}
) = createBinaries("", project.name, NativeOutputKind.EXECUTABLE, buildTypes, ::Executable, configure)
/** Creates an executable with the given [namePrefix] for each build type and configures it. */
@JvmOverloads
fun executable(
namePrefix: String,
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configureClosure: Closure<*>
) = executable(namePrefix, buildTypes) { ConfigureUtil.configure(configureClosure, this) }
/** Creates an executable with the default name prefix for each build type and configures it. */
@JvmOverloads
fun executable(
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configureClosure: Closure<*>
) = executable(buildTypes) { ConfigureUtil.configure(configureClosure, this) }
/** Creates a static library with the given [namePrefix] for each build type and configures it. */
@JvmOverloads
fun staticLib(
namePrefix: String,
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configure: StaticLibrary.() -> Unit = {}
) = createBinaries(namePrefix, namePrefix, NativeOutputKind.STATIC, buildTypes, ::StaticLibrary, configure)
/** Creates a static library with the empty name prefix for each build type and configures it. */
@JvmOverloads
fun staticLib(
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configure: StaticLibrary.() -> Unit = {}
) = createBinaries("", project.name, NativeOutputKind.STATIC, buildTypes, ::StaticLibrary, configure)
/** Creates a static library with the given [namePrefix] for each build type and configures it. */
@JvmOverloads
fun staticLib(
namePrefix: String,
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configureClosure: Closure<*>
) = staticLib(namePrefix, buildTypes) { ConfigureUtil.configure(configureClosure, this) }
/** Creates a static library with the default name prefix for each build type and configures it. */
@JvmOverloads
fun staticLib(
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configureClosure: Closure<*>
) = staticLib(buildTypes) { ConfigureUtil.configure(configureClosure, this) }
/** Creates a shared library with the given [namePrefix] for each build type and configures it. */
@JvmOverloads
fun sharedLib(
namePrefix: String,
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configure: SharedLibrary.() -> Unit = {}
) = createBinaries(namePrefix, namePrefix, NativeOutputKind.DYNAMIC, buildTypes, ::SharedLibrary, configure)
/** Creates a shared library with the empty name prefix for each build type and configures it. */
@JvmOverloads
fun sharedLib(
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configure: SharedLibrary.() -> Unit = {}
) = createBinaries("", project.name, NativeOutputKind.DYNAMIC, buildTypes, ::SharedLibrary, configure)
/** Creates a shared library with the given [namePrefix] for each build type and configures it. */
@JvmOverloads
fun sharedLib(
namePrefix: String,
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configureClosure: Closure<*>
) = sharedLib(namePrefix, buildTypes) { ConfigureUtil.configure(configureClosure, this) }
/** Creates a shared library with the default name prefix for each build type and configures it. */
@JvmOverloads
fun sharedLib(
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configureClosure: Closure<*>
) = sharedLib(buildTypes) { ConfigureUtil.configure(configureClosure, this) }
/** Creates an Objective-C framework with the given [namePrefix] for each build type and configures it. */
@JvmOverloads
fun framework(
namePrefix: String,
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configure: Framework.() -> Unit = {}
) = createBinaries(namePrefix, namePrefix, NativeOutputKind.FRAMEWORK, buildTypes, ::Framework, configure)
/** Creates an Objective-C framework with the empty name prefix for each build type and configures it. */
@JvmOverloads
fun framework(
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configure: Framework.() -> Unit = {}
) = createBinaries("", project.name, NativeOutputKind.FRAMEWORK, buildTypes, ::Framework, configure)
/** Creates an Objective-C framework with the given [namePrefix] for each build type and configures it. */
@JvmOverloads
fun framework(
namePrefix: String,
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configureClosure: Closure<*>
) = framework(namePrefix, buildTypes) { ConfigureUtil.configure(configureClosure, this) }
/** Creates an Objective-C framework with the default name prefix for each build type and configures it. */
@JvmOverloads
fun framework(
buildTypes: Collection<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES,
configureClosure: Closure<*>
) = framework(buildTypes) { ConfigureUtil.configure(configureClosure, this) }
}
@@ -0,0 +1,153 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.dsl
import org.gradle.api.*
import org.gradle.api.plugins.ExtensionAware
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import javax.inject.Inject
/*
Naming:
executable('foo', [debug, release]) -> fooDebugExecutable + fooReleaseExecutable
executable -> debugExecutable, releaseExecutable
executable([debug]) -> debugExecutable
// Tests:
1. Cannot add a second binary with the same parameters.
2. Can access a binary by name
3. Access linkTask
4. Access runTask
5. Final binaries have correct names
6. Old APIs work fine:
- creating binaries,
- getting binaries and link tasks,
- setting binary parameters using compilation DSL: linker opts and entry points
*/
open class KotlinNativeBinaryContainer @Inject constructor(
override val target: KotlinNativeTarget,
backingContainer: DomainObjectSet<NativeBinary>
) : AbstractKotlinNativeBinaryContainer(),
DomainObjectSet<NativeBinary> by backingContainer
{
override val project: Project
get() = target.project
private val defaultCompilation: KotlinNativeCompilation
get() = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
private val nameToBinary = mutableMapOf<String, NativeBinary>()
// region DSL getters.
private fun generateName(prefix: String, buildType: NativeBuildType, outputKindClassifier: String) =
lowerCamelCaseName(prefix, buildType.getName(), outputKindClassifier)
private inline fun <reified T : NativeBinary> getBinary(
namePrefix: String,
buildType: NativeBuildType,
outputKind: NativeOutputKind
): T {
val classifier = outputKind.taskNameClassifier
val name = generateName(namePrefix, buildType, classifier)
val binary = getByName(name)
require(binary is T && binary.buildType == buildType) {
"Binary $name has incorrect outputKind or build type.\n" +
"Expected: ${buildType.getName()} $classifier. Actual: ${binary.buildType.getName()} ${binary.outputKind.taskNameClassifier}."
}
return binary as T
}
private inline fun <reified T : NativeBinary> findBinary(
namePrefix: String,
buildType: NativeBuildType,
outputKind: NativeOutputKind
): T? {
val classifier = outputKind.taskNameClassifier
val name = generateName(namePrefix, buildType, classifier)
val binary = findByName(name)
return if (binary is T && binary.buildType == buildType) {
binary
} else {
null
}
}
override fun getByName(name: String): NativeBinary = nameToBinary.getValue(name)
override fun findByName(name: String): NativeBinary? = nameToBinary[name]
override fun getExecutable(namePrefix: String, buildType: NativeBuildType): Executable =
getBinary(namePrefix, buildType, NativeOutputKind.EXECUTABLE)
override fun getStaticLib(namePrefix: String, buildType: NativeBuildType): StaticLibrary =
getBinary(namePrefix, buildType, NativeOutputKind.STATIC)
override fun getSharedLib(namePrefix: String, buildType: NativeBuildType): SharedLibrary =
getBinary(namePrefix, buildType, NativeOutputKind.DYNAMIC)
override fun getFramework(namePrefix: String, buildType: NativeBuildType): Framework =
getBinary(namePrefix, buildType, NativeOutputKind.FRAMEWORK)
override fun findExecutable(namePrefix: String, buildType: NativeBuildType): Executable? =
findBinary(namePrefix, buildType, NativeOutputKind.EXECUTABLE)
override fun findStaticLib(namePrefix: String, buildType: NativeBuildType): StaticLibrary? =
findBinary(namePrefix, buildType, NativeOutputKind.STATIC)
override fun findSharedLib(namePrefix: String, buildType: NativeBuildType): SharedLibrary? =
findBinary(namePrefix, buildType, NativeOutputKind.DYNAMIC)
override fun findFramework(namePrefix: String, buildType: NativeBuildType): Framework? =
findBinary(namePrefix, buildType, NativeOutputKind.FRAMEWORK)
// endregion.
// region Factories
override fun <T : NativeBinary> createBinaries(
namePrefix: String,
baseName: String,
outputKind: NativeOutputKind,
buildTypes: Collection<NativeBuildType>,
create: (name: String, baseName: String, buildType: NativeBuildType, compilation: KotlinNativeCompilation) -> T,
configure: T.() -> Unit
) = buildTypes.forEach { buildType ->
val name = generateName(namePrefix, buildType, outputKind.taskNameClassifier)
require(name !in nameToBinary) {
"Cannot create binary $name: binary with such a name already exists"
}
require(outputKind.availableFor(target.konanTarget)) {
"Cannot create binary $name: ${outputKind.taskNameClassifier.decapitalize()} binaries are not available for target ${target.name}"
}
val binary = create(name, baseName, buildType, defaultCompilation)
add(binary)
nameToBinary[binary.name] = binary
// Allow accessing binaries as properties of the container in Groovy DSL.
if (this is ExtensionAware) {
extensions.add(binary.name, binary)
}
binary.configure()
}
internal fun defaultTestExecutable(
configure: Executable.() -> Unit
) = createBinaries(
"test",
"test",
NativeOutputKind.EXECUTABLE,
listOf(NativeBuildType.DEBUG),
{ name, baseName, buildType, compilation ->
Executable(name, baseName, buildType, compilation, true)
},
configure
)
// endregion.
}
@@ -23,6 +23,7 @@ import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.Exec
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.testing.Test
import org.gradle.language.base.plugins.LifecycleBasePlugin
@@ -33,8 +34,10 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile
import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
@@ -59,7 +62,8 @@ abstract class AbstractKotlinTargetConfigurator<KotlinTargetType : KotlinTarget>
}
abstract fun configureArchivesAndComponent(target: KotlinTargetType)
abstract protected fun configureArchivesAndComponent(target: KotlinTargetType)
abstract protected fun configureTest(target: KotlinTargetType)
private fun Project.registerOutputsForStaleOutputCleanup(kotlinCompilation: KotlinCompilation<*>) {
val cleanTask = tasks.getByName(LifecycleBasePlugin.CLEAN_TASK_NAME) as Delete
@@ -116,22 +120,6 @@ abstract class AbstractKotlinTargetConfigurator<KotlinTargetType : KotlinTarget>
}
}
protected fun configureTest(target: KotlinTarget) {
val testCompilation = target.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME) as? KotlinCompilationToRunnableFiles
?: return // Otherwise, there is no runtime classpath
target.project.tasks.create(lowerCamelCaseName(target.targetName, testTaskNameSuffix), Test::class.java).apply {
project.afterEvaluate {
// use afterEvaluate to override the JavaPlugin defaults for Test tasks
conventionMapping.map("testClassesDirs") { testCompilation.output.classesDirs }
conventionMapping.map("classpath") { testCompilation.runtimeDependencyFiles }
description = "Runs the unit tests."
group = JavaBasePlugin.VERIFICATION_GROUP
target.project.tasks.findByName(JavaBasePlugin.CHECK_TASK_NAME)?.dependsOn(this@apply)
}
}
}
protected fun configureResourceProcessing(
compilation: KotlinCompilationWithResources<*>,
resourceSet: FileCollection
@@ -372,6 +360,22 @@ open class KotlinTargetConfigurator<KotlinCompilationType : KotlinCompilation<*>
}
}
override fun configureTest(target: KotlinOnlyTarget<KotlinCompilationType>) {
val testCompilation = target.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME) as? KotlinCompilationToRunnableFiles<*>
?: return // Otherwise, there is no runtime classpath
target.project.tasks.create(lowerCamelCaseName(target.targetName, testTaskNameSuffix), Test::class.java).apply {
project.afterEvaluate {
// use afterEvaluate to override the JavaPlugin defaults for Test tasks
conventionMapping.map("testClassesDirs") { testCompilation.output.classesDirs }
conventionMapping.map("classpath") { testCompilation.runtimeDependencyFiles }
description = "Runs the unit tests."
group = JavaBasePlugin.VERIFICATION_GROUP
target.project.tasks.findByName(JavaBasePlugin.CHECK_TASK_NAME)?.dependsOn(this@apply)
}
}
}
private fun addJar(configuration: Configuration, jarArtifact: PublishArtifact) {
val publications = configuration.outgoing
@@ -388,49 +392,6 @@ open class KotlinNativeTargetConfigurator(
createDefaultSourceSets = true,
createTestCompilation = true
) {
private val hostTargets = listOf(KonanTarget.LINUX_X64, KonanTarget.MACOS_X64, KonanTarget.MINGW_X64)
private val Collection<*>.isDimensionVisible: Boolean
get() = size > 1
private fun Project.createTestTask(compilation: KotlinNativeCompilation, testExecutableLinkTask: KotlinNativeCompile) {
val compilationSuffix = compilation.name.takeIf { it != KotlinCompilation.TEST_COMPILATION_NAME }.orEmpty()
val taskName = lowerCamelCaseName(compilation.target.targetName, compilationSuffix, testTaskNameSuffix)
val testTask = 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.target.name}'."
enabled = compilation.target.konanTarget.isCurrentHost
val testExecutableProperty = testExecutableLinkTask.outputFile
executable = testExecutableProperty.get().absolutePath
outputDir = project.layout.projectDirectory.asFile
if (project.hasProperty("teamcity.version")) {
args("--ktest_logger=TEAMCITY")
}
onlyIf { testExecutableProperty.get().exists() }
inputs.file(testExecutableProperty)
dependsOn(testExecutableLinkTask)
}
tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).apply {
dependsOn(testTask)
}
}
private fun Project.binaryOutputDirectory(
buildType: NativeBuildType,
kind: NativeOutputKind,
compilation: KotlinNativeCompilation
): File {
val targetSubDirectory = compilation.target.disambiguationClassifier?.let { "$it/" }.orEmpty()
val buildTypeSubDirectory = buildType.name.toLowerCase()
val kindSubDirectory = kind.outputDirectoryName
return buildDir.resolve("bin/$targetSubDirectory${compilation.name}/$buildTypeSubDirectory/$kindSubDirectory")
}
private fun Project.klibOutputDirectory(
compilation: KotlinNativeCompilation
): File {
@@ -438,55 +399,14 @@ open class KotlinNativeTargetConfigurator(
return buildDir.resolve("classes/kotlin/$targetSubDirectory${compilation.name}")
}
private fun KotlinNativeCompile.addCompilerPlugins() {
private fun AbstractKotlinNativeCompile.addCompilerPlugins() {
SubpluginEnvironment
.loadSubplugins(project, kotlinPluginVersion)
.addSubpluginOptions<CommonCompilerArguments>(project, this, compilerPluginOptions)
compilerPluginClasspath = project.configurations.getByName(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME)
}
private fun Project.createBinaryLinkTasks(compilation: KotlinNativeCompilation) = whenEvaluated {
val konanTarget = compilation.target.konanTarget
val availableOutputKinds = compilation.outputKinds.filter { it.availableFor(konanTarget) }
val linkAll = project.tasks.maybeCreate(compilation.linkAllTaskName)
for (buildType in compilation.buildTypes) {
for (kind in availableOutputKinds) {
val compilerOutputKind = kind.compilerOutputKind
val linkTask = project.tasks.create(
compilation.linkTaskName(kind, buildType),
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}'."
enabled = compilation.target.konanTarget.enabledOnCurrentHost
optimized = buildType.optimized
debuggable = buildType.debuggable
destinationDir = binaryOutputDirectory(buildType, kind, compilation)
addCompilerPlugins()
linkAll.dependsOn(this)
}
compilation.binaryTasks[kind to buildType] = linkTask
if (compilation.isTestCompilation &&
buildType == NativeBuildType.DEBUG &&
konanTarget in hostTargets
) {
// TODO: Refactor and move into the corresponding method of AbstractKotlinTargetConfigurator.
createTestTask(compilation, linkTask)
}
}
}
}
// region Artifact creation.
private fun Project.createKlibArtifact(
compilation: KotlinNativeCompilation,
artifactFile: File,
@@ -541,7 +461,52 @@ open class KotlinNativeTargetConfigurator(
interop: DefaultCInteropSettings,
interopTask: CInteropProcess
) = createKlibArtifact(interop.compilation, interopTask.outputFile, "cinterop-${interop.name}", interopTask, copy = true)
// endregion.
// region Task creation.
private fun Project.createLinkTask(binary: NativeBinary) {
// TODO: drop compilation.linkAllTaskName
// TODO: drop compilation.binaryTasks
// TODO: Provide a link all task.
tasks.create(
binary.linkTaskName,
KotlinNativeLink::class.java
).apply {
val target = binary.target
this.binary = binary
group = BasePlugin.BUILD_GROUP
description = "Links ${binary.outputKind.description} '${binary.name}' for a target '${target.name}'."
enabled = target.konanTarget.enabledOnCurrentHost
destinationDir = binary.outputDirectory
addCompilerPlugins()
}
}
private fun Project.createRunTask(binary: Executable) {
val taskName = binary.runTaskName
// TODO provide a special exec task for tests.
tasks.create(taskName, Exec::class.java).apply {
if (binary.isDefaultTestExecutable) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Executes Kotlin/Native unit tests for target ${binary.target.name}."
tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(this)
if (project.hasProperty("teamcity.version")) {
args("--ktest_logger=TEAMCITY")
}
} else {
group = RUN_GROUP
description = "Executes Kotlin/Native executable ${binary.baseName} for target ${binary.target.name}"
}
enabled = binary.target.konanTarget.isCurrentHost
executable = binary.outputFile.absolutePath
workingDir = project.projectDir
onlyIf { binary.outputFile.exists() }
dependsOn(binary.linkTaskName)
}
}
private fun Project.createKlibCompilationTask(compilation: KotlinNativeCompilation) {
val compileTask = tasks.create(
@@ -549,7 +514,6 @@ open class KotlinNativeTargetConfigurator(
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}'."
@@ -602,12 +566,20 @@ open class KotlinNativeTargetConfigurator(
createCInteropKlibArtifact(interop, interopTask)
}
}
// endregion.
// region Configuration.
override fun configureTarget(target: KotlinNativeTarget) {
super.configureTarget(target)
configureBinaries(target)
configureCInterops(target)
warnAboutIncorrectDependencies(target)
}
override fun configureArchivesAndComponent(target: KotlinNativeTarget): Unit = with(target.project) {
tasks.create(target.artifactsTaskName)
target.compilations.all {
createKlibCompilationTask(it)
createBinaryLinkTasks(it)
}
with(configurations.getByName(target.apiElementsConfigurationName)) {
@@ -615,6 +587,12 @@ open class KotlinNativeTargetConfigurator(
}
}
override fun configureTest(target: KotlinNativeTarget) {
target.binaries.defaultTestExecutable {
compilation = target.compilations.maybeCreate(KotlinCompilation.TEST_COMPILATION_NAME)
}
}
protected fun configureCInterops(target: KotlinNativeTarget): Unit = with(target.project) {
target.compilations.all { compilation ->
createCInteropTasks(compilation)
@@ -633,10 +611,44 @@ open class KotlinNativeTargetConfigurator(
}
}
override fun configureTarget(target: KotlinNativeTarget) {
super.configureTarget(target)
configureCInterops(target)
warnAboutIncorrectDependencies(target)
protected fun configureBinaries(target: KotlinNativeTarget) {
val project = target.project
target.binaries.all {
project.createLinkTask(it)
}
target.binaries.withType(Executable::class.java).all {
project.createRunTask(it)
}
// Create binaries for output kinds declared using the old DSL.
project.whenEvaluated {
target.compilations.all { compilation ->
val binaries = target.binaries
val konanTarget = compilation.target.konanTarget
val name = compilation.name
val buildTypes = compilation.buildTypes
val availableOutputKinds = compilation.outputKinds.filter { it.availableFor(konanTarget) }
val configure: NativeBinary.() -> Unit = {
this.compilation = compilation
linkerOpts.addAll(compilation.linkerOpts)
if (this is Executable) {
entryPoint = compilation.entryPoint
}
compilation.binaryTasks[outputKind to buildType] = this
}
for (kind in availableOutputKinds) {
when (kind) {
NativeOutputKind.EXECUTABLE -> binaries.executable(name, buildTypes, configure)
NativeOutputKind.DYNAMIC -> binaries.sharedLib(name, buildTypes, configure)
NativeOutputKind.STATIC -> binaries.staticLib(name, buildTypes, configure)
NativeOutputKind.FRAMEWORK -> binaries.framework(name, buildTypes, configure)
}
}
}
}
}
override fun defineConfigurationsForTarget(target: KotlinNativeTarget) {
@@ -685,6 +697,7 @@ open class KotlinNativeTargetConfigurator(
}
}
}
// endregion.
object NativeArtifactFormat {
const val KLIB = "org.jetbrains.kotlin.klib"
@@ -692,6 +705,7 @@ open class KotlinNativeTargetConfigurator(
companion object {
const val INTEROP_GROUP = "interop"
const val RUN_GROUP = "run"
protected fun defineConfigurationsForCInterop(
compilation: KotlinNativeCompilation,
@@ -85,12 +85,9 @@ class KotlinNativeCompilationFactory(
KotlinNativeCompilation(target, name).apply {
if (name == KotlinCompilation.TEST_COMPILATION_NAME) {
friendCompilationName = KotlinCompilation.MAIN_COMPILATION_NAME
outputKinds = mutableListOf(NativeOutputKind.EXECUTABLE)
buildTypes = mutableListOf(NativeBuildType.DEBUG)
isTestCompilation = true
} else {
buildTypes = mutableListOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE)
}
buildTypes = mutableListOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE)
}
}
@@ -0,0 +1,156 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.plugin.mpp
import groovy.lang.Closure
import org.gradle.api.Named
import org.gradle.api.Project
import org.gradle.api.tasks.AbstractExecTask
import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.plugin.AbstractKotlinTargetConfigurator
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.io.File
// TODO: Extract API.
// TODO: Should the baseName be a var?
/**
* A base class representing a final binary produced by the Kotlin/Native compiler
* @param name - a name of the DSL entity.
* @param baseName - a base name for the output binary file. E.g. for baseName foo we produce binaries foo.kexe, libfoo.so, foo.framework.
* @param compilation - a compilation used to produce this binary.
*
*/
sealed class NativeBinary(
private val name: String,
val baseName: String,
val buildType: NativeBuildType,
var compilation: KotlinNativeCompilation
) : Named {
val target: KotlinNativeTarget
get() = compilation.target
val project: Project
get() = target.project
abstract val outputKind: NativeOutputKind
// Configuration DSL.
var debuggable: Boolean = false
var optimized: Boolean = false
var linkerOpts: MutableList<String> = mutableListOf()
fun linkerOpts(vararg options: String) {
linkerOpts.addAll(options.toList())
}
fun linkerOpts(options: Iterable<String>) {
linkerOpts.addAll(options)
}
// Link task access.
val linkTaskName: String
get() = lowerCamelCaseName("link", name, target.targetName)
val linkTask: KotlinNativeLink
get() = project.tasks.getByName(linkTaskName) as KotlinNativeLink
// Output access.
// TODO: Provide output configurations and integrate them with Gradle Native.
val outputDirectory: File = with(project) {
val targetSubDirectory = target.disambiguationClassifier?.let { "$it/" }.orEmpty()
buildDir.resolve("bin/$targetSubDirectory${this@NativeBinary.name}")
}
val outputFile: File
get() = linkTask.outputFile.get()
// Named implementation.
override fun getName(): String = name
}
class Executable constructor(
name: String,
baseName: String,
buildType: NativeBuildType,
compilation: KotlinNativeCompilation,
internal val isDefaultTestExecutable: Boolean
) : NativeBinary(name, baseName, buildType, compilation) {
constructor(
name: String,
baseName: String,
buildType: NativeBuildType,
compilation: KotlinNativeCompilation) : this(name, baseName, buildType, compilation, false)
override val outputKind: NativeOutputKind
get() = NativeOutputKind.EXECUTABLE
var entryPoint: String? = null
fun entryPoint(point: String?) {
entryPoint = point
}
val runTaskName: String
get() = if (isDefaultTestExecutable) {
lowerCamelCaseName(compilation.target.targetName, AbstractKotlinTargetConfigurator.testTaskNameSuffix)
} else {
lowerCamelCaseName("run", name, compilation.target.targetName)
}
// TODO: may make it lateinit (along with linkTasks)?
val runTask: AbstractExecTask<*>
get() = project.tasks.getByName(runTaskName) as AbstractExecTask<*>
fun runTask(configure: AbstractExecTask<*>.() -> Unit) {
runTask.configure()
}
fun runTask(configure: Closure<*>) {
ConfigureUtil.configure(configure, runTask)
}
}
class StaticLibrary(
name: String,
baseName: String,
buildType: NativeBuildType,
compilation: KotlinNativeCompilation
) : NativeBinary(name, baseName, buildType, compilation) {
override val outputKind: NativeOutputKind
get() = NativeOutputKind.STATIC
}
class SharedLibrary(
name: String,
baseName: String,
buildType: NativeBuildType,
compilation: KotlinNativeCompilation
) : NativeBinary(name, baseName, buildType, compilation) {
override val outputKind: NativeOutputKind
get() = NativeOutputKind.DYNAMIC
}
class Framework(
name: String,
baseName: String,
buildType: NativeBuildType,
compilation: KotlinNativeCompilation
) : NativeBinary(name, baseName, buildType, compilation) {
override val outputKind: NativeOutputKind
get() = NativeOutputKind.FRAMEWORK
// TODO: Pack task configuration.
}
@@ -1,7 +1,6 @@
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
@@ -16,51 +15,40 @@ enum class NativeBuildType(val optimized: Boolean, val debuggable: Boolean) : Na
DEBUG(false, true);
override fun getName(): String = name.toLowerCase()
companion object {
val DEFAULT_BUILD_TYPES = setOf(DEBUG, RELEASE)
}
}
enum class NativeOutputKind(
val compilerOutputKind: CompilerOutputKind,
val taskNameClassifier: String,
val outputDirectoryName: String = taskNameClassifier,
val description: String = taskNameClassifier,
val additionalCompilerFlags: List<String> = emptyList(),
val runtimeUsageName: String? = null,
val linkUsageName: String? = null,
val publishable: Boolean = true // Not used yet.
val description: String = taskNameClassifier
) {
EXECUTABLE(
CompilerOutputKind.PROGRAM,
"executable",
description = "an executable",
runtimeUsageName = Usage.NATIVE_RUNTIME,
publishable = false
description = "an executable"
),
DYNAMIC(
CompilerOutputKind.DYNAMIC,
"shared",
description = "a dynamic library",
runtimeUsageName = Usage.NATIVE_RUNTIME,
linkUsageName = Usage.NATIVE_LINK,
publishable = false
description = "a dynamic library"
) {
override fun availableFor(target: KonanTarget) = target != KonanTarget.WASM32
},
STATIC(
CompilerOutputKind.STATIC,
"static",
description = "a static library",
runtimeUsageName = Usage.NATIVE_RUNTIME,
linkUsageName = Usage.NATIVE_LINK,
publishable = false
description = "a static library"
) {
override fun availableFor(target: KonanTarget) = target != KonanTarget.WASM32
},
FRAMEWORK(
CompilerOutputKind.FRAMEWORK,
"framework",
description = "an Objective-C framework",
linkUsageName = KotlinNativeUsage.FRAMEWORK,
publishable = false
description = "a framework"
) {
override fun availableFor(target: KonanTarget) =
target.family == Family.OSX || target.family == Family.IOS
@@ -15,13 +15,11 @@ import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.tasks.SourceSet
import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker
import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.io.File
@@ -321,7 +319,8 @@ class KotlinNativeCompilation(
target.compilations.getByName(it)
}
internal val binaryTasks = mutableMapOf<Pair<NativeOutputKind, NativeBuildType>, KotlinNativeCompile>()
// Used only to support the old APIs. TODO: Remove when the old APIs are removed.
internal val binaryTasks = mutableMapOf<Pair<NativeOutputKind, NativeBuildType>, NativeBinary>()
// Native-specific DSL.
var extraOpts = mutableListOf<String>()
@@ -376,10 +375,9 @@ class KotlinNativeCompilation(
}
// Task accessors.
fun findLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink? = binaryTasks[kind to buildType]?.linkTask
fun findLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeCompile? = binaryTasks[kind to buildType]
fun getLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeCompile =
fun getLinkTask(kind: NativeOutputKind, buildType: NativeBuildType): KotlinNativeLink =
findLinkTask(kind, buildType)
?: throw IllegalArgumentException("Cannot find a link task for the binary kind '$kind' and the build type '$buildType'")
@@ -403,6 +401,7 @@ class KotlinNativeCompilation(
override val processResourcesTaskName: String
get() = disambiguateName("processResources")
// TODO: Add linkAll task and add a deprecation here.
val linkAllTaskName: String
get() = lowerCamelCaseName(
"link",
@@ -21,6 +21,7 @@ import org.gradle.util.ConfigureUtil
import org.gradle.util.WrapUtil
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
@@ -191,28 +192,41 @@ class KotlinNativeTarget(
attributes.attribute(konanTargetAttribute, konanTarget.name)
}
// TODO: Should binary files be output of a target or a compilation?
val binaries = if(isGradleVersionAtLeast(4, 2)) {
// Use newInstance to allow accessing binaries by their names in Groovy using the extension mechanism.
project.objects.newInstance(KotlinNativeBinaryContainer::class.java, this, WrapUtil.toDomainObjectSet(NativeBinary::class.java))
} else {
KotlinNativeBinaryContainer(this, WrapUtil.toDomainObjectSet(NativeBinary::class.java))
}
fun binaries(configure: KotlinNativeBinaryContainer.() -> Unit) {
binaries.configure()
}
fun binaries(configure: Closure<*>) {
ConfigureUtil.configure(configure, binaries)
}
override val artifactsTaskName: String
get() = disambiguateName("link")
override val publishable: Boolean
get() = konanTarget.enabledOnCurrentHost
// User-visible constants
val DEBUG = NativeBuildType.DEBUG
val RELEASE = NativeBuildType.RELEASE
val EXECUTABLE = NativeOutputKind.EXECUTABLE
val FRAMEWORK = NativeOutputKind.FRAMEWORK
val DYNAMIC = NativeOutputKind.DYNAMIC
val STATIC = NativeOutputKind.STATIC
companion object {
val konanTargetAttribute = Attribute.of(
"org.jetbrains.kotlin.native.target",
String::class.java
)
// TODO: Can we do it better?
// User-visible constants
val DEBUG = NativeBuildType.DEBUG
val RELEASE = NativeBuildType.RELEASE
val EXECUTABLE = NativeOutputKind.EXECUTABLE
val FRAMEWORK = NativeOutputKind.FRAMEWORK
val DYNAMIC = NativeOutputKind.DYNAMIC
val STATIC = NativeOutputKind.STATIC
}
}
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile
internal class DefaultLanguageSettingsBuilder : LanguageSettingsBuilder {
private var languageVersionImpl: LanguageVersion? = null
@@ -70,7 +70,7 @@ internal class DefaultLanguageSettingsBuilder : LanguageSettingsBuilder {
val pluginOptionsTask = compilerPluginOptionsTask.value ?: return null
return when (pluginOptionsTask) {
is AbstractKotlinCompile<*> -> pluginOptionsTask.pluginOptions
is KotlinNativeCompile -> pluginOptionsTask.compilerPluginOptions
is AbstractKotlinNativeCompile -> pluginOptionsTask.compilerPluginOptions
else -> error("Unexpected task: $pluginOptionsTask")
}.arguments
}
@@ -80,7 +80,7 @@ internal class DefaultLanguageSettingsBuilder : LanguageSettingsBuilder {
val pluginClasspathTask = compilerPluginOptionsTask.value ?: return null
return when (pluginClasspathTask) {
is AbstractKotlinCompile<*> -> pluginClasspathTask.pluginClasspath
is KotlinNativeCompile -> pluginClasspathTask.compilerPluginClasspath ?: pluginClasspathTask.project.files()
is AbstractKotlinNativeCompile -> pluginClasspathTask.compilerPluginClasspath ?: pluginClasspathTask.project.files()
else -> error("Unexpected task: $pluginClasspathTask")
}
}
@@ -16,10 +16,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultCInteropSettings
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.defaultSourceSetName
import org.jetbrains.kotlin.gradle.plugin.mpp.isMainCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.CompilerOutputKind.*
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -81,20 +78,26 @@ private fun FileCollection.filterOutPublishableInteropLibs(project: Project): Fi
}
// endregion
open class KotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOptions> {
abstract class AbstractKotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOptions> {
init {
sourceCompatibility = "1.6"
targetCompatibility = "1.6"
}
@Internal
lateinit var compilation: KotlinNativeCompilation
abstract val compilation: KotlinNativeCompilation
// region inputs/outputs
@Input
lateinit var outputKind: CompilerOutputKind
@get:Input
abstract val outputKind: CompilerOutputKind
@get:Input
abstract val optimized: Boolean
@get:Input
abstract val debuggable: Boolean
abstract val baseName: String
// Inputs and outputs
@InputFiles
@@ -116,23 +119,13 @@ open class KotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOp
throw UnsupportedOperationException("Setting classpath directly is unsupported.")
}
@Input
var optimized = false
@Input
var debuggable = true
val processTests
@Input get() = compilation.isTestCompilation
val target: String
@Input get() = compilation.target.konanTarget.name
val entryPoint: String?
@Optional @Input get() = compilation.entryPoint
val linkerOpts: List<String>
@Input get() = compilation.linkerOpts
// TODO: rework.
val additionalCompilerOptions: Collection<String>
@Input get() = compilation.extraOpts
@@ -163,13 +156,14 @@ open class KotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOp
set(value) { languageSettings!!.apiVersion = value }
override var languageVersion: String?
get() = this@KotlinNativeCompile.languageVersion
get() = this@AbstractKotlinNativeCompile.languageVersion
set(value) { languageSettings!!.languageVersion = value }
override var allWarningsAsErrors: Boolean = false
override var suppressWarnings: Boolean = false
override var verbose: Boolean = false
// TODO: deprecate extraOptions because now we have a uniform way to access these parameters.
// Delegate for compilations's extra options.
override var freeCompilerArgs: List<String>
get() = compilation.extraOpts
@@ -202,7 +196,6 @@ open class KotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOp
val prefix = outputKind.prefix(konanTarget)
val suffix = outputKind.suffix(konanTarget)
val baseName = if (compilation.isMainCompilation) project.name else compilation.name
var filename = "$prefix$baseName$suffix"
if (outputKind in listOf(FRAMEWORK, STATIC, DYNAMIC) || outputKind == PROGRAM && konanTarget == KonanTarget.WASM32) {
filename = filename.replace('-', '_')
@@ -228,7 +221,7 @@ open class KotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOp
val defaultSerializedCompilerArguments: List<String>
@Internal get() = buildCommonArgs(true)
private fun buildCommonArgs(defaultsOnly: Boolean = false) = mutableListOf<String>().apply {
private fun buildCommonArgs(defaultsOnly: Boolean = false): List<String> = mutableListOf<String>().apply {
add("-Xmulti-platform")
@@ -264,7 +257,7 @@ open class KotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOp
}
}
private fun buildArgs(defaultsOnly: Boolean = false) = mutableListOf<String>().apply {
protected open fun buildArgs(defaultsOnly: Boolean = false): List<String> = mutableListOf<String>().apply {
addKey("-opt", optimized)
addKey("-g", debuggable)
addKey("-ea", debuggable)
@@ -272,7 +265,6 @@ open class KotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOp
addArg("-target", target)
addArg("-p", outputKind.name.toLowerCase())
addArgIfNotNull("-entry", entryPoint)
if (!defaultsOnly) {
addArg("-o", outputFile.get().absolutePath)
@@ -291,8 +283,6 @@ open class KotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOp
addArg("-friend-modules", friends.map { it.absolutePath }.joinToString(File.pathSeparator))
}
addListArg("-linker-options", linkerOpts)
addAll(buildCommonArgs(defaultsOnly))
// Sources.
@@ -310,6 +300,73 @@ open class KotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOp
}
}
/**
* A task producing a klibrary from a compilation.
*/
open class KotlinNativeCompile : AbstractKotlinNativeCompile() {
@Internal
override lateinit var compilation: KotlinNativeCompilation
@get:Input
override val outputKind = LIBRARY
@get:Input
override val optimized = false
@get:Input
override val debuggable = true
@get:Internal
override val baseName: String
get() = if (compilation.isMainCompilation) project.name else compilation.name
}
/**
* A task producing a final binary from a compilation.
*/
open class KotlinNativeLink : AbstractKotlinNativeCompile() {
@Internal
lateinit var binary: NativeBinary
@get:Internal
override val compilation: KotlinNativeCompilation
get() = binary.compilation
@get:Input
override val outputKind: CompilerOutputKind
get() = binary.outputKind.compilerOutputKind
@get:Input
override val optimized: Boolean
get() = binary.optimized
@get:Input
override val debuggable: Boolean
get() = binary.debuggable
@get:Internal
override val baseName: String
get() = binary.baseName
@get:Optional
@get:Input
val entryPoint: String?
get() = (binary as? Executable)?.entryPoint
@get:Input
val linkerOpts: List<String>
get() = binary.linkerOpts
override fun buildArgs(defaultsOnly: Boolean): List<String> {
val superArgs = super.buildArgs(defaultsOnly)
return mutableListOf<String>().apply {
addAll(superArgs)
addArgIfNotNull("-entry", entryPoint)
addListArg("-linker-options", linkerOpts)
}
}
}
open class CInteropProcess : DefaultTask() {
@Internal