Skip to main content

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