use crate::compiler::strict_paths::StrictPathsMode;
use crate::core::NormalizedPath;
use std::path::Path;
use std::process::ExitCode;
use super::daemon::{ensure_daemon, which_on_path};
use super::util::{connect, exit_code_from_i32, resolve_endpoint, run_async, slurp_stdin_if_piped};
fn run_passthrough(args: &[String]) -> ExitCode {
let tool = &args[0];
let tool_args = if args.len() > 1 { &args[1..] } else { &[] };
let resolved = resolve_compiler_path(tool);
match std::process::Command::new(&resolved)
.args(tool_args)
.status()
{
Ok(status) => exit_code_from_i32(status.code().unwrap_or(1)),
Err(e) => {
eprintln!("zccache: failed to run {}: {e}", resolved.display());
ExitCode::FAILURE
}
}
}
fn run_rustfmt_cached(rustfmt_path: &Path, args: &[String], cwd: &Path) -> ExitCode {
use crate::compiler::parse_rustfmt::{find_rustfmt_config, parse_rustfmt_invocation};
let parsed = match parse_rustfmt_invocation(args) {
Some(p) => p,
None => {
return run_tool_direct(rustfmt_path, args);
}
};
let context_hash = {
let mut hasher = crate::hash::StreamHasher::new();
hasher.update(b"zccache-fmt-v1");
if let Ok(bin_hash) = crate::hash::hash_file(rustfmt_path) {
hasher.update(bin_hash.as_bytes());
} else {
hasher.update(b"unknown-binary");
}
let config_path = parsed
.config_path
.clone()
.or_else(|| find_rustfmt_config(cwd));
if let Some(ref cfg) = config_path {
if let Ok(cfg_hash) = crate::hash::hash_file(cfg) {
hasher.update(cfg_hash.as_bytes());
}
}
for flag in &parsed.flags {
hasher.update(flag.as_bytes());
hasher.update(b"\0");
}
hasher.finalize().to_hex()
};
let cache_dir = crate::core::config::default_cache_dir()
.join("fmt")
.join(&context_hash);
let _ = std::fs::create_dir_all(&cache_dir);
use rayon::prelude::*;
let results: Vec<(NormalizedPath, bool, Option<crate::hash::ContentHash>)> = parsed
.source_files
.par_iter()
.map(|src| {
let abs = if src.is_absolute() {
src.clone()
} else {
cwd.join(src).into()
};
let (is_hit, hash) = match crate::hash::hash_file(&abs) {
Ok(content_hash) => {
let marker = cache_dir.join(content_hash.to_hex());
(marker.exists(), Some(content_hash))
}
Err(_) => (false, None),
};
(abs, is_hit, hash)
})
.collect();
let mut miss_files: Vec<NormalizedPath> = Vec::new();
let mut all_files: Vec<(NormalizedPath, bool, Option<crate::hash::ContentHash>)> = Vec::new();
for (abs, is_hit, hash) in results {
if !is_hit {
miss_files.push(abs.clone());
}
all_files.push((abs, is_hit, hash));
}
if miss_files.is_empty() {
if parsed.check_mode {
return ExitCode::SUCCESS;
}
return ExitCode::SUCCESS;
}
let exit_code = if parsed.check_mode {
run_rustfmt_on_files(rustfmt_path, args, &miss_files, &parsed)
} else {
run_rustfmt_on_files(rustfmt_path, args, &miss_files, &parsed)
};
let exit_i32 = match exit_code {
Ok(code) => code,
Err(e) => {
eprintln!("zccache: failed to run rustfmt: {e}");
return ExitCode::FAILURE;
}
};
if exit_i32 == 0 {
for (abs, was_hit, cached_hash) in &all_files {
if *was_hit {
continue; }
let new_hash = if parsed.check_mode {
*cached_hash
} else {
crate::hash::hash_file(abs).ok()
};
if let Some(h) = new_hash {
let marker = cache_dir.join(h.to_hex());
let _ = std::fs::write(&marker, b"");
}
}
}
exit_code_from_i32(exit_i32)
}
fn run_rustfmt_on_files(
rustfmt_path: &Path,
original_args: &[String],
files: &[NormalizedPath],
parsed: &crate::compiler::parse_rustfmt::ParsedRustfmt,
) -> Result<i32, std::io::Error> {
let mut cmd = std::process::Command::new(rustfmt_path);
cmd.args(&parsed.flags);
for f in files {
cmd.arg(f);
}
let _ = original_args;
let status = cmd.status()?;
Ok(status.code().unwrap_or(1))
}
fn run_tool_direct(tool: &Path, args: &[String]) -> ExitCode {
match std::process::Command::new(tool).args(args).status() {
Ok(status) => exit_code_from_i32(status.code().unwrap_or(1)),
Err(e) => {
eprintln!("zccache: failed to run {}: {e}", tool.display());
ExitCode::FAILURE
}
}
}
pub(crate) fn strip_leading_strict_paths_flags(
args: &[String],
) -> Result<(Option<StrictPathsMode>, Vec<String>), String> {
let mut strict_paths = None;
let mut index = 0;
while let Some(arg) = args.get(index) {
if arg == "--strict-paths" {
strict_paths = Some(StrictPathsMode::Absolute);
index += 1;
} else if let Some(value) = arg.strip_prefix("--strict-paths=") {
strict_paths = Some(StrictPathsMode::parse(value).map_err(|err| err.to_string())?);
index += 1;
} else {
break;
}
}
Ok((strict_paths, args[index..].to_vec()))
}
pub(crate) fn parse_optional_strict_paths(
value: Option<&str>,
) -> Result<Option<StrictPathsMode>, String> {
value
.map(|value| StrictPathsMode::parse(value).map_err(|err| err.to_string()))
.transpose()
}
fn effective_strict_paths_mode(
strict_paths_override: Option<StrictPathsMode>,
) -> Result<StrictPathsMode, String> {
if let Some(mode) = strict_paths_override {
return Ok(mode);
}
match std::env::var("ZCCACHE_STRICT_PATHS") {
Ok(value) => StrictPathsMode::parse(&value).map_err(|err| err.to_string()),
Err(std::env::VarError::NotPresent) => Ok(StrictPathsMode::Off),
Err(std::env::VarError::NotUnicode(_)) => {
Err("ZCCACHE_STRICT_PATHS is not valid Unicode".to_string())
}
}
}
fn set_client_env(env: &mut Vec<(String, String)>, key: &str, value: String) {
if let Some((_, existing)) = env.iter_mut().find(|(env_key, _)| env_key == key) {
*existing = value;
} else {
env.push((key.to_string(), value));
}
}
pub(crate) fn run_wrap(
args: &[String],
strict_paths_override: Option<StrictPathsMode>,
) -> ExitCode {
if args.is_empty() {
eprintln!("usage: zccache <compiler|tool> <args...>");
return ExitCode::FAILURE;
}
if std::env::var("ZCCACHE_DISABLE").is_ok_and(|v| v == "1" || v.eq_ignore_ascii_case("true")) {
return run_passthrough(args);
}
let strict_paths_mode = match effective_strict_paths_mode(strict_paths_override) {
Ok(mode) => mode,
Err(err) => {
eprintln!("zccache: {err}");
return ExitCode::FAILURE;
}
};
let wrapped_tool = resolve_compiler_path(&args[0]);
let tool_args: Vec<String> = if args.len() > 1 {
args[1..].to_vec()
} else {
Vec::new()
};
let cwd = std::env::current_dir().unwrap_or_default();
let mut client_env: Vec<(String, String)> = std::env::vars().collect();
if let Some(mode) = strict_paths_override {
set_client_env(
&mut client_env,
"ZCCACHE_STRICT_PATHS",
mode.as_str().to_string(),
);
}
let endpoint = resolve_endpoint(None);
let _ = std::env::set_current_dir(std::env::temp_dir());
if crate::compiler::detect_family(&args[0]).is_formatter() {
return run_rustfmt_cached(&wrapped_tool, &tool_args, &cwd);
}
if crate::compiler::parse_archiver::is_archiver(&args[0])
|| crate::compiler::parse_linker::is_link_invocation(&args[0], &tool_args)
{
return run_async(cmd_link_ephemeral(
&endpoint,
&wrapped_tool,
tool_args,
cwd.into(),
client_env,
));
}
if let Err(err) = crate::compiler::strict_paths::validate_args(&tool_args, strict_paths_mode) {
eprintln!("{}", err.diagnostic(&args[0], &tool_args));
return ExitCode::FAILURE;
}
match std::env::var("ZCCACHE_SESSION_ID") {
Ok(session_id) => {
if session_id.is_empty() {
eprintln!("ZCCACHE_SESSION_ID is empty");
return ExitCode::FAILURE;
}
run_async(cmd_compile(
&endpoint,
&session_id,
tool_args,
cwd.into(),
wrapped_tool,
client_env,
))
}
Err(_) => {
run_async(cmd_compile_ephemeral(
&endpoint,
&wrapped_tool,
tool_args,
cwd.into(),
client_env,
))
}
}
}
fn resolve_compiler_path(compiler: &str) -> NormalizedPath {
let normalized = crate::core::path::normalize_msys_path(compiler);
let path = Path::new(&normalized);
if path.is_absolute() {
return normalized.into();
}
match which_on_path(&normalized) {
Some(abs) => abs,
None => normalized.into(), }
}
async fn cmd_compile(
endpoint: &str,
session_id: &str,
args: Vec<String>,
cwd: NormalizedPath,
compiler: NormalizedPath,
client_env: Vec<(String, String)>,
) -> ExitCode {
let stdin_bytes = slurp_stdin_if_piped();
let mut conn = match connect(endpoint).await {
Ok(c) => c,
Err(e) => {
eprintln!("zccache[err][C]: cannot connect to daemon at {endpoint}: {e}");
return ExitCode::FAILURE;
}
};
if let Err(e) = conn
.send(&crate::protocol::Request::Compile {
session_id: session_id.to_string(),
args,
cwd,
compiler,
env: Some(client_env),
stdin: stdin_bytes,
})
.await
{
eprintln!("zccache[err][S]: failed to send to daemon: {e}");
return ExitCode::FAILURE;
}
let recv_result = match conn.recv().await {
Ok(r) => r,
Err(e) => {
eprintln!("zccache[err][R]: broken connection to daemon: {e}");
return ExitCode::FAILURE;
}
};
match recv_result {
Some(crate::protocol::Response::CompileResult {
exit_code,
stdout,
stderr,
..
}) => {
use std::io::Write;
let _ = std::io::stdout().write_all(&stdout);
let _ = std::io::stderr().write_all(&stderr);
exit_code_from_i32(exit_code)
}
Some(crate::protocol::Response::Error { message }) => {
eprintln!("zccache[err][E]: daemon error: {message}");
ExitCode::FAILURE
}
None => {
eprintln!("zccache[err][R]: lost connection to daemon (no response). Often a daemon-CLI protocol version mismatch — try `zccache stop`");
ExitCode::FAILURE
}
Some(other) => {
eprintln!("zccache[err][U]: unexpected response from daemon: {other:?}");
ExitCode::FAILURE
}
}
}
async fn cmd_compile_ephemeral(
endpoint: &str,
compiler: &Path,
args: Vec<String>,
cwd: NormalizedPath,
client_env: Vec<(String, String)>,
) -> ExitCode {
if let Err(e) = ensure_daemon(endpoint).await {
eprintln!("zccache[err][D]: cannot start daemon at {endpoint}: {e}");
return ExitCode::FAILURE;
}
let mut conn = match connect(endpoint).await {
Ok(c) => c,
Err(e) => {
eprintln!("zccache[err][C]: cannot connect to daemon at {endpoint}: {e}");
return ExitCode::FAILURE;
}
};
let stdin_bytes = slurp_stdin_if_piped();
if let Err(e) = conn
.send(&crate::protocol::Request::CompileEphemeral {
client_pid: std::process::id(),
working_dir: cwd.clone(),
compiler: compiler.into(),
args,
cwd,
env: Some(client_env),
stdin: stdin_bytes,
})
.await
{
eprintln!("zccache[err][S]: failed to send to daemon: {e}");
return ExitCode::FAILURE;
}
let recv_result = match conn.recv().await {
Ok(r) => r,
Err(e) => {
eprintln!("zccache[err][R]: broken connection to daemon: {e}");
return ExitCode::FAILURE;
}
};
match recv_result {
Some(crate::protocol::Response::CompileResult {
exit_code,
stdout,
stderr,
..
}) => {
use std::io::Write;
let _ = std::io::stdout().write_all(&stdout);
let _ = std::io::stderr().write_all(&stderr);
exit_code_from_i32(exit_code)
}
Some(crate::protocol::Response::Error { message }) => {
eprintln!("zccache[err][E]: daemon error: {message}");
ExitCode::FAILURE
}
None => {
eprintln!("zccache[err][R]: lost connection to daemon (no response). Often a daemon-CLI protocol version mismatch — try `zccache stop`");
ExitCode::FAILURE
}
Some(other) => {
eprintln!("zccache[err][U]: unexpected response from daemon: {other:?}");
ExitCode::FAILURE
}
}
}
async fn cmd_link_ephemeral(
endpoint: &str,
tool: &Path,
args: Vec<String>,
cwd: NormalizedPath,
client_env: Vec<(String, String)>,
) -> ExitCode {
if let Err(e) = ensure_daemon(endpoint).await {
eprintln!("zccache[err][D]: cannot start daemon at {endpoint}: {e}");
return ExitCode::FAILURE;
}
let mut conn = match connect(endpoint).await {
Ok(c) => c,
Err(e) => {
eprintln!("zccache[err][C]: cannot connect to daemon at {endpoint}: {e}");
return ExitCode::FAILURE;
}
};
if let Err(e) = conn
.send(&crate::protocol::Request::LinkEphemeral {
client_pid: std::process::id(),
tool: tool.into(),
args,
cwd,
env: Some(client_env),
})
.await
{
eprintln!("zccache[err][S]: failed to send to daemon: {e}");
return ExitCode::FAILURE;
}
let recv_result = match conn.recv().await {
Ok(r) => r,
Err(e) => {
eprintln!("zccache[err][R]: broken connection to daemon: {e}");
return ExitCode::FAILURE;
}
};
match recv_result {
Some(crate::protocol::Response::LinkResult {
exit_code,
stdout,
stderr,
warning,
..
}) => {
use std::io::Write;
let _ = std::io::stdout().write_all(&stdout);
let _ = std::io::stderr().write_all(&stderr);
if let Some(w) = warning {
eprintln!("zccache warning: {w}");
}
exit_code_from_i32(exit_code)
}
Some(crate::protocol::Response::Error { message }) => {
eprintln!("zccache[err][E]: daemon error: {message}");
ExitCode::FAILURE
}
None => {
eprintln!("zccache[err][R]: lost connection to daemon (no response). Often a daemon-CLI protocol version mismatch — try `zccache stop`");
ExitCode::FAILURE
}
Some(other) => {
eprintln!("zccache[err][U]: unexpected response from daemon: {other:?}");
ExitCode::FAILURE
}
}
}