1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 06:45:13 +01:00

xtask: Add local-rust-deps command for auto-detecting path dependencies

Add `cargo xtask local-rust-deps` which uses `cargo metadata` to find
local path dependencies outside the workspace (e.g., from [patch] sections)
and outputs podman bind mount arguments.

This enables a cleaner workflow for local development against modified
dependencies like composefs-rs:

1. Add a [patch] section to Cargo.toml with real local paths
2. Run `just build` - the Justfile auto-detects and bind-mounts them

Benefits over the previous BOOTC_extra_src approach:
- No manual env var needed
- Paths work for both local `cargo build` and container builds
- No /run/extra-src indirection or Cargo.toml path munging required
- Auto-detection means it Just Works™

The Justfile's build target now calls `cargo xtask local-rust-deps` to
get bind mount args, falling back gracefully if there are no external deps.
The old BOOTC_extra_src mechanism is still supported for backwards compat.

Assisted-by: OpenCode (Opus 4.5)
Signed-off-by: Colin Walters <walters@verbum.org>
This commit is contained in:
Colin Walters
2026-01-22 17:47:29 -05:00
parent 56ac76e533
commit 21babe7616
6 changed files with 125 additions and 9 deletions

28
Cargo.lock generated
View File

@@ -440,6 +440,29 @@ dependencies = [
"uuid",
]
[[package]]
name = "cargo-platform"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
"thiserror 2.0.17",
]
[[package]]
name = "cc"
version = "1.2.51"
@@ -2275,6 +2298,10 @@ name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "serde"
@@ -3338,6 +3365,7 @@ dependencies = [
"anstream",
"anyhow",
"camino",
"cargo_metadata",
"chrono",
"clap",
"fn-error-context",

View File

@@ -43,16 +43,15 @@ clap_mangen = { version = "0.2.20" }
# Reviewers (including AI tools): The composefs-rs git revision is duplicated for each crate.
# If adding/removing crates here, also update docs/Dockerfile.mdbook and docs/src/internals.md.
#
# To develop against a local composefs-rs checkout:
# 1. Set BOOTC_extra_src to your composefs-rs path when building:
# BOOTC_extra_src=$HOME/src/composefs-rs just build
# 2. Comment out the git refs below and uncomment the path refs:
# To develop against a local composefs-rs checkout, add a [patch] section at the end of this file:
# [patch."https://github.com/containers/composefs-rs"]
# composefs = { path = "/home/user/src/composefs-rs/crates/composefs" }
# composefs-boot = { path = "/home/user/src/composefs-rs/crates/composefs-boot" }
# composefs-oci = { path = "/home/user/src/composefs-rs/crates/composefs-oci" }
# The Justfile will auto-detect these and bind-mount them into container builds.
composefs = { git = "https://github.com/containers/composefs-rs", rev = "4a060161e0122bd2727e639437c61e05ecc7cab3", package = "composefs", features = ["rhel9"] }
composefs-boot = { git = "https://github.com/containers/composefs-rs", rev = "4a060161e0122bd2727e639437c61e05ecc7cab3", package = "composefs-boot" }
composefs-oci = { git = "https://github.com/containers/composefs-rs", rev = "4a060161e0122bd2727e639437c61e05ecc7cab3", package = "composefs-oci" }
# composefs = { path = "/run/extra-src/crates/composefs", package = "composefs", features = ["rhel9"] }
# composefs-boot = { path = "/run/extra-src/crates/composefs-boot", package = "composefs-boot" }
# composefs-oci = { path = "/run/extra-src/crates/composefs-oci", package = "composefs-oci" }
fn-error-context = "0.2.1"
hex = "0.4.3"
indicatif = "0.18.0"

View File

@@ -33,7 +33,7 @@ WORKDIR /src
# See https://www.reddit.com/r/rust/comments/126xeyx/exploring_the_problem_of_faster_cargo_docker/
# We aren't using the full recommendations there, just the simple bits.
# First we download all of our Rust dependencies
# Note: /run/extra-src is optionally bind-mounted via BOOTC_extra_src for local composefs-rs development
# Note: Local path dependencies (from [patch] sections) are auto-detected and bind-mounted by the Justfile
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome cargo fetch
# We always do a "from scratch" build

View File

@@ -25,7 +25,10 @@ base := env("BOOTC_base", "quay.io/centos-bootc/centos-bootc:stream10")
# Buildroot base image
buildroot_base := env("BOOTC_buildroot_base", "quay.io/centos/centos:stream10")
# Optional: path to extra source (e.g. composefs-rs) for local development
# DEPRECATED: Use [patch] sections in Cargo.toml instead, which are auto-detected
extra_src := env("BOOTC_extra_src", "")
# Set to "1" to disable auto-detection of local Rust dependencies
no_auto_local_deps := env("BOOTC_no_auto_local_deps", "")
# Internal variables
nocache := env("BOOTC_nocache", "")
@@ -231,7 +234,12 @@ package:
fi
eval $(just _git-build-vars)
echo "Building RPM with version: ${VERSION}"
podman build {{base_buildargs}} --build-arg=SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} --build-arg=pkgversion=${VERSION} -t localhost/bootc-pkg --target=build .
# Auto-detect local Rust path dependencies (e.g., from [patch] sections)
local_deps_args=""
if [[ -z "{{no_auto_local_deps}}" ]]; then
local_deps_args=$(cargo xtask local-rust-deps)
fi
podman build {{base_buildargs}} --build-arg=SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} --build-arg=pkgversion=${VERSION} -t localhost/bootc-pkg --target=build $local_deps_args .
mkdir -p "${packages}"
rm -vf "${packages}"/*.rpm
podman run --rm localhost/bootc-pkg tar -C /out/ -cf - . | tar -C "${packages}"/ -xvf -

View File

@@ -27,6 +27,7 @@ toml = { workspace = true }
xshell = { workspace = true }
# Crate-specific dependencies
cargo_metadata = "0.19"
mandown = "1.1.0"
rand = "0.9"
serde_yaml = "0.9"

View File

@@ -56,6 +56,8 @@ enum Commands {
CheckBuildsys,
/// Validate composefs digests match between build-time and install-time views
ValidateComposefsDigest(ValidateComposefsDigestArgs),
/// Print podman bind mount arguments for local path dependencies
LocalRustDeps(LocalRustDepsArgs),
}
/// Arguments for validate-composefs-digest command
@@ -65,6 +67,14 @@ pub(crate) struct ValidateComposefsDigestArgs {
pub(crate) image: String,
}
/// Arguments for local-rust-deps command
#[derive(Debug, Args)]
pub(crate) struct LocalRustDepsArgs {
/// Output format: "podman" for -v arguments, "json" for structured data
#[arg(long, default_value = "podman")]
pub(crate) format: String,
}
/// Arguments for run-tmt command
#[derive(Debug, Args)]
pub(crate) struct RunTmtArgs {
@@ -149,6 +159,7 @@ fn try_main() -> Result<()> {
Commands::TmtProvision(args) => tmt::tmt_provision(&sh, &args),
Commands::CheckBuildsys => buildsys::check_buildsys(&sh, "Dockerfile".into()),
Commands::ValidateComposefsDigest(args) => validate_composefs_digest(&sh, &args),
Commands::LocalRustDeps(args) => local_rust_deps(&sh, &args),
}
}
@@ -417,6 +428,75 @@ fn update_generated(sh: &Shell) -> Result<()> {
Ok(())
}
/// Find local path dependencies outside the workspace and output podman bind mount arguments.
///
/// This uses `cargo metadata` to find all packages with no source (i.e., local path deps).
/// For packages outside the workspace root, it computes the minimal set of directories
/// to bind mount into the container.
#[context("Finding local Rust dependencies")]
fn local_rust_deps(_sh: &Shell, args: &LocalRustDepsArgs) -> Result<()> {
let metadata = cargo_metadata::MetadataCommand::new()
.exec()
.context("Running cargo metadata")?;
let workspace_root = &metadata.workspace_root;
let mut external_roots: std::collections::BTreeSet<Utf8PathBuf> =
std::collections::BTreeSet::new();
for pkg in &metadata.packages {
// Packages with source are from registries/git, skip them
if pkg.source.is_some() {
continue;
}
// Get the package directory (parent of Cargo.toml)
let pkg_dir = pkg
.manifest_path
.parent()
.ok_or_else(|| anyhow::anyhow!("No parent for manifest_path"))?;
// Skip packages inside the workspace
if pkg_dir.starts_with(workspace_root) {
continue;
}
// Find the workspace root for this external package by running cargo metadata
// in the package directory
let external_metadata = cargo_metadata::MetadataCommand::new()
.current_dir(pkg_dir)
.exec()
.with_context(|| format!("Running cargo metadata in {pkg_dir}"))?;
external_roots.insert(external_metadata.workspace_root.clone());
}
match args.format.as_str() {
"podman" => {
// Output podman -v arguments
let mut args_out = Vec::new();
for root in &external_roots {
// Mount read-only with SELinux disabled (for cross-context access)
args_out.push("-v".to_string());
args_out.push(format!("{}:{}:ro", root, root));
args_out.push("--security-opt=label=disable".to_string());
}
if !args_out.is_empty() {
println!("{}", args_out.join(" "));
}
}
"json" => {
let roots: Vec<&str> = external_roots.iter().map(|p| p.as_str()).collect();
println!("{}", serde_json::to_string_pretty(&roots)?);
}
other => {
anyhow::bail!("Unknown format: {other}. Use 'podman' or 'json'.");
}
}
Ok(())
}
/// Validate that composefs digests match between build-time and install-time views.
///
/// Compares dumpfiles generated from: