[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 && properties.checkXcodeVersion
&& requiredMajorVersion != null && requiredMajorVersion != null
) { ) {
val currentXcodeVersion = Xcode.findCurrent().version val currentXcodeVersion = Xcode.findCurrent().version.toString()
val currentMajorVersion = currentXcodeVersion.splitToSequence('.').first() val currentMajorVersion = currentXcodeVersion.splitToSequence('.').first()
if (currentMajorVersion != requiredMajorVersion) { if (currentMajorVersion != requiredMajorVersion) {
throw IllegalStateException( throw IllegalStateException(
@@ -25,7 +25,7 @@ import kotlin.io.path.*
import kotlin.test.* import kotlin.test.*
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) @OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("K/N cocoapods tests") @DisplayName("CocoaPods plugin tests")
@NativeGradlePluginTests @NativeGradlePluginTests
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0) @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
@OptIn(EnvironmentalVariablesOverride::class) @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") @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 @GradleTest
fun testConfigurationCacheWorksInAComplexScenario(gradleVersion: GradleVersion) { fun testConfigurationCacheWorksInAComplexScenario(gradleVersion: GradleVersion) {
val buildOptions = defaultBuildOptions.copy( val buildOptions = defaultBuildOptions.copy(
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.gradle.native
import com.intellij.testFramework.TestDataFile import com.intellij.testFramework.TestDataFile
import org.gradle.api.logging.LogLevel import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jdom.input.SAXBuilder import org.jdom.input.SAXBuilder
import org.jetbrains.kotlin.gradle.* 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.testbase.TestVersions.Kotlin.NATIVE_STABLE_RELEASE
import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.gradle.util.runProcess import org.jetbrains.kotlin.gradle.util.runProcess
import org.jetbrains.kotlin.gradle.utils.Xcode import org.jetbrains.kotlin.konan.target.*
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.junit.Assume import org.junit.Assume
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
@@ -364,7 +359,7 @@ class GeneralNativeIT : BaseGradleIT() {
frameworkPaths.forEach { assertFileExists(it) } frameworkPaths.forEach { assertFileExists(it) }
assertTrue(fileInWorkingDir(headerPaths[0]).readText().contains("+ (int32_t)exported")) 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. // Check that by default release frameworks have bitcode embedded.
withNativeCommandLineArguments(":linkMainReleaseFrameworkIos") { arguments -> 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.transformProjectWithPluginsDsl
import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader 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.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget 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.target.presetName
import org.jetbrains.kotlin.konan.util.DependencyDirectories import org.jetbrains.kotlin.konan.util.DependencyDirectories
import org.junit.Assume import org.junit.Assume
@@ -244,7 +244,7 @@ class NativeDownloadAndPlatformLibsIT : BaseGradleIT() {
if (HostManager.hostIsMac) { if (HostManager.hostIsMac) {
// Building platform libs require Xcode 14.1 // 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")) { with(transformNativeTestProjectWithPluginDsl("native-download-maven")) {
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.gradle.native
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.runProcess 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.jetbrains.kotlin.konan.target.KonanTarget
import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
@@ -21,8 +21,7 @@ import kotlin.test.assertEquals
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) @OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
class NativeXcodeSimulatorTestsIT : KGPBaseTest() { class NativeXcodeSimulatorTestsIT : KGPBaseTest() {
private val defaultIosSimulator by lazy { private val defaultIosSimulator by lazy {
val xcode = Xcode ?: error("XCode is expected to be defined") XcodeUtils.getDefaultTestDeviceId(KonanTarget.IOS_SIMULATOR_ARM64) ?: error("No simulator found for iOS ARM64")
xcode.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") @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.FatFrameworkTask
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.registerTask 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.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.utils.mapToFile import org.jetbrains.kotlin.gradle.utils.mapToFile
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -247,7 +248,7 @@ internal abstract class FrameworkCopy : DefaultTask() {
private fun copy(sourceProvider: Provider<File>) { private fun copy(sourceProvider: Provider<File>) {
val source = sourceProvider.get() val source = sourceProvider.get()
val destination = destinationDirectory.asFile.get() val destination = destinationDirectory.getFile()
val destinationFile = File(destination, source.name) val destinationFile = File(destination, source.name)
if (destinationFile.exists()) { 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.PropertiesProvider.Companion.KOTLIN_NATIVE_IGNORE_INCORRECT_DEPENDENCIES
import org.jetbrains.kotlin.gradle.plugin.internal.artifactTypeAttribute import org.jetbrains.kotlin.gradle.plugin.internal.artifactTypeAttribute
import org.jetbrains.kotlin.gradle.plugin.mpp.* 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.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.GradleKpmVariant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyAttributes import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyAttributes
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder 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.configureConventions
import org.jetbrains.kotlin.gradle.testing.internal.kotlinTestRegistry import org.jetbrains.kotlin.gradle.testing.internal.kotlinTestRegistry
import org.jetbrains.kotlin.gradle.testing.testTaskName 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.gradle.utils.newInstance
import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -63,6 +65,7 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
// this afterEvaluate comes from NativeCompilerOptions // this afterEvaluate comes from NativeCompilerOptions
val compilationCompilerOptions = binary.compilation.compilerOptions val compilationCompilerOptions = binary.compilation.compilerOptions
val konanPropertiesBuildService = KonanPropertiesBuildService.registerIfAbsent(project) val konanPropertiesBuildService = KonanPropertiesBuildService.registerIfAbsent(project)
val xcodeVersionTask = XcodeVersionTask.locateOrRegister(project)
val linkTask = registerTask<KotlinNativeLink>( val linkTask = registerTask<KotlinNativeLink>(
binary.linkTaskName, listOf(binary) binary.linkTaskName, listOf(binary)
) { ) {
@@ -75,6 +78,7 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
it.toolOptions.freeCompilerArgs.value(compilationCompilerOptions.options.freeCompilerArgs) it.toolOptions.freeCompilerArgs.value(compilationCompilerOptions.options.freeCompilerArgs)
it.toolOptions.freeCompilerArgs.addAll(providers.provider { PropertiesProvider(project).nativeLinkArgs }) it.toolOptions.freeCompilerArgs.addAll(providers.provider { PropertiesProvider(project).nativeLinkArgs })
it.runViaBuildToolsApi.value(false).disallowChanges() // K/N is not yet supported 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) { override fun configureTestTask(target: KotlinNativeTargetWithSimulatorTests, testTask: KotlinNativeSimulatorTest) {
super.configureTestTask(target, testTask) super.configureTestTask(target, testTask)
if (Xcode != null) { if (isTestTaskEnabled(target)) {
val deviceIdProvider = testTask.project.provider { 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.") ?: error("Xcode does not support simulator tests for ${target.konanTarget.name}. Check that requested SDK is installed.")
} }
testTask.device.convention(deviceIdProvider).finalizeValueOnRead() testTask.device.convention(deviceIdProvider).finalizeValueOnRead()
@@ -13,13 +13,11 @@ import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.HasAttributes import org.gradle.api.attributes.HasAttributes
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.AbstractExecTask import org.gradle.api.tasks.AbstractExecTask
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink 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.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly
@@ -280,12 +278,7 @@ class Framework(
/** /**
* Embed bitcode for the framework or not. See [BitcodeEmbeddingMode]. * Embed bitcode for the framework or not. See [BitcodeEmbeddingMode].
*/ */
val embedBitcodeMode = project.objects.property(org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode::class.java).apply { val embedBitcodeMode = project.objects.property(org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode::class.java)
convention(project.provider {
Xcode?.defaultBitcodeEmbeddingMode(konanTarget, buildType)
?: org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode.DISABLE
})
}
@Deprecated("Use 'embedBitcodeMode' property instead.") @Deprecated("Use 'embedBitcodeMode' property instead.")
var embedBitcode: org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode var embedBitcode: org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
@@ -48,7 +48,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.io.File import java.io.File
internal val ProjectLayout.cocoapodsBuildDirs: CocoapodsBuildDirs internal val ProjectLayout.cocoapodsBuildDirs: CocoapodsBuildDirs
get() = CocoapodsBuildDirs(this) get() = CocoapodsBuildDirs(this)
@@ -515,6 +514,8 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
else -> error("Unknown cocoapods platform: $family") else -> error("Unknown cocoapods platform: $family")
} }
val xcodeVersionTask = XcodeVersionTask.locateOrRegister(project)
val podGenTask = project.registerTask<PodGenTask>(family.toPodGenTaskName) { task -> val podGenTask = project.registerTask<PodGenTask>(family.toPodGenTaskName) { task ->
task.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies" task.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies"
task.podspec.set(podspecTaskProvider.map { it.outputFile }) task.podspec.set(podspecTaskProvider.map { it.outputFile })
@@ -524,7 +525,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
task.family.set(family) task.family.set(family)
task.platformSettings.set(platformSettings) task.platformSettings.set(platformSettings)
task.pods.set(cocoapodsExtension.pods) task.pods.set(cocoapodsExtension.pods)
task.xcodeVersion.set(Xcode?.currentVersion) task.xcodeVersion.set(xcodeVersionTask.version)
} }
project.registerTask<PodInstallSyntheticTask>(family.toPodInstallSyntheticTaskName) { task -> project.registerTask<PodInstallSyntheticTask>(family.toPodInstallSyntheticTaskName) { task ->
@@ -8,6 +8,7 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks package org.jetbrains.kotlin.gradle.targets.native.tasks
import org.gradle.api.file.ProjectLayout import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider 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.CocoapodsExtension.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.plugin.cocoapods.platformLiteral 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.Family
import org.jetbrains.kotlin.konan.target.XcodeVersion
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@@ -52,8 +54,9 @@ abstract class PodGenTask @Inject constructor(projectLayout: ProjectLayout) : Co
@get:Nested @get:Nested
internal abstract val pods: ListProperty<CocoapodsDependency> internal abstract val pods: ListProperty<CocoapodsDependency>
@get:Input @get:Optional
internal abstract val xcodeVersion: Property<XcodeVersion> @get:InputFile
internal abstract val xcodeVersion: RegularFileProperty
@get:OutputFile @get:OutputFile
val podfile: Provider<File> = projectLayout.cocoapodsBuildDirs.synthetic(family).map { it.file("Podfile").asFile } 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 { private fun insertXcode143DeploymentTargetWorkarounds(family: Family): String {
if (xcodeVersion.get() < XcodeVersion(14, 3)) { if (XcodeVersion.parse(xcodeVersion) < XcodeVersion(14, 3)) {
return "" return ""
} }
@@ -183,12 +183,18 @@ constructor(
get() = embedBitcodeMode.get() get() = embedBitcodeMode.get()
@get:Input @get:Input
@get:Optional
val embedBitcodeMode: Provider<BitcodeEmbeddingMode> = val embedBitcodeMode: Provider<BitcodeEmbeddingMode> =
(binary as? Framework)?.embedBitcodeMode ?: project.provider { BitcodeEmbeddingMode.DISABLE } (binary as? Framework)?.embedBitcodeMode ?: objectFactory.property()
@get:Internal @get:Internal
val apiFiles = project.files(project.configurations.getByName(compilation.apiConfigurationName)).filterKlibsPassedToCompiler() 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 externalDependenciesArgs by lazy { ExternalDependenciesBuilder(project, compilation).buildCompilerArgs() }
private val cacheBuilderSettings by lazy { private val cacheBuilderSettings by lazy {
@@ -224,10 +230,10 @@ constructor(
args.generateTestRunner = processTests args.generateTestRunner = processTests
args.mainPackage = entryPoint args.mainPackage = entryPoint
when (embedBitcodeMode.get()) { when (bitcodeEmbeddingMode()) {
BitcodeEmbeddingMode.BITCODE -> args.embedBitcode = true BitcodeEmbeddingMode.BITCODE -> args.embedBitcode = true
BitcodeEmbeddingMode.MARKER -> args.embedBitcodeMarker = true BitcodeEmbeddingMode.MARKER -> args.embedBitcodeMarker = true
null, BitcodeEmbeddingMode.DISABLE -> Unit BitcodeEmbeddingMode.DISABLE -> Unit
} }
args.singleLinkerArguments = (linkerOpts + additionalLinkerOpts).toTypedArray() args.singleLinkerArguments = (linkerOpts + additionalLinkerOpts).toTypedArray()
@@ -391,4 +397,8 @@ constructor(
private inline fun <reified T : Any> lazyConvention(noinline lazyConventionValue: () -> T): Provider<T> { private inline fun <reified T : Any> lazyConvention(noinline lazyConventionValue: () -> T): Provider<T> {
return objectFactory.providerWithLazyConvention(lazyConventionValue) 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.BitcodeEmbeddingMode
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeOutputKind 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.plugin.mpp.enabledOnCurrentHost
import org.jetbrains.kotlin.gradle.tasks.dependsOn import org.jetbrains.kotlin.gradle.tasks.dependsOn
import org.jetbrains.kotlin.gradle.tasks.registerTask 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.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.presetName import org.jetbrains.kotlin.konan.target.presetName
@@ -109,6 +110,7 @@ internal fun KotlinNativeArtifact.registerLinkFrameworkTask(
): TaskProvider<KotlinNativeLinkArtifactTask> { ): TaskProvider<KotlinNativeLinkArtifactTask> {
val kind = NativeOutputKind.FRAMEWORK val kind = NativeOutputKind.FRAMEWORK
val destinationDir = project.buildDir.resolve("$outDirName/${target.visibleName}/${buildType.visibleName}") val destinationDir = project.buildDir.resolve("$outDirName/${target.visibleName}/${buildType.visibleName}")
val xcodeVersionTask = XcodeVersionTask.locateOrRegister(project)
val resultTask = project.registerTask<KotlinNativeLinkArtifactTask>( val resultTask = project.registerTask<KotlinNativeLinkArtifactTask>(
lowerCamelCaseName("assemble", name, buildType.visibleName, kind.taskNameClassifier, target.presetName, taskNameSuffix), lowerCamelCaseName("assemble", name, buildType.visibleName, kind.taskNameClassifier, target.presetName, taskNameSuffix),
listOf(target, kind.compilerOutputKind) listOf(target, kind.compilerOutputKind)
@@ -121,11 +123,10 @@ internal fun KotlinNativeArtifact.registerLinkFrameworkTask(
task.debuggable.set(buildType.debuggable) task.debuggable.set(buildType.debuggable)
task.linkerOptions.set(linkerOptions) task.linkerOptions.set(linkerOptions)
task.binaryOptions.set(binaryOptions) task.binaryOptions.set(binaryOptions)
task.xcodeVersion.set(xcodeVersionTask.version)
task.staticFramework.set(isStatic) task.staticFramework.set(isStatic)
if (embedBitcode != null) { if (embedBitcode != null) {
task.embedBitcode.set(embedBitcode) 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.libraries.setFrom(project.configurations.getByName(librariesConfigurationName))
task.exportLibraries.setFrom(project.configurations.getByName(exportConfigurationName)) 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.DirectoryProperty
import org.gradle.api.file.FileCollection import org.gradle.api.file.FileCollection
import org.gradle.api.file.ProjectLayout import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty 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.plugin.mpp.BitcodeEmbeddingMode
import org.jetbrains.kotlin.gradle.targets.native.tasks.buildKotlinNativeBinaryLinkerArgs import org.jetbrains.kotlin.gradle.targets.native.tasks.buildKotlinNativeBinaryLinkerArgs
import org.jetbrains.kotlin.gradle.tasks.KotlinToolTask 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.gradle.utils.newInstance
import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -73,6 +75,7 @@ abstract class KotlinNativeLinkArtifactTask @Inject constructor(
abstract val staticFramework: Property<Boolean> abstract val staticFramework: Property<Boolean>
@get:Input @get:Input
@get:Optional
abstract val embedBitcode: Property<BitcodeEmbeddingMode> abstract val embedBitcode: Property<BitcodeEmbeddingMode>
@get:Classpath @get:Classpath
@@ -90,6 +93,10 @@ abstract class KotlinNativeLinkArtifactTask @Inject constructor(
@get:Input @get:Input
abstract val binaryOptions: MapProperty<String, String> abstract val binaryOptions: MapProperty<String, String>
@get:Optional
@get:InputFile
abstract val xcodeVersion: RegularFileProperty
private val nativeBinaryOptions = PropertiesProvider(project).nativeBinaryOptions private val nativeBinaryOptions = PropertiesProvider(project).nativeBinaryOptions
@get:Input @get:Input
@@ -163,7 +170,6 @@ abstract class KotlinNativeLinkArtifactTask @Inject constructor(
enableEndorsedLibs.value(false).finalizeValue() enableEndorsedLibs.value(false).finalizeValue()
processTests.convention(false) processTests.convention(false)
staticFramework.convention(false) staticFramework.convention(false)
embedBitcode.convention(BitcodeEmbeddingMode.DISABLE)
destinationDir.convention(debuggable.flatMap { destinationDir.convention(debuggable.flatMap {
val kind = outputKind.visibleName val kind = outputKind.visibleName
val target = konanTarget.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 compilerPlugins = emptyList(),//CompilerPlugins aren't needed here because it's no compilation but linking
processTests = processTests.get(), processTests = processTests.get(),
entryPoint = entryPoint.getOrNull(), entryPoint = entryPoint.getOrNull(),
embedBitcode = embedBitcode.get(), embedBitcode = bitcodeEmbeddingMode(),
linkerOpts = linkerOptions.get(), linkerOpts = linkerOptions.get(),
binaryOptions = allBinaryOptions.get(), binaryOptions = allBinaryOptions.get(),
isStaticFramework = staticFramework.get(), isStaticFramework = staticFramework.get(),
@@ -205,4 +211,8 @@ abstract class KotlinNativeLinkArtifactTask @Inject constructor(
executionContext = KotlinToolRunner.GradleExecutionContext.fromTaskContext(objectFactory, execOperations, logger) executionContext = KotlinToolRunner.GradleExecutionContext.fromTaskContext(objectFactory, execOperations, logger)
).run(buildArgs) ).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) { private fun downloadRuntimeFor(osName: String) {
val version = Xcode.findCurrent().version val version = Xcode.findCurrent().version
val versionSplit = version.split(".") check(version.major >= 14) {
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) {
"Was unable to get the required runtimes running on Xcode $version. Check the Xcode installation" "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 // Option -downloadPlatform NAME available only since 14.1
hostExecutor.run("/usr/bin/xcrun", "xcodebuild", "-downloadPlatform", osName) hostExecutor.run("/usr/bin/xcrun", "xcodebuild", "-downloadPlatform", osName)
} else { } else {
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.konan.target
import org.jetbrains.kotlin.konan.properties.KonanPropertiesLoader import org.jetbrains.kotlin.konan.properties.KonanPropertiesLoader
import org.jetbrains.kotlin.konan.properties.Properties import org.jetbrains.kotlin.konan.properties.Properties
import org.jetbrains.kotlin.konan.util.InternalServer import org.jetbrains.kotlin.konan.util.InternalServer
import kotlin.math.max
class AppleConfigurablesImpl( class AppleConfigurablesImpl(
target: KonanTarget, target: KonanTarget,
@@ -58,7 +57,7 @@ class AppleConfigurablesImpl(
val xcode = Xcode.findCurrent() val xcode = Xcode.findCurrent()
if (properties.getProperty("ignoreXcodeVersionCheck") != "true") { if (properties.getProperty("ignoreXcodeVersionCheck") != "true") {
properties.getProperty("minimalXcodeVersion")?.let { minimalXcodeVersion -> properties.getProperty("minimalXcodeVersion")?.let(XcodeVersion::parse)?.let { minimalXcodeVersion ->
val currentXcodeVersion = xcode.version val currentXcodeVersion = xcode.version
checkXcodeVersion(minimalXcodeVersion, currentXcodeVersion) checkXcodeVersion(minimalXcodeVersion, currentXcodeVersion)
} }
@@ -68,22 +67,9 @@ class AppleConfigurablesImpl(
} }
} }
private fun checkXcodeVersion(minimalVersion: String, currentVersion: String) { private fun checkXcodeVersion(minimalVersion: XcodeVersion, currentVersion: XcodeVersion) {
// Xcode versions contain only numbers (even betas). if (currentVersion < minimalVersion) {
// But we still split by '-' and whitespaces to take into account versions like 11.2-beta. error("Unsupported Xcode version $currentVersion, minimal supported version is $minimalVersion.")
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.")
}
} }
} }
@@ -21,12 +21,35 @@ import org.jetbrains.kotlin.konan.MissingXcodeException
import org.jetbrains.kotlin.konan.exec.Command import org.jetbrains.kotlin.konan.exec.Command
import org.jetbrains.kotlin.konan.file.File 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 { interface Xcode {
val toolchain: String val toolchain: String
val macosxSdk: String val macosxSdk: String
val iphoneosSdk: String val iphoneosSdk: String
val iphonesimulatorSdk: String val iphonesimulatorSdk: String
val version: String val version: XcodeVersion
val appletvosSdk: String val appletvosSdk: String
val appletvsimulatorSdk: String val appletvsimulatorSdk: String
val watchosSdk: String val watchosSdk: String
@@ -87,12 +110,14 @@ internal class CurrentXcode : Xcode {
override val watchosSdk: String by lazy { getSdkPath("watchos") } override val watchosSdk: String by lazy { getSdkPath("watchos") }
override val watchsimulatorSdk: String by lazy { getSdkPath("watchsimulator") } override val watchsimulatorSdk: String by lazy { getSdkPath("watchsimulator") }
internal val xcodebuildVersion: String internal val xcodebuildVersion: XcodeVersion
get() = xcrun("xcodebuild", "-version") 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"""") get() = bash("""/usr/libexec/PlistBuddy "$(xcode-select -print-path)/../Info.plist" -c "Print :CFBundleShortVersionString"""")
.parseXcodeVersion()
override val version by lazy { override val version by lazy {
try { 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 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 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))
}
}