Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: add job to verify binary size #475

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d5e1b23
first pass at adding job to verify gh binary size
justus-camp-microsoft Dec 12, 2024
3884a0f
Merge branch 'main' into verify_size
justus-camp-microsoft Dec 30, 2024
99e745b
comparison of binaries
justus-camp-microsoft Jan 2, 2025
207280e
try comparing two of what should be the same binary
justus-camp-microsoft Jan 2, 2025
40aa921
add done handle, run regen
justus-camp-microsoft Jan 2, 2025
f7ffdcb
fmt so that ci will actually run
justus-camp-microsoft Jan 2, 2025
9853a16
clippy
justus-camp-microsoft Jan 2, 2025
e9854fa
fix command line argument
justus-camp-microsoft Jan 2, 2025
12c39aa
swap old and new position
justus-camp-microsoft Jan 3, 2025
7fe1ab7
try to download hardcoded artifact
justus-camp-microsoft Jan 3, 2025
17ac9d5
remove wrong parameter, clippy
justus-camp-microsoft Jan 3, 2025
9b47097
get merge head to run size check with
justus-camp-microsoft Jan 6, 2025
e9108a5
output error
justus-camp-microsoft Jan 6, 2025
51cb2a3
get stdout and stderr to debug ci issue
justus-camp-microsoft Jan 6, 2025
bc7963e
try getting stderr again
justus-camp-microsoft Jan 6, 2025
a0f9c0d
another attempt at getting git merge-base output
justus-camp-microsoft Jan 6, 2025
d7c38a5
fix build and format
justus-camp-microsoft Jan 6, 2025
77167e0
print git status
justus-camp-microsoft Jan 6, 2025
40363c4
get head ref from github context variable
justus-camp-microsoft Jan 6, 2025
4f01015
write merge commit to variable
justus-camp-microsoft Jan 6, 2025
e17187d
still need to be in checked out repo
justus-camp-microsoft Jan 6, 2025
ce02bdc
change gh context variable
justus-camp-microsoft Jan 6, 2025
1538c7c
build
justus-camp-microsoft Jan 6, 2025
b9c3536
try fetch depth of 0
justus-camp-microsoft Jan 7, 2025
ecb554a
try getting fetch depth 0 again
justus-camp-microsoft Jan 7, 2025
b4811dc
Merge branch 'main' into verify_size
justus-camp-microsoft Jan 7, 2025
e4df6d7
roundabout way of fetching pr branch hopefully
justus-camp-microsoft Jan 7, 2025
b6bed7a
hack in the gh token
justus-camp-microsoft Jan 7, 2025
eea8ab6
run shell from right directory
justus-camp-microsoft Jan 7, 2025
c6881b3
print file tree to debug
justus-camp-microsoft Jan 7, 2025
584f248
try to fix binary path
justus-camp-microsoft Jan 7, 2025
8a01cfb
actually fix path
justus-camp-microsoft Jan 7, 2025
4ac364d
fmt
justus-camp-microsoft Jan 7, 2025
2d1bf6e
another path change...
justus-camp-microsoft Jan 7, 2025
46968a8
try to run a release build as part of PR pipeline
justus-camp-microsoft Jan 7, 2025
3d00b0d
loop through commits
justus-camp-microsoft Jan 7, 2025
0711d49
use gh cli to comment on PR
justus-camp-microsoft Jan 7, 2025
2fc057a
try adding pr write permissions
justus-camp-microsoft Jan 8, 2025
1a6c341
Revert "try adding pr write permissions"
justus-camp-microsoft Jan 8, 2025
2acb5be
Revert "use gh cli to comment on PR"
justus-camp-microsoft Jan 8, 2025
b1f5fa6
more rigorous size diff
justus-camp-microsoft Jan 8, 2025
5a537f2
clippy
justus-camp-microsoft Jan 8, 2025
91e390a
Merge branch 'main' into verify_size
justus-camp-microsoft Jan 8, 2025
ad99ffc
misc fixes
justus-camp-microsoft Jan 8, 2025
ffbcee7
fetch main?
justus-camp-microsoft Jan 8, 2025
ef5977c
Merge branch 'main' into verify_size
justus-camp-microsoft Jan 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,086 changes: 1,929 additions & 1,157 deletions .github/workflows/openvmm-pr.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9089,6 +9089,7 @@ dependencies = [
"ignore",
"log",
"mbrman",
"object",
"rayon",
"serde",
"serde_json",
Expand Down
8 changes: 8 additions & 0 deletions flowey/flowey_core/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1793,6 +1793,14 @@ pub mod steps {

/// `github.token`
pub const GITHUB__TOKEN: GhContextVar = GhContextVar::new_secret("github.token");

/// `github.event.pull_request.head.ref`
pub const GITHUB__HEAD_REF: GhContextVar =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to be mindful about how we choose to expose this particular class of context variables in flowey.

All other constants defined in this list are guaranteed to be valid in any pipeline run. The same cannot be said of these new github.event.pull_request constants, which should only be used in PR-triggered workflows. i.e: it seems unwise to make it "trivial" to access these variables in the context of a CI-triggered workflow via the existing get_gh_context_var API, given that the resulting loosely-typed String could sometimes be empty.

My gut feeling is that we want to have some API that would let us model these sorts of context-dependent variables in a type-safe manner, in order to give users a way to get a ReadVar<Option<PullRequestRelatedThing>>.

Consider the following modification to the existing get_gh_context_var API:

impl NodeCtx {
    fn get_gh_context_var(&mut self) -> GhContextVarReader;
}

impl GhContextVarReader {
    fn global(&mut self, GhContextVar) -> ReadVar<String>;
    fn event(&mut self) -> GhContextVarReaderEvent;
}

impl GhContextVarReaderEvent {
    fn pull_request(&mut self) -> GhContextVarReaderEventPullRequest;
}

// and so on...
//
// thereby enabling:

let global: ReadVar<String> = ctx.get_gh_context_var().global(GhContextVar::RUNNER__TEMP);
let pr_specific: ReadVar<Option<String>> = ctx.get_gh_context_var().event().pull_request().head().ref();

// theoretically, with this scheme, we could switch all existing `const GhContextVar` enums to 
// just hang off a `global()` object, e.g: `global().runner().temp()`

The resulting API is very fluent for end users, and to make our lives easier as implementors, we can leverage the type-state pattern to avoid an explosion of different GhContextVarReader types, and instead, simply transition between various versions of a single backing GhContextVarReader<T> type.

With this API, the leaf-nodes (e.g: pull_request().head().ref()) can then encode any necessary flowey logic to read the raw String data, and then convert it into a Option<String> if need be.


I know this is all somewhat orthogonal to the problem this PR is specifically trying to solve... but we must not forget that flowey is not a framework set in stone. It still has many rough edges and core abstractions that need to be reconsidered / reworked. This sort of flowey rework can and should be done as part of whatever feature work we are doing.

In this case, I don't actually think there's too much work to re-jig the API as I suggest here - there shouldn't be any "backend" flowey work, and it'd simply be some clever refactoring of the user-facing NodeContext APIs.

That said - I would suggest you split this work out into a separate PR, and then rebase this PR on-top of that one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

theoretically, it might even make sense to have a single pull_request() -> ReadVar<Option<GhEventPullRequest>> method, which dumps the entire github.events.pull_request variable as JSON, and then flowey uses a serde defn of the corresponding object to avoid needing to manually write out methods for each field. See https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request

Its a "big" object, but its not a huge object, so I think that might be a viable approach - even if the pipeline only ends up using one or two fields of the object that gets parsed in.

And if its hard to transcribe that particular object structure in its entirety into a serde type, we can always go piecemeal (given that serde will just ignore JSON fields during deserialization that it wasn't explicitly told about)

GhContextVar::new("github.event.pull_request.head.ref");

/// `github.event.pull_request.number`
pub const GITHUB__PR_NUMBER: GhContextVar =
GhContextVar::new("github.event.pull_request.number");
}

impl GhContextVar {
Expand Down
74 changes: 68 additions & 6 deletions flowey/flowey_hvlite/src/pipelines/checkin_gates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,9 +629,9 @@ impl IntoPipeline for CheckinGatesCli {
OpenvmmHclBuildProfile::Debug
};

let (pub_openhcl_igvm, use_openhcl_igvm) =
let (pub_openhcl_igvm_dev, use_openhcl_igvm_dev) =
pipeline.new_artifact(format!("{arch_tag}-openhcl-igvm"));
let (pub_openhcl_igvm_extras, _use_openhcl_igvm_extras) =
let (pub_openhcl_igvm_extras_dev, _use_openhcl_igvm_extras_dev) =
pipeline.new_artifact(format!("{arch_tag}-openhcl-igvm-extras"));
// also build pipette musl on this job, as until we land the
// refactor that allows building musl without the full openhcl
Expand All @@ -644,15 +644,15 @@ impl IntoPipeline for CheckinGatesCli {
match arch {
CommonArch::X86_64 => {
vmm_tests_artifacts_windows_x86.use_openhcl_igvm_files =
Some(use_openhcl_igvm.clone());
Some(use_openhcl_igvm_dev.clone());
vmm_tests_artifacts_windows_x86.use_pipette_linux_musl =
Some(use_pipette_linux_musl.clone());
vmm_tests_artifacts_linux_x86.use_pipette_linux_musl =
Some(use_pipette_linux_musl.clone());
}
CommonArch::Aarch64 => {
vmm_tests_artifacts_windows_aarch64.use_openhcl_igvm_files =
Some(use_openhcl_igvm.clone());
Some(use_openhcl_igvm_dev.clone());
vmm_tests_artifacts_windows_aarch64.use_pipette_linux_musl =
Some(use_pipette_linux_musl.clone());
}
Expand Down Expand Up @@ -685,6 +685,7 @@ impl IntoPipeline for CheckinGatesCli {
.dep_on(|ctx| {
flowey_lib_hvlite::_jobs::build_and_publish_openhcl_igvm_from_recipe::Params {
igvm_files: igvm_recipes
.clone()
.into_iter()
.map(|recipe| OpenhclIgvmBuildParams {
profile: openvmm_hcl_profile,
Expand All @@ -694,9 +695,9 @@ impl IntoPipeline for CheckinGatesCli {
))),
})
.collect(),
artifact_dir_openhcl_igvm: ctx.publish_artifact(pub_openhcl_igvm),
artifact_dir_openhcl_igvm: ctx.publish_artifact(pub_openhcl_igvm_dev),
artifact_dir_openhcl_igvm_extras: ctx
.publish_artifact(pub_openhcl_igvm_extras),
.publish_artifact(pub_openhcl_igvm_extras_dev),
done: ctx.new_done_handle(),
}
})
Expand All @@ -713,6 +714,67 @@ impl IntoPipeline for CheckinGatesCli {
);

all_jobs.push(job.finish());

if arch == CommonArch::X86_64 && !release {
let (pub_openhcl_igvm_release, _use_openhcl_igvm_release) =
pipeline.new_artifact(format!("{arch_tag}-openhcl-igvm-release"));
let (pub_openhcl_igvm_extras_release, use_openhcl_igvm_extras_release) =
pipeline.new_artifact(format!("{arch_tag}-openhcl-igvm-extras-release"));

// Do an underhill-ship build for binary size comparison
let job = pipeline
.new_job(
FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu),
FlowArch::X86_64,
format!("build openhcl [{arch_tag}-linux] release"),
)
.gh_set_pool(crate::pipelines_shared::gh_pools::default_x86_pool(
FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu),
))
.dep_on(|ctx| {
flowey_lib_hvlite::_jobs::build_and_publish_openhcl_igvm_from_recipe::Params {
igvm_files: igvm_recipes
.into_iter()
.map(|recipe| OpenhclIgvmBuildParams {
profile: OpenvmmHclBuildProfile::OpenvmmHclShip,
recipe,
custom_target: Some(CommonTriple::Custom(openhcl_musl_target(
arch,
))),
})
.collect(),
artifact_dir_openhcl_igvm: ctx.publish_artifact(pub_openhcl_igvm_release),
artifact_dir_openhcl_igvm_extras: ctx
.publish_artifact(pub_openhcl_igvm_extras_release),
done: ctx.new_done_handle(),
}
});

all_jobs.push(job.finish());

// emit openvmm verify-size job
let job = pipeline
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use a single job?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed.

If you actually look inside _jobs::build_and_publish_openhcl_igvm_from_recipe, you'll see that its really just a wrapper around the core build_openhcl_igvm_from_recipe node + some wiring to build multiple IGVM files simultaneously, and then publish various artifacts.

If you just peel back a layer, and have your new _jobs::check_openvmm_hcl_size node interface with that build_openhcl_igvm_from_recipe Node directly, you can sidestep all this multi-job coordination, and just use the IGVM file you built in that job.

Plus - you wouldn't be shackled to the existing openhcl-igvm + openhcl-igvm-extras artifact structure, and could instead have a new, verify-size specific openhcl-igvm-verify-size-baseline artifact that you can then use across jobs (and which would only contain the precise artifacts the verify-size infrastructure cares about)

.new_job(
FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu),
FlowArch::X86_64,
format!("verify openhcl binary size [{}]", arch_tag),
)
.gh_set_pool(crate::pipelines_shared::gh_pools::default_x86_pool(
FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu),
))
.dep_on(
|ctx| flowey_lib_hvlite::_jobs::check_openvmm_hcl_size::Request {
target: CommonTriple::Common {
arch,
platform: CommonPlatform::LinuxMusl,
},
new_openhcl: ctx.use_artifact(&use_openhcl_igvm_extras_release),
done: ctx.new_done_handle(),
},
)
.finish();
all_jobs.push(job);
}
}

// Emit clippy + unit-test jobs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

#### Flowey Build Dependencies

# On Linux, install gcc and rust to build flowey.
# On Linux, install gcc and rust to build flowey.
# The apt-get retries below avoid failures in CI that can be
# intermittently caused by other processes temporarily holding
# the necessary dpkg or apt locks.
Expand Down
75 changes: 75 additions & 0 deletions flowey/flowey_lib_common/src/download_gh_artifact.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Download a github release artifact

use flowey::node::prelude::*;

flowey_request! {
pub struct Request {
/// First component of a github repo path
///
/// e.g: the "foo" in "github.com/foo/bar"
pub repo_owner: String,
/// Second component of a github repo path
///
/// e.g: the "bar" in "github.com/foo/bar"
pub repo_name: String,
/// Specific artifact to download.
pub file_name: String,
/// Path to downloaded artifact.
pub path: WriteVar<PathBuf>,
/// The Github actions run id to download artifacts from
pub run_id: ReadVar<String>
}
}

new_simple_flow_node!(struct Node);

impl SimpleFlowNode for Node {
type Request = Request;

fn imports(ctx: &mut ImportCtx<'_>) {
ctx.import::<crate::cache::Node>();
ctx.import::<crate::use_gh_cli::Node>();
}

fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
let Request {
repo_owner,
repo_name,
file_name,
path,
run_id,
} = request;

let gh_token = ctx.get_gh_context_var(GhContextVar::GITHUB__TOKEN);
ctx.req(crate::use_gh_cli::Request::WithAuth(
crate::use_gh_cli::GhCliAuth::AuthToken(gh_token),
));
let gh_cli = ctx.reqv(crate::use_gh_cli::Request::Get);

ctx.emit_rust_step("download artifacts from github actions run", |ctx| {
let gh_cli = gh_cli.claim(ctx);
let run_id = run_id.claim(ctx);
let path = path.claim(ctx);
move |rt| {
let sh = xshell::Shell::new()?;
let gh_cli = rt.read(gh_cli);
let run_id = rt.read(run_id);

let path_end = format!("{repo_owner}/{repo_name}/{run_id}");
let out_dir = std::env::current_dir()?.absolute()?.join(path_end);
fs_err::create_dir_all(&out_dir)?;
sh.change_dir(&out_dir);

xshell::cmd!(sh, "{gh_cli} run download {run_id} -R {repo_owner}/{repo_name} --pattern {file_name}").run()?;

rt.write(path, &out_dir);
Ok(())
}
});

Ok(())
}
}
55 changes: 55 additions & 0 deletions flowey/flowey_lib_common/src/gh_merge_commit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't forget your module docs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(here, and elsewhere)

use flowey::node::prelude::*;

flowey_request! {
pub struct Request {
pub repo_path: ReadVar<PathBuf>,
pub merge_commit: WriteVar<String>,
}
}

new_simple_flow_node!(struct Node);

impl SimpleFlowNode for Node {
type Request = Request;

fn imports(_ctx: &mut ImportCtx<'_>) {}

fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
let Request {
repo_path,
merge_commit,
} = request;

let head_ref = ctx.get_gh_context_var(GhContextVar::GITHUB__HEAD_REF);
let pr_number = ctx.get_gh_context_var(GhContextVar::GITHUB__PR_NUMBER);

ctx.emit_rust_step("get merge commit", |ctx| {
let merge_commit = merge_commit.claim(ctx);
let head_ref = head_ref.claim(ctx);
let repo_path = repo_path.claim(ctx);
let pr_number = pr_number.claim(ctx);

|rt| {
let sh = xshell::Shell::new()?;
let repo_path = rt.read(repo_path);
let head_ref = rt.read(head_ref);
let pr_number = rt.read(pr_number);

sh.change_dir(repo_path);

// TODO: Make this work for non-main PRs
xshell::cmd!(sh, "git fetch origin main").run()?;
xshell::cmd!(sh, "git fetch origin pull/{pr_number}/head:{head_ref}").run()?;
let commit = xshell::cmd!(sh, "git merge-base {head_ref} origin/main").read()?;
rt.write(merge_commit, &commit);

Ok(())
}
});

Ok(())
}
}
82 changes: 82 additions & 0 deletions flowey/flowey_lib_common/src/gh_workflow_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use flowey::node::prelude::*;

flowey_request! {
pub struct Request {
pub github_commit_hash: ReadVar<String>,
pub repo_path: ReadVar<PathBuf>,
pub gh_workflow_id: WriteVar<String>,
}
}

new_simple_flow_node!(struct Node);

impl SimpleFlowNode for Node {
type Request = Request;

fn imports(_ctx: &mut ImportCtx<'_>) {}

fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
let Request {
repo_path,
github_commit_hash,
gh_workflow_id,
} = request;

let gh_token = ctx.get_gh_context_var(GhContextVar::GITHUB__TOKEN);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, maybe not strictly required for this PR, but do we need some way API like get_gh_token_with_permissions(impl Iterator<Item = GhPermission>) -> ReadVar<String>? i.e: in case you need the pipeline to have a particular github permission, outside the context of emit_gh_step?


ctx.emit_rust_step("get action id", |ctx| {
let gh_workflow_id = gh_workflow_id.claim(ctx);
let github_commit_hash = github_commit_hash.claim(ctx);
let gh_token = gh_token.claim(ctx);
let repo_path = repo_path.claim(ctx);

|rt| {
let github_commit_hash = rt.read(github_commit_hash);
let sh = xshell::Shell::new()?;
let gh_token = rt.read(gh_token);
let repo_path = rt.read(repo_path);

sh.change_dir(repo_path);
// Fetches the CI build workflow id for a given commit hash
let get_action_id = |commit: String| {
xshell::cmd!(
sh,
"gh run list --commit {commit} -w '[flowey] OpenVMM CI' -s 'completed' -L 1 --json databaseId --jq '.[].databaseId'"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we def don't want to be hard-coding openvmm specific pipeline names within a flowey_lib_common node. this should be something that gets passed in as a runtime / comptime var.

)
.env("GITHUB_TOKEN", gh_token.clone())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this actually necessary? I would expect GitHub Actions to already have this set ambiently?

.read()
};

let mut github_commit_hash = github_commit_hash.clone();
let mut action_id = get_action_id(github_commit_hash.clone());
let mut loop_count = 0;

// CI may not have finished the build for the merge base, so loop through commits
// until we find a finished build or fail after 5 attempts
while let Err(ref e) = action_id {
if loop_count > 4 {
anyhow::bail!("Failed to get action id after 5 attempts: {}", e);
}

github_commit_hash = xshell::cmd!(sh, "git rev-parse {github_commit_hash}^").read()?;
action_id = get_action_id(github_commit_hash.clone());
loop_count += 1;
}

if let Ok(id) = action_id {
print!("Got action id: {}", id);
rt.write(gh_workflow_id, &id);
} else {
anyhow::bail!("Failed to get action id");
}

Ok(())
}
});

Ok(())
}
}
3 changes: 3 additions & 0 deletions flowey/flowey_lib_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod copy_to_artifact_dir;
pub mod download_azcopy;
pub mod download_cargo_fuzz;
pub mod download_cargo_nextest;
pub mod download_gh_artifact;
pub mod download_gh_cli;
pub mod download_gh_release;
pub mod download_mdbook;
Expand All @@ -31,7 +32,9 @@ pub mod download_mdbook_mermaid;
pub mod download_nuget_exe;
pub mod download_protoc;
pub mod gh_download_azure_key_vault_secret;
pub mod gh_merge_commit;
pub mod gh_task_azure_login;
pub mod gh_workflow_id;
pub mod git_checkout;
pub mod install_azure_cli;
pub mod install_dist_pkg;
Expand Down
Loading
Loading