Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-fast/src/kit.rs

//! **The kit: the ONE box's pieces, built from the sibling checkouts into one directory**
//! ([`crate::one_box::Kit`]). A product's `cargo xtask fast` builds its own box rows and its
//! guest, then asks for this (`monetize-fast kit --out DIR --guest <product>=<wasm>`); the
//! builds run in the toolchain container (nornir-xtask-support's `ci::Container`) unless this
//! process already is in one.
//!
//! | piece | from | how |
//! |---|---|---|
//! | `monetize-admin` | monetize `crates/monetize-admin` | its declared row's target (musl), debug |
//! | `fast-edge` | monetize `crates/fast-edge` (a root member) | musl, the root's `fast-edge` profile |
//! | `mock-upcloud` | monetize | the host side's binary (it runs beside the driver) |
//! | `fake_stripe_serve` | monetize-impl `monetize-pay-impl` example (`stripe`, `testing`) | the host side's |
//! | `guest-upcloud.wasm`, `guest-stripe.wasm` | monetize-impl `guests/upcloud`, `guests/stripe` | wasm32, release, unsigned (the run signs them under its own CA) |
//! | `guest-<product>.wasm` | the product's own build, handed in | copied |

use std::path::{Path, PathBuf};
use std::process::Command;

use nornir_xtask_support::ci::{toolchain_is_here, Container};

/// The musl target the ONE box's row declares.
pub const MUSL: &str = "x86_64-unknown-linux-musl";
/// The guests' target.
pub const WASM: &str = "wasm32-unknown-unknown";

/// One cargo build in `repo` (a checkout under `parent`), in the toolchain container or here.
fn cargo(parent: &Path, repo: &str, args: &[&str]) -> Result<(), String> {
    let root = parent.join(repo);
    if !root.join("Cargo.toml").is_file() {
        return Err(format!("[fast-kit] {} is not checked out beside the others", root.display()));
    }
    let line = format!("cargo {}", args.join(" "));
    println!("fast kit: {repo}: {line}");
    let status = if toolchain_is_here() {
        Command::new("cargo").args(args).current_dir(&root).status()
    } else {
        let c = Container::from_env(&root);
        std::fs::create_dir_all(&c.target).map_err(|e| format!("{}: {e}", c.target.display()))?;
        let argv = c.argv(None, &[], "cargo", &args.iter().map(|a| a.to_string()).collect::<Vec<_>>());
        Command::new(&argv[0]).args(&argv[1..]).status()
    };
    match status {
        Ok(s) if s.success() => Ok(()),
        Ok(s) => Err(format!("[fast-kit] {repo}: {line}: exited {s}")),
        Err(e) => Err(format!("[fast-kit] {repo}: {line}: {e}")),
    }
}

/// The build output directory a build in `repo` lands in (the container's target, or cargo's).
fn target_of(parent: &Path, repo: &str) -> PathBuf {
    let root = parent.join(repo);
    if toolchain_is_here() {
        std::env::var_os("CARGO_TARGET_DIR").map(PathBuf::from).unwrap_or_else(|| root.join("target"))
    } else {
        Container::from_env(&root).target
    }
}

fn take(from: &Path, out: &Path, name: &str) -> Result<String, String> {
    if !from.is_file() {
        return Err(format!("[fast-kit] {} was not built", from.display()));
    }
    let to = out.join(name);
    std::fs::copy(from, &to).map_err(|e| format!("{} -> {}: {e}", from.display(), to.display()))?;
    Ok(name.to_string())
}

/// **Build the kit into `out`** from the checkouts under `parent`; `guests` are the products'
/// own unsigned modules, `(product, path)`. Answers the pieces' names.
pub fn build(parent: &Path, out: &Path, guests: &[(String, PathBuf)]) -> Result<Vec<String>, String> {
    std::fs::create_dir_all(out).map_err(|e| format!("{}: {e}", out.display()))?;
    let mut got = Vec::new();
    cargo(parent, "monetize", &["build", "--locked", "--manifest-path", "crates/monetize-admin/Cargo.toml", "--bin", "monetize-admin", "--target", MUSL])?;
    cargo(parent, "monetize", &["build", "--locked", "-p", "fast-edge", "--profile", "fast-edge", "--target", MUSL])?;
    cargo(parent, "monetize", &["build", "--locked", "-p", "mock-upcloud", "--bin", "mock-upcloud"])?;
    let t = target_of(parent, "monetize");
    got.push(take(&t.join(MUSL).join("debug/monetize-admin"), out, "monetize-admin")?);
    got.push(take(&t.join(MUSL).join("fast-edge/fast-edge"), out, "fast-edge")?);
    got.push(take(&t.join("debug/mock-upcloud"), out, "mock-upcloud")?);
    cargo(parent, "monetize-impl", &["build", "--locked", "-p", "monetize-pay-impl", "--features", "stripe,testing", "--example", "fake_stripe_serve"])?;
    cargo(parent, "monetize-impl", &["build", "--locked", "-p", "monetize-guest-upcloud", "-p", "monetize-guest-stripe", "--release", "--target", WASM])?;
    let t = target_of(parent, "monetize-impl");
    got.push(take(&t.join("debug/examples/fake_stripe_serve"), out, "fake_stripe_serve")?);
    got.push(take(&t.join(WASM).join("release/monetize_guest_upcloud.wasm"), out, "guest-upcloud.wasm")?);
    got.push(take(&t.join(WASM).join("release/monetize_guest_stripe.wasm"), out, "guest-stripe.wasm")?);
    for (product, path) in guests {
        got.push(take(path, out, &format!("guest-{product}.wasm"))?);
    }
    Ok(got)
}

#[cfg(test)]
mod tests {
    #[test]
    fn a_kit_names_its_pieces_by_their_fixed_names() {
        let d = std::env::temp_dir().join(format!("fast-kit-{}", std::process::id()));
        let _ = std::fs::remove_dir_all(&d);
        std::fs::create_dir_all(&d).unwrap();
        for n in ["monetize-admin", "fast-edge", "mock-upcloud", "fake_stripe_serve", "guest-upcloud.wasm", "guest-stripe.wasm", "guest-holger.wasm"] {
            std::fs::write(d.join(n), "x").unwrap();
        }
        let k = crate::one_box::Kit::from_dir(&d).unwrap();
        assert_eq!(k.guests.keys().cloned().collect::<Vec<_>>(), vec!["holger", "stripe", "upcloud"]);
        std::fs::remove_file(d.join("fast-edge")).unwrap();
        assert!(crate::one_box::Kit::from_dir(&d).unwrap_err().contains("fast-edge"));
        let _ = std::fs::remove_dir_all(&d);
    }
}