[gradle-plugin] Support static and shared libraries (#1942)

This commit is contained in:
Ilya Matveev
2018-08-28 12:33:07 +07:00
committed by GitHub
parent ec8e619178
commit aee041fc7a
8 changed files with 233 additions and 13 deletions
@@ -102,10 +102,20 @@ interface KotlinNativeLibrary : KotlinNativeBinary,
ConfigurableComponentWithLinkUsage
/**
* Represents an Objective C framework compiled from KN sources.
* Represents an Objective C framework compiled from Kotlin/Native sources.
*/
interface KotlinNativeFramework : KotlinNativeBinary, ComponentWithOutputs
/**
* A shared library compiled from Kotlin/Native sources.
*/
interface KotlinNativeDynamic : KotlinNativeBinary, ComponentWithOutputs
/**
* A static library compiled from Kotlin/Native sources.
*/
interface KotlinNativeStatic : KotlinNativeBinary, ComponentWithOutputs
/**
* Represents a test executable.
*/
@@ -0,0 +1,52 @@
/*
* 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.experimental.internal
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.file.ProjectLayout
import org.gradle.api.internal.file.FileOperations
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.plugin.experimental.KotlinNativeDynamic
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import javax.inject.Inject
open class KotlinNativeDynamicImpl @Inject constructor(
name: String,
baseName: Provider<String>,
componentImplementation: Configuration,
component: KotlinNativeMainComponent,
identity: KotlinNativeVariantIdentity,
projectLayout: ProjectLayout,
objects: ObjectFactory,
configurations: ConfigurationContainer,
fileOperations: FileOperations
) : AbstractKotlinNativeBinary(
name,
baseName,
component,
identity,
projectLayout,
CompilerOutputKind.DYNAMIC,
objects,
componentImplementation,
configurations,
fileOperations
), KotlinNativeDynamic {
override val outputRootName: String = "lib"
}
@@ -93,13 +93,10 @@ open class KotlinNativeMainComponent @Inject constructor(
// endregion
companion object {
@JvmStatic
val EXECUTABLE = OutputKind.EXECUTABLE
@JvmStatic
val KLIBRARY = OutputKind.KLIBRARY
@JvmStatic
val FRAMEWORK = OutputKind.FRAMEWORK
@JvmStatic val EXECUTABLE = OutputKind.EXECUTABLE
@JvmStatic val KLIBRARY = OutputKind.KLIBRARY
@JvmStatic val FRAMEWORK = OutputKind.FRAMEWORK
@JvmStatic val DYNAMIC = OutputKind.DYNAMIC
@JvmStatic val STATIC = OutputKind.STATIC
}
}
@@ -0,0 +1,53 @@
/*
* 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.experimental.internal
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.file.ProjectLayout
import org.gradle.api.internal.file.FileOperations
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.plugin.experimental.KotlinNativeStatic
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import javax.inject.Inject
open class KotlinNativeStaticImpl @Inject constructor(
name: String,
baseName: Provider<String>,
componentImplementation: Configuration,
component: KotlinNativeMainComponent,
identity: KotlinNativeVariantIdentity,
projectLayout: ProjectLayout,
objects: ObjectFactory,
configurations: ConfigurationContainer,
fileOperations: FileOperations
) : AbstractKotlinNativeBinary(
name,
baseName,
component,
identity,
projectLayout,
CompilerOutputKind.STATIC,
objects,
componentImplementation,
configurations,
fileOperations
), KotlinNativeStatic {
override val outputRootName: String = "lib"
}
@@ -54,6 +54,26 @@ enum class OutputKind(
) {
override fun availableFor(target: KonanTarget) =
target.family == Family.OSX || target.family == Family.IOS
},
DYNAMIC(
CompilerOutputKind.DYNAMIC,
KotlinNativeDynamicImpl::class.java,
3,
Usage.NATIVE_RUNTIME,
Usage.NATIVE_LINK,
false
) {
override fun availableFor(target: KonanTarget): Boolean = target != KonanTarget.WASM32
},
STATIC(
CompilerOutputKind.STATIC,
KotlinNativeStaticImpl::class.java,
4,
Usage.NATIVE_RUNTIME,
Usage.NATIVE_LINK,
false
) {
override fun availableFor(target: KonanTarget): Boolean = target != KonanTarget.WASM32
};
open fun availableFor(target: KonanTarget) = true
@@ -42,6 +42,7 @@ import org.jetbrains.kotlin.gradle.plugin.hasProperty
import org.jetbrains.kotlin.gradle.plugin.konanCompilerDownloadDir
import org.jetbrains.kotlin.gradle.plugin.setProperty
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompilerDownloadTask
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
@@ -120,6 +121,18 @@ class KotlinNativeBasePlugin: Plugin<ProjectInternal> {
).apply {
group = BasePlugin.BUILD_GROUP
description = "Compiles Kotlin/Native source set '${binary.sourceSet.name}' into a ${binary.kind.name.toLowerCase()}"
// Register an API header produced for shared/static library as a task output.
if (binary.kind == CompilerOutputKind.DYNAMIC || binary.kind == CompilerOutputKind.STATIC) {
val headerFileProvider = provider {
with(binary) {
val prefix = kind.prefix(konanTarget)
val baseName = getBaseName().get().replace('-', '_')
outputFile.parentFile.resolve("$prefix${baseName}_api.h")
}
}
outputs.file(headerFileProvider)
}
}
binary.compileTask.set(compileTask)
binary.outputs.from(compileTask.outputLocationProvider)
@@ -30,6 +30,8 @@ import org.jetbrains.kotlin.gradle.plugin.addArg
import org.jetbrains.kotlin.gradle.plugin.addKey
import org.jetbrains.kotlin.gradle.plugin.experimental.internal.AbstractKotlinNativeBinary
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.CompilerOutputKind.*
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
import javax.inject.Inject
@@ -68,7 +70,11 @@ open class KotlinNativeCompile @Inject constructor(internal val binary: Abstract
val baseName = getBaseName().get()
var fileName = "${prefix}${baseName}${suffix}"
if (kind == CompilerOutputKind.FRAMEWORK) {
if (kind == FRAMEWORK ||
kind == STATIC ||
kind == DYNAMIC ||
kind == PROGRAM && konanTarget == KonanTarget.WASM32
) {
fileName = fileName.replace('-', '_')
}
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.gradle.plugin.experimental.internal.KotlinNativeMain
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.tasks.KotlinNativeCompile
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.junit.Assume.assumeTrue
@@ -45,6 +46,9 @@ class ExperimentalPluginTests {
project.block()
}
private fun assertFileExists(path: String, message: String = "No such file: $path")
= assertTrue(projectDirectory.resolve(path).exists(), message)
@Test
fun `Plugin should compile one executable`() {
val project = KonanProject.create(projectDirectory).apply {
@@ -457,9 +461,6 @@ class ExperimentalPluginTests {
}
}
val ttt = project.createRunner().withArguments("tasks").build()
println(ttt.output)
val result1 = project.createRunner().withArguments("assemble").build()
assertCompileOutcome(result1, compileTasks, TaskOutcome.SUCCESS)
@@ -484,4 +485,72 @@ class ExperimentalPluginTests {
assertEquals("test_framework_project", frameworkTask.outputFile.nameWithoutExtension)
assertEquals("test-framework-project", klibraryTask.outputFile.nameWithoutExtension)
}
@Test
fun `Plugin should be able to build static and dynamic libraries`() {
val project = KonanProject.create(projectDirectory).apply {
buildFile.writeText("""
plugins { id 'kotlin-native' }
sourceSets.main {
component {
outputKinds = [ DYNAMIC, STATIC ]
target 'host'
}
}
""".trimIndent())
settingsFile.appendText("\nrootProject.name = 'test-library'")
}
val baseName = "test_library"
val sharedPrefix = CompilerOutputKind.DYNAMIC.prefix(HostManager.host)
val sharedSuffix = CompilerOutputKind.DYNAMIC.suffix(HostManager.host)
val staticPrefix = CompilerOutputKind.STATIC.prefix(HostManager.host)
val staticSuffix = CompilerOutputKind.STATIC.suffix(HostManager.host)
val libraryPaths = listOf(
"build/lib/main/debug/dynamic/$sharedPrefix$baseName$sharedSuffix",
"build/lib/main/release/dynamic/$sharedPrefix$baseName$sharedSuffix",
"build/lib/main/debug/static/$staticPrefix$baseName$staticSuffix",
"build/lib/main/release/static/$staticPrefix$baseName$staticSuffix"
)
val headerPaths = listOf(
"build/lib/main/debug/dynamic/$sharedPrefix${baseName}_api.h",
"build/lib/main/release/dynamic/$sharedPrefix${baseName}_api.h",
"build/lib/main/debug/static/$staticPrefix${baseName}_api.h",
"build/lib/main/release/static/$staticPrefix${baseName}_api.h"
)
val linkTasks = listOf(
":compileDebugDynamicKotlinNative",
":compileReleaseDynamicKotlinNative",
":compileDebugStaticKotlinNative",
":compileReleaseStaticKotlinNative"
)
project.createRunner().withArguments("assemble", "-i").build().let { result ->
libraryPaths.forEach { assertFileExists(it) }
headerPaths.forEach { assertFileExists(it) }
linkTasks.forEach { assertEquals(TaskOutcome.SUCCESS, result.task(it)!!.outcome) }
}
project.createRunner().withArguments("assemble").build().let { result ->
linkTasks.forEach { assertEquals(TaskOutcome.UP_TO_DATE, result.task(it)!!.outcome) }
}
assertTrue(projectDirectory.resolve(headerPaths[0]).delete())
project.createRunner().withArguments("assemble").build().let { result ->
assertEquals(TaskOutcome.SUCCESS, result.task(linkTasks[0])!!.outcome)
linkTasks.drop(1).forEach {
assertEquals(TaskOutcome.UP_TO_DATE, result.task(it)!!.outcome)
}
libraryPaths.forEach { assertFileExists(it) }
headerPaths.forEach { assertFileExists(it) }
}
}
}