Browse Source

Gradle deploy to iOS simulator (#1899)

pull/1929/head
dima-avdeev-jb 3 years ago committed by GitHub
parent
commit
24526947ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      experimental/examples/falling-balls-mpp/build.gradle.kts
  2. 29
      experimental/examples/falling-balls-mpp/project.yml
  3. 18
      experimental/examples/minesweeper/build.gradle.kts
  4. 29
      experimental/examples/minesweeper/project.yml
  5. 4
      gradle-plugins/build.gradle.kts
  6. 1
      gradle-plugins/buildSrc/src/main/kotlin/BuildProperties.kt
  7. 5
      gradle-plugins/compose/build.gradle.kts
  8. 15
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/osUtils.kt
  9. 16
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/dsl/ExperimentalUiKitApplication.kt
  10. 59
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/dsl/IOSDevices.kt
  11. 33
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/dsl/IosDeployConfigurations.kt
  12. 77
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/SimctlListData.kt
  13. 25
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/SimctlUtils.kt
  14. 2
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureExperimentalUikitApplication.kt
  15. 84
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureIosDeployTasks.kt
  16. 57
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureUseXcodeGenTask.kt
  17. 119
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/registerSimulatorTasks.kt
  18. 49
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/tasks/AbstractComposeIosTask.kt
  19. 28
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/requiredDslProperty.kt

18
experimental/examples/falling-balls-mpp/build.gradle.kts

@ -3,6 +3,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.compose.experimental.dsl.IOSDevices
buildscript { buildscript {
repositories { repositories {
@ -150,7 +151,22 @@ compose.desktop {
compose.experimental { compose.experimental {
web.application {} web.application {}
uikit.application {} uikit.application {
bundleIdPrefix = "org.jetbrains"
projectName = "FallingBalls"
deployConfigurations {
simulator("IPhone8") {
//Usage: ./gradlew iosDeployIPhone8
device = IOSDevices.IPHONE_8
buildConfiguration = "Debug" // or "Release"
}
simulator("IPad") {
//Usage: ./gradlew iosDeployIPad
device = IOSDevices.IPAD_MINI_6th_Gen
buildConfiguration = "Debug"
}
}
}
} }
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {

29
experimental/examples/falling-balls-mpp/project.yml

@ -1,29 +0,0 @@
name: ComposeFallingBalls
options:
bundleIdPrefix: org.jetbrains
settings:
DEVELOPMENT_TEAM: N462MKSJ7M
CODE_SIGN_IDENTITY: "iPhone Developer"
CODE_SIGN_STYLE: Automatic
MARKETING_VERSION: "1.0"
CURRENT_PROJECT_VERSION: "4"
SDKROOT: iphoneos
targets:
ComposeFallingBalls:
type: application
platform: iOS
deploymentTarget: "12.0"
prebuildScripts:
- script: cd "$SRCROOT" && ./gradlew -i -p . packComposeUikitApplicationForXCode
name: GradleCompile
info:
path: plists/Ios/Info.plist
properties:
UILaunchStoryboardName: ""
sources:
- "src/"
settings:
LIBRARY_SEARCH_PATHS: "$(inherited)"
ENABLE_BITCODE: "YES"
ONLY_ACTIVE_ARCH: "NO"
VALID_ARCHS: "arm64"

18
experimental/examples/minesweeper/build.gradle.kts

@ -3,6 +3,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.compose.experimental.dsl.IOSDevices
buildscript { buildscript {
repositories { repositories {
@ -151,7 +152,22 @@ compose.desktop {
compose.experimental { compose.experimental {
web.application {} web.application {}
uikit.application {} uikit.application {
bundleIdPrefix = "org.jetbrains"
projectName = "ComposeMinesweeper"
deployConfigurations {
simulator("IPhone8") {
//Usage: ./gradlew iosDeployIPhone8
device = IOSDevices.IPHONE_8
buildConfiguration = "Debug" // or "Release"
}
simulator("IPad) {
//Usage: ./gradlew iosDeployIPad
device = IOSDevices.IPAD_MINI_6th_Gen
buildConfiguration = "Debug"
}
}
}
} }
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {

29
experimental/examples/minesweeper/project.yml

@ -1,29 +0,0 @@
name: ComposeMinesweeper
options:
bundleIdPrefix: org.jetbrains
settings:
DEVELOPMENT_TEAM: N462MKSJ7M
CODE_SIGN_IDENTITY: "iPhone Developer"
CODE_SIGN_STYLE: Automatic
MARKETING_VERSION: "1.0"
CURRENT_PROJECT_VERSION: "4"
SDKROOT: iphoneos
targets:
ComposeMinesweeper:
type: application
platform: iOS
deploymentTarget: "12.0"
prebuildScripts:
- script: cd "$SRCROOT" && ./gradlew -i -p . packComposeUikitApplicationForXCode
name: GradleCompile
info:
path: plists/Ios/Info.plist
properties:
UILaunchStoryboardName: ""
sources:
- "src/"
settings:
LIBRARY_SEARCH_PATHS: "$(inherited)"
ENABLE_BITCODE: "YES"
ONLY_ACTIVE_ARCH: "NO"
VALID_ARCHS: "arm64"

4
gradle-plugins/build.gradle.kts

@ -1,7 +1,9 @@
import com.gradle.publish.PluginBundleExtension import com.gradle.publish.PluginBundleExtension
plugins { plugins {
kotlin("jvm") version "1.5.10" apply false val kotlinVersion = "1.5.10"
kotlin("jvm") version kotlinVersion apply false
kotlin("plugin.serialization") version kotlinVersion apply false
id("com.gradle.plugin-publish") version "0.17.0" apply false id("com.gradle.plugin-publish") version "0.17.0" apply false
} }

1
gradle-plugins/buildSrc/src/main/kotlin/BuildProperties.kt

@ -11,6 +11,7 @@ object BuildProperties {
const val group = "org.jetbrains.compose" const val group = "org.jetbrains.compose"
const val website = "https://www.jetbrains.com/lp/compose/" const val website = "https://www.jetbrains.com/lp/compose/"
const val vcs = "https://github.com/JetBrains/compose-jb" const val vcs = "https://github.com/JetBrains/compose-jb"
const val serializationVersion = "1.2.1"
fun composeVersion(project: Project): String = fun composeVersion(project: Project): String =
System.getenv("COMPOSE_GRADLE_PLUGIN_COMPOSE_VERSION") System.getenv("COMPOSE_GRADLE_PLUGIN_COMPOSE_VERSION")
?: project.findProperty("compose.version") as String ?: project.findProperty("compose.version") as String

5
gradle-plugins/compose/build.gradle.kts

@ -4,6 +4,7 @@ import java.util.zip.ZipFile
plugins { plugins {
kotlin("jvm") kotlin("jvm")
kotlin("plugin.serialization")
id("com.gradle.plugin-publish") id("com.gradle.plugin-publish")
id("java-gradle-plugin") id("java-gradle-plugin")
id("maven-publish") id("maven-publish")
@ -66,6 +67,10 @@ dependencies {
// include relocated download task to avoid potential runtime conflicts // include relocated download task to avoid potential runtime conflicts
embedded("de.undercouch:gradle-download-task:4.1.1") embedded("de.undercouch:gradle-download-task:4.1.1")
embedded("org.jetbrains.kotlinx:kotlinx-serialization-json:${BuildProperties.serializationVersion}")
embedded("org.jetbrains.kotlinx:kotlinx-serialization-core:${BuildProperties.serializationVersion}")
embedded("org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:${BuildProperties.serializationVersion}")
embedded(project(":preview-rpc")) embedded(project(":preview-rpc"))
} }

15
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/osUtils.kt

@ -68,6 +68,21 @@ internal object MacUtils {
val xcrun: File by lazy { val xcrun: File by lazy {
File("/usr/bin/xcrun").checkExistingFile() File("/usr/bin/xcrun").checkExistingFile()
} }
val make: File by lazy {
File("/usr/bin/make").checkExistingFile()
}
val open: File by lazy {
File("/usr/bin/open").checkExistingFile()
}
}
internal object UnixUtils {
val git: File by lazy {
File("/usr/bin/git").checkExistingFile()
}
} }
internal fun jvmToolFile(toolName: String, javaHome: Provider<String>): File { internal fun jvmToolFile(toolName: String, javaHome: Provider<String>): File {

16
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/dsl/ExperimentalUiKitApplication.kt

@ -5,7 +5,21 @@
package org.jetbrains.compose.experimental.dsl package org.jetbrains.compose.experimental.dsl
import org.gradle.api.Action
import org.gradle.api.model.ObjectFactory
import org.jetbrains.compose.internal.requiredDslProperty
import javax.inject.Inject import javax.inject.Inject
abstract class ExperimentalUiKitApplication @Inject constructor(val name: String) { @Suppress("unused")
abstract class ExperimentalUiKitApplication @Inject constructor(
val name: String,
val objects: ObjectFactory
) {
var bundleIdPrefix: String by requiredDslProperty("require property [bundleIdPrefix] in uikit.application { ...")
var projectName: String by requiredDslProperty("require property [projectName] in uikit.application { ...")
val deployConfigurations: IosDeployConfigurations = objects.newInstance(IosDeployConfigurations::class.java)
fun deployConfigurations(fn: Action<IosDeployConfigurations>) {
fn.execute(deployConfigurations)
}
} }

59
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/dsl/IOSDevices.kt

@ -0,0 +1,59 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.dsl
/**
* iOS device type
* xcrun simctl list devices
*/
@Suppress("unused")
public enum class IOSDevices(val id: String) {
IPHONE_6S("com.apple.CoreSimulator.SimDeviceType.iPhone-6s"),
IPHONE_6S_PLUS("com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus"),
IPHONE_SE("com.apple.CoreSimulator.SimDeviceType.iPhone-SE"),
IPHONE_7("com.apple.CoreSimulator.SimDeviceType.iPhone-7"),
IPHONE_7_PLUS("com.apple.CoreSimulator.SimDeviceType.iPhone-7-Plus"),
IPHONE_8("com.apple.CoreSimulator.SimDeviceType.iPhone-8"),
IPHONE_8_PLUS("com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus"),
IPHONE_X("com.apple.CoreSimulator.SimDeviceType.iPhone-X"),
IPHONE_XS("com.apple.CoreSimulator.SimDeviceType.iPhone-XS"),
IPHONE_XS_MAX("com.apple.CoreSimulator.SimDeviceType.iPhone-XS-Max"),
IPHONE_XR("com.apple.CoreSimulator.SimDeviceType.iPhone-XR"),
IPHONE_11("com.apple.CoreSimulator.SimDeviceType.iPhone-11"),
IPHONE_11_PRO("com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro"),
IPHONE_11_PRO_MAX("com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro-Max"),
IPHONE_SE_2nd_Gen("com.apple.CoreSimulator.SimDeviceType.iPhone-SE--2nd-generation-"),
IPHONE_12_MINI("com.apple.CoreSimulator.SimDeviceType.iPhone-12-mini"),
IPHONE_12("com.apple.CoreSimulator.SimDeviceType.iPhone-12"),
IPHONE_12_PRO("com.apple.CoreSimulator.SimDeviceType.iPhone-12-Pro"),
IPHONE_12_PRO_MAX("com.apple.CoreSimulator.SimDeviceType.iPhone-12-Pro-Max"),
IPHONE_13_PRO("com.apple.CoreSimulator.SimDeviceType.iPhone-13-Pro"),
IPHONE_13_PRO_MAX("com.apple.CoreSimulator.SimDeviceType.iPhone-13-Pro-Max"),
IPHONE_13_MINI("com.apple.CoreSimulator.SimDeviceType.iPhone-13-mini"),
IPHONE_13("com.apple.CoreSimulator.SimDeviceType.iPhone-13"),
IPOD_TOUCH_7th_Gen("com.apple.CoreSimulator.SimDeviceType.iPod-touch--7th-generation-"),
IPAD_MINI_4("com.apple.CoreSimulator.SimDeviceType.iPad-mini-4"),
IPAD_AIR_2("com.apple.CoreSimulator.SimDeviceType.iPad-Air-2"),
IPAD_PRO_9_7_INCH("com.apple.CoreSimulator.SimDeviceType.iPad-Pro--9-7-inch-"),
IPAD_PRO("com.apple.CoreSimulator.SimDeviceType.iPad-Pro"),
IPAD_5th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad--5th-generation-"),
IPAD_PRO_12_9_INCH_2nd_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---2nd-generation-"),
IPAD_PRO_10_5_INCH("com.apple.CoreSimulator.SimDeviceType.iPad-Pro--10-5-inch-"),
IPAD_6th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad--6th-generation-"),
IPAD_7th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad--7th-generation-"),
IPAD_PRO_11_INCH("com.apple.CoreSimulator.SimDeviceType.iPad-Pro--11-inch-"),
IPAD_PRO_12_9_INCH_3rd_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---3rd-generation-"),
IPAD_PRO_11_INCH_2nd_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-Pro--11-inch---2nd-generation-"),
IPAD_PRO_12_9_INCH_4th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---4th-generation-"),
IPAD_MINI_5th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-mini--5th-generation-"),
IPAD_AIR_3th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-Air--3rd-generation-"),
IPAD_8th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad--8th-generation-"),
IPAD_9th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-9th-generation"),
IPAD_AIR_4th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-Air--4th-generation-"),
IPAD_PRO_11_INCH_3rd_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-3rd-generation"),
IPAD_12_9_INCH_5th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-Pro-12-9-inch-5th-generation"),
IPAD_MINI_6th_Gen("com.apple.CoreSimulator.SimDeviceType.iPad-mini-6th-generation"),
}

33
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/dsl/IosDeployConfigurations.kt

@ -0,0 +1,33 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.dsl
import org.gradle.api.Action
import org.gradle.api.model.ObjectFactory
import javax.inject.Inject
open class IosDeployConfigurations @Inject constructor(
val objects: ObjectFactory
) {
internal val deployTargets: MutableList<DeployTargetWithId> = mutableListOf()
public fun simulator(id: String, configureSimulator: Action<DeployTarget.Simulator>) {
val currentSimulator = objects.newInstance(DeployTarget.Simulator::class.java)
configureSimulator.execute(currentSimulator)
deployTargets.add(DeployTargetWithId(id, currentSimulator))
}
}
sealed interface DeployTarget {
open class Simulator : DeployTarget {
var device: IOSDevices = IOSDevices.IPHONE_8
var buildConfiguration: String = "Debug"
}
}
internal class DeployTargetWithId(
val id: String,
val deploy: DeployTarget
)

77
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/SimctlListData.kt

@ -0,0 +1,77 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.compose.experimental.uikit.internal
import kotlinx.serialization.Serializable
@Serializable
internal class SimctlListData(
val devicetypes: List<DeviceTypeData>,
val runtimes: List<RuntimeData>,
val devices: Map<String, List<DeviceData>>,
val pairs: Map<String, WatchAndPhonePairData>,
)
@Serializable
internal class DeviceTypeData(
val name: String,
val minRuntimeVersion: Long,
val bundlePath: String,
val maxRuntimeVersion: Long,
val identifier: String,
val productFamily: String
)
@Serializable
internal class RuntimeData(
val name: String,
val bundlePath: String,
val buildversion: String,
val runtimeRoot: String,
val identifier: String,
val version: String,
val isAvailable: Boolean,
val supportedDeviceTypes: List<SupportedDeviceTypeData>
)
@Serializable
internal class SupportedDeviceTypeData(
val bundlePath: String,
val name: String,
val identifier: String,
val productFamily: String
)
@Serializable
internal class DeviceData(
val name: String,
val availabilityError: String? = null,
val dataPath: String,
val dataPathSize: Long,
val logPath: String,
val udid: String,
val isAvailable: Boolean,
val deviceTypeIdentifier: String,
val state: String,
)
internal val DeviceData.booted: Boolean
get() = state == "Booted"
@Serializable
internal class WatchAndPhonePairData(
val watch: DeviceInPairData,
val phone: DeviceInPairData
)
@Serializable
internal class DeviceInPairData(
val name: String,
val udid: String,
val state: String,
)

25
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/SimctlUtils.kt

@ -0,0 +1,25 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.uikit.internal
import kotlinx.serialization.json.Json
import org.jetbrains.compose.desktop.application.internal.MacUtils
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
val json = Json {
ignoreUnknownKeys = true
}
internal fun AbstractComposeIosTask.getSimctlListData(): SimctlListData {
lateinit var simctlResult: SimctlListData
runExternalTool(
MacUtils.xcrun, listOf("simctl", "list", "--json"),
processStdout = { stdout ->
simctlResult = json.decodeFromString(SimctlListData.serializer(), stdout)
}
)
return simctlResult
}

2
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureExperimentalUikitApplication.kt

@ -47,4 +47,6 @@ internal fun Project.configureExperimentalUikitApplication(
packTask.destinationDir.set(targetBuildDir) packTask.destinationDir.set(targetBuildDir)
packTask.executablePath.set(executablePath) packTask.executablePath.set(executablePath)
} }
configureIosDeployTasks(application)
} }

84
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureIosDeployTasks.kt

@ -0,0 +1,84 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.uikit.internal
import org.gradle.api.*
import org.gradle.api.tasks.TaskContainer
import org.jetbrains.compose.desktop.application.internal.MacUtils
import org.jetbrains.compose.desktop.application.internal.UnixUtils
import org.jetbrains.compose.experimental.dsl.DeployTarget
import org.jetbrains.compose.experimental.dsl.ExperimentalUiKitApplication
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
const val XCODE_GEN_GIT = "https://github.com/yonaskolb/XcodeGen.git"
const val XCODE_GEN_TAG = "2.26.0"
const val TASK_INSTALL_XCODE_GEN_NAME = "iosInstallXcodeGen"
const val TASK_USE_XCODE_GEN_NAME = "iosUseXCodeGen"
const val SDK_PREFIFX_SIMULATOR = "iphonesimulator"
const val SDK_PREFIX_IPHONEOS = "iphoneos"
internal fun Project.configureIosDeployTasks(application: ExperimentalUiKitApplication) {
val projectName = application.projectName
val bundleIdPrefix = application.bundleIdPrefix
val xcodeGenSrc = rootProject.buildDir.resolve("xcodegen-$XCODE_GEN_TAG-src")
val xcodeGenExecutable = xcodeGenSrc.resolve(".build/apple/Products/Release/xcodegen")
val buildIosDir = buildDir.resolve("ios")
tasks.composeIosTask<AbstractComposeIosTask>(TASK_INSTALL_XCODE_GEN_NAME) {
onlyIf { !xcodeGenExecutable.exists() }
doLast {
xcodeGenSrc.deleteRecursively()
runExternalTool(
UnixUtils.git,
listOf(
"clone",
"--depth", "1",
"--branch", XCODE_GEN_TAG,
XCODE_GEN_GIT,
xcodeGenSrc.absolutePath
)
)
runExternalTool(
MacUtils.make,
listOf("build"),
workingDir = xcodeGenSrc
)
}
}
configureUseXcodeGenTask(
buildIosDir = buildIosDir,
projectName = projectName,
bundleIdPrefix = bundleIdPrefix,
xcodeGenExecutable = xcodeGenExecutable
)
application.deployConfigurations.deployTargets.forEach { target ->
val id = target.id // .replaceFirstChar { it.uppercase() } // todo upperCase first char? ./gradlew iosDeployId
when (target.deploy) {
is DeployTarget.Simulator -> {
registerSimulatorTasks(
id = id,
deploy = target.deploy,
buildIosDir = buildIosDir,
projectName = projectName,
bundleIdPrefix = bundleIdPrefix
)
}
}
}
}
inline fun <reified T : Task> TaskContainer.composeIosTask(
name: String,
args: List<Any> = emptyList(),
noinline configureFn: T.() -> Unit = {}
) = register(name, T::class.java, *args.toTypedArray()).apply {
configure {
it.group = "Compose iOS"
it.configureFn()
}
}

57
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureUseXcodeGenTask.kt

@ -0,0 +1,57 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.uikit.internal
import org.gradle.api.Project
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
import java.io.File
internal fun Project.configureUseXcodeGenTask(
buildIosDir: File,
projectName: String,
bundleIdPrefix: String,
xcodeGenExecutable: File
) {
tasks.composeIosTask<AbstractComposeIosTask>(TASK_USE_XCODE_GEN_NAME) {
dependsOn(TASK_INSTALL_XCODE_GEN_NAME)
doLast {
buildIosDir.mkdirs()
buildIosDir.resolve("project.yml").writeText(
"""
name: $projectName
options:
bundleIdPrefix: $bundleIdPrefix
settings:
CODE_SIGN_IDENTITY: "iPhone Developer"
CODE_SIGN_STYLE: Automatic
MARKETING_VERSION: "1.0"
CURRENT_PROJECT_VERSION: "4"
SDKROOT: iphoneos
targets:
$projectName:
type: application
platform: iOS
deploymentTarget: "12.0"
prebuildScripts:
- script: cd "${rootDir.absolutePath}" && ./gradlew -i -p . packComposeUikitApplicationForXCode
name: GradleCompile
info:
path: plists/Ios/Info.plist
properties:
UILaunchStoryboardName: ""
sources:
- "../../src/"
settings:
LIBRARY_SEARCH_PATHS: "${'$'}(inherited)"
ENABLE_BITCODE: "YES"
ONLY_ACTIVE_ARCH: "NO"
VALID_ARCHS: "arm64"
""".trimIndent()
)
runExternalTool(xcodeGenExecutable, emptyList(), workingDir = buildIosDir)
}
}
}

119
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/registerSimulatorTasks.kt

@ -0,0 +1,119 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.uikit.internal
import org.gradle.api.*
import org.jetbrains.compose.desktop.application.internal.Arch
import org.jetbrains.compose.desktop.application.internal.MacUtils
import org.jetbrains.compose.desktop.application.internal.currentArch
import org.jetbrains.compose.experimental.dsl.DeployTarget
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
import java.io.File
fun Project.registerSimulatorTasks(
id: String,
deploy: DeployTarget.Simulator,
buildIosDir: File,
projectName: String,
bundleIdPrefix: String
) {
val xcodeProjectDir = buildIosDir.resolve("$projectName.xcodeproj")
val deviceName = "device-$id"
val taskCreateSimulator = tasks.composeIosTask<AbstractComposeIosTask>("iosSimulatorCreate$id") {
onlyIf { getSimctlListData().devices.map { it.value }.flatten().none { it.name == deviceName } }
doFirst {
val availableRuntimes = getSimctlListData().runtimes.filter { runtime ->
runtime.supportedDeviceTypes.any { it.identifier == deploy.device.id }
}
val runtime = availableRuntimes.firstOrNull() ?: error("device not found is runtimes")
runExternalTool(
MacUtils.xcrun,
listOf("simctl", "create", deviceName, deploy.device.id, runtime.identifier)
)
}
}
val taskBootSimulator = tasks.composeIosTask<AbstractComposeIosTask>("iosSimulatorBoot$id") {
onlyIf {
getSimctlListData().devices.map { it.value }.flatten().any { it.name == deviceName && it.booted.not() }
}
dependsOn(taskCreateSimulator)
doLast {
val device = getSimctlListData().devices.map { it.value }.flatten().firstOrNull { it.name == deviceName }
?: error("device '$deviceName' not found")
runExternalTool(
MacUtils.xcrun,
listOf("simctl", "boot", device.udid)
)
runExternalTool(
MacUtils.open,
listOf(
"-a", "Simulator",
"--args", "-CurrentDeviceUDID", device.udid
)
)
}
}
val simulatorArch = when (currentArch) {
Arch.X64 -> "x86_64"
Arch.Arm64 -> "arm64"
}
val iosCompiledAppDir = xcodeProjectDir.resolve("build/Build/Products/Debug-iphonesimulator/$projectName.app")
val taskBuild = tasks.composeIosTask<AbstractComposeIosTask>("iosSimulatorBuild$id") {
dependsOn(TASK_USE_XCODE_GEN_NAME)
doLast {
val sdk = SDK_PREFIFX_SIMULATOR + getSimctlListData().runtimes.first().version // xcrun xcodebuild -showsdks
val scheme = projectName // xcrun xcodebuild -list -project .
repeat(2) {
// todo repeat(2) is workaround of error (domain=NSPOSIXErrorDomain, code=22)
// The bundle identifier of the application could not be determined
// Ensure that the application's Info.plist contains a value for CFBundleIdentifier.
runExternalTool(
MacUtils.xcrun,
listOf(
"xcodebuild",
"-scheme", scheme,
"-project", ".",
"-configuration", deploy.buildConfiguration,
"-derivedDataPath", "build",
"-arch", simulatorArch,
"-sdk", sdk
),
workingDir = xcodeProjectDir
)
}
}
}
val installIosSimulator = tasks.composeIosTask<AbstractComposeIosTask>("iosSimulatorInstall$id") {
dependsOn(taskBuild, taskBootSimulator)
doLast {
val device = getSimctlListData().devices.map { it.value }.flatten()
.firstOrNull { it.name == deviceName && it.booted } ?: error("device $deviceName not booted")
runExternalTool(
MacUtils.xcrun,
listOf("simctl", "install", device.udid, iosCompiledAppDir.absolutePath)
)
}
}
tasks.composeIosTask<AbstractComposeIosTask>("iosDeploy$id") {
dependsOn(installIosSimulator)
doFirst {
val device = getSimctlListData().devices.map { it.value }.flatten()
.firstOrNull { it.name == deviceName && it.booted } ?: error("device $deviceName not booted")
val bundleIdentifier = "$bundleIdPrefix.$projectName"
runExternalTool(
MacUtils.xcrun,
listOf("simctl", "launch", "--console", device.udid, bundleIdentifier)
)
}
}
}

49
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/tasks/AbstractComposeIosTask.kt

@ -0,0 +1,49 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.uikit.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
import org.gradle.api.internal.file.FileOperations
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.LocalState
import org.gradle.process.ExecOperations
import org.jetbrains.compose.desktop.application.internal.ComposeProperties
import org.jetbrains.compose.desktop.application.internal.ExternalToolRunner
import org.jetbrains.compose.desktop.application.internal.notNullProperty
import javax.inject.Inject
abstract class AbstractComposeIosTask : DefaultTask() {
@get:Inject
protected abstract val objects: ObjectFactory
@get:Inject
protected abstract val providers: ProviderFactory
@get:Inject
protected abstract val execOperations: ExecOperations
@get:Inject
protected abstract val fileOperations: FileOperations
@get:LocalState
protected val logsDir: Provider<Directory> = project.layout.buildDirectory.dir("compose/logs/$name")
@get:Internal
val verbose: Property<Boolean> = objects.notNullProperty<Boolean>().apply {
set(providers.provider {
logger.isDebugEnabled || ComposeProperties.isVerbose(providers).get()
})
}
@get:Internal
internal val runExternalTool: ExternalToolRunner
get() = ExternalToolRunner(verbose, logsDir, execOperations)
}

28
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/requiredDslProperty.kt

@ -0,0 +1,28 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.internal
import kotlin.reflect.KProperty
internal fun <T : Any> requiredDslProperty(missingMessage: String) = RequiredPropertyDelegate<T>(missingMessage)
class RequiredPropertyDelegate<T>(val missingMessage: String) {
var realValue: T? = null
operator fun setValue(
ref: Any,
property: KProperty<*>,
newValue: T
) {
realValue = newValue
}
operator fun getValue(
ref: Any,
property: KProperty<*>
): T {
return realValue ?: error(missingMessage)
}
}
Loading…
Cancel
Save