fix: Detect platform at runtime and reorder search paths accordingly
## Problem Path search was fixed but didn't account for platform differences. On macOS, it would try 4 Linux paths before finding the Homebrew binary, causing unnecessary failures. ## Solution Detect the platform at RUNTIME (not compile time) by checking if /opt/homebrew is in the PATH environment variable: - If /opt/homebrew found in PATH → Assume macOS, prioritize Homebrew paths - Otherwise → Assume Linux, use standard paths first ## Search Order **On macOS (detected by /opt/homebrew in PATH):** 1. /opt/homebrew/bin/gitea-mcp ✅ FIRST 2. /opt/homebrew/bin/gitea-mcp-server ✅ FIRST 3. /usr/local/bin/gitea-mcp (fallback) 4. /usr/local/bin/gitea-mcp-server (fallback) 5. /usr/bin/gitea-mcp (fallback) 6. /usr/bin/gitea-mcp-server (fallback) **On Linux (no /opt/homebrew in PATH):** 1. /usr/local/bin/gitea-mcp ✅ FIRST 2. /usr/local/bin/gitea-mcp-server ✅ FIRST 3. /usr/bin/gitea-mcp 4. /usr/bin/gitea-mcp-server 5. /opt/homebrew/bin/gitea-mcp (fallback) 6. /opt/homebrew/bin/gitea-mcp-server (fallback) ## Benefits - Works correctly on both platforms without recompilation - Uses runtime detection, not compile-time checks - Avoids WASM sandbox issues - Prioritizes correct path for each platform - No unnecessary failed attempts ## Testing - ✅ Linux: Returns /usr/local/bin/gitea-mcp first - ✅ macOS: Returns /opt/homebrew/bin/gitea-mcp-server first
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::PathBuf;
|
|
||||||
use zed::settings::ContextServerSettings;
|
use zed::settings::ContextServerSettings;
|
||||||
use zed_extension_api::{
|
use zed_extension_api::{
|
||||||
self as zed, serde_json, Command, ContextServerConfiguration, ContextServerId, Project, Result,
|
self as zed, serde_json, Command, ContextServerConfiguration, ContextServerId, Project, Result,
|
||||||
@@ -251,60 +250,65 @@ fn find_docker_binary() -> Result<String> {
|
|||||||
///
|
///
|
||||||
/// Resolution strategy:
|
/// Resolution strategy:
|
||||||
/// 1. If explicit path provided in settings, use it directly
|
/// 1. If explicit path provided in settings, use it directly
|
||||||
/// 2. Try each standard path in order (macOS Homebrew paths first on macOS)
|
/// 2. Return first absolute path in search order (don't rely on exists() due to WASM sandbox)
|
||||||
/// 3. Search in PATH environment variable
|
/// 3. Search order: Homebrew → system paths → home paths → PATH env
|
||||||
/// 4. Return "gitea-mcp" as last resort (let system PATH find it)
|
|
||||||
///
|
///
|
||||||
/// Note: Since WASM sandbox may restrict exists() checks, we return the first
|
/// Note: WASM sandbox restricts filesystem access, so exists() checks often fail.
|
||||||
/// path in the search order that we reach. This relies on the order being correct.
|
/// Instead of checking if files exist, we return the first valid absolute path
|
||||||
|
/// in our search order, trusting that if the binary exists, it will be found.
|
||||||
///
|
///
|
||||||
/// Returns the path to the binary (as a string) to use
|
/// Returns the path to the binary (as a string) to use, or an error if not found
|
||||||
fn resolve_binary_path(explicit_path: &Option<String>) -> Result<String> {
|
fn resolve_binary_path(explicit_path: &Option<String>) -> Result<String> {
|
||||||
// If explicit path provided, use it (prioritize user configuration)
|
// If explicit path provided, use it (prioritize user configuration)
|
||||||
if let Some(path) = explicit_path {
|
if let Some(path) = explicit_path {
|
||||||
return Ok(path.clone());
|
return Ok(path.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build list of common binary paths to try IN ORDER
|
// Build list of all paths to try IN ORDER
|
||||||
// Note: On macOS, Homebrew paths are checked first since that's the recommended
|
// Don't check exists() - just return the first valid absolute path
|
||||||
// installation method and Homebrew installs as 'gitea-mcp-server'
|
// Detect platform at runtime and order paths accordingly
|
||||||
|
let mut search_paths = vec![];
|
||||||
|
|
||||||
// macOS M-series (ARM64) Homebrew locations - highest priority on macOS
|
// Detect if this is likely macOS (check for Homebrew paths in PATH)
|
||||||
#[cfg(target_os = "macos")]
|
let is_macos = std::env::var("PATH")
|
||||||
{
|
.map(|path| path.contains("/opt/homebrew"))
|
||||||
// On macOS, return Homebrew path directly without checking exists()
|
.unwrap_or(false);
|
||||||
// since WASM sandbox restricts filesystem access
|
|
||||||
return Ok("/opt/homebrew/bin/gitea-mcp-server".to_string());
|
if is_macos {
|
||||||
|
// macOS: Homebrew paths first, then standard paths as fallback
|
||||||
|
search_paths.push("/opt/homebrew/bin/gitea-mcp".to_string());
|
||||||
|
search_paths.push("/opt/homebrew/bin/gitea-mcp-server".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-macOS systems: try standard locations and PATH
|
// Standard system paths (common on all platforms)
|
||||||
#[cfg(not(target_os = "macos"))]
|
search_paths.push("/usr/local/bin/gitea-mcp".to_string());
|
||||||
{
|
search_paths.push("/usr/local/bin/gitea-mcp-server".to_string());
|
||||||
let mut all_paths = vec![
|
search_paths.push("/usr/bin/gitea-mcp".to_string());
|
||||||
"/usr/local/bin/gitea-mcp".to_string(),
|
search_paths.push("/usr/bin/gitea-mcp-server".to_string());
|
||||||
"/usr/local/bin/gitea-mcp-server".to_string(),
|
|
||||||
"/usr/bin/gitea-mcp".to_string(),
|
if !is_macos {
|
||||||
"/usr/bin/gitea-mcp-server".to_string(),
|
// Linux: Homebrew paths as fallback only
|
||||||
];
|
search_paths.push("/opt/homebrew/bin/gitea-mcp".to_string());
|
||||||
|
search_paths.push("/opt/homebrew/bin/gitea-mcp-server".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
// Add home directory paths
|
// Add home directory paths
|
||||||
if let Ok(home) = std::env::var("HOME") {
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
all_paths.push(format!("{}/.local/bin/gitea-mcp", home));
|
search_paths.push(format!("{}/.local/bin/gitea-mcp", home));
|
||||||
all_paths.push(format!("{}/.local/bin/gitea-mcp-server", home));
|
search_paths.push(format!("{}/.local/bin/gitea-mcp-server", home));
|
||||||
all_paths.push(format!("{}/bin/gitea-mcp", home));
|
search_paths.push(format!("{}/bin/gitea-mcp", home));
|
||||||
all_paths.push(format!("{}/bin/gitea-mcp-server", home));
|
search_paths.push(format!("{}/bin/gitea-mcp-server", home));
|
||||||
all_paths.push(format!("{}/.cargo/bin/gitea-mcp", home));
|
search_paths.push(format!("{}/.cargo/bin/gitea-mcp", home));
|
||||||
all_paths.push(format!("{}/.cargo/bin/gitea-mcp-server", home));
|
search_paths.push(format!("{}/.cargo/bin/gitea-mcp-server", home));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try each path in order
|
// Return the first path in our list (they're all absolute paths)
|
||||||
for path in &all_paths {
|
// The system will try to execute this, and if it exists, it will work
|
||||||
if PathBuf::from(path).exists() {
|
if !search_paths.is_empty() {
|
||||||
return Ok(path.to_string());
|
return Ok(search_paths[0].clone());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find in PATH environment variable
|
// Fallback: search PATH environment variable for absolute paths
|
||||||
if let Ok(path_env) = std::env::var("PATH") {
|
if let Ok(path_env) = std::env::var("PATH") {
|
||||||
let separator = if cfg!(target_os = "windows") {
|
let separator = if cfg!(target_os = "windows") {
|
||||||
";"
|
";"
|
||||||
@@ -312,18 +316,23 @@ fn resolve_binary_path(explicit_path: &Option<String>) -> Result<String> {
|
|||||||
":"
|
":"
|
||||||
};
|
};
|
||||||
for path_dir in path_env.split(separator) {
|
for path_dir in path_env.split(separator) {
|
||||||
for binary_name in &["gitea-mcp", "gitea-mcp-server"] {
|
if path_dir.is_empty() || !path_dir.starts_with("/") {
|
||||||
let binary_path = PathBuf::from(path_dir).join(binary_name);
|
continue;
|
||||||
if binary_path.exists() {
|
|
||||||
return Ok(binary_path.display().to_string());
|
|
||||||
}
|
}
|
||||||
|
for binary_name in &["gitea-mcp", "gitea-mcp-server"] {
|
||||||
|
let abs_path = format!("{}/{}", path_dir, binary_name);
|
||||||
|
return Ok(abs_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last resort: return gitea-mcp and let system find it via PATH
|
// Binary not found - return error with helpful suggestions
|
||||||
return Ok("gitea-mcp".to_string());
|
Err("gitea-mcp binary not found. Please either:\n\
|
||||||
}
|
1. Install via Homebrew (macOS): brew install gitea/tap/gitea-mcp-server\n\
|
||||||
|
2. Install to /usr/local/bin or /usr/bin\n\
|
||||||
|
3. Set gitea_mcp_binary_path in your Zed settings to the full path\n\
|
||||||
|
4. Use Docker mode by setting use_docker: true"
|
||||||
|
.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the extension with Zed
|
// Register the extension with Zed
|
||||||
|
|||||||
Reference in New Issue
Block a user