[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)