use cargo_metadata::{CargoOpt, Metadata, MetadataCommand, Package, PackageId};
use std::{
collections::{HashMap, HashSet},
fs,
path::Path,
process::Stdio,
};
fn rerun_if_changed(path: &str) {
let path = path.trim_start_matches(r"\\?\");
for path in glob::glob(path).unwrap() {
println!("cargo:rerun-if-changed={}", path.unwrap().to_string_lossy());
}
}
struct Packages<'a> {
pkgs: HashMap<&'a str, &'a Package>,
}
impl<'a> Packages<'a> {
pub fn from_metadata(metadata: &'a Metadata) -> Self {
let pkgs = metadata
.packages
.iter()
.map(|pkg| (pkg.name.as_str(), pkg))
.collect::<HashMap<_, _>>();
Self { pkgs }
}
pub fn track_implicit_dep(&self, pkg_name: &str) {
let pkg = self.pkgs.get(pkg_name).unwrap_or_else(|| {
let found_names: Vec<&str> = self.pkgs.values().map(|pkg| pkg.name.as_str()).collect();
panic!("Failed to find package {pkg_name:?} among {found_names:?}")
});
{
let mut path = pkg.manifest_path.clone();
path.pop();
rerun_if_changed(path.join("Cargo.toml").as_ref());
rerun_if_changed(path.join("**/*.rs").as_ref());
}
let mut tracked = HashSet::new();
self.track_patched_deps(&mut tracked, pkg);
}
fn track_patched_deps(&self, tracked: &mut HashSet<PackageId>, root: &Package) {
for dep_pkg in root
.dependencies
.iter()
.filter_map(|dep| self.pkgs.get(dep.name.as_str()))
{
let exists_on_local_disk = dep_pkg.source.is_none();
if exists_on_local_disk {
let mut dep_path = dep_pkg.manifest_path.clone();
dep_path.pop();
rerun_if_changed(dep_path.join("Cargo.toml").as_ref()); rerun_if_changed(dep_path.join("**/*.rs").as_ref());
}
if tracked.insert(dep_pkg.id.clone()) {
self.track_patched_deps(tracked, dep_pkg);
}
}
}
}
fn build_web() {
let repository_root_dir = format!("{}/../..", std::env!("CARGO_MANIFEST_DIR"));
let crate_name = "re_viewer";
let build_dir = format!("{repository_root_dir}/web_viewer");
assert!(
Path::new(&build_dir).exists(),
"Failed to find dir {build_dir}. CWD: {:?}, CARGO_MANIFEST_DIR: {:?}",
std::env::current_dir(),
std::env!("CARGO_MANIFEST_DIR")
);
let wasm_path = Path::new(&build_dir).join([crate_name, "_bg.wasm"].concat());
fs::remove_file(wasm_path.clone()).ok();
let js_path = Path::new(&build_dir).join([crate_name, ".js"].concat());
fs::remove_file(js_path).ok();
let metadata = MetadataCommand::new()
.manifest_path("./Cargo.toml")
.features(CargoOpt::AllFeatures)
.exec()
.unwrap();
let target_wasm = format!("{}_wasm", metadata.target_directory);
let release = std::env::var("PROFILE").unwrap() == "release";
let root_dir = metadata.target_directory.parent().unwrap();
let mut cmd = std::process::Command::new("cargo");
cmd.current_dir(root_dir);
cmd.args([
"build",
"--target-dir",
&target_wasm,
"-p",
crate_name,
"--lib",
"--target",
"wasm32-unknown-unknown",
]);
cmd.env("RUSTFLAGS", "--cfg=web_sys_unstable_apis");
cmd.env("CARGO_ENCODED_RUSTFLAGS", "");
if release {
cmd.arg("--release");
}
eprintln!("wasm build cmd: {cmd:?}");
let output = cmd
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
.expect("failed to compile re_viewer for wasm32");
eprintln!("compile status: {}", output.status);
eprintln!(
"compile stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(output.status.success());
let build = if release { "release" } else { "debug" };
let target_name = [crate_name, ".wasm"].concat();
let target_path = Path::new(&target_wasm)
.join("wasm32-unknown-unknown")
.join(build)
.join(target_name);
let mut cmd = std::process::Command::new("wasm-bindgen");
cmd.current_dir(root_dir);
cmd.args([
target_path.to_str().unwrap(),
"--out-dir",
&build_dir,
"--no-modules",
"--no-typescript",
]);
eprintln!("wasm-bindgen cmd: {cmd:?}");
let output = cmd
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
.unwrap_or_else(|err| panic!("Failed to generate JS bindings: {err}. target_path: {target_path:?}, build_dir: {build_dir}"));
eprintln!("wasm-bindgen status: {}", output.status);
eprintln!(
"wasm-bindgen stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(output.status.success());
if release {
let wasm_path = wasm_path.to_str().unwrap();
let mut cmd = std::process::Command::new("wasm-opt");
cmd.current_dir(root_dir);
cmd.args([wasm_path, "-O2", "--fast-math", "-o", wasm_path]);
eprintln!("wasm-opt cmd: {cmd:?}");
let output = cmd
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
.expect("failed to optimize wasm");
eprintln!("wasm-opt status: {}", output.status);
eprintln!(
"wasm-opt stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(output.status.success());
}
}
fn main() {
if std::env::var("IS_IN_RERUN_WORKSPACE") != Ok("yes".to_owned()) {
return;
}
if std::env::var("RERUN_IS_PUBLISHING") == Ok("yes".to_owned()) {
return;
}
let metadata = MetadataCommand::new()
.features(CargoOpt::AllFeatures)
.exec()
.unwrap();
rerun_if_changed("../../web_viewer/favicon.ico");
rerun_if_changed("../../web_viewer/index.html");
rerun_if_changed("../../web_viewer/sw.js");
let pkgs = Packages::from_metadata(&metadata);
pkgs.track_implicit_dep("re_viewer");
if std::env::var("CARGO_FEATURE___CI").is_ok() {
eprintln!("__ci feature detected: Skipping building of web viewer wasm.");
} else {
eprintln!("Build web viewer wasm…");
build_web();
}
}