From 9a98abe5ad39c09373cff58b724d8747537da2c0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 11 Aug 2025 15:18:14 -0700 Subject: [PATCH] allow customizing entitlements (#4533) * allow customizing entitlements * fix printing issues --- packages/cli/src/build/builder.rs | 143 +++++++++++++++++++----------- packages/cli/src/build/request.rs | 4 + packages/cli/src/cli/target.rs | 19 ++++ 3 files changed, 115 insertions(+), 51 deletions(-) diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index f545794a3..5635561ec 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -977,7 +977,10 @@ impl AppBuilder { .await?; if !output.status.success() { - bail!("Failed to install app: {output:?}"); + bail!( + "Failed to install app: {}", + String::from_utf8_lossy(&output.stderr) + ); } let json: Value = serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?) @@ -1047,6 +1050,92 @@ impl AppBuilder { } async fn codesign_ios(&self) -> Result<()> { + // We don't want to drop the entitlements file, until the end of the block, so we hoist it to this temporary. + let mut _saved_entitlements = None; + + let mut app_dev_name = self.build.apple_team_id.clone(); + if app_dev_name.is_none() { + app_dev_name = + Some(self.auto_provision_signing_name().await.context( + "Failed to automatically provision signing name for iOS codesigning.", + )?); + } + + let mut entitlements_file = self.build.apple_entitlements.clone(); + if entitlements_file.is_none() { + let entitlements_xml = self + .auto_provision_entitlements() + .await + .context("Failed to auto-provision entitlements for iOS codesigning.")?; + let entitlements_temp_file = tempfile::NamedTempFile::new()?; + std::fs::write(entitlements_temp_file.path(), entitlements_xml)?; + entitlements_file = Some(entitlements_temp_file.path().to_path_buf()); + _saved_entitlements = Some(entitlements_temp_file); + } + + let entitlements_file = entitlements_file.as_ref().context( + "No entitlements file provided and could not provision entitlements to sign app.", + )?; + let app_dev_name = app_dev_name.as_ref().context( + "No Apple Development signing name provided and could not auto-provision one.", + )?; + + tracing::debug!( + "Codesigning iOS app with entitlements: {} and dev name: {}", + entitlements_file.display(), + app_dev_name + ); + + // codesign the app + let output = Command::new("codesign") + .args([ + "--force", + "--entitlements", + entitlements_file.to_str().unwrap(), + "--sign", + app_dev_name, + ]) + .arg(self.build.root_dir()) + .output() + .await + .context("Failed to codesign the app - is `codesign` in your path?")?; + + if !output.status.success() { + bail!( + "Failed to codesign the app: {}", + String::from_utf8(output.stderr).unwrap_or_default() + ); + } + + Ok(()) + } + + async fn auto_provision_signing_name(&self) -> Result { + let identities = Command::new("security") + .args(["find-identity", "-v", "-p", "codesigning"]) + .output() + .await + .context("Failed to run `security find-identity -v -p codesigning` - is `security` in your path?") + .map(|e| { + String::from_utf8(e.stdout) + .context("Failed to parse `security find-identity -v -p codesigning`") + })??; + + // Parsing this: + // 1231231231231asdasdads123123 "Apple Development: foo@gmail.com (XYZYZY)" + let app_dev_name = regex::Regex::new(r#""Apple Development: (.+)""#) + .unwrap() + .captures(&identities) + .and_then(|caps| caps.get(1)) + .map(|m| m.as_str()) + .context( + "Failed to find Apple Development in `security find-identity -v -p codesigning`", + )?; + + Ok(app_dev_name.to_string()) + } + + async fn auto_provision_entitlements(&self) -> Result { const CODESIGN_ERROR: &str = r#"This is likely because you haven't - Created a provisioning profile before - Accepted the Apple Developer Program License Agreement @@ -1081,27 +1170,6 @@ We checked the folders: ) } - let identities = Command::new("security") - .args(["find-identity", "-v", "-p", "codesigning"]) - .output() - .await - .context("Failed to run `security find-identity -v -p codesigning` - is `security` in your path?") - .map(|e| { - String::from_utf8(e.stdout) - .context("Failed to parse `security find-identity -v -p codesigning`") - })??; - - // Parsing this: - // 1231231231231asdasdads123123 "Apple Development: foo@gmail.com (XYZYZY)" - let app_dev_name = regex::Regex::new(r#""Apple Development: (.+)""#) - .unwrap() - .captures(&identities) - .and_then(|caps| caps.get(1)) - .map(|m| m.as_str()) - .context( - "Failed to find Apple Development in `security find-identity -v -p codesigning`", - )?; - // Acquire the provision file let provision_file = profiles_folder .read_dir()? @@ -1152,7 +1220,7 @@ We checked the folders: keychain_access_groups: Vec, } - let entielements_xml = format!( + Ok(format!( r#" @@ -1172,34 +1240,7 @@ We checked the folders: APPLICATION_IDENTIFIER = mbfile.entitlements.application_identifier, APP_ID_ACCESS_GROUP = mbfile.entitlements.keychain_access_groups[0], TEAM_IDENTIFIER = mbfile.team_identifier[0], - ); - - // write to a temp file - let temp_file = tempfile::NamedTempFile::new()?; - std::fs::write(temp_file.path(), entielements_xml)?; - - // codesign the app - let output = Command::new("codesign") - .args([ - "--force", - "--entitlements", - temp_file.path().to_str().unwrap(), - "--sign", - app_dev_name, - ]) - .arg(self.build.root_dir()) - .output() - .await - .context("Failed to codesign the app - is `codesign` in your path?")?; - - if !output.status.success() { - bail!( - "Failed to codesign the app: {}", - String::from_utf8(output.stderr).unwrap_or_default() - ); - } - - Ok(()) + )) } /// Launch the Android simulator and deploy the application. diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index a6c5ace20..07a7a1723 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -395,6 +395,8 @@ pub(crate) struct BuildRequest { pub(crate) command_file: Arc, pub(crate) base_path: Option, pub(crate) using_dioxus_explicitly: bool, + pub(crate) apple_entitlements: Option, + pub(crate) apple_team_id: Option, } /// dx can produce different "modes" of a build. A "regular" build is a "base" build. The Fat and Thin @@ -826,6 +828,8 @@ impl BuildRequest { wasm_split: args.wasm_split, debug_symbols: args.debug_symbols, inject_loading_scripts: args.inject_loading_scripts, + apple_entitlements: args.apple_entitlements.clone(), + apple_team_id: args.apple_team_id.clone(), }) } diff --git a/packages/cli/src/cli/target.rs b/packages/cli/src/cli/target.rs index 0e047e096..b6f537c57 100644 --- a/packages/cli/src/cli/target.rs +++ b/packages/cli/src/cli/target.rs @@ -117,6 +117,25 @@ pub(crate) struct TargetArgs { /// base path set in the `dioxus` config. #[clap(long, help_heading = HELP_HEADING)] pub(crate) base_path: Option, + + /// The path to the Apple entitlements file to used to sign the resulting app bundle. + /// + /// On iOS, this is required for deploy to a device and some configurations in the simulator. + #[clap(long, help_heading = HELP_HEADING)] + pub(crate) apple_entitlements: Option, + + /// The Apple team ID to use when signing the app bundle. + /// + /// Usually this is an email or name associated with your Apple Developer account, usually in the + /// format `Signing Name (GXTEAMID123)`. + /// + /// This is passed directly to the `codesign` tool. + /// + /// ``` + /// codesign --force --entitlements --sign + /// ``` + #[clap(long, help_heading = HELP_HEADING)] + pub(crate) apple_team_id: Option, } impl Anonymized for TargetArgs {