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 2 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.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ dialoguer = { version = "0.11.0", default-features = false }
serde = { features = ["derive"], version = "1.0.210" }
reqwest = { features = ["blocking", "json"], version = "0.12.7" }
regex = "1.10.6"
toml_edit = { version = "0.22.21", default-features = false }
TimJentzsch marked this conversation as resolved.
Show resolved Hide resolved
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::{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) => build(&args)?,
TimJentzsch marked this conversation as resolved.
Show resolved Hide resolved
}

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,
}
38 changes: 38 additions & 0 deletions src/build/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::{
external_cli::{cargo, rustup, wasm_bindgen},
manifest::package_name,
};

pub use self::args::BuildArgs;

mod args;

pub fn build(args: &BuildArgs) -> anyhow::Result<()> {
if args.is_web() {
ensure_web_setup()?;
}

let cargo_args = args.cargo_args_builder();

if args.is_web() {
TimJentzsch marked this conversation as resolved.
Show resolved Hide resolved
println!("Building for WASM...");
cargo::build::command().args(cargo_args).status()?;
TimJentzsch marked this conversation as resolved.
Show resolved Hide resolved

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).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)
}
}
15 changes: 14 additions & 1 deletion src/external_cli/cargo/mod.rs
TimJentzsch marked this conversation as resolved.
Show resolved Hide resolved
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;

fn program() -> OsString {
env::var_os("BEVY_CLI_CARGO").unwrap_or("cargo".into())
Expand Down Expand Up @@ -65,6 +65,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
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"))
}
}