Skip to main content

flowey_lib_hvlite/
run_cargo_build.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Returns well-formed `cargo build` invocations for building crates
5//! specifically in the the hvlite repo.
6//!
7//! Uses the generic [`flowey_lib_common::run_cargo_build`] helper under
8//! the hood, but fine-tunes the exposed API to the HvLite repo.
9
10use flowey::node::prelude::*;
11use flowey_lib_common::run_cargo_build::CargoBuildProfile;
12use flowey_lib_common::run_cargo_build::CargoCrateType;
13use flowey_lib_common::run_cargo_build::CargoFeatureSet;
14use std::collections::BTreeMap;
15
16/// In the HvLite repo, we use a custom step to strip debug info from linux
17/// binaries
18///
19/// We cannot use rustc's split DWARF option because Azure Watson does not
20/// support split DWARF debuginfo.
21#[derive(Serialize, Deserialize)]
22pub enum CargoBuildOutput {
23    WindowsBin {
24        exe: PathBuf,
25        pdb: PathBuf,
26    },
27    ElfBin {
28        bin: PathBuf,
29        dbg: Option<PathBuf>,
30    },
31    LinuxStaticLib {
32        a: PathBuf,
33    },
34    LinuxDynamicLib {
35        so: PathBuf,
36    },
37    WindowsStaticLib {
38        lib: PathBuf,
39        pdb: PathBuf,
40    },
41    WindowsDynamicLib {
42        dll: PathBuf,
43        dll_lib: PathBuf,
44        pdb: PathBuf,
45    },
46    UefiBin {
47        efi: PathBuf,
48        pdb: PathBuf,
49    },
50}
51
52impl CargoBuildOutput {
53    pub fn from_base_cargo_build_output(
54        base: flowey_lib_common::run_cargo_build::CargoBuildOutput,
55        elf_dbg: Option<PathBuf>,
56    ) -> Self {
57        use flowey_lib_common::run_cargo_build::CargoBuildOutput as Base;
58
59        match base {
60            Base::WindowsBin { exe, pdb } => Self::WindowsBin { exe, pdb },
61            Base::LinuxStaticLib { a } => Self::LinuxStaticLib { a },
62            Base::LinuxDynamicLib { so } => Self::LinuxDynamicLib { so },
63            Base::WindowsStaticLib { lib, pdb } => Self::WindowsStaticLib { lib, pdb },
64            Base::WindowsDynamicLib { dll, dll_lib, pdb } => {
65                Self::WindowsDynamicLib { dll, dll_lib, pdb }
66            }
67            Base::UefiBin { efi, pdb } => Self::UefiBin { efi, pdb },
68
69            Base::ElfBin { bin } => Self::ElfBin { bin, dbg: elf_dbg },
70        }
71    }
72}
73
74#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
75pub enum BuildProfile {
76    Debug,
77    Release,
78    UnderhillShip,
79    BootDev,
80    BootRelease,
81    Light,
82}
83
84flowey_request! {
85    pub struct Request {
86        pub crate_name: String,
87        pub out_name: String,
88        pub profile: BuildProfile, // lock to only hvlite build profiles
89        pub features: CargoFeatureSet,
90        pub crate_type: CargoCrateType,
91        pub target: target_lexicon::Triple,
92        /// If supported by the target, build without split debuginfo.
93        pub no_split_dbg_info: bool,
94        pub extra_env: Option<ReadVar<BTreeMap<String, String>>>,
95        /// Wait for specified side-effects to resolve before running cargo-run.
96        ///
97        /// (e.g: to allow for some ambient packages / dependencies to get
98        /// installed).
99        pub pre_build_deps: Vec<ReadVar<SideEffect>>,
100        /// Resulting build output
101        pub output: WriteVar<CargoBuildOutput>,
102    }
103}
104
105new_flow_node!(struct Node);
106
107impl FlowNode for Node {
108    type Request = Request;
109
110    fn imports(ctx: &mut ImportCtx<'_>) {
111        ctx.import::<crate::install_openvmm_rust_build_essential::Node>();
112        ctx.import::<crate::git_checkout_openvmm_repo::Node>();
113        ctx.import::<crate::init_openvmm_magicpath_openhcl_sysroot::Node>();
114        ctx.import::<crate::run_split_debug_info::Node>();
115        ctx.import::<crate::init_cross_build::Node>();
116        ctx.import::<flowey_lib_common::run_cargo_build::Node>();
117        ctx.import::<flowey_lib_common::install_rust::Node>();
118    }
119
120    fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
121        let base_pre_build_deps =
122            [ctx.reqv(crate::install_openvmm_rust_build_essential::Request)].to_vec();
123
124        let openvmm_repo_path = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir);
125
126        for Request {
127            crate_name,
128            out_name,
129            profile,
130            features,
131            crate_type,
132            mut target,
133            no_split_dbg_info,
134            extra_env,
135            pre_build_deps: user_pre_build_deps,
136            output,
137        } in requests
138        {
139            let mut pre_build_deps = base_pre_build_deps.clone();
140            pre_build_deps.extend(user_pre_build_deps);
141
142            // FIXME: because we set `CC_{arch}_unknown_linux_musl` in our cargo env,
143            // we end up compiling _every_ musl artifact using the openhcl musl
144            // toolchain.
145            //
146            // it's not super clear how to fix this in a clean way without breaking the
147            // dev-ex of anyone using rust-analyzer though...
148            let sysroot_arch = crate::common::CommonArch::from_architecture(target.architecture)?;
149
150            if matches!(target.environment, target_lexicon::Environment::Musl) {
151                pre_build_deps.push(
152                    ctx.reqv(|v| crate::init_openvmm_magicpath_openhcl_sysroot::Request {
153                        arch: sysroot_arch,
154                        path: v,
155                    })
156                    .into_side_effect(),
157                );
158            }
159
160            let injected_env = ctx.reqv(|v| crate::init_cross_build::Request {
161                target: target.clone(),
162                injected_env: v,
163            });
164
165            let extra_env = if let Some(extra_env) = extra_env {
166                extra_env
167                    .zip(ctx, injected_env)
168                    .map(ctx, move |(mut a, b)| {
169                        a.extend(b);
170                        a
171                    })
172            } else {
173                injected_env
174            };
175
176            let mut config = Vec::new();
177
178            // If the target vendor is specified as `minimal_rt`, then this is
179            // our custom target triple for the minimal_rt toolchain. Include the appropriate
180            // config file.
181            let passed_target = if target.vendor.as_str() == "minimal_rt" {
182                config.push(format!(
183                    "openhcl/minimal_rt/{arch}-config.toml",
184                    arch = target.architecture.into_str()
185                ));
186                if target.architecture == target_lexicon::Architecture::X86_64 {
187                    // x86-64 doesn't actually use a custom target currently,
188                    // since the x86_64-unknown-none target is stage 2 and has
189                    // reasonable defaults.
190                    target.vendor = target_lexicon::Vendor::Unknown;
191                    Some(target.clone())
192                } else {
193                    // We are building the target from source, so don't try to
194                    // install it via rustup. But do make sure the rust-src
195                    // component is available.
196                    ctx.req(flowey_lib_common::install_rust::Request::InstallComponent(
197                        "rust-src".into(),
198                    ));
199                    None
200                }
201            } else {
202                Some(target.clone())
203            };
204
205            let base_output = ctx.reqv(|v| flowey_lib_common::run_cargo_build::Request {
206                in_folder: openvmm_repo_path.clone(),
207                crate_name,
208                out_name,
209                profile: match profile {
210                    BuildProfile::Debug => CargoBuildProfile::Debug,
211                    BuildProfile::Release => CargoBuildProfile::Release,
212                    BuildProfile::UnderhillShip => {
213                        CargoBuildProfile::Custom("underhill-ship".into())
214                    }
215                    BuildProfile::BootDev => CargoBuildProfile::Custom("boot-dev".into()),
216                    BuildProfile::BootRelease => CargoBuildProfile::Custom("boot-release".into()),
217                    BuildProfile::Light => CargoBuildProfile::Custom("light".into()),
218                },
219                features,
220                output_kind: crate_type,
221                target: passed_target,
222                extra_env: Some(extra_env),
223                config,
224                pre_build_deps,
225                output: v,
226            });
227
228            if !no_split_dbg_info
229                && matches!(
230                    (crate_type, target.operating_system),
231                    (
232                        CargoCrateType::Bin,
233                        target_lexicon::OperatingSystem::Linux
234                            | target_lexicon::OperatingSystem::None_
235                    )
236                )
237            {
238                let elf_bin = base_output.clone().map(ctx, |o| match o {
239                    flowey_lib_common::run_cargo_build::CargoBuildOutput::ElfBin { bin } => bin,
240                    _ => unreachable!(),
241                });
242
243                let (out_bin, write_out_bin) = ctx.new_var();
244                let (out_dbg, write_out_dbg) = ctx.new_var();
245
246                ctx.req(crate::run_split_debug_info::Request {
247                    arch: crate::common::CommonArch::from_architecture(target.architecture)
248                        .context("cannot split linux dbginfo on specified arch")?,
249                    in_bin: elf_bin,
250                    out_bin: write_out_bin,
251                    out_dbg_info: write_out_dbg,
252                    reproducible_without_debuglink: matches!(
253                        ctx.platform(),
254                        FlowPlatform::Linux(FlowPlatformLinuxDistro::Nix)
255                    ),
256                });
257
258                ctx.emit_minor_rust_step("reporting split debug info", |ctx| {
259                    let out_bin = out_bin.claim(ctx);
260                    let out_dbg = out_dbg.claim(ctx);
261                    let base_output = base_output.claim(ctx);
262                    let output = output.claim(ctx);
263
264                    move |rt| {
265                        let mut fixed = CargoBuildOutput::from_base_cargo_build_output(
266                            rt.read(base_output),
267                            Some(rt.read(out_dbg)),
268                        );
269                        let CargoBuildOutput::ElfBin { bin, .. } = &mut fixed else {
270                            unreachable!()
271                        };
272                        *bin = rt.read(out_bin);
273                        rt.write(output, &fixed);
274                    }
275                });
276            } else {
277                base_output.write_into(ctx, output, |o| {
278                    CargoBuildOutput::from_base_cargo_build_output(o, None)
279                });
280            }
281        }
282
283        Ok(())
284    }
285}