feat(core): add config for Info.plist extensions, closes #13667 (#14108)

* feat(core): add config for Info.plist extensions, closes #13667

* add missing tag

* do not lie :)
This commit is contained in:
Lucas Fernandes Nogueira 2025-10-06 14:05:33 -03:00 committed by GitHub
parent abf7e8850b
commit ed7c9a4100
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 182 additions and 62 deletions

View File

@ -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.

View File

@ -0,0 +1,5 @@
---
"tauri-bundler": minor:breaking
---
Changed `MacOsSettings::info_plist_path` to `MacOsSettings::info_plist`.

View File

@ -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 {

View File

@ -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]

View File

@ -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};

View File

@ -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);

View File

@ -362,8 +362,17 @@ pub struct MacOsSettings {
pub provider_short_name: Option<String>,
/// Path to the entitlements.plist file.
pub entitlements: Option<String>,
/// Path to the Info.plist file for the bundle.
pub info_plist_path: Option<PathBuf>,
/// Path to the Info.plist file or raw plist value to merge with the bundle Info.plist.
pub info_plist: Option<PlistKind>,
}
/// 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.

View File

@ -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

View File

@ -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;

View File

@ -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<PathBuf> for PlistKind {
fn from(p: PathBuf) -> Self {
Self::Path(p)
}
}
impl From<plist::Value> for PlistKind {
fn from(p: plist::Value) -> Self {
Self::Plist(p)
}
}
pub fn merge_plist(src: Vec<PlistKind>) -> crate::Result<plist::Value> {
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))
}

View File

@ -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 {

View File

@ -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(),
])?;

View File

@ -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::{
@ -218,11 +219,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)

View File

@ -488,42 +488,6 @@ fn inject_resources(config: &AppleConfig, tauri_config: &TauriConfig) -> Result<
Ok(())
}
enum PlistKind {
Path(PathBuf),
Plist(plist::Value),
}
impl From<PathBuf> for PlistKind {
fn from(p: PathBuf) -> Self {
Self::Path(p)
}
}
impl From<plist::Value> for PlistKind {
fn from(p: plist::Value) -> Self {
Self::Plist(p)
}
}
fn merge_plist(src: Vec<PlistKind>) -> Result<plist::Value> {
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<tauri_macos_sign::Keychain>,
Option<tauri_macos_sign::ProvisioningProfile>,

View File

@ -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

View File

@ -664,6 +664,11 @@ pub struct MacConfig {
pub provider_short_name: Option<String>,
/// Path to the entitlements file.
pub entitlements: Option<String>,
/// 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<PathBuf>,
/// 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<PathBuf>,
}
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,
}
}
}