deb,cli,test: add --mirror to build and resolve from a pinned mirror
Pinned runners (a caching proxy, a chosen mirror) must resolve every package from one base URL. 'pkh deb --mirror <url>' passes the mirror to mmdebstrap as its mirror positional, so the chroot tarball's apt sources carry it from the bootstrap on and every apt inside the build follows - no per-build source rewriting. The mirror must serve every suite and architecture the build resolves (the security pocket and, for cross builds, the target archive included); that contract is documented, not enforced. The tarball cache is keyed per mirror (a short sha prefix before the '-buildd.tar.xz' suffix prune matches): a --mirror run over a stale distro-default tarball would silently resolve from the distro, and '--incremental' records must never fork across a mirror change. The runtime source passes recognize the pinned URL as an archive source (it carries no official host for the host-based checks): the pocket pass and the universe pass treat it as the archive, and the cross setup gives it every component and suite, no arch scoping (the mirror serves every architecture by contract) and no ports entry. 'pkh test' passes no mirror: its tarballs stay distro-default.
This commit is contained in:
@@ -191,6 +191,17 @@ A build killed hard (OOM) can leave its chroot behind; the next run of
|
||||
that target sweeps it (unmounting and escalating through sudo when
|
||||
needed), and `pkh prune` collects the rest.
|
||||
|
||||
On a pinned runner — a machine that must resolve every package from one
|
||||
mirror, typically a caching proxy — `pkh deb --mirror <url>` builds the
|
||||
chroot and resolves every package from that base URL: the mirror is
|
||||
passed to mmdebstrap at bootstrap, so the chroot tarball's apt sources
|
||||
carry it and every apt inside the build follows. The mirror must serve
|
||||
every suite and architecture the build resolves (the security pocket
|
||||
and, for `--cross` builds, the target architecture's archive included);
|
||||
PPAs and other third-party sources are left alone. The tarball cache is
|
||||
keyed per mirror, so pinned and unpinned builds keep separate tarballs
|
||||
(and `--incremental` records never fork across a mirror change).
|
||||
|
||||
### Running the DEP-8 tests
|
||||
|
||||
`pkh test` runs the package's as-installed tests (`debian/tests/control`,
|
||||
|
||||
+7
-3
@@ -86,9 +86,13 @@ act on the package in the current directory.
|
||||
- `pkh deb` builds the binary packages in an isolated context with the
|
||||
build dependencies installed. `--ppa` (repeatable) adds dependency
|
||||
sources, `--inject <package|.deb>` preinstalls a package, and `-j`
|
||||
caps parallel jobs. `--cross` cross-compiles instead of using
|
||||
qemu-binfmt, but most packages cannot cross-compile, so prefer qemu.
|
||||
Leave `--mode` unset unless you need a specific build context.
|
||||
caps parallel jobs. `--mirror <url>` builds the chroot and resolves
|
||||
every package from that base URL (a caching proxy on a pinned runner;
|
||||
it must serve every suite and architecture the build resolves).
|
||||
`--cross`
|
||||
cross-compiles instead of using qemu-binfmt, but most packages cannot
|
||||
cross-compile, so prefer qemu. Leave `--mode` unset unless you need a
|
||||
specific build context.
|
||||
|
||||
- `pkh lint [path]` runs lintian plus the pkh-native checks. `--json`
|
||||
emits a machine-readable report and `--list-tags` prints the native
|
||||
|
||||
+21
-6
@@ -99,6 +99,10 @@ fn cross_suites(
|
||||
/// are scoped to it and carry every component and cross-build suite, and
|
||||
/// the mirror serving the target architecture (ports, for the non-local
|
||||
/// ones) is added when no existing source serves the arch from it yet.
|
||||
/// Under a mirror pin (`mirror`), the tarball's own source already points
|
||||
/// at the pinned URL: it gets the components and suites, no arch scoping
|
||||
/// (the mirror serves every architecture by contract), and no ports
|
||||
/// entry — a source at the pinned URL counts as serving the target.
|
||||
/// Debian hosts are left alone: one mirror serves every architecture, so
|
||||
/// the host's own sources already cover the target — the os-release gate
|
||||
/// below is what makes that a data conclusion instead of hardcoding.
|
||||
@@ -106,6 +110,7 @@ pub fn ensure_repositories(
|
||||
arch: &str,
|
||||
series: &str,
|
||||
pocket: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
ctx: Arc<Context>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let local_arch = crate::get_current_arch();
|
||||
@@ -146,14 +151,19 @@ pub fn ensure_repositories(
|
||||
// Ensure all components are enabled for the primary architecture
|
||||
for source in &mut sources {
|
||||
// Official sources served by the local mirror (the primary archive
|
||||
// and its security sibling); ports serves the other architectures
|
||||
// and its security sibling), or the mirror-pinned source itself
|
||||
// (the chroot tarball was bootstrapped from it — see
|
||||
// `DebBuildOptions::mirror`); ports serves the other architectures
|
||||
// and is configured below instead
|
||||
if !crate::distro_info::is_mirror_source(local_mirror, &source.uri) {
|
||||
let pinned = mirror.is_some_and(|m| source.uri == m);
|
||||
if !pinned && !crate::distro_info::is_mirror_source(local_mirror, &source.uri) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Scope to local_arch if not already scoped
|
||||
if source.architectures.is_empty() {
|
||||
// Scope to local_arch if not already scoped — never under a pin:
|
||||
// the pinned mirror serves the target architecture too (its
|
||||
// contract), scoping it would cut the target apt off
|
||||
if !pinned && source.architectures.is_empty() {
|
||||
source.architectures.push(local_arch.clone());
|
||||
}
|
||||
|
||||
@@ -174,9 +184,14 @@ pub fn ensure_repositories(
|
||||
|
||||
// Check whether an existing source already serves the target
|
||||
// architecture from its mirror (e.g. the ports mirror for a
|
||||
// non-local arch); when cross-building for the local architecture,
|
||||
// the primary sources above already do
|
||||
// non-local arch), or from the pinned mirror — which serves every
|
||||
// architecture by contract, so its (unscoped) source counts; when
|
||||
// cross-building for the local architecture, the primary sources
|
||||
// above already do
|
||||
let has_target = sources.iter().any(|s| {
|
||||
if mirror.is_some_and(|m| s.uri == m) {
|
||||
return s.architectures.is_empty() || s.architectures.contains(&arch.to_string());
|
||||
}
|
||||
crate::distro_info::is_mirror_source(target_mirror, &s.uri)
|
||||
&& s.architectures.contains(&arch.to_string())
|
||||
});
|
||||
|
||||
+85
-15
@@ -4,6 +4,7 @@ use crate::deb::{Phase, enter_phase};
|
||||
use crate::interrupt::CleanupHookGuard;
|
||||
use crate::report::BuildView;
|
||||
use directories::ProjectDirs;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
@@ -251,19 +252,42 @@ fn is_mountpoint(path: &Path) -> bool {
|
||||
|
||||
/// The cached chroot tarball path for a series/arch, as used by the
|
||||
/// bootstrap: the record's fork gate compares the tarball's digest
|
||||
/// against the sha recorded in a base's manifest.
|
||||
/// against the sha recorded in a base's manifest. A mirror pin is hashed
|
||||
/// into the name (before the `-buildd.tar.xz` suffix prune matches), so
|
||||
/// a pinned tarball never collides with the distro-default one — a
|
||||
/// `--mirror` run over a stale distro tarball would silently resolve
|
||||
/// from the distro, exactly the lie the pin must not tell.
|
||||
pub(crate) fn chroot_tarball_path(
|
||||
series: &str,
|
||||
arch: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
) -> Result<PathBuf, Box<dyn Error>> {
|
||||
let proj_dirs =
|
||||
ProjectDirs::from("com", "pkh", "pkh").ok_or("Could not determine project directories")?;
|
||||
let tarball_filename = if let Some(a) = arch {
|
||||
format!("{}-{}-buildd.tar.xz", series, a)
|
||||
} else {
|
||||
format!("{}-buildd.tar.xz", series)
|
||||
Ok(proj_dirs
|
||||
.cache_dir()
|
||||
.join(chroot_tarball_filename(series, arch, mirror)))
|
||||
}
|
||||
|
||||
/// The cached chroot tarball's file name for a series/arch/mirror
|
||||
pub(crate) fn chroot_tarball_filename(
|
||||
series: &str,
|
||||
arch: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
) -> String {
|
||||
let target = match arch {
|
||||
Some(a) => format!("{series}-{a}"),
|
||||
None => series.to_string(),
|
||||
};
|
||||
Ok(proj_dirs.cache_dir().join(tarball_filename))
|
||||
match mirror {
|
||||
None => format!("{target}-buildd.tar.xz"),
|
||||
Some(m) => {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(m.as_bytes());
|
||||
let hash = format!("{:x}", hasher.finalize());
|
||||
format!("{target}-{}-buildd.tar.xz", &hash[..12])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Root of the shared apt download caches (`/var/tmp/pkh/apt-cache`),
|
||||
@@ -490,10 +514,11 @@ impl EphemeralContextGuard {
|
||||
pub async fn new_with_context(
|
||||
series: &str,
|
||||
arch: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
base_ctx: Arc<Context>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
Self::new_internal(None, series, arch, base_ctx, view, false, None).await
|
||||
Self::new_internal(None, series, arch, mirror, base_ctx, view, false, None).await
|
||||
}
|
||||
|
||||
/// Create the ephemeral build context inside `session`'s chroot.
|
||||
@@ -511,10 +536,12 @@ impl EphemeralContextGuard {
|
||||
/// * `ccache_dir` - The shared ccache directory to mount at the
|
||||
/// chroot's `/var/cache/pkh-ccache` (`pkh deb` computes it from the
|
||||
/// build identity; `None` leaves the chroot unshared)
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn new_for_session(
|
||||
session: Session,
|
||||
series: &str,
|
||||
arch: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
base_ctx: Arc<Context>,
|
||||
view: &dyn BuildView,
|
||||
keep_upper: bool,
|
||||
@@ -524,6 +551,7 @@ impl EphemeralContextGuard {
|
||||
Some(session),
|
||||
series,
|
||||
arch,
|
||||
mirror,
|
||||
base_ctx,
|
||||
view,
|
||||
keep_upper,
|
||||
@@ -532,10 +560,12 @@ impl EphemeralContextGuard {
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn new_internal(
|
||||
session: Option<Session>,
|
||||
series: &str,
|
||||
arch: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
base_ctx: Arc<Context>,
|
||||
view: &dyn BuildView,
|
||||
keep_upper: bool,
|
||||
@@ -603,6 +633,7 @@ impl EphemeralContextGuard {
|
||||
let apt_cache_lock = match Self::download_and_extract_chroot(
|
||||
series,
|
||||
arch,
|
||||
mirror,
|
||||
&chroot_path,
|
||||
base_ctx.clone(),
|
||||
view,
|
||||
@@ -633,7 +664,7 @@ impl EphemeralContextGuard {
|
||||
// cache to decide whether the record's staged tree is still
|
||||
// reusable.
|
||||
if let Some(s) = &session
|
||||
&& let Ok(tarball) = chroot_tarball_path(series, arch)
|
||||
&& let Ok(tarball) = chroot_tarball_path(series, arch, mirror)
|
||||
&& let Ok(sha) = session::sha256_file(&tarball)
|
||||
{
|
||||
s.set_tarball_sha256(&sha);
|
||||
@@ -683,6 +714,7 @@ impl EphemeralContextGuard {
|
||||
async fn download_and_extract_chroot(
|
||||
series: &str,
|
||||
arch: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
chroot_path: &PathBuf,
|
||||
ctx: Arc<context::Context>,
|
||||
view: &dyn BuildView,
|
||||
@@ -700,12 +732,7 @@ impl EphemeralContextGuard {
|
||||
let cache_dir = proj_dirs.cache_dir();
|
||||
fs::create_dir_all(cache_dir)?;
|
||||
|
||||
// Create tarball filename based on series and architecture
|
||||
let tarball_filename = if let Some(a) = arch {
|
||||
format!("{}-{}-buildd.tar.xz", series, a)
|
||||
} else {
|
||||
format!("{}-buildd.tar.xz", series)
|
||||
};
|
||||
let tarball_filename = chroot_tarball_filename(series, arch, mirror);
|
||||
let tarball_path = cache_dir.join(&tarball_filename);
|
||||
|
||||
// Check for existing lockfile, and wait for a timeout if it exists
|
||||
@@ -752,7 +779,7 @@ impl EphemeralContextGuard {
|
||||
arch
|
||||
);
|
||||
enter_phase(view, Phase::PreparingChroot);
|
||||
Self::download_chroot_tarball(series, arch, &tarball_path, ctx, view).await?;
|
||||
Self::download_chroot_tarball(series, arch, mirror, &tarball_path, ctx, view).await?;
|
||||
} else {
|
||||
log::debug!(
|
||||
"Using cached chroot tarball for {} (arch: {:?})",
|
||||
@@ -917,6 +944,7 @@ impl EphemeralContextGuard {
|
||||
async fn download_chroot_tarball(
|
||||
series: &str,
|
||||
arch: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
tarball_path: &Path,
|
||||
ctx: Arc<context::Context>,
|
||||
view: &dyn BuildView,
|
||||
@@ -953,6 +981,14 @@ impl EphemeralContextGuard {
|
||||
cmd.arg(series)
|
||||
.arg(tarball_path.to_string_lossy().to_string());
|
||||
|
||||
// The mirror positional: without it mmdebstrap writes the distro
|
||||
// defaults into the tarball's apt sources verbatim; with it, the
|
||||
// tarball's sources.list carries the mirror, so every apt inside
|
||||
// the build follows.
|
||||
if let Some(m) = mirror {
|
||||
cmd.arg(m);
|
||||
}
|
||||
|
||||
if let Some(s) = view.sink() {
|
||||
cmd.capture(s);
|
||||
}
|
||||
@@ -1287,6 +1323,40 @@ impl Drop for EphemeralContextGuard {
|
||||
mod chroot_cleanup_tests {
|
||||
use super::*;
|
||||
|
||||
/// The tarball cache name: per series/arch as before, and a distinct
|
||||
/// deterministic name per pinned mirror — hashed BEFORE the
|
||||
/// `-buildd.tar.xz` suffix, which is the pattern prune matches (a
|
||||
/// pinned tarball pruning like any other).
|
||||
#[test]
|
||||
fn tarball_filename_keys_the_mirror_in() {
|
||||
assert_eq!(
|
||||
chroot_tarball_filename("noble", None, None),
|
||||
"noble-buildd.tar.xz"
|
||||
);
|
||||
assert_eq!(
|
||||
chroot_tarball_filename("noble", Some("riscv64"), None),
|
||||
"noble-riscv64-buildd.tar.xz"
|
||||
);
|
||||
|
||||
let pinned =
|
||||
chroot_tarball_filename("noble", Some("amd64"), Some("http://localhost:3142/ubuntu"));
|
||||
assert!(pinned.starts_with("noble-amd64-"));
|
||||
assert!(pinned.ends_with("-buildd.tar.xz"));
|
||||
// Deterministic, and distinct per mirror (and from the unpinned).
|
||||
assert_eq!(
|
||||
pinned,
|
||||
chroot_tarball_filename("noble", Some("amd64"), Some("http://localhost:3142/ubuntu"))
|
||||
);
|
||||
assert_ne!(
|
||||
pinned,
|
||||
chroot_tarball_filename("noble", Some("amd64"), Some("http://localhost:3143/ubuntu"))
|
||||
);
|
||||
assert_ne!(
|
||||
pinned,
|
||||
chroot_tarball_filename("noble", Some("amd64"), None)
|
||||
);
|
||||
}
|
||||
|
||||
/// /proc/mounts path fields use octal escapes for whitespace and
|
||||
/// backslashes; anything else must be kept verbatim.
|
||||
#[test]
|
||||
|
||||
+9
-4
@@ -94,6 +94,7 @@ pub async fn build(
|
||||
arch: &str,
|
||||
series: &str,
|
||||
pocket: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
build_root: &str,
|
||||
package_dir: &Path,
|
||||
cross: bool,
|
||||
@@ -146,7 +147,7 @@ pub async fn build(
|
||||
if cross {
|
||||
log::debug!("Setting up environment for local cross build...");
|
||||
cross::setup_environment(&mut env, arch, ctx.clone())?;
|
||||
cross::ensure_repositories(arch, series, pocket, ctx.clone())?;
|
||||
cross::ensure_repositories(arch, series, pocket, mirror, ctx.clone())?;
|
||||
}
|
||||
|
||||
let mut sources = apt::sources::load(Some(ctx.clone()))?;
|
||||
@@ -202,11 +203,13 @@ pub async fn build(
|
||||
// `uri.contains("ubuntu")` gate also caught third-party repositories
|
||||
// whose URL merely mentions Ubuntu; the mirror-data check leaves them
|
||||
// alone. 'universe' is only added when Ubuntu's component list still
|
||||
// carries it.
|
||||
// carries it. A mirror-pinned source (the tarball was bootstrapped
|
||||
// from the mirror) gets the same treatment: it IS the archive here.
|
||||
let ubuntu_components = crate::distro_info::get_dist_components("ubuntu")?;
|
||||
if ubuntu_components.iter().any(|c| c == "universe") {
|
||||
for source in &mut sources {
|
||||
if crate::distro_info::is_official_source("ubuntu", &source.uri)
|
||||
if (crate::distro_info::is_official_source("ubuntu", &source.uri)
|
||||
|| mirror.is_some_and(|m| source.uri == m))
|
||||
&& !source.components.contains(&"universe".to_string())
|
||||
{
|
||||
source.components.push("universe".to_string());
|
||||
@@ -221,7 +224,9 @@ pub async fn build(
|
||||
let pocket_suite = format!("{series}-{pocket_name}");
|
||||
log::info!("Enabling pocket '{}' for build dependencies", pocket_suite);
|
||||
for source in &mut sources {
|
||||
if crate::deb::is_archive_source(&source.uri) && !source.suite.contains(&pocket_suite) {
|
||||
if crate::deb::is_archive_source_pinned(&source.uri, mirror)
|
||||
&& !source.suite.contains(&pocket_suite)
|
||||
{
|
||||
source.suite.push(pocket_suite.clone());
|
||||
modified = true;
|
||||
}
|
||||
|
||||
+55
-1
@@ -101,6 +101,15 @@ pub struct DebBuildOptions<'a> {
|
||||
pub series: Option<String>,
|
||||
/// Distribution pocket to resolve build-dependencies from.
|
||||
pub pocket: Option<String>,
|
||||
/// Base URL the build environment resolves from (`pkh deb --mirror`):
|
||||
/// passed to mmdebstrap as the mirror positional, so the chroot
|
||||
/// tarball's apt sources carry it and every apt inside the build
|
||||
/// follows. A pinned runner (a caching proxy, a chosen mirror) uses
|
||||
/// it to resolve every package from one URL; the mirror must serve
|
||||
/// every suite and architecture the build resolves — the security
|
||||
/// pocket and, for a cross build, the target architecture's archive
|
||||
/// included. PPAs and other third-party sources are left alone.
|
||||
pub mirror: Option<String>,
|
||||
/// Source tree to build; defaults to the process working directory.
|
||||
pub cwd: Option<PathBuf>,
|
||||
/// Cross-compile for the target architecture instead of using
|
||||
@@ -211,6 +220,7 @@ impl Default for DebBuildOptions<'_> {
|
||||
arch: None,
|
||||
series: None,
|
||||
pocket: None,
|
||||
mirror: None,
|
||||
cwd: None,
|
||||
cross: false,
|
||||
mode: None,
|
||||
@@ -255,6 +265,7 @@ async fn build_binary_package_impl(
|
||||
ref arch,
|
||||
ref series,
|
||||
ref pocket,
|
||||
ref mirror,
|
||||
ref cwd,
|
||||
cross,
|
||||
ref mode,
|
||||
@@ -268,6 +279,17 @@ async fn build_binary_package_impl(
|
||||
} = opts;
|
||||
let cwd = cwd.as_deref().unwrap_or_else(|| Path::new("."));
|
||||
|
||||
// A mirror without a scheme cannot be an apt source URI: fail before
|
||||
// any chroot work, where the failure would surface as an opaque
|
||||
// apt-get update error.
|
||||
if let Some(mirror) = mirror
|
||||
&& !mirror.contains("://")
|
||||
{
|
||||
return Err(
|
||||
format!("Invalid mirror '{mirror}': expected a base URL (scheme://host/path)").into(),
|
||||
);
|
||||
}
|
||||
|
||||
// Parse changelog to get package name, version and series
|
||||
let changelog_path = cwd.join("debian/changelog");
|
||||
let (package, version, package_series) =
|
||||
@@ -347,6 +369,7 @@ async fn build_binary_package_impl(
|
||||
&version,
|
||||
series,
|
||||
chroot_arch,
|
||||
mirror.as_deref(),
|
||||
) {
|
||||
Ok((record, keep)) => {
|
||||
keep_upper = keep;
|
||||
@@ -395,12 +418,14 @@ async fn build_binary_package_impl(
|
||||
|
||||
// The shared ccache of this target — skipped with --no-ccache.
|
||||
let ccache_dir = (session_enabled && ccache).then(|| session::ccache_dir(&identity));
|
||||
let mirror_arg = mirror.as_deref();
|
||||
let mut guard = if let Some(s) = session.clone() {
|
||||
Some(
|
||||
ephemeral::EphemeralContextGuard::new_for_session(
|
||||
s,
|
||||
series,
|
||||
chroot_arch,
|
||||
mirror_arg,
|
||||
base_ctx.clone(),
|
||||
view,
|
||||
keep_upper,
|
||||
@@ -413,6 +438,7 @@ async fn build_binary_package_impl(
|
||||
ephemeral::EphemeralContextGuard::new_with_context(
|
||||
series,
|
||||
chroot_arch,
|
||||
mirror_arg,
|
||||
base_ctx.clone(),
|
||||
view,
|
||||
)
|
||||
@@ -533,6 +559,7 @@ async fn build_binary_package_impl(
|
||||
arch,
|
||||
series,
|
||||
pocket.as_deref(),
|
||||
mirror.as_deref(),
|
||||
&build_root,
|
||||
&package_dir,
|
||||
cross,
|
||||
@@ -604,10 +631,11 @@ fn resolve_record(
|
||||
version: &str,
|
||||
series: &str,
|
||||
chroot_arch: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
) -> Result<(session::Session, bool), Box<dyn Error>> {
|
||||
resolve_record_in(
|
||||
&session::sessions_root(),
|
||||
&session::cache_tarball_sha256(series, chroot_arch),
|
||||
&session::cache_tarball_sha256(series, chroot_arch, mirror),
|
||||
incremental,
|
||||
host_tree,
|
||||
identity,
|
||||
@@ -900,6 +928,14 @@ pub(crate) fn is_archive_source(uri: &str) -> bool {
|
||||
.any(|dist| crate::distro_info::is_official_source(dist, uri))
|
||||
}
|
||||
|
||||
/// Whether `uri` counts as an archive source under a mirror pin: a
|
||||
/// distribution archive source, or already the pinned mirror URL itself
|
||||
/// — sources rewritten to it no longer carry an official host, but they
|
||||
/// are exactly the sources a previous pass or build pinned.
|
||||
pub(crate) fn is_archive_source_pinned(uri: &str, mirror: Option<&str>) -> bool {
|
||||
is_archive_source(uri) || mirror.is_some_and(|m| uri == m)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -930,6 +966,24 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Under a mirror pin, the pinned URL itself counts as an archive
|
||||
/// source (a previous pass or build rewrote official sources to it);
|
||||
/// without a pin the check is the plain archive check.
|
||||
#[test]
|
||||
fn archive_sources_under_a_mirror_pin() {
|
||||
let mirror = "http://localhost:3142/ubuntu";
|
||||
assert!(is_archive_source_pinned(mirror, Some(mirror)));
|
||||
assert!(is_archive_source_pinned(
|
||||
"http://archive.ubuntu.com/ubuntu",
|
||||
Some(mirror)
|
||||
));
|
||||
assert!(!is_archive_source_pinned(
|
||||
"https://ppa.launchpadcontent.net/user/ppa/ubuntu",
|
||||
Some(mirror)
|
||||
));
|
||||
assert!(!is_archive_source_pinned(mirror, None));
|
||||
}
|
||||
|
||||
/// An unshare context mapped over `chroot_root`, parented on a local
|
||||
/// context like the ephemeral build contexts are: exists/list_files/
|
||||
/// is_dir answer through the path mapping, no namespace privileges
|
||||
|
||||
+9
-3
@@ -1171,9 +1171,15 @@ pub(crate) fn lock_is_held(path: &Path) -> io::Result<bool> {
|
||||
/// The digest of the chroot tarball a build of `series`/`arch` would
|
||||
/// extract, empty when the cache does not hold one yet (the bootstrap
|
||||
/// downloads it during the run). This is the fork gate's reference
|
||||
/// against the sha recorded in a base's manifest.
|
||||
pub(crate) fn cache_tarball_sha256(series: &str, arch: Option<&str>) -> String {
|
||||
crate::deb::ephemeral::chroot_tarball_path(series, arch)
|
||||
/// against the sha recorded in a base's manifest. `mirror` keys the
|
||||
/// cache (see [`crate::deb::ephemeral::chroot_tarball_path`]), so records
|
||||
/// never fork across a mirror change.
|
||||
pub(crate) fn cache_tarball_sha256(
|
||||
series: &str,
|
||||
arch: Option<&str>,
|
||||
mirror: Option<&str>,
|
||||
) -> String {
|
||||
crate::deb::ephemeral::chroot_tarball_path(series, arch, mirror)
|
||||
.ok()
|
||||
.and_then(|path| sha256_file(&path).ok())
|
||||
.unwrap_or_default()
|
||||
|
||||
@@ -355,6 +355,8 @@ fn main() {
|
||||
.arg(arg!(-a --arch <arch> "Target architecture").required(false))
|
||||
.arg(arg!(-p --pocket <pocket> "Build against dependencies from a specific distribution pocket (updates, security, proposed)").required(false)
|
||||
.long_help("Build against dependencies from a specific distribution pocket (e.g. updates, security, proposed).\nThe '<series>-<pocket>' suite will be enabled on archive sources when resolving build-dependencies."))
|
||||
.arg(arg!(--mirror <url> "Resolve every package of the build from a specific mirror (base URL)").required(false)
|
||||
.long_help("Build the chroot and resolve every package from the given base URL — a caching proxy or a chosen mirror on a pinned runner — instead of the distro default: the mirror is passed to mmdebstrap at bootstrap, so the chroot tarball's apt sources carry it and every apt inside the build follows.\nThe mirror must serve every suite and architecture the build resolves: the security pocket and, for cross builds, the target architecture's archive included. PPAs and other third-party sources are left alone. The tarball cache is keyed per mirror, so pinned and unpinned builds keep separate tarballs."))
|
||||
.arg(arg!(--ppa <ppa> "Build the package adding a specific PPA for dependencies (can be specified multiple times)")
|
||||
.long_help("Build the package adding a specific PPA for dependencies. Can be specified multiple times.").required(false).action(clap::ArgAction::Append))
|
||||
.arg(arg!(--inject <package> "Inject a package into the build environment (can be specified multiple times)")
|
||||
@@ -1002,6 +1004,7 @@ fn main() {
|
||||
arch,
|
||||
series,
|
||||
pocket,
|
||||
mirror: sub_matches.get_one::<String>("mirror").cloned(),
|
||||
cwd: Some(cwd.clone()),
|
||||
cross,
|
||||
mode,
|
||||
|
||||
@@ -41,6 +41,7 @@ impl Bed {
|
||||
let guard = EphemeralContextGuard::new_with_context(
|
||||
series,
|
||||
chroot_arch.as_deref(),
|
||||
None,
|
||||
base_ctx,
|
||||
view,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user