Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTasks.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("PackageDirectoryMismatch") // Old package for compatibility
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.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.api.artifacts.result.DependencyResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.file.*
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.cli.common.arguments.*
import org.jetbrains.kotlin.compilerRunner.*
import org.jetbrains.kotlin.compilerRunner.KotlinNativeCInteropRunner.Companion.run
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
import org.jetbrains.kotlin.gradle.internal.isInIdeaSync
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationInfo
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerArgumentsProducer.CreateCompilerArgumentsContext
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerArgumentsProducer.CreateCompilerArgumentsContext.Companion.create
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmMetadataCompilationData
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmNativeCompilationData
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.native.KonanPropertiesBuildService
import org.jetbrains.kotlin.gradle.targets.native.internal.isAllowCommonizer
import org.jetbrains.kotlin.gradle.targets.native.tasks.*
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.listFilesOrEmpty
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageMode
import org.jetbrains.kotlin.konan.library.KLIB_INTEROP_IR_PROVIDER_IDENTIFIER
import org.jetbrains.kotlin.konan.properties.saveToFile
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.CompilerOutputKind.*
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.project.model.LanguageSettings
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
import java.io.File
import java.nio.file.Files
import javax.inject.Inject
import kotlin.collections.associateBy
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.isNotEmpty
import kotlin.collections.orEmpty
import kotlin.collections.plus
import kotlin.collections.plusAssign
import kotlin.collections.set
import org.jetbrains.kotlin.konan.file.File as KFile
import org.jetbrains.kotlin.utils.ResolvedDependencies as KResolvedDependencies
import org.jetbrains.kotlin.utils.ResolvedDependenciesSupport as KResolvedDependenciesSupport
import org.jetbrains.kotlin.utils.ResolvedDependency as KResolvedDependency
import org.jetbrains.kotlin.utils.ResolvedDependencyArtifactPath as KResolvedDependencyArtifactPath
import org.jetbrains.kotlin.utils.ResolvedDependencyId as KResolvedDependencyId
import org.jetbrains.kotlin.utils.ResolvedDependencyVersion as KResolvedDependencyVersion
// TODO: It's just temporary tasks used while KN isn't integrated with Big Kotlin compilation infrastructure.
// region Useful extensions
internal fun MutableList.addArg(parameter: String, value: String) {
add(parameter)
add(value)
}
internal fun MutableList.addArgs(parameter: String, values: Iterable) {
values.forEach {
addArg(parameter, it)
}
}
internal fun MutableList.addArgIfNotNull(parameter: String, value: String?) {
if (value != null) {
addArg(parameter, value)
}
}
internal fun MutableList.addFileArgs(parameter: String, values: FileCollection) {
values.files.forEach {
addArg(parameter, it.canonicalPath)
}
}
/**
* We pass to the compiler:
*
* - Only *.klib files and directories (normally containing an unpacked klib).
* A dependency configuration may contain jar files
* (e.g. when a common artifact was directly added to commonMain source set).
* So, we need to filter out such artifacts.
*
* - Only existing files. We don't compile a klib if there are no sources
* for it (NO-SOURCE check). So we need to take this case into account
* and skip libraries that were not compiled. See also: GH-2617 (K/N repo).
*/
private val File.canKlibBePassedToCompiler get() = (extension == "klib" || isDirectory) && exists()
internal fun Collection.filterKlibsPassedToCompiler(): List = filter(File::canKlibBePassedToCompiler)
/* Returned FileCollection is lazy */
internal fun FileCollection.filterKlibsPassedToCompiler(): FileCollection = filter(File::canKlibBePassedToCompiler)
// endregion
abstract class AbstractKotlinNativeCompile<
T : KotlinCommonToolOptions,
M : CommonToolArguments
>
@Inject constructor(
private val objectFactory: ObjectFactory
) : AbstractKotlinCompileTool(objectFactory) {
@get:Inject
protected abstract val projectLayout: ProjectLayout
@get:Internal
internal abstract val compilation: KotlinCompilationInfo
// region inputs/outputs
@get:Input
abstract val outputKind: CompilerOutputKind
@get:Input
abstract val optimized: Boolean
@get:Input
abstract val debuggable: Boolean
@get:Internal
abstract val baseName: String
@get:Input
@get:Optional
internal abstract val explicitApiMode: Property
@get:Internal
protected val konanTarget by project.provider {
when (val compilation = compilation) {
is KotlinCompilationInfo.KPM -> (compilation.compilationData as GradleKpmNativeCompilationData<*>).konanTarget
is KotlinCompilationInfo.TCS -> (compilation.compilation as AbstractKotlinNativeCompilation).konanTarget
}
}
@get:Classpath
override val libraries: ConfigurableFileCollection = objectFactory.fileCollection().from(
{
// Avoid resolving these dependencies during task graph construction when we can't build the target:
if (konanTarget.enabledOnCurrentHost)
objectFactory.fileCollection().from({ compilation.compileDependencyFiles })
else objectFactory.fileCollection()
}
)
@get:Classpath
internal val friendModule: FileCollection = project.files({ compilation.friendPaths })
@get:Classpath
internal val refinesModule: FileCollection = project.files({ compilation.refinesPaths })
@get:Input
val target: String by project.provider { konanTarget.name }
// region Compiler options.
@Deprecated(
message = "AbstractKotlinNativeCompile will not provide access to kotlinOptions." +
" Implementations should provide access to compilerOptions",
)
@get:Internal
abstract val kotlinOptions: T
@Deprecated(
message = "AbstractKotlinNativeCompile will not provide access to kotlinOptions()." +
" Implementations should provide access to compilerOptions()",
)
abstract fun kotlinOptions(fn: T.() -> Unit)
@Deprecated(
message = "AbstractKotlinNativeCompile will not provide access to kotlinOptions()." +
" Implementations should provide access to compilerOptions()",
)
abstract fun kotlinOptions(fn: Closure<*>)
@Deprecated("Use implementations compilerOptions to get/set freeCompilerArgs")
@get:Input
abstract val additionalCompilerOptions: Provider>
@get:Internal
val languageSettings: LanguageSettings by project.provider {
compilation.languageSettings
}
@Suppress("DeprecatedCallableAddReplaceWith")
@get:Deprecated("Replaced with 'compilerOptions.progressiveMode'")
@get:Internal
val progressiveMode: Boolean
get() = languageSettings.progressiveMode
// endregion.
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated(
"Please declare explicit dependency on kotlinx-cli. This option has no longer effect since 1.9.0",
level = DeprecationLevel.ERROR
)
@get:Input
val enableEndorsedLibs: Boolean
get() = false
@get:Input
val kotlinNativeVersion: String = project.konanVersion
@get:Input
val artifactVersion = project.version.toString()
@get:Input
internal val useEmbeddableCompilerJar: Boolean = project.nativeUseEmbeddableCompilerJar
@get:Internal
open val outputFile: Provider
get() = destinationDirectory.flatMap {
val prefix = outputKind.prefix(konanTarget)
val suffix = outputKind.suffix(konanTarget)
val filename = "$prefix${baseName}$suffix".let {
when {
outputKind == FRAMEWORK ->
it.asValidFrameworkName()
outputKind in listOf(STATIC, DYNAMIC) || outputKind == PROGRAM && konanTarget == KonanTarget.WASM32 ->
it.replace('-', '_')
else -> it
}
}
objectFactory.property(it.file(filename).asFile)
}
// endregion
@Internal
val compilerPluginOptions = CompilerPluginOptions()
@get:Input
val compilerPluginCommandLine
get() = compilerPluginOptions.arguments
@Optional
@Classpath
open var compilerPluginClasspath: FileCollection? = null
/**
* Plugin Data provided by [KpmCompilerPlugin]
*/
@get:Optional
@get:Nested
var kotlinPluginData: Provider? = null
private val languageSettingsBuilder by project.provider {
compilation.languageSettings
}
@get:Input
@get:Optional
internal val konanTargetsForManifest: String by project.provider {
@Suppress("CAST_NEVER_SUCCEEDS") // TODO: this warning looks very suspicious, as if the code never works as intended.
(compilation as? KotlinSharedNativeCompilation)
?.konanTargets
?.joinToString(separator = " ") { it.visibleName }
.orEmpty()
}
@get:Internal
internal val manifestFile: Provider get() = projectLayout.buildDirectory.file("tmp/$name/inputManifest")
}
// Remove it once actual K2NativeCompilerArguments will be available without 'kotlin.native.enabled = true' flag
class StubK2NativeCompilerArguments : CommonCompilerArguments() {
override fun copyOf(): Freezable = copyCommonCompilerArguments(this, StubK2NativeCompilerArguments())
}
/**
* A task producing a klibrary from a compilation.
*/
@CacheableTask
abstract class KotlinNativeCompile
@Inject
internal constructor(
@get:Internal
@Transient // can't be serialized for Gradle configuration cache
final override val compilation: KotlinCompilationInfo,
override val compilerOptions: KotlinNativeCompilerOptions,
private val objectFactory: ObjectFactory,
private val providerFactory: ProviderFactory,
private val execOperations: ExecOperations
) : AbstractKotlinNativeCompile(objectFactory),
KotlinCompile,
K2MultiplatformCompilationTask,
KotlinCompilationTask {
@get:Input
override val outputKind = LIBRARY
@get:Input
override val optimized = false
@get:Input
override val debuggable = true
@get:Internal
override val baseName: String by lazy {
if (compilation.isMain) project.name
else "${project.name}_${compilation.compilationName}"
}
// Store as an explicit provider in order to allow Gradle Instant Execution to capture the state
// private val allSourceProvider = compilation.map { project.files(it.allSources).asFileTree }
@Deprecated(
message = "Please use 'compilerOptions.moduleName' to configure",
replaceWith = ReplaceWith("compilerOptions.moduleName.get()")
)
@get:Internal
val moduleName: String get() = compilerOptions.moduleName.get()
@get:OutputFile
override val outputFile: Provider
get() = super.outputFile
@get:Input
val shortModuleName: String by providerFactory.provider { baseName }
// Inputs and outputs.
// region Sources.
@get:Internal // these sources are normally a subset of `source` ones which are already tracked
val commonSources: ConfigurableFileCollection = project.files()
@get:Nested
override val multiplatformStructure: K2MultiplatformStructure = objectFactory.newInstance()
private val commonSourcesTree: FileTree
get() = commonSources.asFileTree
// endregion.
// region Language settings imported from a SourceSet.
@Deprecated(
message = "Replaced with kotlinOptions.languageVersion",
replaceWith = ReplaceWith("kotlinOptions.languageVersion")
)
val languageVersion: String?
@Optional @Input get() = languageSettings.languageVersion
@Deprecated(
message = "Replaced with kotlinOptions.apiVersion",
replaceWith = ReplaceWith("kotlinOptions.apiVersion")
)
val apiVersion: String?
@Optional @Input get() = languageSettings.apiVersion
val enabledLanguageFeatures: Set
@Input get() = languageSettings.enabledLanguageFeatures
@Deprecated(
message = "Replaced with compilerOptions.optIn",
replaceWith = ReplaceWith("compilerOptions.optIn")
)
val optInAnnotationsInUse: Set
@Internal get() = compilerOptions.optIn.get().toSet()
// endregion.
// region Kotlin options
override val kotlinOptions: KotlinCommonOptions = object : KotlinCommonOptions {
override val options: KotlinCommonCompilerOptions
get() = compilerOptions
}
override fun kotlinOptions(fn: KotlinCommonOptions.() -> Unit) {
kotlinOptions.fn()
}
@Deprecated(
message = "Replaced with kotlinOptions()",
replaceWith = ReplaceWith("kotlinOptions(fn)")
)
override fun kotlinOptions(fn: Closure<*>) {
@Suppress("DEPRECATION")
fn.delegate = kotlinOptions
fn.call()
}
@Deprecated(
message = "Replaced with compilerOptions.freeCompilerArgs",
replaceWith = ReplaceWith("compilerOptions.freeCompilerArgs.get()")
)
@get:Input
override val additionalCompilerOptions: Provider>
get() = compilerOptions
.freeCompilerArgs
.map { it + (languageSettings as DefaultLanguageSettingsBuilder).freeCompilerArgsForNonImport }
private val runnerSettings = KotlinNativeCompilerRunner.Settings.fromProject(project)
private val isAllowCommonizer: Boolean by lazy { project.isAllowCommonizer() }
// endregion.
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("KTIJ-25227: Necessary override for IDEs < 2023.2", level = DeprecationLevel.ERROR)
override fun setupCompilerArgs(args: K2NativeCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
@Suppress("DEPRECATION_ERROR")
super.setupCompilerArgs(args, defaultsOnly, ignoreClasspathResolutionErrors)
}
override fun createCompilerArguments(context: CreateCompilerArgumentsContext) = context.create {
val sharedCompilationData = createSharedCompilationDataOrNull()
val compilerPlugins = listOfNotNull(
compilerPluginClasspath?.let { CompilerPluginData(it, compilerPluginOptions) },
kotlinPluginData?.orNull?.let { CompilerPluginData(it.classpath, it.options) }
)
primitive { args ->
args.moduleName = compilerOptions.moduleName.get()
args.shortModuleName = shortModuleName
args.multiPlatform = true
args.noendorsedlibs = true
args.outputName = outputFile.get().absolutePath
args.optimization = optimized
args.debug = debuggable
args.enableAssertions = debuggable
args.target = konanTarget.name
args.produce = outputKind.name.toLowerCaseAsciiOnly()
args.expectActualLinker = sharedCompilationData != null
args.metadataKlib = sharedCompilationData != null
args.nodefaultlibs = sharedCompilationData != null
args.manifestFile = sharedCompilationData?.manifestFile?.absolutePath
args.pluginOptions = compilerPlugins.flatMap { it.options.arguments }.toTypedArray()
/* Shared native compilations in K2 still use -Xcommon-sources and klib dependencies */
if (compilerOptions.usesK2.get() && sharedCompilationData == null) {
args.fragments = multiplatformStructure.fragmentsCompilerArgs
args.fragmentRefines = multiplatformStructure.fragmentRefinesCompilerArgs
}
KotlinNativeCompilerOptionsHelper.fillCompilerArguments(compilerOptions, args)
explicitApiMode.orNull?.run { args.explicitApi = toCompilerValue() }
}
pluginClasspath { args ->
args.pluginClasspaths = compilerPlugins.flatMap { classpath -> runSafe { classpath.files } ?: emptySet() }.toPathsArray()
}
dependencyClasspath { args ->
args.libraries = runSafe { libraries.files.filterKlibsPassedToCompiler().toPathsArray() }
args.friendModules = runSafe {
friendModule.files.takeIf { it.isNotEmpty() }?.map { it.absolutePath }?.joinToString(File.pathSeparator)
}
args.refinesPaths = runSafe {
sharedCompilationData?.refinesPaths?.files?.takeIf { it.isNotEmpty() }?.toPathsArray()
}
}
sources { args ->
/* Shared native compilations in K2 still use -Xcommon-sources and klib dependencies */
if (compilerOptions.usesK2.get() && sharedCompilationData == null) {
args.fragmentSources = multiplatformStructure.fragmentSourcesCompilerArgs(sourceFileFilter)
} else {
args.commonSources = commonSourcesTree.files.takeIf { it.isNotEmpty() }?.toPathsArray()
}
args.freeArgs += sources.asFileTree.map { it.absolutePath }
}
}
private val isMetadataCompilation: Boolean = when (compilation) {
is KotlinCompilationInfo.KPM -> compilation.compilationData is GradleKpmMetadataCompilationData<*>
is KotlinCompilationInfo.TCS -> compilation.compilation is KotlinMetadataCompilation<*>
}
private fun createSharedCompilationDataOrNull(): SharedCompilationData? {
if (!isMetadataCompilation) return null
val manifestFile: File = manifestFile.get().asFile
manifestFile.ensureParentDirsCreated()
val properties = java.util.Properties()
properties[KLIB_PROPERTY_NATIVE_TARGETS] = konanTargetsForManifest
properties.saveToFile(org.jetbrains.kotlin.konan.file.File(manifestFile.toPath()))
return SharedCompilationData(manifestFile, isAllowCommonizer, refinesModule)
}
@TaskAction
fun compile() {
val output = outputFile.get()
output.parentFile.mkdirs()
collectCommonCompilerStats()
val arguments = createCompilerArguments()
val buildArguments = ArgumentUtils.convertArgumentsToStringList(arguments)
KotlinNativeCompilerRunner(
settings = runnerSettings,
executionContext = KotlinToolRunner.GradleExecutionContext.fromTaskContext(objectFactory, execOperations, logger)
).run(buildArguments)
}
private fun collectCommonCompilerStats() {
KotlinBuildStatsService.getInstance()?.apply {
report(BooleanMetrics.KOTLIN_PROGRESSIVE_MODE, compilerOptions.progressiveMode.get())
compilerOptions.apiVersion.orNull?.also { v ->
report(StringMetrics.KOTLIN_API_VERSION, v.version)
}
compilerOptions.languageVersion.orNull?.also { v ->
report(StringMetrics.KOTLIN_LANGUAGE_VERSION, v.version)
}
}
}
}
internal class ExternalDependenciesBuilder(
val project: Project,
val compilation: KotlinCompilation<*>,
intermediateLibraryName: String?
) {
constructor(project: Project, compilation: KotlinNativeCompilation) : this(
project, compilation, compilation.compileKotlinTask.moduleName
)
private val compileDependencyConfiguration: Configuration
get() = project.configurations.getByName(compilation.compileDependencyConfigurationName)
private val sourceCodeModuleId: KResolvedDependencyId =
intermediateLibraryName?.let { KResolvedDependencyId(it) } ?: KResolvedDependencyId.DEFAULT_SOURCE_CODE_MODULE_ID
private val konanPropertiesService: KonanPropertiesBuildService
get() = KonanPropertiesBuildService.registerIfAbsent(project).get()
fun buildCompilerArgs(): List {
val dependenciesFile = writeDependenciesFile(buildDependencies(), deleteOnExit = true)
return if (dependenciesFile != null)
listOf("-Xexternal-dependencies=${dependenciesFile.path}")
else
emptyList()
}
private fun buildDependencies(): Collection {
// Collect all artifacts.
val moduleNameToArtifactPaths: MutableMap> = mutableMapOf()
compileDependencyConfiguration.incoming.artifacts.artifacts.mapNotNull { resolvedArtifact ->
val uniqueName = (resolvedArtifact.id.componentIdentifier as? ModuleComponentIdentifier)?.uniqueName ?: return@mapNotNull null
val artifactPath = resolvedArtifact.file.absolutePath
moduleNameToArtifactPaths.getOrPut(uniqueName) { mutableSetOf() } += KResolvedDependencyArtifactPath(artifactPath)
}
// The build system may express the single module as two modules where the first one is a common
// module without artifacts and the second one is a platform-specific module with mandatory artifact.
// Example: "org.jetbrains.kotlinx:atomicfu" (common) and "org.jetbrains.kotlinx:atomicfu-macosx64" (platform-specific).
// Both such modules should be merged into a single module with just two names:
// "org.jetbrains.kotlinx:atomicfu (org.jetbrains.kotlinx:atomicfu-macosx64)".
val moduleIdsToMerge: MutableMap = mutableMapOf()
// Collect plain modules.
val plainModules: MutableMap = mutableMapOf()
val processedDependencies = hashSetOf()
fun processModule(resolvedDependency: DependencyResult, incomingDependencyId: KResolvedDependencyId) {
if (resolvedDependency !is ResolvedDependencyResult) return
if (resolvedDependency.isConstraint) return
if (!processedDependencies.add(resolvedDependency)) return
val requestedModule = resolvedDependency.requested as? ModuleComponentSelector ?: return
val selectedModule = resolvedDependency.selected
val selectedModuleId = selectedModule.id as? ModuleComponentIdentifier ?: return
val moduleId = KResolvedDependencyId(selectedModuleId.uniqueName)
val module = plainModules.getOrPut(moduleId) {
val artifactPaths = moduleId.uniqueNames.asSequence()
.mapNotNull { uniqueName -> moduleNameToArtifactPaths[uniqueName] }
.firstOrNull()
.orEmpty()
KResolvedDependency(
id = moduleId,
selectedVersion = KResolvedDependencyVersion(selectedModuleId.version),
requestedVersionsByIncomingDependencies = mutableMapOf(), // To be filled in just below.
artifactPaths = artifactPaths.toMutableSet()
)
}
// Record the requested version of the module by the current incoming dependency.
module.requestedVersionsByIncomingDependencies[incomingDependencyId] = KResolvedDependencyVersion(requestedModule.version)
// TODO: Use [ResolvedDependencyResult.resolvedVariant.externalVariant] to find a connection between platform-specific
// and common modules when "resolvedVariant" and "externalVariant" graduate from incubating state.
if (module.artifactPaths.isNotEmpty()) {
val originModuleId = resolvedDependency.from.id as? ModuleComponentIdentifier
if (originModuleId != null
&& selectedModuleId.group == originModuleId.group
&& selectedModuleId.module.startsWith(originModuleId.module)
&& selectedModuleId.version == originModuleId.version
) {
// These two modules should be merged.
moduleIdsToMerge[moduleId] = KResolvedDependencyId(originModuleId.uniqueName)
}
}
selectedModule.dependencies.forEach { processModule(it, incomingDependencyId = moduleId) }
}
compileDependencyConfiguration.incoming.resolutionResult.root.dependencies.forEach { dependencyResult ->
processModule(dependencyResult, incomingDependencyId = sourceCodeModuleId)
}
if (moduleIdsToMerge.isEmpty())
return plainModules.values
// Do merge.
val replacedModules: MutableMap = mutableMapOf()
moduleIdsToMerge.forEach { (platformSpecificModuleId, commonModuleId) ->
val platformSpecificModule = plainModules.getValue(platformSpecificModuleId)
val commonModule = plainModules.getValue(commonModuleId)
val replacementModuleId = KResolvedDependencyId(platformSpecificModuleId.uniqueNames + commonModuleId.uniqueNames)
val replacementModule = KResolvedDependency(
id = replacementModuleId,
visibleAsFirstLevelDependency = commonModule.visibleAsFirstLevelDependency,
selectedVersion = commonModule.selectedVersion,
requestedVersionsByIncomingDependencies = mutableMapOf().apply {
this += commonModule.requestedVersionsByIncomingDependencies
this += platformSpecificModule.requestedVersionsByIncomingDependencies - commonModuleId
},
artifactPaths = mutableSetOf().apply {
this += commonModule.artifactPaths
this += platformSpecificModule.artifactPaths
}
)
replacedModules[platformSpecificModuleId] = replacementModule
replacedModules[commonModuleId] = replacementModule
}
// Assemble new modules together (without "replaced" and with "replacements").
val mergedModules: MutableMap = mutableMapOf()
mergedModules += plainModules - replacedModules.keys
replacedModules.values.forEach { replacementModule -> mergedModules[replacementModule.id] = replacementModule }
// Fix references to point to "replacement" modules instead of "replaced" modules.
mergedModules.values.forEach { module ->
module.requestedVersionsByIncomingDependencies.mapNotNull { (replacedModuleId, requestedVersion) ->
val replacementModuleId = replacedModules[replacedModuleId]?.id ?: return@mapNotNull null
Triple(replacedModuleId, replacementModuleId, requestedVersion)
}.forEach { (replacedModuleId, replacementModuleId, requestedVersion) ->
module.requestedVersionsByIncomingDependencies.remove(replacedModuleId)
module.requestedVersionsByIncomingDependencies[replacementModuleId] = requestedVersion
}
}
return mergedModules.values
}
private fun writeDependenciesFile(dependencies: Collection, deleteOnExit: Boolean): File? {
if (dependencies.isEmpty()) return null
val dependenciesFile = Files.createTempFile("kotlin-native-external-dependencies", ".deps").toAbsolutePath().toFile()
if (deleteOnExit) dependenciesFile.deleteOnExit()
dependenciesFile.writeText(KResolvedDependenciesSupport.serialize(KResolvedDependencies(dependencies, sourceCodeModuleId)))
return dependenciesFile
}
private val ModuleComponentIdentifier.uniqueName: String
get() = "$group:$module"
companion object {
@Suppress("unused") // Used for tests only. Accessed via reflection.
@JvmStatic
fun buildExternalDependenciesFileForTests(project: Project): File? {
val compilation = project.tasks.asSequence()
.filterIsInstance()
.map { it.binary }
.filterIsInstance() // Not TestExecutable or any other kind of NativeBinary. Strictly Executable!
.firstOrNull()
?.compilation
?: return null
return with(ExternalDependenciesBuilder(project, compilation)) {
val dependencies = buildDependencies().sortedBy { it.id.toString() }
writeDependenciesFile(dependencies, deleteOnExit = false)
}
}
}
}
internal class CacheBuilder(
private val executionContext: KotlinToolRunner.GradleExecutionContext,
private val settings: Settings,
private val konanPropertiesService: KonanPropertiesBuildService,
) {
class Settings(
val runnerSettings: KotlinNativeCompilerRunner.Settings,
val konanCacheKind: NativeCacheKind,
val libraries: FileCollection,
val gradleUserHomeDir: File,
val binary: NativeBinary,
val konanTarget: KonanTarget,
val toolOptions: KotlinCommonCompilerToolOptions,
val externalDependenciesArgs: List
) {
val rootCacheDirectory
get() = getRootCacheDirectory(
File(runnerSettings.parent.konanHome),
konanTarget,
binary.debuggable,
konanCacheKind
)
companion object {
fun createWithProject(
project: Project,
binary: NativeBinary,
konanTarget: KonanTarget,
toolOptions: KotlinCommonCompilerToolOptions,
externalDependenciesArgs: List
): Settings {
val konanCacheKind = project.getKonanCacheKind(konanTarget)
return Settings(
runnerSettings = KotlinNativeCompilerRunner.Settings.fromProject(project),
konanCacheKind = konanCacheKind,
libraries = binary.compilation.compileDependencyFiles,
gradleUserHomeDir = project.gradle.gradleUserHomeDir,
binary, konanTarget, toolOptions, externalDependenciesArgs
)
}
}
}
private val nativeSingleFileResolveStrategy: SingleFileKlibResolveStrategy
get() = CompilerSingleFileKlibResolveAllowingIrProvidersStrategy(
listOf(KLIB_INTEROP_IR_PROVIDER_IDENTIFIER)
)
private val binary: NativeBinary
get() = settings.binary
private val konanTarget: KonanTarget
get() = settings.konanTarget
private val optimized: Boolean
get() = binary.optimized
private val debuggable: Boolean
get() = binary.debuggable
private val konanCacheKind: NativeCacheKind
get() = settings.konanCacheKind
// Inputs and outputs
private val libraries: FileCollection
get() = settings.libraries
private val target: String
get() = konanTarget.name
private val rootCacheDirectory: File
get() = settings.rootCacheDirectory
private val partialLinkageMode: String
get() = settings.toolOptions.freeCompilerArgs.get().mapNotNull { arg ->
arg.substringAfter("$PARTIAL_LINKAGE_PARAMETER=", missingDelimiterValue = "").takeIf(String::isNotEmpty)
}.lastOrNull() ?: PartialLinkageMode.DEFAULT.name
private fun getCacheDirectory(
resolvedConfiguration: LazyResolvedConfiguration,
dependency: ResolvedDependencyResult
): File = getCacheDirectory(
rootCacheDirectory = rootCacheDirectory,
dependency = dependency,
artifact = null,
resolvedConfiguration = resolvedConfiguration,
partialLinkageMode = partialLinkageMode
)
private fun needCache(libraryPath: String) =
libraryPath.startsWith(settings.gradleUserHomeDir.absolutePath) && libraryPath.endsWith(".klib")
private fun LazyResolvedConfiguration.ensureDependencyPrecached(
dependency: ResolvedDependencyResult,
visitedDependencies: MutableSet
) {
if (dependency in visitedDependencies)
return
visitedDependencies += dependency
dependency
.selected
.dependencies
.filterIsInstance()
.forEach { ensureDependencyPrecached(it, visitedDependencies) }
val artifactsToAddToCache = getArtifacts(dependency).filter { needCache(it.file.absolutePath) }
if (artifactsToAddToCache.isEmpty()) return
val dependenciesCacheDirectories = getDependenciesCacheDirectories(
rootCacheDirectory = rootCacheDirectory,
dependency = dependency,
considerArtifact = false,
resolvedConfiguration = this,
partialLinkageMode = partialLinkageMode
) ?: return
val cacheDirectory = getCacheDirectory(this, dependency)
cacheDirectory.mkdirs()
val artifactsLibraries = artifactsToAddToCache
.map {
resolveSingleFileKlib(
KFile(it.file.absolutePath),
logger = GradleLoggerAdapter(executionContext.logger),
strategy = nativeSingleFileResolveStrategy
)
}
.associateBy { it.uniqueName }
// Top sort artifacts.
val sortedLibraries = mutableListOf()
val visitedLibraries = mutableSetOf()
fun dfs(library: KotlinLibrary) {
visitedLibraries += library
library.unresolvedDependencies
.map { artifactsLibraries[it.path] }
.forEach {
if (it != null && it !in visitedLibraries)
dfs(it)
}
sortedLibraries += library
}
for (library in artifactsLibraries.values)
if (library !in visitedLibraries)
dfs(library)
for (library in sortedLibraries) {
if (File(cacheDirectory, library.uniqueName.cachedName).listFilesOrEmpty().isNotEmpty())
continue
executionContext.logger.info("Compiling ${library.uniqueName} to cache")
val args = mutableListOf(
"-p", konanCacheKind.produce!!,
"-target", target
)
if (debuggable) args += "-g"
args += konanPropertiesService.additionalCacheFlags(konanTarget)
args += settings.externalDependenciesArgs
args += "$PARTIAL_LINKAGE_PARAMETER=$partialLinkageMode"
args += "-Xadd-cache=${library.libraryFile.absolutePath}"
args += "-Xcache-directory=${cacheDirectory.absolutePath}"
args += "-Xcache-directory=${rootCacheDirectory.absolutePath}"
dependenciesCacheDirectories.forEach {
args += "-Xcache-directory=${it.absolutePath}"
}
getAllDependencies(dependency)
.flatMap { getArtifacts(it) }
.map { it.file }
.filterKlibsPassedToCompiler()
.forEach {
args += "-l"
args += it.absolutePath
}
library.unresolvedDependencies
.mapNotNull { artifactsLibraries[it.path] }
.forEach {
args += "-l"
args += it.libraryFile.absolutePath
}
KotlinNativeCompilerRunner(settings.runnerSettings, executionContext).run(args)
}
}
private val String.cachedName
get() = getCacheFileName(this, konanCacheKind)
private fun ensureCompilerProvidedLibPrecached(
platformLibName: String,
platformLibs: Map,
visitedLibs: MutableSet
) {
if (platformLibName in visitedLibs)
return
visitedLibs += platformLibName
val platformLib = platformLibs[platformLibName] ?: error("$platformLibName is not found in platform libs")
if (File(rootCacheDirectory, platformLibName.cachedName).listFilesOrEmpty().isNotEmpty())
return
val unresolvedDependencies = resolveSingleFileKlib(
KFile(platformLib.absolutePath),
logger = GradleLoggerAdapter(executionContext.logger),
strategy = nativeSingleFileResolveStrategy
).unresolvedDependencies
for (dependency in unresolvedDependencies)
ensureCompilerProvidedLibPrecached(dependency.path, platformLibs, visitedLibs)
executionContext.logger.info("Compiling $platformLibName (${visitedLibs.size}/${platformLibs.size}) to cache")
val args = mutableListOf(
"-p", konanCacheKind.produce!!,
"-target", target
)
if (debuggable)
args += "-g"
// It's a dirty workaround, but we need a Gradle Build Service for a proper solution,
// which is too big to put in 1.6.0, so let's use ad-hoc solution for now.
// TODO: https://youtrack.jetbrains.com/issue/KT-48553.
if (konanTarget == KonanTarget.IOS_ARM64) {
// See https://youtrack.jetbrains.com/issue/KT-48552
args += "-Xembed-bitcode-marker"
}
args += "-Xadd-cache=${platformLib.absolutePath}"
args += "-Xcache-directory=${rootCacheDirectory.absolutePath}"
KotlinNativeCompilerRunner(settings.runnerSettings, executionContext).run(args)
}
private fun ensureCompilerProvidedLibsPrecached() {
val distribution = Distribution(settings.runnerSettings.parent.konanHome)
val platformLibs = mutableListOf().apply {
this += File(distribution.stdlib)
this += File(distribution.platformLibs(konanTarget)).listFiles().orEmpty()
}.associateBy { it.name }
val visitedLibs = mutableSetOf()
for (platformLibName in platformLibs.keys)
ensureCompilerProvidedLibPrecached(platformLibName, platformLibs, visitedLibs)
}
fun buildCompilerArgs(resolvedConfiguration: LazyResolvedConfiguration): List = mutableListOf().apply {
if (konanCacheKind != NativeCacheKind.NONE && !optimized && konanPropertiesService.cacheWorksFor(konanTarget)) {
rootCacheDirectory.mkdirs()
ensureCompilerProvidedLibsPrecached()
add("-Xcache-directory=${rootCacheDirectory.absolutePath}")
val visitedDependencies = mutableSetOf()
val allCacheDirectories = mutableSetOf()
for (root in resolvedConfiguration.root.dependencies.filterIsInstance()) {
resolvedConfiguration.ensureDependencyPrecached(root, visitedDependencies)
for (dependency in listOf(root) + getAllDependencies(root)) {
val cacheDirectory = getCacheDirectory(resolvedConfiguration, dependency)
if (cacheDirectory.exists())
allCacheDirectories += cacheDirectory.absolutePath
}
}
for (cacheDirectory in allCacheDirectories)
add("-Xcache-directory=$cacheDirectory")
}
}
companion object {
internal fun getRootCacheDirectory(konanHome: File, target: KonanTarget, debuggable: Boolean, cacheKind: NativeCacheKind): File {
require(cacheKind != NativeCacheKind.NONE) { "Unsupported cache kind: ${NativeCacheKind.NONE}" }
val optionsAwareCacheName = "$target${if (debuggable) "-g" else ""}$cacheKind"
return konanHome.resolve("klib/cache/$optionsAwareCacheName")
}
internal fun getCacheFileName(baseName: String, cacheKind: NativeCacheKind): String =
cacheKind.outputKind?.let {
"${baseName}-cache"
} ?: error("No output for kind $cacheKind")
private const val PARTIAL_LINKAGE_PARAMETER = "-Xpartial-linkage"
}
}
@CacheableTask
open class CInteropProcess @Inject internal constructor(params: Params) : DefaultTask() {
internal class Params(
val settings: DefaultCInteropSettings,
val targetName: String,
val compilationName: String,
val konanTarget: KonanTarget,
val baseKlibName: String,
val services: Services
) {
internal open class Services @Inject constructor(
val objectFactory: ObjectFactory,
val execOperations: ExecOperations
)
}
private val objectFactory: ObjectFactory = params.services.objectFactory
private val execOperations: ExecOperations = params.services.execOperations
@get:Internal
internal val targetName: String = params.targetName
@get:Internal
internal val compilationName: String = params.compilationName
@Internal
val settings: DefaultCInteropSettings = params.settings
@Internal // Taken into account in the outputFileProvider property
lateinit var destinationDir: Provider
@get:Input
val konanTarget: KonanTarget = params.konanTarget
@get:Input
val konanVersion: String = project.konanVersion
@Suppress("unused")
@get:Input
val libraryVersion = project.version.toString()
@Suppress("unused")
@get:Input
val interopName: String = params.settings.name
@get:Input
val baseKlibName: String = params.baseKlibName
@get:Internal
val outputFileName: String = with(LIBRARY) {
"$baseKlibName${suffix(konanTarget)}"
}
@get:Input
val moduleName: String = project.klibModuleName(baseKlibName)
@get:Internal
val outputFile: File
get() = outputFileProvider.get()
private val runnerSettings = KotlinNativeToolRunner.Settings.fromProject(project)
// Inputs and outputs.
@OutputFile
val outputFileProvider: Provider = project.provider { destinationDir.get().resolve(outputFileName) }
@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:NormalizeLineEndings
val defFile: File get() = settings.defFileProperty.get()
@get:Optional
@get:Input
val packageName: String? get() = settings.packageName
@get:Input
val compilerOpts: List get() = settings.compilerOpts
@get:Input
val linkerOpts: List get() = settings.linkerOpts
@get:IgnoreEmptyDirectories
@get:InputFiles
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
val headers: FileCollection get() = settings.headers
@get:IgnoreEmptyDirectories
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
val allHeadersDirs: Set get() = settings.includeDirs.allHeadersDirs.files
@get:IgnoreEmptyDirectories
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
val headerFilterDirs: Set get() = settings.includeDirs.headerFilterDirs.files
@get:IgnoreEmptyDirectories
@get:InputFiles
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
val libraries: FileCollection get() = settings.dependencyFiles
@get:Input
val extraOpts: List get() = settings.extraOpts
private val isInIdeaSync = project.isInIdeaSync
// Task action.
@TaskAction
fun processInterop() {
val args = mutableListOf().apply {
addArg("-o", outputFile.absolutePath)
addArgIfNotNull("-target", konanTarget.visibleName)
addArgIfNotNull("-def", defFile.canonicalPath)
addArgIfNotNull("-pkg", packageName)
addFileArgs("-header", headers)
compilerOpts.forEach {
addArg("-compiler-option", it)
}
linkerOpts.forEach {
addArg("-linker-option", it)
}
libraries.files.filterKlibsPassedToCompiler().forEach { library ->
addArg("-library", library.absolutePath)
}
addArgs("-compiler-option", allHeadersDirs.map { "-I${it.absolutePath}" })
addArgs("-headerFilterAdditionalSearchPrefix", headerFilterDirs.map { it.absolutePath })
addArg("-Xmodule-name", moduleName)
// TODO: uncomment after advancing bootstrap.
//addArg("-libraryVersion", libraryVersion)
addAll(extraOpts)
}
outputFile.parentFile.mkdirs()
KotlinNativeCInteropRunner.createExecutionContext(
task = this,
isInIdeaSync = isInIdeaSync,
runnerSettings = runnerSettings,
gradleExecutionContext = KotlinToolRunner.GradleExecutionContext.fromTaskContext(objectFactory, execOperations, logger)
).run(args)
}
}