flowey_hvlite/pipelines/
build_igvm.rs

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