venus_core/compile/
toolchain.rs1use std::path::PathBuf;
6use std::process::Command;
7
8use crate::error::{Error, Result};
9
10#[derive(Clone)]
12pub struct ToolchainManager {
13 rustup_path: Option<PathBuf>,
15
16 rustc_path: PathBuf,
18
19 cranelift_available: bool,
21
22 version: String,
24}
25
26impl ToolchainManager {
27 pub fn new() -> Result<Self> {
29 let rustup_path = Self::find_rustup();
30 let rustc_path = Self::find_rustc()?;
31 let version = Self::get_rustc_version(&rustc_path)?;
32 let cranelift_available = Self::check_cranelift_available(&rustc_path);
33
34 Ok(Self {
35 rustup_path,
36 rustc_path,
37 cranelift_available,
38 version,
39 })
40 }
41
42 pub fn has_cranelift(&self) -> bool {
44 self.cranelift_available
45 }
46
47 pub fn rustc_path(&self) -> &PathBuf {
49 &self.rustc_path
50 }
51
52 pub fn version(&self) -> &str {
54 &self.version
55 }
56
57 pub fn cranelift_flags(&self) -> Vec<String> {
59 if self.cranelift_available {
60 vec!["-Zcodegen-backend=cranelift".to_string()]
61 } else {
62 Vec::new()
63 }
64 }
65
66 pub fn llvm_flags(&self) -> Vec<String> {
68 Vec::new()
70 }
71
72 pub fn ensure_cranelift(&mut self) -> Result<()> {
74 if self.cranelift_available {
75 return Ok(());
76 }
77
78 let Some(rustup) = &self.rustup_path else {
79 return Err(Error::Compilation {
80 cell_id: None,
81 message: "rustup not found, cannot install Cranelift component".to_string(),
82 });
83 };
84
85 tracing::info!("Installing rustc-codegen-cranelift-preview component...");
86
87 let output = Command::new(rustup)
88 .args(["component", "add", "rustc-codegen-cranelift-preview"])
89 .output()
90 .map_err(|e| Error::Compilation {
91 cell_id: None,
92 message: format!("Failed to run rustup: {}", e),
93 })?;
94
95 if !output.status.success() {
96 let stderr = String::from_utf8_lossy(&output.stderr);
97 return Err(Error::Compilation {
98 cell_id: None,
99 message: format!("Failed to install Cranelift: {}", stderr),
100 });
101 }
102
103 self.cranelift_available = Self::check_cranelift_available(&self.rustc_path);
105
106 if !self.cranelift_available {
107 return Err(Error::Compilation {
108 cell_id: None,
109 message: "Cranelift component installed but not available".to_string(),
110 });
111 }
112
113 tracing::info!("Cranelift backend installed successfully");
114 Ok(())
115 }
116
117 pub fn sysroot(&self) -> Result<PathBuf> {
119 let output = Command::new(&self.rustc_path)
120 .args(["--print", "sysroot"])
121 .output()
122 .map_err(|e| Error::Compilation {
123 cell_id: None,
124 message: format!("Failed to get sysroot: {}", e),
125 })?;
126
127 if !output.status.success() {
128 return Err(Error::Compilation {
129 cell_id: None,
130 message: "Failed to get sysroot".to_string(),
131 });
132 }
133
134 let sysroot = String::from_utf8_lossy(&output.stdout).trim().to_string();
135 Ok(PathBuf::from(sysroot))
136 }
137
138 pub fn target_libdir(&self) -> Result<PathBuf> {
140 let output = Command::new(&self.rustc_path)
141 .args(["--print", "target-libdir"])
142 .output()
143 .map_err(|e| Error::Compilation {
144 cell_id: None,
145 message: format!("Failed to get target-libdir: {}", e),
146 })?;
147
148 if !output.status.success() {
149 return Err(Error::Compilation {
150 cell_id: None,
151 message: "Failed to get target-libdir".to_string(),
152 });
153 }
154
155 let libdir = String::from_utf8_lossy(&output.stdout).trim().to_string();
156 Ok(PathBuf::from(libdir))
157 }
158
159 fn find_rustup() -> Option<PathBuf> {
161 which::which("rustup").ok()
162 }
163
164 fn find_rustc() -> Result<PathBuf> {
166 which::which("rustc").map_err(|_| Error::Compilation {
167 cell_id: None,
168 message: "rustc not found in PATH".to_string(),
169 })
170 }
171
172 fn get_rustc_version(rustc: &PathBuf) -> Result<String> {
174 let output = Command::new(rustc)
175 .args(["--version"])
176 .output()
177 .map_err(|e| Error::Compilation {
178 cell_id: None,
179 message: format!("Failed to run rustc: {}", e),
180 })?;
181
182 if !output.status.success() {
183 return Err(Error::Compilation {
184 cell_id: None,
185 message: "Failed to get rustc version".to_string(),
186 });
187 }
188
189 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
190 }
191
192 fn check_cranelift_available(rustc: &PathBuf) -> bool {
194 let output = Command::new(rustc)
196 .args(["-Zcodegen-backend=cranelift", "--print", "crate-name", "-"])
197 .stdin(std::process::Stdio::null())
198 .stdout(std::process::Stdio::null())
199 .stderr(std::process::Stdio::null())
200 .status();
201
202 output.map(|s| s.success()).unwrap_or(false)
203 }
204}
205
206impl Default for ToolchainManager {
207 fn default() -> Self {
208 Self::new().expect("Failed to initialize toolchain manager")
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_toolchain_detection() {
218 let manager = ToolchainManager::new();
219 assert!(manager.is_ok(), "Should detect toolchain");
220
221 let manager = manager.unwrap();
222 assert!(!manager.version().is_empty());
223 }
224
225 #[test]
226 fn test_cranelift_flags() {
227 let manager = ToolchainManager::new().unwrap();
228
229 if manager.has_cranelift() {
230 let flags = manager.cranelift_flags();
231 assert!(flags.contains(&"-Zcodegen-backend=cranelift".to_string()));
232 }
233 }
234}