Skip to content

wip: install nix on CI and run a build with it#2894

Open
justus-camp-microsoft wants to merge 5 commits intomicrosoft:mainfrom
justus-camp-microsoft:install_nix_and_ci_job
Open

wip: install nix on CI and run a build with it#2894
justus-camp-microsoft wants to merge 5 commits intomicrosoft:mainfrom
justus-camp-microsoft:install_nix_and_ci_job

Conversation

@justus-camp-microsoft
Copy link
Copy Markdown
Contributor

No description provided.

@justus-camp-microsoft justus-camp-microsoft requested a review from a team as a code owner March 5, 2026 23:00
Copilot AI review requested due to automatic review settings March 5, 2026 23:00
@justus-camp-microsoft justus-camp-microsoft requested a review from a team as a code owner March 5, 2026 23:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@justus-camp-microsoft justus-camp-microsoft force-pushed the install_nix_and_ci_job branch 19 times, most recently from b22671c to 5e9066a Compare March 11, 2026 18:58
@justus-camp-microsoft justus-camp-microsoft requested a review from a team as a code owner March 11, 2026 18:58
@justus-camp-microsoft justus-camp-microsoft force-pushed the install_nix_and_ci_job branch 2 times, most recently from b834287 to 9fe01eb Compare March 11, 2026 21:15
Copilot AI review requested due to automatic review settings March 11, 2026 21:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 32 changed files in this pull request and generated 4 comments.

Comment on lines +53 to +60
let first_diff =
bytes_a.iter().zip(bytes_b.iter()).position(|(a, b)| a != b);
anyhow::bail!(
"file {name} is not byte-identical \
({} vs {} bytes, first diff at offset {:?})",
bytes_a.len(),
bytes_b.len(),
first_diff,
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

When the files differ only by length (one is a strict prefix of the other), zip(...).position(...) returns None, so the error message reports “first diff at offset None”, which is confusing for diagnosing CI failures. Consider handling length-mismatch explicitly (e.g., report the first differing offset as min(len_a, len_b) when lengths differ) and/or computing the diff index over the full length.

Suggested change
let first_diff =
bytes_a.iter().zip(bytes_b.iter()).position(|(a, b)| a != b);
anyhow::bail!(
"file {name} is not byte-identical \
({} vs {} bytes, first diff at offset {:?})",
bytes_a.len(),
bytes_b.len(),
first_diff,
let first_diff_offset = bytes_a
.iter()
.zip(bytes_b.iter())
.position(|(a, b)| a != b)
.unwrap_or_else(|| bytes_a.len().min(bytes_b.len()));
anyhow::bail!(
"file {name} is not byte-identical \
({} vs {} bytes, first diff at offset {})",
bytes_a.len(),
bytes_b.len(),
first_diff_offset,

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +105
let is_installed = match ctx.backend() {
FlowBackend::Local => ctx.emit_rust_step("ensure nix is installed", |_ctx| {
|_rt| {
if which::which("nix-shell").is_err() {
anyhow::bail!(
"nix-shell not found on $PATH. \
Please install Nix: https://nixos.org/download/"
);
}
Ok(())
}
}),
FlowBackend::Github | FlowBackend::Ado => {
// Step 1: Add nix profile bin to $PATH for subsequent CI
// steps. This runs before the install step so that when
// the next step starts (as a new process), nix-shell is
// already on $PATH.
let added_to_path = ctx.emit_rust_step("add nix profile to path", |ctx| {
let backend = ctx.backend();
move |_rt| {
let nix_bin = home::home_dir()
.context("unable to get home directory")?
.join(".nix-profile/bin");

match backend {
FlowBackend::Github => {
let gh_path_file = std::env::var("GITHUB_PATH")?;
let mut f =
fs_err::File::options().append(true).open(gh_path_file)?;
use std::io::Write;
writeln!(f, "{}", nix_bin.display())?;
log::info!("added {} to $GITHUB_PATH", nix_bin.display());
}
FlowBackend::Ado => {
println!("##vso[task.prependpath]{}", nix_bin.display());
log::info!("added {} to PATH (ADO)", nix_bin.display());
}
FlowBackend::Local => unreachable!(),
}

Ok(())
}
});

ctx.emit_rust_step("install nix", |ctx| {
added_to_path.claim(ctx);
move |_rt| {
if which::which("nix-shell").is_ok() {
log::info!("nix-shell already available, skipping install");
return Ok(());
}

log::info!("installing Nix package manager...");

// Single-user install (no daemon) — simplest for CI.
// https://nixos.org/download/
#[expect(clippy::disallowed_methods, reason = "can't use the nix xshell wrapper if nix isn't installed yet")]
let sh = xshell::Shell::new()?;
#[expect(clippy::disallowed_macros, reason = "can't use the nix xshell wrapper if nix isn't installed yet")]
xshell::cmd!(
sh,
"sh -c 'curl --proto =https --tlsv1.2 -sSf -L https://nixos.org/nix/install | sh -s -- --no-daemon'"
)
.run()?;

log::info!("nix installed successfully");
Ok(())
}
})
}
};
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

install_nix doesn’t check ctx.platform() / rt.platform() before attempting a curl-based install on CI backends. If this node is ever requested from a Windows or macOS job, it will fail in a non-obvious way. Consider explicitly gating this node to Linux platforms and returning a clear error message on unsupported platforms.

Copilot uses AI. Check for mistakes.
Unix,
}

/// The kind platform the flow is being running on, Windows or Unix.
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The doc comment above FlowPlatformLinuxDistro says “The kind platform … Windows or Unix”, but this enum represents the Linux distribution (Fedora/Ubuntu/Nix/…). Since this type is now serialized/deserialized and used for job selection, the comment should be corrected to avoid confusion for future maintainers.

Suggested change
/// The kind platform the flow is being running on, Windows or Unix.
/// The Linux distribution the flow is running on.

Copilot uses AI. Check for mistakes.
Comment thread shell.nix Outdated
Comment on lines +160 to +166
# Need unstable features on the stable toolchain
RUSTC_BOOTSTRAP = 1;
# Enable path trimming
CARGO_UNSTABLE_TRIM_PATHS = "true";
CARGO_PROFILE_DEV_TRIM_PATHS = "object";
CARGO_PROFILE_RELEASE_TRIM_PATHS = "object";
CARGO_PROFILE_UNDERHILL_SHIP_TRIM_PATHS = "object";
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

Setting RUSTC_BOOTSTRAP=1 globally for the whole nix-shell enables unstable features for every crate built in that environment, which can accidentally mask use of #![feature] and other unstable behavior beyond trim-paths. If the intent is only to enable Cargo’s trim-paths support, consider scoping RUSTC_BOOTSTRAP more narrowly (e.g., only for specific crates, or only for the specific build invocation/profile that needs it) to reduce the blast radius.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 3, 2026 22:12
@github-actions github-actions Bot added the Guide label Apr 3, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 36 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (3)

vm/loader/igvminspect/src/extract.rs:1

  • The output filename is derived from region.component, which ultimately comes from a .map file. As-is, a crafted component name containing path separators or .. can result in path traversal and writing outside regions_dir. Sanitize component strings into a safe filename (e.g., replace / and \\, drop .., restrict to [A-Za-z0-9._-]), or generate filenames from a stable hash + a separately-stored display name in regions.txt.
    vm/loader/igvminspect/src/extract.rs:1
  • The merge path can truncate already-collected data if entry.data.len() > 4096: the first page is resized to page_data_len (larger than 4K), but on the first merge expected_len is page_count * 4096, so last.data.resize(expected_len, 0) shrinks the buffer and drops bytes. Either enforce that PageData is always <= 4K (and treat larger sizes as an error), or carry a per-entry/per-region page size and use that consistently for both expected_len and GPA increments.
    flowey/flowey_lib_hvlite/src/_jobs/test_reproducible_build.rs:1
  • This job accepts a recipe parameter but hard-codes the artifact directories to x64-cvm-*. If recipe is ever not x64-cvm, the job will publish the wrong directories (or fail). Build these paths from recipe to match the invoked pipeline run build-reproducible {recipe} output naming.

"Run with verbose output",
ParameterKind::Stable,
Some(false),
Some(true),
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Changing the cloud default verbose to true will increase log volume across pipelines by default (and the corresponding YAML/workflow defaults are also flipped). If verbose is only needed for specific diagnostics (e.g., investigating --extern ordering), consider keeping the default false and enabling it only in the reproducibility-related jobs/pipelines, or gating it behind a separate diagnostic parameter.

Suggested change
Some(true),
Some(false),

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +62
if bytes_a != bytes_b {
let first_diff =
bytes_a.iter().zip(bytes_b.iter()).position(|(a, b)| a != b);
anyhow::bail!(
"file {name} is not byte-identical \
({} vs {} bytes, first diff at offset {:?})",
bytes_a.len(),
bytes_b.len(),
first_diff,
);
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

When the only difference is file length (common in build artifacts), first_diff will be None (because zip stops at the shorter length), yielding a confusing message. Consider explicitly handling size mismatches (e.g., if lengths differ and all overlapping bytes match, report the first differing offset as min(len_a, len_b) and mention that the shorter file ended).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 53 changed files in this pull request and generated 5 comments.

Comment thread vm/loader/igvminspect/src/extract.rs Outdated
Comment on lines +354 to +358
let ext = extension_for_component(&region.component);
let filename = if total == 1 {
format!("{}.{ext}", region.component)
} else {
format!("{}_{}.{ext}", region.component, idx)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

region.component comes from the user-supplied .bin.map file and is used verbatim to construct output filenames. If the map contains path separators or .., regions_dir.join(filename) can write outside the output directory (path traversal) or fail on invalid characters. Sanitize the component name (e.g., replace / and \, strip .., collapse whitespace) before using it as a filename.

Copilot uses AI. Check for mistakes.
Comment on lines +194 to +196
verbose: _,
no_incremental,
} = flags;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

cfg_cargo_common_flags::Flags still contains verbose, but this node ignores it (verbose: _) and instead relies on a separate Request::verbose. This looks like a behavior regression: setting the common verbose flag will no longer make cargo build verbose unless every caller wires the new field through. Consider deriving Request::verbose from flags.verbose (or removing one of the two knobs) so the common flag continues to work consistently across cargo subcommands.

Suggested change
verbose: _,
no_incremental,
} = flags;
verbose: flags_verbose,
no_incremental,
} = flags;
let verbose = verbose || flags_verbose;

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +98
xshell::cmd!(
sh,
"sh -c 'curl --proto =https --tlsv1.2 -sSf -L https://nixos.org/nix/install | sh -s -- --no-daemon'"
)
.run()?;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The CI installation path executes a remote script via curl ... | sh without any integrity/pinning checks. Even in CI this is a supply-chain risk and can lead to non-reproducible failures if the installer changes. Prefer using a pinned installer (fixed URL + checksum/signature verification) or a well-maintained Nix install mechanism that supports version pinning.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +80
let local_igvm_dir = hvlite_repo.join("flowey-out/artifacts/x64-cvm-openhcl-igvm");
let local_igvm_extras_dir =
hvlite_repo.join("flowey-out/artifacts/x64-cvm-openhcl-igvm-extras");

log::info!("publishing local build output");
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The output directories are hard-coded to x64-cvm-openhcl-igvm / x64-cvm-openhcl-igvm-extras, but the job takes a generic recipe: String and uses it when running the pipeline. Either derive these artifact paths from recipe (if they vary), or validate/bail unless recipe == "x64-cvm" to avoid publishing the wrong directories.

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +57
let first_diff =
bytes_a.iter().zip(bytes_b.iter()).position(|(a, b)| a != b);
anyhow::bail!(
"file {name} is not byte-identical \
({} vs {} bytes, first diff at offset {:?})",
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

If the files have different lengths but share an identical prefix, first_diff will be None, so the error message reports "first diff at offset None". Consider explicitly handling size mismatches (e.g., report min(len_a, len_b) as the first differing offset when one is a prefix of the other) to make failures actionable.

Suggested change
let first_diff =
bytes_a.iter().zip(bytes_b.iter()).position(|(a, b)| a != b);
anyhow::bail!(
"file {name} is not byte-identical \
({} vs {} bytes, first diff at offset {:?})",
let first_diff = bytes_a
.iter()
.zip(bytes_b.iter())
.position(|(a, b)| a != b)
.unwrap_or(bytes_a.len().min(bytes_b.len()));
anyhow::bail!(
"file {name} is not byte-identical \
({} vs {} bytes, first diff at offset {})",

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 6, 2026 20:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 53 changed files in this pull request and generated 6 comments.

Comment thread vm/loader/igvminspect/src/extract.rs Outdated
Comment on lines +351 to +362
for region in &regions {
let total = name_counts[&region.component];
let idx = name_indices.entry(region.component.clone()).or_insert(0);
let ext = extension_for_component(&region.component);
let filename = if total == 1 {
format!("{}.{ext}", region.component)
} else {
format!("{}_{}.{ext}", region.component, idx)
};
*idx += 1;

fs_err::write(regions_dir.join(&filename), &region.data)?;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

component comes from the user-provided .bin.map file and is used directly in output filenames. If the map contains path separators or .., this can write outside regions_dir (path traversal) or create unexpected nested paths. Sanitize component into a safe filename (e.g., reject/replace separators, collapse .., or use Path::new(component).file_name() plus a whitelist) before building filename.

Copilot uses AI. Check for mistakes.
Comment thread vm/loader/igvminspect/src/extract.rs Outdated
Comment on lines +302 to +331
for &idx in &sorted {
let entry = &entries[idx];
let page_data_len = entry.data.len().max(PAGE_SIZE_4K as usize);

// Merge only if contiguous, same component, and same flags/data_type
let can_merge = if let Some(last) = regions.last() {
entry.gpa == last.end_gpa
&& entry.component == last.component
&& entry.flags == last.flags
&& entry.data_type == last.data_type
} else {
false
};

if can_merge {
let last = regions.last_mut().unwrap();
let expected_len = (last.page_count as usize) * (PAGE_SIZE_4K as usize);
last.data.resize(expected_len, 0);
let mut page_buf = entry.data.clone();
page_buf.resize(page_data_len, 0);
last.data.extend_from_slice(&page_buf);
last.end_gpa = entry.gpa + PAGE_SIZE_4K;
last.page_count += 1;
} else {
let mut data = entry.data.clone();
data.resize(page_data_len, 0);
regions.push(Region {
start_gpa: entry.gpa,
end_gpa: entry.gpa + PAGE_SIZE_4K,
page_count: 1,
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

This code assumes each PageData directive represents exactly one 4K page (it always advances end_gpa by PAGE_SIZE_4K). If an IGVM contains a PageData with data.len() > 4096, the extracted region data length and the reported GPA range/page_count will disagree. Consider validating data.len() <= PAGE_SIZE_4K and returning an error if it is larger, to avoid producing misleading output.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +61
if bytes_a != bytes_b {
let first_diff =
bytes_a.iter().zip(bytes_b.iter()).position(|(a, b)| a != b);
anyhow::bail!(
"file {name} is not byte-identical \
({} vs {} bytes, first diff at offset {:?})",
bytes_a.len(),
bytes_b.len(),
first_diff,
);
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

When file sizes differ but the common prefix matches, first_diff becomes None, producing an unclear error (first diff at offset None). Handle length mismatches separately (or report min_len as the first differing offset) so the failure is actionable.

Copilot uses AI. Check for mistakes.
.new_job(
FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu),
FlowArch::X86_64,
"verify reproducible openhcl [x64-cvm-linux-nix]",
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The verify job is created with FlowPlatform::Linux(Ubuntu) but the job name includes [x64-cvm-linux-nix]. This is misleading when reading CI logs and makes it harder to triage platform-specific failures. Rename the job (or run it on the Nix platform if that was intended).

Suggested change
"verify reproducible openhcl [x64-cvm-linux-nix]",
"verify reproducible openhcl [x64-cvm-linux-ubuntu]",

Copilot uses AI. Check for mistakes.
Comment thread reproducibility.md Outdated
Comment on lines +19 to +28
| Component | Status |
|---|---|
| `openhcl_boot` | Identical |
| `openhcl_boot.dbg` | Identical |
| `openhcl.bin.map` | Identical |
| Kernel (`underhill-kernel_*.bin`) | Identical |
| Boot shim (`underhill-boot-shim_*.bin`) | Identical |
| Kernel modules (`.ko` files in initrd) | Identical |
| IGVM structure (`regions.txt` layout) | Identical |
| All measured/private page variants (`_1`) | Identical (except `shim-params_1` — 5 bytes, and `loader-imported-regions_1` — 48 bytes) |
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The markdown tables in this doc use a double leading pipe (||) on each row, which creates an unintended empty first column in most renderers. Consider changing these rows to the standard | ... | ... | form so the tables render as intended.

Copilot uses AI. Check for mistakes.
Comment thread reproducibility.md Outdated
Comment on lines +32 to +38
| Component | Difference |
|---|---|
| `openvmm_hcl` | 1,533,141 bytes differ out of 15,536,928 (9.9%) — same size, different code |
| `openvmm_hcl.dbg` | Size differs by 48 bytes (184,218,880 vs 184,218,832) |
| `underhill-initrd_1.cpio.gz` | 6.3M bytes differ — caused by different `openvmm_hcl` inside |
| Shared page variants (`_0`) | Most differ, filled with ~99.5-99.8% non-zero random-looking data |

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

Same table formatting issue here: the extra leading || produces an empty first column. Use single leading/trailing | for standard markdown table rows.

Copilot uses AI. Check for mistakes.
@justus-camp-microsoft justus-camp-microsoft requested a review from a team as a code owner April 6, 2026 20:20
Copilot AI review requested due to automatic review settings April 6, 2026 22:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 26 changed files in this pull request and generated 3 comments.

let shell_nix = hvlite_repo.join("shell.nix");
log::info!("compiling flowey_hvlite inside nix-shell");
flowey::shell_cmd!(rt, "nix-shell {shell_nix} --pure --run")
.arg("cargo build -p flowey_hvlite")
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

This builds flowey_hvlite without --locked. In CI this can silently update Cargo.lock (or fail in unexpected ways) and undermines reproducibility goals. Consider passing --locked to the cargo build -p flowey_hvlite invocation (and/or reusing the already-configured cfg_cargo_common_flags/locked setting).

Suggested change
.arg("cargo build -p flowey_hvlite")
.arg("cargo build -p flowey_hvlite --locked")

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +18
pub struct Request {
/// Recipe name to pass to `cargo xflowey build-reproducible` (e.g. "x64-cvm").
pub recipe: String,
/// Directory to publish the local build-reproducible igvm output to.
pub artifact_dir_local_igvm: ReadVar<PathBuf>,
/// Directory to publish the local build-reproducible igvm extras output to.
pub artifact_dir_local_igvm_extras: ReadVar<PathBuf>,
pub done: WriteVar<SideEffect>,
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

recipe is a request parameter, but the artifact output paths are hard-coded to x64-cvm-*. If this node is intended to support multiple recipes, derive the output artifact directory names from recipe (or switch recipe to a strongly-typed enum to make the supported set explicit). Otherwise, consider removing the parameter to avoid implying generic support.

Copilot uses AI. Check for mistakes.
Comment on lines +933 to +949
let verify_job = pipeline
.new_job(
FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu),
FlowArch::X86_64,
"verify reproducible openhcl [x64-cvm-linux-nix]",
)
.gh_set_pool(crate::pipelines_shared::gh_pools::gh_hosted_x64_linux())
.dep_on(
|ctx| flowey_lib_hvlite::_jobs::check_reproducible_build::Request {
artifact_dir_a: ctx.use_artifact(&use_openhcl_igvm),
artifact_dir_b: ctx.use_artifact(&use_local_igvm),
file_names: vec!["openhcl-cvm.bin".into()],
done: ctx.new_done_handle(),
},
)
.finish();

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

This verification job runs on Ubuntu, but the job name suffix says linux-nix. Consider renaming the job label to reflect the actual platform to avoid confusion when scanning CI results.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 15 changed files in this pull request and generated 5 comments.

Comment thread shell.nix
Comment on lines 69 to 73
# Include both musl targets for cross-compilation
targets = [
"x86_64-unknown-linux-musl"
"x86_64-unknown-linux-gnu"
"x86_64-unknown-none"
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The comment above targets says to include both musl targets, but the list now includes x86_64-unknown-linux-gnu as well. Please update the comment (or adjust the target list) so it accurately reflects the intent, since this file is used to define the Nix dev/CI environment.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +82
.run()?;

let local_igvm_dir = hvlite_repo.join("flowey-out/artifacts/x64-cvm-openhcl-igvm");
let local_igvm_extras_dir =
hvlite_repo.join("flowey-out/artifacts/x64-cvm-openhcl-igvm-extras");

log::info!("publishing local build output");
flowey::util::copy_dir_all(&local_igvm_dir, &publish_dir)?;
flowey::util::copy_dir_all(&local_igvm_extras_dir, &publish_dir_extras)?;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

local_igvm_dir / local_igvm_extras_dir are hard-coded to the x64-cvm artifact names, but this node takes a recipe parameter and passes it through to build-reproducible. This will silently break if the job is ever used with a different recipe; either derive these output paths from recipe (recommended) or narrow the API (rename/remove recipe) so the hard-coding is explicit.

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +74
log::info!("running {flowey_bin:?} pipeline run build-reproducible {recipe}");
flowey::shell_cmd!(
rt,
"{flowey_bin} pipeline run build-reproducible {recipe} --release"
)
.env(
"I_HAVE_A_GOOD_REASON_TO_RUN_BUILD_REPRODUCIBLE_IN_CI",
"true",
)
.run()?;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

flowey_hvlite is compiled inside nix-shell --pure, but then the produced binary is executed outside of that environment. If the binary ends up dynamically linking against libs provided by the nix shell (common for OpenSSL/zlib), it may fail at runtime on a stock runner. Consider running the pipeline invocation via nix-shell --pure --run ... as well (or building/running via the job-level CommandWrapperKind::NixShell instead of per-command).

Suggested change
log::info!("running {flowey_bin:?} pipeline run build-reproducible {recipe}");
flowey::shell_cmd!(
rt,
"{flowey_bin} pipeline run build-reproducible {recipe} --release"
)
.env(
"I_HAVE_A_GOOD_REASON_TO_RUN_BUILD_REPRODUCIBLE_IN_CI",
"true",
)
.run()?;
log::info!(
"running {flowey_bin:?} pipeline run build-reproducible {recipe} inside nix-shell"
);
flowey::shell_cmd!(rt, "nix-shell {shell_nix} --pure --run")
.arg(format!(
"{} pipeline run build-reproducible {} --release",
flowey_bin.display(),
recipe
))
.env(
"I_HAVE_A_GOOD_REASON_TO_RUN_BUILD_REPRODUCIBLE_IN_CI",
"true",
)
.run()?;

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +55
for name in &file_names {
let bytes_a = fs_err::read(dir_a.join(name))?;
let bytes_b = fs_err::read(dir_b.join(name))?;

if bytes_a != bytes_b {
let first_diff =
bytes_a.iter().zip(bytes_b.iter()).position(|(a, b)| a != b);
anyhow::bail!(
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

This job reads each .bin fully into memory (fs_err::read) before comparing. IGVM binaries can be large enough to cause avoidable memory pressure in CI. Prefer a streaming/chunked comparison (e.g., buffered readers) so memory usage stays bounded while still reporting the first differing offset.

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +103
FlowBackend::Github | FlowBackend::Ado => {
// Step 1: Add nix profile bin to $PATH for subsequent CI
// steps. This runs before the install step so that when
// the next step starts (as a new process), nix-shell is
// already on $PATH.
let added_to_path = ctx.emit_rust_step("add nix profile to path", |ctx| {
let backend = ctx.backend();
move |_rt| {
let nix_bin = home::home_dir()
.context("unable to get home directory")?
.join(".nix-profile/bin");

match backend {
FlowBackend::Github => {
let gh_path_file = std::env::var("GITHUB_PATH")?;
let mut f =
fs_err::File::options().append(true).open(gh_path_file)?;
use std::io::Write;
writeln!(f, "{}", nix_bin.display())?;
log::info!("added {} to $GITHUB_PATH", nix_bin.display());
}
FlowBackend::Ado => {
println!("##vso[task.prependpath]{}", nix_bin.display());
log::info!("added {} to PATH (ADO)", nix_bin.display());
}
FlowBackend::Local => unreachable!(),
}

Ok(())
}
});

ctx.emit_rust_step("install nix", |ctx| {
added_to_path.claim(ctx);
move |_rt| {
if which::which("nix-shell").is_ok() {
log::info!("nix-shell already available, skipping install");
return Ok(());
}

log::info!("installing Nix package manager...");

// Single-user install (no daemon) — simplest for CI.
// https://nixos.org/download/
#[expect(clippy::disallowed_methods, reason = "can't use the nix xshell wrapper if nix isn't installed yet")]
let sh = xshell::Shell::new()?;
#[expect(clippy::disallowed_macros, reason = "can't use the nix xshell wrapper if nix isn't installed yet")]
xshell::cmd!(
sh,
"sh -c 'curl --proto =https --tlsv1.2 -sSf -L https://nixos.org/nix/install | sh -s -- --no-daemon'"
)
.run()?;

log::info!("nix installed successfully");
Ok(())
}
})
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The CI install path runs a POSIX shell pipeline (sh -c ... | sh ...) without checking the current platform. If this node is ever invoked on a Windows GitHub/ADO runner, it will fail due to missing sh/curl. Consider explicitly gating the install logic to FlowPlatform::Linux(_)/MacOs and bailing with a clear message on Windows.

Suggested change
FlowBackend::Github | FlowBackend::Ado => {
// Step 1: Add nix profile bin to $PATH for subsequent CI
// steps. This runs before the install step so that when
// the next step starts (as a new process), nix-shell is
// already on $PATH.
let added_to_path = ctx.emit_rust_step("add nix profile to path", |ctx| {
let backend = ctx.backend();
move |_rt| {
let nix_bin = home::home_dir()
.context("unable to get home directory")?
.join(".nix-profile/bin");
match backend {
FlowBackend::Github => {
let gh_path_file = std::env::var("GITHUB_PATH")?;
let mut f =
fs_err::File::options().append(true).open(gh_path_file)?;
use std::io::Write;
writeln!(f, "{}", nix_bin.display())?;
log::info!("added {} to $GITHUB_PATH", nix_bin.display());
}
FlowBackend::Ado => {
println!("##vso[task.prependpath]{}", nix_bin.display());
log::info!("added {} to PATH (ADO)", nix_bin.display());
}
FlowBackend::Local => unreachable!(),
}
Ok(())
}
});
ctx.emit_rust_step("install nix", |ctx| {
added_to_path.claim(ctx);
move |_rt| {
if which::which("nix-shell").is_ok() {
log::info!("nix-shell already available, skipping install");
return Ok(());
}
log::info!("installing Nix package manager...");
// Single-user install (no daemon) — simplest for CI.
// https://nixos.org/download/
#[expect(clippy::disallowed_methods, reason = "can't use the nix xshell wrapper if nix isn't installed yet")]
let sh = xshell::Shell::new()?;
#[expect(clippy::disallowed_macros, reason = "can't use the nix xshell wrapper if nix isn't installed yet")]
xshell::cmd!(
sh,
"sh -c 'curl --proto =https --tlsv1.2 -sSf -L https://nixos.org/nix/install | sh -s -- --no-daemon'"
)
.run()?;
log::info!("nix installed successfully");
Ok(())
}
})
FlowBackend::Github | FlowBackend::Ado => match ctx.platform() {
FlowPlatform::Linux(_) | FlowPlatform::MacOs => {
// Step 1: Add nix profile bin to $PATH for subsequent CI
// steps. This runs before the install step so that when
// the next step starts (as a new process), nix-shell is
// already on $PATH.
let added_to_path = ctx.emit_rust_step("add nix profile to path", |ctx| {
let backend = ctx.backend();
move |_rt| {
let nix_bin = home::home_dir()
.context("unable to get home directory")?
.join(".nix-profile/bin");
match backend {
FlowBackend::Github => {
let gh_path_file = std::env::var("GITHUB_PATH")?;
let mut f =
fs_err::File::options().append(true).open(gh_path_file)?;
use std::io::Write;
writeln!(f, "{}", nix_bin.display())?;
log::info!("added {} to $GITHUB_PATH", nix_bin.display());
}
FlowBackend::Ado => {
println!("##vso[task.prependpath]{}", nix_bin.display());
log::info!("added {} to PATH (ADO)", nix_bin.display());
}
FlowBackend::Local => unreachable!(),
}
Ok(())
}
});
ctx.emit_rust_step("install nix", |ctx| {
added_to_path.claim(ctx);
move |_rt| {
if which::which("nix-shell").is_ok() {
log::info!("nix-shell already available, skipping install");
return Ok(());
}
log::info!("installing Nix package manager...");
// Single-user install (no daemon) — simplest for CI.
// https://nixos.org/download/
#[expect(clippy::disallowed_methods, reason = "can't use the nix xshell wrapper if nix isn't installed yet")]
let sh = xshell::Shell::new()?;
#[expect(clippy::disallowed_macros, reason = "can't use the nix xshell wrapper if nix isn't installed yet")]
xshell::cmd!(
sh,
"sh -c 'curl --proto =https --tlsv1.2 -sSf -L https://nixos.org/nix/install | sh -s -- --no-daemon'"
)
.run()?;
log::info!("nix installed successfully");
Ok(())
}
})
}
FlowPlatform::Windows => {
anyhow::bail!(
"automatic Nix installation in CI is only supported on Linux and macOS runners; \
Windows runners do not provide the required POSIX shell/curl install path"
);
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 15 changed files in this pull request and generated 7 comments.

Comment thread shell.nix
@@ -69,6 +69,7 @@ let
# Include both musl targets for cross-compilation
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The comment says “Include both musl targets”, but the change adds a GNU target. Please update the comment to accurately reflect the target list (e.g., mention both musl and gnu, or split into separate groups).

Suggested change
# Include both musl targets for cross-compilation
# Include musl, gnu, and bare-metal targets for cross-compilation

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +46
let rust_install = ctx.reqv(flowey_lib_common::install_rust::Request::EnsureInstalled);
let nix_install = ctx.reqv(flowey_lib_common::install_nix::Request::EnsureInstalled);

let step = ctx.emit_rust_step("run cargo xflowey build-reproducible", |ctx| {
rust_install.claim(ctx);
nix_install.claim(ctx);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

This node claims install_rust::EnsureInstalled, which (after the wrapper-aware change) will require cargo to be runnable in the current step environment. However, this job only enters Nix for the cargo build -p flowey_hvlite command, and runs the rest outside nix-shell. If the CI host doesn’t already have cargo on PATH, install_rust can fail before you ever enter nix-shell. A robust fix is to run the entire sequence under the same Nix wrapper (or set the job’s command wrapper to NixShell like the other reproducible job), and/or drop the Rust install dependency for this node if Nix is the source of the toolchain.

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +62
flowey::shell_cmd!(rt, "nix-shell {shell_nix} --pure --run")
.arg("cargo build -p flowey_hvlite")
.run()?;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

This node claims install_rust::EnsureInstalled, which (after the wrapper-aware change) will require cargo to be runnable in the current step environment. However, this job only enters Nix for the cargo build -p flowey_hvlite command, and runs the rest outside nix-shell. If the CI host doesn’t already have cargo on PATH, install_rust can fail before you ever enter nix-shell. A robust fix is to run the entire sequence under the same Nix wrapper (or set the job’s command wrapper to NixShell like the other reproducible job), and/or drop the Rust install dependency for this node if Nix is the source of the toolchain.

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +69
flowey::shell_cmd!(
rt,
"{flowey_bin} pipeline run build-reproducible {recipe} --release"
)
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

This node claims install_rust::EnsureInstalled, which (after the wrapper-aware change) will require cargo to be runnable in the current step environment. However, this job only enters Nix for the cargo build -p flowey_hvlite command, and runs the rest outside nix-shell. If the CI host doesn’t already have cargo on PATH, install_rust can fail before you ever enter nix-shell. A robust fix is to run the entire sequence under the same Nix wrapper (or set the job’s command wrapper to NixShell like the other reproducible job), and/or drop the Rust install dependency for this node if Nix is the source of the toolchain.

Copilot uses AI. Check for mistakes.
FlowArch::X86_64,
"build local reproducible openhcl [x64-cvm-linux-nix]",
)
.gh_set_pool(crate::pipelines_shared::gh_pools::gh_hosted_x64_linux())
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

Unlike the earlier Nix reproducible build job, this job does not configure a NixShell command wrapper, yet test_reproducible_build currently claims Rust installation and runs substantial work outside nix-shell. This combination is likely to fail on CI images without preinstalled Rust. Consider either adding .set_command_wrapper(...NixShell...) here as well, or adjust test_reproducible_build to ensure all required commands run under Nix.

Suggested change
.gh_set_pool(crate::pipelines_shared::gh_pools::gh_hosted_x64_linux())
.gh_set_pool(crate::pipelines_shared::gh_pools::gh_hosted_x64_linux())
.set_command_wrapper(CommandWrapper::NixShell)

Copilot uses AI. Check for mistakes.
.new_job(
FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu),
FlowArch::X86_64,
"verify reproducible openhcl [x64-cvm-linux-nix]",
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The job label includes linux-nix, but the job platform is Ubuntu. Please rename the job string to reflect the actual platform (or change the platform if the intent is to run under Nix).

Suggested change
"verify reproducible openhcl [x64-cvm-linux-nix]",
"verify reproducible openhcl [x64-cvm-linux-ubuntu]",

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +50
let bytes_a = fs_err::read(dir_a.join(name))?;
let bytes_b = fs_err::read(dir_b.join(name))?;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

This reads both files fully into memory before comparing. If .bin artifacts are large, this can significantly increase peak memory usage in CI. Consider streaming comparison (chunked reads) or comparing hashes computed via buffered I/O.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants