From b6847d0835695cb55e949e781545f927c241e86c Mon Sep 17 00:00:00 2001 From: Artem Daugel-Dauge Date: Sun, 11 Jun 2023 21:00:20 +0200 Subject: [PATCH] [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 --- .../org/jetbrains/kotlin/PlatformInfo.kt | 2 +- .../kotlin/gradle/native/CocoaPodsIT.kt | 12 +-- .../kotlin/gradle/native/GeneralNativeIT.kt | 9 +- .../native/NativeDownloadAndPlatformLibsIT.kt | 6 +- .../native/NativeXcodeSimulatorTestsIT.kt | 5 +- .../plugin/mpp/apple/AppleXcodeTasks.kt | 3 +- .../plugin/mpp/apple/XcodeVersionTask.kt | 60 +++++++++++ .../native/KotlinNativeTargetConfigurator.kt | 10 +- .../gradle/targets/native/NativeBinaries.kt | 9 +- .../native/cocoapods/KotlinCocoapodsPlugin.kt | 5 +- .../native/cocoapods/tasks/PodGenTask.kt | 11 +- .../targets/native/tasks/KotlinNativeLink.kt | 16 ++- .../tasks/artifact/KotlinNativeFramework.kt | 7 +- .../artifact/KotlinNativeLinkArtifactTask.kt | 14 ++- .../jetbrains/kotlin/gradle/utils/Xcode.kt | 100 ------------------ .../kotlin/gradle/utils/XcodeUtils.kt | 87 +++++++++++++++ .../kotlin/gradle/unitTests/XcodeUtilsTest.kt | 40 +++++++ .../kotlin/gradle/utils/XcodeVersionTest.kt | 31 ------ .../executors/XcodeSimulatorExecutor.kt | 10 +- .../jetbrains/kotlin/konan/target/Apple.kt | 22 +--- .../jetbrains/kotlin/konan/target/Xcode.kt | 37 ++++++- .../kotlin/konan/target/XcodeVersionTest.kt | 31 ++++++ 22 files changed, 316 insertions(+), 211 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XcodeVersionTask.kt delete mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Xcode.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeUtils.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XcodeUtilsTest.kt delete mode 100644 libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeVersionTest.kt create mode 100644 native/utils/tests/org/jetbrains/kotlin/konan/target/XcodeVersionTest.kt diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/PlatformInfo.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/PlatformInfo.kt index 600a4d1205a..5848009f811 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/PlatformInfo.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/PlatformInfo.kt @@ -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( diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt index fda14b408b4..2fa85fb20df 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt @@ -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( diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt index 55b12c8541c..7d9b6faf456 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt @@ -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 -> diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeDownloadAndPlatformLibsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeDownloadAndPlatformLibsIT.kt index 432e83c25f4..17714ce7150 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeDownloadAndPlatformLibsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeDownloadAndPlatformLibsIT.kt @@ -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")) { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeXcodeSimulatorTestsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeXcodeSimulatorTestsIT.kt index 75edc672aa2..10a738547e6 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeXcodeSimulatorTestsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeXcodeSimulatorTestsIT.kt @@ -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") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt index a7976f5e0b0..ced101e3103 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt @@ -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) { val source = sourceProvider.get() - val destination = destinationDirectory.asFile.get() + val destination = destinationDirectory.getFile() val destinationFile = File(destination, source.name) if (destinationFile.exists()) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XcodeVersionTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XcodeVersionTask.kt new file mode 100644 index 00000000000..42892802d7d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XcodeVersionTask.kt @@ -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 { + 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 + + @get:OutputFile + abstract val outputFile: RegularFileProperty + + @TaskAction + fun execute() { + outputFile.getFile().writeText(Xcode.findCurrent().version.toString()) + } +} + +internal val Provider.version: Provider + get() = flatMap { it.outputFile } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt index f9749ee82b4..eb5824f54ac 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt @@ -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 : AbstractKotl // this afterEvaluate comes from NativeCompilerOptions val compilationCompilerOptions = binary.compilation.compilerOptions val konanPropertiesBuildService = KonanPropertiesBuildService.registerIfAbsent(project) + val xcodeVersionTask = XcodeVersionTask.locateOrRegister(project) val linkTask = registerTask( binary.linkTaskName, listOf(binary) ) { @@ -75,6 +78,7 @@ open class KotlinNativeTargetConfigurator : 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() diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeBinaries.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeBinaries.kt index 00077d81f2e..6c8edf4201e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeBinaries.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeBinaries.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt index b6e06c46379..3557ed67a3f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt @@ -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 { else -> error("Unknown cocoapods platform: $family") } + val xcodeVersionTask = XcodeVersionTask.locateOrRegister(project) + val podGenTask = project.registerTask(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 { 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(family.toPodInstallSyntheticTaskName) { task -> diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodGenTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodGenTask.kt index 8478825480e..6d8f0804435 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodGenTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodGenTask.kt @@ -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 - @get:Input - internal abstract val xcodeVersion: Property + @get:Optional + @get:InputFile + internal abstract val xcodeVersion: RegularFileProperty @get:OutputFile val podfile: Provider = 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 "" } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeLink.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeLink.kt index 2e35384ced7..cade8957f41 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeLink.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeLink.kt @@ -183,12 +183,18 @@ constructor( get() = embedBitcodeMode.get() @get:Input + @get:Optional val embedBitcodeMode: Provider = - (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 lazyConvention(noinline lazyConventionValue: () -> T): Provider { return objectFactory.providerWithLazyConvention(lazyConventionValue) } + + private fun bitcodeEmbeddingMode(): BitcodeEmbeddingMode { + return XcodeUtils.bitcodeEmbeddingMode(outputKind, embedBitcodeMode.orNull, xcodeVersion, konanTarget, debuggable) + } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFramework.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFramework.kt index d0426fc8cf6..fef539c86d8 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFramework.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFramework.kt @@ -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 { val kind = NativeOutputKind.FRAMEWORK val destinationDir = project.buildDir.resolve("$outDirName/${target.visibleName}/${buildType.visibleName}") + val xcodeVersionTask = XcodeVersionTask.locateOrRegister(project) val resultTask = project.registerTask( 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)) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLinkArtifactTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLinkArtifactTask.kt index 3d486cf8810..ebf0ce5d0c8 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLinkArtifactTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLinkArtifactTask.kt @@ -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 @get:Input + @get:Optional abstract val embedBitcode: Property @get:Classpath @@ -90,6 +93,10 @@ abstract class KotlinNativeLinkArtifactTask @Inject constructor( @get:Input abstract val binaryOptions: MapProperty + @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()) + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Xcode.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Xcode.kt deleted file mode 100644 index 467594f2dfa..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Xcode.kt +++ /dev/null @@ -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 { - 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 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() - 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 - } -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeUtils.kt new file mode 100644 index 00000000000..bf90c1cd763 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeUtils.kt @@ -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, + 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, 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 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() + 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): XcodeVersion { + val version = file.getFile().readText() + return parse(version) ?: error("Couldn't parse Xcode version from '$version'") +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XcodeUtilsTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XcodeUtilsTest.kt new file mode 100644 index 00000000000..6bc526cfc0f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XcodeUtilsTest.kt @@ -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 { + 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)) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeVersionTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeVersionTest.kt deleted file mode 100644 index 9e8d1e1ae4a..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeVersionTest.kt +++ /dev/null @@ -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)) - } -} \ No newline at end of file diff --git a/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/XcodeSimulatorExecutor.kt b/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/XcodeSimulatorExecutor.kt index fbab4981706..7247b339131 100644 --- a/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/XcodeSimulatorExecutor.kt +++ b/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/XcodeSimulatorExecutor.kt @@ -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 { diff --git a/native/utils/src/org/jetbrains/kotlin/konan/target/Apple.kt b/native/utils/src/org/jetbrains/kotlin/konan/target/Apple.kt index b4e883dfe58..18011ad8b56 100644 --- a/native/utils/src/org/jetbrains/kotlin/konan/target/Apple.kt +++ b/native/utils/src/org/jetbrains/kotlin/konan/target/Apple.kt @@ -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.") } } diff --git a/native/utils/src/org/jetbrains/kotlin/konan/target/Xcode.kt b/native/utils/src/org/jetbrains/kotlin/konan/target/Xcode.kt index 81ad2ebc654..1076e1399d6 100644 --- a/native/utils/src/org/jetbrains/kotlin/konan/target/Xcode.kt +++ b/native/utils/src/org/jetbrains/kotlin/konan/target/Xcode.kt @@ -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 { + 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'") + } } diff --git a/native/utils/tests/org/jetbrains/kotlin/konan/target/XcodeVersionTest.kt b/native/utils/tests/org/jetbrains/kotlin/konan/target/XcodeVersionTest.kt new file mode 100644 index 00000000000..8b77564bccc --- /dev/null +++ b/native/utils/tests/org/jetbrains/kotlin/konan/target/XcodeVersionTest.kt @@ -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)) + } +} \ No newline at end of file