Files
pkh/src/context/local.rs
T
vhaudiquet 86612efb00
CI / build (push) Waiting to run
CI / test (push) Blocked by required conditions
CI / snap (push) Blocked by required conditions
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.
2026-08-24 17:33:44 +02:00

193 lines
5.8 KiB
Rust

/// Local context: execute commands locally
/// Context driver: Does nothing
use super::api::{ContextDriver, LineSink, Stream};
use super::capture::pump;
use std::io;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::Arc;
use std::time::SystemTime;
pub struct LocalDriver;
impl ContextDriver for LocalDriver {
fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result<PathBuf> {
let dest_root_path = Path::new(dest_root);
let dest = dest_root_path.join(src.file_name().unwrap_or(src.as_os_str()));
if src != dest {
// Copy src inside dest_root
self.copy_path(src, &dest)?;
}
dest.canonicalize()
}
fn create_temp_dir(&self) -> io::Result<String> {
// Generate a unique temporary directory name with random string
let base_timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let mut attempt = 0;
loop {
let work_dir_name = if attempt == 0 {
format!("pkh-{}", base_timestamp)
} else {
format!("pkh-{}-{}", base_timestamp, attempt)
};
let temp_dir_path = std::env::temp_dir().join(&work_dir_name);
// Check if directory already exists
if temp_dir_path.exists() {
attempt += 1;
continue;
}
// Create the directory
std::fs::create_dir_all(&temp_dir_path)?;
// Return the path as a string
return Ok(temp_dir_path.to_string_lossy().to_string());
}
}
fn retrieve_path(&self, src: &Path, dest: &Path) -> io::Result<()> {
self.copy_path(src, dest)
}
fn list_files(&self, path: &Path) -> io::Result<Vec<PathBuf>> {
let mut entries = Vec::new();
for entry in std::fs::read_dir(path)? {
let entry = entry?;
entries.push(entry.path());
}
Ok(entries)
}
fn run(
&self,
program: &str,
args: &[String],
env: &[(String, String)],
cwd: Option<&str>,
) -> io::Result<std::process::ExitStatus> {
let mut cmd = Command::new(program);
cmd.args(args).envs(env.iter().map(|(k, v)| (k, v)));
if let Some(dir) = cwd {
cmd.current_dir(dir);
}
cmd.status()
}
fn run_captured(
&self,
program: &str,
args: &[String],
env: &[(String, String)],
cwd: Option<&str>,
sink: Arc<dyn LineSink>,
) -> io::Result<std::process::ExitStatus> {
let mut cmd = Command::new(program);
cmd.args(args).envs(env.iter().map(|(k, v)| (k, v)));
// Best-effort: ask children not to emit ANSI colors; captured lines are
// stripped anyway.
cmd.env("NO_COLOR", "1");
if let Some(dir) = cwd {
cmd.current_dir(dir);
}
cmd.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null());
let mut child = cmd.spawn()?;
let stdout = child.stdout.take();
let stderr = child.stderr.take();
// One reader thread per stream; lines are forwarded to the sink as
// they arrive so the UI stays live.
let out_sink = sink.clone();
let t_out =
stdout.map(|r| std::thread::spawn(move || pump(r, Stream::Stdout, out_sink.as_ref())));
let err_sink = sink.clone();
let t_err =
stderr.map(|r| std::thread::spawn(move || pump(r, Stream::Stderr, err_sink.as_ref())));
let status = child.wait();
if let Some(t) = t_out {
let _ = t.join();
}
if let Some(t) = t_err {
let _ = t.join();
}
status
}
fn run_output(
&self,
program: &str,
args: &[String],
env: &[(String, String)],
cwd: Option<&str>,
) -> io::Result<std::process::Output> {
let mut cmd = Command::new(program);
cmd.args(args).envs(env.iter().map(|(k, v)| (k, v)));
if let Some(dir) = cwd {
cmd.current_dir(dir);
}
cmd.output()
}
fn copy_path(&self, src: &Path, dest: &Path) -> io::Result<()> {
copy_dir_recursive(src, dest)
}
fn read_file(&self, path: &Path) -> io::Result<String> {
std::fs::read_to_string(path)
}
fn write_file(&self, path: &Path, content: &str) -> io::Result<()> {
std::fs::write(path, content)
}
fn exists(&self, path: &Path) -> io::Result<bool> {
Ok(path.exists())
}
}
fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
// Reproduce symlinks as symlinks rather than following them, so that
// dangling/absolute symlinks do not abort the copy.
if std::fs::symlink_metadata(src)?.file_type().is_symlink() {
let target = std::fs::read_link(src)?;
let _ = std::fs::remove_file(dest);
return symlink(&target, dest);
}
if src.is_dir() {
std::fs::create_dir_all(dest)?;
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)?;
}
} else {
std::fs::copy(src, dest)?;
}
Ok(())
}