flowey_lib_common/
download_protoc.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Download a copy of `protoc` for the current platform
5
6use flowey::node::prelude::*;
7
8#[derive(Serialize, Deserialize)]
9pub struct ProtocPackage {
10    pub protoc_bin: PathBuf,
11    pub include_dir: PathBuf,
12}
13
14flowey_request! {
15    pub enum Request {
16        /// What version to download (e.g: 27.1)
17        Version(String),
18        /// Return paths to items in the protoc package
19        Get(WriteVar<ProtocPackage>),
20    }
21}
22
23new_flow_node!(struct Node);
24
25impl FlowNode for Node {
26    type Request = Request;
27
28    fn imports(ctx: &mut ImportCtx<'_>) {
29        ctx.import::<crate::install_dist_pkg::Node>();
30        ctx.import::<crate::download_gh_release::Node>();
31        ctx.import::<crate::cache::Node>();
32    }
33
34    fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
35        let mut version = None;
36        let mut get_reqs = Vec::new();
37
38        for req in requests {
39            match req {
40                Request::Version(v) => same_across_all_reqs("Version", &mut version, v)?,
41                Request::Get(v) => get_reqs.push(v),
42            }
43        }
44
45        let version = version.ok_or(anyhow::anyhow!("Missing essential request: Version"))?;
46
47        // -- end of req processing -- //
48
49        if get_reqs.is_empty() {
50            return Ok(());
51        }
52
53        let tag = format!("v{version}");
54        let file_name = format!(
55            "protoc-{}-{}.zip",
56            version,
57            match (ctx.platform(), ctx.arch()) {
58                // protoc is not currently available for windows aarch64,
59                // so emulate the x64 version
60                (FlowPlatform::Windows, _) => "win64",
61                (FlowPlatform::Linux(_), FlowArch::X86_64) => "linux-x86_64",
62                (FlowPlatform::Linux(_), FlowArch::Aarch64) => "linux-aarch_64",
63                (FlowPlatform::MacOs, FlowArch::X86_64) => "osx-x86_64",
64                (FlowPlatform::MacOs, FlowArch::Aarch64) => "osx-aarch_64",
65                (platform, arch) => anyhow::bail!("unsupported platform {platform} {arch}"),
66            }
67        );
68
69        let protoc_zip = ctx.reqv(|v| crate::download_gh_release::Request {
70            repo_owner: "protocolbuffers".into(),
71            repo_name: "protobuf".into(),
72            needs_auth: false,
73            tag: tag.clone(),
74            file_name: file_name.clone(),
75            path: v,
76        });
77
78        let extract_zip_deps = crate::_util::extract::extract_zip_if_new_deps(ctx);
79        ctx.emit_rust_step("unpack protoc", |ctx| {
80            let extract_zip_deps = extract_zip_deps.clone().claim(ctx);
81            let get_reqs = get_reqs.claim(ctx);
82            let protoc_zip = protoc_zip.claim(ctx);
83            move |rt| {
84                let protoc_zip = rt.read(protoc_zip);
85
86                let extract_dir = crate::_util::extract::extract_zip_if_new(
87                    rt,
88                    extract_zip_deps,
89                    &protoc_zip,
90                    &tag,
91                )?;
92
93                let protoc_bin = extract_dir
94                    .join("bin")
95                    .join(rt.platform().binary("protoc"))
96                    .absolute()?;
97
98                assert!(protoc_bin.exists());
99
100                // Make sure protoc is executable
101                #[cfg(unix)]
102                {
103                    use std::os::unix::fs::PermissionsExt;
104                    let old_mode = protoc_bin.metadata()?.permissions().mode();
105                    fs_err::set_permissions(
106                        &protoc_bin,
107                        std::fs::Permissions::from_mode(old_mode | 0o111),
108                    )?;
109                }
110
111                let protoc_includes = extract_dir.join("include").absolute()?;
112                assert!(protoc_includes.exists());
113
114                let pkg = ProtocPackage {
115                    protoc_bin,
116                    include_dir: protoc_includes,
117                };
118
119                rt.write_all(get_reqs, &pkg);
120
121                Ok(())
122            }
123        });
124
125        Ok(())
126    }
127}