1use crate::{Clang, clang_resource_dir};
7use std::path::{Path, PathBuf};
8use windows_rdl::{ArchInput, merge_arch_rdl, reader};
9
10pub struct Arch {
12 pub name: String,
14 pub triple: String,
16 pub bits: i32,
18 pub defines: Vec<String>,
20}
21
22impl Arch {
23 pub fn known(name: &str) -> Option<Self> {
25 let (triple, bits) = match name {
26 "x64" => ("x86_64-pc-windows-msvc", 2),
27 "arm64" => ("aarch64-pc-windows-msvc", 4),
28 "x86" => ("i686-pc-windows-msvc", 1),
29 _ => return None,
30 };
31 Some(Self {
32 name: name.to_string(),
33 triple: triple.to_string(),
34 bits,
35 defines: Vec::new(),
36 })
37 }
38
39 pub fn canonical_plus(extra: &[String], build: impl Fn(&str) -> Self) -> Vec<Self> {
41 let mut archs = vec![build("x64")];
42 for name in extra {
43 if name != "x64" {
44 archs.push(build(name));
45 }
46 }
47 archs
48 }
49}
50
51pub struct ScrapePlan {
53 pub root: String,
55 pub rdl_dir: PathBuf,
57 pub out_dir: PathBuf,
59 pub winmd: PathBuf,
61 pub archs: Vec<Arch>,
63 pub reference_winmds: Vec<PathBuf>,
65 pub resolution_winmds: Vec<PathBuf>,
67 pub seed: Option<PathBuf>,
69 pub parallel: bool,
71}
72
73pub struct Summary {
75 pub partitions: usize,
77 pub arch_timings: Vec<(String, f32)>,
79 pub scrape_wall: f32,
81 pub merge_wall: f32,
83 pub winmd_wall: f32,
85 pub multi_arch: bool,
87}
88
89impl std::fmt::Display for Summary {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 writeln!(f, "Timing:")?;
93 for (name, secs) in &self.arch_timings {
94 writeln!(f, " scrape {name:<6} {secs:>8.2}s")?;
95 }
96 writeln!(f, " scrape (parallel wall) {:>8.2}s", self.scrape_wall)?;
97 if self.multi_arch {
98 let arches: Vec<&str> = self.arch_timings.iter().map(|(n, _)| n.as_str()).collect();
99 writeln!(f, "Arch-merged: {}", arches.join(" + "))?;
100 writeln!(f, " arch-merge {:>8.2}s", self.merge_wall)?;
101 writeln!(f, " final winmd {:>8.2}s", self.winmd_wall)?;
102 }
103 Ok(())
104 }
105}
106
107pub fn find_in_dirs(name: &str, dirs: &[String]) -> Option<String> {
109 dirs.iter()
110 .map(|dir| Path::new(dir).join(name))
111 .find(|path| path.is_file())
112 .map(|path| path.to_string_lossy().replace('\\', "/"))
113}
114
115struct Job<'a> {
116 arch: &'a Arch,
117 rdl_dir: PathBuf,
118 winmd: PathBuf,
119}
120
121impl Clang {
122 pub fn scrape(&self, plan: &ScrapePlan) -> Summary {
124 assert!(
125 !plan.archs.is_empty(),
126 "scraper: `plan.archs` must list at least one architecture"
127 );
128
129 std::fs::create_dir_all(&plan.out_dir)
130 .unwrap_or_else(|e| panic!("failed to create `{}`: {e}", plan.out_dir.display()));
131 std::fs::create_dir_all(&plan.rdl_dir)
132 .unwrap_or_else(|e| panic!("failed to create `{}`: {e}", plan.rdl_dir.display()));
133
134 let canonical = &plan.archs[0];
135 let winmd_file = plan
136 .winmd
137 .file_name()
138 .unwrap_or_else(|| panic!("`plan.winmd` has no file name: `{}`", plan.winmd.display()));
139 let stem = plan
140 .winmd
141 .file_stem()
142 .unwrap_or_else(|| panic!("`plan.winmd` has no file stem: `{}`", plan.winmd.display()));
143 let canonical_winmd = plan.out_dir.join(winmd_file);
144
145 let mut jobs = vec![Job {
147 arch: canonical,
148 rdl_dir: plan.rdl_dir.clone(),
149 winmd: canonical_winmd.clone(),
150 }];
151 for arch in &plan.archs[1..] {
152 let mut winmd_file = stem.to_os_string();
153 winmd_file.push(format!(".{}.winmd", arch.name));
154 jobs.push(Job {
155 arch,
156 rdl_dir: plan.out_dir.join(&arch.name),
157 winmd: plan.out_dir.join(winmd_file),
158 });
159 }
160 let multi_arch = jobs.len() > 1;
161
162 let resource_dir = multi_arch.then(clang_resource_dir);
165
166 let timings = std::sync::Mutex::new(Vec::<(String, f32)>::new());
167 let scrape_start = std::time::Instant::now();
168 let scrape_one = |job: &Job| {
169 let t = std::time::Instant::now();
170 let resource = (job.arch.bits != canonical.bits).then(|| {
171 resource_dir
172 .as_deref()
173 .expect("resource dir resolved for multi-arch")
174 });
175 self.scrape_arch(plan, job.arch, &job.rdl_dir, &job.winmd, resource);
176 timings
177 .lock()
178 .unwrap()
179 .push((job.arch.name.clone(), t.elapsed().as_secs_f32()));
180 };
181 if plan.parallel {
182 windows_threading::for_each(jobs.iter(), scrape_one);
183 } else {
184 jobs.iter().for_each(scrape_one);
185 }
186 let scrape_wall = scrape_start.elapsed().as_secs_f32();
187
188 let mut merge_wall = 0.0;
189 let mut winmd_wall = 0.0;
190 if multi_arch {
191 let arch_inputs: Vec<ArchInput> = jobs
193 .iter()
194 .map(|j| ArchInput {
195 rdl_dir: j.rdl_dir.clone(),
196 winmd: j.winmd.clone(),
197 bits: j.arch.bits,
198 })
199 .collect();
200 let m = std::time::Instant::now();
201 merge_arch_rdl(&arch_inputs, plan.seed.as_deref(), &plan.rdl_dir)
202 .unwrap_or_else(|e| panic!("arch-merge failed: {e}"));
203 merge_wall = m.elapsed().as_secs_f32();
204
205 let w = std::time::Instant::now();
206 let mut reader = reader();
207 reader.input(&plan.rdl_dir);
208 if let Some(seed) = &plan.seed {
209 reader.input(seed);
210 }
211 for reference in &plan.reference_winmds {
212 reader.reference(reference);
213 }
214 for resolution in &plan.resolution_winmds {
215 reader.reference(resolution);
216 }
217 reader.output(&plan.winmd).write().unwrap_or_else(|e| {
218 panic!(
219 "failed to compile merged winmd `{}`: {e}",
220 plan.winmd.display()
221 )
222 });
223 winmd_wall = w.elapsed().as_secs_f32();
224 } else {
225 std::fs::copy(&canonical_winmd, &plan.winmd).unwrap_or_else(|e| {
227 panic!("failed to publish winmd to `{}`: {e}", plan.winmd.display())
228 });
229 }
230
231 let mut arch_timings = timings.into_inner().unwrap();
232 arch_timings.sort_by_key(|(name, _)| {
233 plan.archs
234 .iter()
235 .position(|a| a.name == *name)
236 .unwrap_or(usize::MAX)
237 });
238
239 Summary {
240 partitions: count_partitions(&plan.rdl_dir, plan.seed.as_deref()),
241 arch_timings,
242 scrape_wall,
243 merge_wall,
244 winmd_wall,
245 multi_arch,
246 }
247 }
248
249 fn scrape_arch(
251 &self,
252 plan: &ScrapePlan,
253 arch: &Arch,
254 rdl_dir: &Path,
255 winmd: &Path,
256 resource_dir: Option<&str>,
257 ) {
258 clear_rdl_dir(rdl_dir, plan.seed.as_deref());
259
260 let mut clang = self.clone();
261 clang
262 .target(&arch.triple)
263 .args(arch.defines.iter().map(String::as_str));
264 if let Some(dir) = resource_dir {
265 clang.args(["-resource-dir", dir]);
266 }
267 for reference in &plan.reference_winmds {
268 clang.reference(reference);
269 }
270 for resolution in &plan.resolution_winmds {
271 clang.resolution_input(resolution);
272 }
273
274 clang
275 .namespace(&plan.root)
276 .output(rdl_dir)
277 .write_by_header()
278 .unwrap_or_else(|e| {
279 panic!(
280 "failed to generate partitions in `{}`: {e}",
281 rdl_dir.display()
282 )
283 });
284
285 let mut rdl_paths = collect_rdl_paths(rdl_dir);
286 if let Some(seed) = &plan.seed
287 && !rdl_paths.iter().any(|p| p == seed)
288 {
289 rdl_paths.push(seed.clone());
290 }
291
292 let mut reader = reader();
293 reader.inputs(&rdl_paths);
294 for reference in &plan.reference_winmds {
295 reader.reference(reference);
296 }
297 for resolution in &plan.resolution_winmds {
298 reader.reference(resolution);
299 }
300 reader.output(winmd).write().unwrap_or_else(|e| {
301 panic!(
302 "failed to compile `{}` into `{}`: {e}",
303 rdl_dir.display(),
304 winmd.display()
305 )
306 });
307 }
308}
309
310fn clear_rdl_dir(rdl_dir: &Path, seed: Option<&Path>) {
312 std::fs::create_dir_all(rdl_dir)
313 .unwrap_or_else(|e| panic!("failed to create `{}`: {e}", rdl_dir.display()));
314 let seed_name = seed.and_then(Path::file_name);
315 for entry in std::fs::read_dir(rdl_dir)
316 .unwrap_or_else(|e| panic!("failed to read `{}`: {e}", rdl_dir.display()))
317 {
318 let path = entry.unwrap().path();
319 let is_seed = path.file_name() == seed_name;
320 if !is_seed && path.extension().is_some_and(|x| x == "rdl") {
321 std::fs::remove_file(&path)
322 .unwrap_or_else(|e| panic!("failed to remove `{}`: {e}", path.display()));
323 }
324 }
325}
326
327fn collect_rdl_paths(rdl_dir: &Path) -> Vec<PathBuf> {
329 let mut paths: Vec<PathBuf> = std::fs::read_dir(rdl_dir)
330 .unwrap_or_else(|e| panic!("failed to read `{}`: {e}", rdl_dir.display()))
331 .filter_map(|entry| entry.ok())
332 .map(|entry| entry.path())
333 .filter(|path| path.extension().is_some_and(|x| x == "rdl"))
334 .collect();
335 paths.sort();
336 paths
337}
338
339fn count_partitions(rdl_dir: &Path, seed: Option<&Path>) -> usize {
341 let seed_name = seed.and_then(Path::file_name);
342 std::fs::read_dir(rdl_dir).map_or(0, |rd| {
343 rd.filter_map(|e| e.ok())
344 .filter(|e| {
345 let path = e.path();
346 path.extension().is_some_and(|x| x == "rdl") && path.file_name() != seed_name
347 })
348 .count()
349 })
350}