Browse Source

iOS gradle: create task to deploy on connected iOS device (#1944)

pull/1973/head
dima-avdeev-jb 2 years ago committed by GitHub
parent
commit
80cfc4c249
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      experimental/examples/falling-balls-mpp/.gitignore
  2. 34
      experimental/examples/falling-balls-mpp/README.md
  3. BIN
      experimental/examples/falling-balls-mpp/apple-id.png
  4. 9
      experimental/examples/falling-balls-mpp/build.gradle.kts
  5. BIN
      experimental/examples/falling-balls-mpp/ios-app.png
  6. 1
      experimental/examples/minesweeper/.gitignore
  7. 9
      experimental/examples/minesweeper/build.gradle.kts
  8. 4
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/osUtils.kt
  9. 4
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/dsl/ExperimentalUiKitApplication.kt
  10. 23
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/dsl/IosDeployConfigurations.kt
  11. 10
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/dsl/UiKitConfiguration.kt
  12. 40
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureInstallIosDeployTask.kt
  13. 40
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureInstallXcodeGenTask.kt
  14. 66
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureIosDeployTasks.kt
  15. 59
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureTaskToGenerateXcodeProject.kt
  16. 57
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureUseXcodeGenTask.kt
  17. 92
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/registerConnectedDeviceTasks.kt
  18. 108
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/registerSimulatorTasks.kt
  19. 19
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/gradleUtils.kt

2
experimental/examples/falling-balls-mpp/.gitignore vendored

@ -1 +1 @@
ComposeFallingBalls.xcodeproj
local.properties

34
experimental/examples/falling-balls-mpp/README.md

@ -0,0 +1,34 @@
## Run native on MacOS
`./gradlew runDebugExecutableMacosX64` (Works on Intel processors)
## Run web assembly in browser
`./gradlew jsBrowserDevelopmentRun`
## Run on iOS simulator
`./gradlew iosDeployIPhone8Debug`
`./gradlew iosDeployIPadDebug`
## Prepare Xcode to launch on iOS device
1) **Add your Apple ID.**
Xcode -> Preferences... -> Accounts
![apple-id.png](apple-id.png)
2) **Create certificates with XCode in temporary project.**
You can create a certificate in Xcode. Create a new iOS app in Xcode.
File -> New -> Project
Choose iOS, App:
![ios-app.png](ios-app.png)
3) **In next dialog set any product name and choose your teamID.**
4) **Run project on iOS Device.**
Xcode prompts you to install the certificate. Require password from login.keychain
5) **Done**
After a successful launch on the device - you can delete this temporary project.
Certificates will remain in login.keychain
## Run on iOS device
set teamId in local.properties
`./gradlew iosDeployDeviceRelease`
During the build, you will be prompted to access login.keychain

BIN
experimental/examples/falling-balls-mpp/apple-id.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

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

@ -156,14 +156,15 @@ compose.experimental {
projectName = "FallingBalls"
deployConfigurations {
simulator("IPhone8") {
//Usage: ./gradlew iosDeployIPhone8
//Usage: ./gradlew iosDeployIPhone8Debug
device = IOSDevices.IPHONE_8
buildConfiguration = "Debug" // or "Release"
}
simulator("IPad") {
//Usage: ./gradlew iosDeployIPad
//Usage: ./gradlew iosDeployIPadDebug
device = IOSDevices.IPAD_MINI_6th_Gen
buildConfiguration = "Debug"
}
connectedDevice("Device") {
//Usage: ./gradlew iosDeployDeviceRelease
}
}
}

BIN
experimental/examples/falling-balls-mpp/ios-app.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

1
experimental/examples/minesweeper/.gitignore vendored

@ -0,0 +1 @@
local.properties

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

@ -157,14 +157,15 @@ compose.experimental {
projectName = "ComposeMinesweeper"
deployConfigurations {
simulator("IPhone8") {
//Usage: ./gradlew iosDeployIPhone8
//Usage: ./gradlew iosDeployIPhone8Debug
device = IOSDevices.IPHONE_8
buildConfiguration = "Debug" // or "Release"
}
simulator("IPad") {
//Usage: ./gradlew iosDeployIPad
//Usage: ./gradlew iosDeployIPadDebug
device = IOSDevices.IPAD_MINI_6th_Gen
buildConfiguration = "Debug"
}
connectedDevice("Device") {
//Usage: ./gradlew iosDeployDeviceRelease
}
}
}

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

@ -69,6 +69,10 @@ internal object MacUtils {
File("/usr/bin/xcrun").checkExistingFile()
}
val xcodeBuild: File by lazy {
File("/usr/bin/xcodebuild").checkExistingFile()
}
val make: File by lazy {
File("/usr/bin/make").checkExistingFile()
}

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

@ -17,6 +17,10 @@ abstract class ExperimentalUiKitApplication @Inject constructor(
) {
var bundleIdPrefix: String by requiredDslProperty("require property [bundleIdPrefix] in uikit.application { ...")
var projectName: String by requiredDslProperty("require property [projectName] in uikit.application { ...")
val configurations: List<UiKitConfiguration> = listOf(
UiKitConfiguration("Debug"),
UiKitConfiguration("Release"),
)
val deployConfigurations: IosDeployConfigurations = objects.newInstance(IosDeployConfigurations::class.java)
fun deployConfigurations(fn: Action<IosDeployConfigurations>) {

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

@ -7,6 +7,7 @@ package org.jetbrains.compose.experimental.dsl
import org.gradle.api.Action
import org.gradle.api.model.ObjectFactory
import java.io.File
import javax.inject.Inject
open class IosDeployConfigurations @Inject constructor(
@ -18,12 +19,32 @@ open class IosDeployConfigurations @Inject constructor(
configureSimulator.execute(currentSimulator)
deployTargets.add(DeployTargetWithId(id, currentSimulator))
}
public fun localFile(id: String, configureLocalFile: Action<DeployTarget.LocalFile>) {
val current = objects.newInstance(DeployTarget.LocalFile::class.java)
configureLocalFile.execute(current)
deployTargets.add(DeployTargetWithId(id, current))
}
public fun connectedDevice(id: String, configureConnectedDevice: Action<DeployTarget.ConnectedDevice>) {
val current = objects.newInstance(DeployTarget.ConnectedDevice::class.java)
configureConnectedDevice.execute(current)
deployTargets.add(DeployTargetWithId(id, current))
}
}
sealed interface DeployTarget {
open class Simulator : DeployTarget {
var device: IOSDevices = IOSDevices.IPHONE_8
var buildConfiguration: String = "Debug"
}
open class LocalFile : DeployTarget {
var outputFile: File? = null
}
open class ConnectedDevice : DeployTarget {
var teamId: String? = null
}
}

10
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/dsl/UiKitConfiguration.kt

@ -0,0 +1,10 @@
/*
* 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
class UiKitConfiguration(
val name: String
)

40
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureInstallIosDeployTask.kt

@ -0,0 +1,40 @@
/*
* 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.desktop.application.internal.MacUtils
import org.jetbrains.compose.desktop.application.internal.UnixUtils
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
internal val Project.iosDeployExecutable get() = iosDeploySrc.resolve("build/Release/ios-deploy")
private const val IOS_DEPLOY_GIT = "https://github.com/ios-control/ios-deploy.git"
private const val IOS_DEPLOY_TAG = "1.11.4"
private val Project.iosDeploySrc get() = rootProject.buildDir.resolve("ios-deploy-$IOS_DEPLOY_TAG-src")
internal fun Project.configureInstallIosDeployTask() =
tasks.composeIosTask<AbstractComposeIosTask>("iosInstallIosDeploy") {
onlyIf { !iosDeployExecutable.exists() }
doLast {
iosDeploySrc.deleteRecursively()
runExternalTool(
UnixUtils.git,
listOf(
"clone",
"--depth", "1",
"--branch", IOS_DEPLOY_TAG,
IOS_DEPLOY_GIT,
iosDeploySrc.absolutePath
)
)
runExternalTool(
MacUtils.xcodeBuild,
listOf("-target", "ios-deploy"),
workingDir = iosDeploySrc
)
}
}

40
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureInstallXcodeGenTask.kt

@ -0,0 +1,40 @@
/*
* 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.desktop.application.internal.MacUtils
import org.jetbrains.compose.desktop.application.internal.UnixUtils
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
internal val Project.xcodeGenExecutable get() = xcodeGenSrc.resolve(".build/apple/Products/Release/xcodegen")
private const val XCODE_GEN_GIT = "https://github.com/yonaskolb/XcodeGen.git"
private const val XCODE_GEN_TAG = "2.26.0"
private val Project.xcodeGenSrc get() = rootProject.buildDir.resolve("xcodegen-$XCODE_GEN_TAG-src")
fun Project.configureInstallXcodeGenTask() =
tasks.composeIosTask<AbstractComposeIosTask>("iosInstallXcodeGen") {
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
)
}
}

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

@ -7,65 +7,49 @@ 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.gradle.api.tasks.TaskProvider
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"
const val TEAM_ID_PROPERTY_KEY = "compose.ios.teamId"
const val RELATIVE_PRODUCTS_PATH = "build/Build/Products"
fun Project.getBuildIosDir(id: String) = buildDir.resolve("ios").resolve(id)
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
)
val taskInstallXcodeGen: TaskProvider<*> = configureInstallXcodeGenTask()
val taskInstallIosDeploy: TaskProvider<*> = configureInstallIosDeployTask()
application.deployConfigurations.deployTargets.forEach { target ->
val id = target.id // .replaceFirstChar { it.uppercase() } // todo upperCase first char? ./gradlew iosDeployId
val id = target.id // .replaceFirstChar { it.uppercase() }
when (target.deploy) {
is DeployTarget.Simulator -> {
registerSimulatorTasks(
id = id,
deploy = target.deploy,
buildIosDir = buildIosDir,
projectName = projectName,
bundleIdPrefix = bundleIdPrefix
bundleIdPrefix = bundleIdPrefix,
taskInstallXcodeGen = taskInstallXcodeGen,
configurations = application.configurations,
)
}
is DeployTarget.LocalFile -> {
TODO("DeployTarget.LocalFile not implemented")
}
is DeployTarget.ConnectedDevice -> {
registerConnectedDeviceTasks(
id = id,
deploy = target.deploy,
projectName = projectName,
bundleIdPrefix = bundleIdPrefix,
taskInstallXcodeGen = taskInstallXcodeGen,
taskInstallIosDeploy = taskInstallIosDeploy,
configurations = application.configurations,
)
}
}

59
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureTaskToGenerateXcodeProject.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.uikit.internal
import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
internal fun Project.configureTaskToGenerateXcodeProject(
id: String,
projectName: String,
bundleIdPrefix: String,
teamId: String? = null,
taskInstallXcodeGen: TaskProvider<*>,
): TaskProvider<AbstractComposeIosTask> = tasks.composeIosTask<AbstractComposeIosTask>("iosGenerateXcodeProject$id") {
dependsOn(taskInstallXcodeGen)
doLast {
val buildIosDir = getBuildIosDir(id)
buildIosDir.mkdirs()
buildIosDir.resolve("project.yml").writeText(
"""
name: $projectName
options:
bundleIdPrefix: $bundleIdPrefix
settings:
${if (teamId != null) "DEVELOPMENT_TEAM: \"$teamId\"" else ""}
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: ""
method: "development"
sources:
- "../../../src/"
settings:
LIBRARY_SEARCH_PATHS: "$(inherited)"
ENABLE_BITCODE: "YES"
ONLY_ACTIVE_ARCH: "NO"
VALID_ARCHS: "arm64"
""".trimIndent()
)
runExternalTool(xcodeGenExecutable, emptyList(), workingDir = buildIosDir)
}
}

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

@ -1,57 +0,0 @@
/*
* 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)
}
}
}

92
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/registerConnectedDeviceTasks.kt

@ -0,0 +1,92 @@
/*
* 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.TaskProvider
import org.jetbrains.compose.desktop.application.internal.MacUtils
import org.jetbrains.compose.experimental.dsl.DeployTarget
import org.jetbrains.compose.experimental.dsl.UiKitConfiguration
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
import org.jetbrains.compose.internal.getLocalProperty
import org.jetbrains.compose.internal.localPropertiesFile
fun Project.registerConnectedDeviceTasks(
id: String,
deploy: DeployTarget.ConnectedDevice,
projectName: String,
bundleIdPrefix: String,
taskInstallXcodeGen: TaskProvider<*>,
taskInstallIosDeploy: TaskProvider<*>,
configurations: List<UiKitConfiguration>,
) {
val xcodeProjectDir = getBuildIosDir(id).resolve("$projectName.xcodeproj")
val taskGenerateXcodeProject = configureTaskToGenerateXcodeProject(
id = id,
projectName = projectName,
bundleIdPrefix = bundleIdPrefix,
teamId = deploy.teamId ?: getLocalProperty(TEAM_ID_PROPERTY_KEY)
?: error(
buildString {
appendLine("In local.properties (${localPropertiesFile.absolutePath})")
appendLine("Add property")
appendLine("$TEAM_ID_PROPERTY_KEY=***")
appendLine("Or set teamId in deploy with id: $id")
}
),
taskInstallXcodeGen = taskInstallXcodeGen,
)
for (configuration in configurations) {
val configName = configuration.name
val iosCompiledAppDir = xcodeProjectDir.resolve(RELATIVE_PRODUCTS_PATH)
.resolve("$configName-iphoneos/${projectName}.app")
val taskBuild = tasks.composeIosTask<AbstractComposeIosTask>("iosBuildIphoneOs$id$configName") {
dependsOn(taskGenerateXcodeProject)
doLast {
// xcrun xcodebuild -showsdks (list all sdk)
val sdk = SDK_PREFIX_IPHONEOS + getSimctlListData().runtimes.first().version
val scheme = projectName // xcrun xcodebuild -list -project . (list all schemes)
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", configName,
"-derivedDataPath", "build",
"-arch", "arm64",
"-sdk", sdk,
"-allowProvisioningUpdates",
"-allowProvisioningDeviceRegistration",
),
workingDir = xcodeProjectDir
)
}
}
}
val taskDeploy = tasks.composeIosTask<AbstractComposeIosTask>("iosDeploy$id$configName") {
dependsOn(taskInstallIosDeploy)
dependsOn(taskBuild)
doLast {
runExternalTool(
iosDeployExecutable,
listOf(
"--debug",
"--bundle", iosCompiledAppDir.absolutePath,
)
)
}
}
}
}

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

@ -6,10 +6,12 @@
package org.jetbrains.compose.experimental.uikit.internal
import org.gradle.api.*
import org.gradle.api.tasks.TaskProvider
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.dsl.UiKitConfiguration
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
import java.io.File
@ -17,13 +19,21 @@ import java.io.File
fun Project.registerSimulatorTasks(
id: String,
deploy: DeployTarget.Simulator,
buildIosDir: File,
projectName: String,
bundleIdPrefix: String
bundleIdPrefix: String,
taskInstallXcodeGen: TaskProvider<*>,
configurations: List<UiKitConfiguration>,
) {
val xcodeProjectDir = buildIosDir.resolve("$projectName.xcodeproj")
val xcodeProjectDir = getBuildIosDir(id).resolve("$projectName.xcodeproj")
val deviceName = "device-$id"
val taskGenerateXcodeProject = configureTaskToGenerateXcodeProject(
id = id,
projectName = projectName,
bundleIdPrefix = bundleIdPrefix,
taskInstallXcodeGen = taskInstallXcodeGen,
)
val taskCreateSimulator = tasks.composeIosTask<AbstractComposeIosTask>("iosSimulatorCreate$id") {
onlyIf { getSimctlListData().devices.map { it.value }.flatten().none { it.name == deviceName } }
doFirst {
@ -65,55 +75,63 @@ fun Project.registerSimulatorTasks(
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.
for (configuration in configurations) {
val configName = configuration.name
val iosCompiledAppDir = xcodeProjectDir.resolve(RELATIVE_PRODUCTS_PATH)
.resolve("$configName-iphonesimulator/${projectName}.app")
val taskBuild = tasks.composeIosTask<AbstractComposeIosTask>("iosSimulatorBuild$id$configName") {
dependsOn(taskGenerateXcodeProject)
doLast {
// xcrun xcodebuild -showsdks (list all sdk)
val sdk = SDK_PREFIFX_SIMULATOR + getSimctlListData().runtimes.first().version
val scheme = projectName // xcrun xcodebuild -list -project . (list all schemes)
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", configName,
"-derivedDataPath", "build",
"-arch", simulatorArch,
"-sdk", sdk
),
workingDir = xcodeProjectDir
)
}
}
}
val installIosSimulator = tasks.composeIosTask<AbstractComposeIosTask>("iosSimulatorInstall$id$configName") {
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(
"xcodebuild",
"-scheme", scheme,
"-project", ".",
"-configuration", deploy.buildConfiguration,
"-derivedDataPath", "build",
"-arch", simulatorArch,
"-sdk", sdk
),
workingDir = xcodeProjectDir
listOf("simctl", "install", device.udid, iosCompiledAppDir.absolutePath)
)
}
}
}
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$configName") {
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)
)
}
}
}
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)
)
}
}
}

19
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/gradleUtils.kt

@ -8,11 +8,11 @@ package org.jetbrains.compose.internal
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.logging.Logger
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import java.io.File
import java.util.*
internal inline fun Logger.info(fn: () -> String) {
if (isInfoEnabled) {
@ -35,4 +35,19 @@ internal inline fun <reified T : Task> Project.registerTask(
}
internal fun Provider<String>.toDir(project: Project): Provider<Directory> =
project.layout.dir(map { File(it) })
project.layout.dir(map { File(it) })
val Project.localPropertiesFile get() = project.rootProject.file("local.properties")
fun Project.getLocalProperty(key: String): String? {
if (localPropertiesFile.exists()) {
val properties = Properties()
localPropertiesFile.inputStream().buffered().use { input ->
properties.load(input)
}
return properties.getProperty(key)
} else {
localPropertiesFile.createNewFile()
return null
}
}

Loading…
Cancel
Save