From a033dbf131d9fb5cb7fd70e3afee6f7ed4529924 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 22 Nov 2017 18:14:02 +0300 Subject: [PATCH] gradle-plugin: generate CMake file --- .../kotlin/gradle/plugin/KonanPlugin.kt | 2 + .../plugin/tasks/KonanGenerateCMakeTask.kt | 140 ++++++++++++++++++ .../plugin/test/CMakeSpecification.groovy | 68 +++++++++ 3 files changed, 210 insertions(+) create mode 100644 tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/tasks/KonanGenerateCMakeTask.kt create mode 100644 tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/CMakeSpecification.groovy diff --git a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanPlugin.kt b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanPlugin.kt index 2b7a5dbfaa3..c60e7d3222f 100644 --- a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanPlugin.kt +++ b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KonanPlugin.kt @@ -240,6 +240,7 @@ class KonanPlugin @Inject constructor(private val registry: ToolingModelBuilderR companion object { internal const val ARTIFACTS_CONTAINER_NAME = "konanArtifacts" internal const val KONAN_DOWNLOAD_TASK_NAME = "checkKonanCompiler" + internal const val KONAN_GENERATE_CMAKE_TASK_NAME = "generateCMake" internal const val COMPILE_ALL_TASK_NAME = "compileKonan" internal const val KONAN_EXTENSION_NAME = "konan" @@ -260,6 +261,7 @@ class KonanPlugin @Inject constructor(private val registry: ToolingModelBuilderR project.plugins.apply("base") // Create necessary tasks and extensions. project.tasks.create(KONAN_DOWNLOAD_TASK_NAME, KonanCompilerDownloadTask::class.java) + project.tasks.create(KONAN_GENERATE_CMAKE_TASK_NAME, KonanGenerateCMakeTask::class.java) project.extensions.create(KONAN_EXTENSION_NAME, KonanExtension::class.java) project.extensions.create(ARTIFACTS_CONTAINER_NAME, KonanArtifactContainer::class.java, project) diff --git a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/tasks/KonanGenerateCMakeTask.kt b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/tasks/KonanGenerateCMakeTask.kt new file mode 100644 index 00000000000..2ee87fd6b70 --- /dev/null +++ b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/tasks/KonanGenerateCMakeTask.kt @@ -0,0 +1,140 @@ +/* + * 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.DefaultTask +import org.gradle.api.file.FileCollection +import org.gradle.api.tasks.TaskAction +import org.jetbrains.kotlin.gradle.plugin.KonanInteropLibrary +import org.jetbrains.kotlin.gradle.plugin.KonanLibrary +import org.jetbrains.kotlin.gradle.plugin.KonanProgram +import org.jetbrains.kotlin.gradle.plugin.konanArtifactsContainer +import org.jetbrains.kotlin.konan.target.TargetManager +import java.io.File + +open class KonanGenerateCMakeTask : DefaultTask() { + @Suppress("unused") + @TaskAction + fun generateCMake() { + val interops = project.konanArtifactsContainer.toList().filterIsInstance() + val libraries = project.konanArtifactsContainer.toList().filterIsInstance() + val programs = project.konanArtifactsContainer.toList().filterIsInstance() + val cMakeLists = generateCMakeLists( + project.name, + interops, + libraries, + programs + ) + File(project.projectDir, "CMakeLists.txt") + .writeText(cMakeLists) + + // This directory is filled out by the IDE + File(project.projectDir, "KotlinCMakeModule") + .mkdir() + } + + private fun generateCMakeLists( + projectName: String, + interops: List, + libraries: List, + programs: List + ): String { + val cMakeCurrentListDir = "$" + "{CMAKE_CURRENT_LIST_DIR}" + + return buildString { + appendln(""" + cmake_minimum_required(VERSION 3.8) + + set(CMAKE_MODULE_PATH $cMakeCurrentListDir/KotlinCMakeModule) + + project($projectName Kotlin) + """.trimIndent()) + appendln() + + for (interop in interops) { + val task = interop[TargetManager.host] ?: continue + appendln( + Call("cinterop") + .arg("NAME", interop.name) + .arg("DEF_FILE", task.defFile.relativePath.toString()) + .arg("COMPILER_OPTS", task.cMakeCompilerOpts) + ) + } + + for (library in libraries) { + val task = library[TargetManager.host] ?: continue + appendln( + Call("konanc_library") + .arg("NAME", library.name) + .arg("SOURCES", task.cMakeSources) + .arg("LIBRARIES", task.cMakeLibraries) + .arg("LINKER_OPTS", task.cMakeLinkerOpts)) + } + + for (program in programs) { + val task = program[TargetManager.host] ?: continue + appendln( + Call("konanc_executable") + .arg("NAME", program.name) + .arg("SOURCES", task.cMakeSources) + .arg("LIBRARIES", task.cMakeLibraries) + .arg("LINKER_OPTS", task.cMakeLinkerOpts)) + } + } + } + + private val File.relativePath get() = relativeTo(project.projectDir) + + private val FileCollection.asCMakeSourceList: List + get() = files.map { it.relativePath.toString() } + + private val KonanInteropTask.cMakeCompilerOpts: String + get() = compilerOpts.joinToString(" ") + + private val KonanCompileTask.cMakeSources: String + get() = srcFiles.flatMap { it.asCMakeSourceList }.joinToString(" ") + + private val KonanCompileTask.cMakeLibraries: String + get() = mutableListOf().apply { + addAll(libraries.artifacts.map { it.artifactName }) + addAll(libraries.namedKlibs) + addAll(libraries.files.flatMap { it.files }.map { it.canonicalPath }) + }.joinToString(" ") + + private val KonanCompileTask.cMakeLinkerOpts: String + get() = linkerOpts.joinToString(" ") +} + +private class Call(val name: String) { + private val args: MutableList> = mutableListOf() + + fun arg(key: String, value: String?): Call { + if (value != null && value.isNotBlank()) args += key to value + return this + } + + override fun toString(): String = + buildString { + append(name) + append("(") + for ((key, value) in args) { + appendln() + append(" $key $value") + } + appendln(")") + } +} diff --git a/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/CMakeSpecification.groovy b/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/CMakeSpecification.groovy new file mode 100644 index 00000000000..33360795b39 --- /dev/null +++ b/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/CMakeSpecification.groovy @@ -0,0 +1,68 @@ +package org.jetbrains.kotlin.gradle.plugin.test + +class CMakeSpecification extends BaseKonanSpecification { + + def 'Plugin should generate CMake from project without additional settings'() { + when: + def project = KonanProject.createEmpty(projectDirectory) { KonanProject it -> + it.buildFile.write(""" + plugins { id 'konan' } + konanArtifacts { + interop('stdio') + interop('sdl') { + defFile 'src/main/c_interop/sdl.def' + includeDirs '/usr/include/SDL2' + } + library('main_lib') + program('Main') { + libraries { + artifact 'stdio' + artifact 'main_lib' + linkerOpts '-L/usr/lib/x86_64-linux-gnu' + } + } + } + """.stripIndent()) + it.generateDefFile("stdio.def", "") + it.generateSrcFile("main.kt") + } + project.createRunner().withArguments('generateCMake').build() + + def cMakeModule = new File(projectDirectory, "KotlinCMakeModule") + def cMakeLists = new File(projectDirectory, "CMakeLists.txt") + def expectedCMakeLists = """ + cmake_minimum_required(VERSION 3.8) + + set(CMAKE_MODULE_PATH \${CMAKE_CURRENT_LIST_DIR}/KotlinCMakeModule) + + project(${projectDirectory.name} Kotlin) + + cinterop( + NAME sdl + DEF_FILE src/main/c_interop/sdl.def + COMPILER_OPTS -I/usr/include/SDL2) + + cinterop( + NAME stdio + DEF_FILE src/main/c_interop/stdio.def) + + konanc_library( + NAME main_lib + SOURCES src/main/kotlin/main.kt) + + konanc_executable( + NAME Main + SOURCES src/main/kotlin/main.kt + LIBRARIES stdio main_lib + LINKER_OPTS -L/usr/lib/x86_64-linux-gnu) + """.stripIndent().trim() + + then: + cMakeModule.exists() + cMakeLists.exists() + + def actualCMakeLists = cMakeLists.text.trim() + actualCMakeLists == expectedCMakeLists + } + +}