[K/N] Make XCTest runner project configuration cache friendly

Use ValueSource with ExecOperations to obtain developer framework
location where the XCTest.framework is located.
This is necessary for Gradle configuration cache.
This commit is contained in:
Pavel Punegov
2024-02-27 16:05:12 +01:00
committed by Space Team
parent 808d4353e5
commit bc55eccf28
@@ -12,58 +12,89 @@ plugins {
kotlin("multiplatform")
}
/**
* Path to the target SDK platform.
*
* By default, K/N includes only SDK frameworks as platform libs.
* It's required to get the Library frameworks path where the `XCTest.framework` is located.
* Consider making XCTest a platform lib with KT-61709.
*/
fun targetPlatform(target: String): String {
val out = ByteArrayOutputStream()
val result = project.exec {
executable = "/usr/bin/xcrun"
args = listOf("--sdk", target, "--show-sdk-platform-path")
standardOutput = out
}
check(result.exitValue == 0) {
"xcrun failed with ${result.exitValue}. See the output: $out"
}
return out.toString().trim()
}
// region XCTest framework location
/**
* Returns a path to the Xcode developer frameworks location based on the specified KonanTarget.
* Value source for the Xcode developer framework path location.
*/
fun KonanTarget.developerFrameworkPath(): String {
val platform = when (this) {
KonanTarget.MACOS_ARM64, KonanTarget.MACOS_X64 -> "macosx"
KonanTarget.IOS_SIMULATOR_ARM64, KonanTarget.IOS_X64 -> "iphonesimulator"
KonanTarget.IOS_ARM64 -> "iphoneos"
else -> error("Target $this is not supported here")
abstract class DevFrameworkPathValueSource : ValueSource<String, DevFrameworkPathValueSource.Parameters> {
@get:Inject
abstract val execOperations: ExecOperations
interface Parameters : ValueSourceParameters {
val konanTarget: Property<KonanTarget>
}
return targetPlatform(platform) + "/Developer/Library/Frameworks/"
}
override fun obtain(): String {
val devFrameworkPath = parameters.konanTarget.get().developerFrameworkPath()
/**
* Registers a task to copy the XCTest framework to the build directory for the specified KonanTarget.
*
* @param target The KonanTarget for which the copy framework task should be registered.
* @return The TaskProvider representing the registered copy framework task.
*/
fun registerCopyFrameworkTask(target: KonanTarget): TaskProvider<Sync> =
tasks.register<Sync>("${target}FrameworkCopy") {
val devFrameworkPath = Paths.get(target.developerFrameworkPath())
check(devFrameworkPath.toFile().exists()) {
check(File(devFrameworkPath).exists()) {
"Developer frameworks path wasn't found at $devFrameworkPath. Check configuration and Xcode installation"
}
from(devFrameworkPath.resolve("XCTest.framework"))
return parameters.konanTarget.get().developerFrameworkPath()
}
/**
* Path to the target SDK platform.
*
* By default, K/N includes only SDK frameworks as platform libs.
* It's required to get the Library frameworks path where the `XCTest.framework` is located.
* Consider making XCTest a platform lib with KT-61709.
*/
private fun targetPlatform(target: String): String {
val out = ByteArrayOutputStream()
val result = execOperations.exec {
executable = "/usr/bin/xcrun"
args = listOf("--sdk", target, "--show-sdk-platform-path")
standardOutput = out
}
check(result.exitValue == 0) {
"xcrun failed with ${result.exitValue}. See the output: $out"
}
return out.toString().trim()
}
/**
* Returns a path to the Xcode developer frameworks location based on the specified [KonanTarget].
*/
private fun KonanTarget.developerFrameworkPath(): String {
val platform = when (this) {
KonanTarget.MACOS_ARM64, KonanTarget.MACOS_X64 -> "macosx"
KonanTarget.IOS_SIMULATOR_ARM64, KonanTarget.IOS_X64 -> "iphonesimulator"
KonanTarget.IOS_ARM64 -> "iphoneos"
else -> error("Target $this is not supported here")
}
return targetPlatform(platform) + "/Developer/Library/Frameworks/"
}
}
/**
* Registers a task to copy the XCTest framework to the build directory for the specified [KonanTarget].
*
* @param target The [KonanTarget] for which the copy framework task should be registered.
* @return The [TaskProvider] representing the registered copy framework task.
*/
fun registerCopyFrameworkTask(target: KonanTarget): TaskProvider<Sync> =
tasks.register<Sync>("${target}FrameworkCopy") {
from(
providers.of(DevFrameworkPathValueSource::class) {
parameters {
konanTarget = target
}
}.map {
Paths.get(it).resolve("XCTest.framework")
}
)
into(layout.buildDirectory.dir("$target/Frameworks/XCTest.framework"))
}
// endregion
// region Kotlin Multiplatform build configuration
val nativeTargets = mutableListOf<KotlinNativeTarget>()
if (HostManager.hostIsMac) {
@@ -80,13 +111,7 @@ if (HostManager.hostIsMac) {
it.compilations.all {
cinterops {
register("XCTest") {
compilerOpts(
"-iframework", project.layout.buildDirectory
.dir("$konanTarget/Frameworks")
.get()
.asFile
.absolutePath
)
compilerOpts("-iframework", copyTask.map { it.destinationDir }.get().absolutePath)
// cinterop task should depend on the framework copy task
tasks.named(interopProcessingTaskName).configure {
dependsOn(copyTask)
@@ -107,6 +132,10 @@ if (HostManager.hostIsMac) {
}
}
// endregion
// region Artifact collection for consumers
val kotlinTestNativeXCTest by configurations.creating {
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(KotlinUsages.KOTLIN_API))
@@ -143,4 +172,6 @@ nativeTargets.forEach { target ->
builtBy(frameworkCopyTask)
}
}
}
}
// endregion