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
abstract val executionTimeout: Property<Duration>
@get:Input
val platformManagerExtension: PlatformManager = project.extensions.getByType<PlatformManager>()
@TaskAction
fun run() {
val workQueue = workerExecutor.noIsolation()
@@ -157,7 +160,7 @@ abstract class RunGTest : DefaultTask() {
reportFileUnprocessed.set(this@RunGTest.reportFileUnprocessed)
filter.set(this@RunGTest.filter)
tsanSuppressionsFile.set(this@RunGTest.tsanSuppressionsFile)
platformManager.set(project.extensions.getByType<PlatformManager>())
platformManager.set(platformManagerExtension)
targetName.set(this@RunGTest.target.get().name)
executionTimeout.set(this@RunGTest.executionTimeout)
}
@@ -6,16 +6,20 @@
package org.jetbrains.kotlin.testing.native
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.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
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.ExecSpec
import org.jetbrains.kotlin.isNotEmpty
import java.io.File
import java.net.URL
import java.net.URI
import java.util.*
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.
*/
@Suppress("UnstableApiUsage")
open class GitDownloadTask @Inject constructor(
private val repositoryProvider: Provider<URL>,
private val revisionProvider: Provider<String>,
private val outputDirectoryProvider: Provider<File>
abstract class GitDownloadTask @Inject constructor(
objects: ObjectFactory,
private val execOperations: ExecOperations,
private val fileOperations: FileOperations,
) : DefaultTask() {
@get:Input
abstract val repository: Property<URI>
private val repository: URL
get() = repositoryProvider.get()
@get:Input
abstract val revision: Property<String>
private val revision: String
get() = revisionProvider.get()
private val outputDirectory: File
get() = outputDirectoryProvider.get()
@get:OutputDirectory
abstract val outputDirectory: DirectoryProperty
@Option(option = "refresh",
description = "Fetch and checkout the revision even if the output directory already contains it. " +
"All changes in the output directory will be overwritten")
@Input
val refresh: Property<Boolean> = project.objects.property(Boolean::class.java).apply {
set(false)
}
private val upToDateChecker = UpToDateChecker()
@get:Input
val refresh: Property<Boolean> = objects.property<Boolean>().convention(false)
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:
*
@@ -138,38 +57,58 @@ open class GitDownloadTask @Inject constructor(
*
* In all other cases we consider the task UP-TO-DATE.
*/
fun isUpToDate(): Boolean {
return !refresh.get() &&
outputDirectory.let { it.exists() && project.fileTree(it).isNotEmpty } &&
noRevisionChanges()
}
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
outputs.upToDateWhen {
val upToDate = !refresh.get() && outputDirectory.asFileTree.isNotEmpty
if (upToDate) {
logger.info("Skip cloning to avoid rewriting possible debug changes in ${outputDirectory.get().asFile.absolutePath}.")
}
upToDate
}
}
private data class RevisionInfo(val repository: String, val revision: String) {
constructor(repository: URL, revision: String): this(repository.toString(), revision)
private fun git(
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.Plugin
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.ProjectLayout
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.TaskProvider
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.property
import org.jetbrains.kotlin.bitcode.CompileToBitcodeExtension
import org.jetbrains.kotlin.resolve
import java.io.File
import java.net.URL
import java.net.URI
import javax.inject.Inject
@Suppress("UnstableApiUsage")
open class RuntimeTestingPlugin : Plugin<Project> {
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 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> {
val task = tasks.register(
"downloadGoogleTest",
GitDownloadTask::class.java,
provider { URL(extension.repository) },
provider { extension.revision },
provider { extension.fetchDirectory }
)
task.configure {
refresh.set(provider { extension.refresh })
onlyIf { extension.localSourceRoot == null }
description = "Retrieves GoogleTest from the given repository"
group = "Google Test"
}
return task
}
private fun Project.registerDownloadTask(extension: GoogleTestExtension): TaskProvider<GitDownloadTask> =
tasks.register("downloadGoogleTest", GitDownloadTask::class.java) {
description = "Retrieves GoogleTest from the given repository"
group = "Google Test"
onlyIf {
!extension.hasLocalSourceRoot.get()
}
repository.set(extension.repository.map { URI.create(it) })
revision.set(extension.revision)
outputDirectory.set(extension.sourceDirectory)
refresh.set(extension.refresh)
}
private fun Project.createBitcodeTasks(
googleTestRoot: Provider<File>,
googleTestRoot: DirectoryProperty,
dependencies: Iterable<TaskProvider<*>>
) {
pluginManager.withPlugin("compile-to-bitcode") {
@@ -57,12 +58,12 @@ open class RuntimeTestingPlugin : Plugin<Project> {
module("googletest") {
sourceSets {
testFixtures {
inputFiles.from(googleTestRoot.resolve("googletest/src"))
inputFiles.from(googleTestRoot.dir("googletest/src"))
// That's how googletest/CMakeLists.txt builds gtest library.
inputFiles.include("gtest-all.cc")
headersDirs.setFrom(
googleTestRoot.resolve("googletest/include"),
googleTestRoot.resolve("googletest")
googleTestRoot.dir("googletest/include"),
googleTestRoot.dir("googletest")
)
}
}
@@ -73,13 +74,13 @@ open class RuntimeTestingPlugin : Plugin<Project> {
module("googlemock") {
sourceSets {
testFixtures {
inputFiles.from(googleTestRoot.resolve("googlemock/src"))
inputFiles.from(googleTestRoot.dir("googlemock/src"))
// That's how googlemock/CMakeLists.txt builds gmock library.
inputFiles.include("gmock-all.cc")
headersDirs.setFrom(
googleTestRoot.resolve("googlemock"),
googleTestRoot.resolve("googlemock/include"),
googleTestRoot.resolve("googletest/include"),
googleTestRoot.dir("googlemock"),
googleTestRoot.dir("googlemock/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.
*/
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.
*/
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
@@ -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.
*/
var refresh: Boolean = false
val refresh: Property<Boolean> = objects.property<Boolean>().convention(false)
/**
* A directory to fetch the [revision] to.
*/
var fetchDirectory: File = project.file("googletest")
internal var localSourceRoot: File? = null
private val fetchDirectory: Directory = layout.projectDirectory.dir("googletest")
/**
* 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) {
localSourceRoot = directory
}
/**
* 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)
}
abstract val localSourceRoot: DirectoryProperty
val hasLocalSourceRoot: Provider<Boolean> = providers.provider { localSourceRoot.isPresent }
/**
* 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
get() = localSourceRoot ?: fetchDirectory
val sourceDirectory: Provider<Directory> = localSourceRoot.orElse(fetchDirectory)
/**
* A file collection with header directories for GoogleTest and GoogleMock.
* Useful to configure compilation against GTest.
*/
val headersDirs: FileCollection = project.files(
project.provider { sourceDirectory.resolve("googletest/include") },
project.provider { sourceDirectory.resolve("googlemock/include") }
val headersDirs: FileCollection = layout.files(
sourceDirectory.map { it.dir("googletest/include")},
sourceDirectory.map { it.dir("googlemock/include")}
)
}