use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub mod brew_emulate;
pub mod coverage;
pub mod error;
pub mod executor;
pub mod formula;
pub mod lockfile;
pub mod manifest;
pub mod package_index;
pub mod prebuilt;
pub mod recipe;
pub mod recipe_choco;
pub mod registry;
pub mod relocate;
pub mod source_build;
pub mod windows;
pub use error::{Result, ToolchainError};
pub use lockfile::{LockedTool, ToolchainLockfile, ToolchainLockfileExt, LOCKFILE_NAME};
use crate::coverage::{CoverageRecord, CoverageStatus};
use crate::registry::{PublishedToolchain, PulledToolchain, ToolchainArtifactId};
use crate::relocate::RelocationReport;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolPlatform {
MacOS,
Windows,
}
#[derive(Debug, Clone)]
pub struct ToolchainHandle {
pub install_dir: PathBuf,
pub path_dirs: Vec<String>,
pub env: HashMap<String, String>,
}
fn arch_token() -> &'static str {
match std::env::consts::ARCH {
"aarch64" => "arm64",
other => other,
}
}
fn split_pkg(pkg: &str) -> (&str, &str) {
match pkg.split_once('@') {
Some((_, ver)) if !ver.is_empty() => (pkg, ver),
_ => (pkg, "latest"),
}
}
pub async fn ensure_toolchain(
pkg: &str,
platform: ToolPlatform,
cache_dir: &Path,
lockfile: Option<&ToolchainLockfile>,
) -> Result<ToolchainHandle> {
match platform {
ToolPlatform::Windows => {
let toolchain = windows::ensure_windows_toolchain(pkg, cache_dir, lockfile).await?;
build_handle_from_toolchain(toolchain).await
}
ToolPlatform::MacOS => {
let toolchain = ensure_macos_toolchain(pkg, cache_dir, lockfile).await?;
build_handle_from_toolchain(toolchain).await
}
}
}
pub(crate) async fn ensure_macos_toolchain(
pkg: &str,
cache_dir: &Path,
lockfile: Option<&ToolchainLockfile>,
) -> Result<PathBuf> {
let (formula, _version) = split_pkg(pkg);
if prebuilt::is_prebuilt_formula(formula) {
prebuilt::ensure_prebuilt(formula, cache_dir, lockfile).await
} else {
source_build::ensure_from_source(formula, cache_dir, lockfile).await
}
}
fn vendor_arch(arch: &str) -> &str {
if arch == "x86_64" {
"amd64"
} else {
arch
}
}
fn platform_token(platform: ToolPlatform) -> &'static str {
match platform {
ToolPlatform::MacOS => "macos",
ToolPlatform::Windows => "windows",
}
}
fn sanitize(tool: &str) -> String {
tool.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
.collect()
}
pub async fn resolve_locked_tool(
tool: &str,
platform: ToolPlatform,
arch: &str,
) -> Result<LockedTool> {
let (formula, _version) = split_pkg(tool);
let (version, url, expected): (String, String, Option<String>) = match platform {
ToolPlatform::MacOS => {
if prebuilt::is_prebuilt_formula(formula) {
let r = prebuilt::resolve_prebuilt(formula, vendor_arch(arch)).await?;
(r.version, r.url, r.sha256)
} else {
let spec = source_build::resolve_source_spec(formula).await?;
let sha = (!spec.sha256.is_empty()).then_some(spec.sha256);
(spec.version, spec.tarball_url, sha)
}
}
ToolPlatform::Windows => windows::resolve_locked_windows(formula).await?,
};
let tmp = std::env::temp_dir().join(format!(
"zlayer-lock-{}-{arch}-{}",
sanitize(tool),
std::process::id()
));
let sha256 = package_index::download_verified(&url, &tmp, expected.as_deref()).await?;
let _ = tokio::fs::remove_file(&tmp).await;
Ok(LockedTool {
tool: tool.to_string(),
platform: platform_token(platform).to_string(),
arch: arch.to_string(),
version,
url,
sha256,
resolved_at: chrono::Utc::now().to_rfc3339(),
})
}
async fn build_handle_from_toolchain(toolchain: PathBuf) -> Result<ToolchainHandle> {
let manifest = manifest::ToolchainManifest::load_or_synthesize(&toolchain).await?;
Ok(ToolchainHandle {
install_dir: toolchain,
path_dirs: manifest.path_dirs,
env: manifest.env,
})
}
pub async fn probe_ready_toolchain(
pkg: &str,
_platform: ToolPlatform,
cache_dir: &Path,
) -> Option<ToolchainHandle> {
let (formula, _version) = split_pkg(pkg);
let toolchain = newest_ready_toolchain(formula, cache_dir).await?;
build_handle_from_toolchain(toolchain).await.ok()
}
async fn newest_ready_toolchain(formula: &str, cache_dir: &Path) -> Option<PathBuf> {
let prefix = format!("{formula}-");
let arch_suffix = format!("-{}", arch_token());
let mut entries = tokio::fs::read_dir(cache_dir).await.ok()?;
let mut best: Option<(std::time::SystemTime, PathBuf)> = None;
while let Ok(Some(entry)) = entries.next_entry().await {
let name = entry.file_name();
let Some(name) = name.to_str() else { continue };
if !name.starts_with(&prefix) || !name.ends_with(&arch_suffix) {
continue;
}
let toolchain = entry.path();
if !tokio::fs::try_exists(toolchain.join(".ready"))
.await
.unwrap_or(false)
{
continue;
}
let mtime = entry
.metadata()
.await
.ok()
.and_then(|m| m.modified().ok())
.unwrap_or(std::time::UNIX_EPOCH);
if best.as_ref().is_none_or(|(t, _)| mtime >= *t) {
best = Some((mtime, toolchain));
}
}
best.map(|(_, toolchain)| toolchain)
}
const ERROR_TAIL_MAX: usize = 2048;
pub(crate) async fn pull_first(id: &ToolchainArtifactId, toolchain: &Path) -> Result<Option<()>> {
match registry::try_pull_toolchain(id, toolchain).await {
Ok(Some(pulled)) => {
stamp_ready(toolchain).await?;
record_pull_built(id, &pulled).await;
Ok(Some(()))
}
Ok(None) => Ok(None),
Err(e) => {
tracing::warn!(
tool = %id.tool,
error = %e,
"toolchain registry pull failed (non-fatal); building instead"
);
Ok(None)
}
}
}
pub(crate) async fn stamp_ready(toolchain: &Path) -> Result<()> {
tokio::fs::write(toolchain.join(".ready"), b"").await?;
Ok(())
}
pub(crate) async fn finish_built(
id: &ToolchainArtifactId,
toolchain: &Path,
report: Option<&RelocationReport>,
is_prebuilt: bool,
) {
let published = if should_publish(is_prebuilt) {
match report {
Some(report) => try_publish(id, toolchain, report).await,
None => None,
}
} else {
None
};
coverage::record(&build_coverage(
id,
CoverageStatus::Built,
published.as_ref(),
None,
))
.await;
}
pub(crate) async fn record_net_fallback(id: &ToolchainArtifactId) {
coverage::record(&build_coverage(id, CoverageStatus::NetFallback, None, None)).await;
}
pub(crate) async fn record_failed(id: &ToolchainArtifactId, err: &ToolchainError) {
let tail = err.to_string();
coverage::record(&build_coverage(
id,
CoverageStatus::Failed,
None,
Some(&tail),
))
.await;
}
async fn record_pull_built(id: &ToolchainArtifactId, pulled: &PulledToolchain) {
let published = PublishedToolchain {
reference: pulled.reference.clone(),
digest: pulled.digest.clone(),
};
coverage::record(&build_coverage(
id,
CoverageStatus::Built,
Some(&published),
None,
))
.await;
}
async fn try_publish(
id: &ToolchainArtifactId,
toolchain: &Path,
report: &RelocationReport,
) -> Option<PublishedToolchain> {
let manifest = match manifest::ToolchainManifest::read_from_toolchain(toolchain).await {
Ok(Some(manifest)) => manifest,
Ok(None) => {
tracing::warn!(tool = %id.tool, "no manifest present to publish; skipping publish");
return None;
}
Err(e) => {
tracing::warn!(tool = %id.tool, error = %e, "reading manifest for publish failed; skipping publish");
return None;
}
};
match registry::publish_toolchain(toolchain, &manifest, report, toolchain, id).await {
Ok(published) => Some(published),
Err(e) => {
tracing::warn!(
tool = %id.tool,
error = %e,
"toolchain publish failed (non-fatal; an anonymous / no-token push is expected)"
);
None
}
}
}
fn should_publish(is_prebuilt: bool) -> bool {
!is_prebuilt
}
fn build_coverage(
id: &ToolchainArtifactId,
status: CoverageStatus,
published: Option<&PublishedToolchain>,
err: Option<&str>,
) -> CoverageRecord {
CoverageRecord {
tool: id.tool.clone(),
platform: id.os.clone(),
arch: id.arch.clone(),
status,
version: id.version.clone(),
registry_ref: published.map(|p| p.reference.clone()).unwrap_or_default(),
registry_digest: published.map(|p| p.digest.clone()).unwrap_or_default(),
error_tail: err.map(truncate_error_tail).unwrap_or_default(),
recorded_at: coverage::now_rfc3339(),
}
}
fn truncate_error_tail(err: &str) -> String {
if err.len() <= ERROR_TAIL_MAX {
return err.to_string();
}
let mut end = ERROR_TAIL_MAX;
while end > 0 && !err.is_char_boundary(end) {
end -= 1;
}
err[..end].to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::manifest::{ToolchainManifest, ToolchainSource};
#[test]
fn split_pkg_plain_defaults_to_latest() {
assert_eq!(split_pkg("git"), ("git", "latest"));
}
#[test]
fn split_pkg_versioned_keeps_full_formula() {
assert_eq!(split_pkg("openssl@3"), ("openssl@3", "3"));
}
#[test]
fn split_pkg_trailing_at_is_latest() {
assert_eq!(split_pkg("weird@"), ("weird@", "latest"));
}
fn sample_id() -> ToolchainArtifactId {
ToolchainArtifactId {
tool: "jq".to_string(),
version: "1.8.1".to_string(),
os: "macos".to_string(),
arch: "arm64".to_string(),
}
}
#[test]
fn build_coverage_built_carries_registry_ref_and_digest() {
let id = sample_id();
let published = PublishedToolchain {
reference: "ghcr.io/blackleafdigital/zlayer/toolchains:jq-1.8.1-macos-arm64"
.to_string(),
digest: "sha256:abc".to_string(),
};
let rec = build_coverage(&id, CoverageStatus::Built, Some(&published), None);
assert_eq!(rec.tool, "jq");
assert_eq!(rec.platform, "macos");
assert_eq!(rec.arch, "arm64");
assert_eq!(rec.version, "1.8.1");
assert_eq!(rec.status, CoverageStatus::Built);
assert_eq!(rec.registry_ref, published.reference);
assert_eq!(rec.registry_digest, "sha256:abc");
assert!(rec.error_tail.is_empty());
assert!(!rec.recorded_at.is_empty(), "recorded_at is stamped");
}
#[test]
fn build_coverage_built_without_publish_has_no_ref() {
let rec = build_coverage(&sample_id(), CoverageStatus::Built, None, None);
assert_eq!(rec.status, CoverageStatus::Built);
assert!(rec.registry_ref.is_empty());
assert!(rec.registry_digest.is_empty());
assert!(rec.error_tail.is_empty());
}
#[test]
fn build_coverage_failed_carries_error_tail() {
let err = "Registry error: generic source build blew up".to_string();
let rec = build_coverage(&sample_id(), CoverageStatus::Failed, None, Some(&err));
assert_eq!(rec.status, CoverageStatus::Failed);
assert_eq!(rec.error_tail, err);
assert!(rec.registry_ref.is_empty());
assert!(rec.registry_digest.is_empty());
}
#[test]
fn build_coverage_net_fallback_status() {
let rec = build_coverage(&sample_id(), CoverageStatus::NetFallback, None, None);
assert_eq!(rec.status, CoverageStatus::NetFallback);
assert!(rec.registry_ref.is_empty());
assert!(rec.error_tail.is_empty());
}
#[test]
fn error_tail_truncates_to_cap_on_char_boundary() {
let short = "small error";
assert_eq!(truncate_error_tail(short), short);
let long = "é".repeat(4000);
let cut = truncate_error_tail(&long);
assert!(cut.len() <= ERROR_TAIL_MAX);
assert!(cut.len() > ERROR_TAIL_MAX - 4, "cut near the cap, not tiny");
assert!(
long.starts_with(&cut),
"a valid-UTF-8 prefix of the original"
);
}
#[test]
fn should_publish_excludes_prebuilt() {
assert!(
should_publish(false),
"source-build / Windows toolchains publish"
);
assert!(
!should_publish(true),
"language prebuilts are never published"
);
}
#[tokio::test]
async fn stamp_ready_writes_marker_without_clobbering_registry_manifest() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
let mut env = HashMap::new();
env.insert("K".to_string(), format!("{}/lib", dir.display()));
let manifest = ToolchainManifest {
tool: "jq".to_string(),
version: "1.8.1".to_string(),
arch: "arm64".to_string(),
platform: "macos".to_string(),
path_dirs: vec![format!("{}/bin", dir.display())],
env,
source: ToolchainSource::Registry {
reference: "ghcr.io/x/toolchains:jq-1.8.1-macos-arm64".to_string(),
digest: "sha256:deadbeef".to_string(),
},
build_deps: vec![],
provisioned_at: "2026-07-06T00:00:00Z".to_string(),
};
manifest.write_to_toolchain(dir).await.unwrap();
assert!(
!dir.join(".ready").exists(),
"pull leaves .ready to the caller"
);
stamp_ready(dir).await.unwrap();
assert!(dir.join(".ready").is_file(), ".ready stamped");
let reread = ToolchainManifest::read_from_toolchain(dir)
.await
.unwrap()
.unwrap();
match reread.source {
ToolchainSource::Registry { reference, digest } => {
assert_eq!(reference, "ghcr.io/x/toolchains:jq-1.8.1-macos-arm64");
assert_eq!(digest, "sha256:deadbeef");
}
other => panic!("stamp_ready must NOT rewrite the Registry manifest; got {other:?}"),
}
}
#[tokio::test]
#[ignore = "live: resolves + downloads a Chocolatey package through the package index"]
async fn windows_exe_installer_without_executor_is_executor_unavailable() {
let tmp = tempfile::tempdir().unwrap();
let err = ensure_toolchain("7zip", ToolPlatform::Windows, tmp.path(), None)
.await
.unwrap_err();
assert!(
matches!(err, ToolchainError::ExecutorUnavailable { .. }),
"{err}"
);
}
async fn seed_legacy_git_toolchain(cache_dir: &Path, version: &str) -> PathBuf {
let toolchain = cache_dir.join(format!("git-{version}-{}", arch_token()));
tokio::fs::create_dir_all(toolchain.join("bin"))
.await
.unwrap();
tokio::fs::create_dir_all(toolchain.join("libexec/git-core"))
.await
.unwrap();
tokio::fs::create_dir_all(toolchain.join("etc"))
.await
.unwrap();
tokio::fs::write(toolchain.join("etc/gitconfig"), b"")
.await
.unwrap();
tokio::fs::write(toolchain.join(".ready"), b"")
.await
.unwrap();
toolchain
}
async fn seed_toolchain_with_manifest(cache_dir: &Path, tool: &str, version: &str) -> PathBuf {
let toolchain = cache_dir.join(format!("{tool}-{version}-{}", arch_token()));
let bin = toolchain.join("bin");
tokio::fs::create_dir_all(&bin).await.unwrap();
let mut env = HashMap::new();
env.insert("FOO".to_string(), "bar".to_string());
let manifest = ToolchainManifest {
tool: tool.to_string(),
version: version.to_string(),
arch: arch_token().to_string(),
platform: "macos".to_string(),
path_dirs: vec![bin.display().to_string()],
env,
source: ToolchainSource::SourceBuild {
url: String::new(),
sha256: String::new(),
},
build_deps: vec![],
provisioned_at: "2026-06-30T00:00:00Z".to_string(),
};
manifest.write_to_toolchain(&toolchain).await.unwrap();
tokio::fs::write(toolchain.join(".ready"), b"")
.await
.unwrap();
toolchain
}
#[tokio::test]
async fn handle_synthesized_for_legacy_git_toolchain_drops_dyld() {
let tmp = tempfile::tempdir().unwrap();
let toolchain = seed_legacy_git_toolchain(tmp.path(), "2.55.0").await;
let handle = build_handle_from_toolchain(toolchain.clone())
.await
.unwrap();
assert_eq!(handle.install_dir, toolchain);
assert_eq!(
handle.path_dirs,
vec![toolchain.join("bin").display().to_string()]
);
assert_eq!(
handle.env.get("GIT_EXEC_PATH"),
Some(&toolchain.join("libexec/git-core").display().to_string())
);
assert!(!handle.env.contains_key("DYLD_FALLBACK_LIBRARY_PATH"));
assert!(!handle.env.contains_key("GIT_CONFIG_SYSTEM"));
}
#[tokio::test]
async fn handle_reads_manifest_when_present() {
let tmp = tempfile::tempdir().unwrap();
let toolchain = seed_toolchain_with_manifest(tmp.path(), "jq", "1.8.2").await;
let handle = build_handle_from_toolchain(toolchain.clone())
.await
.unwrap();
assert_eq!(handle.install_dir, toolchain);
assert_eq!(
handle.path_dirs,
vec![toolchain.join("bin").display().to_string()]
);
assert_eq!(handle.env.get("FOO"), Some(&"bar".to_string()));
}
#[tokio::test]
async fn probe_ready_returns_handle_for_ready_toolchain() {
let tmp = tempfile::tempdir().unwrap();
let toolchain = seed_legacy_git_toolchain(tmp.path(), "2.55.0").await;
let handle = probe_ready_toolchain("git", ToolPlatform::MacOS, tmp.path())
.await
.expect("ready toolchain should be probed without install");
assert_eq!(handle.install_dir, toolchain);
assert_eq!(
handle.env.get("GIT_EXEC_PATH"),
Some(&toolchain.join("libexec/git-core").display().to_string())
);
}
#[tokio::test]
async fn probe_ready_is_generic_across_tools() {
let tmp = tempfile::tempdir().unwrap();
let toolchain = seed_toolchain_with_manifest(tmp.path(), "jq", "1.8.2").await;
let handle = probe_ready_toolchain("jq", ToolPlatform::MacOS, tmp.path())
.await
.expect("ready jq toolchain should be probed");
assert_eq!(handle.install_dir, toolchain);
assert_eq!(handle.env.get("FOO"), Some(&"bar".to_string()));
}
#[tokio::test]
async fn probe_ready_returns_none_without_ready_marker() {
let tmp = tempfile::tempdir().unwrap();
let toolchain = tmp.path().join(format!("git-2.55.0-{}", arch_token()));
tokio::fs::create_dir_all(toolchain.join("bin"))
.await
.unwrap();
assert!(
probe_ready_toolchain("git", ToolPlatform::MacOS, tmp.path())
.await
.is_none(),
"an unstamped toolchain must not be injected"
);
}
#[tokio::test]
async fn probe_ready_returns_none_for_cold_or_unsupported() {
let tmp = tempfile::tempdir().unwrap();
assert!(
probe_ready_toolchain("git", ToolPlatform::MacOS, tmp.path())
.await
.is_none(),
"cold cache should probe None"
);
assert!(
probe_ready_toolchain("jq", ToolPlatform::MacOS, tmp.path())
.await
.is_none(),
"absent tool should probe None"
);
assert!(
probe_ready_toolchain("git", ToolPlatform::Windows, tmp.path())
.await
.is_none(),
"cold cache probes None on every platform"
);
}
#[tokio::test]
#[ignore = "live build test; fetches + compiles git and jq from source (macOS + CLT only)"]
async fn ensure_git_and_jq_build_from_source_and_run() {
let tmp = tempfile::tempdir().unwrap();
for (tool, version_needle) in [("git", "git version"), ("jq", "jq-")] {
let handle = ensure_toolchain(tool, ToolPlatform::MacOS, tmp.path(), None)
.await
.unwrap_or_else(|e| panic!("{tool} toolchain should build from source: {e}"));
let bin = handle.install_dir.join("bin").join(tool);
assert!(
bin.exists(),
"{tool} binary should exist at <toolchain>/bin/{tool}"
);
let manifest = ToolchainManifest::read_from_toolchain(&handle.install_dir)
.await
.unwrap()
.unwrap_or_else(|| panic!("{tool} toolchain must have a manifest"));
assert_eq!(manifest.tool, tool);
let bytes = tokio::fs::read(&bin).await.expect("read binary");
assert!(
!contains_subslice_bytes(&bytes, b"@@HOMEBREW"),
"source-built {tool} must contain NO @@HOMEBREW@@ references"
);
let mut cmd = tokio::process::Command::new(&bin);
for (k, v) in &handle.env {
cmd.env(k, v);
}
let out = cmd.arg("--version").output().await.expect("run --version");
assert!(out.status.success(), "{tool} --version should succeed");
assert!(
String::from_utf8_lossy(&out.stdout).contains(version_needle)
|| String::from_utf8_lossy(&out.stderr).contains(version_needle),
"{tool} --version output should contain '{version_needle}'"
);
}
}
fn contains_subslice_bytes(haystack: &[u8], needle: &[u8]) -> bool {
if needle.is_empty() || haystack.len() < needle.len() {
return false;
}
haystack
.windows(needle.len())
.any(|window| window == needle)
}
}