import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages import org.jetbrains.kotlin.gradle.tasks.CInteropProcess import org.jetbrains.kotlin.konan.target.* import java.io.ByteArrayOutputStream import java.nio.file.Paths description = "XCTest wrapper of Native kotlin.test" plugins { kotlin("multiplatform") } // region XCTest framework location /** * Value source for the Xcode developer framework path location. */ abstract class DevFrameworkPathValueSource : ValueSource { @get:Inject abstract val execOperations: ExecOperations interface Parameters : ValueSourceParameters { val konanTarget: Property } override fun obtain(): String { val devFrameworkPath = parameters.konanTarget.get().developerFrameworkPath() check(File(devFrameworkPath).exists()) { "Developer frameworks path wasn't found at $devFrameworkPath. Check configuration and Xcode installation" } 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 = tasks.register("${target}FrameworkCopy") { into(layout.buildDirectory.dir("$target/Frameworks")) from( providers.of(DevFrameworkPathValueSource::class) { parameters { konanTarget = target } } ) { include("XCTest.framework/**") } } // endregion // region Kotlin Multiplatform build configuration val nativeTargets = mutableListOf() if (HostManager.hostIsMac) { kotlin { with(nativeTargets) { add(macosX64()) add(macosArm64()) add(iosX64()) add(iosArm64()) add(iosSimulatorArm64()) forEach { val copyTask = registerCopyFrameworkTask(it.konanTarget) it.compilations.all { cinterops { register("XCTest") { compilerOpts("-iframework", copyTask.map { it.destinationDir }.get().absolutePath) // cinterop task should depend on the framework copy task tasks.named(interopProcessingTaskName).configure { dependsOn(copyTask) } } } } } } sourceSets.all { languageSettings.apply { // Oh, yeah! So much experimental, so wow! optIn("kotlinx.cinterop.BetaInteropApi") optIn("kotlinx.cinterop.ExperimentalForeignApi") optIn("kotlin.experimental.ExperimentalNativeApi") } } } } // endregion // region Artifact collection for consumers val kotlinTestNativeXCTest by configurations.creating { attributes { attribute(Usage.USAGE_ATTRIBUTE, objects.named(KotlinUsages.KOTLIN_API)) attribute(KotlinPlatformType.attribute, KotlinPlatformType.native) } } nativeTargets.forEach { target -> val targetName = target.konanTarget.name val mainCompilation = target.compilations.getByName("main") val outputKlibTask = mainCompilation.compileTaskProvider @Suppress("UNCHECKED_CAST") val cinteropKlibTask = tasks.named( mainCompilation.cinterops .getByName("XCTest") .interopProcessingTaskName ) as? TaskProvider ?: error("Unable to get CInteropProcess task provider") val frameworkCopyTask = tasks.named("${targetName}FrameworkCopy") artifacts { add(kotlinTestNativeXCTest.name, outputKlibTask.flatMap { it.outputFile }) { classifier = targetName builtBy(outputKlibTask) } add(kotlinTestNativeXCTest.name, cinteropKlibTask.flatMap { it.outputFileProvider }) { classifier = targetName builtBy(cinteropKlibTask) } // Add a path to a directory that contains copied framework to share it with test infrastructure add(kotlinTestNativeXCTest.name, frameworkCopyTask.map { it.destinationDir }) { classifier = "${targetName}Frameworks" builtBy(frameworkCopyTask) } } } // endregion