flowey_lib_common/
download_cargo_nextest.rs

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