From: Dirkjan Ochtman Date: Sat, 28 Apr 2018 15:33:11 +0000 (+0200) Subject: Extract new build_context module out of context module X-Git-Tag: archive/raspbian/0.35.0-2+rpi1~3^2^2^2^2^2^2^2~22^2~1^2~31^2~2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=509c5b44bf2e633a02a855dd1cd8e01e33b68087;p=cargo.git Extract new build_context module out of context module --- diff --git a/src/cargo/core/compiler/build_context/mod.rs b/src/cargo/core/compiler/build_context/mod.rs new file mode 100644 index 000000000..30708ebc8 --- /dev/null +++ b/src/cargo/core/compiler/build_context/mod.rs @@ -0,0 +1,323 @@ +use std::env; +use std::path::Path; +use std::str::{self, FromStr}; + +use core::profiles::Profiles; +use core::{Dependency, Workspace}; +use core::{Package, PackageId, PackageSet, Resolve}; +use util::errors::CargoResult; +use util::{profile, Cfg, CfgExpr, Config}; + +use super::{BuildConfig, Kind, TargetConfig, Unit}; + +mod target_info; +pub use self::target_info::{FileFlavor, TargetInfo}; + +/// The build context, containing all information about a build task +pub struct BuildContext<'a, 'cfg: 'a> { + /// The workspace the build is for + pub ws: &'a Workspace<'cfg>, + /// The cargo configuration + pub config: &'cfg Config, + /// The dependency graph for our build + pub resolve: &'a Resolve, + pub profiles: &'a Profiles, + pub build_config: &'a BuildConfig, + /// This is a workaround to carry the extra compiler args for either + /// `rustc` or `rustdoc` given on the command-line for the commands `cargo + /// rustc` and `cargo rustdoc`. These commands only support one target, + /// but we don't want the args passed to any dependencies, so we include + /// the `Unit` corresponding to the top-level target. + pub extra_compiler_args: Option<(Unit<'a>, Vec)>, + pub packages: &'a PackageSet<'cfg>, + + pub target_info: TargetInfo, + pub host_info: TargetInfo, + pub incremental_env: Option, +} + +impl<'a, 'cfg> BuildContext<'a, 'cfg> { + pub fn new( + ws: &'a Workspace<'cfg>, + resolve: &'a Resolve, + packages: &'a PackageSet<'cfg>, + config: &'cfg Config, + build_config: &'a BuildConfig, + profiles: &'a Profiles, + extra_compiler_args: Option<(Unit<'a>, Vec)>, + ) -> CargoResult> { + let incremental_env = match env::var("CARGO_INCREMENTAL") { + Ok(v) => Some(v == "1"), + Err(_) => None, + }; + + let (host_info, target_info) = { + let _p = profile::start("BuildContext::probe_target_info"); + debug!("probe_target_info"); + let host_info = TargetInfo::new(config, &build_config, Kind::Host)?; + let target_info = TargetInfo::new(config, &build_config, Kind::Target)?; + (host_info, target_info) + }; + + Ok(BuildContext { + ws, + resolve, + packages, + config, + target_info, + host_info, + build_config, + profiles, + incremental_env, + extra_compiler_args, + }) + } + + pub fn extern_crate_name(&self, unit: &Unit<'a>, dep: &Unit<'a>) -> CargoResult { + let deps = { + let a = unit.pkg.package_id(); + let b = dep.pkg.package_id(); + if a == b { + &[] + } else { + self.resolve.dependencies_listed(a, b) + } + }; + + let crate_name = dep.target.crate_name(); + let mut names = deps.iter() + .map(|d| d.rename().unwrap_or(&crate_name)); + let name = names.next().unwrap_or(&crate_name); + for n in names { + if n == name { + continue + } + bail!("multiple dependencies listed for the same crate must \ + all have the same name, but the dependency on `{}` \ + is listed as having different names", dep.pkg.package_id()); + } + Ok(name.to_string()) + } + + /// Whether a dependency should be compiled for the host or target platform, + /// specified by `Kind`. + pub fn dep_platform_activated(&self, dep: &Dependency, kind: Kind) -> 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, info) = match kind { + Kind::Host => (self.build_config.host_triple(), &self.host_info), + Kind::Target => (self.build_config.target_triple(), &self.target_info), + }; + platform.matches(name, info.cfg()) + } + + /// Gets a package for the given package id. + pub fn get_package(&self, id: &PackageId) -> CargoResult<&'a Package> { + self.packages.get(id) + } + + /// Get the user-specified linker for a particular host or target + pub fn linker(&self, kind: Kind) -> Option<&Path> { + self.target_config(kind).linker.as_ref().map(|s| s.as_ref()) + } + + /// Get the user-specified `ar` program for a particular host or target + pub fn ar(&self, kind: Kind) -> Option<&Path> { + self.target_config(kind).ar.as_ref().map(|s| s.as_ref()) + } + + /// Get the list of cfg printed out from the compiler for the specified kind + pub fn cfg(&self, kind: Kind) -> &[Cfg] { + let info = match kind { + Kind::Host => &self.host_info, + Kind::Target => &self.target_info, + }; + info.cfg().unwrap_or(&[]) + } + + /// Get the target configuration for a particular host or target + fn target_config(&self, kind: Kind) -> &TargetConfig { + match kind { + Kind::Host => &self.build_config.host, + Kind::Target => &self.build_config.target, + } + } + + /// Number of jobs specified for this build + pub fn jobs(&self) -> u32 { + self.build_config.jobs + } + + pub fn rustflags_args(&self, unit: &Unit) -> CargoResult> { + env_args( + self.config, + &self.build_config, + self.info(&unit.kind).cfg(), + unit.kind, + "RUSTFLAGS", + ) + } + + pub fn rustdocflags_args(&self, unit: &Unit) -> CargoResult> { + env_args( + self.config, + &self.build_config, + self.info(&unit.kind).cfg(), + unit.kind, + "RUSTDOCFLAGS", + ) + } + + pub fn show_warnings(&self, pkg: &PackageId) -> bool { + pkg.source_id().is_path() || self.config.extra_verbose() + } + + fn info(&self, kind: &Kind) -> &TargetInfo { + match *kind { + Kind::Host => &self.host_info, + Kind::Target => &self.target_info, + } + } + + pub fn extra_args_for(&self, unit: &Unit<'a>) -> Option<&Vec> { + if let Some((ref args_unit, ref args)) = self.extra_compiler_args { + if args_unit == unit { + return Some(args); + } + } + None + } +} + +/// 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, + build_config: &BuildConfig, + target_cfg: Option<&[Cfg]>, + kind: Kind, + name: &str, +) -> CargoResult> { + // 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. + let compiling_with_target = build_config.requested_target.is_some(); + let is_target_kind = kind == Kind::Target; + + if compiling_with_target && !is_target_kind { + // 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::(); + // Then the target.*.rustflags value... + let target = build_config + .requested_target + .as_ref() + .map(|s| s.as_str()) + .unwrap_or(build_config.host_triple()); + let key = format!("target.{}.{}", target, name); + if let Some(args) = config.get_list_or_split_string(&key)? { + let args = args.val.into_iter(); + rustflags.extend(args); + } + // ...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_map(|t| { + if t.starts_with("cfg(") && t.ends_with(')') { + let cfg = &t[4..t.len() - 1]; + CfgExpr::from_str(cfg).ok().and_then(|c| { + if c.matches(target_cfg) { + Some(t) + } else { + None + } + }) + } else { + None + } + }); + + // 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::>(); + cfgs.sort(); + + for n in cfgs { + let key = format!("target.{}.{}", n, name); + if let Some(args) = config.get_list_or_split_string(&key)? { + let args = args.val.into_iter(); + rustflags.extend(args); + } + } + } + } + + if !rustflags.is_empty() { + return Ok(rustflags); + } + + // Then the build.rustflags value + let key = format!("build.{}", name); + if let Some(args) = config.get_list_or_split_string(&key)? { + let args = args.val.into_iter(); + return Ok(args.collect()); + } + + Ok(Vec::new()) +} diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs new file mode 100644 index 000000000..ff691ff2a --- /dev/null +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -0,0 +1,276 @@ +use std::cell::RefCell; +use std::collections::hash_map::{Entry, HashMap}; +use std::path::PathBuf; +use std::str::{self, FromStr}; + +use super::{env_args, BuildConfig}; +use util::{CargoResult, CargoResultExt, Cfg, Config, ProcessBuilder}; +use core::TargetKind; +use super::Kind; + +#[derive(Clone)] +pub struct TargetInfo { + crate_type_process: Option, + crate_types: RefCell>>, + cfg: Option>, + pub sysroot_libdir: Option, +} + +/// Type of each file generated by a Unit. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum FileFlavor { + /// Not a special file type. + Normal, + /// It is something you can link against (e.g. a library) + Linkable, + /// It is a piece of external debug information (e.g. *.dSYM and *.pdb) + DebugInfo, +} + +pub struct FileType { + pub flavor: FileFlavor, + suffix: String, + prefix: String, + // wasm bin target will generate two files in deps such as + // "web-stuff.js" and "web_stuff.wasm". Note the different usages of + // "-" and "_". should_replace_hyphens is a flag to indicate that + // we need to convert the stem "web-stuff" to "web_stuff", so we + // won't miss "web_stuff.wasm". + 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, build_config: &BuildConfig, kind: Kind) -> CargoResult { + let rustflags = env_args(config, build_config, None, kind, "RUSTFLAGS")?; + let mut process = build_config.rustc.process(); + process + .arg("-") + .arg("--crate-name") + .arg("___") + .arg("--print=file-names") + .args(&rustflags) + .env_remove("RUST_LOG"); + + if kind == Kind::Target { + process.arg("--target").arg(&build_config.target_triple()); + } + + 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); + } + + let mut with_cfg = process.clone(); + with_cfg.arg("--print=sysroot"); + with_cfg.arg("--print=cfg"); + + let mut has_cfg_and_sysroot = true; + let (output, error) = build_config + .rustc + .cached_output(&with_cfg) + .or_else(|_| { + has_cfg_and_sysroot = false; + build_config.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, &error, &mut lines)?; + map.insert(crate_type.to_string(), out); + } + + let mut sysroot_libdir = None; + if has_cfg_and_sysroot { + let line = match lines.next() { + Some(line) => line, + None => bail!( + "output of --print=sysroot missing when learning about \ + target-specific information from rustc" + ), + }; + let mut rustlib = PathBuf::from(line); + if kind == Kind::Host { + if cfg!(windows) { + rustlib.push("bin"); + } else { + rustlib.push("lib"); + } + sysroot_libdir = Some(rustlib); + } else { + rustlib.push("lib"); + rustlib.push("rustlib"); + rustlib.push(build_config.target_triple()); + rustlib.push("lib"); + sysroot_libdir = Some(rustlib); + } + } + + let cfg = if has_cfg_and_sysroot { + Some(lines.map(Cfg::from_str).collect::>()?) + } else { + None + }; + + Ok(TargetInfo { + crate_type_process: Some(crate_type_process), + crate_types: RefCell::new(map), + cfg, + sysroot_libdir, + }) + } + + pub fn cfg(&self) -> Option<&[Cfg]> { + self.cfg.as_ref().map(|v| v.as_ref()) + } + + pub fn file_types( + &self, + crate_type: &str, + flavor: FileFlavor, + kind: &TargetKind, + target_triple: &str, + ) -> CargoResult>> { + 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, + }, + ]; + + // 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, + }) + } + + // 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::Normal, + should_replace_hyphens: true, + }) + } + + // rust-lang/cargo#4490, rust-lang/cargo#4960 + // - only uplift debuginfo for binaries. + // tests are run directly from target/debug/deps/ + // and examples are inside target/debug/examples/ which already have symbols next to them + // so no need to do anything. + if *kind == TargetKind::Bin { + if target_triple.contains("-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> { + let mut process = self.crate_type_process.clone().unwrap(); + + 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, 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, prefix = "lib", 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 1 file per type (with wasm32-unknown-emscripten, there +// are 2 files for bin (.wasm and .js)) +fn parse_crate_type( + crate_type: &str, + error: &str, + lines: &mut str::Lines, +) -> CargoResult> { + let not_supported = error.lines().any(|line| { + (line.contains("unsupported crate type") || line.contains("unknown crate type")) + && line.contains(crate_type) + }); + if not_supported { + return Ok(None); + } + let line = match lines.next() { + Some(line) => line, + None => bail!( + "malformed output when learning about \ + crate-type {} information", + crate_type + ), + }; + let mut parts = line.trim().split("___"); + let prefix = parts.next().unwrap(); + let suffix = match parts.next() { + Some(part) => part, + None => bail!( + "output of --print=file-names has changed in \ + the compiler, cannot parse" + ), + }; + + Ok(Some((prefix.to_string(), suffix.to_string()))) +} diff --git a/src/cargo/core/compiler/context/mod.rs b/src/cargo/core/compiler/context/mod.rs index 0eda1caf8..b9846f5c1 100644 --- a/src/cargo/core/compiler/context/mod.rs +++ b/src/cargo/core/compiler/context/mod.rs @@ -1,26 +1,22 @@ #![allow(deprecated)] use std::collections::{HashMap, HashSet}; -use std::env; -use std::path::{Path, PathBuf}; -use std::str::{self, FromStr}; +use std::path::PathBuf; use std::sync::Arc; use jobserver::Client; -use core::profiles::{Profile, Profiles}; -use core::{Dependency, Workspace}; -use core::{Package, PackageId, PackageSet, Resolve, Target}; +use core::{Package, PackageId, Target}; +use core::profiles::Profile; use ops::CompileMode; use util::errors::{CargoResult, CargoResultExt}; -use util::{internal, profile, Cfg, CfgExpr, Config}; +use util::{internal, profile, Config}; use super::custom_build::{self, BuildDeps, BuildScripts, BuildState}; use super::fingerprint::Fingerprint; use super::job_queue::JobQueue; use super::layout::Layout; use super::links::Links; -use super::TargetConfig; -use super::{BuildConfig, Compilation, Executor, Kind}; +use super::{BuildContext, Compilation, Executor, FileFlavor, Kind}; mod unit_dependencies; use self::unit_dependencies::build_unit_dependencies; @@ -29,9 +25,6 @@ mod compilation_files; pub use self::compilation_files::Metadata; use self::compilation_files::{CompilationFiles, OutputFile}; -mod target_info; -pub use self::target_info::{FileFlavor, TargetInfo}; - /// All information needed to define a Unit. /// /// A unit is an object that has enough information so that cargo knows how to build it. @@ -70,186 +63,6 @@ pub struct Unit<'a> { pub mode: CompileMode, } -/// The build context, containing all information about a build task -pub struct BuildContext<'a, 'cfg: 'a> { - /// The workspace the build is for - pub ws: &'a Workspace<'cfg>, - /// The cargo configuration - pub config: &'cfg Config, - /// The dependency graph for our build - pub resolve: &'a Resolve, - pub profiles: &'a Profiles, - pub build_config: &'a BuildConfig, - /// This is a workaround to carry the extra compiler args for either - /// `rustc` or `rustdoc` given on the command-line for the commands `cargo - /// rustc` and `cargo rustdoc`. These commands only support one target, - /// but we don't want the args passed to any dependencies, so we include - /// the `Unit` corresponding to the top-level target. - extra_compiler_args: Option<(Unit<'a>, Vec)>, - pub packages: &'a PackageSet<'cfg>, - - target_info: TargetInfo, - host_info: TargetInfo, - incremental_env: Option, -} - -impl<'a, 'cfg> BuildContext<'a, 'cfg> { - pub fn new( - ws: &'a Workspace<'cfg>, - resolve: &'a Resolve, - packages: &'a PackageSet<'cfg>, - config: &'cfg Config, - build_config: &'a BuildConfig, - profiles: &'a Profiles, - extra_compiler_args: Option<(Unit<'a>, Vec)>, - ) -> CargoResult> { - let incremental_env = match env::var("CARGO_INCREMENTAL") { - Ok(v) => Some(v == "1"), - Err(_) => None, - }; - - let (host_info, target_info) = { - let _p = profile::start("BuildContext::probe_target_info"); - debug!("probe_target_info"); - let host_info = TargetInfo::new(config, &build_config, Kind::Host)?; - let target_info = TargetInfo::new(config, &build_config, Kind::Target)?; - (host_info, target_info) - }; - - Ok(BuildContext { - ws, - resolve, - packages, - config, - target_info, - host_info, - build_config, - profiles, - incremental_env, - extra_compiler_args, - }) - } - - pub fn extern_crate_name(&self, unit: &Unit<'a>, dep: &Unit<'a>) -> CargoResult { - let deps = { - let a = unit.pkg.package_id(); - let b = dep.pkg.package_id(); - if a == b { - &[] - } else { - self.resolve.dependencies_listed(a, b) - } - }; - - let crate_name = dep.target.crate_name(); - let mut names = deps.iter() - .map(|d| d.rename().unwrap_or(&crate_name)); - let name = names.next().unwrap_or(&crate_name); - for n in names { - if n == name { - continue - } - bail!("multiple dependencies listed for the same crate must \ - all have the same name, but the dependency on `{}` \ - is listed as having different names", dep.pkg.package_id()); - } - Ok(name.to_string()) - } - - /// Whether a dependency should be compiled for the host or target platform, - /// specified by `Kind`. - fn dep_platform_activated(&self, dep: &Dependency, kind: Kind) -> 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, info) = match kind { - Kind::Host => (self.build_config.host_triple(), &self.host_info), - Kind::Target => (self.build_config.target_triple(), &self.target_info), - }; - platform.matches(name, info.cfg()) - } - - /// Gets a package for the given package id. - pub fn get_package(&self, id: &PackageId) -> CargoResult<&'a Package> { - self.packages.get(id) - } - - /// Get the user-specified linker for a particular host or target - pub fn linker(&self, kind: Kind) -> Option<&Path> { - self.target_config(kind).linker.as_ref().map(|s| s.as_ref()) - } - - /// Get the user-specified `ar` program for a particular host or target - pub fn ar(&self, kind: Kind) -> Option<&Path> { - self.target_config(kind).ar.as_ref().map(|s| s.as_ref()) - } - - /// Get the list of cfg printed out from the compiler for the specified kind - pub fn cfg(&self, kind: Kind) -> &[Cfg] { - let info = match kind { - Kind::Host => &self.host_info, - Kind::Target => &self.target_info, - }; - info.cfg().unwrap_or(&[]) - } - - /// Get the target configuration for a particular host or target - fn target_config(&self, kind: Kind) -> &TargetConfig { - match kind { - Kind::Host => &self.build_config.host, - Kind::Target => &self.build_config.target, - } - } - - /// Number of jobs specified for this build - pub fn jobs(&self) -> u32 { - self.build_config.jobs - } - - pub fn rustflags_args(&self, unit: &Unit) -> CargoResult> { - env_args( - self.config, - &self.build_config, - self.info(&unit.kind).cfg(), - unit.kind, - "RUSTFLAGS", - ) - } - - pub fn rustdocflags_args(&self, unit: &Unit) -> CargoResult> { - env_args( - self.config, - &self.build_config, - self.info(&unit.kind).cfg(), - unit.kind, - "RUSTDOCFLAGS", - ) - } - - pub fn show_warnings(&self, pkg: &PackageId) -> bool { - pkg.source_id().is_path() || self.config.extra_verbose() - } - - fn info(&self, kind: &Kind) -> &TargetInfo { - match *kind { - Kind::Host => &self.host_info, - Kind::Target => &self.target_info, - } - } - - pub fn extra_args_for(&self, unit: &Unit<'a>) -> Option<&Vec> { - if let Some((ref args_unit, ref args)) = self.extra_compiler_args { - if args_unit == unit { - return Some(args); - } - } - None - } -} - pub struct Context<'a, 'cfg: 'a> { pub bcx: &'a BuildContext<'a, 'cfg>, pub compilation: Compilation<'cfg>, @@ -617,132 +430,3 @@ impl<'a, 'cfg> Context<'a, 'cfg> { Ok(vec!["-C".to_string(), format!("incremental={}", dir)]) } } - -/// 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, - build_config: &BuildConfig, - target_cfg: Option<&[Cfg]>, - kind: Kind, - name: &str, -) -> CargoResult> { - // 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. - let compiling_with_target = build_config.requested_target.is_some(); - let is_target_kind = kind == Kind::Target; - - if compiling_with_target && !is_target_kind { - // 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::(); - // Then the target.*.rustflags value... - let target = build_config - .requested_target - .as_ref() - .map(|s| s.as_str()) - .unwrap_or(build_config.host_triple()); - let key = format!("target.{}.{}", target, name); - if let Some(args) = config.get_list_or_split_string(&key)? { - let args = args.val.into_iter(); - rustflags.extend(args); - } - // ...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_map(|t| { - if t.starts_with("cfg(") && t.ends_with(')') { - let cfg = &t[4..t.len() - 1]; - CfgExpr::from_str(cfg).ok().and_then(|c| { - if c.matches(target_cfg) { - Some(t) - } else { - None - } - }) - } else { - None - } - }); - - // 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::>(); - cfgs.sort(); - - for n in cfgs { - let key = format!("target.{}.{}", n, name); - if let Some(args) = config.get_list_or_split_string(&key)? { - let args = args.val.into_iter(); - rustflags.extend(args); - } - } - } - } - - if !rustflags.is_empty() { - return Ok(rustflags); - } - - // Then the build.rustflags value - let key = format!("build.{}", name); - if let Some(args) = config.get_list_or_split_string(&key)? { - let args = args.val.into_iter(); - return Ok(args.collect()); - } - - Ok(Vec::new()) -} diff --git a/src/cargo/core/compiler/context/target_info.rs b/src/cargo/core/compiler/context/target_info.rs deleted file mode 100644 index ff691ff2a..000000000 --- a/src/cargo/core/compiler/context/target_info.rs +++ /dev/null @@ -1,276 +0,0 @@ -use std::cell::RefCell; -use std::collections::hash_map::{Entry, HashMap}; -use std::path::PathBuf; -use std::str::{self, FromStr}; - -use super::{env_args, BuildConfig}; -use util::{CargoResult, CargoResultExt, Cfg, Config, ProcessBuilder}; -use core::TargetKind; -use super::Kind; - -#[derive(Clone)] -pub struct TargetInfo { - crate_type_process: Option, - crate_types: RefCell>>, - cfg: Option>, - pub sysroot_libdir: Option, -} - -/// Type of each file generated by a Unit. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum FileFlavor { - /// Not a special file type. - Normal, - /// It is something you can link against (e.g. a library) - Linkable, - /// It is a piece of external debug information (e.g. *.dSYM and *.pdb) - DebugInfo, -} - -pub struct FileType { - pub flavor: FileFlavor, - suffix: String, - prefix: String, - // wasm bin target will generate two files in deps such as - // "web-stuff.js" and "web_stuff.wasm". Note the different usages of - // "-" and "_". should_replace_hyphens is a flag to indicate that - // we need to convert the stem "web-stuff" to "web_stuff", so we - // won't miss "web_stuff.wasm". - 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, build_config: &BuildConfig, kind: Kind) -> CargoResult { - let rustflags = env_args(config, build_config, None, kind, "RUSTFLAGS")?; - let mut process = build_config.rustc.process(); - process - .arg("-") - .arg("--crate-name") - .arg("___") - .arg("--print=file-names") - .args(&rustflags) - .env_remove("RUST_LOG"); - - if kind == Kind::Target { - process.arg("--target").arg(&build_config.target_triple()); - } - - 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); - } - - let mut with_cfg = process.clone(); - with_cfg.arg("--print=sysroot"); - with_cfg.arg("--print=cfg"); - - let mut has_cfg_and_sysroot = true; - let (output, error) = build_config - .rustc - .cached_output(&with_cfg) - .or_else(|_| { - has_cfg_and_sysroot = false; - build_config.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, &error, &mut lines)?; - map.insert(crate_type.to_string(), out); - } - - let mut sysroot_libdir = None; - if has_cfg_and_sysroot { - let line = match lines.next() { - Some(line) => line, - None => bail!( - "output of --print=sysroot missing when learning about \ - target-specific information from rustc" - ), - }; - let mut rustlib = PathBuf::from(line); - if kind == Kind::Host { - if cfg!(windows) { - rustlib.push("bin"); - } else { - rustlib.push("lib"); - } - sysroot_libdir = Some(rustlib); - } else { - rustlib.push("lib"); - rustlib.push("rustlib"); - rustlib.push(build_config.target_triple()); - rustlib.push("lib"); - sysroot_libdir = Some(rustlib); - } - } - - let cfg = if has_cfg_and_sysroot { - Some(lines.map(Cfg::from_str).collect::>()?) - } else { - None - }; - - Ok(TargetInfo { - crate_type_process: Some(crate_type_process), - crate_types: RefCell::new(map), - cfg, - sysroot_libdir, - }) - } - - pub fn cfg(&self) -> Option<&[Cfg]> { - self.cfg.as_ref().map(|v| v.as_ref()) - } - - pub fn file_types( - &self, - crate_type: &str, - flavor: FileFlavor, - kind: &TargetKind, - target_triple: &str, - ) -> CargoResult>> { - 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, - }, - ]; - - // 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, - }) - } - - // 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::Normal, - should_replace_hyphens: true, - }) - } - - // rust-lang/cargo#4490, rust-lang/cargo#4960 - // - only uplift debuginfo for binaries. - // tests are run directly from target/debug/deps/ - // and examples are inside target/debug/examples/ which already have symbols next to them - // so no need to do anything. - if *kind == TargetKind::Bin { - if target_triple.contains("-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> { - let mut process = self.crate_type_process.clone().unwrap(); - - 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, 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, prefix = "lib", 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 1 file per type (with wasm32-unknown-emscripten, there -// are 2 files for bin (.wasm and .js)) -fn parse_crate_type( - crate_type: &str, - error: &str, - lines: &mut str::Lines, -) -> CargoResult> { - let not_supported = error.lines().any(|line| { - (line.contains("unsupported crate type") || line.contains("unknown crate type")) - && line.contains(crate_type) - }); - if not_supported { - return Ok(None); - } - let line = match lines.next() { - Some(line) => line, - None => bail!( - "malformed output when learning about \ - crate-type {} information", - crate_type - ), - }; - let mut parts = line.trim().split("___"); - let prefix = parts.next().unwrap(); - let suffix = match parts.next() { - Some(part) => part, - None => bail!( - "output of --print=file-names has changed in \ - the compiler, cannot parse" - ), - }; - - Ok(Some((prefix.to_string(), suffix.to_string()))) -} diff --git a/src/cargo/core/compiler/fingerprint.rs b/src/cargo/core/compiler/fingerprint.rs index 6eba51405..78b18af26 100644 --- a/src/cargo/core/compiler/fingerprint.rs +++ b/src/cargo/core/compiler/fingerprint.rs @@ -15,7 +15,7 @@ use util::errors::{CargoResult, CargoResultExt}; use util::paths; use util::{internal, profile, Dirty, Fresh, Freshness}; -use super::context::{BuildContext, Context, FileFlavor, Unit}; +use super::{Context, BuildContext, FileFlavor, Unit}; use super::custom_build::BuildDeps; use super::job::Work; diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 2becebccd..845d3d2c4 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -23,11 +23,13 @@ use self::job_queue::JobQueue; use self::output_depinfo::output_depinfo; +pub use self::build_context::{BuildContext, FileFlavor, TargetInfo}; pub use self::compilation::Compilation; -pub use self::context::{BuildContext, Context, FileFlavor, TargetInfo, Unit}; +pub use self::context::{Context, Unit}; pub use self::custom_build::{BuildMap, BuildOutput, BuildScripts}; pub use self::layout::is_bad_artifact_name; +mod build_context; mod compilation; mod context; mod custom_build;