#![cfg_attr(not(all(unix, feature = "download")), allow(dead_code, unused_imports))]
#[cfg(all(unix, feature = "download"))]
mod cache;
#[cfg(all(unix, feature = "download"))]
mod download;
mod pins;
#[cfg(all(unix, feature = "download"))]
mod tar;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use crate::error::Error;
use crate::options::Options;
use crate::process::BINARY_ENV;
use pins::{PINS, PINS_VERSION, Pin};
const DEFAULT_BASE: &str = "https://megacode-harness.vercel.app/dl";
const BASE_ENV: &str = "MEGACODE_AGENT_SDK_DOWNLOAD_BASE";
const TARGETS: [&str; 3] = [
"x86_64-unknown-linux-musl",
"aarch64-apple-darwin",
"x86_64-apple-darwin",
];
pub(crate) async fn locate(options: &Options) -> Result<PathBuf, Error> {
if let Some(path) = named(options.binary.as_deref(), std::env::var_os(BINARY_ENV)) {
return Ok(path);
}
#[cfg(all(unix, feature = "download"))]
{
Release::current()?.fetch().await
}
#[cfg(not(all(unix, feature = "download")))]
{
Err(Error::BinaryUnavailable(format!(
"no binary was named, and this build of the SDK does not download one: set \
Options::binary or {BINARY_ENV}"
)))
}
}
fn named(option: Option<&Path>, env: Option<OsString>) -> Option<PathBuf> {
match (option, env) {
(Some(path), _) => Some(path.to_path_buf()),
(None, Some(path)) if !path.is_empty() => Some(PathBuf::from(path)),
_ => None,
}
}
fn target_for(os: &str, arch: &str) -> Result<&'static str, Error> {
match (os, arch) {
("linux", "x86_64") => Ok(TARGETS[0]),
("macos", "aarch64") => Ok(TARGETS[1]),
("macos", "x86_64") => Ok(TARGETS[2]),
_ => Err(Error::BinaryUnavailable(format!(
"no harness release is built for {os} on {arch}: releases exist for {}, {} and {}. \
Name an installed harness with the `binary` option (Options::binary) or \
{BINARY_ENV}",
TARGETS[0], TARGETS[1], TARGETS[2]
))),
}
}
fn cache_root_from(
os: &str,
xdg_cache_home: Option<OsString>,
home: Option<OsString>,
) -> Result<PathBuf, Error> {
if os != "macos"
&& let Some(xdg) = xdg_cache_home.map(PathBuf::from)
&& xdg.is_absolute()
{
return Ok(xdg);
}
let home = home
.map(PathBuf::from)
.filter(|h| h.is_absolute())
.ok_or_else(|| {
Error::BinaryUnavailable(format!(
"no cache folder for the harness download: HOME is not set to an absolute \
path. Name an installed harness with Options::binary or {BINARY_ENV}"
))
})?;
Ok(if os == "macos" {
home.join("Library").join("Caches")
} else {
home.join(".cache")
})
}
fn pinned(pins_version: &str, pins: &[(&str, Pin)], version: &str, target: &str) -> Option<Pin> {
if pins_version != version {
return None;
}
pins.iter().find(|(t, _)| *t == target).map(|(_, p)| *p)
}
fn hex(bytes: &[u8]) -> String {
use std::fmt::Write as _;
bytes.iter().fold(String::new(), |mut s, b| {
let _ = write!(s, "{b:02x}");
s
})
}
#[cfg(all(unix, feature = "download"))]
#[derive(Debug, Clone)]
pub struct Release {
version: String,
target: String,
base: String,
cache: PathBuf,
archive_sha256: String,
executable_sha256: String,
}
#[cfg(all(unix, feature = "download"))]
impl Release {
pub fn new(
version: &str,
target: &str,
base: &str,
cache: &Path,
archive_sha256: &str,
executable_sha256: &str,
) -> Release {
Release {
version: version.into(),
target: target.into(),
base: base.trim_end_matches('/').into(),
cache: cache.to_path_buf(),
archive_sha256: archive_sha256.into(),
executable_sha256: executable_sha256.into(),
}
}
fn current() -> Result<Release, Error> {
let target = target_for(std::env::consts::OS, std::env::consts::ARCH)?;
let version = env!("CARGO_PKG_VERSION");
let pin = pinned(PINS_VERSION, PINS, version, target).ok_or_else(|| {
Error::BinaryUnavailable(format!(
"this build of megacode-agent-sdk {version} carries no pinned harness release \
for {target}, so it cannot download one. Name an installed harness with \
Options::binary or {BINARY_ENV}"
))
})?;
let base = std::env::var(BASE_ENV)
.ok()
.filter(|b| !b.is_empty())
.unwrap_or_else(|| DEFAULT_BASE.to_string());
let cache = cache_root_from(
std::env::consts::OS,
std::env::var_os("XDG_CACHE_HOME"),
std::env::var_os("HOME"),
)?;
Ok(Release::new(
version,
target,
&base,
&cache,
pin.archive_sha256,
pin.executable_sha256,
))
}
pub async fn fetch(&self) -> Result<PathBuf, Error> {
use cache::Cached;
let layout = cache::Layout::new(&self.cache, &self.version, &self.target);
if let Cached::Verified = self.check(&layout).await? {
return Ok(layout.file);
}
let executable = self.download().await.map_err(Error::BinaryUnavailable)?;
let (l, digest) = (layout.clone(), self.executable_sha256.clone());
tokio::task::spawn_blocking(move || cache::install(&l, &executable, &digest))
.await
.map_err(|e| Error::BinaryUnavailable(format!("the cache write stopped: {e}")))??;
match self.check(&layout).await? {
Cached::Verified => Ok(layout.file),
Cached::Missing | Cached::Mismatch => Err(Error::BinaryUnavailable(format!(
"the harness cached at {} does not match its pinned SHA-256 after a fresh \
download; it is not run",
layout.file.display()
))),
}
}
async fn check(&self, layout: &cache::Layout) -> Result<cache::Cached, Error> {
let (l, digest) = (layout.clone(), self.executable_sha256.clone());
tokio::task::spawn_blocking(move || cache::check(&l, &digest))
.await
.map_err(|e| Error::BinaryUnavailable(format!("the cache check stopped: {e}")))?
}
async fn download(&self) -> Result<Vec<u8>, String> {
let url = format!(
"{}/v{}/harness-{}.tar.gz",
self.base, self.version, self.target
);
let archive = download::archive(&url, &self.archive_sha256).await?;
let digest = self.executable_sha256.clone();
let unpacked = tokio::task::spawn_blocking(move || {
let executable = tar::executable(&archive)?;
let got = {
use sha2::Digest as _;
hex(&sha2::Sha256::digest(&executable))
};
if got != digest {
return Err(format!(
"the harness in the archive does not match its pinned SHA-256 (got {got}, \
pinned {digest})"
));
}
Ok(executable)
})
.await
.map_err(|e| e.to_string())?;
unpacked.map_err(|why| format!("cannot use {url}: {why}; nothing was cached"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_option_comes_before_the_environment_and_both_before_the_cache() {
let opt = Path::new("/opt/harness");
assert_eq!(
named(Some(opt), Some("/env/harness".into())),
Some(opt.to_path_buf())
);
assert_eq!(
named(None, Some("/env/harness".into())),
Some(PathBuf::from("/env/harness"))
);
assert_eq!(named(None, Some(OsString::new())), None);
assert_eq!(named(None, None), None);
}
#[test]
fn another_platform_is_told_the_three_targets_and_the_binary_option() {
assert_eq!(
target_for("linux", "x86_64").unwrap(),
"x86_64-unknown-linux-musl"
);
assert_eq!(
target_for("macos", "aarch64").unwrap(),
"aarch64-apple-darwin"
);
assert_eq!(
target_for("macos", "x86_64").unwrap(),
"x86_64-apple-darwin"
);
for (os, arch) in [("linux", "aarch64"), ("windows", "x86_64")] {
let Err(Error::BinaryUnavailable(why)) = target_for(os, arch) else {
panic!("{os} {arch}");
};
for t in TARGETS {
assert!(why.contains(t), "{why}");
}
assert!(why.contains("`binary` option"), "{why}");
}
}
#[test]
fn the_cache_folder_per_platform() {
let some = |s: &str| Some(OsString::from(s));
assert_eq!(
cache_root_from("linux", some("/x/cache"), some("/home/u")).unwrap(),
PathBuf::from("/x/cache")
);
assert_eq!(
cache_root_from("linux", some("relative"), some("/home/u")).unwrap(),
PathBuf::from("/home/u/.cache")
);
assert_eq!(
cache_root_from("linux", None, some("/home/u")).unwrap(),
PathBuf::from("/home/u/.cache")
);
assert_eq!(
cache_root_from("macos", some("/x/cache"), some("/Users/u")).unwrap(),
PathBuf::from("/Users/u/Library/Caches")
);
assert!(cache_root_from("linux", None, None).is_err());
assert!(cache_root_from("linux", None, some("relative")).is_err());
}
#[test]
fn pins_of_another_version_are_no_pins() {
let pin = Pin {
archive_sha256: "a",
executable_sha256: "b",
};
let pins = [("x86_64-unknown-linux-musl", pin)];
assert_eq!(
pinned("0.30.0", &pins, "0.30.0", "x86_64-unknown-linux-musl"),
Some(pin)
);
assert_eq!(
pinned("0.30.0", &pins, "0.31.0", "x86_64-unknown-linux-musl"),
None
);
assert_eq!(
pinned("0.30.0", &pins, "0.30.0", "aarch64-apple-darwin"),
None
);
}
#[test]
fn the_pins_are_empty_or_well_formed() {
if PINS.is_empty() {
return;
}
let digest =
|d: &str| d.len() == 64 && d.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'));
assert!(!PINS_VERSION.is_empty(), "pins without a version");
let mut targets: Vec<&str> = PINS.iter().map(|(t, _)| *t).collect();
targets.sort_unstable();
let mut known = TARGETS.to_vec();
known.sort_unstable();
assert_eq!(targets, known, "one pin per release target");
for (t, p) in PINS {
assert!(digest(p.archive_sha256), "{t}: {}", p.archive_sha256);
assert!(digest(p.executable_sha256), "{t}: {}", p.executable_sha256);
}
}
}