diff --git a/.changes/cli-multiple-config.md b/.changes/cli-multiple-config.md new file mode 100644 index 000000000..6f0882529 --- /dev/null +++ b/.changes/cli-multiple-config.md @@ -0,0 +1,6 @@ +--- +"@tauri-apps/cli": minor:feat +"tauri-cli": minor:feat +--- + +Allow merging multiple configuration values on `tauri dev`, `tauri build`, `tauri bundle`, `tauri android dev`, `tauri android build`, `tauri ios dev` and `tauri ios build`. diff --git a/crates/tauri-cli/src/build.rs b/crates/tauri-cli/src/build.rs index 52d30262c..6f905b951 100644 --- a/crates/tauri-cli/src/build.rs +++ b/crates/tauri-cli/src/build.rs @@ -47,9 +47,15 @@ pub struct Options { /// Skip the bundling step even if `bundle > active` is `true` in tauri config. #[clap(long)] pub no_bundle: bool, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Command line arguments passed to the runner. Use `--` to explicitly mark the start of the arguments. pub args: Vec, /// Skip prompting for values @@ -68,7 +74,10 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> { .map(Target::from_triple) .unwrap_or_else(Target::current); - let config = get_config(target, options.config.as_ref().map(|c| &c.0))?; + let config = get_config( + target, + &options.config.iter().map(|c| &c.0).collect::>(), + )?; let mut interface = AppInterface::new( config.lock().unwrap().as_ref().unwrap(), diff --git a/crates/tauri-cli/src/bundle.rs b/crates/tauri-cli/src/bundle.rs index e94abb077..4d1b6e581 100644 --- a/crates/tauri-cli/src/bundle.rs +++ b/crates/tauri-cli/src/bundle.rs @@ -67,9 +67,15 @@ pub struct Options { /// Note that the `updater` bundle is not automatically added so you must specify it if the updater is enabled. #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')] pub bundles: Option>, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Space or comma separated list of features, should be the same features passed to `tauri build` if any. #[clap(short, long, action = ArgAction::Append, num_args(0..))] pub features: Option>, @@ -109,7 +115,10 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> { .map(Target::from_triple) .unwrap_or_else(Target::current); - let config = get_config(target, options.config.as_ref().map(|c| &c.0))?; + let config = get_config( + target, + &options.config.iter().map(|c| &c.0).collect::>(), + )?; let interface = AppInterface::new( config.lock().unwrap().as_ref().unwrap(), diff --git a/crates/tauri-cli/src/dev.rs b/crates/tauri-cli/src/dev.rs index 841b3b3d5..de1fee5e2 100644 --- a/crates/tauri-cli/src/dev.rs +++ b/crates/tauri-cli/src/dev.rs @@ -59,9 +59,15 @@ pub struct Options { /// Exit on panic #[clap(short, long)] pub exit_on_panic: bool, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Run the code in release mode #[clap(long = "release")] pub release_mode: bool, @@ -104,7 +110,10 @@ fn command_internal(mut options: Options) -> Result<()> { .map(Target::from_triple) .unwrap_or_else(Target::current); - let config = get_config(target, options.config.as_ref().map(|c| &c.0))?; + let config = get_config( + target, + &options.config.iter().map(|c| &c.0).collect::>(), + )?; let mut interface = AppInterface::new( config.lock().unwrap().as_ref().unwrap(), @@ -262,26 +271,13 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand let server_url = format!("http://{server_url}"); dev_url = Some(server_url.parse().unwrap()); - if let Some(c) = &mut options.config { - if let Some(build) = c - .0 - .as_object_mut() - .and_then(|root| root.get_mut("build")) - .and_then(|build| build.as_object_mut()) - { - build.insert("devUrl".into(), server_url.into()); + options.config.push(crate::ConfigValue(serde_json::json!({ + "build": { + "devUrl": server_url } - } else { - options - .config - .replace(crate::ConfigValue(serde_json::json!({ - "build": { - "devUrl": server_url - } - }))); - } + }))); - reload_config(options.config.as_ref().map(|c| &c.0))?; + reload_config(&options.config.iter().map(|c| &c.0).collect::>())?; } } } diff --git a/crates/tauri-cli/src/helpers/config.rs b/crates/tauri-cli/src/helpers/config.rs index 6df6db20a..f544c5e27 100644 --- a/crates/tauri-cli/src/helpers/config.rs +++ b/crates/tauri-cli/src/helpers/config.rs @@ -138,7 +138,7 @@ fn config_handle() -> &'static ConfigHandle { /// Gets the static parsed config from `tauri.conf.json`. fn get_internal( - merge_config: Option<&serde_json::Value>, + merge_configs: &[&serde_json::Value], reload: bool, target: Target, ) -> crate::Result { @@ -162,12 +162,17 @@ fn get_internal( ); } - if let Some(merge_config) = merge_config { + if !merge_configs.is_empty() { + let mut merge_config = serde_json::Value::Object(Default::default()); + for conf in merge_configs { + merge(&mut merge_config, conf); + } + let merge_config_str = serde_json::to_string(&merge_config).unwrap(); set_var("TAURI_CONFIG", merge_config_str); - merge(&mut config, merge_config); + merge(&mut config, &merge_config); extensions.insert(MERGE_CONFIG_EXTENSION_NAME.into(), merge_config.clone()); - }; + } if config_path.extension() == Some(OsStr::new("json")) || config_path.extension() == Some(OsStr::new("json5")) @@ -217,35 +222,42 @@ fn get_internal( Ok(config_handle().clone()) } -pub fn get( - target: Target, - merge_config: Option<&serde_json::Value>, -) -> crate::Result { - get_internal(merge_config, false, target) +pub fn get(target: Target, merge_configs: &[&serde_json::Value]) -> crate::Result { + get_internal(merge_configs, false, target) } -pub fn reload(merge_config: Option<&serde_json::Value>) -> crate::Result { +pub fn reload(merge_configs: &[&serde_json::Value]) -> crate::Result { let target = config_handle() .lock() .unwrap() .as_ref() .map(|conf| conf.target); if let Some(target) = target { - get_internal(merge_config, true, target) + get_internal(merge_configs, true, target) } else { Err(anyhow::anyhow!("config not loaded")) } } /// merges the loaded config with the given value -pub fn merge_with(merge_config: &serde_json::Value) -> crate::Result { +pub fn merge_with(merge_configs: &[&serde_json::Value]) -> crate::Result { let handle = config_handle(); + + if merge_configs.is_empty() { + return Ok(handle.clone()); + } + if let Some(config_metadata) = &mut *handle.lock().unwrap() { - let merge_config_str = serde_json::to_string(merge_config).unwrap(); + let mut merge_config = serde_json::Value::Object(Default::default()); + for conf in merge_configs { + merge(&mut merge_config, conf); + } + + let merge_config_str = serde_json::to_string(&merge_config).unwrap(); set_var("TAURI_CONFIG", merge_config_str); let mut value = serde_json::to_value(config_metadata.inner.clone())?; - merge(&mut value, merge_config); + merge(&mut value, &merge_config); config_metadata.inner = serde_json::from_value(value)?; Ok(handle.clone()) diff --git a/crates/tauri-cli/src/info/app.rs b/crates/tauri-cli/src/info/app.rs index 6987ff08b..624d79bd3 100644 --- a/crates/tauri-cli/src/info/app.rs +++ b/crates/tauri-cli/src/info/app.rs @@ -13,7 +13,7 @@ use tauri_utils::platform::Target; pub fn items(frontend_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec { let mut items = Vec::new(); if tauri_dir.is_some() { - if let Ok(config) = crate::helpers::config::get(Target::current(), None) { + if let Ok(config) = crate::helpers::config::get(Target::current(), &[]) { let config_guard = config.lock().unwrap(); let config = config_guard.as_ref().unwrap(); diff --git a/crates/tauri-cli/src/inspect.rs b/crates/tauri-cli/src/inspect.rs index 87c4ae951..d08a81835 100644 --- a/crates/tauri-cli/src/inspect.rs +++ b/crates/tauri-cli/src/inspect.rs @@ -31,7 +31,7 @@ fn wix_upgrade_code() -> Result<()> { crate::helpers::app_paths::resolve(); let target = tauri_utils::platform::Target::Windows; - let config = crate::helpers::config::get(target, None)?; + let config = crate::helpers::config::get(target, &[])?; let interface = AppInterface::new(config.lock().unwrap().as_ref().unwrap(), None)?; diff --git a/crates/tauri-cli/src/interface/rust.rs b/crates/tauri-cli/src/interface/rust.rs index f28b481a8..bfbf83bb6 100644 --- a/crates/tauri-cli/src/interface/rust.rs +++ b/crates/tauri-cli/src/interface/rust.rs @@ -51,7 +51,7 @@ pub struct Options { pub target: Option, pub features: Option>, pub args: Vec, - pub config: Option, + pub config: Vec, pub no_watch: bool, } @@ -101,7 +101,7 @@ pub struct MobileOptions { pub debug: bool, pub features: Option>, pub args: Vec, - pub config: Option, + pub config: Vec, pub no_watch: bool, } @@ -207,14 +207,14 @@ impl Interface for Rust { rx.recv().unwrap(); Ok(()) } else { - let config = options.config.clone().map(|c| c.0); + let merge_configs = options.config.iter().map(|c| &c.0).collect::>(); let run = Arc::new(|rust: &mut Rust| { let on_exit = on_exit.clone(); rust.run_dev(options.clone(), run_args.clone(), move |status, reason| { on_exit(status, reason) }) }); - self.run_dev_watcher(config, run) + self.run_dev_watcher(&merge_configs, run) } } @@ -236,9 +236,9 @@ impl Interface for Rust { runner(options)?; Ok(()) } else { - let config = options.config.clone().map(|c| c.0); + let merge_configs = options.config.iter().map(|c| &c.0).collect::>(); let run = Arc::new(|_rust: &mut Rust| runner(options.clone())); - self.run_dev_watcher(config, run) + self.run_dev_watcher(&merge_configs, run) } } @@ -487,7 +487,7 @@ impl Rust { fn run_dev_watcher crate::Result>>( &mut self, - config: Option, + merge_configs: &[&serde_json::Value], run: Arc, ) -> crate::Result<()> { let child = run(self)?; @@ -537,7 +537,7 @@ impl Rust { if let Some(event_path) = event.paths.first() { if !ignore_matcher.is_ignore(event_path, event_path.is_dir()) { if is_configuration_file(self.app_settings.target, event_path) { - if let Ok(config) = reload_config(config.as_ref()) { + if let Ok(config) = reload_config(merge_configs) { let (manifest, modified) = rewrite_manifest(config.lock().unwrap().as_ref().unwrap())?; if modified { diff --git a/crates/tauri-cli/src/mobile/android/android_studio_script.rs b/crates/tauri-cli/src/mobile/android/android_studio_script.rs index 32f936130..34aaa1060 100644 --- a/crates/tauri-cli/src/mobile/android/android_studio_script.rs +++ b/crates/tauri-cli/src/mobile/android/android_studio_script.rs @@ -46,7 +46,7 @@ pub fn command(options: Options) -> Result<()> { Profile::Debug }; - let tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, None)?; + let tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[])?; let (config, metadata, cli_options) = { let tauri_config_guard = tauri_config.lock().unwrap(); @@ -72,8 +72,14 @@ pub fn command(options: Options) -> Result<()> { MobileTarget::Android, )?; - if let Some(config) = &cli_options.config { - crate::helpers::config::merge_with(&config.0)?; + if !cli_options.config.is_empty() { + crate::helpers::config::merge_with( + &cli_options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), + )?; } let env = env()?; diff --git a/crates/tauri-cli/src/mobile/android/build.rs b/crates/tauri-cli/src/mobile/android/build.rs index f6ccf7eaf..26e27399d 100644 --- a/crates/tauri-cli/src/mobile/android/build.rs +++ b/crates/tauri-cli/src/mobile/android/build.rs @@ -49,9 +49,15 @@ pub struct Options { /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] pub features: Option>, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Whether to split the APKs and AABs per ABIs. #[clap(long)] pub split_per_abi: bool, @@ -105,7 +111,11 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { let tauri_config = get_tauri_config( tauri_utils::platform::Target::Android, - options.config.as_ref().map(|c| &c.0), + &options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), )?; let (interface, config, metadata) = { let tauri_config_guard = tauri_config.lock().unwrap(); diff --git a/crates/tauri-cli/src/mobile/android/dev.rs b/crates/tauri-cli/src/mobile/android/dev.rs index 6899b21a1..0a8b13050 100644 --- a/crates/tauri-cli/src/mobile/android/dev.rs +++ b/crates/tauri-cli/src/mobile/android/dev.rs @@ -48,9 +48,15 @@ pub struct Options { /// Exit on panic #[clap(short, long)] exit_on_panic: bool, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Run the code in release mode #[clap(long = "release")] pub release_mode: bool, @@ -126,7 +132,11 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { let tauri_config = get_tauri_config( tauri_utils::platform::Target::Android, - options.config.as_ref().map(|c| &c.0), + &options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), )?; let env = env()?; diff --git a/crates/tauri-cli/src/mobile/init.rs b/crates/tauri-cli/src/mobile/init.rs index c24ddbe84..eaa5ea991 100644 --- a/crates/tauri-cli/src/mobile/init.rs +++ b/crates/tauri-cli/src/mobile/init.rs @@ -43,7 +43,7 @@ pub fn exec( #[allow(unused_variables)] reinstall_deps: bool, skip_targets_install: bool, ) -> Result { - let tauri_config = get_tauri_config(target.platform_target(), None)?; + let tauri_config = get_tauri_config(target.platform_target(), &[])?; let tauri_config_guard = tauri_config.lock().unwrap(); let tauri_config_ = tauri_config_guard.as_ref().unwrap(); diff --git a/crates/tauri-cli/src/mobile/ios/build.rs b/crates/tauri-cli/src/mobile/ios/build.rs index 82a6d2eff..f33b5b81d 100644 --- a/crates/tauri-cli/src/mobile/ios/build.rs +++ b/crates/tauri-cli/src/mobile/ios/build.rs @@ -60,9 +60,15 @@ pub struct Options { /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] pub features: Option>, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Build number to append to the app version. #[clap(long)] pub build_number: Option, @@ -145,7 +151,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { let tauri_config = get_tauri_config( tauri_utils::platform::Target::Ios, - options.config.as_ref().map(|c| &c.0), + &options.config.iter().map(|c| &c.0).collect::>(), )?; let (interface, mut config) = { let tauri_config_guard = tauri_config.lock().unwrap(); diff --git a/crates/tauri-cli/src/mobile/ios/dev.rs b/crates/tauri-cli/src/mobile/ios/dev.rs index 892afa76e..22f336110 100644 --- a/crates/tauri-cli/src/mobile/ios/dev.rs +++ b/crates/tauri-cli/src/mobile/ios/dev.rs @@ -54,9 +54,15 @@ pub struct Options { /// Exit on panic #[clap(short, long)] exit_on_panic: bool, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Run the code in release mode #[clap(long = "release")] pub release_mode: bool, @@ -148,7 +154,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { let tauri_config = get_tauri_config( tauri_utils::platform::Target::Ios, - options.config.as_ref().map(|c| &c.0), + &options.config.iter().map(|c| &c.0).collect::>(), )?; let (interface, config) = { let tauri_config_guard = tauri_config.lock().unwrap(); diff --git a/crates/tauri-cli/src/mobile/ios/xcode_script.rs b/crates/tauri-cli/src/mobile/ios/xcode_script.rs index c9599f402..c3302b370 100644 --- a/crates/tauri-cli/src/mobile/ios/xcode_script.rs +++ b/crates/tauri-cli/src/mobile/ios/xcode_script.rs @@ -78,7 +78,7 @@ pub fn command(options: Options) -> Result<()> { let profile = profile_from_configuration(&options.configuration); let macos = macos_from_platform(&options.platform); - let tauri_config = get_tauri_config(tauri_utils::platform::Target::Ios, None)?; + let tauri_config = get_tauri_config(tauri_utils::platform::Target::Ios, &[])?; let (config, metadata, cli_options) = { let tauri_config_guard = tauri_config.lock().unwrap(); @@ -103,8 +103,14 @@ pub fn command(options: Options) -> Result<()> { MobileTarget::Ios, )?; - if let Some(config) = &cli_options.config { - crate::helpers::config::merge_with(&config.0)?; + if !cli_options.config.is_empty() { + crate::helpers::config::merge_with( + &cli_options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), + )?; } let env = env()?.explicit_env_vars(cli_options.vars); diff --git a/crates/tauri-cli/src/mobile/mod.rs b/crates/tauri-cli/src/mobile/mod.rs index d9e5a10db..10300a208 100644 --- a/crates/tauri-cli/src/mobile/mod.rs +++ b/crates/tauri-cli/src/mobile/mod.rs @@ -187,7 +187,7 @@ pub struct CliOptions { pub args: Vec, pub noise_level: NoiseLevel, pub vars: HashMap, - pub config: Option, + pub config: Vec, pub target_device: Option, } @@ -199,7 +199,7 @@ impl Default for CliOptions { args: vec!["--lib".into()], noise_level: Default::default(), vars: Default::default(), - config: None, + config: Vec::new(), target_device: None, } } @@ -292,26 +292,21 @@ fn use_network_address_for_dev_url( url.path() ))?; - if let Some(c) = &mut dev_options.config { - if let Some(build) = c - .0 - .as_object_mut() - .and_then(|root| root.get_mut("build")) - .and_then(|build| build.as_object_mut()) - { - build.insert("devUrl".into(), url.to_string().into()); - } - } else { - let mut build = serde_json::Map::new(); - build.insert("devUrl".into(), url.to_string().into()); + dev_options + .config + .push(crate::ConfigValue(serde_json::json!({ + "build": { + "devUrl": url + } + }))); - dev_options + reload_config( + &dev_options .config - .replace(crate::ConfigValue(serde_json::json!({ - "build": build - }))); - } - reload_config(dev_options.config.as_ref().map(|c| &c.0))?; + .iter() + .map(|conf| &conf.0) + .collect::>(), + )?; Some(ip) } else {