use std::path::{Path, PathBuf};
#[cfg(test)]
mod integrity;
const SIDECAR_NAMES: &[&str] = if cfg!(windows) {
&[
"sidecar-slim.exe",
"truffle-sidecar.exe",
"sidecar-slim",
"truffle-sidecar",
]
} else {
&["sidecar-slim", "truffle-sidecar"]
};
pub fn sidecar_path() -> PathBuf {
if let Ok(path) = std::env::var("TRUFFLE_SIDECAR_PATH") {
let p = PathBuf::from(&path);
if p.exists() {
return p;
}
}
let exe_dir = std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|d| d.to_path_buf()));
resolve(
exe_dir.as_deref(),
&config_bin_dirs(),
Path::new(env!("TRUFFLE_SIDECAR_PATH")),
&system_bin_dirs(),
)
}
fn resolve(
exe_dir: Option<&Path>,
config_dirs: &[PathBuf],
build_path: &Path,
system_dirs: &[PathBuf],
) -> PathBuf {
if let Some(dir) = exe_dir {
for name in SIDECAR_NAMES {
let candidate = dir.join(name);
if candidate.exists() {
return candidate;
}
}
}
for dir in config_dirs {
for name in SIDECAR_NAMES {
let candidate = dir.join(name);
if candidate.exists() {
return candidate;
}
}
}
if build_path.is_absolute() && build_path.exists() {
return build_path.to_path_buf();
}
for dir in system_dirs {
for name in SIDECAR_NAMES {
let candidate = dir.join(name);
if candidate.exists() {
eprintln!(
"truffle-sidecar: warning: using system sidecar at {} (not managed by truffle)",
candidate.display()
);
return candidate;
}
}
}
eprintln!(
"truffle-sidecar: warning: sidecar not found in trusted locations; falling back to \"sidecar-slim\" on PATH"
);
PathBuf::from("sidecar-slim")
}
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
fn config_bin_dirs() -> Vec<PathBuf> {
#[cfg(target_os = "macos")]
{
std::env::var("HOME")
.ok()
.map(|h| {
let home = PathBuf::from(h);
vec![
home.join(".config/truffle/bin"),
home.join("Library/Application Support/truffle/bin"),
]
})
.unwrap_or_default()
}
#[cfg(target_os = "linux")]
{
std::env::var("XDG_CONFIG_HOME")
.ok()
.map(PathBuf::from)
.or_else(|| {
std::env::var("HOME")
.ok()
.map(|h| PathBuf::from(h).join(".config"))
})
.map(|d| vec![d.join("truffle/bin")])
.unwrap_or_default()
}
#[cfg(target_os = "windows")]
{
std::env::var("APPDATA")
.ok()
.map(|d| vec![PathBuf::from(d).join("truffle\\bin")])
.unwrap_or_default()
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
Vec::new()
}
}
fn system_bin_dirs() -> Vec<PathBuf> {
#[cfg(not(windows))]
{
vec![PathBuf::from("/usr/local/bin")]
}
#[cfg(windows)]
{
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn touch_sidecar(dir: &Path) -> PathBuf {
let p = dir.join(SIDECAR_NAMES[0]);
fs::write(&p, b"").unwrap();
p
}
#[test]
fn build_path_preferred_over_system_dir() {
let build = tempfile::tempdir().unwrap();
let system = tempfile::tempdir().unwrap();
let build_bin = touch_sidecar(build.path());
touch_sidecar(system.path());
let got = resolve(None, &[], &build_bin, &[system.path().to_path_buf()]);
assert_eq!(got, build_bin);
}
#[test]
fn exe_dir_preferred_over_all() {
let exe = tempfile::tempdir().unwrap();
let config = tempfile::tempdir().unwrap();
let build = tempfile::tempdir().unwrap();
let system = tempfile::tempdir().unwrap();
let exe_bin = touch_sidecar(exe.path());
touch_sidecar(config.path());
let build_bin = touch_sidecar(build.path());
touch_sidecar(system.path());
let got = resolve(
Some(exe.path()),
&[config.path().to_path_buf()],
&build_bin,
&[system.path().to_path_buf()],
);
assert_eq!(got, exe_bin);
}
#[test]
fn config_dir_preferred_over_build_path() {
let config = tempfile::tempdir().unwrap();
let build = tempfile::tempdir().unwrap();
let config_bin = touch_sidecar(config.path());
let build_bin = touch_sidecar(build.path());
let got = resolve(None, &[config.path().to_path_buf()], &build_bin, &[]);
assert_eq!(got, config_bin);
}
#[test]
fn system_dir_used_when_no_trusted_candidate() {
let system = tempfile::tempdir().unwrap();
let system_bin = touch_sidecar(system.path());
let missing = system.path().join("missing-build-binary");
let got = resolve(None, &[], &missing, &[system.path().to_path_buf()]);
assert_eq!(got, system_bin);
}
#[test]
fn falls_back_to_bare_path_name() {
let empty = tempfile::tempdir().unwrap();
let missing = empty.path().join("missing-build-binary");
let got = resolve(None, &[], &missing, &[]);
assert_eq!(got, PathBuf::from("sidecar-slim"));
}
#[test]
fn relative_build_path_is_ignored() {
let got = resolve(None, &[], Path::new("truffle-sidecar"), &[]);
assert_eq!(got, PathBuf::from("sidecar-slim"));
}
#[cfg(target_os = "macos")]
#[test]
fn macos_config_dirs_prefer_installer_location() {
let dirs = config_bin_dirs();
assert_eq!(dirs.len(), 2);
assert!(dirs[0].ends_with(".config/truffle/bin"));
assert!(dirs[1].ends_with("Library/Application Support/truffle/bin"));
}
}