Skip to main content

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