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:
+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
|
||||
|
||||
Reference in New Issue
Block a user