use std::fs;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::setup_core::archive::{self, Limits};
use crate::setup_core::digest;
use crate::setup_core::error::{Error, ReasonCode, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Shape {
Raw,
GzipTar,
Zip,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Artifact {
pub platform: &'static str,
pub url: &'static str,
pub bytes: u64,
pub sha256: &'static str,
pub shape: Shape,
pub member: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub enum Delivery {
Artifacts(&'static [Artifact]),
Manager {
tool: &'static str,
reason: &'static str,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Previous {
pub version: &'static str,
pub artifacts: &'static [Artifact],
}
#[derive(Debug, Clone, Copy)]
pub struct Software {
pub version: &'static str,
pub command: &'static str,
pub delivery: Delivery,
pub unsupported: &'static [&'static str],
pub previous: Option<Previous>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Present {
pub versions: Vec<String>,
pub exposed: Option<String>,
}
impl Present {
#[must_use]
fn marker(root: &Path, command: &str) -> PathBuf {
root.join("bin").join(format!(".{command}.version"))
}
#[must_use]
pub fn holds(&self, version: &str) -> bool {
self.versions.iter().any(|found| found == version)
}
#[must_use]
pub fn under_named(root: &Path, command: &str, member: &str) -> Self {
Self::under_on(root, command, member, cfg!(windows))
}
#[must_use]
pub fn under_on(root: &Path, command: &str, member: &str, windows: bool) -> Self {
let mut versions: Vec<String> = fs::read_dir(root)
.into_iter()
.flatten()
.flatten()
.filter(|entry| entry.path().is_dir())
.filter_map(|entry| entry.file_name().into_string().ok())
.filter(|name| name != "bin" && !name.starts_with('.'))
.collect();
versions.sort();
let exposed_as = exposed_name_on(command, member, windows);
let usable = fs::metadata(root.join("bin").join(&exposed_as)).is_ok();
let marker = Self::marker(root, command);
let exposed = fs::read_to_string(&marker)
.ok()
.filter(|_| usable)
.map(|held| held.trim().to_owned())
.filter(|name| versions.contains(name))
.or_else(|| {
fs::canonicalize(root.join("bin").join(&exposed_as))
.ok()
.zip(fs::canonicalize(root).ok())
.and_then(|(to, base)| {
to.strip_prefix(&base).ok().and_then(|rest| {
rest.components()
.next()
.map(|first| first.as_os_str().to_string_lossy().into_owned())
})
})
.filter(|name| versions.contains(name))
});
Self { versions, exposed }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Installed {
pub version: String,
pub root: PathBuf,
pub executable: PathBuf,
pub files: usize,
}
impl Software {
#[must_use]
pub fn versions(&self) -> Vec<&'static str> {
let mut named = vec![self.version];
if let Some(earlier) = self.previous {
named.push(earlier.version);
}
named
}
#[must_use]
pub fn at(&self, asked: Option<&str>) -> Option<Self> {
match asked {
None => Some(*self),
Some(wanted) if wanted == self.version => Some(*self),
Some(wanted) => self
.previous
.filter(|earlier| earlier.version == wanted)
.map(|earlier| Self {
version: earlier.version,
delivery: Delivery::Artifacts(earlier.artifacts),
..*self
}),
}
}
#[must_use]
pub fn for_bytes(&self, os: &str, arch: &str, digest: &str) -> Option<Self> {
self.versions()
.into_iter()
.filter_map(|version| self.at(Some(version)))
.find(|candidate| {
candidate
.artifact_for(os, arch)
.is_ok_and(|artifact| artifact.sha256 == digest)
})
}
#[must_use]
fn member_hint(&self) -> &'static str {
match self.delivery {
Delivery::Artifacts(artifacts) => {
artifacts.first().map_or(self.command, |entry| entry.member)
}
Delivery::Manager { .. } => self.command,
}
}
#[must_use]
pub fn member_on(&self, os: &str, arch: &str) -> &'static str {
self.artifact_for(os, arch)
.map_or_else(|_| self.member_hint(), |artifact| artifact.member)
}
#[must_use]
pub fn member_here(&self) -> &'static str {
let (os, arch) = crate::setup_core::platform_of_this_host();
self.member_on(os, arch)
}
pub fn artifact_for(&self, os: &str, arch: &str) -> Result<&'static Artifact> {
let Delivery::Artifacts(artifacts) = self.delivery else {
return Err(Error::new(
ReasonCode::UnsupportedOperation,
match self.delivery {
Delivery::Manager { tool, reason } => {
format!("{} is delivered by {tool}: {reason}", self.command)
}
Delivery::Artifacts(_) => unreachable!(),
},
));
};
let wanted = format!("{os}/{arch}");
if let Some(found) = artifacts.iter().find(|entry| entry.platform == wanted) {
return Ok(found);
}
let prefix = format!("{os}/");
let system_is_published = artifacts
.iter()
.any(|entry| entry.platform.starts_with(&prefix));
let reason = if system_is_published {
ReasonCode::UnsupportedArchitecture
} else {
ReasonCode::UnsupportedPlatform
};
Err(Error::new(
reason,
format!(
"{} publishes no build for {wanted}; it publishes {}",
self.command,
artifacts
.iter()
.map(|entry| entry.platform)
.collect::<Vec<_>>()
.join(", ")
),
))
}
}
impl Artifact {
pub fn verify(&self, downloaded: &Path) -> Result<()> {
let found = fs::metadata(downloaded)
.map_err(|error| {
Error::new(
ReasonCode::StateUnavailable,
format!("downloaded artifact could not be read: {error}"),
)
.with_source(error)
})?
.len();
if found != self.bytes {
return Err(Error::new(
ReasonCode::IntegrityMismatch,
format!(
"{} is {found} bytes; the plan named {}",
downloaded.display(),
self.bytes
),
));
}
let measured = digest::of_file(downloaded)?;
if measured != self.sha256 {
return Err(Error::new(
ReasonCode::IntegrityMismatch,
format!(
"{} hashes to {measured}; the plan named {}",
downloaded.display(),
self.sha256
),
));
}
Ok(())
}
#[must_use]
pub const fn limits(&self) -> Limits {
Limits {
entries: 65_536,
bytes: match self.bytes.checked_mul(16) {
Some(scaled) if scaled > 64 * 1024 * 1024 => scaled,
_ => 64 * 1024 * 1024,
},
}
}
}
pub fn install(
software: &Software,
artifact: &Artifact,
downloaded: &Path,
root: &Path,
) -> Result<Installed> {
artifact.verify(downloaded)?;
let version_root = root.join(software.version);
let staging = root.join(format!(".incoming-{}", software.version));
let quarantine = root.join(format!(".replaced-{}", software.version));
for leftover in [&staging, &quarantine] {
if leftover.exists() {
fs::remove_dir_all(leftover).map_err(|error| {
Error::new(
ReasonCode::StateUnavailable,
format!("{} could not be cleared: {error}", leftover.display()),
)
.with_source(error)
})?;
}
}
let source = fs::File::open(downloaded).map_err(|error| {
Error::new(
ReasonCode::StateUnavailable,
format!("downloaded artifact could not be opened: {error}"),
)
.with_source(error)
})?;
let (executable, files) = match artifact.shape {
Shape::Raw => {
let placed = staging.join(software.command);
archive::place_executable(source, &placed)?;
(placed, 1)
}
Shape::GzipTar | Shape::Zip => {
let entries = if artifact.shape == Shape::Zip {
archive::extract_zip(source, &staging, artifact.limits())?
} else {
archive::extract_gzip_tar(source, &staging, artifact.limits())?
};
let found = entries
.iter()
.any(|entry| entry.path == artifact.member && entry.kind == archive::Kind::File);
if !found {
let _ = fs::remove_dir_all(&staging);
return Err(Error::new(
ReasonCode::IntegrityMismatch,
format!(
"the archive does not contain {}, which the plan named as the executable",
artifact.member
),
));
}
(staging.join(artifact.member), entries.len())
}
};
let replaced = version_root.exists();
if replaced {
fs::rename(&version_root, &quarantine).map_err(|error| {
Error::new(
ReasonCode::StateUnavailable,
format!(
"the installed {} tree could not be moved aside: {error}",
software.version
),
)
.with_source(error)
})?;
}
if let Err(error) = fs::rename(&staging, &version_root) {
let restored = !replaced || fs::rename(&quarantine, &version_root).is_ok();
return Err(Error::new(
ReasonCode::StateUnavailable,
format!(
"the staged {} tree could not be promoted: {error}{}",
software.version,
if restored {
""
} else {
". The previous tree is in .replaced-<version> and was not put back"
}
),
)
.with_source(error));
}
if replaced {
let _ = fs::remove_dir_all(&quarantine);
}
let executable = version_root.join(
executable
.strip_prefix(&staging)
.unwrap_or_else(|_| Path::new(software.command)),
);
let exposed = root
.join("bin")
.join(exposed_name(software.command, artifact.member));
expose(&executable, &exposed, software.version, software.command)?;
Ok(Installed {
version: software.version.to_owned(),
root: version_root,
executable: exposed,
files,
})
}
pub fn recover(root: &Path) -> Result<Vec<String>> {
fn fail(what: String) -> impl FnOnce(std::io::Error) -> Error {
move |error: std::io::Error| {
Error::new(ReasonCode::StateUnavailable, format!("{what}: {error}")).with_source(error)
}
}
let mut done = Vec::new();
let mut entries: Vec<PathBuf> = fs::read_dir(root)
.into_iter()
.flatten()
.flatten()
.map(|entry| entry.path())
.collect();
entries.sort();
for path in entries {
let Some(name) = path
.file_name()
.map(|name| name.to_string_lossy().into_owned())
else {
continue;
};
if let Some(version) = name.strip_prefix(".incoming-") {
fs::remove_dir_all(&path).map_err(fail(format!(
"the staged {version} tree could not be cleared"
)))?;
done.push(format!(
"an install of {version} was interrupted before it landed; the staged tree \
is gone and whatever was installed is untouched"
));
} else if let Some(version) = name.strip_prefix(".replaced-") {
let final_path = root.join(version);
if final_path.exists() {
fs::remove_dir_all(&path).map_err(fail(format!(
"the replaced {version} tree could not be cleared"
)))?;
done.push(format!(
"an install of {version} landed and its cleanup did not; the new tree is \
in place and the old one is gone"
));
} else {
fs::rename(&path, &final_path).map_err(fail(format!(
"the previous {version} tree could not be put back"
)))?;
done.push(format!(
"an install of {version} failed after the installed tree stepped aside; \
it is back"
));
}
} else if name.ends_with(".incoming") {
let _ = fs::remove_file(&path);
done.push(format!("a half-written {name} was cleared"));
}
}
Ok(done)
}
pub fn remove(software: &Software, root: &Path) -> Result<bool> {
let version_root = root.join(software.version);
if !version_root.exists() {
return Ok(false);
}
let exposed_version =
Present::under_named(root, software.command, software.member_here()).exposed;
fs::remove_dir_all(&version_root).map_err(|error| {
Error::new(
ReasonCode::StateUnavailable,
format!(
"the {} tree could not be removed: {error}",
software.version
),
)
.with_source(error)
})?;
let ours = exposed_version.as_deref() == Some(software.version);
if !ours && exposed_version.is_some() {
return Ok(true);
}
let exposed = root
.join("bin")
.join(exposed_name(software.command, software.member_here()));
if exposed.symlink_metadata().is_ok() {
fs::remove_file(&exposed).map_err(|error| {
Error::new(
ReasonCode::StateUnavailable,
format!("{} could not be removed: {error}", exposed.display()),
)
.with_source(error)
})?;
}
let _ = fs::remove_file(Present::marker(root, software.command));
Ok(true)
}
fn executable_candidates(
software: &Software,
version_root: &Path,
os: &str,
arch: &str,
) -> Vec<PathBuf> {
let mut candidates = Vec::new();
let mut push = |relative: &str| {
let path = version_root.join(relative);
if !candidates.contains(&path) {
candidates.push(path);
}
};
if let Ok(artifact) = software.artifact_for(os, arch) {
push(artifact.member);
}
push(software.member_hint());
push(software.command);
if os == "windows" {
push(&format!("{}.exe", software.command));
}
candidates
}
pub fn rollback(software: &Software, root: &Path, to: &str) -> Result<Installed> {
let present = Present::under_named(root, software.command, software.member_here());
if !present.versions.iter().any(|found| found == to) {
return Err(Error::new(
ReasonCode::InvalidTarget,
if present.versions.is_empty() {
format!(
"{} holds no installed version of {}",
root.display(),
software.command
)
} else {
format!(
"{to} is not installed under {}; it holds {}",
root.display(),
present.versions.join(", ")
)
},
));
}
let version_root = root.join(to);
let (os, arch) = crate::setup_core::platform_of_this_host();
let candidates = executable_candidates(software, &version_root, os, arch);
let Some(executable) = candidates.iter().find(|path| path.is_file()) else {
return Err(Error::new(
ReasonCode::StateUnavailable,
format!(
"the {to} tree holds no {} executable; looked at {}",
software.command,
candidates
.iter()
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
.join(" and ")
),
));
};
let exposed = root
.join("bin")
.join(exposed_name(software.command, software.member_here()));
expose(executable, &exposed, to, software.command)?;
Ok(Installed {
version: to.to_owned(),
root: version_root,
executable: exposed,
files: 0,
})
}
#[must_use]
pub fn exposed_name(command: &str, member: &str) -> String {
exposed_name_on(command, member, cfg!(windows))
}
#[must_use]
pub fn exposed_name_on(command: &str, member: &str, windows: bool) -> String {
match member_kind(member, windows) {
MemberKind::JavaScript | MemberKind::CommandScript => format!("{command}.cmd"),
MemberKind::Native => command.to_owned(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemberKind {
Native,
JavaScript,
CommandScript,
}
#[must_use]
pub fn member_kind(member: &str, windows: bool) -> MemberKind {
if !windows {
return MemberKind::Native;
}
match Path::new(member)
.extension()
.map(|kind| kind.to_string_lossy().to_ascii_lowercase())
.as_deref()
{
Some("js") => MemberKind::JavaScript,
Some("cmd" | "bat") => MemberKind::CommandScript,
_ => MemberKind::Native,
}
}
fn write_atomically(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
let staging = match (path.parent(), path.file_name()) {
(Some(parent), Some(name)) => {
let name = name.to_string_lossy();
let dotted = if name.starts_with('.') {
format!("{name}.incoming")
} else {
format!(".{name}.incoming")
};
parent.join(dotted)
}
_ => return fs::write(path, bytes),
};
fs::write(&staging, bytes)?;
fs::rename(&staging, path)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Manifest {
pub schema_version: u32,
pub version: String,
pub executable: String,
pub executable_sha256: String,
}
impl Manifest {
#[must_use]
pub fn path(root: &Path, command: &str) -> PathBuf {
root.join("bin").join(format!(".{command}.manifest.json"))
}
#[must_use]
pub fn read(root: &Path, command: &str) -> Option<Self> {
match Self::inspect(root, command) {
ManifestState::Present(found) => Some(found),
ManifestState::Missing
| ManifestState::Unreadable
| ManifestState::Malformed
| ManifestState::Unsupported { .. } => None,
}
}
#[must_use]
pub fn inspect(root: &Path, command: &str) -> ManifestState {
match fs::read_to_string(Self::path(root, command)) {
Err(error) if error.kind() == std::io::ErrorKind::NotFound => ManifestState::Missing,
Err(_) => ManifestState::Unreadable,
Ok(raw) => match serde_json::from_str::<Self>(&raw) {
Err(_) => ManifestState::Malformed,
Ok(found) if found.schema_version != 1 => ManifestState::Unsupported {
schema_version: found.schema_version,
},
Ok(found) => ManifestState::Present(found),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ManifestState {
Missing,
Unreadable,
Malformed,
Unsupported {
schema_version: u32,
},
Present(Manifest),
}
fn expose(executable: &Path, exposed: &Path, version: &str, command: &str) -> Result<()> {
let fail = |error: std::io::Error| {
Error::new(
ReasonCode::StateUnavailable,
format!("{} could not be exposed: {error}", exposed.display()),
)
.with_source(error)
};
if let Some(parent) = exposed.parent() {
fs::create_dir_all(parent).map_err(fail)?;
}
if exposed.symlink_metadata().is_ok() {
fs::remove_file(exposed).map_err(fail)?;
}
#[cfg(unix)]
{
std::os::unix::fs::symlink(executable, exposed).map_err(fail)?;
}
#[cfg(not(unix))]
{
match member_kind(&executable.to_string_lossy(), true) {
MemberKind::JavaScript => {
fs::write(
exposed,
format!("@node \"{}\" %*\r\n", executable.display()),
)
.map_err(fail)?;
}
MemberKind::CommandScript => {
fs::write(
exposed,
format!("@call \"{}\" %*\r\n", executable.display()),
)
.map_err(fail)?;
}
MemberKind::Native => {
fs::hard_link(executable, exposed)
.or_else(|_| fs::copy(executable, exposed).map(|_| ()))
.map_err(fail)?;
}
}
}
if let Some(root) = exposed.parent().and_then(Path::parent) {
write_atomically(&Present::marker(root, command), version.as_bytes()).map_err(fail)?;
let relative = executable.strip_prefix(root).unwrap_or(executable);
let manifest = Manifest {
schema_version: 1,
version: version.to_owned(),
executable: relative.to_string_lossy().replace('\\', "/"),
executable_sha256: digest::of_file(executable)?,
};
let body = serde_json::to_vec(&manifest).map_err(|error| {
Error::new(
ReasonCode::StateUnavailable,
format!("the installation record could not be written: {error}"),
)
})?;
write_atomically(&Manifest::path(root, command), &body).map_err(fail)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::panic)]
use super::*;
use crate::setup_core::archive::build::{Dialect, Item, gzip_tar};
const CODEX_MEMBER: &str = "package/vendor/x86_64-unknown-linux-musl/bin/codex";
const ARTIFACTS: &[Artifact] = &[
Artifact {
platform: "linux/x86_64",
url: "https://example.invalid/linux-x86_64.tgz",
bytes: 0,
sha256: "sha256:0",
shape: Shape::GzipTar,
member: CODEX_MEMBER,
},
Artifact {
platform: "linux/arm64",
url: "https://example.invalid/linux-arm64.tgz",
bytes: 0,
sha256: "sha256:0",
shape: Shape::GzipTar,
member: CODEX_MEMBER,
},
];
fn software() -> Software {
Software {
version: "1.2.3",
command: "codex",
delivery: Delivery::Artifacts(ARTIFACTS),
unsupported: &["windows/x86_64"],
previous: None,
}
}
const EARLIER_ARTIFACTS: &[Artifact] = &[
Artifact {
platform: "linux/x86_64",
url: "https://example.invalid/linux-x86_64-1.2.2.tgz",
bytes: 0,
sha256: "sha256:earlier",
shape: Shape::GzipTar,
member: CODEX_MEMBER,
},
Artifact {
platform: "linux/arm64",
url: "https://example.invalid/linux-arm64-1.2.2.tgz",
bytes: 0,
sha256: "sha256:earlier",
shape: Shape::GzipTar,
member: CODEX_MEMBER,
},
];
fn bumped() -> Software {
Software {
previous: Some(Previous {
version: "1.2.2",
artifacts: EARLIER_ARTIFACTS,
}),
..software()
}
}
const MIXED_ARTIFACTS: &[Artifact] = &[
Artifact {
platform: "linux/arm64",
url: "https://example.invalid/linux-arm64.tgz",
bytes: 0,
sha256: "sha256:0",
shape: Shape::GzipTar,
member: "dist-package/cursor-agent",
},
Artifact {
platform: "windows/x86_64",
url: "https://example.invalid/windows-x86_64.zip",
bytes: 0,
sha256: "sha256:0",
shape: Shape::Zip,
member: "dist-package/cursor-agent.cmd",
},
];
#[test]
fn the_windows_entry_point_names_the_member_windows_actually_gets() {
let sw = Software {
command: "agent",
delivery: Delivery::Artifacts(MIXED_ARTIFACTS),
..software()
};
let planned = exposed_name_on(sw.command, sw.member_on("windows", "x86_64"), true);
let written = exposed_name_on(
sw.command,
sw.artifact_for("windows", "x86_64").unwrap().member,
true,
);
assert_eq!(planned, written, "the plan and the apply name one file");
assert_eq!(planned, "agent.cmd");
assert_eq!(exposed_name_on(sw.command, sw.member_hint(), true), "agent");
}
#[test]
fn a_platform_without_an_artifact_falls_back_to_the_hint() {
let sw = software();
assert_eq!(sw.member_on("windows", "x86_64"), sw.member_hint());
}
#[test]
fn a_build_with_no_second_pin_names_one_version_and_refuses_the_rest() {
let only = software();
assert_eq!(only.versions(), vec!["1.2.3"]);
assert!(only.at(None).is_some());
assert!(only.at(Some("1.2.3")).is_some());
assert!(only.at(Some("1.2.2")).is_none());
}
#[test]
fn naming_the_earlier_version_selects_the_earlier_bytes() {
let both = bumped();
assert_eq!(both.versions(), vec!["1.2.3", "1.2.2"]);
let earlier = both.at(Some("1.2.2")).unwrap();
assert_eq!(earlier.version, "1.2.2");
assert_eq!(
earlier.artifact_for("linux", "x86_64").unwrap().url,
"https://example.invalid/linux-x86_64-1.2.2.tgz"
);
let current = both.at(None).unwrap();
assert_eq!(current.version, "1.2.3");
assert_eq!(
current.artifact_for("linux", "x86_64").unwrap().url,
"https://example.invalid/linux-x86_64.tgz"
);
}
#[test]
fn the_release_a_file_belongs_to_is_read_from_its_digest() {
let both = bumped();
assert_eq!(
both.for_bytes("linux", "x86_64", "sha256:0")
.map(|found| found.version),
Some("1.2.3")
);
assert_eq!(
both.for_bytes("linux", "x86_64", "sha256:earlier")
.map(|found| found.version),
Some("1.2.2")
);
assert!(
both.for_bytes("linux", "x86_64", "sha256:someone-elses")
.is_none()
);
assert!(both.for_bytes("windows", "x86_64", "sha256:0").is_none());
}
#[test]
fn rollback_points_the_command_at_a_version_already_on_disk() {
let (at, artifact) = staged("rollback", b"#!/bin/sh\necho new\n", CODEX_MEMBER);
let root = at.join("prefix");
let installed = install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
assert_eq!(installed.version, "1.2.3");
let older = root.join("1.2.2");
fs::create_dir_all(older.join("package/vendor/x86_64-unknown-linux-musl/bin")).unwrap();
fs::write(older.join(CODEX_MEMBER), b"#!/bin/sh\necho old\n").unwrap();
let rolled = rollback(&software(), &root, "1.2.2").unwrap();
assert_eq!(rolled.version, "1.2.2");
let present = Present::under_named(&root, "codex", CODEX_MEMBER);
assert_eq!(present.exposed.as_deref(), Some("1.2.2"));
assert_eq!(present.versions, vec!["1.2.2", "1.2.3"]);
assert!(root.join("1.2.3").join(CODEX_MEMBER).is_file());
assert_eq!(
rollback(&software(), &root, "1.2.3").unwrap().version,
"1.2.3"
);
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER)
.exposed
.as_deref(),
Some("1.2.3")
);
}
#[test]
fn removing_an_inactive_version_leaves_the_active_one_exposed() {
let (at, artifact) = staged("remove-inactive", b"#!/bin/sh\necho new\n", CODEX_MEMBER);
let root = at.join("prefix");
install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
let older = root.join("1.2.2");
fs::create_dir_all(older.join("package/vendor/x86_64-unknown-linux-musl/bin")).unwrap();
fs::write(older.join(CODEX_MEMBER), b"#!/bin/sh\necho old\n").unwrap();
rollback(&software(), &root, "1.2.2").unwrap();
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER)
.exposed
.as_deref(),
Some("1.2.2"),
"the rollback did not take"
);
assert!(remove(&software(), &root).unwrap());
assert!(
!root.join("1.2.3").exists(),
"the removed version's tree is still here"
);
assert!(
root.join("1.2.2").join(CODEX_MEMBER).is_file(),
"removing one version took another version's files"
);
let after = Present::under_named(&root, "codex", CODEX_MEMBER);
assert_eq!(
after.exposed.as_deref(),
Some("1.2.2"),
"removing an inactive version took the command that was running the active one"
);
assert_eq!(after.versions, vec!["1.2.2"]);
}
#[test]
fn a_reinstall_that_fails_leaves_the_installation_that_was_working() {
let (at, artifact) = staged("reinstall-fails", b"#!/bin/sh\necho good\n", CODEX_MEMBER);
let root = at.join("prefix");
install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
let good = fs::read(root.join("1.2.3").join(CODEX_MEMBER)).unwrap();
let raw = gzip_tar(
&[
Item::directory("package"),
Item::file("package/README.md", b"no executable here", 0o644),
],
Dialect::Gnu,
);
let broken_at = at.join("broken.tgz");
fs::write(&broken_at, &raw).unwrap();
let broken = Artifact {
bytes: raw.len() as u64,
sha256: Box::leak(digest::of_bytes(&raw).into_boxed_str()),
..artifact
};
let refused = install(&software(), &broken, &broken_at, &root).unwrap_err();
assert_eq!(
refused.reason(),
ReasonCode::IntegrityMismatch,
"the refusal is not the one this test drives: {refused:?}"
);
assert_eq!(
fs::read(root.join("1.2.3").join(CODEX_MEMBER))
.ok()
.as_deref(),
Some(good.as_slice()),
"a failed reinstall destroyed the installation that was working"
);
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER)
.exposed
.as_deref(),
Some("1.2.3"),
"the exposed command no longer names an installed version"
);
}
#[test]
fn a_command_script_member_keeps_an_extension_windows_can_run() {
assert_eq!(
exposed_name_on("agent", "dist-package/cursor-agent", true),
"agent"
);
assert_eq!(
exposed_name_on("agent", "dist-package/cursor-agent", false),
"agent"
);
assert_eq!(
exposed_name_on("pi", "package/dist/bundle/cli.js", true),
"pi.cmd"
);
assert_eq!(
exposed_name_on("pi", "package/dist/bundle/cli.js", false),
"pi"
);
assert_eq!(
exposed_name_on("agent", "dist-package/cursor-agent.cmd", true),
"agent.cmd",
"a .cmd member was exposed as a name Windows cannot run"
);
assert_eq!(
exposed_name_on("agent", "dist-package/cursor-agent.cmd", false),
"agent"
);
assert_eq!(
exposed_name_on("codex", "package/bin/codex.exe", true),
"codex"
);
assert_eq!(
exposed_name_on("codex", "package/bin/codex.exe", false),
"codex"
);
}
#[test]
fn an_interrupted_install_is_resolved_by_the_state_it_left() {
let root = scratch("recover-prefix");
fs::create_dir_all(&root).unwrap();
fs::create_dir_all(root.join(".incoming-1.2.3/package")).unwrap();
fs::create_dir_all(root.join("1.2.2")).unwrap();
fs::write(root.join("1.2.2/kept"), b"the installation that was there").unwrap();
let said = recover(&root).unwrap();
assert_eq!(said.len(), 1, "{said:?}");
assert!(said[0].contains("interrupted before it landed"), "{said:?}");
assert!(!root.join(".incoming-1.2.3").exists());
assert!(
root.join("1.2.2/kept").is_file(),
"an untouched tree was taken"
);
fs::create_dir_all(root.join(".replaced-1.2.3")).unwrap();
fs::write(root.join(".replaced-1.2.3/old"), b"the previous tree").unwrap();
fs::create_dir_all(root.join("1.2.3")).unwrap();
fs::write(root.join("1.2.3/new"), b"the tree that landed").unwrap();
let said = recover(&root).unwrap();
assert!(
said.iter().any(|line| line.contains("cleanup did not")),
"{said:?}"
);
assert!(!root.join(".replaced-1.2.3").exists());
assert_eq!(
fs::read(root.join("1.2.3/new")).unwrap(),
b"the tree that landed",
"the promoted tree was replaced by the one it replaced"
);
fs::remove_dir_all(root.join("1.2.3")).unwrap();
fs::create_dir_all(root.join(".replaced-1.2.3")).unwrap();
fs::write(root.join(".replaced-1.2.3/old"), b"the previous tree").unwrap();
let said = recover(&root).unwrap();
assert!(
said.iter().any(|line| line.contains("it is back")),
"{said:?}"
);
assert_eq!(
fs::read(root.join("1.2.3/old")).unwrap(),
b"the previous tree",
"the tree that stepped aside was not put back"
);
assert!(recover(&root).unwrap().is_empty());
}
#[test]
fn a_marker_truncated_onto_a_sibling_version_is_not_believed() {
let (at, artifact) = staged("truncated-marker", b"#!/bin/sh\necho new\n", CODEX_MEMBER);
let root = at.join("prefix");
install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
let sibling = root.join("1.2");
fs::create_dir_all(sibling.join("package/vendor/x86_64-unknown-linux-musl/bin")).unwrap();
fs::write(sibling.join(CODEX_MEMBER), b"#!/bin/sh\necho sibling\n").unwrap();
fs::write(root.join("bin").join(".codex.version"), "1.2").unwrap();
let read = Present::under_named(&root, "codex", CODEX_MEMBER);
assert_eq!(
read.exposed.as_deref(),
Some("1.2"),
"this test drives the state; if it stops reproducing, say why here"
);
let marker = root.join("bin").join(".codex.version");
fs::write(&marker, "1.2.3").unwrap();
let staging = marker.with_extension("version.incoming");
fs::create_dir_all(&staging).unwrap(); let refused = expose(
&root.join("1.2").join(CODEX_MEMBER),
&root.join("bin").join("codex"),
"1.2",
"codex",
);
assert!(
refused.is_err(),
"the marker write did not stage: {refused:?}"
);
assert_eq!(
fs::read_to_string(&marker).unwrap(),
"1.2.3",
"a failed marker write replaced the marker that was there"
);
}
#[test]
fn a_javascript_entry_point_is_exposed_as_something_the_platform_can_run() {
assert_eq!(exposed_name("codex", CODEX_MEMBER), "codex");
assert_eq!(exposed_name("grok", ""), "grok");
let js = "package/dist/bundle/cli.js";
assert_eq!(exposed_name_on("pi", js, true), "pi.cmd");
assert_eq!(exposed_name_on("pi", js, false), "pi");
assert_eq!(exposed_name_on("codex", CODEX_MEMBER, true), "codex");
assert_eq!(exposed_name_on("codex", CODEX_MEMBER, false), "codex");
assert_eq!(
exposed_name("pi", js),
exposed_name_on("pi", js, cfg!(windows))
);
}
#[test]
fn a_windows_shaped_prefix_is_read_back_by_the_member_and_not_by_the_command() {
let member = "package/dist/bundle/cli.js";
let at = scratch("windows-readback");
let root = at.join("prefix");
fs::create_dir_all(root.join("0.84.4").join("package/dist/bundle")).unwrap();
fs::write(root.join("0.84.4").join(member), b"// the bundle").unwrap();
fs::create_dir_all(root.join("bin")).unwrap();
let launcher = exposed_name_on("pi", member, true);
assert_eq!(launcher, "pi.cmd");
fs::write(root.join("bin").join(&launcher), b"@echo off\r\n").unwrap();
fs::write(root.join("bin").join(".pi.version"), b"0.84.4\n").unwrap();
assert_eq!(
Present::under_on(&root, "pi", member, true)
.exposed
.as_deref(),
Some("0.84.4"),
"the reading told the member did not find the launcher beside it"
);
assert_eq!(
Present::under_on(&root, "pi", "", true).exposed,
None,
"a member-blind reading found something, so this test proves nothing"
);
assert_eq!(
Present::under_on(&root, "pi", "", true).versions,
vec!["0.84.4".to_owned()]
);
}
#[test]
#[cfg(unix)]
fn a_javascript_program_installs_from_one_archive_and_runs() {
let member = "package/dist/bundle/cli.js";
let (at, artifact) = staged(
"js-entry",
b"#!/usr/bin/env sh\necho 'js-stand-in 9.9.9'\n",
member,
);
let software = Software {
version: "9.9.9",
command: "jsprog",
delivery: Delivery::Artifacts(&[]),
unsupported: &[],
previous: None,
};
let root = at.join("prefix");
let installed = install(&software, &artifact, &at.join("artifact.tgz"), &root).unwrap();
assert_eq!(
installed.executable,
root.join("bin").join(exposed_name("jsprog", member))
);
assert_eq!(
fs::canonicalize(&installed.executable).unwrap(),
fs::canonicalize(root.join("9.9.9").join(member)).unwrap()
);
assert_eq!(
Present::under_named(&root, "jsprog", member)
.exposed
.as_deref(),
Some("9.9.9")
);
}
#[test]
fn the_exposed_version_is_readable_without_a_link_to_resolve() {
let (at, artifact) = staged("exposed-marker", b"#!/bin/sh\necho hi\n", CODEX_MEMBER);
let root = at.join("prefix");
install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER)
.exposed
.as_deref(),
Some("1.2.3")
);
let exposed = root.join("bin").join("codex");
let bytes = fs::read(root.join("1.2.3").join(CODEX_MEMBER)).unwrap();
fs::remove_file(&exposed).unwrap();
fs::write(&exposed, &bytes).unwrap();
assert!(!exposed.symlink_metadata().unwrap().is_symlink());
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER)
.exposed
.as_deref(),
Some("1.2.3"),
"the exposed version was unreadable without a link to resolve"
);
fs::write(root.join("bin").join(".codex.version"), "9.9.9").unwrap();
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER).exposed,
None
);
fs::write(root.join("bin").join(".codex.version"), "1.2.3").unwrap();
remove(&software(), &root).unwrap();
assert!(!root.join("bin").join(".codex.version").exists());
}
#[test]
fn rollback_to_a_version_that_is_not_installed_names_the_ones_that_are() {
let (at, artifact) = staged("rollback-missing", b"x", CODEX_MEMBER);
let root = at.join("prefix");
install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
let error = rollback(&software(), &root, "9.9.9").unwrap_err();
assert!(error.detail().contains("9.9.9"), "{}", error.detail());
assert!(error.detail().contains("1.2.3"), "{}", error.detail());
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER)
.exposed
.as_deref(),
Some("1.2.3")
);
}
#[test]
fn a_version_tree_with_no_executable_is_refused_naming_where_it_looked() {
let (at, artifact) = staged("rollback-empty", b"x", CODEX_MEMBER);
let root = at.join("prefix");
install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
fs::create_dir_all(root.join("1.2.2")).unwrap();
let error = rollback(&software(), &root, "1.2.2").unwrap_err();
assert!(error.detail().contains("1.2.2"), "{}", error.detail());
assert!(error.detail().contains("looked at"), "{}", error.detail());
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER)
.exposed
.as_deref(),
Some("1.2.3")
);
}
const CROSS_ARTIFACTS: &[Artifact] = &[
Artifact {
platform: "linux/x86_64",
url: "https://example.invalid/linux.tgz",
bytes: 0,
sha256: "sha256:0",
shape: Shape::GzipTar,
member: "package/bin/tool",
},
Artifact {
platform: "windows/x86_64",
url: "https://example.invalid/windows.tgz",
bytes: 0,
sha256: "sha256:0",
shape: Shape::GzipTar,
member: "package/bin/tool.exe",
},
];
fn cross() -> Software {
Software {
version: "1.2.3",
command: "tool",
delivery: Delivery::Artifacts(CROSS_ARTIFACTS),
unsupported: &[],
previous: None,
}
}
#[test]
fn the_candidate_list_is_this_hosts_member_and_not_the_tables_first() {
let root = Path::new("/prefix/1.2.2");
let windows = executable_candidates(&cross(), root, "windows", "x86_64");
assert_eq!(
windows.first(),
Some(&root.join("package/bin/tool.exe")),
"this host's own member comes first: {windows:?}"
);
assert!(
windows.contains(&root.join("tool.exe")),
"the bare command with its extension is a shape an older tree may \
have used: {windows:?}"
);
let linux = executable_candidates(&cross(), root, "linux", "x86_64");
assert_eq!(
linux.first(),
Some(&root.join("package/bin/tool")),
"{linux:?}"
);
assert!(
!linux.contains(&root.join("tool.exe")),
"the Windows shape is not offered to a platform that cannot run it: {linux:?}"
);
assert_eq!(cross().member_hint(), "package/bin/tool");
assert_ne!(
windows.first(),
Some(&root.join(cross().member_hint())),
"a Windows candidate list must not start at the hint"
);
}
#[test]
fn a_platform_with_no_artifact_still_offers_the_older_shapes() {
let root = Path::new("/prefix/1.2.2");
let found = executable_candidates(&software(), root, "windows", "x86_64");
assert!(!found.is_empty(), "{found:?}");
assert!(found.contains(&root.join("codex.exe")), "{found:?}");
}
fn scratch(name: &str) -> PathBuf {
let path =
std::env::temp_dir().join(format!("setup-core-software-{name}-{}", std::process::id()));
let _ = fs::remove_dir_all(&path);
path
}
fn staged(name: &str, body: &[u8], member: &'static str) -> (PathBuf, Artifact) {
let raw = gzip_tar(
&[
Item::directory("package"),
Item::file(member, body, 0o755),
Item::file("package/README.md", b"read me", 0o644),
],
Dialect::Gnu,
);
let at = scratch(name);
fs::create_dir_all(&at).unwrap();
let file = at.join("artifact.tgz");
fs::write(&file, &raw).unwrap();
let artifact = Artifact {
platform: "linux/x86_64",
url: "https://example.invalid/artifact.tgz",
bytes: raw.len() as u64,
sha256: Box::leak(digest::of_bytes(&raw).into_boxed_str()),
shape: Shape::GzipTar,
member,
};
(at, artifact)
}
#[test]
fn the_artifact_for_this_platform_is_the_one_named_for_it() {
let found = software().artifact_for("linux", "x86_64").unwrap();
assert_eq!(found.url, "https://example.invalid/linux-x86_64.tgz");
}
#[test]
fn a_system_the_vendor_does_not_build_for_is_an_unsupported_platform() {
let error = software().artifact_for("windows", "x86_64").unwrap_err();
assert_eq!(error.reason(), ReasonCode::UnsupportedPlatform);
assert!(
error.detail().contains("linux/x86_64"),
"{}",
error.detail()
);
}
#[test]
fn a_machine_the_vendor_does_not_build_for_is_an_unsupported_architecture() {
let error = software().artifact_for("linux", "riscv64").unwrap_err();
assert_eq!(error.reason(), ReasonCode::UnsupportedArchitecture);
}
#[test]
fn a_product_delivered_by_a_package_manager_says_so_rather_than_pretending() {
let pi = Software {
version: "0.84.3",
command: "pi",
delivery: Delivery::Manager {
tool: "npm",
reason: "its dependency closure is resolved at install time",
},
unsupported: &[],
previous: None,
};
let error = pi.artifact_for("linux", "x86_64").unwrap_err();
assert_eq!(error.reason(), ReasonCode::UnsupportedOperation);
assert!(error.detail().contains("npm"), "{}", error.detail());
}
#[test]
fn an_archive_installs_and_exposes_one_stable_command() {
let (at, artifact) = staged("install", b"#!/bin/sh\necho hi\n", CODEX_MEMBER);
let root = at.join("software");
let installed = install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
assert_eq!(installed.version, "1.2.3");
assert_eq!(installed.files, 3);
assert_eq!(installed.executable, root.join("bin/codex"));
assert!(root.join("1.2.3").join(CODEX_MEMBER).is_file());
assert!(root.join("1.2.3/package/README.md").is_file());
assert_eq!(
fs::read(&installed.executable).unwrap(),
b"#!/bin/sh\necho hi\n"
);
fs::remove_dir_all(&at).unwrap();
}
#[test]
fn installing_twice_replaces_the_tree_rather_than_merging_into_it() {
let (at, artifact) = staged("twice", b"first", CODEX_MEMBER);
let root = at.join("software");
install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
let stray = root.join("1.2.3/package/left-over");
fs::write(&stray, b"from an older install").unwrap();
install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
assert!(!stray.exists(), "a replaced tree must not keep older files");
fs::remove_dir_all(&at).unwrap();
}
#[test]
fn bytes_that_are_not_the_ones_the_plan_named_are_refused() {
let (at, mut artifact) = staged("digest", b"payload", CODEX_MEMBER);
artifact.sha256 = "sha256:0000000000000000000000000000000000000000000000000000000000000000";
let error = install(
&software(),
&artifact,
&at.join("artifact.tgz"),
&at.join("s"),
)
.unwrap_err();
assert_eq!(error.reason(), ReasonCode::IntegrityMismatch);
assert!(error.detail().contains("hashes to"), "{}", error.detail());
fs::remove_dir_all(&at).unwrap();
}
#[test]
fn a_truncated_download_is_named_as_a_length_problem_not_a_digest_one() {
let (at, artifact) = staged("length", b"payload", CODEX_MEMBER);
let file = at.join("artifact.tgz");
let mut bytes = fs::read(&file).unwrap();
bytes.truncate(bytes.len() - 4);
fs::write(&file, &bytes).unwrap();
let error = install(&software(), &artifact, &file, &at.join("s")).unwrap_err();
assert!(
error.detail().contains("bytes; the plan named"),
"{}",
error.detail()
);
fs::remove_dir_all(&at).unwrap();
}
#[test]
fn an_archive_without_the_member_the_plan_named_is_refused() {
let (at, mut artifact) = staged("member", b"payload", CODEX_MEMBER);
artifact.member = "package/vendor/somewhere-else/bin/codex";
let error = install(
&software(),
&artifact,
&at.join("artifact.tgz"),
&at.join("s"),
)
.unwrap_err();
assert!(
error.detail().contains("does not contain"),
"{}",
error.detail()
);
fs::remove_dir_all(&at).unwrap();
}
#[test]
fn removing_takes_the_tree_and_the_exposed_command_with_it() {
let (at, artifact) = staged("remove", b"payload", CODEX_MEMBER);
let root = at.join("software");
let installed = install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
assert!(installed.executable.symlink_metadata().is_ok());
assert!(remove(&software(), &root).unwrap());
assert!(!root.join("1.2.3").exists());
assert!(installed.executable.symlink_metadata().is_err());
assert!(!remove(&software(), &root).unwrap());
fs::remove_dir_all(&at).unwrap();
}
#[test]
fn an_empty_or_absent_prefix_reads_as_holding_nothing() {
let nowhere = scratch("present-absent");
assert_eq!(
Present::under_named(&nowhere, "codex", CODEX_MEMBER),
Present::default()
);
}
#[test]
fn what_is_under_a_prefix_is_read_including_which_version_is_exposed() {
let (at, artifact) = staged("present-read", b"payload", CODEX_MEMBER);
let root = at.join("software");
install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
fs::create_dir_all(root.join("9.9.9")).unwrap();
let found = Present::under_named(&root, "codex", CODEX_MEMBER);
assert_eq!(found.versions, vec!["1.2.3".to_owned(), "9.9.9".to_owned()]);
assert!(found.holds("1.2.3"));
assert!(!found.holds("0.0.1"));
fs::remove_dir_all(&at).unwrap();
}
#[test]
#[cfg(unix)]
fn the_exposed_version_is_read_through_the_link_however_it_was_written() {
let (at, artifact) = staged("present-link", b"payload", CODEX_MEMBER);
let root = at.join("software");
install(&software(), &artifact, &at.join("artifact.tgz"), &root).unwrap();
let link = root.join("bin").join("codex");
assert!(fs::read_link(&link).unwrap().is_absolute());
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER)
.exposed
.as_deref(),
Some("1.2.3")
);
fs::remove_file(&link).unwrap();
std::os::unix::fs::symlink(
std::path::Path::new("..").join("1.2.3").join(CODEX_MEMBER),
&link,
)
.unwrap();
assert!(!fs::read_link(&link).unwrap().is_absolute());
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER)
.exposed
.as_deref(),
Some("1.2.3")
);
fs::remove_file(&link).unwrap();
std::os::unix::fs::symlink(root.join("9.9.9").join("codex"), &link).unwrap();
assert_eq!(
Present::under_named(&root, "codex", CODEX_MEMBER).exposed,
None
);
fs::remove_dir_all(&at).unwrap();
}
#[test]
fn bin_and_the_control_directory_are_not_versions() {
let at = scratch("present-notversions");
fs::create_dir_all(at.join("bin")).unwrap();
fs::create_dir_all(at.join(".codex-setup-system")).unwrap();
fs::create_dir_all(at.join("1.2.3")).unwrap();
assert_eq!(
Present::under_named(&at, "codex", CODEX_MEMBER).versions,
vec!["1.2.3".to_owned()]
);
fs::remove_dir_all(&at).unwrap();
}
#[test]
fn the_inflation_limit_scales_with_the_artifact_but_never_below_a_floor() {
let small = Artifact {
bytes: 10,
..ARTIFACTS[0]
};
assert_eq!(small.limits().bytes, 64 * 1024 * 1024);
let large = Artifact {
bytes: 121_422_431,
..ARTIFACTS[0]
};
assert!(large.limits().bytes > 391_948_592);
}
}