Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions anneal/v2/toolchain-config/build.rs

This file was deleted.

285 changes: 94 additions & 191 deletions anneal/v2/toolchain-config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,139 +1,47 @@
/// Decodes a hexadecimal string into its byte representation.
const fn decode_hex(s: &str) -> Option<[u8; 32]> {
let bytes = s.as_bytes();
if bytes.len() != 64 {
return None;
}
let mut res = [0u8; 32];
let mut i = 0;
while i < 32 {
let (h, l) = (bytes[i * 2], bytes[i * 2 + 1]);
let h_nib = match decode_nibble(h) {
Some(n) => n,
None => return None,
};
let l_nib = match decode_nibble(l) {
Some(n) => n,
None => return None,
};
res[i] = (h_nib << 4) | l_nib;
i += 1;
}
Some(res)
/// Setup configuration binding external platform environments and baseline paths.
#[derive(Debug, Clone)]
pub struct Config<'a> {
pub os: &'a str,
pub arch: &'a str,
pub url: &'a str,
pub sha256: [u8; 32],
}

const fn decode_nibble(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
/// Optional runtime override directing installation from local dev builds instead of remote hosts.
#[derive(Debug, Clone)]
pub enum LocalOverride {
Dir(std::path::PathBuf),
Archive(std::path::PathBuf),
}

macro_rules! decode_hex_env {
($key:expr) => {
const { decode_hex(env!($key)).expect("valid hex") }
};
}

/// Supported platforms for Anneal dependencies.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Platform {
LinuxX86_64,
LinuxAArch64,
MacosAArch64,
MacosX86_64,
}

impl Platform {
/// Returns the target triple for this platform.
pub fn triple(&self) -> &'static str {
match self {
Self::LinuxX86_64 => "x86_64-unknown-linux-gnu",
Self::LinuxAArch64 => "aarch64-unknown-linux-gnu",
Self::MacosAArch64 => "aarch64-apple-darwin",
Self::MacosX86_64 => "x86_64-apple-darwin",
}
}

/// Detects the current host platform.
pub fn detect() -> Result<Self, String> {
let os = std::env::consts::OS;
let arch = std::env::consts::ARCH;

match (os, arch) {
("linux", "x86_64") => Ok(Self::LinuxX86_64),
("linux", "aarch64") => Ok(Self::LinuxAArch64),
("macos", "aarch64") => Ok(Self::MacosAArch64),
("macos", "x86_64") => Ok(Self::MacosX86_64),
_ => Err(format!("Unsupported platform: {}-{}", os, arch)),
impl<'a> Config<'a> {
/// Instantiates static toolchain parameters auto-detecting current runtime OS and Architecture.
pub fn new(url: &'a str, sha256: [u8; 32]) -> Self {
Self {
os: std::env::consts::OS,
arch: std::env::consts::ARCH,
url,
sha256,
}
}

/// Returns the expected SHA-256 checksum for the specified archive on this
/// platform.
///
/// These hashes are baked into the binary at compile time from values
/// provided by `build.rs` from the project's `Cargo.toml`. This ensures
/// that we always download and verify the exact versions of dependencies
/// that this version of Anneal was built to work with.
pub fn expected_archive_hash(&self) -> [u8; 32] {
use Platform::*;
match self {
LinuxX86_64 => decode_hex_env!("SETUP_ARCHIVE_SHA256_LINUX_X86_64"),
LinuxAArch64 => decode_hex_env!("SETUP_ARCHIVE_SHA256_LINUX_AARCH64"),
MacosAArch64 => decode_hex_env!("SETUP_ARCHIVE_SHA256_MACOS_AARCH64"),
MacosX86_64 => decode_hex_env!("SETUP_ARCHIVE_SHA256_MACOS_X86_64"),
/// Explicitly overrides target platform parameters for specialized configurations.
pub fn new_platform(os: &'a str, arch: &'a str, url: &'a str, sha256: [u8; 32]) -> Self {
Self {
os,
arch,
url,
sha256,
}
}

/// Returns the baseline remote URL from which the pre-verified toolchain archive
/// for this platform can be downloaded.
///
/// This function constructs an absolute URL by interpolating the target platform's
/// specific archive filename onto a base URL baked into the binary at compile time
/// via `SETUP_ARCHIVE_BASE_URL`. This compile-time binding guarantees that downloads
/// exactly correspond to the release artifacts verified during the build phase.
pub fn remote_url(&self) -> String {
use Platform::*;
const BASE: &str = env!("SETUP_ARCHIVE_BASE_URL");
match self {
LinuxX86_64 => format!("{BASE}/anneal-toolchain-linux-x86_64.tar.zst"),
LinuxAArch64 => format!("{BASE}/anneal-toolchain-linux-aarch64.tar.zst"),
MacosAArch64 => format!("{BASE}/anneal-toolchain-macos-aarch64.tar.zst"),
MacosX86_64 => format!("{BASE}/anneal-toolchain-macos-x64_64.tar.zst"),
}
/// Resolves the deterministic subdirectory path containing the verified toolchain files.
pub fn toolchain_dir(&self, root: &std::path::Path) -> std::path::PathBuf {
let expected_hex = encode_hex(&self.sha256);
root.join(&expected_hex[..6])
}
}

/// Setup configuration
pub struct Config {}

/// An archive format that can be extracted using a particular [`Extractor`] implementation.
pub enum ArchiveFormat {
TarZst,
}

impl ArchiveFormat {
fn new_extractor(&self) -> impl Extractor {
use ArchiveFormat::*;
match self {
TarZst => TarZstLibraryExtractor,
}
}
}

/// The source for an archive of dependencies that `setup` must put in place.
pub enum Source {
/// A fully assembled local directory tree containing uncompressed toolchain components.
LocalDirectory(std::path::PathBuf),
/// A local archive awaiting extraction.
LocalArchive(std::path::PathBuf, ArchiveFormat),
/// The archive format to expect from the statically configured remote URL source.
Remote(ArchiveFormat),
}

/// An abstract extraction factory instantiating operational output streams targeting designated
/// filesystem paths.
///
Expand Down Expand Up @@ -289,50 +197,48 @@ fn setup_from_directory(src: &std::path::Path, dst: &std::path::Path) -> Result<
}

fn setup_inner(
src: Source,
config: &Config<'_>,
local_override: Option<LocalOverride>,
toolchain_dir: std::path::PathBuf,
expected_hash: [u8; 32],
fetcher: impl FnOnce(&str) -> Result<Box<dyn std::io::Read>, String>,
) -> Result<(), String> {
let expected_hex = encode_hex(&expected_hash);
let subdir_name = &expected_hex[..6];
let target_dir = toolchain_dir.join(subdir_name);

use Source::*;
match src {
LocalArchive(path, format) => {
log::warn!(
"Toolchain contents from local archive may not match expected toolchain hash/version number."
);
let extractor = format.new_extractor();
let file = std::fs::File::open(path)
.map_err(|e| format!("Failed to open local archive: {e}"))?;
setup_from_archive(file, &target_dir, &extractor)
.map_err(|e| format!("Failed to extract archive: {e}"))?;
}
LocalDirectory(path) => {
log::warn!(
"Toolchain contents from local directory may not match expected toolchain hash/version number."
);
setup_from_directory(&path, &target_dir)?;
}
Remote(format) => {
let extractor = format.new_extractor();
let platform = Platform::detect().map_err(|e| format!("Failed to detect platform: {e}"))?;
let response = fetcher(&platform.remote_url())?;

let actual_hash: [u8; 32] = setup_from_archive(response, &target_dir, &extractor)
.map_err(|e| format!("Failed to extract downloaded archive: {e}"))?;

if actual_hash != expected_hash {
let _ = std::fs::remove_dir_all(&target_dir);
return Err(format!(
"Checksum mismatch for downloaded archive. Expected {}, got {}",
expected_hex,
encode_hex(&actual_hash)
));
let target_dir = config.toolchain_dir(&toolchain_dir);
let expected_hex = encode_hex(&config.sha256);

if let Some(override_src) = local_override {
match override_src {
LocalOverride::Archive(path) => {
log::warn!(
"Toolchain contents from local archive may not match expected toolchain hash/version number."
);
let extractor = TarZstLibraryExtractor;
let file = std::fs::File::open(path)
.map_err(|e| format!("Failed to open local archive: {e}"))?;
setup_from_archive(file, &target_dir, &extractor)
.map_err(|e| format!("Failed to extract archive: {e}"))?;
}
LocalOverride::Dir(path) => {
log::warn!(
"Toolchain contents from local directory may not match expected toolchain hash/version number."
);
setup_from_directory(&path, &target_dir)?;
}
}
} else {
let extractor = TarZstLibraryExtractor;
let response = fetcher(config.url)?;

let actual_hash: [u8; 32] = setup_from_archive(response, &target_dir, &extractor)
.map_err(|e| format!("Failed to extract downloaded archive: {e}"))?;

if actual_hash != config.sha256 {
let _ = std::fs::remove_dir_all(&target_dir);
return Err(format!(
"Checksum mismatch for downloaded archive. Expected {}, got {}",
expected_hex,
encode_hex(&actual_hash)
));
}
}

Ok(())
Expand All @@ -342,17 +248,12 @@ fn setup_inner(
///
/// This function processes the incoming dependency source and installs it into a toolchain
/// directory named according to the source SHA256 hash.
///
/// # Caveats
///
/// - When `src` is a `Source::Local*` variant, the toolchain will be installed in a directory
/// named after the _expected_ SHA256 hash associated with the detected platform. This will not
/// necessarily match the behaviour of the platform's archive and should only be used for local
/// development of the managed toolchain itself.
pub fn setup(src: Source, toolchain_dir: std::path::PathBuf) -> Result<(), String> {
let platform = Platform::detect().expect("detect platform");
let expected_hash = platform.expected_archive_hash();
setup_inner(src, toolchain_dir, expected_hash, |url| {
pub fn setup(
config: &Config<'_>,
local_override: Option<LocalOverride>,
toolchain_dir: std::path::PathBuf,
) -> Result<(), String> {
setup_inner(config, local_override, toolchain_dir, |url| {
let response = reqwest::blocking::get(url)
.map_err(|e| format!("Failed to download archive: {e}"))?;
let response = response
Expand Down Expand Up @@ -431,13 +332,13 @@ mod tests {
std::fs::write(src.join("test.txt"), "local_dir").unwrap();

let expected_hash = [1u8; 32];
let expected_hex = encode_hex(&expected_hash);
let target_dir = temp.path().join(&expected_hex[..6]);
let config = Config::new("http://example.com", expected_hash);
let target_dir = config.toolchain_dir(temp.path());

setup_inner(
Source::LocalDirectory(src),
&config,
Some(LocalOverride::Dir(src)),
temp.path().to_path_buf(),
expected_hash,
|_| unreachable!(),
)
.unwrap();
Expand All @@ -456,13 +357,13 @@ mod tests {
create_test_archive(&src, &archive_path);

let expected_hash = [2u8; 32];
let expected_hex = encode_hex(&expected_hash);
let target_dir = temp.path().join(&expected_hex[..6]);
let config = Config::new("http://example.com", expected_hash);
let target_dir = config.toolchain_dir(temp.path());

setup_inner(
Source::LocalArchive(archive_path, ArchiveFormat::TarZst),
&config,
Some(LocalOverride::Archive(archive_path)),
temp.path().to_path_buf(),
expected_hash,
|_| unreachable!(),
)
.unwrap();
Expand All @@ -481,15 +382,16 @@ mod tests {
create_test_archive(&src, &archive_path);

let actual_hash = compute_sha256(&archive_path);
let expected_hex = encode_hex(&actual_hash);
let target_dir = temp.path().join(&expected_hex[..6]);
let config = Config::new("http://example.com", actual_hash);
let target_dir = config.toolchain_dir(temp.path());

let archive_path_clone = archive_path.clone();
setup_inner(
Source::Remote(ArchiveFormat::TarZst),
&config,
None,
temp.path().to_path_buf(),
actual_hash,
move |_url| {
let file = std::fs::File::open(&archive_path).unwrap();
let file = std::fs::File::open(&archive_path_clone).unwrap();
Ok(Box::new(file))
},
)
Expand All @@ -512,15 +414,16 @@ mod tests {
let mut expected_hash = actual_hash;
expected_hash[0] ^= 1; // invalidate checksum

let expected_hex = encode_hex(&expected_hash);
let target_dir = temp.path().join(&expected_hex[..6]);
let config = Config::new("http://example.com", expected_hash);
let target_dir = config.toolchain_dir(temp.path());

let archive_path_clone = archive_path.clone();
let res = setup_inner(
Source::Remote(ArchiveFormat::TarZst),
&config,
None,
temp.path().to_path_buf(),
expected_hash,
move |_url| {
let file = std::fs::File::open(&archive_path).unwrap();
let file = std::fs::File::open(&archive_path_clone).unwrap();
Ok(Box::new(file))
},
);
Expand Down
Loading