1use crate::core::compiler::CompileKind;
2use crate::core::compiler::CompileTarget;
3use crate::core::{Dependency, TargetKind, Workspace};
4use crate::util::config::{Config, StringList, TargetConfig};
5use crate::util::{CargoResult, CargoResultExt, ProcessBuilder, Rustc};
6use cargo_platform::{Cfg, CfgExpr};
7use std::cell::RefCell;
8use std::collections::hash_map::{Entry, HashMap};
9use std::env;
10use std::path::PathBuf;
11use std::str::{self, FromStr};
12
13#[derive(Clone)]
18pub struct TargetInfo {
19 crate_type_process: ProcessBuilder,
23 crate_types: RefCell<HashMap<String, Option<(String, String)>>>,
30 cfg: Vec<Cfg>,
32 pub sysroot: PathBuf,
34 pub sysroot_host_libdir: PathBuf,
37 pub sysroot_target_libdir: PathBuf,
40 pub rustflags: Vec<String>,
42 pub rustdocflags: Vec<String>,
44}
45
46#[derive(Clone, PartialEq, Eq, Debug)]
48pub enum FileFlavor {
49 Normal,
51 Auxiliary,
53 Linkable { rmeta: bool },
55 DebugInfo,
57}
58
59pub struct FileType {
61 pub flavor: FileFlavor,
63 suffix: String,
66 prefix: String,
69 should_replace_hyphens: bool,
76}
77
78impl FileType {
79 pub fn filename(&self, stem: &str) -> String {
80 let stem = if self.should_replace_hyphens {
81 stem.replace("-", "_")
82 } else {
83 stem.to_string()
84 };
85 format!("{}{}{}", self.prefix, stem, self.suffix)
86 }
87}
88
89impl TargetInfo {
90 pub fn new(
91 config: &Config,
92 requested_kind: CompileKind,
93 rustc: &Rustc,
94 kind: CompileKind,
95 ) -> CargoResult<TargetInfo> {
96 let rustflags = env_args(config, requested_kind, &rustc.host, None, kind, "RUSTFLAGS")?;
97 let mut process = rustc.process();
98 process
99 .arg("-")
100 .arg("--crate-name")
101 .arg("___")
102 .arg("--print=file-names")
103 .args(&rustflags)
104 .env_remove("RUSTC_LOG");
105
106 if let CompileKind::Target(target) = kind {
107 process.arg("--target").arg(target.rustc_target());
108 }
109
110 let crate_type_process = process.clone();
111 const KNOWN_CRATE_TYPES: &[&str] =
112 &["bin", "rlib", "dylib", "cdylib", "staticlib", "proc-macro"];
113 for crate_type in KNOWN_CRATE_TYPES.iter() {
114 process.arg("--crate-type").arg(crate_type);
115 }
116
117 process.arg("--print=sysroot");
118 process.arg("--print=cfg");
119
120 let (output, error) = rustc
121 .cached_output(&process)
122 .chain_err(|| "failed to run `rustc` to learn about target-specific information")?;
123
124 let mut lines = output.lines();
125 let mut map = HashMap::new();
126 for crate_type in KNOWN_CRATE_TYPES {
127 let out = parse_crate_type(crate_type, &process, &output, &error, &mut lines)?;
128 map.insert(crate_type.to_string(), out);
129 }
130
131 let line = match lines.next() {
132 Some(line) => line,
133 None => anyhow::bail!(
134 "output of --print=sysroot missing when learning about \
135 target-specific information from rustc\n{}",
136 output_err_info(&process, &output, &error)
137 ),
138 };
139 let sysroot = PathBuf::from(line);
140 let sysroot_host_libdir = if cfg!(windows) {
141 sysroot.join("bin")
142 } else {
143 sysroot.join("lib")
144 };
145 let mut sysroot_target_libdir = sysroot.clone();
146 sysroot_target_libdir.push("lib");
147 sysroot_target_libdir.push("rustlib");
148 sysroot_target_libdir.push(match &kind {
149 CompileKind::Host => rustc.host.as_str(),
150 CompileKind::Target(target) => target.short_name(),
151 });
152 sysroot_target_libdir.push("lib");
153
154 let cfg = lines
155 .map(|line| Ok(Cfg::from_str(line)?))
156 .filter(TargetInfo::not_user_specific_cfg)
157 .collect::<CargoResult<Vec<_>>>()
158 .chain_err(|| {
159 format!(
160 "failed to parse the cfg from `rustc --print=cfg`, got:\n{}",
161 output
162 )
163 })?;
164
165 Ok(TargetInfo {
166 crate_type_process,
167 crate_types: RefCell::new(map),
168 sysroot,
169 sysroot_host_libdir,
170 sysroot_target_libdir,
171 rustflags: env_args(
174 config,
175 requested_kind,
176 &rustc.host,
177 Some(&cfg),
178 kind,
179 "RUSTFLAGS",
180 )?,
181 rustdocflags: env_args(
182 config,
183 requested_kind,
184 &rustc.host,
185 Some(&cfg),
186 kind,
187 "RUSTDOCFLAGS",
188 )?,
189 cfg,
190 })
191 }
192
193 fn not_user_specific_cfg(cfg: &CargoResult<Cfg>) -> bool {
194 if let Ok(Cfg::Name(cfg_name)) = cfg {
195 if cfg_name == "proc_macro" {
199 return false;
200 }
201 }
202 true
203 }
204
205 pub fn cfg(&self) -> &[Cfg] {
207 &self.cfg
208 }
209
210 pub fn file_types(
214 &self,
215 crate_type: &str,
216 flavor: FileFlavor,
217 kind: &TargetKind,
218 target_triple: &str,
219 ) -> CargoResult<Option<Vec<FileType>>> {
220 let mut crate_types = self.crate_types.borrow_mut();
221 let entry = crate_types.entry(crate_type.to_string());
222 let crate_type_info = match entry {
223 Entry::Occupied(o) => &*o.into_mut(),
224 Entry::Vacant(v) => {
225 let value = self.discover_crate_type(v.key())?;
226 &*v.insert(value)
227 }
228 };
229 let (prefix, suffix) = match *crate_type_info {
230 Some((ref prefix, ref suffix)) => (prefix, suffix),
231 None => return Ok(None),
232 };
233 let mut ret = vec![FileType {
234 suffix: suffix.clone(),
235 prefix: prefix.clone(),
236 flavor,
237 should_replace_hyphens: false,
238 }];
239
240 if target_triple.ends_with("-windows-msvc")
242 && crate_type.ends_with("dylib")
243 && suffix == ".dll"
244 {
245 ret.push(FileType {
246 suffix: ".dll.lib".to_string(),
247 prefix: prefix.clone(),
248 flavor: FileFlavor::Normal,
249 should_replace_hyphens: false,
250 })
251 }
252
253 if target_triple.starts_with("wasm32-") && crate_type == "bin" && suffix == ".js" {
255 ret.push(FileType {
256 suffix: ".wasm".to_string(),
257 prefix: prefix.clone(),
258 flavor: FileFlavor::Auxiliary,
259 should_replace_hyphens: true,
260 })
261 }
262
263 let is_apple = target_triple.contains("-apple-");
272 if *kind == TargetKind::Bin || (*kind == TargetKind::ExampleBin && is_apple) {
273 if is_apple {
274 ret.push(FileType {
275 suffix: ".dSYM".to_string(),
276 prefix: prefix.clone(),
277 flavor: FileFlavor::DebugInfo,
278 should_replace_hyphens: false,
279 })
280 } else if target_triple.ends_with("-msvc") {
281 ret.push(FileType {
282 suffix: ".pdb".to_string(),
283 prefix: prefix.clone(),
284 flavor: FileFlavor::DebugInfo,
285 should_replace_hyphens: false,
286 })
287 }
288 }
289
290 Ok(Some(ret))
291 }
292
293 fn discover_crate_type(&self, crate_type: &str) -> CargoResult<Option<(String, String)>> {
294 let mut process = self.crate_type_process.clone();
295
296 process.arg("--crate-type").arg(crate_type);
297
298 let output = process.exec_with_output().chain_err(|| {
299 format!(
300 "failed to run `rustc` to learn about crate-type {} information",
301 crate_type
302 )
303 })?;
304
305 let error = str::from_utf8(&output.stderr).unwrap();
306 let output = str::from_utf8(&output.stdout).unwrap();
307 Ok(parse_crate_type(
308 crate_type,
309 &process,
310 output,
311 error,
312 &mut output.lines(),
313 )?)
314 }
315}
316
317fn parse_crate_type(
327 crate_type: &str,
328 cmd: &ProcessBuilder,
329 output: &str,
330 error: &str,
331 lines: &mut str::Lines<'_>,
332) -> CargoResult<Option<(String, String)>> {
333 let not_supported = error.lines().any(|line| {
334 (line.contains("unsupported crate type") || line.contains("unknown crate type"))
335 && line.contains(&format!("crate type `{}`", crate_type))
336 });
337 if not_supported {
338 return Ok(None);
339 }
340 let line = match lines.next() {
341 Some(line) => line,
342 None => anyhow::bail!(
343 "malformed output when learning about crate-type {} information\n{}",
344 crate_type,
345 output_err_info(cmd, output, error)
346 ),
347 };
348 let mut parts = line.trim().split("___");
349 let prefix = parts.next().unwrap();
350 let suffix = match parts.next() {
351 Some(part) => part,
352 None => anyhow::bail!(
353 "output of --print=file-names has changed in the compiler, cannot parse\n{}",
354 output_err_info(cmd, output, error)
355 ),
356 };
357
358 Ok(Some((prefix.to_string(), suffix.to_string())))
359}
360
361fn output_err_info(cmd: &ProcessBuilder, stdout: &str, stderr: &str) -> String {
363 let mut result = format!("command was: {}\n", cmd);
364 if !stdout.is_empty() {
365 result.push_str("\n--- stdout\n");
366 result.push_str(stdout);
367 }
368 if !stderr.is_empty() {
369 result.push_str("\n--- stderr\n");
370 result.push_str(stderr);
371 }
372 if stdout.is_empty() && stderr.is_empty() {
373 result.push_str("(no output received)");
374 }
375 result
376}
377
378fn env_args(
396 config: &Config,
397 requested_kind: CompileKind,
398 host_triple: &str,
399 target_cfg: Option<&[Cfg]>,
400 kind: CompileKind,
401 name: &str,
402) -> CargoResult<Vec<String>> {
403 if !requested_kind.is_host() && kind.is_host() {
423 return Ok(Vec::new());
427 }
428
429 if let Ok(a) = env::var(name) {
431 let args = a
432 .split(' ')
433 .map(str::trim)
434 .filter(|s| !s.is_empty())
435 .map(str::to_string);
436 return Ok(args.collect());
437 }
438
439 let mut rustflags = Vec::new();
440
441 let name = name
442 .chars()
443 .flat_map(|c| c.to_lowercase())
444 .collect::<String>();
445 let target = match &kind {
447 CompileKind::Host => host_triple,
448 CompileKind::Target(target) => target.short_name(),
449 };
450 let key = format!("target.{}.{}", target, name);
451 if let Some(args) = config.get::<Option<StringList>>(&key)? {
452 rustflags.extend(args.as_slice().iter().cloned());
453 }
454 if let Some(target_cfg) = target_cfg {
456 config
457 .target_cfgs()?
458 .iter()
459 .filter_map(|(key, cfg)| {
460 cfg.rustflags
461 .as_ref()
462 .map(|rustflags| (key, &rustflags.val))
463 })
464 .filter(|(key, _rustflags)| CfgExpr::matches_key(key, target_cfg))
465 .for_each(|(_key, cfg_rustflags)| {
466 rustflags.extend(cfg_rustflags.as_slice().iter().cloned());
467 });
468 }
469
470 if !rustflags.is_empty() {
471 return Ok(rustflags);
472 }
473
474 let build = config.build_config()?;
476 let list = if name == "rustflags" {
477 &build.rustflags
478 } else {
479 &build.rustdocflags
480 };
481 if let Some(list) = list {
482 return Ok(list.as_slice().to_vec());
483 }
484
485 Ok(Vec::new())
486}
487
488pub struct RustcTargetData {
490 pub rustc: Rustc,
492 host_config: TargetConfig,
496 host_info: TargetInfo,
497
498 target_config: HashMap<CompileTarget, TargetConfig>,
503 target_info: HashMap<CompileTarget, TargetInfo>,
504}
505
506impl RustcTargetData {
507 pub fn new(ws: &Workspace<'_>, requested_kind: CompileKind) -> CargoResult<RustcTargetData> {
508 let config = ws.config();
509 let rustc = config.load_global_rustc(Some(ws))?;
510 let host_config = config.target_cfg_triple(&rustc.host)?;
511 let host_info = TargetInfo::new(config, requested_kind, &rustc, CompileKind::Host)?;
512 let mut target_config = HashMap::new();
513 let mut target_info = HashMap::new();
514 if let CompileKind::Target(target) = requested_kind {
515 let tcfg = config.target_cfg_triple(target.short_name())?;
516 target_config.insert(target, tcfg);
517 target_info.insert(
518 target,
519 TargetInfo::new(config, requested_kind, &rustc, CompileKind::Target(target))?,
520 );
521 }
522
523 Ok(RustcTargetData {
524 rustc,
525 target_config,
526 target_info,
527 host_config,
528 host_info,
529 })
530 }
531
532 pub fn short_name<'a>(&'a self, kind: &'a CompileKind) -> &'a str {
535 match kind {
536 CompileKind::Host => &self.rustc.host,
537 CompileKind::Target(target) => target.short_name(),
538 }
539 }
540
541 pub fn dep_platform_activated(&self, dep: &Dependency, kind: CompileKind) -> bool {
544 let platform = match dep.platform() {
547 Some(p) => p,
548 None => return true,
549 };
550 let name = self.short_name(&kind);
551 platform.matches(name, self.cfg(kind))
552 }
553
554 pub fn cfg(&self, kind: CompileKind) -> &[Cfg] {
556 self.info(kind).cfg()
557 }
558
559 pub fn info(&self, kind: CompileKind) -> &TargetInfo {
561 match kind {
562 CompileKind::Host => &self.host_info,
563 CompileKind::Target(s) => &self.target_info[&s],
564 }
565 }
566
567 pub fn target_config(&self, kind: CompileKind) -> &TargetConfig {
569 match kind {
570 CompileKind::Host => &self.host_config,
571 CompileKind::Target(s) => &self.target_config[&s],
572 }
573 }
574}