flowey_lib_common/
download_cargo_fuzz.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Download (and optionally, install) a copy of `cargo-fuzz`.
5
6use crate::cache::CacheHit;
7use flowey::node::prelude::*;
8
9flowey_request! {
10    pub enum Request {
11        /// Version of `cargo fuzz` to install (e.g: "0.12.0")
12        Version(String),
13        /// Install `cargo-fuzz` as a `cargo` extension (invoked via `cargo fuzz`).
14        InstallWithCargo(WriteVar<SideEffect>),
15    }
16}
17
18new_flow_node!(struct Node);
19
20impl FlowNode for Node {
21    type Request = Request;
22
23    fn imports(ctx: &mut ImportCtx<'_>) {
24        ctx.import::<crate::cache::Node>();
25        ctx.import::<crate::cfg_persistent_dir_cargo_install::Node>();
26        ctx.import::<crate::install_rust::Node>();
27    }
28
29    fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
30        let mut version = None;
31        let mut install_with_cargo = Vec::new();
32
33        for req in requests {
34            match req {
35                Request::Version(v) => same_across_all_reqs("Version", &mut version, v)?,
36                Request::InstallWithCargo(v) => install_with_cargo.push(v),
37            }
38        }
39
40        let version = version.ok_or(anyhow::anyhow!("Missing essential request: Version"))?;
41        let install_with_cargo = install_with_cargo;
42
43        // -- end of req processing -- //
44
45        if install_with_cargo.is_empty() {
46            return Ok(());
47        }
48
49        let cargo_fuzz_bin = ctx.platform().binary("cargo-fuzz");
50
51        let cache_dir = ctx.emit_rust_stepv("create cargo-fuzz cache dir", |_| {
52            |_| Ok(std::env::current_dir()?.absolute()?)
53        });
54
55        let cache_key = ReadVar::from_static(format!("cargo-fuzz-{version}"));
56        let hitvar = ctx.reqv(|v| {
57            crate::cache::Request {
58                label: "cargo-fuzz".into(),
59                dir: cache_dir.clone(),
60                key: cache_key,
61                restore_keys: None, // we want an exact hit
62                hitvar: v,
63            }
64        });
65
66        let cargo_install_persistent_dir =
67            ctx.reqv(crate::cfg_persistent_dir_cargo_install::Request);
68        let rust_toolchain = ctx.reqv(crate::install_rust::Request::GetRustupToolchain);
69        let cargo_home = ctx.reqv(crate::install_rust::Request::GetCargoHome);
70
71        ctx.emit_rust_step("installing cargo-fuzz", |ctx| {
72            install_with_cargo.claim(ctx);
73
74            let cache_dir = cache_dir.claim(ctx);
75            let hitvar = hitvar.claim(ctx);
76            let cargo_install_persistent_dir = cargo_install_persistent_dir.claim(ctx);
77            let rust_toolchain = rust_toolchain.claim(ctx);
78            let cargo_home = cargo_home.claim(ctx);
79
80            move |rt| {
81                let cache_dir = rt.read(cache_dir);
82
83                let cached_bin_path = cache_dir.join(&cargo_fuzz_bin);
84                let cached = if matches!(rt.read(hitvar), CacheHit::Hit) {
85                    assert!(cached_bin_path.exists());
86                    Some(cached_bin_path.clone())
87                } else {
88                    None
89                };
90
91                let path_to_cargo_fuzz = if let Some(cached) = cached {
92                    cached
93                } else {
94                    let root = rt.read(cargo_install_persistent_dir).unwrap_or("./".into());
95
96                    let sh = xshell::Shell::new()?;
97                    let rust_toolchain = rt.read(rust_toolchain);
98                    let run = |offline| {
99                        let rust_toolchain = rust_toolchain.as_ref().map(|s| format!("+{s}"));
100
101                        xshell::cmd!(
102                            sh,
103                            "cargo {rust_toolchain...}
104                                install
105                                --locked
106                                {offline...}
107                                --root {root}
108                                --target-dir {root}
109                                --version {version}
110                                cargo-fuzz
111                            "
112                        )
113                        .run()
114                    };
115
116                    // Try --offline to avoid an unnecessary git fetch on rerun.
117                    if run(Some("--offline")).is_err() {
118                        // Try again without --offline.
119                        run(None)?;
120                    }
121
122                    let out_bin = root.absolute()?.join("bin").join(&cargo_fuzz_bin);
123
124                    // move the compiled bin into the cache dir
125                    fs_err::rename(out_bin, &cached_bin_path)?;
126                    cached_bin_path.absolute()?
127                };
128
129                // is installing with cargo, make sure the bin we built /
130                // downloaded is accessible via cargo fuzz
131                fs_err::copy(
132                    &path_to_cargo_fuzz,
133                    rt.read(cargo_home).join("bin").join(&cargo_fuzz_bin),
134                )?;
135
136                Ok(())
137            }
138        });
139
140        Ok(())
141    }
142}