1use crate::assert::Assert;
2use lazy_static::lazy_static;
3use regex::Regex;
4use std::{
5 borrow::Cow, collections::HashMap, env, error::Error, ffi::OsString, io::prelude::*,
6 path::PathBuf, process::Command,
7};
8
9#[doc(hidden)]
10pub enum Language {
11 C,
12 Cxx,
13}
14
15impl ToString for Language {
16 fn to_string(&self) -> String {
17 match self {
18 Self::C => String::from("c"),
19 Self::Cxx => String::from("cpp"),
20 }
21 }
22}
23
24#[doc(hidden)]
25pub fn run(language: Language, program: &str) -> Result<Assert, Box<dyn Error>> {
26 let (program, variables) = collect_environment_variables(program);
27
28 let mut program_file = tempfile::Builder::new()
29 .prefix("inline-c-rs-")
30 .suffix(&format!(".{}", language.to_string()))
31 .tempfile()?;
32 program_file.write_all(program.as_bytes())?;
33
34 let host = target_lexicon::HOST.to_string();
35 let target = &host;
36
37 let msvc = target.contains("msvc");
38
39 let (_, input_path) = program_file.keep()?;
40 let mut output_temp = tempfile::Builder::new();
41 let output_temp = output_temp.prefix("inline-c-rs-");
42
43 if msvc {
44 output_temp.suffix(".exe");
45 }
46
47 let (_, output_path) = output_temp.tempfile()?.keep()?;
48
49 let mut build = cc::Build::new();
50 let mut build = build
51 .cargo_metadata(false)
52 .warnings(true)
53 .extra_warnings(true)
54 .warnings_into_errors(true)
55 .debug(false)
56 .host(&host)
57 .target(target)
58 .opt_level(1);
59
60 if let Language::Cxx = language {
61 build = build.cpp(true);
62 }
63
64 let compiler = build.try_get_compiler()?;
71 let mut command;
72
73 if msvc {
74 command = compiler.to_command();
75
76 command_add_compiler_flags(&mut command, &variables, msvc);
77 command_add_output_file(&mut command, &output_path, msvc, compiler.is_like_clang());
78 command.arg(input_path.clone());
79 command_add_linker_flags_msvc(&mut command, &variables);
80 } else {
81 command = Command::new(compiler.path());
82
83 command.arg(input_path.clone()); command.args(compiler.args());
85 command_add_compiler_flags(&mut command, &variables, msvc);
86 command_add_output_file(&mut command, &output_path, msvc, compiler.is_like_clang());
87 }
88
89 command.envs(variables.clone());
90
91 let mut files_to_remove = vec![input_path, output_path.clone()];
92 if msvc {
93 let mut intermediate_path = output_path.clone();
94 intermediate_path.set_extension("obj");
95 files_to_remove.push(intermediate_path);
96 }
97
98 let clang_output = command.output()?;
99
100 if !clang_output.status.success() {
101 return Ok(Assert::new(command, Some(files_to_remove)));
102 }
103
104 let mut command = Command::new(output_path);
105 command.envs(variables);
106
107 Ok(Assert::new(command, Some(files_to_remove)))
108}
109
110fn collect_environment_variables<'p>(program: &'p str) -> (Cow<'p, str>, HashMap<String, String>) {
111 const ENV_VAR_PREFIX: &str = "INLINE_C_RS_";
112
113 lazy_static! {
114 static ref REGEX: Regex = Regex::new(
115 r#"#inline_c_rs (?P<variable_name>[^:]+):\s*"(?P<variable_value>[^"]+)"\r?\n"#
116 )
117 .unwrap();
118 }
119
120 let mut variables = HashMap::new();
121
122 for (variable_name, variable_value) in env::vars().filter_map(|(mut name, value)| {
123 if name.starts_with(ENV_VAR_PREFIX) {
124 Some((name.split_off(ENV_VAR_PREFIX.len()), value))
125 } else {
126 None
127 }
128 }) {
129 variables.insert(variable_name, variable_value);
130 }
131
132 for captures in REGEX.captures_iter(program) {
133 variables.insert(
134 captures["variable_name"].trim().to_string(),
135 captures["variable_value"].to_string(),
136 );
137 }
138
139 let program = REGEX.replace_all(program, "");
140
141 (program, variables)
142}
143
144fn command_add_output_file(command: &mut Command, output_path: &PathBuf, msvc: bool, clang: bool) {
146 if msvc && !clang {
147 let mut intermediate_path = output_path.clone();
148 intermediate_path.set_extension("obj");
149
150 let mut fo_arg = OsString::from("-Fo");
151 fo_arg.push(intermediate_path);
152 command.arg(fo_arg);
153
154 let mut fe_arg = OsString::from("-Fe");
155 fe_arg.push(output_path);
156 command.arg(fe_arg);
157 } else {
158 command.arg("-o").arg(output_path);
159 }
160}
161
162fn get_env_flags(variables: &HashMap<String, String>, env_name: &str) -> Vec<String> {
163 variables
164 .get(env_name)
165 .map(|e| e.to_string())
166 .ok_or_else(|| env::var(env_name))
167 .unwrap_or_default()
168 .split_ascii_whitespace()
169 .map(|slice| slice.to_string())
170 .collect()
171}
172
173fn command_add_compiler_flags(
174 command: &mut Command,
175 variables: &HashMap<String, String>,
176 msvc: bool,
177) {
178 command.args(get_env_flags(variables, "CFLAGS"));
179 command.args(get_env_flags(variables, "CPPFLAGS"));
180 command.args(get_env_flags(variables, "CXXFLAGS"));
181
182 if !msvc {
183 for linker_argument in get_env_flags(variables, "LDFLAGS") {
184 command.arg(format!("-Wl,{}", linker_argument));
185 }
186 }
187}
188
189fn command_add_linker_flags_msvc(command: &mut Command, variables: &HashMap<String, String>) {
190 let linker_flags = get_env_flags(variables, "LDFLAGS");
191 if !linker_flags.is_empty() {
192 command.arg("/link");
193 for linker_argument in linker_flags {
194 command.arg(linker_argument);
195 }
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::predicates::*;
203
204 #[test]
205 fn test_run_c() {
206 run(
207 Language::C,
208 r#"
209 #include <stdio.h>
210
211 int main() {
212 printf("Hello, World!\n");
213
214 return 0;
215 }
216 "#,
217 )
218 .unwrap()
219 .success()
220 .stdout(predicate::eq("Hello, World!\n").normalize());
221 }
222
223 #[cfg(not(target_os = "macos"))] #[test]
225 fn test_run_c_ldflags() {
226 use std::env::{remove_var, set_var};
227
228 let host = target_lexicon::HOST.to_string();
229 let msvc = host.contains("msvc");
230 if msvc {
231 set_var("INLINE_C_RS_LDFLAGS", "/STACK:0x40000");
236 run(
237 Language::C,
238 r#"
239 #include <Windows.h>
240
241 #include <stdio.h>
242
243 int main() {
244 ULONG_PTR stack_low_limit, stack_high_limit;
245 GetCurrentThreadStackLimits(&stack_low_limit, &stack_high_limit);
246 printf("%#llx", stack_high_limit-stack_low_limit);
247
248 return 0;
249 }
250 "#,
251 )
252 .unwrap()
253 .success()
254 .stdout(predicate::eq("0x40000").normalize());
255
256 remove_var("INLINE_C_RS_LDFLAGS");
257 } else {
258 set_var("INLINE_C_RS_LDFLAGS", "--defsym MY_SYMBOL=0x0");
263 run(
264 Language::C,
265 r#"
266 #include <stdio.h>
267
268 extern void* MY_SYMBOL;
269
270 int main() {
271 printf("%p", MY_SYMBOL);
272 return 0;
273 }
274 "#,
275 )
276 .unwrap()
277 .success();
278
279 remove_var("INLINE_C_RS_LDFLAGS");
280 }
281 }
282
283 #[test]
284 fn test_run_cxx() {
285 run(
286 Language::Cxx,
287 r#"
288 #include <stdio.h>
289
290 int main() {
291 printf("Hello, World!\n");
292
293 return 0;
294 }
295 "#,
296 )
297 .unwrap()
298 .success()
299 .stdout(predicate::eq("Hello, World!\n").normalize());
300 }
301}