Skip to content

Commit d91bfa5

Browse files
authored
feat(cli): allow merging multiple configuration values (#12970)
* feat(cli): allow merging multiple configuration values Currently the dev/build/bundle commands can only merge a single Tauri configuration value (file or raw JSON string), which imposes a limitation in scenarios where you need more flexibility (like multiple app flavors and environments). This changes the config CLI option to allow multiple values, letting you merge multiple Tauri config files with the main one. * fix ios build
1 parent f67a4a6 commit d91bfa5

File tree

16 files changed

+161
-90
lines changed

16 files changed

+161
-90
lines changed

.changes/cli-multiple-config.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@tauri-apps/cli": minor:feat
3+
"tauri-cli": minor:feat
4+
---
5+
6+
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`.

crates/tauri-cli/src/build.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,15 @@ pub struct Options {
4747
/// Skip the bundling step even if `bundle > active` is `true` in tauri config.
4848
#[clap(long)]
4949
pub no_bundle: bool,
50-
/// JSON string or path to JSON file to merge with tauri.conf.json
50+
/// JSON strings or path to JSON files to merge with the default configuration file
51+
///
52+
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
53+
///
54+
/// Note that a platform-specific file is looked up and merged with the default file by default
55+
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
56+
/// but you can use this for more specific use cases such as different build flavors.
5157
#[clap(short, long)]
52-
pub config: Option<ConfigValue>,
58+
pub config: Vec<ConfigValue>,
5359
/// Command line arguments passed to the runner. Use `--` to explicitly mark the start of the arguments.
5460
pub args: Vec<String>,
5561
/// Skip prompting for values
@@ -68,7 +74,10 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
6874
.map(Target::from_triple)
6975
.unwrap_or_else(Target::current);
7076

71-
let config = get_config(target, options.config.as_ref().map(|c| &c.0))?;
77+
let config = get_config(
78+
target,
79+
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
80+
)?;
7281

7382
let mut interface = AppInterface::new(
7483
config.lock().unwrap().as_ref().unwrap(),

crates/tauri-cli/src/bundle.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,15 @@ pub struct Options {
6767
/// Note that the `updater` bundle is not automatically added so you must specify it if the updater is enabled.
6868
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
6969
pub bundles: Option<Vec<BundleFormat>>,
70-
/// JSON string or path to JSON file to merge with tauri.conf.json
70+
/// JSON strings or path to JSON files to merge with the default configuration file
71+
///
72+
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
73+
///
74+
/// Note that a platform-specific file is looked up and merged with the default file by default
75+
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
76+
/// but you can use this for more specific use cases such as different build flavors.
7177
#[clap(short, long)]
72-
pub config: Option<ConfigValue>,
78+
pub config: Vec<ConfigValue>,
7379
/// Space or comma separated list of features, should be the same features passed to `tauri build` if any.
7480
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
7581
pub features: Option<Vec<String>>,
@@ -109,7 +115,10 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> {
109115
.map(Target::from_triple)
110116
.unwrap_or_else(Target::current);
111117

112-
let config = get_config(target, options.config.as_ref().map(|c| &c.0))?;
118+
let config = get_config(
119+
target,
120+
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
121+
)?;
113122

114123
let interface = AppInterface::new(
115124
config.lock().unwrap().as_ref().unwrap(),

crates/tauri-cli/src/dev.rs

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,15 @@ pub struct Options {
5959
/// Exit on panic
6060
#[clap(short, long)]
6161
pub exit_on_panic: bool,
62-
/// JSON string or path to JSON file to merge with tauri.conf.json
62+
/// JSON strings or path to JSON files to merge with the default configuration file
63+
///
64+
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
65+
///
66+
/// Note that a platform-specific file is looked up and merged with the default file by default
67+
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
68+
/// but you can use this for more specific use cases such as different build flavors.
6369
#[clap(short, long)]
64-
pub config: Option<ConfigValue>,
70+
pub config: Vec<ConfigValue>,
6571
/// Run the code in release mode
6672
#[clap(long = "release")]
6773
pub release_mode: bool,
@@ -104,7 +110,10 @@ fn command_internal(mut options: Options) -> Result<()> {
104110
.map(Target::from_triple)
105111
.unwrap_or_else(Target::current);
106112

107-
let config = get_config(target, options.config.as_ref().map(|c| &c.0))?;
113+
let config = get_config(
114+
target,
115+
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
116+
)?;
108117

109118
let mut interface = AppInterface::new(
110119
config.lock().unwrap().as_ref().unwrap(),
@@ -262,26 +271,13 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
262271
let server_url = format!("http://{server_url}");
263272
dev_url = Some(server_url.parse().unwrap());
264273

265-
if let Some(c) = &mut options.config {
266-
if let Some(build) = c
267-
.0
268-
.as_object_mut()
269-
.and_then(|root| root.get_mut("build"))
270-
.and_then(|build| build.as_object_mut())
271-
{
272-
build.insert("devUrl".into(), server_url.into());
274+
options.config.push(crate::ConfigValue(serde_json::json!({
275+
"build": {
276+
"devUrl": server_url
273277
}
274-
} else {
275-
options
276-
.config
277-
.replace(crate::ConfigValue(serde_json::json!({
278-
"build": {
279-
"devUrl": server_url
280-
}
281-
})));
282-
}
278+
})));
283279

284-
reload_config(options.config.as_ref().map(|c| &c.0))?;
280+
reload_config(&options.config.iter().map(|c| &c.0).collect::<Vec<_>>())?;
285281
}
286282
}
287283
}

crates/tauri-cli/src/helpers/config.rs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ fn config_handle() -> &'static ConfigHandle {
138138

139139
/// Gets the static parsed config from `tauri.conf.json`.
140140
fn get_internal(
141-
merge_config: Option<&serde_json::Value>,
141+
merge_configs: &[&serde_json::Value],
142142
reload: bool,
143143
target: Target,
144144
) -> crate::Result<ConfigHandle> {
@@ -162,12 +162,17 @@ fn get_internal(
162162
);
163163
}
164164

165-
if let Some(merge_config) = merge_config {
165+
if !merge_configs.is_empty() {
166+
let mut merge_config = serde_json::Value::Object(Default::default());
167+
for conf in merge_configs {
168+
merge(&mut merge_config, conf);
169+
}
170+
166171
let merge_config_str = serde_json::to_string(&merge_config).unwrap();
167172
set_var("TAURI_CONFIG", merge_config_str);
168-
merge(&mut config, merge_config);
173+
merge(&mut config, &merge_config);
169174
extensions.insert(MERGE_CONFIG_EXTENSION_NAME.into(), merge_config.clone());
170-
};
175+
}
171176

172177
if config_path.extension() == Some(OsStr::new("json"))
173178
|| config_path.extension() == Some(OsStr::new("json5"))
@@ -217,35 +222,42 @@ fn get_internal(
217222
Ok(config_handle().clone())
218223
}
219224

220-
pub fn get(
221-
target: Target,
222-
merge_config: Option<&serde_json::Value>,
223-
) -> crate::Result<ConfigHandle> {
224-
get_internal(merge_config, false, target)
225+
pub fn get(target: Target, merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
226+
get_internal(merge_configs, false, target)
225227
}
226228

227-
pub fn reload(merge_config: Option<&serde_json::Value>) -> crate::Result<ConfigHandle> {
229+
pub fn reload(merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
228230
let target = config_handle()
229231
.lock()
230232
.unwrap()
231233
.as_ref()
232234
.map(|conf| conf.target);
233235
if let Some(target) = target {
234-
get_internal(merge_config, true, target)
236+
get_internal(merge_configs, true, target)
235237
} else {
236238
Err(anyhow::anyhow!("config not loaded"))
237239
}
238240
}
239241

240242
/// merges the loaded config with the given value
241-
pub fn merge_with(merge_config: &serde_json::Value) -> crate::Result<ConfigHandle> {
243+
pub fn merge_with(merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
242244
let handle = config_handle();
245+
246+
if merge_configs.is_empty() {
247+
return Ok(handle.clone());
248+
}
249+
243250
if let Some(config_metadata) = &mut *handle.lock().unwrap() {
244-
let merge_config_str = serde_json::to_string(merge_config).unwrap();
251+
let mut merge_config = serde_json::Value::Object(Default::default());
252+
for conf in merge_configs {
253+
merge(&mut merge_config, conf);
254+
}
255+
256+
let merge_config_str = serde_json::to_string(&merge_config).unwrap();
245257
set_var("TAURI_CONFIG", merge_config_str);
246258

247259
let mut value = serde_json::to_value(config_metadata.inner.clone())?;
248-
merge(&mut value, merge_config);
260+
merge(&mut value, &merge_config);
249261
config_metadata.inner = serde_json::from_value(value)?;
250262

251263
Ok(handle.clone())

crates/tauri-cli/src/info/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use tauri_utils::platform::Target;
1313
pub fn items(frontend_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<SectionItem> {
1414
let mut items = Vec::new();
1515
if tauri_dir.is_some() {
16-
if let Ok(config) = crate::helpers::config::get(Target::current(), None) {
16+
if let Ok(config) = crate::helpers::config::get(Target::current(), &[]) {
1717
let config_guard = config.lock().unwrap();
1818
let config = config_guard.as_ref().unwrap();
1919

crates/tauri-cli/src/inspect.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn wix_upgrade_code() -> Result<()> {
3131
crate::helpers::app_paths::resolve();
3232

3333
let target = tauri_utils::platform::Target::Windows;
34-
let config = crate::helpers::config::get(target, None)?;
34+
let config = crate::helpers::config::get(target, &[])?;
3535

3636
let interface = AppInterface::new(config.lock().unwrap().as_ref().unwrap(), None)?;
3737

crates/tauri-cli/src/interface/rust.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub struct Options {
5151
pub target: Option<String>,
5252
pub features: Option<Vec<String>>,
5353
pub args: Vec<String>,
54-
pub config: Option<ConfigValue>,
54+
pub config: Vec<ConfigValue>,
5555
pub no_watch: bool,
5656
}
5757

@@ -101,7 +101,7 @@ pub struct MobileOptions {
101101
pub debug: bool,
102102
pub features: Option<Vec<String>>,
103103
pub args: Vec<String>,
104-
pub config: Option<ConfigValue>,
104+
pub config: Vec<ConfigValue>,
105105
pub no_watch: bool,
106106
}
107107

@@ -207,14 +207,14 @@ impl Interface for Rust {
207207
rx.recv().unwrap();
208208
Ok(())
209209
} else {
210-
let config = options.config.clone().map(|c| c.0);
210+
let merge_configs = options.config.iter().map(|c| &c.0).collect::<Vec<_>>();
211211
let run = Arc::new(|rust: &mut Rust| {
212212
let on_exit = on_exit.clone();
213213
rust.run_dev(options.clone(), run_args.clone(), move |status, reason| {
214214
on_exit(status, reason)
215215
})
216216
});
217-
self.run_dev_watcher(config, run)
217+
self.run_dev_watcher(&merge_configs, run)
218218
}
219219
}
220220

@@ -236,9 +236,9 @@ impl Interface for Rust {
236236
runner(options)?;
237237
Ok(())
238238
} else {
239-
let config = options.config.clone().map(|c| c.0);
239+
let merge_configs = options.config.iter().map(|c| &c.0).collect::<Vec<_>>();
240240
let run = Arc::new(|_rust: &mut Rust| runner(options.clone()));
241-
self.run_dev_watcher(config, run)
241+
self.run_dev_watcher(&merge_configs, run)
242242
}
243243
}
244244

@@ -487,7 +487,7 @@ impl Rust {
487487

488488
fn run_dev_watcher<F: Fn(&mut Rust) -> crate::Result<Box<dyn DevProcess + Send>>>(
489489
&mut self,
490-
config: Option<serde_json::Value>,
490+
merge_configs: &[&serde_json::Value],
491491
run: Arc<F>,
492492
) -> crate::Result<()> {
493493
let child = run(self)?;
@@ -537,7 +537,7 @@ impl Rust {
537537
if let Some(event_path) = event.paths.first() {
538538
if !ignore_matcher.is_ignore(event_path, event_path.is_dir()) {
539539
if is_configuration_file(self.app_settings.target, event_path) {
540-
if let Ok(config) = reload_config(config.as_ref()) {
540+
if let Ok(config) = reload_config(merge_configs) {
541541
let (manifest, modified) =
542542
rewrite_manifest(config.lock().unwrap().as_ref().unwrap())?;
543543
if modified {

crates/tauri-cli/src/mobile/android/android_studio_script.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub fn command(options: Options) -> Result<()> {
4646
Profile::Debug
4747
};
4848

49-
let tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, None)?;
49+
let tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[])?;
5050

5151
let (config, metadata, cli_options) = {
5252
let tauri_config_guard = tauri_config.lock().unwrap();
@@ -72,8 +72,14 @@ pub fn command(options: Options) -> Result<()> {
7272
MobileTarget::Android,
7373
)?;
7474

75-
if let Some(config) = &cli_options.config {
76-
crate::helpers::config::merge_with(&config.0)?;
75+
if !cli_options.config.is_empty() {
76+
crate::helpers::config::merge_with(
77+
&cli_options
78+
.config
79+
.iter()
80+
.map(|conf| &conf.0)
81+
.collect::<Vec<_>>(),
82+
)?;
7783
}
7884

7985
let env = env()?;

crates/tauri-cli/src/mobile/android/build.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,15 @@ pub struct Options {
4949
/// List of cargo features to activate
5050
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
5151
pub features: Option<Vec<String>>,
52-
/// JSON string or path to JSON file to merge with tauri.conf.json
52+
/// JSON strings or path to JSON files to merge with the default configuration file
53+
///
54+
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
55+
///
56+
/// Note that a platform-specific file is looked up and merged with the default file by default
57+
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
58+
/// but you can use this for more specific use cases such as different build flavors.
5359
#[clap(short, long)]
54-
pub config: Option<ConfigValue>,
60+
pub config: Vec<ConfigValue>,
5561
/// Whether to split the APKs and AABs per ABIs.
5662
#[clap(long)]
5763
pub split_per_abi: bool,
@@ -105,7 +111,11 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
105111

106112
let tauri_config = get_tauri_config(
107113
tauri_utils::platform::Target::Android,
108-
options.config.as_ref().map(|c| &c.0),
114+
&options
115+
.config
116+
.iter()
117+
.map(|conf| &conf.0)
118+
.collect::<Vec<_>>(),
109119
)?;
110120
let (interface, config, metadata) = {
111121
let tauri_config_guard = tauri_config.lock().unwrap();

0 commit comments

Comments
 (0)