flowey_lib_hvlite/_jobs/
local_build_igvm.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A local-only job that supports the `cargo xflowey build-igvm` CLI
5
6use flowey::node::prelude::*;
7
8use crate::build_openhcl_boot::OpenhclBootOutput;
9use crate::build_openhcl_igvm_from_recipe::IgvmManifestPath;
10use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipe;
11use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipeDetails;
12use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipeDetailsLocalOnly;
13use crate::build_openhcl_igvm_from_recipe::OpenhclKernelPackage;
14use crate::build_openhcl_igvm_from_recipe::Vtl0KernelType;
15use crate::build_openhcl_initrd::OpenhclInitrdExtraParams;
16use crate::build_openvmm_hcl::MaxTraceLevel;
17use crate::build_openvmm_hcl::OpenvmmHclBuildProfile;
18use crate::build_openvmm_hcl::OpenvmmHclFeature;
19use crate::build_openvmm_hcl::OpenvmmHclOutput;
20use crate::run_cargo_build::common::CommonArch;
21use crate::run_cargo_build::common::CommonTriple;
22use crate::run_igvmfilegen::IgvmOutput;
23
24#[derive(Default, Serialize, Deserialize, PartialEq, Eq)]
25pub struct Customizations {
26    pub build_label: Option<String>,
27    pub custom_directory: Vec<PathBuf>,
28    pub custom_kernel: Option<PathBuf>,
29    pub custom_layer: Vec<PathBuf>,
30    pub custom_openhcl_boot: Option<PathBuf>,
31    pub custom_openvmm_hcl: Option<PathBuf>,
32    pub custom_sidecar: Option<PathBuf>,
33    pub custom_uefi: Option<PathBuf>,
34    pub custom_vtl0_kernel: Option<PathBuf>,
35    pub custom_extra_rootfs: Vec<PathBuf>,
36    pub override_arch: Option<CommonArch>,
37    pub override_kernel_pkg: Option<OpenhclKernelPackage>,
38    pub override_manifest: Option<PathBuf>,
39    pub override_openvmm_hcl_feature: Vec<String>,
40    pub override_max_trace_level: Option<MaxTraceLevel>,
41    pub with_debuginfo: bool,
42    pub with_perf_tools: bool,
43    pub with_sidecar: bool,
44}
45
46flowey_request! {
47    pub struct Params {
48        pub artifact_dir: ReadVar<PathBuf>,
49        pub done: WriteVar<SideEffect>,
50
51        pub base_recipe: OpenhclIgvmRecipe,
52        pub release: bool,
53        pub release_cfg: bool,
54
55        pub customizations: Customizations,
56    }
57}
58
59new_simple_flow_node!(struct Node);
60
61impl SimpleFlowNode for Node {
62    type Request = Params;
63
64    fn imports(ctx: &mut ImportCtx<'_>) {
65        ctx.import::<crate::build_openhcl_igvm_from_recipe::Node>();
66    }
67
68    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
69        let Params {
70            artifact_dir,
71            done,
72
73            base_recipe,
74            release,
75            release_cfg,
76
77            customizations,
78        } = request;
79
80        let has_customizations = customizations != Customizations::default();
81
82        let Customizations {
83            build_label,
84            custom_directory,
85            custom_kernel,
86            custom_layer,
87            override_manifest,
88            custom_openhcl_boot,
89            custom_openvmm_hcl,
90            custom_sidecar,
91            custom_uefi,
92            custom_vtl0_kernel,
93            override_arch,
94            override_kernel_pkg,
95            override_openvmm_hcl_feature,
96            override_max_trace_level,
97            with_debuginfo,
98            with_perf_tools,
99            with_sidecar,
100            custom_extra_rootfs,
101        } = customizations;
102
103        if release_cfg && !release {
104            log::warn!(
105                "You are building a debug binary with a release configuration.\n\
106                The produced binary likely will not function properly due to memory restrictions."
107            )
108        }
109
110        let build_profile = if release {
111            OpenvmmHclBuildProfile::OpenvmmHclShip
112        } else {
113            OpenvmmHclBuildProfile::Debug
114        };
115        let mut recipe_details = base_recipe.recipe_details(release_cfg);
116
117        {
118            let OpenhclIgvmRecipeDetails {
119                local_only,
120                igvm_manifest,
121                openhcl_kernel_package,
122                openvmm_hcl_features,
123                target,
124                vtl0_kernel_type,
125                with_uefi,
126                with_interactive,
127                with_sidecar: with_sidecar_details,
128                max_trace_level,
129            } = &mut recipe_details;
130
131            if custom_kernel.is_some() {
132                *with_uefi = true
133            }
134
135            if with_sidecar || custom_sidecar.is_some() {
136                *with_sidecar_details = true;
137            }
138
139            // Debug configurations include --interactive by default, for busybox, gdbserver, and perf.
140            *with_interactive = !release_cfg || with_perf_tools;
141
142            assert!(local_only.is_none());
143            *local_only = Some(OpenhclIgvmRecipeDetailsLocalOnly {
144                // ensure binary remains un-sripped if perf tooling was also
145                // requested
146                openvmm_hcl_no_strip: with_perf_tools || with_debuginfo,
147                openhcl_initrd_extra_params: Some(OpenhclInitrdExtraParams {
148                    extra_initrd_layers: custom_layer
149                        .into_iter()
150                        .map(|p| p.absolute())
151                        .collect::<Result<_, _>>()?,
152                    extra_initrd_directories: custom_directory
153                        .into_iter()
154                        .map(|p| p.absolute())
155                        .collect::<Result<_, _>>()?,
156                }),
157                custom_openvmm_hcl: custom_openvmm_hcl.map(|p| p.absolute()).transpose()?,
158                custom_openhcl_boot: custom_openhcl_boot.map(|p| p.absolute()).transpose()?,
159                custom_uefi: custom_uefi.map(|p| p.absolute()).transpose()?,
160                custom_kernel: custom_kernel.map(|p| p.absolute()).transpose()?,
161                custom_sidecar: custom_sidecar.map(|p| p.absolute()).transpose()?,
162                custom_extra_rootfs: custom_extra_rootfs
163                    .into_iter()
164                    .map(|p| p.absolute())
165                    .collect::<Result<_, _>>()?,
166            });
167
168            if let Some(p) = override_manifest {
169                *igvm_manifest = IgvmManifestPath::LocalOnlyCustom(p.absolute()?);
170            }
171
172            if let Some(override_kernel_pkg) = override_kernel_pkg {
173                *openhcl_kernel_package = override_kernel_pkg;
174            }
175
176            if !override_openvmm_hcl_feature.is_empty() {
177                *openvmm_hcl_features = override_openvmm_hcl_feature
178                    .into_iter()
179                    .map(OpenvmmHclFeature::LocalOnlyCustom)
180                    .collect()
181            }
182
183            if let Some(arch) = override_arch {
184                *target = match arch {
185                    CommonArch::X86_64 => CommonTriple::X86_64_LINUX_MUSL,
186                    CommonArch::Aarch64 => CommonTriple::AARCH64_LINUX_MUSL,
187                };
188            }
189
190            if let Some(lvl) = override_max_trace_level {
191                *max_trace_level = lvl;
192            }
193
194            if let Some(p) = custom_vtl0_kernel {
195                *vtl0_kernel_type = Some(Vtl0KernelType::LocalOnlyCustom(p.absolute()?))
196            }
197        }
198
199        let build_label = if let Some(label) = build_label {
200            label
201        } else {
202            let base = match &recipe_details.igvm_manifest {
203                IgvmManifestPath::InTree(_) => {
204                    non_production_build_igvm_tool_out_name(&base_recipe).to_string()
205                }
206                IgvmManifestPath::LocalOnlyCustom(path) => path
207                    .file_name()
208                    .unwrap()
209                    .to_str()
210                    .unwrap()
211                    .strip_suffix(".json")
212                    .unwrap()
213                    .to_string(),
214            };
215
216            if has_customizations {
217                format!("{base}-custom")
218            } else {
219                base
220            }
221        };
222
223        let (built_openvmm_hcl, write_built_openvmm_hcl) = ctx.new_var();
224        let (built_openhcl_boot, write_built_openhcl_boot) = ctx.new_var();
225        let (built_openhcl_igvm, write_built_openhcl_igvm) = ctx.new_var();
226        let (built_sidecar, write_built_sidecar) = ctx.new_var();
227
228        ctx.req(crate::build_openhcl_igvm_from_recipe::Request {
229            build_profile,
230            release_cfg,
231            recipe: OpenhclIgvmRecipe::LocalOnlyCustom(recipe_details),
232            custom_target: None,
233            built_openvmm_hcl: write_built_openvmm_hcl,
234            built_openhcl_boot: write_built_openhcl_boot,
235            built_openhcl_igvm: write_built_openhcl_igvm,
236            built_sidecar: write_built_sidecar,
237        });
238
239        ctx.emit_rust_step("copy to output directory", |ctx| {
240            done.claim(ctx);
241            let artifact_dir = artifact_dir.claim(ctx);
242            let built_openvmm_hcl = built_openvmm_hcl.claim(ctx);
243            let built_openhcl_boot = built_openhcl_boot.claim(ctx);
244            let built_openhcl_igvm = built_openhcl_igvm.claim(ctx);
245            let built_sidecar = built_sidecar.claim(ctx);
246            move |rt| {
247                let output_dir = rt
248                    .read(artifact_dir)
249                    .join(match build_profile {
250                        OpenvmmHclBuildProfile::Debug => "debug",
251                        OpenvmmHclBuildProfile::Release => "release",
252                        OpenvmmHclBuildProfile::OpenvmmHclShip => "ship",
253                    })
254                    .join(&build_label);
255                fs_err::create_dir_all(&output_dir)?;
256
257                let OpenvmmHclOutput { bin, dbg } = rt.read(built_openvmm_hcl);
258                fs_err::copy(bin, output_dir.join("openvmm_hcl"))?;
259                if let Some(dbg) = dbg {
260                    fs_err::copy(dbg, output_dir.join("openvmm_hcl.dbg"))?;
261                }
262
263                let OpenhclBootOutput { bin, dbg } = rt.read(built_openhcl_boot);
264                fs_err::copy(bin, output_dir.join("openhcl_boot"))?;
265                fs_err::copy(dbg, output_dir.join("openhcl_boot.dbg"))?;
266
267                if let Some(built_sidecar) = rt.read(built_sidecar) {
268                    let crate::build_sidecar::SidecarOutput { bin, dbg } = built_sidecar;
269                    fs_err::copy(bin, output_dir.join("sidecar"))?;
270                    fs_err::copy(dbg, output_dir.join("sidecar.dbg"))?;
271                }
272
273                let IgvmOutput {
274                    igvm_bin,
275                    igvm_map,
276                    igvm_tdx_json,
277                    igvm_snp_json,
278                    igvm_vbs_json,
279                } = rt.read(built_openhcl_igvm);
280                fs_err::copy(
281                    igvm_bin,
282                    output_dir.join(format!("openhcl-{build_label}.bin")),
283                )?;
284                if let Some(igvm_map) = igvm_map {
285                    fs_err::copy(
286                        igvm_map,
287                        output_dir.join(format!("openhcl-{build_label}.bin.map")),
288                    )?;
289                }
290                if let Some(igvm_tdx_json) = igvm_tdx_json {
291                    fs_err::copy(igvm_tdx_json, output_dir.join("openhcl-tdx.json"))?;
292                }
293                if let Some(igvm_snp_json) = igvm_snp_json {
294                    fs_err::copy(igvm_snp_json, output_dir.join("openhcl-snp.json"))?;
295                }
296                if let Some(igvm_vbs_json) = igvm_vbs_json {
297                    fs_err::copy(igvm_vbs_json, output_dir.join("openhcl-vbs.json"))?;
298                }
299                for e in fs_err::read_dir(output_dir)? {
300                    let e = e?;
301                    log::info!("{}", e.path().display());
302                }
303
304                Ok(())
305            }
306        });
307
308        Ok(())
309    }
310}
311
312pub fn non_production_build_igvm_tool_out_name(recipe: &OpenhclIgvmRecipe) -> &'static str {
313    match recipe {
314        OpenhclIgvmRecipe::X64 => "x64",
315        OpenhclIgvmRecipe::X64Devkern => "x64-devkern",
316        OpenhclIgvmRecipe::X64TestLinuxDirect => "x64-test-linux-direct",
317        OpenhclIgvmRecipe::X64TestLinuxDirectDevkern => "x64-test-linux-direct-devkern",
318        OpenhclIgvmRecipe::X64Cvm => "x64-cvm",
319        OpenhclIgvmRecipe::X64CvmDevkern => "x64-cvm-devkern",
320        OpenhclIgvmRecipe::Aarch64 => "aarch64",
321        OpenhclIgvmRecipe::Aarch64Devkern => "aarch64-devkern",
322        OpenhclIgvmRecipe::LocalOnlyCustom(_) => unreachable!(),
323    }
324}