1use crate::toolchain;
10use std::{
11 env,
12 ffi::OsString,
13 path::{Path, PathBuf},
14 process::Command,
15};
16
17pub(crate) const TARGET_MARKER: &str = "CARGO_CFG_CARGO_LIBC_PROFILE";
19pub(crate) const HOST_CC_ENV: &str = "CARGO_LIBC_HOST_CC";
20pub(crate) const HOST_CXX_ENV: &str = "CARGO_LIBC_HOST_CXX";
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum Language {
25 C,
27 Cxx,
29}
30
31fn compiler_variables(base: &str) -> [String; 4] {
34 let triple = toolchain::TARGET;
35 [
36 format!("{base}_{triple}"),
37 format!("{base}_{}", triple.replace('-', "_")),
38 format!("HOST_{base}"),
39 base.to_string(),
40 ]
41}
42
43pub(crate) fn variables(
48 cc: &Path,
49 cxx: &Path,
50 shell_names: bool,
51) -> Result<Vec<(String, OsString)>, String> {
52 let mut variables = Vec::new();
53 for (base, wrapper, host_env, default) in [
54 ("CC", cc, HOST_CC_ENV, "cc"),
55 ("CXX", cxx, HOST_CXX_ENV, "c++"),
56 ] {
57 let [hyphen, underscore, host, plain] = compiler_variables(base);
58 for key in [&hyphen, &underscore] {
59 if env::var_os(key).is_some_and(|value| !value.is_empty()) {
60 return Err(format!("{key} is already set; cargo-libc selects the target C compiler itself, so use a shell without that override"));
61 }
62 }
63 let host_compiler = [&host, &plain]
64 .into_iter()
65 .find_map(|key| env::var_os(key).filter(|value| !value.is_empty()))
66 .unwrap_or_else(|| default.into());
67 variables.push((host_env.to_string(), host_compiler));
68 let key = if shell_names { underscore } else { hyphen };
69 variables.push((key, wrapper.as_os_str().to_owned()));
70 }
71 Ok(variables)
72}
73
74pub(crate) fn set_environment(cmd: &mut Command, cc: &Path, cxx: &Path) -> Result<(), String> {
75 cmd.envs(variables(cc, cxx, false)?);
76 Ok(())
77}
78
79#[derive(Debug, PartialEq, Eq)]
81pub(crate) enum Action {
82 Query,
84 Compile,
86 Link {
87 shared: bool,
88 },
89 Relocatable,
91}
92
93const SEPARATE_VALUE: &[&str] = &[
95 "-o",
96 "-I",
97 "-L",
98 "-D",
99 "-U",
100 "-include",
101 "-imacros",
102 "-isystem",
103 "-iquote",
104 "-idirafter",
105 "-iprefix",
106 "-isysroot",
107 "-x",
108 "-MF",
109 "-MT",
110 "-MQ",
111 "-Xlinker",
112 "-Xassembler",
113 "-Xpreprocessor",
114 "-Xclang",
115 "-target",
116 "-T",
117 "-z",
118 "-u",
119 "-e",
120 "-arch",
121];
122
123pub(crate) fn classify(args: &[OsString]) -> Result<Action, String> {
124 let mut inputs = 0;
125 let mut shared = false;
126 let mut relocatable = false;
127 let mut compile = false;
128 let mut i = 0;
129 while i < args.len() {
130 let arg = args[i].to_string_lossy();
131 match arg.as_ref() {
132 "-E" | "-M" | "-MM" | "--version" | "-dumpversion" | "-dumpfullversion"
133 | "-dumpmachine" | "-###" => return Ok(Action::Query),
134 "-c" | "-S" | "-fsyntax-only" => compile = true,
135 "-static" | "-static-pie" => {
136 return Err(
137 "static linking is not supported: providers have no static glibc".into(),
138 )
139 }
140 "-shared" => shared = true,
141 "-r" => relocatable = true,
142 _ if arg.starts_with("-print-") || arg.starts_with("--print-") => {
143 return Ok(Action::Query)
144 }
145 _ if SEPARATE_VALUE.contains(&arg.as_ref()) => i += 1,
146 _ if arg == "-" || !arg.starts_with('-') => inputs += 1,
148 _ => {}
149 }
150 i += 1;
151 }
152 Ok(if inputs == 0 {
153 Action::Query
154 } else if compile {
155 Action::Compile
156 } else if relocatable {
157 Action::Relocatable
158 } else {
159 Action::Link { shared }
160 })
161}
162
163fn is_cxx(args: &[OsString]) -> bool {
165 let mut language_cxx = false;
166 let mut i = 0;
167 while i < args.len() {
168 let arg = args[i].to_string_lossy();
169 i += 1;
170 let language = if arg == "-x" {
171 i += 1;
172 Some(
173 args.get(i - 1)
174 .map_or(String::new(), |v| v.to_string_lossy().into_owned()),
175 )
176 } else {
177 arg.strip_prefix("-x").map(str::to_string)
178 };
179 if let Some(language) = language {
180 language_cxx = language.starts_with("c++");
181 continue;
182 }
183 if SEPARATE_VALUE.contains(&arg.as_ref()) {
184 i += 1;
185 continue;
186 }
187 if arg.starts_with('-') && arg != "-" {
188 continue;
189 }
190 let ext = Path::new(arg.as_ref())
191 .extension()
192 .map(|e| e.to_string_lossy().into_owned());
193 if language_cxx
194 || matches!(
195 ext.as_deref(),
196 Some("cc" | "cp" | "cpp" | "cxx" | "CPP" | "c++" | "C" | "ii" | "cppm" | "ixx")
197 )
198 {
199 return true;
200 }
201 }
202 false
203}
204
205pub(crate) fn command(language: Language, args: Vec<OsString>) -> Result<Command, String> {
207 if env::var_os(TARGET_MARKER).is_none() {
208 let key = match language {
209 Language::C => HOST_CC_ENV,
210 Language::Cxx => HOST_CXX_ENV,
211 };
212 let host = env::var_os(key)
213 .ok_or_else(|| format!("{key} is missing; this wrapper is launched by cargo libc"))?;
214 let text = host.to_string_lossy().into_owned();
216 let mut words = text.split_whitespace();
217 let program = words.next().ok_or(format!("{key} is empty"))?;
218 let mut cmd = Command::new(program);
219 cmd.args(words).args(args);
220 return Ok(cmd);
221 }
222 let action = classify(&args)?;
223 if action != Action::Query && (language == Language::Cxx || is_cxx(&args)) {
225 return Err("C++ for the target is not supported yet: providers have no target C++ runtime. Build only C for the target (CMake: project(NAME C))".into());
226 }
227 toolchain::target_compiler(action, args)
228}
229
230pub fn run(language: Language) -> i32 {
236 let name = match language {
237 Language::C => "libc-cc",
238 Language::Cxx => "libc-cxx",
239 };
240 let result = command(language, env::args_os().skip(1).collect()).and_then(|mut cmd| {
241 cmd.status()
242 .map_err(|e| format!("cannot run {:?}: {e}", cmd.get_program()))
243 });
244 match result {
245 Ok(status) => status.code().unwrap_or(1),
246 Err(error) => {
247 eprintln!("{name}: {error}");
248 1
249 }
250 }
251}
252
253pub(crate) fn wrappers() -> Result<(PathBuf, PathBuf), String> {
255 Ok((
256 toolchain::sibling("libc-cc")?,
257 toolchain::sibling("libc-cxx")?,
258 ))
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 fn args(values: &[&str]) -> Vec<OsString> {
266 values.iter().map(OsString::from).collect()
267 }
268
269 #[test]
270 fn compiles_queries_and_links_are_told_apart() {
271 assert_eq!(
272 classify(&args(&["-c", "a.c", "-o", "a.o"])).unwrap(),
273 Action::Compile
274 );
275 for query in [
276 &["-E", "probe.c"][..],
277 &["--version"],
278 &["-v"],
279 &["-print-file-name=crt1.o"],
280 &["-o", "out", "-I", "include"],
281 ] {
282 assert_eq!(classify(&args(query)).unwrap(), Action::Query, "{query:?}");
283 }
284 assert_eq!(
285 classify(&args(&["a.o", "-o", "cmTC_1"])).unwrap(),
286 Action::Link { shared: false }
287 );
288 assert_eq!(
289 classify(&args(&["-shared", "a.o", "-o", "liba.so"])).unwrap(),
290 Action::Link { shared: true }
291 );
292 assert_eq!(
293 classify(&args(&["@objects.rsp", "-o", "app"])).unwrap(),
294 Action::Link { shared: false }
295 );
296 assert_eq!(
297 classify(&args(&["-r", "a.o", "-o", "b.o"])).unwrap(),
298 Action::Relocatable
299 );
300 assert!(classify(&args(&["-static", "a.o"])).is_err());
301 }
302
303 #[test]
304 fn cxx_sources_are_recognized() {
305 assert!(is_cxx(&args(&["-c", "a.cpp"])));
306 assert!(is_cxx(&args(&["-x", "c++", "-c", "a.h"])));
307 assert!(is_cxx(&args(&["-xc++", "-"])));
308 assert!(!is_cxx(&args(&["-c", "a.c", "-o", "a.o"])));
309 assert!(!is_cxx(&args(&["-x", "c++", "-x", "c", "a.c"])));
310 }
311}