venus_server/
rust_analyzer.rs1use std::path::PathBuf;
6use std::process::Stdio;
7
8use tokio::io::AsyncWriteExt;
9use tokio::process::Command;
10
11const RUST_ANALYZER_VERSION: &str = "2025-12-29";
13
14pub fn cache_dir() -> PathBuf {
16 dirs::cache_dir()
17 .unwrap_or_else(|| PathBuf::from("."))
18 .join("venus")
19 .join("bin")
20}
21
22pub fn rust_analyzer_path() -> PathBuf {
24 let binary_name = if cfg!(windows) {
25 "rust-analyzer.exe"
26 } else {
27 "rust-analyzer"
28 };
29 cache_dir().join(binary_name)
30}
31
32pub async fn is_available() -> bool {
34 let cached = rust_analyzer_path();
36 if cached.exists() {
37 return true;
38 }
39
40 Command::new("rust-analyzer")
42 .arg("--version")
43 .stdout(Stdio::null())
44 .stderr(Stdio::null())
45 .status()
46 .await
47 .map(|s| s.success())
48 .unwrap_or(false)
49}
50
51pub async fn get_command_path() -> Option<PathBuf> {
53 let cached = rust_analyzer_path();
54 if cached.exists() {
55 return Some(cached);
56 }
57
58 if Command::new("rust-analyzer")
60 .arg("--version")
61 .stdout(Stdio::null())
62 .stderr(Stdio::null())
63 .status()
64 .await
65 .map(|s| s.success())
66 .unwrap_or(false)
67 {
68 return Some(PathBuf::from("rust-analyzer"));
69 }
70
71 None
72}
73
74pub async fn ensure_available() -> Result<PathBuf, String> {
76 if let Some(path) = get_command_path().await {
78 tracing::info!("rust-analyzer available at: {}", path.display());
79 return Ok(path);
80 }
81
82 tracing::info!("rust-analyzer not found, downloading...");
83 download().await
84}
85
86pub async fn download() -> Result<PathBuf, String> {
88 let target = get_target_triple();
89 let url = format!(
90 "https://github.com/rust-lang/rust-analyzer/releases/download/{}/rust-analyzer-{}.gz",
91 RUST_ANALYZER_VERSION, target
92 );
93
94 tracing::info!("Downloading rust-analyzer from: {}", url);
95
96 let cache = cache_dir();
98 tokio::fs::create_dir_all(&cache)
99 .await
100 .map_err(|e| format!("Failed to create cache directory: {}", e))?;
101
102 let response = reqwest::get(&url)
104 .await
105 .map_err(|e| format!("Failed to download rust-analyzer: {}", e))?;
106
107 if !response.status().is_success() {
108 return Err(format!(
109 "Failed to download rust-analyzer: HTTP {}",
110 response.status()
111 ));
112 }
113
114 let bytes = response
115 .bytes()
116 .await
117 .map_err(|e| format!("Failed to read response: {}", e))?;
118
119 let mut decoder = flate2::read::GzDecoder::new(&bytes[..]);
121 let mut decompressed = Vec::new();
122 std::io::Read::read_to_end(&mut decoder, &mut decompressed)
123 .map_err(|e| format!("Failed to decompress: {}", e))?;
124
125 let binary_path = rust_analyzer_path();
127 let mut file = tokio::fs::File::create(&binary_path)
128 .await
129 .map_err(|e| format!("Failed to create file: {}", e))?;
130 file.write_all(&decompressed)
131 .await
132 .map_err(|e| format!("Failed to write file: {}", e))?;
133
134 #[cfg(unix)]
136 {
137 use std::os::unix::fs::PermissionsExt;
138 let mut perms = tokio::fs::metadata(&binary_path)
139 .await
140 .map_err(|e| format!("Failed to get metadata: {}", e))?
141 .permissions();
142 perms.set_mode(0o755);
143 tokio::fs::set_permissions(&binary_path, perms)
144 .await
145 .map_err(|e| format!("Failed to set permissions: {}", e))?;
146 }
147
148 tracing::info!("rust-analyzer downloaded to: {}", binary_path.display());
149 Ok(binary_path)
150}
151
152fn get_target_triple() -> &'static str {
154 #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
155 {
156 "x86_64-unknown-linux-gnu"
157 }
158 #[cfg(all(target_os = "linux", target_arch = "aarch64"))]
159 {
160 "aarch64-unknown-linux-gnu"
161 }
162 #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
163 {
164 "x86_64-apple-darwin"
165 }
166 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
167 {
168 "aarch64-apple-darwin"
169 }
170 #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
171 {
172 "x86_64-pc-windows-msvc"
173 }
174 #[cfg(not(any(
175 all(target_os = "linux", target_arch = "x86_64"),
176 all(target_os = "linux", target_arch = "aarch64"),
177 all(target_os = "macos", target_arch = "x86_64"),
178 all(target_os = "macos", target_arch = "aarch64"),
179 all(target_os = "windows", target_arch = "x86_64"),
180 )))]
181 {
182 compile_error!("Unsupported platform for rust-analyzer download")
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_cache_dir() {
192 let dir = cache_dir();
193 assert!(dir.ends_with("venus/bin") || dir.ends_with("venus\\bin"));
194 }
195
196 #[test]
197 fn test_target_triple() {
198 let triple = get_target_triple();
199 assert!(!triple.is_empty());
200 }
201}