pull,context: preserve file mtimes when copying sources
std::fs::copy resets the destination mtime to "now", which breaks timestamp-based build systems (autotools/gnulib). Packages like 'hello' from Debian sid ship pre-generated files alongside their prerequisites; when the copy flattens all mtimes, make considers the generated targets out-of-date and tries to regenerate them with tools (e.g. gperf) that are not declared build-dependencies, failing the build. Restore the source modification and access times after every file copy in pull::copy_dir_all, pull::fetch_archive_sources merge step, and context::unshare copy_dir_recursive/ensure_available.
This commit is contained in:
+59
-13
@@ -77,24 +77,70 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
|
|||||||
// Recursively copy subdirectories
|
// Recursively copy subdirectories
|
||||||
copy_dir_recursive(&src_path, &dest_path)?;
|
copy_dir_recursive(&src_path, &dest_path)?;
|
||||||
} else {
|
} else {
|
||||||
// Copy regular files
|
// Copy regular files, preserving the source modification and
|
||||||
std::fs::copy(&src_path, &dest_path).map_err(|e| {
|
// access times. This is important for autotools/gnulib-based
|
||||||
io::Error::new(
|
// packages (e.g. 'hello' from Debian sid) that ship pre-generated
|
||||||
e.kind(),
|
// files alongside their prerequisites: if the copy resets the
|
||||||
format!(
|
// mtime to "now", the prerequisites appear as new as the
|
||||||
"Failed to copy '{}' to '{}': {}",
|
// generated targets and `make` tries to regenerate them using
|
||||||
src_path.display(),
|
// tools (like gperf) that are not declared build-dependencies.
|
||||||
dest_path.display(),
|
copy_file_with_times(&src_path, &dest_path)?;
|
||||||
e
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copy a single regular file from `src` to `dest`, preserving the source's
|
||||||
|
/// modification and access times.
|
||||||
|
///
|
||||||
|
/// `std::fs::copy` resets the destination mtime to "now", which breaks
|
||||||
|
/// timestamp-based build systems (autotools/gnulib) that ship pre-generated
|
||||||
|
/// files alongside their prerequisites. Restoring the original timestamps
|
||||||
|
/// prevents `make` from needlessly regenerating those files with tools that
|
||||||
|
/// may not be installed (e.g. `gperf`).
|
||||||
|
fn copy_file_with_times(src: &Path, dest: &Path) -> io::Result<()> {
|
||||||
|
std::fs::copy(src, dest).map_err(|e| {
|
||||||
|
io::Error::new(
|
||||||
|
e.kind(),
|
||||||
|
format!(
|
||||||
|
"Failed to copy '{}' to '{}': {}",
|
||||||
|
src.display(),
|
||||||
|
dest.display(),
|
||||||
|
e
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Restore the original timestamps on the destination file.
|
||||||
|
let metadata = std::fs::metadata(src).map_err(|e| {
|
||||||
|
io::Error::new(
|
||||||
|
e.kind(),
|
||||||
|
format!("Failed to read metadata of '{}': {}", src.display(), e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut times = std::fs::FileTimes::new();
|
||||||
|
let mut have_times = false;
|
||||||
|
if let Ok(mtime) = metadata.modified() {
|
||||||
|
times = times.set_modified(mtime);
|
||||||
|
have_times = true;
|
||||||
|
}
|
||||||
|
if let Ok(atime) = metadata.accessed() {
|
||||||
|
times = times.set_accessed(atime);
|
||||||
|
have_times = true;
|
||||||
|
}
|
||||||
|
if have_times && let Ok(dest_file) = std::fs::File::open(dest) {
|
||||||
|
let _ = dest_file.set_times(times).map_err(|e| {
|
||||||
|
io::Error::new(
|
||||||
|
e.kind(),
|
||||||
|
format!("Failed to set times on '{}': {}", dest.display(), e),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl ContextDriver for UnshareDriver {
|
impl ContextDriver for UnshareDriver {
|
||||||
fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result<PathBuf> {
|
fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result<PathBuf> {
|
||||||
// Construct the destination path inside the chroot
|
// Construct the destination path inside the chroot
|
||||||
@@ -125,7 +171,7 @@ impl ContextDriver for UnshareDriver {
|
|||||||
dest_path.display()
|
dest_path.display()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
std::fs::copy(src, &dest_path)?;
|
copy_file_with_times(src, &dest_path)?;
|
||||||
debug!("Copied file {} to {}", src.display(), dest_path.display());
|
debug!("Copied file {} to {}", src.display(), dest_path.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
@@ -113,13 +113,46 @@ fn copy_dir_all(src: &Path, dst: &Path) -> Result<(), Box<dyn Error>> {
|
|||||||
} else if src_path.is_dir() {
|
} else if src_path.is_dir() {
|
||||||
copy_dir_all(&src_path, &dst_path)?;
|
copy_dir_all(&src_path, &dst_path)?;
|
||||||
} else {
|
} else {
|
||||||
|
// Copy the file, preserving the source modification and access
|
||||||
|
// times. This is important for autotools/gnulib-based packages
|
||||||
|
// (e.g. 'hello' from Debian sid) that ship pre-generated files
|
||||||
|
// alongside their prerequisites: if the copy resets the mtime to
|
||||||
|
// "now", the prerequisites appear as new as the generated targets
|
||||||
|
// and `make` tries to regenerate them using tools (like gperf)
|
||||||
|
// that are not declared build-dependencies.
|
||||||
std::fs::copy(&src_path, &dst_path)?;
|
std::fs::copy(&src_path, &dst_path)?;
|
||||||
|
copy_file_times(&src_path, &dst_path)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Restore the modification and access times of `dest` to match `src`.
|
||||||
|
///
|
||||||
|
/// `std::fs::copy` resets the destination mtime to "now", which breaks
|
||||||
|
/// timestamp-based build systems (autotools/gnulib) that ship pre-generated
|
||||||
|
/// files alongside their prerequisites. Restoring the original timestamps
|
||||||
|
/// prevents `make` from needlessly regenerating those files with tools that
|
||||||
|
/// may not be installed (e.g. `gperf`).
|
||||||
|
fn copy_file_times(src: &Path, dest: &Path) -> Result<(), Box<dyn Error>> {
|
||||||
|
let metadata = std::fs::metadata(src)?;
|
||||||
|
let mut times = std::fs::FileTimes::new();
|
||||||
|
let mut have_times = false;
|
||||||
|
if let Ok(mtime) = metadata.modified() {
|
||||||
|
times = times.set_modified(mtime);
|
||||||
|
have_times = true;
|
||||||
|
}
|
||||||
|
if let Ok(atime) = metadata.accessed() {
|
||||||
|
times = times.set_accessed(atime);
|
||||||
|
have_times = true;
|
||||||
|
}
|
||||||
|
if have_times && let Ok(dest_file) = std::fs::File::open(dest) {
|
||||||
|
let _ = dest_file.set_times(times);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper function to extract tar archive with progress tracking
|
/// Helper function to extract tar archive with progress tracking
|
||||||
fn extract_tar_archive<D, F>(
|
fn extract_tar_archive<D, F>(
|
||||||
file_path: &Path,
|
file_path: &Path,
|
||||||
@@ -467,6 +500,7 @@ async fn fetch_archive_sources(
|
|||||||
copy_dir_all(&sub_path, &target_path)?;
|
copy_dir_all(&sub_path, &target_path)?;
|
||||||
} else {
|
} else {
|
||||||
std::fs::copy(&sub_path, &target_path)?;
|
std::fs::copy(&sub_path, &target_path)?;
|
||||||
|
copy_file_times(&sub_path, &target_path)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::fs::remove_dir_all(&src_dir)?;
|
std::fs::remove_dir_all(&src_dir)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user