use anyhow::Result;
use std::path::Path;
use std::process::Command;
pub trait Platform: Send + Sync {
fn name(&self) -> &'static str;
fn ensure_binary_loadable(&self, path: &Path) -> Result<()>;
}
pub fn current() -> Box<dyn Platform> {
#[cfg(target_os = "macos")]
{
Box::new(MacOsPlatform)
}
#[cfg(target_os = "linux")]
{
Box::new(LinuxPlatform)
}
#[cfg(target_os = "windows")]
{
Box::new(WindowsPlatform)
}
}
#[allow(dead_code)]
pub struct MacOsPlatform;
impl Platform for MacOsPlatform {
fn name(&self) -> &'static str {
"macos"
}
fn ensure_binary_loadable(&self, path: &Path) -> Result<()> {
if std::env::consts::ARCH != "aarch64" {
return Ok(());
}
if std::env::consts::OS != "macos" {
return Ok(());
}
let verify = match Command::new("codesign")
.args(["--verify", "--strict"])
.arg(path)
.status()
{
Ok(status) => status,
Err(err) => {
tracing::warn!(
"unable to run codesign --verify for {}: {err}",
path.display()
);
return Ok(());
}
};
if verify.success() {
tracing::debug!(
"ad-hoc signature already valid for {}, skipping re-sign",
path.display()
);
return Ok(());
}
tracing::debug!(
"ad-hoc signature missing or invalid for {}, re-applying",
path.display()
);
let status = match Command::new("codesign")
.args(["--sign", "-", "--force"])
.arg(path)
.status()
{
Ok(status) => status,
Err(err) => {
tracing::warn!(
"unable to run codesign --sign for {}: {err}",
path.display()
);
return Ok(());
}
};
if !status.success() {
tracing::warn!("ad-hoc codesign failed for {}", path.display());
}
Ok(())
}
}
#[allow(dead_code)]
pub struct LinuxPlatform;
impl Platform for LinuxPlatform {
fn name(&self) -> &'static str {
"linux"
}
fn ensure_binary_loadable(&self, _path: &Path) -> Result<()> {
Ok(())
}
}
#[allow(dead_code)]
pub struct WindowsPlatform;
impl Platform for WindowsPlatform {
fn name(&self) -> &'static str {
"windows"
}
fn ensure_binary_loadable(&self, _path: &Path) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
pub struct CountingPlatform {
ensure_binary_loadable_calls: AtomicUsize,
}
impl CountingPlatform {
pub fn new() -> Self {
Self {
ensure_binary_loadable_calls: AtomicUsize::new(0),
}
}
pub fn ensure_calls(&self) -> usize {
self.ensure_binary_loadable_calls.load(Ordering::Relaxed)
}
}
impl Platform for CountingPlatform {
fn name(&self) -> &'static str {
"counting"
}
fn ensure_binary_loadable(&self, _path: &Path) -> Result<()> {
self.ensure_binary_loadable_calls
.fetch_add(1, Ordering::Relaxed);
Ok(())
}
}
#[test]
fn current_returns_a_platform_named_after_the_host() {
let platform = current();
let expected = if cfg!(target_os = "macos") {
"macos"
} else if cfg!(target_os = "linux") {
"linux"
} else if cfg!(target_os = "windows") {
"windows"
} else {
panic!("unsupported host OS in test")
};
assert_eq!(platform.name(), expected);
}
#[test]
fn linux_ensure_binary_loadable_is_noop_for_any_path() {
let platform = LinuxPlatform;
platform
.ensure_binary_loadable(Path::new("/no/such/file"))
.unwrap();
}
#[test]
fn windows_ensure_binary_loadable_is_noop_for_any_path() {
let platform = WindowsPlatform;
platform
.ensure_binary_loadable(Path::new("/no/such/file"))
.unwrap();
}
#[test]
fn macos_ensure_binary_loadable_does_not_propagate_errors() {
let platform = MacOsPlatform;
platform
.ensure_binary_loadable(Path::new("/no/such/file"))
.unwrap();
}
#[test]
fn counting_platform_records_ensure_calls() {
let platform = CountingPlatform::new();
assert_eq!(platform.ensure_calls(), 0);
platform.ensure_binary_loadable(Path::new("/x")).unwrap();
platform.ensure_binary_loadable(Path::new("/y")).unwrap();
assert_eq!(platform.ensure_calls(), 2);
}
}