Temp
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/XCFrameworkIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/XCFrameworkIT.kt
index c991407..e43dbc9 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/XCFrameworkIT.kt
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/XCFrameworkIT.kt
@@ -27,19 +27,19 @@
assertTasksExecuted(":shared:linkDebugFrameworkIosX64")
assertTasksExecuted(":shared:linkDebugFrameworkIosArm64")
assertTasksExecuted(":shared:linkDebugFrameworkIosSimulatorArm64")
- assertTasksExecuted(":shared:assembleDebugIosSimulatorFatFrameworkForSharedXCFramework")
+ assertTasksExecuted(":shared:assembleDebugIosSimulatorUniversalFrameworkForSharedXCFramework")
assertTasksExecuted(":shared:linkDebugFrameworkWatchosArm32")
assertTasksExecuted(":shared:linkDebugFrameworkWatchosArm64")
assertTasksExecuted(":shared:linkDebugFrameworkWatchosDeviceArm64")
assertTasksExecuted(":shared:linkDebugFrameworkWatchosSimulatorArm64")
assertTasksExecuted(":shared:linkDebugFrameworkWatchosX64")
- assertTasksExecuted(":shared:assembleDebugWatchosFatFrameworkForSharedXCFramework")
- assertTasksExecuted(":shared:assembleDebugWatchosSimulatorFatFrameworkForSharedXCFramework")
+ assertTasksExecuted(":shared:assembleDebugWatchosUniversalFrameworkForSharedXCFramework")
+ assertTasksExecuted(":shared:assembleDebugWatchosSimulatorUniversalFrameworkForSharedXCFramework")
assertTasksExecuted(":shared:assembleSharedDebugXCFramework")
assertDirectoryInProjectExists("shared/build/XCFrameworks/debug/shared.xcframework")
assertDirectoryInProjectExists("shared/build/XCFrameworks/debug/shared.xcframework/ios-arm64_x86_64-simulator/dSYMs/shared.framework.dSYM")
- assertDirectoryInProjectExists("shared/build/sharedXCFrameworkTemp/fatframework/debug/watchos/shared.framework")
- assertDirectoryInProjectExists("shared/build/sharedXCFrameworkTemp/fatframework/debug/watchos/shared.framework.dSYM")
+ assertDirectoryInProjectExists("shared/build/XCFrameworkTemp/shared/universalFramework/debug/watchos/shared.framework")
+ assertDirectoryInProjectExists("shared/build/XCFrameworkTemp/shared/universalFramework/debug/watchos/shared.framework.dSYM")
}
build("assembleSharedDebugXCFramework") {
@@ -49,7 +49,7 @@
assertTasksUpToDate(":shared:linkDebugFrameworkWatchosArm64")
assertTasksUpToDate(":shared:linkDebugFrameworkWatchosDeviceArm64")
assertTasksUpToDate(":shared:linkDebugFrameworkWatchosX64")
- assertTasksUpToDate(":shared:assembleDebugWatchosFatFrameworkForSharedXCFramework")
+ assertTasksUpToDate(":shared:assembleDebugWatchosUniversalFrameworkForSharedXCFramework")
assertTasksUpToDate(":shared:assembleSharedDebugXCFramework")
}
}
@@ -70,7 +70,7 @@
assertTasksExecuted(":shared:assembleOtherDebugXCFramework")
assertDirectoryInProjectExists("shared/build/XCFrameworks/debug/other.xcframework")
assertDirectoryInProjectExists("shared/build/XCFrameworks/debug/other.xcframework/ios-arm64/dSYMs/shared.framework.dSYM")
- assertHasDiagnostic(KotlinToolingDiagnostics.XCFrameworkDifferentInnerFrameworksName)
+ assertHasDiagnostic(KotlinToolingDiagnostics.XCFrameworkNameIsDifferentFromInnerFrameworksName)
}
}
}
@@ -115,7 +115,7 @@
.replaceFirst("baseName = \"shared\"", "baseName = \"awesome\"")
buildAndFail("tasks") {
- assertOutputContains("All inner frameworks in XCFramework 'shared' should have same names. But there are two with 'awesome' and 'shared' names")
+ assertOutputContains("All inner frameworks in XCFramework 'shared' (RELEASE) should have same names. But there are two with 'awesome' and 'shared' names")
}
}
}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinProjectExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinProjectExtension.kt
index ae189fb..468ef8c 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinProjectExtension.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinProjectExtension.kt
@@ -36,6 +36,7 @@
import kotlin.reflect.KClass
private const val KOTLIN_PROJECT_EXTENSION_NAME = "kotlin"
+private const val COCOAPODS_PROJECT_EXTENSION_NAME = "cocoapods"
internal fun Project.createKotlinExtension(extensionClass: KClass<out KotlinTopLevelExtension>): KotlinTopLevelExtension {
return extensions.create(KOTLIN_PROJECT_EXTENSION_NAME, extensionClass.java, this)
@@ -65,6 +66,9 @@
internal val Project.multiplatformExtension: KotlinMultiplatformExtension
get() = extensions.getByName(KOTLIN_PROJECT_EXTENSION_NAME).castIsolatedKotlinPluginClassLoaderAware()
+internal val Project.cocoapodsExtension: KotlinMultiplatformExtension
+ get() = extensions.getByName(COCOAPODS_PROJECT_EXTENSION_NAME).castIsolatedKotlinPluginClassLoaderAware()
+
abstract class KotlinTopLevelExtension(internal val project: Project) : KotlinTopLevelExtensionConfig {
override lateinit var coreLibrariesVersion: String
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt
index ec09edf..cd5a48c 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt
@@ -20,6 +20,7 @@
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_IGNORE_DISABLED_TARGETS
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_SUPPRESS_EXPERIMENTAL_ARTIFACTS_DSL_WARNING
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnostic.Severity.*
+import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.plugin.sources.android.multiplatformAndroidSourceSetLayoutV1
import org.jetbrains.kotlin.gradle.plugin.sources.android.multiplatformAndroidSourceSetLayoutV2
import java.io.File
@@ -601,12 +602,41 @@
)
}
- object XCFrameworkDifferentInnerFrameworksName : ToolingDiagnosticFactory(WARNING) {
+ object XCFrameworkNameIsDifferentFromInnerFrameworksName : ToolingDiagnosticFactory(WARNING) {
operator fun invoke(xcFramework: String, innerFrameworks: String) = build(
"Name of XCFramework '$xcFramework' differs from inner frameworks name '$innerFrameworks'! Framework renaming is not supported yet"
)
}
+ object FrameworksInXCFrameworkHaveDifferentNames : ToolingDiagnosticFactory(ERROR) {
+ operator fun invoke(xcFramework: String, buildType: NativeBuildType, frameworkPaths: List<String>) = build(
+ errorText(xcFramework, buildType, frameworkPaths)
+ )
+
+ fun errorText(xcFramework: String, buildType: NativeBuildType, frameworkPaths: List<String>): String {
+ return "All inner frameworks in XCFramework '$xcFramework' (${buildType.name}) should have same names!" +
+ frameworkPaths.map { "\n${it}" }.joinToString()
+ }
+ }
+
+ object XCFrameworkHasNoFrameworks : ToolingDiagnosticFactory(WARNING) {
+ operator fun invoke(xcFramework: String, buildType: NativeBuildType) = build(
+ "XCFramework '${xcFramework}' (${buildType.name}) is empty and it's task is going to be skipped"
+ )
+ }
+
+ object MissingBuildTypeInXCFrameworkConfiguration : ToolingDiagnosticFactory(ERROR) {
+ operator fun invoke(xcFrameworkConfigation: String, buildType: NativeBuildType) = build(
+ "XCFramework configuration '$xcFrameworkConfigation' is missing build type '${buildType.name}'"
+ )
+ }
+
+ object AddingFrameworkToXCFrameworkAfterDSLFinalized : ToolingDiagnosticFactory(ERROR) {
+ operator fun invoke(xcFramework: String, buildType: NativeBuildType, framework: String) = build(
+ "Adding framework '$framework' to XCFramework '${xcFramework}' (${buildType.name}) after DSL is finalized isn't allowed"
+ )
+ }
+
object UnknownAppleFrameworkBuildType : ToolingDiagnosticFactory(WARNING) {
operator fun invoke(envConfiguration: String) = build(
"""
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkConfig.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkConfig.kt
new file mode 100644
index 0000000..edf5b7e
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkConfig.kt
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2010-2021 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.Project
+import org.gradle.api.Task
+import org.gradle.api.file.Directory
+import org.gradle.api.plugins.BasePlugin
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.*
+import org.jetbrains.kotlin.gradle.plugin.*
+import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle
+import org.jetbrains.kotlin.gradle.plugin.KotlinProjectSetupAction
+import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
+import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
+import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnostic
+import org.jetbrains.kotlin.gradle.plugin.launchInStage
+import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
+import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
+import org.jetbrains.kotlin.gradle.tasks.*
+import org.jetbrains.kotlin.gradle.utils.*
+import org.jetbrains.kotlin.gradle.utils.dir
+import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
+import org.jetbrains.kotlin.konan.target.KonanTarget
+import java.io.File
+
+
+internal val SetUpXCFrameworksTasksAction = KotlinProjectSetupAction {
+ launchInStage(KotlinPluginLifecycle.Stage.AfterFinaliseDsl) {
+ xcframeworkConfigs.forEach {
+ it.configureDependentTasksAndFinalizeAllFrameworks()
+ }
+ }
+ launchInStage(KotlinPluginLifecycle.Stage.ReadyForExecution) {
+ xcframeworkConfigs.forEach {
+ it.validateAllXCFrameworks()
+ }
+ }
+}
+
+private val Project.xcframeworkConfigs: MutableList<XCFrameworkConfig>
+ get() {
+ return project.extraProperties.getOrPut<MutableList<XCFrameworkConfig>>(XCFrameworkConfig.XCFRAMEWORK_CONFIGS_EXTENSION) {
+ mutableListOf<XCFrameworkConfig>()
+ }
+ }
+
+
+/**
+ * XCFrameworkConfig is configuration-time entity that is responsible for:
+ * 1. Registering .xcframework producing tasks (XCFrameworkTask) for each build type
+ * 2. Validating that XCFrameworkTask has sensible inputs
+ * 3. Wiring up the dependencies of XCFrameworkTask
+ *
+ * .xcframework bundle can only be created with lipo'ed frameworks per platform (e.g. iphonesimulator). For example x86_64-sim and arm64-sim
+ * binaries must be lipo'ed before they may be bundled in the xcframework. If needed XCFrameworkConfig creates the universal framework
+ * (FatFrameworkTask) tasks and wire it into the XCFrameworkTask.
+ */
+class XCFrameworkConfig {
+ internal data class FrameworkDescriptorWithProducingTask(
+ val descriptor: FrameworkDescriptor,
+ val descriptorProducingTask: TaskProvider<*>,
+ )
+
+ internal data class XCFrameworkDescriptor(
+ val xcframeworkTask: TaskProvider<XCFrameworkTask>,
+ val frameworks: MutableList<FrameworkDescriptorWithProducingTask> = mutableListOf(),
+ )
+
+ internal data class ConfiguredXCFrameworkTask(
+ val outputDir: Provider<File>,
+ val buildType: NativeBuildType,
+ )
+
+ internal data class XCFrameworkTaskConfiguration(
+ val taskName: (xcframeworkConfigurationName: String) -> String,
+ val group: String = BasePlugin.BUILD_GROUP,
+ val description: String,
+ val baseName: Provider<String>,
+ val customOutputDir: File? = null,
+ val aggregateTask: TaskProvider<*>,
+ val dependOnTasks: (ConfiguredXCFrameworkTask) -> (List<TaskProvider<*>>) = { _ -> emptyList() },
+ )
+
+ internal val project: Project
+
+ internal val descriptorForBuildType: Map<NativeBuildType, XCFrameworkDescriptor>
+
+ internal val xcframeworkIntermediatesName: String
+ internal val xcframeworkConfigurationName: String
+
+ internal val universalFrameworkTaskNamePrefix: List<String>
+
+ internal constructor(
+ project: Project,
+ buildTypes: Set<NativeBuildType>,
+
+ xcframeworkConfigurationName: String,
+ xcframeworkIntermediatesName: String = defaultXCFrameworkIntermediatesName,
+ xcframeworkTaskConfigurationProvider: (NativeBuildType) -> XCFrameworkTaskConfiguration,
+
+ universalFrameworkTaskNamePrefix: List<String> = emptyList(),
+ ) {
+ require(xcframeworkConfigurationName.isNotBlank())
+ this.project = project
+ this.xcframeworkConfigurationName = xcframeworkConfigurationName
+ this.xcframeworkIntermediatesName = xcframeworkIntermediatesName
+ this.universalFrameworkTaskNamePrefix = universalFrameworkTaskNamePrefix
+
+ val descriptorForBuildType = mutableMapOf<NativeBuildType, XCFrameworkDescriptor>()
+ buildTypes.forEach { buildType ->
+ val xcframeworkTaskConfiguration = xcframeworkTaskConfigurationProvider(buildType)
+ val xcframeworkTask = project.registerXCFrameworkTask(
+ xcframeworkConfigurationName,
+ xcframeworkTaskConfiguration,
+ buildType
+ )
+ xcframeworkTaskConfiguration.aggregateTask.dependsOn(xcframeworkTask)
+ descriptorForBuildType[buildType] = XCFrameworkDescriptor(xcframeworkTask)
+
+ xcframeworkTaskConfiguration.dependOnTasks(
+ ConfiguredXCFrameworkTask(
+ xcframeworkTask.map { it.outputDir },
+ buildType,
+ )
+ ).forEach {
+ xcframeworkTask.dependsOn(it)
+ }
+ }
+
+ this.descriptorForBuildType = descriptorForBuildType
+ project.xcframeworkConfigs.add(this)
+ }
+
+ constructor(project: Project, xcFrameworkName: String, buildTypes: Set<NativeBuildType>) : this(
+ project = project,
+ buildTypes = buildTypes,
+ xcframeworkConfigurationName = xcFrameworkName,
+ xcframeworkTaskConfigurationProvider = { buildType ->
+ XCFrameworkTaskConfiguration(
+ taskName = { xcFrameworkName -> defaultXCFrameworkTaskName(buildType, xcFrameworkName) },
+ baseName = project.provider { xcFrameworkName },
+ description = defaultXCFrameworkTaskDescription(buildType),
+ aggregateTask = project.parentAssembleXCFrameworkTask(xcFrameworkName),
+ )
+ },
+ )
+ constructor(project: Project) : this(project, project.name)
+ constructor(project: Project, xcFrameworkName: String) : this(project, xcFrameworkName, NativeBuildType.values().toSet())
+
+ /**
+ * Adds the specified frameworks in this XCFramework.
+ */
+ fun add(framework: Framework) = add(
+ frameworkDescriptor = FrameworkDescriptor(framework),
+ frameworkDescriptorProducingTask = framework.linkTaskProvider,
+ buildType = framework.buildType
+ )
+
+ internal fun add(
+ frameworkDescriptor: FrameworkDescriptor,
+ frameworkDescriptorProducingTask: TaskProvider<*>,
+ buildType: NativeBuildType,
+ ) {
+ if (isFrameworkListFinalized) {
+ project.reportDiagnostic(
+ KotlinToolingDiagnostics.AddingFrameworkToXCFrameworkAfterDSLFinalized(
+ xcFramework = xcframeworkConfigurationName,
+ buildType = buildType,
+ framework = frameworkDescriptor.name,
+ )
+ )
+ }
+ val descriptor = descriptorForBuildType[buildType]
+ if (descriptor == null) {
+ project.reportDiagnostic(
+ KotlinToolingDiagnostics.MissingBuildTypeInXCFrameworkConfiguration(
+ xcFrameworkConfigation = xcframeworkConfigurationName,
+ buildType = buildType,
+ )
+ )
+ return
+ }
+ descriptor.frameworks.add(
+ FrameworkDescriptorWithProducingTask(
+ frameworkDescriptor,
+ frameworkDescriptorProducingTask,
+ )
+ )
+ }
+
+ private var isFrameworkListFinalized = false
+ internal fun configureDependentTasksAndFinalizeAllFrameworks() {
+ descriptorForBuildType.forEach {
+ configureDependentTasksAndFinalizeFrameworks(
+ it.value,
+ it.key
+ )
+ }
+ }
+
+ private fun configureDependentTasksAndFinalizeFrameworks(
+ xcFrameworkDescriptor: XCFrameworkDescriptor,
+ buildType: NativeBuildType,
+ ) {
+ isFrameworkListFinalized = true
+ xcFrameworkDescriptor.frameworks
+ .groupBy { universalGroupFromTarget[it.descriptor.target] ?: error("Target ${it.descriptor.target.name} is missing a universal framework group") }
+ .forEach {
+ val universalGroupName = it.key
+ val frameworkDescriptors = it.value
+
+ if (frameworkDescriptors.size > 1) {
+ val universalFrameworkTaskProvider = project.registerUniversalFrameworkTask(
+ buildType = buildType,
+ universalGroupName = universalGroupName,
+ universalFrameworkName = frameworkDescriptors[0].descriptor.name,
+ frameworkDescriptors = frameworkDescriptors,
+ )
+ xcFrameworkDescriptor.xcframeworkTask.configure {
+ it.xcframeworkSlices.add(
+ universalFrameworkTaskProvider.map { universalFrameworkTask ->
+ XCFrameworkTask.XCFrameworkSlice(
+ universalFrameworkTask.fatFramework,
+ frameworkDescriptors[0].descriptor.isStatic,
+ )
+ }
+ )
+ }
+ } else {
+ val framework = frameworkDescriptors.single()
+ val descriptor = framework.descriptor
+ xcFrameworkDescriptor.xcframeworkTask.dependsOn(framework.descriptorProducingTask)
+ xcFrameworkDescriptor.xcframeworkTask.configure {
+ it.xcframeworkSlices.add(
+ project.provider {
+ XCFrameworkTask.XCFrameworkSlice(
+ descriptor.file,
+ descriptor.isStatic,
+ )
+ }
+ )
+ }
+ }
+ }
+ }
+
+ internal fun validateAllXCFrameworks() {
+ descriptorForBuildType.forEach {
+ validate(it.value)
+ }
+ }
+
+ private fun validate(xcframeworkDescriptor: XCFrameworkDescriptor) {
+ xcframeworkDescriptor.xcframeworkTask.configure {
+ with(it) {
+ val xcframeworkBaseName = baseName.get()
+ val frameworkDescriptors = xcframeworkDescriptor.frameworks.map { it.descriptor }
+ if (frameworkDescriptors.isEmpty()) {
+ reportDiagnostic(
+ KotlinToolingDiagnostics.XCFrameworkHasNoFrameworks(
+ xcFramework = xcframeworkBaseName,
+ buildType = buildType,
+ )
+ )
+ return@with
+ }
+
+ val expectedFrameworkName = frameworkDescriptors[0].name
+ if (frameworkDescriptors.any { it.name != expectedFrameworkName }) {
+ reportDiagnostic(
+ KotlinToolingDiagnostics.FrameworksInXCFrameworkHaveDifferentNames(
+ xcFramework = xcframeworkBaseName,
+ buildType = buildType,
+ frameworkPaths = frameworkDescriptors.map { it.file.path },
+ )
+ )
+ frameworksConfigurationError.set(
+ KotlinToolingDiagnostics.FrameworksInXCFrameworkHaveDifferentNames.errorText(
+ xcFramework = xcframeworkBaseName,
+ buildType = buildType,
+ frameworkPaths = frameworkDescriptors.map { it.file.path },
+ )
+ )
+ }
+ val xcframeworkName = xcFrameworkName.get()
+ if (expectedFrameworkName != xcframeworkName) {
+ reportDiagnostic(
+ KotlinToolingDiagnostics.XCFrameworkNameIsDifferentFromInnerFrameworksName(
+ xcFramework = xcframeworkBaseName,
+ innerFrameworks = expectedFrameworkName,
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun Project.registerXCFrameworkTask(
+ xcFrameworkTaskName: String,
+ configuration: XCFrameworkTaskConfiguration,
+ buildType: NativeBuildType,
+ ): TaskProvider<XCFrameworkTask> {
+ val taskName = configuration.taskName(xcFrameworkTaskName)
+ return registerTask(taskName) { task ->
+ task.buildType = buildType
+ task.baseName = configuration.baseName
+ task.group = configuration.group
+ task.description = configuration.description
+ configuration.customOutputDir?.let { task.outputDir = it }
+ }
+ }
+
+ private fun Project.registerUniversalFrameworkTask(
+ buildType: NativeBuildType,
+ universalGroupName: String,
+ universalFrameworkName: String,
+ frameworkDescriptors: List<FrameworkDescriptorWithProducingTask>,
+ ): TaskProvider<FatFrameworkTask> {
+ return registerTask(
+ universalFrameworkTaskName(
+ universalFrameworkTaskNamePrefix,
+ buildType,
+ universalGroupName,
+ xcframeworkConfigurationName,
+ )
+ ) { universalFrameworkTask ->
+ universalFrameworkTask.destinationDirProperty.set(
+ universalFrameworkDir(
+ project,
+ xcframeworkIntermediatesName,
+ xcframeworkConfigurationName,
+ buildType,
+ universalGroupName
+ )
+ )
+ universalFrameworkTask.baseName = universalFrameworkName
+ universalFrameworkTask.fromFrameworkDescriptors(frameworkDescriptors.map { it.descriptor })
+ universalFrameworkTask.dependsOn(frameworkDescriptors.map { it.descriptorProducingTask })
+ }
+ }
+
+ internal companion object {
+ const val XCFRAMEWORK_CONFIGS_EXTENSION = "${PropertiesProvider.KOTLIN_INTERNAL_NAMESPACE}.xcframeworkConfigs"
+
+ internal fun defaultXCFrameworkTaskName(
+ buildType: NativeBuildType,
+ xcFrameworkName: String,
+ ) = lowerCamelCaseName(
+ "assemble",
+ xcFrameworkName,
+ buildType.getName(),
+ "XCFramework"
+ )
+
+ internal fun defaultXCFrameworkTaskDescription(
+ buildType: NativeBuildType
+ ): String = "Assemble .xcframework bundle for build type ${buildType.name}"
+
+ private fun universalFrameworkTaskName(
+ prefix: List<String>,
+ buildType: NativeBuildType,
+ universalGroupName: String,
+ xcFrameworkName: String,
+ ) = lowerCamelCaseName(
+ *prefix.toTypedArray(),
+ "assemble",
+ buildType.getName(),
+ universalGroupName,
+ "UniversalFrameworkFor",
+ xcFrameworkName,
+ "XCFramework"
+ )
+
+ private val defaultXCFrameworkIntermediatesName = "XCFrameworkTemp"
+ private fun universalFrameworkDir(
+ project: Project,
+ xcFrameworkIntermediatesName: String,
+ xcFrameworkName: String,
+ buildType: NativeBuildType,
+ universalGroupName: String,
+ ): Provider<Directory> = project.layout.buildDirectory
+ .dir(xcFrameworkIntermediatesName)
+ .dir(xcFrameworkName.asValidFrameworkName())
+ .dir("universalFramework")
+ .dir(buildType.getName())
+ .dir(universalGroupName)
+
+ private val macosGroup = "macos"
+ private val iOSGroup = "ios"
+ private val iOSSimulatorGroup = "iosSimulator"
+ private val watchOSGroup = "watchos"
+ private val watchOSSimulatorGroup = "watchosSimulator"
+ private val tvOSGroup = "tvos"
+ private val tvOSSimulatorGroup = "tvosSimulator"
+ private val universalGroupFromTarget = mapOf(
+ KonanTarget.MACOS_X64 to macosGroup,
+ KonanTarget.MACOS_ARM64 to macosGroup,
+ KonanTarget.IOS_ARM64 to iOSGroup,
+ KonanTarget.IOS_X64 to iOSSimulatorGroup,
+ KonanTarget.IOS_SIMULATOR_ARM64 to iOSSimulatorGroup,
+ KonanTarget.WATCHOS_ARM32 to watchOSGroup,
+ KonanTarget.WATCHOS_ARM64 to watchOSGroup,
+ KonanTarget.WATCHOS_DEVICE_ARM64 to watchOSGroup,
+ KonanTarget.WATCHOS_X64 to watchOSSimulatorGroup,
+ KonanTarget.WATCHOS_SIMULATOR_ARM64 to watchOSSimulatorGroup,
+ KonanTarget.TVOS_ARM64 to tvOSGroup,
+ KonanTarget.TVOS_X64 to tvOSSimulatorGroup,
+ KonanTarget.TVOS_SIMULATOR_ARM64 to tvOSSimulatorGroup,
+ )
+ }
+}
+
+fun Project.XCFramework(xcFrameworkName: String = name) = XCFrameworkConfig(this, xcFrameworkName)
+
+private fun Project.eraseIfDefault(xcFrameworkName: String) =
+ if (name == xcFrameworkName) "" else xcFrameworkName
+
+private fun Project.parentAssembleXCFrameworkTask(xcFrameworkName: String): TaskProvider<Task> =
+ locateOrRegisterTask(lowerCamelCaseName("assemble", eraseIfDefault(xcFrameworkName), "XCFramework")) {
+ it.group = "build"
+ it.description = "Assemble all types of registered '$xcFrameworkName' XCFramework"
+ }
+
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkTask.kt
index 64e9812..c4783f7 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkTask.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkTask.kt
@@ -1,206 +1,82 @@
/*
- * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Copyright 2010-2024 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.Task
-import org.gradle.api.file.Directory
-import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.ProjectLayout
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import org.gradle.work.DisableCachingByDefault
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
-import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.UsesKotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
-import org.jetbrains.kotlin.gradle.tasks.*
+import org.jetbrains.kotlin.gradle.tasks.FrameworkDescriptor
import org.jetbrains.kotlin.gradle.utils.existsCompat
-import org.jetbrains.kotlin.gradle.utils.getFile
-import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
+import org.jetbrains.kotlin.gradle.utils.onlyIfCompat
+import org.jetbrains.kotlin.gradle.utils.property
import org.jetbrains.kotlin.konan.target.HostManager
-import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
+import java.io.Serializable
import javax.inject.Inject
-@Suppress("unused") // used through .values() call
-internal enum class AppleTarget(
- val targetName: String,
- val targets: List<KonanTarget>
-) {
- MACOS_DEVICE("macos", listOf(KonanTarget.MACOS_X64, KonanTarget.MACOS_ARM64)),
- IPHONE_DEVICE("ios", listOf(KonanTarget.IOS_ARM64)),
- IPHONE_SIMULATOR("iosSimulator", listOf(KonanTarget.IOS_X64, KonanTarget.IOS_SIMULATOR_ARM64)),
- WATCHOS_DEVICE("watchos", listOf(KonanTarget.WATCHOS_ARM32, KonanTarget.WATCHOS_ARM64, KonanTarget.WATCHOS_DEVICE_ARM64)),
- WATCHOS_SIMULATOR("watchosSimulator", listOf(KonanTarget.WATCHOS_X64, KonanTarget.WATCHOS_SIMULATOR_ARM64)),
- TVOS_DEVICE("tvos", listOf(KonanTarget.TVOS_ARM64)),
- TVOS_SIMULATOR("tvosSimulator", listOf(KonanTarget.TVOS_X64, KonanTarget.TVOS_SIMULATOR_ARM64))
-}
-
-internal class XCFrameworkTaskHolder(
- val buildType: NativeBuildType,
- val task: TaskProvider<XCFrameworkTask>,
- val fatTasks: Map<AppleTarget, TaskProvider<FatFrameworkTask>>
-) {
- companion object {
- fun create(project: Project, xcFrameworkName: String, buildType: NativeBuildType): XCFrameworkTaskHolder {
- require(xcFrameworkName.isNotBlank())
- val task = project.registerAssembleXCFrameworkTask(xcFrameworkName, buildType)
-
- val fatTasks = AppleTarget.values().associate { fatTarget ->
- val fatTask = project.registerAssembleFatForXCFrameworkTask(xcFrameworkName, buildType, fatTarget)
- task.dependsOn(fatTask)
- fatTarget to fatTask
- }
-
- return XCFrameworkTaskHolder(buildType, task, fatTasks)
- }
- }
-}
-
-class XCFrameworkConfig {
- private val taskHolders: List<XCFrameworkTaskHolder>
-
- constructor(project: Project, xcFrameworkName: String, buildTypes: Set<NativeBuildType>) {
- val parentTask = project.parentAssembleXCFrameworkTask(xcFrameworkName)
- taskHolders = buildTypes.map { buildType ->
- XCFrameworkTaskHolder.create(project, xcFrameworkName, buildType).also {
- parentTask.dependsOn(it.task)
- }
- }
- }
-
- constructor(project: Project) : this(project, project.name)
- constructor(project: Project, xcFrameworkName: String) : this(project, xcFrameworkName, NativeBuildType.values().toSet())
-
- /**
- * Adds the specified frameworks in this XCFramework.
- */
- fun add(framework: Framework) {
- taskHolders.forEach { holder ->
- if (framework.buildType == holder.buildType) {
- holder.task.configure { task -> task.from(framework) }
- AppleTarget.values()
- .firstOrNull { it.targets.contains(framework.konanTarget) }
- ?.also { appleTarget ->
- holder.fatTasks[appleTarget]?.configure { fatTask ->
- fatTask.baseName = framework.baseName //all frameworks should have same names
- fatTask.from(framework)
- }
- }
- }
- }
- }
-}
-
-fun Project.XCFramework(xcFrameworkName: String = name) = XCFrameworkConfig(this, xcFrameworkName)
-
-private fun Project.eraseIfDefault(xcFrameworkName: String) =
- if (name == xcFrameworkName) "" else xcFrameworkName
-
-private fun Project.parentAssembleXCFrameworkTask(xcFrameworkName: String): TaskProvider<Task> =
- locateOrRegisterTask(lowerCamelCaseName("assemble", eraseIfDefault(xcFrameworkName), "XCFramework")) {
- it.group = "build"
- it.description = "Assemble all types of registered '$xcFrameworkName' XCFramework"
- }
-
-private fun Project.registerAssembleXCFrameworkTask(
- xcFrameworkName: String,
- buildType: NativeBuildType
-): TaskProvider<XCFrameworkTask> {
- val taskName = lowerCamelCaseName(
- "assemble",
- xcFrameworkName,
- buildType.getName(),
- "XCFramework"
- )
- return registerTask(taskName) { task ->
- task.baseName = provider { xcFrameworkName }
- task.buildType = buildType
- }
-}
-
-//see: https://developer.apple.com/forums/thread/666335
-private fun Project.registerAssembleFatForXCFrameworkTask(
- xcFrameworkName: String,
- buildType: NativeBuildType,
- appleTarget: AppleTarget
-): TaskProvider<FatFrameworkTask> {
- val taskName = lowerCamelCaseName(
- "assemble",
- buildType.getName(),
- appleTarget.targetName,
- "FatFrameworkFor",
- xcFrameworkName,
- "XCFramework"
- )
-
- return registerTask(taskName) { task ->
- task.destinationDirProperty.set(XCFrameworkTask.fatFrameworkDir(project, xcFrameworkName, buildType, appleTarget))
- task.onlyIf {
- task.frameworks.size > 1
- }
- }
-}
-
@DisableCachingByDefault
abstract class XCFrameworkTask
@Inject
internal constructor(
private val execOperations: ExecOperations,
- private val projectLayout: ProjectLayout,
+ projectLayout: ProjectLayout,
) : DefaultTask(), UsesKotlinToolingDiagnostics {
init {
- onlyIf { HostManager.hostIsMac }
+ onlyIfCompat("XCFramework may only be produced on macOS") { HostManager.hostIsMac }
}
- private val projectBuildDir: File get() = projectLayout.buildDirectory.asFile.get()
-
/**
* A base name for the XCFramework.
*/
@Input
var baseName: Provider<String> = project.provider { project.name }
- @get:Internal
- internal val xcFrameworkName: Provider<String>
- get() = baseName.map { it.asValidFrameworkName() }
-
/**
* A build type of the XCFramework.
*/
@Input
var buildType: NativeBuildType = NativeBuildType.RELEASE
- private val groupedFrameworkFiles: MutableMap<AppleTarget, MutableList<FrameworkDescriptor>> = mutableMapOf()
+ /**
+ * A parent directory for the XCFramework.
+ */
+ @Internal
+ var outputDir: File = projectLayout.buildDirectory.asFile.get().resolve("XCFrameworks")
@get:IgnoreEmptyDirectories
@get:InputFiles
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:SkipWhenEmpty
val inputFrameworkFiles: Collection<File>
- get() = groupedFrameworkFiles.values.flatten().map { it.file }.filter {
- it.existsCompat()
- }
+ get() = (xcframeworkSlices.get().map { it.file }).filter { it.existsCompat() }
- /**
- * A parent directory for the XCFramework.
- */
- @get:Internal // We take it into account as an output in the outputXCFrameworkFile property.
- var outputDir: File = projectBuildDir.resolve("XCFrameworks")
+ @get:Internal
+ internal val xcFrameworkName: Provider<String>
+ get() = baseName.map { it.asValidFrameworkName() }
- /**
- * A parent directory for the fat frameworks.
- */
- @get:Internal // We take it into account as an input in the buildType and baseName properties.
- protected val fatFrameworksDir: File
- get() = fatFrameworkDir(projectLayout.buildDirectory, xcFrameworkName.get(), buildType).getFile()
+ internal data class XCFrameworkSlice(
+ val file: File,
+ val isStatic: Boolean,
+ ) : Serializable
+
+ @get:Input
+ internal abstract val xcframeworkSlices: ListProperty<XCFrameworkSlice>
+
+ @get:Optional
+ @get:Input
+ internal abstract val frameworksConfigurationError: Property<String>
@get:OutputDirectory
protected val outputXCFrameworkFile: File
@@ -210,75 +86,55 @@
* Adds the specified frameworks in this XCFramework.
*/
fun from(vararg frameworks: Framework) {
- frameworks.forEach { framework ->
- require(framework.konanTarget.family.isAppleFamily) {
- "XCFramework supports Apple frameworks only"
- }
- dependsOn(framework.linkTask)
- }
+ dependsOn(frameworks.map { it.linkTaskProvider })
fromFrameworkDescriptors(frameworks.map { FrameworkDescriptor(it) })
}
fun fromFrameworkDescriptors(vararg frameworks: FrameworkDescriptor) = fromFrameworkDescriptors(frameworks.toList())
fun fromFrameworkDescriptors(frameworks: Iterable<FrameworkDescriptor>) {
- val frameworkName = groupedFrameworkFiles.values.flatten().firstOrNull()?.name
-
frameworks.forEach { framework ->
- if (frameworkName != null && framework.name != frameworkName) {
- error(
- "All inner frameworks in XCFramework '${baseName.get()}' should have same names. " +
- "But there are two with '$frameworkName' and '${framework.name}' names"
- )
+ require(framework.target.family.isAppleFamily) {
+ "XCFramework supports Apple frameworks only"
}
- val group = AppleTarget.values().first { it.targets.contains(framework.target) }
- groupedFrameworkFiles.getOrPut(group, { mutableListOf() }).add(framework)
+ xcframeworkSlices.add(
+ project.provider {
+ XCFrameworkSlice(
+ framework.file,
+ framework.isStatic,
+ )
+ }
+ )
}
}
@TaskAction
- fun assemble() {
- val frameworks = groupedFrameworkFiles.values.flatten()
- val xcfName = xcFrameworkName.get()
- if (frameworks.isNotEmpty()) {
- val rawXcfName = baseName.get()
- val name = frameworks.first().name
- if (frameworks.any { it.name != name }) {
- error("All inner frameworks in XCFramework '$rawXcfName' should have same names!" +
- frameworks.joinToString("\n") { it.file.path })
- }
- if (name != xcfName) {
- toolingDiagnosticsCollector.get().report(this, KotlinToolingDiagnostics.XCFrameworkDifferentInnerFrameworksName(
- xcFramework = rawXcfName,
- innerFrameworks = name,
- ))
- }
+ fun createXCFramework() {
+ val configurationError = frameworksConfigurationError.orNull
+ if (configurationError != null) {
+ error(configurationError)
}
- val frameworksForXCFramework = groupedFrameworkFiles.entries.mapNotNull { (group, files) ->
- when {
- files.size == 1 -> files.first()
- files.size > 1 -> FrameworkDescriptor(
- fatFrameworksDir.resolve(group.targetName).resolve("$xcfName.framework"),
- files.all { it.isStatic },
- group.targets.first() //will be not used
- )
- else -> null
- }
+ execOperations.exec {
+ it.commandLine(
+ prepareOutputAndCreateXcodebuildCommand()
+ )
}
- createXCFramework(frameworksForXCFramework, outputXCFrameworkFile)
}
- private fun createXCFramework(frameworkFiles: List<FrameworkDescriptor>, output: File) {
+ internal fun prepareOutputAndCreateXcodebuildCommand(
+ fileExists: (File) -> (Boolean) = { it.exists() },
+ ): List<String> {
+ val output = outputXCFrameworkFile
if (output.exists()) output.deleteRecursively()
val cmdArgs = mutableListOf("xcodebuild", "-create-xcframework")
- frameworkFiles.forEach { frameworkFile ->
+ xcframeworkSlices.get().forEach { slice ->
cmdArgs.add("-framework")
- cmdArgs.add(frameworkFile.file.path)
- if (!frameworkFile.isStatic) {
- val dsymFile = File(frameworkFile.file.path + ".dSYM")
- if (dsymFile.exists()) {
+ cmdArgs.add(slice.file.path)
+ if (!slice.isStatic) {
+ val dsymFile = File(slice.file.path + ".dSYM")
+ if (fileExists(dsymFile)) {
cmdArgs.add("-debug-symbols")
cmdArgs.add(dsymFile.path)
}
@@ -286,29 +142,6 @@
}
cmdArgs.add("-output")
cmdArgs.add(output.path)
- execOperations.exec { it.commandLine(cmdArgs) }
+ return cmdArgs
}
-
- internal companion object {
- fun fatFrameworkDir(
- project: Project,
- xcFrameworkName: String,
- buildType: NativeBuildType,
- appleTarget: AppleTarget? = null
- ): Provider<Directory> = fatFrameworkDir(project.layout.buildDirectory, xcFrameworkName, buildType, appleTarget)
-
- fun fatFrameworkDir(
- buildDir: DirectoryProperty,
- xcFrameworkName: String,
- buildType: NativeBuildType,
- appleTarget: AppleTarget? = null
- ): Provider<Directory> = buildDir.map {
- it.dir(xcFrameworkName.asValidFrameworkName() + "XCFrameworkTemp")
- .dir("fatframework")
- .dir(buildType.getName())
- .dirIfNotNull(appleTarget?.targetName)
- }
-
- private fun Directory.dirIfNotNull(relative: String?): Directory = if (relative == null) this else this.dir(relative)
- }
-}
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt
index 01b6247..ab66844 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt
@@ -17,6 +17,7 @@
import org.jetbrains.kotlin.gradle.plugin.ide.IdeResolveDependenciesTaskSetupAction
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AddBuildListenerForXCodeSetupAction
+import org.jetbrains.kotlin.gradle.plugin.mpp.apple.SetUpXCFrameworksTasksAction
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XcodeVersionSetupAction
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.*
import org.jetbrains.kotlin.gradle.plugin.mpp.internal.DeprecatedMppGradlePropertiesMigrationSetupAction
@@ -86,6 +87,7 @@
register(project, RegisterMultiplatformResourcesPublicationExtensionAction)
register(project, SetUpMultiplatformJvmResourcesPublicationAction)
register(project, SetUpMultiplatformAndroidAssetsAndResourcesPublicationAction)
+ register(project, SetUpXCFrameworksTasksAction)
}
}
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 adea195..cacbd2c 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
@@ -31,7 +31,6 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.*
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AppleSdk
-import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AppleTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.FrameworkCopy.Companion.dsymFile
import org.jetbrains.kotlin.gradle.utils.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.CocoapodsPluginDiagnostics
@@ -167,10 +166,22 @@
private fun KotlinMultiplatformExtension.targetsForPlatform(requestedPlatform: KonanTarget) =
supportedTargets().matching { it.konanTarget == requestedPlatform }
+ private val Project.defaultFrameworkName: String get() = name.asValidFrameworkName()
+
+ private fun KotlinMultiplatformExtension.forAllPodFrameworks(action: Framework.() -> (Unit)) {
+ supportedTargets().all { target ->
+ target.binaries.withType(Framework::class.java).matching {
+ it.name.startsWith(POD_FRAMEWORK_PREFIX)
+ }.all {
+ it.action()
+ }
+ }
+ }
+
private fun createDefaultFrameworks(kotlinExtension: KotlinMultiplatformExtension) {
kotlinExtension.supportedTargets().all { target ->
target.binaries.framework(POD_FRAMEWORK_PREFIX) {
- baseName = project.name.asValidFrameworkName()
+ baseName = project.defaultFrameworkName
}
}
}
@@ -214,7 +225,7 @@
fatTargets.forEach { (_, targets) ->
targets.singleOrNull()?.let {
val framework = it.binaries.getFramework(POD_FRAMEWORK_PREFIX, requestedBuildType)
- task.baseName = framework.baseName //all frameworks should have same names
+ task.baseNameProvider.set(project.provider { framework.baseName })
task.from(framework)
}
}
@@ -701,12 +712,6 @@
): TaskProvider<XCFrameworkTask> =
with(project) {
registerTask(lowerCamelCaseName(POD_FRAMEWORK_PREFIX, "publish", buildType.getName(), "XCFramework")) { task ->
- multiplatformExtension.supportedTargets().all { target ->
- target.binaries.matching { it.buildType == buildType && it.name.startsWith(POD_FRAMEWORK_PREFIX) }
- .withType(Framework::class.java) { framework ->
- task.from(framework)
- }
- }
task.outputDir = cocoapodsExtension.publishDir
task.buildType = buildType
task.baseName = cocoapodsExtension.podFrameworkName
@@ -718,19 +723,17 @@
private fun registerPodspecPublishTask(
project: Project,
cocoapodsExtension: CocoapodsExtension,
- xcFrameworkTask: TaskProvider<XCFrameworkTask>,
- buildType: NativeBuildType
+ configuredXCFrameworkTask: XCFrameworkConfig.ConfiguredXCFrameworkTask,
): TaskProvider<PodspecTask> {
+ val buildType = configuredXCFrameworkTask.buildType
val task = project.registerTask<PodspecTask>(lowerCamelCaseName(POD_FRAMEWORK_PREFIX, "spec", buildType.getName())) { task ->
task.description = "Generates podspec for ${buildType.getName().capitalizeAsciiOnly()} XCFramework publishing"
- task.outputDir.set(xcFrameworkTask.map { it.outputDir.resolve(it.buildType.getName()) })
+ task.outputDir.set(configuredXCFrameworkTask.outputDir.map { it.resolve(buildType.getName()) })
task.needPodspec.set(true)
task.publishing.set(true)
task.source.set(project.provider { cocoapodsExtension.source })
-
task.configure(cocoapodsExtension, project)
}
- xcFrameworkTask.dependsOn(task)
return task
}
@@ -754,52 +757,36 @@
hasPodfile.set(project.hasPodfileOwnOrParent())
}
- private fun registerPodPublishFatFrameworkTasks(
- project: Project,
- xcFrameworkTask: TaskProvider<XCFrameworkTask>,
- buildType: NativeBuildType
- ) =
- with(project) {
- multiplatformExtension.supportedTargets().all { target ->
- target.binaries.matching { it.buildType == buildType && it.name.startsWith(POD_FRAMEWORK_PREFIX) }
- .withType(Framework::class.java) { framework ->
-
- val appleTarget = AppleTarget.values().firstOrNull { it.targets.contains(target.konanTarget) } ?: return@withType
- val fatFrameworkTaskName =
- lowerCamelCaseName(POD_FRAMEWORK_PREFIX, buildType.getName(), appleTarget.targetName, "FatFramework")
- val fatFrameworkTask = locateOrRegisterTask<FatFrameworkTask>(fatFrameworkTaskName) { fatTask ->
- fatTask.baseName = framework.baseName
- fatTask.destinationDirProperty.set(
- XCFrameworkTask.fatFrameworkDir(this, fatTask.fatFrameworkName, buildType, appleTarget)
- )
- fatTask.onlyIf {
- fatTask.frameworks.size > 1
- }
- }
-
- fatFrameworkTask.configure {
- it.from(framework)
- }
-
- xcFrameworkTask.dependsOn(fatFrameworkTask)
- }
- }
- }
-
private fun registerPodPublishTasks(project: Project, cocoapodsExtension: CocoapodsExtension) {
-
- val xcFrameworkTasks = NativeBuildType.values().map { buildType ->
- val xcFrameworkTask = registerPodXCFrameworkTask(project, cocoapodsExtension, buildType)
- registerPodPublishFatFrameworkTasks(project, xcFrameworkTask, buildType)
- registerPodspecPublishTask(project, cocoapodsExtension, xcFrameworkTask, buildType)
- xcFrameworkTask
- }
-
- project.registerTask("podPublishXCFramework", DefaultTask::class.java) { task ->
+ val parentTask = project.registerTask("podPublishXCFramework", DefaultTask::class.java) { task ->
task.description = "Produces Release and Debug XCFrameworks with respective podspecs"
- task.dependsOn(xcFrameworkTasks)
task.group = TASK_GROUP
}
+ val xcframework = XCFrameworkConfig(
+ project = project,
+ buildTypes = NativeBuildType.values().toSet(),
+
+ xcframeworkConfigurationName = project.defaultFrameworkName,
+ xcframeworkIntermediatesName = XCFRAMEWORK_TEMPORARY_ROOT,
+
+ xcframeworkTaskConfigurationProvider = { buildType ->
+ XCFrameworkConfig.XCFrameworkTaskConfiguration(
+ taskName = { _ -> lowerCamelCaseName(POD_FRAMEWORK_PREFIX, "publish", buildType.getName(), "XCFramework") },
+ baseName = cocoapodsExtension.podFrameworkName,
+ group = TASK_GROUP,
+ description = "Produces ${buildType.getName().capitalizeAsciiOnly()} XCFramework for all requested targets",
+ customOutputDir = cocoapodsExtension.publishDir,
+ aggregateTask = parentTask,
+ dependOnTasks = { configuredXCFrameworkTask ->
+ listOf(registerPodspecPublishTask(project, cocoapodsExtension, configuredXCFrameworkTask))
+ }
+ )
+ },
+ universalFrameworkTaskNamePrefix = listOf(POD_FRAMEWORK_PREFIX),
+ )
+// project.multiplatformExtension.forAllPodFrameworks {
+// xcframework.add(this)
+// }
}
private fun checkLinkOnlyNotUsedWithStaticFramework(project: Project, cocoapodsExtension: CocoapodsExtension) {
@@ -903,6 +890,7 @@
const val POD_BUILD_TASK_NAME = "podBuild"
const val POD_IMPORT_TASK_NAME = "podImport"
const val ARTIFACTS_PODSPEC_EXTENSION_NAME = "withPodspec"
+ const val XCFRAMEWORK_TEMPORARY_ROOT = "podXCFrameworkTemp"
// We don't move these properties in PropertiesProvider because
// they are not intended to be overridden in local.properties.
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/FatFrameworkTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/FatFrameworkTask.kt
index 0a0a9a8..f430e7f 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/FatFrameworkTask.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/FatFrameworkTask.kt
@@ -9,6 +9,7 @@
import org.gradle.api.DefaultTask
import org.gradle.api.file.*
import org.gradle.api.model.ObjectFactory
+import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
@@ -19,6 +20,7 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeOutputKind
import org.jetbrains.kotlin.gradle.utils.appendLine
import org.jetbrains.kotlin.gradle.utils.getFile
+import org.jetbrains.kotlin.gradle.utils.property
import org.jetbrains.kotlin.konan.target.Architecture
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.HostManager
@@ -143,8 +145,13 @@
/**
* A base name for the fat framework.
*/
- @Input
- var baseName: String = project.name
+ @get:Input
+ var baseName: String
+ get() = baseNameProvider.get()
+ set(value) = baseNameProvider.set(value)
+
+ @get:Internal
+ internal val baseNameProvider: Property<String> = objectFactory.property(project.name)
@get:Internal
internal val defaultDestinationDir: Provider<Directory> = projectLayout.buildDirectory.dir("fat-framework")
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt
index c34f80e..6a8d330 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt
@@ -13,11 +13,11 @@
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.AppleTarget
-import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkTaskHolder
+import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkConfig
import org.jetbrains.kotlin.gradle.tasks.FrameworkDescriptor
import org.jetbrains.kotlin.gradle.tasks.dependsOn
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.konan.target.KonanTarget
import javax.inject.Inject
@@ -88,13 +88,24 @@
}
project.tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).dependsOn(parentTask)
- modes.forEach { buildType ->
- val holder = XCFrameworkTaskHolder.create(project, artifactName, buildType).also {
- parentTask.dependsOn(it.task)
- }
+ val config = XCFrameworkConfig(
+ project = project,
+ buildTypes = modes,
+ xcframeworkConfigurationName = artifactName,
+ xcframeworkTaskConfigurationProvider = { buildType ->
+ XCFrameworkConfig.XCFrameworkTaskConfiguration(
+ taskName = { xcFrameworkName -> XCFrameworkConfig.defaultXCFrameworkTaskName(buildType, xcFrameworkName) },
+ baseName = project.provider { artifactName },
+ description = XCFrameworkConfig.defaultXCFrameworkTaskDescription(buildType),
+ customOutputDir = project.layout.buildDirectory.dir(outDir).getFile(),
+ aggregateTask = parentTask,
+ )
+ },
+ )
+ modes.forEach { buildType ->
val nameSuffix = "ForXCF"
- val frameworkDescriptors: List<FrameworkDescriptor> = targets.map { target ->
+ targets.forEach { target ->
val librariesConfigurationName = project.registerLibsDependencies(target, artifactName + nameSuffix, modules)
val exportConfigurationName = project.registerExportDependencies(target, artifactName + nameSuffix, modules)
val targetTask = registerLinkFrameworkTask(
@@ -108,21 +119,9 @@
outDirName = "${artifactName}XCFrameworkTemp",
taskNameSuffix = nameSuffix
)
- holder.task.dependsOn(targetTask)
val frameworkFileProvider = targetTask.flatMap { it.outputFile }
val descriptor = FrameworkDescriptor(frameworkFileProvider.get(), isStatic, target)
-
- val group = AppleTarget.values().firstOrNull { it.targets.contains(target) }
- holder.fatTasks[group]?.configure { fatTask ->
- fatTask.baseName = artifactName
- fatTask.fromFrameworkDescriptors(listOf(descriptor))
- fatTask.dependsOn(targetTask)
- }
- descriptor
- }
- holder.task.configure {
- it.fromFrameworkDescriptors(frameworkDescriptors)
- it.outputDir = project.layout.buildDirectory.dir(outDir).get().asFile
+ config.add(descriptor, targetTask, buildType)
}
}
}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CocoapodsUnitTests.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CocoapodsUnitTests.kt
index 2872d07..5004936 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CocoapodsUnitTests.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CocoapodsUnitTests.kt
@@ -58,4 +58,16 @@
assertContainsDiagnostic(CocoapodsPluginDiagnostics.DeprecatedPropertiesUsed(listOf(FRAMEWORK_PATHS_PROPERTY, HEADER_PATHS_PROPERTY)))
}
}
+
+ @Test
+ fun test() {
+ buildProjectWithMPP {
+ applyCocoapodsPlugin()
+ kotlin {
+ iosSimulatorArm64()
+ iosX64()
+ }
+ }.evaluate().tasks.getByName("fatFramework")
+ }
+
}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XCFrameworkCocoaPodsTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XCFrameworkCocoaPodsTest.kt
new file mode 100644
index 0000000..6b4c44d
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XCFrameworkCocoaPodsTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2010-2024 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.unitTests
+
+import org.gradle.api.Project
+import org.gradle.api.tasks.TaskProvider
+import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkTask
+import org.jetbrains.kotlin.gradle.tasks.PodspecTask
+import org.jetbrains.kotlin.gradle.util.applyCocoapodsPlugin
+import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
+import org.jetbrains.kotlin.gradle.util.kotlin
+import org.jetbrains.kotlin.konan.target.HostManager
+import org.junit.Assume
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class XCFrameworkCocoaPodsTest {
+
+ @BeforeTest
+ fun runOnMacOSOnly() {
+ Assume.assumeTrue(HostManager.hostIsMac)
+ }
+
+ @Test
+ fun `expected xcframework input slices - in cocoapods generated xcframework`() {
+ val project = buildProjectWithMPP {
+ kotlin {
+ listOf(
+ iosSimulatorArm64(),
+ iosX64(),
+ iosArm64(),
+ )
+ }
+ applyCocoapodsPlugin()
+ }.evaluate()
+
+ val xcframeworkTask = project.tasks.getByName("podPublishReleaseXCFramework") as XCFrameworkTask
+
+ assertEquals(
+ listOf(
+ project.buildFile("bin/iosArm64/podReleaseFramework/test.framework"),
+ project.buildFile("podXCFrameworkTemp/test/universalFramework/release/iosSimulator/test.framework"),
+ ),
+ xcframeworkTask.xcframeworkSlices.get().map { it.file }
+ )
+ }
+
+ @Test
+ fun `parent task dependency - is created - for cocoapods xcframework`() {
+ val project = buildProjectWithMPP {
+ kotlin {
+ iosSimulatorArm64()
+ }
+ applyCocoapodsPlugin()
+ }.evaluate()
+
+ val parentTask = project.tasks.getByName("podPublishXCFramework")
+
+ assertEquals(
+ hashSetOf(
+ project.tasks.named("podPublishDebugXCFramework"),
+ project.tasks.named("podPublishReleaseXCFramework"),
+ ),
+ parentTask.dependsOn,
+ )
+ }
+
+ @Test
+ fun `cocoapods podspec publication - depends on xcframework task`() {
+ val project = buildProjectWithMPP {
+ kotlin {
+ iosSimulatorArm64()
+ }
+ applyCocoapodsPlugin()
+ }.evaluate()
+
+ assertEquals(
+ listOf(project.tasks.named("podSpecRelease")),
+ project.tasks.getByName("podPublishReleaseXCFramework").dependsOn.filter {
+ it is TaskProvider<*> && it.get() is PodspecTask
+ }
+ )
+ }
+
+ private fun Project.buildFile(path: String) = layout.buildDirectory.file(path).get().asFile
+
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XCFrameworkKotlinArtifactsTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XCFrameworkKotlinArtifactsTest.kt
new file mode 100644
index 0000000..ba0b74b
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XCFrameworkKotlinArtifactsTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2010-2024 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.unitTests
+
+import org.gradle.api.Project
+import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
+import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkTask
+import org.jetbrains.kotlin.gradle.targets.native.tasks.artifact.KotlinNativeLinkArtifactTask
+import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
+import org.jetbrains.kotlin.gradle.util.*
+import org.jetbrains.kotlin.konan.target.HostManager
+import org.jetbrains.kotlin.konan.target.KonanTarget
+import org.junit.Assume
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class XCFrameworkKotlinArtifactsTest {
+
+ @BeforeTest
+ fun runOnMacOSOnly() {
+ Assume.assumeTrue(HostManager.hostIsMac)
+ }
+
+ @Test
+ fun `xcframework task graph - with universal and regular frameworks`() {
+ val project = buildProjectWithMPP {
+ kotlin { linuxArm64() }
+ kotlinArtifacts {
+ Native.XCFramework { xcframeworkConfig ->
+ xcframeworkConfig.targets(
+ KonanTarget.IOS_ARM64,
+ KonanTarget.IOS_SIMULATOR_ARM64,
+ KonanTarget.IOS_X64,
+ )
+ }
+ }
+ }.evaluate()
+
+ val xcframeworkTask = assertIsInstance<XCFrameworkTask>(project.tasks.getByName("assembleTestReleaseXCFramework"))
+
+ assertEquals(
+ project.buildFile("out/xcframework"),
+ xcframeworkTask.outputDir,
+ )
+
+ val universalFrameworkTask = assertIsInstance<FatFrameworkTask>(
+ project.tasks.getByName("assembleReleaseIosSimulatorUniversalFrameworkForTestXCFramework")
+ )
+ val thinFrameworkIosArm64Task = assertIsInstance<KotlinNativeLinkArtifactTask>(
+ project.tasks.getByName("assembleTestReleaseFrameworkIosArm64ForXCF")
+ )
+
+ assertEquals(
+ hashSetOf(
+ universalFrameworkTask,
+ thinFrameworkIosArm64Task,
+ ),
+ xcframeworkTask.taskDependencies.getDependencies(null),
+ )
+
+ assertEquals(
+ listOf(
+ project.buildFile("testXCFrameworkTemp/ios_arm64/release/test.framework"),
+ project.buildFile("XCFrameworkTemp/test/universalFramework/release/iosSimulator/test.framework"),
+ ),
+ xcframeworkTask.xcframeworkSlices.get().map { it.file },
+ )
+ assertEquals(
+ project.buildFile("testXCFrameworkTemp/ios_arm64/release/test.framework"),
+ thinFrameworkIosArm64Task.outputFile.get()
+ )
+
+ val thinFrameworkIosSimulatorArm64Task = assertIsInstance<KotlinNativeLinkArtifactTask>(
+ project.tasks.getByName("assembleTestReleaseFrameworkIosSimulatorArm64ForXCF")
+ )
+ val thinFrameworkIosX64Task = assertIsInstance<KotlinNativeLinkArtifactTask>(
+ project.tasks.getByName("assembleTestReleaseFrameworkIosX64ForXCF")
+ )
+ assertEquals(
+ hashSetOf(
+ thinFrameworkIosSimulatorArm64Task,
+ thinFrameworkIosX64Task,
+ ),
+ universalFrameworkTask.taskDependencies.getDependencies(null)
+ )
+ assertEquals(
+ listOf(
+ project.buildFile("testXCFrameworkTemp/ios_simulator_arm64/release/test.framework"),
+ project.buildFile("testXCFrameworkTemp/ios_x64/release/test.framework"),
+ ),
+ universalFrameworkTask.frameworks.map { it.file },
+ )
+ }
+
+ @Test
+ fun `parent task dependency - is created`() {
+ val project = buildProjectWithMPP {
+ kotlin { linuxArm64() }
+ kotlinArtifacts {
+ Native.XCFramework { xcframeworkConfig ->
+ xcframeworkConfig.targets(
+ KonanTarget.IOS_ARM64,
+ KonanTarget.IOS_SIMULATOR_ARM64,
+ KonanTarget.IOS_X64,
+ )
+ }
+ }
+ }.evaluate()
+
+ val parentTask = project.tasks.getByName("assembleTestXCFramework")
+
+ assertEquals(
+ hashSetOf(
+ project.tasks.named("assembleTestDebugXCFramework"),
+ project.tasks.named("assembleTestReleaseXCFramework"),
+ ),
+ parentTask.dependsOn,
+ )
+ }
+
+ private fun Project.buildFile(path: String) = layout.buildDirectory.file(path).get().asFile
+
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XCFrameworkTaskTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XCFrameworkTaskTest.kt
new file mode 100644
index 0000000..dab975d
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/XCFrameworkTaskTest.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2010-2024 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.unitTests
+
+import org.gradle.api.Project
+import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
+import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle
+import org.jetbrains.kotlin.gradle.plugin.await
+import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
+import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
+import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
+import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkTask
+import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
+import org.jetbrains.kotlin.gradle.util.*
+import org.jetbrains.kotlin.konan.target.HostManager
+import org.junit.Assume
+import kotlin.test.*
+
+class XCFrameworkTaskTest {
+
+ @BeforeTest
+ fun runOnMacOSOnly() {
+ Assume.assumeTrue(HostManager.hostIsMac)
+ }
+
+ @Test
+ fun `expected xcframework input slices - when exporting a single framework`() {
+ val project = buildProjectWithMPP {
+ val xcframework = XCFramework()
+ kotlin {
+ iosSimulatorArm64().binaries.framework {
+ xcframework.add(this)
+ }
+ }
+ }.evaluate()
+
+ val xcframeworkTask = assertIsInstance<XCFrameworkTask>(project.tasks.getByName("assembleTestReleaseXCFramework"))
+
+ xcframeworkTask.assertDependsOn(
+ project.tasks.getByName("linkReleaseFrameworkIosSimulatorArm64")
+ )
+ assertEquals(
+ listOf(
+ project.buildFile("bin/iosSimulatorArm64/releaseFramework/test.framework")
+ ),
+ xcframeworkTask.xcframeworkSlices.get().map { it.file }
+ )
+ }
+
+ @Test
+ fun `expected xcframework input slices - when exporting multiple frameworks`() {
+ val project = buildProjectWithMPP {
+ val xcframework = XCFramework()
+ kotlin {
+ listOf(
+ iosSimulatorArm64(),
+ iosArm64(),
+ ).forEach {
+ it.binaries.framework {
+ xcframework.add(this)
+ }
+ }
+ }
+ }.evaluate()
+
+ val xcframeworkTask = assertIsInstance<XCFrameworkTask>(project.tasks.getByName("assembleTestReleaseXCFramework"))
+
+ assertEquals(
+ listOf(
+ project.buildFile("bin/iosSimulatorArm64/releaseFramework/test.framework"),
+ project.buildFile("bin/iosArm64/releaseFramework/test.framework"),
+ ),
+ xcframeworkTask.xcframeworkSlices.get().map { it.file }
+ )
+ }
+
+ @Test
+ fun `expected xcframework input slices - contain only universal framework - when multiple frameworks must be merged`() {
+ val project = buildProjectWithMPP {
+ val xcframework = XCFramework()
+ kotlin {
+ listOf(
+ iosSimulatorArm64(),
+ iosX64(),
+ iosArm64(),
+ ).forEach {
+ it.binaries.framework {
+ xcframework.add(this)
+ }
+ }
+ }
+ }.evaluate()
+
+ val xcframeworkTask = project.tasks.getByName("assembleTestReleaseXCFramework") as XCFrameworkTask
+
+ assertEquals(
+ listOf(
+ project.buildFile("XCFrameworkTemp/test/universalFramework/release/iosSimulator/test.framework"),
+ project.buildFile("bin/iosArm64/releaseFramework/test.framework"),
+ ),
+ xcframeworkTask.xcframeworkSlices.get().map { it.file }
+ )
+ }
+
+ @Test
+ fun `universal framework dependency - with multiple universal frameworks`() {
+ val project = buildProjectWithMPP {
+ val xcframework = XCFramework()
+ kotlin {
+ listOf(
+ iosSimulatorArm64(),
+ iosX64(),
+ watchosArm64(),
+ watchosDeviceArm64(),
+ watchosArm32(),
+ ).forEach {
+ it.binaries.framework {
+ xcframework.add(this)
+ }
+ }
+ }
+ }.evaluate()
+
+ val xcframeworkTask = assertIsInstance<XCFrameworkTask>(project.tasks.getByName("assembleTestReleaseXCFramework"))
+
+ assertEquals(
+ listOf(
+ project.buildFile("XCFrameworkTemp/test/universalFramework/release/iosSimulator/test.framework"),
+ project.buildFile("XCFrameworkTemp/test/universalFramework/release/watchos/test.framework"),
+ ),
+ xcframeworkTask.xcframeworkSlices.get().map { it.file }
+ )
+
+ val universalFrameworkTasks = xcframeworkTask.taskDependencies.getDependencies(null).map {
+ assertIsInstance<FatFrameworkTask>(it)
+ }
+ assertEquals(2, universalFrameworkTasks.size)
+
+ assertEquals(
+ hashSetOf(
+ project.multiplatformExtension.iosSimulatorArm64().binaries.getFramework(NativeBuildType.RELEASE).linkTask,
+ project.multiplatformExtension.iosX64().binaries.getFramework(NativeBuildType.RELEASE).linkTask,
+ ),
+ universalFrameworkTasks[0].taskDependencies.getDependencies(null)
+ )
+ assertEquals(
+ listOf(
+ project.buildFile("bin/iosSimulatorArm64/releaseFramework/test.framework"),
+ project.buildFile("bin/iosX64/releaseFramework/test.framework"),
+ ),
+ universalFrameworkTasks[0].frameworks.map { it.file },
+ )
+ assertEquals(
+ project.buildFile("XCFrameworkTemp/test/universalFramework/release/iosSimulator/test.framework"),
+ universalFrameworkTasks[0].fatFramework,
+ )
+ assertEquals(
+ listOf(
+ project.buildFile("bin/watchosArm64/releaseFramework/test.framework"),
+ project.buildFile("bin/watchosDeviceArm64/releaseFramework/test.framework"),
+ project.buildFile("bin/watchosArm32/releaseFramework/test.framework"),
+ ),
+ universalFrameworkTasks[1].frameworks.map { it.file },
+ )
+ assertEquals(
+ project.buildFile("XCFrameworkTemp/test/universalFramework/release/watchos/test.framework"),
+ universalFrameworkTasks[1].fatFramework,
+ )
+ }
+
+ @Test
+ fun `parent task dependency - is created`() {
+ val project = buildProjectWithMPP {
+ val xcframework = XCFramework()
+ kotlin {
+ iosSimulatorArm64().binaries.framework {
+ xcframework.add(this)
+ }
+ }
+ }.evaluate()
+
+ val parentTask = project.tasks.getByName("assembleXCFramework")
+
+ assertEquals(
+ hashSetOf(
+ project.tasks.named("assembleTestDebugXCFramework"),
+ project.tasks.named("assembleTestReleaseXCFramework"),
+ ),
+ parentTask.dependsOn,
+ )
+ }
+
+ @Test
+ fun `framework names are different diagnostic - when indepent frameworks have different names`() {
+ val project = buildProjectWithMPP {
+ val xcframework = XCFramework()
+ kotlin {
+ iosSimulatorArm64().binaries.framework {
+ baseName = "Test"
+ xcframework.add(this)
+ }
+ iosArm64().binaries.framework {
+ baseName = "test"
+ xcframework.add(this)
+ }
+ }
+ }.evaluate()
+
+ // Force XCFramework task to configure
+ project.tasks.getByName("assembleTestReleaseXCFramework")
+
+ project.assertContainsDiagnostic(KotlinToolingDiagnostics.FrameworksInXCFrameworkHaveDifferentNames)
+ }
+
+ @Test
+ fun `xcodebuild call - with universal and regular framework - points to corrent frameworks and dSYMs`() {
+ val project = buildProjectWithMPP {
+ val xcframework = XCFramework()
+ kotlin {
+ listOf(
+ iosSimulatorArm64(),
+ iosX64(),
+ iosArm64(),
+ ).forEach {
+ it.binaries.framework {
+ baseName = "bar"
+ xcframework.add(this)
+ }
+ }
+ }
+ }.evaluate()
+
+ assertEquals(
+ listOf(
+ "xcodebuild", "-create-xcframework",
+ "-framework", project.buildFile("XCFrameworkTemp/test/universalFramework/release/iosSimulator/bar.framework").path,
+ "-debug-symbols", project.buildFile("XCFrameworkTemp/test/universalFramework/release/iosSimulator/bar.framework.dSYM").path,
+ "-framework", project.buildFile("bin/iosArm64/releaseFramework/bar.framework").path,
+ "-debug-symbols", project.buildFile("bin/iosArm64/releaseFramework/bar.framework.dSYM").path,
+ "-output", project.buildFile("XCFrameworks/release/test.xcframework").path,
+ ),
+ assertIsInstance<XCFrameworkTask>(
+ project.tasks.getByName("assembleTestReleaseXCFramework")
+ ).prepareOutputAndCreateXcodebuildCommand(
+ // Assume dSYM was created
+ fileExists = { true }
+ )
+ )
+ }
+
+ @Test
+ fun `xcodebuild call - doesn't point to dSYMs - when framework is static`() {
+ val project = buildProjectWithMPP {
+ val xcframework = XCFramework()
+ kotlin {
+ listOf(
+ iosSimulatorArm64(),
+ ).forEach {
+ it.binaries.framework {
+ baseName = "bar"
+ isStatic = true
+ xcframework.add(this)
+ }
+ }
+ }
+ }.evaluate()
+
+ assertEquals(
+ listOf(
+ "xcodebuild", "-create-xcframework",
+ "-framework", project.buildFile("bin/iosSimulatorArm64/releaseFramework/bar.framework").path,
+ "-output", project.buildFile("XCFrameworks/release/test.xcframework").path,
+ ),
+ assertIsInstance<XCFrameworkTask>(
+ project.tasks.getByName("assembleTestReleaseXCFramework")
+ ).prepareOutputAndCreateXcodebuildCommand(
+ // Assume dSYM was created
+ fileExists = { true }
+ )
+ )
+ }
+
+ @Test
+ fun `framework names are different diagnostic - when universal frameworks have different names`() {
+ val project = buildProjectWithMPP {
+ val xcframework = XCFramework()
+ kotlin {
+ iosSimulatorArm64().binaries.framework {
+ baseName = "Test"
+ xcframework.add(this)
+ }
+ iosX64().binaries.framework {
+ baseName = "test"
+ xcframework.add(this)
+ }
+ }
+ }.evaluate()
+
+ assertNotNull(
+ assertIsInstance<XCFrameworkTask>(
+ project.tasks.getByName("assembleTestReleaseXCFramework")
+ ).frameworksConfigurationError.orNull
+ )
+
+ project.assertContainsDiagnostic(KotlinToolingDiagnostics.FrameworksInXCFrameworkHaveDifferentNames)
+ }
+
+ @Test
+ fun `xcframework and framework names are different diagnostic`() {
+ val project = buildProjectWithMPP {
+ val xcframework = XCFramework("foo")
+ kotlin {
+ iosSimulatorArm64().binaries.framework {
+ baseName = "bar"
+ xcframework.add(this)
+ }
+ }
+ }.evaluate()
+
+ // Force XCFramework task to configure
+ project.tasks.getByName("assembleFooReleaseXCFramework")
+
+ project.assertContainsDiagnostic(KotlinToolingDiagnostics.XCFrameworkNameIsDifferentFromInnerFrameworksName)
+ }
+
+ @Test
+ fun `adding framework after DSL finalization`() {
+ buildProjectWithMPP {
+ val xcframework = XCFramework()
+ kotlin {
+ iosSimulatorArm64().binaries.framework()
+ }
+ runLifecycleAwareTest {
+ KotlinPluginLifecycle.Stage.ReadyForExecution.await()
+
+ xcframework.add(
+ this.multiplatformExtension.iosSimulatorArm64().binaries.getFramework(NativeBuildType.RELEASE)
+ )
+
+ assertContainsDiagnostic(KotlinToolingDiagnostics.AddingFrameworkToXCFrameworkAfterDSLFinalized)
+ }
+ }
+ }
+
+ @Test
+ fun `empty xcframework diagnostic`() {
+ val project = buildProjectWithMPP {
+ XCFramework()
+ kotlin {
+ iosSimulatorArm64().binaries.framework()
+ }
+ }.evaluate()
+
+ // Force XCFramework task to configure
+ project.tasks.getByName("assembleTestReleaseXCFramework")
+
+ project.assertContainsDiagnostic(KotlinToolingDiagnostics.XCFrameworkHasNoFrameworks)
+ }
+
+ private fun Project.buildFile(path: String) = layout.buildDirectory.file(path).get().asFile
+
+}
\ No newline at end of file