//! 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 `` 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), ©, &["CMakeLists.txt", "src", "LICENSE"], ); // Upstream master (2026-07-20) relies on transitive 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 ") { let patched = text.replacen( "#include ", "#include \n#include ", 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(©) .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 = 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() {}