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