feat: add cross-platform support via platform detection module

Introduces src/platform.rs with OS detection and env var overrides.
All hardcoded Linux paths replaced with Platform::detect() across
8 source files. Key changes:

- New Platform struct with 11 fields (all overridable via env vars)
- /proc/ access gated to Linux (#[cfg(target_os = "linux")])
- pgrep/pkill patterns broadened for cross-platform LS discovery
- sec-ch-ua-platform header now dynamic per OS
- Token, traces, config, CA cert paths use platform module
- LD_PRELOAD DNS redirect gated to Linux only
- Setup scripts for Linux (systemd) and macOS (launchd)
- find_ls_binary_path has cross-platform stubs

All 46 tests pass, cargo check clean.
This commit is contained in:
Nikketryhard
2026-02-18 02:13:23 -06:00
parent 7136c0e53c
commit 8a9662edea
10 changed files with 587 additions and 108 deletions

View File

@@ -21,26 +21,54 @@ struct DetectedVersions {
/// back to its binary, then walking up to the app root. Falls back to
/// well-known install paths.
fn find_install_dir() -> Option<String> {
// 1. Try tracing the running language server → /usr/share/antigravity/resources/app/extensions/...
if let Ok(output) = Command::new("sh")
.args(["-c", "pgrep -f language_server_linux | head -1"])
.output()
let p = crate::platform::Platform::detect();
// 1. Check if platform-detected app_root exists
let app_root_parent = std::path::Path::new(&p.app_root)
.parent()
.and_then(|p| p.parent())
.map(|p| p.to_string_lossy().to_string());
if let Some(ref dir) = app_root_parent {
if fs::metadata(format!("{dir}/resources/app/product.json")).is_ok() {
return Some(dir.clone());
}
}
// 2. Try tracing the running language server via /proc (Linux only)
#[cfg(target_os = "linux")]
{
let pid = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !pid.is_empty() {
if let Ok(exe) = fs::read_link(format!("/proc/{pid}/exe")) {
let exe_str = exe.to_string_lossy().to_string();
// exe is like: /usr/share/antigravity/resources/app/extensions/antigravity/bin/language_server_linux_x64
// We want: /usr/share/antigravity
if let Some(idx) = exe_str.find("/resources/") {
return Some(exe_str[..idx].to_string());
if let Ok(output) = Command::new("sh")
.args(["-c", "pgrep -f language_server | head -1"])
.output()
{
let pid = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !pid.is_empty() {
if let Ok(exe) = fs::read_link(format!("/proc/{pid}/exe")) {
let exe_str = exe.to_string_lossy().to_string();
if let Some(idx) = exe_str.find("/resources/") {
return Some(exe_str[..idx].to_string());
}
}
}
}
}
// 2. Fall back to well-known install paths
for path in &["/usr/share/antigravity", "/opt/Antigravity"] {
// 3. Fall back to well-known install paths
#[cfg(target_os = "linux")]
let candidates = ["/usr/share/antigravity", "/opt/Antigravity"];
#[cfg(target_os = "macos")]
let candidates = [
"/Applications/Antigravity.app/Contents",
&format!("{}/Applications/Antigravity.app/Contents", std::env::var("HOME").unwrap_or_default()),
];
#[cfg(target_os = "windows")]
let candidates = [
&format!("{}\\Programs\\Antigravity", std::env::var("LOCALAPPDATA").unwrap_or_default()),
];
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
let candidates: [&str; 0] = [];
for path in &candidates {
if fs::metadata(format!("{path}/resources/app/product.json")).is_ok() {
return Some(path.to_string());
}
@@ -178,14 +206,23 @@ pub const LS_SERVICE: &str = "exa.language_server_pb.LanguageServerService";
/// Log base directory for Antigravity.
pub fn log_base() -> String {
let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
format!("{home}/.config/Antigravity/logs")
let p = crate::platform::Platform::detect();
// Antigravity logs live next to its state DB
let state_parent = std::path::Path::new(&p.state_db_path)
.parent()
.and_then(|p| p.parent())
.and_then(|p| p.parent())
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| {
let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
format!("{home}/.config/Antigravity")
});
format!("{state_parent}/logs")
}
/// Token file path.
pub fn token_file_path() -> String {
let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
format!("{home}/.config/zerogravity/token")
crate::platform::Platform::detect().token_path.to_string_lossy().to_string()
}
/// User-Agent string matching the Electron webview — computed once.