blob: de4b197a07f5eac507934bd9f1c096eeb1fb3a80 [file] [edit]
//! 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::compiler::CompileKind;
use crate::compiler::CompileMode;
use crate::compiler::CompileTarget;
use crate::compiler::CrateType;
use crate::compiler::apply_env_config;
use crate::context::{GlobalContext, StringList, TargetConfig};
use crate::util::interning::InternedString;
use crate::util::{CargoResult, Rustc};
use crate::workspace::{Dependency, Package, Target, TargetKind, Workspace};
use anyhow::Context as _;
use cargo_platform::{Cfg, CfgExpr};
use cargo_util::ProcessBuilder;
use serde::Deserialize;
use crate::util::data_structures::HashMap;
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::path::PathBuf;
use std::rc::Rc;
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>,
/// `supports_std` information extracted from `rustc --print=target-spec-json`
pub supports_std: Option<bool>,
/// Supported values for `-Csplit-debuginfo=` flag, queried from rustc
support_split_debuginfo: Vec<String>,
/// 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: Rc<[String]>,
/// Extra flags to pass to `rustdoc`, see [`extra_args`].
pub rustdocflags: Rc<[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,
/// SBOM (Software Bill of Materials pre-cursor) file (e.g. cargo-sbon.json).
Sbom,
/// Unremap file for `-Ztrim-paths` (e.g. `foo.trim-paths.jsonl`).
Unremap,
/// Cross-crate info JSON files generated by rustdoc.
DocParts,
}
/// 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` created 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,
}
}
pub fn output_prefix_suffix(&self, target: &Target) -> (String, String) {
(
format!("{}{}-", self.prefix, target.crate_name()),
self.suffix.clone(),
)
}
}
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.
///
/// When a Workspace is provided,
#[tracing::instrument(skip_all)]
pub fn new(
gctx: &GlobalContext,
requested_kinds: &[CompileKind],
rustc: &Rustc,
kind: CompileKind,
) -> CargoResult<TargetInfo> {
let mut rustflags =
extra_args(gctx, requested_kinds, &rustc.host, None, kind, Flags::Rust)?;
let sysroot = gctx.get_sysroot(rustc)?;
let sysroot_target_libdir = sysroot
.join("lib")
.join("rustlib")
.join(match &kind {
CompileKind::Host => rustc.host.as_str(),
CompileKind::Target(target) => target.short_name(),
})
.join("lib");
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) split-debuginfo
// 2) cfg
//
// Search `--print` to see what we query so far.
let mut process = rustc.workspace_process();
apply_env_config(gctx, &mut process)?;
process
.arg("-")
.arg("--crate-name")
.arg("___")
.arg("--print=file-names")
.args(&rustflags)
.env_remove("RUSTC_LOG");
// Removes `FD_CLOEXEC` set by `jobserver::Client` to pass jobserver
// as environment variables specify.
if let Some(client) = gctx.jobserver_from_env() {
process.inherit_jobserver(client);
}
kind.add_target_arg(&mut process);
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=split-debuginfo");
process.arg("--print=crate-name"); // `___` as a delimiter.
process.arg("--print=cfg");
// parse_crate_type() relies on "unsupported/unknown crate type" error message,
// so make warnings always emitted as warnings.
process.arg("-Wwarnings");
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::default();
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 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(
gctx,
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 {
gctx.shell().warn("non-trivial mutual dependency between target-specific configuration and RUSTFLAGS")?;
}
let mut supports_std: Option<bool> = None;
// The '--print=target-spec-json' is an unstable option of rustc, therefore only
// try to fetch this information if rustc allows nightly features. Additionally,
// to avoid making two rustc queries when not required, only try to fetch the
// target-spec when the '-Zbuild-std' option is passed.
if gctx.cli_unstable().build_std.is_some() {
let mut target_spec_process = rustc.workspace_process();
apply_env_config(gctx, &mut target_spec_process)?;
target_spec_process
.arg("--print=target-spec-json")
.arg("-Zunstable-options")
.args(&rustflags)
.env_remove("RUSTC_LOG");
kind.add_target_arg(&mut target_spec_process);
#[derive(Deserialize)]
struct Metadata {
pub std: Option<bool>,
}
#[derive(Deserialize)]
struct TargetSpec {
pub metadata: Metadata,
}
if let Ok(output) = target_spec_process.output() {
if let Ok(spec) = serde_json::from_slice::<TargetSpec>(&output.stdout) {
supports_std = spec.metadata.std;
}
}
}
return Ok(TargetInfo {
crate_type_process,
crate_types: RefCell::new(map),
sysroot_target_libdir,
rustflags: rustflags.into(),
rustdocflags: extra_args(
gctx,
requested_kinds,
&rustc.host,
Some(&cfg),
kind,
Flags::Rustdoc,
)?
.into(),
cfg,
supports_std,
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 Some((prefix, suffix)) = crate_type_info else {
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 suffix == ".dll"
&& (target_triple.ends_with("windows-gnu")
|| target_triple.ends_with("windows-gnullvm")
|| target_triple.ends_with("cygwin"))
{
// 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") || target_triple.ends_with("-uefi") {
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,
gctx: &GlobalContext,
) -> CargoResult<(Vec<FileType>, Vec<CrateType>)> {
match mode {
CompileMode::Build => self.calc_rustc_outputs(target_kind, target_triple, gctx),
CompileMode::Test => {
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,
gctx: &GlobalContext,
) -> 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() {
if !gctx.should_embed_metadata()
&& crate_types
.iter()
.any(|ct| ct.benefits_from_no_embed_metadata())
{
// Add .rmeta when we apply -Zembed-metadata=no to the unit.
result.push(FileType::new_rmeta());
} else if !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())
}
/// Checks if a target maybe support std.
///
/// If no explicitly stated in target spec json, we treat it as "maybe support".
///
/// This is only useful for `-Zbuild-std` to determine the default set of
/// crates it is going to build.
pub fn maybe_support_std(&self) -> bool {
matches!(self.supports_std, Some(true) | None)
}
}
/// 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 Some(line) = lines.next() else {
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.
///
/// Rules above also applies to rustdoc. Just the key would be `rustdocflags`/`RUSTDOCFLAGS`.
fn extra_args(
gctx: &GlobalContext,
requested_kinds: &[CompileKind],
host_triple: &str,
target_cfg: Option<&[Cfg]>,
kind: CompileKind,
flags: Flags,
) -> CargoResult<Vec<String>> {
if host_artifact_uses_only_host_config(gctx, requested_kinds, kind)? {
return Ok(rustflags_from_host(gctx, 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(gctx, flags) {
Ok(rustflags)
} else if let Some(rustflags) =
rustflags_from_target(gctx, host_triple, target_cfg, kind, flags)?
{
Ok(rustflags)
} else if let Some(rustflags) = rustflags_from_build(gctx, flags)? {
Ok(rustflags)
} else {
Ok(Vec::new())
}
}
/// Gets compiler flags from environment variables.
/// See [`extra_args`] for more.
fn rustflags_from_env(gctx: &GlobalContext, 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) = gctx.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) = gctx.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(
gctx: &GlobalContext,
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) = gctx.get::<Option<StringList>>(&key)? {
rustflags.extend(args.as_slice().iter().cloned());
}
// ...including target.'cfg(...)'.rustflags
if let Some(target_cfg) = target_cfg {
gctx.target_cfgs()?
.iter()
.filter_map(|(key, cfg)| match flag {
Flags::Rust => cfg
.rustflags
.as_ref()
.map(|rustflags| (key, &rustflags.val)),
Flags::Rustdoc => cfg
.rustdocflags
.as_ref()
.map(|rustdocflags| (key, &rustdocflags.val)),
})
.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(
gctx: &GlobalContext,
flag: Flags,
host_triple: &str,
) -> CargoResult<Option<Vec<String>>> {
let target_cfg = gctx.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(gctx: &GlobalContext, flag: Flags) -> CargoResult<Option<Vec<String>>> {
// Then the `build.rustflags` value.
let build = gctx.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()))
}
/// Whether a host artifact must take its configuration solely from `[host]` and ignore `[target]`.
fn host_artifact_uses_only_host_config(
gctx: &GlobalContext,
requested_kinds: &[CompileKind],
kind: CompileKind,
) -> CargoResult<bool> {
let target_applies_to_host = gctx.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(true);
}
}
Ok(false)
}
/// Collection of information about `rustc` and the host and target.
pub struct RustcTargetData<'gctx> {
/// Information about `rustc` itself.
pub rustc: Rustc,
/// Config
pub gctx: &'gctx GlobalContext,
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
/// selecting a linker, and applying link overrides.
///
/// The configuration read into this depends on whether or not
/// `target-applies-to-host=true`.
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<'gctx> RustcTargetData<'gctx> {
#[tracing::instrument(skip_all)]
pub fn new(
ws: &Workspace<'gctx>,
requested_kinds: &[CompileKind],
) -> CargoResult<RustcTargetData<'gctx>> {
let gctx = ws.gctx();
let rustc = gctx.load_global_rustc(Some(ws))?;
let mut target_config = HashMap::default();
let mut target_info = HashMap::default();
let target_applies_to_host = gctx.target_applies_to_host()?;
let host_target = CompileTarget::new(&rustc.host, gctx.cli_unstable().json_target_spec)?;
let host_info = TargetInfo::new(gctx, requested_kinds, &rustc, CompileKind::Host)?;
// This config is used for link overrides and choosing a linker.
let host_config = if target_applies_to_host {
gctx.target_cfg_triple(&rustc.host)?
} else {
gctx.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) {
target_config.insert(host_target, gctx.target_cfg_triple(&rustc.host)?);
// If target_applies_to_host is true, the host_info is the target info,
// otherwise we need to build target info for the target.
if target_applies_to_host {
target_info.insert(host_target, host_info.clone());
} else {
let host_target_info = TargetInfo::new(
gctx,
requested_kinds,
&rustc,
CompileKind::Target(host_target),
)?;
target_info.insert(host_target, host_target_info);
}
};
let mut res = RustcTargetData {
rustc,
gctx,
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.
pub 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.gctx.target_cfg_triple(target.short_name())?);
}
if !self.target_info.contains_key(&target) {
self.target_info.insert(
target,
TargetInfo::new(self.gctx, &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 Some(platform) = dep.platform() else {
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],
}
}
pub fn get_unsupported_std_targets(&self) -> Vec<&str> {
let mut unsupported = Vec::new();
for (target, target_info) in &self.target_info {
if target_info.supports_std == Some(false) {
unsupported.push(target.short_name());
}
}
unsupported
}
pub fn requested_kinds(&self) -> &[CompileKind] {
&self.requested_kinds
}
}