| use std::cell::RefCell; |
| use std::collections::hash_map::{Entry, HashMap}; |
| use std::env; |
| use std::path::PathBuf; |
| use std::str::{self, FromStr}; |
| |
| use crate::core::compiler::CompileKind; |
| use crate::core::TargetKind; |
| use crate::util::config::StringList; |
| use crate::util::{CargoResult, CargoResultExt, Config, ProcessBuilder, Rustc}; |
| use cargo_platform::{Cfg, CfgExpr}; |
| |
| /// Information about the platform target gleaned from querying rustc. |
| /// |
| /// The `BuildContext` keeps two of these, one for the host and one for the |
| /// target. 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<String, Option<(String, String)>>>, |
| /// `cfg` information extracted from `rustc --print=cfg`. |
| cfg: Vec<Cfg>, |
| /// 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 `env_args`. |
| pub rustflags: Vec<String>, |
| /// Extra flags to pass to `rustdoc`, see `env_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 |
| Auxiliary, |
| /// Something you can link against (e.g., a library). |
| Linkable { rmeta: bool }, |
| /// Piece of external debug information (e.g., `.dSYM`/`.pdb` file). |
| DebugInfo, |
| } |
| |
| /// Type of each file generated by a Unit. |
| pub struct FileType { |
| /// The kind of file. |
| pub flavor: FileFlavor, |
| /// 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. |
| /// |
| /// wasm bin targets will generate two files in deps such as |
| /// "web-stuff.js" and "web_stuff.wasm". Note the different usages of "-" |
| /// and "_". This flag indicates that the stem "web-stuff" should be |
| /// converted to "web_stuff". |
| should_replace_hyphens: bool, |
| } |
| |
| impl FileType { |
| pub fn filename(&self, stem: &str) -> String { |
| let stem = if self.should_replace_hyphens { |
| stem.replace("-", "_") |
| } else { |
| stem.to_string() |
| }; |
| format!("{}{}{}", self.prefix, stem, self.suffix) |
| } |
| } |
| |
| impl TargetInfo { |
| pub fn new( |
| config: &Config, |
| requested_kind: CompileKind, |
| rustc: &Rustc, |
| kind: CompileKind, |
| ) -> CargoResult<TargetInfo> { |
| let rustflags = env_args(config, requested_kind, &rustc.host, None, kind, "RUSTFLAGS")?; |
| let mut process = rustc.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: &[&str] = |
| &["bin", "rlib", "dylib", "cdylib", "staticlib", "proc-macro"]; |
| for crate_type in KNOWN_CRATE_TYPES.iter() { |
| process.arg("--crate-type").arg(crate_type); |
| } |
| |
| process.arg("--print=sysroot"); |
| process.arg("--print=cfg"); |
| |
| let (output, error) = rustc |
| .cached_output(&process) |
| .chain_err(|| "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.to_string(), out); |
| } |
| |
| let line = match lines.next() { |
| Some(line) => line, |
| None => failure::bail!( |
| "output of --print=sysroot missing when learning about \ |
| target-specific information from rustc\n{}", |
| output_err_info(&process, &output, &error) |
| ), |
| }; |
| let mut sysroot_host_libdir = PathBuf::from(line); |
| if cfg!(windows) { |
| sysroot_host_libdir.push("bin"); |
| } else { |
| sysroot_host_libdir.push("lib"); |
| } |
| let mut sysroot_target_libdir = PathBuf::from(line); |
| 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 cfg = lines |
| .map(|line| Ok(Cfg::from_str(line)?)) |
| .collect::<CargoResult<Vec<_>>>() |
| .chain_err(|| { |
| format!( |
| "failed to parse the cfg from `rustc --print=cfg`, got:\n{}", |
| output |
| ) |
| })?; |
| |
| Ok(TargetInfo { |
| crate_type_process, |
| crate_types: RefCell::new(map), |
| sysroot_host_libdir, |
| sysroot_target_libdir, |
| // recalculate `rustflags` from above now that we have `cfg` |
| // information |
| rustflags: env_args( |
| config, |
| requested_kind, |
| &rustc.host, |
| Some(&cfg), |
| kind, |
| "RUSTFLAGS", |
| )?, |
| rustdocflags: env_args( |
| config, |
| requested_kind, |
| &rustc.host, |
| Some(&cfg), |
| kind, |
| "RUSTDOCFLAGS", |
| )?, |
| cfg, |
| }) |
| } |
| |
| pub fn cfg(&self) -> &[Cfg] { |
| &self.cfg |
| } |
| |
| pub fn file_types( |
| &self, |
| crate_type: &str, |
| flavor: FileFlavor, |
| kind: &TargetKind, |
| target_triple: &str, |
| ) -> CargoResult<Option<Vec<FileType>>> { |
| let mut crate_types = self.crate_types.borrow_mut(); |
| let entry = crate_types.entry(crate_type.to_string()); |
| 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, |
| should_replace_hyphens: false, |
| }]; |
| |
| // See rust-lang/cargo#4500. |
| if target_triple.ends_with("pc-windows-msvc") |
| && crate_type.ends_with("dylib") |
| && suffix == ".dll" |
| { |
| ret.push(FileType { |
| suffix: ".dll.lib".to_string(), |
| prefix: prefix.clone(), |
| flavor: FileFlavor::Normal, |
| should_replace_hyphens: false, |
| }) |
| } |
| |
| // See rust-lang/cargo#4535. |
| if target_triple.starts_with("wasm32-") && crate_type == "bin" && suffix == ".js" { |
| ret.push(FileType { |
| suffix: ".wasm".to_string(), |
| prefix: prefix.clone(), |
| flavor: FileFlavor::Auxiliary, |
| should_replace_hyphens: true, |
| }) |
| } |
| |
| // See rust-lang/cargo#4490, rust-lang/cargo#4960. |
| // Only uplift debuginfo for binaries. |
| // - Tests are run directly from `target/debug/deps/` with the |
| // metadata hash still in the filename. |
| // - Examples are only uplifted for apple because the symbol file |
| // needs to match the executable file name to be found (i.e., it |
| // needs to remove the hash in the filename). On Windows, the path |
| // to the .pdb with the hash is embedded in the executable. |
| let is_apple = target_triple.contains("-apple-"); |
| if *kind == TargetKind::Bin || (*kind == TargetKind::ExampleBin && is_apple) { |
| if is_apple { |
| ret.push(FileType { |
| suffix: ".dSYM".to_string(), |
| prefix: prefix.clone(), |
| flavor: FileFlavor::DebugInfo, |
| should_replace_hyphens: false, |
| }) |
| } else if target_triple.ends_with("-msvc") { |
| ret.push(FileType { |
| suffix: ".pdb".to_string(), |
| prefix: prefix.clone(), |
| flavor: FileFlavor::DebugInfo, |
| should_replace_hyphens: false, |
| }) |
| } |
| } |
| |
| Ok(Some(ret)) |
| } |
| |
| fn discover_crate_type(&self, crate_type: &str) -> CargoResult<Option<(String, String)>> { |
| let mut process = self.crate_type_process.clone(); |
| |
| process.arg("--crate-type").arg(crate_type); |
| |
| let output = process.exec_with_output().chain_err(|| { |
| 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(); |
| Ok(parse_crate_type( |
| crate_type, |
| &process, |
| output, |
| error, |
| &mut output.lines(), |
| )?) |
| } |
| } |
| |
| /// 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: &str, |
| 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 => failure::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 suffix = match parts.next() { |
| Some(part) => part, |
| None => failure::bail!( |
| "output of --print=file-names has changed in the compiler, cannot parse\n{}", |
| output_err_info(cmd, output, error) |
| ), |
| }; |
| |
| Ok(Some((prefix.to_string(), suffix.to_string()))) |
| } |
| |
| /// 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 |
| } |
| |
| /// Acquire extra flags to pass to the compiler from various locations. |
| /// |
| /// The locations are: |
| /// |
| /// - the `RUSTFLAGS` environment variable |
| /// |
| /// then if this was not found |
| /// |
| /// - `target.*.rustflags` from the manifest (Cargo.toml) |
| /// - `target.cfg(..).rustflags` from the manifest |
| /// |
| /// then if neither of these were found |
| /// |
| /// - `build.rustflags` from the manifest |
| /// |
| /// Note that if a `target` is specified, no args will be passed to host code (plugins, build |
| /// scripts, ...), even if it is the same as the target. |
| fn env_args( |
| config: &Config, |
| requested_kind: CompileKind, |
| host_triple: &str, |
| target_cfg: Option<&[Cfg]>, |
| kind: CompileKind, |
| name: &str, |
| ) -> CargoResult<Vec<String>> { |
| // We *want* to apply RUSTFLAGS only to builds for the |
| // requested target architecture, and not to things like build |
| // scripts and plugins, which may be for an entirely different |
| // architecture. Cargo's present architecture makes it quite |
| // hard to only apply flags to things that are not build |
| // scripts and plugins though, so we do something more hacky |
| // instead to avoid applying the same RUSTFLAGS to multiple targets |
| // arches: |
| // |
| // 1) If --target is not specified we just apply RUSTFLAGS to |
| // all builds; they are all going to have the same target. |
| // |
| // 2) If --target *is* specified then we only apply RUSTFLAGS |
| // to compilation units with the Target kind, which indicates |
| // it was chosen by the --target flag. |
| // |
| // This means that, e.g., even if the specified --target is the |
| // same as the host, build scripts in plugins won't get |
| // RUSTFLAGS. |
| if !requested_kind.is_host() && kind.is_host() { |
| // This is probably a build script or plugin and we're |
| // compiling with --target. In this scenario there are |
| // no rustflags we can apply. |
| return Ok(Vec::new()); |
| } |
| |
| // First try RUSTFLAGS from the environment |
| if let Ok(a) = env::var(name) { |
| let args = a |
| .split(' ') |
| .map(str::trim) |
| .filter(|s| !s.is_empty()) |
| .map(str::to_string); |
| return Ok(args.collect()); |
| } |
| |
| let mut rustflags = Vec::new(); |
| |
| let name = name |
| .chars() |
| .flat_map(|c| c.to_lowercase()) |
| .collect::<String>(); |
| // Then the target.*.rustflags value... |
| let target = match &kind { |
| CompileKind::Host => host_triple, |
| CompileKind::Target(target) => target.short_name(), |
| }; |
| let key = format!("target.{}.{}", target, name); |
| 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 { |
| if let Some(table) = config.get_table("target")? { |
| let cfgs = table |
| .val |
| .keys() |
| .filter(|key| CfgExpr::matches_key(key, target_cfg)); |
| |
| // Note that we may have multiple matching `[target]` sections and |
| // because we're passing flags to the compiler this can affect |
| // cargo's caching and whether it rebuilds. Ensure a deterministic |
| // ordering through sorting for now. We may perhaps one day wish to |
| // ensure a deterministic ordering via the order keys were defined |
| // in files perhaps. |
| let mut cfgs = cfgs.collect::<Vec<_>>(); |
| cfgs.sort(); |
| |
| for n in cfgs { |
| let key = format!("target.{}.{}", n, name); |
| if let Some(args) = config.get::<Option<StringList>>(&key)? { |
| rustflags.extend(args.as_slice().iter().cloned()); |
| } |
| } |
| } |
| } |
| |
| if !rustflags.is_empty() { |
| return Ok(rustflags); |
| } |
| |
| // Then the `build.rustflags` value. |
| let build = config.build_config()?; |
| let list = if name == "rustflags" { |
| &build.rustflags |
| } else { |
| &build.rustdocflags |
| }; |
| if let Some(list) = list { |
| return Ok(list.as_slice().to_vec()); |
| } |
| |
| Ok(Vec::new()) |
| } |