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