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