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                output: v,
230            });
231
232            if !no_split_dbg_info
233                && matches!(
234                    (crate_type, target.operating_system),
235                    (
236                        CargoCrateType::Bin,
237                        target_lexicon::OperatingSystem::Linux
238                            | target_lexicon::OperatingSystem::None_
239                    )
240                )
241            {
242                let elf_bin = base_output.clone().map(ctx, |o| match o {
243                    flowey_lib_common::run_cargo_build::CargoBuildOutput::ElfBin { bin } => bin,
244                    _ => unreachable!(),
245                });
246
247                let (out_bin, write_out_bin) = ctx.new_var();
248                let (out_dbg, write_out_dbg) = ctx.new_var();
249
250                ctx.req(crate::run_split_debug_info::Request {
251                    arch: crate::common::CommonArch::from_architecture(target.architecture)
252                        .context("cannot split linux dbginfo on specified arch")?,
253                    in_bin: elf_bin,
254                    out_bin: write_out_bin,
255                    out_dbg_info: write_out_dbg,
256                    reproducible_without_debuglink: matches!(
257                        ctx.platform(),
258                        FlowPlatform::Linux(FlowPlatformLinuxDistro::Nix)
259                    ),
260                });
261
262                ctx.emit_minor_rust_step("reporting split debug info", |ctx| {
263                    let out_bin = out_bin.claim(ctx);
264                    let out_dbg = out_dbg.claim(ctx);
265                    let base_output = base_output.claim(ctx);
266                    let output = output.claim(ctx);
267
268                    move |rt| {
269                        let mut fixed = CargoBuildOutput::from_base_cargo_build_output(
270                            rt.read(base_output),
271                            Some(rt.read(out_dbg)),
272                        );
273                        let CargoBuildOutput::ElfBin { bin, .. } = &mut fixed else {
274                            unreachable!()
275                        };
276                        *bin = rt.read(out_bin);
277                        rt.write(output, &fixed);
278                    }
279                });
280            } else {
281                base_output.write_into_with(ctx, output, |o| {
282                    CargoBuildOutput::from_base_cargo_build_output(o, None)
283                });
284            }
285        }
286
287        Ok(())
288    }
289}