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