| //! This modules contains types storing information of target platforms. |
| //! |
| //! Normally, call [`RustcTargetData::new`] to construct all the target |
| //! platform once, and then query info on your demand. For example, |
| //! |
| //! * [`RustcTargetData::dep_platform_activated`] to check if platform is activated. |
| //! * [`RustcTargetData::info`] to get a [`TargetInfo`] for an in-depth query. |
| //! * [`TargetInfo::rustc_outputs`] to get a list of supported file types. |
| |
| use crate::core::compiler::{ |
| BuildOutput, CompileKind, CompileMode, CompileTarget, Context, CrateType, |
| }; |
| use crate::core::{Dependency, Package, Target, TargetKind, Workspace}; |
| use crate::util::config::{Config, StringList, TargetConfig}; |
| use crate::util::interning::InternedString; |
| use crate::util::{CargoResult, Rustc}; |
| use anyhow::Context as _; |
| use cargo_platform::{Cfg, CfgExpr}; |
| use cargo_util::{paths, ProcessBuilder}; |
| use serde::{Deserialize, Serialize}; |
| use std::cell::RefCell; |
| use std::collections::hash_map::{Entry, HashMap}; |
| use std::path::{Path, PathBuf}; |
| use std::str::{self, FromStr}; |
| |
| /// Information about the platform target gleaned from querying rustc. |
| /// |
| /// [`RustcTargetData`] keeps several of these, one for the host and the others |
| /// for other specified targets. If no target is specified, it uses a clone from |
| /// the host. |
| #[derive(Clone)] |
| pub struct TargetInfo { |
| /// A base process builder for discovering crate type information. In |
| /// particular, this is used to determine the output filename prefix and |
| /// suffix for a crate type. |
| crate_type_process: ProcessBuilder, |
| /// Cache of output filename prefixes and suffixes. |
| /// |
| /// The key is the crate type name (like `cdylib`) and the value is |
| /// `Some((prefix, suffix))`, for example `libcargo.so` would be |
| /// `Some(("lib", ".so")). The value is `None` if the crate type is not |
| /// supported. |
| crate_types: RefCell<HashMap<CrateType, Option<(String, String)>>>, |
| /// `cfg` information extracted from `rustc --print=cfg`. |
| cfg: Vec<Cfg>, |
| /// Supported values for `-Csplit-debuginfo=` flag, queried from rustc |
| support_split_debuginfo: Vec<String>, |
| /// Path to the sysroot. |
| pub sysroot: PathBuf, |
| /// Path to the "lib" or "bin" directory that rustc uses for its dynamic |
| /// libraries. |
| pub sysroot_host_libdir: PathBuf, |
| /// Path to the "lib" directory in the sysroot which rustc uses for linking |
| /// target libraries. |
| pub sysroot_target_libdir: PathBuf, |
| /// Extra flags to pass to `rustc`, see [`extra_args`]. |
| pub rustflags: Vec<String>, |
| /// Extra flags to pass to `rustdoc`, see [`extra_args`]. |
| pub rustdocflags: Vec<String>, |
| } |
| |
| /// Kind of each file generated by a Unit, part of `FileType`. |
| #[derive(Clone, PartialEq, Eq, Debug)] |
| pub enum FileFlavor { |
| /// Not a special file type. |
| Normal, |
| /// Like `Normal`, but not directly executable. |
| /// For example, a `.wasm` file paired with the "normal" `.js` file. |
| Auxiliary, |
| /// Something you can link against (e.g., a library). |
| Linkable, |
| /// An `.rmeta` Rust metadata file. |
| Rmeta, |
| /// Piece of external debug information (e.g., `.dSYM`/`.pdb` file). |
| DebugInfo, |
| } |
| |
| /// Type of each file generated by a Unit. |
| #[derive(Debug)] |
| pub struct FileType { |
| /// The kind of file. |
| pub flavor: FileFlavor, |
| /// The crate-type that generates this file. |
| /// |
| /// `None` for things that aren't associated with a specific crate type, |
| /// for example `rmeta` files. |
| pub crate_type: Option<CrateType>, |
| /// The suffix for the file (for example, `.rlib`). |
| /// This is an empty string for executables on Unix-like platforms. |
| suffix: String, |
| /// The prefix for the file (for example, `lib`). |
| /// This is an empty string for things like executables. |
| prefix: String, |
| /// Flag to convert hyphen to underscore when uplifting. |
| should_replace_hyphens: bool, |
| } |
| |
| impl FileType { |
| /// The filename for this FileType crated by rustc. |
| pub fn output_filename(&self, target: &Target, metadata: Option<&str>) -> String { |
| match metadata { |
| Some(metadata) => format!( |
| "{}{}-{}{}", |
| self.prefix, |
| target.crate_name(), |
| metadata, |
| self.suffix |
| ), |
| None => format!("{}{}{}", self.prefix, target.crate_name(), self.suffix), |
| } |
| } |
| |
| /// The filename for this FileType that Cargo should use when "uplifting" |
| /// it to the destination directory. |
| pub fn uplift_filename(&self, target: &Target) -> String { |
| let name = match target.binary_filename() { |
| Some(name) => name, |
| None => { |
| // For binary crate type, `should_replace_hyphens` will always be false. |
| if self.should_replace_hyphens { |
| target.crate_name() |
| } else { |
| target.name().to_string() |
| } |
| } |
| }; |
| |
| format!("{}{}{}", self.prefix, name, self.suffix) |
| } |
| |
| /// Creates a new instance representing a `.rmeta` file. |
| pub fn new_rmeta() -> FileType { |
| // Note that even binaries use the `lib` prefix. |
| FileType { |
| flavor: FileFlavor::Rmeta, |
| crate_type: None, |
| suffix: ".rmeta".to_string(), |
| prefix: "lib".to_string(), |
| should_replace_hyphens: true, |
| } |
| } |
| } |
| |
| impl TargetInfo { |
| /// Learns the information of target platform from `rustc` invocation(s). |
| /// |
| /// Generally, the first time calling this function is expensive, as it may |
| /// query `rustc` several times. To reduce the cost, output of each `rustc` |
| /// invocation is cached by [`Rustc::cached_output`]. |
| /// |
| /// Search `Tricky` to learn why querying `rustc` several times is needed. |
| pub fn new( |
| config: &Config, |
| requested_kinds: &[CompileKind], |
| rustc: &Rustc, |
| kind: CompileKind, |
| ) -> CargoResult<TargetInfo> { |
| let mut rustflags = extra_args( |
| config, |
| requested_kinds, |
| &rustc.host, |
| None, |
| kind, |
| Flags::Rust, |
| )?; |
| let mut turn = 0; |
| loop { |
| let extra_fingerprint = kind.fingerprint_hash(); |
| |
| // Query rustc for several kinds of info from each line of output: |
| // 0) file-names (to determine output file prefix/suffix for given crate type) |
| // 1) sysroot |
| // 2) split-debuginfo |
| // 3) cfg |
| // |
| // Search `--print` to see what we query so far. |
| let mut process = rustc.workspace_process(); |
| process |
| .arg("-") |
| .arg("--crate-name") |
| .arg("___") |
| .arg("--print=file-names") |
| .args(&rustflags) |
| .env_remove("RUSTC_LOG"); |
| |
| if let CompileKind::Target(target) = kind { |
| process.arg("--target").arg(target.rustc_target()); |
| } |
| |
| let crate_type_process = process.clone(); |
| const KNOWN_CRATE_TYPES: &[CrateType] = &[ |
| CrateType::Bin, |
| CrateType::Rlib, |
| CrateType::Dylib, |
| CrateType::Cdylib, |
| CrateType::Staticlib, |
| CrateType::ProcMacro, |
| ]; |
| for crate_type in KNOWN_CRATE_TYPES.iter() { |
| process.arg("--crate-type").arg(crate_type.as_str()); |
| } |
| |
| process.arg("--print=sysroot"); |
| process.arg("--print=split-debuginfo"); |
| process.arg("--print=crate-name"); // `___` as a delimiter. |
| process.arg("--print=cfg"); |
| |
| let (output, error) = rustc |
| .cached_output(&process, extra_fingerprint) |
| .with_context(|| { |
| "failed to run `rustc` to learn about target-specific information" |
| })?; |
| |
| let mut lines = output.lines(); |
| let mut map = HashMap::new(); |
| for crate_type in KNOWN_CRATE_TYPES { |
| let out = parse_crate_type(crate_type, &process, &output, &error, &mut lines)?; |
| map.insert(crate_type.clone(), out); |
| } |
| |
| let Some(line) = lines.next() else { |
| return error_missing_print_output("sysroot", &process, &output, &error); |
| }; |
| let sysroot = PathBuf::from(line); |
| let sysroot_host_libdir = if cfg!(windows) { |
| sysroot.join("bin") |
| } else { |
| sysroot.join("lib") |
| }; |
| let mut sysroot_target_libdir = sysroot.clone(); |
| sysroot_target_libdir.push("lib"); |
| sysroot_target_libdir.push("rustlib"); |
| sysroot_target_libdir.push(match &kind { |
| CompileKind::Host => rustc.host.as_str(), |
| CompileKind::Target(target) => target.short_name(), |
| }); |
| sysroot_target_libdir.push("lib"); |
| |
| let support_split_debuginfo = { |
| // HACK: abuse `--print=crate-name` to use `___` as a delimiter. |
| let mut res = Vec::new(); |
| loop { |
| match lines.next() { |
| Some(line) if line == "___" => break, |
| Some(line) => res.push(line.into()), |
| None => { |
| return error_missing_print_output( |
| "split-debuginfo", |
| &process, |
| &output, |
| &error, |
| ) |
| } |
| } |
| } |
| res |
| }; |
| |
| let cfg = lines |
| .map(|line| Ok(Cfg::from_str(line)?)) |
| .filter(TargetInfo::not_user_specific_cfg) |
| .collect::<CargoResult<Vec<_>>>() |
| .with_context(|| { |
| format!( |
| "failed to parse the cfg from `rustc --print=cfg`, got:\n{}", |
| output |
| ) |
| })?; |
| |
| // recalculate `rustflags` from above now that we have `cfg` |
| // information |
| let new_flags = extra_args( |
| config, |
| requested_kinds, |
| &rustc.host, |
| Some(&cfg), |
| kind, |
| Flags::Rust, |
| )?; |
| |
| // Tricky: `RUSTFLAGS` defines the set of active `cfg` flags, active |
| // `cfg` flags define which `.cargo/config` sections apply, and they |
| // in turn can affect `RUSTFLAGS`! This is a bona fide mutual |
| // dependency, and it can even diverge (see `cfg_paradox` test). |
| // |
| // So what we do here is running at most *two* iterations of |
| // fixed-point iteration, which should be enough to cover |
| // practically useful cases, and warn if that's not enough for |
| // convergence. |
| let reached_fixed_point = new_flags == rustflags; |
| if !reached_fixed_point && turn == 0 { |
| turn += 1; |
| rustflags = new_flags; |
| continue; |
| } |
| if !reached_fixed_point { |
| config.shell().warn("non-trivial mutual dependency between target-specific configuration and RUSTFLAGS")?; |
| } |
| |
| return Ok(TargetInfo { |
| crate_type_process, |
| crate_types: RefCell::new(map), |
| sysroot, |
| sysroot_host_libdir, |
| sysroot_target_libdir, |
| rustflags, |
| rustdocflags: extra_args( |
| config, |
| requested_kinds, |
| &rustc.host, |
| Some(&cfg), |
| kind, |
| Flags::Rustdoc, |
| )?, |
| cfg, |
| support_split_debuginfo, |
| }); |
| } |
| } |
| |
| fn not_user_specific_cfg(cfg: &CargoResult<Cfg>) -> bool { |
| if let Ok(Cfg::Name(cfg_name)) = cfg { |
| // This should also include "debug_assertions", but it causes |
| // regressions. Maybe some day in the distant future it can be |
| // added (and possibly change the warning to an error). |
| if cfg_name == "proc_macro" { |
| return false; |
| } |
| } |
| true |
| } |
| |
| /// All the target [`Cfg`] settings. |
| pub fn cfg(&self) -> &[Cfg] { |
| &self.cfg |
| } |
| |
| /// Returns the list of file types generated by the given crate type. |
| /// |
| /// Returns `None` if the target does not support the given crate type. |
| fn file_types( |
| &self, |
| crate_type: &CrateType, |
| flavor: FileFlavor, |
| target_triple: &str, |
| ) -> CargoResult<Option<Vec<FileType>>> { |
| let crate_type = if *crate_type == CrateType::Lib { |
| CrateType::Rlib |
| } else { |
| crate_type.clone() |
| }; |
| |
| let mut crate_types = self.crate_types.borrow_mut(); |
| let entry = crate_types.entry(crate_type.clone()); |
| let crate_type_info = match entry { |
| Entry::Occupied(o) => &*o.into_mut(), |
| Entry::Vacant(v) => { |
| let value = self.discover_crate_type(v.key())?; |
| &*v.insert(value) |
| } |
| }; |
| let (prefix, suffix) = match *crate_type_info { |
| Some((ref prefix, ref suffix)) => (prefix, suffix), |
| None => return Ok(None), |
| }; |
| let mut ret = vec![FileType { |
| suffix: suffix.clone(), |
| prefix: prefix.clone(), |
| flavor, |
| crate_type: Some(crate_type.clone()), |
| should_replace_hyphens: crate_type != CrateType::Bin, |
| }]; |
| |
| // Window shared library import/export files. |
| if crate_type.is_dynamic() { |
| // Note: Custom JSON specs can alter the suffix. For now, we'll |
| // just ignore non-DLL suffixes. |
| if target_triple.ends_with("-windows-msvc") && suffix == ".dll" { |
| // See https://docs.microsoft.com/en-us/cpp/build/reference/working-with-import-libraries-and-export-files |
| // for more information about DLL import/export files. |
| ret.push(FileType { |
| suffix: ".dll.lib".to_string(), |
| prefix: prefix.clone(), |
| flavor: FileFlavor::Auxiliary, |
| crate_type: Some(crate_type.clone()), |
| should_replace_hyphens: true, |
| }); |
| // NOTE: lld does not produce these |
| ret.push(FileType { |
| suffix: ".dll.exp".to_string(), |
| prefix: prefix.clone(), |
| flavor: FileFlavor::Auxiliary, |
| crate_type: Some(crate_type.clone()), |
| should_replace_hyphens: true, |
| }); |
| } else if target_triple.ends_with("windows-gnu") && suffix == ".dll" { |
| // See https://cygwin.com/cygwin-ug-net/dll.html for more |
| // information about GNU import libraries. |
| // LD can link DLL directly, but LLD requires the import library. |
| ret.push(FileType { |
| suffix: ".dll.a".to_string(), |
| prefix: "lib".to_string(), |
| flavor: FileFlavor::Auxiliary, |
| crate_type: Some(crate_type.clone()), |
| should_replace_hyphens: true, |
| }) |
| } |
| } |
| |
| if target_triple.starts_with("wasm32-") && crate_type == CrateType::Bin && suffix == ".js" { |
| // emscripten binaries generate a .js file, which loads a .wasm |
| // file. |
| ret.push(FileType { |
| suffix: ".wasm".to_string(), |
| prefix: prefix.clone(), |
| flavor: FileFlavor::Auxiliary, |
| crate_type: Some(crate_type.clone()), |
| // Name `foo-bar` will generate a `foo_bar.js` and |
| // `foo_bar.wasm`. Cargo will translate the underscore and |
| // copy `foo_bar.js` to `foo-bar.js`. However, the wasm |
| // filename is embedded in the .js file with an underscore, so |
| // it should not contain hyphens. |
| should_replace_hyphens: true, |
| }); |
| // And a map file for debugging. This is only emitted with debug=2 |
| // (-g4 for emcc). |
| ret.push(FileType { |
| suffix: ".wasm.map".to_string(), |
| prefix: prefix.clone(), |
| flavor: FileFlavor::DebugInfo, |
| crate_type: Some(crate_type.clone()), |
| should_replace_hyphens: true, |
| }); |
| } |
| |
| // Handle separate debug files. |
| let is_apple = target_triple.contains("-apple-"); |
| if matches!( |
| crate_type, |
| CrateType::Bin | CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro |
| ) { |
| if is_apple { |
| let suffix = if crate_type == CrateType::Bin { |
| ".dSYM".to_string() |
| } else { |
| ".dylib.dSYM".to_string() |
| }; |
| ret.push(FileType { |
| suffix, |
| prefix: prefix.clone(), |
| flavor: FileFlavor::DebugInfo, |
| crate_type: Some(crate_type), |
| // macOS tools like lldb use all sorts of magic to locate |
| // dSYM files. See https://lldb.llvm.org/use/symbols.html |
| // for some details. It seems like a `.dSYM` located next |
| // to the executable with the same name is one method. The |
| // dSYM should have the same hyphens as the executable for |
| // the names to match. |
| should_replace_hyphens: false, |
| }) |
| } else if target_triple.ends_with("-msvc") { |
| ret.push(FileType { |
| suffix: ".pdb".to_string(), |
| prefix: prefix.clone(), |
| flavor: FileFlavor::DebugInfo, |
| crate_type: Some(crate_type), |
| // The absolute path to the pdb file is embedded in the |
| // executable. If the exe/pdb pair is moved to another |
| // machine, then debuggers will look in the same directory |
| // of the exe with the original pdb filename. Since the |
| // original name contains underscores, they need to be |
| // preserved. |
| should_replace_hyphens: true, |
| }) |
| } else { |
| // Because DWARF Package (dwp) files are produced after the |
| // fact by another tool, there is nothing in the binary that |
| // provides a means to locate them. By convention, debuggers |
| // take the binary filename and append ".dwp" (including to |
| // binaries that already have an extension such as shared libs) |
| // to find the dwp. |
| ret.push(FileType { |
| // It is important to preserve the existing suffix for |
| // e.g. shared libraries, where the dwp for libfoo.so is |
| // expected to be at libfoo.so.dwp. |
| suffix: format!("{suffix}.dwp"), |
| prefix: prefix.clone(), |
| flavor: FileFlavor::DebugInfo, |
| crate_type: Some(crate_type.clone()), |
| // Likewise, the dwp needs to match the primary artifact's |
| // hyphenation exactly. |
| should_replace_hyphens: crate_type != CrateType::Bin, |
| }) |
| } |
| } |
| |
| Ok(Some(ret)) |
| } |
| |
| fn discover_crate_type(&self, crate_type: &CrateType) -> CargoResult<Option<(String, String)>> { |
| let mut process = self.crate_type_process.clone(); |
| |
| process.arg("--crate-type").arg(crate_type.as_str()); |
| |
| let output = process.exec_with_output().with_context(|| { |
| format!( |
| "failed to run `rustc` to learn about crate-type {} information", |
| crate_type |
| ) |
| })?; |
| |
| let error = str::from_utf8(&output.stderr).unwrap(); |
| let output = str::from_utf8(&output.stdout).unwrap(); |
| parse_crate_type(crate_type, &process, output, error, &mut output.lines()) |
| } |
| |
| /// Returns all the file types generated by rustc for the given mode/target_kind. |
| /// |
| /// The first value is a Vec of file types generated, the second value is |
| /// a list of CrateTypes that are not supported by the given target. |
| pub fn rustc_outputs( |
| &self, |
| mode: CompileMode, |
| target_kind: &TargetKind, |
| target_triple: &str, |
| ) -> CargoResult<(Vec<FileType>, Vec<CrateType>)> { |
| match mode { |
| CompileMode::Build => self.calc_rustc_outputs(target_kind, target_triple), |
| CompileMode::Test | CompileMode::Bench => { |
| match self.file_types(&CrateType::Bin, FileFlavor::Normal, target_triple)? { |
| Some(fts) => Ok((fts, Vec::new())), |
| None => Ok((Vec::new(), vec![CrateType::Bin])), |
| } |
| } |
| CompileMode::Check { .. } => Ok((vec![FileType::new_rmeta()], Vec::new())), |
| CompileMode::Doc { .. } |
| | CompileMode::Doctest |
| | CompileMode::Docscrape |
| | CompileMode::RunCustomBuild => { |
| panic!("asked for rustc output for non-rustc mode") |
| } |
| } |
| } |
| |
| fn calc_rustc_outputs( |
| &self, |
| target_kind: &TargetKind, |
| target_triple: &str, |
| ) -> CargoResult<(Vec<FileType>, Vec<CrateType>)> { |
| let mut unsupported = Vec::new(); |
| let mut result = Vec::new(); |
| let crate_types = target_kind.rustc_crate_types(); |
| for crate_type in &crate_types { |
| let flavor = if crate_type.is_linkable() { |
| FileFlavor::Linkable |
| } else { |
| FileFlavor::Normal |
| }; |
| let file_types = self.file_types(crate_type, flavor, target_triple)?; |
| match file_types { |
| Some(types) => { |
| result.extend(types); |
| } |
| None => { |
| unsupported.push(crate_type.clone()); |
| } |
| } |
| } |
| if !result.is_empty() && !crate_types.iter().any(|ct| ct.requires_upstream_objects()) { |
| // Only add rmeta if pipelining. |
| result.push(FileType::new_rmeta()); |
| } |
| Ok((result, unsupported)) |
| } |
| |
| /// Checks if the debuginfo-split value is supported by this target |
| pub fn supports_debuginfo_split(&self, split: InternedString) -> bool { |
| self.support_split_debuginfo |
| .iter() |
| .any(|sup| sup.as_str() == split.as_str()) |
| } |
| } |
| |
| /// Takes rustc output (using specialized command line args), and calculates the file prefix and |
| /// suffix for the given crate type, or returns `None` if the type is not supported. (e.g., for a |
| /// Rust library like `libcargo.rlib`, we have prefix "lib" and suffix "rlib"). |
| /// |
| /// The caller needs to ensure that the lines object is at the correct line for the given crate |
| /// type: this is not checked. |
| /// |
| /// This function can not handle more than one file per type (with wasm32-unknown-emscripten, there |
| /// are two files for bin (`.wasm` and `.js`)). |
| fn parse_crate_type( |
| crate_type: &CrateType, |
| cmd: &ProcessBuilder, |
| output: &str, |
| error: &str, |
| lines: &mut str::Lines<'_>, |
| ) -> CargoResult<Option<(String, String)>> { |
| let not_supported = error.lines().any(|line| { |
| (line.contains("unsupported crate type") || line.contains("unknown crate type")) |
| && line.contains(&format!("crate type `{}`", crate_type)) |
| }); |
| if not_supported { |
| return Ok(None); |
| } |
| let line = match lines.next() { |
| Some(line) => line, |
| None => anyhow::bail!( |
| "malformed output when learning about crate-type {} information\n{}", |
| crate_type, |
| output_err_info(cmd, output, error) |
| ), |
| }; |
| let mut parts = line.trim().split("___"); |
| let prefix = parts.next().unwrap(); |
| let Some(suffix) = parts.next() else { |
| return error_missing_print_output("file-names", cmd, output, error); |
| }; |
| |
| Ok(Some((prefix.to_string(), suffix.to_string()))) |
| } |
| |
| /// Helper for creating an error message for missing output from a certain `--print` request. |
| fn error_missing_print_output<T>( |
| request: &str, |
| cmd: &ProcessBuilder, |
| stdout: &str, |
| stderr: &str, |
| ) -> CargoResult<T> { |
| let err_info = output_err_info(cmd, stdout, stderr); |
| anyhow::bail!( |
| "output of --print={request} missing when learning about \ |
| target-specific information from rustc\n{err_info}", |
| ) |
| } |
| |
| /// Helper for creating an error message when parsing rustc output fails. |
| fn output_err_info(cmd: &ProcessBuilder, stdout: &str, stderr: &str) -> String { |
| let mut result = format!("command was: {}\n", cmd); |
| if !stdout.is_empty() { |
| result.push_str("\n--- stdout\n"); |
| result.push_str(stdout); |
| } |
| if !stderr.is_empty() { |
| result.push_str("\n--- stderr\n"); |
| result.push_str(stderr); |
| } |
| if stdout.is_empty() && stderr.is_empty() { |
| result.push_str("(no output received)"); |
| } |
| result |
| } |
| |
| /// Compiler flags for either rustc or rustdoc. |
| #[derive(Debug, Copy, Clone)] |
| enum Flags { |
| Rust, |
| Rustdoc, |
| } |
| |
| impl Flags { |
| fn as_key(self) -> &'static str { |
| match self { |
| Flags::Rust => "rustflags", |
| Flags::Rustdoc => "rustdocflags", |
| } |
| } |
| |
| fn as_env(self) -> &'static str { |
| match self { |
| Flags::Rust => "RUSTFLAGS", |
| Flags::Rustdoc => "RUSTDOCFLAGS", |
| } |
| } |
| } |
| |
| /// Acquire extra flags to pass to the compiler from various locations. |
| /// |
| /// The locations are: |
| /// |
| /// - the `CARGO_ENCODED_RUSTFLAGS` environment variable |
| /// - the `RUSTFLAGS` environment variable |
| /// |
| /// then if none of those were found |
| /// |
| /// - `target.*.rustflags` from the config (.cargo/config) |
| /// - `target.cfg(..).rustflags` from the config |
| /// - `host.*.rustflags` from the config if compiling a host artifact or without `--target` |
| /// (requires `-Zhost-config`) |
| /// |
| /// then if none of those were found |
| /// |
| /// - `build.rustflags` from the config |
| /// |
| /// The behavior differs slightly when cross-compiling (or, specifically, when `--target` is |
| /// provided) for artifacts that are always built for the host (plugins, build scripts, ...). |
| /// For those artifacts, _only_ `host.*.rustflags` is respected, and no other configuration |
| /// sources, _regardless of the value of `target-applies-to-host`_. This is counterintuitive, but |
| /// necessary to retain backwards compatibility with older versions of Cargo. |
| fn extra_args( |
| config: &Config, |
| requested_kinds: &[CompileKind], |
| host_triple: &str, |
| target_cfg: Option<&[Cfg]>, |
| kind: CompileKind, |
| flags: Flags, |
| ) -> CargoResult<Vec<String>> { |
| let target_applies_to_host = config.target_applies_to_host()?; |
| |
| // Host artifacts should not generally pick up rustflags from anywhere except [host]. |
| // |
| // The one exception to this is if `target-applies-to-host = true`, which opts into a |
| // particular (inconsistent) past Cargo behavior where host artifacts _do_ pick up rustflags |
| // set elsewhere when `--target` isn't passed. |
| if kind.is_host() { |
| if target_applies_to_host && requested_kinds == [CompileKind::Host] { |
| // This is the past Cargo behavior where we fall back to the same logic as for other |
| // artifacts without --target. |
| } else { |
| // In all other cases, host artifacts just get flags from [host], regardless of |
| // --target. Or, phrased differently, no `--target` behaves the same as `--target |
| // <host>`, and host artifacts are always "special" (they don't pick up `RUSTFLAGS` for |
| // example). |
| return Ok(rustflags_from_host(config, flags, host_triple)?.unwrap_or_else(Vec::new)); |
| } |
| } |
| |
| // All other artifacts pick up the RUSTFLAGS, [target.*], and [build], in that order. |
| // NOTE: It is impossible to have a [host] section and reach this logic with kind.is_host(), |
| // since [host] implies `target-applies-to-host = false`, which always early-returns above. |
| |
| if let Some(rustflags) = rustflags_from_env(config, flags) { |
| Ok(rustflags) |
| } else if let Some(rustflags) = |
| rustflags_from_target(config, host_triple, target_cfg, kind, flags)? |
| { |
| Ok(rustflags) |
| } else if let Some(rustflags) = rustflags_from_build(config, flags)? { |
| Ok(rustflags) |
| } else { |
| Ok(Vec::new()) |
| } |
| } |
| |
| /// Gets compiler flags from environment variables. |
| /// See [`extra_args`] for more. |
| fn rustflags_from_env(config: &Config, flags: Flags) -> Option<Vec<String>> { |
| // First try CARGO_ENCODED_RUSTFLAGS from the environment. |
| // Prefer this over RUSTFLAGS since it's less prone to encoding errors. |
| if let Ok(a) = config.get_env(format!("CARGO_ENCODED_{}", flags.as_env())) { |
| if a.is_empty() { |
| return Some(Vec::new()); |
| } |
| return Some(a.split('\x1f').map(str::to_string).collect()); |
| } |
| |
| // Then try RUSTFLAGS from the environment |
| if let Ok(a) = config.get_env(flags.as_env()) { |
| let args = a |
| .split(' ') |
| .map(str::trim) |
| .filter(|s| !s.is_empty()) |
| .map(str::to_string); |
| return Some(args.collect()); |
| } |
| |
| // No rustflags to be collected from the environment |
| None |
| } |
| |
| /// Gets compiler flags from `[target]` section in the config. |
| /// See [`extra_args`] for more. |
| fn rustflags_from_target( |
| config: &Config, |
| host_triple: &str, |
| target_cfg: Option<&[Cfg]>, |
| kind: CompileKind, |
| flag: Flags, |
| ) -> CargoResult<Option<Vec<String>>> { |
| let mut rustflags = Vec::new(); |
| |
| // Then the target.*.rustflags value... |
| let target = match &kind { |
| CompileKind::Host => host_triple, |
| CompileKind::Target(target) => target.short_name(), |
| }; |
| let key = format!("target.{}.{}", target, flag.as_key()); |
| if let Some(args) = config.get::<Option<StringList>>(&key)? { |
| rustflags.extend(args.as_slice().iter().cloned()); |
| } |
| // ...including target.'cfg(...)'.rustflags |
| if let Some(target_cfg) = target_cfg { |
| config |
| .target_cfgs()? |
| .iter() |
| .filter_map(|(key, cfg)| { |
| match flag { |
| Flags::Rust => cfg |
| .rustflags |
| .as_ref() |
| .map(|rustflags| (key, &rustflags.val)), |
| // `target.cfg(…).rustdocflags` is currently not supported. |
| // In fact, neither is `target.<triple>.rustdocflags`. |
| Flags::Rustdoc => None, |
| } |
| }) |
| .filter(|(key, _rustflags)| CfgExpr::matches_key(key, target_cfg)) |
| .for_each(|(_key, cfg_rustflags)| { |
| rustflags.extend(cfg_rustflags.as_slice().iter().cloned()); |
| }); |
| } |
| |
| if rustflags.is_empty() { |
| Ok(None) |
| } else { |
| Ok(Some(rustflags)) |
| } |
| } |
| |
| /// Gets compiler flags from `[host]` section in the config. |
| /// See [`extra_args`] for more. |
| fn rustflags_from_host( |
| config: &Config, |
| flag: Flags, |
| host_triple: &str, |
| ) -> CargoResult<Option<Vec<String>>> { |
| let target_cfg = config.host_cfg_triple(host_triple)?; |
| let list = match flag { |
| Flags::Rust => &target_cfg.rustflags, |
| Flags::Rustdoc => { |
| // host.rustdocflags is not a thing, since it does not make sense |
| return Ok(None); |
| } |
| }; |
| Ok(list.as_ref().map(|l| l.val.as_slice().to_vec())) |
| } |
| |
| /// Gets compiler flags from `[build]` section in the config. |
| /// See [`extra_args`] for more. |
| fn rustflags_from_build(config: &Config, flag: Flags) -> CargoResult<Option<Vec<String>>> { |
| // Then the `build.rustflags` value. |
| let build = config.build_config()?; |
| let list = match flag { |
| Flags::Rust => &build.rustflags, |
| Flags::Rustdoc => &build.rustdocflags, |
| }; |
| Ok(list.as_ref().map(|l| l.as_slice().to_vec())) |
| } |
| |
| /// Collection of information about `rustc` and the host and target. |
| pub struct RustcTargetData<'cfg> { |
| /// Information about `rustc` itself. |
| pub rustc: Rustc, |
| |
| /// Config |
| pub config: &'cfg Config, |
| requested_kinds: Vec<CompileKind>, |
| |
| /// Build information for the "host", which is information about when |
| /// `rustc` is invoked without a `--target` flag. This is used for |
| /// procedural macros, build scripts, etc. |
| host_config: TargetConfig, |
| /// Information about the host platform. |
| host_info: TargetInfo, |
| |
| /// Build information for targets that we're building for. |
| target_config: HashMap<CompileTarget, TargetConfig>, |
| /// Information about the target platform that we're building for. |
| target_info: HashMap<CompileTarget, TargetInfo>, |
| } |
| |
| impl<'cfg> RustcTargetData<'cfg> { |
| pub fn new( |
| ws: &Workspace<'cfg>, |
| requested_kinds: &[CompileKind], |
| ) -> CargoResult<RustcTargetData<'cfg>> { |
| let config = ws.config(); |
| let rustc = config.load_global_rustc(Some(ws))?; |
| let mut target_config = HashMap::new(); |
| let mut target_info = HashMap::new(); |
| let target_applies_to_host = config.target_applies_to_host()?; |
| let host_info = TargetInfo::new(config, requested_kinds, &rustc, CompileKind::Host)?; |
| let host_config = if target_applies_to_host { |
| config.target_cfg_triple(&rustc.host)? |
| } else { |
| config.host_cfg_triple(&rustc.host)? |
| }; |
| |
| // This is a hack. The unit_dependency graph builder "pretends" that |
| // `CompileKind::Host` is `CompileKind::Target(host)` if the |
| // `--target` flag is not specified. Since the unit_dependency code |
| // needs access to the target config data, create a copy so that it |
| // can be found. See `rebuild_unit_graph_shared` for why this is done. |
| if requested_kinds.iter().any(CompileKind::is_host) { |
| let ct = CompileTarget::new(&rustc.host)?; |
| target_info.insert(ct, host_info.clone()); |
| target_config.insert(ct, config.target_cfg_triple(&rustc.host)?); |
| }; |
| |
| let mut res = RustcTargetData { |
| rustc, |
| config, |
| requested_kinds: requested_kinds.into(), |
| host_config, |
| host_info, |
| target_config, |
| target_info, |
| }; |
| |
| // Get all kinds we currently know about. |
| // |
| // For now, targets can only ever come from the root workspace |
| // units and artifact dependencies, so this |
| // correctly represents all the kinds that can happen. When we have |
| // other ways for targets to appear at places that are not the root units, |
| // we may have to revisit this. |
| fn artifact_targets(package: &Package) -> impl Iterator<Item = CompileKind> + '_ { |
| package |
| .manifest() |
| .dependencies() |
| .iter() |
| .filter_map(|d| d.artifact()?.target()?.to_compile_kind()) |
| } |
| let all_kinds = requested_kinds |
| .iter() |
| .copied() |
| .chain(ws.members().flat_map(|p| { |
| p.manifest() |
| .default_kind() |
| .into_iter() |
| .chain(p.manifest().forced_kind()) |
| .chain(artifact_targets(p)) |
| })); |
| for kind in all_kinds { |
| res.merge_compile_kind(kind)?; |
| } |
| |
| Ok(res) |
| } |
| |
| /// Insert `kind` into our `target_info` and `target_config` members if it isn't present yet. |
| fn merge_compile_kind(&mut self, kind: CompileKind) -> CargoResult<()> { |
| if let CompileKind::Target(target) = kind { |
| if !self.target_config.contains_key(&target) { |
| self.target_config |
| .insert(target, self.config.target_cfg_triple(target.short_name())?); |
| } |
| if !self.target_info.contains_key(&target) { |
| self.target_info.insert( |
| target, |
| TargetInfo::new(self.config, &self.requested_kinds, &self.rustc, kind)?, |
| ); |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Returns a "short" name for the given kind, suitable for keying off |
| /// configuration in Cargo or presenting to users. |
| pub fn short_name<'a>(&'a self, kind: &'a CompileKind) -> &'a str { |
| match kind { |
| CompileKind::Host => &self.rustc.host, |
| CompileKind::Target(target) => target.short_name(), |
| } |
| } |
| |
| /// Whether a dependency should be compiled for the host or target platform, |
| /// specified by `CompileKind`. |
| pub fn dep_platform_activated(&self, dep: &Dependency, kind: CompileKind) -> bool { |
| // If this dependency is only available for certain platforms, |
| // make sure we're only enabling it for that platform. |
| let platform = match dep.platform() { |
| Some(p) => p, |
| None => return true, |
| }; |
| let name = self.short_name(&kind); |
| platform.matches(name, self.cfg(kind)) |
| } |
| |
| /// Gets the list of `cfg`s printed out from the compiler for the specified kind. |
| pub fn cfg(&self, kind: CompileKind) -> &[Cfg] { |
| self.info(kind).cfg() |
| } |
| |
| /// Information about the given target platform, learned by querying rustc. |
| /// |
| /// # Panics |
| /// |
| /// Panics, if the target platform described by `kind` can't be found. |
| /// See [`get_info`](Self::get_info) for a non-panicking alternative. |
| pub fn info(&self, kind: CompileKind) -> &TargetInfo { |
| self.get_info(kind).unwrap() |
| } |
| |
| /// Information about the given target platform, learned by querying rustc. |
| /// |
| /// Returns `None` if the target platform described by `kind` can't be found. |
| pub fn get_info(&self, kind: CompileKind) -> Option<&TargetInfo> { |
| match kind { |
| CompileKind::Host => Some(&self.host_info), |
| CompileKind::Target(s) => self.target_info.get(&s), |
| } |
| } |
| |
| /// Gets the target configuration for a particular host or target. |
| pub fn target_config(&self, kind: CompileKind) -> &TargetConfig { |
| match kind { |
| CompileKind::Host => &self.host_config, |
| CompileKind::Target(s) => &self.target_config[&s], |
| } |
| } |
| |
| /// If a build script is overridden, this returns the `BuildOutput` to use. |
| /// |
| /// `lib_name` is the `links` library name and `kind` is whether it is for |
| /// Host or Target. |
| pub fn script_override(&self, lib_name: &str, kind: CompileKind) -> Option<&BuildOutput> { |
| self.target_config(kind).links_overrides.get(lib_name) |
| } |
| } |
| |
| /// Structure used to deal with Rustdoc fingerprinting |
| #[derive(Debug, Serialize, Deserialize)] |
| pub struct RustDocFingerprint { |
| pub rustc_vv: String, |
| } |
| |
| impl RustDocFingerprint { |
| /// This function checks whether the latest version of `Rustc` used to compile this |
| /// `Workspace`'s docs was the same as the one is currently being used in this `cargo doc` |
| /// call. |
| /// |
| /// In case it's not, it takes care of removing the `doc/` folder as well as overwriting |
| /// the rustdoc fingerprint info in order to guarantee that we won't end up with mixed |
| /// versions of the `js/html/css` files that `rustdoc` autogenerates which do not have |
| /// any versioning. |
| pub fn check_rustdoc_fingerprint(cx: &Context<'_, '_>) -> CargoResult<()> { |
| if cx.bcx.config.cli_unstable().skip_rustdoc_fingerprint { |
| return Ok(()); |
| } |
| let actual_rustdoc_target_data = RustDocFingerprint { |
| rustc_vv: cx.bcx.rustc().verbose_version.clone(), |
| }; |
| |
| let fingerprint_path = cx.files().host_root().join(".rustdoc_fingerprint.json"); |
| let write_fingerprint = || -> CargoResult<()> { |
| paths::write( |
| &fingerprint_path, |
| serde_json::to_string(&actual_rustdoc_target_data)?, |
| ) |
| }; |
| let rustdoc_data = match paths::read(&fingerprint_path) { |
| Ok(rustdoc_data) => rustdoc_data, |
| // If the fingerprint does not exist, do not clear out the doc |
| // directories. Otherwise this ran into problems where projects |
| // like rustbuild were creating the doc directory before running |
| // `cargo doc` in a way that deleting it would break it. |
| Err(_) => return write_fingerprint(), |
| }; |
| match serde_json::from_str::<RustDocFingerprint>(&rustdoc_data) { |
| Ok(fingerprint) => { |
| if fingerprint.rustc_vv == actual_rustdoc_target_data.rustc_vv { |
| return Ok(()); |
| } else { |
| log::debug!( |
| "doc fingerprint changed:\noriginal:\n{}\nnew:\n{}", |
| fingerprint.rustc_vv, |
| actual_rustdoc_target_data.rustc_vv |
| ); |
| } |
| } |
| Err(e) => { |
| log::debug!("could not deserialize {:?}: {}", fingerprint_path, e); |
| } |
| }; |
| // Fingerprint does not match, delete the doc directories and write a new fingerprint. |
| log::debug!( |
| "fingerprint {:?} mismatch, clearing doc directories", |
| fingerprint_path |
| ); |
| cx.bcx |
| .all_kinds |
| .iter() |
| .map(|kind| cx.files().layout(*kind).doc()) |
| .filter(|path| path.exists()) |
| .try_for_each(|path| clean_doc(path))?; |
| write_fingerprint()?; |
| return Ok(()); |
| |
| fn clean_doc(path: &Path) -> CargoResult<()> { |
| let entries = path |
| .read_dir() |
| .with_context(|| format!("failed to read directory `{}`", path.display()))?; |
| for entry in entries { |
| let entry = entry?; |
| // Don't remove hidden files. Rustdoc does not create them, |
| // but the user might have. |
| if entry |
| .file_name() |
| .to_str() |
| .map_or(false, |name| name.starts_with('.')) |
| { |
| continue; |
| } |
| let path = entry.path(); |
| if entry.file_type()?.is_dir() { |
| paths::remove_dir_all(path)?; |
| } else { |
| paths::remove_file(path)?; |
| } |
| } |
| Ok(()) |
| } |
| } |
| } |