From 5559e8cb01e10f9e734d9430bcceea803f0ce5e7 Mon Sep 17 00:00:00 2001 From: Mark Dittmer Date: Sat, 23 May 2026 14:32:58 +0000 Subject: [PATCH] [anneal][v2] Add exocrate toolchain setup and Toolchain resolver TAG=agy gherrit-pr-id: Gbbpbt76nsgp2ohpclea46vot5joxx7b5 --- anneal/v2/src/main.rs | 7 +- anneal/v2/src/setup.rs | 154 +++++++++++++++++++++++++++++++++++++++++ exocrate/src/lib.rs | 23 ++++-- 3 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 anneal/v2/src/setup.rs diff --git a/anneal/v2/src/main.rs b/anneal/v2/src/main.rs index 81caf2cd76..4e799a8a9c 100644 --- a/anneal/v2/src/main.rs +++ b/anneal/v2/src/main.rs @@ -9,6 +9,8 @@ use clap::Parser as _; +#[allow(dead_code)] +mod setup; #[allow(dead_code)] mod util; @@ -60,10 +62,11 @@ fn setup_installation_dir(args: SetupArgs) -> std::path::PathBuf { None => exocrate::Source::Remote(REMOTE), }; - CONFIG + let (installation_dir, _) = CONFIG .resolve_installation_dir_or_install(location, source) // FIXME: Implement unified error reporting (e.g., via `anyhow`). - .expect("failed to resolve-or-install dependencies") + .expect("failed to resolve-or-install dependencies"); + installation_dir } fn setup(args: SetupArgs) { diff --git a/anneal/v2/src/setup.rs b/anneal/v2/src/setup.rs new file mode 100644 index 0000000000..9e17911f4d --- /dev/null +++ b/anneal/v2/src/setup.rs @@ -0,0 +1,154 @@ +// Copyright 2026 The Fuchsia Authors +// +// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 +// , or the MIT +// license , at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. + +use anyhow::Context as _; + +pub struct SetupArgs { + pub local_archive: Option, +} + +exocrate::config! { + pub const CONFIG: Config = Config { + rel_dir_path: [".anneal", "toolchain"], + versioned_files: &["../Cargo.toml", "../Cargo.lock"], + }; +} + +exocrate::parse_remote_archive! { + pub const REMOTE: RemoteArchive = "Cargo.toml" [ + (linux, x86_64), + (macos, x86_64), + (linux, aarch64), + (macos, aarch64), + ]; +} + +pub enum Tool { + Cargo, + Charon, + Rustc, +} + +impl Tool { + pub fn name(&self) -> &'static str { + match self { + Self::Cargo => "cargo", + Self::Charon => "charon", + Self::Rustc => "rustc", + } + } + + pub fn path(&self, toolchain: &Toolchain) -> std::path::PathBuf { + match self { + Self::Cargo | Self::Rustc => toolchain.rust_bin().join(self.name()), + Self::Charon => toolchain.aeneas_bin_dir().join(self.name()), + } + } +} + +const AENEAS_DIR: &str = "aeneas"; +const RUST_SYSROOT: &str = "rust"; +const AENEAS_BIN_DIR: &str = "bin"; +const RUST_BIN_DIR: &str = "bin"; +const RUST_LIB_DIR: &str = "lib"; + +pub struct Toolchain { + root: std::path::PathBuf, +} + +impl Toolchain { + pub fn resolve() -> anyhow::Result { + let location = resolve_location(); + let root = CONFIG + .resolve_installation_dir(location) + .context("Toolchain not installed. Please run 'cargo anneal setup' first.")?; + Ok(Self { root }) + } + + #[cfg(all(test, feature = "exocrate_tests"))] + pub fn root(&self) -> &std::path::Path { + &self.root + } + + pub fn aeneas_bin_dir(&self) -> std::path::PathBuf { + self.root.join(AENEAS_DIR).join(AENEAS_BIN_DIR) + } + + pub fn rust_sysroot(&self) -> std::path::PathBuf { + self.root.join(RUST_SYSROOT) + } + + pub fn rust_bin(&self) -> std::path::PathBuf { + self.rust_sysroot().join(RUST_BIN_DIR) + } + + pub fn rust_lib(&self) -> std::path::PathBuf { + self.rust_sysroot().join(RUST_LIB_DIR) + } + + pub fn command(&self, tool: Tool) -> anyhow::Result { + let mut cmd = std::process::Command::new(tool.path(self)); + cmd.env_clear(); + match tool { + Tool::Cargo | Tool::Rustc => {} + Tool::Charon => { + // The archive supplies Rust tools, but not host build tools + // such as the linker. Keep the caller's `PATH` after our Rust + // bin directory so Cargo builds use the managed Rust toolchain + // while still finding those host tools. + cmd.env("CHARON_TOOLCHAIN_IS_IN_PATH", "1") + .env("PATH", prepend_current_path(self.rust_bin())?) + .env(rust_library_path_env_var(), self.rust_lib()); + } + } + Ok(cmd) + } +} + +fn prepend_current_path(path: std::path::PathBuf) -> anyhow::Result { + let mut paths = vec![path]; + if let Some(current_path) = std::env::var_os("PATH") { + paths.extend(std::env::split_paths(¤t_path)); + } + std::env::join_paths(paths).context("failed to construct PATH for tool command") +} + +/// Returns the platform library search path variable used by Rust tools. +pub(crate) fn rust_library_path_env_var() -> &'static str { + if cfg!(target_os = "macos") { "DYLD_LIBRARY_PATH" } else { "LD_LIBRARY_PATH" } +} + +pub fn run_setup(args: SetupArgs) -> anyhow::Result<()> { + let location = resolve_location(); + let source = match args.local_archive { + Some(local_archive) => exocrate::Source::Local(local_archive), + None => exocrate::Source::Remote(REMOTE), + }; + + let (installation_dir, status) = CONFIG + .resolve_installation_dir_or_install(location, source) + .context("failed to resolve-or-install dependencies")?; + match status { + exocrate::ResolvedOrInstalled::ResolvedExisting => { + log::warn!("anneal toolchain was already installed at {:?}", installation_dir); + } + exocrate::ResolvedOrInstalled::NewlyInstalled => { + log::info!("anneal toolchain freshly installed at {:?}", installation_dir); + } + } + Ok(()) +} + +fn resolve_location() -> exocrate::Location { + if std::env::var("__ANNEAL_LOCAL_DEV").is_ok() { + exocrate::Location::LocalDev + } else { + exocrate::Location::UserGlobal + } +} diff --git a/exocrate/src/lib.rs b/exocrate/src/lib.rs index 3032fc4264..fdbf1efcbf 100644 --- a/exocrate/src/lib.rs +++ b/exocrate/src/lib.rs @@ -73,7 +73,7 @@ //! }; //! //! // Check whether `source` archive is already installed at `location`, and if not, install it. -//! let installed_exocrate_dir = CONFIG.resolve_installation_dir_or_install(location, source) +//! let (installed_exocrate_dir, _) = CONFIG.resolve_installation_dir_or_install(location, source) //! .expect("failed to resolve or install my-tool's exocrate"); //! //! // Invoke tool installed from `tests/my-tool-deps.tar.zst` extracted to versioned exocrate @@ -186,6 +186,15 @@ pub enum Source { Local(PathBuf), } +/// Whether an installation was resolved or newly installed. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ResolvedOrInstalled { + /// An existing installation was resolved. + ResolvedExisting, + /// A new installation was installed. + NewlyInstalled, +} + impl Config { /// Resolves the dependency directory, failing if it doesn't exist. pub fn resolve_installation_dir(&self, location: Location) -> IoResult { @@ -199,14 +208,14 @@ impl Config { &self, location: Location, source: Source, - ) -> IoResult { + ) -> IoResult<(PathBuf, ResolvedOrInstalled)> { let dir_path = self.dir_path(location)?; if ManagedDirName::new(&dir_path).check_exists().is_ok() { - return Ok(dir_path); + return Ok((dir_path, ResolvedOrInstalled::ResolvedExisting)); } let (reader, expected_sha) = self.open_source(source)?; install(reader, &dir_path, expected_sha)?; - Ok(dir_path) + Ok((dir_path, ResolvedOrInstalled::NewlyInstalled)) } /// Opens the given source. @@ -766,19 +775,21 @@ mod tests { let dev_path = config.dir_path(Location::LocalDev).unwrap(); let _ = fs::remove_dir_all(&dev_path); - let resolved = config + let (resolved, status) = config .resolve_installation_dir_or_install(Location::LocalDev, Source::Local(archive_path)) .unwrap(); assert_eq!(resolved, dev_path); + assert_eq!(status, ResolvedOrInstalled::NewlyInstalled); assert!(dev_path.join("bin/compiler").exists()); - let resolved2 = config + let (resolved2, status2) = config .resolve_installation_dir_or_install( Location::LocalDev, Source::Local(PathBuf::from("/nonexistent/path/should/not/be/accessed")), ) .unwrap(); assert_eq!(resolved2, dev_path); + assert_eq!(status2, ResolvedOrInstalled::ResolvedExisting); fs::remove_dir_all(&dev_path).unwrap(); }