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