deb: strip VCS metadata from prepared build trees
Copying a git checkout verbatim into /tmp/pkh-build-*/ ships '.git', flipping autotools' building-from-VCS detection (GNU hello's BUILD_FROM_GIT): the shipped man page gets cleaned and regenerated via help2man, which is correctly not in Build-Depends. Skip VCS dirs (.git/.hg/.svn/.bzr/CVS) in local/unshare/ssh copies, and prune them after an unshare overlayfs mount.
This commit is contained in:
@@ -173,6 +173,15 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
|
||||
for entry in std::fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
// Never ship VCS metadata into the build tree: its presence
|
||||
// flips autotools 'building from VCS' detection (see
|
||||
// is_vcs_dir_name) and activates maintainer-only regeneration
|
||||
// rules requiring undeclared tools (e.g. help2man).
|
||||
if path.symlink_metadata().map(|m| m.is_dir()).unwrap_or(false)
|
||||
&& super::is_vcs_dir_name(&entry.file_name())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let dest_path = dest.join(entry.file_name());
|
||||
copy_dir_recursive(&path, &dest_path)?;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,49 @@ pub fn current() -> Arc<Context> {
|
||||
manager::MANAGER.current()
|
||||
}
|
||||
|
||||
/// Version-control metadata directories that must never leak into a
|
||||
/// prepared build tree.
|
||||
///
|
||||
/// Their presence flips autotools' "building from VCS" detection (e.g. GNU
|
||||
/// hello's `BUILD_FROM_GIT`, triggered by a `.git` directory next to
|
||||
/// `configure.ac`), which activates maintainer-only regeneration rules
|
||||
/// requiring tools that are deliberately not declared as build-dependencies
|
||||
/// (e.g. `help2man`). Source packages produced by dpkg-source never contain
|
||||
/// them, so package builds must not see them either.
|
||||
pub(crate) fn is_vcs_dir_name(name: &std::ffi::OsStr) -> bool {
|
||||
matches!(
|
||||
name.to_str(),
|
||||
Some(".git") | Some(".hg") | Some(".svn") | Some(".bzr") | Some("CVS")
|
||||
)
|
||||
}
|
||||
|
||||
/// Recursively remove version-control metadata directories below `root`.
|
||||
///
|
||||
/// Used after an overlay mount, where the source tree is exposed verbatim
|
||||
/// and entries cannot be filtered during the copy.
|
||||
pub(crate) fn prune_vcs_dirs(root: &std::path::Path) -> std::io::Result<()> {
|
||||
let mut pending = vec![root.to_path_buf()];
|
||||
while let Some(dir) = pending.pop() {
|
||||
for entry in std::fs::read_dir(&dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
// symlink_metadata: never follow symlinks while pruning.
|
||||
let Ok(meta) = std::fs::symlink_metadata(&path) else {
|
||||
continue;
|
||||
};
|
||||
if meta.is_dir() {
|
||||
if is_vcs_dir_name(&entry.file_name()) {
|
||||
log::debug!("Removing VCS metadata from build tree: {}", path.display());
|
||||
std::fs::remove_dir_all(&path)?;
|
||||
} else {
|
||||
pending.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -178,4 +221,43 @@ mod tests {
|
||||
std::path::Path::new("/nonexistent/target")
|
||||
);
|
||||
}
|
||||
|
||||
/// Copying a source tree into a build context must strip version-control
|
||||
/// metadata directories at any depth: their presence flips autotools
|
||||
/// "building from git" detection and activates maintainer-only rules
|
||||
/// needing undeclared tools (e.g. help2man for GNU hello's man page).
|
||||
#[test]
|
||||
fn test_ensure_available_strips_vcs_metadata() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let src_root = temp_dir.path().join("pkg");
|
||||
fs::create_dir_all(src_root.join(".git/objects")).unwrap();
|
||||
fs::write(src_root.join(".git/HEAD"), "ref: refs/heads/main").unwrap();
|
||||
fs::create_dir_all(src_root.join("src/.svn")).unwrap();
|
||||
fs::write(src_root.join("src/hello.c"), "int main() {}").unwrap();
|
||||
|
||||
let ctx = Context::new(ContextConfig::Local);
|
||||
let dest = ctx.ensure_available(&src_root, "/tmp").unwrap();
|
||||
|
||||
assert!(dest.join("src/hello.c").exists());
|
||||
assert!(!dest.join(".git").exists());
|
||||
assert!(!dest.join("src/.svn").exists());
|
||||
}
|
||||
|
||||
/// The overlay-mount path exposes the tree verbatim, so pruning happens
|
||||
/// after the fact: nested VCS metadata must be removed recursively.
|
||||
#[test]
|
||||
fn test_prune_vcs_dirs_removes_nested_metadata() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let root = temp_dir.path().join("tree");
|
||||
fs::create_dir_all(root.join("a/.git/objects")).unwrap();
|
||||
fs::create_dir_all(root.join("b/c/CVS")).unwrap();
|
||||
fs::write(root.join("a/.git/HEAD"), "ref").unwrap();
|
||||
fs::write(root.join("b/keep.txt"), "x").unwrap();
|
||||
|
||||
prune_vcs_dirs(&root).unwrap();
|
||||
|
||||
assert!(!root.join("a/.git").exists());
|
||||
assert!(!root.join("b/c/CVS").exists());
|
||||
assert!(root.join("b/keep.txt").exists());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +315,14 @@ impl SshDriver {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let name = entry.file_name();
|
||||
// Skip VCS metadata (see is_vcs_dir_name): shipping it into
|
||||
// the remote build tree would flip autotools 'building from
|
||||
// VCS' detection and activate maintainer-only rules.
|
||||
if path.symlink_metadata().map(|m| m.is_dir()).unwrap_or(false)
|
||||
&& super::is_vcs_dir_name(&name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let dest_path = dest.join(name);
|
||||
Self::upload_recursive(sftp, &path, &dest_path)?;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,14 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
// Never ship VCS metadata into the build tree: its presence flips
|
||||
// autotools 'building from VCS' detection (see is_vcs_dir_name) and
|
||||
// activates maintainer-only regeneration rules requiring tools that
|
||||
// are not declared build-dependencies (e.g. help2man).
|
||||
if metadata.is_dir() && super::is_vcs_dir_name(&entry.file_name()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if metadata.file_type().is_symlink() {
|
||||
let target = std::fs::read_link(&src_path).map_err(|e| {
|
||||
io::Error::new(
|
||||
@@ -428,6 +436,20 @@ impl UnshareDriver {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(dest_path.to_path_buf());
|
||||
|
||||
// The overlay exposes the source tree verbatim: strip VCS metadata
|
||||
// (e.g. '.git') whose presence would flip autotools 'building from
|
||||
// git' detection and activate maintainer-only regeneration rules
|
||||
// (see is_vcs_dir_name). Removals land as whiteouts in the upper
|
||||
// layer, leaving the lowerdir untouched.
|
||||
if let Err(e) = super::prune_vcs_dirs(dest_path) {
|
||||
log::warn!(
|
||||
"Failed to prune VCS metadata from '{}': {}",
|
||||
dest_path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user