flowey_lib_common/
download_gh_cli.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Download a copy of the GitHub CLI.
5//!
6//! NOTE: this node will _not_ set up any form of authentication for the
7//! downloaded CLI binary!
8
9use crate::cache::CacheHit;
10use flowey::node::prelude::*;
11
12flowey_request! {
13    pub enum Request {
14        /// Version of `gh` to download (e.g: 2.52.0)
15        Version(String),
16        /// Get a path to downloaded `gh`
17        Get(WriteVar<PathBuf>),
18    }
19}
20
21new_flow_node!(struct Node);
22
23impl FlowNode for Node {
24    type Request = Request;
25
26    fn imports(ctx: &mut ImportCtx<'_>) {
27        ctx.import::<crate::install_dist_pkg::Node>();
28        ctx.import::<crate::cache::Node>();
29    }
30
31    fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
32        let mut version = None;
33        let mut install_reqs = Vec::new();
34
35        for req in requests {
36            match req {
37                Request::Version(v) => same_across_all_reqs("Version", &mut version, v)?,
38                Request::Get(v) => install_reqs.push(v),
39            }
40        }
41
42        let version = version.ok_or(anyhow::anyhow!("Missing essential request: Version"))?;
43        let install_reqs = install_reqs;
44
45        // -- end of req processing -- //
46
47        if install_reqs.is_empty() {
48            return Ok(());
49        }
50
51        let gh_bin = ctx.platform().binary("gh");
52
53        let gh_arch = match ctx.arch() {
54            FlowArch::X86_64 => "amd64",
55            FlowArch::Aarch64 => "arm64",
56            arch => anyhow::bail!("unsupported architecture {arch}"),
57        };
58
59        let cache_dir = ctx.emit_rust_stepv("create gh cache dir", |_| {
60            |_| Ok(std::env::current_dir()?.absolute()?)
61        });
62
63        let cache_key = ReadVar::from_static(format!("gh-cli-{version}"));
64        let hitvar = ctx.reqv(|hitvar| crate::cache::Request {
65            label: "gh-cli".into(),
66            dir: cache_dir.clone(),
67            key: cache_key,
68            restore_keys: None,
69            hitvar,
70        });
71
72        ctx.emit_rust_step("installing gh", |ctx| {
73            let cache_dir = cache_dir.claim(ctx);
74            let hitvar = hitvar.claim(ctx);
75            let install_reqs = install_reqs.claim(ctx);
76            move |rt| {
77                let cache_dir = rt.read(cache_dir);
78
79                let cached = if matches!(rt.read(hitvar), CacheHit::Hit) {
80                    let cached_bin = cache_dir.join(&gh_bin);
81                    assert!(cached_bin.exists());
82                    Some(cached_bin)
83                } else {
84                    None
85                };
86
87                let path_to_gh = if let Some(cached) = cached {
88                    cached
89                } else {
90                    let sh = xshell::Shell::new()?;
91                    match rt.platform() {
92                        FlowPlatform::Windows => {
93                            xshell::cmd!(sh, "curl --fail -L https://github.com/cli/cli/releases/download/v{version}/gh_{version}_windows_{gh_arch}.zip -o gh.zip").run()?;
94                            xshell::cmd!(sh, "tar -xf gh.zip").run()?;
95                        },
96                        FlowPlatform::Linux(_) => {
97                            xshell::cmd!(sh, "curl --fail -L https://github.com/cli/cli/releases/download/v{version}/gh_{version}_linux_{gh_arch}.tar.gz -o gh.tar.gz").run()?;
98                            xshell::cmd!(sh, "tar -xf gh.tar.gz --strip-components=1").run()?;
99                        },
100                        FlowPlatform::MacOs => {
101                            xshell::cmd!(sh, "curl --fail -L https://github.com/cli/cli/releases/download/v{version}/gh_{version}_macOS_{gh_arch}.zip -o gh.zip").run()?;
102                            xshell::cmd!(sh, "tar -xf gh.zip --strip-components=1").run()?;
103                        }
104                        platform => anyhow::bail!("unsupported platform {platform}"),
105                    };
106
107                    // move the unzipped bin into the cache dir
108                    let final_bin = cache_dir.join(&gh_bin);
109                    fs_err::rename(format!("bin/{gh_bin}"), &final_bin)?;
110
111                    final_bin.absolute()?
112                };
113
114                for var in install_reqs {
115                    rt.write(var, &path_to_gh)
116                }
117
118                Ok(())
119            }
120        });
121
122        Ok(())
123    }
124}