diff --git a/src/bin_target.rs b/src/bin_target.rs index d3a0ce16..969f081b 100644 --- a/src/bin_target.rs +++ b/src/bin_target.rs @@ -7,32 +7,11 @@ pub struct BinTarget<'p> { /// The package containing the binary. pub package: &'p Package, /// The path to the directory in `target` which contains the binary. - // Only used with `web` feature, but cfg-ing it out adds too much boilerplate - #[allow(dead_code)] pub artifact_directory: PathBuf, /// The name of the binary (without any extensions). - // Only used with `web` feature, but cfg-ing it out adds too much boilerplate - #[allow(dead_code)] pub bin_name: String, } -impl BinTarget<'_> { - pub fn update_artifact_directory( - &mut self, - target_directory: impl Into, - compile_target: Option<&str>, - compile_profile: &str, - is_example: bool, - ) { - self.artifact_directory = get_artifact_directory( - target_directory, - compile_target, - compile_profile, - is_example, - ); - } -} - /// Determine which binary target should be run. /// /// The `--package` arg narrows down the search space to the given package, diff --git a/src/commands/build/args.rs b/src/commands/build/args.rs index 4d318373..bbf2915d 100644 --- a/src/commands/build/args.rs +++ b/src/commands/build/args.rs @@ -60,6 +60,7 @@ impl BuildArgs { } /// The profile used to compile the app. + #[cfg(feature = "web")] pub(crate) fn profile(&self) -> &str { self.cargo_args.compilation_args.profile(self.is_web()) } diff --git a/src/commands/build/mod.rs b/src/commands/build/mod.rs index 046906b6..c72d6ca8 100644 --- a/src/commands/build/mod.rs +++ b/src/commands/build/mod.rs @@ -4,7 +4,7 @@ pub use args::*; #[cfg(feature = "web")] use crate::web::build::build_web; -use crate::{bin_target::select_run_binary, config::CliConfig, external_cli::cargo}; +use crate::{commands::get_package, config::CliConfig, external_cli::cargo}; mod args; @@ -16,21 +16,20 @@ mod args; pub fn build(args: &mut BuildArgs) -> anyhow::Result<()> { let metadata = cargo::metadata::metadata()?; - let mut bin_target = select_run_binary( + let package = get_package( &metadata, - args.cargo_args.package_args.package.as_deref(), - args.cargo_args.target_args.bin.as_deref(), - args.cargo_args.target_args.example.as_deref(), - args.target().as_deref(), - args.profile(), + args.cargo_args.package_args.package.as_ref(), + false, )?; - let mut config = CliConfig::for_package( - &metadata, - bin_target.package, - args.is_web(), - args.is_release(), - )?; + // apply the package specific config, otherwise use the default config (this happens when + // `bevy build` was called from a workspace root with no package selection (we do not support + // workspace config at the moment). + let mut config = if let Some(package) = package { + CliConfig::for_package(&metadata, package, args.is_web(), args.is_release())? + } else { + CliConfig::default() + }; // Read config files hierarchically from the current directory, merge them, // apply environment variables, and resolve relative paths. @@ -39,17 +38,10 @@ pub fn build(args: &mut BuildArgs) -> anyhow::Result<()> { config.append_cargo_config_rustflags(args.target(), &cargo_config)?; args.apply_config(&config); - // Update the artifact directory based on the config, e.g. in case the `target` changed - bin_target.update_artifact_directory( - &metadata.target_directory, - args.target().as_deref(), - args.profile(), - args.cargo_args.target_args.example.is_some(), - ); #[cfg(feature = "web")] if args.is_web() { - build_web(args, &metadata, &bin_target)?; + build_web(args, &metadata)?; return Ok(()); } diff --git a/src/commands/lint/args.rs b/src/commands/lint/args.rs index 1b079a86..f2400819 100644 --- a/src/commands/lint/args.rs +++ b/src/commands/lint/args.rs @@ -51,11 +51,6 @@ impl LintArgs { || self.cargo_args.compilation_args.is_release } - /// The profile used to compile the app. - pub(crate) fn profile(&self) -> &str { - self.cargo_args.compilation_args.profile(self.is_web()) - } - /// The targeted platform. pub(crate) fn target(&self) -> Option { self.cargo_args.compilation_args.target(self.is_web()) diff --git a/src/commands/lint/mod.rs b/src/commands/lint/mod.rs index b4ed1579..8bc9a50b 100644 --- a/src/commands/lint/mod.rs +++ b/src/commands/lint/mod.rs @@ -4,8 +4,7 @@ use tracing::error; #[cfg(feature = "rustup")] use crate::commands::lint::install::install_linter; use crate::{ - bin_target::select_run_binary, - commands::lint::install::list, + commands::{get_package, lint::install::list}, config::CliConfig, external_cli::{ CommandExt, @@ -105,21 +104,20 @@ fn build_lint_cmd(args: &mut LintArgs) -> anyhow::Result { let metadata = cargo::metadata::metadata()?; - let mut bin_target = select_run_binary( + let package = get_package( &metadata, - args.cargo_args.package_args.package.as_deref(), - args.cargo_args.target_args.bin.as_deref(), - args.cargo_args.target_args.example.as_deref(), - args.target().as_deref(), - args.profile(), + args.cargo_args.package_args.package.as_ref(), + false, )?; - let mut config = CliConfig::for_package( - &metadata, - bin_target.package, - args.is_web(), - args.is_release(), - )?; + // apply the package specific config, otherwise use the default config (this happens when + // `bevy build` was called from a workspace root with no package selection (we do not support + // workspace config at the moment). + let mut config = if let Some(package) = package { + CliConfig::for_package(&metadata, package, args.is_web(), args.is_release())? + } else { + CliConfig::default() + }; // Read config files hierarchically from the current directory, merge them, // apply environment variables, and resolve relative paths. @@ -128,14 +126,6 @@ fn build_lint_cmd(args: &mut LintArgs) -> anyhow::Result { args.apply_config(&config); - // Update the artifact directory based on the config, e.g. in case the `target` changed - bin_target.update_artifact_directory( - &metadata.target_directory, - args.target().as_deref(), - args.profile(), - args.cargo_args.target_args.example.is_some(), - ); - #[cfg(feature = "web")] if matches!(args.subcommand, Some(LintSubcommands::Web)) { use tracing::info; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 28e90752..57bb52fd 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,7 +1,99 @@ //! All available commands for the Bevy CLI. +use cargo_metadata::{Metadata, Package, TargetKind}; + pub mod build; pub mod completions; pub mod lint; pub mod new; pub mod run; + +/// Determine the package to pass to `cargo`. +/// +/// Either returns the [`Package`] specified in `package_arg` or tries to resolve the default +/// package: +/// +/// If the [`Package`] should be passed to `cargo run`: workspace_packages > default_packages > +/// "only one binary target" > `default-run`. +/// +/// If the [`Package`] is not passed to `cargo run` and no `package_arg` is present, just check if +/// there is a [`Package`] in the current directory and return that if one is found. +fn get_package<'m>( + metadata: &'m Metadata, + package_arg: Option<&String>, + run_command: bool, +) -> anyhow::Result> { + // If the `--package` arg was passed, search for the given package, otherwise + // check if the current directory contains a package. + let package = if let Some(package_name) = package_arg { + metadata + .packages + .iter() + .find(|package| package.name.as_str() == package_name) + } else if run_command { + let workspace_packages = metadata.workspace_packages(); + let default_packages = metadata.workspace_default_packages(); + let packages = if default_packages.is_empty() { + workspace_packages + } else { + default_packages + }; + + // If there is only one binary, pick that one + let bins: Vec<_> = packages + .iter() + .flat_map(|package| { + package + .targets + .iter() + .filter(|target| target.kind.contains(&TargetKind::Bin)) + .map(move |_| package) + }) + .collect(); + + let bin_package = if bins.is_empty() { + anyhow::bail!("No binaries available!"); + } else if bins.len() == 1 { + bins[0] + } else { + // Otherwise, check if there is a default run target defined + let default_runs: Vec<_> = packages + .iter() + .filter_map(|package| package.default_run.as_ref()) + .collect(); + + if default_runs.is_empty() { + anyhow::bail!( + "There are multiple binaries available, try one of the following: +- add `--bin` or `--package` after `bevy run` to specify which binary or package to run, +- define `default-run` in the Cargo.toml to define the default binary that should be executed in a package, +- define `default-members` in the Cargo.toml of your workspace to define the default package to pick the binary from." + ); + } else if default_runs.len() > 1 { + anyhow::bail!( + "Found multiple `default-run` definitions, I don't know which one to pick!" + ); + } + + let default_run = default_runs[0]; + **bins + .iter() + .find(|bin| bin.name == *default_run) + .ok_or_else(|| anyhow::anyhow!("Didn't find `default-run` binary {default_run}"))? + }; + Some(bin_package) + } else { + // Get the current directory + let current_dir = std::env::current_dir()?; + + // Find the package whose manifest_path matches the current directory + metadata.packages.iter().find(|pkg| { + pkg.manifest_path + .parent() + .map(cargo_metadata::camino::Utf8Path::as_std_path) + == Some(¤t_dir) + }) + }; + + Ok(package) +} diff --git a/src/commands/run/args.rs b/src/commands/run/args.rs index 6b62b1f5..cb56bcf3 100644 --- a/src/commands/run/args.rs +++ b/src/commands/run/args.rs @@ -66,11 +66,6 @@ impl RunArgs { || self.cargo_args.compilation_args.is_release } - /// The profile used to compile the app. - pub(crate) fn profile(&self) -> &str { - self.cargo_args.compilation_args.profile(self.is_web()) - } - /// The targeted platform. pub(crate) fn target(&self) -> Option { self.cargo_args.compilation_args.target(self.is_web()) diff --git a/src/commands/run/mod.rs b/src/commands/run/mod.rs index dbf5e3e7..eb42ee58 100644 --- a/src/commands/run/mod.rs +++ b/src/commands/run/mod.rs @@ -3,7 +3,7 @@ pub use self::args::*; #[cfg(feature = "web")] use crate::web::run::run_web; -use crate::{bin_target::select_run_binary, config::CliConfig, external_cli::cargo}; +use crate::{commands::get_package, config::CliConfig, external_cli::cargo}; mod args; @@ -15,21 +15,20 @@ mod args; pub fn run(args: &mut RunArgs) -> anyhow::Result<()> { let metadata = cargo::metadata::metadata()?; - let mut bin_target = select_run_binary( + let package = get_package( &metadata, - args.cargo_args.package_args.package.as_deref(), - args.cargo_args.target_args.bin.as_deref(), - args.cargo_args.target_args.example.as_deref(), - args.target().as_deref(), - args.profile(), + args.cargo_args.package_args.package.as_ref(), + true, )?; - let mut config = CliConfig::for_package( - &metadata, - bin_target.package, - args.is_web(), - args.is_release(), - )?; + // apply the package specific config, otherwise use the default config (this happens when + // `bevy build` was called from a workspace root with no package selection (we do not support + // workspace config at the moment). + let mut config = if let Some(package) = package { + CliConfig::for_package(&metadata, package, args.is_web(), args.is_release())? + } else { + CliConfig::default() + }; // Read config files hierarchically from the current directory, merge them, // apply environment variables, and resolve relative paths. @@ -37,17 +36,10 @@ pub fn run(args: &mut RunArgs) -> anyhow::Result<()> { config.append_cargo_config_rustflags(args.target(), &cargo_config)?; args.apply_config(&config); - // Update the artifact directory based on the config, e.g. in case the `target` changed - bin_target.update_artifact_directory( - &metadata.target_directory, - args.target().as_deref(), - args.profile(), - args.cargo_args.target_args.example.is_some(), - ); #[cfg(feature = "web")] if args.is_web() { - return run_web(args, &metadata, &bin_target); + return run_web(args, &metadata); } let cargo_args = args.cargo_args_builder(); diff --git a/src/lib.rs b/src/lib.rs index dcb7cb9f..23874367 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ //! The library backend for the prototype Bevy CLI. +#[cfg(feature = "web")] pub(crate) mod bin_target; pub mod commands; pub(crate) mod config; diff --git a/src/web/build.rs b/src/web/build.rs index 399249db..68f62fe2 100644 --- a/src/web/build.rs +++ b/src/web/build.rs @@ -4,7 +4,7 @@ use tracing::info; use super::bundle::WebBundle; use crate::{ - bin_target::BinTarget, + bin_target::select_run_binary, commands::build::{BuildArgs, BuildSubcommands}, external_cli::{cargo, wasm_bindgen, wasm_opt}, web::{ @@ -23,11 +23,16 @@ use crate::{ /// - Optimizing the Wasm binary (in release mode) /// - Creating JavaScript bindings /// - Creating a bundled folder (if requested) -pub fn build_web( - args: &mut BuildArgs, - metadata: &Metadata, - bin_target: &BinTarget, -) -> anyhow::Result { +pub fn build_web(args: &mut BuildArgs, metadata: &Metadata) -> anyhow::Result { + let bin_target = select_run_binary( + metadata, + args.cargo_args.package_args.package.as_deref(), + args.cargo_args.target_args.bin.as_deref(), + args.cargo_args.target_args.example.as_deref(), + args.target().as_deref(), + args.profile(), + )?; + let mut profile_args = configure_default_web_profiles(metadata)?; // `--config` args are resolved from left to right, // so the default configuration needs to come before the user args @@ -64,8 +69,8 @@ pub fn build_web( })?; info!("bundling JavaScript bindings..."); - wasm_bindgen::bundle(metadata, bin_target, args.auto_install())?; - wasm_opt::optimize_path(bin_target, args.auto_install(), &args.wasm_opt_args())?; + wasm_bindgen::bundle(metadata, &bin_target, args.auto_install())?; + wasm_opt::optimize_path(&bin_target, args.auto_install(), &args.wasm_opt_args())?; let web_args = args .subcommand @@ -75,7 +80,7 @@ pub fn build_web( let web_bundle = create_web_bundle( metadata, args.profile(), - bin_target, + &bin_target, web_args.is_some_and(|web_args| web_args.create_packed_bundle), ) .context("failed to create web bundle")?; diff --git a/src/web/run.rs b/src/web/run.rs index c010d3f9..23bd2957 100644 --- a/src/web/run.rs +++ b/src/web/run.rs @@ -9,22 +9,15 @@ use http::{HeaderMap, HeaderValue}; use tracing::{error, info}; use super::{build::build_web, serve::serve}; -use crate::{ - bin_target::BinTarget, - commands::{ - build::BuildArgs, - run::{RunArgs, RunSubcommands, RunWebArgs}, - }, +use crate::commands::{ + build::BuildArgs, + run::{RunArgs, RunSubcommands, RunWebArgs}, }; /// Run the app in the browser. /// /// Requires [`RunSubcommands::Web`] to be defined. -pub(crate) fn run_web( - args: &mut RunArgs, - metadata: &Metadata, - bin_target: &BinTarget, -) -> anyhow::Result<()> { +pub(crate) fn run_web(args: &mut RunArgs, metadata: &Metadata) -> anyhow::Result<()> { let mut build_args: BuildArgs = args.clone().into(); let web_args = match &mut args.subcommand { @@ -45,15 +38,7 @@ pub(crate) fn run_web( let header_map = parse_headers(web_args.headers.iter())?; - // When no target is selected, search for the default-run field and append the binary name - // as `--bin` flag to only compile the default run target - if build_args.cargo_args.target_args.bin.is_none() - && build_args.cargo_args.target_args.example.is_none() - { - build_args.cargo_args.target_args.bin = Some(bin_target.bin_name.clone()); - } - - let web_bundle = build_web(&mut build_args, metadata, bin_target)?; + let web_bundle = build_web(&mut build_args, metadata)?; let port = web_args.port; let host = IpAddr::from_str(&web_args.host).context("failed to parse host address")?;