diff --git a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/EnvironmentVariables.kt b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/EnvironmentVariables.kt new file mode 100644 index 00000000000..248fb3b82be --- /dev/null +++ b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/EnvironmentVariables.kt @@ -0,0 +1,13 @@ +package org.jetbrains.kotlin.gradle.plugin + +import java.io.File + +object EnvironmentVariables { + val configurationBuildDir: File? + get() = System.getenv("CONFIGURATION_BUILD_DIR")?.let { + File(it).apply {check(isAbsolute) { "A path passed using CONFIGURATION_BUILD_DIR should be absolute" } } + } + + val debuggingSymbols: Boolean + get() = System.getenv("DEBUGGING_SYMBOLS")?.toUpperCase() == "YES" +} \ No newline at end of file diff --git a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanBuildingConfig.kt b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanBuildingConfig.kt index 66b87b10e96..3876f68f3a2 100644 --- a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanBuildingConfig.kt +++ b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanBuildingConfig.kt @@ -93,9 +93,28 @@ abstract class KonanBuildingConfig(private val name_: Stri targetToTask[toAdd.konanTarget] = toAdd } + 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 and have + // different names depending on their target (e.g. foo/bar/baz_linux.kexe and foo/bar/baz_macbook.kexe). + protected fun determineOutputPlacement(target: KonanTarget): OutputPlacement { + val configurationBuildDir = EnvironmentVariables.configurationBuildDir + return if (configurationBuildDir != null) { + OutputPlacement(configurationBuildDir, "${name}_${target.visibleName}") + } else { + OutputPlacement(defaultBaseDir.targetSubdir(target), name) + } + } + protected fun createTask(target: KonanTarget): T = project.tasks.create(generateTaskName(target), type) { - it.init(defaultBaseDir.targetSubdir(target), name, target) + val outputDescription = determineOutputPlacement(target) + it.init(outputDescription.destinationDir, outputDescription.artifactName, target) it.group = BasePlugin.BUILD_GROUP it.description = generateTaskDescription(it) } ?: throw Exception("Cannot create task for target: ${target.visibleName}") diff --git a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/tasks/KonanCompileTask.kt b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/tasks/KonanCompileTask.kt index b80decfa770..235a5270f17 100644 --- a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/tasks/KonanCompileTask.kt +++ b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/tasks/KonanCompileTask.kt @@ -69,9 +69,8 @@ abstract class KonanCompileTask: KonanBuildingTask(), KonanCompileSpec { @Input val linkerOpts = mutableListOf() - @Input var enableDebug = - project.properties.containsKey("enableDebug") && - project.properties["enableDebug"].toString().toBoolean() + @Input var enableDebug = project.findProperty("enableDebug")?.toString()?.toBoolean() + ?: EnvironmentVariables.debuggingSymbols @Input var noStdLib = false @Input var noMain = false diff --git a/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/EnvVariableSpecification.groovy b/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/EnvVariableSpecification.groovy new file mode 100644 index 00000000000..10d40cbac05 --- /dev/null +++ b/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/EnvVariableSpecification.groovy @@ -0,0 +1,216 @@ +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.Unroll + + +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 TestKid 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. + runner.withArguments("wrapper").build() + + def classpath = runner.pluginClasspath.collect { "'$it.absolutePath'" }.join(", ") + project.buildFile.write("""\ + buildscript { + dependencies { + classpath files($classpath) + } + } + """.stripIndent()) + return project + } + + private WrapperResult runWrapper(KonanProject project, List tasks, Map environment = [:]) { + def wrapper = (HostManager.host.family == Family.WINDOWS) ? "gradlew.bat" : "gradlew" + def command = ["$project.projectDir.absolutePath/$wrapper".toString()] + command.addAll(tasks) + 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 environment = [:]) { + return runWrapper(project, [task], environment) + } + + 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 + } + return "$prefix${baseName}_${target.visibleName}.$suffix" + } + + @Unroll("Plugin should support #action debug via an env variable") + def 'Plugin should support enabling/disabling debug via an env variable'() { + when: + def project = createProjectWithWrapper() + project.buildFile.append("""\ + apply plugin: 'konan' + konanArtifacts { + library('main') + } + + task assertEnableDebug { + doLast { + konanArtifacts.main.forEach { + $check + } + } + } + """.stripIndent()) + def result = runWrapper(project,"assertEnableDebug", ["DEBUGGING_SYMBOLS": value]) + .printStderr() + .getExitValue() + + then: + result == 0 + + where: + action |value |check + "enabling" |"YES" |"if (!it.enableDebug) throw new AssertionError(\"Debug should be enabled for \${it.name}\")" + "disabling" |"NO" |"if (it.enableDebug) throw new AssertionError(\"Debug should be disabled for \${it.name}\")" + } + + 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 != '$newDestinationPath'){ + throw new AssertionError("Unexpected destination dir for \$it.name\\n" + + "expected: $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)) + if (HostManager.hostIsMac) { + files.contains(artifactFileName("framework", ArtifactType.FRAMEWORK)) + } + } + + def 'Plugin should throw an exception if CONFIGURATION_BUILD_DIR contains a relative path'() { + when: + def project = createProjectWithWrapper() + project.buildFile.append("""\ + apply plugin: 'konan' + konanArtifacts { + library('main') + } + """.stripIndent()) + def wrapperResult = runWrapper(project, "tasks", ["CONFIGURATION_BUILD_DIR": "some_relative_path"]) + + then: + wrapperResult.getExitValue() != 0 + wrapperResult.getStderr().contains("A path passed using CONFIGURATION_BUILD_DIR should be absolute") + } + + 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)) + } + +}