gradle-plugin: Rework DSL

* Remove konanInterop block
* Use konanArtifact block for both compiler and interop artifacts
* Specify targets in a project-wide manner
* Autogenerate tasks for all targets specified for a project
* Add an ability to configure each target for an artifact separately

With this patch the following old DSL:

konanInterop {
    fooMacbook { target 'macbook' ... }
    fooLinux { target 'linux' ... }
}

konanArtifacts {
    barMacbook { target 'macbook' }
    barLinux { target 'linux' }
}

Transforms into:

konanTargets = ['macbook', 'linux']

konanArtifacts {
    interop('foo') { ... }
    program('bar') { ... }
}
This commit is contained in:
Ilya Matveev
2017-10-17 19:53:50 +03:00
committed by ilmat192
parent 476bed32f8
commit d3f9e335fb
21 changed files with 1066 additions and 1050 deletions
@@ -92,7 +92,7 @@ dependencies {
}
test {
dependsOn ':cross_dist'
dependsOn ':dist'
//testLogging.showStandardStreams = true
systemProperty("konan.home", rootProject.file('dist').canonicalPath)
}
@@ -0,0 +1,76 @@
package org.jetbrains.kotlin.gradle.plugin
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.internal.reflect.Instantiator
import org.gradle.util.ConfigureUtil
open class KonanArtifactContainer(val project: ProjectInternal)
: DefaultPolymorphicDomainObjectContainer<KonanBuildingConfig<*>>(
KonanBuildingConfig::class.java,
project.services.get(Instantiator::class.java)
) {
init {
registerFactory(KonanProgram::class.java) { name ->
instantiator.newInstance(KonanProgram::class.java, name, project, instantiator)
}
registerFactory(KonanLibrary::class.java) { name ->
instantiator.newInstance(KonanLibrary::class.java, name, project, instantiator)
}
registerFactory(KonanBitcode::class.java) { name ->
instantiator.newInstance(KonanBitcode::class.java, name, project, instantiator)
}
registerFactory(KonanInteropLibrary::class.java) { name ->
instantiator.newInstance(KonanInteropLibrary::class.java, name, project, instantiator)
}
}
override fun createConfigureDelegate(configureClosure: Closure<*>?): ConfigureDelegate {
return ConfigureDelegate(configureClosure)
}
// TODO: Investigate if we can support the same DSL as project.task:
// program foo(targets: [target1, target2, target3]) {
// ...
// }
open inner class ConfigureDelegate(configureClosure: Closure<*>?)
: org.gradle.internal.metaobject.ConfigureDelegate(configureClosure, this@KonanArtifactContainer) {
fun program(name: String) = create(name, KonanProgram::class.java)
fun program(name: String, configureAction: Action<KonanProgram>) =
create(name, KonanProgram::class.java, configureAction)
fun program(name: String, configureAction: KonanProgram.() -> Unit) =
create(name, KonanProgram::class.java, configureAction)
fun program(name: String, configureAction: Closure<*>) =
program(name, ConfigureUtil.configureUsing(configureAction))
fun library(name: String) = create(name, KonanLibrary::class.java)
fun library(name: String, configureAction: Action<KonanLibrary>) =
create(name, KonanLibrary::class.java, configureAction)
fun library(name: String, configureAction: KonanLibrary.() -> Unit) =
create(name, KonanLibrary::class.java, configureAction)
fun library(name: String, configureAction: Closure<*>) =
library(name, ConfigureUtil.configureUsing(configureAction))
fun bitcode(name: String) = create(name, KonanBitcode::class.java)
fun bitcode(name: String, configureAction: Action<KonanBitcode>) =
create(name, KonanBitcode::class.java, configureAction)
fun bitcode(name: String, configureAction: KonanBitcode.() -> Unit) =
create(name, KonanBitcode::class.java, configureAction)
fun bitcode(name: String, configureAction: Closure<*>) =
bitcode(name, ConfigureUtil.configureUsing(configureAction))
fun interop(name: String) = create(name, KonanInteropLibrary::class.java)
fun interop(name: String, configureAction: Action<KonanInteropLibrary>) =
create(name, KonanInteropLibrary::class.java, configureAction)
fun interop(name: String, configureAction: KonanInteropLibrary.() -> Unit) =
create(name, KonanInteropLibrary::class.java, configureAction)
fun interop(name: String, configureAction: Closure<*>) =
interop(name, ConfigureUtil.configureUsing(configureAction))
}
}
@@ -0,0 +1,131 @@
package org.jetbrains.kotlin.gradle.plugin
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.InvalidUserDataException
import org.gradle.api.Named
import org.gradle.api.Task
import org.gradle.api.internal.DefaultNamedDomainObjectSet
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.plugins.BasePlugin
import org.gradle.internal.reflect.Instantiator
import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanBuildingTask
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.TargetManager
import java.io.File
/** Base class for all Kotlin/Native artifacts. */
abstract class KonanBuildingConfig<T: KonanBuildingTask>(private val name_: String,
type: Class<T>,
val project: ProjectInternal,
instantiator: Instantiator)
: KonanBuildingSpec, Named, DefaultNamedDomainObjectSet<T>(type, instantiator, { it.target.userName }) {
override fun getName() = name_
protected val targetToTask = mutableMapOf<KonanTarget, T>()
internal val aggregateBuildTask: Task
val declaredTargets: Iterable<KonanTarget> = project.konanTargets.map { TargetManager(it).target }
init {
declaredTargets.forEach {
if (it.enabled) {
super.add(createTask(it))
} else {
project.logger.warn("The target is not enabled on the current host: ${it.userName}")
}
}
aggregateBuildTask = createAggregateTask()
createHostTaskIfDeclared()
}
protected open fun generateTaskName(target: KonanTarget) =
"compileKonan${name.capitalize()}${target.userName.capitalize()}"
protected open fun generateAggregateTaskName() =
"compileKonan${name.capitalize()}"
protected open fun generateHostTaskName() =
"compileKonan${name.capitalize()}Host"
protected abstract fun generateTaskDescription(task: T): String
protected abstract fun generateAggregateTaskDescription(task: Task): String
protected abstract fun generateHostTaskDescription(task: Task, hostTarget: KonanTarget): String
protected abstract val defaultOutputDir: File
override fun didAdd(toAdd: T) {
super.didAdd(toAdd)
assert(toAdd.target !in targetToTask)
targetToTask[toAdd.target] = toAdd
}
protected fun createTask(target: KonanTarget): T =
project.tasks.create(generateTaskName(target), type) {
it.init(defaultOutputDir, name, target)
it.group = BasePlugin.BUILD_GROUP
it.description = generateTaskDescription(it)
} ?: throw Exception("Cannot create task for target: ${target.userName}")
protected fun createAggregateTask(): Task =
project.tasks.create(generateAggregateTaskName()) { task ->
task.group = BasePlugin.BUILD_GROUP
task.description = generateAggregateTaskDescription(task)
this.filter {
project.targetIsRequested(it.target)
}.forEach {
task.dependsOn(it)
}
project.compileAllTask.dependsOn(task)
}
protected fun createHostTaskIfDeclared(): Task? =
this[TargetManager.host]?.let { hostBuild ->
project.tasks.create(generateHostTaskName()) {
it.group = BasePlugin.BUILD_GROUP
it.description = generateHostTaskDescription(it, hostBuild.target)
it.dependsOn(hostBuild)
}
}
operator fun get(target: KonanTarget) = targetToTask[target]
// Common building DSL.
override fun outputName(name: String) = forEach { it.outputName(name) }
override fun baseDir(dir: Any) = forEach { it.baseDir(dir) }
override fun libraries(closure: Closure<Unit>) = forEach { it.libraries(closure) }
override fun libraries(action: Action<KonanLibrariesSpec>) = forEach { it.libraries(action) }
override fun libraries(configure: KonanLibrariesSpec.() -> Unit) = forEach { it.libraries(configure) }
override fun noDefaultLibs(flag: Boolean) = forEach { it.noDefaultLibs(flag) }
override fun dumpParameters(flag: Boolean) = forEach { it.dumpParameters(flag) }
override fun extraOpts(vararg values: Any) = forEach { it.extraOpts(*values) }
override fun extraOpts(values: List<Any>) = forEach { it.extraOpts(values) }
fun dependsOn(vararg dependencies: Any?) = forEach { it.dependsOn(*dependencies) }
fun target(targetString: String, configureAction: T.() -> Unit) {
val target = TargetManager(targetString).target
if (!target.enabled) {
project.logger.info("Target '$targetString' of artifact '$name' is not supported on the current host")
return
}
val task = this[target] ?:
throw InvalidUserDataException("Target '$targetString' is not declared. Please add it into project.konanTasks list")
task.configureAction()
}
fun target(targetString: String, configureAction: Action<T>) =
target(targetString) { configureAction.execute(this) }
fun target(targetString: String, configureAction: Closure<Unit>) =
target(targetString, ConfigureUtil.configureUsing(configureAction))
}
@@ -0,0 +1,90 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.internal.reflect.Instantiator
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileBitcodeTask
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileLibraryTask
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileProgramTask
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileTask
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
abstract class KonanCompileConfig<T: KonanCompileTask>(name: String,
type: Class<T>,
project: ProjectInternal,
instantiator: Instantiator)
: KonanBuildingConfig<T>(name, type, project, instantiator), KonanCompileSpec {
protected abstract val typeForDescription: String
override fun generateTaskDescription(task: T) =
"Build the Kotlin/Native $typeForDescription '${task.name}' for target '${task.target}'"
override fun generateAggregateTaskDescription(task: Task) =
"Build the Kotlin/Native $typeForDescription '${task.name}' for all supported and declared targets"
override fun generateHostTaskDescription(task: Task, hostTarget: KonanTarget) =
"Build the Kotlin/Native $typeForDescription '${task.name}' for current host"
override fun inputDir(dir: Any) = forEach { it.inputDir(dir) }
override fun inputFiles(vararg files: Any) = forEach { it.inputFiles(*files) }
override fun inputFiles(files: Collection<Any>) = forEach { it.inputFiles(files) }
override fun nativeLibrary(lib: Any) = forEach { it.nativeLibrary(lib) }
override fun nativeLibraries(vararg libs: Any) = forEach { it.nativeLibraries(*libs) }
override fun nativeLibraries(libs: FileCollection) = forEach { it.nativeLibraries(libs) }
override fun linkerOpts(args: List<String>) = forEach { it.linkerOpts(args) }
override fun linkerOpts(vararg args: String) = forEach { it.linkerOpts(*args) }
override fun languageVersion(version: String) = forEach { it.languageVersion(version) }
override fun apiVersion(version: String) = forEach { it.apiVersion(version) }
override fun enableDebug(flag: Boolean) = forEach { it.enableDebug(flag) }
override fun noStdLib(flag: Boolean) = forEach { it.noStdLib(flag) }
override fun noMain(flag: Boolean) = forEach { it.noMain(flag) }
override fun enableOptimizations(flag: Boolean) = forEach { it.enableOptimizations(flag) }
override fun enableAssertions(flag: Boolean) = forEach { it.enableAssertions(flag) }
override fun measureTime(flag: Boolean) = forEach { it.measureTime(flag) }
}
open class KonanProgram(name: String, project: ProjectInternal, instantiator: Instantiator)
: KonanCompileConfig<KonanCompileProgramTask>(name, KonanCompileProgramTask::class.java, project, instantiator) {
override val typeForDescription: String
get() = "executable"
override val defaultOutputDir: File
get() = project.konanBinOutputDir
}
open class KonanLibrary(name: String, project: ProjectInternal, instantiator: Instantiator)
: KonanCompileConfig<KonanCompileLibraryTask>(name, KonanCompileLibraryTask::class.java, project, instantiator) {
override val typeForDescription: String
get() = "library"
override val defaultOutputDir: File
get() = project.konanLibsOutputDir
}
open class KonanBitcode(name: String, project: ProjectInternal, instantiator: Instantiator)
: KonanCompileConfig<KonanCompileBitcodeTask>(name, KonanCompileBitcodeTask::class.java, project, instantiator) {
override val typeForDescription: String
get() = "bitcode"
override fun generateTaskDescription(task: KonanCompileBitcodeTask) =
"Generates bitcode for the artifact '${task.name}' and target '${task.target}'"
override fun generateAggregateTaskDescription(task: Task) =
"Generates bitcode for the artifact '${task.name}' for all supported and declared targets'"
override fun generateHostTaskDescription(task: Task, hostTarget: KonanTarget) =
"Generates bitcode for the artifact '${task.name}' for current host"
override val defaultOutputDir: File
get() = project.konanBitcodeOutputDir
}
@@ -0,0 +1,44 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.internal.reflect.Instantiator
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanInteropTask
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
open class KonanInteropLibrary(name: String, project: ProjectInternal, instantiator: Instantiator)
: KonanBuildingConfig<KonanInteropTask>(name, KonanInteropTask::class.java, project, instantiator), KonanInteropSpec {
override fun generateTaskDescription(task: KonanInteropTask) =
"Build the Kotlin/Native interop library '${task.name}' for target '${task.target}'"
override fun generateAggregateTaskDescription(task: Task) =
"Build the Kotlin/Native interop library '${task.name}' for all supported and declared targets'"
override fun generateHostTaskDescription(task: Task, hostTarget: KonanTarget) =
"Build the Kotlin/Native interop library '${task.name}' for current host"
override val defaultOutputDir: File
get() = project.konanLibsOutputDir
// DSL
override fun defFile(file: Any) = forEach { it.defFile(file) }
override fun pkg(value: String) = forEach { it.pkg(value) }
override fun compilerOpts(vararg values: String) = forEach { it.compilerOpts(*values) }
override fun headers(vararg files: Any) = forEach { it.headers(*files) }
override fun headers(files: FileCollection) = forEach { it.headers(files) }
override fun includeDirs(vararg values: Any) = forEach { it.includeDirs(*values) }
override fun linkerOpts(values: List<String>) = forEach { it.linkerOpts(values) }
override fun link(vararg files: Any) = forEach { it.link(*files) }
override fun link(files: FileCollection) = forEach { it.link(files) }
}
@@ -1,72 +1,37 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.InvalidUserDataException
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Internal
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanArtifactWithLibrariesTask
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanBuildingTask
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
open class KonanLibrariesSpec(val task: KonanArtifactWithLibrariesTask, val project: Project) {
/**
* libraries {
file 'sdfsdf'
file project.files(sdfsd) // == file: Any
klib 'posix' // no changes
klib config
artifact 'myLib' // in the project
interop 'microhttpd'
artifact project, 'myLib'
interop project, 'name'
artifact config
interop config
allArtifactsFrom project
allInteropsFrom project
allArtifactsFrom 'project'
allInteropsFrom 'project'
useRepo Any (-> Project.files)
}
val task = libConfig.task
if (config == libConfig) {
throw IllegalArgumentException("Attempt to use a library as its own dependency: " +
"${libConfig.name} (in project: ${project.path})")
}
project.evaluationDependsOn(libConfig.project.path)
config.dependsOn(task)
*
*
*
*/
// TODO: Get rid of two things: Interop and artifact. Replace with one KonanArtifact.
class KonanLibrariesSpec(val project: Project) {
@InputFiles val files = mutableSetOf<FileCollection>() // TODO: Immutable interface?
@InputFiles val files = mutableSetOf<FileCollection>()
@Input val namedKlibs = mutableSetOf<String>()
@Nested val artifacts = mutableSetOf<KonanBuildingConfig>()
@Internal val artifacts = mutableListOf<KonanBuildingTask>()
// TODO: input?
val repos = mutableSetOf<File>().apply {
add(project.file(project.konanCompilerOutputDir))
add(project.file(project.konanInteropOutputDir))
}
val artifactFiles: List<File>
@InputFiles get() = artifacts.map { it.artifact }
@Internal val explicitRepos = mutableSetOf<File>()
val repos: Set<File>
@Input get() = explicitRepos +
task.outputDir +
task.project.konanLibsOutputDir +
artifacts.flatMap { it.libraries.repos }.toSet()
val target: KonanTarget
@Internal get() = task.target
// DSL Methods
@@ -75,72 +40,77 @@ class KonanLibrariesSpec(val project: Project) {
fun files(vararg files: Any) = this.files.addAll(files.map { project.files(it) })
fun files(collection: FileCollection) = this.files.add(collection)
// TODO: doc.
/** The compiler with search the library in repos */
fun klib(lib: String) = namedKlibs.add(lib)
fun klibs(vararg libs: String) = namedKlibs.addAll(libs)
fun klibs(libs: Iterable<String>) = namedKlibs.addAll(libs)
/** Direct link to a config */
fun klib(libConfig: KonanBuildingConfig) {
useReposOf(libConfig)
artifacts.add(libConfig)
private fun klibInternal(lib: KonanBuildingConfig<*>) {
if (!(lib is KonanLibrary || lib is KonanInteropLibrary)) {
throw InvalidUserDataException("Config ${lib.name} is not a library")
}
val libraryTask = lib[target] ?:
throw InvalidUserDataException("Library ${lib.name} has no target ${target.userName}")
if (libraryTask == task) {
throw InvalidUserDataException("Attempt to use a library as its own dependency: " +
"${task.name} (in project: ${project.path})")
}
artifacts.add(libraryTask)
task.dependsOn(libraryTask)
}
/** Direct link to a config */
fun klib(lib: KonanLibrary) = klibInternal(lib)
/** Direct link to a config */
fun klib(lib: KonanInteropLibrary) = klibInternal(lib)
/** Artifact in the specified project by name */
fun artifact(libraryProject: Project, name: String) {
project.evaluationDependsOn(libraryProject)
klib(libraryProject.konanArtifactsContainer.getByName(name))
}
/** Interop in the specified project by name */
fun interop(libraryProject: Project, name: String) {
project.evaluationDependsOn(libraryProject)
klib(libraryProject.konanInteropContainer.getByName(name))
klibInternal(libraryProject.konanArtifactsContainer.getByName(name))
}
/** Artifact in the current project by name */
fun artifact(name: String) = artifact(project, name)
/** Interop library in the current project by name */
fun interop(name: String) = interop(project, name)
/** Artifact by direct link */
fun artifact(artifact: KonanCompileConfig) = klib(artifact)
/** Interop library by direct link */
fun interop(artifact: KonanInteropConfig) = klib(artifact)
fun artifact(artifact: KonanLibrary) = klib(artifact)
/** Direct link to a config */
fun artifact(artifact: KonanInteropLibrary) = klib(artifact)
/** All artifacts from the projects by direct link */
fun allArtifactsFrom(vararg libraryProjects: Project) = libraryProjects.forEach {
project.evaluationDependsOn(it)
it.konanArtifactsContainer
.filter { it.task.isLibrary }
.forEach { klib(it) }
}
/** All interop libraries from the projects by direct link */
fun allInteropsFrom(vararg libraryProjects: Project) = libraryProjects.forEach {
project.evaluationDependsOn(it)
it.konanInteropContainer
.forEach { klib(it) }
private fun allArtifactsFromInternal(libraryProjects: Array<out Project>,
filter: (KonanBuildingConfig<*>) -> Boolean) {
libraryProjects.forEach { prj ->
project.evaluationDependsOn(prj)
prj.konanArtifactsContainer.filter(filter).forEach {
klibInternal(it)
}
}
}
/** All artifacts from the project by its relative path */
fun allArtifactsFrom(vararg paths: String) = allArtifactsFrom(*paths.map { project.project(it) }.toTypedArray())
/** All interop libraries from the project by its name */
/** All libraries (both interop and non-interop ones) from the projects by direct references */
fun allLibrariesFrom(vararg libraryProjects: Project) = allArtifactsFromInternal(libraryProjects) {
it is KonanLibrary || it is KonanInteropLibrary
}
/** All libraries (both interop and non-interop ones) from the projects by paths */
fun allLibrariesFrom(vararg paths: String) = allLibrariesFrom(*paths.map { project.project(it) }.toTypedArray())
/** All interop libraries from the projects by direct references */
fun allInteropsFrom(vararg libraryProjects: Project) = allArtifactsFromInternal(libraryProjects) {
it is KonanInteropLibrary
}
/** All interop libraries from the projects by paths */
fun allInteropsFrom(vararg paths: String) = allInteropsFrom(*paths.map { project.project(it) }.toTypedArray())
/** Add repo for library search */
fun useRepo(directory: Any) = repos.add(project.file(directory))
fun useRepo(directory: Any) = explicitRepos.add(project.file(directory))
/** Add repos for library search */
fun useRepos(vararg directories: Any) = directories.forEach { useRepo(it) }
/** Add repos for library search */
fun useRepos(directories: Iterable<Any>) = directories.forEach { useRepo(it) }
/** Add the output directory of the config and default output directories of its project as repos */
private fun useReposOf(config: KonanBuildingConfig) {
useRepo(config.task.outputDir)
useRepo(config.project.konanCompilerOutputDir)
useRepo(config.project.konanInteropOutputDir)
}
fun Project.evaluationDependsOn(another: Project) {
if (this != another) { evaluationDependsOn(another.path) }
}
@@ -18,9 +18,13 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.*
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.TaskCollection
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.jetbrains.kotlin.gradle.plugin.KonanPlugin.Companion.COMPILE_ALL_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.tasks.*
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.util.*
import javax.inject.Inject
@@ -31,15 +35,15 @@ import javax.inject.Inject
*/
internal fun Project.hasProperty(property: KonanPlugin.ProjectProperty) = hasProperty(property.propertyName)
internal fun Project.findProperty(property: KonanPlugin.ProjectProperty) = findProperty(property.propertyName)
internal fun Project.findProperty(property: KonanPlugin.ProjectProperty): Any? = findProperty(property.propertyName)
internal fun Project.getProperty(property: KonanPlugin.ProjectProperty) = findProperty(property)
?: throw IllegalArgumentException("No such property in the project: ${property.propertyName}")
internal fun Project.getProperty(property: KonanPlugin.ProjectProperty, defaultValue: Any?) =
internal fun Project.getProperty(property: KonanPlugin.ProjectProperty, defaultValue: Any) =
findProperty(property) ?: defaultValue
internal fun Project.setProperty(property: KonanPlugin.ProjectProperty, value: Any?) {
internal fun Project.setProperty(property: KonanPlugin.ProjectProperty, value: Any) {
extensions.extraProperties.set(property.propertyName, value)
}
@@ -50,47 +54,51 @@ internal val Project.konanHome: String
return project.file(getProperty(KonanPlugin.ProjectProperty.KONAN_HOME)).canonicalPath
}
internal val Project.konanBuildRoot get() = "${buildDir.canonicalPath}/konan"
internal val Project.konanCompilerOutputDir get() = "${konanBuildRoot}/bin"
internal val Project.konanInteropOutputDir get() = "${konanBuildRoot}/c_interop"
internal val Project.konanBuildRoot get() = buildDir.resolve("konan")
internal val Project.konanBinOutputDir get() = konanBuildRoot.resolve("bin")
internal val Project.konanLibsOutputDir get() = konanBuildRoot.resolve("libs")
internal val Project.konanBitcodeOutputDir get() = konanBuildRoot.resolve("bitcode")
internal val Project.konanDefaultSrcFiles get() = fileTree("${projectDir.canonicalPath}/src/main/kotlin")
internal fun Project.konanDefaultDefFile(libName: String)
= file("${projectDir.canonicalPath}/src/main/c_interop/$libName.def")
@Suppress("UNCHECKED_CAST")
internal val Project.konanArtifactsContainer: NamedDomainObjectContainer<KonanCompileConfig>
get() = extensions.getByName(KonanPlugin.ARTIFACTS_CONTAINER_NAME) as NamedDomainObjectContainer<KonanCompileConfig>
internal val Project.konanArtifactsContainer: NamedDomainObjectContainer<KonanBuildingConfig<*>>
get() = extensions.getByName(KonanPlugin.ARTIFACTS_CONTAINER_NAME)
as NamedDomainObjectContainer<KonanBuildingConfig<*>>
@Suppress("UNCHECKED_CAST")
internal val Project.konanInteropContainer: NamedDomainObjectContainer<KonanInteropConfig>
get() = extensions.getByName(KonanPlugin.INTEROP_CONTAINER_NAME) as NamedDomainObjectContainer<KonanInteropConfig>
internal val Project.konanTargets: MutableList<String>
get() = extensions.extraProperties[KonanPlugin.TARGETS_EXTENSION_NAME] as MutableList<String>
internal val Project.konanCompilerDownloadTask get() = tasks.getByName(KonanPlugin.KONAN_DOWNLOAD_TASK_NAME)
internal val Project.konanCompilerDownloadTask
get() = tasks.getByName(KonanPlugin.KONAN_DOWNLOAD_TASK_NAME)
internal val Project.konanVersion
get() = getProperty(KonanPlugin.ProjectProperty.KONAN_VERSION, KonanPlugin.DEFAULT_KONAN_VERSION) as String
internal fun Project.targetIsRequested(target: String?): Boolean {
val targets = getProperty(KonanPlugin.ProjectProperty.KONAN_BUILD_TARGETS).toString().trim().split(' ')
internal val Project.requestedTargets
get() = findProperty(KonanPlugin.ProjectProperty.KONAN_BUILD_TARGETS)?.toString()?.trim()?.split(' ') ?: emptyList()
return (targets.contains(target) ||
targets.contains("all") ||
target == null)
internal val Project.compileAllTask
get() = getOrCreateTask(COMPILE_ALL_TASK_NAME)
internal fun Project.targetIsRequested(target: KonanTarget): Boolean {
val targets = requestedTargets
return (targets.isEmpty() || targets.contains(target.userName) || targets.contains("all"))
}
internal fun Project.targetIsSupportedAndRequested(task: KonanTargetableTask)
= task.targetIsSupported && this.targetIsRequested(task.target)
/** Looks for task with given name in the given project. Throws [UnknownTaskException] if there's not such task. */
private fun Project.getTask(name: String): Task = tasks.getByPath(name)
internal val Project.supportedCompileTasks: TaskCollection<KonanCompileTask>
get() = project.tasks.withType(KonanCompileTask::class.java).matching {
targetIsSupportedAndRequested(it)
}
internal val Project.supportedInteropTasks: TaskCollection<KonanInteropTask>
get() = project.tasks.withType(KonanInteropTask::class.java).matching {
targetIsSupportedAndRequested(it)
}
/**
* 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 Project.getOrCreateTask(name: String): Task = with(tasks) {
findByPath(name) ?: create(name, DefaultTask::class.java)
}
internal fun Project.konanCompilerName(): String =
"kotlin-native-${project.simpleOsName}-${this.konanVersion}"
@@ -98,15 +106,7 @@ internal fun Project.konanCompilerName(): String =
internal fun Project.konanCompilerDownloadDir(): String =
KonanCompilerDownloadTask.KONAN_PARENT_DIR + "/" + project.konanCompilerName()
internal class KonanCompileConfigFactory(val project: Project): NamedDomainObjectFactory<KonanCompileConfig> {
override fun create(name: String): KonanCompileConfig = KonanCompileConfig(name, project)
}
internal class KonanInteropConfigFactory(val project: Project): NamedDomainObjectFactory<KonanInteropConfig> {
override fun create(name: String): KonanInteropConfig = KonanInteropConfig(name, project)
}
// Useful extensions and functions ---------------------------------------
// region Useful extensions and functions ---------------------------------------
internal fun MutableList<String>.addArg(parameter: String, value: String) {
add(parameter)
@@ -149,52 +149,58 @@ internal fun MutableList<String>.addListArg(parameter: String, values: List<Stri
}
}
// endregion
internal fun dumpProperties(task: Task) {
fun Iterable<String>.dump() = joinToString(prefix = "[", separator = ",\n${" ".repeat(22)}", postfix = "]")
fun Collection<FileCollection>.dump() = flatMap { it.files }.map { it.canonicalPath }.dump()
when (task) {
is KonanCompileTask -> {
is KonanCompileTask -> with(task) {
println()
println("Compilation task: ${task.name}")
println("outputDir : ${task.outputDir}")
println("artifact : ${task.artifact}")
println("artifactPath : ${task.artifactPath}")
println("inputFiles : ${task.inputFiles.dump()}")
println("produce : ${task.produce}")
println("libraries : ${task.libraries.files.dump()}")
println(" : ${task.libraries.artifacts.map {
it.task.artifact.canonicalPath
println("Compilation task: ${name}")
println("outputDir : ${outputDir}")
println("artifact : ${artifact.canonicalPath}")
println("inputFiles : ${inputFiles.dump()}")
println("produce : ${produce}")
println("libraries : ${libraries.files.dump()}")
println(" : ${libraries.artifacts.map {
it.artifact.canonicalPath
}.dump()}")
println(" : ${task.libraries.namedKlibs.dump()}")
println("nativeLibraries : ${task.nativeLibraries.dump()}")
println("linkerOpts : ${task.linkerOpts}")
println("enableDebug : ${task.enableDebug}")
println("noStdLib : ${task.noStdLib}")
println("noMain : ${task.noMain}")
println("enableOptimization : ${task.enableOptimization}")
println("enableAssertions : ${task.enableAssertions}")
println("target : ${task.target}")
println("languageVersion : ${task.languageVersion}")
println("apiVersion : ${task.apiVersion}")
println("konanVersion : ${task.konanVersion}")
println("konanHome : ${task.konanHome}")
println(" : ${libraries.namedKlibs.dump()}")
println("nativeLibraries : ${nativeLibraries.dump()}")
println("linkerOpts : ${linkerOpts}")
println("enableDebug : ${enableDebug}")
println("noStdLib : ${noStdLib}")
println("noMain : ${noMain}")
println("enableOptimization : ${enableOptimizations}")
println("enableAssertions : ${enableAssertions}")
println("noDefaultLibs : ${noDefaultLibs}")
println("target : ${target}")
println("languageVersion : ${languageVersion}")
println("apiVersion : ${apiVersion}")
println("konanVersion : ${konanVersion}")
println("konanHome : ${konanHome}")
println()
}
is KonanInteropTask -> {
is KonanInteropTask -> with(task) {
println()
println("Stub generation task: ${task.name}")
println("outputDir : ${task.outputDir}")
println("klib : ${task.klib}")
println("defFile : ${task.defFile}")
println("target : ${task.target}")
println("pkg : ${task.pkg}")
println("compilerOpts : ${task.compilerOpts}")
println("linkerOpts : ${task.linkerOpts}")
println("headers : ${task.headers.dump()}")
println("linkFiles : ${task.linkFiles.dump()}")
println("konanVersion : ${task.konanVersion}")
println("konanHome : ${task.konanHome}")
println("Stub generation task: ${name}")
println("outputDir : ${outputDir}")
println("artifact : ${artifact}")
println("libraries : ${libraries.files.dump()}")
println(" : ${libraries.artifacts.map {
it.artifact.canonicalPath
}.dump()}")
println(" : ${libraries.namedKlibs.dump()}")
println("defFile : ${defFile}")
println("target : ${target}")
println("pkg : ${pkg}")
println("compilerOpts : ${compilerOpts}")
println("linkerOpts : ${linkerOpts}")
println("headers : ${headers.dump()}")
println("linkFiles : ${linkFiles.dump()}")
println("konanVersion : ${konanVersion}")
println("konanHome : ${konanHome}")
println()
}
else -> {
@@ -204,7 +210,7 @@ internal fun dumpProperties(task: Task) {
}
class KonanPlugin @Inject constructor(private val registry: ToolingModelBuilderRegistry)
: Plugin<Project> {
: Plugin<ProjectInternal> {
enum class ProjectProperty(val propertyName: String) {
KONAN_HOME ("konan.home"),
@@ -215,8 +221,10 @@ class KonanPlugin @Inject constructor(private val registry: ToolingModelBuilderR
companion object {
internal const val ARTIFACTS_CONTAINER_NAME = "konanArtifacts"
internal const val INTEROP_CONTAINER_NAME = "konanInterop"
internal const val KONAN_DOWNLOAD_TASK_NAME = "checkKonanCompiler"
internal const val COMPILE_ALL_TASK_NAME = "compileKonan"
internal const val TARGETS_EXTENSION_NAME = "konanTargets"
internal val DEFAULT_KONAN_VERSION = Properties().apply {
load(KonanPlugin::class.java.getResourceAsStream("/META-INF/gradle-plugins/konan.properties") ?:
@@ -224,46 +232,29 @@ class KonanPlugin @Inject constructor(private val registry: ToolingModelBuilderR
}.getProperty("default-konan-version") ?: throw RuntimeException("Cannot read the default compiler version")
}
/** Looks for task with given name in the given project. Throws [UnknownTaskException] if there's not such task. */
private fun Project.getTask(name: String): Task = tasks.getByPath(name)
/**
* 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 Project.getOrCreateTask(name: String): Task = with(tasks) {
findByPath(name) ?: create(name, DefaultTask::class.java)
private fun Project.cleanKonan() = project.tasks.withType(KonanBuildingTask::class.java).forEach {
project.delete(it.artifact)
}
private fun Project.cleanKonan() = project.konanArtifactsContainer.forEach {
project.delete(it.compilationTask.outputDir)
}
override fun apply(project: Project?) {
override fun apply(project: ProjectInternal?) {
if (project == null) { return }
registry.register(KonanToolingModelBuilder)
project.plugins.apply("base")
// Create necessary tasks and extensions.
project.tasks.create(KONAN_DOWNLOAD_TASK_NAME, KonanCompilerDownloadTask::class.java)
project.extensions.add(ARTIFACTS_CONTAINER_NAME,
project.container(KonanCompileConfig::class.java, KonanCompileConfigFactory(project)))
project.extensions.add(INTEROP_CONTAINER_NAME,
project.container(KonanInteropConfig::class.java, KonanInteropConfigFactory(project)))
project.extensions.extraProperties.set(TARGETS_EXTENSION_NAME, mutableListOf("host"))
project.extensions.create(ARTIFACTS_CONTAINER_NAME, KonanArtifactContainer::class.java, project)
// Set additional project properties like konan.home, konan.build.targets etc.
if (!project.hasProperty(ProjectProperty.KONAN_HOME)) {
project.setProperty(ProjectProperty.KONAN_HOME, project.konanCompilerDownloadDir())
project.setProperty(ProjectProperty.DOWNLOAD_COMPILER, true)
}
if (!project.hasProperty(ProjectProperty.KONAN_BUILD_TARGETS)) {
project.setProperty(ProjectProperty.KONAN_BUILD_TARGETS, project.host)
}
// Create and set up aggregate building tasks.
val compileKonanTask = project.getOrCreateTask("compileKonan").apply {
dependsOn(project.supportedCompileTasks)
val compileKonanTask = project.getOrCreateTask(COMPILE_ALL_TASK_NAME).apply {
group = BasePlugin.BUILD_GROUP
description = "Compiles all the Kotlin/Native artifacts supported"
description = "Compiles all the Kotlin/Native artifacts"
}
project.getTask("build").apply {
dependsOn(compileKonanTask)
@@ -276,14 +267,14 @@ class KonanPlugin @Inject constructor(private val registry: ToolingModelBuilderR
project.getOrCreateTask("run").apply {
dependsOn(project.getTask("build"))
doLast {
for (task in project.tasks.withType(KonanCompileTask::class.java).matching { !it.isCrossCompile}) {
if (task?.produce == "program") {
project.exec {
with(it) {
commandLine(task.artifactPath)
if (project.extensions.extraProperties.has("runArgs")) {
args(project.extensions.extraProperties.get("runArgs").toString().split(' '))
}
for (task in project.tasks
.withType(KonanCompileProgramTask::class.java)
.matching { !it.isCrossCompile}) {
project.exec {
with(it) {
commandLine(task.artifact.canonicalPath)
if (project.extensions.extraProperties.has("runArgs")) {
args(project.extensions.extraProperties.get("runArgs").toString().split(' '))
}
}
}
@@ -0,0 +1,75 @@
package org.jetbrains.kotlin.gradle.plugin
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.file.FileCollection
interface KonanArtifactSpec {
fun outputName(name: String)
fun baseDir(dir: Any)
}
interface KonanArtifactWithLibrariesSpec: KonanArtifactSpec {
fun libraries(closure: Closure<Unit>)
fun libraries(action: Action<KonanLibrariesSpec>)
fun libraries(configure: KonanLibrariesSpec.() -> Unit)
fun noDefaultLibs(flag: Boolean)
}
interface KonanBuildingSpec: KonanArtifactWithLibrariesSpec {
fun dumpParameters(flag: Boolean)
fun extraOpts(vararg values: Any)
fun extraOpts(values: List<Any>)
}
interface KonanCompileSpec: KonanBuildingSpec {
fun inputDir(dir: Any)
fun inputFiles(vararg files: Any)
fun inputFiles(files: Collection<Any>)
// DSL. Native libraries.
fun nativeLibrary(lib: Any)
fun nativeLibraries(vararg libs: Any)
fun nativeLibraries(libs: FileCollection)
// DSL. Other parameters.
fun linkerOpts(args: List<String>)
fun linkerOpts(vararg args: String)
fun languageVersion(version: String)
fun apiVersion(version: String)
fun enableDebug(flag: Boolean)
fun noStdLib(flag: Boolean)
fun noMain(flag: Boolean)
fun enableOptimizations(flag: Boolean)
fun enableAssertions(flag: Boolean)
fun measureTime(flag: Boolean)
}
interface KonanInteropSpec: KonanBuildingSpec {
fun defFile(file: Any)
fun pkg(value: String)
fun compilerOpts(vararg values: String)
fun header(file: Any) = headers(file)
fun headers(vararg files: Any)
fun headers(files: FileCollection)
fun includeDirs(vararg values: Any)
fun linkerOpts(vararg values: String) = linkerOpts(values.toList())
fun linkerOpts(values: List<String>)
fun link(vararg files: Any)
fun link(files: FileCollection)
}
@@ -3,6 +3,7 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Named
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.gradle.plugin.tasks.host
internal interface KonanToolRunner: Named {
val mainClass: String
@@ -25,9 +25,9 @@ object KonanToolingModelBuilder : ToolingModelBuilder {
override fun canBuild(modelName: String): Boolean = KonanModel::class.java.name == modelName
override fun buildAll(modelName: String, project: Project): KonanModel {
val artifacts = project.supportedCompileTasks
val artifacts = project.konanArtifactsContainer.flatten()
.toList()
.map { KonanArtifactImpl(it.artifactName, it.artifactPath) }
.map { KonanArtifactImpl("${it.artifact.name}-${it.target.userName}", it.artifact.canonicalPath) }
return KonanModelImpl(project.konanVersion, artifacts)
}
}
@@ -14,37 +14,100 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin
package org.jetbrains.kotlin.gradle.plugin.tasks
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.konan.target.*
import org.jetbrains.kotlin.konan.target.KonanTarget.*
abstract class KonanTargetableTask: DefaultTask() {
@Optional @Input var target : String? = null
internal set
internal val targetManager: TargetManager by lazy {
TargetManager(target ?: "host")
} @Internal get
val targetIsSupported: Boolean
@Internal get() = targetManager.target.enabled
val isCrossCompile: Boolean
@Internal get() = (targetManager.target != TargetManager.host)
@Internal fun produceSuffix(produce: String): String
= CompilerOutputKind.valueOf(produce.toUpperCase())
.suffix(targetManager.target)
}
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputFile
import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.plugin.KonanArtifactSpec
import org.jetbrains.kotlin.gradle.plugin.KonanArtifactWithLibrariesSpec
import org.jetbrains.kotlin.gradle.plugin.KonanLibrariesSpec
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.TargetManager
import java.io.File
internal val Project.host
get() = TargetManager.host.name.toLowerCase()
internal val Project.simpleOsName
get() = TargetManager.simpleOsName()
/** A task with a KonanTarget specified. */
abstract class KonanTargetableTask: DefaultTask() {
@Input lateinit var target: KonanTarget
internal set
internal open fun init(target: KonanTarget) {
this.target = target
}
val targetIsSupported: Boolean
@Internal get() = target.enabled
val isCrossCompile: Boolean
@Internal get() = (target != TargetManager.host)
}
/** A task building an artifact. */
abstract class KonanArtifactTask: KonanTargetableTask(), KonanArtifactSpec {
val artifact: File
@OutputFile get() = outputDir.resolve(artifactNameWithSuffix)
@Internal lateinit var baseDir: File
@Internal lateinit var outputName: String
val outputDir
@Internal get() = baseDir.resolve(target.userName)
protected val artifactNameWithSuffix: String
@Internal get() = "$outputName$artifactSuffix"
protected val artifactPath: String
@Internal get() = artifact.canonicalPath
protected abstract val artifactSuffix: String
@Internal get
internal open fun init(baseDir: File, outputName: String, target: KonanTarget) {
super.init(target)
this.baseDir = baseDir
this.outputName = outputName
}
// DSL.
override fun outputName(name: String) {
outputName = name
}
override fun baseDir(dir: Any) {
baseDir = project.file(dir)
}
}
/** Task building an artifact with libraries */
abstract class KonanArtifactWithLibrariesTask: KonanArtifactTask(), KonanArtifactWithLibrariesSpec {
@Nested
val libraries = KonanLibrariesSpec(this, project)
@Input
var noDefaultLibs = false
// DSL
override fun libraries(closure: Closure<Unit>) = libraries(ConfigureUtil.configureUsing(closure))
override fun libraries(action: Action<KonanLibrariesSpec>) = libraries { action.execute(this) }
override fun libraries(configure: KonanLibrariesSpec.() -> Unit) { libraries.configure() }
override fun noDefaultLibs(flag: Boolean) {
noDefaultLibs = flag
}
}
@@ -1,66 +1,49 @@
package org.jetbrains.kotlin.gradle.plugin
package org.jetbrains.kotlin.gradle.plugin.tasks
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.Named
import org.gradle.api.Project
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputFile
import org.gradle.util.ConfigureUtil
import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
abstract class KonanBuildingTask: KonanTargetableTask() {
abstract val artifactPath: String
@Internal get
abstract val artifact: File
@OutputFile get
abstract val outputDir: File
@Internal get
/** Base class for both interop and compiler tasks. */
abstract class KonanBuildingTask: KonanArtifactWithLibrariesTask(), KonanBuildingSpec {
abstract val isLibrary: Boolean
@Internal get
internal abstract val toolRunner: KonanToolRunner
override fun init(baseDir: File, outputName: String, target: KonanTarget) {
dependsOn(project.konanCompilerDownloadTask)
super.init(baseDir, outputName, target)
}
@Input
var dumpParameters: Boolean = false
@Input
val extraOpts = mutableListOf<String>()
val konanVersion
@Input get() = project.konanVersion
val konanHome
@Input get() = project.konanHome
@Nested
val libraries = KonanLibrariesSpec(project)
protected abstract fun buildArgs(): List<String>
@TaskAction
fun run() {
outputDir.mkdirs()
if (dumpParameters) { dumpProperties(this) }
toolRunner.run(buildArgs())
}
// DSL.
override fun dumpParameters(flag: Boolean) {
dumpParameters = flag
}
override fun extraOpts(vararg values: Any) = extraOpts(values.toList())
override fun extraOpts(values: List<Any>) {
extraOpts.addAll(values.map { it.toString() })
}
}
// TODO: Implement the target properties here.
abstract class KonanBuildingConfig(val configName: String,
val project: Project): Named {
override fun getName(): String = configName
fun dumpParameters(value: Boolean) {
task.dumpParameters = value
}
// TODO: Replace with target enum
fun target(target: String) {
task.target = target
}
// TODO: Replace with a collection for target support
abstract internal val task: KonanBuildingTask
fun dependsOn(dependency: Any) = task.dependsOn(dependency)
fun libraries(closure: Closure<Unit>) = libraries(ConfigureUtil.configureUsing(closure))
fun libraries(action: Action<KonanLibrariesSpec>) = libraries { action.execute(this) }
fun libraries(configure: KonanLibrariesSpec.() -> Unit) {
task.libraries.configure()
// TODO: May be rework.
task.libraries.artifacts.forEach {
dependsOn(it.task)
}
}
}
@@ -14,48 +14,34 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin
package org.jetbrains.kotlin.gradle.plugin.tasks
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.Named
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.*
import org.gradle.util.ConfigureUtil
import java.io.File
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
enum class Produce(val cliOption: String, val kind: CompilerOutputKind) {
PROGRAM("program", CompilerOutputKind.PROGRAM),
LIBRARY("library", CompilerOutputKind.LIBRARY),
BITCODE("bitcode", CompilerOutputKind.BITCODE)
}
/**
* A task compiling the target executable/library using Kotlin/Native compiler
*/
open class KonanCompileTask: KonanBuildingTask() {
abstract class KonanCompileTask: KonanBuildingTask(), KonanCompileSpec {
// TODO: Support custom runner options (java options)
@Internal override val toolRunner = KonanCompilerRunner(project)
abstract val produce: Produce
@Internal get
// Output artifact --------------------------------------------------------
internal lateinit var artifactName: String
@Internal get
@Internal override lateinit var outputDir: File
internal set
internal fun init(artifactName: String) {
dependsOn(project.konanCompilerDownloadTask)
this.artifactName = artifactName
outputDir = project.file(project.konanCompilerOutputDir)
}
protected val artifactNamePath: String
@Internal get() = "${outputDir.absolutePath}/$artifactName"
protected val artifactSuffix: String
@Internal get() = produceSuffix(produce)
override val artifactPath: String
@Internal get() = "$artifactNamePath$artifactSuffix"
override val artifact: File
@OutputFile get() = project.file(artifactPath)
override val artifactSuffix: String
@Internal get() = produce.kind.suffix(target)
// Other compilation parameters -------------------------------------------
@@ -65,228 +51,130 @@ open class KonanCompileTask: KonanBuildingTask() {
@InputFiles val nativeLibraries = mutableSetOf<FileCollection>()
@Input var produce = "program"
internal set
@Input val linkerOpts = mutableListOf<String>()
// TODO: Replace produce string with an enum.
override val isLibrary: Boolean
@Internal get() = produce == "library"
@Input var enableDebug =
project.properties.containsKey("enableDebug") &&
project.properties["enableDebug"].toString().toBoolean()
@Input val extraOpts = mutableListOf<String>()
internal var _linkerOpts = mutableListOf<String>()
val linkerOpts: List<String>
@Input get() = _linkerOpts // TODO: use the original linkerOpts prop.
@Input var enableDebug = project.properties.containsKey("enableDebug") && project.properties["enableDebug"].toString().toBoolean()
internal set
@Input var noStdLib = false
internal set
@Input var noMain = false
internal set
@Input var enableOptimization = false
internal set
@Input var enableAssertions = false
internal set
@Input var noDefaultLibs = false
internal set
@Console var measureTime = false
internal set
@Input var noStdLib = false
@Input var noMain = false
@Input var enableOptimizations = false
@Input var enableAssertions = false
@Console var measureTime = false
@Optional @Input var languageVersion : String? = null
internal set
@Optional @Input var apiVersion : String? = null
internal set
// Task action ------------------------------------------------------------
// Command line ------------------------------------------------------------
protected fun buildArgs() = mutableListOf<String>().apply {
addArg("-output", artifactNamePath)
override fun buildArgs() = mutableListOf<String>().apply {
addArg("-output", artifact.canonicalPath)
// TODO: remove this repo
addArg("-repo", outputDir.canonicalPath)
addArgs("-repo", libraries.repos.map { it.canonicalPath })
addFileArgs("-library", libraries.files)
addArgs("-library", libraries.namedKlibs)
addArgs("-library", libraries.artifacts.map { it.task.artifact.canonicalPath })
addArgs("-repo", libraries.repos.map { it.canonicalPath })
addArgs("-library", libraries.artifacts.map { it.artifact.canonicalPath })
addFileArgs("-nativelibrary", nativeLibraries)
addArg("-produce", produce)
addArg("-produce", produce.cliOption)
addListArg("-linkerOpts", linkerOpts)
addArgIfNotNull("-target", target)
addArgIfNotNull("-target", target.userName)
addArgIfNotNull("-language-version", languageVersion)
addArgIfNotNull("-api-version", apiVersion)
addKey("-g", enableDebug)
addKey("-nostdlib", noStdLib)
addKey("-nomain", noMain)
addKey("-opt", enableOptimization)
addKey("-opt", enableOptimizations)
addKey("-ea", enableAssertions)
addKey("-nodefaultlibs", noDefaultLibs)
addKey("--time", measureTime)
addKey("-nodefaultlibs", noDefaultLibs)
addAll(extraOpts)
inputFiles.flatMap { it.files }.filter { it.name.endsWith(".kt") }.mapTo(this) { it.canonicalPath }
}
@TaskAction
fun compile() {
outputDir.mkdirs()
if (dumpParameters) dumpProperties(this@KonanCompileTask)
// TODO: Use compiler service.
KonanCompilerRunner(project).run(buildArgs())
}
}
// TODO: Use +=/-= syntax for libraries and inputFiles
open class KonanCompileConfig(
configName: String,
project: Project,
taskNamePrefix: String = "compileKonan"): KonanBuildingConfig(configName, project) {
override fun getName() = configName
val compilationTask: KonanCompileTask = project.tasks.create(
"$taskNamePrefix${configName.capitalize()}",
KonanCompileTask::class.java) {
it.init(this@KonanCompileConfig.name)
it.group = BasePlugin.BUILD_GROUP
it.description = "Compiles the Kotlin/Native artifact '${this@KonanCompileConfig.name}'"
}
override val task: KonanCompileTask
get() = compilationTask
private fun evaluationDependsOn(anotherProject: Project) {
if (anotherProject != project) {
project.evaluationDependsOn(anotherProject.path)
}
}
// DSL methods. --------------------------------------------------
@Deprecated("Use `libraries` block instead")
fun useInterop(interop: KonanInteropConfig) = libraries { interop(interop) }
@Deprecated("Use `libraries` block instead")
fun useInterop(interop: String) = libraries { interop(interop) }
@Deprecated("Use `libraries` block instead")
fun useInterops(interops: Collection<Any>) {
interops.forEach {
when(it) {
is String -> useInterop(it)
is KonanInteropConfig -> useInterop(it)
else -> throw IllegalArgumentException("Cannot convert the object to an interop description: $it")
}
}
}
// region DSL.
// DSL. Input/output files.
fun inputDir(dir: Any) = with(compilationTask) {
override fun inputDir(dir: Any) {
_inputFiles.add(project.fileTree(dir).apply {
include("**/*.kt")
exclude { it.file.startsWith(project.buildDir) }
})
}
fun inputFiles(vararg files: Any) = with(compilationTask) {
override fun inputFiles(vararg files: Any) {
_inputFiles.add(project.files(files))
}
fun inputFiles(files: FileCollection) = compilationTask._inputFiles.add(files)
fun inputFiles(files: Collection<FileCollection>) = compilationTask._inputFiles.addAll(files)
// TODO: Remove this functional.
fun outputDir(dir: Any) = with(compilationTask) {
outputDir = project.file(dir)
}
fun outputName(name: String) = with(compilationTask) {
artifactName = name
}
// DSL. Libraries.
@Deprecated("Use `libraries` block instead")
fun library(project: Project) = libraries { allArtifactsFrom(project) }
@Deprecated("Use `libraries` block instead")
fun library(project: Project, artifactName: String) = libraries { this.artifact(project, artifactName) }
@Deprecated("Use `libraries` block instead")
fun library(lib: Any) = libraries { file(lib) }
@Deprecated("Use `libraries` block instead")
fun libraries(vararg libs: Any) = libraries { files(*libs) }
@Deprecated("Use `libraries` block instead")
fun libraries(libs: FileCollection) = libraries { files(libs) }
override fun inputFiles(files: Collection<Any>) = inputFiles(*files.toTypedArray())
// DSL. Native libraries.
fun nativeLibrary(lib: Any) = nativeLibraries(lib)
fun nativeLibraries(vararg libs: Any) = with(compilationTask) {
override fun nativeLibrary(lib: Any) = nativeLibraries(lib)
override fun nativeLibraries(vararg libs: Any) {
nativeLibraries.add(project.files(*libs))
}
fun nativeLibraries(libs: FileCollection) = with(compilationTask) {
override fun nativeLibraries(libs: FileCollection) {
nativeLibraries.add(libs)
}
// DSL. Other parameters.
fun linkerOpts(args: List<String>) = linkerOpts(*args.toTypedArray())
fun linkerOpts(vararg args: String) = with(compilationTask) {
_linkerOpts.addAll(args)
override fun linkerOpts(args: List<String>) = linkerOpts(*args.toTypedArray())
override fun linkerOpts(vararg args: String) {
linkerOpts.addAll(args)
}
fun languageVersion(version: String) = with(compilationTask) {
override fun languageVersion(version: String) {
languageVersion = version
}
fun apiVersion(version: String) = with(compilationTask) {
override fun apiVersion(version: String) {
apiVersion = version
}
fun enableDebug(flag: Boolean) = with(compilationTask) {
override fun enableDebug(flag: Boolean) {
enableDebug = flag
}
fun noStdLib() = with(compilationTask) {
noStdLib = true
override fun noStdLib(flag: Boolean) {
noStdLib = flag
}
fun produce(prod: String) = with(compilationTask) {
produce = prod
override fun noMain(flag: Boolean) {
noMain = flag
}
fun noMain() = with(compilationTask) {
noMain = true
override fun enableOptimizations(flag: Boolean) {
enableOptimizations = true
}
fun enableOptimization() = with(compilationTask) {
enableOptimization = true
override fun enableAssertions(flag: Boolean) {
enableAssertions = flag
}
fun enableAssertions() = with(compilationTask) {
enableAssertions = true
}
fun noDefaultLibs(flag: Boolean) = with(compilationTask) {
noDefaultLibs = flag
}
fun measureTime(value: Boolean) = with(compilationTask) {
measureTime = value
}
fun extraOpts(vararg values: Any) = extraOpts(values.asList())
fun extraOpts(values: List<Any>) {
values.mapTo(compilationTask.extraOpts) { it.toString() }
override fun measureTime(flag: Boolean) {
measureTime = flag
}
// endregion
}
open class KonanCompileProgramTask: KonanCompileTask() {
override val produce: Produce get() = Produce.PROGRAM
}
open class KonanCompileLibraryTask: KonanCompileTask() {
override val produce: Produce get() = Produce.LIBRARY
}
open class KonanCompileBitcodeTask: KonanCompileTask() {
override val produce: Produce get() = Produce.BITCODE
}
@@ -14,11 +14,13 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin
package org.jetbrains.kotlin.gradle.plugin.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.GradleScriptException
import org.gradle.api.tasks.*
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.konan.util.DependencyProcessor
import java.io.File
import java.io.IOException
@@ -54,7 +56,7 @@ open class KonanCompilerDownloadTask : DefaultTask() {
} else {
try {
val konanCompiler = project.konanCompilerName()
logger.info("Downloading Kotlin/Native compiler from $DOWNLOAD_URL/$konanCompiler into $KONAN_PARENT_DIR")
logger.info("Downloading Kotlin/Native compiler from ${DOWNLOAD_URL}/$konanCompiler into ${KONAN_PARENT_DIR}")
DependencyProcessor(File(KONAN_PARENT_DIR), DOWNLOAD_URL, listOf(konanCompiler)).run()
} catch (e: IOException) {
throw GradleScriptException("Cannot download Kotlin/Native compiler", e)
@@ -14,88 +14,54 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin
package org.jetbrains.kotlin.gradle.plugin.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.Named
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
/**
* A task executing cinterop tool with the given args and compiling the stubs produced by this tool.
*/
open class KonanInteropTask: KonanBuildingTask() {
open class KonanInteropTask: KonanBuildingTask(), KonanInteropSpec {
internal fun init(libName: String) {
dependsOn(project.konanCompilerDownloadTask)
this.libName = libName
this.defFile = project.konanDefaultDefFile(libName)
this.klib = outputDir.resolve("$libName.klib")
@Internal override val toolRunner: KonanToolRunner = KonanInteropRunner(project)
override fun init(baseDir: File, outputName: String, target: KonanTarget) {
super.init(baseDir, outputName, target)
this.defFile = project.konanDefaultDefFile(outputName)
}
// Output directories -----------------------------------------------------
@Internal override val outputDir = project.file(project.konanInteropOutputDir)
// TODO: Mark as deprecated
lateinit var klib: File
@Internal get
internal set
override val artifact: File
@OutputFile get() = klib
// TODO Annotations are not inherited
override val artifactPath: String
@Internal get() = artifact.canonicalPath
override val isLibrary: Boolean
@Internal get() = true
override val artifactSuffix: String
@Internal get() = ".klib"
// Interop stub generator parameters -------------------------------------
@InputFile lateinit var defFile: File
internal set
@Optional @Input var pkg: String? = null
internal set
@Input lateinit var libName: String
internal set
@Internal internal val klibDependencies_ = mutableListOf<KonanInteropConfig>()
@Input val compilerOpts = mutableListOf<String>()
@Input val linkerOpts = mutableListOf<String>()
@Input val extraOpts = mutableListOf<String>()
// TODO: Check if we can use only one FileCollection instead of set.
@InputFiles val headers = mutableSetOf<FileCollection>()
@InputFiles val linkFiles = mutableSetOf<FileCollection>()
@TaskAction
fun exec() {
outputDir.mkdirs()
if (dumpParameters) dumpProperties(this@KonanInteropTask)
KonanInteropRunner(project).run(buildArgs())
}
protected fun buildArgs() = mutableListOf<String>().apply {
override fun buildArgs() = mutableListOf<String>().apply {
addArg("-properties", "${project.konanHome}/konan/konan.properties")
addArg("-o", klib.canonicalPath)
addArg("-o", artifact.canonicalPath)
addArgIfNotNull("-target", target)
addArgIfNotNull("-target", target.userName)
addArgIfNotNull("-def", defFile.canonicalPath)
addArgIfNotNull("-pkg", pkg)
addFileArgs("-h", headers)
addKey("-nodefaultlibs", noDefaultLibs)
compilerOpts.forEach {
addArg("-copt", it)
}
@@ -108,105 +74,55 @@ open class KonanInteropTask: KonanBuildingTask() {
addArg("-lopt", it)
}
addArgs("-repo", libraries.repos.map { it.canonicalPath })
addFileArgs("-library", libraries.files)
addArgs("-library", libraries.namedKlibs)
addArgs("-library", libraries.artifacts.map { it.task.artifact.canonicalPath })
addArgs("-repo", libraries.repos.map { it.canonicalPath })
addArgs("-library", libraries.artifacts.map { it.artifact.canonicalPath })
addKey("-nodefaultlibs", noDefaultLibs)
addAll(extraOpts)
}
}
open class KonanInteropConfig(
configName: String,
project: Project
): KonanBuildingConfig(configName, project) {
// region DSL.
override fun getName() = configName
// Child tasks ------------------------------------------------------------
// TODO: Remove dummy tasks and properties in 0.5
val generateStubsTask: KonanInteropTask
get() = throw NotImplementedError("This property is not supported now. Use interopProcessingTask instead.")
val compileStubsTask: KonanCompileTask
get() = throw NotImplementedError("This property is not supported now. Use interopProcessingTask instead.")
val compileStubsConfig: KonanCompileConfig
get() = throw NotImplementedError("This property is not supported now.")
open class DummyTask: DefaultTask() {
var replacementName: String = ""
@TaskAction
fun doAction(): Unit = throw NotImplementedError("This task is not supported now. Use $replacementName instead.")
}
// Task to process the library and generate stubs
val interopProcessingTask: KonanInteropTask = project.tasks.create(
"process${name.capitalize()}Interop",
KonanInteropTask::class.java) {
it.init(name)
it.group = BasePlugin.BUILD_GROUP
it.description = "Generates a klib for the Kotlin/Native interop '$name'"
}
// TODO: Rename tasks
override val task: KonanInteropTask
get() = interopProcessingTask
init {
val dummyGenerateStubsTask = project.tasks.create(
"gen${name.capitalize()}InteropStubs",
DummyTask::class.java) { it.replacementName = interopProcessingTask.name }
val dummyCompileStubsTask = project.tasks.create(
"compile${name.capitalize()}InteropStubs",
DummyTask::class.java) { it.replacementName = interopProcessingTask.name }
}
// DSL methods ------------------------------------------------------------
fun defFile(file: Any) = with(interopProcessingTask) {
override fun defFile(file: Any) {
defFile = project.file(file)
}
fun pkg(value: String) = with(interopProcessingTask) {
override fun pkg(value: String) {
pkg = value
}
fun compilerOpts(vararg values: String) = with(interopProcessingTask) {
override fun compilerOpts(vararg values: String) {
compilerOpts.addAll(values)
}
fun header(file: Any) = headers(file)
fun headers(vararg files: Any) = with(interopProcessingTask) {
override fun header(file: Any) = headers(file)
override fun headers(vararg files: Any) {
headers.add(project.files(files))
}
fun headers(files: FileCollection) = with(interopProcessingTask) {
override fun headers(files: FileCollection) {
headers.add(files)
}
fun includeDirs(vararg values: Any) = with(interopProcessingTask) {
override fun includeDirs(vararg values: Any) {
compilerOpts.addAll(values.map { "-I${project.file(it).canonicalPath}" })
}
fun linkerOpts(vararg values: String) = linkerOpts(values.toList())
fun linkerOpts(values: List<String>) = with(interopProcessingTask) {
override fun linkerOpts(vararg values: String) = linkerOpts(values.toList())
override fun linkerOpts(values: List<String>) {
linkerOpts.addAll(values)
}
fun link(vararg files: Any) = with(interopProcessingTask) {
override fun link(vararg files: Any) {
linkFiles.add(project.files(files))
}
fun link(files: FileCollection) = with(interopProcessingTask) {
override fun link(files: FileCollection) {
linkFiles.add(files)
}
fun dependsOn(dependency: Any) = interopProcessingTask.dependsOn(dependency)
// Other.
fun extraOpts(vararg values: Any) = extraOpts(values.asList())
fun extraOpts(values: List<Any>) {
values.mapTo(interopProcessingTask.extraOpts) { it.toString() }
}
// endregion
}
@@ -1,24 +1,25 @@
package org.jetbrains.kotlin.gradle.plugin.test
import org.gradle.testkit.runner.TaskOutcome
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
class DefaultSpecification extends BaseKonanSpecification {
def 'Plugin should build a project without additional settings'() {
when:
def project = KonanInteropProject.create(projectDirectory) { KonanInteropProject it ->
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.buildFile.write("""
plugins { id 'konan' }
konanInterop { stdio {} }
konanArtifacts { main {} }
konanArtifacts {
interop('stdio')
program('main')
}
""".stripIndent())
it.generateDefFile("stdio.def", "")
it.generateSrcFile("main.kt")
}
def result = project.createRunner().withArguments('build').build()
then:
!result.tasks.collect { it.outcome }.contains(TaskOutcome.FAILED)
}
@@ -2,15 +2,12 @@ package org.jetbrains.kotlin.gradle.plugin.test
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.IgnoreIf
import spock.lang.Specification
import spock.lang.Unroll
class IncrementalSpecification extends BaseKonanSpecification {
Tuple buildTwice(KonanInteropProject project, Closure change) {
Tuple buildTwice(KonanProject project, Closure change) {
def runner = project.createRunner().withArguments('build')
def firstResult = runner.build()
change(project)
@@ -19,16 +16,10 @@ class IncrementalSpecification extends BaseKonanSpecification {
}
Tuple buildTwice(Closure change) {
return buildTwice(KonanInteropProject.create(projectDirectory), change)
return buildTwice(KonanProject.createWithInterop(projectDirectory), change)
}
Tuple buildTwiceEmpty(Closure change) {
def project = KonanInteropProject.createEmpty(projectDirectory)
project.generateSrcFile("main.kt")
return buildTwice(project, change)
}
Boolean noRecompilationHappened(KonanInteropProject project, BuildResult firstResult, BuildResult secondResult) {
Boolean noRecompilationHappened(KonanProject project, BuildResult firstResult, BuildResult secondResult) {
return project.with {
firstResult.tasks.collect { it.path }.containsAll(buildingTasks) &&
firstResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks) &&
@@ -38,7 +29,7 @@ class IncrementalSpecification extends BaseKonanSpecification {
}
}
Boolean onlyRecompilationHappened(KonanInteropProject project, BuildResult firstResult, BuildResult secondResult) {
Boolean onlyRecompilationHappened(KonanProject project, BuildResult firstResult, BuildResult secondResult) {
return project.with {
firstResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks) &&
secondResult.taskPaths(TaskOutcome.SUCCESS).containsAll(compilationTasks) &&
@@ -46,7 +37,7 @@ class IncrementalSpecification extends BaseKonanSpecification {
}
}
Boolean recompilationAndInteropProcessingHappened(KonanInteropProject project, BuildResult firstResult, BuildResult secondResult) {
Boolean recompilationAndInteropProcessingHappened(KonanProject project, BuildResult firstResult, BuildResult secondResult) {
return project.with {
firstResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks) &&
secondResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks)
@@ -64,7 +55,7 @@ class IncrementalSpecification extends BaseKonanSpecification {
def 'Source change should cause only recompilation'() {
when:
def results = buildTwice { KonanInteropProject project ->
def results = buildTwice { KonanProject project ->
project.srcFiles[0].append("\n // Some change in the source file")
}
@@ -75,7 +66,7 @@ class IncrementalSpecification extends BaseKonanSpecification {
def 'Def-file change should cause recompilation and interop reprocessing'() {
when:
def results = buildTwice { KonanInteropProject project ->
def results = buildTwice { KonanProject project ->
project.defFiles[0].append("\n # Some change in the def-file")
}
@@ -85,7 +76,7 @@ class IncrementalSpecification extends BaseKonanSpecification {
def 'Compilation is up-to-date if there is no changes in empty project'() {
when:
def results = buildTwiceEmpty {}
def results = buildTwice {}
then:
noRecompilationHappened(*results)
@@ -94,8 +85,8 @@ class IncrementalSpecification extends BaseKonanSpecification {
@Unroll("#parameter change for a compilation task should cause only recompilation")
def 'Parameter changes should cause only recompilaton'() {
when:
def results = buildTwiceEmpty { KonanInteropProject project ->
project.addCompilationSetting("main", parameter, value)
def results = buildTwice { KonanProject project ->
project.addSetting("main", parameter, value)
}
then:
@@ -103,83 +94,81 @@ class IncrementalSpecification extends BaseKonanSpecification {
where:
parameter | value
"outputDir" | "'build/new/outputDir'"
"produce" | "'library'"
"enableOptimization" | "()"
"linkerOpts" | "'--help'"
"languageVersion" | "'1.2'"
"apiVersion" | "'1.0'"
"enableAssertions" | "()"
"enableDebug" | "true"
"outputName" | "'foo'"
"extraOpts" | "'--time'"
parameter | value
"baseDir" | "'build/new/outputDir'"
"enableOptimizations" | "true"
"linkerOpts" | "'--help'"
"languageVersion" | "'1.2'"
"apiVersion" | "'1.0'"
"enableAssertions" | "true"
"enableDebug" | "true"
"outputName" | "'foo'"
"extraOpts" | "'--time'"
"noDefaultLibs" | "true"
}
def 'inputFiles change for a compilation task should cause only recompilation'() {
when:
def project = KonanInteropProject.createEmpty(projectDirectory) { KonanInteropProject it ->
it.generateSrcFile('main.kt')
def project = KonanProject.createWithInterop(projectDirectory) { KonanProject it ->
it.generateSrcFile(["src", "foo", "kotlin"], 'bar.kt', """
fun main(args: Array<String>) { println("Hello!") }
""".stripIndent())
}
def results = buildTwice(project) { KonanInteropProject it ->
it.addCompilationSetting("main", "inputFiles", "project.fileTree('src/foo/kotlin')")
def results = buildTwice(project) { KonanProject it ->
it.addSetting("main", "inputFiles", "project.fileTree('src/foo/kotlin')")
}
then:
onlyRecompilationHappened(*results)
}
@Unroll("#parameter change for a compilation task should cause only recompilation")
def 'Library changes should cause only recompilaton'() {
def 'Library change for a compilation task should cause only recompilation'() {
when:
def project = KonanInteropProject.createEmpty(projectDirectory) { KonanInteropProject it ->
it.generateSrcFile('main.kt')
def project = KonanProject.create(projectDirectory) { KonanProject it ->
it.generateSrcFile(["src", "lib", "kotlin"], "lib.kt", "fun bar() { println(\"Hello!\") }")
it.buildFile.append("""
konanArtifacts {
lib {
library('lib') {
inputFiles fileTree('src/lib/kotlin')
produce '$produce'
}
}
""".stripIndent())
}
def results = buildTwice(project) { KonanInteropProject it ->
it.addCompilationSetting("main", parameter, "konanArtifacts['lib'].compilationTask.artifactPath")
def results = buildTwice(project) { KonanProject it ->
it.addLibraryToArtifact("main", 'lib')
}
then:
onlyRecompilationHappened(*results)
where:
parameter | produce
"library" | "library"
"nativeLibrary" | "bitcode"
}
def 'useInterop change for a compilation task should cause only recompilation'() {
def 'Library changes should cause only recompilaton'() {
when:
def project = KonanInteropProject.createEmpty(projectDirectory) { KonanInteropProject it ->
it.generateSrcFile('main.kt')
it.buildFile.append("konanInterop { foo {} }\n")
it.generateDefFile("foo.def", "")
def project = KonanProject.createWithInterop(projectDirectory) { KonanProject it ->
it.generateSrcFile(["src", "lib", "kotlin"], "lib.kt", "fun bar() { println(\"Hello!\") }")
it.buildFile.append("""
konanArtifacts {
bitcode('lib') {
inputFiles fileTree('src/lib/kotlin')
}
}
""".stripIndent())
}
def results = buildTwice(project) { KonanInteropProject it ->
it.addCompilationSetting("main", "useInterop", "'foo'")
def results = buildTwice(project) { KonanProject it ->
it.addSetting("main", "nativeLibrary", "compileKonanLib${KonanProject.HOST.capitalize()}.artifact")
}
then:
onlyRecompilationHappened(*results)
}
// TODO: Test library for incremental compilation.
@Unroll("#parameter change for an interop task should cause recompilation and interop reprocessing")
def 'Parameter change for an interop task should cause recompilation and interop reprocessing'() {
when:
def results = buildTwiceEmpty { KonanInteropProject project ->
project.addInteropSetting("stdio", parameter, value)
def results = buildTwice { KonanProject project ->
project.addSetting("stdio", parameter, value)
}
then:
@@ -192,15 +181,15 @@ class IncrementalSpecification extends BaseKonanSpecification {
"linkerOpts" | "'--help'"
"includeDirs" | "'src'"
"extraOpts" | "'-shims', 'false'"
"noDefaultLibs" | "true"
}
def 'defFile change for an interop task should cause recompilation and interop reprocessing'() {
when:
def project = KonanInteropProject.createEmpty(projectDirectory)
project.generateSrcFile('main.kt')
def project = KonanProject.createWithInterop(projectDirectory)
def defFile = project.generateDefFile("foo.def", "#some content")
def results = buildTwice(project) { KonanInteropProject it ->
it.addInteropSetting("stdio", "defFile", defFile)
def results = buildTwice(project) { KonanProject it ->
it.addSetting("stdio", "defFile", defFile)
}
then:
@@ -209,11 +198,10 @@ class IncrementalSpecification extends BaseKonanSpecification {
def 'header change for an interop task should cause recompilation and interop reprocessing'() {
when:
def project = KonanInteropProject.createEmpty(projectDirectory)
project.generateSrcFile('main.kt')
def project = KonanProject.createWithInterop(projectDirectory)
def header = project.generateSrcFile('header.h', "#define CONST 1")
def results = buildTwice(project) { KonanInteropProject it ->
it.addInteropSetting("stdio", "headers", header)
def results = buildTwice(project) { KonanProject it ->
it.addSetting("stdio", "headers", header)
}
then:
@@ -222,22 +210,19 @@ class IncrementalSpecification extends BaseKonanSpecification {
def 'link change for an interop task should cause recompilation and interop reprocessing'() {
when:
def project = KonanInteropProject.createEmpty(projectDirectory) { KonanInteropProject it ->
it.generateSrcFile('main.kt')
def project = KonanProject.createWithInterop(projectDirectory) { KonanProject it ->
it.generateSrcFile(["src", "lib", "kotlin"], 'lib.kt', 'fun foo() { println(42) }')
it.buildFile.append("""
konanArtifacts {
lib {
bitcode('lib') {
inputFiles fileTree('src/lib/kotlin')
produce 'bitcode'
noMain()
}
}
""".stripIndent())
}
def results = buildTwice(project) { KonanInteropProject it ->
it.addInteropSetting("stdio", "interopProcessingTask.dependsOn", "konanArtifacts['lib'].compilationTask")
it.addInteropSetting("stdio", "link", "files(konanArtifacts['lib'].compilationTask.artifactPath)")
def results = buildTwice(project) { KonanProject it ->
it.addSetting("stdio", "dependsOn", "konanArtifacts.lib.${KonanProject.HOST}")
it.addSetting("stdio", "link", "files(konanArtifacts.lib.${KonanProject.HOST}.artifactPath)")
}
then:
@@ -246,11 +231,10 @@ class IncrementalSpecification extends BaseKonanSpecification {
def 'konan version change should cause recompilation and interop reprocessing'() {
when:
def project = KonanInteropProject.createEmpty(projectDirectory) { KonanInteropProject it ->
it.generateSrcFile('main.kt')
def project = KonanProject.createWithInterop(projectDirectory) { KonanProject it ->
it.propertiesFile.append("konan.version=0.3\n")
}
def results = buildTwice(project) { KonanInteropProject it ->
def results = buildTwice(project) { KonanProject it ->
def newText = it.propertiesFile.text.replace('konan.version=0.3', 'konan.version=0.4')
it.propertiesFile.write(newText)
}
@@ -259,31 +243,5 @@ class IncrementalSpecification extends BaseKonanSpecification {
recompilationAndInteropProcessingHappened(*results)
}
@IgnoreIf({ System.getProperty('os.name').toLowerCase().contains('windows') })
def 'target change should cause recompilation and interop reprocessing'() {
when:
def newTarget
if (System.getProperty('os.name').toLowerCase().contains('linux')) {
newTarget = "raspberrypi"
} else if (System.getProperty('os.name').toLowerCase().contains('mac')) {
newTarget = "iphone"
} else {
throw new IllegalStateException("Unknown host platform")
}
def project = KonanInteropProject.createEmpty(projectDirectory) { KonanInteropProject it ->
it.generateSrcFile('main.kt')
it.propertiesFile.append("konan.build.targets=all\n")
}
def results = buildTwice(project) { KonanInteropProject it ->
project.addCompilationSetting("main", "target", "'$newTarget'")
project.addInteropSetting("stdio", "target", "'$newTarget'")
}
then:
recompilationAndInteropProcessingHappened(*results)
}
//endregion
}
@@ -1,6 +1,8 @@
package org.jetbrains.kotlin.gradle.plugin.test
import org.gradle.testkit.runner.GradleRunner
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.TargetManager
import java.nio.file.Files
import java.nio.file.Path
@@ -9,6 +11,9 @@ import java.nio.file.Paths
class KonanProject {
static String DEFAULT_ARTIFACT_NAME = 'main'
static String DEFAULT_INTEROP_NAME = "stdio"
static String HOST = TargetManager.host.userName
File projectDir
Path projectPath
@@ -21,15 +26,40 @@ class KonanProject {
File settingsFile
Set<File> srcFiles = []
Set<File> defFiles = []
List<String> compilationTasks = []
String downloadTask = ":downloadKonanCompiler"
List<String> interopTasks = []
List<String> compilationTasks = []
String downloadTask = ":checkKonanCompiler"
List<String> getBuildingTasks() { return compilationTasks }
List<String> targets
List<String> getBuildingTasks() { return compilationTasks + interopTasks }
List<String> getKonanTasks() { return getBuildingTasks() + downloadTask }
protected KonanProject(File projectDir) {
// TODO: Must be an enum.
static String PROGRAM = "program"
static String LIBRARY = "library"
static String BITCODE = "bitcode"
static String INTEROP = "interop"
static String DEFAULT_SRC_CONTENT = """
fun main(args: Array<String>) {
println(42)
}
"""
static String DEFAULT_DEF_CONTENT = """
headers = stdio.h
""".stripIndent()
protected KonanProject(File projectDir){
this(projectDir, [HOST])
}
protected KonanProject(File projectDir, List<String> targets) {
this.projectDir = projectDir
this.targets = targets
projectPath = projectDir.toPath()
konanBuildDir = projectPath.resolve('build/konan').toFile()
def konanHome = System.getProperty("konan.home")
@@ -76,6 +106,7 @@ class KonanProject {
/** Creates a folder for project source files (src/main/kotlin). */
void generateFolders() {
createSubDir("src", "main", "kotlin")
createSubDir("src", "main", "c_interop")
}
/** Generates a build.gradle file in the root project directory with the given content. */
@@ -94,22 +125,20 @@ class KonanProject {
* Generates a build.gradle file in root project directory with the default content (see below)
* and fills the compilationTasks array.
*
* plugins { id 'konan' }
* plugins { id 'konan' }
*
* konanArtifacts {
* $DEFAULT_ARTIFACT_NAME { }
* }
* konanArtifacts {
* program('$DEFAULT_ARTIFACT_NAME')
* }
*/
File generateBuildFile() {
def result = generateBuildFile("""
plugins { id 'konan' }
konanArtifacts {
$DEFAULT_ARTIFACT_NAME { }
}
konanTargets = [${targets.collect { "'$it'" }.join(", ")}]
""".stripIndent()
)
compilationTasks = [defaultCompilationTask(), ":compileKonan", ":build"]
compilationTasks = [":compileKonan", ":build"]
return result
}
@@ -139,151 +168,7 @@ class KonanProject {
* }
*/
File generateSrcFile(String fileName) {
return generateSrcFile(fileName, """
fun main(args: Array<String>) {
println(42)
}
""".stripIndent()
)
}
/** Generates gradle.properties file with the konan.home property set. */
File generatePropertiesFile(String konanHome) {
propertiesFile = createFile(projectPath, "gradle.properties", "konan.home=$konanHome\n")
return propertiesFile
}
/**
* Sets the given setting of the given project extension.
* In other words adds the following string in the build file:
*
* $container['$section'].$parameter $value
*/
protected void addSetting(String container, String section, String parameter, String value) {
buildFile.append("$container['$section'].$parameter $value\n")
}
/**
* Sets the given setting of the given project extension using the path of the file as a value.
* In other words adds the following string in the build file:
*
* $container['$section'].$parameter ${value.canonicalPath.replace(\, \\)}
*/
protected void addSetting(String container, String section, String parameter, File value) {
addSetting(container, section, parameter, "'${value.canonicalPath.replace('\\', '\\\\')}'")
}
/** Sets the given setting of the given konanArtifact */
void addCompilationSetting(String artifactName = DEFAULT_ARTIFACT_NAME, String parameter, String value) {
addSetting("konanArtifacts", artifactName, parameter, value)
}
/** Sets the given setting of the given konanArtifact using the path of the file as a value. */
void addCompilationSetting(String artifactName = DEFAULT_ARTIFACT_NAME, String parameter, File value) {
addSetting("konanArtifacts", artifactName, parameter, value)
}
/** Returns the path of compileKonan... task for the default artifact. */
String defaultCompilationTask() {
return compilationTask(DEFAULT_ARTIFACT_NAME)
}
/** Returns the path of compileKonan... task for the artifact specified. */
String compilationTask(String artifactName) {
return ":compileKonan${artifactName.capitalize()}"
}
String defaultArtifactConfig() {
return artifactConfig(DEFAULT_ARTIFACT_NAME)
}
String artifactConfig(String artifactName) {
return "konanArtifacts['$artifactName']"
}
/** Creates a project with default build and source files. */
static KonanProject create(File projectDir) {
return createEmpty(projectDir) {
it.generateSrcFile("main.kt")
}
}
/** Creates a project with default build and source files. */
static KonanProject create(File projectDir, Closure config) {
def result = create(projectDir)
config(result)
return result
}
/** Creates a project with the default build file and without any source files. */
static KonanProject createEmpty(File projectDir) {
def result = new KonanProject(projectDir)
result.with {
generateFolders()
generateBuildFile()
generatePropertiesFile(konanHome)
generateSettingsFile("")
}
return result
}
/** Creates a project with the default build file and without any source files. */
static KonanProject createEmpty(File projectDir, Closure config) {
def result = createEmpty(projectDir)
config(result)
return result
}
}
class KonanInteropProject extends KonanProject {
static String DEFAULT_INTEROP_NAME = "stdio"
Set<File> defFiles = []
List<String> interopTasks = []
protected KonanInteropProject(File projectDir) { super(projectDir) }
List<String> getBuildingTasks() { return interopTasks + compilationTasks }
/** Creates a folder for project source files (src/main/kotlin) and a folder for def-files (src/main/c_interop). */
void generateFolders() {
super.generateFolders()
createSubDir("src", "main", "c_interop")
}
/**
* Generates a build.gradle file in root project directory with the default content (see below)
* and fills compilationTasks and interopTasks arrays.
*
* plugins { id 'konan' }
*
* konanInterop {
* $DEFAULT_INTEROP_NAME { }
* }
*
* konanArtifacts {
* $DEFAULT_ARTIFACT_NAME { useInterop '$DEFAULT_INTEROP_NAME' }
}
*/
File generateBuildFile() {
def result = generateBuildFile("""
plugins { id 'konan' }
konanInterop {
$DEFAULT_INTEROP_NAME { }
}
konanArtifacts {
$DEFAULT_ARTIFACT_NAME { useInterop '$DEFAULT_INTEROP_NAME' }
}
""".stripIndent()
)
interopTasks = [defaultInteropProcessingTask()]
compilationTasks = [defaultCompilationTask(), ":compileKonan", ":build"]
return result
return generateSrcFile(fileName, DEFAULT_SRC_CONTENT)
}
/** Creates a def-file with the given name and content in src/main/c_interop directory and adds it to defFiles. */
@@ -300,69 +185,137 @@ class KonanInteropProject extends KonanProject {
* headers = stdio.h stdlib.h string.h
*/
File generateDefFile(String fileName = "${DEFAULT_INTEROP_NAME}.def") {
return generateDefFile(fileName, """
headers = stdio.h stdlib.h string.h
""".stripIndent()
)
return generateDefFile(fileName, DEFAULT_DEF_CONTENT)
}
/** Sets the given setting of the given konanInterop */
void addInteropSetting(String interopName = DEFAULT_INTEROP_NAME, String parameter, String value) {
addSetting("konanInterop", interopName, parameter, value)
/** Generates gradle.properties file with the konan.home property set. */
File generatePropertiesFile(String konanHome) {
propertiesFile = createFile(projectPath, "gradle.properties", "konan.home=$konanHome\n")
return propertiesFile
}
/** Sets the given setting of the given konanInterop using the path of the file as a value. */
void addInteropSetting(String interopName = DEFAULT_INTEROP_NAME, String parameter, File value) {
addSetting("konanInterop", interopName, parameter, value)
/**
* Sets the given setting of the given project extension.
* In other words adds the following string in the build file:
*
* $container.$section.$parameter $value
*/
protected void addSetting(String container, String section, String parameter, String value) {
buildFile.append("$container.$section.$parameter $value\n")
}
String defaultInteropProcessingTask() {
return interopProcessingTask(DEFAULT_INTEROP_NAME)
/**
* Sets the given setting of the given project extension using the path of the file as a value.
* In other words adds the following string in the build file:
*
* $container.$section.$parameter ${value.canonicalPath.replace(\, \\)}
*/
protected void addSetting(String container, String section, String parameter, File value) {
addSetting(container, section, parameter, "'${value.canonicalPath.replace('\\', '\\\\')}'")
}
String interopProcessingTask(String interopName) {
return ":process${interopName.capitalize()}Interop"
/** Sets the given setting of the given konanArtifact */
void addSetting(String artifactName = DEFAULT_ARTIFACT_NAME, String parameter, String value) {
addSetting("konanArtifacts", artifactName, parameter, value)
}
/** Sets the given setting of the given konanArtifact using the path of the file as a value. */
void addSetting(String artifactName = DEFAULT_ARTIFACT_NAME, String parameter, File value) {
addSetting("konanArtifacts", artifactName, parameter, value)
}
void addLibraryToArtifact(String artifactName = DEFAULT_ARTIFACT_NAME, String library = DEFAULT_INTEROP_NAME) {
addLibraryToArtifactCustom(artifactName, "artifact '$library'")
}
void addLibraryToArtifactCustom(String artifactName = DEFAULT_ARTIFACT_NAME, String closureContent) {
buildFile.append("konanArtifacts.${artifactName}.libraries { $closureContent }\n")
}
/** Returns the path of compileKonan... task for the default artifact. */
String defaultCompilationTask(String target = HOST) {
return compilationTask(DEFAULT_ARTIFACT_NAME, target)
}
String defaultInteropTask(String target = HOST) {
return compilationTask(DEFAULT_INTEROP_NAME, target)
}
/** Returns the path of compileKonan... task for the artifact specified. */
String compilationTask(String artifactName, String target = HOST) {
return ":compileKonan${artifactName.capitalize()}${target.capitalize()}"
}
String defaultCompilationConfig() {
return artifactConfig(DEFAULT_ARTIFACT_NAME)
}
String defaultInteropConfig() {
return interopConfig(DEFAULT_INTEROP_NAME)
return artifactConfig(DEFAULT_INTEROP_NAME)
}
String interopConfig(String interopName) {
return "konanInterop[$interopName]"
String artifactConfig(String artifactName) {
return "konanArtifacts.$artifactName"
}
/** Creates a project with default build, source and def files. */
static KonanInteropProject create(File projectDir) {
return createEmpty(projectDir) {
it.generateSrcFile("main.kt")
it.generateDefFile("${DEFAULT_INTEROP_NAME}.def")
void addCompilerArtifact(String name, String content = "", String type = PROGRAM) {
def newTasks = targets.collect { compilationTask(name, it) } + ":compileKonan${name.capitalize()}".toString()
if (type == INTEROP) {
defFiles += generateDefFile("${name}.def", content)
interopTasks += newTasks
} else {
srcFiles += generateSrcFile(projectPath.resolve("src/$name/kotlin"), "source.kt", content)
compilationTasks += newTasks
}
buildFile.append("konanArtifacts { $type('$name') }\n")
}
/** Creates a project with default build and source files. */
static KonanProject create(File projectDir, List<String> targets = [HOST]) {
return createEmpty(projectDir, targets) { KonanProject p ->
p.addCompilerArtifact(DEFAULT_ARTIFACT_NAME, DEFAULT_SRC_CONTENT)
}
}
/** Creates a project with default build, source and def files. */
static KonanInteropProject create(File projectDir, Closure config) {
def result = create(projectDir)
// TODO: Add 'type parameter'
/** Creates a project with default build and source files. */
static KonanProject create(File projectDir, List<String> targets = [HOST], Closure config) {
def result = create(projectDir, targets)
config(result)
return result
}
/** Creates a project with the default build file, without any source and with an empty def file. */
static KonanInteropProject createEmpty(File projectDir) {
def result = new KonanInteropProject(projectDir)
static KonanProject createWithInterop(File projectDir, List<String> targets = [HOST]) {
return create(projectDir, targets) { KonanProject p ->
p.addCompilerArtifact(DEFAULT_INTEROP_NAME, DEFAULT_DEF_CONTENT, INTEROP)
p.addLibraryToArtifact()
}
}
static KonanProject createWithInterop(File projectDir, List<String> targets = [HOST], Closure config) {
def result = createWithInterop(projectDir, targets)
config(result)
return result
}
/** Creates a project with the default build file and without any source files. */
static KonanProject createEmpty(File projectDir, List<String> targets = [HOST]) {
def result = new KonanProject(projectDir, targets)
result.with {
generateFolders()
generateBuildFile()
generatePropertiesFile(konanHome)
generateDefFile("${DEFAULT_INTEROP_NAME}.def", "")
generateSettingsFile("")
}
return result
}
/** Creates a project with the default build file, without any source and with an empty def file. */
static KonanInteropProject createEmpty(File projectDir, Closure config) {
def result = createEmpty(projectDir)
/** Creates a project with the default build file and without any source files. */
static KonanProject createEmpty(File projectDir, List<String> targets = [HOST], Closure config) {
def result = createEmpty(projectDir, targets)
config(result)
return result
}
}
@@ -1,26 +1,36 @@
package org.jetbrains.kotlin.gradle.plugin.test
import org.gradle.testkit.runner.TaskOutcome
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.TargetManager
class PathSpecification extends BaseKonanSpecification {
boolean fileExists(KonanProject project, String path) {
project.konanBuildDir.toPath().resolve(path).toFile().exists()
}
def 'Plugin should create all necessary directories'() {
when:
def project = KonanInteropProject.create(projectDirectory)
def project = KonanProject.createWithInterop(projectDirectory)
project.addCompilerArtifact("lib", "fun foo() {}", KonanProject.LIBRARY)
project.addCompilerArtifact("bit", "fun bar() {}", KonanProject.BITCODE)
println(project.buildFile.text)
def result = project.createRunner().withArguments('build').build()
then:
def konan = project.konanBuildDir
new File("$konan/bin").listFiles().findAll {
File it -> it.file && it.name.matches('^main\\.[^.]+')
project.konanBuildDir.toPath().resolve("bin/$KonanProject.HOST").toFile().listFiles().findAll {
File it -> it.file && it.name.matches("^${KonanProject.DEFAULT_ARTIFACT_NAME}\\.[^.]+")
}.size() > 0
def klib = new File("$konan/c_interop/stdio.klib")
klib.exists() && klib.file
fileExists(project, "libs/$KonanProject.HOST/${KonanProject.DEFAULT_INTEROP_NAME}.klib")
fileExists(project, "libs/$KonanProject.HOST/lib.klib")
fileExists(project, "bitcode/$KonanProject.HOST/bit.bc")
}
def 'Plugin should stop building if the compiler classpath is empty'() {
when:
def project = KonanProject.createEmpty(projectDirectory)
def project = KonanProject.create(projectDirectory)
project.propertiesFile.write("konan.home=${projectDirectory.canonicalPath}}")
def result = project.createRunner().withArguments('build').buildAndFail()
@@ -30,29 +40,37 @@ class PathSpecification extends BaseKonanSpecification {
def 'Plugin should stop building if the stub generator classpath is empty'() {
when:
def project = KonanInteropProject.createEmpty(projectDirectory)
def project = KonanProject.createWithInterop(projectDirectory)
project.propertiesFile.write("konan.home=${projectDirectory.canonicalPath}}")
def result = project.createRunner().withArguments('build').buildAndFail()
then:
result.task(project.defaultInteropProcessingTask()).outcome == TaskOutcome.FAILED
result.task(project.compilationTask(KonanProject.DEFAULT_INTEROP_NAME)).outcome == TaskOutcome.FAILED
}
def 'Plugin should remove custom output directories'() {
when:
def customOutputDir = projectDirectory.toPath().resolve("foo").toFile()
def project = KonanInteropProject.create(projectDirectory) { KonanProject it ->
it.addCompilationSetting("outputDir", customOutputDir)
def project = KonanProject.create(projectDirectory) { KonanProject it ->
it.addSetting("baseDir", customOutputDir)
}
def res1 = project.createRunner().withArguments("build").build()
def customDirExistsAfterBuild = customOutputDir.exists()
def artifactExistsAfterBuild = customOutputDir.toPath()
.resolve("${KonanProject.HOST}").toFile()
.listFiles()
.findAll { it.name.matches("^${KonanProject.DEFAULT_ARTIFACT_NAME}\\.[^.]+") }.size() > 0
def res2 = project.createRunner().withArguments("clean").build()
def customDirDoesntNotExistAfterClean = !customOutputDir.exists()
def artifactDoesntNotExistAfterClean = customOutputDir.toPath()
.resolve("${KonanProject.HOST}").toFile()
.listFiles()
.findAll { it.name.matches("^${KonanProject.DEFAULT_ARTIFACT_NAME}\\.[^.]+") }.isEmpty()
then:
res1.taskPaths(TaskOutcome.SUCCESS).containsAll(project.buildingTasks)
res2.taskPaths(TaskOutcome.SUCCESS).contains(":clean")
customDirExistsAfterBuild
customDirDoesntNotExistAfterClean
artifactExistsAfterBuild
artifactDoesntNotExistAfterClean
}
}
@@ -40,13 +40,13 @@ class RegressionSpecification extends BaseKonanSpecification {
def 'KT-20192'() {
when:
def project = KonanProject.createEmpty(getProjectDirectory()) { KonanProject prj ->
prj.generateSrcFile("main.kt", """
prj.addCompilerArtifact(KonanProject.DEFAULT_ARTIFACT_NAME,"""
external fun foo()
fun main(args: Array<String>) {
foo()
}
""")
""", KonanProject.PROGRAM)
}
def result = project.createRunner().withArguments('build').buildAndFail()
@@ -9,15 +9,13 @@ class TaskSpecification extends BaseKonanSpecification {
def 'Configs should allow user to add dependencies to them'() {
when:
def project = KonanInteropProject.createEmpty(projectDirectory) { KonanInteropProject it ->
it.generateSrcFile("main.kt")
}
def project = KonanProject.createWithInterop(projectDirectory)
project.buildFile.append("""
task beforeInterop(type: DefaultTask) { doLast { println("Before Interop") } }
task beforeCompilation(type: DefaultTask) { doLast { println("Before compilation") } }
""".stripIndent())
project.addInteropSetting("dependsOn", "beforeInterop")
project.addCompilationSetting("dependsOn", "beforeCompilation")
project.addSetting(KonanProject.DEFAULT_INTEROP_NAME,"dependsOn", "beforeInterop")
project.addSetting("dependsOn", "beforeCompilation")
def result = project.createRunner().withArguments('build').build()
then:
@@ -27,62 +25,10 @@ class TaskSpecification extends BaseKonanSpecification {
beforeCompilation != null && beforeCompilation.outcome == TaskOutcome.SUCCESS
}
def 'Compilation config should work with konanInterop from another project'() {
when:
def rootProject = KonanProject.create(projectDirectory) { KonanProject it ->
it.buildFile.append("evaluationDependsOn(':interop')\n")
it.createFile("settings.gradle", "include ':interop'")
it.addCompilationSetting("useInterop", "project(':interop').konanInterop['interop']")
}
def interopProjectDir = rootProject.createSubDir("interop")
def interopProject = KonanInteropProject.createEmpty(interopProjectDir) { KonanInteropProject it ->
it.generateBuildFile("""
apply plugin: 'konan'
konanInterop {
interop { }
}
""".stripIndent())
it.interopTasks = [":interop:processInteropInterop"]
it.generateDefFile("interop.def")
}
def result = rootProject.createRunner().withArguments("build").build()
then:
result.taskPaths(TaskOutcome.SUCCESS).containsAll(rootProject.compilationTasks + interopProject.interopTasks)
}
def 'Compilation should support interop parameters changing after `useInterop` call'() {
when:
def project = KonanInteropProject.create(projectDirectory)
project.addInteropSetting("linkerOpts", "'-lpthread'")
project.buildFile.append("""
task printArgs {
dependsOn 'build'
doLast {
println(konanArtifacts['$project.DEFAULT_ARTIFACT_NAME'].compilationTask.linkerOpts)
konanArtifacts['$project.DEFAULT_ARTIFACT_NAME'].compilationTask.libraries.each { println it.files }
konanArtifacts['$project.DEFAULT_ARTIFACT_NAME'].compilationTask.nativeLibraries.each { println it.files }
}
}
""".stripIndent())
def result = project.createRunner().withArguments('printArgs').build()
then:
result.task(":printArgs") != null
result.task(":printArgs").outcome == TaskOutcome.SUCCESS
def expectedKlibPath = project.konanBuildDir.toPath()
.resolve("c_interop${File.separator}stdio.klib")
.toFile().canonicalPath
def ls = System.lineSeparator()
result.output.contains("[-lpthread]$ls[$expectedKlibPath]".stripIndent().trim())
}
def 'Compiler should print time measurements if measureTime flag is set'() {
when:
def project = KonanInteropProject.createEmpty(projectDirectory)
project.generateSrcFile("main.kt")
project.addCompilationSetting("measureTime", "true")
def project = KonanProject.create(projectDirectory)
project.addSetting("measureTime", "true")
def result = project.createRunner().withArguments('build').build()
then:
@@ -92,7 +38,7 @@ class TaskSpecification extends BaseKonanSpecification {
}
BuildResult failOnPropertyAccess(String property) {
def project = KonanInteropProject.createEmpty(projectDirectory)
def project = KonanProject.createWithInterop(projectDirectory)
project.buildFile.append("""
task testTask(type: DefaultTask) {
doLast {
@@ -104,7 +50,7 @@ class TaskSpecification extends BaseKonanSpecification {
}
BuildResult failOnTaskAccess(String task) {
def project = KonanInteropProject.createEmpty(projectDirectory)
def project = KonanProject.createWithInterop(projectDirectory)
project.buildFile.append("""
task testTask(type: DefaultTask) {
dependsOn $task
@@ -112,94 +58,4 @@ class TaskSpecification extends BaseKonanSpecification {
""".stripIndent())
return project.createRunner().withArguments("testTask").buildAndFail()
}
def 'Deprecated properties/tasks should generate exceptions'() {
expect:
failOnPropertyAccess("generateStubsTask")
failOnPropertyAccess("compileStubsTask")
failOnPropertyAccess("compileStubsConfig")
failOnTaskAccess("gen${KonanInteropProject.DEFAULT_INTEROP_NAME.capitalize()}InteropStubs")
failOnTaskAccess("compile${KonanInteropProject.DEFAULT_INTEROP_NAME.capitalize()}InteropStubs")
}
def 'Compilation task should be able to use another project as a library (with artifact name)'() {
when:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject prj ->
prj.generateSrcFile("main.kt", """\
external fun foo()
fun main(args: Array<String>) = foo()
""")
prj.settingsFile.append("include ':foo'")
}
def subproject = KonanProject.createEmpty(project.projectPath.resolve("foo").toFile()) {
KonanProject prj ->
prj.generateSrcFile("foo.kt", "fun foo() { println(42) } ")
prj.addCompilationSetting("produce", "'library'")
def buildScript = prj.buildFile.text.replace("plugins { id 'konan' }", "apply plugin: 'konan'")
prj.buildFile.write(buildScript)
}
project.addCompilationSetting("library", "findProject(':foo'), 'main'")
def result = project.createRunner().withArguments("build").build()
def successfulTasks = project.buildingTasks + subproject.buildingTasks.collect { ":foo$it".toString() }
subproject.addCompilationSetting("produce", "'program'")
def failedResult = project.createRunner().withArguments("build").buildAndFail()
then:
result.taskPaths(TaskOutcome.SUCCESS).containsAll(successfulTasks)
}
def 'Compilation task should be able to use another project as a library (all klibs in the project)'() {
when:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject prj ->
prj.generateSrcFile("main.kt", """\
external fun foo()
external fun bar()
fun baz() { println("baz") }
fun main(args: Array<String>) { foo(); bar(); baz() }
""".stripIndent())
prj.settingsFile.append("include ':foo'")
}
def subproject = KonanProject.createEmpty(project.projectPath.resolve("foo").toFile()) {
KonanProject prj ->
def buildScript = prj.buildFile.text.replace("plugins { id 'konan' }", "apply plugin: 'konan'")
prj.buildFile.write(buildScript)
// Default artifact: main, should be recognized as a library with foo function.
prj.generateSrcFile("foo.kt", "fun foo() { println(42) } ")
prj.addCompilationSetting("produce", "'library'")
prj.generateSrcFile(Paths.get("src", "bar" , "kotlin"),
"bar.kt", "fun bar() { println(\"bar\") }")
prj.generateSrcFile(Paths.get("src", "baz", "kotlin"),
"baz.kt", "fun baz() { println(\"another baz\") }")
prj.buildFile.append("""\
konanArtifacts {
// Should be recognized as a library.
bar {
inputDir "src/bar/kotlin"
produce "library"
}
// Should not be recognized as a library.
baz {
inputDir "src/baz/kotlin"
produce "bitcode"
}
}
""".stripIndent())
}
project.addCompilationSetting("library", "findProject(':foo')")
def result = project.createRunner().withArguments("build").build()
def successfulTasks = project.buildingTasks + subproject.buildingTasks.collect { ":foo$it".toString() }
then:
result.taskPaths(TaskOutcome.SUCCESS).containsAll(successfulTasks)
}
}