cargo/core/compiler/build_context/
target_info.rs

1//! This modules contains types storing information of target platforms.
2//!
3//! Normally, call [`RustcTargetData::new`] to construct all the target
4//! platform once, and then query info on your demand. For example,
5//!
6//! * [`RustcTargetData::dep_platform_activated`] to check if platform is activated.
7//! * [`RustcTargetData::info`] to get a [`TargetInfo`] for an in-depth query.
8//! * [`TargetInfo::rustc_outputs`] to get a list of supported file types.
9
10use crate::core::compiler::apply_env_config;
11use crate::core::compiler::{BuildRunner, CompileKind, CompileMode, CompileTarget, CrateType};
12use crate::core::{Dependency, Package, Target, TargetKind, Workspace};
13use crate::util::context::{GlobalContext, StringList, TargetConfig};
14use crate::util::interning::InternedString;
15use crate::util::{CargoResult, Rustc};
16use anyhow::Context as _;
17use cargo_platform::{Cfg, CfgExpr};
18use cargo_util::{paths, ProcessBuilder};
19use serde::{Deserialize, Serialize};
20use std::cell::RefCell;
21use std::collections::hash_map::{Entry, HashMap};
22use std::path::{Path, PathBuf};
23use std::rc::Rc;
24use std::str::{self, FromStr};
25
26/// Information about the platform target gleaned from querying rustc.
27///
28/// [`RustcTargetData`] keeps several of these, one for the host and the others
29/// for other specified targets. If no target is specified, it uses a clone from
30/// the host.
31#[derive(Clone)]
32pub struct TargetInfo {
33    /// A base process builder for discovering crate type information. In
34    /// particular, this is used to determine the output filename prefix and
35    /// suffix for a crate type.
36    crate_type_process: ProcessBuilder,
37    /// Cache of output filename prefixes and suffixes.
38    ///
39    /// The key is the crate type name (like `cdylib`) and the value is
40    /// `Some((prefix, suffix))`, for example `libcargo.so` would be
41    /// `Some(("lib", ".so"))`. The value is `None` if the crate type is not
42    /// supported.
43    crate_types: RefCell<HashMap<CrateType, Option<(String, String)>>>,
44    /// `cfg` information extracted from `rustc --print=cfg`.
45    cfg: Vec<Cfg>,
46    /// `supports_std` information extracted from `rustc --print=target-spec-json`
47    pub supports_std: Option<bool>,
48    /// Supported values for `-Csplit-debuginfo=` flag, queried from rustc
49    support_split_debuginfo: Vec<String>,
50    /// Path to the sysroot.
51    pub sysroot: PathBuf,
52    /// Path to the "lib" directory in the sysroot which rustc uses for linking
53    /// target libraries.
54    pub sysroot_target_libdir: PathBuf,
55    /// Extra flags to pass to `rustc`, see [`extra_args`].
56    pub rustflags: Rc<[String]>,
57    /// Extra flags to pass to `rustdoc`, see [`extra_args`].
58    pub rustdocflags: Rc<[String]>,
59}
60
61/// Kind of each file generated by a Unit, part of `FileType`.
62#[derive(Clone, PartialEq, Eq, Debug)]
63pub enum FileFlavor {
64    /// Not a special file type.
65    Normal,
66    /// Like `Normal`, but not directly executable.
67    /// For example, a `.wasm` file paired with the "normal" `.js` file.
68    Auxiliary,
69    /// Something you can link against (e.g., a library).
70    Linkable,
71    /// An `.rmeta` Rust metadata file.
72    Rmeta,
73    /// Piece of external debug information (e.g., `.dSYM`/`.pdb` file).
74    DebugInfo,
75}
76
77/// Type of each file generated by a Unit.
78#[derive(Debug)]
79pub struct FileType {
80    /// The kind of file.
81    pub flavor: FileFlavor,
82    /// The crate-type that generates this file.
83    ///
84    /// `None` for things that aren't associated with a specific crate type,
85    /// for example `rmeta` files.
86    pub crate_type: Option<CrateType>,
87    /// The suffix for the file (for example, `.rlib`).
88    /// This is an empty string for executables on Unix-like platforms.
89    suffix: String,
90    /// The prefix for the file (for example, `lib`).
91    /// This is an empty string for things like executables.
92    prefix: String,
93    /// Flag to convert hyphen to underscore when uplifting.
94    should_replace_hyphens: bool,
95}
96
97impl FileType {
98    /// The filename for this `FileType` crated by rustc.
99    pub fn output_filename(&self, target: &Target, metadata: Option<&str>) -> String {
100        match metadata {
101            Some(metadata) => format!(
102                "{}{}-{}{}",
103                self.prefix,
104                target.crate_name(),
105                metadata,
106                self.suffix
107            ),
108            None => format!("{}{}{}", self.prefix, target.crate_name(), self.suffix),
109        }
110    }
111
112    /// The filename for this `FileType` that Cargo should use when "uplifting"
113    /// it to the destination directory.
114    pub fn uplift_filename(&self, target: &Target) -> String {
115        let name = match target.binary_filename() {
116            Some(name) => name,
117            None => {
118                // For binary crate type, `should_replace_hyphens` will always be false.
119                if self.should_replace_hyphens {
120                    target.crate_name()
121                } else {
122                    target.name().to_string()
123                }
124            }
125        };
126
127        format!("{}{}{}", self.prefix, name, self.suffix)
128    }
129
130    /// Creates a new instance representing a `.rmeta` file.
131    pub fn new_rmeta() -> FileType {
132        // Note that even binaries use the `lib` prefix.
133        FileType {
134            flavor: FileFlavor::Rmeta,
135            crate_type: None,
136            suffix: ".rmeta".to_string(),
137            prefix: "lib".to_string(),
138            should_replace_hyphens: true,
139        }
140    }
141}
142
143impl TargetInfo {
144    /// Learns the information of target platform from `rustc` invocation(s).
145    ///
146    /// Generally, the first time calling this function is expensive, as it may
147    /// query `rustc` several times. To reduce the cost, output of each `rustc`
148    /// invocation is cached by [`Rustc::cached_output`].
149    ///
150    /// Search `Tricky` to learn why querying `rustc` several times is needed.
151    #[tracing::instrument(skip_all)]
152    pub fn new(
153        gctx: &GlobalContext,
154        requested_kinds: &[CompileKind],
155        rustc: &Rustc,
156        kind: CompileKind,
157    ) -> CargoResult<TargetInfo> {
158        let mut rustflags =
159            extra_args(gctx, requested_kinds, &rustc.host, None, kind, Flags::Rust)?;
160        let mut turn = 0;
161        loop {
162            let extra_fingerprint = kind.fingerprint_hash();
163
164            // Query rustc for several kinds of info from each line of output:
165            // 0) file-names (to determine output file prefix/suffix for given crate type)
166            // 1) sysroot
167            // 2) split-debuginfo
168            // 3) cfg
169            //
170            // Search `--print` to see what we query so far.
171            let mut process = rustc.workspace_process();
172            apply_env_config(gctx, &mut process)?;
173            process
174                .arg("-")
175                .arg("--crate-name")
176                .arg("___")
177                .arg("--print=file-names")
178                .args(&rustflags)
179                .env_remove("RUSTC_LOG");
180
181            // Removes `FD_CLOEXEC` set by `jobserver::Client` to pass jobserver
182            // as environment variables specify.
183            if let Some(client) = gctx.jobserver_from_env() {
184                process.inherit_jobserver(client);
185            }
186
187            if let CompileKind::Target(target) = kind {
188                process.arg("--target").arg(target.rustc_target());
189            }
190
191            let crate_type_process = process.clone();
192            const KNOWN_CRATE_TYPES: &[CrateType] = &[
193                CrateType::Bin,
194                CrateType::Rlib,
195                CrateType::Dylib,
196                CrateType::Cdylib,
197                CrateType::Staticlib,
198                CrateType::ProcMacro,
199            ];
200            for crate_type in KNOWN_CRATE_TYPES.iter() {
201                process.arg("--crate-type").arg(crate_type.as_str());
202            }
203
204            process.arg("--print=sysroot");
205            process.arg("--print=split-debuginfo");
206            process.arg("--print=crate-name"); // `___` as a delimiter.
207            process.arg("--print=cfg");
208
209            // parse_crate_type() relies on "unsupported/unknown crate type" error message,
210            // so make warnings always emitted as warnings.
211            process.arg("-Wwarnings");
212
213            let (output, error) = rustc
214                .cached_output(&process, extra_fingerprint)
215                .with_context(|| {
216                    "failed to run `rustc` to learn about target-specific information"
217                })?;
218
219            let mut lines = output.lines();
220            let mut map = HashMap::new();
221            for crate_type in KNOWN_CRATE_TYPES {
222                let out = parse_crate_type(crate_type, &process, &output, &error, &mut lines)?;
223                map.insert(crate_type.clone(), out);
224            }
225
226            let Some(line) = lines.next() else {
227                return error_missing_print_output("sysroot", &process, &output, &error);
228            };
229            let sysroot = PathBuf::from(line);
230            let sysroot_target_libdir = {
231                let mut libdir = sysroot.clone();
232                libdir.push("lib");
233                libdir.push("rustlib");
234                libdir.push(match &kind {
235                    CompileKind::Host => rustc.host.as_str(),
236                    CompileKind::Target(target) => target.short_name(),
237                });
238                libdir.push("lib");
239                libdir
240            };
241
242            let support_split_debuginfo = {
243                // HACK: abuse `--print=crate-name` to use `___` as a delimiter.
244                let mut res = Vec::new();
245                loop {
246                    match lines.next() {
247                        Some(line) if line == "___" => break,
248                        Some(line) => res.push(line.into()),
249                        None => {
250                            return error_missing_print_output(
251                                "split-debuginfo",
252                                &process,
253                                &output,
254                                &error,
255                            )
256                        }
257                    }
258                }
259                res
260            };
261
262            let cfg = lines
263                .map(|line| Ok(Cfg::from_str(line)?))
264                .filter(TargetInfo::not_user_specific_cfg)
265                .collect::<CargoResult<Vec<_>>>()
266                .with_context(|| {
267                    format!(
268                        "failed to parse the cfg from `rustc --print=cfg`, got:\n{}",
269                        output
270                    )
271                })?;
272
273            // recalculate `rustflags` from above now that we have `cfg`
274            // information
275            let new_flags = extra_args(
276                gctx,
277                requested_kinds,
278                &rustc.host,
279                Some(&cfg),
280                kind,
281                Flags::Rust,
282            )?;
283
284            // Tricky: `RUSTFLAGS` defines the set of active `cfg` flags, active
285            // `cfg` flags define which `.cargo/config` sections apply, and they
286            // in turn can affect `RUSTFLAGS`! This is a bona fide mutual
287            // dependency, and it can even diverge (see `cfg_paradox` test).
288            //
289            // So what we do here is running at most *two* iterations of
290            // fixed-point iteration, which should be enough to cover
291            // practically useful cases, and warn if that's not enough for
292            // convergence.
293            let reached_fixed_point = new_flags == rustflags;
294            if !reached_fixed_point && turn == 0 {
295                turn += 1;
296                rustflags = new_flags;
297                continue;
298            }
299            if !reached_fixed_point {
300                gctx.shell().warn("non-trivial mutual dependency between target-specific configuration and RUSTFLAGS")?;
301            }
302
303            let mut supports_std: Option<bool> = None;
304
305            // The '--print=target-spec-json' is an unstable option of rustc, therefore only
306            // try to fetch this information if rustc allows nightly features. Additionally,
307            // to avoid making two rustc queries when not required, only try to fetch the
308            // target-spec when the '-Zbuild-std' option is passed.
309            if gctx.cli_unstable().build_std.is_some() {
310                let mut target_spec_process = rustc.workspace_process();
311                apply_env_config(gctx, &mut target_spec_process)?;
312                target_spec_process
313                    .arg("--print=target-spec-json")
314                    .arg("-Zunstable-options")
315                    .args(&rustflags)
316                    .env_remove("RUSTC_LOG");
317
318                if let CompileKind::Target(target) = kind {
319                    target_spec_process
320                        .arg("--target")
321                        .arg(target.rustc_target());
322                }
323
324                #[derive(Deserialize)]
325                struct Metadata {
326                    pub std: Option<bool>,
327                }
328
329                #[derive(Deserialize)]
330                struct TargetSpec {
331                    pub metadata: Metadata,
332                }
333
334                if let Ok(output) = target_spec_process.output() {
335                    if let Ok(spec) = serde_json::from_slice::<TargetSpec>(&output.stdout) {
336                        supports_std = spec.metadata.std;
337                    }
338                }
339            }
340
341            return Ok(TargetInfo {
342                crate_type_process,
343                crate_types: RefCell::new(map),
344                sysroot,
345                sysroot_target_libdir,
346                rustflags: rustflags.into(),
347                rustdocflags: extra_args(
348                    gctx,
349                    requested_kinds,
350                    &rustc.host,
351                    Some(&cfg),
352                    kind,
353                    Flags::Rustdoc,
354                )?
355                .into(),
356                cfg,
357                supports_std,
358                support_split_debuginfo,
359            });
360        }
361    }
362
363    fn not_user_specific_cfg(cfg: &CargoResult<Cfg>) -> bool {
364        if let Ok(Cfg::Name(cfg_name)) = cfg {
365            // This should also include "debug_assertions", but it causes
366            // regressions. Maybe some day in the distant future it can be
367            // added (and possibly change the warning to an error).
368            if cfg_name == "proc_macro" {
369                return false;
370            }
371        }
372        true
373    }
374
375    /// All the target [`Cfg`] settings.
376    pub fn cfg(&self) -> &[Cfg] {
377        &self.cfg
378    }
379
380    /// Returns the list of file types generated by the given crate type.
381    ///
382    /// Returns `None` if the target does not support the given crate type.
383    fn file_types(
384        &self,
385        crate_type: &CrateType,
386        flavor: FileFlavor,
387        target_triple: &str,
388    ) -> CargoResult<Option<Vec<FileType>>> {
389        let crate_type = if *crate_type == CrateType::Lib {
390            CrateType::Rlib
391        } else {
392            crate_type.clone()
393        };
394
395        let mut crate_types = self.crate_types.borrow_mut();
396        let entry = crate_types.entry(crate_type.clone());
397        let crate_type_info = match entry {
398            Entry::Occupied(o) => &*o.into_mut(),
399            Entry::Vacant(v) => {
400                let value = self.discover_crate_type(v.key())?;
401                &*v.insert(value)
402            }
403        };
404        let Some((prefix, suffix)) = crate_type_info else {
405            return Ok(None);
406        };
407        let mut ret = vec![FileType {
408            suffix: suffix.clone(),
409            prefix: prefix.clone(),
410            flavor,
411            crate_type: Some(crate_type.clone()),
412            should_replace_hyphens: crate_type != CrateType::Bin,
413        }];
414
415        // Window shared library import/export files.
416        if crate_type.is_dynamic() {
417            // Note: Custom JSON specs can alter the suffix. For now, we'll
418            // just ignore non-DLL suffixes.
419            if target_triple.ends_with("-windows-msvc") && suffix == ".dll" {
420                // See https://docs.microsoft.com/en-us/cpp/build/reference/working-with-import-libraries-and-export-files
421                // for more information about DLL import/export files.
422                ret.push(FileType {
423                    suffix: ".dll.lib".to_string(),
424                    prefix: prefix.clone(),
425                    flavor: FileFlavor::Auxiliary,
426                    crate_type: Some(crate_type.clone()),
427                    should_replace_hyphens: true,
428                });
429                // NOTE: lld does not produce these
430                ret.push(FileType {
431                    suffix: ".dll.exp".to_string(),
432                    prefix: prefix.clone(),
433                    flavor: FileFlavor::Auxiliary,
434                    crate_type: Some(crate_type.clone()),
435                    should_replace_hyphens: true,
436                });
437            } else if suffix == ".dll"
438                && (target_triple.ends_with("windows-gnu")
439                    || target_triple.ends_with("windows-gnullvm"))
440            {
441                // See https://cygwin.com/cygwin-ug-net/dll.html for more
442                // information about GNU import libraries.
443                // LD can link DLL directly, but LLD requires the import library.
444                ret.push(FileType {
445                    suffix: ".dll.a".to_string(),
446                    prefix: "lib".to_string(),
447                    flavor: FileFlavor::Auxiliary,
448                    crate_type: Some(crate_type.clone()),
449                    should_replace_hyphens: true,
450                })
451            }
452        }
453
454        if target_triple.starts_with("wasm32-") && crate_type == CrateType::Bin && suffix == ".js" {
455            // emscripten binaries generate a .js file, which loads a .wasm
456            // file.
457            ret.push(FileType {
458                suffix: ".wasm".to_string(),
459                prefix: prefix.clone(),
460                flavor: FileFlavor::Auxiliary,
461                crate_type: Some(crate_type.clone()),
462                // Name `foo-bar` will generate a `foo_bar.js` and
463                // `foo_bar.wasm`. Cargo will translate the underscore and
464                // copy `foo_bar.js` to `foo-bar.js`. However, the wasm
465                // filename is embedded in the .js file with an underscore, so
466                // it should not contain hyphens.
467                should_replace_hyphens: true,
468            });
469            // And a map file for debugging. This is only emitted with debug=2
470            // (-g4 for emcc).
471            ret.push(FileType {
472                suffix: ".wasm.map".to_string(),
473                prefix: prefix.clone(),
474                flavor: FileFlavor::DebugInfo,
475                crate_type: Some(crate_type.clone()),
476                should_replace_hyphens: true,
477            });
478        }
479
480        // Handle separate debug files.
481        let is_apple = target_triple.contains("-apple-");
482        if matches!(
483            crate_type,
484            CrateType::Bin | CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro
485        ) {
486            if is_apple {
487                let suffix = if crate_type == CrateType::Bin {
488                    ".dSYM".to_string()
489                } else {
490                    ".dylib.dSYM".to_string()
491                };
492                ret.push(FileType {
493                    suffix,
494                    prefix: prefix.clone(),
495                    flavor: FileFlavor::DebugInfo,
496                    crate_type: Some(crate_type),
497                    // macOS tools like lldb use all sorts of magic to locate
498                    // dSYM files. See https://lldb.llvm.org/use/symbols.html
499                    // for some details. It seems like a `.dSYM` located next
500                    // to the executable with the same name is one method. The
501                    // dSYM should have the same hyphens as the executable for
502                    // the names to match.
503                    should_replace_hyphens: false,
504                })
505            } else if target_triple.ends_with("-msvc") || target_triple.ends_with("-uefi") {
506                ret.push(FileType {
507                    suffix: ".pdb".to_string(),
508                    prefix: prefix.clone(),
509                    flavor: FileFlavor::DebugInfo,
510                    crate_type: Some(crate_type),
511                    // The absolute path to the pdb file is embedded in the
512                    // executable. If the exe/pdb pair is moved to another
513                    // machine, then debuggers will look in the same directory
514                    // of the exe with the original pdb filename. Since the
515                    // original name contains underscores, they need to be
516                    // preserved.
517                    should_replace_hyphens: true,
518                })
519            } else {
520                // Because DWARF Package (dwp) files are produced after the
521                // fact by another tool, there is nothing in the binary that
522                // provides a means to locate them. By convention, debuggers
523                // take the binary filename and append ".dwp" (including to
524                // binaries that already have an extension such as shared libs)
525                // to find the dwp.
526                ret.push(FileType {
527                    // It is important to preserve the existing suffix for
528                    // e.g. shared libraries, where the dwp for libfoo.so is
529                    // expected to be at libfoo.so.dwp.
530                    suffix: format!("{suffix}.dwp"),
531                    prefix: prefix.clone(),
532                    flavor: FileFlavor::DebugInfo,
533                    crate_type: Some(crate_type.clone()),
534                    // Likewise, the dwp needs to match the primary artifact's
535                    // hyphenation exactly.
536                    should_replace_hyphens: crate_type != CrateType::Bin,
537                })
538            }
539        }
540
541        Ok(Some(ret))
542    }
543
544    fn discover_crate_type(&self, crate_type: &CrateType) -> CargoResult<Option<(String, String)>> {
545        let mut process = self.crate_type_process.clone();
546
547        process.arg("--crate-type").arg(crate_type.as_str());
548
549        let output = process.exec_with_output().with_context(|| {
550            format!(
551                "failed to run `rustc` to learn about crate-type {} information",
552                crate_type
553            )
554        })?;
555
556        let error = str::from_utf8(&output.stderr).unwrap();
557        let output = str::from_utf8(&output.stdout).unwrap();
558        parse_crate_type(crate_type, &process, output, error, &mut output.lines())
559    }
560
561    /// Returns all the file types generated by rustc for the given `mode`/`target_kind`.
562    ///
563    /// The first value is a Vec of file types generated, the second value is
564    /// a list of `CrateTypes` that are not supported by the given target.
565    pub fn rustc_outputs(
566        &self,
567        mode: CompileMode,
568        target_kind: &TargetKind,
569        target_triple: &str,
570    ) -> CargoResult<(Vec<FileType>, Vec<CrateType>)> {
571        match mode {
572            CompileMode::Build => self.calc_rustc_outputs(target_kind, target_triple),
573            CompileMode::Test | CompileMode::Bench => {
574                match self.file_types(&CrateType::Bin, FileFlavor::Normal, target_triple)? {
575                    Some(fts) => Ok((fts, Vec::new())),
576                    None => Ok((Vec::new(), vec![CrateType::Bin])),
577                }
578            }
579            CompileMode::Check { .. } => Ok((vec![FileType::new_rmeta()], Vec::new())),
580            CompileMode::Doc { .. }
581            | CompileMode::Doctest
582            | CompileMode::Docscrape
583            | CompileMode::RunCustomBuild => {
584                panic!("asked for rustc output for non-rustc mode")
585            }
586        }
587    }
588
589    fn calc_rustc_outputs(
590        &self,
591        target_kind: &TargetKind,
592        target_triple: &str,
593    ) -> CargoResult<(Vec<FileType>, Vec<CrateType>)> {
594        let mut unsupported = Vec::new();
595        let mut result = Vec::new();
596        let crate_types = target_kind.rustc_crate_types();
597        for crate_type in &crate_types {
598            let flavor = if crate_type.is_linkable() {
599                FileFlavor::Linkable
600            } else {
601                FileFlavor::Normal
602            };
603            let file_types = self.file_types(crate_type, flavor, target_triple)?;
604            match file_types {
605                Some(types) => {
606                    result.extend(types);
607                }
608                None => {
609                    unsupported.push(crate_type.clone());
610                }
611            }
612        }
613        if !result.is_empty() && !crate_types.iter().any(|ct| ct.requires_upstream_objects()) {
614            // Only add rmeta if pipelining.
615            result.push(FileType::new_rmeta());
616        }
617        Ok((result, unsupported))
618    }
619
620    /// Checks if the debuginfo-split value is supported by this target
621    pub fn supports_debuginfo_split(&self, split: InternedString) -> bool {
622        self.support_split_debuginfo
623            .iter()
624            .any(|sup| sup.as_str() == split.as_str())
625    }
626
627    /// Checks if a target maybe support std.
628    ///
629    /// If no explictly stated in target spec json, we treat it as "maybe support".
630    ///
631    /// This is only useful for `-Zbuild-std` to determine the default set of
632    /// crates it is going to build.
633    pub fn maybe_support_std(&self) -> bool {
634        matches!(self.supports_std, Some(true) | None)
635    }
636}
637
638/// Takes rustc output (using specialized command line args), and calculates the file prefix and
639/// suffix for the given crate type, or returns `None` if the type is not supported. (e.g., for a
640/// Rust library like `libcargo.rlib`, we have prefix "lib" and suffix "rlib").
641///
642/// The caller needs to ensure that the lines object is at the correct line for the given crate
643/// type: this is not checked.
644///
645/// This function can not handle more than one file per type (with wasm32-unknown-emscripten, there
646/// are two files for bin (`.wasm` and `.js`)).
647fn parse_crate_type(
648    crate_type: &CrateType,
649    cmd: &ProcessBuilder,
650    output: &str,
651    error: &str,
652    lines: &mut str::Lines<'_>,
653) -> CargoResult<Option<(String, String)>> {
654    let not_supported = error.lines().any(|line| {
655        (line.contains("unsupported crate type") || line.contains("unknown crate type"))
656            && line.contains(&format!("crate type `{}`", crate_type))
657    });
658    if not_supported {
659        return Ok(None);
660    }
661    let Some(line) = lines.next() else {
662        anyhow::bail!(
663            "malformed output when learning about crate-type {} information\n{}",
664            crate_type,
665            output_err_info(cmd, output, error)
666        )
667    };
668    let mut parts = line.trim().split("___");
669    let prefix = parts.next().unwrap();
670    let Some(suffix) = parts.next() else {
671        return error_missing_print_output("file-names", cmd, output, error);
672    };
673
674    Ok(Some((prefix.to_string(), suffix.to_string())))
675}
676
677/// Helper for creating an error message for missing output from a certain `--print` request.
678fn error_missing_print_output<T>(
679    request: &str,
680    cmd: &ProcessBuilder,
681    stdout: &str,
682    stderr: &str,
683) -> CargoResult<T> {
684    let err_info = output_err_info(cmd, stdout, stderr);
685    anyhow::bail!(
686        "output of --print={request} missing when learning about \
687     target-specific information from rustc\n{err_info}",
688    )
689}
690
691/// Helper for creating an error message when parsing rustc output fails.
692fn output_err_info(cmd: &ProcessBuilder, stdout: &str, stderr: &str) -> String {
693    let mut result = format!("command was: {}\n", cmd);
694    if !stdout.is_empty() {
695        result.push_str("\n--- stdout\n");
696        result.push_str(stdout);
697    }
698    if !stderr.is_empty() {
699        result.push_str("\n--- stderr\n");
700        result.push_str(stderr);
701    }
702    if stdout.is_empty() && stderr.is_empty() {
703        result.push_str("(no output received)");
704    }
705    result
706}
707
708/// Compiler flags for either rustc or rustdoc.
709#[derive(Debug, Copy, Clone)]
710enum Flags {
711    Rust,
712    Rustdoc,
713}
714
715impl Flags {
716    fn as_key(self) -> &'static str {
717        match self {
718            Flags::Rust => "rustflags",
719            Flags::Rustdoc => "rustdocflags",
720        }
721    }
722
723    fn as_env(self) -> &'static str {
724        match self {
725            Flags::Rust => "RUSTFLAGS",
726            Flags::Rustdoc => "RUSTDOCFLAGS",
727        }
728    }
729}
730
731/// Acquire extra flags to pass to the compiler from various locations.
732///
733/// The locations are:
734///
735///  - the `CARGO_ENCODED_RUSTFLAGS` environment variable
736///  - the `RUSTFLAGS` environment variable
737///
738/// then if none of those were found
739///
740///  - `target.*.rustflags` from the config (.cargo/config)
741///  - `target.cfg(..).rustflags` from the config
742///  - `host.*.rustflags` from the config if compiling a host artifact or without `--target`
743///     (requires `-Zhost-config`)
744///
745/// then if none of those were found
746///
747///  - `build.rustflags` from the config
748///
749/// The behavior differs slightly when cross-compiling (or, specifically, when `--target` is
750/// provided) for artifacts that are always built for the host (plugins, build scripts, ...).
751/// For those artifacts, _only_ `host.*.rustflags` is respected, and no other configuration
752/// sources, _regardless of the value of `target-applies-to-host`_. This is counterintuitive, but
753/// necessary to retain backwards compatibility with older versions of Cargo.
754///
755/// Rules above also applies to rustdoc. Just the key would be `rustdocflags`/`RUSTDOCFLAGS`.
756fn extra_args(
757    gctx: &GlobalContext,
758    requested_kinds: &[CompileKind],
759    host_triple: &str,
760    target_cfg: Option<&[Cfg]>,
761    kind: CompileKind,
762    flags: Flags,
763) -> CargoResult<Vec<String>> {
764    let target_applies_to_host = gctx.target_applies_to_host()?;
765
766    // Host artifacts should not generally pick up rustflags from anywhere except [host].
767    //
768    // The one exception to this is if `target-applies-to-host = true`, which opts into a
769    // particular (inconsistent) past Cargo behavior where host artifacts _do_ pick up rustflags
770    // set elsewhere when `--target` isn't passed.
771    if kind.is_host() {
772        if target_applies_to_host && requested_kinds == [CompileKind::Host] {
773            // This is the past Cargo behavior where we fall back to the same logic as for other
774            // artifacts without --target.
775        } else {
776            // In all other cases, host artifacts just get flags from [host], regardless of
777            // --target. Or, phrased differently, no `--target` behaves the same as `--target
778            // <host>`, and host artifacts are always "special" (they don't pick up `RUSTFLAGS` for
779            // example).
780            return Ok(rustflags_from_host(gctx, flags, host_triple)?.unwrap_or_else(Vec::new));
781        }
782    }
783
784    // All other artifacts pick up the RUSTFLAGS, [target.*], and [build], in that order.
785    // NOTE: It is impossible to have a [host] section and reach this logic with kind.is_host(),
786    // since [host] implies `target-applies-to-host = false`, which always early-returns above.
787
788    if let Some(rustflags) = rustflags_from_env(gctx, flags) {
789        Ok(rustflags)
790    } else if let Some(rustflags) =
791        rustflags_from_target(gctx, host_triple, target_cfg, kind, flags)?
792    {
793        Ok(rustflags)
794    } else if let Some(rustflags) = rustflags_from_build(gctx, flags)? {
795        Ok(rustflags)
796    } else {
797        Ok(Vec::new())
798    }
799}
800
801/// Gets compiler flags from environment variables.
802/// See [`extra_args`] for more.
803fn rustflags_from_env(gctx: &GlobalContext, flags: Flags) -> Option<Vec<String>> {
804    // First try CARGO_ENCODED_RUSTFLAGS from the environment.
805    // Prefer this over RUSTFLAGS since it's less prone to encoding errors.
806    if let Ok(a) = gctx.get_env(format!("CARGO_ENCODED_{}", flags.as_env())) {
807        if a.is_empty() {
808            return Some(Vec::new());
809        }
810        return Some(a.split('\x1f').map(str::to_string).collect());
811    }
812
813    // Then try RUSTFLAGS from the environment
814    if let Ok(a) = gctx.get_env(flags.as_env()) {
815        let args = a
816            .split(' ')
817            .map(str::trim)
818            .filter(|s| !s.is_empty())
819            .map(str::to_string);
820        return Some(args.collect());
821    }
822
823    // No rustflags to be collected from the environment
824    None
825}
826
827/// Gets compiler flags from `[target]` section in the config.
828/// See [`extra_args`] for more.
829fn rustflags_from_target(
830    gctx: &GlobalContext,
831    host_triple: &str,
832    target_cfg: Option<&[Cfg]>,
833    kind: CompileKind,
834    flag: Flags,
835) -> CargoResult<Option<Vec<String>>> {
836    let mut rustflags = Vec::new();
837
838    // Then the target.*.rustflags value...
839    let target = match &kind {
840        CompileKind::Host => host_triple,
841        CompileKind::Target(target) => target.short_name(),
842    };
843    let key = format!("target.{}.{}", target, flag.as_key());
844    if let Some(args) = gctx.get::<Option<StringList>>(&key)? {
845        rustflags.extend(args.as_slice().iter().cloned());
846    }
847    // ...including target.'cfg(...)'.rustflags
848    if let Some(target_cfg) = target_cfg {
849        gctx.target_cfgs()?
850            .iter()
851            .filter_map(|(key, cfg)| {
852                match flag {
853                    Flags::Rust => cfg
854                        .rustflags
855                        .as_ref()
856                        .map(|rustflags| (key, &rustflags.val)),
857                    // `target.cfg(…).rustdocflags` is currently not supported.
858                    Flags::Rustdoc => None,
859                }
860            })
861            .filter(|(key, _rustflags)| CfgExpr::matches_key(key, target_cfg))
862            .for_each(|(_key, cfg_rustflags)| {
863                rustflags.extend(cfg_rustflags.as_slice().iter().cloned());
864            });
865    }
866
867    if rustflags.is_empty() {
868        Ok(None)
869    } else {
870        Ok(Some(rustflags))
871    }
872}
873
874/// Gets compiler flags from `[host]` section in the config.
875/// See [`extra_args`] for more.
876fn rustflags_from_host(
877    gctx: &GlobalContext,
878    flag: Flags,
879    host_triple: &str,
880) -> CargoResult<Option<Vec<String>>> {
881    let target_cfg = gctx.host_cfg_triple(host_triple)?;
882    let list = match flag {
883        Flags::Rust => &target_cfg.rustflags,
884        Flags::Rustdoc => {
885            // host.rustdocflags is not a thing, since it does not make sense
886            return Ok(None);
887        }
888    };
889    Ok(list.as_ref().map(|l| l.val.as_slice().to_vec()))
890}
891
892/// Gets compiler flags from `[build]` section in the config.
893/// See [`extra_args`] for more.
894fn rustflags_from_build(gctx: &GlobalContext, flag: Flags) -> CargoResult<Option<Vec<String>>> {
895    // Then the `build.rustflags` value.
896    let build = gctx.build_config()?;
897    let list = match flag {
898        Flags::Rust => &build.rustflags,
899        Flags::Rustdoc => &build.rustdocflags,
900    };
901    Ok(list.as_ref().map(|l| l.as_slice().to_vec()))
902}
903
904/// Collection of information about `rustc` and the host and target.
905pub struct RustcTargetData<'gctx> {
906    /// Information about `rustc` itself.
907    pub rustc: Rustc,
908
909    /// Config
910    pub gctx: &'gctx GlobalContext,
911    requested_kinds: Vec<CompileKind>,
912
913    /// Build information for the "host", which is information about when
914    /// `rustc` is invoked without a `--target` flag. This is used for
915    /// selecting a linker, and applying link overrides.
916    ///
917    /// The configuration read into this depends on whether or not
918    /// `target-applies-to-host=true`.
919    host_config: TargetConfig,
920    /// Information about the host platform.
921    host_info: TargetInfo,
922
923    /// Build information for targets that we're building for.
924    target_config: HashMap<CompileTarget, TargetConfig>,
925    /// Information about the target platform that we're building for.
926    target_info: HashMap<CompileTarget, TargetInfo>,
927}
928
929impl<'gctx> RustcTargetData<'gctx> {
930    #[tracing::instrument(skip_all)]
931    pub fn new(
932        ws: &Workspace<'gctx>,
933        requested_kinds: &[CompileKind],
934    ) -> CargoResult<RustcTargetData<'gctx>> {
935        let gctx = ws.gctx();
936        let rustc = gctx.load_global_rustc(Some(ws))?;
937        let mut target_config = HashMap::new();
938        let mut target_info = HashMap::new();
939        let target_applies_to_host = gctx.target_applies_to_host()?;
940        let host_target = CompileTarget::new(&rustc.host)?;
941        let host_info = TargetInfo::new(gctx, requested_kinds, &rustc, CompileKind::Host)?;
942
943        // This config is used for link overrides and choosing a linker.
944        let host_config = if target_applies_to_host {
945            gctx.target_cfg_triple(&rustc.host)?
946        } else {
947            gctx.host_cfg_triple(&rustc.host)?
948        };
949
950        // This is a hack. The unit_dependency graph builder "pretends" that
951        // `CompileKind::Host` is `CompileKind::Target(host)` if the
952        // `--target` flag is not specified. Since the unit_dependency code
953        // needs access to the target config data, create a copy so that it
954        // can be found. See `rebuild_unit_graph_shared` for why this is done.
955        if requested_kinds.iter().any(CompileKind::is_host) {
956            target_config.insert(host_target, gctx.target_cfg_triple(&rustc.host)?);
957
958            // If target_applies_to_host is true, the host_info is the target info,
959            // otherwise we need to build target info for the target.
960            if target_applies_to_host {
961                target_info.insert(host_target, host_info.clone());
962            } else {
963                let host_target_info = TargetInfo::new(
964                    gctx,
965                    requested_kinds,
966                    &rustc,
967                    CompileKind::Target(host_target),
968                )?;
969                target_info.insert(host_target, host_target_info);
970            }
971        };
972
973        let mut res = RustcTargetData {
974            rustc,
975            gctx,
976            requested_kinds: requested_kinds.into(),
977            host_config,
978            host_info,
979            target_config,
980            target_info,
981        };
982
983        // Get all kinds we currently know about.
984        //
985        // For now, targets can only ever come from the root workspace
986        // units and artifact dependencies, so this
987        // correctly represents all the kinds that can happen. When we have
988        // other ways for targets to appear at places that are not the root units,
989        // we may have to revisit this.
990        fn artifact_targets(package: &Package) -> impl Iterator<Item = CompileKind> + '_ {
991            package
992                .manifest()
993                .dependencies()
994                .iter()
995                .filter_map(|d| d.artifact()?.target()?.to_compile_kind())
996        }
997        let all_kinds = requested_kinds
998            .iter()
999            .copied()
1000            .chain(ws.members().flat_map(|p| {
1001                p.manifest()
1002                    .default_kind()
1003                    .into_iter()
1004                    .chain(p.manifest().forced_kind())
1005                    .chain(artifact_targets(p))
1006            }));
1007        for kind in all_kinds {
1008            res.merge_compile_kind(kind)?;
1009        }
1010
1011        Ok(res)
1012    }
1013
1014    /// Insert `kind` into our `target_info` and `target_config` members if it isn't present yet.
1015    pub fn merge_compile_kind(&mut self, kind: CompileKind) -> CargoResult<()> {
1016        if let CompileKind::Target(target) = kind {
1017            if !self.target_config.contains_key(&target) {
1018                self.target_config
1019                    .insert(target, self.gctx.target_cfg_triple(target.short_name())?);
1020            }
1021            if !self.target_info.contains_key(&target) {
1022                self.target_info.insert(
1023                    target,
1024                    TargetInfo::new(self.gctx, &self.requested_kinds, &self.rustc, kind)?,
1025                );
1026            }
1027        }
1028        Ok(())
1029    }
1030
1031    /// Returns a "short" name for the given kind, suitable for keying off
1032    /// configuration in Cargo or presenting to users.
1033    pub fn short_name<'a>(&'a self, kind: &'a CompileKind) -> &'a str {
1034        match kind {
1035            CompileKind::Host => &self.rustc.host,
1036            CompileKind::Target(target) => target.short_name(),
1037        }
1038    }
1039
1040    /// Whether a dependency should be compiled for the host or target platform,
1041    /// specified by `CompileKind`.
1042    pub fn dep_platform_activated(&self, dep: &Dependency, kind: CompileKind) -> bool {
1043        // If this dependency is only available for certain platforms,
1044        // make sure we're only enabling it for that platform.
1045        let Some(platform) = dep.platform() else {
1046            return true;
1047        };
1048        let name = self.short_name(&kind);
1049        platform.matches(name, self.cfg(kind))
1050    }
1051
1052    /// Gets the list of `cfg`s printed out from the compiler for the specified kind.
1053    pub fn cfg(&self, kind: CompileKind) -> &[Cfg] {
1054        self.info(kind).cfg()
1055    }
1056
1057    /// Information about the given target platform, learned by querying rustc.
1058    ///
1059    /// # Panics
1060    ///
1061    /// Panics, if the target platform described by `kind` can't be found.
1062    /// See [`get_info`](Self::get_info) for a non-panicking alternative.
1063    pub fn info(&self, kind: CompileKind) -> &TargetInfo {
1064        self.get_info(kind).unwrap()
1065    }
1066
1067    /// Information about the given target platform, learned by querying rustc.
1068    ///
1069    /// Returns `None` if the target platform described by `kind` can't be found.
1070    pub fn get_info(&self, kind: CompileKind) -> Option<&TargetInfo> {
1071        match kind {
1072            CompileKind::Host => Some(&self.host_info),
1073            CompileKind::Target(s) => self.target_info.get(&s),
1074        }
1075    }
1076
1077    /// Gets the target configuration for a particular host or target.
1078    pub fn target_config(&self, kind: CompileKind) -> &TargetConfig {
1079        match kind {
1080            CompileKind::Host => &self.host_config,
1081            CompileKind::Target(s) => &self.target_config[&s],
1082        }
1083    }
1084
1085    pub fn get_unsupported_std_targets(&self) -> Vec<&str> {
1086        let mut unsupported = Vec::new();
1087        for (target, target_info) in &self.target_info {
1088            if target_info.supports_std == Some(false) {
1089                unsupported.push(target.short_name());
1090            }
1091        }
1092        unsupported
1093    }
1094}
1095
1096/// Structure used to deal with Rustdoc fingerprinting
1097#[derive(Debug, Serialize, Deserialize)]
1098pub struct RustDocFingerprint {
1099    pub rustc_vv: String,
1100}
1101
1102impl RustDocFingerprint {
1103    /// This function checks whether the latest version of `Rustc` used to compile this
1104    /// `Workspace`'s docs was the same as the one is currently being used in this `cargo doc`
1105    /// call.
1106    ///
1107    /// In case it's not, it takes care of removing the `doc/` folder as well as overwriting
1108    /// the rustdoc fingerprint info in order to guarantee that we won't end up with mixed
1109    /// versions of the `js/html/css` files that `rustdoc` autogenerates which do not have
1110    /// any versioning.
1111    pub fn check_rustdoc_fingerprint(build_runner: &BuildRunner<'_, '_>) -> CargoResult<()> {
1112        if build_runner
1113            .bcx
1114            .gctx
1115            .cli_unstable()
1116            .skip_rustdoc_fingerprint
1117        {
1118            return Ok(());
1119        }
1120        let actual_rustdoc_target_data = RustDocFingerprint {
1121            rustc_vv: build_runner.bcx.rustc().verbose_version.clone(),
1122        };
1123
1124        let fingerprint_path = build_runner
1125            .files()
1126            .host_root()
1127            .join(".rustdoc_fingerprint.json");
1128        let write_fingerprint = || -> CargoResult<()> {
1129            paths::write(
1130                &fingerprint_path,
1131                serde_json::to_string(&actual_rustdoc_target_data)?,
1132            )
1133        };
1134        let Ok(rustdoc_data) = paths::read(&fingerprint_path) else {
1135            // If the fingerprint does not exist, do not clear out the doc
1136            // directories. Otherwise this ran into problems where projects
1137            // like bootstrap were creating the doc directory before running
1138            // `cargo doc` in a way that deleting it would break it.
1139            return write_fingerprint();
1140        };
1141        match serde_json::from_str::<RustDocFingerprint>(&rustdoc_data) {
1142            Ok(fingerprint) => {
1143                if fingerprint.rustc_vv == actual_rustdoc_target_data.rustc_vv {
1144                    return Ok(());
1145                } else {
1146                    tracing::debug!(
1147                        "doc fingerprint changed:\noriginal:\n{}\nnew:\n{}",
1148                        fingerprint.rustc_vv,
1149                        actual_rustdoc_target_data.rustc_vv
1150                    );
1151                }
1152            }
1153            Err(e) => {
1154                tracing::debug!("could not deserialize {:?}: {}", fingerprint_path, e);
1155            }
1156        };
1157        // Fingerprint does not match, delete the doc directories and write a new fingerprint.
1158        tracing::debug!(
1159            "fingerprint {:?} mismatch, clearing doc directories",
1160            fingerprint_path
1161        );
1162        build_runner
1163            .bcx
1164            .all_kinds
1165            .iter()
1166            .map(|kind| build_runner.files().layout(*kind).doc())
1167            .filter(|path| path.exists())
1168            .try_for_each(|path| clean_doc(path))?;
1169        write_fingerprint()?;
1170        return Ok(());
1171
1172        fn clean_doc(path: &Path) -> CargoResult<()> {
1173            let entries = path
1174                .read_dir()
1175                .with_context(|| format!("failed to read directory `{}`", path.display()))?;
1176            for entry in entries {
1177                let entry = entry?;
1178                // Don't remove hidden files. Rustdoc does not create them,
1179                // but the user might have.
1180                if entry
1181                    .file_name()
1182                    .to_str()
1183                    .map_or(false, |name| name.starts_with('.'))
1184                {
1185                    continue;
1186                }
1187                let path = entry.path();
1188                if entry.file_type()?.is_dir() {
1189                    paths::remove_dir_all(path)?;
1190                } else {
1191                    paths::remove_file(path)?;
1192                }
1193            }
1194            Ok(())
1195        }
1196    }
1197}