Add gradle plugin with simple DSL for Compilator and Stub generator

This commit is contained in:
Ilya Matveev
2017-03-10 15:32:16 +03:00
committed by ilmat192
parent 5a001faaaa
commit c6680f0735
10 changed files with 757 additions and 14 deletions
+1
View File
@@ -24,3 +24,4 @@ include ':runtime'
include ':common'
include ':backend.native:tests'
include ':tools:helpers'
include ':tools:gradle-plugin'
+74
View File
@@ -0,0 +1,74 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: "java"
apply plugin: "kotlin"
// TODO: move this code to top level (the same for backend.native)
// Copied from backend.native
allprojects {
repositories {
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
}
configurations.all {
// kotlin-compiler module includes Kotlin runtime bundled;
// make Gradle aware of this to avoid multiple Kotlin runtimes in classpath:
resolutionStrategy.dependencySubstitution {
substitute module('org.jetbrains.kotlin:kotlin-runtime') with module(kotlinCompilerModule)
substitute module('org.jetbrains.kotlin:kotlin-stdlib') with module(kotlinCompilerModule)
substitute module('org.jetbrains.kotlin:kotlin-reflect') with module(kotlinCompilerModule)
}
// TODO: probably we should use kotlin-compiler without bundled runtime
}
}
repositories {
mavenCentral()
}
configurations {
konanCompiler
}
evaluationDependsOn(':tools:helpers')
dependencies {
// TODO: do we need it?
konanCompiler project(':backend.native')
compile project(':tools:helpers')
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile gradleApi()
}
jar {
dependsOn(':tools:helpers:jar')
from (rootProject.findProject(':tools:helpers').sourceSets.main.output)
}
task dist(type: Copy) {
dependsOn(jar)
destinationDir project.file('dist')
from jar.outputs.files
}
task copy_to_samples(type: Copy) {
dependsOn(dist)
destinationDir rootProject.file('samples')
from dist.outputs.files
}
clean {
doFirst {
delete project.file('dist')
}
}
@@ -0,0 +1,42 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.DefaultTask
import org.gradle.api.GradleScriptException
import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.konan.DependencyDownloader
import java.io.File
open class CompilerDownloadTask: DefaultTask() {
internal companion object {
internal const val DOWNLOAD_URL = "http://download.jetbrains.com/kotlin/native"
internal val KONAN_NAME = "kotlin-native-${simpleOsName()}-${KonanPlugin.KONAN_VERSION}"
private val KONAN_PARENT_DIR = "${System.getProperty("user.home")}/.konan"
internal fun simpleOsName(): String {
val osName = System.getProperty("os.name")
return when (osName) {
"Mac OS X" -> "macos"
"Linux" -> "linux"
else -> throw IllegalStateException("Unsupported platform: $osName")
}
}
}
@TaskAction
fun downloadAndExtract() {
if (project.hasProperty(KonanPlugin.KONAN_HOME_PROPERTY_NAME)) {
val konanHome = project.property(KonanPlugin.KONAN_HOME_PROPERTY_NAME)
logger.info("Use a user-defined compiler path: $konanHome")
return
}
try {
logger.info("Downloading Kotlin Native compiler from $DOWNLOAD_URL into $KONAN_PARENT_DIR")
DependencyDownloader(File(KONAN_PARENT_DIR), DOWNLOAD_URL, listOf(KONAN_NAME)).run()
project.extensions.extraProperties.set(KonanPlugin.KONAN_HOME_PROPERTY_NAME, "$KONAN_PARENT_DIR/$KONAN_NAME")
} catch (e: RuntimeException) {
throw GradleScriptException("Cannot download Kotlin Native compiler", e)
}
}
}
@@ -0,0 +1,203 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Named
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.project.ProjectInternal
import java.io.File
/**
* We can the following:
*
* konanArtifacts {
*
* artifactName1 {
*
* inputDir "path/to/files"
* inputFiles "files" "to" "be" "compiled"
*
* outputDir "path/to/output/dir"
*
* library "path/to/library"
* library File("Library")
*
* nativeLibrary "path/to/library"
* nativeLibrary File("Library")
*
* noStdLib
* noLink
* enableOptimization
*
* linkerOpts "linker" "args"
* target "target"
*
* languageVersion "version"
* apiVersion "version"
*
* }
*
* artifactName2 {
*
* extends artifactName1
*
* inputDir "someDir"
* outputDir "someDir"
* }
*
* }
*/
// TODO: check debug outputs
// TODO: Use +=/-= syntax for libraries and inputFiles
open class KonanCompilerConfig(
val configName: String,
val project: ProjectInternal,
taskNamePrefix: String = "compileKonan"): Named {
override fun getName() = configName
val compilationTask: KonanCompileTask = project.tasks.create(
"$taskNamePrefix${configName.capitalize()}",
KonanCompileTask::class.java
) { it.initialize(this@KonanCompilerConfig.name) }
protected fun String.toAbsolutePath() = project.file(this).canonicalPath
// DSL methods --------------------------------------------------
// TODO: Check if we copied all data or not
fun extendsFrom(anotherConfig: KonanCompilerConfig) = with(compilationTask) {
val anotherTask = anotherConfig.compilationTask
outputDir(anotherTask.outputDir.absolutePath)
linkerOpts(anotherTask.linkerOpts)
anotherTask.target?.let { target(it) }
anotherTask.languageVersion?.let { languageVersion(it) }
anotherTask.apiVersion?.let { apiVersion(it) }
if (anotherTask.noStdLib) noStdLib()
if (anotherTask.noLink) noLink()
if (anotherTask.noMain) noMain()
if (anotherTask.enableOptimization) enableOptimization()
if (anotherTask.enableAssertions) enableAssertions()
}
fun useInterop(interopConfig: KonanInteropConfig) {
var generateStubsTask = interopConfig.generateStubsTask
var compileStubsTask = interopConfig.compileStubsTask
compilationTask.dependsOn(compileStubsTask)
compilationTask.dependsOn(generateStubsTask)
linkerOpts(generateStubsTask.linkerOpts)
libraries(project.fileTree(compileStubsTask.outputDir).apply {
builtBy(compileStubsTask)
include("**/*.bc")
})
nativeLibraries(project.fileTree(generateStubsTask.libsDir).apply {
builtBy(generateStubsTask)
include("**/*.bc")
})
}
fun useInterop(interop: String) = useInterop(project.konanInteropContainer.getByName(interop))
// DSL. Input/output files
fun inputDir(dir: String) = with(compilationTask) {
inputFiles.add(project.fileTree(dir))
}
fun inputFiles(vararg files: Any) = with(compilationTask) {
inputFiles.add(project.files(files))
}
fun inputFiles(files: FileCollection) = compilationTask.inputFiles.add(files)
fun inputFiles(files: Collection<FileCollection>) = compilationTask.inputFiles.addAll(files)
fun outputDir(dir: Any) = with(compilationTask) {
outputDir = project.file(dir)
}
// DSL. Libraries.
fun library(lib: Any) = libraries(lib)
fun libraries(vararg libs: Any) = with(compilationTask) {
libraries.add(project.files(*libs))
}
fun libraries(libs: FileCollection) = with(compilationTask) {
libraries.add(libs)
}
fun nativeLibrary(lib: Any) = nativeLibraries(lib)
fun nativeLibraries(vararg libs: Any) = with(compilationTask) {
nativeLibraries.add(project.files(*libs))
}
fun nativeLibraries(libs: FileCollection) = with(compilationTask) {
nativeLibraries.add(libs)
}
// DSL. Other parameters.
fun linkerOpts(args: List<String>) = linkerOpts(*args.toTypedArray())
fun linkerOpts(vararg args: String) = with(compilationTask) {
linkerOpts.addAll(args)
}
fun target(tgt: String) = with(compilationTask) {
target = tgt
}
fun languageVersion(version: String) = with(compilationTask) {
languageVersion = version
}
fun apiVersion(version: String) = with(compilationTask) {
apiVersion = version
}
fun noStdLib() = with(compilationTask) {
noStdLib = true
}
fun noLink() = with(compilationTask) {
noLink = true
}
fun noMain() = with(compilationTask) {
noMain = true
}
fun enableOptimization() = with(compilationTask) {
enableOptimization = true
}
fun enableAssertions() = with(compilationTask) {
enableAssertions = true
}
}
@@ -0,0 +1,142 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.*
import java.io.File
// TODO: form groups for tasks
// TODO: Make the task class nested for config with properties accessible for outer users.
open class KonanCompileTask: DefaultTask() {
companion object {
const val COMPILER_MAIN = "org.jetbrains.kotlin.cli.bc.K2NativeKt"
}
val COMPILER_JVM_ARGS: List<String>
get() = listOf("-Dkonan.home=${project.konanHome}", "-Djava.library.path=${project.konanHome}/konan/nativelib")
val COMPILER_CLASSPATH: String
get() = "${project.konanHome}/konan/lib/"
// Output artifact --------------------------------------------------------
protected lateinit var artifactName: String
@OutputDirectory
lateinit var outputDir: File
internal set
internal fun initialize(artifactName: String) {
dependsOn(project.konanCompilerDownloadTask)
this.artifactName = artifactName
outputDir = project.file("${project.konanCompilerOutputDir}/$artifactName")
}
val artifactPath: String
get() = "${outputDir.absolutePath}/$artifactName.${ if (noLink) "bc" else "kexe" }"
// Other compilation parameters -------------------------------------------
@InputFiles var inputFiles = mutableSetOf<FileCollection>()
internal set
@InputFiles var libraries = mutableSetOf<FileCollection>()
internal set
@InputFiles var nativeLibraries = mutableSetOf<FileCollection>()
internal set
@Input var linkerOpts = mutableListOf<String>()
internal set
@Input var noStdLib = false
internal set
@Input var noLink = false
internal set
@Input var noMain = false
internal set
@Input var enableOptimization = false
internal set
@Input var enableAssertions = false
internal set
@Optional @Input var target : String? = null
internal set
@Optional @Input var languageVersion : String? = null
internal set
@Optional @Input var apiVersion : String? = null
internal set
// Useful extensions and functions ---------------------------------------
protected fun MutableList<String>.addArg(parameter: String, value: String) {
add(parameter)
add(value)
}
protected fun MutableList<String>.addArgIfNotNull(parameter: String, value: String?) {
if (value != null) {
addArg(parameter, value)
}
}
protected fun MutableList<String>.addKey(key: String, enabled: Boolean) {
if (enabled) {
add(key)
}
}
protected fun MutableList<String>.addFileArgs(parameter: String, values: FileCollection) {
values.files.forEach {
addArg(parameter, it.canonicalPath)
}
}
protected fun MutableList<String>.addFileArgs(parameter: String, values: Collection<FileCollection>) {
values.forEach {
addFileArgs(parameter, it)
}
}
// Task action ------------------------------------------------------------
protected fun buildArgs() = mutableListOf<String>().apply {
addArg("-output", artifactPath)
addFileArgs("-library", libraries)
addFileArgs("-nativelibrary", nativeLibraries)
linkerOpts.forEach {
addArg("-linkerArgs", it)
}
addArgIfNotNull("-target", target)
addArgIfNotNull("-language-version", languageVersion)
addArgIfNotNull("-api-version", apiVersion)
addKey("-nostdlib", noStdLib)
addKey("-nolink", noLink)
addKey("-nomain", noMain)
addKey("-opt", enableOptimization)
addKey("-ea", enableAssertions)
inputFiles.forEach {
it.files.filter { it.name.endsWith(".kt") }.mapTo(this) { it.canonicalPath }
}
}
@TaskAction
fun compile() {
project.file(outputDir).mkdirs()
// TODO: Use compiler service.
project.javaexec {
with(it) {
main = COMPILER_MAIN
classpath = project.fileTree(COMPILER_CLASSPATH).apply { include("*.jar") }
jvmArgs(COMPILER_JVM_ARGS)
args(buildArgs())
}
}
}
}
@@ -0,0 +1,99 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Named
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.project.ProjectInternal
/**
* What we can:
*
* konanInterop {
* foo {
* defFile <def-file>
* pkg <package with stubs>
* target <target: linux/macbook/iphone/iphone_sim>
* compilerOpts <Options for native stubs compilation>
* linkerOpts <Options for native stubs >
* headers <headers to process>
* includeDirs <directories where headers are located>
* linkFiles <files which will be linked with native stubs>
* }
*
* // TODO: add configuration for konan compiler
* }
*/
open class KonanInteropConfig(
val configName: String,
val project: ProjectInternal
): Named {
override fun getName() = configName
// Child tasks ------------------------------------------------------------
// Task to process the library and generate stubs
val generateStubsTask: KonanInteropTask = project.tasks.create(
"gen${name.capitalize()}InteropStubs",
KonanInteropTask::class.java
)
// Config and task to compile *.kt stubs in a *.bc library
internal val compileStubsConfig = KonanCompilerConfig("${name}InteropStubs", project, "compile").apply {
compilationTask.dependsOn(generateStubsTask)
outputDir(generateStubsTask.stubsDir.absolutePath)
noLink()
inputFiles(project.fileTree(generateStubsTask.stubsDir).apply { builtBy(generateStubsTask) })
}
val compileStubsTask = compileStubsConfig.compilationTask
// DSL methods ------------------------------------------------------------
fun defFile(file: Any) = with(generateStubsTask) {
defFile = project.file(file)
}
fun pkg(value: String) = with(generateStubsTask) {
pkg = value
}
fun target(value: String) = with(generateStubsTask) {
target = value
}
fun compilerOpts(vararg values: String) = with(generateStubsTask) {
compilerOpts.addAll(values)
}
fun header(file: Any) = headers(file)
fun headers(vararg files: Any) = with(generateStubsTask) {
headers.add(project.files(files))
}
fun headers(files: FileCollection) = with(generateStubsTask) {
headers.add(files)
}
fun includeDirs(vararg values: String) = with(generateStubsTask) {
compilerOpts.addAll(values.map { "-I$it" })
}
fun linker(value: String) = with(generateStubsTask) {
linker = value
}
fun linkerOpts(vararg values: String) = linkerOpts(values.toList())
fun linkerOpts(values: List<String>) = with(generateStubsTask) {
linkerOpts.addAll(values)
}
fun link(vararg files: Any) = with(generateStubsTask) {
linkFiles.add(project.files(files))
}
fun link(files: FileCollection) = with(generateStubsTask) {
linkFiles.add(files)
}
}
@@ -0,0 +1,102 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.DefaultTask
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.*
import java.io.File
open class KonanInteropTask: DefaultTask() {
internal companion object {
const val INTEROP_MAIN = "org.jetbrains.kotlin.native.interop.gen.jvm.MainKt"
}
init {
dependsOn(project.konanCompilerDownloadTask)
}
internal val INTEROP_JVM_ARGS: List<String>
get() = listOf("-Dkonan.home=${project.konanHome}", "-Djava.library.path=${project.konanHome}/konan/nativelib")
internal val INTEROP_CLASSPATH: String
get() = "${project.konanHome}/konan/lib/"
// Output directories -----------------------------------------------------
/** Directory with autogenerated interop stubs (*.kt) */
@OutputDirectory
val stubsDir = project.file("${project.konanInteropStubsOutputDir}/$name")
/** Directory with library bitcodes (*.bc) */
@OutputDirectory
val libsDir = project.file("${project.konanInteropLibsOutputDir}/$name")
// Interop stub generator parameters -------------------------------------
@Optional @InputFile var defFile: File? = null
internal set
@Optional @Input var target: String? = null
internal set
@Optional @Input var pkg: String? = null
internal set
@Optional @Input var linker: String? = null
internal set
@Input var compilerOpts = mutableListOf<String>()
internal set
@Input var linkerOpts = mutableListOf<String>()
internal set
// TODO: Check if we can use only one FileCollection instead of set.
@InputFiles var headers = mutableSetOf<FileCollection>()
internal set
@InputFiles var linkFiles = mutableSetOf<FileCollection>()
internal set
@TaskAction
fun exec() {
project.javaexec {
with(it) {
main = INTEROP_MAIN
classpath = project.fileTree(INTEROP_CLASSPATH).apply { include("*.jar") }
jvmArgs(INTEROP_JVM_ARGS)
environment("LIBCLANG_DISABLE_CRASH_RECOVERY", "1")
environment("LD_LIBRARY_PATH", "${project.konanHome}/nativelib")
environment("DYLD_LIBRARY_PATH", "${project.konanHome}/nativelib")
args(buildArgs())
}
}
}
protected fun buildArgs() = mutableListOf<String>().apply {
add("-properties:${project.konanHome}/konan/konan.properties")
add("-flavor:native")
add("-generated:$stubsDir")
add("-natives:$libsDir")
target?.let { add("-target:$target") }
defFile?.let { add("-def:$it") }
pkg?.let { add("-pkg:$it") }
linker?.let { add("-linker:$it") }
headers.forEach {
add("-h:$it")
}
compilerOpts.forEach {
add("-copt:$it")
}
val linkerOpts = mutableListOf<String>().apply { addAll(linkerOpts) }
linkFiles.forEach {
linkerOpts.addAll(it.files.map { it.canonicalPath })
}
linkerOpts.forEach {
add("-lopt:$it")
}
}
}
@@ -0,0 +1,92 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.internal.AbstractNamedDomainObjectContainer
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.internal.reflect.Instantiator
/**
* We use the follwing properties:
* kotlin.native.home - directory where compiler is located (aka dist in konan project output)
*/
// konanHome extension is set by downloadKonanCompiler task.
internal val Project.konanHome: String
get() {
assert(extensions.extraProperties.has(KonanPlugin.KONAN_HOME_PROPERTY_NAME))
return extensions.extraProperties.get(KonanPlugin.KONAN_HOME_PROPERTY_NAME).toString()
}
internal val Project.konanBuildRoot get() = "${buildDir.canonicalPath}/konan"
internal val Project.konanCompilerOutputDir get() = "${konanBuildRoot}/bin"
internal val Project.konanInteropStubsOutputDir get() = "${konanBuildRoot}/interopStubs"
internal val Project.konanInteropLibsOutputDir get() = "${konanBuildRoot}/nativelibs"
internal val Project.konanArtifactsContainer: KonanArtifactsContainer
get() = extensions.getByName(KonanPlugin.COMPILER_EXTENSION_NAME) as KonanArtifactsContainer
internal val Project.konanInteropContainer: KonanInteropContainer
get() = extensions.getByName(KonanPlugin.INTEROP_EXTENSION_NAME) as KonanInteropContainer
internal val Project.konanCompilerDownloadTask get() = tasks.getByName(KonanPlugin.KONAN_DOWNLOAD_TASK_NAME)
class KonanArtifactsContainer(val project: ProjectInternal): AbstractNamedDomainObjectContainer<KonanCompilerConfig>(
KonanCompilerConfig::class.java,
project.gradle.services.get(Instantiator::class.java)) {
override fun doCreate(name: String): KonanCompilerConfig =
KonanCompilerConfig(name, project)
}
class KonanInteropContainer(val project: ProjectInternal): AbstractNamedDomainObjectContainer<KonanInteropConfig>(
KonanInteropConfig::class.java,
project.gradle.services.get(Instantiator::class.java)) {
override fun doCreate(name: String): KonanInteropConfig =
KonanInteropConfig(name, project)
}
class KonanPlugin: Plugin<ProjectInternal> {
companion object {
internal const val COMPILER_EXTENSION_NAME = "konanArtifacts"
internal const val INTEROP_EXTENSION_NAME = "konanInterop"
internal const val KONAN_HOME_PROPERTY_NAME = "kotlin.native.home"
internal const val KONAN_DOWNLOAD_TASK_NAME = "downloadKonanCompiler"
// TODO: Move in config.
internal const val KONAN_VERSION = "0.1"
}
/**
* Looks for task with given name in the given project.
* If such task isn't found, will create it. Returns created/found task.
*/
private fun getTask(project: ProjectInternal, name: String): Task {
var tasks = project.getTasksByName(name, false)
assert(tasks.size <= 1)
return if (tasks.isEmpty()) {
project.tasks.create(name, DefaultTask::class.java)
} else {
tasks.single()
}
}
// TODO: Create default config? what about test sources?
override fun apply(project: ProjectInternal?) {
if (project == null) { return }
project.tasks.create(KONAN_DOWNLOAD_TASK_NAME, CompilerDownloadTask::class.java)
project.extensions.add(COMPILER_EXTENSION_NAME, KonanArtifactsContainer(project))
project.extensions.add(INTEROP_EXTENSION_NAME, KonanInteropContainer(project))
getTask(project, "clean").doLast {
project.delete(project.konanBuildRoot)
}
getTask(project, "build").apply {
dependsOn(project.tasks.withType(KonanCompileTask::class.java))
dependsOn(project.tasks.withType(KonanInteropTask::class.java))
}
}
}
@@ -0,0 +1,2 @@
# TODO: find a good name for the plugin
implementation-class=org.jetbrains.kotlin.gradle.plugin.KonanPlugin
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module relativePaths="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output/>
<orderEntry type="inheritedJdk"/>
<content url="file://$MODULE_DIR$/">
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false"/>
<excludeFolder url="file://$MODULE_DIR$/.gradle"/>
<excludeFolder url="file://$MODULE_DIR$/build"/>
</content>
<orderEntry type="sourceFolder" forTests="false"/>
</component>
<component name="ModuleRootManager"/>
</module>