[Gradle] Setup Xcode tasks
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/XcodeExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/XcodeExtension.kt new file mode 100644 index 0000000..ee52672 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/XcodeExtension.kt
@@ -0,0 +1,101 @@ +/* + * Copyright 2010-2025 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.xcode + +import org.gradle.api.Named +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import javax.inject.Inject + +/** + * The main entry point for the Xcode plugin configuration. + * + * Example Usage: + * ```kotlin + * xcode { + * projectPath.set(file("iosApp.xcodeproj")) + * scheme.set("iosApp") + * simulators { + * register("iPhone15") { + * deviceType.set("iPhone 15") + * osVersion.set("iOS 17.2") + * } + * } + * } + * ``` + */ +abstract class XcodeExtension @Inject constructor(objects: ObjectFactory) { + + /** The path to the .xcodeproj or .xcworkspace directory. */ + abstract val projectPath: DirectoryProperty + + /** The Scheme to build (e.g., "iosApp"). */ + abstract val scheme: Property<String> + + /** The build configuration (e.g., "Debug", "Release"). Defaults to "Debug". */ + abstract val configuration: Property<String> + + /** + * Container for configuring Simulator targets. + * These are virtual devices managed by `xcrun simctl`. + */ + val simulators: NamedDomainObjectContainer<SimulatorTarget> = + objects.domainObjectContainer(SimulatorTarget::class.java) + + /** + * Container for configuring Physical Device targets. + * These are hardware devices connected via USB/WiFi. + */ + val devices: NamedDomainObjectContainer<DeviceTarget> = + objects.domainObjectContainer(DeviceTarget::class.java) + + init { + // Set safe defaults + configuration.convention("Debug") + } +} + +/** + * Represents a virtual simulator device configuration. + */ +abstract class SimulatorTarget(private val name: String) : Named { + override fun getName(): String = name + + /** + * The device type identifier used by Apple. + * Run `xcrun simctl list devicetypes` to see options. + * Example: "iPhone 15" + */ + abstract val deviceType: Property<String> + + /** + * The OS version for the runtime. + * Run `xcrun simctl list runtimes` to see options. + * Example: "iOS 17.2" + */ + abstract val osVersion: Property<String> +} + +/** + * Represents a physical hardware device configuration. + */ +abstract class DeviceTarget(private val name: String) : Named { + override fun getName(): String = name + + /** + * The Unique Device Identifier (UDID). + * Run `xcrun devicectl list devices` or `xcodebuild -showdestinations` to find this. + */ + abstract val udid: Property<String> + + /** + * Optional friendly model name for logging/reporting. + * Example: "My iPhone 14 Pro" + */ + abstract val modelName: Property<String> +} \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/internal/XcodeConfigurationSetupAction.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/internal/XcodeConfigurationSetupAction.kt index f5e319d..70c2d30 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/internal/XcodeConfigurationSetupAction.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/internal/XcodeConfigurationSetupAction.kt
@@ -19,7 +19,7 @@ import org.jetbrains.kotlin.konan.target.KonanTarget import java.io.File -private const val XCODE_TASK_GROUP = "xcode" +private const val XCODE_TASK_GROUP = "Xcode" /** * Registers two tasks:
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/internal/XcodeDslSetupAction.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/internal/XcodeDslSetupAction.kt new file mode 100644 index 0000000..492f05d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/internal/XcodeDslSetupAction.kt
@@ -0,0 +1,119 @@ +/* + * Copyright 2010-2025 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.xcode.internal + +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinProjectSetupCoroutine +import org.jetbrains.kotlin.gradle.plugin.addExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.Framework +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.xcode.XcodeExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.xcode.tasks.BootSimulatorTask +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.xcode.tasks.BuildXcodeTask +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.xcode.tasks.CreateSimulatorTask +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.xcode.tasks.ListXcodeInfoTask +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.xcode.tasks.RunXcodeTask +import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask + +internal object XcodeDSLConstants { + const val XCODE_DSL_EXTENSION_NAME = "xcode" + const val TASK_GROUP = "Xcode" +} + +internal val XcodeDSLSetupAction = KotlinProjectSetupCoroutine { + val xcodeExtension = objects.newInstance(XcodeExtension::class.java) + + multiplatformExtension.addExtension( + XcodeDSLConstants.XCODE_DSL_EXTENSION_NAME, + xcodeExtension + ) + + val targets = multiplatformExtension + .awaitTargets() + .filterIsInstance<KotlinNativeTarget>() + .filter { it.konanTarget.family.isAppleFamily } + + if (targets.isEmpty()) return@KotlinProjectSetupCoroutine + + val hasBinaries = targets.flatMap { it.binaries }.filterIsInstance<Framework>().isNotEmpty() + if (!hasBinaries) return@KotlinProjectSetupCoroutine + + registerXcodePipeline(xcodeExtension) +} + +private fun Project.registerXcodePipeline( + extension: XcodeExtension, +) { + // 1. Register Global Lifecycle Tasks + locateOrRegisterTask<ListXcodeInfoTask>("listXcodeDestinations") + + // 2. Process Simulators dynamically + extension.simulators.all { simulator -> + val capitalizedName = simulator.name.replaceFirstChar { it.uppercase() } + + // Task: Create Simulator (Idempotent) + val createTask = locateOrRegisterTask<CreateSimulatorTask>("createSimulator$capitalizedName") { task -> + task.group = XcodeDSLConstants.TASK_GROUP + task.description = "Creates the '${simulator.name}' simulator if missing." + task.deviceName.set(simulator.name) + task.deviceType.set(simulator.deviceType) + task.osVersion.set(simulator.osVersion) + } + + // Task: Boot Simulator + val bootTask = locateOrRegisterTask<BootSimulatorTask>("bootSimulator$capitalizedName") { task -> + task.group = XcodeDSLConstants.TASK_GROUP + task.description = "Boots the '${simulator.name}' simulator." + task.simulatorName.set(simulator.name) + task.dependsOn(createTask) + } + + // Task: Build App for this Simulator + val buildTask = locateOrRegisterTask<BuildXcodeTask>("buildXcode$capitalizedName") { task -> + task.group = XcodeDSLConstants.TASK_GROUP + task.description = "Builds the iOS app for ${simulator.name}." + + task.projectPath.set(extension.projectPath) + task.scheme.set(extension.scheme) + task.configuration.set(extension.configuration) + // Construct destination string for xcodebuild + task.destination.set(project.provider { + "platform=iOS Simulator,name=${simulator.name},OS=${simulator.osVersion.get()}" + }) + + // Ensure simulator exists before building (optional, but good for 'run') + task.dependsOn(createTask) + } + + // Task: Run App on Simulator + locateOrRegisterTask<RunXcodeTask>("runXcode$capitalizedName") { task -> + task.group = XcodeDSLConstants.TASK_GROUP + task.description = "Installs and runs the app on ${simulator.name}." + + task.deviceIdOrName.set(simulator.name) + // Note: In a real plugin, bundleId should be parsed from Info.plist + task.bundleId.set(project.provider { "com.example.${extension.scheme.get()}" }) + task.scheme.set(extension.scheme) + task.buildDir.set(project.layout.buildDirectory) + + task.dependsOn(bootTask, buildTask) + } + } + + // 3. Process Physical Devices (Simplified flow - no creation/booting) + extension.devices.all { device -> + val capitalizedName = device.name.replaceFirstChar { it.uppercase() } + + locateOrRegisterTask<BuildXcodeTask>("buildXcode$capitalizedName") { task -> + task.group = XcodeDSLConstants.TASK_GROUP + task.projectPath.set(extension.projectPath) + task.scheme.set(extension.scheme) + task.configuration.set(extension.configuration) + task.destination.set(device.udid.map { "platform=iOS,id=$it" }) + } + } +} \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/AbstractXcodeTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/AbstractXcodeTask.kt new file mode 100644 index 0000000..15514dc --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/AbstractXcodeTask.kt
@@ -0,0 +1,32 @@ +/* + * Copyright 2010-2025 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.xcode.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.process.ExecOperations +import java.io.ByteArrayOutputStream +import javax.inject.Inject + +/** + * Base task providing helper methods for Xcode command execution. + */ +abstract class AbstractXcodeTask : DefaultTask() { + @get:Inject + abstract val execOperations: ExecOperations + + protected fun runCommand(vararg args: String): String { + val stdout = ByteArrayOutputStream() + val result = execOperations.exec { + it.commandLine(*args) + it.standardOutput = stdout + } + if (result.exitValue != 0) { + throw GradleException("Command failed: ${args.joinToString(" ")}") + } + return stdout.toString().trim() + } +} \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/BootSimulatorTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/BootSimulatorTask.kt new file mode 100644 index 0000000..0b4a0bd --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/BootSimulatorTask.kt
@@ -0,0 +1,42 @@ +/* + * Copyright 2010-2025 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.xcode.tasks + +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction + +/** + * Boots a simulator if it is currently shutdown. + */ +abstract class BootSimulatorTask : AbstractXcodeTask() { + @get:Input + abstract val simulatorName: Property<String> + + @TaskAction + fun boot() { + // We resolve ID by name for simplicity in this step, + // or we could pass the ID from the creation task if we wired outputs. + val name = simulatorName.get() + + // Naive boot command. 'xcrun simctl boot' fails if already booted, + // so we ignore exit code or check status first. + try { + logger.lifecycle("Booting simulator '$name'...") + execOperations.exec { + it.commandLine("xcrun", "simctl", "boot", name) + it.isIgnoreExitValue = true + } + + // Wait specifically for the service to be ready + execOperations.exec { + it.commandLine("xcrun", "simctl", "bootstatus", name) + } + } catch (e: Exception) { + logger.warn("Attempt to boot '$name' finished. It might already be booted.") + } + } +} \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/BuildXcodeTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/BuildXcodeTask.kt new file mode 100644 index 0000000..6c3db70 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/BuildXcodeTask.kt
@@ -0,0 +1,45 @@ +/* + * Copyright 2010-2025 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.xcode.tasks + +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.TaskAction +import org.jetbrains.kotlin.gradle.utils.getFile + +/** + * Runs xcodebuild to compile the app. + */ +abstract class BuildXcodeTask : AbstractXcodeTask() { + @get:InputDirectory + abstract val projectPath: DirectoryProperty + + @get:Input + abstract val scheme: Property<String> + + @get:Input + abstract val configuration: Property<String> + + @get:Input + abstract val destination: Property<String> + + @TaskAction + fun build() { + logger.lifecycle("Building Xcode project for destination: ${destination.get()}") + + runCommand( + "xcodebuild", + "-project", projectPath.get().asFile.absolutePath, + "-scheme", scheme.get(), + "-configuration", configuration.get(), + "-destination", destination.get(), + "-derivedDataPath", project.layout.buildDirectory.dir("xcodeDerivedData").getFile().absolutePath, + "clean", "build" + ) + } +} \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/CreateSimulatorTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/CreateSimulatorTask.kt new file mode 100644 index 0000000..9d14215 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/CreateSimulatorTask.kt
@@ -0,0 +1,69 @@ +/* + * Copyright 2010-2025 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.xcode.tasks + +import com.google.gson.Gson +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction + +/** + * Ensures a simulator exists with the specified configuration. + * If it exists, it does nothing. If not, it creates it. + */ +abstract class CreateSimulatorTask : AbstractXcodeTask() { + @get:Input + abstract val deviceName: Property<String> + + @get:Input + abstract val deviceType: Property<String> + + @get:Input + abstract val osVersion: Property<String> + + @get:Internal + val simulatorId: Property<String> = project.objects.property(String::class.java) + + @TaskAction + fun create() { + val name = deviceName.get() + + // 1. Check if exists + val listJson = runCommand("xcrun", "simctl", "list", "devices", "--json") + + // Use Gson to parse the JSON output + val json = Gson().fromJson(listJson, Map::class.java) + val devices = json["devices"] as? Map<*, *> ?: emptyMap<Any, Any>() + + var existingId: String? = null + + // Parse the nested JSON structure: Runtimes -> Devices list + devices.forEach { (_, deviceList) -> + (deviceList as List<*>).forEach { device -> + val d = device as Map<*, *> + if (d["name"] == name) { + existingId = d["udid"] as String + } + } + } + + if (existingId != null) { + logger.lifecycle("Simulator '$name' already exists with ID: $existingId") + simulatorId.set(existingId) + } else { + logger.lifecycle("Creating simulator '$name'...") + // Construct runtime ID (e.g., com.apple.CoreSimulator.SimRuntime.iOS-17-2) + // Note: This is a simplified mapping. Real implementation might need exact lookup. + val runtimeId = "com.apple.CoreSimulator.SimRuntime.${osVersion.get().replace(" ", "-").replace(".", "-")}" + val typeId = "com.apple.CoreSimulator.SimDeviceType.${deviceType.get().replace(" ", "-")}" + + val newId = runCommand("xcrun", "simctl", "create", name, typeId, runtimeId) + logger.lifecycle("Created simulator with ID: $newId") + simulatorId.set(newId) + } + } +} \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/ListXcodeInfoTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/ListXcodeInfoTask.kt new file mode 100644 index 0000000..1316403 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/ListXcodeInfoTask.kt
@@ -0,0 +1,21 @@ +/* + * Copyright 2010-2025 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.xcode.tasks + +import org.gradle.api.tasks.TaskAction + +/** + * Lists all available Simulator Runtimes and Device Types. + * Useful for debugging configuration strings. + */ +abstract class ListXcodeInfoTask : AbstractXcodeTask() { + @TaskAction + fun list() { + logger.lifecycle("Fetching Xcode Runtime Information...") + val output = runCommand("xcrun", "simctl", "list", "runtimes") + logger.lifecycle(output) + } +} \ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/RunXcodeTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/RunXcodeTask.kt new file mode 100644 index 0000000..78975bd --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/xcode/tasks/RunXcodeTask.kt
@@ -0,0 +1,44 @@ +/* + * Copyright 2010-2025 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.xcode.tasks + +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.TaskAction +import org.jetbrains.kotlin.gradle.utils.getFile + +/** + * Installs the built .app onto the simulator. + */ +abstract class RunXcodeTask : AbstractXcodeTask() { + @get:Input + abstract val deviceIdOrName: Property<String> + + @get:Input + abstract val bundleId: Property<String> + + @get:InputDirectory + abstract val buildDir: DirectoryProperty + + @get:Input + abstract val scheme: Property<String> + + @TaskAction + fun run() { + // 1. Find the .app path. + // In a real plugin, we would parse the build settings or use a fixed output path. + // Assuming standard DerivedData structure for this example: + val appPath = buildDir.dir("xcodeDerivedData/Build/Products/Debug-iphonesimulator/${scheme.get()}.app").getFile().absolutePath + + logger.lifecycle("Installing app: $appPath") + runCommand("xcrun", "simctl", "install", deviceIdOrName.get(), appPath) + + logger.lifecycle("Launching app: ${bundleId.get()}") + runCommand("xcrun", "simctl", "launch", "--console-pty", deviceIdOrName.get(), bundleId.get()) + } +} \ 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 d7b201a..cb7a83e 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
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XcodeVersionSetupAction import org.jetbrains.kotlin.gradle.plugin.mpp.apple.swiftexport.SetUpSwiftExportAction import org.jetbrains.kotlin.gradle.plugin.mpp.apple.xcode.internal.XcodeConfigurationSetupAction +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.xcode.internal.XcodeDSLSetupAction import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.* import org.jetbrains.kotlin.gradle.plugin.mpp.internal.DeprecatedMppGradlePropertiesMigrationSetupAction import org.jetbrains.kotlin.gradle.plugin.mpp.internal.ProjectStructureMetadataForKMPSetupAction @@ -94,6 +95,7 @@ register(project, CInteropCommonizedCInteropApiElementsConfigurationsSetupAction) register(project, XcodeVersionSetupAction) register(project, XcodeConfigurationSetupAction) + register(project, XcodeDSLSetupAction) register(project, AddBuildListenerForXcodeSetupAction) register(project, CreateFatFrameworksSetupAction) register(project, KotlinRegisterCompilationArchiveTasksExtension)