use anyhow::{bail, Context, Result};
use std::path::{Path, PathBuf};
pub const GUEST_BIN: &str = "/usr/local/bin/omh";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Delivery {
HostBinary(PathBuf),
Cached(PathBuf),
MustBuild(PathBuf),
}
pub fn cached_at(root: &Path, arch: &str) -> PathBuf {
root.join("bin").join(format!("omh-linux-{arch}"))
}
pub fn plan_delivery(
os: &str,
arch: &str,
current_exe: &Path,
root: &Path,
exists: &dyn Fn(&Path) -> bool,
) -> Delivery {
if os == "linux" {
return Delivery::HostBinary(current_exe.to_path_buf());
}
let cached = cached_at(root, arch);
match exists(&cached) {
true => Delivery::Cached(cached),
false => Delivery::MustBuild(cached),
}
}
pub fn target_arch(host_arch: &str) -> Result<&'static str> {
match host_arch {
"aarch64" | "arm64" => Ok("aarch64"),
"x86_64" | "amd64" => Ok("x86_64"),
other => bail!("no linux build target known for `{other}`"),
}
}
pub fn available(paths: &crate::profile::Paths) -> Option<PathBuf> {
let arch = match target_arch(std::env::consts::ARCH) {
Ok(a) => a,
Err(e) => {
eprintln!("omh: no memory server here — {e:#}");
return None;
}
};
let exe = match std::env::current_exe() {
Ok(p) => p,
Err(e) => {
eprintln!("omh: no memory server here — cannot locate the running omh: {e}");
return None;
}
};
let plan = plan_delivery(
std::env::consts::OS,
arch,
&exe,
&paths.root,
&|p: &Path| p.exists(),
);
match plan {
Delivery::HostBinary(p) | Delivery::Cached(p) => Some(p),
Delivery::MustBuild(_) => None,
}
}
pub fn ensure(program: &str, paths: &crate::profile::Paths, crate_dir: &Path) -> Result<PathBuf> {
let arch = target_arch(std::env::consts::ARCH)?;
let exe = std::env::current_exe().context("locating the running omh")?;
match plan_delivery(
std::env::consts::OS,
arch,
&exe,
&paths.root,
&|p: &Path| p.exists(),
) {
Delivery::HostBinary(p) | Delivery::Cached(p) => Ok(p),
Delivery::MustBuild(out) => {
if !crate_dir.join("Cargo.toml").exists() {
bail!(
"no omh sources at {} — a released build cannot cross-build \
itself, so the memory server needs a published linux binary",
crate_dir.display()
);
}
build(program, crate_dir, &out, arch)?;
Ok(out)
}
}
}
pub fn build(program: &str, crate_dir: &Path, out: &Path, arch: &str) -> Result<()> {
std::fs::create_dir_all(out.parent().context("cache has no parent")?)?;
let target = format!("{arch}-unknown-linux-gnu");
eprintln!("omh: cross-building the memory server for linux/{arch} (first run only)");
let status = std::process::Command::new(program)
.args([
"run",
"--rm",
"--platform",
&format!("linux/{}", docker_arch(arch)),
])
.arg("-v")
.arg(format!("{}:/src:ro", crate_dir.display()))
.arg("-v")
.arg(format!("{}:/out", out.parent().unwrap().display()))
.args(["-v", "omh-selfbuild:/cargo"])
.args(["-e", "CARGO_HOME=/cargo"])
.args(["-w", "/build"])
.arg("rust:1-bookworm")
.args([
"sh",
"-c",
&format!(
"cp -r /src/src /src/Cargo.toml /src/Cargo.lock /build/ 2>/dev/null; \
cd /build && cargo build --release --locked \
&& cp target/release/omh /out/{}",
out.file_name().unwrap().to_string_lossy()
),
])
.status()
.with_context(|| format!("running {program} for the cross-build"))?;
if !status.success() {
bail!("cross-building omh for linux/{arch} failed");
}
let _ = target; Ok(())
}
fn docker_arch(arch: &str) -> &str {
match arch {
"aarch64" => "arm64",
_ => "amd64",
}
}
#[cfg(test)]
mod tests {
use super::*;
const NEVER: &dyn Fn(&Path) -> bool = &|_: &Path| false;
const ALWAYS: &dyn Fn(&Path) -> bool = &|_: &Path| true;
#[test]
fn on_linux_the_running_binary_is_mounted_as_it_is() {
let plan = plan_delivery(
"linux",
"aarch64",
Path::new("/usr/bin/omh"),
Path::new("/home/x/.omh"),
NEVER,
);
assert_eq!(plan, Delivery::HostBinary(PathBuf::from("/usr/bin/omh")));
}
#[test]
fn on_macos_the_hosts_own_binary_is_never_used() {
for exists in [NEVER, ALWAYS] {
let plan = plan_delivery(
"macos",
"aarch64",
Path::new("/opt/homebrew/bin/omh"),
Path::new("/home/x/.omh"),
exists,
);
let chosen = match plan {
Delivery::HostBinary(p) | Delivery::Cached(p) | Delivery::MustBuild(p) => p,
};
assert_ne!(
chosen,
PathBuf::from("/opt/homebrew/bin/omh"),
"a darwin binary in a linux container is `exec format error`"
);
}
}
#[test]
fn a_cross_build_is_reused_when_it_is_already_there() {
let root = Path::new("/home/x/.omh");
assert_eq!(
plan_delivery("macos", "aarch64", Path::new("/bin/omh"), root, ALWAYS),
Delivery::Cached(cached_at(root, "aarch64"))
);
assert_eq!(
plan_delivery("macos", "aarch64", Path::new("/bin/omh"), root, NEVER),
Delivery::MustBuild(cached_at(root, "aarch64"))
);
}
#[test]
fn each_architecture_caches_to_its_own_path() {
let root = Path::new("/home/x/.omh");
assert_ne!(cached_at(root, "aarch64"), cached_at(root, "x86_64"));
for arch in ["aarch64", "x86_64"] {
assert!(
cached_at(root, arch).to_string_lossy().contains(arch),
"the path must name the architecture it holds"
);
}
}
#[test]
fn an_unknown_host_architecture_is_an_error_not_a_guess() {
assert_eq!(target_arch("aarch64").unwrap(), "aarch64");
assert_eq!(target_arch("arm64").unwrap(), "aarch64");
assert_eq!(target_arch("x86_64").unwrap(), "x86_64");
let err = target_arch("riscv64").unwrap_err().to_string();
assert!(err.contains("riscv64"), "got: {err}");
}
#[test]
fn a_build_with_no_sources_says_what_is_actually_wrong() {
let dir = tempfile::tempdir().unwrap();
let paths = crate::profile::Paths {
root: dir.path().join("home"),
repo: dir.path().join("repo"),
};
if std::env::consts::OS == "linux" {
return;
}
let err = ensure("docker", &paths, dir.path())
.unwrap_err()
.to_string();
assert!(err.contains("published linux binary"), "got: {err}");
assert!(
err.contains(&dir.path().display().to_string()),
"got: {err}"
);
}
#[test]
fn the_guest_path_is_on_the_default_path() {
assert!(GUEST_BIN.starts_with("/usr/local/bin/"));
assert!(
!GUEST_BIN.starts_with(crate::image::GUEST_HOME),
"a program is not state, and the home is mounted over"
);
}
}