[K/N] Use TargetDomainObjectContainer ^KT-53776

Merge-request: KT-MR-7110
Merged-by: Alexander Shabalin <Alexander.Shabalin@jetbrains.com>
This commit is contained in:
Alexander Shabalin
2022-09-17 08:40:09 +00:00
committed by Space
parent ad9de1f8c4
commit 529a29ae52
7 changed files with 567 additions and 445 deletions
@@ -23,13 +23,42 @@ import org.jetbrains.kotlin.cpp.RunGTest
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.PlatformManager import org.jetbrains.kotlin.konan.target.PlatformManager
import org.jetbrains.kotlin.konan.target.SanitizerKind import org.jetbrains.kotlin.konan.target.SanitizerKind
import org.jetbrains.kotlin.konan.target.supportedSanitizers import org.jetbrains.kotlin.konan.target.TargetDomainObjectContainer
import org.jetbrains.kotlin.testing.native.GoogleTestExtension import org.jetbrains.kotlin.testing.native.GoogleTestExtension
import org.jetbrains.kotlin.utils.Maybe import org.jetbrains.kotlin.utils.Maybe
import org.jetbrains.kotlin.utils.asMaybe import org.jetbrains.kotlin.utils.asMaybe
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@OptIn(ExperimentalStdlibApi::class)
private val String.capitalized: String
get() = replaceFirstChar { it.uppercase() }
private fun String.snakeCaseToUpperCamelCase() = split('_').joinToString(separator = "") { it.capitalized }
private fun fullTaskName(name: String, targetName: String, sanitizer: SanitizerKind?) = "${targetName}${name.snakeCaseToUpperCamelCase()}${sanitizer.taskSuffix}"
private val SanitizerKind?.taskSuffix
get() = when (this) {
null -> ""
SanitizerKind.ADDRESS -> "_ASAN"
SanitizerKind.THREAD -> "_TSAN"
}
private val SanitizerKind?.dirSuffix
get() = when (this) {
null -> ""
SanitizerKind.ADDRESS -> "-asan"
SanitizerKind.THREAD -> "-tsan"
}
private val SanitizerKind?.description
get() = when (this) {
null -> ""
SanitizerKind.ADDRESS -> " with ASAN"
SanitizerKind.THREAD -> " with TSAN"
}
private abstract class RunGTestSemaphore : BuildService<BuildServiceParameters.None> private abstract class RunGTestSemaphore : BuildService<BuildServiceParameters.None>
private abstract class CompileTestsSemaphore : BuildService<BuildServiceParameters.None> private abstract class CompileTestsSemaphore : BuildService<BuildServiceParameters.None>
@@ -47,7 +76,13 @@ open class CompileToBitcodePlugin : Plugin<Project> {
} }
} }
open class CompileToBitcodeExtension @Inject constructor(val project: Project) { open class CompileToBitcodeExtension @Inject constructor(val project: Project) : TargetDomainObjectContainer<CompileToBitcodeExtension.Target>(project) {
init {
this.factory = { target, sanitizer ->
project.objects.newInstance<Target>(this, target, sanitizer.asMaybe)
}
}
// TODO: These should be set by the plugin users. // TODO: These should be set by the plugin users.
private val DEFAULT_CPP_FLAGS = listOfNotNull( private val DEFAULT_CPP_FLAGS = listOfNotNull(
"-gdwarf-2".takeIf { project.kotlinBuildProperties.getBoolean("kotlin.native.isNativeRuntimeDebugInfoEnabled", false) }, "-gdwarf-2".takeIf { project.kotlinBuildProperties.getBoolean("kotlin.native.isNativeRuntimeDebugInfoEnabled", false) },
@@ -60,24 +95,6 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) {
"-Wno-unused-parameter", // False positives with polymorphic functions. "-Wno-unused-parameter", // False positives with polymorphic functions.
) )
private val compilationDatabase = project.extensions.getByType<CompilationDatabaseExtension>()
private val execClang = project.extensions.getByType<ExecClang>()
private val platformManager = project.extensions.getByType<PlatformManager>()
// googleTestExtension is only used if testsGroup is used.
private val googleTestExtension by lazy { project.extensions.getByType<GoogleTestExtension>() }
// A shared service used to limit parallel execution of test binaries.
private val runGTestSemaphore = project.gradle.sharedServices.registerIfAbsent("runGTestSemaphore", RunGTestSemaphore::class.java) {
// Probably can be made configurable if test reporting moves away from simple gtest stdout dumping.
maxParallelUsages.set(1)
}
// TODO: remove when tests compilation does not consume so much memory.
private val compileTestsSemaphore = project.gradle.sharedServices.registerIfAbsent("compileTestsSemaphore", CompileTestsSemaphore::class.java) {
maxParallelUsages.set(5)
}
private val targetList = with(project) { private val targetList = with(project) {
provider { (rootProject.project(":kotlin-native").property("targetList") as? List<*>)?.filterIsInstance<String>() ?: emptyList() } // TODO: Can we make it better? provider { (rootProject.project(":kotlin-native").property("targetList") as? List<*>)?.filterIsInstance<String>() ?: emptyList() } // TODO: Can we make it better?
} }
@@ -102,14 +119,48 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) {
}) })
} }
private fun addToCompdb(compileTask: CompileToBitcode, konanTarget: KonanTarget) { abstract class TestsGroup @Inject constructor(
// No need to generate compdb entry for sanitizers. val target: KonanTarget,
if (compileTask.sanitizer != null) { private val _sanitizer: Maybe<SanitizerKind>,
return ) {
val sanitizer
get() = _sanitizer.orNull
abstract val testedModules: ListProperty<String>
abstract val testSupportModules: ListProperty<String>
abstract val testLauncherModule: Property<String>
} }
compilationDatabase.target(konanTarget) {
abstract class Target @Inject constructor(
private val owner: CompileToBitcodeExtension,
val target: KonanTarget,
_sanitizer: Maybe<SanitizerKind>,
) {
val sanitizer = _sanitizer.orNull
private val project by owner::project
private val compilationDatabase = project.extensions.getByType<CompilationDatabaseExtension>()
private val execClang = project.extensions.getByType<ExecClang>()
private val platformManager = project.extensions.getByType<PlatformManager>()
// googleTestExtension is only used if testsGroup is used.
private val googleTestExtension by lazy { project.extensions.getByType<GoogleTestExtension>() }
// A shared service used to limit parallel execution of test binaries.
private val runGTestSemaphore = project.gradle.sharedServices.registerIfAbsent("runGTestSemaphore", RunGTestSemaphore::class.java) {
// Probably can be made configurable if test reporting moves away from simple gtest stdout dumping.
maxParallelUsages.set(1)
}
// TODO: remove when tests compilation does not consume so much memory.
private val compileTestsSemaphore = project.gradle.sharedServices.registerIfAbsent("compileTestsSemaphore", CompileTestsSemaphore::class.java) {
maxParallelUsages.set(5)
}
private fun addToCompdb(compileTask: CompileToBitcode) {
compilationDatabase.target(target, sanitizer) {
entry { entry {
val args = listOf(execClang.resolveExecutable(compileTask.compiler.get())) + compileTask.compilerFlags.get() + execClang.clangArgsForCppRuntime(konanTarget.name) val args = listOf(execClang.resolveExecutable(compileTask.compiler.get())) + compileTask.compilerFlags.get() + execClang.clangArgsForCppRuntime(target.name)
directory.set(compileTask.compilerWorkingDirectory) directory.set(compileTask.compilerWorkingDirectory)
files.setFrom(compileTask.inputFiles) files.setFrom(compileTask.inputFiles)
arguments.set(args) arguments.set(args)
@@ -120,18 +171,15 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) {
} }
fun module(name: String, srcRoot: File = project.file("src/$name"), outputGroup: String = "main", configurationBlock: CompileToBitcode.() -> Unit = {}) { fun module(name: String, srcRoot: File = project.file("src/$name"), outputGroup: String = "main", configurationBlock: CompileToBitcode.() -> Unit = {}) {
targetList.get().forEach { targetName -> val targetName = target.name
val target = platformManager.targetByName(targetName) val allMainModulesTask = owner.allMainModulesTasks[targetName]!!
val sanitizers: List<SanitizerKind?> = target.supportedSanitizers() + listOf(null)
val allMainModulesTask = allMainModulesTasks[targetName]!!
sanitizers.forEach { sanitizer ->
val taskName = fullTaskName(name, targetName, sanitizer) val taskName = fullTaskName(name, targetName, sanitizer)
val task = project.tasks.create(taskName, CompileToBitcode::class.java, target, sanitizer.asMaybe).apply { val task = project.tasks.create(taskName, CompileToBitcode::class.java, target, sanitizer.asMaybe).apply {
this.moduleName.set(name) this.moduleName.set(name)
this.outputFile.convention(moduleName.flatMap { project.layout.buildDirectory.file("bitcode/$outputGroup/$target${sanitizer.dirSuffix}/$it.bc") }) this.outputFile.convention(moduleName.flatMap { project.layout.buildDirectory.file("bitcode/$outputGroup/$target${sanitizer.dirSuffix}/$it.bc") })
this.outputDirectory.convention(moduleName.flatMap { project.layout.buildDirectory.dir("bitcode/$outputGroup/$target${sanitizer.dirSuffix}/$it") }) this.outputDirectory.convention(moduleName.flatMap { project.layout.buildDirectory.dir("bitcode/$outputGroup/$target${sanitizer.dirSuffix}/$it") })
this.compiler.convention("clang++") this.compiler.convention("clang++")
this.compilerArgs.set(DEFAULT_CPP_FLAGS) this.compilerArgs.set(owner.DEFAULT_CPP_FLAGS)
this.inputFiles.from(srcRoot.resolve("cpp")) this.inputFiles.from(srcRoot.resolve("cpp"))
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")
@@ -145,31 +193,23 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) {
dependsOn(":kotlin-native:dependencies:update") dependsOn(":kotlin-native:dependencies:update")
configurationBlock() configurationBlock()
} }
addToCompdb(task, target) addToCompdb(task)
if (outputGroup == "main" && sanitizer == null) { if (outputGroup == "main" && sanitizer == null) {
allMainModulesTask.configure { allMainModulesTask.configure {
dependsOn(taskName) dependsOn(taskName)
} }
} }
} }
}
}
abstract class TestsGroup @Inject constructor( fun testsGroup(
val target: KonanTarget,
private val _sanitizer: Maybe<SanitizerKind>,
) {
val sanitizer
get() = _sanitizer.orNull
abstract val testedModules: ListProperty<String>
abstract val testSupportModules: ListProperty<String>
abstract val testLauncherModule: Property<String>
}
private fun createTestTask(
testTaskName: String, testTaskName: String,
testsGroup: TestsGroup, action: Action<in TestsGroup>,
) { ) {
val testsGroup = project.objects.newInstance(TestsGroup::class.java, target, sanitizer.asMaybe).apply {
testSupportModules.convention(listOf("googletest", "googlemock"))
testLauncherModule.convention("test_support")
action.execute(this)
}
val target = testsGroup.target val target = testsGroup.target
val sanitizer = testsGroup.sanitizer val sanitizer = testsGroup.sanitizer
val testName = fullTaskName(testTaskName, target.name, sanitizer) val testName = fullTaskName(testTaskName, target.name, sanitizer)
@@ -197,7 +237,7 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) {
dependsOn(":kotlin-native:dependencies:update") dependsOn(":kotlin-native:dependencies:update")
dependsOn("downloadGoogleTest") dependsOn("downloadGoogleTest")
addToCompdb(this, target) addToCompdb(this)
} }
if (task.inputFiles.count() == 0) null if (task.inputFiles.count() == 0) null
else task else task
@@ -217,7 +257,7 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) {
group = VERIFICATION_BUILD_TASK_GROUP group = VERIFICATION_BUILD_TASK_GROUP
this.target.set(target) this.target.set(target)
this.sanitizer.set(sanitizer) this.sanitizer.set(sanitizer)
this.outputFile.set(project.layout.buildDirectory.file("bin/test/${target}/$testName${target.executableExtension}")) this.outputFile.set(project.layout.buildDirectory.file("bin/test/${target}/$testName.${target.family.exeSuffix}"))
this.llvmLinkFirstStageOutputFile.set(project.layout.buildDirectory.file("bitcode/test/$target/$testName-firstStage.bc")) this.llvmLinkFirstStageOutputFile.set(project.layout.buildDirectory.file("bitcode/test/$target/$testName-firstStage.bc"))
this.llvmLinkOutputFile.set(project.layout.buildDirectory.file("bitcode/test/$target/$testName.bc")) this.llvmLinkOutputFile.set(project.layout.buildDirectory.file("bitcode/test/$target/$testName.bc"))
this.compilerOutputFile.set(project.layout.buildDirectory.file("obj/$target/$testName.o")) this.compilerOutputFile.set(project.layout.buildDirectory.file("obj/$target/$testName.o"))
@@ -243,68 +283,15 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) {
usesService(runGTestSemaphore) usesService(runGTestSemaphore)
} }
allTestsTasks[target.name]!!.configure { owner.allTestsTasks[target.name]!!.configure {
dependsOn(runTask) dependsOn(runTask)
} }
} }
fun testsGroup(
testTaskName: String,
action: Action<in TestsGroup>,
) {
platformManager.enabled.forEach { target ->
val sanitizers: List<SanitizerKind?> = target.supportedSanitizers() + listOf(null)
sanitizers.forEach { sanitizer ->
val instance = project.objects.newInstance(TestsGroup::class.java, target, sanitizer.asMaybe).apply {
testSupportModules.convention(listOf("googletest", "googlemock"))
testLauncherModule.convention("test_support")
action.execute(this)
}
createTestTask(testTaskName, instance)
}
}
} }
companion object { companion object {
const val BUILD_TASK_GROUP = LifecycleBasePlugin.BUILD_GROUP const val BUILD_TASK_GROUP = LifecycleBasePlugin.BUILD_GROUP
const val VERIFICATION_TASK_GROUP = LifecycleBasePlugin.VERIFICATION_GROUP const val VERIFICATION_TASK_GROUP = LifecycleBasePlugin.VERIFICATION_GROUP
const val VERIFICATION_BUILD_TASK_GROUP = "verification build" const val VERIFICATION_BUILD_TASK_GROUP = "verification build"
@OptIn(ExperimentalStdlibApi::class)
private val String.capitalized: String
get() = replaceFirstChar { it.uppercase() }
private fun String.snakeCaseToUpperCamelCase() = split('_').joinToString(separator = "") { it.capitalized }
private fun fullTaskName(name: String, targetName: String, sanitizer: SanitizerKind?) = "${targetName}${name.snakeCaseToUpperCamelCase()}${sanitizer.taskSuffix}"
private val SanitizerKind?.taskSuffix
get() = when (this) {
null -> ""
SanitizerKind.ADDRESS -> "_ASAN"
SanitizerKind.THREAD -> "_TSAN"
}
private val SanitizerKind?.dirSuffix
get() = when (this) {
null -> ""
SanitizerKind.ADDRESS -> "-asan"
SanitizerKind.THREAD -> "-tsan"
}
private val SanitizerKind?.description
get() = when (this) {
null -> ""
SanitizerKind.ADDRESS -> " with ASAN"
SanitizerKind.THREAD -> " with TSAN"
}
private val KonanTarget.executableExtension
get() = when (this) {
is KonanTarget.MINGW_X64 -> ".exe"
is KonanTarget.MINGW_X86 -> ".exe"
else -> ""
}
} }
} }
@@ -11,13 +11,18 @@ import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.* import org.gradle.kotlin.dsl.create
import org.jetbrains.kotlin.konan.target.HostManager import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.newInstance
import org.gradle.kotlin.dsl.register
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.PlatformManager import org.jetbrains.kotlin.konan.target.SanitizerKind
import org.jetbrains.kotlin.konan.target.TargetDomainObjectContainer
import org.jetbrains.kotlin.konan.target.targetSuffix
import org.jetbrains.kotlin.utils.Maybe
import org.jetbrains.kotlin.utils.asMaybe
import javax.inject.Inject import javax.inject.Inject
/** /**
@@ -40,10 +45,12 @@ import javax.inject.Inject
* *
* @see CompilationDatabasePlugin gradle plugin that creates this extension. * @see CompilationDatabasePlugin gradle plugin that creates this extension.
*/ */
abstract class CompilationDatabaseExtension @Inject constructor(private val project: Project) { abstract class CompilationDatabaseExtension @Inject constructor(private val project: Project) : TargetDomainObjectContainer<CompilationDatabaseExtension.Target>(project) {
// TODO: This platformManager should acquired be from something service-ish. init {
// But for usefulness this service should be accessible from WorkAction. this.factory = { target, sanitizer ->
private val platformManager = project.extensions.getByType<PlatformManager>() project.objects.newInstance<Target>(project, target, sanitizer.asMaybe)
}
}
/** /**
* Entries in the compilation database. * Entries in the compilation database.
@@ -51,8 +58,14 @@ abstract class CompilationDatabaseExtension @Inject constructor(private val proj
* Single [Entry] generates a number of compilation database entries: one for each file in [files]. * Single [Entry] generates a number of compilation database entries: one for each file in [files].
* *
* @property target target for which this [Entry] is generated. * @property target target for which this [Entry] is generated.
* @property sanitizer optional sanitizer for [target].
*/ */
abstract class Entry @Inject constructor(val target: KonanTarget) { abstract class Entry @Inject constructor(
val target: KonanTarget,
_sanitizer: Maybe<SanitizerKind>,
) {
val sanitizer = _sanitizer.orNull
/** /**
* **directory** from the [JSON Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html#format). * **directory** from the [JSON Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html#format).
* *
@@ -92,34 +105,38 @@ abstract class CompilationDatabaseExtension @Inject constructor(private val proj
* [task] is the gradle task for compilation database generation. * [task] is the gradle task for compilation database generation.
* *
* @property target target for which compilation database is generated. * @property target target for which compilation database is generated.
* @property sanitizer optional sanitizer for which compilation database is generated.
*/ */
abstract class Target @Inject constructor( abstract class Target @Inject constructor(
private val project: Project, private val project: Project,
val target: KonanTarget, val target: KonanTarget,
_sanitizer: Maybe<SanitizerKind>,
) { ) {
val sanitizer = _sanitizer.orNull
protected abstract val mergeFrom: ListProperty<GenerateCompilationDatabase> protected abstract val mergeFrom: ListProperty<GenerateCompilationDatabase>
/** /**
* Merge compilation database generated for [from] project for [target]. * Merge compilation database generated for [from] project for [target] with optional [sanitizer].
* *
* @param from project with applied [CompilationDatabasePlugin] to merge compilation database from. * @param from project with applied [CompilationDatabasePlugin] to merge compilation database from.
*/ */
fun mergeFrom(from: Provider<Project>) { fun mergeFrom(from: Provider<Project>) {
mergeFrom.add(from.flatMap { project -> mergeFrom.add(from.flatMap { project ->
project.extensions.getByType<CompilationDatabaseExtension>().target(target).task project.extensions.getByType<CompilationDatabaseExtension>().target(target, sanitizer).task
}) })
} }
protected abstract val entries: ListProperty<GenerateCompilationDatabase.Entry> protected abstract val entries: ListProperty<GenerateCompilationDatabase.Entry>
/** /**
* Add an entry to the compilation database for [target]. * Add an entry to the compilation database for [target] with optional [sanitizer].
* *
* @param action configure [Entry] * @param action configure [Entry]
*/ */
fun entry(action: Action<in Entry>) { fun entry(action: Action<in Entry>) {
entries.add(project.provider { entries.add(project.provider {
val instance = project.objects.newInstance<Entry>(target).apply { val instance = project.objects.newInstance<Entry>(target, sanitizer.asMaybe).apply {
action.execute(this) action.execute(this)
} }
project.objects.newInstance<GenerateCompilationDatabase.Entry>().apply { project.objects.newInstance<GenerateCompilationDatabase.Entry>().apply {
@@ -132,67 +149,17 @@ abstract class CompilationDatabaseExtension @Inject constructor(private val proj
} }
/** /**
* Gradle task that generates compilation database for [target]. * Gradle task that generates compilation database for [target] with optional [sanitizer].
*/ */
val task = project.tasks.register<GenerateCompilationDatabase>("${target}CompilationDatabase") { val task = project.tasks.register<GenerateCompilationDatabase>("${target}${sanitizer.targetSuffix}CompilationDatabase") {
description = "Generate compilation database for $target" description = "Generate compilation database for $target${sanitizer.targetSuffix}"
group = TASK_GROUP group = TASK_GROUP
mergeFiles.from(mergeFrom) mergeFiles.from(mergeFrom)
entries.set(this@Target.entries) entries.set(this@Target.entries)
outputFile.set(project.layout.buildDirectory.file("${target}/compile_commands.json")) outputFile.set(project.layout.buildDirectory.file("${target}${sanitizer.targetSuffix}/compile_commands.json"))
} }
} }
protected abstract val targets: MapProperty<KonanTarget, Target>
private fun targetGetOrPut(target: KonanTarget) = targets.getting(target).orNull
?: project.objects.newInstance<Target>(project, target).apply {
targets.put(target, this)
}
/**
* Get [compilation database configuration][Target] for [target] and apply [action] to it.
*
* @param target target to configure.
* @param action action to apply to configuration.
*/
fun target(target: KonanTarget, action: Action<in Target>) = targetGetOrPut(target).apply {
action.execute(this)
}
/**
* Get [compilation database configuration][Target] for [target].
*
* @param target target to configure.
*/
fun target(target: KonanTarget) = this.target(target) {}
/**
* Get [compilation database configurations][Target] for all known targets and apply [action] to each.
*
* @param action action to apply to configurations.
*/
fun allTargets(action: Action<in Target>) = platformManager.enabled.map { target(it, action) }
/**
* Get [compilation database configurations][Target] for all known targets.
*/
val allTargets
get() = allTargets {}
/**
* Get [compilation database configuration][Target] for [host target][HostManager.host] and apply [action] to it.
*
* @param action action to apply to configuration.
*/
fun hostTarget(action: Action<in Target>) = target(HostManager.host, action)
/**
* Get [compilation database configuration][Target] for [host target][HostManager.host].
*/
val hostTarget
get() = hostTarget {}
companion object { companion object {
@JvmStatic @JvmStatic
val TASK_GROUP = "development support" val TASK_GROUP = "development support"
@@ -0,0 +1,146 @@
/*
* 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.konan.target
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.UnknownDomainObjectException
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.kotlin.dsl.getByType
/**
* Associative container from a [KonanTarget] with optional [SanitizerKind] to [T].
*
* Serves similar purpose to [NamedDomainObjectContainer][org.gradle.api.NamedDomainObjectContainer]
* except this is keyed on a target instead of a name. Also this implementation does not support lazy
* creation.
*
* Plugin extensions can inherit from this to automatically get API suitable for `build.gradle.kts`.
* The extension must set [factory] field for [T].
*
* Example usage:
* ```
* someExtension {
* allTargets {
* // This is a lambda inside of a T scope. Called once for each known target with their respective sanitizer.
* }
* target(someTarget) {
* // This is a lambda inside of a T scope. Called for `someTarget` without any sanitizer.
* }
* target(someTarget, someSanitizer) {
* // This is a lambda inside of a T scope. Called for `someTarget` with `someSanitizer`.
* }
* hostTarget {
* // This is a lambda inside of a T scope. Called for the host target without any sanitizer.
* }
* hostTarget(someSanitizer) {
* // This is a lambda inside of a T scope. Called for the host target with `someSanitizer`.
* }
* }
*
* someExtension.target(someTarget) // returns T if `someTarget` was configured. Otherwise fails with UnknownDomainObjectException.
* someExtension.allTargets // returns a Provider<List<T>> of all created configurations.
* ```
*/
// TODO: Consider splitting out interface and the default implementation. Plugins will inherit from the interface via delegation to the implementation.
// TODO: Consider implementing everything from `NamedDomainObjectContainer` but keyed on a target instead of a name.
open class TargetDomainObjectContainer<T> constructor(
private val providerFactory: ProviderFactory,
private val platformManager: PlatformManager,
) {
constructor(project: Project) : this(project.providers, project.extensions.getByType<PlatformManager>())
/**
* How to create [T]. Must be set before using the rest of API.
*/
lateinit var factory: (KonanTarget, SanitizerKind?) -> T
private val targets: MutableMap<Pair<KonanTarget, SanitizerKind?>, T> = mutableMapOf()
/**
* Create or update configuration [T] for [target] with optional [sanitizer] and apply [action] to it.
*
* @param target target of the configuration
* @param sanitizer optional sanitizer for [target]
* @param action action to apply to the configuration
* @return resulting configuration
*/
fun target(target: KonanTarget, sanitizer: SanitizerKind? = null, action: Action<in T>): T {
val key = target to sanitizer
val element = targets.getOrPut(key) { factory(target, sanitizer) }
action.execute(element)
return element
}
/**
* Get configuration [T] for [target] with optional [sanitizer].
*
* @param target target of the configuration
* @param sanitizer optional sanitizer for [target]
* @return resulting configuration
* @throws UnknownDomainObjectException if configuration for [target] and [sanitizer] does not exist
*/
fun target(target: KonanTarget, sanitizer: SanitizerKind? = null): T {
val key = target to sanitizer
return targets.get(key) ?: throw UnknownDomainObjectException("Configuration for $target${sanitizer.targetSuffix} does not exists")
}
/**
* Create or update configurations [T] for all known targets with their sanitizers and apply [action] to each of it.
*
* @param action action to apply to the configuration
* @return list of configurations
*/
fun allTargets(action: Action<in T>): List<T> {
return platformManager.enabled.flatMap { target ->
val sanitizers = target.supportedSanitizers() + listOf(null)
sanitizers.map { sanitizer ->
this.target(target, sanitizer, action)
}
}
}
/**
* Get all created configurations [T].
*
* @return [Provider] with list of all created configurations
*/
val allTargets: Provider<List<T>> = providerFactory.provider {
targets.values.toList()
}
/**
* Create or update configuration [T] for [host target][HostManager.host] with optional [sanitizer] and apply [action] to it.
*
* @param sanitizer optional sanitizer for [host target][HostManager.host]
* @param action action to apply to the configuration
* @return resulting configuration
*/
fun hostTarget(sanitizer: SanitizerKind? = null, action: Action<in T>): T {
return target(HostManager.host, sanitizer, action)
}
/**
* Get configuration [T] for [host target][HostManager.host] with optional [sanitizer].
*
* @param sanitizer optional sanitizer for [host target][HostManager.host]
* @return resulting configuration
* @throws UnknownDomainObjectException if configuration for [host target][HostManager.host] and [sanitizer] does not exist
*/
fun hostTarget(sanitizer: SanitizerKind? = null): T {
return target(HostManager.host, sanitizer)
}
/**
* Get configuration [T] for [host target][HostManager.host] without sanitizer.
*
* @return resulting configuration
* @throws UnknownDomainObjectException if configuration for [host target][HostManager.host] without sanitizer does not exist
*/
val hostTarget: T
get() = hostTarget()
}
@@ -11,6 +11,7 @@ import org.gradle.api.Project
import org.gradle.api.file.FileCollection import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.getByType
import org.jetbrains.kotlin.bitcode.CompileToBitcodeExtension import org.jetbrains.kotlin.bitcode.CompileToBitcodeExtension
import org.jetbrains.kotlin.bitcode.CompileToBitcodePlugin import org.jetbrains.kotlin.bitcode.CompileToBitcodePlugin
import org.jetbrains.kotlin.resolve import org.jetbrains.kotlin.resolve
@@ -51,10 +52,10 @@ open class RuntimeTestingPlugin : Plugin<Project> {
dependencies: Iterable<TaskProvider<*>> dependencies: Iterable<TaskProvider<*>>
) { ) {
pluginManager.withPlugin("compile-to-bitcode") { pluginManager.withPlugin("compile-to-bitcode") {
val bitcodeExtension = val bitcodeExtension = project.extensions.getByType<CompileToBitcodeExtension>()
project.extensions.getByName(CompileToBitcodePlugin.EXTENSION_NAME) as CompileToBitcodeExtension
bitcodeExtension.module("googletest", outputGroup = "test") { bitcodeExtension.allTargets {
module("googletest", outputGroup = "test") {
inputFiles.from(googleTestRoot.resolve("googletest/src")) inputFiles.from(googleTestRoot.resolve("googletest/src"))
inputFiles.include("*.cc") inputFiles.include("*.cc")
inputFiles.exclude("gtest-all.cc", "gtest_main.cc") inputFiles.exclude("gtest-all.cc", "gtest_main.cc")
@@ -66,7 +67,7 @@ open class RuntimeTestingPlugin : Plugin<Project> {
dependsOn(dependencies) dependsOn(dependencies)
} }
bitcodeExtension.module("googlemock", outputGroup = "test") { module("googlemock", outputGroup = "test") {
inputFiles.from(googleTestRoot.resolve("googlemock/src")) inputFiles.from(googleTestRoot.resolve("googlemock/src"))
inputFiles.include("*.cc") inputFiles.include("*.cc")
inputFiles.exclude("gmock-all.cc", "gmock_main.cc") inputFiles.exclude("gmock-all.cc", "gmock_main.cc")
@@ -80,6 +81,7 @@ open class RuntimeTestingPlugin : Plugin<Project> {
} }
} }
} }
}
companion object { companion object {
internal const val GOOGLE_TEST_EXTENSION_NAME = "googletest" internal const val GOOGLE_TEST_EXTENSION_NAME = "googletest"
+3
View File
@@ -8,12 +8,15 @@ plugins {
} }
bitcode { bitcode {
// These are only used in kotlin-native/backend.native/build.gradle where only the host target is needed.
hostTarget {
module("files") { module("files") {
headersDirs.from(layout.projectDirectory.dir("src/files/headers")) headersDirs.from(layout.projectDirectory.dir("src/files/headers"))
} }
module("env") { module("env") {
headersDirs.from(layout.projectDirectory.dir("src/env/headers")) headersDirs.from(layout.projectDirectory.dir("src/env/headers"))
} }
}
} }
val hostName: String by project val hostName: String by project
+2
View File
@@ -37,6 +37,7 @@ val hostName: String by project
val targetList: List<String> by project val targetList: List<String> by project
bitcode { bitcode {
allTargets {
module("main") { module("main") {
// TODO: Split out out `base` module and merge it together with `main` into `runtime.bc` // TODO: Split out out `base` module and merge it together with `main` into `runtime.bc`
if (sanitizer == null) { if (sanitizer == null) {
@@ -222,6 +223,7 @@ bitcode {
testsGroup("experimentalMM_noop_std_alloc_runtime_tests") { testsGroup("experimentalMM_noop_std_alloc_runtime_tests") {
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "noop_gc", "std_alloc", "objc") testedModules.addAll("main", "experimental_memory_manager", "common_gc", "noop_gc", "std_alloc", "objc")
} }
}
} }
val hostRuntime by tasks.registering { val hostRuntime by tasks.registering {
@@ -9,3 +9,18 @@ enum class SanitizerKind {
ADDRESS, ADDRESS,
THREAD, THREAD,
} }
/**
* Suffix for [KonanTarget] name.
*
* In string interpolation use
* ```
* "… ${target}${sanitizer.targetSuffix} …"
* ```
*/
val SanitizerKind?.targetSuffix: String
get() = when (this) {
null -> ""
SanitizerKind.THREAD -> "_tsan"
SanitizerKind.ADDRESS -> "_asan"
}