use std::path::Path;
use crate::core::path::normalize_for_key;
use crate::core::NormalizedPath;
use crate::hash::ContentHash;
use super::args::ParsedArgs;
use super::rustc_args::RustcParsedArgs;
use super::search_paths::IncludeSearchPaths;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ContextKey(ContentHash);
impl ContextKey {
#[must_use]
pub fn hash(&self) -> &ContentHash {
&self.0
}
#[must_use]
pub fn from_raw(bytes: [u8; 32]) -> Self {
Self(ContentHash::from_bytes(bytes))
}
}
impl std::fmt::Display for ContextKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ctx:{}", self.0.to_hex())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ArtifactKey(ContentHash);
impl ArtifactKey {
#[must_use]
pub fn hash(&self) -> &ContentHash {
&self.0
}
#[must_use]
pub fn from_raw(bytes: [u8; 32]) -> Self {
Self(ContentHash::from_bytes(bytes))
}
}
impl std::fmt::Display for ArtifactKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "art:{}", self.0.to_hex())
}
}
#[derive(Debug, Clone)]
pub struct CompileContext {
pub source_file: NormalizedPath,
pub include_search: IncludeSearchPaths,
pub defines: Vec<String>,
pub flags: Vec<String>,
pub force_includes: Vec<NormalizedPath>,
pub unknown_flags: Vec<String>,
}
impl CompileContext {
#[must_use]
pub fn from_parsed_args(args: ParsedArgs) -> Self {
let mut defines = args.defines;
defines.sort();
let mut flags = args.flags;
flags.sort();
let mut unknown_flags = args.unknown_flags;
unknown_flags.sort();
Self {
source_file: args.source_file,
include_search: args.include_search,
defines,
flags,
force_includes: args.force_includes,
unknown_flags,
}
}
#[must_use]
pub fn context_key(&self) -> ContextKey {
compute_context_key(self, None)
}
}
fn extern_path_key(path: &str) -> &str {
Path::new(path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(path)
}
fn normalize_key_path(path: &Path, key_root: Option<&Path>) -> String {
if let Some(root) = key_root {
if let Ok(stripped) = path.strip_prefix(root) {
return normalize_for_key(stripped);
}
}
normalize_for_key(path)
}
fn normalize_remap_path_prefix_for_key(remap: &str, key_root: Option<&Path>) -> String {
let Some(root) = key_root else {
return remap.to_string();
};
let Some((from, to)) = remap.split_once('=') else {
return remap.to_string();
};
let from_path = Path::new(from);
if from_path.strip_prefix(root).is_ok() {
format!("{}={}", normalize_key_path(from_path, key_root), to)
} else {
remap.to_string()
}
}
fn normalize_cxx_prefix_map_flag_for_key(flag: &str, key_root: Option<&Path>) -> String {
const PREFIX_MAP_FLAGS: [&str; 5] = [
"-ffile-prefix-map=",
"-fdebug-prefix-map=",
"-fmacro-prefix-map=",
"-fcoverage-prefix-map=",
"-fprofile-prefix-map=",
];
for prefix in PREFIX_MAP_FLAGS {
if let Some(remap) = flag.strip_prefix(prefix) {
return format!(
"{}{}",
prefix,
normalize_remap_path_prefix_for_key(remap, key_root)
);
}
}
flag.to_string()
}
#[must_use]
pub fn compute_context_key(ctx: &CompileContext, key_root: Option<&Path>) -> ContextKey {
let mut hasher = blake3::Hasher::new();
hasher.update(b"zccache-context-key-v1\0");
hasher.update(normalize_key_path(&ctx.source_file, key_root).as_bytes());
hasher.update(b"\0");
hasher.update(b"iquote\0");
for dir in &ctx.include_search.iquote {
hasher.update(normalize_key_path(dir, key_root).as_bytes());
hasher.update(b"\0");
}
hasher.update(b"user\0");
for dir in &ctx.include_search.user {
hasher.update(normalize_key_path(dir, key_root).as_bytes());
hasher.update(b"\0");
}
hasher.update(b"system\0");
for dir in &ctx.include_search.system {
hasher.update(normalize_key_path(dir, key_root).as_bytes());
hasher.update(b"\0");
}
hasher.update(b"after\0");
for dir in &ctx.include_search.after {
hasher.update(normalize_key_path(dir, key_root).as_bytes());
hasher.update(b"\0");
}
hasher.update(b"defines\0");
for def in &ctx.defines {
hasher.update(def.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"flags\0");
for flag in &ctx.flags {
let flag = normalize_cxx_prefix_map_flag_for_key(flag, key_root);
hasher.update(flag.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"force-include\0");
for fi in &ctx.force_includes {
hasher.update(normalize_key_path(fi, key_root).as_bytes());
hasher.update(b"\0");
}
hasher.update(b"unknown\0");
for flag in &ctx.unknown_flags {
let flag = normalize_cxx_prefix_map_flag_for_key(flag, key_root);
hasher.update(flag.as_bytes());
hasher.update(b"\0");
}
ContextKey(ContentHash::from_bytes(*hasher.finalize().as_bytes()))
}
pub fn compute_artifact_key<P: AsRef<Path> + Ord>(
context_key: &ContextKey,
file_hashes: &mut [(P, ContentHash)],
key_root: Option<&Path>,
) -> ArtifactKey {
file_hashes.sort_by(|a, b| a.0.cmp(&b.0));
let mut hasher = blake3::Hasher::new();
hasher.update(b"zccache-artifact-key-v1\0");
hasher.update(context_key.0.as_bytes());
hasher.update(b"\0");
for (path, hash) in file_hashes.iter() {
let path = normalize_key_path(path.as_ref(), key_root);
hasher.update(path.as_bytes());
hasher.update(b"\0");
hasher.update(hash.as_bytes());
hasher.update(b"\0");
}
ArtifactKey(ContentHash::from_bytes(*hasher.finalize().as_bytes()))
}
const VOLATILE_CARGO_ENV_VARS: &[&str] = &[
"CARGO_MANIFEST_DIR",
"CARGO_MANIFEST_PATH",
"CARGO_TARGET_DIR",
];
#[derive(Debug, Clone)]
pub struct RustcCompileContext {
pub source_file: NormalizedPath,
pub crate_name: Option<String>,
pub crate_types: Vec<String>,
pub edition: Option<String>,
pub emit_types: Vec<String>,
pub cfgs: Vec<String>,
pub check_cfgs: Vec<String>,
pub codegen_flags: Vec<String>,
pub cargo_metadata: Option<String>,
pub extra_filename: Option<String>,
pub target: Option<String>,
pub cap_lints: Option<String>,
pub extern_crates: Vec<(String, String)>,
pub lint_flags: Vec<String>,
pub unknown_flags: Vec<String>,
pub remap_path_prefixes: Vec<String>,
pub env_vars: Vec<(String, String)>,
pub compiler_hash: Option<ContentHash>,
}
impl RustcCompileContext {
#[must_use]
pub fn from_parsed_args(
args: &RustcParsedArgs,
client_env: &[(String, String)],
compiler_hash: Option<ContentHash>,
) -> Self {
let mut crate_types = args.crate_types.clone();
crate_types.sort();
let mut emit_types = args.emit_types.clone();
emit_types.sort();
let mut extern_crates: Vec<(String, String)> = args
.externs
.iter()
.map(|e| (e.name.clone(), e.path.to_string_lossy().into_owned()))
.collect();
extern_crates.sort();
let mut remap_path_prefixes = args.remap_path_prefixes.clone();
remap_path_prefixes.sort();
let mut env_vars: Vec<(String, String)> = client_env
.iter()
.filter(|(k, _)| {
k.starts_with("CARGO_")
&& k != "CARGO_MAKEFLAGS"
&& k != "CARGO_INCREMENTAL"
&& !VOLATILE_CARGO_ENV_VARS.contains(&k.as_str())
})
.cloned()
.collect();
env_vars.sort();
Self {
source_file: args.source_file.clone(),
crate_name: args.crate_name.clone(),
crate_types,
edition: args.edition.clone(),
emit_types,
cfgs: args.cfgs.clone(),
check_cfgs: args.check_cfgs.clone(),
codegen_flags: args.codegen_flags.clone(),
cargo_metadata: args.cargo_metadata.clone(),
extra_filename: args.extra_filename.clone(),
target: args.target.clone(),
cap_lints: args.cap_lints.clone(),
extern_crates,
lint_flags: args.lint_flags.clone(),
unknown_flags: args.unknown_flags.clone(),
remap_path_prefixes,
env_vars,
compiler_hash,
}
}
#[must_use]
pub fn context_key(&self) -> ContextKey {
self.context_key_with_root(None)
}
#[must_use]
pub fn context_key_with_root(&self, key_root: Option<&Path>) -> ContextKey {
let mut hasher = blake3::Hasher::new();
hasher.update(b"zccache-rustc-context-key-v2\0");
if let Some(ref ch) = self.compiler_hash {
hasher.update(b"compiler\0");
hasher.update(ch.as_bytes());
hasher.update(b"\0");
}
let source_file = normalize_key_path(&self.source_file, key_root);
hasher.update(source_file.as_bytes());
hasher.update(b"\0");
if let Some(ref name) = self.crate_name {
hasher.update(b"crate-name\0");
hasher.update(name.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"crate-types\0");
for ct in &self.crate_types {
hasher.update(ct.as_bytes());
hasher.update(b"\0");
}
if let Some(ref edition) = self.edition {
hasher.update(b"edition\0");
hasher.update(edition.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"emit\0");
for et in &self.emit_types {
hasher.update(et.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"cfg\0");
for cfg in &self.cfgs {
hasher.update(cfg.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"check-cfg\0");
for cfg in &self.check_cfgs {
hasher.update(cfg.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"codegen\0");
for flag in &self.codegen_flags {
hasher.update(flag.as_bytes());
hasher.update(b"\0");
}
if let Some(ref metadata) = self.cargo_metadata {
hasher.update(b"cargo-metadata\0");
hasher.update(metadata.as_bytes());
hasher.update(b"\0");
}
if let Some(ref extra_filename) = self.extra_filename {
hasher.update(b"extra-filename\0");
hasher.update(extra_filename.as_bytes());
hasher.update(b"\0");
}
if let Some(ref target) = self.target {
hasher.update(b"target\0");
hasher.update(target.as_bytes());
hasher.update(b"\0");
}
if let Some(ref cap) = self.cap_lints {
hasher.update(b"cap-lints\0");
hasher.update(cap.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"externs\0");
for (name, path) in &self.extern_crates {
hasher.update(name.as_bytes());
hasher.update(b"=");
hasher.update(extern_path_key(path).as_bytes());
hasher.update(b"\0");
}
hasher.update(b"lints\0");
for flag in &self.lint_flags {
hasher.update(flag.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"unknown\0");
for flag in &self.unknown_flags {
hasher.update(flag.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"remap\0");
if key_root.is_some() {
let mut remap_path_prefixes: Vec<String> = self
.remap_path_prefixes
.iter()
.map(|remap| normalize_remap_path_prefix_for_key(remap, key_root))
.collect();
remap_path_prefixes.sort();
for remap in &remap_path_prefixes {
hasher.update(remap.as_bytes());
hasher.update(b"\0");
}
} else {
for remap in &self.remap_path_prefixes {
hasher.update(remap.as_bytes());
hasher.update(b"\0");
}
}
hasher.update(b"env\0");
for (key, val) in &self.env_vars {
if VOLATILE_CARGO_ENV_VARS.contains(&key.as_str()) {
continue;
}
hasher.update(key.as_bytes());
hasher.update(b"=");
hasher.update(val.as_bytes());
hasher.update(b"\0");
}
ContextKey(ContentHash::from_bytes(*hasher.finalize().as_bytes()))
}
}
pub fn compute_rustc_artifact_key<P: AsRef<Path> + Ord>(
context_key: &ContextKey,
file_hashes: &mut [(P, ContentHash)],
extern_hashes: &mut [(String, ContentHash)],
) -> ArtifactKey {
compute_rustc_artifact_key_with_root(context_key, file_hashes, extern_hashes, None)
}
pub fn compute_rustc_artifact_key_with_root<P: AsRef<Path> + Ord>(
context_key: &ContextKey,
file_hashes: &mut [(P, ContentHash)],
extern_hashes: &mut [(String, ContentHash)],
key_root: Option<&Path>,
) -> ArtifactKey {
if key_root.is_some() {
file_hashes.sort_by(|a, b| {
normalize_key_path(a.0.as_ref(), key_root)
.cmp(&normalize_key_path(b.0.as_ref(), key_root))
});
} else {
file_hashes.sort_by(|a, b| a.0.cmp(&b.0));
}
extern_hashes.sort_by(|a, b| a.0.cmp(&b.0));
let mut hasher = blake3::Hasher::new();
hasher.update(b"zccache-rustc-artifact-key-v1\0");
hasher.update(context_key.0.as_bytes());
hasher.update(b"\0");
for (path, hash) in file_hashes.iter() {
let path = normalize_key_path(path.as_ref(), key_root);
hasher.update(path.as_bytes());
hasher.update(b"\0");
hasher.update(hash.as_bytes());
hasher.update(b"\0");
}
hasher.update(b"externs\0");
for (name, hash) in extern_hashes.iter() {
hasher.update(name.as_bytes());
hasher.update(b"\0");
hasher.update(hash.as_bytes());
hasher.update(b"\0");
}
ArtifactKey(ContentHash::from_bytes(*hasher.finalize().as_bytes()))
}