diff --git a/.gitignore b/.gitignore index 4fffb2f..e25d354 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ +/bin /target /Cargo.lock +.crates.toml +.crates2.json diff --git a/README.md b/README.md index 068db8d..6050b2c 100644 --- a/README.md +++ b/README.md @@ -157,10 +157,17 @@ For more examples using the window, see the [examples](https://github.com/nanoqs cargo run -p ``` -To build and run a wasm example, make sure [`wasm-pack`](https://github.com/rustwasm/wasm-pack) is installed and then run: +To build and run a wasm example: ```sh -cargo xtask +cargo xtask build +cargo xtask serve ``` -It will start a local server and you can open http://localhost:3000 in your browser to see the application running. For the web, only [WebGPU](https://gpuweb.github.io/gpuweb/) backend is supported, so make sure your browser supports it. + +If [`wasm-pack`](https://github.com/rustwasm/wasm-pack) is already installed on the system, the build script will find it and use it to compile a wasm artifact. Otherwise, `wasm-pack` will be installed locally. To prevent this behavior add the `no-install` flag: +```sh +cargo xtask --no-install build +``` + +Eventually it will start a local server and you can open http://localhost:3000 in your browser to see the application running. For the web, only [WebGPU](https://gpuweb.github.io/gpuweb/) backend is supported, so make sure your browser supports it. Also see the [test](https://github.com/nanoqsh/dunge/tree/main/dunge/tests) directory for small examples of creation a single image. diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 58d9788..adbef54 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,43 +1,165 @@ -type Error = Box; +use std::{ + env, error, + io::ErrorKind, + path::{Path, PathBuf}, + process::{Command, ExitCode}, +}; -fn main() { - if let Err(err) = run() { - eprintln!("error: {err}"); +type Error = Box; + +fn main() -> ExitCode { + let (opts, mode) = match parse() { + Ok(ok) => ok, + Err(err) => { + eprintln!("args error: {err}"); + return ExitCode::FAILURE; + } + }; + + match start(opts, mode) { + Ok(()) => ExitCode::SUCCESS, + Err(err) => { + eprintln!("error: {err}"); + ExitCode::FAILURE + } } } -fn run() -> Result<(), Error> { - use std::{env, path::Path, process::Command}; +fn parse() -> Result<(Opts, Mode), &'static str> { + const MODEERR: &str = "undefined mode"; + const MODLERR: &str = "undefined module"; + + let mut args = env::args().skip(1); + let mut no_install = false; + let mode = loop { + match args.next().ok_or(MODEERR)?.as_str() { + "build" => break Mode::Build, + "serve" => break Mode::Serve, + opt => match opt.strip_prefix("--") { + Some("no-install") => no_install = true, + _ => return Err(MODEERR), + }, + } + }; + + let opts = Opts { + module: args.next().ok_or(MODLERR)?, + no_install, + }; + + Ok((opts, mode)) +} + +struct Opts { + module: String, + no_install: bool, +} + +enum Mode { + Build, + Serve, +} - let module = env::args().nth(1).ok_or("no module specified")?; +fn start(opts: Opts, mode: Mode) -> Result<(), Error> { + match mode { + Mode::Build => build(opts), + Mode::Serve => serve(opts), + } +} + +fn build(opts: Opts) -> Result<(), Error> { let root = Path::new(&env!("CARGO_MANIFEST_DIR")) .parent() .ok_or("root dir not found")?; - let status = Command::new("wasm-pack") - .current_dir(root) + let mut cmd = Command::new("wasm-pack"); + cmd.args([ + "build", + "examples/wasm", + "--no-pack", + "--no-typescript", + "--target", + "web", + ]) + .arg("--out-dir") + .arg(root.join("xtask/web").join(&opts.module)) + .args(["-F", &opts.module]); + + install_and_run(&mut cmd, opts.no_install) +} + +fn install_and_run(cmd: &mut Command, no_install: bool) -> Result<(), Error> { + add_bin_path(cmd); + let name = cmd.get_program().to_string_lossy().into_owned(); + match run(cmd, &name) { + Run::Ok => return Ok(()), + Run::NotFound if no_install => {} + Run::NotFound => { + eprintln!("{name} not found, installing.."); + install(&name)?; + } + Run::Failed(s) => return Err(Error::from(s)), + } + + match run(cmd, &name) { + Run::Ok => Ok(()), + Run::NotFound if no_install => Err(Error::from(format!("{name} not found"))), + Run::NotFound => Err(Error::from(format!("failed to install {name}"))), + Run::Failed(s) => Err(Error::from(s)), + } +} + +fn add_bin_path(cmd: &mut Command) { + let bin_path = PathBuf::from("./bin"); + let mut paths: Vec<_> = env::var_os("PATH") + .map(|path| env::split_paths(&path).collect()) + .unwrap_or_default(); + + if !paths.contains(&bin_path) { + paths.push(bin_path); + } + + let paths = env::join_paths(paths).expect("join paths"); + cmd.env("PATH", paths); +} + +enum Run { + Ok, + NotFound, + Failed(String), +} + +fn run(cmd: &mut Command, name: &str) -> Run { + match cmd.status() { + Ok(status) if status.success() => Run::Ok, + Ok(_) => Run::Failed(format!("execution of {name} failed")), + Err(err) if err.kind() == ErrorKind::NotFound => Run::NotFound, + Err(err) => Run::Failed(format!("failed to run {name}: {err}")), + } +} + +fn install(name: &str) -> Result<(), Error> { + let status = Command::new("cargo") .args([ - "build", - "examples/wasm", - "--no-pack", - "--no-typescript", - "--target", - "web", + "install", + "--root", + ".", + "--target-dir", + "target", + "--locked", ]) - .arg("--out-dir") - .arg(root.join("xtask/web").join(&module)) - .args(["-F", &module]) + .arg(name) .status() - .map_err(|err| format!("failed to run wasm-pack: {err}"))?; + .map_err(|err| format!("failed to run cargo: {err}"))?; - if !status.success() { - return Err(Error::from("wasm-pack build failed")); + if status.success() { + Ok(()) + } else { + Err(Error::from(format!("failed to install {name}"))) } - - serv(&module) } -fn serv(module: &str) -> Result<(), Error> { +fn serve(Opts { module, .. }: Opts) -> Result<(), Error> { use { askama::Template, helpers::serv::{self, Page}, @@ -51,7 +173,7 @@ fn serv(module: &str) -> Result<(), Error> { module: &'a str, } - Index { module } + Index { module: &module } }; let html = index.render()?.leak();