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