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:
+51
-5
@@ -77,19 +77,65 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
|
||||
// Recursively copy subdirectories
|
||||
copy_dir_recursive(&src_path, &dest_path)?;
|
||||
} else {
|
||||
// Copy regular files
|
||||
std::fs::copy(&src_path, &dest_path).map_err(|e| {
|
||||
// Copy regular files, 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.
|
||||
copy_file_with_times(&src_path, &dest_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
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_path.display(),
|
||||
dest_path.display(),
|
||||
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(())
|
||||
@@ -125,7 +171,7 @@ impl ContextDriver for UnshareDriver {
|
||||
dest_path.display()
|
||||
);
|
||||
} else {
|
||||
std::fs::copy(src, &dest_path)?;
|
||||
copy_file_with_times(src, &dest_path)?;
|
||||
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() {
|
||||
copy_dir_all(&src_path, &dst_path)?;
|
||||
} 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)?;
|
||||
copy_file_times(&src_path, &dst_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
fn extract_tar_archive<D, F>(
|
||||
file_path: &Path,
|
||||
@@ -467,6 +500,7 @@ async fn fetch_archive_sources(
|
||||
copy_dir_all(&sub_path, &target_path)?;
|
||||
} else {
|
||||
std::fs::copy(&sub_path, &target_path)?;
|
||||
copy_file_times(&sub_path, &target_path)?;
|
||||
}
|
||||
}
|
||||
std::fs::remove_dir_all(&src_dir)?;
|
||||
|
||||
Reference in New Issue
Block a user