Skip to main content

flowey_lib_hvlite/
artifact_to_build_mapping.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Mapping from petri artifact IDs to flowey build/download selections.
5//!
6//! This module defines a lookup table that maps the string representation of
7//! petri `ArtifactHandle` IDs (as output by `--list-required-artifacts`) to
8//! their corresponding build selections and download artifacts.
9//!
10//! FUTURE: This is currently a manual lookup table that requires fixups each
11//! time a new artifact is added, but this requires a rearchitecture of petri's
12//! artifact system and type erasure. Live with this for now since this is only
13//! used in the local vmm-test-run workflow, and is straightforward to fix.
14
15use crate::_jobs::local_build_and_run_nextest_vmm_tests::BuildSelections;
16use std::collections::BTreeSet;
17use vmm_test_images::KnownTestArtifacts;
18
19/// Result of resolving artifact requirements to build/download selections.
20#[derive(Debug)]
21pub struct ResolvedArtifactSelections {
22    /// What to build
23    pub build: BuildSelections,
24    /// What to download
25    pub downloads: BTreeSet<KnownTestArtifacts>,
26    /// Any unknown artifacts that couldn't be mapped
27    pub unknown: Vec<String>,
28    /// Target triple from the artifacts file (if present)
29    pub target_from_file: Option<String>,
30    /// Whether any tests need release IGVM files from GitHub
31    pub needs_release_igvm: bool,
32}
33
34impl Default for ResolvedArtifactSelections {
35    fn default() -> Self {
36        Self {
37            build: BuildSelections::none(),
38            downloads: BTreeSet::new(),
39            unknown: Vec::new(),
40            target_from_file: None,
41            needs_release_igvm: false,
42        }
43    }
44}
45
46impl ResolvedArtifactSelections {
47    /// Parse the JSON output from `--list-required-artifacts` and resolve to
48    /// build/download selections.
49    ///
50    /// The `target_arch` and `target_os` parameters specify the target to
51    /// validate against. If the JSON contains a `target` field, it will be
52    /// checked to ensure it matches.
53    pub fn from_artifact_list_json(
54        json: &str,
55        target_arch: target_lexicon::Architecture,
56        target_os: target_lexicon::OperatingSystem,
57    ) -> anyhow::Result<Self> {
58        let parsed: ArtifactListOutput = serde_json::from_str(json)?;
59
60        // Validate target if present in the JSON
61        if let Some(ref file_target) = parsed.target {
62            let expected_target = format!(
63                "{}-{}",
64                match target_arch {
65                    target_lexicon::Architecture::X86_64 => "x86_64",
66                    target_lexicon::Architecture::Aarch64(_) => "aarch64",
67                    _ => "unknown",
68                },
69                match target_os {
70                    target_lexicon::OperatingSystem::Windows => "pc-windows-msvc",
71                    target_lexicon::OperatingSystem::Linux => "unknown-linux-gnu",
72                    _ => "unknown",
73                }
74            );
75
76            // Check if the target in the file is compatible with what we're building for
77            if !file_target.contains(expected_target.split('-').next().unwrap_or(""))
78                || (target_os == target_lexicon::OperatingSystem::Windows
79                    && !file_target.contains("windows"))
80                || (target_os == target_lexicon::OperatingSystem::Linux
81                    && !file_target.contains("linux"))
82            {
83                anyhow::bail!(
84                    "Target mismatch: artifacts file was generated for '{}', but building for '{}'",
85                    file_target,
86                    expected_target
87                );
88            }
89        }
90
91        let mut result = Self {
92            target_from_file: parsed.target,
93            ..Default::default()
94        };
95
96        // Process both required and optional artifacts
97        for artifact in parsed.required.iter().chain(parsed.optional.iter()) {
98            if !result.resolve_artifact(artifact, target_arch, target_os) {
99                result.unknown.push(artifact.clone());
100            }
101        }
102
103        Ok(result)
104    }
105
106    /// Resolve a single artifact ID and update selections. Returns true if the
107    /// artifact was recognized.
108    fn resolve_artifact(
109        &mut self,
110        artifact_id: &str,
111        target_arch: target_lexicon::Architecture,
112        target_os: target_lexicon::OperatingSystem,
113    ) -> bool {
114        // Artifact IDs are in the format:
115        // "petri_artifacts_vmm_test::artifacts::ARTIFACT_NAME"
116        // or nested like:
117        // "petri_artifacts_vmm_test::artifacts::test_vhd::ARTIFACT_NAME"
118
119        let is_windows = matches!(target_os, target_lexicon::OperatingSystem::Windows);
120        let is_x64 = matches!(target_arch, target_lexicon::Architecture::X86_64);
121
122        match artifact_id {
123            // OpenVMM binary
124            "petri_artifacts_vmm_test::artifacts::OPENVMM_WIN_X64"
125            | "petri_artifacts_vmm_test::artifacts::OPENVMM_LINUX_X64"
126            | "petri_artifacts_vmm_test::artifacts::OPENVMM_WIN_AARCH64"
127            | "petri_artifacts_vmm_test::artifacts::OPENVMM_LINUX_AARCH64"
128            | "petri_artifacts_vmm_test::artifacts::OPENVMM_MACOS_AARCH64" => {
129                self.build.openvmm = true;
130                true
131            }
132
133            // OpenVMM vhost binary (Linux only)
134            "petri_artifacts_vmm_test::artifacts::OPENVMM_VHOST_LINUX_X64"
135            | "petri_artifacts_vmm_test::artifacts::OPENVMM_VHOST_LINUX_AARCH64" => {
136                self.build.openvmm_vhost = true;
137                true
138            }
139
140            // OpenHCL IGVM files
141            "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_X64"
142            | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_DEV_KERNEL_X64"
143            | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_CVM_X64"
144            | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_LINUX_DIRECT_TEST_X64"
145            | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_AARCH64"
146            | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_DEV_KERNEL_AARCH64" =>
147            {
148                self.build.openhcl = true;
149                true
150            }
151
152            // Release IGVM files (downloaded, not built)
153            "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_RELEASE_STANDARD_X64"
154            | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_RELEASE_LINUX_DIRECT_X64"
155            | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_RELEASE_STANDARD_AARCH64" =>
156            {
157                // These are downloaded from GitHub releases, not built
158                self.needs_release_igvm = true;
159                true
160            }
161
162            // Guest test UEFI
163            "petri_artifacts_vmm_test::artifacts::test_vhd::GUEST_TEST_UEFI_X64"
164            | "petri_artifacts_vmm_test::artifacts::test_vhd::GUEST_TEST_UEFI_AARCH64" => {
165                self.build.guest_test_uefi = true;
166                true
167            }
168
169            // TMKs
170            "petri_artifacts_vmm_test::artifacts::tmks::SIMPLE_TMK_X64"
171            | "petri_artifacts_vmm_test::artifacts::tmks::SIMPLE_TMK_AARCH64" => {
172                self.build.tmks = true;
173                true
174            }
175
176            // TMK VMM
177            "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_WIN_X64"
178            | "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_WIN_AARCH64" => {
179                self.build.tmk_vmm_windows = true;
180                true
181            }
182            "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_X64"
183            | "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_AARCH64"
184            | "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_X64_MUSL"
185            | "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_AARCH64_MUSL"
186            | "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_MACOS_AARCH64" => {
187                self.build.tmk_vmm_linux = true;
188                true
189            }
190
191            // VMGSTool
192            "petri_artifacts_vmm_test::artifacts::VMGSTOOL_WIN_X64"
193            | "petri_artifacts_vmm_test::artifacts::VMGSTOOL_WIN_AARCH64" => {
194                self.build.vmgstool = true;
195                true
196            }
197            "petri_artifacts_vmm_test::artifacts::VMGSTOOL_LINUX_X64"
198            | "petri_artifacts_vmm_test::artifacts::VMGSTOOL_LINUX_AARCH64"
199            | "petri_artifacts_vmm_test::artifacts::VMGSTOOL_MACOS_AARCH64" => {
200                self.build.vmgstool = true;
201                true
202            }
203
204            // TPM guest tests
205            "petri_artifacts_vmm_test::artifacts::guest_tools::TPM_GUEST_TESTS_WINDOWS_X64" => {
206                self.build.tpm_guest_tests_windows = true;
207                true
208            }
209            "petri_artifacts_vmm_test::artifacts::guest_tools::TPM_GUEST_TESTS_LINUX_X64" => {
210                self.build.tpm_guest_tests_linux = true;
211                true
212            }
213
214            // Host tools
215            "petri_artifacts_vmm_test::artifacts::host_tools::TEST_IGVM_AGENT_RPC_SERVER_WINDOWS_X64" =>
216            {
217                self.build.test_igvm_agent_rpc_server = true;
218                true
219            }
220
221            // Loadable firmware artifacts (these come from deps, not built)
222            "petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_KERNEL_X64"
223            | "petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_INITRD_X64"
224            | "petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_KERNEL_AARCH64"
225            | "petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_INITRD_AARCH64"
226            | "petri_artifacts_vmm_test::artifacts::loadable::PCAT_FIRMWARE_X64"
227            | "petri_artifacts_vmm_test::artifacts::loadable::SVGA_FIRMWARE_X64"
228            | "petri_artifacts_vmm_test::artifacts::loadable::UEFI_FIRMWARE_X64"
229            | "petri_artifacts_vmm_test::artifacts::loadable::UEFI_FIRMWARE_AARCH64" => {
230                // These are resolved from OpenVMM deps, always available
231                true
232            }
233
234            // Test VHDs (downloaded)
235            "petri_artifacts_vmm_test::artifacts::test_vhd::GEN1_WINDOWS_DATA_CENTER_CORE2022_X64" =>
236            {
237                self.downloads
238                    .insert(KnownTestArtifacts::Gen1WindowsDataCenterCore2022X64Vhd);
239                true
240            }
241            "petri_artifacts_vmm_test::artifacts::test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2022_X64" =>
242            {
243                self.downloads
244                    .insert(KnownTestArtifacts::Gen2WindowsDataCenterCore2022X64Vhd);
245                true
246            }
247            "petri_artifacts_vmm_test::artifacts::test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64" =>
248            {
249                self.downloads
250                    .insert(KnownTestArtifacts::Gen2WindowsDataCenterCore2025X64Vhd);
251                // Requires prep_steps for CVM tests
252                self.build.prep_steps = is_windows && is_x64;
253                true
254            }
255            "petri_artifacts_vmm_test::artifacts::test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64_PREPPED" =>
256            {
257                // This is created by prep_steps, not downloaded
258                self.build.prep_steps = is_windows && is_x64;
259                true
260            }
261            "petri_artifacts_vmm_test::artifacts::test_vhd::FREE_BSD_13_2_X64" => {
262                self.downloads.insert(KnownTestArtifacts::FreeBsd13_2X64Vhd);
263                true
264            }
265            "petri_artifacts_vmm_test::artifacts::test_vhd::ALPINE_3_23_X64" => {
266                self.downloads.insert(KnownTestArtifacts::Alpine323X64Vhd);
267                true
268            }
269            "petri_artifacts_vmm_test::artifacts::test_vhd::ALPINE_3_23_AARCH64" => {
270                self.downloads
271                    .insert(KnownTestArtifacts::Alpine323Aarch64Vhd);
272                true
273            }
274            "petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2404_SERVER_X64" => {
275                self.downloads
276                    .insert(KnownTestArtifacts::Ubuntu2404ServerX64Vhd);
277                true
278            }
279            "petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2504_SERVER_X64" => {
280                self.downloads
281                    .insert(KnownTestArtifacts::Ubuntu2504ServerX64Vhd);
282                true
283            }
284            "petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2404_SERVER_AARCH64" => {
285                self.downloads
286                    .insert(KnownTestArtifacts::Ubuntu2404ServerAarch64Vhd);
287                true
288            }
289            "petri_artifacts_vmm_test::artifacts::test_vhd::WINDOWS_11_ENTERPRISE_AARCH64" => {
290                self.downloads
291                    .insert(KnownTestArtifacts::Windows11EnterpriseAarch64Vhdx);
292                true
293            }
294
295            // Test ISOs (downloaded)
296            "petri_artifacts_vmm_test::artifacts::test_iso::FREE_BSD_13_2_X64" => {
297                self.downloads.insert(KnownTestArtifacts::FreeBsd13_2X64Iso);
298                true
299            }
300
301            // Test VMGS files (downloaded)
302            "petri_artifacts_vmm_test::artifacts::test_vmgs::VMGS_WITH_BOOT_ENTRY" => {
303                self.downloads.insert(KnownTestArtifacts::VmgsWithBootEntry);
304                true
305            }
306
307            // OpenHCL usermode binaries (built as part of IGVM)
308            "petri_artifacts_vmm_test::artifacts::openhcl_igvm::um_bin::LATEST_LINUX_DIRECT_TEST_X64"
309            | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::um_dbg::LATEST_LINUX_DIRECT_TEST_X64" =>
310            {
311                self.build.openhcl = true;
312                true
313            }
314
315            // Common artifacts (always available, no build needed)
316            "petri_artifacts_common::artifacts::TEST_LOG_DIRECTORY" => true,
317
318            // Pipette binaries (from petri_artifacts_common)
319            "petri_artifacts_common::artifacts::PIPETTE_LINUX_X64"
320            | "petri_artifacts_common::artifacts::PIPETTE_LINUX_AARCH64" => {
321                self.build.pipette_linux = true;
322                true
323            }
324            "petri_artifacts_common::artifacts::PIPETTE_WINDOWS_X64"
325            | "petri_artifacts_common::artifacts::PIPETTE_WINDOWS_AARCH64" => {
326                self.build.pipette_windows = true;
327                true
328            }
329
330            "petri_artifacts_vmm_test::artifacts::test_vmgs::VMGS_WITH_16K_TPM" => {
331                self.downloads.insert(KnownTestArtifacts::VmgsWith16kTpm);
332                true
333            }
334
335            _ => {
336                log::warn!("unknown artifact ID with no build mapping: {artifact_id}");
337                false
338            }
339        }
340    }
341}
342
343/// JSON structure matching the output of `--list-required-artifacts`
344#[derive(serde::Deserialize)]
345struct ArtifactListOutput {
346    /// Target triple the artifacts were discovered for (if present)
347    #[serde(default)]
348    target: Option<String>,
349    required: Vec<String>,
350    optional: Vec<String>,
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    #[test]
358    fn test_resolve_openvmm() {
359        let json = r#"{"required":["petri_artifacts_vmm_test::artifacts::OPENVMM_WIN_X64"],"optional":[]}"#;
360        let result = ResolvedArtifactSelections::from_artifact_list_json(
361            json,
362            target_lexicon::Architecture::X86_64,
363            target_lexicon::OperatingSystem::Windows,
364        )
365        .unwrap();
366
367        assert!(result.build.openvmm);
368        assert!(!result.build.openhcl);
369        assert!(result.downloads.is_empty());
370        assert!(result.unknown.is_empty());
371    }
372
373    #[test]
374    fn test_resolve_with_downloads() {
375        let json = r#"{"required":["petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2404_SERVER_X64","petri_artifacts_common::artifacts::PIPETTE_LINUX_X64"],"optional":[]}"#;
376        let result = ResolvedArtifactSelections::from_artifact_list_json(
377            json,
378            target_lexicon::Architecture::X86_64,
379            target_lexicon::OperatingSystem::Linux,
380        )
381        .unwrap();
382
383        assert!(result.build.pipette_linux);
384        assert!(
385            result
386                .downloads
387                .contains(&KnownTestArtifacts::Ubuntu2404ServerX64Vhd)
388        );
389    }
390
391    #[test]
392    fn test_unknown_artifact() {
393        let json = r#"{"required":["some::unknown::artifact"],"optional":[]}"#;
394        let result = ResolvedArtifactSelections::from_artifact_list_json(
395            json,
396            target_lexicon::Architecture::X86_64,
397            target_lexicon::OperatingSystem::Linux,
398        )
399        .unwrap();
400
401        assert_eq!(result.unknown, vec!["some::unknown::artifact"]);
402    }
403}