wasm_sandbox/compiler/
mod.rs

1//! Wrapper compilation toolchain
2
3use std::path::{Path, PathBuf};
4use std::process::Command;
5
6use crate::error::{Error, Result};
7
8/// Compiler options
9#[derive(Debug, Clone)]
10pub struct CompilerOptions {
11    /// Target triple (e.g. wasm32-wasi)
12    pub target: String,
13    
14    /// Optimization level
15    pub opt_level: OptimizationLevel,
16    
17    /// Debug info level
18    pub debug_level: DebugLevel,
19    
20    /// Features to enable
21    pub features: Vec<String>,
22    
23    /// No default features flag
24    pub no_default_features: bool,
25    
26    /// Additional compiler arguments
27    pub extra_args: Vec<String>,
28    
29    /// Toolchain version (e.g. "stable", "nightly")
30    pub toolchain: String,
31    
32    /// Build profile
33    pub profile: BuildProfile,
34    
35    /// Target CPU specification (optional)
36    pub target_cpu: Option<String>,
37    
38    /// Additional RUSTFLAGS to pass to the compiler (optional)
39    pub rustflags: Option<String>,
40}
41
42impl Default for CompilerOptions {
43    fn default() -> Self {
44        Self {
45            target: "wasm32-wasi".to_string(),
46            opt_level: OptimizationLevel::Default,
47            debug_level: DebugLevel::None,
48            features: Vec::new(),
49            no_default_features: false,
50            extra_args: Vec::new(),
51            toolchain: "stable".to_string(),
52            profile: BuildProfile::Release,
53            target_cpu: None,
54            rustflags: None,
55        }
56    }
57}
58
59/// Optimization level
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum OptimizationLevel {
62    /// No optimizations
63    None,
64    
65    /// Basic optimizations
66    Basic,
67    
68    /// Default optimizations
69    Default,
70    
71    /// Size optimizations
72    Size,
73    
74    /// Speed optimizations
75    Speed,
76}
77
78/// Debug information level
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum DebugLevel {
81    /// No debug information
82    None,
83    
84    /// Basic debug information
85    Basic,
86    
87    /// Full debug information
88    Full,
89}
90
91/// Build profile
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum BuildProfile {
94    /// Debug profile
95    Debug,
96    
97    /// Release profile
98    Release,
99}
100
101/// Compiler abstraction
102pub trait Compiler {
103    /// Compile a Rust project to WASM
104    fn compile(
105        &self,
106        project_path: &Path,
107        output_path: &Path,
108        options: &CompilerOptions,
109    ) -> Result<PathBuf>;
110    
111    /// Check if the compiler is available
112    fn check_available(&self) -> bool;
113    
114    /// Get compiler version
115    fn version(&self) -> Result<String>;
116}
117
118/// Cargo compiler implementation
119pub struct CargoCompiler;
120
121impl CargoCompiler {
122    /// Create a new cargo compiler
123    pub fn new() -> Self {
124        Self
125    }
126}
127
128impl Compiler for CargoCompiler {
129    fn compile(
130        &self,
131        project_path: &Path,
132        output_path: &Path,
133        options: &CompilerOptions,
134    ) -> Result<PathBuf> {
135        // Check if cargo is available
136        if !self.check_available() {
137            return Err(Error::Compilation { message: "Cargo is not available".to_string() });
138        }
139        
140        // Create output directory if it doesn't exist
141        std::fs::create_dir_all(output_path)
142            .map_err(|e| Error::Filesystem { 
143                operation: "create_dir_all".to_string(), 
144                path: output_path.to_path_buf(), 
145                reason: format!("Failed to create output directory: {}", e) 
146            })?;
147        
148        // Build the cargo command
149        let mut cmd = Command::new("cargo");
150        
151        // Add the toolchain if not stable
152        if options.toolchain != "stable" {
153            cmd.arg(format!("+{}", options.toolchain));
154        }
155        
156        // Basic build command
157        cmd.current_dir(project_path)
158            .arg("build")
159            .arg("--target").arg(&options.target);
160        
161        // Add profile
162        match options.profile {
163            BuildProfile::Debug => {
164                // Debug is the default, no need to add flags
165            },
166            BuildProfile::Release => {
167                cmd.arg("--release");
168            },
169        }
170        
171        // Add optimization level
172        match options.opt_level {
173            OptimizationLevel::None => {
174                cmd.env("RUSTFLAGS", "-C opt-level=0");
175            },
176            OptimizationLevel::Basic => {
177                cmd.env("RUSTFLAGS", "-C opt-level=1");
178            },
179            OptimizationLevel::Default => {
180                // Default, no need to set
181            },
182            OptimizationLevel::Size => {
183                cmd.env("RUSTFLAGS", "-C opt-level=s");
184            },
185            OptimizationLevel::Speed => {
186                cmd.env("RUSTFLAGS", "-C opt-level=3");
187            },
188        }
189        
190        // Add debug level
191        match options.debug_level {
192            DebugLevel::None => {
193                // No debug info is the default for release builds
194                if options.profile == BuildProfile::Debug {
195                    cmd.env("RUSTFLAGS", "-C debuginfo=0");
196                }
197            },
198            DebugLevel::Basic => {
199                cmd.env("RUSTFLAGS", "-C debuginfo=1");
200            },
201            DebugLevel::Full => {
202                cmd.env("RUSTFLAGS", "-C debuginfo=2");
203            },
204        }
205        
206        // Add features
207        if !options.features.is_empty() {
208            cmd.arg("--features").arg(options.features.join(","));
209        }
210        
211        // No default features
212        if options.no_default_features {
213            cmd.arg("--no-default-features");
214        }
215        
216        // Add extra args
217        for arg in &options.extra_args {
218            cmd.arg(arg);
219        }
220        
221        // Run the build
222        let output = cmd.output()
223            .map_err(|e| Error::Compilation { message: format!("Failed to execute cargo: {}", e) })?;
224        
225        // Check for errors
226        if !output.status.success() {
227            let stderr = String::from_utf8_lossy(&output.stderr);
228            return Err(Error::Compilation { message: format!("Build failed: {}", stderr) });
229        }
230        
231        // Determine the output file path
232        let profile_dir = match options.profile {
233            BuildProfile::Debug => "debug",
234            BuildProfile::Release => "release",
235        };
236        
237        // Get the project name
238        let cargo_toml_path = project_path.join("Cargo.toml");
239        let cargo_toml = std::fs::read_to_string(&cargo_toml_path)
240            .map_err(|e| Error::Filesystem { 
241                operation: "read".to_string(), 
242                path: cargo_toml_path.clone(), 
243                reason: format!("Failed to read Cargo.toml: {}", e) 
244            })?;
245        
246        // Simple parser to extract the package name
247        let package_name = cargo_toml
248            .lines()
249            .find_map(|line| {
250                if line.trim().starts_with("name") {
251                    line.split('=')
252                        .nth(1)
253                        .map(|s| s.trim().trim_matches('"').to_string())
254                } else {
255                    None
256                }
257            })
258            .ok_or_else(|| Error::Compilation { message: "Failed to determine package name".to_string() })?;
259        
260        // Construct the output file path
261        let wasm_file = format!("{}.wasm", package_name);
262        let wasm_path = project_path
263            .join("target")
264            .join(&options.target)
265            .join(profile_dir)
266            .join(wasm_file);
267        
268        // Copy to the output path
269        let output_wasm_path = output_path.join(format!("{}.wasm", package_name));
270        std::fs::copy(&wasm_path, &output_wasm_path)
271            .map_err(|e| Error::Filesystem { 
272                operation: "copy".to_string(), 
273                path: wasm_path.clone(), 
274                reason: format!("Failed to copy WASM file: {}", e) 
275            })?;
276        
277        Ok(output_wasm_path)
278    }
279    
280    fn check_available(&self) -> bool {
281        Command::new("cargo")
282            .arg("--version")
283            .output()
284            .is_ok()
285    }
286    
287    fn version(&self) -> Result<String> {
288        let output = Command::new("cargo")
289            .arg("--version")
290            .output()
291            .map_err(|e| Error::Compilation { message: format!("Failed to get cargo version: {}", e) })?;
292        
293        if output.status.success() {
294            let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
295            Ok(version)
296        } else {
297            let stderr = String::from_utf8_lossy(&output.stderr);
298            Err(Error::Compilation { message: format!("Failed to get cargo version: {}", stderr) })
299        }
300    }
301}
302
303pub mod cargo;
304pub mod wasi;