flawless_utils/
build.rs

1use std::{
2    env::{self, VarError},
3    ffi::OsStr,
4    fs::{self, read_dir},
5    io::ErrorKind,
6    path::PathBuf,
7    process::{exit, Command},
8};
9
10use toml::{map::Map, Value};
11
12/// Builds the flawless module located in the `workflows` directory in release mode.
13pub fn build_release(module: &str) {
14    build(module, true);
15}
16
17/// Builds the flawless module located in the `workflows` directory in debug mode.
18pub fn build_debug(module: &str) {
19    build(module, false);
20}
21
22/// Builds the flawless module located in the `workflows` directory.
23fn build(module: &str, relase: bool) {
24    // Find the module directory inside the `workflows` one, located at the cargo project root.
25    let cargo_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|err| {
26        eprintln!("`flawless::build` failed to read the `CARGO_MANIFEST_DIR` environment variable.");
27        eprintln!("Error: {}", err);
28        if err == VarError::NotPresent {
29            eprintln!("Hint: `flawless::build` should only be used from a Rust `build.rs` script.");
30        }
31        exit(-1);
32    });
33
34    let modules_dir = PathBuf::from(cargo_dir).join("workflows");
35    let mut modules = read_dir(&modules_dir).unwrap_or_else(|err| {
36        eprintln!("`flawless::build` couldn't read directory {modules_dir:?}, {err:?}.");
37        if err.kind() == ErrorKind::NotFound {
38            eprintln!("Hint: `flawless::build` modules should be placed inside the `workflows` directory.");
39        }
40        exit(-1);
41    });
42
43    let module_path = modules.find(|entry| entry.as_ref().unwrap().file_name().to_string_lossy() == module);
44    if module_path.is_none() {
45        eprintln!("`flawless::build` failed to find module '{module}' in `{modules_dir:?}`.");
46        exit(-1);
47    }
48
49    let module_path = module_path.unwrap().unwrap().path(); // Unwraps should succeed if find worked.
50    if !module_path.is_dir() {
51        eprintln!("`flawless::build` error: `{:?}` is not a directory.", module_path);
52        exit(-1);
53    }
54
55    // Check if module directory is a valid Rust WebAssembly crate.
56    let module_cargo_path = module_path.join("Cargo.toml");
57    let cargo_toml = fs::read_to_string(&module_cargo_path).unwrap_or_else(|err| {
58        eprintln!("`flawless::build` failed to read `{:?}`", module_cargo_path);
59        eprintln!("Error: {err}");
60        exit(-1);
61    });
62
63    let toml: Value = toml::from_str(&cargo_toml).unwrap_or_else(|err| {
64        eprintln!("`flawless::build` failed to parse toml `{:?}`", module_cargo_path);
65        eprintln!("Error: {err}");
66        exit(-1);
67    });
68
69    let package_name = toml
70        .get("package")
71        // Empty map forces next entry to not contain the `name` field and fail.
72        .map_or(Value::Table(Map::new()), |v| v.clone())
73        .get("name")
74        // Empty map is not going to be a string and the error will be triggered.
75        .map_or(Value::Table(Map::new()), |v| v.clone());
76    let package_name = package_name.as_str().unwrap_or_else(|| {
77        eprintln!("`flawless::build` failed to retrieve package name from `{:?}`.", module_cargo_path);
78        exit(-1);
79    });
80
81    // Compile the module directory into a WebAssembly artifact.
82    let out_dir = match env::var("OUT_DIR") {
83        Ok(out_dir) => out_dir,
84        Err(err) => {
85            eprintln!("`flawless::build` failed to read the `OUT_DIR` environment variable.");
86            eprintln!("Error: {}", err);
87            if err == VarError::NotPresent {
88                eprintln!("Hint: `flawless::build` should only be used from a Rust `build.rs` script.");
89            }
90            exit(-1);
91        }
92    };
93
94    let out_dir = PathBuf::from(out_dir).join("flawless").join(module);
95
96    let mut cmd = Command::new("cargo");
97    cmd.current_dir(module_path)
98        .args(["rustc"])
99        .args(["--lib"])
100        .args(["--crate-type", "cdylib"])
101        .args(["--target", "wasm32-unknown-unknown"])
102        .args([OsStr::new("--target-dir"), out_dir.as_os_str()])
103        .args(["--color=always"]);
104    if relase {
105        cmd.args(["--release"]);
106    }
107    let output = cmd.output().expect("Building flawless module failed");
108    if !output.status.success() {
109        eprintln!("`flawless::build` failed to compile `{module}` module.");
110        eprintln!("{}", String::from_utf8_lossy(&output.stderr));
111        exit(-1);
112    }
113
114    let output_path = if relase { "release" } else { "debug" };
115
116    let module_wasm_path =
117        out_dir.join("wasm32-unknown-unknown").join(output_path).join(format!("{package_name}.wasm"));
118
119    // Set environment variable to be used during the rust compilation.
120    // This is needed for the `load_module_from_build!` macro to work.
121    println!("cargo::rustc-env=FLAWLESS_UTIL_BUILD_{}={}", module, module_wasm_path.to_string_lossy());
122}