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"