Skip to content

Commit 10d0dbc

Browse files
authored
Add support for cargo's --config arg (#209)
# Objective Closes #201. Allows users to quickly try out different configurations without modifying `Cargo.toml`. For us, it's most useful for finding the best default config for the web profiles. # Solution Add support for `--config` argument for `bevy build` and `bevy run`, analogue to the `cargo` counterpart. This essentially allows you to modify the `Cargo.toml` without editing the file, e.g. `--config "profile.web.debug=false"`. We already exploit `cargo`'s `--config` arg to configure our default web compilation profiles. So we have to ensure that the user-provided args _overwrite_ our default ones. To do this, we change the default web profiles to be prepended to the user-provided `--config` args, instead of converting them to `--config` arguments directly. Since `--config` is resolved left-to-right, the defaults will be overwritten by the user. # Testing Try for example: ``` bevy build --config "profile.web.debug=false" --config 'profile.web.inherits= "release"' web ``` Now instead of ``` Finished `web` profile [unoptimized + debuginfo] target(s) ``` you should get ``` Finished `web` profile [optimized] target(s) ``` Debug info removed and optimizations enabled! (Of course this can be done easier via `bevy build --release web`, but like this it's easier to benchmark different compilation profiles to choose the best default)
1 parent 12635b7 commit 10d0dbc

File tree

9 files changed

+111
-28
lines changed

9 files changed

+111
-28
lines changed

src/bin/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fn main() -> Result<()> {
1010
bevy_cli::template::generate_template(&new.name, &new.template, &new.branch)?;
1111
}
1212
Subcommands::Lint { args } => bevy_cli::lint::lint(args)?,
13-
Subcommands::Build(args) => bevy_cli::build::build(&args)?,
13+
Subcommands::Build(mut args) => bevy_cli::build::build(&mut args)?,
1414
Subcommands::Run(args) => bevy_cli::run::run(&args)?,
1515
}
1616

src/build/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::{
1212

1313
pub mod args;
1414

15-
pub fn build(args: &BuildArgs) -> anyhow::Result<()> {
15+
pub fn build(args: &mut BuildArgs) -> anyhow::Result<()> {
1616
if args.is_web() {
1717
build_web(args)?;
1818
} else {
@@ -32,7 +32,7 @@ pub fn build(args: &BuildArgs) -> anyhow::Result<()> {
3232
/// - Optimizing the Wasm binary (in release mode)
3333
/// - Creating JavaScript bindings
3434
/// - Creating a bundled folder (if requested)
35-
pub fn build_web(args: &BuildArgs) -> anyhow::Result<WebBundle> {
35+
pub fn build_web(args: &mut BuildArgs) -> anyhow::Result<WebBundle> {
3636
let Some(BuildSubcommands::Web(web_args)) = &args.subcommand else {
3737
bail!("tried to build for the web without matching arguments");
3838
};
@@ -49,9 +49,13 @@ pub fn build_web(args: &BuildArgs) -> anyhow::Result<WebBundle> {
4949
args.profile(),
5050
)?;
5151

52-
let cargo_args = args
53-
.cargo_args_builder()
54-
.append(configure_default_web_profiles(&metadata)?);
52+
let mut profile_args = configure_default_web_profiles(&metadata)?;
53+
// `--config` args are resolved from left to right,
54+
// so the default configuration needs to come before the user args
55+
profile_args.append(&mut args.cargo_args.common_args.config);
56+
args.cargo_args.common_args.config = profile_args;
57+
58+
let cargo_args = args.cargo_args_builder();
5559

5660
println!("Compiling to WebAssembly...");
5761
cargo::build::command().args(cargo_args).ensure_status()?;

src/external_cli/arg_builder.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// A helper to make passing arguments to [`Command`](std::process::Command) more convenient.
22
#[derive(Debug, Clone)]
3+
#[must_use]
34
pub struct ArgBuilder(Vec<String>);
45

56
impl ArgBuilder {
@@ -83,7 +84,7 @@ impl ArgBuilder {
8384
}
8485
}
8586

86-
/// Add an argument with multiple values.
87+
/// Add an argument with multiple values, separated by commas.
8788
///
8889
/// # Example
8990
///
@@ -107,6 +108,33 @@ impl ArgBuilder {
107108
}
108109
}
109110

111+
/// Add an argument with multiple values, reusing the same argument name.
112+
///
113+
/// # Example
114+
///
115+
/// ```
116+
/// # use bevy_cli::external_cli::arg_builder::ArgBuilder;
117+
/// let features = ["dev", "file_watcher"];
118+
/// ArgBuilder::new().add_values_separately("--features", features);
119+
/// ```
120+
pub fn add_values_separately<N, V>(
121+
mut self,
122+
name: N,
123+
value_list: impl IntoIterator<Item = V>,
124+
) -> Self
125+
where
126+
N: Into<String>,
127+
V: Into<String>,
128+
{
129+
let arg: String = name.into();
130+
131+
for value in value_list {
132+
self = self.add_with_value(&arg, value);
133+
}
134+
135+
self
136+
}
137+
110138
/// Add all arguments from the other builder to this one.
111139
pub fn append(mut self, mut other: ArgBuilder) -> Self {
112140
self.0.append(&mut other.0);
@@ -211,6 +239,32 @@ mod tests {
211239
);
212240
}
213241

242+
#[test]
243+
fn add_values_separately_adds_multiple_args() {
244+
let args = ArgBuilder::new().add_values_separately(
245+
"--config",
246+
[r#"profile.web.inherits="dev""#, "profile.web.debug=false"],
247+
);
248+
assert_eq!(
249+
args.into_iter().collect::<Vec<String>>(),
250+
vec![
251+
"--config",
252+
r#"profile.web.inherits="dev""#,
253+
"--config",
254+
"profile.web.debug=false"
255+
]
256+
);
257+
}
258+
259+
#[test]
260+
fn add_values_separately_empty_no_changes() {
261+
let args = ArgBuilder::new().add_values_separately("--config", Vec::<String>::new());
262+
assert_eq!(
263+
args.into_iter().collect::<Vec<String>>(),
264+
Vec::<String>::new()
265+
);
266+
}
267+
214268
#[test]
215269
fn append_adds_args_after_self() {
216270
let args = ArgBuilder::new()

src/external_cli/cargo/build.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use clap::{ArgAction, Args};
44

55
use crate::external_cli::arg_builder::ArgBuilder;
66

7-
use super::{program, CargoCompilationArgs, CargoFeatureArgs, CargoManifestArgs};
7+
use super::{program, CargoCommonArgs, CargoCompilationArgs, CargoFeatureArgs, CargoManifestArgs};
88

99
/// Create a command to run `cargo build`.
1010
pub(crate) fn command() -> Command {
@@ -15,6 +15,8 @@ pub(crate) fn command() -> Command {
1515

1616
#[derive(Debug, Args)]
1717
pub struct CargoBuildArgs {
18+
#[clap(flatten)]
19+
pub common_args: CargoCommonArgs,
1820
#[clap(flatten)]
1921
pub package_args: CargoPackageBuildArgs,
2022
#[clap(flatten)]
@@ -30,6 +32,7 @@ pub struct CargoBuildArgs {
3032
impl CargoBuildArgs {
3133
pub(crate) fn args_builder(&self, is_web: bool) -> ArgBuilder {
3234
ArgBuilder::new()
35+
.append(self.common_args.args_builder())
3336
.append(self.package_args.args_builder())
3437
.append(self.target_args.args_builder())
3538
.append(self.feature_args.args_builder())

src/external_cli/cargo/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,21 @@ impl CargoManifestArgs {
145145
.add_flag_if("--frozen", self.is_frozen)
146146
}
147147
}
148+
149+
/// Common options available for `cargo` commands.
150+
#[derive(Debug, Args, Clone)]
151+
pub struct CargoCommonArgs {
152+
/// Override a configuration value.
153+
///
154+
/// The argument should be in TOML syntax of KEY=VALUE,
155+
/// or provided as a path to an extra configuration file.
156+
/// This flag may be specified multiple times.
157+
#[clap(long = "config", value_name = "KEY=VALUE|PATH")]
158+
pub config: Vec<String>,
159+
}
160+
161+
impl CargoCommonArgs {
162+
pub(crate) fn args_builder(&self) -> ArgBuilder {
163+
ArgBuilder::new().add_values_separately("--config", self.config.iter())
164+
}
165+
}

src/external_cli/cargo/run.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use clap::Args;
44

55
use crate::external_cli::arg_builder::ArgBuilder;
66

7-
use super::{program, CargoCompilationArgs, CargoFeatureArgs, CargoManifestArgs};
7+
use super::{program, CargoCommonArgs, CargoCompilationArgs, CargoFeatureArgs, CargoManifestArgs};
88

99
/// Create a command to run `cargo run`.
1010
pub(crate) fn command() -> Command {
@@ -15,6 +15,8 @@ pub(crate) fn command() -> Command {
1515

1616
#[derive(Debug, Args, Clone)]
1717
pub struct CargoRunArgs {
18+
#[clap(flatten)]
19+
pub common_args: CargoCommonArgs,
1820
#[clap(flatten)]
1921
pub package_args: CargoPackageRunArgs,
2022
#[clap(flatten)]
@@ -30,6 +32,7 @@ pub struct CargoRunArgs {
3032
impl CargoRunArgs {
3133
pub(crate) fn args_builder(&self, is_web: bool) -> ArgBuilder {
3234
ArgBuilder::new()
35+
.append(self.common_args.args_builder())
3336
.append(self.package_args.args_builder())
3437
.append(self.target_args.args_builder())
3538
.append(self.feature_args.args_builder())

src/run/args.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ impl From<RunArgs> for BuildArgs {
6060
BuildArgs {
6161
skip_prompts: args.skip_prompts,
6262
cargo_args: CargoBuildArgs {
63+
common_args: args.cargo_args.common_args,
6364
compilation_args: args.cargo_args.compilation_args,
6465
feature_args: args.cargo_args.feature_args,
6566
manifest_args: args.cargo_args.manifest_args,

src/run/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ mod serve;
1717

1818
pub fn run(args: &RunArgs) -> anyhow::Result<()> {
1919
if let Some(RunSubcommands::Web(web_args)) = &args.subcommand {
20-
let build_args = args.clone().into();
21-
let web_bundle = build_web(&build_args)?;
20+
let mut build_args = args.clone().into();
21+
let web_bundle = build_web(&mut build_args)?;
2222

2323
let port = web_args.port;
2424
let url = format!("http://localhost:{port}");

src/web/profiles.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,23 @@ use std::{collections::HashMap, fs};
33
use anyhow::Context as _;
44
use toml_edit::DocumentMut;
55

6-
use crate::external_cli::{arg_builder::ArgBuilder, cargo::metadata::Metadata};
6+
use crate::external_cli::cargo::metadata::Metadata;
77

88
/// Create `--config` args to configure the default profiles to use when compiling for the web.
9-
pub(crate) fn configure_default_web_profiles(metadata: &Metadata) -> anyhow::Result<ArgBuilder> {
9+
pub(crate) fn configure_default_web_profiles(metadata: &Metadata) -> anyhow::Result<Vec<String>> {
1010
let manifest = fs::read_to_string(metadata.workspace_root.join("Cargo.toml"))
1111
.context("failed to read workspace manifest")?
1212
.parse::<DocumentMut>()
1313
.context("failed to parse workspace manifest")?;
1414

15-
let mut args = ArgBuilder::new();
15+
let mut args = Vec::new();
1616

1717
if !is_profile_defined_in_manifest(&manifest, "web") {
18-
args = args.append(configure_web_profile());
18+
configure_web_profile(&mut args);
1919
}
2020

2121
if !is_profile_defined_in_manifest(&manifest, "web-release") {
22-
args = args.append(configure_web_release_profile());
22+
configure_web_release_profile(&mut args);
2323
}
2424

2525
Ok(args)
@@ -34,21 +34,21 @@ fn is_profile_defined_in_manifest(manifest: &DocumentMut, profile: &str) -> bool
3434
/// Configure the default profile for web debug builds.
3535
///
3636
/// It is optimized for fast iteration speeds.
37-
fn configure_web_profile() -> ArgBuilder {
38-
configure_profile("web", "dev", HashMap::new())
37+
fn configure_web_profile(args: &mut Vec<String>) {
38+
configure_profile("web", "dev", HashMap::new(), args);
3939
}
4040

4141
/// Configure the default profile for web release builds.
4242
///
4343
/// It is optimized both for run time performance and loading times.
44-
fn configure_web_release_profile() -> ArgBuilder {
44+
fn configure_web_release_profile(args: &mut Vec<String>) {
4545
let config = HashMap::from_iter([
4646
// Optimize for size, greatly reducing loading times
4747
("opt-level", "s"),
4848
// Remove debug information, reducing file size further
4949
("strip", "debuginfo"),
5050
]);
51-
configure_profile("web-release", "release", config)
51+
configure_profile("web-release", "release", config, args);
5252
}
5353

5454
/// Create `--config` args for `cargo` to configure a new compilation profile.
@@ -61,17 +61,17 @@ fn configure_web_release_profile() -> ArgBuilder {
6161
/// # config
6262
/// key = "value"
6363
/// ```
64-
fn configure_profile(profile: &str, inherits: &str, config: HashMap<&str, &str>) -> ArgBuilder {
65-
let mut args = ArgBuilder::new().add_with_value(
66-
"--config",
67-
format!(r#"profile.{profile}.inherits="{inherits}""#),
68-
);
64+
fn configure_profile(
65+
profile: &str,
66+
inherits: &str,
67+
config: HashMap<&str, &str>,
68+
args: &mut Vec<String>,
69+
) {
70+
args.push(format!(r#"profile.{profile}.inherits="{inherits}""#));
6971

7072
for (key, value) in config {
71-
args = args.add_with_value("--config", format!(r#"profile.{profile}.{key}="{value}""#));
73+
args.push(format!(r#"profile.{profile}.{key}="{value}""#));
7274
}
73-
74-
args
7575
}
7676

7777
#[cfg(test)]

0 commit comments

Comments
 (0)