use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
struct Source {
name: String,
url: String,
owner: String,
repo_name: String,
commit: String,
}
fn main() {
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let dir = manifest.join("boilerplates");
println!("cargo:rerun-if-changed={}", dir.display());
let code = if env::var_os("CARGO_FEATURE_EMBEDDED").is_some() {
generate(&dir)
} else {
String::new()
};
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
fs::write(out.join("embedded.rs"), code).expect("failed to write the embedded boilerplates");
}
fn generate(dir: &Path) -> String {
let sources = read_sources(&dir.join("SOURCES"));
let mut code = String::from(
"// Generated by build.rs from the snapshot under boilerplates/. Do not edit.\n\n\
/// (repository name, url, owner, repository name, the commit the snapshot came from)\n\
pub(super) static REPOSITORIES: &[(&str, &str, &str, &str, &str)] = &[\n",
);
for s in &sources {
code.push_str(&format!(
" ({:?}, {:?}, {:?}, {:?}, {:?}),\n",
s.name, s.url, s.owner, s.repo_name, s.commit
));
}
code.push_str(
"];\n\n\
/// (repository name, path from the repository root, content)\n\
pub(super) static BOILERPLATES: &[(&str, &str, &str)] = &[\n",
);
for s in &sources {
let root = dir.join(&s.name);
let mut files = BTreeMap::new();
collect(&root, &root, &mut files);
assert!(
!files.is_empty(),
"{}: the snapshot holds no boilerplate; run `just vendor_boilerplates`",
root.display()
);
for (relative, absolute) in files {
println!("cargo:rerun-if-changed={}", absolute.display());
code.push_str(&format!(
" ({:?}, {:?}, include_str!({:?})),\n",
s.name,
relative,
absolute.display().to_string().replace('\\', "/")
));
}
}
code.push_str("];\n");
code
}
fn collect(root: &Path, dir: &Path, found: &mut BTreeMap<String, PathBuf>) {
let entries = match fs::read_dir(dir) {
Ok(entries) => entries,
Err(e) => panic!("{}: cannot read the snapshot: {e}", dir.display()),
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect(root, &path, found);
} else if path.extension().is_some_and(|e| e == "gitignore") {
let relative = path
.strip_prefix(root)
.expect("walked from root")
.display()
.to_string()
.replace('\\', "/");
found.insert(relative, path);
}
}
}
fn read_sources(path: &Path) -> Vec<Source> {
println!("cargo:rerun-if-changed={}", path.display());
let content = fs::read_to_string(path).unwrap_or_else(|e| {
panic!("{}: cannot read the snapshot sources: {e}", path.display())
});
let sources = content
.lines()
.map(str::trim)
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.map(|line| {
let fields = line.split_whitespace().collect::<Vec<_>>();
assert_eq!(
fields.len(),
5,
"SOURCES: expected `name url owner repo-name commit`, found {line:?}"
);
Source {
name: fields[0].to_string(),
url: fields[1].to_string(),
owner: fields[2].to_string(),
repo_name: fields[3].to_string(),
commit: fields[4].to_string(),
}
})
.collect::<Vec<_>>();
assert!(!sources.is_empty(), "SOURCES: no repository is listed");
sources
}