Skip to main content

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