1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! Manifest verification: does the tree a `.alef/` cache entry vouches for still hold?
//!
//! Split out of [`crate::cli::cache`] because it answers a different question from the rest of
//! that module. A cache key ([`crate::cli::cache_identity`]) says *what* was generated and by
//! which alef build; these two predicates say whether the files that generation produced are
//! still on disk and still unmodified. Both must hold before a skip is honest.
//!
//! [`outputs_exist`] alone was the whole check, and existence turned out not to be enough: a
//! consumer appended a line to a generated file, re-ran `alef generate` with `.alef/` intact,
//! and got `Generated 0 files` with the edit still in place — the file existed, so the language
//! stayed a cache hit and was dropped from the generation set before anything read it.
//! [`stamped_outputs_agree_with_disk`] is what makes the hit mean something. ~keep
use fs;
use Path;
/// Check that all files listed in a manifest exist on disk. False if any listed file is missing,
/// if the manifest is empty, or if the manifest could not be read at all.
///
/// Every failure to read is a cache **miss**, never a hit. A manifest is absent for three
/// different reasons -- the stage has never run, the cache predates the manifest format, or the
/// previous run died between `fs::write`ing the hash and writing the manifest
/// (`write_lang_hash` and `write_stage_hash` are two separate writes, so an interrupt leaves
/// exactly a matching hash with no manifest) -- and only the read itself can no longer tell them
/// apart. The callers spend this answer on "may I skip regenerating?", where an unknown costs one
/// regeneration, while a wrong `true` skips the write entirely and leaves the tree permanently
/// short of files the cache insists are present. An empty-but-readable manifest already answered
/// `false`; an unreadable one is strictly less evidence and must not answer better. ~keep
pub
/// Whether every stamped file in `manifest_path` still hashes to its own embedded `alef:hash:`
/// value.
///
/// This is the same comparison `alef verify` runs, so a tree that passes verify passes here.
/// Paths that are absent (already a miss via [`outputs_exist`]) or unreadable as UTF-8 return
/// `true`: the question is "was a stamped file modified after alef wrote it", and only a stamped
/// file can be asked. Genuinely unstampable outputs -- `generated_header: false` create-once seeds,
/// and formats with no comment syntax at all (`.json`, `.jar`) -- carry no alef marker either, so
/// they keep the existence-only rule and a warm run still hits.
///
/// A file that carries the marker but *no* `alef:hash:` line is the one unstamped shape that must
/// NOT be read as agreement. It is not an unstampable output: alef claims it, and
/// `hash::inject_hash_line` shares `content_has_alef_marker`'s scan window, so anything claimed is
/// stampable. The missing line therefore means the stamping pass never ran for it -- an
/// interrupted run, or a stage that aborted before `finalize_hashes`. Answering `true` there is
/// what makes that state permanent: the stage reads as cached, its `finalize_hashes` call is
/// skipped, and the file stays claimed-but-unstamped, so `poly`'s hash-keyed skip never covers it,
/// `poly fmt` reformats it, and the next alef write puts alef's own bytes back -- an unbreakable
/// ping-pong neither tool yields on. Repo-root scaffold files (`.cargo/config.toml`,
/// `rust-toolchain.toml`, `poly.toml`, `rustfmt.toml`) have no second route out: they sit outside
/// every `generate::orphans::generate_sweep_roots` root, so `finalize_hashes_sweeping`'s disk-scan
/// self-heal cannot reach them either. ~keep
pub