Skip to main content

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