venus_core/compile/
toolchain.rs

1//! Toolchain management for Venus compilation.
2//!
3//! Manages the nightly Rust toolchain with Cranelift codegen backend.
4
5use std::path::PathBuf;
6use std::process::Command;
7
8use crate::error::{Error, Result};
9
10/// Manages the Rust toolchain for Venus compilation.
11#[derive(Clone)]
12pub struct ToolchainManager {
13    /// Path to rustup (if available)
14    rustup_path: Option<PathBuf>,
15
16    /// Path to rustc
17    rustc_path: PathBuf,
18
19    /// Whether Cranelift is available
20    cranelift_available: bool,
21
22    /// Toolchain version string
23    version: String,
24}
25
26impl ToolchainManager {
27    /// Create a new toolchain manager, detecting available tools.
28    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    /// Check if Cranelift backend is available.
43    pub fn has_cranelift(&self) -> bool {
44        self.cranelift_available
45    }
46
47    /// Get the rustc path.
48    pub fn rustc_path(&self) -> &PathBuf {
49        &self.rustc_path
50    }
51
52    /// Get the toolchain version.
53    pub fn version(&self) -> &str {
54        &self.version
55    }
56
57    /// Get rustc flags for Cranelift compilation.
58    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    /// Get rustc flags for LLVM compilation.
67    pub fn llvm_flags(&self) -> Vec<String> {
68        // Default LLVM backend, no special flags needed
69        Vec::new()
70    }
71
72    /// Ensure the Cranelift component is installed.
73    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        // Re-check availability
104        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    /// Get the sysroot path for linking.
118    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    /// Get the target libdir for linking.
139    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    /// Find rustup in PATH.
160    fn find_rustup() -> Option<PathBuf> {
161        which::which("rustup").ok()
162    }
163
164    /// Find rustc in PATH.
165    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    /// Get rustc version string.
173    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    /// Check if Cranelift backend is available.
193    fn check_cranelift_available(rustc: &PathBuf) -> bool {
194        // Try to use Cranelift backend on a simple test
195        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}