1use std::path::{Path, PathBuf};
4
5use crate::bindings::{generate_bindings, BindingOptions, GeneratedBinding};
6use crate::compiler::compile_to_c;
7use crate::detector::{detect_language, find_compiler, Language};
8use crate::exports::{discover_exports_with_options, ExportOptions, ExportSource};
9use crate::imports::{generate_imports, GeneratedImport, ImportOptions};
10
11#[derive(Clone, Debug)]
13pub struct LoadOptions {
14 pub generate_bindings: bool,
16 pub compile: bool,
18 pub output_dir: Option<PathBuf>,
20 pub binding_options: Option<BindingOptions>,
22 pub link: bool,
24 pub link_args: Vec<String>,
26 pub compile_args: Vec<String>,
28 pub exports: Vec<String>,
29 pub config_path: Option<PathBuf>,
30 pub consumer_languages: Vec<Language>,
31}
32
33impl Default for LoadOptions {
34 fn default() -> Self {
35 Self {
36 generate_bindings: true,
37 compile: true,
38 output_dir: None,
39 binding_options: None,
40 link: true,
41 link_args: Vec::new(),
42 compile_args: Vec::new(),
43 exports: Vec::new(),
44 config_path: None,
45 consumer_languages: Vec::new(),
46 }
47 }
48}
49
50impl LoadOptions {
51 pub fn exports<I, S>(mut self, exports: I) -> Self
52 where
53 I: IntoIterator<Item = S>,
54 S: Into<String>,
55 {
56 self.exports = exports.into_iter().map(Into::into).collect();
57 self
58 }
59
60 pub fn output_dir<P: AsRef<Path>>(mut self, output_dir: P) -> Self {
61 self.output_dir = Some(output_dir.as_ref().to_path_buf());
62 self
63 }
64
65 pub fn generate_bindings(mut self, generate_bindings: bool) -> Self {
66 self.generate_bindings = generate_bindings;
67 self
68 }
69
70 pub fn config_path<P: AsRef<Path>>(mut self, path: P) -> Self {
71 self.config_path = Some(path.as_ref().to_path_buf());
72 self
73 }
74
75 pub fn consumer_languages<I>(mut self, languages: I) -> Self
76 where
77 I: IntoIterator<Item = Language>,
78 {
79 self.consumer_languages = languages.into_iter().collect();
80 self
81 }
82}
83
84#[derive(Clone, Debug)]
86pub struct LoadedModule {
87 pub output_path: PathBuf,
89 pub header_path: Option<PathBuf>,
91 pub bindings: Option<GeneratedBinding>,
93 pub language: Language,
95 pub source_path: PathBuf,
97 pub exports: Vec<String>,
98 pub export_source: ExportSource,
99 pub warnings: Vec<String>,
100 pub imports: Vec<GeneratedImport>,
101}
102
103impl LoadedModule {
104 pub fn has_bindings(&self) -> bool {
106 self.bindings.is_some()
107 }
108
109 pub fn bindings_code(&self) -> Option<&str> {
111 self.bindings.as_ref().map(|b| b.code.as_str())
112 }
113}
114
115pub fn load<S: AsRef<Path>>(source: S) -> Result<LoadedModule, LoadError> {
135 load_with_options(source, LoadOptions::default())
136}
137
138pub fn load_with_options<S: AsRef<Path>>(
140 source: S,
141 options: LoadOptions,
142) -> Result<LoadedModule, LoadError> {
143 let source = source.as_ref();
144 let source = source
145 .canonicalize()
146 .unwrap_or_else(|_| source.to_path_buf());
147
148 let Some(lang) = detect_language(&source) else {
149 return Err(LoadError::UnknownLanguage(source.clone()));
150 };
151
152 let export_options = ExportOptions {
153 exports: options.exports.clone(),
154 config_path: options.config_path.clone(),
155 };
156 let export_discovery = discover_exports_with_options(&source, lang, &export_options)
157 .map_err(|e| LoadError::ExportFailed(e.to_string()))?;
158
159 let output_dir = options.output_dir.clone().unwrap_or_else(|| {
160 std::env::var("CARGO_MANIFEST_DIR")
162 .map(PathBuf::from)
163 .unwrap_or_else(|_| PathBuf::from("target"))
164 .join("native")
165 .join(format!("{:?}", lang).to_lowercase())
166 });
167
168 std::fs::create_dir_all(&output_dir).map_err(|e| LoadError::Io {
169 path: output_dir.clone(),
170 error: e,
171 })?;
172
173 let _compiler = find_compiler(lang).ok_or(LoadError::CompilerNotFound(lang))?;
174
175 let result = compile_to_c(&source, &output_dir)
176 .map_err(|e| LoadError::CompilationFailed(lang, e.to_string()))?;
177
178 let bindings = if options.generate_bindings {
179 if let Some(ref header_path) = result.header_path {
180 generate_bindings(
181 header_path,
182 options
183 .binding_options
184 .as_ref()
185 .unwrap_or(&BindingOptions::default()),
186 )
187 .ok()
188 } else {
189 None
190 }
191 } else {
192 None
193 };
194
195 let import_source = result.header_path.clone().unwrap_or_else(|| source.clone());
196 let import_options =
197 ImportOptions::default().allowlist_functions(export_discovery.exports.clone());
198 let mut imports = Vec::new();
199 let mut warnings = export_discovery.warnings;
200 for language in &options.consumer_languages {
201 let generated = generate_imports(&import_source, *language, &import_options)
202 .map_err(LoadError::ImportFailed)?;
203 warnings.extend(generated.warnings.clone());
204 imports.push(generated);
205 }
206
207 Ok(LoadedModule {
208 output_path: result.output_path,
209 header_path: result.header_path,
210 bindings,
211 language: lang,
212 source_path: source,
213 exports: export_discovery.exports,
214 export_source: export_discovery.source,
215 warnings,
216 imports,
217 })
218}
219
220#[derive(Debug)]
222pub enum LoadError {
223 UnknownLanguage(PathBuf),
224 CompilerNotFound(Language),
225 CompilationFailed(Language, String),
226 Io {
227 path: PathBuf,
228 error: std::io::Error,
229 },
230 BindingFailed(String),
231 ExportFailed(String),
232 ImportFailed(String),
233}
234
235impl std::fmt::Display for LoadError {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 match self {
238 LoadError::UnknownLanguage(path) => {
239 write!(f, "Unknown language for file: {}", path.display())
240 }
241 LoadError::CompilerNotFound(lang) => {
242 write!(f, "Compiler for {:?} not found", lang)
243 }
244 LoadError::CompilationFailed(lang, msg) => {
245 write!(f, "Compilation of {:?} failed: {}", lang, msg)
246 }
247 LoadError::Io { path, error } => {
248 write!(f, "IO error for {}: {}", path.display(), error)
249 }
250 LoadError::BindingFailed(msg) => {
251 write!(f, "Binding generation failed: {}", msg)
252 }
253 LoadError::ExportFailed(msg) => {
254 write!(f, "Export discovery failed: {}", msg)
255 }
256 LoadError::ImportFailed(msg) => {
257 write!(f, "Import generation failed: {}", msg)
258 }
259 }
260 }
261}
262
263impl std::error::Error for LoadError {}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use tempfile::Builder;
269
270 #[test]
271 fn test_load_c() {
272 let tmp = Builder::new().tempfile_in(std::env::temp_dir()).unwrap();
273 let path = tmp.path();
274 std::fs::write(path, "int add(int a, int b) { return a + b; }").unwrap();
275
276 let result = load(path);
277 println!("{:?}", result);
279 }
280
281 #[test]
282 fn test_load_options() {
283 let opts = LoadOptions::default();
284 assert!(opts.generate_bindings);
285 assert!(opts.compile);
286 assert!(opts.link);
287 }
288}