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::OpenhclIgvmEndorsements;
12use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipe;
13use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipeDetails;
14use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipeDetailsLocalOnly;
15use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipeType;
16use crate::build_openhcl_igvm_from_recipe::OpenhclKernelPackage;
17use crate::build_openhcl_igvm_from_recipe::Vtl0KernelType;
18use crate::build_openhcl_initrd::OpenhclInitrdExtraParams;
19use crate::build_openvmm_hcl::MaxTraceLevel;
20use crate::build_openvmm_hcl::OpenvmmHclBuildProfile;
21use crate::build_openvmm_hcl::OpenvmmHclFeature;
22use crate::build_openvmm_hcl::OpenvmmHclOutput;
23use crate::common::CommonArch;
24use crate::common::CommonTriple;
25
26#[derive(Default, Serialize, Deserialize, PartialEq, Eq)]
27pub struct Customizations {
28    pub build_label: Option<String>,
29    pub custom_directory: Vec<PathBuf>,
30    pub custom_kernel: Option<PathBuf>,
31    pub custom_layer: Vec<PathBuf>,
32    pub custom_openhcl_boot: Option<PathBuf>,
33    pub custom_openvmm_hcl: Option<PathBuf>,
34    pub custom_sidecar: Option<PathBuf>,
35    pub custom_vtl0_kernel: Option<PathBuf>,
36    pub custom_extra_rootfs: Vec<PathBuf>,
37    pub confidential_debug: bool,
38    pub disable_secure_avic: bool,
39    pub override_arch: Option<CommonArch>,
40    pub override_kernel_pkg: Option<OpenhclKernelPackage>,
41    pub override_manifest: Option<PathBuf>,
42    pub override_openvmm_hcl_feature: Vec<String>,
43    pub override_max_trace_level: Option<MaxTraceLevel>,
44    pub with_debuginfo: bool,
45    pub with_mi_secure: bool,
46    pub with_perf_tools: bool,
47    pub with_sidecar: bool,
48}
49
50flowey_request! {
51    pub struct Params {
52        pub artifact_dir: ReadVar<PathBuf>,
53        pub done: WriteVar<SideEffect>,
54
55        pub base_recipe: OpenhclIgvmRecipe,
56        pub release: bool,
57        pub release_cfg: bool,
58
59        pub customizations: Customizations,
60    }
61}
62
63new_simple_flow_node!(struct Node);
64
65impl SimpleFlowNode for Node {
66    type Request = Params;
67
68    fn imports(ctx: &mut ImportCtx<'_>) {
69        ctx.import::<crate::build_openhcl_igvm_from_recipe::Node>();
70    }
71
72    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
73        let Params {
74            artifact_dir,
75            done,
76
77            base_recipe,
78            release,
79            release_cfg,
80
81            customizations,
82        } = request;
83
84        let has_customizations = customizations != Customizations::default();
85
86        let Customizations {
87            build_label,
88            custom_directory,
89            custom_kernel,
90            custom_layer,
91            override_manifest,
92            custom_openhcl_boot,
93            custom_openvmm_hcl,
94            custom_sidecar,
95            custom_vtl0_kernel,
96            override_arch,
97            override_kernel_pkg,
98            override_openvmm_hcl_feature,
99            override_max_trace_level,
100            confidential_debug,
101            disable_secure_avic,
102            with_debuginfo,
103            with_mi_secure,
104            with_perf_tools,
105            with_sidecar,
106            custom_extra_rootfs,
107        } = customizations;
108
109        if release_cfg && !release {
110            log::warn!(
111                "You are building a debug binary with a release configuration.\n\
112                The produced binary likely will not function properly due to memory restrictions."
113            )
114        }
115
116        if disable_secure_avic && (release_cfg || release) {
117            anyhow::bail!("--disable-secure-avic cannot be used with release builds.");
118        }
119
120        let build_profile = if release {
121            OpenvmmHclBuildProfile::OpenvmmHclShip
122        } else {
123            OpenvmmHclBuildProfile::Debug
124        };
125
126        let recipe_details = {
127            let mut recipe_details = base_recipe.recipe_details(release_cfg);
128
129            let OpenhclIgvmRecipeDetails {
130                local_only,
131                igvm_manifest,
132                openhcl_kernel_package,
133                openvmm_hcl_features,
134                target,
135                vtl0_kernel_type,
136                with_uefi,
137                with_interactive,
138                with_sidecar: with_sidecar_details,
139                max_trace_level,
140            } = &mut recipe_details;
141
142            if custom_kernel.is_some() {
143                *with_uefi = true
144            }
145
146            if with_sidecar || custom_sidecar.is_some() {
147                *with_sidecar_details = true;
148            }
149
150            assert!(local_only.is_none());
151            *local_only = Some(OpenhclIgvmRecipeDetailsLocalOnly {
152                // ensure binary remains un-sripped if perf tooling was also
153                // requested
154                openvmm_hcl_no_strip: with_perf_tools || with_debuginfo,
155                openhcl_initrd_extra_params: Some(OpenhclInitrdExtraParams {
156                    extra_initrd_layers: custom_layer
157                        .into_iter()
158                        .map(|p| p.absolute())
159                        .collect::<Result<_, _>>()?,
160                    extra_initrd_directories: custom_directory
161                        .into_iter()
162                        .map(|p| p.absolute())
163                        .collect::<Result<_, _>>()?,
164                }),
165                custom_openvmm_hcl: custom_openvmm_hcl.map(|p| p.absolute()).transpose()?,
166                custom_openhcl_boot: custom_openhcl_boot.map(|p| p.absolute()).transpose()?,
167                custom_kernel: custom_kernel.map(|p| p.absolute()).transpose()?,
168                custom_sidecar: custom_sidecar.map(|p| p.absolute()).transpose()?,
169                custom_extra_rootfs: custom_extra_rootfs
170                    .into_iter()
171                    .map(|p| p.absolute())
172                    .collect::<Result<_, _>>()?,
173            });
174
175            if let Some(p) = override_manifest {
176                *igvm_manifest = IgvmManifestPath::LocalOnlyCustom(p.absolute()?);
177            }
178
179            if let Some(override_kernel_pkg) = override_kernel_pkg {
180                *openhcl_kernel_package = override_kernel_pkg;
181            }
182
183            if !override_openvmm_hcl_feature.is_empty() {
184                *openvmm_hcl_features = override_openvmm_hcl_feature
185                    .into_iter()
186                    .map(OpenvmmHclFeature::LocalOnlyCustom)
187                    .collect()
188            }
189
190            if with_mi_secure {
191                openvmm_hcl_features.insert(OpenvmmHclFeature::MiSecure);
192            }
193
194            if let Some(arch) = override_arch {
195                *target = match arch {
196                    CommonArch::X86_64 => CommonTriple::X86_64_LINUX_MUSL,
197                    CommonArch::Aarch64 => CommonTriple::AARCH64_LINUX_MUSL,
198                };
199            }
200
201            if let Some(lvl) = override_max_trace_level {
202                *max_trace_level = lvl;
203            }
204
205            if let Some(p) = custom_vtl0_kernel {
206                *vtl0_kernel_type = Some(Vtl0KernelType::LocalOnlyCustom(p.absolute()?))
207            }
208
209            // Debug configurations already include --interactive by default
210            // on x86 for busybox, gdbserver, and perf (aarch64 is currently
211            // broken and always disabled by default to allow the shell to
212            // work with ohcldiag-dev. see #1234).
213            *with_interactive |= with_perf_tools;
214
215            if *with_interactive && target.common_arch()? == CommonArch::Aarch64 {
216                log::warn!(
217                    "Please note that using perf tools on ARM currently breaks ohcldiag-dev shell"
218                );
219            }
220
221            recipe_details
222        };
223
224        let build_label = if let Some(label) = build_label {
225            label
226        } else {
227            let base = match &recipe_details.igvm_manifest {
228                IgvmManifestPath::InTree(_) => base_recipe.non_production_tag(),
229                IgvmManifestPath::LocalOnlyCustom(path) => path
230                    .file_name()
231                    .unwrap()
232                    .to_str()
233                    .unwrap()
234                    .strip_suffix(".json")
235                    .unwrap()
236                    .to_string(),
237            };
238
239            if has_customizations {
240                format!("{base}-custom")
241            } else {
242                base
243            }
244        };
245
246        let (openhcl_igvm, write_openhcl_igvm) = ctx.new_var();
247        let (openhcl_igvm_extras, write_openhcl_igvm_extras) = ctx.new_var();
248
249        ctx.req(crate::build_openhcl_igvm_from_recipe::Request {
250            build_profile,
251            release_cfg,
252            recipe: OpenhclIgvmRecipeType::LocalOnlyCustom(recipe_details),
253            custom_target: None,
254            extra_features: BTreeSet::new(),
255            disable_secure_avic,
256            uefi_firmware_flavor: None,
257            confidential_debug,
258            openhcl_igvm: write_openhcl_igvm,
259            openhcl_igvm_extras: write_openhcl_igvm_extras,
260        });
261
262        ctx.emit_rust_step("copy to output directory", |ctx| {
263            done.claim(ctx);
264            claim_vars!(ctx, (artifact_dir, openhcl_igvm, openhcl_igvm_extras));
265            move |rt| {
266                read_vars!(rt, (artifact_dir, openhcl_igvm, openhcl_igvm_extras));
267
268                let output_dir = artifact_dir
269                    .join(match build_profile {
270                        OpenvmmHclBuildProfile::Debug => "debug",
271                        OpenvmmHclBuildProfile::Release => "release",
272                        OpenvmmHclBuildProfile::OpenvmmHclShip => "ship",
273                    })
274                    .join(&build_label);
275                fs_err::create_dir_all(&output_dir)?;
276
277                let OpenvmmHclOutput { bin, dbg } = openhcl_igvm_extras.openvmm_hcl;
278                fs_err::copy(bin, output_dir.join("openvmm_hcl"))?;
279                if let Some(dbg) = dbg {
280                    fs_err::copy(dbg, output_dir.join("openvmm_hcl.dbg"))?;
281                }
282
283                let OpenhclBootOutput { bin, dbg } = openhcl_igvm_extras.openhcl_boot;
284                fs_err::copy(bin, output_dir.join("openhcl_boot"))?;
285                fs_err::copy(dbg, output_dir.join("openhcl_boot.dbg"))?;
286
287                if let Some(built_sidecar) = openhcl_igvm_extras.sidecar {
288                    let crate::build_sidecar::SidecarOutput { bin, dbg } = built_sidecar;
289                    fs_err::copy(bin, output_dir.join("sidecar"))?;
290                    fs_err::copy(dbg, output_dir.join("sidecar.dbg"))?;
291                }
292
293                let igvm_bin = openhcl_igvm.igvm_bin();
294                fs_err::copy(
295                    igvm_bin,
296                    output_dir.join(format!("openhcl-{build_label}.bin")),
297                )?;
298                if let Some(igvm_map) = openhcl_igvm_extras.igvm_map {
299                    fs_err::copy(
300                        igvm_map,
301                        output_dir.join(format!("openhcl-{build_label}.bin.map")),
302                    )?;
303                }
304                if let Some(OpenhclIgvmEndorsements::X64 {
305                    igvm_tdx_json,
306                    igvm_snp_json,
307                    igvm_vbs_json,
308                    igvm_snp_idblock,
309                    igvm_tdx_corim,
310                    igvm_snp_corim,
311                    igvm_vbs_corim,
312                }) = openhcl_igvm.endorsements()
313                {
314                    if let Some(igvm_tdx_json) = igvm_tdx_json {
315                        fs_err::copy(igvm_tdx_json, output_dir.join("openhcl-tdx.json"))?;
316                    }
317                    if let Some(igvm_snp_json) = igvm_snp_json {
318                        fs_err::copy(igvm_snp_json, output_dir.join("openhcl-snp.json"))?;
319                    }
320                    if let Some(igvm_vbs_json) = igvm_vbs_json {
321                        fs_err::copy(igvm_vbs_json, output_dir.join("openhcl-vbs.json"))?;
322                    }
323                    if let Some(igvm_snp_idblock) = igvm_snp_idblock {
324                        fs_err::copy(igvm_snp_idblock, output_dir.join("openhcl-snp.idblock"))?;
325                    }
326                    if let Some(igvm_tdx_corim) = igvm_tdx_corim {
327                        fs_err::copy(igvm_tdx_corim, output_dir.join("openhcl-tdx.cbor"))?;
328                    }
329                    if let Some(igvm_snp_corim) = igvm_snp_corim {
330                        fs_err::copy(igvm_snp_corim, output_dir.join("openhcl-snp.cbor"))?;
331                    }
332                    if let Some(igvm_vbs_corim) = igvm_vbs_corim {
333                        fs_err::copy(igvm_vbs_corim, output_dir.join("openhcl-vbs.cbor"))?;
334                    }
335                }
336                for e in fs_err::read_dir(output_dir)? {
337                    let e = e?;
338                    log::info!("{}", e.path().display());
339                }
340
341                Ok(())
342            }
343        });
344
345        Ok(())
346    }
347}