Skip to main content

flowey_hvlite/pipelines/
build_igvm.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! See [`BuildIgvmCli`]
5
6use crate::pipelines_shared::cfg_common_params::CommonArchCli;
7use flowey::node::prelude::ReadVar;
8use flowey::pipeline::prelude::*;
9use flowey_lib_hvlite::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipe;
10use flowey_lib_hvlite::build_openhcl_igvm_from_recipe::OpenhclKernelPackage;
11use flowey_lib_hvlite::build_openvmm_hcl::MaxTraceLevel;
12use flowey_lib_hvlite::common::CommonArch;
13use std::path::PathBuf;
14
15#[derive(clap::ValueEnum, Copy, Clone)]
16pub enum OpenhclRecipeCli {
17    /// Aarch64 OpenHCL
18    Aarch64,
19    /// Aarch64 OpenHCL, using the dev kernel in VTL2
20    Aarch64Devkern,
21    /// X64 OpenHCL, with CVM support.
22    X64Cvm,
23    /// X64 OpenHCL, with CVM support using the dev kernel in VTL2
24    X64CvmDevkern,
25    /// X64 OpenHCL booting VTL0 using a test linux-direct kernel + initrd (no
26    /// UEFI).
27    X64TestLinuxDirect,
28    /// X64 OpenHCL booting VTL0 using a test linux-direct kernel + initrd (no
29    /// UEFI), using the dev kernel in VTL2.
30    X64TestLinuxDirectDevkern,
31    /// X64 OpenHCL
32    X64,
33    /// X64 OpenHCL, using the dev kernel in VTL2
34    X64Devkern,
35}
36
37/// Build OpenHCL IGVM files for local development. DO NOT USE IN CI.
38#[derive(clap::Args)]
39pub struct BuildIgvmCli<Recipe = OpenhclRecipeCli>
40where
41    // Make the recipe generic so that out-of-tree flowey implementations can
42    // slot in a custom set of recipes to build with.
43    Recipe: clap::ValueEnum + Clone + Send + Sync + 'static,
44{
45    /// Specify which OpenHCL recipe to build / customize off-of.
46    ///
47    /// A "recipe" corresponds to the various standard IGVM SKUs that are
48    /// actively supported and tested in our build infrastructure.
49    ///
50    /// It encodes all the details of what goes into an individual IGVM file,
51    /// such as what build flags `openvmm_hcl` should be built with, what goes
52    /// into a VTL2 initrd, what `igvmfilegen` manifest is being used, etc...
53    pub recipe: Recipe,
54
55    /// Build using release variants of all constituent binary components.
56    ///
57    /// Uses --profile=boot-release for openhcl_boot, --profile=openhcl-ship
58    /// when building openvmm_hcl, etc...
59    #[clap(long)]
60    pub release: bool,
61
62    /// Configure the IGVM file with the appropriate `-release.json`
63    /// manifest variant, and disable debug-only features.
64    #[clap(long)]
65    pub release_cfg: bool,
66
67    /// pass `--verbose` to cargo
68    #[clap(long)]
69    pub verbose: bool,
70
71    /// pass `--locked` to cargo
72    #[clap(long)]
73    pub locked: bool,
74
75    /// Automatically install any missing required dependencies.
76    #[clap(long)]
77    pub install_missing_deps: bool,
78
79    #[clap(flatten)]
80    pub customizations: BuildIgvmCliCustomizations,
81}
82
83#[derive(clap::Args)]
84#[clap(next_help_heading = "Customizations")]
85pub struct BuildIgvmCliCustomizations {
86    /// Set a custom label for this `build-igvm` invocation. If no label is
87    /// provided, customized IGVM files will be output with the label
88    /// `{base_recipe_name}-custom`
89    #[clap(long, short = 'o')]
90    pub build_label: Option<String>,
91
92    /// Override which kernel package to use.
93    #[clap(long)]
94    pub override_kernel_pkg: Option<KernelPackageKindCli>,
95
96    /// Pass additional features when building openmm_hcl
97    #[clap(long)]
98    pub override_openvmm_hcl_feature: Vec<String>,
99
100    /// Override architecture used when building. You probably don't want this -
101    /// prefer changing the base recipe to something more appropriate.
102    #[clap(long)]
103    pub override_arch: Option<CommonArchCli>,
104
105    /// Override the json manifest passed to igvmfilegen, none means the
106    /// debug/release manifest from the base recipe will be used.
107    #[clap(long)]
108    pub override_manifest: Option<PathBuf>,
109
110    /// Ensure perf tools are included in the release initrd.
111    ///
112    /// Ensures that openvmm_hcl is not stripped, so that perf tools work
113    /// correctly, and requires that the file be built in `--release` mode, so
114    /// that perf numbers are more representative of production binaries.
115    #[clap(long, requires = "release")]
116    pub with_perf_tools: bool,
117
118    /// Preserve debuginfo in the openvmm_hcl binary in the IGVM file.
119    ///
120    /// This increases the VTL2 memory requirements significantly, and will
121    /// likely require passing a `--override-manifest` to compensate.
122    #[clap(long)]
123    pub with_debuginfo: bool,
124
125    /// Build mimalloc with secure mode enabled. This adds extra security
126    /// hardening (guard pages, randomized allocation, encrypted free lists)
127    /// at a small performance cost.
128    #[clap(long)]
129    pub with_mi_secure: bool,
130
131    /// Disable secure AVIC support for SNP. This adds the
132    /// `disable_secure_avic` cargo feature and sets `secure_avic` to
133    /// `disabled` in the IGVM manifest.
134    #[clap(long)]
135    pub disable_secure_avic: bool,
136
137    /// Enable per-VM measured product policy support. This adds the
138    /// `product_policy` cargo feature when building openvmm_hcl.
139    #[clap(long)]
140    pub enable_product_policy: bool,
141    /// Enable confidential diagnostics by adding `OPENHCL_CONFIDENTIAL_DEBUG=1`
142    /// to the measured OpenHCL command line. This disables the diagnostic
143    /// filtering that CVM release builds otherwise apply, so
144    /// diagnostics remain available
145    ///
146    /// WARNING: This is security-sensitive. OpenHCL uses this flag to decide
147    /// whether it can trust host-provided boot options for isolated guests.
148    /// Only enable this flag if you understand the security implications.
149    #[clap(long)]
150    pub confidential_debug: bool,
151
152    /// Path to custom openvmm_hcl binary, none means openhcl will be built.
153    #[clap(long)]
154    pub custom_openvmm_hcl: Option<PathBuf>,
155
156    /// Path to custom openhcl_boot, none means the boot loader will be built.
157    #[clap(long)]
158    pub custom_openhcl_boot: Option<PathBuf>,
159
160    /// Path to custom uefi MSVM.fd, none means the packaged uefi will be used.
161    #[clap(long)]
162    pub custom_uefi: Option<PathBuf>,
163
164    /// Path to custom kernel vmlinux / Image, none means the packaged kernel
165    /// will be used.
166    #[clap(long)]
167    pub custom_kernel: Option<PathBuf>,
168
169    /// Path to kernel modules, none means the packaged kernel modules will be
170    /// used.
171    #[clap(long, requires = "custom_kernel")]
172    pub custom_kernel_modules: Option<PathBuf>,
173
174    /// Path to custom vtl0 linux kernel to use if the manifest includes a
175    /// direct-boot linux VM.
176    ///
177    /// If not specified, the packaged openvmm test linux direct kernel is used.
178    #[clap(long)]
179    pub custom_vtl0_kernel: Option<PathBuf>,
180
181    /// Additional layers to be included in the initrd
182    #[clap(long)]
183    pub custom_layer: Vec<PathBuf>,
184
185    /// Additional directories to be included in the initrd
186    #[clap(long)]
187    pub custom_directory: Vec<PathBuf>,
188
189    /// Additional rootfs.config files to use to generate the initrd
190    #[clap(long)]
191    pub custom_extra_rootfs: Vec<PathBuf>,
192
193    /// (experimental) Include the AP kernel in the IGVM file
194    #[clap(long)]
195    pub with_sidecar: bool,
196
197    /// (experimental) Path to custom sidecar kernel binary, none means sidecar
198    /// will be built.
199    #[clap(long, requires = "with_sidecar")]
200    pub custom_sidecar: Option<PathBuf>,
201
202    /// The maximum trace level to set for the openvmm_hcl build. Defaults
203    /// to `trace` for debug builds and `debug` for release builds.
204    #[clap(long)]
205    pub max_trace_level: Option<MaxTraceLevelCli>,
206
207    /// (experimental) Only use local dependencies to build. Keeps flowey from
208    /// downloading any dependencies from the internet.
209    #[clap(long, requires_all = ["custom_openvmm_deps", "custom_protoc", "custom_kernel", "custom_kernel_modules", "custom_uefi"])]
210    pub use_local_deps: bool,
211
212    /// Use a custom openvmm_deps directory.
213    #[clap(long)]
214    pub custom_openvmm_deps: Option<PathBuf>,
215
216    /// Use a custom protoc directory.
217    #[clap(long)]
218    pub custom_protoc: Option<PathBuf>,
219}
220
221#[derive(clap::ValueEnum, Copy, Clone, PartialEq, Eq, Debug)]
222pub enum KernelPackageKindCli {
223    /// Kernel from the hcl-main branch
224    Main,
225    /// CVM kernel from the hcl-main branch
226    Cvm,
227    /// Kernel from the hcl-dev branch
228    Dev,
229    /// CVM kernel from the hcl-dev brnach
230    CvmDev,
231}
232
233#[derive(clap::ValueEnum, Copy, Clone, PartialEq, Eq, Debug)]
234pub enum MaxTraceLevelCli {
235    /// All trace events.
236    Trace,
237    /// Debug and higher.
238    Debug,
239    /// Info and higher.
240    Info,
241    /// Warn and higher.
242    Warn,
243    /// Error events only.
244    Error,
245    /// No tracing.
246    Off,
247}
248
249impl From<MaxTraceLevelCli> for MaxTraceLevel {
250    fn from(cli: MaxTraceLevelCli) -> Self {
251        match cli {
252            MaxTraceLevelCli::Trace => MaxTraceLevel::Trace,
253            MaxTraceLevelCli::Debug => MaxTraceLevel::Debug,
254            MaxTraceLevelCli::Info => MaxTraceLevel::Info,
255            MaxTraceLevelCli::Warn => MaxTraceLevel::Warn,
256            MaxTraceLevelCli::Error => MaxTraceLevel::Error,
257            MaxTraceLevelCli::Off => MaxTraceLevel::Off,
258        }
259    }
260}
261
262pub fn bail_if_running_in_ci() -> anyhow::Result<()> {
263    const OVERRIDE_ENV: &str = "I_HAVE_A_GOOD_REASON_TO_RUN_BUILD_IGVM_IN_CI";
264
265    if std::env::var(OVERRIDE_ENV).is_ok() {
266        return Ok(());
267    }
268
269    for ci_env in ["TF_BUILD", "GITHUB_ACTIONS"] {
270        if std::env::var(ci_env).is_ok() {
271            log::warn!("Detected that {ci_env} is set");
272            log::warn!("");
273            log::warn!("Do not use `build-igvm` in CI scripts!");
274            log::warn!(
275                "This is a local-only, inner-dev-loop tool to build IGVM files, with an UNSTABLE CLI."
276            );
277            log::warn!("");
278            log::warn!(
279                "Automated pipelines should use the underlying `flowey` nodes that power build-igvm directly, _without_ relying on its CLI!"
280            );
281            log::warn!("");
282            log::warn!(
283                "If you _really_ know what you're doing, you can set {OVERRIDE_ENV} to disable this error."
284            );
285            anyhow::bail!("attempted to run `build-igvm` in CI")
286        }
287    }
288
289    Ok(())
290}
291
292impl IntoPipeline for BuildIgvmCli {
293    fn into_pipeline(self, backend_hint: PipelineBackendHint) -> anyhow::Result<Pipeline> {
294        if !matches!(backend_hint, PipelineBackendHint::Local) {
295            anyhow::bail!("build-igvm is for local use only")
296        }
297
298        bail_if_running_in_ci()?;
299
300        let openvmm_repo = flowey_lib_common::git_checkout::RepoSource::ExistingClone(
301            ReadVar::from_static(crate::repo_root()),
302        );
303
304        let Self {
305            recipe,
306            release,
307            release_cfg,
308            verbose,
309            locked,
310            install_missing_deps,
311            customizations:
312                BuildIgvmCliCustomizations {
313                    build_label,
314                    override_kernel_pkg,
315                    override_openvmm_hcl_feature,
316                    override_arch,
317                    override_manifest,
318                    with_perf_tools,
319                    with_debuginfo,
320                    with_mi_secure,
321                    disable_secure_avic,
322                    enable_product_policy,
323                    confidential_debug,
324                    custom_openvmm_hcl,
325                    custom_openhcl_boot,
326                    custom_uefi,
327                    custom_kernel,
328                    custom_kernel_modules,
329                    custom_vtl0_kernel,
330                    custom_layer,
331                    custom_directory,
332                    with_sidecar,
333                    custom_sidecar,
334                    mut custom_extra_rootfs,
335                    max_trace_level,
336                    custom_openvmm_deps,
337                    custom_protoc,
338                    use_local_deps: _, // Clap already validated that all required fields are present
339                },
340        } = self;
341
342        if with_perf_tools {
343            custom_extra_rootfs.push(crate::repo_root().join("openhcl/perftoolsfs.config"));
344        }
345
346        let mut pipeline = Pipeline::new();
347
348        let (pub_out_dir, _) = pipeline.new_artifact("build-igvm");
349
350        // Determine the architecture from the recipe
351        let recipe_arch = match recipe {
352            OpenhclRecipeCli::X64
353            | OpenhclRecipeCli::X64Devkern
354            | OpenhclRecipeCli::X64Cvm
355            | OpenhclRecipeCli::X64CvmDevkern
356            | OpenhclRecipeCli::X64TestLinuxDirect
357            | OpenhclRecipeCli::X64TestLinuxDirectDevkern => CommonArch::X86_64,
358            OpenhclRecipeCli::Aarch64 | OpenhclRecipeCli::Aarch64Devkern => CommonArch::Aarch64,
359        };
360
361        // Use the effective arch, accounting for any --override-arch
362        let effective_arch = override_arch.map(CommonArch::from).unwrap_or(recipe_arch);
363
364        let mut job = pipeline.new_job(
365            FlowPlatform::host(backend_hint),
366            FlowArch::host(backend_hint),
367            "build-igvm",
368        );
369
370        // Initialize cfg_versions job, this makes sure everything will be downloaded
371        // and versions are set up correctly unless overriden by other parameters.
372        job = job.dep_on(|_| flowey_lib_hvlite::_jobs::cfg_versions::Request::Init);
373
374        // Override openvmm_deps with a local path if specified
375        if let Some(openvmm_deps_path) = custom_openvmm_deps {
376            job = job.dep_on(move |_| {
377                flowey_lib_hvlite::_jobs::cfg_versions::Request::LocalOpenvmmDeps(
378                    effective_arch,
379                    ReadVar::from_static(openvmm_deps_path),
380                )
381            });
382        }
383
384        // Override protoc with a local path if specified
385        if let Some(protoc_path) = custom_protoc {
386            job = job.dep_on(move |_| {
387                flowey_lib_hvlite::_jobs::cfg_versions::Request::LocalProtoc(ReadVar::from_static(
388                    protoc_path,
389                ))
390            });
391        }
392
393        // Override kernel with local paths if both kernel and modules are specified
394        if let (Some(kernel_path), Some(modules_path)) =
395            (custom_kernel.clone(), custom_kernel_modules.clone())
396        {
397            job =
398                job.dep_on(
399                    move |_| flowey_lib_hvlite::_jobs::cfg_versions::Request::LocalKernel {
400                        arch: effective_arch,
401                        kernel: ReadVar::from_static(kernel_path),
402                        modules: ReadVar::from_static(modules_path),
403                    },
404                );
405        }
406
407        // Override UEFI with a local path if specified
408        if let Some(uefi_path) = custom_uefi {
409            job = job.dep_on(move |_| {
410                flowey_lib_hvlite::_jobs::cfg_versions::Request::LocalUefi(
411                    effective_arch,
412                    ReadVar::from_static(uefi_path),
413                )
414            });
415        }
416
417        job.dep_on(
418            |_| flowey_lib_hvlite::_jobs::cfg_hvlite_reposource::Params {
419                hvlite_repo_source: openvmm_repo,
420            },
421        )
422        .dep_on(|_| flowey_lib_hvlite::_jobs::cfg_common::Params {
423            local_only: Some(flowey_lib_hvlite::_jobs::cfg_common::LocalOnlyParams {
424                interactive: true,
425                auto_install: install_missing_deps,
426                ignore_rust_version: true,
427            }),
428            verbose: ReadVar::from_static(verbose),
429            locked,
430            deny_warnings: false,
431            no_incremental: false,
432        })
433        .dep_on(|ctx| flowey_lib_hvlite::_jobs::local_build_igvm::Params {
434            artifact_dir: ctx.publish_artifact(pub_out_dir),
435            done: ctx.new_done_handle(),
436
437            base_recipe: match recipe {
438                OpenhclRecipeCli::X64 => OpenhclIgvmRecipe::X64,
439                OpenhclRecipeCli::X64Devkern => OpenhclIgvmRecipe::X64Devkern,
440                OpenhclRecipeCli::X64TestLinuxDirect => OpenhclIgvmRecipe::X64TestLinuxDirect,
441                OpenhclRecipeCli::X64TestLinuxDirectDevkern => {
442                    OpenhclIgvmRecipe::X64TestLinuxDirectDevkern
443                }
444                OpenhclRecipeCli::X64Cvm => OpenhclIgvmRecipe::X64Cvm,
445                OpenhclRecipeCli::X64CvmDevkern => OpenhclIgvmRecipe::X64CvmDevkern,
446                OpenhclRecipeCli::Aarch64 => OpenhclIgvmRecipe::Aarch64,
447                OpenhclRecipeCli::Aarch64Devkern => OpenhclIgvmRecipe::Aarch64Devkern,
448            },
449            release,
450            release_cfg,
451
452            customizations: flowey_lib_hvlite::_jobs::local_build_igvm::Customizations {
453                build_label,
454                override_arch: override_arch.map(CommonArch::from),
455                with_perf_tools,
456                with_debuginfo,
457                with_mi_secure,
458                disable_secure_avic,
459                enable_product_policy,
460                confidential_debug,
461                override_kernel_pkg: override_kernel_pkg.map(|p| match p {
462                    KernelPackageKindCli::Main => OpenhclKernelPackage::Main,
463                    KernelPackageKindCli::Cvm => OpenhclKernelPackage::Cvm,
464                    KernelPackageKindCli::Dev => OpenhclKernelPackage::Dev,
465                    KernelPackageKindCli::CvmDev => OpenhclKernelPackage::CvmDev,
466                }),
467                with_sidecar,
468                custom_extra_rootfs,
469                override_openvmm_hcl_feature,
470                custom_sidecar,
471                override_manifest,
472                override_max_trace_level: max_trace_level.map(Into::into),
473                custom_openvmm_hcl,
474                custom_openhcl_boot,
475                custom_kernel,
476                custom_vtl0_kernel,
477                custom_layer,
478                custom_directory,
479            },
480        })
481        .finish();
482
483        Ok(pipeline)
484    }
485}