1pub mod assert {
2 use std::{fs, path::PathBuf, process::Command};
3
4 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 pub fn success(&mut self) -> assert_cmd::assert::Assert {
25 self.assert().success()
26 }
27
28 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 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 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 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}