Skip to main content

cargo/core/compiler/build_context/
target_info.rs

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/// Information about the platform target gleaned from querying rustc.
14///
15/// `RustcTargetData` keeps two of these, one for the host and one for the
16/// target. If no target is specified, it uses a clone from the host.
17#[derive(Clone)]
18pub struct TargetInfo {
19    /// A base process builder for discovering crate type information. In
20    /// particular, this is used to determine the output filename prefix and
21    /// suffix for a crate type.
22    crate_type_process: ProcessBuilder,
23    /// Cache of output filename prefixes and suffixes.
24    ///
25    /// The key is the crate type name (like `cdylib`) and the value is
26    /// `Some((prefix, suffix))`, for example `libcargo.so` would be
27    /// `Some(("lib", ".so")). The value is `None` if the crate type is not
28    /// supported.
29    crate_types: RefCell<HashMap<String, Option<(String, String)>>>,
30    /// `cfg` information extracted from `rustc --print=cfg`.
31    cfg: Vec<Cfg>,
32    /// Path to the sysroot.
33    pub sysroot: PathBuf,
34    /// Path to the "lib" or "bin" directory that rustc uses for its dynamic
35    /// libraries.
36    pub sysroot_host_libdir: PathBuf,
37    /// Path to the "lib" directory in the sysroot which rustc uses for linking
38    /// target libraries.
39    pub sysroot_target_libdir: PathBuf,
40    /// Extra flags to pass to `rustc`, see `env_args`.
41    pub rustflags: Vec<String>,
42    /// Extra flags to pass to `rustdoc`, see `env_args`.
43    pub rustdocflags: Vec<String>,
44}
45
46/// Kind of each file generated by a Unit, part of `FileType`.
47#[derive(Clone, PartialEq, Eq, Debug)]
48pub enum FileFlavor {
49    /// Not a special file type.
50    Normal,
51    /// Like `Normal`, but not directly executable
52    Auxiliary,
53    /// Something you can link against (e.g., a library).
54    Linkable { rmeta: bool },
55    /// Piece of external debug information (e.g., `.dSYM`/`.pdb` file).
56    DebugInfo,
57}
58
59/// Type of each file generated by a Unit.
60pub struct FileType {
61    /// The kind of file.
62    pub flavor: FileFlavor,
63    /// The suffix for the file (for example, `.rlib`).
64    /// This is an empty string for executables on Unix-like platforms.
65    suffix: String,
66    /// The prefix for the file (for example, `lib`).
67    /// This is an empty string for things like executables.
68    prefix: String,
69    /// Flag to convert hyphen to underscore.
70    ///
71    /// wasm bin targets will generate two files in deps such as
72    /// "web-stuff.js" and "web_stuff.wasm". Note the different usages of "-"
73    /// and "_". This flag indicates that the stem "web-stuff" should be
74    /// converted to "web_stuff".
75    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            // recalculate `rustflags` from above now that we have `cfg`
172            // information
173            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            // This should also include "debug_assertions", but it causes
196            // regressions. Maybe some day in the distant future it can be
197            // added (and possibly change the warning to an error).
198            if cfg_name == "proc_macro" {
199                return false;
200            }
201        }
202        true
203    }
204
205    /// All the target `cfg` settings.
206    pub fn cfg(&self) -> &[Cfg] {
207        &self.cfg
208    }
209
210    /// Returns the list of file types generated by the given crate type.
211    ///
212    /// Returns `None` if the target does not support the given crate type.
213    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        // See rust-lang/cargo#4500.
241        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        // See rust-lang/cargo#4535.
254        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        // See rust-lang/cargo#4490, rust-lang/cargo#4960.
264        // Only uplift debuginfo for binaries.
265        // - Tests are run directly from `target/debug/deps/` with the
266        //   metadata hash still in the filename.
267        // - Examples are only uplifted for apple because the symbol file
268        //   needs to match the executable file name to be found (i.e., it
269        //   needs to remove the hash in the filename). On Windows, the path
270        //   to the .pdb with the hash is embedded in the executable.
271        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
317/// Takes rustc output (using specialized command line args), and calculates the file prefix and
318/// suffix for the given crate type, or returns `None` if the type is not supported. (e.g., for a
319/// Rust library like `libcargo.rlib`, we have prefix "lib" and suffix "rlib").
320///
321/// The caller needs to ensure that the lines object is at the correct line for the given crate
322/// type: this is not checked.
323///
324/// This function can not handle more than one file per type (with wasm32-unknown-emscripten, there
325/// are two files for bin (`.wasm` and `.js`)).
326fn 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
361/// Helper for creating an error message when parsing rustc output fails.
362fn 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
378/// Acquire extra flags to pass to the compiler from various locations.
379///
380/// The locations are:
381///
382///  - the `RUSTFLAGS` environment variable
383///
384/// then if this was not found
385///
386///  - `target.*.rustflags` from the config (.cargo/config)
387///  - `target.cfg(..).rustflags` from the config
388///
389/// then if neither of these were found
390///
391///  - `build.rustflags` from the config
392///
393/// Note that if a `target` is specified, no args will be passed to host code (plugins, build
394/// scripts, ...), even if it is the same as the target.
395fn 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    // We *want* to apply RUSTFLAGS only to builds for the
404    // requested target architecture, and not to things like build
405    // scripts and plugins, which may be for an entirely different
406    // architecture. Cargo's present architecture makes it quite
407    // hard to only apply flags to things that are not build
408    // scripts and plugins though, so we do something more hacky
409    // instead to avoid applying the same RUSTFLAGS to multiple targets
410    // arches:
411    //
412    // 1) If --target is not specified we just apply RUSTFLAGS to
413    // all builds; they are all going to have the same target.
414    //
415    // 2) If --target *is* specified then we only apply RUSTFLAGS
416    // to compilation units with the Target kind, which indicates
417    // it was chosen by the --target flag.
418    //
419    // This means that, e.g., even if the specified --target is the
420    // same as the host, build scripts in plugins won't get
421    // RUSTFLAGS.
422    if !requested_kind.is_host() && kind.is_host() {
423        // This is probably a build script or plugin and we're
424        // compiling with --target. In this scenario there are
425        // no rustflags we can apply.
426        return Ok(Vec::new());
427    }
428
429    // First try RUSTFLAGS from the environment
430    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    // Then the target.*.rustflags value...
446    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    // ...including target.'cfg(...)'.rustflags
455    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    // Then the `build.rustflags` value.
475    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
488/// Collection of information about `rustc` and the host and target.
489pub struct RustcTargetData {
490    /// Information about `rustc` itself.
491    pub rustc: Rustc,
492    /// Build information for the "host", which is information about when
493    /// `rustc` is invoked without a `--target` flag. This is used for
494    /// procedural macros, build scripts, etc.
495    host_config: TargetConfig,
496    host_info: TargetInfo,
497
498    /// Build information for targets that we're building for. This will be
499    /// empty if the `--target` flag is not passed, and currently also only ever
500    /// has at most one entry, but eventually we'd like to support multi-target
501    /// builds with Cargo.
502    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    /// Returns a "short" name for the given kind, suitable for keying off
533    /// configuration in Cargo or presenting to users.
534    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    /// Whether a dependency should be compiled for the host or target platform,
542    /// specified by `CompileKind`.
543    pub fn dep_platform_activated(&self, dep: &Dependency, kind: CompileKind) -> bool {
544        // If this dependency is only available for certain platforms,
545        // make sure we're only enabling it for that platform.
546        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    /// Gets the list of `cfg`s printed out from the compiler for the specified kind.
555    pub fn cfg(&self, kind: CompileKind) -> &[Cfg] {
556        self.info(kind).cfg()
557    }
558
559    /// Information about the given target platform, learned by querying rustc.
560    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    /// Gets the target configuration for a particular host or target.
568    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}