use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use anyhow::{Result, bail};
use crate::color;
use crate::deps_cache::DepsCache;
use crate::graph::{BuildGraph, Product};
use super::Builder;
impl Builder {
pub fn product_show(
&self,
ctx: &crate::build_context::BuildContext,
path: &str,
verbose: bool,
) -> Result<()> {
let graph = self.build_graph_for_cache(ctx)?;
let target = PathBuf::from(path);
let product = resolve_product(&graph, &target).ok_or_else(|| {
anyhow::anyhow!(
"No product owns or consumes '{path}'. Try the output path \
(e.g. README.md) or the primary input path.",
)
})?;
let deps_cache = DepsCache::open()?;
let analyzer_entries = deps_cache.get_raw_for_path(product.primary_input());
let analyzer_inputs: BTreeMap<String, Vec<PathBuf>> = analyzer_entries
.iter()
.map(|(deps, name)| (name.clone(), deps.clone()))
.collect();
let analyzers = self.create_analyzers(false)?;
let mut hash_pieces: BTreeMap<String, Vec<String>> = BTreeMap::new();
for (name, analyzer) in &analyzers {
if let Ok(Some(pieces)) = analyzer.scan_hash_pieces(ctx, product.primary_input())
&& !pieces.is_empty()
{
hash_pieces.insert(name.clone(), pieces);
}
}
let input_checksum = match crate::checksum::combined_input_checksum(ctx, &product.inputs) {
Ok(c) => c,
Err(e) => {
bail!("Failed to compute input checksum: {e}");
}
};
let descriptor_key = product.descriptor_key(&input_checksum);
let cache_state =
self.object_store()
.explain_descriptor(ctx, &descriptor_key, &product.outputs, false);
if crate::json_output::is_json_mode() {
print_json(
product,
&input_checksum,
&descriptor_key,
&analyzer_inputs,
&hash_pieces,
&cache_state,
)?;
} else {
print_text(
product,
&input_checksum,
&descriptor_key,
&analyzer_inputs,
&hash_pieces,
&cache_state,
verbose,
);
}
Ok(())
}
}
fn resolve_product<'a>(graph: &'a BuildGraph, path: &Path) -> Option<&'a Product> {
if let Some(id) = graph.path_owner(path) {
return graph.get_product(id);
}
graph.products().iter().find(|p| p.primary_input() == path)
}
fn print_text(
product: &Product,
input_checksum: &str,
descriptor_key: &str,
analyzer_inputs: &BTreeMap<String, Vec<PathBuf>>,
hash_pieces: &BTreeMap<String, Vec<String>>,
cache_state: &crate::object_store::ExplainAction,
verbose: bool,
) {
println!(
"{} {}",
color::dim("processor:"),
color::cyan(&product.processor)
);
if let Some(v) = &product.variant {
println!("{} {}", color::dim("variant:"), v);
}
println!("{}", color::dim("outputs:"));
if product.outputs.is_empty() {
println!(" {}", color::dim("(checker, no outputs)"));
} else {
for o in &product.outputs {
println!(" {}", o.display());
}
}
println!("{}", color::dim("inputs:"));
let primary = product.primary_input();
let analyzer_added: std::collections::HashSet<&PathBuf> =
analyzer_inputs.values().flatten().collect();
println!(" {} {}", color::cyan("primary"), primary.display());
let mut other_count = 0;
for inp in product.inputs.iter().skip(1) {
if analyzer_added.contains(inp) {
continue;
}
if other_count == 0 {
println!(" {}", color::cyan("configured"));
}
println!(" {}", inp.display());
other_count += 1;
}
for (analyzer, deps) in analyzer_inputs {
if deps.is_empty() {
continue;
}
println!(" {} [{}]", color::cyan("analyzer"), analyzer);
for d in deps {
println!(" {}", d.display());
}
}
if product.cache_key.is_empty() {
println!(
"{} {}",
color::dim("cache_key:"),
color::dim("(inputs only)")
);
} else {
println!(
"{} {}",
color::dim("cache_key:"),
product.cache_key.digest().unwrap_or_default(),
);
for (component, value) in product.cache_key.components() {
if value.contains('\n') {
let lines = value.lines().count();
println!(
" {} {}",
color::cyan(component.tag()),
color::dim(&format!("({lines} lines, see hash_pieces below)")),
);
} else {
println!(" {} {}", color::cyan(component.tag()), value);
}
}
}
if hash_pieces.is_empty() {
println!("{} {}", color::dim("hash_pieces:"), color::dim("(none)"));
} else {
println!("{}", color::dim("hash_pieces:"));
for (analyzer, pieces) in hash_pieces {
println!(" [{analyzer}]");
for piece in pieces {
let (kind, body) = piece.split_once(':').unwrap_or((piece.as_str(), ""));
if !verbose && kind.ends_with("_resolved") {
let n = if body.is_empty() {
0
} else {
body.lines().count()
};
println!(
" {} {}",
color::cyan(kind),
color::dim(&format!("({n} files, pass --verbose to list)")),
);
} else if body.contains('\n') {
println!(" {}", color::cyan(kind));
for line in body.lines() {
println!(" {line}");
}
} else {
println!(" {} {}", color::cyan(kind), body);
}
}
}
}
println!("{} {}", color::dim("input_checksum:"), input_checksum);
println!("{} {}", color::dim("descriptor_key:"), descriptor_key);
println!("{} {}", color::dim("cache_state:"), cache_state);
}
fn print_json(
product: &Product,
input_checksum: &str,
descriptor_key: &str,
analyzer_inputs: &BTreeMap<String, Vec<PathBuf>>,
hash_pieces: &BTreeMap<String, Vec<String>>,
cache_state: &crate::object_store::ExplainAction,
) -> Result<()> {
let primary = product.primary_input();
let analyzer_added: std::collections::HashSet<&PathBuf> =
analyzer_inputs.values().flatten().collect();
let configured: Vec<String> = product
.inputs
.iter()
.skip(1)
.filter(|p| !analyzer_added.contains(*p))
.map(|p| p.display().to_string())
.collect();
let analyzer_inputs_json: serde_json::Map<String, serde_json::Value> = analyzer_inputs
.iter()
.map(|(k, v)| {
(
k.clone(),
serde_json::Value::Array(
v.iter()
.map(|p| serde_json::Value::String(p.display().to_string()))
.collect(),
),
)
})
.collect();
let hash_pieces_json: serde_json::Map<String, serde_json::Value> = hash_pieces
.iter()
.map(|(k, v)| {
(
k.clone(),
serde_json::Value::Array(
v.iter()
.map(|s| serde_json::Value::String(s.clone()))
.collect(),
),
)
})
.collect();
let cache_state_str = cache_state.to_string();
let out = serde_json::json!({
"processor": product.processor,
"variant": product.variant,
"outputs": product.outputs.iter().map(|p| p.display().to_string()).collect::<Vec<_>>(),
"inputs": {
"primary": primary.display().to_string(),
"configured": configured,
"analyzer": analyzer_inputs_json,
},
"cache_key": product.cache_key.digest(),
"cache_key_components": product.cache_key.components().iter()
.map(|(c, v)| serde_json::json!({"component": c.tag(), "value": v}))
.collect::<Vec<_>>(),
"hash_pieces": hash_pieces_json,
"input_checksum": input_checksum,
"descriptor_key": descriptor_key,
"cache_state": cache_state_str,
});
println!("{}", serde_json::to_string_pretty(&out)?);
Ok(())
}