Skip to main content

flowey_lib_hvlite/_jobs/
check_openvmm_hcl_size.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Compares the size of the OpenHCL binary in the current PR with the size of the binary from the last successful merge to the PR's base branch.
5
6use crate::build_openhcl_igvm_from_recipe;
7use crate::build_openvmm_hcl;
8use crate::common::CommonArch;
9use crate::common::CommonTriple;
10use flowey::node::prelude::*;
11use flowey_lib_common::download_gh_artifact;
12use flowey_lib_common::gh_workflow_id;
13use flowey_lib_common::git_merge_commit;
14
15pub fn artifact_name_openhcl_baseline(arch: CommonArch) -> &'static str {
16    match arch {
17        CommonArch::X86_64 => "x64-openhcl-baseline",
18        CommonArch::Aarch64 => "aarch64-openhcl-baseline",
19    }
20}
21
22flowey_request! {
23    pub struct Request {
24        pub target: CommonTriple,
25        pub done: WriteVar<SideEffect>,
26        pub pipeline_name: String,
27        pub job_name: String,
28    }
29}
30
31new_simple_flow_node!(struct Node);
32
33impl SimpleFlowNode for Node {
34    type Request = Request;
35
36    fn imports(ctx: &mut ImportCtx<'_>) {
37        ctx.import::<crate::build_xtask::Node>();
38        ctx.import::<crate::git_checkout_openvmm_repo::Node>();
39        ctx.import::<download_gh_artifact::Node>();
40        ctx.import::<git_merge_commit::Node>();
41        ctx.import::<gh_workflow_id::Node>();
42        ctx.import::<build_openhcl_igvm_from_recipe::Node>();
43        ctx.import::<build_openvmm_hcl::Node>();
44        ctx.import::<crate::_jobs::build_and_publish_openvmm_hcl_baseline::Node>();
45    }
46
47    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
48        let Request {
49            target,
50            done,
51            pipeline_name,
52            job_name,
53        } = request;
54
55        let xtask_target = CommonTriple::Common {
56            arch: ctx.arch().try_into()?,
57            platform: ctx.platform().try_into()?,
58        };
59
60        let xtask = ctx.reqv(|v| crate::build_xtask::Request {
61            target: xtask_target,
62            xtask: v,
63        });
64        let openvmm_repo_path = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir);
65
66        let new_openvmm_hcl_baseline =
67            ctx.reqv(
68                |v| crate::_jobs::build_and_publish_openvmm_hcl_baseline::Request {
69                    target: target.clone(),
70                    baseline: v,
71                },
72            );
73
74        let file_name = artifact_name_openhcl_baseline(target.common_arch().unwrap());
75
76        // Compare against the PR's target branch, which is not always `main`
77        // (e.g. backports target a `release/*` branch).
78        let base_branch = if ctx.backend() == FlowBackend::Github {
79            ctx.get_gh_context_var().global().base_ref()
80        } else {
81            ReadVar::from_static("main".into())
82        };
83
84        let merge_commit = ctx.reqv(|v| git_merge_commit::Request {
85            repo_path: openvmm_repo_path.clone(),
86            merge_commit: v,
87            base_branch,
88        });
89
90        let merge_run = ctx.reqv(|v| gh_workflow_id::Request {
91            repo_owner: "microsoft".into(),
92            repo_name: "openvmm".into(),
93            commit_or_branch: gh_workflow_id::GitCommitOrBranch::Commit(merge_commit),
94            pipeline_name,
95            require_run_status: Some(gh_workflow_id::GhRunStatus::Completed),
96            require_successful_job_with_name: Some(job_name),
97            gh_workflow: v,
98        });
99
100        let run_id = merge_run.map(ctx, |r| r.id);
101        // TODO: this should return a `ReadVar<OpenvmmHclBaselineOutput>`
102        let merge_head_artifact = ctx.reqv(|old_openhcl| download_gh_artifact::Request {
103            repo_owner: "microsoft".into(),
104            repo_name: "openvmm".into(),
105            file_name: file_name.into(),
106            path: old_openhcl,
107            run_id,
108        });
109
110        // Publish the built binary as an artifact for offline analysis.
111        //
112        // FUTURE: Flowey should have a general mechanism for this. We cannot
113        // use the existing artifact support because all artifacts are only
114        // published at the end of the job, if everything else succeeds.
115        //
116        // The general mechanism should also support typed artifacts.
117        let publish_artifact = if ctx.backend() == FlowBackend::Github {
118            let dir = ctx.emit_rust_stepv("collect openvmm_hcl files for analysis", |ctx| {
119                let new_openvmm_hcl_baseline = new_openvmm_hcl_baseline.clone().claim(ctx);
120                move |rt| {
121                    let new_openvmm_hcl_baseline = rt.read(new_openvmm_hcl_baseline);
122                    let path = Path::new("artifact");
123                    fs_err::create_dir_all(path)?;
124                    fs_err::copy(new_openvmm_hcl_baseline.bin, path.join("openhcl"))?;
125                    Ok(std::path::absolute(path)?
126                        .into_os_string()
127                        .into_string()
128                        .ok()
129                        .unwrap())
130                }
131            });
132            Some(
133                // actions/upload-artifact v7.0.1
134                ctx.emit_gh_step(
135                    "publish openvmm_hcl for analysis",
136                    "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a",
137                )
138                .with("name", file_name)
139                .with("path", dir)
140                .finish(ctx),
141            )
142        } else {
143            None
144        };
145
146        let comparison = ctx.emit_rust_step("binary size comparison", |ctx| {
147            // Ensure the artifact is published before the analysis since this step may fail.
148            let _publish_artifact = publish_artifact.claim(ctx);
149            let xtask = xtask.claim(ctx);
150            let openvmm_repo_path = openvmm_repo_path.claim(ctx);
151            let old_openhcl = merge_head_artifact.claim(ctx);
152            let new_openhcl = new_openvmm_hcl_baseline.claim(ctx);
153            let merge_run = merge_run.claim(ctx);
154
155            move |rt| {
156                let xtask = match rt.read(xtask) {
157                    crate::build_xtask::XtaskOutput::LinuxBin { bin, .. } => bin,
158                    crate::build_xtask::XtaskOutput::WindowsBin { exe, .. } => exe,
159                };
160
161                let old_openhcl = rt.read(old_openhcl);
162                let new_openhcl = rt.read(new_openhcl);
163                let merge_run = rt.read(merge_run);
164
165                // The contents of the artifact should match `OpenvmmHclBaselineOutput`
166                let old_path = old_openhcl.join("openhcl");
167                let new_path = new_openhcl.bin;
168
169                println!(
170                    "comparing HEAD to merge commit {} and workflow {}",
171                    merge_run.commit, merge_run.id
172                );
173
174                let path = rt.read(openvmm_repo_path);
175                rt.sh.change_dir(path);
176                flowey::shell_cmd!(
177                    rt,
178                    "{xtask} verify-size --original {old_path} --new {new_path}"
179                )
180                .run()?;
181
182                Ok(())
183            }
184        });
185
186        ctx.emit_side_effect_step(vec![comparison], [done]);
187
188        Ok(())
189    }
190}