Skip to content

Commit 783f549

Browse files
committed
[anneal][v2] Add exocrate toolchain setup and Toolchain resolver
TAG=agy gherrit-pr-id: Gbbpbt76nsgp2ohpclea46vot5joxx7b5
1 parent 04ae4fd commit 783f549

2 files changed

Lines changed: 167 additions & 6 deletions

File tree

anneal/v2/src/setup.rs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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 anyhow::Context as _;
11+
12+
pub struct SetupArgs {
13+
pub local_archive: Option<std::path::PathBuf>,
14+
}
15+
16+
exocrate::config! {
17+
pub const CONFIG: Config = Config {
18+
rel_dir_path: [".anneal", "toolchain"],
19+
versioned_files: &["../Cargo.toml", "../Cargo.lock"],
20+
};
21+
}
22+
23+
exocrate::parse_remote_archive! {
24+
pub const REMOTE: RemoteArchive = "Cargo.toml" [
25+
(linux, x86_64),
26+
(macos, x86_64),
27+
(linux, aarch64),
28+
(macos, aarch64),
29+
];
30+
}
31+
32+
pub enum Tool {
33+
Cargo,
34+
Charon,
35+
Rustc,
36+
}
37+
38+
impl Tool {
39+
pub fn name(&self) -> &'static str {
40+
match self {
41+
Self::Cargo => "cargo",
42+
Self::Charon => "charon",
43+
Self::Rustc => "rustc",
44+
}
45+
}
46+
47+
pub fn path(&self, toolchain: &Toolchain) -> std::path::PathBuf {
48+
match self {
49+
Self::Cargo | Self::Rustc => toolchain.rust_bin().join(self.name()),
50+
Self::Charon => toolchain.aeneas_bin_dir().join(self.name()),
51+
}
52+
}
53+
}
54+
55+
const AENEAS_DIR: &str = "aeneas";
56+
const RUST_SYSROOT: &str = "rust";
57+
const AENEAS_BIN_DIR: &str = "bin";
58+
const RUST_BIN_DIR: &str = "bin";
59+
const RUST_LIB_DIR: &str = "lib";
60+
61+
pub struct Toolchain {
62+
root: std::path::PathBuf,
63+
}
64+
65+
impl Toolchain {
66+
pub fn resolve() -> anyhow::Result<Self> {
67+
let location = resolve_location();
68+
let root = CONFIG
69+
.resolve_installation_dir(location)
70+
.context("Toolchain not installed. Please run 'cargo anneal setup' first.")?;
71+
Ok(Self { root })
72+
}
73+
74+
pub fn root(&self) -> &std::path::Path {
75+
&self.root
76+
}
77+
78+
pub fn aeneas_bin_dir(&self) -> std::path::PathBuf {
79+
self.root.join(AENEAS_DIR).join(AENEAS_BIN_DIR)
80+
}
81+
82+
pub fn rust_sysroot(&self) -> std::path::PathBuf {
83+
self.root.join(RUST_SYSROOT)
84+
}
85+
86+
pub fn rust_bin(&self) -> std::path::PathBuf {
87+
self.rust_sysroot().join(RUST_BIN_DIR)
88+
}
89+
90+
pub fn rust_lib(&self) -> std::path::PathBuf {
91+
self.rust_sysroot().join(RUST_LIB_DIR)
92+
}
93+
94+
pub fn command(&self, tool: Tool) -> anyhow::Result<std::process::Command> {
95+
let mut cmd = std::process::Command::new(tool.path(self));
96+
cmd.env_clear();
97+
match tool {
98+
Tool::Cargo | Tool::Rustc => {}
99+
Tool::Charon => {
100+
// The archive supplies Rust tools, but not host build tools
101+
// such as the linker. Keep the caller's `PATH` after our Rust
102+
// bin directory so Cargo builds use the managed Rust toolchain
103+
// while still finding those host tools.
104+
cmd.env("CHARON_TOOLCHAIN_IS_IN_PATH", "1")
105+
.env("PATH", prepend_current_path(self.rust_bin())?)
106+
.env(rust_library_path_env_var(), self.rust_lib());
107+
}
108+
}
109+
Ok(cmd)
110+
}
111+
}
112+
113+
fn prepend_current_path(path: std::path::PathBuf) -> anyhow::Result<std::ffi::OsString> {
114+
let mut paths = vec![path];
115+
if let Some(current_path) = std::env::var_os("PATH") {
116+
paths.extend(std::env::split_paths(&current_path));
117+
}
118+
std::env::join_paths(paths).context("failed to construct PATH for tool command")
119+
}
120+
121+
/// Returns the platform library search path variable used by Rust tools.
122+
pub(crate) fn rust_library_path_env_var() -> &'static str {
123+
if cfg!(target_os = "macos") { "DYLD_LIBRARY_PATH" } else { "LD_LIBRARY_PATH" }
124+
}
125+
126+
pub fn run_setup(args: SetupArgs) -> anyhow::Result<()> {
127+
let location = resolve_location();
128+
let source = match args.local_archive {
129+
Some(local_archive) => exocrate::Source::Local(local_archive),
130+
None => exocrate::Source::Remote(REMOTE),
131+
};
132+
133+
let (installation_dir, status) = CONFIG
134+
.resolve_installation_dir_or_install(location, source)
135+
.context("failed to resolve-or-install dependencies")?;
136+
if status == exocrate::ResolvedOrInstalled::ResolvedExisting {
137+
log::warn!("anneal toolchain was already installed at {:?}", installation_dir);
138+
} else {
139+
log::info!("anneal toolchain freshly installed at {:?}", installation_dir);
140+
}
141+
Ok(())
142+
}
143+
144+
fn resolve_location() -> exocrate::Location {
145+
if std::env::var("__ANNEAL_LOCAL_DEV").is_ok() {
146+
exocrate::Location::LocalDev
147+
} else {
148+
exocrate::Location::UserGlobal
149+
}
150+
}

exocrate/src/lib.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
//! };
7474
//!
7575
//! // Check whether `source` archive is already installed at `location`, and if not, install it.
76-
//! let installed_exocrate_dir = CONFIG.resolve_installation_dir_or_install(location, source)
76+
//! let (installed_exocrate_dir, _) = CONFIG.resolve_installation_dir_or_install(location, source)
7777
//! .expect("failed to resolve or install my-tool's exocrate");
7878
//!
7979
//! // Invoke tool installed from `tests/my-tool-deps.tar.zst` extracted to versioned exocrate
@@ -186,6 +186,15 @@ pub enum Source {
186186
Local(PathBuf),
187187
}
188188

189+
/// Whether an installation was resolved or newly installed.
190+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
191+
pub enum ResolvedOrInstalled {
192+
/// An existing installation was resolved.
193+
ResolvedExisting,
194+
/// A new installation was installed.
195+
NewlyInstalled,
196+
}
197+
189198
impl Config {
190199
/// Resolves the dependency directory, failing if it doesn't exist.
191200
pub fn resolve_installation_dir(&self, location: Location) -> IoResult<PathBuf> {
@@ -199,14 +208,14 @@ impl Config {
199208
&self,
200209
location: Location,
201210
source: Source,
202-
) -> IoResult<PathBuf> {
211+
) -> IoResult<(PathBuf, ResolvedOrInstalled)> {
203212
let dir_path = self.dir_path(location);
204213
if ManagedDirName::new(&dir_path).check_exists().is_ok() {
205-
return Ok(dir_path);
214+
return Ok((dir_path, ResolvedOrInstalled::ResolvedExisting));
206215
}
207216
let (reader, expected_sha) = self.open_source(source)?;
208217
install(reader, &dir_path, expected_sha)?;
209-
Ok(dir_path)
218+
Ok((dir_path, ResolvedOrInstalled::NewlyInstalled))
210219
}
211220

212221
/// Opens the given source.
@@ -763,19 +772,21 @@ mod tests {
763772
let dev_path = config.dir_path(Location::LocalDev);
764773
let _ = fs::remove_dir_all(&dev_path);
765774

766-
let resolved = config
775+
let (resolved, status) = config
767776
.resolve_installation_dir_or_install(Location::LocalDev, Source::Local(archive_path))
768777
.unwrap();
769778
assert_eq!(resolved, dev_path);
779+
assert_eq!(status, ResolvedOrInstalled::NewlyInstalled);
770780
assert!(dev_path.join("bin/compiler").exists());
771781

772-
let resolved2 = config
782+
let (resolved2, status2) = config
773783
.resolve_installation_dir_or_install(
774784
Location::LocalDev,
775785
Source::Local(PathBuf::from("/nonexistent/path/should/not/be/accessed")),
776786
)
777787
.unwrap();
778788
assert_eq!(resolved2, dev_path);
789+
assert_eq!(status2, ResolvedOrInstalled::ResolvedExisting);
779790

780791
fs::remove_dir_all(&dev_path).unwrap();
781792
}

0 commit comments

Comments
 (0)