Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bevy build #102

Merged
merged 7 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,10 @@ serde_json = "1.0.128"
reqwest = { features = ["blocking", "json"], version = "0.12.7" }
regex = "1.10.6"

# Understanding Cargo.toml
toml_edit = { version = "0.22.21", default-features = false, features = [
"parse",
] }

# Understanding package versions
semver = { version = "1.0.23", features = ["serde"] }
8 changes: 8 additions & 0 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::Result;
use bevy_cli::build::BuildArgs;
use clap::{Args, Parser, Subcommand};

fn main() -> Result<()> {
Expand All @@ -9,6 +10,7 @@ fn main() -> Result<()> {
bevy_cli::template::generate_template(&new.name, &new.template, &new.branch)?;
}
Subcommands::Lint { args } => bevy_cli::lint::lint(args)?,
Subcommands::Build(args) => bevy_cli::build::build(&args)?,
}

Ok(())
Expand All @@ -27,10 +29,16 @@ pub struct Cli {
}

/// Available subcommands for `bevy`.
#[expect(
clippy::large_enum_variant,
reason = "Only constructed once, not expected to have a performance impact."
)]
#[derive(Subcommand)]
pub enum Subcommands {
/// Create a new Bevy project from a specified template.
New(NewArgs),
/// Build your Bevy app.
Build(BuildArgs),
/// Check the current project using Bevy-specific lints.
///
/// This command requires `bevy_lint` to be installed, and will fail if it is not. Please see
Expand Down
37 changes: 37 additions & 0 deletions src/build/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use clap::{Args, Subcommand};

use crate::external_cli::{arg_builder::ArgBuilder, cargo::build::CargoBuildArgs};

#[derive(Debug, Args)]
pub struct BuildArgs {
/// The subcommands available for the build command.
#[clap(subcommand)]
pub subcommand: Option<BuildSubcommands>,

/// Arguments to forward to `cargo build`.
#[clap(flatten)]
pub cargo_args: CargoBuildArgs,
}

impl BuildArgs {
/// Determine if the app is being built for the web.
pub(crate) fn is_web(&self) -> bool {
matches!(self.subcommand, Some(BuildSubcommands::Web))
}

/// The profile used to compile the app.
pub(crate) fn profile(&self) -> &str {
self.cargo_args.compilation_args.profile()
}

/// Generate arguments to forward to `cargo build`.
pub(crate) fn cargo_args_builder(&self) -> ArgBuilder {
self.cargo_args.args_builder(self.is_web())
}
}

#[derive(Debug, Subcommand)]
pub enum BuildSubcommands {
/// Build your app for the browser.
Web,
}
36 changes: 36 additions & 0 deletions src/build/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use crate::{
external_cli::{cargo, rustup, wasm_bindgen, CommandHelpers},
manifest::package_name,
};

pub use self::args::BuildArgs;

mod args;

pub fn build(args: &BuildArgs) -> anyhow::Result<()> {
let cargo_args = args.cargo_args_builder();

if args.is_web() {
ensure_web_setup()?;

println!("Building for WASM...");
cargo::build::command().args(cargo_args).ensure_status()?;

println!("Bundling for the web...");
wasm_bindgen::bundle(&package_name()?, args.profile())
.expect("Failed to bundle for the web");
} else {
cargo::build::command().args(cargo_args).ensure_status()?;
}

Ok(())
}

pub(crate) fn ensure_web_setup() -> anyhow::Result<()> {
// `wasm32-unknown-unknown` compilation target
rustup::install_target_if_needed("wasm32-unknown-unknown")?;
// `wasm-bindgen-cli` for bundling
cargo::install::if_needed(wasm_bindgen::PROGRAM, wasm_bindgen::PACKAGE, true, false)?;

Ok(())
}
56 changes: 56 additions & 0 deletions src/external_cli/cargo/install.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::process::{exit, Command};

use dialoguer::Confirm;

/// Check if the given program is installed on the system.
///
/// This assumes that the program offers a `--version` flag.
fn is_installed(program: &str) -> bool {
let output = Command::new(program).arg("--version").output();

if let Ok(output) = output {
output.status.success()
} else {
false
}
}

/// Checks if the program is installed and installs it if it isn't.
///
/// Returns `true` if the program needed to be installed.
pub(crate) fn if_needed(
program: &str,
package: &str,
ask_user: bool,
hidden: bool,
) -> anyhow::Result<bool> {
if is_installed(program) {
return Ok(false);
}

// Abort if the user doesn't want to install it
if ask_user
&& !Confirm::new()
.with_prompt(format!(
"`{program}` is missing, should I install it for you?"
))
.interact()?
{
exit(1);
}

let mut cmd = Command::new(super::program());
cmd.arg("install").arg(package);

let status = if hidden {
cmd.output()?.status
} else {
cmd.status()?
};

if !status.success() {
Err(anyhow::anyhow!("Failed to install `{program}`."))
} else {
Ok(true)
}
}
1 change: 1 addition & 0 deletions src/external_cli/cargo/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![expect(dead_code, reason = "Will be used for bevy bump and perhaps bevy run")]
Copy link
Collaborator Author

@TimJentzsch TimJentzsch Sep 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this lint wasn't emitted before...

use std::{ffi::OsStr, path::PathBuf, process::Command};

use semver::{Version, VersionReq};
Expand Down
15 changes: 14 additions & 1 deletion src/external_cli/cargo/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#![expect(dead_code, reason = "Will be used for `bevy build` and `bevy run`")]
use std::{env, ffi::OsString};

use clap::{ArgAction, Args};

use super::arg_builder::ArgBuilder;

pub(crate) mod build;
pub(crate) mod install;
pub(crate) mod metadata;

fn program() -> OsString {
Expand Down Expand Up @@ -66,6 +66,19 @@ pub struct CargoCompilationArgs {
}

impl CargoCompilationArgs {
/// The profile used to compile the app.
///
/// This is determined by the `--release` and `--profile` arguments.
pub(crate) fn profile(&self) -> &str {
if self.is_release {
"release"
} else if let Some(profile) = &self.profile {
profile
} else {
"debug"
}
}

pub(crate) fn args_builder(&self, is_web: bool) -> ArgBuilder {
// web takes precedence over --target <TRIPLE>
let target = if is_web {
Expand Down
20 changes: 20 additions & 0 deletions src/external_cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
//! Wrappers and utilities to deal with external CLI applications, like `cargo`.

use std::process::{Command, ExitStatus};

pub mod arg_builder;
pub(crate) mod cargo;
pub(crate) mod rustup;
pub(crate) mod wasm_bindgen;

pub trait CommandHelpers {
fn ensure_status(&mut self) -> anyhow::Result<ExitStatus>;
}

impl CommandHelpers for Command {
/// Ensure that the command exits with a successful status code.
fn ensure_status(&mut self) -> anyhow::Result<ExitStatus> {
let status = self.status()?;
anyhow::ensure!(
status.success(),
"Command {} exited with status code {}",
self.get_program().to_str().unwrap_or_default(),
status
);
Ok(status)
}
}
2 changes: 0 additions & 2 deletions src/external_cli/rustup.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//! Utilities for the `rustup` CLI tool.

#![expect(dead_code, reason = "Will be used for build/run commands")]

use std::{env, ffi::OsString, process::Command};

use dialoguer::Confirm;
Expand Down
2 changes: 0 additions & 2 deletions src/external_cli/wasm_bindgen.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![expect(dead_code, reason = "Will be used for the build/run commands")]

use std::process::Command;

use super::arg_builder::ArgBuilder;
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! The library backend for the Bevy CLI.

pub mod build;
pub mod external_cli;
pub mod lint;
pub mod manifest;
pub mod template;
24 changes: 24 additions & 0 deletions src/manifest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use std::{fs::File, io::Read as _};

use toml_edit::{DocumentMut, Item, Value};

/// Get the contents of the manifest file.
fn get_cargo_toml(folder_name: &str) -> anyhow::Result<DocumentMut> {
let mut file = File::open(format!("{folder_name}/Cargo.toml"))?;

let mut content = String::new();
file.read_to_string(&mut content)?;

Ok(content.parse()?)
}

/// Determine the name of the cargo package.
pub(crate) fn package_name() -> anyhow::Result<String> {
let cargo_toml = get_cargo_toml("./")?;

if let Item::Value(Value::String(name)) = &cargo_toml["package"]["name"] {
Ok(name.value().clone())
} else {
Err(anyhow::anyhow!("No package name defined in Cargo.toml"))
}
}