[Gradle] Support configuration cache in Xcode/CocoaPods tasks with Gradle 8.1

This commit moves invocation of `xcodebuild` from configuration to execution time. This also fixes the problem with Xcode version being cached inside the daemon.

^KT-59252 Verification Pending
This commit is contained in:
Artem Daugel-Dauge
2023-06-11 21:00:20 +02:00
committed by Space Team
parent 78f4d399d4
commit b6847d0835
22 changed files with 316 additions and 211 deletions
@@ -80,7 +80,7 @@ object PlatformInfo {
&& properties.checkXcodeVersion
&& requiredMajorVersion != null
) {
val currentXcodeVersion = Xcode.findCurrent().version
val currentXcodeVersion = Xcode.findCurrent().version.toString()
val currentMajorVersion = currentXcodeVersion.splitToSequence('.').first()
if (currentMajorVersion != requiredMajorVersion) {
throw IllegalStateException(
@@ -25,7 +25,7 @@ import kotlin.io.path.*
import kotlin.test.*
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("K/N cocoapods tests")
@DisplayName("CocoaPods plugin tests")
@NativeGradlePluginTests
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
@OptIn(EnvironmentalVariablesOverride::class)
@@ -771,16 +771,8 @@ class CocoaPodsIT : KGPBaseTest() {
}
}
@DisplayName("Configuration cache works in a complex scenario with Gradle 8_1")
@GradleTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_8_1)
@Disabled // will be fixed in the next step
fun testConfigurationCacheWorksInAComplexScenarioWithGradle8_1(gradleVersion: GradleVersion) {
testConfigurationCacheWorksInAComplexScenario(gradleVersion)
}
@DisplayName("Configuration cache works in a complex scenario")
@GradleTestVersions(minVersion = TestVersions.Gradle.MAX_SUPPORTED)
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_6, maxVersion = TestVersions.Gradle.G_8_1)
@GradleTest
fun testConfigurationCacheWorksInAComplexScenario(gradleVersion: GradleVersion) {
val buildOptions = defaultBuildOptions.copy(
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.gradle.native
import com.intellij.testFramework.TestDataFile
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode
import org.gradle.util.GradleVersion
import org.jdom.input.SAXBuilder
import org.jetbrains.kotlin.gradle.*
@@ -18,11 +17,7 @@ import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.testbase.TestVersions.Kotlin.NATIVE_STABLE_RELEASE
import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.gradle.util.runProcess
import org.jetbrains.kotlin.gradle.utils.Xcode
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.presetName
import org.jetbrains.kotlin.konan.target.*
import org.junit.Assume
import org.junit.Ignore
import org.junit.Test
@@ -364,7 +359,7 @@ class GeneralNativeIT : BaseGradleIT() {
frameworkPaths.forEach { assertFileExists(it) }
assertTrue(fileInWorkingDir(headerPaths[0]).readText().contains("+ (int32_t)exported"))
val xcodeMajorVersion = Xcode!!.currentVersion.major
val xcodeMajorVersion = Xcode.findCurrent().version.major
// Check that by default release frameworks have bitcode embedded.
withNativeCommandLineArguments(":linkMainReleaseFrameworkIos") { arguments ->
@@ -14,10 +14,10 @@ import org.jetbrains.kotlin.gradle.testbase.extractNativeCompilerCommandLineArgu
import org.jetbrains.kotlin.gradle.transformProjectWithPluginsDsl
import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader
import org.jetbrains.kotlin.gradle.utils.Xcode
import org.jetbrains.kotlin.gradle.utils.XcodeVersion
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.Xcode
import org.jetbrains.kotlin.konan.target.XcodeVersion
import org.jetbrains.kotlin.konan.target.presetName
import org.jetbrains.kotlin.konan.util.DependencyDirectories
import org.junit.Assume
@@ -244,7 +244,7 @@ class NativeDownloadAndPlatformLibsIT : BaseGradleIT() {
if (HostManager.hostIsMac) {
// Building platform libs require Xcode 14.1
Assume.assumeTrue(Xcode!!.currentVersion >= XcodeVersion(14, 1))
Assume.assumeTrue(Xcode.findCurrent().version >= XcodeVersion(14, 1))
}
with(transformNativeTestProjectWithPluginDsl("native-download-maven")) {
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.gradle.native
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.runProcess
import org.jetbrains.kotlin.gradle.utils.Xcode
import org.jetbrains.kotlin.gradle.utils.XcodeUtils
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.DisplayName
@@ -21,8 +21,7 @@ import kotlin.test.assertEquals
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
class NativeXcodeSimulatorTestsIT : KGPBaseTest() {
private val defaultIosSimulator by lazy {
val xcode = Xcode ?: error("XCode is expected to be defined")
xcode.getDefaultTestDeviceId(KonanTarget.IOS_SIMULATOR_ARM64) ?: error("No simulator found for iOS ARM64")
XcodeUtils.getDefaultTestDeviceId(KonanTarget.IOS_SIMULATOR_ARM64) ?: error("No simulator found for iOS ARM64")
}
@DisplayName("A user-friendly error message is produced when the standalone mode is disabled and no simulator has booted")
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost
import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.getFile
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.utils.mapToFile
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -247,7 +248,7 @@ internal abstract class FrameworkCopy : DefaultTask() {
private fun copy(sourceProvider: Provider<File>) {
val source = sourceProvider.get()
val destination = destinationDirectory.asFile.get()
val destination = destinationDirectory.getFile()
val destinationFile = File(destination, source.name)
if (destinationFile.exists()) {
@@ -0,0 +1,60 @@
/*
* Copyright 2010-2023 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.gradle.plugin.mpp.apple
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.utils.getFile
import org.jetbrains.kotlin.gradle.utils.onlyIfCompat
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.Xcode
internal abstract class XcodeVersionTask : DefaultTask() {
companion object {
fun locateOrRegister(project: Project): Provider<XcodeVersionTask> {
return project.rootProject.locateOrRegisterTask("xcodeVersion") { task ->
if (!HostManager.hostIsMac) {
task.onlyIfCompat("Task can be run only on MacOS") { false }
return@locateOrRegisterTask
}
// xcode-select stores a symlink to a developer dir of currently selected Xcode in "/var/db/xcode_select_link"
task.xcodeSelectLink.from("/var/db/xcode_select_link/usr/bin/xcodebuild")
// DEVELOPER_DIR may override currently selected Xcode (it's respected by xcode-select and xcrun)
task.xcodeDeveloperDir.convention(project.providers.environmentVariable("DEVELOPER_DIR"))
task.outputFile.convention(project.layout.buildDirectory.file("xcode-version.txt"))
}
}
}
@get:InputFiles // @InputFiles instead of @InputFile because it allows non-existing files
abstract val xcodeSelectLink: ConfigurableFileCollection
@get:Input
@get:Optional
abstract val xcodeDeveloperDir: Property<String>
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun execute() {
outputFile.getFile().writeText(Xcode.findCurrent().version.toString())
}
}
internal val Provider<XcodeVersionTask>.version: Provider<RegularFile>
get() = flatMap { it.outputFile }
@@ -29,7 +29,9 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.ReadyForEx
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.KOTLIN_NATIVE_IGNORE_INCORRECT_DEPENDENCIES
import org.jetbrains.kotlin.gradle.plugin.internal.artifactTypeAttribute
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XcodeVersionTask
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.registerEmbedAndSignAppleFrameworkTask
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.version
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmVariant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyAttributes
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
@@ -46,7 +48,7 @@ import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.testing.internal.configureConventions
import org.jetbrains.kotlin.gradle.testing.internal.kotlinTestRegistry
import org.jetbrains.kotlin.gradle.testing.testTaskName
import org.jetbrains.kotlin.gradle.utils.Xcode
import org.jetbrains.kotlin.gradle.utils.XcodeUtils
import org.jetbrains.kotlin.gradle.utils.newInstance
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -63,6 +65,7 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
// this afterEvaluate comes from NativeCompilerOptions
val compilationCompilerOptions = binary.compilation.compilerOptions
val konanPropertiesBuildService = KonanPropertiesBuildService.registerIfAbsent(project)
val xcodeVersionTask = XcodeVersionTask.locateOrRegister(project)
val linkTask = registerTask<KotlinNativeLink>(
binary.linkTaskName, listOf(binary)
) {
@@ -75,6 +78,7 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
it.toolOptions.freeCompilerArgs.value(compilationCompilerOptions.options.freeCompilerArgs)
it.toolOptions.freeCompilerArgs.addAll(providers.provider { PropertiesProvider(project).nativeLinkArgs })
it.runViaBuildToolsApi.value(false).disallowChanges() // K/N is not yet supported
it.xcodeVersion.set(xcodeVersionTask.version)
}
@@ -584,9 +588,9 @@ class KotlinNativeTargetWithSimulatorTestsConfigurator :
override fun configureTestTask(target: KotlinNativeTargetWithSimulatorTests, testTask: KotlinNativeSimulatorTest) {
super.configureTestTask(target, testTask)
if (Xcode != null) {
if (isTestTaskEnabled(target)) {
val deviceIdProvider = testTask.project.provider {
Xcode.getDefaultTestDeviceId(target.konanTarget)
XcodeUtils.getDefaultTestDeviceId(target.konanTarget)
?: error("Xcode does not support simulator tests for ${target.konanTarget.name}. Check that requested SDK is installed.")
}
testTask.device.convention(deviceIdProvider).finalizeValueOnRead()
@@ -13,13 +13,11 @@ import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.HasAttributes
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.AbstractExecTask
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
import org.jetbrains.kotlin.gradle.utils.Xcode
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly
@@ -280,12 +278,7 @@ class Framework(
/**
* Embed bitcode for the framework or not. See [BitcodeEmbeddingMode].
*/
val embedBitcodeMode = project.objects.property(org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode::class.java).apply {
convention(project.provider {
Xcode?.defaultBitcodeEmbeddingMode(konanTarget, buildType)
?: org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode.DISABLE
})
}
val embedBitcodeMode = project.objects.property(org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode::class.java)
@Deprecated("Use 'embedBitcodeMode' property instead.")
var embedBitcode: org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
@@ -48,7 +48,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.io.File
internal val ProjectLayout.cocoapodsBuildDirs: CocoapodsBuildDirs
get() = CocoapodsBuildDirs(this)
@@ -515,6 +514,8 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
else -> error("Unknown cocoapods platform: $family")
}
val xcodeVersionTask = XcodeVersionTask.locateOrRegister(project)
val podGenTask = project.registerTask<PodGenTask>(family.toPodGenTaskName) { task ->
task.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies"
task.podspec.set(podspecTaskProvider.map { it.outputFile })
@@ -524,7 +525,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
task.family.set(family)
task.platformSettings.set(platformSettings)
task.pods.set(cocoapodsExtension.pods)
task.xcodeVersion.set(Xcode?.currentVersion)
task.xcodeVersion.set(xcodeVersionTask.version)
}
project.registerTask<PodInstallSyntheticTask>(family.toPodInstallSyntheticTaskName) { task ->
@@ -8,6 +8,7 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
@@ -15,8 +16,9 @@ import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.plugin.cocoapods.platformLiteral
import org.jetbrains.kotlin.gradle.utils.XcodeVersion
import org.jetbrains.kotlin.gradle.utils.parse
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.XcodeVersion
import java.io.File
import javax.inject.Inject
@@ -52,8 +54,9 @@ abstract class PodGenTask @Inject constructor(projectLayout: ProjectLayout) : Co
@get:Nested
internal abstract val pods: ListProperty<CocoapodsDependency>
@get:Input
internal abstract val xcodeVersion: Property<XcodeVersion>
@get:Optional
@get:InputFile
internal abstract val xcodeVersion: RegularFileProperty
@get:OutputFile
val podfile: Provider<File> = projectLayout.cocoapodsBuildDirs.synthetic(family).map { it.file("Podfile").asFile }
@@ -123,7 +126,7 @@ abstract class PodGenTask @Inject constructor(projectLayout: ProjectLayout) : Co
}
private fun insertXcode143DeploymentTargetWorkarounds(family: Family): String {
if (xcodeVersion.get() < XcodeVersion(14, 3)) {
if (XcodeVersion.parse(xcodeVersion) < XcodeVersion(14, 3)) {
return ""
}
@@ -183,12 +183,18 @@ constructor(
get() = embedBitcodeMode.get()
@get:Input
@get:Optional
val embedBitcodeMode: Provider<BitcodeEmbeddingMode> =
(binary as? Framework)?.embedBitcodeMode ?: project.provider { BitcodeEmbeddingMode.DISABLE }
(binary as? Framework)?.embedBitcodeMode ?: objectFactory.property()
@get:Internal
val apiFiles = project.files(project.configurations.getByName(compilation.apiConfigurationName)).filterKlibsPassedToCompiler()
@get:Optional
@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val xcodeVersion = objectFactory.fileProperty()
private val externalDependenciesArgs by lazy { ExternalDependenciesBuilder(project, compilation).buildCompilerArgs() }
private val cacheBuilderSettings by lazy {
@@ -224,10 +230,10 @@ constructor(
args.generateTestRunner = processTests
args.mainPackage = entryPoint
when (embedBitcodeMode.get()) {
when (bitcodeEmbeddingMode()) {
BitcodeEmbeddingMode.BITCODE -> args.embedBitcode = true
BitcodeEmbeddingMode.MARKER -> args.embedBitcodeMarker = true
null, BitcodeEmbeddingMode.DISABLE -> Unit
BitcodeEmbeddingMode.DISABLE -> Unit
}
args.singleLinkerArguments = (linkerOpts + additionalLinkerOpts).toTypedArray()
@@ -391,4 +397,8 @@ constructor(
private inline fun <reified T : Any> lazyConvention(noinline lazyConventionValue: () -> T): Provider<T> {
return objectFactory.providerWithLazyConvention(lazyConventionValue)
}
private fun bitcodeEmbeddingMode(): BitcodeEmbeddingMode {
return XcodeUtils.bitcodeEmbeddingMode(outputKind, embedBitcodeMode.orNull, xcodeVersion, konanTarget, debuggable)
}
}
@@ -15,10 +15,11 @@ import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeOutputKind
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XcodeVersionTask
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.version
import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost
import org.jetbrains.kotlin.gradle.tasks.dependsOn
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.Xcode
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.presetName
@@ -109,6 +110,7 @@ internal fun KotlinNativeArtifact.registerLinkFrameworkTask(
): TaskProvider<KotlinNativeLinkArtifactTask> {
val kind = NativeOutputKind.FRAMEWORK
val destinationDir = project.buildDir.resolve("$outDirName/${target.visibleName}/${buildType.visibleName}")
val xcodeVersionTask = XcodeVersionTask.locateOrRegister(project)
val resultTask = project.registerTask<KotlinNativeLinkArtifactTask>(
lowerCamelCaseName("assemble", name, buildType.visibleName, kind.taskNameClassifier, target.presetName, taskNameSuffix),
listOf(target, kind.compilerOutputKind)
@@ -121,11 +123,10 @@ internal fun KotlinNativeArtifact.registerLinkFrameworkTask(
task.debuggable.set(buildType.debuggable)
task.linkerOptions.set(linkerOptions)
task.binaryOptions.set(binaryOptions)
task.xcodeVersion.set(xcodeVersionTask.version)
task.staticFramework.set(isStatic)
if (embedBitcode != null) {
task.embedBitcode.set(embedBitcode)
} else if (Xcode != null) {
task.embedBitcode.set(project.provider { Xcode.defaultBitcodeEmbeddingMode(target, buildType) })
}
task.libraries.setFrom(project.configurations.getByName(librariesConfigurationName))
task.exportLibraries.setFrom(project.configurations.getByName(exportConfigurationName))
@@ -11,6 +11,7 @@ import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
@@ -26,6 +27,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
import org.jetbrains.kotlin.gradle.targets.native.tasks.buildKotlinNativeBinaryLinkerArgs
import org.jetbrains.kotlin.gradle.tasks.KotlinToolTask
import org.jetbrains.kotlin.gradle.utils.XcodeUtils
import org.jetbrains.kotlin.gradle.utils.newInstance
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -73,6 +75,7 @@ abstract class KotlinNativeLinkArtifactTask @Inject constructor(
abstract val staticFramework: Property<Boolean>
@get:Input
@get:Optional
abstract val embedBitcode: Property<BitcodeEmbeddingMode>
@get:Classpath
@@ -90,6 +93,10 @@ abstract class KotlinNativeLinkArtifactTask @Inject constructor(
@get:Input
abstract val binaryOptions: MapProperty<String, String>
@get:Optional
@get:InputFile
abstract val xcodeVersion: RegularFileProperty
private val nativeBinaryOptions = PropertiesProvider(project).nativeBinaryOptions
@get:Input
@@ -163,7 +170,6 @@ abstract class KotlinNativeLinkArtifactTask @Inject constructor(
enableEndorsedLibs.value(false).finalizeValue()
processTests.convention(false)
staticFramework.convention(false)
embedBitcode.convention(BitcodeEmbeddingMode.DISABLE)
destinationDir.convention(debuggable.flatMap {
val kind = outputKind.visibleName
val target = konanTarget.visibleName
@@ -191,7 +197,7 @@ abstract class KotlinNativeLinkArtifactTask @Inject constructor(
compilerPlugins = emptyList(),//CompilerPlugins aren't needed here because it's no compilation but linking
processTests = processTests.get(),
entryPoint = entryPoint.getOrNull(),
embedBitcode = embedBitcode.get(),
embedBitcode = bitcodeEmbeddingMode(),
linkerOpts = linkerOptions.get(),
binaryOptions = allBinaryOptions.get(),
isStaticFramework = staticFramework.get(),
@@ -205,4 +211,8 @@ abstract class KotlinNativeLinkArtifactTask @Inject constructor(
executionContext = KotlinToolRunner.GradleExecutionContext.fromTaskContext(objectFactory, execOperations, logger)
).run(buildArgs)
}
private fun bitcodeEmbeddingMode(): BitcodeEmbeddingMode {
return XcodeUtils.bitcodeEmbeddingMode(outputKind, embedBitcode.orNull, xcodeVersion, konanTarget, debuggable.get())
}
}
@@ -1,100 +0,0 @@
/*
* 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.gradle.utils
import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.konan.target.Architecture.ARM32
import org.jetbrains.kotlin.konan.target.Architecture.ARM64
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.Family.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly
import java.io.Serializable
@InternalKotlinGradlePluginApi
val Xcode = XcodeUtils.INSTANCE
@InternalKotlinGradlePluginApi
data class XcodeVersion(val major: Int, val minor: Int) : Serializable, Comparable<XcodeVersion> {
override fun compareTo(other: XcodeVersion): Int {
return when (val majorComparison = major.compareTo(other.major)) {
0 -> minor.compareTo(other.minor)
else -> majorComparison
}
}
}
@InternalKotlinGradlePluginApi
class XcodeUtils private constructor() {
companion object {
internal val INSTANCE: XcodeUtils? =
if (HostManager.hostIsMac) XcodeUtils() else null
internal fun parseFromXcodebuild(output: String): XcodeVersion? {
val version = output.lines()[0].removePrefix("Xcode ")
val split = version.split("(\\s+|\\.|-)".toRegex())
return XcodeVersion(
major = split[0].toIntOrNull() ?: return null,
minor = split.getOrNull(1)?.toIntOrNull() ?: return null,
)
}
}
val currentVersion: XcodeVersion by lazy {
val out = runCommand(listOf("/usr/bin/xcrun", "xcodebuild", "-version"))
parseFromXcodebuild(out) ?: XcodeVersion(1, 0)
}
private val defaultTestDevices: Map<Family, String> by lazy {
val osRegex = "-- .* --".toRegex()
val deviceRegex = """[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}""".toRegex()
val out = runCommand(listOf("/usr/bin/xcrun", "simctl", "list", "devices", "available"))
val result = mutableMapOf<Family, String>()
var os: Family? = null
out.lines().forEach { s ->
val osFound = osRegex.find(s)?.value
if (osFound != null) {
val osName = osFound.split(" ")[1]
os = try {
Family.valueOf(osName.toUpperCaseAsciiOnly())
} catch (e: Exception) {
null
}
} else {
val currentOs = os
if (currentOs != null) {
val deviceFound = deviceRegex.find(s)?.value
if (deviceFound != null) {
result[currentOs] = deviceFound
os = null
}
}
}
}
result
}
fun getDefaultTestDeviceId(target: KonanTarget): String? = defaultTestDevices[target.family]
fun defaultBitcodeEmbeddingMode(target: KonanTarget, buildType: NativeBuildType): BitcodeEmbeddingMode {
if (currentVersion.major < 14) {
if (target.family in listOf(IOS, WATCHOS, TVOS) && target.architecture in listOf(ARM32, ARM64)) {
when (buildType) {
NativeBuildType.RELEASE -> return BitcodeEmbeddingMode.BITCODE
NativeBuildType.DEBUG -> return BitcodeEmbeddingMode.MARKER
}
}
}
return BitcodeEmbeddingMode.DISABLE
}
}
@@ -0,0 +1,87 @@
/*
* 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.gradle.utils
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
import org.jetbrains.kotlin.konan.target.*
import org.jetbrains.kotlin.konan.target.Architecture.ARM32
import org.jetbrains.kotlin.konan.target.Architecture.ARM64
import org.jetbrains.kotlin.konan.target.Family.*
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly
@InternalKotlinGradlePluginApi
object XcodeUtils {
fun bitcodeEmbeddingMode(
outputKind: CompilerOutputKind,
userMode: BitcodeEmbeddingMode?,
xcodeVersion: Provider<RegularFile>,
target: KonanTarget,
debuggable: Boolean,
): BitcodeEmbeddingMode {
return when {
outputKind != CompilerOutputKind.FRAMEWORK -> BitcodeEmbeddingMode.DISABLE
userMode != null -> userMode
bitcodeSupported(xcodeVersion, target) -> when (debuggable) {
true -> BitcodeEmbeddingMode.MARKER
false -> BitcodeEmbeddingMode.BITCODE
}
else -> BitcodeEmbeddingMode.DISABLE
}
}
private fun bitcodeSupported(xcodeVersion: Provider<RegularFile>, target: KonanTarget): Boolean {
return XcodeVersion.parse(xcodeVersion).major < 14
&& target.family in listOf(IOS, WATCHOS, TVOS)
&& target.architecture in listOf(ARM32, ARM64)
}
fun getDefaultTestDeviceId(target: KonanTarget): String? = defaultTestDevices[target.family]
private val defaultTestDevices: Map<Family, String> by lazy {
if (!HostManager.hostIsMac) {
return@lazy emptyMap()
}
val osRegex = "-- .* --".toRegex()
val deviceRegex = """[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}""".toRegex()
val out = runCommand(listOf("/usr/bin/xcrun", "simctl", "list", "devices", "available"))
val result = mutableMapOf<Family, String>()
var os: Family? = null
out.lines().forEach { s ->
val osFound = osRegex.find(s)?.value
if (osFound != null) {
val osName = osFound.split(" ")[1]
os = try {
Family.valueOf(osName.toUpperCaseAsciiOnly())
} catch (e: Exception) {
null
}
} else {
val currentOs = os
if (currentOs != null) {
val deviceFound = deviceRegex.find(s)?.value
if (deviceFound != null) {
result[currentOs] = deviceFound
os = null
}
}
}
}
result
}
}
internal fun XcodeVersion.Companion.parse(file: Provider<RegularFile>): XcodeVersion {
val version = file.getFile().readText()
return parse(version) ?: error("Couldn't parse Xcode version from '$version'")
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2023 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.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle.unitTests
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode.*
import org.jetbrains.kotlin.gradle.util.buildProject
import org.jetbrains.kotlin.gradle.utils.XcodeUtils.bitcodeEmbeddingMode
import org.jetbrains.kotlin.konan.target.CompilerOutputKind.FRAMEWORK
import org.jetbrains.kotlin.konan.target.KonanTarget.*
import org.jetbrains.kotlin.konan.target.XcodeVersion
import org.junit.Test
import kotlin.test.assertEquals
class XcodeUtilsTest {
@Test
fun `test bitcode embedding mode selection`() {
val project = buildProject()
fun xcode(major: Int, minor: Int): Provider<RegularFile> {
val version = XcodeVersion(major, minor).toString()
val file = project.file(version)
file.writeText(version)
return project.layout.file(project.provider { file })
}
assertEquals(DISABLE, bitcodeEmbeddingMode(FRAMEWORK, null, xcode(14, 1), IOS_ARM64, debuggable = true))
assertEquals(MARKER, bitcodeEmbeddingMode(FRAMEWORK, null, xcode(13, 9), IOS_ARM64, debuggable = true))
assertEquals(BITCODE, bitcodeEmbeddingMode(FRAMEWORK, null, xcode(13, 4), IOS_ARM64, debuggable = false))
assertEquals(DISABLE, bitcodeEmbeddingMode(FRAMEWORK, null, xcode(10, 3), IOS_X64, debuggable = true))
assertEquals(DISABLE, bitcodeEmbeddingMode(FRAMEWORK, null, xcode(13, 4), MACOS_ARM64, debuggable = false))
}
}
@@ -1,31 +0,0 @@
/*
* Copyright 2010-2023 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.gradle.utils
import org.junit.Test
import kotlin.test.assertEquals
class XcodeVersionTest {
@Test
fun testParsing() {
assertEquals(XcodeVersion(14, 1), XcodeUtils.parseFromXcodebuild("Xcode 14.1\nBuild version 14B47b\n"))
assertEquals(XcodeVersion(14, 3), XcodeUtils.parseFromXcodebuild("Xcode 14.3\nBuild version 14E222b\n"))
assertEquals(XcodeVersion(1, 0), XcodeUtils.parseFromXcodebuild("Xcode 1.0 RC\nBuild version 7B85\n"))
assertEquals(XcodeVersion(1, 15), XcodeUtils.parseFromXcodebuild("Xcode 1.15-RC\nBuild version 7B85\n"))
assertEquals(XcodeVersion(14, 1), XcodeUtils.parseFromXcodebuild("Xcode 14.1.1.3.2.1.123\nBuild version 14B47b\n"))
assertEquals(null, XcodeUtils.parseFromXcodebuild("Xcode 14\nBuild version 14B47b\n"))
assertEquals(null, XcodeUtils.parseFromXcodebuild("XC 14.1\nBuild version 14B47b\n"))
}
@Test
fun testComparison() {
assert(XcodeVersion(3, 2) == XcodeVersion(3, 2))
assert(XcodeVersion(3, 2) > XcodeVersion(2, 14))
assert(XcodeVersion(3, 2) > XcodeVersion(3, 1))
assert(XcodeVersion(3, 2) < XcodeVersion(3, 3))
assert(XcodeVersion(4, 0) > XcodeVersion(3, 3))
}
}
@@ -143,16 +143,10 @@ class XcodeSimulatorExecutor(
private fun downloadRuntimeFor(osName: String) {
val version = Xcode.findCurrent().version
val versionSplit = version.split(".")
check(versionSplit.size >= 2) {
"Unrecognised version of Xcode $version was split to $versionSplit"
}
val major = versionSplit[0].toInt()
val minor = versionSplit[1].toInt()
check(major >= 14) {
check(version.major >= 14) {
"Was unable to get the required runtimes running on Xcode $version. Check the Xcode installation"
}
if (minor >= 1) {
if (version.minor >= 1) {
// Option -downloadPlatform NAME available only since 14.1
hostExecutor.run("/usr/bin/xcrun", "xcodebuild", "-downloadPlatform", osName)
} else {
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.konan.target
import org.jetbrains.kotlin.konan.properties.KonanPropertiesLoader
import org.jetbrains.kotlin.konan.properties.Properties
import org.jetbrains.kotlin.konan.util.InternalServer
import kotlin.math.max
class AppleConfigurablesImpl(
target: KonanTarget,
@@ -58,7 +57,7 @@ class AppleConfigurablesImpl(
val xcode = Xcode.findCurrent()
if (properties.getProperty("ignoreXcodeVersionCheck") != "true") {
properties.getProperty("minimalXcodeVersion")?.let { minimalXcodeVersion ->
properties.getProperty("minimalXcodeVersion")?.let(XcodeVersion::parse)?.let { minimalXcodeVersion ->
val currentXcodeVersion = xcode.version
checkXcodeVersion(minimalXcodeVersion, currentXcodeVersion)
}
@@ -68,22 +67,9 @@ class AppleConfigurablesImpl(
}
}
private fun checkXcodeVersion(minimalVersion: String, currentVersion: String) {
// Xcode versions contain only numbers (even betas).
// But we still split by '-' and whitespaces to take into account versions like 11.2-beta.
val minimalVersionParts = minimalVersion.split("(\\s+|\\.|-)".toRegex()).map { it.toIntOrNull() ?: 0 }
val currentVersionParts = currentVersion.split("(\\s+|\\.|-)".toRegex()).map { it.toIntOrNull() ?: 0 }
val size = max(minimalVersionParts.size, currentVersionParts.size)
for (i in 0 until size) {
val currentPart = currentVersionParts.getOrElse(i) { 0 }
val minimalPart = minimalVersionParts.getOrElse(i) { 0 }
when {
currentPart > minimalPart -> return
currentPart < minimalPart ->
error("Unsupported Xcode version $currentVersion, minimal supported version is $minimalVersion.")
}
private fun checkXcodeVersion(minimalVersion: XcodeVersion, currentVersion: XcodeVersion) {
if (currentVersion < minimalVersion) {
error("Unsupported Xcode version $currentVersion, minimal supported version is $minimalVersion.")
}
}
@@ -21,12 +21,35 @@ import org.jetbrains.kotlin.konan.MissingXcodeException
import org.jetbrains.kotlin.konan.exec.Command
import org.jetbrains.kotlin.konan.file.File
data class XcodeVersion(val major: Int, val minor: Int) : Comparable<XcodeVersion> {
override fun compareTo(other: XcodeVersion): Int {
return when (val majorComparison = major.compareTo(other.major)) {
0 -> minor.compareTo(other.minor)
else -> majorComparison
}
}
override fun toString(): String {
return "$major.$minor"
}
companion object {
fun parse(version: String): XcodeVersion? {
val split = version.split("(\\s+|\\.|-)".toRegex())
return XcodeVersion(
major = split[0].toIntOrNull() ?: return null,
minor = split.getOrNull(1)?.toIntOrNull() ?: return null,
)
}
}
}
interface Xcode {
val toolchain: String
val macosxSdk: String
val iphoneosSdk: String
val iphonesimulatorSdk: String
val version: String
val version: XcodeVersion
val appletvosSdk: String
val appletvsimulatorSdk: String
val watchosSdk: String
@@ -87,12 +110,14 @@ internal class CurrentXcode : Xcode {
override val watchosSdk: String by lazy { getSdkPath("watchos") }
override val watchsimulatorSdk: String by lazy { getSdkPath("watchsimulator") }
internal val xcodebuildVersion: String
internal val xcodebuildVersion: XcodeVersion
get() = xcrun("xcodebuild", "-version")
.removePrefix("Xcode ")
.removePrefix("Xcode ")
.parseXcodeVersion()
internal val bundleVersion: String
internal val bundleVersion: XcodeVersion
get() = bash("""/usr/libexec/PlistBuddy "$(xcode-select -print-path)/../Info.plist" -c "Print :CFBundleShortVersionString"""")
.parseXcodeVersion()
override val version by lazy {
try {
@@ -119,4 +144,8 @@ internal class CurrentXcode : Xcode {
private fun bash(command: String): String = Command("/bin/bash", "-c", command).getOutputLines().joinToString("\n")
private fun getSdkPath(sdk: String) = xcrun("--sdk", sdk, "--show-sdk-path")
private fun String.parseXcodeVersion(): XcodeVersion {
return XcodeVersion.parse(this) ?: throw MissingXcodeException("Couldn't parse Xcode version from '$this'")
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2023 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.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
class XcodeVersionTest {
@Test
fun `xcode version parsed correctly`() {
assertEquals(XcodeVersion(14, 1), XcodeVersion.parse("14.1\nBuild version 14B47b\n"))
assertEquals(XcodeVersion(14, 3), XcodeVersion.parse("14.3\nBuild version 14E222b\n"))
assertEquals(XcodeVersion(1, 0), XcodeVersion.parse("1.0 RC\nBuild version 7B85\n"))
assertEquals(XcodeVersion(1, 15), XcodeVersion.parse("1.15-RC\nBuild version 7B85\n"))
assertEquals(XcodeVersion(14, 1), XcodeVersion.parse("14.1.1.3.2.1.123"))
assertEquals(null, XcodeVersion.parse("14\nBuild version 14B47b\n"))
assertEquals(null, XcodeVersion.parse("13"))
}
@Test
fun `test xcode version comparison`() {
assert(XcodeVersion(3, 2) == XcodeVersion(3, 2))
assert(XcodeVersion(3, 2) > XcodeVersion(2, 14))
assert(XcodeVersion(3, 2) > XcodeVersion(3, 1))
assert(XcodeVersion(3, 2) < XcodeVersion(3, 3))
assert(XcodeVersion(4, 0) > XcodeVersion(3, 3))
}
}