flawless-utils 1.0.0-beta.2

Utility tools for integrating flawless workflows into Rust projects.
Documentation
use std::{
    env::{self, VarError},
    ffi::OsStr,
    fs::{self, read_dir},
    io::ErrorKind,
    path::PathBuf,
    process::{exit, Command},
};

use toml::{map::Map, Value};

/// Builds the flawless module located in the `workflows` directory.
pub fn build(module: &str) {
    // Find the module directory inside the `workflows` one, located at the cargo project root.
    let cargo_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|err| {
        eprintln!("`flawless::build` failed to read the `CARGO_MANIFEST_DIR` environment variable.");
        eprintln!("Error: {}", err);
        if err == VarError::NotPresent {
            eprintln!("Hint: `flawless::build` should only be used from a Rust `build.rs` script.");
        }
        exit(-1);
    });

    let modules_dir = PathBuf::from(cargo_dir).join("workflows");
    let mut modules = read_dir(&modules_dir).unwrap_or_else(|err| {
        eprintln!("`flawless::build` douldn't read directory {modules_dir:?}, {err:?}.");
        if err.kind() == ErrorKind::NotFound {
            eprintln!("Hint: `flawless::build` modules should be placed inside the `workflows` directory.");
        }
        exit(-1);
    });

    let module_path = modules.find(|entry| entry.as_ref().unwrap().file_name().to_string_lossy() == module);
    if module_path.is_none() {
        eprintln!("`flawless::build` failed to find module '{module}' in `{modules_dir:?}`.");
        exit(-1);
    }

    let module_path = module_path.unwrap().unwrap().path(); // Unwraps should succeed if find worked.
    if !module_path.is_dir() {
        eprintln!("`flawless::build` error: `{:?}` is not a directory.", module_path);
        exit(-1);
    }

    // Check if module directory is a valid Rust WebAssembly crate.
    let module_cargo_path = module_path.join("Cargo.toml");
    let cargo_toml = fs::read_to_string(&module_cargo_path).unwrap_or_else(|err| {
        eprintln!("`flawless::build` failed to read `{:?}`", module_cargo_path);
        eprintln!("Error: {err}");
        exit(-1);
    });

    let toml: Value = toml::from_str(&cargo_toml).unwrap_or_else(|err| {
        eprintln!("`flawless::build` failed to parse toml `{:?}`", module_cargo_path);
        eprintln!("Error: {err}");
        exit(-1);
    });

    let package_name = toml
        .get("package")
        // Empty map forces next entry to not contain the `name` field and fail.
        .map_or(Value::Table(Map::new()), |v| v.clone())
        .get("name")
        // Empty map is not going to be a string and the error will be triggered.
        .map_or(Value::Table(Map::new()), |v| v.clone());
    let package_name = package_name.as_str().unwrap_or_else(|| {
        eprintln!("`flawless::build` failed to retrieve package name from `{:?}`.", module_cargo_path);
        exit(-1);
    });

    // Compile the module directory into a WebAssembly artifact.
    let out_dir = match env::var("OUT_DIR") {
        Ok(out_dir) => out_dir,
        Err(err) => {
            eprintln!("`flawless::build` failed to read the `OUT_DIR` environment variable.");
            eprintln!("Error: {}", err);
            if err == VarError::NotPresent {
                eprintln!("Hint: `flawless::build` should only be used from a Rust `build.rs` script.");
            }
            exit(-1);
        }
    };

    let out_dir = PathBuf::from(out_dir).join("flawless").join(module);

    let output = Command::new("cargo")
        .current_dir(module_path)
        .args(&["rustc"])
        .args(&["--lib"])
        .args(&["--release"])
        .args(&["--crate-type", "cdylib"])
        .args(&["--target", "wasm32-unknown-unknown"])
        .args(&[OsStr::new("--target-dir"), out_dir.as_os_str()])
        .args(&["--color=always"])
        .output()
        .expect("Building flawless module failed");
    if !output.status.success() {
        eprintln!("`flawless::build` failed to compile `{module}` module.");
        eprintln!("{}", String::from_utf8_lossy(&output.stderr));
        exit(-1);
    }

    let module_wasm_path =
        out_dir.join("wasm32-unknown-unknown").join("release").join(format!("{package_name}.wasm"));

    // Set environment variable to be used during the rust compilation.
    // This is needed for the `load_module_from_build!` macro to work.
    println!("cargo::rustc-env=FLAWLESS_UTIL_BUILD_{}={}", module, module_wasm_path.to_string_lossy());
}