From 710c691aa8eb720a5b5dec808771dd6a4e8c3dbe Mon Sep 17 00:00:00 2001 From: Ilya Matveev Date: Thu, 6 Sep 2018 19:15:39 +0700 Subject: [PATCH] [gradle-plugin] Support Konan tooling model in the exp plugin (#2008) --- .../experimental/KotlinNativeBinaries.kt | 14 +-- .../gradle/plugin/model/KonanModelImpl.kt | 69 ++++++++++++-- .../test/kotlin/ExperimentalPluginTests.kt | 91 ++++++++++++++++++- 3 files changed, 156 insertions(+), 18 deletions(-) diff --git a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/experimental/KotlinNativeBinaries.kt b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/experimental/KotlinNativeBinaries.kt index 98fc9b1bc55..3f30576bcc9 100644 --- a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/experimental/KotlinNativeBinaries.kt +++ b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/experimental/KotlinNativeBinaries.kt @@ -32,7 +32,7 @@ import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.konan.target.KonanTarget // TODO: implement ComponentWithObjectFiles when we build klibs as objects -interface KotlinNativeBinary: ComponentWithDependencies, BuildableComponent { +interface KotlinNativeBinary : ComponentWithDependencies, BuildableComponent, ComponentWithOutputs { /** A component this binary belongs to. */ val component: KotlinNativeComponent @@ -86,7 +86,6 @@ interface KotlinNativeBinary: ComponentWithDependencies, BuildableComponent { */ // TODO: Consider implementing ComponentWithExecutable and ComponentWithInstallation. interface KotlinNativeExecutable : KotlinNativeBinary, - ComponentWithOutputs, PublishableComponent, ConfigurableComponentWithRuntimeUsage @@ -95,29 +94,26 @@ interface KotlinNativeExecutable : KotlinNativeBinary, */ // TODO: Consider implementing ComponentWithLinkFile. interface KotlinNativeLibrary : KotlinNativeBinary, - ComponentWithOutputs, PublishableComponent, ConfigurableComponentWithLinkUsage /** * Represents an Objective C framework compiled from Kotlin/Native sources. */ -interface KotlinNativeFramework : KotlinNativeBinary, ComponentWithOutputs +interface KotlinNativeFramework : KotlinNativeBinary /** * A shared library compiled from Kotlin/Native sources. */ -interface KotlinNativeDynamic : KotlinNativeBinary, ComponentWithOutputs +interface KotlinNativeDynamic : KotlinNativeBinary /** * A static library compiled from Kotlin/Native sources. */ -interface KotlinNativeStatic : KotlinNativeBinary, ComponentWithOutputs +interface KotlinNativeStatic : KotlinNativeBinary /** * Represents a test executable. */ // TODO: Consider implementing ComponentWithExecutable and ComponentWithInstallation. -interface KotlinNativeTestExecutable : KotlinNativeBinary, - ComponentWithOutputs, - TestComponent \ No newline at end of file +interface KotlinNativeTestExecutable : KotlinNativeBinary, TestComponent \ No newline at end of file diff --git a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/model/KonanModelImpl.kt b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/model/KonanModelImpl.kt index 5facae5d019..af8c8dde354 100644 --- a/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/model/KonanModelImpl.kt +++ b/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/model/KonanModelImpl.kt @@ -18,29 +18,84 @@ package org.jetbrains.kotlin.gradle.plugin.model import org.gradle.api.Project import org.gradle.tooling.provider.model.ToolingModelBuilder +import org.jetbrains.kotlin.gradle.plugin.experimental.internal.AbstractKotlinNativeBinary import org.jetbrains.kotlin.gradle.plugin.konanArtifactsContainer import org.jetbrains.kotlin.gradle.plugin.konanExtension import org.jetbrains.kotlin.gradle.plugin.konanHome import org.jetbrains.kotlin.konan.KonanVersion import org.jetbrains.kotlin.konan.target.CompilerOutputKind import java.io.File +import java.lang.IllegalStateException object KonanToolingModelBuilder : ToolingModelBuilder { override fun canBuild(modelName: String) = KonanModel::class.java.name == modelName - override fun buildAll(modelName: String, project: Project): KonanModel { + private fun buildModelKonan(project: Project): KonanModel { val artifacts = project.konanArtifactsContainer.flatten().toList().map { it.toModelArtifact() } return KonanModelImpl( - artifacts, - project.file(project.konanHome), - KonanVersion.CURRENT, - // TODO: Provide defaults for these versions. - project.konanExtension.languageVersion, - project.konanExtension.apiVersion + artifacts, + project.file(project.konanHome), + KonanVersion.CURRENT, + // TODO: Provide defaults for these versions. + project.konanExtension.languageVersion, + project.konanExtension.apiVersion ) } + + private fun buildModelKotlinNative(project: Project): KonanModel { + val artifacts = project.components.withType(AbstractKotlinNativeBinary::class.java).map { + it.toModelArtifact() + } + return KonanModelImpl( + artifacts, + project.file(project.konanHome), + KonanVersion.CURRENT, + null, + null + ) + } + + private fun AbstractKotlinNativeBinary.toModelArtifact(): KonanModelArtifact { + val compileTask = compileTask.get() + val sourceRoots = with(component.sources) { + kotlin.srcDirs + getPlatformSources(konanTarget).srcDirs + } + return KonanModelArtifactImpl( + name, + compileTask.outputFile, + kind, + konanTarget.name, + compileTask.name, + sourceRoots.toList(), + sources.files.toList(), + klibraries.files.toList(), + klibraries.files.map { it.parentFile } + ) + } + + private val Project.hasKonanPlugin: Boolean + get() = with(pluginManager) { + hasPlugin("konan") || + hasPlugin("org.jetbrains.kotlin.konan") + } + + private val Project.hasKotlinNativePlugin: Boolean + get() = with(pluginManager) { + hasPlugin("kotlin-native") || + hasPlugin("kotlin-platform-native") || + hasPlugin("org.jetbrains.kotlin.native") || + hasPlugin("org.jetbrains.kotlin.platform.native") + } + + + override fun buildAll(modelName: String, project: Project): KonanModel = + when { + project.hasKotlinNativePlugin -> buildModelKotlinNative(project) + project.hasKonanPlugin -> buildModelKonan(project) + else -> throw IllegalStateException("The project '${project.path}' has no Kotlin/Native plugin") + } } internal data class KonanModelImpl( diff --git a/tools/kotlin-native-gradle-plugin/src/test/kotlin/ExperimentalPluginTests.kt b/tools/kotlin-native-gradle-plugin/src/test/kotlin/ExperimentalPluginTests.kt index 7134d29e7ed..83b8a96ba2f 100644 --- a/tools/kotlin-native-gradle-plugin/src/test/kotlin/ExperimentalPluginTests.kt +++ b/tools/kotlin-native-gradle-plugin/src/test/kotlin/ExperimentalPluginTests.kt @@ -9,7 +9,9 @@ import org.gradle.testkit.runner.TaskOutcome import org.jetbrains.kotlin.gradle.plugin.experimental.internal.KotlinNativeMainComponent import org.jetbrains.kotlin.gradle.plugin.experimental.internal.OutputKind import org.jetbrains.kotlin.gradle.plugin.experimental.plugins.KotlinNativePlugin +import org.jetbrains.kotlin.gradle.plugin.experimental.plugins.kotlinNativeSourceSets import org.jetbrains.kotlin.gradle.plugin.experimental.tasks.KotlinNativeCompile +import org.jetbrains.kotlin.gradle.plugin.model.KonanToolingModelBuilder import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget @@ -406,8 +408,8 @@ class ExperimentalPluginTests { @Test fun `Plugin should not create compilation tasks for targets unsupported by the current host`() = withProject { - val hosts = arrayOf("macos_x64", "linux_x64", "mingw_x64") - components.withType(KotlinNativeMainComponent::class.java).getByName("main").target(*hosts) + val hosts = listOf("macos_x64", "linux_x64", "mingw_x64") + components.withType(KotlinNativeMainComponent::class.java).getByName("main").targets = hosts evaluate() hosts.map { HostManager().targetByName(it) }.forEach { val task = tasks.findByName("compileDebug${it.name.capitalize()}KotlinNative") @@ -727,4 +729,89 @@ class ExperimentalPluginTests { output.contains("Transitive call!") } } + + @Test + fun `Plugin should support the konan tooling model`() { + withProject { + val hostName = HostManager.hostName + val mainSrcDirs = listOf("src/main/kotlin", "src/other/kotlin").map { + file(it).apply { mkdirs() } + } + val testDir = file("src/test/kotlin").apply { mkdirs() } + + val mainSrcFiles = listOf( + "src/main/kotlin/main.kt" to "fun main(args: Array) { foo() }", + "src/other/kotlin/foo.kt" to "fun foo() { println(42) }" + ).map { (name, content) -> + project.file(name).apply { createNewFile(); writeText(content) } + } + val testSrcFile = file("src/test/kotlin/test.kt").apply { + createNewFile() + writeText(""" + import kotlin.test.* + @Test fun test() { foo() } + """.trimIndent()) + } + + components.withType(KotlinNativeMainComponent::class.java).getByName("main").apply { + outputKinds.set(listOf(OutputKind.KLIBRARY, OutputKind.EXECUTABLE)) + targets = listOf(hostName, "wasm32") + } + kotlinNativeSourceSets.getByName("main").kotlin.srcDirs(*mainSrcDirs.toTypedArray()) + + evaluate() + + val model = KonanToolingModelBuilder.buildAll("konan", this) + val mainArtifacts = model.artifacts.filterNot { it.name.toLowerCase().contains("test") } + val testArtifacts = model.artifacts.filter { it.name.toLowerCase().contains("test") } + + mainArtifacts.forEach { + assertEquals(mainSrcDirs.toSet(), it.srcDirs.toSet()) + assertEquals(mainSrcFiles.toSet(), it.srcFiles.toSet()) + } + testArtifacts.forEach { + assertEquals(setOf(testDir), it.srcDirs.toSet()) + assertEquals(setOf(testSrcFile) + mainSrcFiles.toSet(), it.srcFiles.toSet()) + } + + val nameToArtifact = model.artifacts.map { it.name to it }.toMap() + + val buildTypes = listOf("debug", "release") + val kinds = listOf(OutputKind.EXECUTABLE, OutputKind.KLIBRARY) + val targets = listOf(HostManager.host, KonanTarget.WASM32) + + // Production binaries + buildTypes.forEach { buildType -> + kinds.forEach { kind -> + targets.forEach { target -> + val suffix = "${buildType.capitalize()}${kind.name.toLowerCase().capitalize()}${target.name.capitalize()}" + val artifact = nameToArtifact.getValue("main$suffix") + assertEquals(target.name, artifact.targetPlatform) + assertEquals(kind.compilerOutputKind, artifact.type) + assertEquals("compile${suffix}KotlinNative", artifact.buildTaskName) + + val outputRoot = if (kind == OutputKind.EXECUTABLE) "exe" else "lib" + val outputFile = file( + "build/$outputRoot/main/$buildType/${kind.name.toLowerCase()}/${target.name}/" + + "testProject${kind.compilerOutputKind.suffix(target)}" + ) + assertEquals(outputFile, artifact.file) + } + } + } + + // Test binaries + targets.forEach { target -> + val suffix = "Debug${target.name.capitalize()}" + val artifact = nameToArtifact.getValue("test$suffix") + assertEquals(target.name, artifact.targetPlatform) + assertEquals(CompilerOutputKind.PROGRAM, artifact.type) + assertEquals("compileTest${suffix}KotlinNative", artifact.buildTaskName) + assertEquals( + file("build/test-exe/test/debug/${target.name}/test${CompilerOutputKind.PROGRAM.suffix(target)}"), + artifact.file + ) + } + } + } } \ No newline at end of file