Make :kotlin-native:runtime:hostRuntimeTests CC compatible

Required for KTI-1553
This commit is contained in:
cristiangarcia
2024-03-07 23:09:57 +01:00
committed by Space Team
parent 09dc9c6743
commit 8f1f6ca9c9
3 changed files with 119 additions and 183 deletions
@@ -146,6 +146,9 @@ abstract class RunGTest : DefaultTask() {
@get:Input @get:Input
abstract val executionTimeout: Property<Duration> abstract val executionTimeout: Property<Duration>
@get:Input
val platformManagerExtension: PlatformManager = project.extensions.getByType<PlatformManager>()
@TaskAction @TaskAction
fun run() { fun run() {
val workQueue = workerExecutor.noIsolation() val workQueue = workerExecutor.noIsolation()
@@ -157,7 +160,7 @@ abstract class RunGTest : DefaultTask() {
reportFileUnprocessed.set(this@RunGTest.reportFileUnprocessed) reportFileUnprocessed.set(this@RunGTest.reportFileUnprocessed)
filter.set(this@RunGTest.filter) filter.set(this@RunGTest.filter)
tsanSuppressionsFile.set(this@RunGTest.tsanSuppressionsFile) tsanSuppressionsFile.set(this@RunGTest.tsanSuppressionsFile)
platformManager.set(project.extensions.getByType<PlatformManager>()) platformManager.set(platformManagerExtension)
targetName.set(this@RunGTest.target.get().name) targetName.set(this@RunGTest.target.get().name)
executionTimeout.set(this@RunGTest.executionTimeout) executionTimeout.set(this@RunGTest.executionTimeout)
} }
@@ -6,16 +6,20 @@
package org.jetbrains.kotlin.testing.native package org.jetbrains.kotlin.testing.native
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.internal.file.FileOperations
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option import org.gradle.api.tasks.options.Option
import org.gradle.kotlin.dsl.property
import org.gradle.process.ExecOperations
import org.gradle.process.ExecResult import org.gradle.process.ExecResult
import org.gradle.process.ExecSpec import org.gradle.process.ExecSpec
import org.jetbrains.kotlin.isNotEmpty import org.jetbrains.kotlin.isNotEmpty
import java.io.File import java.net.URI
import java.net.URL
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@@ -23,112 +27,27 @@ import javax.inject.Inject
* Clones the given revision of the given Git repository to the given directory. * Clones the given revision of the given Git repository to the given directory.
*/ */
@Suppress("UnstableApiUsage") @Suppress("UnstableApiUsage")
open class GitDownloadTask @Inject constructor( abstract class GitDownloadTask @Inject constructor(
private val repositoryProvider: Provider<URL>, objects: ObjectFactory,
private val revisionProvider: Provider<String>, private val execOperations: ExecOperations,
private val outputDirectoryProvider: Provider<File> private val fileOperations: FileOperations,
) : DefaultTask() { ) : DefaultTask() {
@get:Input
abstract val repository: Property<URI>
private val repository: URL @get:Input
get() = repositoryProvider.get() abstract val revision: Property<String>
private val revision: String @get:OutputDirectory
get() = revisionProvider.get() abstract val outputDirectory: DirectoryProperty
private val outputDirectory: File
get() = outputDirectoryProvider.get()
@Option(option = "refresh", @Option(option = "refresh",
description = "Fetch and checkout the revision even if the output directory already contains it. " + description = "Fetch and checkout the revision even if the output directory already contains it. " +
"All changes in the output directory will be overwritten") "All changes in the output directory will be overwritten")
@Input @get:Input
val refresh: Property<Boolean> = project.objects.property(Boolean::class.java).apply { val refresh: Property<Boolean> = objects.property<Boolean>().convention(false)
set(false)
}
private val upToDateChecker = UpToDateChecker()
init { init {
outputs.upToDateWhen { upToDateChecker.isUpToDate() }
}
private fun git(
vararg args: String,
ignoreExitValue: Boolean = false,
execConfiguration: ExecSpec.() -> Unit = {}
): ExecResult =
project.exec {
executable = "git"
args(*args)
isIgnoreExitValue = ignoreExitValue
execConfiguration()
}
private fun tryCloneBranch(): Boolean {
val execResult = git(
"clone", repository.toString(),
outputDirectory.absolutePath,
"--depth", "1",
"--branch", revision,
ignoreExitValue = true
)
return execResult.exitValue == 0
}
private fun fetchByHash() {
git("init", outputDirectory.absolutePath)
git("fetch", repository.toString(), "--depth", "1", revision) {
workingDir(outputDirectory)
}
git("reset", "--hard", revision) {
workingDir(outputDirectory)
}
}
@TaskAction
fun clone() {
// Gradle ignores outputs.upToDateWhen { ... } and reruns a task if the classpath of the task was changed
// So we have to perform the up-to-date check manually one more time in the task action.
if (upToDateChecker.isUpToDate()) {
logger.info("Skip cloning to avoid rewriting possible debug changes in ${outputDirectory.absolutePath}.")
return
}
project.delete {
delete(outputDirectory)
}
if (!tryCloneBranch()) {
logger.info("Cannot use the revision '$revision' to clone the repository. Trying to use init && fetch instead.")
fetchByHash()
}
// Store info about used revision for the manual up-to-date check.
upToDateChecker.storeRevisionInfo()
// Delete the .git directory of the cloned repo to avoid adding it to IDEA's VCS roots.
outputDirectory.resolve(".git").deleteRecursively()
}
/**
* This class performs manual UP-TO-DATE checking.
*
* We want to be able to edit downloaded sources for debug purposes. Thus this task should not rewrite changes in
* the output directory.
*
* Gradle does allow us to provide a custom logic to determine if task outputs are UP-TO-DATE or not (see
* Task.outputs.upToDateWhen). But Gradle still reruns a task if its classpath was changed. This may lead
* to rewriting our manual debug changes in downloaded sources if there are some changes in the
* `build-tools` project.
*
* So we have to manually check up-to-dateness in upToDateWhen and as a first step of the task action.
*/
private inner class UpToDateChecker() {
private val revisionInfoFile: File
get() = outputDirectory.resolve(".revision")
/** /**
* The download task should be executed in the following cases: * The download task should be executed in the following cases:
* *
@@ -138,38 +57,58 @@ open class GitDownloadTask @Inject constructor(
* *
* In all other cases we consider the task UP-TO-DATE. * In all other cases we consider the task UP-TO-DATE.
*/ */
fun isUpToDate(): Boolean { outputs.upToDateWhen {
return !refresh.get() && val upToDate = !refresh.get() && outputDirectory.asFileTree.isNotEmpty
outputDirectory.let { it.exists() && project.fileTree(it).isNotEmpty } && if (upToDate) {
noRevisionChanges() logger.info("Skip cloning to avoid rewriting possible debug changes in ${outputDirectory.get().asFile.absolutePath}.")
}
private fun noRevisionChanges(): Boolean =
revisionInfoFile.exists() && loadRevisionInfo() == RevisionInfo(repository, revision)
fun storeRevisionInfo() {
val properties = Properties()
properties["repository"] = repository.toString()
properties["revision"] = revision
revisionInfoFile.bufferedWriter().use {
properties.store(it, null)
}
}
private fun loadRevisionInfo(): RevisionInfo? {
return try {
val properties = Properties()
revisionInfoFile.bufferedReader().use {
properties.load(it)
}
RevisionInfo(properties.getProperty("repository"), properties.getProperty("revision"))
} catch (_ : Exception) {
null
} }
upToDate
} }
} }
private data class RevisionInfo(val repository: String, val revision: String) { private fun git(
constructor(repository: URL, revision: String): this(repository.toString(), revision) vararg args: String,
ignoreExitValue: Boolean = false,
execConfiguration: ExecSpec.() -> Unit = {}
): ExecResult =
execOperations.exec {
executable = "git"
args(*args)
isIgnoreExitValue = ignoreExitValue
execConfiguration()
}
private fun tryCloneBranch(): Boolean {
val execResult = git(
"clone", repository.get().toString(),
outputDirectory.get().asFile.absolutePath,
"--depth", "1",
"--branch", revision.get(),
ignoreExitValue = true
)
return execResult.exitValue == 0
}
private fun fetchByHash() {
git("init", outputDirectory.get().asFile.absolutePath)
git("fetch", repository.get().toString(), "--depth", "1", revision.get()) {
workingDir(outputDirectory)
}
git("reset", "--hard", revision.get()) {
workingDir(outputDirectory)
}
}
@TaskAction
fun clone() {
fileOperations.delete(outputDirectory)
if (!tryCloneBranch()) {
logger.info("Cannot use the revision '${revision.get()}' to clone the repository. Trying to use init && fetch instead.")
fetchByHash()
}
// Delete the .git directory of the cloned repo to avoid adding it to IDEA's VCS roots.
fileOperations.delete(outputDirectory.dir(".git"))
} }
} }
@@ -8,46 +8,47 @@ package org.jetbrains.kotlin.testing.native
import org.gradle.api.InvalidUserDataException import org.gradle.api.InvalidUserDataException
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection import org.gradle.api.file.FileCollection
import org.gradle.api.file.ProjectLayout
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.property
import org.jetbrains.kotlin.bitcode.CompileToBitcodeExtension import org.jetbrains.kotlin.bitcode.CompileToBitcodeExtension
import org.jetbrains.kotlin.resolve import java.net.URI
import java.io.File
import java.net.URL
import javax.inject.Inject import javax.inject.Inject
@Suppress("UnstableApiUsage") @Suppress("UnstableApiUsage")
open class RuntimeTestingPlugin : Plugin<Project> { open class RuntimeTestingPlugin : Plugin<Project> {
override fun apply(target: Project): Unit = with(target) { override fun apply(target: Project): Unit = with(target) {
val extension = extensions.create(GOOGLE_TEST_EXTENSION_NAME, GoogleTestExtension::class.java, target) val extension = extensions.create(GOOGLE_TEST_EXTENSION_NAME, GoogleTestExtension::class.java)
val downloadTask = registerDownloadTask(extension) val downloadTask = registerDownloadTask(extension)
val googleTestRoot = project.provider { extension.sourceDirectory } val googleTestRoot = extension.sourceDirectory
createBitcodeTasks(googleTestRoot, listOf(downloadTask)) createBitcodeTasks(project.objects.directoryProperty().value(googleTestRoot), listOf(downloadTask))
} }
private fun Project.registerDownloadTask(extension: GoogleTestExtension): TaskProvider<GitDownloadTask> { private fun Project.registerDownloadTask(extension: GoogleTestExtension): TaskProvider<GitDownloadTask> =
val task = tasks.register( tasks.register("downloadGoogleTest", GitDownloadTask::class.java) {
"downloadGoogleTest", description = "Retrieves GoogleTest from the given repository"
GitDownloadTask::class.java, group = "Google Test"
provider { URL(extension.repository) }, onlyIf {
provider { extension.revision }, !extension.hasLocalSourceRoot.get()
provider { extension.fetchDirectory } }
) repository.set(extension.repository.map { URI.create(it) })
task.configure { revision.set(extension.revision)
refresh.set(provider { extension.refresh }) outputDirectory.set(extension.sourceDirectory)
onlyIf { extension.localSourceRoot == null } refresh.set(extension.refresh)
description = "Retrieves GoogleTest from the given repository" }
group = "Google Test"
}
return task
}
private fun Project.createBitcodeTasks( private fun Project.createBitcodeTasks(
googleTestRoot: Provider<File>, googleTestRoot: DirectoryProperty,
dependencies: Iterable<TaskProvider<*>> dependencies: Iterable<TaskProvider<*>>
) { ) {
pluginManager.withPlugin("compile-to-bitcode") { pluginManager.withPlugin("compile-to-bitcode") {
@@ -57,12 +58,12 @@ open class RuntimeTestingPlugin : Plugin<Project> {
module("googletest") { module("googletest") {
sourceSets { sourceSets {
testFixtures { testFixtures {
inputFiles.from(googleTestRoot.resolve("googletest/src")) inputFiles.from(googleTestRoot.dir("googletest/src"))
// That's how googletest/CMakeLists.txt builds gtest library. // That's how googletest/CMakeLists.txt builds gtest library.
inputFiles.include("gtest-all.cc") inputFiles.include("gtest-all.cc")
headersDirs.setFrom( headersDirs.setFrom(
googleTestRoot.resolve("googletest/include"), googleTestRoot.dir("googletest/include"),
googleTestRoot.resolve("googletest") googleTestRoot.dir("googletest")
) )
} }
} }
@@ -73,13 +74,13 @@ open class RuntimeTestingPlugin : Plugin<Project> {
module("googlemock") { module("googlemock") {
sourceSets { sourceSets {
testFixtures { testFixtures {
inputFiles.from(googleTestRoot.resolve("googlemock/src")) inputFiles.from(googleTestRoot.dir("googlemock/src"))
// That's how googlemock/CMakeLists.txt builds gmock library. // That's how googlemock/CMakeLists.txt builds gmock library.
inputFiles.include("gmock-all.cc") inputFiles.include("gmock-all.cc")
headersDirs.setFrom( headersDirs.setFrom(
googleTestRoot.resolve("googlemock"), googleTestRoot.dir("googlemock"),
googleTestRoot.resolve("googlemock/include"), googleTestRoot.dir("googlemock/include"),
googleTestRoot.resolve("googletest/include"), googleTestRoot.dir("googletest/include"),
) )
} }
} }
@@ -99,12 +100,16 @@ open class RuntimeTestingPlugin : Plugin<Project> {
/** /**
* A project extension to configure from where we get the GoogleTest framework. * A project extension to configure from where we get the GoogleTest framework.
*/ */
open class GoogleTestExtension @Inject constructor(private val project: Project) { abstract class GoogleTestExtension @Inject constructor(
layout: ProjectLayout,
providers: ProviderFactory,
objects: ObjectFactory,
) {
/** /**
* A repository to fetch GoogleTest from. * A repository to fetch GoogleTest from.
*/ */
var repository: String = "https://github.com/google/googletest.git" val repository: Property<String> = objects.property<String>().convention("https://github.com/google/googletest.git")
private var _revision: String? = null private var _revision: String? = null
@@ -122,43 +127,32 @@ open class GoogleTestExtension @Inject constructor(private val project: Project)
/** /**
* Fetch the [revision] even if the [fetchDirectory] already contains it. Overwrite all changes manually made in the output directory. * Fetch the [revision] even if the [fetchDirectory] already contains it. Overwrite all changes manually made in the output directory.
*/ */
var refresh: Boolean = false val refresh: Property<Boolean> = objects.property<Boolean>().convention(false)
/** /**
* A directory to fetch the [revision] to. * A directory to fetch the [revision] to.
*/ */
var fetchDirectory: File = project.file("googletest") private val fetchDirectory: Directory = layout.projectDirectory.dir("googletest")
internal var localSourceRoot: File? = null
/** /**
* Use a local [directory] with GoogleTest instead of the fetched one. If set, the download task will not be executed. * Use a local directory with GoogleTest instead of the fetched one. If set, the download task will not be executed.
*/ */
fun useLocalSources(directory: File) { abstract val localSourceRoot: DirectoryProperty
localSourceRoot = directory val hasLocalSourceRoot: Provider<Boolean> = providers.provider { localSourceRoot.isPresent }
}
/**
* Use a local [directory] with GoogleTest instead of the fetched one. If set, the download task will not be executed.
*/
fun useLocalSources(directory: String) {
localSourceRoot = project.file(directory)
}
/** /**
* A getter for directory that contains the GTest sources. * A getter for directory that contains the GTest sources.
* Returns a local source directory if it's specified (see [useLocalSources]) or [fetchDirectory] otherwise. * Returns a local source directory if it's specified (see [localSourceRoot]) or [fetchDirectory] otherwise.
*/ */
val sourceDirectory: File val sourceDirectory: Provider<Directory> = localSourceRoot.orElse(fetchDirectory)
get() = localSourceRoot ?: fetchDirectory
/** /**
* A file collection with header directories for GoogleTest and GoogleMock. * A file collection with header directories for GoogleTest and GoogleMock.
* Useful to configure compilation against GTest. * Useful to configure compilation against GTest.
*/ */
val headersDirs: FileCollection = project.files( val headersDirs: FileCollection = layout.files(
project.provider { sourceDirectory.resolve("googletest/include") }, sourceDirectory.map { it.dir("googletest/include")},
project.provider { sourceDirectory.resolve("googlemock/include") } sourceDirectory.map { it.dir("googlemock/include")}
) )
} }