Skip to main content

xtask/
main.rs

1#![feature(variant_count)]
2
3use std::{
4    env, fs, io,
5    mem::variant_count,
6    path::{Path, PathBuf},
7    process::{Command, ExitStatus},
8};
9
10use anyhow::anyhow;
11use cargo_metadata::{
12    camino::{Utf8Path, Utf8PathBuf},
13    Artifact, Message, TargetKind,
14};
15use flux_dev::Suite;
16use flux_sysroot::{
17    default_flux_sysroot_dir, SysrootCrate, SysrootManifest, FLUX_SYSROOT, SYSROOT_MANIFEST,
18};
19
20xflags::xflags! {
21    cmd xtask {
22        /// If true, run all cargo commands with `--offline`
23        optional --offline
24        /// If true, run cargo build commands with --features rust-fixpiont
25        optional --rust-fixpoint
26        /// If true, run cargo build commands with --features suggestions
27        optional --suggestions
28
29        /// Run regression tests
30        cmd test {
31            /// Only run tests containing `filter` as a substring.
32            optional filter: String
33            /// Run only the named suite(s). May be repeated. If omitted, all suites run.
34            repeated --suite suite: Suite
35            /// Timeout in seconds for each fixpoint query (passed as `-Ffixpoint-timeout`)
36            optional --fixpoint-timeout seconds: u64
37        }
38        /// Run lean benchmarks: emit lean files for each test in tests/pos/
39        cmd lean-bench {
40            /// Only run tests containing `filter` as a substring.
41            optional filter: String
42        }
43        /// Run the `flux` binary on the given input file.
44        cmd run {
45            /// Input file
46            required input: PathBuf
47            /// Extra options to pass to the `flux` binary, e.g., `cargo x run file.rs -- -Zdump-mir=renumber`
48            repeated opts: String
49            /// Do not build Flux libs for extern specs
50            optional --no-extern-specs
51        }
52        /// Expand Flux macros
53        cmd expand {
54            /// Input file
55            required input: PathBuf
56        }
57        /// Install Flux binaries to `~/.cargo/bin` and precompiled libraries and driver to `~/.flux`
58        cmd install {
59            /// Select build profile for the `flux-driver`, either 'release', 'dev', or 'profiling'. Default 'release'
60            optional --profile profile: Profile
61            /// Do not install Flux libs or extern specs
62            optional --no-extern-specs
63        }
64        /// Uninstall Flux binaries and libraries
65        cmd uninstall { }
66        /// Generate precompiled libraries
67        cmd build-sysroot { }
68        /// Build the documentation
69        cmd doc { }
70    }
71}
72
73#[derive(Clone, Copy, Debug)]
74enum Profile {
75    Release,
76    Dev,
77    Profiling,
78}
79
80impl Profile {
81    fn as_str(self) -> &'static str {
82        match self {
83            Profile::Release => "release",
84            Profile::Dev => "dev",
85            Profile::Profiling => "profiling",
86        }
87    }
88}
89
90impl std::str::FromStr for Profile {
91    type Err = &'static str;
92
93    fn from_str(s: &str) -> Result<Self, Self::Err> {
94        match s {
95            "release" => Ok(Self::Release),
96            "dev" => Ok(Self::Dev),
97            "profiling" => Ok(Self::Profiling),
98            _ => Err("invalid profile"),
99        }
100    }
101}
102
103fn main() -> anyhow::Result<()> {
104    let cmd = match Xtask::from_env() {
105        Ok(cmd) => cmd,
106        Err(err) => {
107            if err.is_help() {
108                println!("{}", Xtask::HELP_);
109                std::process::exit(0);
110            } else {
111                eprintln!("{err}");
112                std::process::exit(2);
113            }
114        }
115    };
116
117    let mut extra = vec![];
118    if cmd.offline {
119        extra.push("--offline");
120    }
121    match cmd.subcommand {
122        XtaskCmd::Test(args) => test(args, cmd.rust_fixpoint, cmd.suggestions),
123        XtaskCmd::LeanBench(args) => lean_bench(args, cmd.rust_fixpoint),
124        XtaskCmd::Run(args) => run(args, cmd.rust_fixpoint, cmd.suggestions),
125        XtaskCmd::Install(args) => install(&args, &extra, cmd.rust_fixpoint, cmd.suggestions),
126        XtaskCmd::Doc(args) => doc(args),
127        XtaskCmd::BuildSysroot(_) => {
128            let config = SysrootConfig {
129                profile: Profile::Dev,
130                rust_fixpoint: cmd.rust_fixpoint,
131                suggestions: cmd.suggestions,
132                dst: local_sysroot_dir()?,
133                build_libs: BuildLibs { force: true, libs: FluxLib::ALL },
134            };
135            install_sysroot(&config)?;
136            Ok(())
137        }
138        XtaskCmd::Uninstall(_) => uninstall(),
139        XtaskCmd::Expand(args) => expand(args),
140    }
141}
142
143fn run_tests(
144    flux_driver: &Utf8Path,
145    sysroot: &Path,
146    suite: &str,
147    filter: Option<&str>,
148    fixpoint_timeout: Option<u64>,
149) -> anyhow::Result<()> {
150    let mut cmd = Command::new("cargo");
151    cmd.args(["test", "-p", "tests", "--"])
152        .args(["--flux-driver", flux_driver.as_str()])
153        .args(["--sysroot".as_ref(), sysroot.as_os_str()])
154        .args(["--suite", suite]);
155    if let Some(filter) = filter {
156        cmd.args(["--filter", filter]);
157    }
158    if let Some(timeout) = fixpoint_timeout {
159        cmd.arg("--fixpoint-timeout").arg(timeout.to_string());
160    }
161    cmd.run()
162}
163
164fn test(args: Test, rust_fixpoint: bool, suggestions: bool) -> anyhow::Result<()> {
165    let dst = local_sysroot_dir()?;
166
167    let suites: &[Suite] = if args.suite.is_empty() { Suite::ALL } else { &args.suite };
168
169    for suite in suites {
170        let libs = match suite {
171            Suite::Basic => &[FluxLib::FluxAttrs],
172            Suite::WithDeps => FluxLib::ALL,
173        };
174        let config = SysrootConfig {
175            profile: Profile::Dev,
176            rust_fixpoint,
177            suggestions,
178            dst: dst.clone(),
179            build_libs: BuildLibs { force: false, libs },
180        };
181        let flux_driver = install_sysroot(&config)?;
182        run_tests(&flux_driver, &dst, suite.name(), args.filter.as_deref(), args.fixpoint_timeout)?;
183    }
184    Ok(())
185}
186
187fn lean_bench(args: LeanBench, rust_fixpoint: bool) -> anyhow::Result<()> {
188    use walkdir::WalkDir;
189
190    let config = SysrootConfig {
191        profile: Profile::Dev,
192        rust_fixpoint,
193        suggestions: false,
194        dst: local_sysroot_dir()?,
195        build_libs: BuildLibs { force: false, libs: FluxLib::ALL },
196    };
197    let flux_driver = install_sysroot(&config)?;
198
199    let pos_path = PathBuf::from("tests/tests/pos");
200    let lean_bench_dir = PathBuf::from("tests/lean_bench");
201
202    if !pos_path.exists() {
203        return Err(anyhow!("tests/tests/pos directory not found"));
204    }
205
206    // Find all .rs test files
207    let test_files: Vec<PathBuf> = WalkDir::new(&pos_path)
208        .into_iter()
209        .filter_map(|e| e.ok())
210        .filter(|e| e.path().extension().is_some_and(|ext| ext == "rs"))
211        .map(|e| e.path().to_path_buf())
212        .filter(|path| {
213            // Apply filter if specified
214            if let Some(ref filter) = args.filter {
215                path.to_string_lossy().contains(filter)
216            } else {
217                true
218            }
219        })
220        .collect();
221
222    if test_files.is_empty() {
223        if args.filter.is_some() {
224            eprintln!("No test files found matching filter: {:?}", args.filter);
225        } else {
226            eprintln!("No test files found under {:?}", pos_path);
227        }
228        return Ok(());
229    }
230
231    eprintln!("Found {} test files", test_files.len());
232    eprintln!("{}", "-".repeat(60));
233
234    let mut failures: Vec<(PathBuf, String)> = Vec::new();
235    let mut successes = 0;
236
237    for (i, test_path) in test_files.iter().enumerate() {
238        let rel_path = test_path.strip_prefix(&pos_path).unwrap();
239
240        // Create lean output dir: ./tests/lean_bench/<path>/<to>/<file>/
241        let mut lean_dir = lean_bench_dir.clone();
242        if let Some(parent) = rel_path.parent() {
243            if parent != Path::new("") {
244                lean_dir.push(parent);
245            }
246        }
247        if let Some(stem) = rel_path.file_stem() {
248            lean_dir.push(stem);
249        }
250
251        eprint!("[{}/{}] Running: {} ... ", i + 1, test_files.len(), rel_path.display());
252
253        // Create the output directory
254        if let Err(e) = fs::create_dir_all(&lean_dir) {
255            eprintln!("ERROR");
256            failures.push((test_path.clone(), format!("Failed to create directory: {}", e)));
257            continue;
258        }
259
260        // Build rustc flags
261        let mut rustc_flags =
262            flux_dev::default_flags(&config.dst, SysrootManifest::extern_args(&config.dst));
263        rustc_flags.push("-Flean=emit".to_string());
264        rustc_flags.push(format!("-Flean-dir={}", lean_dir.display()));
265
266        // Run the test
267        let result = Command::new(&flux_driver)
268            .args(&rustc_flags)
269            .arg(test_path)
270            .env(FLUX_SYSROOT, &config.dst)
271            .stdout(std::process::Stdio::null())
272            .stderr(std::process::Stdio::piped())
273            .output();
274
275        match result {
276            Ok(output) if output.status.success() => {
277                eprintln!("OK");
278                successes += 1;
279            }
280            Ok(output) => {
281                eprintln!("ERROR");
282                let stderr = String::from_utf8_lossy(&output.stderr).to_string();
283                failures.push((test_path.clone(), stderr));
284            }
285            Err(e) => {
286                eprintln!("ERROR");
287                failures.push((test_path.clone(), e.to_string()));
288            }
289        }
290    }
291
292    // Print summary
293    eprintln!();
294    eprintln!("{}", "=".repeat(60));
295    eprintln!("SUMMARY");
296    eprintln!("{}", "=".repeat(60));
297    eprintln!("Total tests run: {}", test_files.len());
298    eprintln!("Passed: {}", successes);
299    eprintln!("Failed: {}", failures.len());
300
301    if !failures.is_empty() {
302        eprintln!();
303        eprintln!("Failed tests:");
304        for (path, _) in &failures {
305            let rel_path = path.strip_prefix(&pos_path).unwrap_or(path);
306            eprintln!("  - {}", rel_path.display());
307        }
308        eprintln!("{}", "=".repeat(60));
309        return Err(anyhow!("{} test(s) failed", failures.len()));
310    }
311
312    eprintln!("{}", "=".repeat(60));
313    Ok(())
314}
315
316fn run(args: Run, rust_fixpoint: bool, suggestions: bool) -> anyhow::Result<()> {
317    let libs = if args.no_extern_specs { &[FluxLib::FluxRs] } else { FluxLib::ALL };
318    run_inner(
319        args.input,
320        BuildLibs { force: false, libs },
321        ["-Ztrack-diagnostics=y".to_string()]
322            .into_iter()
323            .chain(args.opts),
324        rust_fixpoint,
325        suggestions,
326    )?;
327    Ok(())
328}
329
330fn expand(args: Expand) -> Result<(), anyhow::Error> {
331    run_inner(
332        args.input,
333        BuildLibs { force: false, libs: &[FluxLib::FluxRs] },
334        ["-Zunpretty=expanded".to_string()],
335        false,
336        false,
337    )?;
338    Ok(())
339}
340
341fn run_inner(
342    input: PathBuf,
343    build_libs: BuildLibs,
344    flags: impl IntoIterator<Item = String>,
345    rust_fixpoint: bool,
346    suggestions: bool,
347) -> Result<(), anyhow::Error> {
348    let config = SysrootConfig {
349        profile: Profile::Dev,
350        rust_fixpoint,
351        suggestions,
352        dst: local_sysroot_dir()?,
353        build_libs,
354    };
355
356    let flux_driver = install_sysroot(&config)?;
357
358    let mut rustc_flags =
359        flux_dev::default_flags(&config.dst, SysrootManifest::extern_args(&config.dst));
360    rustc_flags.extend(flags);
361
362    Command::new(flux_driver)
363        .args(&rustc_flags)
364        .arg(&input)
365        .env(FLUX_SYSROOT, &config.dst)
366        .run()
367}
368
369fn install(
370    args: &Install,
371    extra: &[&str],
372    rust_fixpoint: bool,
373    suggestions: bool,
374) -> anyhow::Result<()> {
375    let libs = if args.no_extern_specs { &[FluxLib::FluxRs] } else { FluxLib::ALL };
376    let config = SysrootConfig {
377        profile: args.profile(),
378        rust_fixpoint,
379        suggestions,
380        dst: default_flux_sysroot_dir(),
381        build_libs: BuildLibs { force: false, libs },
382    };
383    install_sysroot(&config)?;
384    Command::new("cargo")
385        .args(["install", "--path", "crates/flux-bin", "--force"])
386        .args(extra)
387        .run()
388}
389
390fn uninstall() -> anyhow::Result<()> {
391    Command::new("cargo")
392        .args(["uninstall", "-p", "flux-bin"])
393        .run()?;
394    eprintln!("$ rm -rf ~/.flux");
395    remove_path(&default_flux_sysroot_dir())?;
396    Ok(())
397}
398
399fn doc(_args: Doc) -> anyhow::Result<()> {
400    Command::new("cargo")
401        .args(["doc", "--workspace", "--document-private-items", "--no-deps"])
402        .env("RUSTDOCFLAGS", "-Zunstable-options --enable-index-page")
403        .run()?;
404    Ok(())
405}
406
407fn build_binary(
408    bin: &str,
409    profile: Profile,
410    rust_fixpoint: bool,
411    suggestions: bool,
412) -> anyhow::Result<Utf8PathBuf> {
413    let mut args = vec!["build", "--bin", bin, "--profile", profile.as_str()];
414    if rust_fixpoint {
415        args.extend_from_slice(&["--features", "rust-fixpoint"]);
416    }
417    if suggestions {
418        args.extend_from_slice(&["--features", "suggestions"]);
419    }
420    Command::new("cargo")
421        .args(&args)
422        .run_with_cargo_metadata()?
423        .into_iter()
424        .find(|artifact| artifact.target.name == bin && artifact.target.is_kind(TargetKind::Bin))
425        .and_then(|artifact| artifact.executable)
426        .ok_or_else(|| anyhow!("cannot find binary: `{bin}`"))
427}
428
429struct SysrootConfig {
430    /// Profile used to build `flux-driver` and libraries
431    profile: Profile,
432    /// Whether rust-fixpoint should be enabled to build `flux-driver`
433    rust_fixpoint: bool,
434    /// Whether suggestions should be enabled to build `flux-driver`
435    suggestions: bool,
436    /// Destination path for sysroot artifacts
437    dst: PathBuf,
438    build_libs: BuildLibs,
439}
440
441struct BuildLibs {
442    /// If true, forces a clean build.
443    force: bool,
444    /// List of libraries to install
445    libs: &'static [FluxLib],
446}
447
448#[allow(clippy::enum_variant_names)]
449#[derive(Clone, Copy)]
450enum FluxLib {
451    FluxAlloc,
452    FluxAttrs,
453    FluxCore,
454    FluxRs,
455}
456
457impl FluxLib {
458    const ALL: &[FluxLib] = &[Self::FluxAlloc, Self::FluxAttrs, Self::FluxCore, Self::FluxRs];
459
460    const _ASSERT_ALL: () = { assert!(Self::ALL.len() == variant_count::<Self>()) };
461
462    const fn package_name(self) -> &'static str {
463        match self {
464            FluxLib::FluxAlloc => "flux-alloc",
465            FluxLib::FluxAttrs => "flux-attrs",
466            FluxLib::FluxCore => "flux-core",
467            FluxLib::FluxRs => "flux-rs",
468        }
469    }
470
471    const fn target_name(self) -> &'static str {
472        match self {
473            FluxLib::FluxAlloc => "flux_alloc",
474            FluxLib::FluxAttrs => "flux_attrs",
475            FluxLib::FluxCore => "flux_core",
476            FluxLib::FluxRs => "flux_rs",
477        }
478    }
479
480    /// Whether this lib's specs are injected as force externs under `-Fstd-extern-specs`.
481    const fn is_std_extern_spec(self) -> bool {
482        match self {
483            FluxLib::FluxCore | FluxLib::FluxAlloc => true,
484            FluxLib::FluxAttrs | FluxLib::FluxRs => false,
485        }
486    }
487
488    fn from_artifact(artifact: &Artifact) -> Option<FluxLib> {
489        Self::ALL
490            .iter()
491            .copied()
492            .find(|lib| artifact.target.name == lib.target_name())
493    }
494}
495
496fn install_sysroot(config: &SysrootConfig) -> anyhow::Result<Utf8PathBuf> {
497    remove_path(&config.dst)?;
498    create_dir(&config.dst)?;
499
500    let flux_driver =
501        build_binary("flux-driver", config.profile, config.rust_fixpoint, config.suggestions)?;
502    copy_file(&flux_driver, &config.dst)?;
503
504    let cargo_flux =
505        build_binary("cargo-flux", config.profile, config.rust_fixpoint, config.suggestions)?;
506
507    if config.build_libs.force {
508        Command::new(&cargo_flux)
509            .args(["flux", "clean"])
510            .env(FLUX_SYSROOT, &config.dst)
511            .run()?;
512    }
513    let artifacts = Command::new(&cargo_flux)
514        .args(["flux", "build"])
515        .args(
516            config
517                .build_libs
518                .libs
519                .iter()
520                .flat_map(|lib| ["-p", lib.package_name()]),
521        )
522        .env(FLUX_SYSROOT, &config.dst)
523        .run_with_cargo_metadata()?;
524    let manifest = copy_artifacts(&artifacts, &config.dst)?;
525    write_sysroot_toml(&manifest, &config.dst)?;
526    Ok(flux_driver)
527}
528
529fn copy_artifacts(artifacts: &[Artifact], sysroot: &Path) -> anyhow::Result<SysrootManifest> {
530    let mut manifest = SysrootManifest::default();
531    for artifact in artifacts {
532        let Some(lib) = FluxLib::from_artifact(artifact) else { continue };
533
534        let entry = manifest
535            .crates
536            .entry(lib.target_name().to_string())
537            .or_default();
538        entry.extern_spec = lib.is_std_extern_spec();
539
540        for filename in &artifact.filenames {
541            copy_artifact(artifact, filename, sysroot, entry)?;
542        }
543    }
544    Ok(manifest)
545}
546
547fn copy_artifact(
548    artifact: &Artifact,
549    filename: &Utf8Path,
550    dst: &Path,
551    entry: &mut SysrootCrate,
552) -> anyhow::Result<()> {
553    let name = sysroot_file_name(artifact, filename)?;
554    copy_file(filename, dst.join(&name))?;
555    record_artifact(entry, &name);
556
557    if filename.extension() == Some("rmeta") {
558        // Flux finds its own metadata by swapping the extension on the resolved `.rmeta` path,
559        // so the two must travel together.
560        let fluxmeta = filename.with_extension("fluxmeta");
561        if fluxmeta.exists() {
562            let name = sysroot_file_name(artifact, &fluxmeta)?;
563            copy_file(&fluxmeta, dst.join(&name))?;
564            record_artifact(entry, &name);
565        }
566    }
567    Ok(())
568}
569
570fn record_artifact(entry: &mut SysrootCrate, name: &str) {
571    let slot = match Utf8Path::new(name).extension() {
572        Some("rlib") => &mut entry.rlib,
573        Some("rmeta") => &mut entry.rmeta,
574        Some("fluxmeta") => &mut entry.fluxmeta,
575        // Proc macros are always built for the host. Anything else cargo reports, like `.pdb`
576        // debug symbols on Windows, is not a crate.
577        Some(extension) if extension == env::consts::DLL_EXTENSION => &mut entry.dylib,
578        _ => return,
579    };
580    *slot = Some(name.to_string());
581}
582
583/// Name an artifact gets inside the sysroot.
584///
585/// Cargo reports an uplifted, hash-free `.rlib` for these crates but a hashed `.rmeta`, and rustc
586/// pairs the two by the infix between `lib<crate>` and the extension, so we cannot reuse cargo's
587/// names. Naming by crate keeps the pairing intact.
588fn sysroot_file_name(artifact: &Artifact, filename: &Utf8Path) -> anyhow::Result<String> {
589    let krate = &artifact.target.name;
590    let name = filename
591        .file_name()
592        .ok_or_else(|| anyhow!("artifact `{filename}` has no file name"))?;
593    let extension = filename
594        .extension()
595        .ok_or_else(|| anyhow!("artifact `{filename}` has no extension"))?;
596    match extension {
597        // rustc hardcodes the `lib` prefix for its own formats on every target.
598        "rlib" | "rmeta" | "fluxmeta" => Ok(format!("lib{krate}.{extension}")),
599        // A dylib's prefix is target-dependent (empty on Windows), so recover it from the name
600        // cargo produced.
601        _ => {
602            let prefix = name
603                .find(krate.as_str())
604                .map(|idx| &name[..idx])
605                .ok_or_else(|| anyhow!("artifact `{name}` does not mention crate `{krate}`"))?;
606            Ok(format!("{prefix}{krate}.{extension}"))
607        }
608    }
609}
610
611fn write_sysroot_toml(manifest: &SysrootManifest, sysroot: &Path) -> anyhow::Result<()> {
612    if manifest.crates.is_empty() {
613        return Ok(());
614    }
615    let content = toml::to_string(manifest)?;
616    let path = sysroot.join(SYSROOT_MANIFEST);
617    eprintln!("$ write {}", path.display());
618    fs::write(&path, &content).map_err(|e| anyhow!("failed to write `{}`: {e}", path.display()))
619}
620
621impl Install {
622    fn profile(&self) -> Profile {
623        self.profile.unwrap_or(Profile::Release)
624    }
625}
626
627fn local_sysroot_dir() -> anyhow::Result<PathBuf> {
628    Ok(Path::new(file!())
629        .canonicalize()?
630        .ancestors()
631        .nth(3)
632        .unwrap()
633        .join("sysroot"))
634}
635
636fn check_status(st: ExitStatus) -> anyhow::Result<()> {
637    if st.success() {
638        return Ok(());
639    }
640    let err = match st.code() {
641        Some(code) => anyhow!("command exited with non-zero code: {code}"),
642        #[cfg(unix)]
643        None => {
644            use std::os::unix::process::ExitStatusExt;
645            match st.signal() {
646                Some(sig) => anyhow!("command was terminated by a signal: {sig}"),
647                None => anyhow!("command was terminated by a signal"),
648            }
649        }
650        #[cfg(not(unix))]
651        None => anyhow!("command was terminated by a signal"),
652    };
653    Err(err)
654}
655
656fn display_command(cmd: &Command) {
657    for var in cmd.get_envs() {
658        if let Some(val) = var.1 {
659            eprintln!("$ export {}={}", var.0.display(), val.display());
660        }
661    }
662
663    let prog = cmd.get_program();
664    eprint!("$ {}", prog.display());
665    for arg in cmd.get_args() {
666        eprint!(" {}", arg.display());
667    }
668    eprintln!();
669}
670
671fn copy_file<S: AsRef<Path>, D: AsRef<Path>>(src: S, dst: D) -> anyhow::Result<()> {
672    let src = src.as_ref();
673    let dst = dst.as_ref();
674    eprintln!("$ cp {} {}", src.display(), dst.display());
675
676    let mut _tmp;
677    let mut dst = dst;
678    if dst.is_dir() {
679        if let Some(file_name) = src.file_name() {
680            _tmp = dst.join(file_name);
681            dst = &_tmp;
682        }
683    }
684    std::fs::copy(src, dst).map_err(|err| {
685        anyhow!("failed to copy `{}` to `{}`: {err}", src.display(), dst.display())
686    })?;
687
688    Ok(())
689}
690
691trait CommandExt {
692    fn run(&mut self) -> anyhow::Result<()>;
693    fn run_with_cargo_metadata(&mut self) -> anyhow::Result<Vec<Artifact>>;
694}
695
696impl CommandExt for Command {
697    fn run(&mut self) -> anyhow::Result<()> {
698        display_command(self);
699        let mut child = self.spawn()?;
700        check_status(child.wait()?)
701    }
702
703    fn run_with_cargo_metadata(&mut self) -> anyhow::Result<Vec<Artifact>> {
704        self.arg("--message-format=json-render-diagnostics")
705            .stdout(std::process::Stdio::piped());
706
707        display_command(self);
708
709        let mut child = self.spawn()?;
710
711        let mut artifacts = vec![];
712        let reader = std::io::BufReader::new(child.stdout.take().unwrap());
713        for message in cargo_metadata::Message::parse_stream(reader) {
714            match message.unwrap() {
715                Message::CompilerMessage(msg) => {
716                    println!("{msg}");
717                }
718                Message::CompilerArtifact(artifact) => {
719                    artifacts.push(artifact);
720                }
721                _ => (),
722            }
723        }
724
725        check_status(child.wait()?)?;
726
727        Ok(artifacts)
728    }
729}
730
731fn remove_path(path: &Path) -> anyhow::Result<()> {
732    match path.metadata() {
733        Ok(meta) => {
734            if meta.is_dir() { remove_dir_all(path) } else { fs::remove_file(path) }
735                .map_err(|err| anyhow!("failed to remove path `{}`: {err}", path.display()))
736        }
737        Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(()),
738        Err(err) => Err(anyhow!("failed to remove path `{}`: {err}", path.display())),
739    }
740}
741
742#[cfg(not(windows))]
743fn remove_dir_all(path: &Path) -> io::Result<()> {
744    std::fs::remove_dir_all(path)
745}
746
747// Copied from xshell
748#[cfg(windows)]
749fn remove_dir_all(path: &Path) -> io::Result<()> {
750    for _ in 0..99 {
751        if fs::remove_dir_all(path).is_ok() {
752            return Ok(());
753        }
754        std::thread::sleep(std::time::Duration::from_millis(10))
755    }
756    fs::remove_dir_all(path)
757}
758
759fn create_dir(path: &Path) -> anyhow::Result<()> {
760    match fs::create_dir_all(path) {
761        Ok(()) => Ok(()),
762        Err(err) => Err(anyhow!("failed to create directory `{}`: {err}", path.display())),
763    }
764}