use crate::env::{EnvKind, Owner};
crate::declare_env_vars! {
pub const DECLARED_PLATFORM;
COMPUTERNAME => "COMPUTERNAME",
EnvKind::Text, Owner::Foreign, "hostname is unknown",
"Windows machine name, reported as the host name.";
DISPLAY => "DISPLAY",
EnvKind::Text, Owner::Foreign, "no X11 display; window icons unsupported",
"X11 display; its presence is what makes a Linux window icon possible.";
HOME => "HOME",
EnvKind::Path, Owner::Foreign, "autostart paths cannot be resolved",
"Home directory; roots the autostart entries and the fallback APE loader cache directory.";
LOCALAPPDATA => "LOCALAPPDATA",
EnvKind::Path, Owner::Foreign, "the platform default is derived",
"Windows per-user application data root.";
PATH => "PATH",
EnvKind::Text, Owner::Foreign, "the child inherits no explicit PATH",
"Executable search path, forwarded to the symbolization worker and searched for an APE image or loader.";
APE_CACHE_DIR => "RUNNING_PROCESS_APE_CACHE_DIR",
EnvKind::Path, Owner::Crate, "the XDG cache, runtime and temporary directories",
"Preferred directory for loaders extracted from APE images; used only when private and exec-capable.";
APE_LOADER => "RUNNING_PROCESS_APE_LOADER",
EnvKind::Path, Owner::Crate, "the image's embedded loader, then `ape`, then `/bin/sh`",
"Explicit loader (an `ape` binary or a POSIX shell) for APE images.";
CONPTY_CACHE => "RUNNING_PROCESS_CONPTY_CACHE",
EnvKind::Path, Owner::Crate, "the platform cache directory",
"Root under which the ConPTY sidecar is cached on Windows.";
CONPTY_DIAGNOSTICS => "RUNNING_PROCESS_CONPTY_DIAGNOSTICS",
EnvKind::Text, Owner::Crate, "ConPTY resolution is silent",
"Print how the ConPTY implementation was chosen and fetched, to stderr.";
CONPTY_OFFLINE => "RUNNING_PROCESS_CONPTY_OFFLINE",
EnvKind::Text, Owner::Crate, "the ConPTY sidecar may be fetched",
"Forbid fetching the ConPTY sidecar over the network.";
CONPTY_SIDECAR_FETCH_TIMEOUT_MS => "RUNNING_PROCESS_CONPTY_SIDECAR_FETCH_TIMEOUT_MS",
EnvKind::Number { zero_selects_default: true }, Owner::Crate, "the built-in fetch timeout",
"Upper bound on the ConPTY sidecar download, in milliseconds.";
KILL_DRAIN_TIMEOUT_MS => "RUNNING_PROCESS_KILL_DRAIN_TIMEOUT_MS",
EnvKind::Number { zero_selects_default: false }, Owner::Crate, "two seconds",
"How long `kill()` waits for output capture to drain, in milliseconds.";
NATIVE_TERMINAL_INPUT_TRACE_PATH => "RUNNING_PROCESS_NATIVE_TERMINAL_INPUT_TRACE_PATH",
EnvKind::Text, Owner::Crate, "terminal input is not traced",
"Where Windows native terminal input events are traced.";
USE_SYSTEM_CONPTY => "RUNNING_PROCESS_USE_SYSTEM_CONPTY",
EnvKind::Text, Owner::Crate, "the bundled ConPTY sidecar is preferred",
"Use the system ConPTY instead of the bundled sidecar on Windows.";
TMPDIR => "TMPDIR",
EnvKind::Path, Owner::Foreign, "the platform temporary directory",
"macOS per-session temporary directory; a broker endpoint root.";
WAYLAND_DISPLAY => "WAYLAND_DISPLAY",
EnvKind::Text, Owner::Foreign, "not a Wayland session",
"Wayland session marker; window icons are unsupported under Wayland.";
WINDOWID => "WINDOWID",
EnvKind::Text, Owner::Foreign, "the X11 window is unknown",
"X11 window id exported by the terminal emulator.";
WT_SESSION => "WT_SESSION",
EnvKind::Text, Owner::Foreign, "not inside Windows Terminal",
"Windows Terminal session marker; runtime window icons are degraded there.";
XDG_CACHE_HOME => "XDG_CACHE_HOME",
EnvKind::Path, Owner::Foreign, "`~/.cache` is used",
"XDG per-user cache root; where extracted APE loaders are installed.";
XDG_CONFIG_HOME => "XDG_CONFIG_HOME",
EnvKind::Path, Owner::Foreign, "`~/.config` is used",
"XDG per-user configuration root; where service definitions are read.";
XDG_DATA_HOME => "XDG_DATA_HOME",
EnvKind::Path, Owner::Foreign, "the platform default is derived",
"XDG per-user data root, used by the daemon runtime collector.";
XDG_RUNTIME_DIR => "XDG_RUNTIME_DIR",
EnvKind::Path, Owner::Foreign, "a per-user directory under /tmp",
"XDG per-user runtime root; where broker sockets and fallback APE loaders are placed.";
XDG_STATE_HOME => "XDG_STATE_HOME",
EnvKind::Path, Owner::Foreign, "~/.local/state",
"XDG state root.";
}
#[cfg(test)]
mod tests {
use super::*;
use crate::env::Owner;
#[test]
fn declarations_are_sorted_unique_and_documented() {
let names: Vec<&str> = DECLARED_PLATFORM.iter().map(|var| var.name).collect();
let mut sorted = names.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(names, sorted, "DECLARED_PLATFORM must be sorted and unique");
for var in DECLARED_PLATFORM {
assert!(
!var.summary.trim().is_empty(),
"{} has no summary",
var.name
);
assert!(
!var.default.trim().is_empty(),
"{} has no default",
var.name
);
}
}
#[test]
fn owner_follows_the_name() {
for var in DECLARED_PLATFORM {
let ours = var.name.starts_with("RUNNING_PROCESS_");
assert_eq!(
var.owner == Owner::Crate,
ours,
"{} is declared {:?}",
var.name,
var.owner
);
}
}
const DYNAMIC_KEYS: &[&str] = &[];
#[test]
fn production_code_calls_std_env_only_for_dynamic_keys() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut offenders = Vec::new();
visit(&root, &mut offenders);
assert!(
offenders.is_empty(),
"undeclared environment reads: {offenders:#?}"
);
}
fn visit(dir: &std::path::Path, offenders: &mut Vec<String>) {
for entry in std::fs::read_dir(dir).expect("read src") {
let path = entry.expect("dir entry").path();
if path.is_dir() {
if path.file_name().is_some_and(|name| name == "tests") {
continue;
}
visit(&path, offenders);
continue;
}
if path.extension().is_none_or(|ext| ext != "rs")
|| path.file_name().is_some_and(|name| {
let name = name.to_string_lossy();
name == "env.rs" || name == "env_vars.rs" || name.ends_with("_tests.rs")
})
{
continue;
}
let text = std::fs::read_to_string(&path).expect("read source");
let lines: Vec<&str> = text.lines().collect();
let cut = lines
.iter()
.enumerate()
.position(|(index, line)| {
let line = line.trim_start();
(line.starts_with("#[cfg(test)]") || line.starts_with("#[cfg(all(test"))
&& lines[index + 1..]
.iter()
.map(|next| next.trim())
.find(|next| !next.is_empty() && !next.starts_with("#["))
.is_some_and(|next| next.starts_with("mod ") && next.ends_with('{'))
})
.unwrap_or(lines.len());
for (index, line) in lines[..cut].iter().enumerate() {
let direct = [
"env::var(",
"env::var_os(",
"env::set_var(",
"env::remove_var(",
]
.iter()
.find_map(|call| line.find(call).map(|at| &line[at + call.len()..]));
if let Some(arg) = direct {
if !DYNAMIC_KEYS.iter().any(|key| arg.starts_with(key)) {
offenders.push(format!("{}:{}", path.display(), index + 1));
}
}
}
}
}
}