Skip to content

Commit

Permalink
Simplify resource management for iOS (#3340)
Browse files Browse the repository at this point in the history
* Simplify resource management for iOS

Introduces new a new task 'sync<FRAMEWORK_CLASSIFIER>ComposeIosResources',
which collects resources from all source sets, included in iOS targets.

With this change:
* CocoaPods integration does not require any configuration or calling 'pod install' after changing resources.
    * Important: existing projects need to remove 'extraSpecAttributes["resources"] = ...' from build scripts, and rerun `./gradlew podInstall` once!
* Without CocoaPods, the resource directory should be added to XCode build phases once.

Resolves #3073
Resolves #3113
Resolves #3066
  • Loading branch information
AlexeyTsvetkov authored Jul 18, 2023
1 parent 1346fee commit 16114b2
Show file tree
Hide file tree
Showing 22 changed files with 554 additions and 109 deletions.
2 changes: 1 addition & 1 deletion components/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ kotlin.code.style=official
# __KOTLIN_COMPOSE_VERSION__
kotlin.version=1.8.20
# __LATEST_COMPOSE_RELEASE_VERSION__
compose.version=1.4.1
compose.version=0.0.0-dev1101
agp.version=7.3.1
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
Expand Down
6 changes: 0 additions & 6 deletions components/resources/demo/iosApp/Podfile

This file was deleted.

56 changes: 18 additions & 38 deletions components/resources/demo/iosApp/iosApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@

/* Begin PBXBuildFile section */
2152FB042600AC8F00CF470E /* iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iosApp.swift */; };
C1FC908188C4E8695729CB06 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DE96E47030356CE6AD9794A /* Pods_iosApp.framework */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
1EB65E27D2C0F884D0A1A133 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = "<group>"; };
2152FB032600AC8F00CF470E /* iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosApp.swift; sourceTree = "<group>"; };
3D7A606AB0AD7636269BD9D0 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = "<group>"; };
7555FF7B242A565900829871 /* ResourcesDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ResourcesDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8DE96E47030356CE6AD9794A /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AB3632DC29227652001CCB65 /* TeamId.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = TeamId.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand All @@ -26,7 +22,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C1FC908188C4E8695729CB06 /* Pods_iosApp.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -72,16 +67,13 @@
B62309C7396AD7BF607A63B2 /* Frameworks */ = {
isa = PBXGroup;
children = (
8DE96E47030356CE6AD9794A /* Pods_iosApp.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
E1DAFBE8E1CFC0878361EF0E /* Pods */ = {
isa = PBXGroup;
children = (
1EB65E27D2C0F884D0A1A133 /* Pods-iosApp.debug.xcconfig */,
3D7A606AB0AD7636269BD9D0 /* Pods-iosApp.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
Expand All @@ -93,11 +85,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
buildPhases = (
E8D673591E7196AEA2EA10E2 /* [CP] Check Pods Manifest.lock */,
05FBB7462A65505400D51BB4 /* Compile Kotlin */,
7555FF77242A565900829871 /* Sources */,
7555FF79242A565900829871 /* Resources */,
9964867F0862B4D9FB6ABFC7 /* Frameworks */,
A51DDDB74597C98E89765935 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
Expand Down Expand Up @@ -152,44 +143,23 @@
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
A51DDDB74597C98E89765935 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n";
showEnvVarsInLog = 0;
};
E8D673591E7196AEA2EA10E2 /* [CP] Check Pods Manifest.lock */ = {
05FBB7462A65505400D51BB4 /* Compile Kotlin */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
name = "Compile Kotlin";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
shellScript = "cd \"$SRCROOT/..\"\n../.././gradlew :resources:demo:shared:embedAndSignAppleFrameworkForXcode\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down Expand Up @@ -325,22 +295,27 @@
};
7555FFA6242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1EB65E27D2C0F884D0A1A133 /* Pods-iosApp.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "${TEAM_ID}";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = iosApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
shared,
);
PRODUCT_BUNDLE_IDENTIFIER = "org.jetbrains.ResourcesDemo${TEAM_ID}";
PRODUCT_NAME = "ResourcesDemo";
PRODUCT_NAME = ResourcesDemo;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand All @@ -349,22 +324,27 @@
};
7555FFA7242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3D7A606AB0AD7636269BD9D0 /* Pods-iosApp.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "${TEAM_ID}";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n";
INFOPLIST_FILE = iosApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
shared,
);
PRODUCT_BUNDLE_IDENTIFIER = "org.jetbrains.ResourcesDemo${TEAM_ID}";
PRODUCT_NAME = "ResourcesDemo";
PRODUCT_NAME = ResourcesDemo;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down
46 changes: 28 additions & 18 deletions components/resources/demo/shared/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
id("org.jetbrains.compose")
}
Expand All @@ -10,8 +9,16 @@ version = "1.0-SNAPSHOT"
kotlin {
android()
jvm("desktop")
ios()
iosSimulatorArm64()
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "shared"
isStatic = true
}
}
js(IR) {
browser()
binaries.executable()
Expand All @@ -31,18 +38,6 @@ kotlin {
}
}

cocoapods {
summary = "Shared code for the sample"
homepage = "https://github.com/JetBrains/compose-jb"
ios.deploymentTarget = "14.1"
podfile = project.file("../iosApp/Podfile")
framework {
baseName = "shared"
isStatic = true
}
extraSpecAttributes["resources"] = "['src/commonMain/resources/**', 'src/iosMain/resources/**']"
}

sourceSets {
val commonMain by getting {
dependencies {
Expand All @@ -53,13 +48,28 @@ kotlin {
implementation(project(":resources:library"))
}
}
val iosMain by getting
val iosTest by getting
val iosMain by creating {
dependsOn(commonMain)
}
val iosTest by creating {
}
val iosX64Main by getting {
dependsOn(iosMain)
}
val iosArm64Main by getting {
dependsOn(iosMain)
}
val iosSimulatorArm64Main by getting {
dependsOn(iosMain)
}
val iosX64Test by getting {
dependsOn(iosMain)
}
val iosArm64Test by getting {
dependsOn(iosMain)
}
val iosSimulatorArm64Test by getting {
dependsOn(iosTest)
dependsOn(iosMain)
}
val desktopMain by getting {
dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Application
import androidx.compose.ui.window.ComposeUIViewController
import org.jetbrains.compose.resources.demo.shared.UseResources
import platform.UIKit.UIViewController

fun MainViewController(): UIViewController =
Application("Resources demo") {
fun MainViewController() =
ComposeUIViewController {
Column {
Box(
modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ actual fun resource(path: String): Resource = UIKitResourceImpl(path)
@ExperimentalResourceApi
private class UIKitResourceImpl(path: String) : AbstractResourceImpl(path) {
override suspend fun readBytes(): ByteArray {
val absolutePath = NSBundle.mainBundle.resourcePath + "/" + path
val contentsAtPath: NSData? = NSFileManager.defaultManager().contentsAtPath(absolutePath)
val fileManager = NSFileManager.defaultManager()
// todo: support fallback path at bundle root?
val composeResourcesPath = NSBundle.mainBundle.resourcePath + "/compose-resources/" + path
val contentsAtPath: NSData? = fileManager.contentsAtPath(composeResourcesPath)
if (contentsAtPath != null) {
val byteArray = ByteArray(contentsAtPath.length.toInt())
byteArray.usePinned {
Expand Down
4 changes: 2 additions & 2 deletions examples/imageviewer/shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ kotlin {
baseName = "shared"
isStatic = true
}
extraSpecAttributes["resources"] =
"['src/commonMain/resources/**', 'src/iosMain/resources/**']"
extraSpecAttributes["resources"] = "['src/commonMain/resources/**', 'src/iosMain/resources/**']"

}

sourceSets {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ package org.jetbrains.compose
import groovy.lang.Closure
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.ComponentMetadataContext
import org.gradle.api.artifacts.ComponentMetadataRule
import org.gradle.api.artifacts.dsl.ComponentModuleMetadataHandler
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
Expand All @@ -24,6 +21,9 @@ import org.jetbrains.compose.desktop.preview.internal.initializePreview
import org.jetbrains.compose.experimental.dsl.ExperimentalExtension
import org.jetbrains.compose.experimental.internal.configureExperimentalTargetsFlagsCheck
import org.jetbrains.compose.experimental.internal.configureExperimental
import org.jetbrains.compose.experimental.uikit.internal.resources.configureSyncTask
import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID
import org.jetbrains.compose.internal.mppExt
import org.jetbrains.compose.internal.utils.currentTarget
import org.jetbrains.compose.web.WebExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
Expand Down Expand Up @@ -53,7 +53,11 @@ class ComposePlugin : Plugin<Project> {
project.afterEvaluate {
configureDesktop(project, desktopExtension)
project.configureExperimental(composeExtension, experimentalExtension)
project.configureExperimentalTargetsFlagsCheck()
project.plugins.withId(KOTLIN_MPP_PLUGIN_ID) {
val mppExt = project.mppExt
project.configureExperimentalTargetsFlagsCheck(mppExt)
project.configureSyncTask(mppExt)
}

project.tasks.withType(KotlinCompile::class.java).configureEach {
it.kotlinOptions.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package org.jetbrains.compose.desktop.application.internal

import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.jetbrains.compose.internal.utils.findProperty
import org.jetbrains.compose.internal.utils.toBooleanProvider

internal object ComposeProperties {
internal const val VERBOSE = "compose.desktop.verbose"
Expand All @@ -20,13 +22,13 @@ internal object ComposeProperties {
internal const val MAC_NOTARIZATION_ASC_PROVIDER = "compose.desktop.mac.notarization.ascProvider"

fun isVerbose(providers: ProviderFactory): Provider<Boolean> =
providers.findProperty(VERBOSE).toBoolean()
providers.findProperty(VERBOSE).toBooleanProvider(false)

fun preserveWorkingDir(providers: ProviderFactory): Provider<Boolean> =
providers.findProperty(PRESERVE_WD).toBoolean()
providers.findProperty(PRESERVE_WD).toBooleanProvider(false)

fun macSign(providers: ProviderFactory): Provider<Boolean> =
providers.findProperty(MAC_SIGN).toBoolean()
providers.findProperty(MAC_SIGN).toBooleanProvider(false)

fun macSignIdentity(providers: ProviderFactory): Provider<String?> =
providers.findProperty(MAC_SIGN_ID)
Expand All @@ -45,20 +47,4 @@ internal object ComposeProperties {

fun macNotarizationAscProvider(providers: ProviderFactory): Provider<String?> =
providers.findProperty(MAC_NOTARIZATION_ASC_PROVIDER)

private fun ProviderFactory.findProperty(prop: String): Provider<String?> =
provider {
gradleProperty(prop).forUseAtConfigurationTimeSafe().orNull
}

private fun Provider<String?>.forUseAtConfigurationTimeSafe(): Provider<String?> =
try {
forUseAtConfigurationTime()
} catch (e: NoSuchMethodError) {
// todo: remove once we drop support for Gradle 6.4
this
}

private fun Provider<String?>.toBoolean(): Provider<Boolean> =
orElse("false").map { "true" == it }
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ abstract class AbstractConfigureDesktopPreviewTask : AbstractComposeDesktopTask(
@get:Optional
internal val jvmArgs: ListProperty<String> = objects.listProperty(String::class.java)

@get:Optional
@get:Input
internal val previewTarget: Provider<String> =
project.providers.gradleProperty("compose.desktop.preview.target")

@get:Optional
@get:Input
internal val idePort: Provider<String> =
project.providers.gradleProperty("compose.desktop.preview.ide.port")
Expand Down
Loading

0 comments on commit 16114b2

Please sign in to comment.