use std::path::Path;
use nichlink::identity::NodeId;
use nichlink_build_method::{
DeclaredGraft, FaceView, declared_grafts, read_build_scope, read_pruning_manifest,
};
use serde_json::{Value, json};
pub(super) fn scope_report(out_dir: &Path, face: &FaceView, current: bool) -> (Value, Vec<String>) {
if !current {
return (
json!({
"known": false,
"mode": Value::Null,
"all": Value::Null,
"reason": Value::Null,
"selected": Value::Null,
"kept": Value::Null,
"error": "build output is out of date with the sources",
}),
vec![
" scope: unknown (build output is out of date with the sources); \
run `nichlink check` to publish source_scope.tsv"
.to_owned(),
],
);
}
match read_build_scope(out_dir) {
Ok(scope) => {
let selected = scope.all
|| scope.selected_sources.contains(&face.source)
|| scope.selected_ids.contains(&face.id);
let kept = selected || scope.keeps(&face.module);
let reason = scope.reason.clone();
let report = json!({
"known": true,
"mode": scope.mode,
"all": scope.all,
"reason": reason,
"selected": selected,
"kept": kept,
});
let why = if selected {
"declared slot"
} else {
"kept because a selected face needs it"
};
let note = if kept {
format!(
" scope: KEPT ({why}; mode={}, reason={})",
scope.mode,
scope.reason.as_deref().unwrap_or("<none>")
)
} else {
format!(
" scope: PRUNED (mode={}, reason={}); the build-time scope does not include this face",
scope.mode,
scope.reason.as_deref().unwrap_or("<none>")
)
};
(report, vec![note])
}
Err(error) => (
json!({
"known": false,
"mode": Value::Null,
"all": Value::Null,
"reason": Value::Null,
"selected": Value::Null,
"kept": Value::Null,
"error": error,
}),
vec![format!(
" scope: unknown ({error}); run `nichlink check` to publish source_scope.tsv"
)],
),
}
}
pub(super) fn pruning_report(
out_dir: &Path,
face: &FaceView,
current: bool,
) -> (Value, Vec<String>) {
if !current {
return (
json!({
"known": false,
"pruned": Value::Null,
"symbols": Value::Null,
"error": "build output is out of date with the sources",
}),
vec![
" pruning: unknown (build output is out of date with the sources); \
run `nichlink check` to publish pruning_manifest.tsv"
.to_owned(),
],
);
}
match read_pruning_manifest(out_dir) {
Ok(rows) => {
let mut symbols = rows
.into_iter()
.filter(|row| row.source == face.source || row.id == face.id)
.map(|row| row.symbol)
.collect::<Vec<_>>();
symbols.sort();
symbols.dedup();
let pruned = symbols.iter().any(|symbol| symbol != "-");
let report = json!({
"known": true,
"pruned": pruned,
"symbols": symbols,
});
let note = if symbols.is_empty() {
" pruning: no symbols published for this face".to_owned()
} else {
format!(
" pruning: {} [{}]",
if pruned {
"release-time pruning strips symbols"
} else {
"nothing to strip"
},
symbols.join(", ")
)
};
(report, vec![note])
}
Err(error) => (
json!({
"known": false,
"pruned": Value::Null,
"symbols": Value::Null,
"error": error,
}),
vec![format!(
" pruning: unknown ({error}); run `nichlink check` to publish pruning_manifest.tsv"
)],
),
}
}
pub(super) fn declared_report(root: &Path, face: &FaceView) -> (Value, Vec<String>) {
match declared_grafts(root) {
Ok(declared) => {
let naming = declared
.cuts
.iter()
.filter(|cut| cut.names_face(&face.path, Some(&face.module)))
.collect::<Vec<_>>();
let cuts = naming.iter().map(|cut| graft_json(cut)).collect::<Vec<_>>();
let mut note = vec![if naming.is_empty() {
" declared grafts: none (no static_graft_plan! cut names this face)".to_owned()
} else {
format!(
" declared grafts: {} (entry {})",
naming.len(),
declared.entry.display()
)
}];
for cut in &naming {
note.push(format!(
" - cut={} graft={} full={} line={} form={}",
cut_endpoint(cut),
cut.graft,
cut.full,
cut.line,
cut_form(cut)
));
}
(
json!({
"entry": declared.entry.display().to_string(),
"count": naming.len(),
"cuts": cuts,
}),
note,
)
}
Err(error) => (
json!({
"entry": Value::Null,
"count": 0,
"cuts": [],
"error": error,
}),
vec![format!(
" declared grafts: cannot read the host entry ({error})"
)],
),
}
}
pub(super) fn parent_path(faces: &[FaceView], id: NodeId) -> String {
faces
.iter()
.find(|face| face.id == id)
.map(|face| face.path.clone())
.unwrap_or_else(|| "root".to_owned())
}
fn graft_json(cut: &DeclaredGraft) -> Value {
json!({
"cut": cut_endpoint(cut),
"cut_end": cut.cut_end,
"graft": cut.graft,
"full": cut.full,
"cfg": cut.cfg,
"line": cut.line,
"form": cut_form(cut),
})
}
pub(super) fn cut_endpoint(cut: &DeclaredGraft) -> String {
cut.cut_label()
}
pub(super) fn cut_form(cut: &DeclaredGraft) -> &'static str {
match (&cut.expressions, &cut.cut_end) {
(Some(_), Some(_)) => "typed-range",
(Some(_), None) => "typed",
(None, Some(_)) => "range",
(None, None) => "string",
}
}