Support -Xexport-library for K/N in the MPP plugin

This commit is contained in:
Ilya Matveev
2018-12-14 13:30:39 +07:00
parent 0bcb253e53
commit c963da7761
4 changed files with 113 additions and 33 deletions
@@ -14,22 +14,10 @@ import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import javax.inject.Inject
/*
Naming:
executable('foo', [debug, release]) -> fooDebugExecutable + fooReleaseExecutable
executable -> debugExecutable, releaseExecutable
executable([debug]) -> debugExecutable
// Tests:
1. Cannot add a second binary with the same parameters.
2. Can access a binary by name
3. Access linkTask
4. Access runTask
5. Final binaries have correct names
6. Old APIs work fine:
- creating binaries,
- getting binaries and link tasks,
- setting binary parameters using compilation DSL: linker opts and entry points
Use the following naming scheme:
executable('foo', [debug, release]) -> fooDebugExecutable + fooReleaseExecutable
executable() -> debugExecutable, releaseExecutable
executable([debug]) -> debugExecutable
*/
open class KotlinNativeBinaryContainer @Inject constructor(
@@ -131,7 +119,7 @@ open class KotlinNativeBinaryContainer @Inject constructor(
}
require(outputKind.availableFor(target.konanTarget)) {
"Cannot create binary $name: ${outputKind.taskNameClassifier.decapitalize()} binaries are not available for target ${target.name}"
"Cannot create ${outputKind.description}: $name. Binaries of this kind are not available for target ${target.name}"
}
val binary = create(name, baseName, buildType, defaultCompilation)
@@ -7,10 +7,7 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.PublishArtifact
import org.gradle.api.artifacts.*
import org.gradle.api.artifacts.type.ArtifactTypeDefinition
import org.gradle.api.attributes.Usage
import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE
@@ -29,6 +26,7 @@ import org.gradle.api.tasks.testing.Test
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.gradle.language.jvm.tasks.ProcessResources
import org.gradle.nativeplatform.test.tasks.RunTestExecutable
import org.jetbrains.kotlin.backend.common.atMostOne
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
@@ -464,7 +462,7 @@ open class KotlinNativeTargetConfigurator(
// endregion.
// region Task creation.
private fun Project.createLinkTask(target: KotlinNativeTarget, binary: NativeBinary) {
private fun Project.createLinkTask(binary: NativeBinary) {
tasks.create(
binary.linkTaskName,
KotlinNativeLink::class.java
@@ -569,6 +567,7 @@ open class KotlinNativeTargetConfigurator(
override fun configureTarget(target: KotlinNativeTarget) {
super.configureTarget(target)
configureBinaries(target)
configureFrameworkExport(target)
configureCInterops(target)
warnAboutIncorrectDependencies(target)
}
@@ -610,8 +609,9 @@ open class KotlinNativeTargetConfigurator(
protected fun configureBinaries(target: KotlinNativeTarget) {
val project = target.project
// Create link and run tasks.
target.binaries.all {
project.createLinkTask(target, it)
project.createLinkTask(it)
}
target.binaries.withType(Executable::class.java).all {
@@ -655,6 +655,33 @@ open class KotlinNativeTargetConfigurator(
}
}
fun configureFrameworkExport(target: KotlinNativeTarget) {
val project = target.project
target.compilations.all {
// Allow resolving api configurations directly to be able to check that
// all exported dependency are also added in the corresponding api configurations.
// The check is performed during a link task execution.
project.configurations.maybeCreate(it.apiConfigurationName).apply {
isCanBeResolved = true
usesPlatformOf(target)
attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.consumerApiUsage(target))
}
}
target.binaries.withType(Framework::class.java).all { framework ->
val exportConfiguration = project.configurations.maybeCreate(framework.exportConfigurationName).apply {
isVisible = false
isTransitive = false
isCanBeConsumed = false
isCanBeResolved = true
usesPlatformOf(target)
attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.consumerApiUsage(target))
description = "Dependenceis to be exported in framework ${framework.name} for target ${target.targetName}"
}
}
}
override fun defineConfigurationsForTarget(target: KotlinNativeTarget) {
super.defineConfigurationsForTarget(target)
val configurations = target.project.configurations
@@ -725,7 +752,7 @@ open class KotlinNativeTargetConfigurator(
usesPlatformOf(target)
isVisible = false
isCanBeConsumed = false
attributes.attribute(USAGE_ATTRIBUTE, compilation.target.project.usageByName(KotlinUsages.KOTLIN_API))
attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.consumerApiUsage(target))
description = "Dependencies for cinterop '${cinterop.name}' (compilation '${compilation.name}')."
}
}
@@ -149,7 +149,22 @@ class Framework(
override val outputKind: NativeOutputKind
get() = NativeOutputKind.FRAMEWORK
// TODO: Pack task configuration.
val exportConfigurationName: String
get() = target.disambiguateName(lowerCamelCaseName(name, "export"))
var transitiveExport: Boolean
get() = project.configurations.maybeCreate(exportConfigurationName).isTransitive
set(value) {
project.configurations.maybeCreate(exportConfigurationName).isTransitive = value
}
fun export(dependency: Any) {
project.dependencies.add(exportConfigurationName, dependency)
}
fun export(dependency: Any, configure: Closure<*>) {
project.dependencies.add(exportConfigurationName, dependency, configure)
}
}
@@ -3,6 +3,7 @@ package org.jetbrains.kotlin.gradle.tasks
import groovy.lang.Closure
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.artifacts.*
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.provider.Provider
@@ -77,6 +78,11 @@ private fun FileCollection.filterOutPublishableInteropLibs(project: Project): Fi
}
}
private fun Collection<File>.filterExternalKlibs(project: Project) = filter {
// Support only klib files for now.
it.extension == "klib" && !it.providedByCompiler(project)
}
// endregion
abstract class AbstractKotlinNativeCompile : AbstractCompile(), KotlinCompile<KotlinCommonOptions> {
@@ -270,10 +276,7 @@ abstract class AbstractKotlinNativeCompile : AbstractCompile(), KotlinCompile<Ko
addArg("-o", outputFile.get().absolutePath)
// Libraries.
libraries.files.filter {
// Support only klib files for now.
it.extension == "klib" && !it.providedByCompiler(project)
}.forEach { library ->
libraries.files.filterExternalKlibs(project).forEach { library ->
addArg("-l", library.absolutePath)
}
}
@@ -357,14 +360,64 @@ open class KotlinNativeLink : AbstractKotlinNativeCompile() {
val linkerOpts: List<String>
get() = binary.linkerOpts
@get:InputFiles
val exportLibraries: FileCollection
get() = binary.let {
if (it is Framework) {
project.configurations.getByName(it.exportConfigurationName)
} else {
project.files()
}
}
override fun buildArgs(defaultsOnly: Boolean): List<String> {
val superArgs = super.buildArgs(defaultsOnly)
return mutableListOf<String>().apply {
addAll(superArgs)
addArgIfNotNull("-entry", entryPoint)
addListArg("-linker-options", linkerOpts)
exportLibraries.files.filterExternalKlibs(project).forEach {
add("-Xexport-library=${it.absolutePath}")
}
}
}
private fun validatedExportedLibraries() {
val exportConfiguration = exportLibraries as? Configuration ?: return
val apiFiles = project.configurations.getByName(compilation.apiConfigurationName).files.filterExternalKlibs(project)
val failed = mutableSetOf<Dependency>()
exportConfiguration.allDependencies.forEach {
val dependencyFiles = exportConfiguration.files(it).filterExternalKlibs(project)
if (!apiFiles.containsAll(dependencyFiles)) {
failed.add(it)
}
}
check(failed.isEmpty()) {
val failedDependenciesList = failed.joinToString(separator = "\n") {
when (it) {
is FileCollectionDependency -> "|Files: ${it.files.files}"
is ProjectDependency -> "|Project ${it.dependencyProject.path}"
else -> "|${it.group}:${it.name}:${it.version}"
}
}
"""
|Following dependencies exported in the ${binary.name} binary are not specified as API-dependencies of a corresponding source set:
|
$failedDependenciesList
|
|Please add them in the API-dependencies and rerun the build.
""".trimMargin()
}
}
@TaskAction
override fun compile() {
validatedExportedLibraries()
super.compile()
}
}
open class CInteropProcess : DefaultTask() {
@@ -450,10 +503,7 @@ open class CInteropProcess : DefaultTask() {
addArg("-lopt", it)
}
libraries.files.filter {
// Support only klib files for now.
it.extension == "klib" && !it.providedByCompiler(project)
}.forEach { library ->
libraries.files.filterExternalKlibs(project).forEach { library ->
addArg("-l", library.absolutePath)
}