wasmer_inline_c/
lib.rs

1pub mod assert {
2    use std::{fs, path::PathBuf, process::Command};
3
4    /// `Assert` is a wrapper around the [`assert_cmd::assert::Assert`]
5    /// struct.
6    pub struct Assert {
7        command: assert_cmd::Command,
8        files_to_remove: Option<Vec<PathBuf>>,
9    }
10
11    impl Assert {
12        pub(crate) fn new(command: Command, files_to_remove: Option<Vec<PathBuf>>) -> Self {
13            Self {
14                command: assert_cmd::Command::from_std(command),
15                files_to_remove,
16            }
17        }
18
19        pub fn assert(&mut self) -> assert_cmd::assert::Assert {
20            self.command.assert()
21        }
22
23        /// Shortcut to `self.assert().success()`.
24        pub fn success(&mut self) -> assert_cmd::assert::Assert {
25            self.assert().success()
26        }
27
28        /// Shortcut to `self.assert().failure()`.
29        pub fn failure(&mut self) -> assert_cmd::assert::Assert {
30            self.assert().failure()
31        }
32    }
33
34    impl Drop for Assert {
35        fn drop(&mut self) {
36            if let Some(files_to_remove) = &self.files_to_remove {
37                for file in files_to_remove.iter() {
38                    if fs::metadata(file).is_ok() {
39                        fs::remove_file(file)
40                            .unwrap_or_else(|_| panic!("Failed to remove `{:?}`", file));
41                    }
42                }
43            }
44        }
45    }
46}
47
48pub mod run {
49
50    use crate::Assert;
51    use lazy_static::lazy_static;
52    use regex::Regex;
53    use std::{
54        borrow::Cow, collections::HashMap, env, error::Error, ffi::OsString, io::prelude::*,
55        path::PathBuf, process::Command,
56    };
57
58    static INCLUDE_REGEX: &str = "#include \"(.*)\"";
59
60    #[doc(hidden)]
61    pub enum Language {
62        C,
63        Cxx,
64    }
65
66    impl ToString for Language {
67        fn to_string(&self) -> String {
68            match self {
69                Self::C => String::from("c"),
70                Self::Cxx => String::from("cpp"),
71            }
72        }
73    }
74
75    #[doc(hidden)]
76    pub fn run(language: Language, program: &str) -> Result<Assert, Box<dyn Error>> {
77        let (program, variables) = collect_environment_variables(program);
78
79        let mut program_file = tempfile::Builder::new()
80            .prefix("inline-c-rs-")
81            .suffix(&format!(".{}", language.to_string()))
82            .tempfile()?;
83
84        program_file.write_all(program.as_bytes())?;
85
86        let host = target_lexicon::HOST.to_string();
87        let target = &host;
88
89        let msvc = target.contains("msvc");
90        if !msvc {
91            panic!("This crate only works with MSVC and Wasmer on Windows!");
92        }
93
94        let (_, input_path) = program_file.keep()?;
95        let mut output_temp = tempfile::Builder::new();
96        let output_temp = output_temp.prefix("inline-c-rs-");
97        output_temp.suffix(".exe");
98
99        let (_, output_path) = output_temp.tempfile()?.keep()?;
100
101        let mut build = cc::Build::new();
102        let mut build = build
103            .cargo_metadata(false)
104            .warnings(true)
105            .extra_warnings(true)
106            .warnings_into_errors(true)
107            .debug(false)
108            .host(&host)
109            .target(target)
110            .opt_level(1);
111
112        if let Language::Cxx = language {
113            build = build.cpp(true);
114        }
115
116        let compiler = build.try_get_compiler()?;
117        let mut command = compiler.to_command();
118
119        let cflags = get_env_flags(&variables, "CFLAGS");
120
121        // MSVC cannot follow symlinks for some reason
122        let mut log = String::new();
123        let include_paths = cflags
124            .iter()
125            .filter(|s| s.starts_with("-I"))
126            .cloned()
127            .collect::<Vec<_>>();
128        fixup_symlinks(include_paths.as_ref(), &mut log)?;
129
130        let regex = regex::Regex::new(INCLUDE_REGEX).unwrap();
131        let filepaths = regex
132            .captures_iter(&program)
133            .map(|c| c[1].to_string())
134            .collect::<Vec<_>>();
135        log.push_str(&format!("regex captures (program): {:#?}\n", filepaths));
136        let joined_filepaths = filepaths
137            .iter()
138            .filter_map(|s| {
139                let path =
140                    std::path::Path::new(&include_paths.first().unwrap().replacen("-I", "", 1))
141                        .join(s);
142                Some(format!("{}", path.display()))
143            })
144            .collect::<Vec<_>>();
145        fixup_symlinks_inner(&joined_filepaths, &mut log)?;
146
147        let cppflags = get_env_flags(&variables, "CPPFLAGS");
148        let cxxflags = get_env_flags(&variables, "CXXFLAGS");
149        let ldflags = get_env_flags(&variables, "LDFLAGS");
150
151        command.args(cflags);
152
153        let link_path = ldflags
154            .get(0)
155            .expect("no link path for .dll")
156            .replace("-rpath,", "");
157        let mut dll_path = ldflags.get(1).expect("no .dll").clone();
158        if dll_path.ends_with(".dll") {
159            dll_path = format!("{}.lib", dll_path);
160        }
161        command_add_output_file(&mut command, &output_path, msvc, compiler.is_like_clang());
162        command.arg(input_path.clone());
163        command.arg("/link");
164        command.arg(dll_path);
165        command.arg(format!("/LIBPATH:{}", link_path));
166
167        command.envs(variables.clone());
168
169        let mut files_to_remove = vec![input_path, output_path.clone()];
170
171        let mut intermediate_path = output_path.clone();
172        intermediate_path.set_extension("obj");
173
174        files_to_remove.push(intermediate_path);
175
176        Ok(Assert::new(command, Some(files_to_remove)))
177    }
178
179    fn collect_environment_variables<'p>(
180        program: &'p str,
181    ) -> (Cow<'p, str>, HashMap<String, String>) {
182        const ENV_VAR_PREFIX: &str = "INLINE_C_RS_";
183
184        lazy_static! {
185            static ref REGEX: Regex = Regex::new(
186                r#"#inline_c_rs (?P<variable_name>[^:]+):\s*"(?P<variable_value>[^"]+)"\r?\n"#
187            )
188            .unwrap();
189        }
190
191        let mut variables = HashMap::new();
192
193        for (variable_name, variable_value) in env::vars().filter_map(|(mut name, value)| {
194            if name.starts_with(ENV_VAR_PREFIX) {
195                Some((name.split_off(ENV_VAR_PREFIX.len()), value))
196            } else {
197                None
198            }
199        }) {
200            variables.insert(variable_name, variable_value);
201        }
202
203        for captures in REGEX.captures_iter(program) {
204            variables.insert(
205                captures["variable_name"].trim().to_string(),
206                captures["variable_value"].to_string(),
207            );
208        }
209
210        let program = REGEX.replace_all(program, "");
211
212        (program, variables)
213    }
214
215    // This is copy-pasted and edited from `cc-rs`.
216    fn command_add_output_file(
217        command: &mut Command,
218        output_path: &PathBuf,
219        msvc: bool,
220        clang: bool,
221    ) {
222        if msvc && !clang {
223            let mut intermediate_path = output_path.clone();
224            intermediate_path.set_extension("obj");
225
226            let mut fo_arg = OsString::from("-Fo");
227            fo_arg.push(intermediate_path);
228            command.arg(fo_arg);
229
230            let mut fe_arg = OsString::from("-Fe");
231            fe_arg.push(output_path);
232            command.arg(fe_arg);
233        } else {
234            command.arg("-o").arg(output_path);
235        }
236    }
237
238    fn get_env_flags(variables: &HashMap<String, String>, env_name: &str) -> Vec<String> {
239        variables
240            .get(env_name)
241            .map(|e| e.to_string())
242            .ok_or_else(|| env::var(env_name))
243            .unwrap_or_default()
244            .split_ascii_whitespace()
245            .map(|slice| slice.to_string())
246            .collect()
247    }
248
249    fn fixup_symlinks(include_paths: &[String], log: &mut String) -> Result<(), Box<dyn Error>> {
250        log.push_str(&format!("include paths: {include_paths:?}"));
251        for i in include_paths {
252            let i = i.replacen("-I", "", 1);
253            let mut paths_headers = Vec::new();
254            for entry in std::fs::read_dir(&i)? {
255                let entry = entry?;
256                let path = entry.path();
257                let path_display = format!("{}", path.display());
258                if path_display.ends_with("h") {
259                    paths_headers.push(path_display);
260                }
261            }
262            fixup_symlinks_inner(&paths_headers, log)?;
263        }
264
265        Ok(())
266    }
267
268    fn fixup_symlinks_inner(
269        include_paths: &[String],
270        log: &mut String,
271    ) -> Result<(), Box<dyn Error>> {
272        log.push_str(&format!("fixup symlinks: {include_paths:#?}"));
273        let regex = regex::Regex::new(INCLUDE_REGEX).unwrap();
274        for path in include_paths.iter() {
275            let file = std::fs::read_to_string(&path)?;
276            let lines_3 = file.lines().take(3).collect::<Vec<_>>();
277            log.push_str(&format!("first 3 lines of {path:?}: {:#?}\n", lines_3));
278
279            let parent = std::path::Path::new(&path).parent().unwrap();
280            if let Ok(symlink) = std::fs::read_to_string(parent.clone().join(&file)) {
281                log.push_str(&format!("symlinking {path:?}\n"));
282                std::fs::write(&path, symlink)?;
283            }
284
285            // follow #include directives and recurse
286            let filepaths = regex
287                .captures_iter(&file)
288                .map(|c| c[1].to_string())
289                .collect::<Vec<_>>();
290            log.push_str(&format!("regex captures: ({path:?}): {:#?}\n", filepaths));
291            let joined_filepaths = filepaths
292                .iter()
293                .filter_map(|s| {
294                    let path = parent.clone().join(s);
295                    Some(format!("{}", path.display()))
296                })
297                .collect::<Vec<_>>();
298            fixup_symlinks_inner(&joined_filepaths, log)?;
299        }
300        Ok(())
301    }
302}
303
304pub use crate::run::{run, Language};
305pub use assert::Assert;
306pub use wasmer_inline_c_macro::{assert_c, assert_cxx};
307pub mod predicates {
308    pub use predicates::prelude::*;
309}