use crate::{Clang, clang_resource_dir};
use std::path::{Path, PathBuf};
use windows_rdl::{ArchInput, merge_arch_rdl, reader};
pub struct Arch {
pub name: String,
pub triple: String,
pub bits: i32,
pub defines: Vec<String>,
}
impl Arch {
pub fn known(name: &str) -> Option<Self> {
let (triple, bits) = match name {
"x64" => ("x86_64-pc-windows-msvc", 2),
"arm64" => ("aarch64-pc-windows-msvc", 4),
"x86" => ("i686-pc-windows-msvc", 1),
_ => return None,
};
Some(Self {
name: name.to_string(),
triple: triple.to_string(),
bits,
defines: Vec::new(),
})
}
pub fn canonical_plus(extra: &[String], build: impl Fn(&str) -> Self) -> Vec<Self> {
let mut archs = vec![build("x64")];
for name in extra {
if name != "x64" {
archs.push(build(name));
}
}
archs
}
}
pub struct ScrapePlan {
pub root: String,
pub rdl_dir: PathBuf,
pub out_dir: PathBuf,
pub winmd: PathBuf,
pub archs: Vec<Arch>,
pub reference_winmds: Vec<PathBuf>,
pub resolution_winmds: Vec<PathBuf>,
pub seed: Option<PathBuf>,
pub parallel: bool,
}
pub struct Summary {
pub partitions: usize,
pub arch_timings: Vec<(String, f32)>,
pub scrape_wall: f32,
pub merge_wall: f32,
pub winmd_wall: f32,
pub multi_arch: bool,
}
impl std::fmt::Display for Summary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Timing:")?;
for (name, secs) in &self.arch_timings {
writeln!(f, " scrape {name:<6} {secs:>8.2}s")?;
}
writeln!(f, " scrape (parallel wall) {:>8.2}s", self.scrape_wall)?;
if self.multi_arch {
let arches: Vec<&str> = self.arch_timings.iter().map(|(n, _)| n.as_str()).collect();
writeln!(f, "Arch-merged: {}", arches.join(" + "))?;
writeln!(f, " arch-merge {:>8.2}s", self.merge_wall)?;
writeln!(f, " final winmd {:>8.2}s", self.winmd_wall)?;
}
Ok(())
}
}
pub fn find_in_dirs(name: &str, dirs: &[String]) -> Option<String> {
dirs.iter()
.map(|dir| Path::new(dir).join(name))
.find(|path| path.is_file())
.map(|path| path.to_string_lossy().replace('\\', "/"))
}
struct Job<'a> {
arch: &'a Arch,
rdl_dir: PathBuf,
winmd: PathBuf,
}
impl Clang {
pub fn scrape(&self, plan: &ScrapePlan) -> Summary {
assert!(
!plan.archs.is_empty(),
"scraper: `plan.archs` must list at least one architecture"
);
std::fs::create_dir_all(&plan.out_dir)
.unwrap_or_else(|e| panic!("failed to create `{}`: {e}", plan.out_dir.display()));
std::fs::create_dir_all(&plan.rdl_dir)
.unwrap_or_else(|e| panic!("failed to create `{}`: {e}", plan.rdl_dir.display()));
let canonical = &plan.archs[0];
let winmd_file = plan
.winmd
.file_name()
.unwrap_or_else(|| panic!("`plan.winmd` has no file name: `{}`", plan.winmd.display()));
let stem = plan
.winmd
.file_stem()
.unwrap_or_else(|| panic!("`plan.winmd` has no file stem: `{}`", plan.winmd.display()));
let canonical_winmd = plan.out_dir.join(winmd_file);
let mut jobs = vec![Job {
arch: canonical,
rdl_dir: plan.rdl_dir.clone(),
winmd: canonical_winmd.clone(),
}];
for arch in &plan.archs[1..] {
let mut winmd_file = stem.to_os_string();
winmd_file.push(format!(".{}.winmd", arch.name));
jobs.push(Job {
arch,
rdl_dir: plan.out_dir.join(&arch.name),
winmd: plan.out_dir.join(winmd_file),
});
}
let multi_arch = jobs.len() > 1;
let resource_dir = multi_arch.then(clang_resource_dir);
let timings = std::sync::Mutex::new(Vec::<(String, f32)>::new());
let scrape_start = std::time::Instant::now();
let scrape_one = |job: &Job| {
let t = std::time::Instant::now();
let resource = (job.arch.bits != canonical.bits).then(|| {
resource_dir
.as_deref()
.expect("resource dir resolved for multi-arch")
});
self.scrape_arch(plan, job.arch, &job.rdl_dir, &job.winmd, resource);
timings
.lock()
.unwrap()
.push((job.arch.name.clone(), t.elapsed().as_secs_f32()));
};
if plan.parallel {
windows_threading::for_each(jobs.iter(), scrape_one);
} else {
jobs.iter().for_each(scrape_one);
}
let scrape_wall = scrape_start.elapsed().as_secs_f32();
let mut merge_wall = 0.0;
let mut winmd_wall = 0.0;
if multi_arch {
let arch_inputs: Vec<ArchInput> = jobs
.iter()
.map(|j| ArchInput {
rdl_dir: j.rdl_dir.clone(),
winmd: j.winmd.clone(),
bits: j.arch.bits,
})
.collect();
let m = std::time::Instant::now();
merge_arch_rdl(&arch_inputs, plan.seed.as_deref(), &plan.rdl_dir)
.unwrap_or_else(|e| panic!("arch-merge failed: {e}"));
merge_wall = m.elapsed().as_secs_f32();
let w = std::time::Instant::now();
let mut reader = reader();
reader.input(&plan.rdl_dir);
if let Some(seed) = &plan.seed {
reader.input(seed);
}
for reference in &plan.reference_winmds {
reader.reference(reference);
}
for resolution in &plan.resolution_winmds {
reader.reference(resolution);
}
reader.output(&plan.winmd).write().unwrap_or_else(|e| {
panic!(
"failed to compile merged winmd `{}`: {e}",
plan.winmd.display()
)
});
winmd_wall = w.elapsed().as_secs_f32();
} else {
std::fs::copy(&canonical_winmd, &plan.winmd).unwrap_or_else(|e| {
panic!("failed to publish winmd to `{}`: {e}", plan.winmd.display())
});
}
let mut arch_timings = timings.into_inner().unwrap();
arch_timings.sort_by_key(|(name, _)| {
plan.archs
.iter()
.position(|a| a.name == *name)
.unwrap_or(usize::MAX)
});
Summary {
partitions: count_partitions(&plan.rdl_dir, plan.seed.as_deref()),
arch_timings,
scrape_wall,
merge_wall,
winmd_wall,
multi_arch,
}
}
fn scrape_arch(
&self,
plan: &ScrapePlan,
arch: &Arch,
rdl_dir: &Path,
winmd: &Path,
resource_dir: Option<&str>,
) {
clear_rdl_dir(rdl_dir, plan.seed.as_deref());
let mut clang = self.clone();
clang
.target(&arch.triple)
.args(arch.defines.iter().map(String::as_str));
if let Some(dir) = resource_dir {
clang.args(["-resource-dir", dir]);
}
for reference in &plan.reference_winmds {
clang.reference(reference);
}
for resolution in &plan.resolution_winmds {
clang.resolution_input(resolution);
}
clang
.namespace(&plan.root)
.output(rdl_dir)
.write_by_header()
.unwrap_or_else(|e| {
panic!(
"failed to generate partitions in `{}`: {e}",
rdl_dir.display()
)
});
let mut rdl_paths = collect_rdl_paths(rdl_dir);
if let Some(seed) = &plan.seed
&& !rdl_paths.iter().any(|p| p == seed)
{
rdl_paths.push(seed.clone());
}
let mut reader = reader();
reader.inputs(&rdl_paths);
for reference in &plan.reference_winmds {
reader.reference(reference);
}
for resolution in &plan.resolution_winmds {
reader.reference(resolution);
}
reader.output(winmd).write().unwrap_or_else(|e| {
panic!(
"failed to compile `{}` into `{}`: {e}",
rdl_dir.display(),
winmd.display()
)
});
}
}
fn clear_rdl_dir(rdl_dir: &Path, seed: Option<&Path>) {
std::fs::create_dir_all(rdl_dir)
.unwrap_or_else(|e| panic!("failed to create `{}`: {e}", rdl_dir.display()));
let seed_name = seed.and_then(Path::file_name);
for entry in std::fs::read_dir(rdl_dir)
.unwrap_or_else(|e| panic!("failed to read `{}`: {e}", rdl_dir.display()))
{
let path = entry.unwrap().path();
let is_seed = path.file_name() == seed_name;
if !is_seed && path.extension().is_some_and(|x| x == "rdl") {
std::fs::remove_file(&path)
.unwrap_or_else(|e| panic!("failed to remove `{}`: {e}", path.display()));
}
}
}
fn collect_rdl_paths(rdl_dir: &Path) -> Vec<PathBuf> {
let mut paths: Vec<PathBuf> = std::fs::read_dir(rdl_dir)
.unwrap_or_else(|e| panic!("failed to read `{}`: {e}", rdl_dir.display()))
.filter_map(|entry| entry.ok())
.map(|entry| entry.path())
.filter(|path| path.extension().is_some_and(|x| x == "rdl"))
.collect();
paths.sort();
paths
}
fn count_partitions(rdl_dir: &Path, seed: Option<&Path>) -> usize {
let seed_name = seed.and_then(Path::file_name);
std::fs::read_dir(rdl_dir).map_or(0, |rd| {
rd.filter_map(|e| e.ok())
.filter(|e| {
let path = e.path();
path.extension().is_some_and(|x| x == "rdl") && path.file_name() != seed_name
})
.count()
})
}