Skip to content

Commit 2be59d2

Browse files
mdittmerjoshlf
authored andcommitted
[anneal][v2] Add scanner module to map workspace packages to AnnealArtifacts
TAG=agy gherrit-pr-id: G2xsxxkfz5kle6aunmehujqcrly3rjr4f
1 parent 05bfb0a commit 2be59d2

1 file changed

Lines changed: 119 additions & 0 deletions

File tree

anneal/v2/src/scanner.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2026 The Fuchsia Authors
2+
//
3+
// Licensed under the 2-Clause BSD License <LICENSE-BSD or
4+
// https://opensource.org/license/bsd-2-clause>, Apache License, Version 2.0
5+
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
6+
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
7+
// This file may not be copied, modified, or distributed except according to
8+
// those terms.
9+
10+
use sha2::Digest as _;
11+
12+
/// Represents a compilation target (artifact) that needs to be processed.
13+
///
14+
/// In this rewrite, we no longer directly parse Rust files to extract annotations
15+
/// or entry points. Instead, Charon compiles the entire target to generate LLBC
16+
/// files, and annotations will be processed from LLBC directly at a later stage.
17+
pub struct AnnealArtifact {
18+
pub name: crate::resolve::AnnealTargetName,
19+
pub target_kind: crate::resolve::AnnealTargetKind,
20+
/// The path to the crate's `Cargo.toml`.
21+
pub manifest_path: std::path::PathBuf,
22+
}
23+
24+
impl AnnealArtifact {
25+
/// Returns a unique, Lean-compatible "slug" for this artifact that matches
26+
/// the name that Aeneas will expect for the corresponding Lean module.
27+
///
28+
/// Guarantees uniqueness based on manifest path even if multiple packages
29+
/// have the same name. The slug is guaranteed to be a valid Lean
30+
/// identifier (no hyphens).
31+
pub fn artifact_slug(&self) -> String {
32+
fn hash(data: &[u8]) -> u64 {
33+
let mut hasher = sha2::Sha256::new();
34+
hasher.update(data);
35+
let result = hasher.finalize();
36+
let mut bytes = [0u8; 8];
37+
bytes.copy_from_slice(&result[0..8]);
38+
u64::from_le_bytes(bytes)
39+
}
40+
41+
// Double-hash to make sure we can distinguish between e.g.
42+
// (manifest_path, target_name) = ("abc", "def") and ("ab", "cdef"),
43+
// which would hash identically if we just hashed their concatenation.
44+
//
45+
// Use SHA-256 not for security but rather stability – Rust's
46+
// `DefaultHasher` doesn't guarantee stability even across runs of the
47+
// same binary.
48+
//
49+
// `ANNEAL_HASH_WITH_REMOVED_PREFIX` allows our integration test
50+
// framework to strip the randomized sandbox prefix from the manifest
51+
// path before hashing, ensuring deterministic hashes even when running
52+
// in a sandboxed environment.
53+
let mut manifest_path_to_hash = self.manifest_path.as_path();
54+
if let Ok(prefix) = std::env::var("ANNEAL_HASH_WITH_REMOVED_PREFIX") {
55+
if let Ok(stripped) = self.manifest_path.strip_prefix(&prefix) {
56+
manifest_path_to_hash = stripped;
57+
}
58+
}
59+
let h0 = hash(manifest_path_to_hash.as_os_str().as_encoded_bytes());
60+
let h1 = hash(self.name.target_name.as_bytes());
61+
let h2 = hash(&[self.target_kind as u8]);
62+
let hashes = [h0, h1, h2];
63+
let h = hash(&hashes.map(u64::to_ne_bytes).concat());
64+
65+
// Converts kebab-case -> PascalCase.
66+
// We convert both package and target names to PascalCase to ensure
67+
// the generated Lean module name is a valid and idiomatic Lean
68+
// identifier, matching Aeneas's output format.
69+
let to_pascal = |s: &str| {
70+
s.split(['-', '_'])
71+
.map(|segment| {
72+
let mut chars = segment.chars();
73+
match chars.next() {
74+
None => String::new(),
75+
Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
76+
}
77+
})
78+
.collect::<String>()
79+
};
80+
81+
let pkg = to_pascal(self.name.package_name.as_str());
82+
let target = to_pascal(&self.name.target_name);
83+
84+
// We use the hash to ensure uniqueness.
85+
format!("{}{}{:08x}", pkg, target, h)
86+
}
87+
88+
/// Returns the name of the `.llbc` file to use for this artifact.
89+
pub fn llbc_file_name(&self) -> String {
90+
format!("{}.llbc", self.artifact_slug())
91+
}
92+
93+
/// Returns the name of the `.lean` spec file to use for this artifact.
94+
pub fn lean_spec_file_name(&self) -> String {
95+
format!("{}.lean", self.artifact_slug())
96+
}
97+
98+
/// Returns the absolute path to the .llbc file.
99+
///
100+
/// This method requires [`crate::resolve::LockedRoots`] to ensure that the caller holds the
101+
/// build lock before accessing the build artifact path.
102+
pub fn llbc_path(&self, roots: &crate::resolve::LockedRoots) -> std::path::PathBuf {
103+
roots.llbc_root().join(self.llbc_file_name())
104+
}
105+
}
106+
107+
/// Scans the resolved workspace roots to identify the targets that need to be passed
108+
/// to Charon. No Rust source code parsing is performed during this step.
109+
pub fn scan_workspace(roots: &crate::resolve::Roots) -> anyhow::Result<Vec<AnnealArtifact>> {
110+
let mut artifacts = Vec::new();
111+
for target in &roots.roots {
112+
artifacts.push(AnnealArtifact {
113+
name: target.name.clone(),
114+
target_kind: target.kind,
115+
manifest_path: target.manifest_path.clone(),
116+
});
117+
}
118+
Ok(artifacts)
119+
}

0 commit comments

Comments
 (0)