#![expect(clippy::unwrap_used)]
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use cargo_metadata::camino::Utf8Path;
use cargo_metadata::{CargoOpt, Metadata, MetadataCommand, Package, PackageId};
use crate::should_output_cargo_build_instructions;
fn should_run() -> bool {
#![expect(clippy::match_same_arms)]
use super::Environment;
match Environment::detect() {
Environment::PublishingCrates | Environment::CondaBuild => false,
Environment::RerunCI => true,
Environment::DeveloperInWorkspace => true,
Environment::UsedAsDependency => false,
}
}
pub fn rebuild_if_crate_changed(pkg_name: &str) {
if !should_run() {
return;
}
let metadata = MetadataCommand::new()
.features(CargoOpt::AllFeatures)
.exec()
.unwrap();
let mut files_to_watch = Default::default();
let pkgs = Packages::from_metadata(&metadata);
pkgs.track_implicit_dep(pkg_name, &mut files_to_watch);
for path in &files_to_watch {
rerun_if_changed(path);
}
}
pub fn get_and_track_env_var(env_var_name: &str) -> Result<String, std::env::VarError> {
if should_output_cargo_build_instructions() {
println!("cargo:rerun-if-env-changed={env_var_name}");
}
std::env::var(env_var_name)
}
pub fn is_tracked_env_var_set(env_var_name: &str) -> bool {
match get_and_track_env_var(env_var_name) {
Err(_) => false,
Ok(value) => match value.to_lowercase().as_str() {
"1" | "yes" | "true" => true,
"0" | "no" | "false" => false,
_ => {
panic!("Failed to understand boolean env-var {env_var_name}={value}");
}
},
}
}
pub fn rerun_if_changed(path: impl AsRef<Path>) {
let path = path.as_ref();
assert!(path.exists(), "Failed to find {path:?}");
if should_output_cargo_build_instructions() {
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
}
}
pub fn rerun_if_changed_or_doesnt_exist(path: impl AsRef<Path>) {
let path = path.as_ref();
if should_output_cargo_build_instructions() {
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
}
}
pub fn rerun_if_changed_glob(path: impl AsRef<Path>, files_to_watch: &mut HashSet<PathBuf>) {
let path = path.as_ref();
let path = path.to_str().unwrap().trim_start_matches(r"\\?\");
for path in glob::glob(path).unwrap() {
files_to_watch.insert(path.unwrap());
}
}
pub fn write_file_if_necessary(
path: impl AsRef<std::path::Path>,
content: &[u8],
) -> std::io::Result<()> {
if let Ok(cur_bytes) = std::fs::read(&path)
&& cur_bytes == content
{
return Ok(());
}
std::fs::write(path, content)
}
fn track_crate_files(manifest_path: &Utf8Path, files_to_watch: &mut HashSet<PathBuf>) {
let mut dep_path = manifest_path.to_owned();
dep_path.pop();
rerun_if_changed_glob(dep_path.join("Cargo.toml"), files_to_watch); rerun_if_changed_glob(dep_path.join("**/*.rs"), files_to_watch);
rerun_if_changed_glob(dep_path.join("**/*.wgsl"), files_to_watch);
}
pub 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, files_to_watch: &mut HashSet<PathBuf>) {
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:?}")
});
track_crate_files(&pkg.manifest_path, files_to_watch);
let mut tracked = HashSet::new();
self.track_patched_deps(&mut tracked, pkg, files_to_watch);
}
fn track_patched_deps(
&self,
tracked: &mut HashSet<PackageId>,
root: &Package,
files_to_watch: &mut HashSet<PathBuf>,
) {
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 {
track_crate_files(&dep_pkg.manifest_path, files_to_watch);
}
if tracked.insert(dep_pkg.id.clone()) {
self.track_patched_deps(tracked, dep_pkg, files_to_watch);
}
}
}
}