fumi/core/build.rs

187 lines
7.1 KiB
Rust

//! Builds the microReticulum client-carrier shim when the `rns` feature is on.
//!
//! The library source is not vendored into this repository: the environment
//! must point at a checkout via MICRORETICULUM_SOURCE_DIR (the Nix flake and
//! the dev shell both set it). The source tree is copied into OUT_DIR and
//! patched there — a one-line `<cstdint>` include the upstream master needs
//! under current libstdc++/libc++ — because the input may be read-only and
//! both build paths should compile identical sources.
#[cfg(feature = "rns")]
fn main() {
println!("cargo:rerun-if-changed=shim/smolmail_rns.cpp");
println!("cargo:rerun-if-changed=shim/udp_interface.cpp");
println!("cargo:rerun-if-env-changed=MICRORETICULUM_SOURCE_DIR");
for dep in DEP_VARS {
println!("cargo:rerun-if-env-changed={dep}");
}
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
let source = std::env::var("MICRORETICULUM_SOURCE_DIR")
.expect("the rns feature requires MICRORETICULUM_SOURCE_DIR (a microReticulum checkout)");
// Copy the parts cmake needs: the root CMakeLists.txt and src/.
let copy = out_dir.join("microReticulum");
copy_tree(
std::path::Path::new(&source),
&copy,
&["CMakeLists.txt", "src", "LICENSE"],
);
// Upstream master (2026-07-20) relies on transitive <cstdint> includes
// that libstdc++ 15 dropped; add the include itself.
let memory_h = copy.join("src/microReticulum/Utilities/Memory.h");
let text = std::fs::read_to_string(&memory_h).expect("Memory.h readable");
if !text.contains("#include <cstdint>") {
let patched = text.replacen(
"#include <memory>",
"#include <memory>\n#include <cstdint>",
1,
);
std::fs::write(&memory_h, patched).expect("Memory.h writable");
}
// Configure + build the library. Dependency overrides (RNS_*_SOURCE_DIR)
// are passed straight through so a sandboxed build never fetches.
let build = out_dir.join("rns-build");
let mut configure = std::process::Command::new("cmake");
configure
.arg("-S")
.arg(&copy)
.arg("-B")
.arg(&build)
.arg("-DCMAKE_BUILD_TYPE=Release")
// The NixOS gcc wrapper injects -Werror=format-security, which
// errors once the project's -Wno-format disables -Wformat.
.arg("-DCMAKE_C_FLAGS=-Wno-error=format-security")
.arg("-DCMAKE_CXX_FLAGS=-Wno-error=format-security")
.arg("-DRNS_BUILD_TESTS=OFF")
.arg("-DRNS_BUILD_EXAMPLES=OFF")
.arg("-DRNS_BUILD_INTEROP=OFF")
.arg("-DRNS_USE_FS=ON")
.arg("-DRNS_PERSIST_PATHS=OFF")
// The known-destinations store is what Identity::recall reads; a
// server never recalls a remote identity (links arrive proved), so
// bunshin disables it, but a client's path discovery is dead without
// it: Transport refuses to add a destination whose identity it
// cannot remember.
.arg("-DRNS_PERSIST_KNOWN_DESTINATIONS=ON")
.arg("-DRNS_PERSIST_HASHLIST=OFF");
for dep in DEP_VARS {
if let Ok(path) = std::env::var(dep) {
configure.arg(format!("-D{dep}={path}"));
}
}
let status = configure.status().expect("cmake configure");
assert!(status.success(), "cmake configure failed");
let status = std::process::Command::new("cmake")
.arg("--build")
.arg(&build)
.arg("--target")
.arg("microReticulum")
.status()
.expect("cmake build");
assert!(status.success(), "cmake build failed");
// The same include roots and defines the library's CMake target exports
// publicly, mirrored so the shim TU sees identical macros (the upstream
// CMakeLists warns about ODR divergence otherwise). A vendored dep (env
// override) lives at its checkout root; a fetched one under _deps.
let deps = build.join("_deps");
let mut includes: Vec<std::path::PathBuf> = vec![copy.join("src")];
for (env_var, fetch_name, sub) in DEP_INCLUDES {
includes.push(match std::env::var(env_var) {
Ok(root) => std::path::Path::new(&root).join(sub),
Err(_) => deps.join(format!("{fetch_name}-src")).join(sub),
});
}
let mut build_cc = cc::Build::new();
build_cc
.cpp(true)
.std("c++17")
.define("NATIVE", None)
.define("USTORE_USE_UNIVERSALFS", None)
.define("RNS_USE_FS", None)
.file("shim/smolmail_rns.cpp")
.file("shim/udp_interface.cpp");
for include in &includes {
build_cc.include(include);
}
build_cc.compile("smolmail_rns_shim");
println!("cargo:rustc-link-search=native={}", build.display());
println!("cargo:rustc-link-lib=static=microReticulum");
println!("cargo:rustc-link-lib=static=CryptoLib");
}
// microReticulum's own local-checkout override variables.
#[cfg(feature = "rns")]
const DEP_VARS: &[&str] = &[
"RNS_ARDUINOJSON_SOURCE_DIR",
"RNS_MSGPACK_SOURCE_DIR",
"RNS_CRYPTO_SOURCE_DIR",
"RNS_MICROSTORE_SOURCE_DIR",
"RNS_ARXCONTAINER_SOURCE_DIR",
"RNS_ARXTYPETRAITS_SOURCE_DIR",
"RNS_DEBUGLOG_SOURCE_DIR",
];
// (override env var, FetchContent name, include subdir within the dep root).
#[cfg(feature = "rns")]
const DEP_INCLUDES: &[(&str, &str, &str)] = &[
("RNS_ARDUINOJSON_SOURCE_DIR", "arduinojson", "src"),
("RNS_MSGPACK_SOURCE_DIR", "msgpack", ""),
("RNS_ARXCONTAINER_SOURCE_DIR", "arxcontainer", ""),
("RNS_ARXTYPETRAITS_SOURCE_DIR", "arxtypetraits", ""),
("RNS_DEBUGLOG_SOURCE_DIR", "debuglog", ""),
("RNS_CRYPTO_SOURCE_DIR", "crypto", ""),
("RNS_MICROSTORE_SOURCE_DIR", "microstore", "include"),
];
#[cfg(feature = "rns")]
fn copy_tree(src: &std::path::Path, dst: &std::path::Path, entries: &[&str]) {
std::fs::create_dir_all(dst).expect("OUT_DIR writable");
for entry in entries {
let from = src.join(entry);
if !from.exists() {
continue;
}
let to = dst.join(entry);
if from.is_dir() {
copy_dir(&from, &to);
} else {
copy_file(&from, &to);
}
}
}
#[cfg(feature = "rns")]
fn copy_dir(src: &std::path::Path, dst: &std::path::Path) {
std::fs::create_dir_all(dst).expect("OUT_DIR writable");
for entry in std::fs::read_dir(src).expect("readable source dir") {
let entry = entry.expect("readable source dir").path();
let to = dst.join(entry.file_name().unwrap());
if entry.is_dir() {
copy_dir(&entry, &to);
} else {
copy_file(&entry, &to);
}
}
}
/// fs::copy preserves the source's mode, and a Nix store checkout is
/// read-only, so the copy must be made writable before the patch step.
#[cfg(feature = "rns")]
fn copy_file(from: &std::path::Path, to: &std::path::Path) {
std::fs::copy(from, to).expect("copy file");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(to, std::fs::Permissions::from_mode(0o644))
.expect("copied file writable");
}
}
#[cfg(not(feature = "rns"))]
fn main() {}