[K/N][build] Move old Konan plugin to native-build-tools

This commit is contained in:
Pavel Punegov
2023-08-31 15:27:19 +02:00
committed by Space Team
parent 04c5ac0eb6
commit b1a85d45c6
34 changed files with 76 additions and 2916 deletions
@@ -1,196 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.jetbrains.kotlin.konan.*
buildscript {
ext.rootBuildDirectory = file('../../')
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
repositories {
mavenCentral()
maven {
url "https://cache-redirector.jetbrains.com/plugins.gradle.org/m2/"
}
gradlePluginPortal()
}
dependencies {
classpath 'com.github.johnrengelman:shadow:8.1.1'
classpath "org.jetbrains.kotlin:kotlin-native-shared:$kotlinVersion"
}
}
apply plugin: 'java-gradle-plugin'
apply plugin: 'kotlin'
apply plugin: 'groovy'
apply plugin: 'com.github.johnrengelman.shadow'
group = 'org.jetbrains.kotlin'
version = CompilerVersionGeneratedKt.getCurrentCompilerVersion()
repositories {
mavenCentral()
}
configurations {
bundleDependencies {
transitive = false
}
implementation.extendsFrom shadow
compileOnly.extendsFrom bundleDependencies
testImplementation.extendsFrom bundleDependencies
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions.freeCompilerArgs = ["-Xskip-prerelease-check"]
}
dependencies {
shadow "org.jetbrains.kotlin:kotlin-stdlib:1.3.0"
// Bundle the serialization plugin into the final jar because we shade classes of the kotlin plugin
// while the serialization one extends them.
bundleDependencies "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion"
bundleDependencies "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
bundleDependencies "org.jetbrains.kotlin:kotlin-gradle-plugin-api:$kotlinVersion"
bundleDependencies "org.jetbrains.kotlin:kotlin-native-utils:$kotlinVersion"
bundleDependencies "org.jetbrains.kotlin:kotlin-native-shared:$kotlinVersion"
bundleDependencies "org.jetbrains.kotlin:kotlin-util-io:$kotlinVersion"
bundleDependencies "org.jetbrains.kotlin:kotlin-util-klib:$kotlinVersion"
testImplementation DependenciesKt.commonDependency(project, "junit")
testImplementation "org.jetbrains.kotlin:kotlin-test:${project.bootstrapKotlinVersion}"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${project.bootstrapKotlinVersion}"
testImplementation "org.tools4j:tools4j-spockito:1.6"
testImplementation('org.spockframework:spock-core:1.1-groovy-2.4') {
exclude module: 'groovy-all'
}
}
shadowJar {
from sourceSets.main.output
configurations = [project.configurations.bundleDependencies]
archiveClassifier.set(null)
relocate('org.jetbrains.kotlinx', 'shadow.org.jetbrains.kotlinx')
relocate('org.jetbrains.kotlin.compilerRunner', 'shadow.org.jetbrains.kotlin.compilerRunner')
relocate('org.jetbrains.kotlin.konan', 'shadow.org.jetbrains.kotlin.konan')
relocate('org.jetbrains.kotlin.gradle', 'shadow.org.jetbrains.kotlin.gradle') {
exclude('org.jetbrains.kotlin.gradle.plugin.experimental.**')
exclude('org.jetbrains.kotlin.gradle.plugin.konan.**')
exclude('org.jetbrains.kotlin.gradle.plugin.model.**')
}
exclude {
def path = it.relativePath.pathString
if (path.startsWith("META-INF/gradle-plugins") && path.endsWith(".properties")) {
def fileName = it.name
def id = fileName.take(fileName.lastIndexOf('.'))
return project.gradlePlugin.plugins.findByName(id) == null
}
return false
}
exclude('META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar')
exclude('META-INF/services/org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin')
}
jar {
dependsOn shadowJar
enabled = false
}
pluginUnderTestMetadata {
dependsOn shadowJar
doLast {
// Since Gradle 4.10 it isn't possible to edit the pluginUnderTest classpath.
// So we have to manually set the implementation-classpath to get the output fat-jar.
def pluginMetadata = outputDirectory.get().file(PluginUnderTestMetadata.METADATA_FILE_NAME).getAsFile()
def classpath = files(shadowJar.archivePath) + configurations.shadow
new Properties().with { properties ->
pluginMetadata.withInputStream {
properties.load(it)
}
properties.setProperty(PluginUnderTestMetadata.IMPLEMENTATION_CLASSPATH_PROP_KEY , classpath.asPath)
pluginMetadata.withOutputStream {
properties.store(it, null)
}
}
}
}
test {
dependsOn shadowJar
systemProperty("kotlin.version", kotlinVersion)
systemProperty("kotlin.repo", project.bootstrapKotlinRepo)
if (project.hasProperty("konan.home")) {
systemProperty("konan.home", project.property("konan.home"))
systemProperty("org.jetbrains.kotlin.native.home", project.property("konan.home"))
} else if (project.hasProperty("org.jetbrains.kotlin.native.home")) {
systemProperty("org.jetbrains.kotlin.native.home", project.property("org.jetbrains.kotlin.native.home"))
} else {
// The Koltin/Native compiler must be built before test execution.
systemProperty("konan.home", distDir.absolutePath)
systemProperty("org.jetbrains.kotlin.native.home", distDir.absolutePath)
}
if (project.hasProperty("konan.jvmArgs")) {
systemProperty("konan.jvmArgs", project.property("konan.jvmArgs"))
}
// Uncomment for debugging.
//testLogging.showStandardStreams = true
if (project.hasProperty("maxParallelForks")) {
maxParallelForks=project.property("maxParallelForks")
}
if (project.hasProperty("filter")) {
filter.includeTestsMatching project.property("filter")
}
if (project.hasProperty("gradleVersion")) {
systemProperty("gradleVersion", project.property("gradleVersion"))
}
}
processResources {
from(file("$rootBuildDirectory/utilities/env_blacklist"))
}
tasks.named('compileTestGroovy') {
classpath = sourceSets.test.compileClasspath
}
tasks.named('compileTestKotlin') {
classpath += files(sourceSets.test.groovy.classesDirectory)
}
gradlePlugin {
plugins {
create('konan') {
id = 'konan'
implementationClass = 'org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin'
}
// We bundle a shaded version of kotlinx-serialization plugin
create('kotlinx-serialization-native') {
id = 'kotlinx-serialization-native'
implementationClass = 'shadow.org.jetbrains.kotlinx.serialization.gradle.SerializationGradleSubplugin'
}
create('org.jetbrains.kotlin.konan') {
id = 'org.jetbrains.kotlin.konan'
implementationClass = 'org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin'
}
}
}
@@ -1,2 +0,0 @@
rootProject.name = "kotlin-native-gradle-plugin"
includeBuild '../../shared'
@@ -1,95 +0,0 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.plugin.experimental.internal
import org.gradle.api.Project
import org.gradle.api.internal.component.UsageContext
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider
import org.gradle.internal.os.OperatingSystem
import org.gradle.language.cpp.internal.NativeVariantIdentity
import org.gradle.nativeplatform.MachineArchitecture
import org.gradle.nativeplatform.OperatingSystemFamily
import org.gradle.nativeplatform.TargetMachine
import org.gradle.nativeplatform.TargetMachineFactory
import org.gradle.nativeplatform.platform.NativePlatform
import org.gradle.nativeplatform.platform.internal.*
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.util.visibleName
interface KotlinNativePlatform : NativePlatform {
val target: KonanTarget
}
fun KonanTarget.getGradleOS(): OperatingSystemInternal = family.visibleName.let {
DefaultOperatingSystem(it, OperatingSystem.forName(it))
}
fun KonanTarget.getGradleOSFamily(objectFactory: ObjectFactory): OperatingSystemFamily {
return objectFactory.named(OperatingSystemFamily::class.java, family.visibleName)
}
fun KonanTarget.getGradleCPU(): ArchitectureInternal = architecture.visibleName.let {
Architectures.forInput(it)
}
fun KonanTarget.toTargetMachine(objectFactory: ObjectFactory): TargetMachine = object : TargetMachine {
override fun getOperatingSystemFamily(): OperatingSystemFamily =
getGradleOSFamily(objectFactory)
override fun getArchitecture(): MachineArchitecture =
objectFactory.named(MachineArchitecture::class.java, this@toTargetMachine.architecture.visibleName)
}
class DefaultKotlinNativePlatform(name: String, override val target: KonanTarget) :
DefaultNativePlatform(name, target.getGradleOS(), target.getGradleCPU()),
KotlinNativePlatform {
constructor(target: KonanTarget) : this(target.visibleName, target)
// TODO: Extend ImmutableDefaultNativePlatform and get rid of these methods after switch to Gradle 4.8
private fun notImplemented(): Nothing = throw NotImplementedError("Not Implemented in Kotlin/Native plugin")
override fun operatingSystem(name: String?) = notImplemented()
override fun withArchitecture(architecture: ArchitectureInternal?) = notImplemented()
override fun architecture(name: String?) = notImplemented()
}
// NativeVariantIdentity constructor was changed in Gradle 5.1
// So we have to use reflection to create instance of this class in earlier versions.
internal fun compatibleVariantIdentity(
project: Project,
name: String,
baseName: Provider<String>,
group: Provider<String>,
version: Provider<String>,
debuggable: Boolean,
optimized: Boolean,
target: KonanTarget,
linkUsage: UsageContext?,
runtimeUsage: UsageContext?
): NativeVariantIdentity =
NativeVariantIdentity::class.java.getConstructor(
String::class.java,
Provider::class.java,
Provider::class.java,
Provider::class.java,
Boolean::class.javaPrimitiveType,
Boolean::class.javaPrimitiveType,
OperatingSystemFamily::class.java,
UsageContext::class.java,
UsageContext::class.java
).newInstance(
name,
baseName,
group,
version,
debuggable,
optimized,
target.getGradleOSFamily(project.objects),
linkUsage,
runtimeUsage
)
@@ -1,110 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.konan
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin.ProjectProperty
import java.io.File
import java.util.*
/**
* The plugin allows an IDE to specify some building parameters. These parameters
* are passed to the plugin via environment variables. Two variables are supported:
* - CONFIGURATION_BUILD_DIR - A path to a destination directory for all compilation tasks.
* The IDE should take care about specifying different directories
* for different targets. This setting has less priority than
* an explicitly specified destination directory in the build script.
*
* - DEBUGGING_SYMBOLS - If YES, the debug support will be enabled for all artifacts. This option has less
* priority than explicitly specified enableDebug option in the build script and
* enableDebug project property.
*
* - KONAN_ENABLE_OPTIMIZATIONS - If YES, optimizations will be enabled for all artifacts by default. This option
* has less priority than explicitly specified enableOptimizations option in the
* build script.
*
* Support for environment variables should be explicitly enabled by setting a project property:
* konan.useEnvironmentVariables = true.
*/
internal interface EnvironmentVariables {
val configurationBuildDir: File?
val debuggingSymbols: Boolean
val enableOptimizations: Boolean
}
internal class EnvironmentVariablesUnused: EnvironmentVariables {
override val configurationBuildDir: File?
get() = null
override val debuggingSymbols: Boolean
get() = false
override val enableOptimizations: Boolean
get() = false
}
internal class EnvironmentVariablesImpl(val project: Project): EnvironmentVariables {
override val configurationBuildDir: File?
get() = System.getenv("CONFIGURATION_BUILD_DIR")?.let {
project.file(it)
}
override val debuggingSymbols: Boolean
get() = System.getenv("DEBUGGING_SYMBOLS")?.uppercase() == "YES"
override val enableOptimizations: Boolean
get() = System.getenv("KONAN_ENABLE_OPTIMIZATIONS")?.uppercase() == "YES"
}
/**
* Due to https://github.com/gradle/gradle/issues/3468 we cannot use environment
* variables in Java 9. Until Gradle API for environment variables is provided
* we use project properties instead of them. TODO: Return to using env vars when the issue is fixed.
*/
internal class EnvironmentVariablesFromProperties(val project: Project): EnvironmentVariables {
override val configurationBuildDir: File?
get() = project.findProperty(ProjectProperty.KONAN_CONFIGURATION_BUILD_DIR)?.let {
project.file(it)
}
override val debuggingSymbols: Boolean
get() = project.findProperty(ProjectProperty.KONAN_DEBUGGING_SYMBOLS)?.toString()?.uppercase(Locale.getDefault()).let {
it == "YES" || it == "TRUE"
}
override val enableOptimizations: Boolean
get() = project.findProperty(ProjectProperty.KONAN_OPTIMIZATIONS_ENABLE)?.toString()?.uppercase(Locale.getDefault()).let {
it == "YES" || it == "TRUE"
}
}
internal val Project.useEnvironmentVariables: Boolean
get() = findProperty(ProjectProperty.KONAN_USE_ENVIRONMENT_VARIABLES)?.toString()?.toBoolean() ?: false
/*
TODO: Return to using env vars when the issue is fixed.
Take into account the useEnvironmentVariables property (and may be rename it) in the following way:
if (useEnvironmentVariables) {
EnvironmentVariablesImpl(project)
} else {
EnvironmentVariablesUnused()
}
*/
internal val Project.environmentVariables: EnvironmentVariables
get() = EnvironmentVariablesFromProperties(project)
@@ -1,170 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.konan
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.NamedDomainObjectFactory
import org.gradle.api.internal.CollectionCallbackActionDecorator
import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.internal.reflect.Instantiator
import kotlin.reflect.KClass
open class KonanArtifactContainer(val project: ProjectInternal) : DefaultPolymorphicDomainObjectContainer<KonanBuildingConfig<*>>(
KonanBuildingConfig::class.java,
project.services.get(Instantiator::class.java),
CollectionCallbackActionDecorator.NOOP
) {
private inner class KonanBuildingConfigFactory<T: KonanBuildingConfig<*>>(val configClass: KClass<T>)
: NamedDomainObjectFactory<T> {
var targets: Iterable<String> = emptyList()
override fun create(name: String): T =
instantiator.newInstance(configClass.java, name, project, targets)
}
private val factories = mutableMapOf<KClass<out KonanBuildingConfig<*>>, KonanBuildingConfigFactory<*>>()
private fun <T: KonanBuildingConfig<*>> createFactory(configClass: KClass<T>) {
val factory = KonanBuildingConfigFactory(configClass)
super.registerFactory(configClass.java, factory)
factories.put(configClass, factory)
}
init {
createFactory(KonanProgram::class)
createFactory(KonanDynamic::class)
createFactory(KonanFramework::class)
createFactory(KonanLibrary::class)
createFactory(KonanBitcode::class)
createFactory(KonanInteropLibrary::class)
}
private fun determineTargets(configClass: KClass<out KonanBuildingConfig<*>>, args: Map<String, Any?>) {
val targetsArg = args["targets"]
val targets = when {
targetsArg == null -> project.konanExtension.targets
targetsArg is Iterable<*> -> targetsArg.map { it.toString() }
else -> listOf(targetsArg.toString())
}
factories[configClass]?.targets = targets
}
private fun <T: KonanBuildingConfig<*>> create(name: String,
configClass: KClass<T>,
args: Map<String, Any?>,
configureAction: Action<T>) {
determineTargets(configClass, args)
super.create(name, configClass.java, configureAction)
}
private fun <T: KonanBuildingConfig<*>> create(name: String,
configClass: KClass<T>,
args: Map<String, Any?>,
configureAction: T.() -> Unit) {
determineTargets(configClass, args)
super.create(name, configClass.java, configureAction)
}
private fun <T: KonanBuildingConfig<*>> create(name: String,
configClass: KClass<T>,
args: Map<String, Any?>) {
determineTargets(configClass, args)
super.create(name, configClass.java)
}
fun program(args: Map<String, Any?>, name: String) = create(name, KonanProgram::class, args)
fun program(args: Map<String, Any?>, name: String, configureAction: Action<KonanProgram>) =
create(name, KonanProgram::class, args, configureAction)
fun program(args: Map<String, Any?>, name: String, configureAction: KonanProgram.() -> Unit) =
create(name, KonanProgram::class, args, configureAction)
fun program(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
program(args, name) { project.configure(this, configureAction) }
fun dynamic(args: Map<String, Any?>, name: String) = create(name, KonanDynamic::class, args)
fun dynamic(args: Map<String, Any?>, name: String, configureAction: Action<KonanDynamic>) =
create(name, KonanDynamic::class, args, configureAction)
fun dynamic(args: Map<String, Any?>, name: String, configureAction: KonanDynamic.() -> Unit) =
create(name, KonanDynamic::class, args, configureAction)
fun dynamic(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
dynamic(args, name) { project.configure(this, configureAction) }
fun framework(args: Map<String, Any?>, name: String) = create(name, KonanFramework::class, args)
fun framework(args: Map<String, Any?>, name: String, configureAction: Action<KonanFramework>) =
create(name, KonanFramework::class, args, configureAction)
fun framework(args: Map<String, Any?>, name: String, configureAction: KonanFramework.() -> Unit) =
create(name, KonanFramework::class, args, configureAction)
fun framework(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
framework(args, name) { project.configure(this, configureAction) }
fun library(args: Map<String, Any?>, name: String) = create(name, KonanLibrary::class, args)
fun library(args: Map<String, Any?>, name: String, configureAction: Action<KonanLibrary>) =
create(name, KonanLibrary::class, args, configureAction)
fun library(args: Map<String, Any?>, name: String, configureAction: KonanLibrary.() -> Unit) =
create(name, KonanLibrary::class, args, configureAction)
fun library(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
library(args, name) { project.configure(this, configureAction) }
fun bitcode(args: Map<String, Any?>, name: String) = create(name, KonanBitcode::class, args)
fun bitcode(args: Map<String, Any?>, name: String, configureAction: Action<KonanBitcode>) =
create(name, KonanBitcode::class, args, configureAction)
fun bitcode(args: Map<String, Any?>, name: String, configureAction: KonanBitcode.() -> Unit) =
create(name, KonanBitcode::class, args, configureAction)
fun bitcode(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
bitcode(args, name) { project.configure(this, configureAction) }
fun interop(args: Map<String, Any?>, name: String) = create(name, KonanInteropLibrary::class, args)
fun interop(args: Map<String, Any?>, name: String, configureAction: Action<KonanInteropLibrary>) =
create(name, KonanInteropLibrary::class, args, configureAction)
fun interop(args: Map<String, Any?>, name: String, configureAction: KonanInteropLibrary.() -> Unit) =
create(name, KonanInteropLibrary::class, args, configureAction)
fun interop(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
interop(args, name) { project.configure(this, configureAction) }
fun program(name: String) = program(emptyMap(), name)
fun program(name: String, configureAction: Action<KonanProgram>) = program(emptyMap(), name, configureAction)
fun program(name: String, configureAction: KonanProgram.() -> Unit) = program(emptyMap(), name, configureAction)
fun program(name: String, configureAction: Closure<*>) = program(emptyMap(), name, configureAction)
fun dynamic(name: String) = dynamic(emptyMap(), name)
fun dynamic(name: String, configureAction: Action<KonanDynamic>) = dynamic(emptyMap(), name, configureAction)
fun dynamic(name: String, configureAction: KonanDynamic.() -> Unit) = dynamic(emptyMap(), name, configureAction)
fun dynamic(name: String, configureAction: Closure<*>) = dynamic(emptyMap(), name, configureAction)
fun framework(name: String) = framework(emptyMap(), name)
fun framework(name: String, configureAction: Action<KonanFramework>) = framework(emptyMap(), name, configureAction)
fun framework(name: String, configureAction: KonanFramework.() -> Unit) = framework(emptyMap(), name, configureAction)
fun framework(name: String, configureAction: Closure<*>) = framework(emptyMap(), name, configureAction)
fun library(name: String) = library(emptyMap(), name)
fun library(name: String, configureAction: Action<KonanLibrary>) = library(emptyMap(), name, configureAction)
fun library(name: String, configureAction: KonanLibrary.() -> Unit) = library(emptyMap(), name, configureAction)
fun library(name: String, configureAction: Closure<*>) = library(emptyMap(), name, configureAction)
fun bitcode(name: String) = bitcode(emptyMap(), name)
fun bitcode(name: String, configureAction: Action<KonanBitcode>) = bitcode(emptyMap(), name, configureAction)
fun bitcode(name: String, configureAction: KonanBitcode.() -> Unit) = bitcode(emptyMap(), name, configureAction)
fun bitcode(name: String, configureAction: Closure<*>) = bitcode(emptyMap(), name, configureAction)
fun interop(name: String) = interop(emptyMap(), name)
fun interop(name: String, configureAction: Action<KonanInteropLibrary>) = interop(emptyMap(), name, configureAction)
fun interop(name: String, configureAction: KonanInteropLibrary.() -> Unit) = interop(emptyMap(), name, configureAction)
fun interop(name: String, configureAction: Closure<*>) = interop(emptyMap(), name, configureAction)
}
@@ -1,222 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.konan
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.project.ProjectInternal
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.publish.maven.MavenPom
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanBuildingTask
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
import java.util.*
import kotlin.NoSuchElementException
/** Base class for all Kotlin/Native artifacts. */
abstract class KonanBuildingConfig<T : KonanBuildingTask>(
private val name_: String,
val type: Class<T>,
val project: ProjectInternal,
val targets: Iterable<String>
) : KonanBuildingSpec, Named {
internal val mainVariant = KonanSoftwareComponent(project)
override fun getName() = name_
protected val targetToTask = mutableMapOf<KonanTarget, TaskProvider<T>>()
fun tasks() = targetToTask.values
private val aggregateBuildTask: TaskProvider<Task>
internal var pomActions = mutableListOf<Action<MavenPom>>()
private val konanTargets: Iterable<KonanTarget>
get() = project.hostManager.toKonanTargets(targets).distinct()
init {
for (targetName in targets.distinct()) {
val konanTarget = project.hostManager.targetByName(targetName)
if (!project.hostManager.isEnabled(konanTarget)) {
project.logger.info("The target is not enabled on the current host: $targetName")
continue
}
if (!targetIsSupported(konanTarget)) {
project.logger.info("The target $targetName is not supported by the artifact $name")
continue
}
if (this[konanTarget] == null) {
val task = createTask(konanTarget)
targetToTask[konanTarget] = task
// Allow accessing targets just by their names in Groovy DSL.
(this as? ExtensionAware)?.extensions?.add(konanTarget.visibleName, task)
}
if (targetName != konanTarget.visibleName) {
createTargetAliasTaskIfDeclared(targetName)
}
}
aggregateBuildTask = createAggregateTask()
}
protected open fun generateTaskName(target: KonanTarget) =
"compileKonan${name.replaceFirstChar { it.uppercase() }}${target.visibleName.replaceFirstChar { it.uppercase() }}"
protected open fun generateAggregateTaskName() =
"compileKonan${name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}"
protected open fun generateTargetAliasTaskName(targetName: String) =
"compileKonan${name.replaceFirstChar { it.uppercase() }}${targetName.replaceFirstChar { it.uppercase() }}"
protected abstract fun generateTaskDescription(task: T): String
protected abstract fun generateAggregateTaskDescription(task: Task): String
protected abstract fun generateTargetAliasTaskDescription(task: Task, targetName: String): String
protected abstract val defaultBaseDir: File
protected open fun targetIsSupported(target: KonanTarget): Boolean = true
data class OutputPlacement(val destinationDir: File, val artifactName: String)
// There are two options for output placement.
// 1. Gradle's build directory. We use it by default, e.g. if user runs Gradle from command line.
// In this case all produced files has the same name but are placed in different directories
// depending on their targets (e.g. linux/foo.kexe and macbook/foo.kexe).
// 2. Custom path provided by IDE. In this case CONFIGURATION_BUILD_DIR environment variable should
// contain a path to a destination directory. All produced files are placed in this directory so IDE
// should take care about setting different CONFIGURATION_BUILD_DIR for different targets.
protected fun determineOutputPlacement(target: KonanTarget): OutputPlacement {
val configurationBuildDir = project.environmentVariables.configurationBuildDir
return if (configurationBuildDir != null) {
OutputPlacement(configurationBuildDir, name)
} else {
OutputPlacement(defaultBaseDir.targetSubdir(target), name)
}
}
private fun createTask(target: KonanTarget): TaskProvider<T> =
project.tasks.register(generateTaskName(target), type) {
val outputDescription = determineOutputPlacement(target)
init(this@KonanBuildingConfig, outputDescription.destinationDir, outputDescription.artifactName, target)
group = BasePlugin.BUILD_GROUP
description = generateTaskDescription(this)
}
private fun createAggregateTask(): TaskProvider<Task> =
project.tasks.register(generateAggregateTaskName()) {
group = BasePlugin.BUILD_GROUP
description = generateAggregateTaskDescription(this)
targetToTask.filter {
project.targetIsRequested(it.key)
}.forEach {
dependsOn(it.value)
}
}.also {
project.compileAllTask.configure { dependsOn(it) }
}
protected fun createTargetAliasTaskIfDeclared(targetName: String): TaskProvider<Task>? {
val canonicalTarget = project.hostManager.targetByName(targetName)
return this[canonicalTarget]?.let { canonicalBuild ->
project.tasks.register(generateTargetAliasTaskName(targetName)) {
group = BasePlugin.BUILD_GROUP
description = generateTargetAliasTaskDescription(this, targetName)
dependsOn(canonicalBuild)
}
}
}
internal operator fun get(target: KonanTarget) = targetToTask[target]
fun getByTarget(target: String) =
findByTarget(target) ?: throw NoSuchElementException("No such target for artifact $name: $target")
fun findByTarget(target: String) = this[project.hostManager.targetByName(target)]
fun getArtifactByTarget(target: String) = getByTarget(target).get().artifact
fun findArtifactByTarget(target: String) = findByTarget(target)?.get()?.artifact
// Common building DSL.
override fun artifactName(name: String) = tasks().forEach { it.configure { artifactName(name) } }
fun baseDir(dir: Any) =
tasks().forEach {
it.configure {
destinationDir(
project.file(dir).targetSubdir(konanTarget)
)
}
}
override fun libraries(closure: Closure<Unit>) =
tasks().forEach { it.configure { libraries(closure) } }
override fun libraries(action: Action<KonanLibrariesSpec>) =
tasks().forEach { it.configure { libraries(action) } }
override fun libraries(configure: KonanLibrariesSpec.() -> Unit) =
tasks().forEach { it.configure { libraries(configure) } }
override fun noDefaultLibs(flag: Boolean) =
tasks().forEach { it.configure { noDefaultLibs(flag) } }
override fun noEndorsedLibs(flag: Boolean) =
tasks().forEach { it.configure { noEndorsedLibs(flag) } }
override fun dumpParameters(flag: Boolean) =
tasks().forEach { it.configure { dumpParameters(flag) } }
override fun extraOpts(vararg values: Any) =
tasks().forEach { it.configure { extraOpts(*values) } }
override fun extraOpts(values: List<Any>) =
tasks().forEach { it.configure { extraOpts(values) } }
fun dependsOn(vararg dependencies: Any?) =
tasks().forEach { it.configure { dependsOn(*dependencies) } }
fun target(targetString: String, configureAction: T.() -> Unit) {
val target = project.hostManager.targetByName(targetString)
if (!project.hostManager.isEnabled(target)) {
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.configure(configureAction)
}
fun target(targetString: String, configureAction: Action<T>) =
target(targetString) { configureAction.execute(this) }
fun target(targetString: String, configureAction: Closure<Unit>) =
target(targetString) { project.configure(this, configureAction) }
fun pom(action: Action<MavenPom>) = pomActions + action
}
@@ -1,167 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.konan
import groovy.lang.Closure
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.project.ProjectInternal
import org.jetbrains.kotlin.gradle.plugin.tasks.*
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.KonanTarget.WASM32
import java.io.File
abstract class KonanCompileConfig<T: KonanCompileTask>(name: String,
type: Class<T>,
project: ProjectInternal,
targets: Iterable<String>)
: KonanBuildingConfig<T>(name, type, project, targets), KonanCompileSpec {
protected abstract val typeForDescription: String
override fun generateTaskDescription(task: T) =
"Build the Kotlin/Native $typeForDescription '${task.name}' for target '${task.konanTarget}'"
override fun generateAggregateTaskDescription(task: Task) =
"Build the Kotlin/Native $typeForDescription '${task.name}' for all supported and declared targets"
override fun generateTargetAliasTaskDescription(task: Task, targetName: String) =
"Build the Kotlin/Native $typeForDescription '${task.name}' for target '$targetName'"
override fun srcDir(dir: Any) = tasks().forEach { it.configure { srcDir(dir) } }
override fun srcFiles(vararg files: Any) = tasks().forEach { it.configure { srcFiles(*files) } }
override fun srcFiles(files: Collection<Any>) = tasks().forEach { it.configure { srcFiles(files) } }
override fun nativeLibrary(lib: Any) = tasks().forEach { it.configure { nativeLibrary(lib) } }
override fun nativeLibraries(vararg libs: Any) = tasks().forEach { it.configure { nativeLibraries(*libs) } }
override fun nativeLibraries(libs: FileCollection) = tasks().forEach { it.configure { nativeLibraries(libs) } }
@Deprecated("Use commonSourceSets instead", ReplaceWith("commonSourceSets(sourceSetName)"))
override fun commonSourceSet(sourceSetName: String) = tasks().forEach { it.configure { commonSourceSets(sourceSetName) } }
override fun commonSourceSets(vararg sourceSetNames: String) = tasks().forEach { it.configure { commonSourceSets(*sourceSetNames) } }
override fun enableMultiplatform(flag: Boolean) = tasks().forEach { it.configure { enableMultiplatform(flag) } }
override fun commonSrcDir(dir: Any) = tasks().forEach { it.configure { commonSrcDir(dir) } }
override fun commonSrcFiles(vararg files: Any) = tasks().forEach { it.configure { commonSrcFiles(*files) } }
override fun commonSrcFiles(files: Collection<Any>) = tasks().forEach { it.configure { commonSrcFiles(files) } }
override fun linkerOpts(values: List<String>) = tasks().forEach { it.configure { linkerOpts(values) } }
override fun linkerOpts(vararg values: String) = tasks().forEach { it.configure { linkerOpts(*values) } }
override fun enableDebug(flag: Boolean) = tasks().forEach { it.configure { enableDebug(flag) } }
override fun noStdLib(flag: Boolean) = tasks().forEach { it.configure { noStdLib(flag) } }
override fun noMain(flag: Boolean) = tasks().forEach { it.configure { noMain(flag) } }
override fun noPack(flag: Boolean) = tasks().forEach { it.configure { noPack(flag) } }
override fun enableOptimizations(flag: Boolean) = tasks().forEach { it.configure { enableOptimizations(flag) } }
override fun enableAssertions(flag: Boolean) = tasks().forEach { it.configure { enableAssertions(flag) } }
override fun entryPoint(entryPoint: String) = tasks().forEach { it.configure { entryPoint(entryPoint) } }
override fun measureTime(flag: Boolean) = tasks().forEach { it.configure { measureTime(flag) } }
override fun dependencies(closure: Closure<Unit>) = tasks().forEach { it.configure { dependencies(closure) } }
}
open class KonanProgram(name: String,
project: ProjectInternal,
targets: Iterable<String> = project.konanExtension.targets
) : KonanCompileConfig<KonanCompileProgramTask>(name,
KonanCompileProgramTask::class.java,
project,
targets
) {
override val typeForDescription: String
get() = "executable"
override val defaultBaseDir: File
get() = project.konanBinBaseDir
}
open class KonanDynamic(name: String,
project: ProjectInternal,
targets: Iterable<String> = project.konanExtension.targets)
: KonanCompileConfig<KonanCompileDynamicTask>(name,
KonanCompileDynamicTask::class.java,
project,
targets
) {
override val typeForDescription: String
get() = "dynamic library"
override val defaultBaseDir: File
get() = project.konanBinBaseDir
override fun targetIsSupported(target: KonanTarget): Boolean = target != WASM32
}
open class KonanFramework(name: String,
project: ProjectInternal,
targets: Iterable<String> = project.konanExtension.targets)
: KonanCompileConfig<KonanCompileFrameworkTask>(name,
KonanCompileFrameworkTask::class.java,
project,
targets
) {
override val typeForDescription: String
get() = "framework"
override val defaultBaseDir: File
get() = project.konanBinBaseDir
override fun targetIsSupported(target: KonanTarget): Boolean =
target.family.isAppleFamily
}
open class KonanLibrary(name: String,
project: ProjectInternal,
targets: Iterable<String> = project.konanExtension.targets)
: KonanCompileConfig<KonanCompileLibraryTask>(name,
KonanCompileLibraryTask::class.java,
project,
targets
) {
override val typeForDescription: String
get() = "library"
override val defaultBaseDir: File
get() = project.konanLibsBaseDir
}
open class KonanBitcode(name: String,
project: ProjectInternal,
targets: Iterable<String> = project.konanExtension.targets)
: KonanCompileConfig<KonanCompileBitcodeTask>(name,
KonanCompileBitcodeTask::class.java,
project,
targets
) {
override val typeForDescription: String
get() = "bitcode"
override fun generateTaskDescription(task: KonanCompileBitcodeTask) =
"Generates bitcode for the artifact '${task.name}' and target '${task.konanTarget}'"
override fun generateAggregateTaskDescription(task: Task) =
"Generates bitcode for the artifact '${task.name}' for all supported and declared targets'"
override fun generateTargetAliasTaskDescription(task: Task, targetName: String) =
"Generates bitcode for the artifact '${task.name}' for '$targetName'"
override val defaultBaseDir: File
get() = project.konanBitcodeBaseDir
}
@@ -1,84 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.konan
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.project.ProjectInternal
import org.jetbrains.kotlin.gradle.plugin.konan.KonanInteropSpec.IncludeDirectoriesSpec
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanInteropTask
import java.io.File
open class KonanInteropLibrary(name: String,
project: ProjectInternal,
targets: Iterable<String> = project.konanExtension.targets
) : KonanBuildingConfig<KonanInteropTask>(name, KonanInteropTask::class.java, project, targets),
KonanInteropSpec
{
override fun generateTaskDescription(task: KonanInteropTask) =
"Build the Kotlin/Native interop library '${task.name}' for target '${task.konanTarget}'"
override fun generateAggregateTaskDescription(task: Task) =
"Build the Kotlin/Native interop library '${task.name}' for all supported and declared targets'"
override fun generateTargetAliasTaskDescription(task: Task, targetName: String) =
"Build the Kotlin/Native interop library '${task.name}' for '$targetName'"
override val defaultBaseDir: File
get() = project.konanLibsBaseDir
// DSL
inner class IncludeDirectoriesSpecImpl: IncludeDirectoriesSpec {
override fun allHeaders(vararg includeDirs: Any) = allHeaders(includeDirs.toList())
override fun allHeaders(includeDirs: Collection<Any>) = tasks().forEach {
it.configure { this@configure.includeDirs.allHeaders(includeDirs) }
}
override fun headerFilterOnly(vararg includeDirs: Any) = headerFilterOnly(includeDirs.toList())
override fun headerFilterOnly(includeDirs: Collection<Any>) = tasks().forEach {
it.configure { this@configure.includeDirs.headerFilterOnly(includeDirs) }
}
}
val includeDirs = IncludeDirectoriesSpecImpl()
override fun defFile(file: Any) = tasks().forEach { it.configure { defFile(file) } }
override fun packageName(value: String) = tasks().forEach { it.configure { packageName(value) } }
override fun compilerOpts(vararg values: String) = tasks().forEach { it.configure { compilerOpts(*values) } }
override fun headers(vararg files: Any) = tasks().forEach { it.configure { headers(*files) } }
override fun headers(files: FileCollection) = tasks().forEach { it.configure { headers(files) } }
override fun includeDirs(vararg values: Any) = tasks().forEach { it.configure { includeDirs(*values) } }
override fun includeDirs(closure: Closure<Unit>) = includeDirs { project.configure(this, closure) }
override fun includeDirs(action: Action<IncludeDirectoriesSpec>) = includeDirs { action.execute(this) }
override fun includeDirs(configure: IncludeDirectoriesSpec.() -> Unit) = includeDirs.configure()
override fun linkerOpts(values: List<String>) = tasks().forEach { it.configure { linkerOpts(values) } }
override fun linkerOpts(vararg values: String) = linkerOpts(values.toList())
override fun link(vararg files: Any) = tasks().forEach { it.configure { link(*files) } }
override fun link(files: FileCollection) = tasks().forEach { it.configure { link(files) } }
override fun dependencies(closure: Closure<Unit>) = tasks().forEach { it.configure { dependencies(closure) }}
}
@@ -1,167 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.konan
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.Internal
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanArtifactWithLibrariesTask
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanBuildingTask
import org.jetbrains.kotlin.konan.*
import org.jetbrains.kotlin.konan.library.defaultResolver
import org.jetbrains.kotlin.konan.library.impl.KonanLibraryImpl
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.SearchPathResolver
import java.io.File
open class KonanLibrariesSpec(
@Internal val task: KonanArtifactWithLibrariesTask,
@Internal val project: Project
) {
@InputFiles val files = mutableSetOf<FileCollection>()
@Input val namedKlibs = mutableSetOf<String>()
@Internal val artifacts = mutableListOf<KonanBuildingTask>()
val artifactFiles: List<File>
@InputFiles get() = artifacts.map { it.artifact }
@Internal val explicitRepos = mutableSetOf<File>()
val repos: Set<File>
@Input get() = mutableSetOf<File>().apply {
addAll(explicitRepos)
add(task.destinationDir) // TODO: Check if task is a library - create a Library interface
add(task.destinationDir) // TODO: Check if task is a library - create a Library interface
add(task.project.konanLibsBaseDir.targetSubdir(target))
addAll(artifacts.flatMap { it.libraries.repos })
addAll(task.platformConfiguration.files.map { it.parentFile })
}
val target: KonanTarget
@Internal get() = task.konanTarget
private val friendsTasks = mutableSetOf<KonanBuildingTask>()
@get:Internal // Taken into account by tasks's dependOn.
val friends: Set<File> get() = mutableSetOf<File>().apply {
addAll(friendsTasks.map { it.artifact })
}
// DSL Methods
/** Absolute path */
fun file(file: Any) = files.add(project.files(file))
fun files(vararg files: Any) = this.files.addAll(files.map { project.files(it) })
fun files(collection: FileCollection) = this.files.add(collection)
/** 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)
private fun klibInternal(lib: KonanBuildingConfig<*>, friend: Boolean) {
if (!(lib is KonanLibrary || lib is KonanInteropLibrary)) {
throw InvalidUserDataException("Config ${lib.name} is not a library")
}
val libraryTask = lib[target]?.get() ?:
throw InvalidUserDataException("Library ${lib.name} has no target ${target.visibleName}")
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)
if (friend) friendsTasks.add(libraryTask)
}
/** Direct link to a config */
fun klib(lib: KonanLibrary) = klibInternal(lib, false)
/** Direct link to a config */
fun klib(lib: KonanInteropLibrary) = klibInternal(lib, false)
/** Artifact in the specified project by name */
fun artifact(libraryProject: Project, name: String, friend: Boolean) {
project.evaluationDependsOn(libraryProject)
klibInternal(libraryProject.konanArtifactsContainer.getByName(name), friend)
}
fun artifact(libraryProject: Project, name: String) = artifact(libraryProject, name, false)
/** Artifact in the current project by name */
fun artifact(name: String, friend: Boolean) = artifact(project, name, friend)
fun artifact(name: String) = artifact(project, name, false)
/** Artifact by direct link */
fun artifact(artifact: KonanLibrary) = klib(artifact)
/** Direct link to a config */
fun artifact(artifact: KonanInteropLibrary) = klib(artifact)
private fun allArtifactsFromInternal(libraryProjects: Array<out Project>,
filter: (KonanBuildingConfig<*>) -> Boolean) {
libraryProjects.forEach { prj ->
project.evaluationDependsOn(prj)
prj.konanArtifactsContainer.filter(filter).forEach {
klibInternal(it, false)
}
}
}
/** 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 interop libraries from the projects by direct references */
fun allInteropLibrariesFrom(vararg libraryProjects: Project) = allArtifactsFromInternal(libraryProjects) {
it is KonanInteropLibrary
}
/** Add repo for library search */
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) }
private fun Project.evaluationDependsOn(another: Project) {
if (this != another) { evaluationDependsOn(another.path) }
}
fun asFiles(): List<File> = asFiles(
defaultResolver(
repos.map { it.absolutePath },
task.konanTarget,
Distribution(project.konanHome)
)
)
fun asFiles(resolver: SearchPathResolver<*>): List<File> = mutableListOf<File>().apply {
files.flatMapTo(this) { it.files }
addAll(artifactFiles)
addAll(task.platformConfiguration.files)
namedKlibs.mapTo(this) { project.file(resolver.resolve(it).libraryFile.absolutePath) }
}
}
@@ -1,456 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.konan
import groovy.lang.Closure
import org.codehaus.groovy.runtime.GStringImpl
import org.gradle.api.*
import org.gradle.api.component.ComponentWithVariants
import org.gradle.api.component.SoftwareComponent
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.component.SoftwareComponentInternal
import org.gradle.api.internal.component.UsageContext
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.Provider
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal
import org.gradle.api.tasks.Exec
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.jvm.toolchain.JavaToolchainSpec
import org.gradle.language.cpp.internal.NativeVariantIdentity
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin.Companion.COMPILE_ALL_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.tasks.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.buildDistribution
import org.jetbrains.kotlin.konan.target.customerDistribution
import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.konan.util.DependencyDirectories
import java.io.File
import javax.inject.Inject
/**
* We use the following properties:
* org.jetbrains.kotlin.native.home - directory where compiler is located (aka dist in konan project output).
* org.jetbrains.kotlin.native.version - a konan compiler version for downloading.
* konan.build.targets - list of targets to build (by default all the declared targets are built).
* konan.jvmArgs - additional args to be passed to a JVM executing the compiler/cinterop tool.
*/
internal fun Project.warnAboutDeprecatedProperty(property: KonanPlugin.ProjectProperty) =
property.deprecatedPropertyName?.let { deprecated ->
if (project.hasProperty(deprecated)) {
logger.warn("Project property '$deprecated' is deprecated. Use '${property.propertyName}' instead.")
}
}
internal fun Project.hasProperty(property: KonanPlugin.ProjectProperty) = with(property) {
when {
hasProperty(propertyName) -> true
deprecatedPropertyName != null && hasProperty(deprecatedPropertyName) -> true
else -> false
}
}
internal fun Project.findProperty(property: KonanPlugin.ProjectProperty): Any? = with(property) {
return findProperty(propertyName) ?: deprecatedPropertyName?.let { findProperty(it) }
}
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) =
findProperty(property) ?: defaultValue
internal fun Project.setProperty(property: KonanPlugin.ProjectProperty, value: Any) {
extensions.extraProperties.set(property.propertyName, value)
}
// konanHome extension is set by downloadKonanCompiler task.
internal val Project.konanHome: String
get() {
return project.kotlinNativeDist.absolutePath
}
// Used only for distribution downloading that is not used in the project and should be removed
internal val Project.konanVersion: String
get() = project.findProperty(KonanPlugin.ProjectProperty.KONAN_VERSION)
?.toString()
?: project.version.toString()
internal val Project.konanBuildRoot get() = buildDir.resolve("konan")
internal val Project.konanBinBaseDir get() = konanBuildRoot.resolve("bin")
internal val Project.konanLibsBaseDir get() = konanBuildRoot.resolve("libs")
internal val Project.konanBitcodeBaseDir get() = konanBuildRoot.resolve("bitcode")
internal fun File.targetSubdir(target: KonanTarget) = resolve(target.visibleName)
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: KonanArtifactContainer
get() = extensions.getByName(KonanPlugin.ARTIFACTS_CONTAINER_NAME) as KonanArtifactContainer
// TODO: The Kotlin/Native compiler is downloaded manually by a special task so the compilation tasks
// are configured without the compile distribution. After target management refactoring
// we need .properties files from the distribution to configure targets. This is worked around here
// by using HostManager instead of PlatformManager. But we need to download the compiler at the configuration
// stage (e.g. by getting it from maven as a plugin dependency) and bring back the PlatformManager here.
internal val Project.hostManager: HostManager
get() = findProperty("hostManager") as HostManager? ?:
if (hasProperty("org.jetbrains.kotlin.native.experimentalTargets"))
HostManager(buildDistribution(rootProject.rootDir.absolutePath), true)
else
HostManager(customerDistribution(konanHome))
internal val Project.konanTargets: List<KonanTarget>
get() = hostManager.toKonanTargets(konanExtension.targets)
.filter{ hostManager.isEnabled(it) }
.distinct()
@Suppress("UNCHECKED_CAST")
internal val Project.konanExtension: KonanExtension
get() = extensions.getByName(KonanPlugin.KONAN_EXTENSION_NAME) as KonanExtension
internal val Project.requestedTargets
get() = findProperty(KonanPlugin.ProjectProperty.KONAN_BUILD_TARGETS)?.let {
it.toString().trim().split("\\s+".toRegex())
}.orEmpty()
internal val Project.jvmArgs
get() = (findProperty(KonanPlugin.ProjectProperty.KONAN_JVM_ARGS) as String?)?.split("\\s+".toRegex()).orEmpty()
internal val Project.compileAllTask
get() = getOrRegisterTask(COMPILE_ALL_TASK_NAME)
internal fun Project.targetIsRequested(target: KonanTarget): Boolean {
val targets = requestedTargets
return (targets.isEmpty() || targets.contains(target.visibleName) || targets.contains("all"))
}
/**
* Looks for task with given name in the given project.
* If such task isn't found, will register it. Returns registered/found task.
*/
private fun Project.getOrRegisterTask(name: String): TaskProvider<out Task> = if (tasks.names.contains(name)) {
tasks.named(name)
} else {
tasks.register(name, DefaultTask::class.java)
}
internal fun Project.konanCompilerName(): String =
"kotlin-native-${project.simpleOsName}-${project.konanVersion}"
internal fun Project.konanCompilerDownloadDir(): String =
DependencyDirectories.localKonanDir.resolve(project.konanCompilerName()).absolutePath
// region Useful extensions and functions ---------------------------------------
internal fun MutableList<String>.addArg(parameter: String, value: String) {
add(parameter)
add(value)
}
internal fun MutableList<String>.addArgs(parameter: String, values: Iterable<String>) {
values.forEach {
addArg(parameter, it)
}
}
internal fun MutableList<String>.addArgIfNotNull(parameter: String, value: String?) {
if (value != null) {
addArg(parameter, value)
}
}
internal fun MutableList<String>.addKey(key: String, enabled: Boolean) {
if (enabled) {
add(key)
}
}
internal fun MutableList<String>.addFileArgs(parameter: String, values: FileCollection) {
values.files.forEach {
addArg(parameter, it.canonicalPath)
}
}
internal fun MutableList<String>.addFileArgs(parameter: String, values: Collection<FileCollection>) {
values.forEach {
addFileArgs(parameter, it)
}
}
// 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 -> with(task) {
println()
println("Compilation task: $name")
println("destinationDir : $destinationDir")
println("artifact : ${artifact.canonicalPath}")
println("srcFiles : ${srcFiles.dump()}")
println("produce : $produce")
println("libraries : ${libraries.files.dump()}")
println(" : ${libraries.artifacts.map {
it.artifact.canonicalPath
}.dump()}")
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("noEndorsedLibs : $noEndorsedLibs")
println("target : $target")
println("languageVersion : $languageVersion")
println("apiVersion : $apiVersion")
println("konanVersion : ${KotlinVersion.CURRENT}")
println("konanHome : $konanHome")
println()
}
is KonanInteropTask -> with(task) {
println()
println("Stub generation task: $name")
println("destinationDir : $destinationDir")
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("packageName : $packageName")
println("compilerOpts : $compilerOpts")
println("linkerOpts : $linkerOpts")
println("headers : ${headers.dump()}")
println("linkFiles : ${linkFiles.dump()}")
println("konanVersion : ${KotlinVersion.CURRENT}")
println("konanHome : $konanHome")
println()
}
else -> {
println("Unsupported task.")
}
}
}
open class KonanExtension {
var targets = mutableListOf("host")
var languageVersion: String? = null
var apiVersion: String? = null
var jvmArgs = mutableListOf<String>()
}
open class KonanSoftwareComponent(val project: ProjectInternal?): SoftwareComponentInternal, ComponentWithVariants {
private val usages = mutableSetOf<UsageContext>()
override fun getUsages(): MutableSet<out UsageContext> = usages
private val variants = mutableSetOf<SoftwareComponent>()
override fun getName() = "main"
override fun getVariants(): Set<SoftwareComponent> = variants
fun addVariant(component: SoftwareComponent) = variants.add(component)
}
class KonanPlugin @Inject constructor(private val registry: ToolingModelBuilderRegistry)
: Plugin<ProjectInternal> {
enum class ProjectProperty(val propertyName: String, val deprecatedPropertyName: String? = null) {
KONAN_HOME ("org.jetbrains.kotlin.native.home", "konan.home"),
KONAN_VERSION ("org.jetbrains.kotlin.native.version"),
KONAN_BUILD_TARGETS ("konan.build.targets"),
KONAN_JVM_ARGS ("konan.jvmArgs"),
KONAN_JVM_LAUNCHER ("konan.javaLauncher"),
KONAN_USE_ENVIRONMENT_VARIABLES("konan.useEnvironmentVariables"),
DOWNLOAD_COMPILER ("download.compiler"),
// Properties used instead of env vars until https://github.com/gradle/gradle/issues/3468 is fixed.
// TODO: Remove them when an API for env vars is provided.
KONAN_CONFIGURATION_BUILD_DIR ("konan.configuration.build.dir"),
KONAN_DEBUGGING_SYMBOLS ("konan.debugging.symbols"),
KONAN_OPTIMIZATIONS_ENABLE ("konan.optimizations.enable"),
}
companion object {
internal const val ARTIFACTS_CONTAINER_NAME = "konanArtifacts"
internal const val COMPILE_ALL_TASK_NAME = "compileKonan"
internal const val KONAN_EXTENSION_NAME = "konan"
internal val REQUIRED_GRADLE_VERSION = GradleVersion.version("6.7")
}
private fun Project.cleanKonan() = project.tasks.withType(KonanBuildingTask::class.java).forEach {
project.delete(it.artifact)
}
private fun checkGradleVersion() = GradleVersion.current().let { current ->
check(current >= REQUIRED_GRADLE_VERSION) {
"Kotlin/Native Gradle plugin is incompatible with this version of Gradle.\n" +
"The minimal required version is $REQUIRED_GRADLE_VERSION\n" +
"Current version is ${current}"
}
}
private lateinit var konanJvmLauncher: JavaLauncher
private fun getJavaLauncher(project: Project): Provider<JavaLauncher> = project.providers.provider {
if (!::konanJvmLauncher.isInitialized) {
val toolchain = project.extensions.getByType(JavaPluginExtension::class.java).toolchain
val service = project.extensions.getByType(JavaToolchainService::class.java)
konanJvmLauncher = try {
service.launcherFor(toolchain).get()
} catch (ex: GradleException) {
// If the JDK that was set is not available get the JDK 11 as a default
service.launcherFor(object : Action<JavaToolchainSpec> {
override fun execute(toolchainSpec: JavaToolchainSpec) {
toolchainSpec.languageVersion.set(JavaLanguageVersion.of(11)) // FIXME: not resolved from buildSrc JdkMajorVersion.JDK_11_0.majorVersion))
}
}).get()
}
}
konanJvmLauncher
}
override fun apply(project: ProjectInternal) {
checkGradleVersion()
project.plugins.apply("base")
project.plugins.apply("java")
// Create necessary tasks and extensions.
project.extensions.create(KONAN_EXTENSION_NAME, KonanExtension::class.java)
val container = project.extensions.create(
KonanArtifactContainer::class.java,
ARTIFACTS_CONTAINER_NAME,
KonanArtifactContainer::class.java,
project
)
project.setProperty(ProjectProperty.KONAN_JVM_LAUNCHER, getJavaLauncher(project))
project.warnAboutDeprecatedProperty(ProjectProperty.KONAN_HOME)
// Set additional project properties like org.jetbrains.kotlin.native.home, konan.build.targets etc.
if (!project.useCustomDist) {
project.setProperty(ProjectProperty.KONAN_HOME, project.konanCompilerDownloadDir())
project.setProperty(ProjectProperty.DOWNLOAD_COMPILER, true)
}
// Register and set up aggregate building tasks.
val compileKonanTask = project.getOrRegisterTask(COMPILE_ALL_TASK_NAME).configure {
group = BasePlugin.BUILD_GROUP
description = "Compiles all the Kotlin/Native artifacts"
}
project.tasks.named("build").configure {
dependsOn(compileKonanTask)
}
project.tasks.named("clean").configure {
doLast { project.cleanKonan() }
}
project.afterEvaluate {
project.tasks
.withType(KonanCompileProgramTask::class.java)
.forEach { task ->
val isCrossCompile = (task.target != HostManager.host.visibleName)
if (!isCrossCompile && !project.hasProperty("konanNoRun"))
task.runTask = project.tasks.register(
"run${task.artifactName.replaceFirstChar { it.uppercase() }}", Exec::class.java) {
group = "run"
dependsOn(task)
val artifactPathClosure = object : Closure<String>(this) {
override fun call() = task.artifactPath
}
// Use GString to evaluate a path to the artifact lazily thus allow changing it at configuration phase.
val lazyArtifactPath = GStringImpl(arrayOf(artifactPathClosure), arrayOf(""))
executable(lazyArtifactPath)
// Add values passed in the runArgs project property as arguments.
argumentProviders.add(task.RunArgumentProvider())
}
}
}
val runTask = project.getOrRegisterTask("run")
project.afterEvaluate {
project.konanArtifactsContainer
.filterIsInstance(KonanProgram::class.java)
.forEach { program ->
program.tasks().forEach { compile ->
compile.configure { this@configure.runTask?.let { runTask.configure { dependsOn(it) } } }
}
}
}
// Enable multiplatform support
project.pluginManager.apply(KotlinNativePlatformPlugin::class.java)
project.afterEvaluate {
project.pluginManager.withPlugin("maven-publish") {
container.all { buildingConfig ->
val konanSoftwareComponent = buildingConfig.mainVariant
project.extensions.configure(PublishingExtension::class.java) {
val builtArtifact = buildingConfig.name
val mavenPublication = publications.maybeCreate(builtArtifact, MavenPublication::class.java)
mavenPublication.apply {
artifactId = builtArtifact
groupId = project.group.toString()
from(konanSoftwareComponent)
}
(mavenPublication as MavenPublicationInternal).publishWithOriginalFileName()
buildingConfig.pomActions.forEach {
mavenPublication.pom(it)
}
}
project.extensions.configure(PublishingExtension::class.java) {
for (v in konanSoftwareComponent.variants) {
this@configure.publications.create(v.name, MavenPublication::class.java) {
val coordinates = (v as NativeVariantIdentity).coordinates
project.logger.info("variant with coordinates($coordinates) and module: ${coordinates.module}")
artifactId = coordinates.module.name
groupId = coordinates.group
version = coordinates.version
from(v)
(this as MavenPublicationInternal).publishWithOriginalFileName()
buildingConfig.pomActions.forEach {
pom(it)
}
}
}
}
true
}
}
}
}
}
@@ -1,117 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.konan
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.file.FileCollection
interface KonanArtifactSpec {
fun artifactName(name: String)
}
interface KonanArtifactWithLibrariesSpec: KonanArtifactSpec {
fun libraries(closure: Closure<Unit>)
fun libraries(action: Action<KonanLibrariesSpec>)
fun libraries(configure: KonanLibrariesSpec.() -> Unit)
fun noDefaultLibs(flag: Boolean)
fun noEndorsedLibs(flag: Boolean)
fun dependencies(closure: Closure<Unit>)
}
interface KonanBuildingSpec: KonanArtifactWithLibrariesSpec {
fun dumpParameters(flag: Boolean)
fun extraOpts(vararg values: Any)
fun extraOpts(values: List<Any>)
}
interface KonanCompileSpec: KonanBuildingSpec {
fun srcDir(dir: Any)
fun srcFiles(vararg files: Any)
fun srcFiles(files: Collection<Any>)
// DSL. Native libraries.
fun nativeLibrary(lib: Any)
fun nativeLibraries(vararg libs: Any)
fun nativeLibraries(libs: FileCollection)
// DSL. Multiplatform projects
fun enableMultiplatform(flag: Boolean)
// TODO: Get rid of commonSourceSet in 0.7
@Deprecated("Use commonSourceSets instead", ReplaceWith("commonSourceSets(sourceSetName)"))
fun commonSourceSet(sourceSetName: String)
fun commonSourceSets(vararg sourceSetNames: String)
fun commonSrcDir(dir: Any)
fun commonSrcFiles(vararg files: Any)
fun commonSrcFiles(files: Collection<Any>)
// DSL. Other parameters.
fun linkerOpts(vararg values: String)
fun linkerOpts(values: List<String>)
fun enableDebug(flag: Boolean)
fun noStdLib(flag: Boolean)
fun noMain(flag: Boolean)
fun noPack(flag: Boolean)
fun enableOptimizations(flag: Boolean)
fun enableAssertions(flag: Boolean)
fun entryPoint(entryPoint: String)
fun measureTime(flag: Boolean)
}
interface KonanInteropSpec: KonanBuildingSpec {
interface IncludeDirectoriesSpec {
fun allHeaders(vararg includeDirs: Any)
fun allHeaders(includeDirs: Collection<Any>)
fun headerFilterOnly(vararg includeDirs: Any)
fun headerFilterOnly(includeDirs: Collection<Any>)
}
fun defFile(file: Any)
fun packageName(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 includeDirs(closure: Closure<Unit>)
fun includeDirs(action: Action<IncludeDirectoriesSpec>)
fun includeDirs(configure: IncludeDirectoriesSpec.() -> Unit)
fun linkerOpts(vararg values: String)
fun linkerOpts(values: List<String>)
fun link(vararg files: Any)
fun link(files: FileCollection)
}
@@ -1,192 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.konan
import kotlinBuildProperties
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin.ProjectProperty.KONAN_HOME
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.nio.file.Files
import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.konan.properties.resolvablePropertyString
import org.jetbrains.kotlin.konan.util.DependencyDirectories
import java.io.File
import java.util.Properties
import org.jetbrains.kotlin.compilerRunner.KotlinToolRunner
import org.jetbrains.kotlin.konan.target.AbstractToolConfig
import java.net.URLClassLoader
import java.util.concurrent.ConcurrentHashMap
internal interface KonanToolRunner {
fun run(args: List<String>)
}
internal fun KonanToolRunner.run(vararg args: String) = run(args.toList())
private const val runFromDaemonPropertyName = "kotlin.native.tool.runFromDaemon"
@Suppress("DEPRECATION") // calling KotlinToolRunner(project) constructor is deprecated
internal abstract class KonanCliRunner(
protected val toolName: String,
project: Project,
val additionalJvmArgs: List<String> = emptyList(),
val konanHome: String = project.konanHome
) : KotlinToolRunner(project), KonanToolRunner {
final override val displayName get() = toolName
final override val mainClass get() = "org.jetbrains.kotlin.cli.utilities.MainKt"
final override val daemonEntryPoint get() = "daemonMain"
override val mustRunViaExec get() = false.also { System.setProperty(runFromDaemonPropertyName, "true") }
final override val execSystemPropertiesBlacklist: Set<String>
get() = super.execSystemPropertiesBlacklist + runFromDaemonPropertyName
// We need to unset some environment variables which are set by XCode and may potentially affect the tool executed.
final override val execEnvironmentBlacklist: Set<String> by lazy {
HashSet<String>().also { collector ->
KonanPlugin::class.java.getResourceAsStream("/env_blacklist")?.let { stream ->
stream.reader().use { r -> r.forEachLine { collector.add(it) } }
}
}
}
final override val execSystemProperties by lazy { mapOf("konan.home" to konanHome) }
final override val classpath by lazy {
project.fileTree("$konanHome/konan/lib/").apply {
include("trove4j.jar")
include("kotlin-native-compiler-embeddable.jar")
}.files
}
final override fun checkClasspath() =
check(classpath.isNotEmpty()) {
"""
Classpath of the tool is empty: $toolName
Probably the '${KONAN_HOME.propertyName}' project property contains an incorrect path.
Please change it to the compiler root directory and rerun the build.
""".trimIndent()
}
data class IsolatedClassLoaderCacheKey(val classpath: Set<File>)
// TODO: can't we use this for other implementations too?
final override val isolatedClassLoaderCacheKey get() = IsolatedClassLoaderCacheKey(classpath)
// A separate map for each build for automatic cleaning the daemon after the build have finished.
@Suppress("UNCHECKED_CAST")
final override val isolatedClassLoaders = project.project(":kotlin-native").ext["toolClassLoadersMap"] as ConcurrentHashMap<Any, URLClassLoader>
override fun transformArgs(args: List<String>) = listOf(toolName) + args
final override fun getCustomJvmArgs() = additionalJvmArgs
}
/** Kotlin/Native compiler runner */
internal class KonanCliCompilerRunner(
project: Project,
additionalJvmArgs: List<String> = emptyList(),
val useArgFile: Boolean = true,
konanHome: String = project.konanHome
) : KonanCliRunner("konanc", project, additionalJvmArgs, konanHome) {
override fun transformArgs(args: List<String>): List<String> {
if (!useArgFile) return super.transformArgs(args)
val argFile = Files.createTempFile(/* prefix = */ "konancArgs", /* suffix = */ ".lst").toFile().apply { deleteOnExit() }
argFile.printWriter().use { w ->
for (arg in args) {
val escapedArg = arg
.replace("\\", "\\\\")
.replace("\"", "\\\"")
w.println("\"$escapedArg\"")
}
}
return listOf(toolName, "@${argFile.absolutePath}")
}
}
private val load0 = Runtime::class.java.getDeclaredMethod("load0", Class::class.java, String::class.java).also {
it.isAccessible = true
}
internal class CliToolConfig(konanHome: String, target: String) : AbstractToolConfig(konanHome, target, emptyMap()) {
override fun loadLibclang() {
// Load libclang into the system class loader. This is needed to allow developers to make changes
// in the tooling infrastructure without having to stop the daemon (otherwise libclang might end up
// loaded in two different class loaders which is not allowed by the JVM).
load0.invoke(Runtime.getRuntime(), String::class.java, libclang)
}
}
/** Kotlin/Native C-interop tool runner */
internal class KonanCliInteropRunner(
private val project: Project,
additionalJvmArgs: List<String> = emptyList(),
konanHome: String = project.konanHome
) : KonanCliRunner("cinterop", project, additionalJvmArgs, konanHome) {
private val projectDir = project.projectDir.toString()
override val mustRunViaExec: Boolean
get() = if (project.kotlinBuildProperties.getBoolean("kotlin.native.allowRunningCinteropInProcess")) {
super.mustRunViaExec
} else {
true
}
override fun transformArgs(args: List<String>): List<String> {
return super.transformArgs(args) + listOf("-Xproject-dir", projectDir)
}
override val execEnvironment by lazy {
val result = mutableMapOf<String, String>()
result.putAll(super.execEnvironment)
result["LIBCLANG_DISABLE_CRASH_RECOVERY"] = "1"
llvmExecutablesPath?.let {
result["PATH"] = "$it;${System.getenv("PATH")}"
}
result
}
fun init(target: String) {
CliToolConfig(konanHome, target).prepare()
}
private val llvmExecutablesPath: String? by lazy {
if (HostManager.host == KonanTarget.MINGW_X64) {
// TODO: Read it from Platform properties when it is accessible.
val konanProperties = Properties().apply {
project.file("$konanHome/konan/konan.properties").inputStream().use(::load)
}
konanProperties.resolvablePropertyString("llvmHome.mingw_x64")?.let { toolchainDir ->
DependencyDirectories.defaultDependenciesRoot
.resolve("$toolchainDir/bin")
.absolutePath
}
} else
null
}
}
internal class KonanKlibRunner(
project: Project,
additionalJvmArgs: List<String> = emptyList(),
konanHome: String = project.konanHome
) : KonanCliRunner("klib", project, additionalJvmArgs, konanHome)
@@ -1,38 +0,0 @@
package org.jetbrains.kotlin.gradle.plugin.konan
import org.gradle.api.Named
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformImplementationPluginBase
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileTask
import javax.inject.Inject
open class KotlinNativePlatformPlugin: KotlinPlatformImplementationPluginBase("native") {
private val Project.konanMultiplatformTasks: Collection<KonanCompileTask>
get() = tasks.withType(KonanCompileTask::class.java).filter { it.enableMultiplatform }
open class RequestedCommonSourceSet @Inject constructor(private val name: String): Named {
override fun getName() = name
}
override fun addCommonSourceSetToPlatformSourceSet(commonSourceSet: Named, platformProject: Project) {
val commonSourceSetName = commonSourceSet.name
platformProject.konanMultiplatformTasks
.filter { it.commonSourceSets.contains(commonSourceSetName) }
.forEach { task: KonanCompileTask ->
getKotlinSourceDirectorySetSafe(commonSourceSet)!!.srcDirs.forEach {
task.commonSrcDir(it)
}
}
}
override fun namedSourceSetsContainer(project: Project): NamedDomainObjectContainer<*> =
project.container(RequestedCommonSourceSet::class.java).apply {
project.konanMultiplatformTasks.forEach { task ->
task.commonSourceSets.forEach { maybeCreate(it) }
}
}
}
@@ -1,187 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.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.artifacts.*
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.attributes.Usage
import org.gradle.api.capabilities.Capability
import org.gradle.api.internal.component.DefaultSoftwareComponentVariant
import org.gradle.api.internal.component.UsageContext
import org.gradle.api.internal.tasks.DefaultTaskDependency
import org.gradle.api.tasks.*
import org.gradle.language.cpp.CppBinary
import org.gradle.nativeplatform.Linkage
import org.jetbrains.kotlin.gradle.plugin.experimental.internal.compatibleVariantIdentity
import org.jetbrains.kotlin.gradle.plugin.konan.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
import java.util.*
internal val Project.host
get() = HostManager.host.visibleName
internal val Project.simpleOsName
get() = HostManager.platformName()
/** A task with a KonanTarget specified. */
abstract class KonanTargetableTask: DefaultTask() {
@get:Input
val konanTargetName: String
get() = konanTarget.name
@get:Internal
internal lateinit var konanTarget: KonanTarget
internal open fun init(target: KonanTarget) {
this.konanTarget = target
}
val isCrossCompile: Boolean
@Internal get() = (konanTarget != HostManager.host)
val target: String
@Internal get() = konanTarget.visibleName
}
/** A task building an artifact. */
abstract class KonanArtifactTask: KonanTargetableTask(), KonanArtifactSpec {
open val artifact: File
@OutputFile get() = destinationDir.resolve(artifactFullName)
@Internal lateinit var destinationDir: File
@Internal lateinit var artifactName: String
@Internal lateinit var platformConfiguration: Configuration
@Internal lateinit var configuration: Configuration
protected val artifactFullName: String
@Internal get() = "$artifactPrefix$artifactName$artifactSuffix"
val artifactPath: String
@Internal get() = artifact.canonicalPath
protected abstract val artifactSuffix: String
@Internal get
protected abstract val artifactPrefix: String
@Internal get
internal open fun init(config: KonanBuildingConfig<*>, destinationDir: File, artifactName: String, target: KonanTarget) {
super.init(target)
this.destinationDir = destinationDir
this.artifactName = artifactName
configuration = project.configurations.maybeCreate("artifact$artifactName")
platformConfiguration = project.configurations.create("artifact${artifactName}_${target.name}")
platformConfiguration.extendsFrom(configuration)
platformConfiguration.attributes{
attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, Usage.NATIVE_LINK))
attribute(CppBinary.LINKAGE_ATTRIBUTE, Linkage.STATIC)
attribute(CppBinary.OPTIMIZED_ATTRIBUTE, false)
attribute(CppBinary.DEBUGGABLE_ATTRIBUTE, false)
attribute(Attribute.of("org.gradle.native.kotlin.platform", String::class.java), target.name)
}
val artifactNameWithoutSuffix = artifact.name.removeSuffix("$artifactSuffix")
project.pluginManager.withPlugin("maven-publish") {
platformConfiguration.artifacts.add(object: PublishArtifact {
override fun getName(): String = artifactNameWithoutSuffix
override fun getExtension() = if (artifactSuffix.startsWith('.')) artifactSuffix.substring(1) else artifactSuffix
override fun getType() = artifactSuffix
override fun getClassifier():String? = target.name
override fun getFile() = artifact
override fun getDate() = Date(artifact.lastModified())
override fun getBuildDependencies(): TaskDependency =
DefaultTaskDependency().apply { add(this@KonanArtifactTask) }
})
val objectFactory = project.objects
val linkUsage = objectFactory.named(Usage::class.java, Usage.NATIVE_LINK)
val konanSoftwareComponent = config.mainVariant
val variantName = "${artifactNameWithoutSuffix}_${target.name}"
val context = DefaultSoftwareComponentVariant(
"${variantName}Link",
platformConfiguration.attributes,
platformConfiguration.allArtifacts,
mutableSetOf(),
mutableSetOf(),
mutableSetOf(),
emptySet(),
)
konanSoftwareComponent.addVariant(
compatibleVariantIdentity(
project,
variantName,
project.provider{ artifactName },
project.provider{ project.group.toString() },
project.provider{ project.version.toString() },
false,
false,
target,
context,
null
)
)
}
}
fun dependencies(closure: Closure<Unit>) {
if (konanTarget in project.konanTargets)
project.dependencies(closure)
}
// DSL.
override fun artifactName(name: String) {
artifactName = name
}
fun destinationDir(dir: Any) {
destinationDir = project.file(dir)
}
}
/** Task building an artifact with libraries */
abstract class KonanArtifactWithLibrariesTask: KonanArtifactTask(), KonanArtifactWithLibrariesSpec {
@Nested
val libraries = KonanLibrariesSpec(this, project)
@Input
var noDefaultLibs = false
@Input
var noEndorsedLibs = false
// DSL
override fun libraries(closure: Closure<Unit>) = libraries { project.configure(this, 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
}
override fun noEndorsedLibs(flag: Boolean) {
noEndorsedLibs = flag
}
}
@@ -1,60 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.tasks
import org.gradle.api.tasks.Console
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.plugin.konan.*
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
import org.jetbrains.kotlin.dependsOnDist
/** Base class for both interop and compiler tasks. */
abstract class KonanBuildingTask: KonanArtifactWithLibrariesTask(), KonanBuildingSpec {
@get:Internal
internal abstract val toolRunner: KonanToolRunner
override fun init(config: KonanBuildingConfig<*>, destinationDir: File, artifactName: String, target: KonanTarget) {
super.init(config, destinationDir, artifactName, target)
}
@Console
var dumpParameters: Boolean = false
@Input
val extraOpts = mutableListOf<String>()
val konanHome
@Input get() = project.konanHome
@TaskAction
abstract fun run()
// 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() })
}
}
@@ -1,106 +0,0 @@
package org.jetbrains.kotlin.gradle.plugin.konan.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.konan.KonanCliCompilerRunner
import org.jetbrains.kotlin.gradle.plugin.konan.konanHome
import org.jetbrains.kotlin.konan.library.defaultResolver
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.konan.target.PlatformManager
import org.jetbrains.kotlin.library.uniqueName
import org.jetbrains.kotlin.*
import java.io.File
enum class KonanCacheKind(val outputKind: CompilerOutputKind) {
STATIC(CompilerOutputKind.STATIC_CACHE),
DYNAMIC(CompilerOutputKind.DYNAMIC_CACHE)
}
open class KonanCacheTask: DefaultTask() {
@get:InputDirectory
var originalKlib: File? = null
@get:Input
lateinit var klibUniqName: String
@get:Input
lateinit var cacheRoot: String
@get:Input
lateinit var target: String
@get:Internal
// TODO: Reuse NativeCacheKind from Big Kotlin plugin when it is available.
val cacheDirectory: File
get() = File("$cacheRoot/$target-g$cacheKind")
@get:OutputDirectory
val cacheFile: File
get() = cacheDirectory.resolve(if (makePerFileCache) "${klibUniqName}-per-file-cache" else "${klibUniqName}-cache")
/**
* Note: we can't use this function instead of [klibUniqName] in [cacheFile],
* because the latter is `@OutputDirectory`, so Gradle can call it even before
* the task dependencies are finished, and [originalKlib] might be not build yet.
*/
private fun readKlibUniqNameFromManifest(): String {
val konanHome = compilerDistributionPath.get().absolutePath
val resolver = defaultResolver(
emptyList(),
PlatformManager(konanHome).targetByName(target),
Distribution(konanHome)
)
return resolver.resolve(originalKlib!!.absolutePath).uniqueName
}
@get:Input
var cacheKind: KonanCacheKind = KonanCacheKind.STATIC
@get:Input
var makePerFileCache: Boolean = false
@get:Input
/** Path to a compiler distribution that is used to build this cache. */
val compilerDistributionPath: Property<File> = project.objects.property(File::class.java).apply {
set(project.provider { project.kotlinNativeDist })
}
@get:Input
var cachedLibraries: Map<File, File> = emptyMap()
@TaskAction
fun compile() {
// This code uses bootstrap version of util-klib and fails due to the older default ABI than library being used
// A possible solution is to read it manually from manifest file or this check should be done by the compiler itself
// check(klibUniqName == readKlibUniqNameFromManifest()) {
// "klibUniqName mismatch: configured '$klibUniqName', resolved '${readKlibUniqNameFromManifest()}'"
// }
// Compiler doesn't create a cache if the cacheFile already exists. So we need to remove it manually.
if (cacheFile.exists()) {
val deleted = cacheFile.deleteRecursively()
check(deleted) { "Cannot delete stale cache: ${cacheFile.absolutePath}" }
}
cacheDirectory.mkdirs()
val konanHome = compilerDistributionPath.get().absolutePath
val additionalCacheFlags = PlatformManager(konanHome).let {
it.targetByName(target).let(it::loader).additionalCacheFlags
}
requireNotNull(originalKlib)
val args = mutableListOf(
"-g",
"-target", target,
"-produce", cacheKind.outputKind.name.toLowerCase(),
"-Xadd-cache=${originalKlib?.absolutePath}",
"-Xcache-directory=${cacheDirectory.absolutePath}"
)
if (makePerFileCache)
args += "-Xmake-per-file-cache"
args += additionalCacheFlags
args += cachedLibraries.map { "-Xcached-library=${it.key},${it.value}" }
KonanCliCompilerRunner(project, konanHome = konanHome).run(args)
}
}
@@ -1,421 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.tasks
import groovy.lang.Closure
import org.codehaus.groovy.runtime.GStringImpl
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileTree
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.*
import org.gradle.process.CommandLineArgumentProvider
import org.jetbrains.kotlin.gradle.plugin.konan.*
import org.jetbrains.kotlin.konan.library.defaultResolver
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
/**
* A task compiling the target executable/library using Kotlin/Native compiler
*/
abstract class KonanCompileTask: KonanBuildingTask(), KonanCompileSpec {
@get:Internal
override val toolRunner = KonanCliCompilerRunner(project, project.konanExtension.jvmArgs)
abstract val produce: CompilerOutputKind
@Internal get
// Output artifact --------------------------------------------------------
override val artifactSuffix: String
@Internal get() = produce.suffix(konanTarget)
override val artifactPrefix: String
@Internal get() = produce.prefix(konanTarget)
// Multiplatform support --------------------------------------------------
@Input var commonSourceSets = listOf("main")
@Internal var enableMultiplatform = false
private val commonSrcFiles_ = mutableSetOf<FileCollection>()
val commonSrcFiles: Collection<FileCollection>
@Internal get() = if (enableMultiplatform) commonSrcFiles_ else emptyList()
// Other compilation parameters -------------------------------------------
private val srcFiles_ = mutableSetOf<FileCollection>()
val srcFiles: Collection<FileCollection>
@Internal get() = srcFiles_.takeIf { !it.isEmpty() } ?: listOf(project.konanDefaultSrcFiles)
val allSources: Collection<FileCollection>
@InputFiles get() = listOf(srcFiles, commonSrcFiles).flatten()
private val allSourceFiles: List<File>
get() = allSources
.flatMap { it.files }
.filter { it.name.endsWith(".kt") }
@InputFiles val nativeLibraries = mutableSetOf<FileCollection>()
@Input val linkerOpts = mutableListOf<String>()
@Input var enableDebug = project.findProperty("enableDebug")?.toString()?.toBoolean()
?: project.environmentVariables.debuggingSymbols
@Input var noStdLib = false
@Input var noMain = false
@Input var noPack: Boolean = false
@Input var enableOptimizations = project.environmentVariables.enableOptimizations
@Input var enableAssertions = false
@Optional @Input var entryPoint: String? = null
@Console var measureTime = false
val languageVersion : String?
@Optional @Input get() = project.konanExtension.languageVersion
val apiVersion : String?
@Optional @Input get() = project.konanExtension.apiVersion
/**
* Is the two-stage compilation enabled.
*
* In regular (one-stage) compilation, sources are directly compiled into a final native binary.
* In two-stage compilation, sources are compiled into a klib first and then a final native binary is produced from this klib.
*/
@get:Input
abstract val enableTwoStageCompilation: Boolean
protected fun directoryToKt(dir: Any) = project.fileTree(dir).apply {
include("**/*.kt")
exclude { it.file.startsWith(project.buildDir) }
}
// Command line ------------------------------------------------------------
// Exclude elements matching the predicate.
private fun List<String>.excludeFlags(predicate: (String) -> Boolean) = filterNot(predicate)
// Exclude the listed elements.
private fun List<String>.excludeFlags(vararg keys: String) = keys.toSet().let { keysToExclude ->
excludeFlags { it in keysToExclude }
}
// Exclude the arguments passed by the given keys.
// E.g. if the list contains the following elements: ["-l", "foo", "-r", "bar"],
// call exclude("-r") returns the following list: ["-l", "foo"].
private fun List<String>.excludeArguments(vararg args: String): List<String> {
val argsToExclude = args.toSet()
val xPrefixesToExclude = argsToExclude.filter { it.startsWith("-X") }.map { "$it=" }
val result = mutableListOf<String>()
var i = 0
while (i < size) {
val key = this[i]
when {
key in argsToExclude -> {
// Skip the key and the following arg.
i++
}
// Support args passed as -X<arg>=<value>.
xPrefixesToExclude.any { key.startsWith(it) } -> { /* Skip the key. */ }
else -> result += key
}
i++
}
return result
}
// Don't include coverage flags into the first stage because they are not supported when compiling a klib.
private fun firstStageExtraOpts() = extraOpts
.excludeFlags("-Xcoverage")
.excludeArguments("-Xcoverage-file", "-Xlibrary-to-cover", "-Xpartial-linkage", "-Xpartial-linkage-loglevel")
// Don't include the -Xemit-lazy-objc-header and -language-version flags into
// the second stage because this stage have no sources.
private fun secondStageExtraOpts() = extraOpts
.excludeArguments("-Xemit-lazy-objc-header", "-language-version")
/** Args passed to the compiler at the first stage of two-stage compilation (klib building). */
protected fun buildFirstStageArgs(klibPath: String) = mutableListOf<String>().apply {
addArg("-output", klibPath)
addArg("-produce", CompilerOutputKind.LIBRARY.name.toLowerCase())
addAll(buildCommonArgs())
addAll(firstStageExtraOpts())
allSourceFiles.mapTo(this) { it.absolutePath }
commonSrcFiles
.flatMap { it.files }
.mapTo(this) { "-Xcommon-sources=${it.absolutePath}" }
}
/** Args passed to the compiler at the second stage of two-stage compilation (producing a final binary from the klib). */
protected fun buildSecondStageArgs(klibPath: String) = mutableListOf<String>().apply {
addArg("-output", artifact.canonicalPath)
addArg("-produce", produce.name.toLowerCase())
addArgIfNotNull("-entry", entryPoint)
addAll(buildCommonArgs())
addFileArgs("-native-library", nativeLibraries)
linkerOpts.forEach {
addArg("-linker-option", it)
}
addAll(secondStageExtraOpts())
add("-Xinclude=${klibPath}")
}
/** Args passed to the compiler at both stages of the two-stage compilation and during the singe-stage compilation. */
protected open fun buildCommonArgs() = mutableListOf<String>().apply {
addArgs("-repo", libraries.repos.map { it.canonicalPath })
if (platformConfiguration.files.isNotEmpty()) {
platformConfiguration.files.filter { it.name.endsWith(".klib") }.forEach {
// The library's directory is added in libraries.repos.
addArg("-library", it.nameWithoutExtension)
}
}
addFileArgs("-library", libraries.files)
addArgs("-library", libraries.namedKlibs)
// The library's directory is added in libraries.repos.
addArgs("-library", libraries.artifacts.map { it.artifact.nameWithoutExtension })
addArgIfNotNull("-target", konanTarget.visibleName)
addArgIfNotNull("-language-version", languageVersion)
addArgIfNotNull("-api-version", apiVersion)
addArgIfNotNull("-entry", entryPoint)
addKey("-g", enableDebug)
addKey("-nostdlib", noStdLib)
addKey("-nomain", noMain)
addKey("-opt", enableOptimizations)
addKey("-ea", enableAssertions)
addKey("-Xtime", measureTime)
addKey("-Xprofile-phases", measureTime)
addKey("-no-default-libs", noDefaultLibs)
addKey("-no-endorsed-libs", noEndorsedLibs)
addKey("-Xmulti-platform", enableMultiplatform)
if (libraries.friends.isNotEmpty())
addArg("-friend-modules", libraries.friends.joinToString(File.pathSeparator))
}
/** Args passed to the compiler if the two-stage compilation is disabled. */
fun buildSingleStageArgs() = mutableListOf<String>().apply {
addArg("-output", artifact.canonicalPath)
addArg("-produce", produce.name.toLowerCase())
addArgIfNotNull("-entry", entryPoint)
addAll(buildCommonArgs())
addFileArgs("-native-library", nativeLibraries)
linkerOpts.forEach {
addArg("-linker-option", it)
}
if (produce != CompilerOutputKind.LIBRARY) {
add("-Xpartial-linkage=enable")
add("-Xpartial-linkage-loglevel=error")
}
addAll(extraOpts)
allSourceFiles.mapTo(this) { it.absolutePath }
commonSrcFiles
.flatMap { it.files }
.mapTo(this) { "-Xcommon-sources=${it.absolutePath}" }
}
// region DSL.
// DSL. Input/output files.
override fun srcDir(dir: Any) {
srcFiles_.add(directoryToKt(dir))
}
override fun srcFiles(vararg files: Any) {
srcFiles_.add(project.files(files))
}
override fun srcFiles(files: Collection<Any>) = srcFiles(*files.toTypedArray())
// DSL. Native libraries.
override fun nativeLibrary(lib: Any) = nativeLibraries(lib)
override fun nativeLibraries(vararg libs: Any) {
nativeLibraries.add(project.files(*libs))
}
override fun nativeLibraries(libs: FileCollection) {
nativeLibraries.add(libs)
}
// DSL. Multiplatform projects.
override fun enableMultiplatform(flag: Boolean) {
enableMultiplatform = flag
}
@Deprecated("Use commonSourceSets instead", ReplaceWith("commonSourceSets(sourceSetName)"))
override fun commonSourceSet(sourceSetName: String) {
commonSourceSets = listOf(sourceSetName)
enableMultiplatform(true)
}
override fun commonSourceSets(vararg sourceSetNames: String) {
commonSourceSets = sourceSetNames.toList()
enableMultiplatform(true)
}
override fun commonSrcDir(dir: Any) {
commonSrcFiles_.add(directoryToKt(dir))
}
override fun commonSrcFiles(vararg files: Any) {
commonSrcFiles_.add(project.files(files))
}
override fun commonSrcFiles(files: Collection<Any>) = commonSrcFiles(*files.toTypedArray())
// DSL. Other parameters.
override fun linkerOpts(values: List<String>) = linkerOpts(*values.toTypedArray())
override fun linkerOpts(vararg values: String) {
linkerOpts.addAll(values)
}
override fun enableDebug(flag: Boolean) {
enableDebug = flag
}
override fun noStdLib(flag: Boolean) {
noStdLib = flag
}
override fun noMain(flag: Boolean) {
noMain = flag
}
override fun noPack(flag: Boolean) {
noPack = flag
}
override fun enableOptimizations(flag: Boolean) {
enableOptimizations = flag
}
override fun enableAssertions(flag: Boolean) {
enableAssertions = flag
}
override fun entryPoint(entryPoint: String) {
this.entryPoint = entryPoint
}
override fun measureTime(flag: Boolean) {
measureTime = flag
}
// endregion
override fun run() {
destinationDir.mkdirs()
if (dumpParameters) {
dumpProperties(this)
}
if (enableTwoStageCompilation) {
logger.info("Start two-stage compilation")
val intermediateDir = project.konanBuildRoot
.resolve("intermediate")
.targetSubdir(konanTarget)
.apply { mkdirs() }
val klibPrefix = CompilerOutputKind.LIBRARY.prefix(konanTarget)
val klibSuffix = CompilerOutputKind.LIBRARY.suffix(konanTarget)
val intermediateKlib = intermediateDir.resolve("$klibPrefix$artifactName$klibSuffix").absolutePath
logger.info("Start first stage")
toolRunner.run(buildFirstStageArgs(intermediateKlib))
logger.info("Start second stage")
toolRunner.run(buildSecondStageArgs(intermediateKlib))
} else {
toolRunner.run(buildSingleStageArgs())
}
}
}
abstract class KonanCompileNativeBinary: KonanCompileTask() {
@Input
override var enableTwoStageCompilation: Boolean = false
}
open class KonanCompileProgramTask: KonanCompileNativeBinary() {
override val produce: CompilerOutputKind get() = CompilerOutputKind.PROGRAM
@Internal
var runTask: TaskProvider<Exec>? = null
inner class RunArgumentProvider: CommandLineArgumentProvider {
override fun asArguments() = project.findProperty("runArgs")?.let {
it.toString().split(' ')
} ?: emptyList()
}
}
open class KonanCompileDynamicTask: KonanCompileNativeBinary() {
override val produce: CompilerOutputKind get() = CompilerOutputKind.DYNAMIC
val headerFile: File
@OutputFile get() = destinationDir.resolve("$artifactPrefix${artifactName}_api.h")
}
open class KonanCompileFrameworkTask: KonanCompileNativeBinary() {
override val produce: CompilerOutputKind get() = CompilerOutputKind.FRAMEWORK
override val artifact
@OutputDirectory get() = super.artifact
}
open class KonanCompileLibraryTask: KonanCompileTask() {
override val artifact: File
@Internal get() = destinationDir.resolve(artifactFullName)
val artifactFile: File?
@Optional @OutputFile get() = if (!noPack) artifact else null
val artifactDirectory: File?
@Optional @OutputDirectory get() = if (noPack) artifact else null
override val artifactSuffix: String
@Internal get() = if (!noPack) produce.suffix(konanTarget) else ""
override fun buildCommonArgs() = super.buildCommonArgs().apply {
addKey("-nopack", noPack)
}
override val produce: CompilerOutputKind get() = CompilerOutputKind.LIBRARY
override val enableTwoStageCompilation: Boolean = false
}
open class KonanCompileBitcodeTask: KonanCompileNativeBinary() {
override val produce: CompilerOutputKind get() = CompilerOutputKind.BITCODE
}
@@ -1,199 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.tasks
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.gradle.plugin.konan.*
import org.jetbrains.kotlin.gradle.plugin.konan.KonanInteropSpec.IncludeDirectoriesSpec
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
/**
* A task executing cinterop tool with the given args and compiling the stubs produced by this tool.
*/
open class KonanInteropTask @Inject constructor(@Internal val workerExecutor: WorkerExecutor) : KonanBuildingTask(), KonanInteropSpec {
private val interopRunner = KonanCliInteropRunner(project, project.konanExtension.jvmArgs)
@get:Internal
override val toolRunner: KonanToolRunner = interopRunner
override fun init(config: KonanBuildingConfig<*>, destinationDir: File, artifactName: String, target: KonanTarget) {
super.init(config, destinationDir, artifactName, target)
this.defFile = project.konanDefaultDefFile(artifactName)
}
// Output directories -----------------------------------------------------
override val artifactSuffix: String
@Internal get() = ".klib"
override val artifactPrefix: String
@Internal get() = ""
// Interop stub generator parameters -------------------------------------
@Internal var enableParallel: Boolean = false
@InputFile lateinit var defFile: File
@Optional @Input var packageName: String? = null
@Input val compilerOpts = mutableListOf<String>()
@Input val linkerOpts = mutableListOf<String>()
@Nested val includeDirs = IncludeDirectoriesSpecImpl()
@InputFiles val headers = mutableSetOf<FileCollection>()
@InputFiles val linkFiles = mutableSetOf<FileCollection>()
fun buildArgs() = mutableListOf<String>().apply {
addArg("-o", artifact.canonicalPath)
addArgIfNotNull("-target", konanTarget.visibleName)
addArgIfNotNull("-def", defFile.canonicalPath)
addArgIfNotNull("-pkg", packageName)
addFileArgs("-header", headers)
compilerOpts.forEach {
addArg("-compiler-option", it)
}
val linkerOpts = mutableListOf<String>().apply { addAll(linkerOpts) }
linkFiles.forEach {
linkerOpts.addAll(it.files.map { it.canonicalPath })
}
linkerOpts.forEach {
addArg("-linker-option", it)
}
addArgs("-compiler-option", includeDirs.allHeadersDirs.map { "-I${it.absolutePath}" })
addArgs("-headerFilterAdditionalSearchPrefix", includeDirs.headerFilterDirs.map { it.absolutePath })
addArgs("-repo", libraries.repos.map { it.canonicalPath })
addFileArgs("-library", libraries.files)
addArgs("-library", libraries.namedKlibs)
addArgs("-library", libraries.artifacts.map { it.artifact.canonicalPath })
addKey("-no-default-libs", noDefaultLibs)
addKey("-no-endorsed-libs", noEndorsedLibs)
addAll(extraOpts)
}
// region DSL.
inner class IncludeDirectoriesSpecImpl: IncludeDirectoriesSpec {
@Input val allHeadersDirs = mutableSetOf<File>()
@Input val headerFilterDirs = mutableSetOf<File>()
override fun allHeaders(vararg includeDirs: Any) = allHeaders(includeDirs.toList())
override fun allHeaders(includeDirs: Collection<Any>) {
allHeadersDirs.addAll(includeDirs.map { project.file(it) })
}
override fun headerFilterOnly(vararg includeDirs: Any) = headerFilterOnly(includeDirs.toList())
override fun headerFilterOnly(includeDirs: Collection<Any>) {
headerFilterDirs.addAll(includeDirs.map { project.file(it) })
}
}
override fun defFile(file: Any) {
defFile = project.file(file)
}
override fun packageName(value: String) {
packageName = value
}
override fun compilerOpts(vararg values: String) {
compilerOpts.addAll(values)
}
override fun header(file: Any) = headers(file)
override fun headers(vararg files: Any) {
headers.add(project.files(files))
}
override fun headers(files: FileCollection) {
headers.add(files)
}
override fun includeDirs(vararg values: Any) = includeDirs.allHeaders(values.toList())
override fun includeDirs(closure: Closure<Unit>) = includeDirs { project.configure(this, closure) }
override fun includeDirs(action: Action<IncludeDirectoriesSpec>) = includeDirs { action.execute(this) }
override fun includeDirs(configure: IncludeDirectoriesSpec.() -> Unit) = includeDirs.configure()
override fun linkerOpts(vararg values: String) = linkerOpts(values.toList())
override fun linkerOpts(values: List<String>) {
linkerOpts.addAll(values)
}
override fun link(vararg files: Any) {
linkFiles.add(project.files(files))
}
override fun link(files: FileCollection) {
linkFiles.add(files)
}
// endregion
internal interface RunToolParameters: WorkParameters {
var taskName: String
var args: List<String>
}
internal abstract class RunTool @Inject constructor() : WorkAction<RunToolParameters> {
override fun execute() {
val toolRunner = interchangeBox.remove(parameters.taskName) ?: error(":(")
toolRunner.run(parameters.args)
}
}
override fun run() {
interopRunner.init(target)
destinationDir.mkdirs()
if (dumpParameters) {
dumpProperties(this)
}
val args = buildArgs()
if (enableParallel) {
val workQueue = workerExecutor.noIsolation()
interchangeBox[this.path] = toolRunner
workQueue.submit(RunTool::class.java) {
taskName = path
this.args = args
}
} else {
toolRunner.run(args)
}
}
}
internal val interchangeBox = ConcurrentHashMap<String, KonanToolRunner>()
@@ -1,48 +0,0 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin
import groovy.lang.Closure
import org.gradle.api.Task
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.gradle.plugin.konan.KonanKlibRunner
import java.io.File
open class KonanKlibInstallTask : DefaultTask() {
@get:InputFile
var klib: Provider<File> = project.provider { project.buildDir }
@get:Internal
var repo: File = project.rootDir
@get:Input
val repoPath
get() = repo.absolutePath
val installDir: Provider<File>
@OutputDirectory
get() = project.provider {
val klibName = klib.get().nameWithoutExtension
project.file("${repo.absolutePath}/$klibName")
}
@get:Input
var target: String = HostManager.hostName
@TaskAction
fun exec() {
val args = listOf(
"install", klib.get().absolutePath,
"-target", target,
"-repository", repo.absolutePath
)
KonanKlibRunner(project, konanHome = project.kotlinNativeDist.absolutePath).run(args)
}
}
@@ -1,17 +0,0 @@
#
# Copyright 2010-2017 JetBrains s.r.o.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
shadow.org.jetbrains.kotlinx.serialization.gradle.SerializationKotlinGradleSubplugin
@@ -1,29 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
class BaseKonanSpecification extends Specification {
@Rule
TemporaryFolder tmpFolder = new TemporaryFolder()
File getProjectDirectory() { return tmpFolder.root }
}
@@ -1,42 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.gradle.testkit.runner.TaskOutcome
class DefaultSpecification extends BaseKonanSpecification {
def 'Plugin should build a project without additional settings'() {
when:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.buildFile.write("""
plugins { id 'konan' }
konanArtifacts {
interop('stdio')
library('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)
}
}
@@ -1,336 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import spock.lang.Ignore
import spock.lang.Unroll
import static org.jetbrains.kotlin.gradle.plugin.test.KonanProject.escapeBackSlashes
// TODO: Rewrite tests using Kotlin.
class EnvVariableSpecification extends BaseKonanSpecification {
class WrapperResult {
private int exitValue;
private String stdout;
private String stderr;
WrapperResult(Process process) {
exitValue = process.exitValue()
stdout = process.getInputStream().readLines().join("\n")
stderr = process.getErrorStream().readLines().join("\n")
}
int getExitValue() { return exitValue }
String getStdout() { return stdout }
String getStderr() { return stderr }
WrapperResult printStdout() { println(stdout); return this }
WrapperResult printStderr() { println(stderr); return this }
}
private KonanProject createProjectWithWrapper() {
def project = KonanProject.createEmpty(projectDirectory)
def runner = project.createRunner()
// Gradle TestKit doesn't support setting environment variables for runners.
// So we use the following hack: we create a gradle wrapper, start it as a separate
// process with custom environment variables and check its exit code and output.
runner.withArguments("wrapper").build()
def classpath = runner.pluginClasspath.collect { "'${escapeBackSlashes(it.absolutePath)}'" }.join(", ")
project.buildFile.write("""\
buildscript {
dependencies {
classpath files($classpath)
}
}
""".stripIndent())
return project
}
private WrapperResult runWrapper(KonanProject project,
List<String> tasks,
Map<String, String> environment = [:],
Map<String, String> properties = ["konan.useEnvironmentVariables": 'true']) {
def wrapper = (HostManager.host.family == Family.MINGW) ? "gradlew.bat" : "gradlew"
def command = ["$project.projectDir.absolutePath/$wrapper".toString()]
command.addAll(tasks)
command.addAll(properties.collect { "-P${it.key}=${it.value}".toString() })
def projectBuilder = new ProcessBuilder()
.directory(project.projectDir)
.command(command)
projectBuilder.environment().putAll(environment)
def process = projectBuilder.start()
process.waitFor()
return new WrapperResult(process)
}
private WrapperResult runWrapper(KonanProject project,
String task,
Map<String, String> environment = [:],
Map<String, String> properties = ["konan.useEnvironmentVariables": 'true']) {
return runWrapper(project, [task], environment, properties)
}
private String artifactFileName(String baseName, ArtifactType type, KonanTarget target = HostManager.host) {
String suffix = ""
String prefix = ""
switch (type) {
case ArtifactType.PROGRAM:
suffix = target.family.exeSuffix
break
case ArtifactType.INTEROP:
case ArtifactType.LIBRARY:
suffix = "klib"
break
case ArtifactType.BITCODE:
suffix = "bc"
break;
case ArtifactType.DYNAMIC:
prefix = target.family.dynamicPrefix
suffix = target.family.dynamicSuffix
break
case ArtifactType.STATIC:
prefix = target.family.staticPrefix
suffix = target.family.staticSuffix
break
case ArtifactType.FRAMEWORK:
suffix = "framework"
}
return "$prefix${baseName}.$suffix"
}
@Ignore("The plugin doesn't use env vars until https://github.com/gradle/gradle/issues/3468 is fixed.")
@Unroll("Plugin should support #action via an env variable")
def 'Plugin should support enabling/disabling debug/opt via an env variable'() {
when:
def project = createProjectWithWrapper()
project.buildFile.append("""\
apply plugin: 'konan'
konanArtifacts {
library('main')
}
task assertEnableDebug {
doLast {
konanArtifacts.main.forEach {
if (!($assertion)) throw new AssertionError("$message for \${it.name}")
}
}
}
""".stripIndent())
def result = runWrapper(project,"assertEnableDebug", [(variable): value])
.printStderr()
.getExitValue()
then:
result == 0
where:
action |variable |value |assertion |message
"enabling debug" |"DEBUGGING_SYMBOLS" |"YES" |"it.enableDebug" |"Debug should be enabled"
"disabling debug" |"DEBUGGING_SYMBOLS" |"NO" |"!it.enableDebug" |"Debug should be disabled"
"enabling opt" |"KONAN_ENABLE_OPTIMIZATIONS" |"YES" |"it.enableOptimizations" |"Opts should be enabled"
"disabling opt" |"KONAN_ENABLE_OPTIMIZATIONS" |"NO" |"!it.enableOptimizations" |"Opts should be disabled"
}
@Ignore("The plugin doesn't use env vars until https://github.com/gradle/gradle/issues/3468 is fixed.")
def 'Plugin should support setting destination directory via an env variable'() {
when:
def project = createProjectWithWrapper()
def newDestinationDir = project.createSubDir("newDestination")
def newDestinationPath = newDestinationDir.absolutePath
project.buildFile.append("""\
apply plugin: 'konan'
konanArtifacts {
program('program')
library('library')
dynamic('dynamic')
framework('framework')
}
task assertDestinationDir {
doLast {
konanArtifacts.forEach { artifact ->
artifact.forEach {
if (it.destinationDir.absolutePath != '${escapeBackSlashes(newDestinationPath)}'){
throw new AssertionError("Unexpected destination dir for \$it.name\\n" +
"expected: ${escapeBackSlashes(newDestinationPath)}\\n" +
"actual: \$it.destinationDir")
}
}
}
}
}
""".stripIndent())
project.generateSrcFile("main.kt")
def assertResult = runWrapper(project, "assertDestinationDir", ["CONFIGURATION_BUILD_DIR": newDestinationPath])
.printStderr()
.getExitValue()
def buildResult = runWrapper(project, "build", ["CONFIGURATION_BUILD_DIR": newDestinationPath])
.printStderr()
.getExitValue()
def files = newDestinationDir.list()
then:
assertResult == 0
buildResult == 0
files.contains(artifactFileName("program", ArtifactType.PROGRAM))
files.contains(artifactFileName("library", ArtifactType.LIBRARY))
files.contains(artifactFileName("dynamic", ArtifactType.DYNAMIC))
files.contains(artifactFileName("static", ArtifactType.STATIC))
if (HostManager.hostIsMac) {
files.contains(artifactFileName("framework", ArtifactType.FRAMEWORK))
}
}
@Ignore("The plugin doesn't use env vars until https://github.com/gradle/gradle/issues/3468 is fixed.")
def 'Plugin should rerun tasks if CONFIGURATION_BUILD_DIR has been changed'() {
when:
def project = createProjectWithWrapper()
def destination1 = project.createSubDir("destination1", "subdir")
def destination2 = project.createSubDir("destination2", "subdir")
project.buildFile.append("""\
apply plugin: 'konan'
konanArtifacts {
library('main')
}
""".stripIndent())
project.generateSrcFile("main.kt")
def buildResult1 = runWrapper(project, "build", ["CONFIGURATION_BUILD_DIR": destination1.absolutePath])
.printStderr()
.getExitValue()
def buildResult2 = runWrapper(project, "build", ["CONFIGURATION_BUILD_DIR": destination2.absolutePath])
.printStderr()
.getExitValue()
def files1 = destination1.list()
def files2 = destination2.list()
then:
buildResult1 == 0
buildResult2 == 0
destination1.exists()
destination2.exists()
files1.contains(artifactFileName("main", ArtifactType.LIBRARY))
files2.contains(artifactFileName("main", ArtifactType.LIBRARY))
}
@Ignore("The plugin doesn't use env vars until https://github.com/gradle/gradle/issues/3468 is fixed.")
def 'Plugin should ignore environmentVariables if konan.useEnvironmentVariables is false or is not set'() {
when:
def project = createProjectWithWrapper()
def newDestinationDir = project.createSubDir("newDestination")
def newDestinationPath = newDestinationDir.absolutePath
project.buildFile.append("""\
apply plugin: 'konan'
konanArtifacts {
program('program')
library('library')
dynamic('dynamic')
framework('framework')
}
task assertNoOverrides {
doLast {
konanArtifacts.forEach { artifact ->
artifact.forEach {
if (it.destinationDir.absolutePath == '${escapeBackSlashes(newDestinationPath)}'){
throw new AssertionError("CONFIGURATION_BUILD_DIR overrides a default output path " +
"when it shouldn't.\\n" +
"Task: \${it.name}, Path: \${it.destinationDir}")
}
if (it.enableDebug) {
throw new AssertionError("DEBUGGING_SYMBOLS overrides a default value " +
"when it shouldn't\\n" +
"Task: \${it.name}")
}
}
}
}
}
""".stripIndent())
def resultNoProp = runWrapper(project,
"assertNoOverrides",
["DEBUGGING_SYMBOLS": "true", "CONFIGURATION_BUILD_DIR": newDestinationPath], [:])
.printStderr()
.getExitValue()
def resultFalseValue = runWrapper(project,
"assertNoOverrides",
["DEBUGGING_SYMBOLS": "true", "CONFIGURATION_BUILD_DIR": newDestinationPath],
["konan.useEnvironmentVariables": "false"])
.printStderr()
.getExitValue()
then:
resultNoProp == 0
resultFalseValue == 0
}
@Ignore("The plugin doesn't use env vars until https://github.com/gradle/gradle/issues/3468 is fixed.")
def 'Up-to-date checks should work with different directories for different targets'() {
when:
def project = createProjectWithWrapper()
def fooDir = project.createSubDir("foo")
def barDir = project.createSubDir("bar")
project.buildFile.append("""\
apply plugin: 'konan'
konanArtifacts {
library('foo')
library('bar')
}
task assertUpToDate {
dependsOn 'compileKonanFoo'
doLast {
if (!konanArtifacts.foo.getByTarget('host').state.upToDate) {
throw new AssertionError("Compilation task is not up-to-date")
}
}
}
""".stripIndent())
project.generateSrcFile("main.kt")
def buildResult1 = runWrapper(project, "compileKonanFoo",
["CONFIGURATION_BUILD_DIR": fooDir.absolutePath])
.printStderr()
.getExitValue()
def buildResult2 = runWrapper(project, "compileKonanBar",
["CONFIGURATION_BUILD_DIR": barDir.absolutePath])
.printStderr()
.getExitValue()
def buildResult3 = runWrapper(project,
"assertUpToDate",
["CONFIGURATION_BUILD_DIR": fooDir.absolutePath])
.printStderr()
.getExitValue()
then:
buildResult1 == 0
buildResult2 == 0
buildResult3 == 0
}
}
@@ -1,312 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
import spock.lang.Unroll
class IncrementalSpecification extends BaseKonanSpecification {
Tuple buildTwice(KonanProject project, String task = 'build', Closure change) {
def runner = project.createRunner().withArguments(task)
def firstResult = runner.build()
change(project)
def secondResult = runner.build()
return new Tuple(project, firstResult, secondResult)
}
Tuple buildTwice(ArtifactType mainArtifactType = ArtifactType.LIBRARY, String task = 'build', Closure change) {
return buildTwice(KonanProject.createWithInterop(projectDirectory, mainArtifactType), change)
}
Boolean noRecompilationHappened(KonanProject project, BuildResult firstResult, BuildResult secondResult) {
return project.with {
firstResult.tasks.collect { it.path }.containsAll(buildingTasks) &&
firstResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks) &&
secondResult.taskPaths(TaskOutcome.UP_TO_DATE).containsAll(buildingTasks) &&
firstResult.task(downloadTask).outcome == TaskOutcome.SUCCESS &&
secondResult.task(downloadTask).outcome == TaskOutcome.SUCCESS
}
}
Boolean onlyRecompilationHappened(KonanProject project, BuildResult firstResult, BuildResult secondResult) {
return project.with {
firstResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks) &&
secondResult.taskPaths(TaskOutcome.SUCCESS).containsAll(compilationTasks) &&
secondResult.taskPaths(TaskOutcome.UP_TO_DATE).containsAll(interopTasks)
}
}
Boolean recompilationAndInteropProcessingHappened(KonanProject project, BuildResult firstResult, BuildResult secondResult) {
return project.with {
firstResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks) &&
secondResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks)
}
}
//region tests =====================================================================================================
def 'Compilation is up-to-date if there is no changes'() {
when:
def results = buildTwice {}
then:
noRecompilationHappened(*results)
}
def 'Source change should cause only recompilation'() {
when:
def results = buildTwice { KonanProject project ->
project.srcFiles[0].append("\n // Some change in the source file")
}
then:
onlyRecompilationHappened(*results)
}
def 'Def-file change should cause recompilation and interop reprocessing'() {
when:
def results = buildTwice { KonanProject project ->
project.defFiles[0].append("\n # Some change in the def-file")
}
then:
recompilationAndInteropProcessingHappened(*results)
}
@Unroll("#parameter change for a compilation task should cause only recompilation")
def 'Parameter changes should cause only recompilaton'() {
when:
def results = buildTwice { KonanProject project ->
project.addSetting("main", parameter, value)
}
then:
onlyRecompilationHappened(*results)
where:
parameter | value
"baseDir" | "'build/new/outputDir'"
"enableOptimizations" | "true"
"linkerOpts" | "'--help'"
"enableAssertions" | "true"
"enableDebug" | "true"
"artifactName" | "'foo'"
"extraOpts" | "'-Xtime'"
"noDefaultLibs" | "true"
"noEndorsedLibs" | "true"
}
def 'Plugin should support a custom entry point and recompile an artifact if it changes'() {
when:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.addCompilerArtifact("main", """
|fun main(args: Array<String>) { println("default main") }
|
""".stripMargin())
}
def results = buildTwice(project) { KonanProject it ->
it.srcFiles[0].write("""
|package foo
|
|fun bar(args: Array<String>) { println("changed main") }
|
""".stripMargin())
it.addSetting("main", "entryPoint", "'foo.bar'")
}
then:
onlyRecompilationHappened(*results)
}
def 'srcFiles change for a compilation task should cause only recompilation'() {
when:
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY) { KonanProject it ->
it.generateSrcFile(["src", "foo", "kotlin"], 'bar.kt', """
fun foo(args: Array<String>) { println("Hello!") }
""".stripIndent())
}
def results = buildTwice(project) { KonanProject it ->
it.addSetting("main", "srcFiles", "project.fileTree('src/foo/kotlin')")
}
then:
onlyRecompilationHappened(*results)
}
def 'Library change for a compilation task should cause only recompilation'() {
when:
def project = KonanProject.create(projectDirectory, ArtifactType.LIBRARY) { KonanProject it ->
it.generateSrcFile(["src", "lib", "kotlin"], "lib.kt", "fun bar() { println(\"Hello!\") }")
it.buildFile.append("""
konanArtifacts {
library('lib') {
srcFiles fileTree('src/lib/kotlin')
}
}
""".stripIndent())
}
def results = buildTwice(project) { KonanProject it ->
it.addLibraryToArtifact("main", 'lib')
}
then:
onlyRecompilationHappened(*results)
}
def 'Native library change for a compilation task should cause only recompilaton'() {
when:
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY) { KonanProject it ->
it.generateSrcFile(["src", "lib", "kotlin"], "lib.kt", "fun bar() { println(\"Hello!\") }")
it.buildFile.append("""
konanArtifacts {
bitcode('lib') {
srcFiles fileTree('src/lib/kotlin')
}
}
""".stripIndent())
}
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 = buildTwice { KonanProject project ->
project.addSetting("stdio", parameter, value)
}
then:
recompilationAndInteropProcessingHappened(*results)
where:
parameter | value
"packageName" | "'org.sample'"
"compilerOpts" | "'-g'"
"linkerOpts" | "'--help'"
"includeDirs" | "'src'"
"includeDirs.allHeaders" | "'src'"
"extraOpts" | "'-verbose'"
"noDefaultLibs" | "true"
"noEndorsedLibs" | "true"
}
def 'includeDirs.headerFilterOnly change should cause recompilation and interop reprocessing'() {
when:
def project = KonanProject.createWithInterop(projectDirectory) { KonanProject it ->
it.defFiles.first().write("headers = stdio.h\nheaderFilter = stdio.h")
}
def results = buildTwice(project) { KonanProject it ->
it.addSetting(KonanProject.DEFAULT_INTEROP_NAME, "includeDirs.headerFilterOnly", "'.'")
}
then:
recompilationAndInteropProcessingHappened(*results)
}
def 'defFile change for an interop task should cause recompilation and interop reprocessing'() {
when:
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY)
def defFile = project.generateDefFile("foo.def", "#some content")
def results = buildTwice(project) { KonanProject it ->
it.addSetting("stdio", "defFile", defFile)
}
then:
recompilationAndInteropProcessingHappened(*results)
}
def 'header change for an interop task should cause recompilation and interop reprocessing'() {
when:
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY)
def header = project.generateSrcFile('header.h', "#define CONST 1")
def results = buildTwice(project) { KonanProject it ->
it.addSetting("stdio", "headers", header)
}
then:
recompilationAndInteropProcessingHappened(*results)
}
def 'link change for an interop task should cause recompilation and interop reprocessing'() {
when:
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY) { KonanProject it ->
it.generateSrcFile(["src", "lib", "kotlin"], 'lib.kt', 'fun foo() { println(42) }')
it.buildFile.append("""
konanArtifacts {
bitcode('lib') {
srcFiles fileTree('src/lib/kotlin')
}
}
""".stripIndent())
}
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:
recompilationAndInteropProcessingHappened(*results)
}
def 'Common source change should cause recompilation'() {
when:
File commonSource
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = MultiplatformSpecification.createCommonProject(it)
commonSource = MultiplatformSpecification.createCommonSource(commonDirectory,
["src", "main", "kotlin"],
"common.kt",
"expect fun foo(): Int")
println(it.settingsFile.text)
it.generateSrcFile("actual.kt", "actual fun foo() = 42")
it.buildFile.append("""
konanArtifacts {
library('foo') {
enableMultiplatform true
}
}
dependencies {
expectedBy project(':common')
}
""".stripIndent())
it.buildingTasks.addAll([":compileKonanFoo", ":compileKonanFoo${KonanProject.HOST}}"])
}
def results = buildTwice(project, ':build') { KonanProject ->
commonSource.append("\nfun bar() = 43")
}
then:
onlyRecompilationHappened(*results)
}
// TODO: Add incremental tests for the 'libraries' block.
//endregion
}
@@ -1,377 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.gradle.testkit.runner.GradleRunner
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.konan.target.HostManager
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
enum ArtifactType {
PROGRAM("program"),
LIBRARY("library"),
BITCODE("bitcode"),
INTEROP("interop"),
DYNAMIC("dynamic"),
STATIC("static"),
FRAMEWORK("framework")
String type
ArtifactType(String type) { this.type = type }
String toString() { return type }
}
class KonanProject {
static String DEFAULT_ARTIFACT_NAME = 'main'
static String DEFAULT_INTEROP_NAME = "stdio"
static String HOST = HostManager.hostName
File projectDir
Path projectPath
File konanBuildDir
String konanHome
String gradleVersion
File buildFile
File propertiesFile
File settingsFile
Set<File> srcFiles = []
Set<File> defFiles = []
List<String> interopTasks = []
List<String> compilationTasks = []
String downloadTask = ":checkKonanCompiler"
List<String> targets
List<String> getBuildingTasks() { return compilationTasks + interopTasks }
List<String> getKonanTasks() { return getBuildingTasks() + downloadTask }
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 konanHomeDir = new File(getKonanHome())
if (!konanHomeDir.exists() || !konanHomeDir.directory) {
throw new IllegalStateException("konan.home doesn't exist or is not a directory: $konanHomeDir.canonicalPath")
}
// Escape windows path separator
this.konanHome = escapeBackSlashes(konanHomeDir.canonicalPath)
this.gradleVersion = System.getProperty("gradleVersion") ?: GradleVersion.current().version
}
GradleRunner createRunner(boolean withDebug = true) {
return GradleRunner.create()
.withProjectDir(projectDir)
.withPluginClasspath()
.withDebug(withDebug)
.withGradleVersion(gradleVersion)
}
/** Creates a subdirectory specified by the given path. */
File createSubDir(String ... path) {
return createSubDir(Paths.get(*path))
}
/** Creates a subdirectory specified by the given path. */
File createSubDir(Path path) {
return Files.createDirectories(projectPath.resolve(path)).toFile()
}
/** Creates a file with the given content in project subdirectory specified by parentDirectory. */
File createFile(Path parentDirectory = projectPath, String fileName, String content) {
def parent = projectPath.resolve(parentDirectory)
Files.createDirectories(parent)
def result = parent.resolve(fileName).toFile()
result.createNewFile()
result.write(content)
return result
}
/** Creates a file with the given content in project subdirectory specified by parentPath. */
File createFile(List<String> parentPath, String fileName, String content) {
return createFile(Paths.get(*parentPath), fileName, content)
}
/** 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. */
File generateBuildFile(String content) {
buildFile = createFile(projectPath, "build.gradle", content)
return buildFile
}
/** Generates a settings.gradle file in the root project directory with the given content. */
File generateSettingsFile(String content) {
settingsFile = createFile(projectPath, "settings.gradle", content)
return settingsFile
}
/**
* Generates a build.gradle file in root project directory with the default content (see below)
* and fills the compilationTasks array.
*
* plugins { id 'konan' }
*
* konanArtifacts {
* program('$DEFAULT_ARTIFACT_NAME')
* }
*/
File generateBuildFile() {
def result = generateBuildFile("""
|plugins { id 'konan' }
|
|konan.targets = [${targets.collect { "'$it'" }.join(", ")}]
|""".stripMargin()
)
compilationTasks = [":compileKonan", ":build"]
return result
}
/** Generates a source file with the given name and content in the given directory and adds it into srcFiles */
File generateSrcFile(Path parentDirectory, String fileName, String content) {
def result = createFile(parentDirectory, fileName, content)
srcFiles.add(result)
return result
}
/** Generates a source file with the given name and content in the given directory and adds it into srcFiles */
File generateSrcFile(List<String> parentPath, String fileName, String content) {
return generateSrcFile(Paths.get(*parentPath), fileName, content)
}
/** Generates a source file with the given name and content in 'src/main/kotlin' and adds it into srcFiles */
File generateSrcFile(String fileName, String content) {
return generateSrcFile(["src", "main", "kotlin"], fileName, content)
}
/**
* Generates a source file with the given name and default content (see below) in src/main/kotlin
* and adds it into srcFiles.
*
* fun main(args: Array<String>) {
* println(42)
* }
*/
File generateSrcFile(String fileName) {
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. */
File generateDefFile(String fileName, String content) {
def result = createFile(["src", "main", "c_interop"], fileName, content)
defFiles.add(result)
return result
}
/**
* Creates a def-file with the given name and the default content (see below) in src/main/c_interop directory
* and adds it to defFiles.
*
* headers = stdio.h stdlib.h string.h
*/
File generateDefFile(String fileName = "${DEFAULT_INTEROP_NAME}.def") {
return generateDefFile(fileName, DEFAULT_DEF_CONTENT)
}
/** Generates gradle.properties file with the konan.home and konan.jvmArgs properties set. */
File generatePropertiesFile(String konanHome, String konanJvmArgs = System.getProperty("konan.jvmArgs") ?: "") {
propertiesFile = createFile(projectPath, "gradle.properties", """\
org.jetbrains.kotlin.native.home=$konanHome
${!konanJvmArgs.isEmpty() ? "konan.jvmArgs=$konanJvmArgs\n" : ""}
""".stripIndent())
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, "'${escapeBackSlashes(value.canonicalPath)}'")
}
/** 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. */
static String defaultCompilationTask(String target = HOST) {
return compilationTask(DEFAULT_ARTIFACT_NAME, target)
}
static String defaultInteropTask(String target = HOST) {
return compilationTask(DEFAULT_INTEROP_NAME, target)
}
/** Returns the path of compileKonan... task for the artifact specified. */
static String compilationTask(String artifactName, String target = HOST) {
return ":compileKonan${artifactName.capitalize()}${target.capitalize()}"
}
static String defaultCompilationConfig() {
return artifactConfig(DEFAULT_ARTIFACT_NAME)
}
static String defaultInteropConfig() {
return artifactConfig(DEFAULT_INTEROP_NAME)
}
static String artifactConfig(String artifactName) {
return "konanArtifacts.$artifactName"
}
static String outputAccessCode(String artifact, String target = HOST) {
return "${artifactConfig(artifact)}.${target}.artifact"
}
void addCompilerArtifact(String name, String content = "", ArtifactType type = ArtifactType.PROGRAM) {
def newTasks = targets.collect { compilationTask(name, it) } + ":compileKonan${name.capitalize()}".toString()
buildFile.append("konanArtifacts { $type('$name') }\n")
if (type == ArtifactType.INTEROP) {
defFiles += generateDefFile("${name}.def", content)
interopTasks += newTasks
} else {
def src = generateSrcFile(projectPath.resolve("src/$name/kotlin"), "source.kt", content)
addSetting(name, "srcFiles", src)
srcFiles += src
compilationTasks += newTasks
}
}
/** Creates a project with default build and source files. */
static KonanProject create(File projectDir,
ArtifactType artifactType = ArtifactType.PROGRAM,
List<String> targets = [HOST]) {
return createEmpty(projectDir, targets) { KonanProject p ->
p.addCompilerArtifact(DEFAULT_ARTIFACT_NAME, DEFAULT_SRC_CONTENT, artifactType)
}
}
/** Creates a project with default build and source files. */
static KonanProject create(File projectDir,
ArtifactType artifactType = ArtifactType.PROGRAM,
List<String> targets = [HOST],
Closure config) {
def result = create(projectDir, artifactType, targets)
config(result)
return result
}
static KonanProject createWithInterop(File projectDir,
ArtifactType mainArtifactType = ArtifactType.PROGRAM,
List<String> targets = [HOST]) {
return create(projectDir, mainArtifactType, targets) { KonanProject p ->
p.addCompilerArtifact(DEFAULT_INTEROP_NAME, DEFAULT_DEF_CONTENT, ArtifactType.INTEROP)
p.addLibraryToArtifact()
}
}
static KonanProject createWithInterop(File projectDir,
ArtifactType mainArtifactType = ArtifactType.PROGRAM,
List<String> targets = [HOST],
Closure config) {
def result = createWithInterop(projectDir, mainArtifactType, 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)
generateSettingsFile("")
}
return result
}
/** 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
}
static String escapeBackSlashes(String value) {
return value.replace('\\', '\\\\')
}
static String getKonanHome() {
def konanHome = System.getProperty("konan.home") ?: System.getProperty("org.jetbrains.kotlin.native.home")
if (konanHome == null) {
throw new IllegalStateException("konan.home isn't specified")
}
return konanHome
}
}
@@ -1,330 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
class LibrarySpecification extends BaseKonanSpecification {
def libraries = [
[manualDependsOn: true , code: { l1, l2 ->
"file ${KonanProject.outputAccessCode(l1)}\nfile ${KonanProject.outputAccessCode(l2)}"
}],
[manualDependsOn: true , code: { l1, l2 ->
"files ${KonanProject.outputAccessCode(l1)}, ${KonanProject.outputAccessCode(l2)}"
}],
[manualDependsOn: true , code: { l1, l2 ->
"files project.files(${KonanProject.outputAccessCode(l1)}, ${KonanProject.outputAccessCode(l2)})"
}],
[manualDependsOn: true , code: { l1, l2 -> "klib '$l1'\nklib '$l2'" }],
[manualDependsOn: true , code: { l1, l2 -> "klibs '$l1', '$l2'" }],
[manualDependsOn: false, code: { l1, l2 -> "artifact '$l1'\nartifact '$l2'" }],
[manualDependsOn: false, code: { l1, l2 -> "artifact konanArtifacts.$l1\nartifact konanArtifacts.$l2" }],
]
String createMainWithCalls(List<Tuple2<String, String>> functions, Closure<String> callBuilder) {
def result = new StringBuilder("""
|fun main(args: Array<String>) {\n
""".stripMargin())
functions.forEach {
result.append(callBuilder(it.first))
result.append(callBuilder(it.second))
}
result.append("}")
return result.toString()
}
void createLibraryWithFunction(KonanProject project, String name) {
project.addCompilerArtifact(name, """
|package $name
|
|fun $name() {
| println("$name")
|}
""".stripMargin(), ArtifactType.LIBRARY)
project.addSetting(name, "noDefaultLibs", "true")
project.addSetting(name, "noEndorsedLibs", "true")
}
void createInteropLibrary(KonanProject project, String name) {
project.addCompilerArtifact(name, "headers = math.h", ArtifactType.INTEROP)
project.addSetting(name, "noDefaultLibs", "true")
project.addSetting(name, "noEndorsedLibs", "true")
}
KonanProject createProjectWithLibraries(Closure createLibraryFunction, Closure callBuilder) {
def result = KonanProject.createEmpty(projectDirectory)
def libraryNames = new ArrayList<Tuple2<String, String>>()
for (int i = 0; i < libraries.size(); i++) {
libraryNames.add(new Tuple2("foo$i", "bar$i"))
}
libraryNames.forEach {
createLibraryFunction(result, it.first)
createLibraryFunction(result, it.second)
}
result.addCompilerArtifact("main", createMainWithCalls(libraryNames, callBuilder))
result.addSetting("main", "noDefaultLibs", "true")
result.addSetting("main", "noEndorsedLibs", "true")
for (int i = 0; i < libraries.size(); i++) {
def foo = libraryNames[i].first
def bar = libraryNames[i].second
result.addLibraryToArtifactCustom("main", libraries[i].code(foo, bar) )
if (libraries[i].manualDependsOn) {
result.addSetting("main", "dependsOn", "konanArtifacts.$foo")
result.addSetting("main", "dependsOn", "konanArtifacts.$bar")
}
}
return result
}
KonanProject createProjectWithSimpleLibraries() {
return createProjectWithLibraries(
{ p, n -> createLibraryWithFunction(p, n) },
{ "$it.$it()\n" } )
}
KonanProject createProjectWithInteropLibraries() {
return createProjectWithLibraries(
{ p, n -> createInteropLibrary(p, n) },
{ "println(${it}.cos(0.0))\n" }
)
}
def 'Plugin should support libraries from the same project'() {
expect:
createProjectWithSimpleLibraries()
.createRunner()
.withArguments(KonanProject.compilationTask("main"))
.build()
}
def 'Plugin should support interop libraries from the same project'() {
expect:
createProjectWithInteropLibraries()
.createRunner()
.withArguments(KonanProject.compilationTask("main"))
.build()
}
def 'Plugin should support allLibrariesFrom method for the current project'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
it.addSetting("foo", "noDefaultLibs", "true")
it.addSetting("foo", "noEndorsedLibs", "true")
it.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
it.addSetting("bar", "noDefaultLibs", "true")
it.addSetting("bar", "noEndorsedLibs", "true")
it.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
it.addSetting("main", "noDefaultLibs", "true" )
it.addSetting("main", "noEndorsedLibs", "true" )
it.addLibraryToArtifactCustom("main", "allLibrariesFrom project")
}
project.createRunner()
.withArguments(KonanProject.compilationTask("main"))
.build()
}
def 'Plugin should support allLibrariesFrom method for another project'() {
expect:
def project = KonanProject.createEmpty(projectDirectory)
def subproject = KonanProject.createEmpty(project.createSubDir("subproject")) { KonanProject it ->
it.buildFile.write("apply plugin: 'konan'\n")
}
project.settingsFile.append("include ':subproject'")
project.addCompilerArtifact("wrongFoo","fun foo() { println(24) }", ArtifactType.LIBRARY)
project.addSetting("wrongFoo", "noDefaultLibs", "true")
project.addSetting("wrongFoo", "noEndorsedLibs", "true")
subproject.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
subproject.addSetting("foo", "noDefaultLibs", "true")
subproject.addSetting("foo", "noEndorsedLibs", "true")
subproject.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
subproject.addSetting("bar", "noDefaultLibs", "true")
subproject.addSetting("bar", "noEndorsedLibs", "true")
project.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
project.addSetting("main", "noDefaultLibs", "true" )
project.addSetting("main", "noEndorsedLibs", "true" )
project.addLibraryToArtifactCustom("main", "allLibrariesFrom project('subproject')")
project.createRunner()
.withArguments(KonanProject.compilationTask("main"))
.build()
}
def 'Plugin should support allInteropLibrariesFrom method for the current project'() {
expect:
def project = KonanProject.createEmpty(projectDirectory)
def subproject = KonanProject.createEmpty(project.createSubDir("subproject")) { KonanProject it ->
it.buildFile.write("apply plugin: 'konan'\n")
}
project.settingsFile.append("include ':subproject'")
project.addCompilerArtifact("wrongFoo1", "fun foo() { println(42) }", ArtifactType.LIBRARY)
project.addSetting("wrongFoo1", "noDefaultLibs", "true")
project.addSetting("wrongFoo1", "noEndorsedLibs", "true")
subproject.addCompilerArtifact("wrongFoo2", "fun foo() { println(42) }", ArtifactType.LIBRARY)
subproject.addSetting("wrongFoo2", "noDefaultLibs", "true")
subproject.addSetting("wrongFoo2", "noEndorsedLibs", "true")
subproject.addCompilerArtifact("math1", "headers = math.h", ArtifactType.INTEROP)
subproject.addSetting("math1", "noDefaultLibs", "true")
subproject.addSetting("math1", "noEndorsedLibs", "true")
subproject.addCompilerArtifact("math2", "headers = math.h", ArtifactType.INTEROP)
subproject.addSetting("math2", "noDefaultLibs", "true")
subproject.addSetting("math2", "noEndorsedLibs", "true")
project.addCompilerArtifact("main" ,"""
|fun foo() {}
|
|fun main(args: Array<String>) { foo(); math1.cos(0.0); math2.cos(0.0) }
""".stripMargin())
project.addSetting("main", "noDefaultLibs", "true" )
project.addSetting("main", "noEndorsedLibs", "true" )
project.addLibraryToArtifactCustom("main", "allInteropLibrariesFrom project('subproject')")
project.createRunner()
.withArguments(KonanProject.compilationTask("main"))
.build()
}
def 'Plugin should support allInteropLibrariesFrom method for another projecct'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.addCompilerArtifact("wrongFoo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
it.addSetting("wrongFoo", "noDefaultLibs", "true")
it.addSetting("wrongFoo", "noEndorsedLibs", "true")
it.addCompilerArtifact("math1", "headers = math.h", ArtifactType.INTEROP)
it.addSetting("math1", "noDefaultLibs", "true")
it.addSetting("math1", "noEndorsedLibs", "true")
it.addCompilerArtifact("math2", "headers = math.h", ArtifactType.INTEROP)
it.addSetting("math2", "noDefaultLibs", "true")
it.addSetting("math2", "noEndorsedLibs", "true")
it.addCompilerArtifact("main" ,"""
|fun foo() {}
|
|fun main(args: Array<String>) { foo(); math1.cos(0.0); math2.cos(0.0) }
""".stripMargin())
it.addSetting("main", "noDefaultLibs", "true" )
it.addSetting("main", "noEndorsedLibs", "true" )
it.addLibraryToArtifactCustom("main", "allInteropLibrariesFrom project")
}
project.createRunner()
.withArguments(KonanProject.compilationTask("main"))
.build()
}
def 'Plugin should support custom repositories for libraries'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
it.addSetting("foo", "noDefaultLibs", "true")
it.addSetting("foo", "noEndorsedLibs", "true")
it.addSetting("foo", "baseDir", "file('out')")
it.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo() }")
it.addSetting("main", "noDefaultLibs", "true" )
it.addSetting("main", "noEndorsedLibs", "true" )
it.addSetting("main", "dependsOn", "konanArtifacts.foo.$KonanProject.HOST")
it.addLibraryToArtifactCustom("main", "klib 'foo'")
it.addLibraryToArtifactCustom("main", "useRepo 'out/$KonanProject.HOST'")
}
project.createRunner()
.withArguments(KonanProject.compilationTask("main"), "-i")
.build()
}
def 'Plugin should support library dependencies in the same project'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
it.addSetting("foo", "noDefaultLibs", "true")
it.addSetting("foo", "noEndorsedLibs", "true")
it.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
it.addSetting("bar", "noDefaultLibs", "true")
it.addSetting("bar", "noEndorsedLibs", "true")
it.addLibraryToArtifact("bar", "foo")
it.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
it.addSetting("main", "noDefaultLibs", "true" )
it.addSetting("main", "noEndorsedLibs", "true" )
it.addLibraryToArtifact("main", "bar")
}
project.createRunner()
.withArguments(KonanProject.compilationTask("main"), "-i")
.build()
}
def 'Plugin should support library dependencies from other projects'() {
expect:
def project = KonanProject.createEmpty(projectDirectory)
def subproject1 = KonanProject.createEmpty(project.createSubDir("subproject1")) { KonanProject it ->
it.buildFile.write("apply plugin: 'konan'\n")
}
def subproject2 = KonanProject.createEmpty(project.createSubDir("subproject2")) { KonanProject it ->
it.buildFile.write("apply plugin: 'konan'\n")
}
project.settingsFile.append("include ':subproject1'\ninclude ':subproject2'")
subproject1.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
subproject1.addSetting("foo", "noDefaultLibs", "true")
subproject1.addSetting("foo", "noEndorsedLibs", "true")
subproject2.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
subproject2.addSetting("bar", "noDefaultLibs", "true")
subproject2.addSetting("bar", "noEndorsedLibs", "true")
subproject2.addLibraryToArtifactCustom(
"bar", "artifact rootProject.project('subproject1'), 'foo'"
)
project.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
project.addSetting("main", "noDefaultLibs", "true" )
project.addSetting("main", "noEndorsedLibs", "true" )
project.addLibraryToArtifactCustom(
"main", "artifact project('subproject2'), 'bar'"
)
project.createRunner()
.withArguments(KonanProject.compilationTask("main"))
.build()
}
// TODO: Add tests for incorrect cases (e.g. attempt to use an executable as a library)
}
@@ -1,390 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import spock.lang.Ignore
import java.nio.file.Files
import java.nio.file.Paths
class MultiplatformSpecification extends BaseKonanSpecification {
public static final String KOTLIN_VERSION = System.getProperty("kotlin.version")
public static final String KOTLIN_REPO = System.getProperty("kotlin.repo")
public static final String DEFAULT_COMMON_BUILD_FILE_CONTENT = """\
buildscript {
repositories {
maven {
url = '$KOTLIN_REPO'
}
maven {
url = 'https://cache-redirector.jetbrains.com/maven-central'
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
}
}
apply plugin: 'kotlin-platform-common'
repositories {
maven {
url = '$KOTLIN_REPO'
}
maven {
url = 'https://cache-redirector.jetbrains.com/maven-central'
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-common:$KOTLIN_VERSION"
}
""".stripIndent()
static File createCommonProject(KonanProject platformProject,
String commonProjectName = "common",
String commonBuildFileContent = DEFAULT_COMMON_BUILD_FILE_CONTENT) {
def commonDirectory = platformProject.createSubDir(commonProjectName)
def commonBuildFile = Paths.get(commonDirectory.absolutePath, "build.gradle")
commonBuildFile.write(commonBuildFileContent)
platformProject.settingsFile.append("include ':$commonProjectName'\n")
return commonDirectory
}
static File createCommonSource(File commonDirectory,
Iterable<String> subdirectory,
String fileName,
String content) {
def commonSrcDir = commonDirectory.toPath().resolve(Paths.get(*subdirectory))
def commonSource = commonSrcDir.resolve(fileName)
Files.createDirectories(commonSrcDir)
commonSource.write(content)
return commonSource.toFile()
}
def 'Plugin should support multiplatform projects'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = createCommonProject(it)
createCommonSource(commonDirectory,
["src", "main", "kotlin"],
"common.kt",
"""\
@file:Suppress("OPT_IN_USAGE_ERROR")
@OptionalExpectation
expect annotation class Optional()
@Optional
fun opt() = 42
expect fun foo(): Int
""".stripIndent()
)
it.generateSrcFile("platform.kt", "actual fun foo() = 42")
it.buildFile.append("""
konanArtifacts {
library('foo') {
enableMultiplatform true
}
}
dependencies {
expectedBy project(':common')
}
""".stripIndent())
}
project.createRunner().withArguments(":build").build()
}
def 'Multiplatform projects should be disabled by default'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = createCommonProject(it)
createCommonSource(commonDirectory,
["src", "main", "kotlin"],
"common.kt",
"expect fun foo(): Int")
it.generateSrcFile("platform.kt", "fun foo() = 42")
it.buildFile.append("""
konanArtifacts {
library('foo') {}
}
dependencies {
expectedBy project(':common')
}
""".stripIndent())
}
project.createRunner().withArguments(":build").build()
}
def 'Plugin should use the \'main\' source set as a default common source set'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = createCommonProject(it)
Paths.get(commonDirectory.absolutePath, "build.gradle").append("""
sourceSets {
common.kotlin.srcDir 'src/common/kotlin'
}
""".stripIndent())
createCommonSource(commonDirectory,
["src", "main", "kotlin"],
"common.kt",
"expect fun foo(): Int")
createCommonSource(commonDirectory,
["src", "common", "kotlin"],
"common.kt",
"expect fun bar(): Int")
it.generateSrcFile("platform.kt", "actual fun foo() = 42")
it.buildFile.append("""
konanArtifacts {
library('foo') {
enableMultiplatform true
}
}
dependencies {
expectedBy project(':common')
}
""".stripIndent())
}
project.createRunner().withArguments(":build").build()
}
def 'Plugin should allow a user to specify custom common source set'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = createCommonProject(it)
Paths.get(commonDirectory.absolutePath, "build.gradle").append("""
sourceSets {
common.kotlin.srcDir 'src/common/kotlin'
}
""".stripIndent())
createCommonSource(commonDirectory,
["src", "common", "kotlin"],
"common.kt",
"expect fun bar(): Int")
it.generateSrcFile("platform.kt", "actual fun bar() = 42")
it.buildFile.append("""
konanArtifacts {
library('foo') {
enableMultiplatform true
commonSourceSets 'common'
}
}
dependencies {
expectedBy project(':common')
}
""".stripIndent())
}
project.createRunner().withArguments(":build").build()
}
def 'Plugin should allow setting several common source sets'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = createCommonProject(it)
Paths.get(commonDirectory.absolutePath, "build.gradle").append("""
sourceSets {
common.kotlin.srcDir 'src/common/kotlin'
}
""".stripIndent())
createCommonSource(commonDirectory,
["src", "common", "kotlin"],
"common.kt",
"expect fun bar(): Int")
createCommonSource(commonDirectory,
["src", "main", "kotlin"],
"main.kt",
"expect fun foo() : Int")
it.generateSrcFile("platform.kt", "actual fun bar() = 42\nactual fun foo() = 43")
it.buildFile.append("""
konanArtifacts {
library('foo') {
enableMultiplatform true
commonSourceSets 'common', 'main'
}
}
dependencies {
expectedBy project(':common')
}
""".stripIndent())
}
project.createRunner().withArguments(":build").build()
}
def 'Build should fail if the expectedBy dependency is not a project one'() {
when:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = createCommonProject(it)
createCommonSource(commonDirectory,
["src", "main", "kotlin"],
"common.kt",
"expect fun foo(): Int")
it.generateSrcFile("platform.kt", "actual fun foo() = 42")
it.buildFile.append("""
konanArtifacts {
library('foo') {
enableMultiplatform true
}
}
dependencies {
expectedBy files('common/src/main/kotlin/common.kt')
}
""".stripIndent())
}
def result = project.createRunner().withArguments(":build").buildAndFail()
then:
result.output.contains("dependency is not a project: ")
}
def 'Build should support several expectedBy-dependencies'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = createCommonProject(it, "commonFoo")
createCommonSource(commonDirectory,
["src", "main", "kotlin"],
"common.kt",
"expect fun foo(): Int")
commonDirectory = createCommonProject(it, "commonBar")
createCommonSource(commonDirectory,
["src", "main", "kotlin"],
"common.kt",
"expect fun bar(): Int")
it.generateSrcFile("platform.kt", "actual fun foo() = 0\nactual fun bar() = 0")
it.buildFile.append("""
konanArtifacts {
library('foo') {
enableMultiplatform true
}
}
dependencies {
expectedBy project(':commonFoo')
expectedBy project(':commonBar')
}
""".stripIndent())
}
project.createRunner().withArguments(":build").build()
}
def 'Build should fail if the common project has no common plugin'() {
when:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = createCommonProject(it,"common", "")
createCommonSource(commonDirectory,
["src", "main", "kotlin"],
"common.kt",
"expect fun foo(): Int")
it.generateSrcFile("platform.kt", "actual fun bar() = 42")
it.buildFile.append("""
konanArtifacts {
library('foo') {
enableMultiplatform true
}
}
dependencies {
expectedBy project(':common')
}
""".stripIndent())
}
def result = project.createRunner().withArguments(":build").buildAndFail()
then:
result.output.contains("has an 'expectedBy' dependency to non-common project")
}
@Ignore("TODO in the Big Kotlin plugin")
def 'Build should fail if custom common source set doesn\'t exist'() {
when:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = createCommonProject(it)
createCommonSource(commonDirectory,
["src", "main", "kotlin"],
"common.kt",
"expect fun foo(): Int")
it.generateSrcFile("platform.kt", "actual fun bar() = 42")
it.buildFile.append("""
konanArtifacts {
library('foo') {
enableMultiplatform true
commonSourceSets 'common'
}
}
dependencies {
expectedBy project(':common')
}
""".stripIndent())
}
def result = project.createRunner().withArguments(":build").buildAndFail()
then:
result.output.contains("Cannot find a source set with name 'common' in a common project")
}
def 'Setting custom source set should enable the multiplatform support'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
def commonDirectory = createCommonProject(it)
Paths.get(commonDirectory.absolutePath, "build.gradle").append("""
sourceSets {
common.kotlin.srcDir 'src/common/kotlin'
}
""".stripIndent())
createCommonSource(commonDirectory,
["src", "common", "kotlin"],
"common.kt",
"expect fun bar(): Int")
it.generateSrcFile("platform.kt", "actual fun bar() = 42")
it.buildFile.append("""
konanArtifacts {
library('foo') {
commonSourceSets 'common'
}
}
dependencies {
expectedBy project(':common')
}
""".stripIndent())
}
project.createRunner().withArguments(":build").build()
}
}
@@ -1,130 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.gradle.testkit.runner.TaskOutcome
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.konan.target.PlatformManager
class PathSpecification extends BaseKonanSpecification {
boolean fileExists(KonanProject project, String path) {
project.konanBuildDir.toPath().resolve(path).toFile().exists()
}
def platformManager = new PlatformManager(new Distribution(KonanProject.konanHome, false, null), false)
def 'Plugin should provide a correct path to the artifacts created'() {
expect:
def project = KonanProject.createEmpty(
projectDirectory,
platformManager.enabled.collect { t -> t.visibleName }
) { KonanProject it ->
it.generateSrcFile("main.kt")
it.generateDefFile("interop.def", "")
it.buildFile.append("""
konanArtifacts {
program('program')
library('library')
bitcode('bitcode')
interop('interop')
framework('framework')
dynamic('dynamic')
}
tasks.register("checkArtifacts", DefaultTask) {
dependsOn(':build')
doLast {
for(artifact in konanArtifacts) {
for (target in artifact) {
if (!target.artifact.exists()) throw new Exception("Artifact doesn't exist. Type: \${artifact.name}, target: \${target.target}")
}
}
for (target in konanArtifacts.dynamic) {
if (!target.headerFile.exists()) throw new Exception("Header file doesn't exist. Target: \${target.target}")
}
}
}
""".stripIndent())
}
project.createRunner().withArguments("checkArtifacts").build()
}
def 'Plugin should create all necessary directories'() {
when:
def project = KonanProject.createWithInterop(projectDirectory)
project.addCompilerArtifact("lib", "fun foo() {}", ArtifactType.LIBRARY)
project.addCompilerArtifact("bit", "fun bar() {}", ArtifactType.BITCODE)
project.createRunner().withArguments('build').build()
then:
project.konanBuildDir.toPath().resolve("bin/$KonanProject.HOST").toFile().listFiles().findAll {
File it -> it.file && it.name.matches("^${KonanProject.DEFAULT_ARTIFACT_NAME}\\.[^.]+")
}.size() > 0
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.create(projectDirectory)
project.propertiesFile.write("konan.home=fakepath")
def result = project.createRunner().withArguments('build').buildAndFail()
def task = result.task(project.defaultCompilationTask())
then:
task == null || task.outcome == TaskOutcome.FAILED
}
def 'Plugin should stop building if the stub generator classpath is empty'() {
when:
def project = KonanProject.createWithInterop(projectDirectory)
project.propertiesFile.write("konan.home=fakepath")
def result = project.createRunner().withArguments('build').buildAndFail()
def task = result.task(project.compilationTask(KonanProject.DEFAULT_INTEROP_NAME))
then:
task == null || task.outcome == TaskOutcome.FAILED
}
def 'Plugin should remove custom output directories'() {
when:
def customOutputDir = projectDirectory.toPath().resolve("foo").toFile()
def project = KonanProject.create(projectDirectory, ArtifactType.LIBRARY) { KonanProject it ->
it.addSetting("baseDir", customOutputDir)
}
def res1 = project.createRunner().withArguments("build").build()
def artifactExistsAfterBuild = customOutputDir.toPath()
.resolve("${KonanProject.HOST}/${KonanProject.DEFAULT_ARTIFACT_NAME}.klib").toFile()
.exists()
def res2 = project.createRunner().withArguments("clean").build()
def artifactExistsAfterClean = customOutputDir.toPath()
.resolve("${KonanProject.HOST}/${KonanProject.DEFAULT_ARTIFACT_NAME}.klib").toFile()
.exists()
then:
res1.taskPaths(TaskOutcome.SUCCESS).containsAll(project.buildingTasks)
res2.taskPaths(TaskOutcome.SUCCESS).contains(":clean")
artifactExistsAfterBuild
!artifactExistsAfterClean
}
}
@@ -1,73 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.gradle.testkit.runner.TaskOutcome
class RegressionSpecification extends BaseKonanSpecification {
def 'KT-19916'() {
when:
def project = KonanProject.createEmpty(getProjectDirectory()) { KonanProject prj ->
prj.generateSettingsFile("include ':subproject'")
def subprojectDir = prj.projectPath.resolve("subproject").toFile()
subprojectDir.mkdirs()
subprojectDir.toPath().resolve("build.gradle").write("""
dependencies {
libs gradleApi()
}
""".stripIndent())
prj.buildFile.append("""
subprojects {
apply plugin: 'konan'
apply plugin: Foo
}
class Foo implements Plugin<Project> {
void apply(Project project) {
project.configurations.maybeCreate("libs")
}
}
""".stripIndent())
}
def result = project.createRunner().withArguments('tasks').build()
then:
result.task(':tasks').outcome == TaskOutcome.SUCCESS
}
// Ensure gradle plugin fails in case of linker errors.
def 'KT-20192'() {
when:
def project = KonanProject.createEmpty(getProjectDirectory()) { KonanProject prj ->
prj.addCompilerArtifact(KonanProject.DEFAULT_ARTIFACT_NAME,"""
external fun foo()
fun main(args: Array<String>) {
foo()
}
""", ArtifactType.PROGRAM)
}
def result = project.createRunner().withArguments('build').buildAndFail()
then:
result.taskPaths(TaskOutcome.FAILED).contains(project.defaultCompilationTask())
}
}
@@ -1,167 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import spock.lang.Requires
import spock.lang.Unroll
class TaskSpecification extends BaseKonanSpecification {
def 'Configs should allow user to add dependencies to them'() {
when:
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY)
project.buildFile.append("""
tasks.register("beforeInterop", DefaultTask) { doLast { println("Before Interop") } }
tasks.register("beforeCompilation", DefaultTask) { doLast { println("Before compilation") } }
""".stripIndent())
project.addSetting(KonanProject.DEFAULT_INTEROP_NAME,"dependsOn", "beforeInterop")
project.addSetting("dependsOn", "beforeCompilation")
def result = project.createRunner().withArguments('build').build()
then:
def beforeInterop = result.task(":beforeInterop")
beforeInterop != null && beforeInterop.outcome == TaskOutcome.SUCCESS
def beforeCompilation = result.task(":beforeCompilation")
beforeCompilation != null && beforeCompilation.outcome == TaskOutcome.SUCCESS
}
def 'Compiler should print time measurements if measureTime flag is set'() {
when:
def project = KonanProject.create(projectDirectory, ArtifactType.LIBRARY)
project.addSetting("measureTime", "true")
def result = project.createRunner().withArguments('build').build()
then:
result.output.findAll(~/Frontend builds AST:\s+\d+\s+msec/).size() == 1
result.output.findAll(~/IR Lowering:\s+\d+\s+msec/).size() == 1
}
@Unroll('Plugin should support #option option for cinterop')
def 'Plugin should support includeDir option for cinterop'() {
expect:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.addCompilerArtifact("interopLib", "headers=foo.h\n$headerFilter", ArtifactType.INTEROP)
it.generateSrcFile(it.projectPath, "foo.h", "#include <bar.h>")
def fooDir = it.projectPath.resolve("foo")
it.generateSrcFile(fooDir, "bar.h", "const int foo = 5;")
it.addSetting("interopLib", option, fooDir.toFile())
it.addSetting("interopLib", option, it.projectDir)
}
project.createRunner().withArguments("build").build()
where:
option | headerFilter
"includeDirs.headerFilterOnly" | "headerFilter=foo.h bar.h"
"includeDirs.allHeaders" | ""
"includeDirs" | ""
}
@Requires({ HostManager.host instanceof KonanTarget.MACOS_X64 })
def 'Plugin should create framework tasks only for Apple targets'() {
when:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.buildFile.append("""
konan.targets = ['wasm32', 'macbook', 'iphone', 'iphone_sim']
konanArtifacts {
framework('foo')
}
""".stripIndent())
}
def result = project.createRunner().withArguments('tasks', '--all').build()
then:
!compilationTaskExists(result,'foo', 'wasm32')
compilationTaskExists (result,'foo', 'macbook')
compilationTaskExists (result,'foo', 'iphone')
compilationTaskExists (result,'foo', 'iphone_sim')
}
def 'Plugin should support different targets for different artifacts'() {
when:
def project = KonanProject.createEmpty(projectDirectory, ['host']) { KonanProject it ->
it.buildFile.append("""
konanArtifacts {
program('defaultTarget')
program('customTarget', targets: ['wasm32'])
program('customTargets', targets: ['host', 'wasm32'])
}
""".stripIndent())
}
def result = project.createRunner().withArguments('tasks', '--all').build()
def hostName = HostManager.hostName
then:
compilationTaskExists (result, 'defaultTarget', hostName)
!compilationTaskExists(result, 'defaultTarget', 'wasm32')
!compilationTaskExists(result, 'customTarget', hostName)
compilationTaskExists (result, 'customTarget', 'wasm32')
compilationTaskExists (result, 'customTargets', hostName)
compilationTaskExists (result, 'customTargets', 'wasm32')
}
def 'Plugin should not create dynamic task for wasm'() {
when:
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.buildFile.append("""
konan.targets = ['wasm32']
konanArtifacts {
dynamic('foo')
}
""".stripIndent())
}
def result = project.createRunner().withArguments('tasks', '--all').build()
then:
!compilationTaskExists(result, 'foo', 'wasm32')
}
boolean taskExists(BuildResult result, String taskName) {
def taskNameForSearch = taskName.startsWith(':') ? taskName.substring(1) : taskName
return result.output =~ "\\s$taskNameForSearch\\s"
}
boolean compilationTaskExists(BuildResult result, String artifactName, String targetName) {
return taskExists(result, KonanProject.compilationTask(artifactName, targetName))
}
BuildResult failOnPropertyAccess(KonanProject project, String property) {
project.buildFile.append("""
tasks.register("testTask", DefaultTask) {
doLast {
println(${project.defaultInteropConfig()}.$property)
}
}
""".stripIndent())
return project.createRunner().withArguments("testTask").buildAndFail()
}
BuildResult failOnTaskAccess(KonanProject project, String task) {
project.buildFile.append("""
tasks.register("testTask", DefaultTask) {
dependsOn $task
}
""".stripIndent())
return project.createRunner().withArguments("testTask").buildAndFail()
}
}
@@ -1,48 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import java.io.File
import java.net.URI
import kotlin.test.Test
import kotlin.test.assertTrue
open class CompatibilityTests {
val tmpFolder = TemporaryFolder()
@Rule get
val projectDirectory: File
get() = tmpFolder.root
@Test
fun `Plugin should fail if running with Gradle prior to the required one`() {
val project = KonanProject.createEmpty(projectDirectory)
val result = project
.createRunner()
.withGradleDistribution(URI.create(
"https://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-4.5-bin.zip"
))
.withArguments("tasks")
.buildAndFail()
println(result.output)
assertTrue("Build doesn't show the warning message") {
result.output.contains("Kotlin/Native Gradle plugin is incompatible with this version of Gradle.")
}
}
}
@@ -1,204 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.jetbrains.kotlin.gradle.plugin.test.KonanProject.escapeBackSlashes
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.tools4j.spockito.Spockito
import java.io.File
import kotlin.test.Test
@RunWith(Spockito::class)
open class PropertiesAsEnvVariablesTest {
val tmpFolder = TemporaryFolder()
@Rule get
val projectDirectory: File
get() = tmpFolder.root
private fun artifactFileName(baseName: String, type: ArtifactType, target: KonanTarget = HostManager.host): String {
var suffix = ""
var prefix = ""
when (type) {
ArtifactType.PROGRAM -> suffix = target.family.exeSuffix
ArtifactType.INTEROP,
ArtifactType.LIBRARY -> suffix = "klib"
ArtifactType.BITCODE -> suffix = "bc"
ArtifactType.FRAMEWORK -> suffix = "framework"
ArtifactType.DYNAMIC -> {
prefix = target.family.dynamicPrefix
suffix = target.family.dynamicSuffix
}
ArtifactType.STATIC -> {
prefix = target.family.staticPrefix
suffix = target.family.staticSuffix
}
}
return "$prefix${baseName}.$suffix"
}
private fun assertFileExists(directory: File, filename: String) = assert(directory.list().contains(filename)) {
"No such file: $filename in directory: ${directory.absolutePath}"
}
@Test
@Spockito.Unroll(
"|property |value |assertion |message |",
"|konan.debugging.symbols |YES |it.enableDebug |Debug should be enabled |",
"|konan.debugging.symbols |true |it.enableDebug |Debug should be enabled |",
"|konan.debugging.symbols |NO |!it.enableDebug |Debug should be disabled |",
"|konan.debugging.symbols |false |!it.enableDebug |Debug should be disabled |",
"|konan.optimizations.enable |YES |it.enableOptimizations |Opts should be enabled |",
"|konan.optimizations.enable |true |it.enableOptimizations |Opts should be enabled |",
"|konan.optimizations.enable |NO |!it.enableOptimizations |Opts should be disabled |",
"|konan.optimizations.enable |false |!it.enableOptimizations |Opts should be disabled |"
)
@Spockito.Name("[{row}]: {variable}={value}")
fun `Plugin should support enabling and disabling debug and opt options via a project property`(
property: String,
value: String,
assertion: String,
message: String
) {
val project = KonanProject.createEmpty(projectDirectory)
project.buildFile.appendText("""
apply plugin: 'konan'
konanArtifacts {
library('main')
}
task assertEnableDebug {
doLast {
konanArtifacts.main.forEach {
if (!($assertion)) throw new AssertionError("$message for ${'$'}it.name")
}
}
}
""".trimIndent())
project.createRunner()
.withArguments("assertEnableDebug", "-P${property}=${value}")
.build()
}
@Test
fun `Plugin should support setting destination directory via a project property`() {
val project = KonanProject.createEmpty(projectDirectory)
val newDestinationDir = project.createSubDir("newDestination")
val newDestinationPath = newDestinationDir.absolutePath
project.buildFile.appendText("""
apply plugin: 'konan'
konanArtifacts {
program('program')
library('library')
dynamic('dynamic')
framework('framework')
}
task assertDestinationDir {
doLast {
konanArtifacts.forEach { artifact ->
artifact.forEach {
if (it.destinationDir.absolutePath != '${escapeBackSlashes(newDestinationPath)}'){
throw new AssertionError("Unexpected destination dir for ${'$'}it.name\\n" +
"expected: ${escapeBackSlashes(newDestinationPath)}\\n" +
"actual: ${'$'}it.destinationDir")
}
}
}
}
}
""".trimIndent())
project.generateSrcFile("main.kt")
project.createRunner()
.withArguments("assertDestinationDir", "build", "-Pkonan.configuration.build.dir=$newDestinationPath")
.build()
assertFileExists(newDestinationDir, artifactFileName("program", ArtifactType.PROGRAM))
assertFileExists(newDestinationDir, artifactFileName("library", ArtifactType.LIBRARY))
assertFileExists(newDestinationDir, artifactFileName("dynamic", ArtifactType.DYNAMIC))
if (HostManager.hostIsMac) {
assertFileExists(newDestinationDir, artifactFileName("framework", ArtifactType.FRAMEWORK))
}
}
@Test
fun `Plugin should rerun tasks if konan_configuration_build_dir has been changed`() {
val project = KonanProject.createEmpty(projectDirectory)
val destination1 = project.createSubDir("destination1", "subdir")
val destination2 = project.createSubDir("destination2", "subdir")
project.buildFile.appendText("""
apply plugin: 'konan'
konanArtifacts {
library('main')
}
""".trimIndent())
project.generateSrcFile("main.kt")
project.createRunner()
.withArguments("build", "-Pkonan.configuration.build.dir=${destination1.absolutePath}")
.build()
project.createRunner()
.withArguments("build", "-Pkonan.configuration.build.dir=${destination2.absolutePath}")
.build()
assertFileExists(destination1, artifactFileName("main", ArtifactType.LIBRARY))
assertFileExists(destination2, artifactFileName("main", ArtifactType.LIBRARY))
}
@Test
fun `Up-to-date checks should work with different directories for different targets`() {
val project = KonanProject.createEmpty(projectDirectory)
val fooDir = project.createSubDir("foo")
val barDir = project.createSubDir("bar")
project.buildFile.appendText("""
apply plugin: 'konan'
konanArtifacts {
library('foo')
library('bar')
}
task assertUpToDate {
dependsOn 'compileKonanFoo'
doLast {
if (!konanArtifacts.foo.getByTarget('host').state.upToDate) {
throw new AssertionError("Compilation task is not up-to-date")
}
}
}
""".trimIndent())
project.generateSrcFile("main.kt")
project.createRunner()
.withArguments("compileKonanFoo", "-Pkonan.configuration.build.dir=${fooDir.absolutePath}")
.build()
project.createRunner()
.withArguments("compileKonanBar", "-Pkonan.configuration.build.dir=${barDir.absolutePath}")
.build()
project.createRunner()
.withArguments("assertUpToDate", "-Pkonan.configuration.build.dir=${fooDir.absolutePath}")
.build()
}
}
@@ -1,67 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin.test
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import java.io.File
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class TaskTests {
val tmpFolder = TemporaryFolder()
@Rule get
val projectDirectory: File
get() = tmpFolder.root
@Test
fun `Plugin should support separate run tasks for different binaries`() {
val project = KonanProject.createEmpty(projectDirectory).apply {
buildFile.appendText("""
konanArtifacts {
program('foo') {
srcDir 'src/foo/kotlin'
}
program('bar') {
srcDir 'src/bar/kotlin'
}
}
""".trimIndent())
}
project.generateSrcFile(
listOf("src", "foo", "kotlin"),
"main.kt",
"fun main(args: Array<String>) = println(\"Run Foo: \${args[0]}, \${args[1]}\")")
project.generateSrcFile(
listOf("src", "bar", "kotlin"),
"main.kt",
"fun main(args: Array<String>) = println(\"Run Bar: \${args[0]}, \${args[1]}\")")
val resultFoo = project.createRunner()
.withArguments("runFoo", "-PrunArgs=arg1 arg2")
.build()
val resultAll = project.createRunner()
.withArguments("run", "-PrunArgs=arg1 arg2")
.build()
assertTrue(resultFoo.output.contains("Run Foo: arg1, arg2"), "No Foo output for 'runFoo'")
assertFalse(resultFoo.output.contains("Run Bar: "), "There is Bar output for 'runFoo'")
assertTrue(resultAll.output.contains("Run Foo: arg1, arg2"), "No Foo output for 'run'")
assertTrue(resultAll.output.contains("Run Bar: arg1, arg2"), "No Bar output for 'run'")
}
}