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 std::collections::BTreeMap;
14use std::collections::BTreeSet;
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/// Outside of a few binaries / libraries that are intimately tied to one
75/// particular architecture / platform, most things in the hvlite tree run on a
76/// common subset of supported target triples + build profiles.
77pub mod common {
78    use serde::Deserialize;
79    use serde::Serialize;
80
81    /// Vocabulary type for artifacts that only get built using the two most
82    /// common cargo build profiles (i.e: `release` vs. `debug`).
83    ///
84    /// More specialized artifacts should use the
85    /// [`BuildProfile`](super::BuildProfile) type, which enumerates _all_ build
86    /// profiles defined in HvLite's `Cargo.toml` file.
87    #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
88    pub enum CommonProfile {
89        Release,
90        Debug,
91    }
92
93    impl CommonProfile {
94        pub fn from_release(release: bool) -> Self {
95            match release {
96                true => Self::Release,
97                false => Self::Debug,
98            }
99        }
100
101        pub fn to_release(self) -> bool {
102            match self {
103                Self::Release => true,
104                Self::Debug => false,
105            }
106        }
107    }
108
109    impl From<CommonProfile> for super::BuildProfile {
110        fn from(value: CommonProfile) -> Self {
111            match value {
112                CommonProfile::Release => super::BuildProfile::Release,
113                CommonProfile::Debug => super::BuildProfile::Debug,
114            }
115        }
116    }
117
118    /// Vocabulary type for artifacts that only get built for the most common
119    /// actively-supported architectures in the hvlite tree.
120    #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
121    pub enum CommonArch {
122        X86_64,
123        Aarch64,
124    }
125
126    impl CommonArch {
127        pub fn as_arch(&self) -> target_lexicon::Architecture {
128            match self {
129                CommonArch::X86_64 => target_lexicon::Architecture::X86_64,
130                CommonArch::Aarch64 => target_lexicon::Architecture::Aarch64(
131                    target_lexicon::Aarch64Architecture::Aarch64,
132                ),
133            }
134        }
135
136        pub fn from_triple(triple: &target_lexicon::Triple) -> Option<Self> {
137            let arch = match triple.architecture {
138                target_lexicon::Architecture::Aarch64(
139                    target_lexicon::Aarch64Architecture::Aarch64,
140                ) => Self::Aarch64,
141                target_lexicon::Architecture::X86_64 => Self::X86_64,
142                _ => return None,
143            };
144            Some(arch)
145        }
146    }
147
148    /// Vocabulary type for artifacts that only get built for the most common
149    /// actively-supported platforms in the hvlite tree.
150    #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
151    pub enum CommonPlatform {
152        WindowsMsvc,
153        LinuxGnu,
154        LinuxMusl,
155        MacOs,
156    }
157
158    #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
159    pub enum CommonTriple {
160        Common {
161            arch: CommonArch,
162            platform: CommonPlatform,
163        },
164        Custom(target_lexicon::Triple),
165    }
166
167    impl CommonTriple {
168        pub const X86_64_WINDOWS_MSVC: Self = Self::Common {
169            arch: CommonArch::X86_64,
170            platform: CommonPlatform::WindowsMsvc,
171        };
172        pub const X86_64_LINUX_GNU: Self = Self::Common {
173            arch: CommonArch::X86_64,
174            platform: CommonPlatform::LinuxGnu,
175        };
176        pub const X86_64_LINUX_MUSL: Self = Self::Common {
177            arch: CommonArch::X86_64,
178            platform: CommonPlatform::LinuxMusl,
179        };
180        pub const AARCH64_WINDOWS_MSVC: Self = Self::Common {
181            arch: CommonArch::Aarch64,
182            platform: CommonPlatform::WindowsMsvc,
183        };
184        pub const AARCH64_LINUX_GNU: Self = Self::Common {
185            arch: CommonArch::Aarch64,
186            platform: CommonPlatform::LinuxGnu,
187        };
188        pub const AARCH64_LINUX_MUSL: Self = Self::Common {
189            arch: CommonArch::Aarch64,
190            platform: CommonPlatform::LinuxMusl,
191        };
192    }
193
194    impl std::fmt::Debug for CommonTriple {
195        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196            std::fmt::Debug::fmt(&self.as_triple(), f)
197        }
198    }
199
200    impl std::fmt::Display for CommonTriple {
201        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202            std::fmt::Display::fmt(&self.as_triple(), f)
203        }
204    }
205
206    impl PartialOrd for CommonTriple {
207        fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
208            Some(self.cmp(other))
209        }
210    }
211
212    impl Ord for CommonTriple {
213        fn cmp(&self, other: &Self) -> std::cmp::Ordering {
214            self.as_triple()
215                .to_string()
216                .cmp(&other.as_triple().to_string())
217        }
218    }
219
220    impl CommonTriple {
221        pub fn as_triple(&self) -> target_lexicon::Triple {
222            match self {
223                CommonTriple::Common { arch, platform } => match platform {
224                    CommonPlatform::WindowsMsvc => target_lexicon::Triple {
225                        architecture: arch.as_arch(),
226                        vendor: target_lexicon::Vendor::Pc,
227                        operating_system: target_lexicon::OperatingSystem::Windows,
228                        environment: target_lexicon::Environment::Msvc,
229                        binary_format: target_lexicon::BinaryFormat::Coff,
230                    },
231                    CommonPlatform::LinuxGnu => target_lexicon::Triple {
232                        architecture: arch.as_arch(),
233                        vendor: target_lexicon::Vendor::Unknown,
234                        operating_system: target_lexicon::OperatingSystem::Linux,
235                        environment: target_lexicon::Environment::Gnu,
236                        binary_format: target_lexicon::BinaryFormat::Elf,
237                    },
238                    CommonPlatform::LinuxMusl => target_lexicon::Triple {
239                        architecture: arch.as_arch(),
240                        vendor: target_lexicon::Vendor::Unknown,
241                        operating_system: target_lexicon::OperatingSystem::Linux,
242                        environment: target_lexicon::Environment::Musl,
243                        binary_format: target_lexicon::BinaryFormat::Elf,
244                    },
245                    CommonPlatform::MacOs => target_lexicon::Triple {
246                        architecture: arch.as_arch(),
247                        vendor: target_lexicon::Vendor::Apple,
248                        operating_system: target_lexicon::OperatingSystem::Darwin(None),
249                        environment: target_lexicon::Environment::Unknown,
250                        binary_format: target_lexicon::BinaryFormat::Macho,
251                    },
252                },
253                CommonTriple::Custom(t) => t.clone(),
254            }
255        }
256
257        pub fn common_arch(&self) -> Option<CommonArch> {
258            match self {
259                CommonTriple::Common { arch, .. } => Some(*arch),
260                CommonTriple::Custom(target) => CommonArch::from_triple(target),
261            }
262        }
263    }
264}
265
266#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
267pub enum BuildProfile {
268    Debug,
269    Release,
270    UnderhillShip,
271    BootDev,
272    BootRelease,
273    Light,
274}
275
276flowey_request! {
277    pub struct Request {
278        pub crate_name: String,
279        pub out_name: String,
280        pub profile: BuildProfile, // lock to only hvlite build profiles
281        pub features: BTreeSet<String>,
282        pub crate_type: CargoCrateType,
283        pub target: target_lexicon::Triple,
284        /// If supported by the target, build without split debuginfo.
285        pub no_split_dbg_info: bool,
286        pub extra_env: Option<ReadVar<BTreeMap<String, String>>>,
287        /// Wait for specified side-effects to resolve before running cargo-run.
288        ///
289        /// (e.g: to allow for some ambient packages / dependencies to get
290        /// installed).
291        pub pre_build_deps: Vec<ReadVar<SideEffect>>,
292        /// Resulting build output
293        pub output: WriteVar<CargoBuildOutput>,
294    }
295}
296
297new_flow_node!(struct Node);
298
299impl FlowNode for Node {
300    type Request = Request;
301
302    fn imports(ctx: &mut ImportCtx<'_>) {
303        ctx.import::<crate::install_openvmm_rust_build_essential::Node>();
304        ctx.import::<crate::git_checkout_openvmm_repo::Node>();
305        ctx.import::<crate::init_openvmm_magicpath_openhcl_sysroot::Node>();
306        ctx.import::<crate::run_split_debug_info::Node>();
307        ctx.import::<crate::init_cross_build::Node>();
308        ctx.import::<flowey_lib_common::run_cargo_build::Node>();
309        ctx.import::<flowey_lib_common::install_rust::Node>();
310    }
311
312    fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
313        let base_pre_build_deps =
314            [ctx.reqv(crate::install_openvmm_rust_build_essential::Request)].to_vec();
315
316        let openvmm_repo_path = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir);
317
318        for Request {
319            crate_name,
320            out_name,
321            profile,
322            features,
323            crate_type,
324            mut target,
325            no_split_dbg_info,
326            extra_env,
327            pre_build_deps: user_pre_build_deps,
328            output,
329        } in requests
330        {
331            let mut pre_build_deps = base_pre_build_deps.clone();
332            pre_build_deps.extend(user_pre_build_deps);
333
334            // FIXME: because we set `CC_{arch}_unknown_linux_musl` in our cargo env,
335            // we end up compiling _every_ musl artifact using the openhcl musl
336            // toolchain.
337            //
338            // it's not super clear how to fix this in a clean way without breaking the
339            // dev-ex of anyone using rust-analyzer though...
340            let sysroot_arch = match target.architecture {
341                target_lexicon::Architecture::Aarch64(_) => {
342                    crate::init_openvmm_magicpath_openhcl_sysroot::OpenvmmSysrootArch::Aarch64
343                }
344                target_lexicon::Architecture::X86_64 => {
345                    crate::init_openvmm_magicpath_openhcl_sysroot::OpenvmmSysrootArch::X64
346                }
347                arch => anyhow::bail!("unsupported arch {arch}"),
348            };
349
350            if matches!(target.environment, target_lexicon::Environment::Musl) {
351                pre_build_deps.push(
352                    ctx.reqv(|v| crate::init_openvmm_magicpath_openhcl_sysroot::Request {
353                        arch: sysroot_arch,
354                        path: v,
355                    })
356                    .into_side_effect(),
357                );
358            }
359
360            let injected_env = ctx.reqv(|v| crate::init_cross_build::Request {
361                target: target.clone(),
362                injected_env: v,
363            });
364
365            let extra_env = if let Some(extra_env) = extra_env {
366                extra_env
367                    .zip(ctx, injected_env)
368                    .map(ctx, move |(mut a, b)| {
369                        a.extend(b);
370                        a
371                    })
372            } else {
373                injected_env
374            };
375
376            let mut config = Vec::new();
377
378            // If the target vendor is specified as `minimal_rt`, then this is
379            // our custom target triple for the minimal_rt toolchain. Include the appropriate
380            // config file.
381            let passed_target = if target.vendor.as_str() == "minimal_rt" {
382                config.push(format!(
383                    "openhcl/minimal_rt/{arch}-config.toml",
384                    arch = target.architecture.into_str()
385                ));
386                if target.architecture == target_lexicon::Architecture::X86_64 {
387                    // x86-64 doesn't actually use a custom target currently,
388                    // since the x86_64-unknown-none target is stage 2 and has
389                    // reasonable defaults.
390                    target.vendor = target_lexicon::Vendor::Unknown;
391                    Some(target.clone())
392                } else {
393                    // We are building the target from source, so don't try to
394                    // install it via rustup. But do make sure the rust-src
395                    // component is available.
396                    ctx.req(flowey_lib_common::install_rust::Request::InstallComponent(
397                        "rust-src".into(),
398                    ));
399                    None
400                }
401            } else {
402                Some(target.clone())
403            };
404
405            let base_output = ctx.reqv(|v| flowey_lib_common::run_cargo_build::Request {
406                in_folder: openvmm_repo_path.clone(),
407                crate_name,
408                out_name,
409                profile: match profile {
410                    BuildProfile::Debug => CargoBuildProfile::Debug,
411                    BuildProfile::Release => CargoBuildProfile::Release,
412                    BuildProfile::UnderhillShip => {
413                        CargoBuildProfile::Custom("underhill-ship".into())
414                    }
415                    BuildProfile::BootDev => CargoBuildProfile::Custom("boot-dev".into()),
416                    BuildProfile::BootRelease => CargoBuildProfile::Custom("boot-release".into()),
417                    BuildProfile::Light => CargoBuildProfile::Custom("light".into()),
418                },
419                features,
420                output_kind: crate_type,
421                target: passed_target,
422                extra_env: Some(extra_env),
423                config,
424                pre_build_deps,
425                output: v,
426            });
427
428            if !no_split_dbg_info
429                && matches!(
430                    (crate_type, target.operating_system),
431                    (
432                        CargoCrateType::Bin,
433                        target_lexicon::OperatingSystem::Linux
434                            | target_lexicon::OperatingSystem::None_
435                    )
436                )
437            {
438                let elf_bin = base_output.clone().map(ctx, |o| match o {
439                    flowey_lib_common::run_cargo_build::CargoBuildOutput::ElfBin { bin } => bin,
440                    _ => unreachable!(),
441                });
442
443                let (out_bin, write_out_bin) = ctx.new_var();
444                let (out_dbg, write_out_dbg) = ctx.new_var();
445
446                ctx.req(crate::run_split_debug_info::Request {
447                    arch: match target.architecture {
448                        target_lexicon::Architecture::Aarch64(_) => common::CommonArch::Aarch64,
449                        target_lexicon::Architecture::X86_64 => common::CommonArch::X86_64,
450                        _ => anyhow::bail!("cannot split linux dbginfo on specified arch"),
451                    },
452                    in_bin: elf_bin,
453                    out_bin: write_out_bin,
454                    out_dbg_info: write_out_dbg,
455                });
456
457                ctx.emit_minor_rust_step("reporting split debug info", |ctx| {
458                    let out_bin = out_bin.claim(ctx);
459                    let out_dbg = out_dbg.claim(ctx);
460                    let base_output = base_output.claim(ctx);
461                    let output = output.claim(ctx);
462
463                    move |rt| {
464                        let mut fixed = CargoBuildOutput::from_base_cargo_build_output(
465                            rt.read(base_output),
466                            Some(rt.read(out_dbg)),
467                        );
468                        let CargoBuildOutput::ElfBin { bin, .. } = &mut fixed else {
469                            unreachable!()
470                        };
471                        *bin = rt.read(out_bin);
472                        rt.write(output, &fixed);
473                    }
474                });
475            } else {
476                base_output.write_into(ctx, output, |o| {
477                    CargoBuildOutput::from_base_cargo_build_output(o, None)
478                });
479            }
480        }
481
482        Ok(())
483    }
484}