[K/N] Extract LlvmLink out of CompileToBitcode ^KT-53776
Merge-request: KT-MR-8120 Merged-by: Alexander Shabalin <Alexander.Shabalin@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
8e37a07af2
commit
9a2cb2609f
-252
@@ -1,252 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
||||||
* that can be found in the license/LICENSE.txt file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.bitcode
|
|
||||||
|
|
||||||
import org.gradle.api.DefaultTask
|
|
||||||
import org.gradle.api.file.ConfigurableFileCollection
|
|
||||||
import org.gradle.api.file.ConfigurableFileTree
|
|
||||||
import org.gradle.api.file.DirectoryProperty
|
|
||||||
import org.gradle.api.file.RegularFileProperty
|
|
||||||
import org.gradle.api.model.ObjectFactory
|
|
||||||
import org.gradle.api.provider.ListProperty
|
|
||||||
import org.gradle.api.provider.Property
|
|
||||||
import org.gradle.api.provider.Provider
|
|
||||||
import org.gradle.api.tasks.*
|
|
||||||
import org.gradle.kotlin.dsl.getByType
|
|
||||||
import org.gradle.process.ExecOperations
|
|
||||||
import org.gradle.workers.WorkAction
|
|
||||||
import org.gradle.workers.WorkParameters
|
|
||||||
import org.gradle.workers.WorkerExecutor
|
|
||||||
import org.jetbrains.kotlin.ExecClang
|
|
||||||
import org.jetbrains.kotlin.execLlvmUtility
|
|
||||||
import org.jetbrains.kotlin.konan.target.PlatformManager
|
|
||||||
import org.jetbrains.kotlin.konan.target.SanitizerKind
|
|
||||||
import org.jetbrains.kotlin.konan.target.TargetWithSanitizer
|
|
||||||
import java.io.File
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
private data class CompilerWorkUnit(
|
|
||||||
val input: File,
|
|
||||||
val output: File,
|
|
||||||
) : java.io.Serializable
|
|
||||||
|
|
||||||
private abstract class CompileToBitcodeJob : WorkAction<CompileToBitcodeJob.Parameters> {
|
|
||||||
interface Parameters : WorkParameters {
|
|
||||||
val workingDirectory: DirectoryProperty
|
|
||||||
// TODO: Figure out a way to pass KonanTarget, but it is used as a key into PlatformManager,
|
|
||||||
// so object identity matters, and platform managers are different between project and worker sides.
|
|
||||||
val targetName: Property<String>
|
|
||||||
val compilerExecutable: Property<String>
|
|
||||||
val compilerWorkUnits: ListProperty<CompilerWorkUnit>
|
|
||||||
val compilerArgs: ListProperty<String>
|
|
||||||
val llvmLinkOutputFile: RegularFileProperty
|
|
||||||
val llvmLinkArgs: ListProperty<String>
|
|
||||||
val platformManager: Property<PlatformManager>
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:Inject
|
|
||||||
abstract val objects: ObjectFactory
|
|
||||||
|
|
||||||
@get:Inject
|
|
||||||
abstract val execOperations: ExecOperations
|
|
||||||
|
|
||||||
override fun execute() {
|
|
||||||
with(parameters) {
|
|
||||||
val execClang = ExecClang.create(objects, platformManager.get())
|
|
||||||
|
|
||||||
val baseDir = workingDirectory.asFile.get()
|
|
||||||
|
|
||||||
compilerWorkUnits.get().forEach { workUnit ->
|
|
||||||
workUnit.output.parentFile.mkdirs()
|
|
||||||
val inputRelativePath = baseDir.toPath().relativize(workUnit.input.toPath())
|
|
||||||
execClang.execKonanClang(targetName.get()) {
|
|
||||||
workingDir = baseDir
|
|
||||||
executable = compilerExecutable.get()
|
|
||||||
args = compilerArgs.get() + listOf(inputRelativePath.toString(), "-o", workUnit.output.absolutePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Extract llvm-link out. This will allow parallelizing clang compilation.
|
|
||||||
execOperations.execLlvmUtility(platformManager.get(), "llvm-link") {
|
|
||||||
args = listOf("-o", llvmLinkOutputFile.asFile.get().absolutePath) + llvmLinkArgs.get() + compilerWorkUnits.get().map { it.output.absolutePath }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compiling files with clang into LLVM bitcode.
|
|
||||||
*
|
|
||||||
* Compiles [inputFiles] into [outputFile] with [compiler] and `llvm-link`.
|
|
||||||
*
|
|
||||||
* @see CompileToBitcodePlugin
|
|
||||||
*/
|
|
||||||
abstract class CompileToBitcode @Inject constructor(
|
|
||||||
private val _target: TargetWithSanitizer,
|
|
||||||
) : DefaultTask() {
|
|
||||||
/**
|
|
||||||
* Target for which to compile
|
|
||||||
*/
|
|
||||||
@get:Input
|
|
||||||
val target by _target::target
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional [sanitizer][SanitizerKind] for [target].
|
|
||||||
*/
|
|
||||||
@get:Input
|
|
||||||
@get:Optional
|
|
||||||
val sanitizer by _target::sanitizer
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Final output file.
|
|
||||||
*/
|
|
||||||
@get:OutputFile
|
|
||||||
abstract val outputFile: RegularFileProperty
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Where to put bitcode files generated by clang.
|
|
||||||
*/
|
|
||||||
@get:Internal
|
|
||||||
abstract val outputDirectory: DirectoryProperty
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The compiler to be used.
|
|
||||||
*
|
|
||||||
* Currently only `clang` and `clang++` are supported.
|
|
||||||
*/
|
|
||||||
@get:Input
|
|
||||||
abstract val compiler: Property<String>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra arguments for `llvm-link`.
|
|
||||||
*/
|
|
||||||
@get:Input
|
|
||||||
abstract val linkerArgs: ListProperty<String>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra arguments for [compiler].
|
|
||||||
*/
|
|
||||||
// Marked as input via [compilerFlags].
|
|
||||||
@get:Internal
|
|
||||||
abstract val compilerArgs: ListProperty<String>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Locations to search for headers.
|
|
||||||
*
|
|
||||||
* Will be passed to the compiler as `-I…` and will also be used to compute task dependencies: recompile if the headers change.
|
|
||||||
*/
|
|
||||||
// Marked as input via [headers] and [compilerFlags].
|
|
||||||
@get:Internal
|
|
||||||
abstract val headersDirs: ConfigurableFileCollection
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Final computed compiler arguments.
|
|
||||||
*/
|
|
||||||
@get:Input
|
|
||||||
val compilerFlags: Provider<List<String>> = project.provider {
|
|
||||||
listOfNotNull(
|
|
||||||
"-c",
|
|
||||||
"-emit-llvm"
|
|
||||||
) + headersDirs.map { "-I${it.absolutePath}" } + when (sanitizer) {
|
|
||||||
null -> listOf()
|
|
||||||
SanitizerKind.ADDRESS -> listOf("-fsanitize=address")
|
|
||||||
SanitizerKind.THREAD -> listOf("-fsanitize=thread")
|
|
||||||
} + compilerArgs.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Source files to compile from.
|
|
||||||
*/
|
|
||||||
@get:SkipWhenEmpty
|
|
||||||
@get:InputFiles
|
|
||||||
abstract val inputFiles: ConfigurableFileTree
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Working directory for the compiler.
|
|
||||||
*
|
|
||||||
* All inputs will be passed to the compiler as relative paths to this directory.
|
|
||||||
*/
|
|
||||||
@get:Internal
|
|
||||||
abstract val compilerWorkingDirectory: DirectoryProperty
|
|
||||||
|
|
||||||
@get:Input
|
|
||||||
protected val compilerWorkingDirectoryPath: Provider<String> = project.provider {
|
|
||||||
compilerWorkingDirectory.get().asFile.absolutePath
|
|
||||||
}
|
|
||||||
|
|
||||||
private val compilerWorkUnits: Provider<List<CompilerWorkUnit>> = project.provider {
|
|
||||||
val workUnits = mutableListOf<CompilerWorkUnit>()
|
|
||||||
inputFiles.visit {
|
|
||||||
if (!isDirectory) {
|
|
||||||
val output = outputDirectory.file(relativePath.parent.append(true, "${file.nameWithoutExtension}.bc").pathString)
|
|
||||||
workUnits.add(CompilerWorkUnit(file, output.get().asFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workUnits
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Output files from the [compiler].
|
|
||||||
*/
|
|
||||||
// TODO: Make it OutputFiles. Currently clashes with `kotlinNativeInterop` `linkOutputs` in kotlin-native/backend.native/build.gradle Can be fixed when the task is split into clang and llvm-link.
|
|
||||||
@get:Internal
|
|
||||||
val compilerOutputFiles = compilerWorkUnits.map { it.map { it.output } }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computed header files used for task dependencies tracking.
|
|
||||||
*/
|
|
||||||
@get:InputFiles
|
|
||||||
protected val headers: Provider<List<File>> = project.provider {
|
|
||||||
// Not using clang's -M* flags because there's a problem with our current include system:
|
|
||||||
// We allow includes relative to the current directory and also pass -I for each imported module
|
|
||||||
// Given file tree:
|
|
||||||
// a:
|
|
||||||
// header.hpp
|
|
||||||
// b:
|
|
||||||
// impl.cpp
|
|
||||||
// Assume module b adds a to its include path.
|
|
||||||
// If b/impl.cpp has #include "header.hpp", it'll be included from a/header.hpp. If we add another file
|
|
||||||
// header.hpp into b/, the next compilation of b/impl.cpp will include b/header.hpp. -M flags, however,
|
|
||||||
// won't generate a dependency on b/header.hpp, so incremental compilation will be broken.
|
|
||||||
// TODO: Apart from dependency generation this also makes it awkward to have two files with
|
|
||||||
// the same name (e.g. Utils.h) in directories a/ and b/: For the b/impl.cpp to include a/header.hpp
|
|
||||||
// it needs to have #include "../a/header.hpp"
|
|
||||||
|
|
||||||
val dirs = mutableSetOf<File>()
|
|
||||||
// First add dirs with sources, as clang by default adds directory with the source to the include path.
|
|
||||||
inputFiles.forEach {
|
|
||||||
dirs.add(it.parentFile)
|
|
||||||
}
|
|
||||||
// Now add manually given header dirs.
|
|
||||||
dirs.addAll(headersDirs.files)
|
|
||||||
dirs.flatMap { dir ->
|
|
||||||
project.fileTree(dir) {
|
|
||||||
include("**/*.h", "**/*.hpp")
|
|
||||||
}.files
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:Inject
|
|
||||||
protected abstract val workerExecutor: WorkerExecutor
|
|
||||||
|
|
||||||
private val platformManager = project.extensions.getByType<PlatformManager>()
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
fun compile() {
|
|
||||||
val workQueue = workerExecutor.noIsolation()
|
|
||||||
|
|
||||||
workQueue.submit(CompileToBitcodeJob::class.java) {
|
|
||||||
workingDirectory.set(this@CompileToBitcode.compilerWorkingDirectory)
|
|
||||||
targetName.set(this@CompileToBitcode.target.name)
|
|
||||||
compilerExecutable.set(this@CompileToBitcode.compiler)
|
|
||||||
compilerArgs.set(this@CompileToBitcode.compilerFlags)
|
|
||||||
compilerWorkUnits.set(this@CompileToBitcode.compilerWorkUnits)
|
|
||||||
llvmLinkOutputFile.set(this@CompileToBitcode.outputFile)
|
|
||||||
llvmLinkArgs.set(this@CompileToBitcode.linkerArgs)
|
|
||||||
platformManager.set(this@CompileToBitcode.platformManager)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+42
-14
@@ -10,7 +10,10 @@ import org.gradle.api.*
|
|||||||
import org.gradle.api.artifacts.Configuration
|
import org.gradle.api.artifacts.Configuration
|
||||||
import org.gradle.api.artifacts.ConfigurationVariant
|
import org.gradle.api.artifacts.ConfigurationVariant
|
||||||
import org.gradle.api.attributes.Usage
|
import org.gradle.api.attributes.Usage
|
||||||
import org.gradle.api.file.*
|
import org.gradle.api.file.ConfigurableFileCollection
|
||||||
|
import org.gradle.api.file.ConfigurableFileTree
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
import org.gradle.api.provider.ListProperty
|
import org.gradle.api.provider.ListProperty
|
||||||
import org.gradle.api.provider.Property
|
import org.gradle.api.provider.Property
|
||||||
import org.gradle.api.provider.Provider
|
import org.gradle.api.provider.Provider
|
||||||
@@ -204,21 +207,25 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
|
|||||||
private val execClang = project.extensions.getByType<ExecClang>()
|
private val execClang = project.extensions.getByType<ExecClang>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task that produces [outputFile].
|
* Compiles source files into bitcode files.
|
||||||
*/
|
*/
|
||||||
val task = project.tasks.register<CompileToBitcode>("compileToBitcode${module.name.capitalized}${name.capitalized}${_target.toString().capitalized}", _target).apply {
|
val compileTask = project.tasks.register<ClangFrontend>("clangFrontend${module.name.capitalized}${name.capitalized}${_target.toString().capitalized}").apply {
|
||||||
configure {
|
configure {
|
||||||
this.description = "Compiles '${module.name}' (${this@SourceSet.name} sources) to bitcode for ${_target}"
|
this.description = "Compiles '${module.name}' (${this@SourceSet.name} sources) to bitcode for $_target"
|
||||||
this.outputFile.set(this@SourceSet.outputFile)
|
|
||||||
this.outputDirectory.set(this@SourceSet.outputDirectory)
|
this.outputDirectory.set(this@SourceSet.outputDirectory)
|
||||||
|
this.targetName.set(target.name)
|
||||||
this.compiler.set(module.compiler)
|
this.compiler.set(module.compiler)
|
||||||
this.linkerArgs.set(module.linkerArgs)
|
this.arguments.set(module.compilerArgs)
|
||||||
this.compilerArgs.set(module.compilerArgs)
|
this.arguments.addAll(when (sanitizer) {
|
||||||
|
null -> emptyList()
|
||||||
|
SanitizerKind.ADDRESS -> listOf("-fsanitize=address")
|
||||||
|
SanitizerKind.THREAD -> listOf("-fsanitize=thread")
|
||||||
|
})
|
||||||
this.headersDirs.from(this@SourceSet.headersDirs)
|
this.headersDirs.from(this@SourceSet.headersDirs)
|
||||||
this.inputFiles.from(this@SourceSet.inputFiles.dir)
|
this.inputFiles.from(this@SourceSet.inputFiles.dir)
|
||||||
this.inputFiles.setIncludes(this@SourceSet.inputFiles.includes)
|
this.inputFiles.setIncludes(this@SourceSet.inputFiles.includes)
|
||||||
this.inputFiles.setExcludes(this@SourceSet.inputFiles.excludes)
|
this.inputFiles.setExcludes(this@SourceSet.inputFiles.excludes)
|
||||||
this.compilerWorkingDirectory.set(module.compilerWorkingDirectory)
|
this.workingDirectory.set(module.compilerWorkingDirectory)
|
||||||
// TODO: Should depend only on the toolchain needed to build for the _target
|
// TODO: Should depend only on the toolchain needed to build for the _target
|
||||||
dependsOn(":kotlin-native:dependencies:update")
|
dependsOn(":kotlin-native:dependencies:update")
|
||||||
dependsOn(this@SourceSet.dependencies)
|
dependsOn(this@SourceSet.dependencies)
|
||||||
@@ -230,11 +237,11 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
|
|||||||
entry {
|
entry {
|
||||||
val compileTask = this@apply.get()
|
val compileTask = this@apply.get()
|
||||||
val args = listOf(execClang.resolveExecutable(compileTask.compiler.get())) + compileTask.compilerFlags.get() + execClang.clangArgsForCppRuntime(target.name)
|
val args = listOf(execClang.resolveExecutable(compileTask.compiler.get())) + compileTask.compilerFlags.get() + execClang.clangArgsForCppRuntime(target.name)
|
||||||
directory.set(compileTask.compilerWorkingDirectory)
|
directory.set(compileTask.workingDirectory)
|
||||||
files.setFrom(compileTask.inputFiles)
|
files.setFrom(compileTask.inputFiles)
|
||||||
arguments.set(args)
|
arguments.set(args)
|
||||||
// Only the location of output file matters, compdb does not depend on the compilation result.
|
// Only the location of output file matters, compdb does not depend on the compilation result.
|
||||||
output.set(compileTask.outputFile.locationOnly.map { it.asFile.absolutePath })
|
output.set(compileTask.outputDirectory.locationOnly.map { it.asFile.absolutePath })
|
||||||
}
|
}
|
||||||
task.configure {
|
task.configure {
|
||||||
// Compile task depends on the toolchain (including headers) and on the source code (e.g. googletest).
|
// Compile task depends on the toolchain (including headers) and on the source code (e.g. googletest).
|
||||||
@@ -245,6 +252,21 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
|
|||||||
dependsOn(this@SourceSet.dependencies)
|
dependsOn(this@SourceSet.dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links bitcode files together.
|
||||||
|
*/
|
||||||
|
val task = project.tasks.register<LlvmLink>("llvmLink${module.name.capitalized}${name.capitalized}${_target.toString().capitalized}").apply {
|
||||||
|
configure {
|
||||||
|
this.description = "Link '${module.name}' bitcode files (${this@SourceSet.name} sources) into a single bitcode file for $_target"
|
||||||
|
this.inputFiles.from(compileTask)
|
||||||
|
this.outputFile.set(this@SourceSet.outputFile)
|
||||||
|
this.arguments.set(module.linkerArgs)
|
||||||
|
onlyIf {
|
||||||
|
this@SourceSet.onlyIf.get().all { it.isSatisfiedBy(this@SourceSet) }
|
||||||
|
}
|
||||||
|
}
|
||||||
project.compileBitcodeElements(this@SourceSet.name).targetVariant(_target).artifact(this)
|
project.compileBitcodeElements(this@SourceSet.name).targetVariant(_target).artifact(this)
|
||||||
project.moduleCompileBitcodeElements(module.name, this@SourceSet.name).targetVariant(_target).artifact(this)
|
project.moduleCompileBitcodeElements(module.name, this@SourceSet.name).targetVariant(_target).artifact(this)
|
||||||
}
|
}
|
||||||
@@ -262,10 +284,7 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
|
|||||||
*
|
*
|
||||||
* 3 source sets are well known: [main], [testFixtures] and [test].
|
* 3 source sets are well known: [main], [testFixtures] and [test].
|
||||||
*/
|
*/
|
||||||
abstract class SourceSets @Inject constructor(
|
abstract class SourceSets @Inject constructor(private val module: Module, private val container: ExtensiblePolymorphicDomainObjectContainer<SourceSet>) : NamedDomainObjectContainer<SourceSet> by container {
|
||||||
private val module: Module,
|
|
||||||
private val container: ExtensiblePolymorphicDomainObjectContainer<SourceSet>
|
|
||||||
) : NamedDomainObjectContainer<SourceSet> by container {
|
|
||||||
private val project by module::project
|
private val project by module::project
|
||||||
|
|
||||||
// googleTestExtension is only used if testFixtures or tests are used.
|
// googleTestExtension is only used if testFixtures or tests are used.
|
||||||
@@ -283,6 +302,9 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
|
|||||||
fun main(action: Action<in SourceSet>): SourceSet = create(MAIN_SOURCE_SET_NAME) {
|
fun main(action: Action<in SourceSet>): SourceSet = create(MAIN_SOURCE_SET_NAME) {
|
||||||
this.inputFiles.include("**/*.cpp", "**/*.mm")
|
this.inputFiles.include("**/*.cpp", "**/*.mm")
|
||||||
this.inputFiles.exclude("**/*Test.cpp", "**/*TestSupport.cpp", "**/*Test.mm", "**/*TestSupport.mm")
|
this.inputFiles.exclude("**/*Test.cpp", "**/*TestSupport.cpp", "**/*Test.mm", "**/*TestSupport.mm")
|
||||||
|
compileTask.configure {
|
||||||
|
this.group = BUILD_TASK_GROUP
|
||||||
|
}
|
||||||
task.configure {
|
task.configure {
|
||||||
this.group = BUILD_TASK_GROUP
|
this.group = BUILD_TASK_GROUP
|
||||||
}
|
}
|
||||||
@@ -303,6 +325,9 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
|
|||||||
this.headersDirs.from(googleTestExtension.headersDirs)
|
this.headersDirs.from(googleTestExtension.headersDirs)
|
||||||
// TODO: Must generally depend on googletest module headers which must itself depend on sources being present.
|
// TODO: Must generally depend on googletest module headers which must itself depend on sources being present.
|
||||||
dependencies.add(project.tasks.named("downloadGoogleTest"))
|
dependencies.add(project.tasks.named("downloadGoogleTest"))
|
||||||
|
compileTask.configure {
|
||||||
|
this.group = VERIFICATION_BUILD_TASK_GROUP
|
||||||
|
}
|
||||||
task.configure {
|
task.configure {
|
||||||
this.group = VERIFICATION_BUILD_TASK_GROUP
|
this.group = VERIFICATION_BUILD_TASK_GROUP
|
||||||
}
|
}
|
||||||
@@ -323,6 +348,9 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
|
|||||||
this.headersDirs.from(googleTestExtension.headersDirs)
|
this.headersDirs.from(googleTestExtension.headersDirs)
|
||||||
// TODO: Must generally depend on googletest module headers which must itself depend on sources being present.
|
// TODO: Must generally depend on googletest module headers which must itself depend on sources being present.
|
||||||
dependencies.add(project.tasks.named("downloadGoogleTest"))
|
dependencies.add(project.tasks.named("downloadGoogleTest"))
|
||||||
|
compileTask.configure {
|
||||||
|
this.group = VERIFICATION_BUILD_TASK_GROUP
|
||||||
|
}
|
||||||
task.configure {
|
task.configure {
|
||||||
this.group = VERIFICATION_BUILD_TASK_GROUP
|
this.group = VERIFICATION_BUILD_TASK_GROUP
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.cpp
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.file.*
|
||||||
|
import org.gradle.api.model.ObjectFactory
|
||||||
|
import org.gradle.api.provider.ListProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.provider.Provider
|
||||||
|
import org.gradle.api.tasks.*
|
||||||
|
import org.gradle.kotlin.dsl.getByType
|
||||||
|
import org.gradle.workers.WorkAction
|
||||||
|
import org.gradle.workers.WorkParameters
|
||||||
|
import org.gradle.workers.WorkerExecutor
|
||||||
|
import org.jetbrains.kotlin.ExecClang
|
||||||
|
import org.jetbrains.kotlin.bitcode.CompileToBitcodePlugin
|
||||||
|
import org.jetbrains.kotlin.konan.target.PlatformManager
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private abstract class ClangFrontendJob : WorkAction<ClangFrontendJob.Parameters> {
|
||||||
|
interface Parameters : WorkParameters {
|
||||||
|
val workingDirectory: DirectoryProperty
|
||||||
|
val targetName: Property<String>
|
||||||
|
val inputFile: RegularFileProperty
|
||||||
|
val outputFile: RegularFileProperty
|
||||||
|
val compilerExecutable: Property<String>
|
||||||
|
val arguments: ListProperty<String>
|
||||||
|
val platformManager: Property<PlatformManager>
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Inject
|
||||||
|
abstract val objects: ObjectFactory
|
||||||
|
|
||||||
|
override fun execute() {
|
||||||
|
with(parameters) {
|
||||||
|
val execClang = ExecClang.create(objects, platformManager.get())
|
||||||
|
|
||||||
|
val baseDir = workingDirectory.asFile.get()
|
||||||
|
outputFile.get().asFile.parentFile.mkdirs()
|
||||||
|
val inputRelativePath = baseDir.toPath().relativize(inputFile.get().asFile.toPath())
|
||||||
|
execClang.execKonanClang(targetName.get()) {
|
||||||
|
workingDir = baseDir
|
||||||
|
executable = compilerExecutable.get()
|
||||||
|
args = arguments.get() + listOf(inputRelativePath.toString(), "-o", outputFile.get().asFile.absolutePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiling [inputFiles] with clang into LLVM bitcode in [outputFiles].
|
||||||
|
*
|
||||||
|
* @see CompileToBitcodePlugin
|
||||||
|
*/
|
||||||
|
abstract class ClangFrontend : DefaultTask() {
|
||||||
|
private data class WorkUnit(
|
||||||
|
val inputFile: File,
|
||||||
|
val outputFile: File,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val workUnits: Provider<List<WorkUnit>> = project.provider {
|
||||||
|
val result = mutableListOf<WorkUnit>()
|
||||||
|
inputFiles.visit {
|
||||||
|
if (!isDirectory) {
|
||||||
|
val inputFile = this.file
|
||||||
|
val outputFile = outputDirectory.file(relativePath.parent.append(true, "${file.nameWithoutExtension}.bc").pathString).get().asFile
|
||||||
|
result.add(WorkUnit(inputFile, outputFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to put bitcode files generated by clang.
|
||||||
|
*/
|
||||||
|
// Marked as output via [outputFiles]
|
||||||
|
@get:Internal
|
||||||
|
abstract val outputDirectory: DirectoryProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source files to compile from.
|
||||||
|
*/
|
||||||
|
@get:SkipWhenEmpty
|
||||||
|
@get:InputFiles
|
||||||
|
abstract val inputFiles: ConfigurableFileTree
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bitcode files generated by clang.
|
||||||
|
*/
|
||||||
|
@get:OutputFiles
|
||||||
|
val outputFiles: FileCollection = project.files(workUnits.map { it.map { workUnit -> workUnit.outputFile } })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will select the appropriate compiler and additional flags.
|
||||||
|
*/
|
||||||
|
// TODO: Consider specifying full clang execution here and configure it from the plugin.
|
||||||
|
@get:Input
|
||||||
|
abstract val targetName: Property<String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The compiler to be used.
|
||||||
|
*
|
||||||
|
* Currently only `clang` and `clang++` are supported.
|
||||||
|
*/
|
||||||
|
@get:Input
|
||||||
|
abstract val compiler: Property<String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra arguments for [compiler].
|
||||||
|
*/
|
||||||
|
// Marked as input via [compilerFlags].
|
||||||
|
@get:Internal
|
||||||
|
abstract val arguments: ListProperty<String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locations to search for headers.
|
||||||
|
*
|
||||||
|
* Will be passed to the compiler as `-I…` and will also be used to compute task dependencies: recompile if the headers change.
|
||||||
|
*/
|
||||||
|
// Marked as input via [headers] and [compilerFlags].
|
||||||
|
@get:Internal
|
||||||
|
abstract val headersDirs: ConfigurableFileCollection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Final computed compiler arguments.
|
||||||
|
*/
|
||||||
|
@get:Input
|
||||||
|
val compilerFlags: Provider<List<String>> = project.provider {
|
||||||
|
listOfNotNull(
|
||||||
|
"-c",
|
||||||
|
"-emit-llvm"
|
||||||
|
) + headersDirs.map { "-I${it.absolutePath}" } + arguments.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Working directory for [compiler].
|
||||||
|
*
|
||||||
|
* All inputs will be passed to the compiler as relative paths to this directory.
|
||||||
|
*/
|
||||||
|
@get:Internal
|
||||||
|
abstract val workingDirectory: DirectoryProperty
|
||||||
|
|
||||||
|
@get:Input
|
||||||
|
protected val workingDirectoryPath: Provider<String> = project.provider {
|
||||||
|
workingDirectory.get().asFile.absolutePath
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed header files used for task dependencies tracking.
|
||||||
|
*/
|
||||||
|
@get:InputFiles
|
||||||
|
protected val headers: Provider<List<File>> = project.provider {
|
||||||
|
// Not using clang's -M* flags because there's a problem with our current include system:
|
||||||
|
// We allow includes relative to the current directory and also pass -I for each imported module
|
||||||
|
// Given file tree:
|
||||||
|
// a:
|
||||||
|
// header.hpp
|
||||||
|
// b:
|
||||||
|
// impl.cpp
|
||||||
|
// Assume module b adds a to its include path.
|
||||||
|
// If b/impl.cpp has #include "header.hpp", it'll be included from a/header.hpp. If we add another file
|
||||||
|
// header.hpp into b/, the next compilation of b/impl.cpp will include b/header.hpp. -M flags, however,
|
||||||
|
// won't generate a dependency on b/header.hpp, so incremental compilation will be broken.
|
||||||
|
// TODO: Apart from dependency generation this also makes it awkward to have two files with
|
||||||
|
// the same name (e.g. Utils.h) in directories a/ and b/: For the b/impl.cpp to include a/header.hpp
|
||||||
|
// it needs to have #include "../a/header.hpp"
|
||||||
|
|
||||||
|
val dirs = mutableSetOf<File>()
|
||||||
|
// First add dirs with sources, as clang by default adds directory with the source to the include path.
|
||||||
|
workUnits.get().forEach {
|
||||||
|
dirs.add(it.inputFile)
|
||||||
|
}
|
||||||
|
// Now add manually given header dirs.
|
||||||
|
dirs.addAll(headersDirs.files)
|
||||||
|
dirs.flatMap { dir ->
|
||||||
|
project.fileTree(dir) {
|
||||||
|
include("**/*.h", "**/*.hpp")
|
||||||
|
}.files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Inject
|
||||||
|
protected abstract val workerExecutor: WorkerExecutor
|
||||||
|
|
||||||
|
private val platformManager = project.extensions.getByType<PlatformManager>()
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
fun compile() {
|
||||||
|
val workQueue = workerExecutor.noIsolation()
|
||||||
|
|
||||||
|
workUnits.get().forEach { workUnit ->
|
||||||
|
workQueue.submit(ClangFrontendJob::class.java) {
|
||||||
|
workingDirectory.set(this@ClangFrontend.workingDirectory)
|
||||||
|
targetName.set(this@ClangFrontend.targetName)
|
||||||
|
inputFile.set(workUnit.inputFile)
|
||||||
|
outputFile.set(workUnit.outputFile)
|
||||||
|
compilerExecutable.set(this@ClangFrontend.compiler)
|
||||||
|
arguments.set(this@ClangFrontend.compilerFlags)
|
||||||
|
platformManager.set(this@ClangFrontend.platformManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.cpp
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
|
import org.gradle.api.provider.ListProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputFiles
|
||||||
|
import org.gradle.api.tasks.OutputFile
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.gradle.kotlin.dsl.getByType
|
||||||
|
import org.gradle.process.ExecOperations
|
||||||
|
import org.gradle.workers.WorkAction
|
||||||
|
import org.gradle.workers.WorkParameters
|
||||||
|
import org.gradle.workers.WorkerExecutor
|
||||||
|
import org.jetbrains.kotlin.execLlvmUtility
|
||||||
|
import org.jetbrains.kotlin.konan.target.PlatformManager
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private abstract class LlvmLinkJob : WorkAction<LlvmLinkJob.Parameters> {
|
||||||
|
interface Parameters : WorkParameters {
|
||||||
|
val inputFiles: ConfigurableFileCollection
|
||||||
|
val outputFile: RegularFileProperty
|
||||||
|
val arguments: ListProperty<String>
|
||||||
|
val platformManager: Property<PlatformManager>
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Inject
|
||||||
|
abstract val execOperations: ExecOperations
|
||||||
|
|
||||||
|
override fun execute() {
|
||||||
|
with(parameters) {
|
||||||
|
execOperations.execLlvmUtility(platformManager.get(), "llvm-link") {
|
||||||
|
args = listOf("-o", outputFile.asFile.get().absolutePath) + arguments.get() + inputFiles.map { it.absolutePath }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run `llvm-link` on [inputFiles] with extra [arguments] and produce [outputFile]
|
||||||
|
*/
|
||||||
|
abstract class LlvmLink : DefaultTask() {
|
||||||
|
/**
|
||||||
|
* Bitcode files to link together.
|
||||||
|
*/
|
||||||
|
@get:InputFiles
|
||||||
|
abstract val inputFiles: ConfigurableFileCollection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output file.
|
||||||
|
*/
|
||||||
|
@get:OutputFile
|
||||||
|
abstract val outputFile: RegularFileProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra arguments for `llvm-link`.
|
||||||
|
*/
|
||||||
|
@get:Input
|
||||||
|
abstract val arguments: ListProperty<String>
|
||||||
|
|
||||||
|
@get:Inject
|
||||||
|
protected abstract val workerExecutor: WorkerExecutor
|
||||||
|
|
||||||
|
private val platformManager = project.extensions.getByType<PlatformManager>()
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
fun link() {
|
||||||
|
val workQueue = workerExecutor.noIsolation()
|
||||||
|
|
||||||
|
workQueue.submit(LlvmLinkJob::class.java) {
|
||||||
|
inputFiles.from(this@LlvmLink.inputFiles)
|
||||||
|
outputFile.set(this@LlvmLink.outputFile)
|
||||||
|
arguments.set(this@LlvmLink.arguments)
|
||||||
|
platformManager.set(this@LlvmLink.platformManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-4
@@ -59,8 +59,8 @@ open class RuntimeTestingPlugin : Plugin<Project> {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
testFixtures {
|
testFixtures {
|
||||||
inputFiles.from(googleTestRoot.resolve("googletest/src"))
|
inputFiles.from(googleTestRoot.resolve("googletest/src"))
|
||||||
inputFiles.include("*.cc")
|
// That's how googletest/CMakeLists.txt builds gtest library.
|
||||||
inputFiles.exclude("gtest-all.cc", "gtest_main.cc")
|
inputFiles.include("gtest-all.cc")
|
||||||
headersDirs.setFrom(
|
headersDirs.setFrom(
|
||||||
googleTestRoot.resolve("googletest/include"),
|
googleTestRoot.resolve("googletest/include"),
|
||||||
googleTestRoot.resolve("googletest")
|
googleTestRoot.resolve("googletest")
|
||||||
@@ -75,8 +75,8 @@ open class RuntimeTestingPlugin : Plugin<Project> {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
testFixtures {
|
testFixtures {
|
||||||
inputFiles.from(googleTestRoot.resolve("googlemock/src"))
|
inputFiles.from(googleTestRoot.resolve("googlemock/src"))
|
||||||
inputFiles.include("*.cc")
|
// That's how googlemock/CMakeLists.txt builds gmock library.
|
||||||
inputFiles.exclude("gmock-all.cc", "gmock_main.cc")
|
inputFiles.include("gmock-all.cc")
|
||||||
headersDirs.setFrom(
|
headersDirs.setFrom(
|
||||||
googleTestRoot.resolve("googlemock"),
|
googleTestRoot.resolve("googlemock"),
|
||||||
googleTestRoot.resolve("googlemock/include"),
|
googleTestRoot.resolve("googlemock/include"),
|
||||||
|
|||||||
Reference in New Issue
Block a user