diff --git a/.changes/info-plist-config.md b/.changes/info-plist-config.md new file mode 100644 index 000000000000..cafab53c8132 --- /dev/null +++ b/.changes/info-plist-config.md @@ -0,0 +1,8 @@ +--- +"tauri-utils": minor:feat +"tauri-cli": minor:feat +"@tauri-apps/cli": minor:feat +--- + +Added `bundle > macOS > infoPlist` and `bundle > iOS > infoPlist` configurations to allow defining custom Info.plist extensions. + diff --git a/.changes/refactor-info-plist.md b/.changes/refactor-info-plist.md new file mode 100644 index 000000000000..734969f27d03 --- /dev/null +++ b/.changes/refactor-info-plist.md @@ -0,0 +1,5 @@ +--- +"tauri-bundler": minor:breaking +--- + +Changed `MacOsSettings::info_plist_path` to `MacOsSettings::info_plist`. diff --git a/crates/tauri-build/src/codegen/context.rs b/crates/tauri-build/src/codegen/context.rs index 224857e100cc..d130899cd017 100644 --- a/crates/tauri-build/src/codegen/context.rs +++ b/crates/tauri-build/src/codegen/context.rs @@ -120,6 +120,13 @@ impl CodegenContext { if info_plist_path.exists() { println!("cargo:rerun-if-changed={}", info_plist_path.display()); } + + if let Some(plist_path) = &config.bundle.macos.info_plist { + let info_plist_path = config_parent.join(plist_path); + if info_plist_path.exists() { + println!("cargo:rerun-if-changed={}", info_plist_path.display()); + } + } } let code = context_codegen(ContextData { diff --git a/crates/tauri-bundler/Cargo.toml b/crates/tauri-bundler/Cargo.toml index d625da5bae06..707775d2a553 100644 --- a/crates/tauri-bundler/Cargo.toml +++ b/crates/tauri-bundler/Cargo.toml @@ -44,6 +44,7 @@ url = "2" uuid = { version = "1", features = ["v4", "v5"] } regex = "1" goblin = "0.9" +plist = "1" [target."cfg(target_os = \"windows\")".dependencies] bitness = "0.4" @@ -57,7 +58,6 @@ features = ["Win32_System_SystemInformation", "Win32_System_Diagnostics_Debug"] [target."cfg(target_os = \"macos\")".dependencies] icns = { package = "tauri-icns", version = "0.1" } time = { version = "0.3", features = ["formatting"] } -plist = "1" tauri-macos-sign = { version = "2.2.0", path = "../tauri-macos-sign" } [target."cfg(target_os = \"linux\")".dependencies] diff --git a/crates/tauri-bundler/src/bundle.rs b/crates/tauri-bundler/src/bundle.rs index ad4852f38580..dae3ba71d4bb 100644 --- a/crates/tauri-bundler/src/bundle.rs +++ b/crates/tauri-bundler/src/bundle.rs @@ -45,8 +45,8 @@ pub use self::{ category::AppCategory, settings::{ AppImageSettings, BundleBinary, BundleSettings, CustomSignCommandSettings, DebianSettings, - DmgSettings, IosSettings, MacOsSettings, PackageSettings, PackageType, Position, RpmSettings, - Settings, SettingsBuilder, Size, UpdaterSettings, + DmgSettings, IosSettings, MacOsSettings, PackageSettings, PackageType, PlistKind, Position, + RpmSettings, Settings, SettingsBuilder, Size, UpdaterSettings, }, }; pub use settings::{NsisSettings, WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings}; diff --git a/crates/tauri-bundler/src/bundle/macos/app.rs b/crates/tauri-bundler/src/bundle/macos/app.rs index 87e900ac9203..7e6e6e5648fa 100644 --- a/crates/tauri-bundler/src/bundle/macos/app.rs +++ b/crates/tauri-bundler/src/bundle/macos/app.rs @@ -27,6 +27,7 @@ use super::{ sign::{notarize, notarize_auth, notarize_without_stapling, sign, SignTarget}, }; use crate::{ + bundle::settings::PlistKind, error::{Context, ErrorExt, NotarizeAuthError}, utils::{fs_utils, CommandExt}, Error::GenericError, @@ -361,8 +362,11 @@ fn create_info_plist( plist.insert("NSAppTransportSecurity".into(), security.into()); } - if let Some(user_plist_path) = &settings.macos().info_plist_path { - let user_plist = plist::Value::from_file(user_plist_path)?; + if let Some(user_plist) = &settings.macos().info_plist { + let user_plist = match user_plist { + PlistKind::Path(path) => plist::Value::from_file(path)?, + PlistKind::Plist(value) => value.clone(), + }; if let Some(dict) = user_plist.into_dictionary() { for (key, value) in dict { plist.insert(key, value); diff --git a/crates/tauri-bundler/src/bundle/settings.rs b/crates/tauri-bundler/src/bundle/settings.rs index be22a94a7d1c..4b3165cc5409 100644 --- a/crates/tauri-bundler/src/bundle/settings.rs +++ b/crates/tauri-bundler/src/bundle/settings.rs @@ -362,8 +362,17 @@ pub struct MacOsSettings { pub provider_short_name: Option, /// Path to the entitlements.plist file. pub entitlements: Option, - /// Path to the Info.plist file for the bundle. - pub info_plist_path: Option, + /// Path to the Info.plist file or raw plist value to merge with the bundle Info.plist. + pub info_plist: Option, +} + +/// Plist format. +#[derive(Debug, Clone)] +pub enum PlistKind { + /// Path to a .plist file. + Path(PathBuf), + /// Raw plist value. + Plist(plist::Value), } /// Configuration for a target language for the WiX build. diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index 8b97ad39540c..b84ac6d358dd 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -3572,6 +3572,13 @@ "null" ] }, + "infoPlist": { + "description": "Path to a Info.plist file to merge with the default Info.plist.\n\n Note that Tauri also looks for a `Info.plist` file in the same directory as the Tauri configuration file.", + "type": [ + "string", + "null" + ] + }, "dmg": { "description": "DMG-specific settings.", "default": { @@ -3743,6 +3750,13 @@ "description": "A version string indicating the minimum iOS version that the bundled application supports. Defaults to `13.0`.\n\n Maps to the IPHONEOS_DEPLOYMENT_TARGET value.", "default": "14.0", "type": "string" + }, + "infoPlist": { + "description": "Path to a Info.plist file to merge with the default Info.plist.\n\n Note that Tauri also looks for a `Info.plist` and `Info.ios.plist` file in the same directory as the Tauri configuration file.", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/crates/tauri-cli/src/helpers/mod.rs b/crates/tauri-cli/src/helpers/mod.rs index 2381286864e6..06ff052cebc0 100644 --- a/crates/tauri-cli/src/helpers/mod.rs +++ b/crates/tauri-cli/src/helpers/mod.rs @@ -13,6 +13,8 @@ pub mod http; pub mod npm; #[cfg(target_os = "macos")] pub mod pbxproj; +#[cfg(target_os = "macos")] +pub mod plist; pub mod plugins; pub mod prompts; pub mod template; diff --git a/crates/tauri-cli/src/helpers/plist.rs b/crates/tauri-cli/src/helpers/plist.rs new file mode 100644 index 000000000000..6e689d99e24c --- /dev/null +++ b/crates/tauri-cli/src/helpers/plist.rs @@ -0,0 +1,42 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use crate::error::Context; + +pub enum PlistKind { + Path(PathBuf), + Plist(plist::Value), +} + +impl From for PlistKind { + fn from(p: PathBuf) -> Self { + Self::Path(p) + } +} +impl From for PlistKind { + fn from(p: plist::Value) -> Self { + Self::Plist(p) + } +} + +pub fn merge_plist(src: Vec) -> crate::Result { + let mut merged_plist = plist::Dictionary::new(); + + for plist_kind in src { + let src_plist = match plist_kind { + PlistKind::Path(p) => plist::Value::from_file(&p) + .with_context(|| format!("failed to parse plist from {}", p.display()))?, + PlistKind::Plist(v) => v, + }; + if let Some(dict) = src_plist.into_dictionary() { + for (key, value) in dict { + merged_plist.insert(key, value); + } + } + } + + Ok(plist::Value::Dictionary(merged_plist)) +} diff --git a/crates/tauri-cli/src/interface/rust.rs b/crates/tauri-cli/src/interface/rust.rs index 2ccd73648357..b6e176e85c5f 100644 --- a/crates/tauri-cli/src/interface/rust.rs +++ b/crates/tauri-cli/src/interface/rust.rs @@ -1488,13 +1488,23 @@ fn tauri_config_to_bundle_settings( hardened_runtime: config.macos.hardened_runtime, provider_short_name, entitlements: config.macos.entitlements, - info_plist_path: { + #[cfg(not(target_os = "macos"))] + info_plist: None, + #[cfg(target_os = "macos")] + info_plist: { + let mut src_plists = vec![]; + let path = tauri_dir().join("Info.plist"); if path.exists() { - Some(path) - } else { - None + src_plists.push(path.into()); + } + if let Some(info_plist) = &config.macos.info_plist { + src_plists.push(info_plist.clone().into()); } + + Some(tauri_bundler::bundle::PlistKind::Plist( + crate::helpers::plist::merge_plist(src_plists)?, + )) }, }, windows: WindowsSettings { diff --git a/crates/tauri-cli/src/mobile/ios/build.rs b/crates/tauri-cli/src/mobile/ios/build.rs index 712e7d0a827e..cacab1d784ec 100644 --- a/crates/tauri-cli/src/mobile/ios/build.rs +++ b/crates/tauri-cli/src/mobile/ios/build.rs @@ -4,8 +4,8 @@ use super::{ detect_target_ok, ensure_init, env, get_app, get_config, inject_resources, load_pbxproj, - log_finished, merge_plist, open_and_wait, project_config, synchronize_project_config, - MobileTarget, OptionsHandle, + log_finished, open_and_wait, project_config, synchronize_project_config, MobileTarget, + OptionsHandle, }; use crate::{ build::Options as BuildOptions, @@ -14,6 +14,7 @@ use crate::{ app_paths::tauri_dir, config::{get as get_tauri_config, ConfigHandle}, flock, + plist::merge_plist, }, interface::{AppInterface, Interface, Options as InterfaceOptions}, mobile::{ios::ensure_ios_runtime_installed, write_options, CliOptions}, @@ -215,12 +216,26 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { .project_dir() .join(config.scheme()) .join("Info.plist"); - let merged_info_plist = merge_plist(vec![ - info_plist_path.clone().into(), - tauri_path.join("Info.plist").into(), - tauri_path.join("Info.ios.plist").into(), - plist::Value::Dictionary(plist).into(), - ])?; + let mut src_plists = vec![info_plist_path.clone().into()]; + src_plists.push(plist::Value::Dictionary(plist).into()); + if tauri_path.join("Info.plist").exists() { + src_plists.push(tauri_path.join("Info.plist").into()); + } + if tauri_path.join("Info.ios.plist").exists() { + src_plists.push(tauri_path.join("Info.ios.plist").into()); + } + if let Some(info_plist) = &tauri_config + .lock() + .unwrap() + .as_ref() + .unwrap() + .bundle + .ios + .info_plist + { + src_plists.push(info_plist.clone().into()); + } + let merged_info_plist = merge_plist(src_plists)?; merged_info_plist .to_file_xml(&info_plist_path) .map_err(std::io::Error::other) @@ -283,7 +298,6 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { tempfile::NamedTempFile::new().context("failed to create temporary file")?; let merged_plist = merge_plist(vec![ - export_options.path().to_owned().into(), export_options_plist_path.clone().into(), plist::Value::from(export_options_plist).into(), ])?; diff --git a/crates/tauri-cli/src/mobile/ios/dev.rs b/crates/tauri-cli/src/mobile/ios/dev.rs index 406a72a0b211..218f7c64deff 100644 --- a/crates/tauri-cli/src/mobile/ios/dev.rs +++ b/crates/tauri-cli/src/mobile/ios/dev.rs @@ -4,7 +4,7 @@ use super::{ device_prompt, ensure_init, env, get_app, get_config, inject_resources, load_pbxproj, - merge_plist, open_and_wait, synchronize_project_config, MobileTarget, ProjectConfig, + open_and_wait, synchronize_project_config, MobileTarget, ProjectConfig, }; use crate::{ dev::Options as DevOptions, @@ -13,6 +13,7 @@ use crate::{ app_paths::tauri_dir, config::{get as get_tauri_config, ConfigHandle}, flock, + plist::merge_plist, }, interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions}, mobile::{ @@ -217,11 +218,25 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { .project_dir() .join(config.scheme()) .join("Info.plist"); - let merged_info_plist = merge_plist(vec![ - info_plist_path.clone().into(), - tauri_path.join("Info.plist").into(), - tauri_path.join("Info.ios.plist").into(), - ])?; + let mut src_plists = vec![info_plist_path.clone().into()]; + if tauri_path.join("Info.plist").exists() { + src_plists.push(tauri_path.join("Info.plist").into()); + } + if tauri_path.join("Info.ios.plist").exists() { + src_plists.push(tauri_path.join("Info.ios.plist").into()); + } + if let Some(info_plist) = &tauri_config + .lock() + .unwrap() + .as_ref() + .unwrap() + .bundle + .ios + .info_plist + { + src_plists.push(info_plist.clone().into()); + } + let merged_info_plist = merge_plist(src_plists)?; merged_info_plist .to_file_xml(&info_plist_path) .map_err(std::io::Error::other) diff --git a/crates/tauri-cli/src/mobile/ios/mod.rs b/crates/tauri-cli/src/mobile/ios/mod.rs index 64181b8653e0..c01ebc693739 100644 --- a/crates/tauri-cli/src/mobile/ios/mod.rs +++ b/crates/tauri-cli/src/mobile/ios/mod.rs @@ -488,42 +488,6 @@ fn inject_resources(config: &AppleConfig, tauri_config: &TauriConfig) -> Result< Ok(()) } -enum PlistKind { - Path(PathBuf), - Plist(plist::Value), -} - -impl From for PlistKind { - fn from(p: PathBuf) -> Self { - Self::Path(p) - } -} -impl From for PlistKind { - fn from(p: plist::Value) -> Self { - Self::Plist(p) - } -} - -fn merge_plist(src: Vec) -> Result { - let mut merged_plist = plist::Dictionary::new(); - - for plist_kind in src { - let plist = match plist_kind { - PlistKind::Path(p) => plist::Value::from_file(p).context("failed to read plist file"), - PlistKind::Plist(v) => Ok(v), - }; - if let Ok(src_plist) = plist { - if let Some(dict) = src_plist.into_dictionary() { - for (key, value) in dict { - merged_plist.insert(key, value); - } - } - } - } - - Ok(plist::Value::Dictionary(merged_plist)) -} - pub fn signing_from_env() -> Result<( Option, Option, diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index 8b97ad39540c..b84ac6d358dd 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -3572,6 +3572,13 @@ "null" ] }, + "infoPlist": { + "description": "Path to a Info.plist file to merge with the default Info.plist.\n\n Note that Tauri also looks for a `Info.plist` file in the same directory as the Tauri configuration file.", + "type": [ + "string", + "null" + ] + }, "dmg": { "description": "DMG-specific settings.", "default": { @@ -3743,6 +3750,13 @@ "description": "A version string indicating the minimum iOS version that the bundled application supports. Defaults to `13.0`.\n\n Maps to the IPHONEOS_DEPLOYMENT_TARGET value.", "default": "14.0", "type": "string" + }, + "infoPlist": { + "description": "Path to a Info.plist file to merge with the default Info.plist.\n\n Note that Tauri also looks for a `Info.plist` and `Info.ios.plist` file in the same directory as the Tauri configuration file.", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/crates/tauri-utils/src/config.rs b/crates/tauri-utils/src/config.rs index 3891220b7eee..a42ae99b8d89 100644 --- a/crates/tauri-utils/src/config.rs +++ b/crates/tauri-utils/src/config.rs @@ -664,6 +664,11 @@ pub struct MacConfig { pub provider_short_name: Option, /// Path to the entitlements file. pub entitlements: Option, + /// Path to a Info.plist file to merge with the default Info.plist. + /// + /// Note that Tauri also looks for a `Info.plist` file in the same directory as the Tauri configuration file. + #[serde(alias = "info-plist")] + pub info_plist: Option, /// DMG-specific settings. #[serde(default)] pub dmg: DmgConfig, @@ -682,6 +687,7 @@ impl Default for MacConfig { hardened_runtime: true, provider_short_name: None, entitlements: None, + info_plist: None, dmg: Default::default(), } } @@ -2850,6 +2856,11 @@ pub struct IosConfig { default = "ios_minimum_system_version" )] pub minimum_system_version: String, + /// Path to a Info.plist file to merge with the default Info.plist. + /// + /// Note that Tauri also looks for a `Info.plist` and `Info.ios.plist` file in the same directory as the Tauri configuration file. + #[serde(alias = "info-plist")] + pub info_plist: Option, } impl Default for IosConfig { @@ -2860,6 +2871,7 @@ impl Default for IosConfig { development_team: None, bundle_version: None, minimum_system_version: ios_minimum_system_version(), + info_plist: None, } } }