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