From: Aleksey Kladov Date: Fri, 23 Mar 2018 11:51:32 +0000 (+0300) Subject: Refactor context to extract dependencies calculation to a separate mod X-Git-Tag: archive/raspbian/0.35.0-2+rpi1~3^2^2^2^2^2^2^2~22^2~2^2~10^2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=1b63fcf74e4049669ebf4073886086335905afd7;p=cargo.git Refactor context to extract dependencies calculation to a separate mod --- diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 89939e02d..7f7d96225 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -100,6 +100,7 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> { } cx.probe_target_info()?; + cx.build_unit_dependencies(&units)?; for unit in units.iter() { rm_rf(&cx.fingerprint_dir(unit), config)?; diff --git a/src/cargo/ops/cargo_rustc/context.rs b/src/cargo/ops/cargo_rustc/context.rs deleted file mode 100644 index cdcfe414f..000000000 --- a/src/cargo/ops/cargo_rustc/context.rs +++ /dev/null @@ -1,1378 +0,0 @@ -#![allow(deprecated)] - -use std::collections::{HashMap, HashSet}; -use std::collections::hash_map::Entry; -use std::env; -use std::fmt; -use std::hash::{Hash, Hasher, SipHasher}; -use std::path::{Path, PathBuf}; -use std::str::{self, FromStr}; -use std::sync::Arc; -use std::cell::RefCell; - -use jobserver::Client; - -use core::{Package, PackageId, PackageSet, Profile, Resolve, Target}; -use core::{Dependency, Profiles, TargetKind, Workspace}; -use core::dependency::Kind as DepKind; -use util::{self, internal, profile, Cfg, CfgExpr, Config, ProcessBuilder}; -use util::errors::{CargoResult, CargoResultExt}; - -use super::TargetConfig; -use super::custom_build::{BuildDeps, BuildScripts, BuildState}; -use super::fingerprint::Fingerprint; -use super::layout::Layout; -use super::links::Links; -use super::{BuildConfig, Compilation, Kind}; - -/// All information needed to define a Unit. -/// -/// A unit is an object that has enough information so that cargo knows how to build it. -/// For example, if your project has dependencies, then every dependency will be built as a library -/// unit. If your project is a library, then it will be built as a library unit as well, or if it -/// is a binary with `main.rs`, then a binary will be output. There are also separate unit types -/// for `test`ing and `check`ing, amongst others. -/// -/// The unit also holds information about all possible metadata about the package in `pkg`. -/// -/// A unit needs to know extra information in addition to the type and root source file. For -/// example, it needs to know the target architecture (OS, chip arch etc.) and it needs to know -/// whether you want a debug or release build. There is enough information in this struct to figure -/// all that out. -#[derive(Clone, Copy, Eq, PartialEq, Hash)] -pub struct Unit<'a> { - /// Information about available targets, which files to include/exclude, etc. Basically stuff in - /// `Cargo.toml`. - pub pkg: &'a Package, - /// Information about the specific target to build, out of the possible targets in `pkg`. Not - /// to be confused with *target-triple* (or *target architecture* ...), the target arch for a - /// build. - pub target: &'a Target, - /// The profile contains information about *how* the build should be run, including debug - /// level, extra args to pass to rustc, etc. - pub profile: &'a Profile, - /// Whether this compilation unit is for the host or target architecture. - /// - /// For example, when - /// cross compiling and using a custom build script, the build script needs to be compiled for - /// the host architecture so the host rustc can use it (when compiling to the target - /// architecture). - pub kind: Kind, -} - -/// Type of each file generated by a Unit. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum TargetFileType { - /// 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, -} - -/// The build context, containing all information about a build task -pub struct Context<'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, - /// Information on the compilation output - pub compilation: Compilation<'cfg>, - pub packages: &'a PackageSet<'cfg>, - pub build_state: Arc, - pub build_script_overridden: HashSet<(PackageId, Kind)>, - pub build_explicit_deps: HashMap, BuildDeps>, - pub fingerprints: HashMap, Arc>, - pub compiled: HashSet>, - pub build_config: BuildConfig, - pub build_scripts: HashMap, Arc>, - pub links: Links<'a>, - pub used_in_plugin: HashSet>, - pub jobserver: Client, - - /// The target directory layout for the host (and target if it is the same as host) - host: Layout, - /// The target directory layout for the target (if different from then host) - target: Option, - target_info: TargetInfo, - host_info: TargetInfo, - profiles: &'a Profiles, - incremental_env: Option, - - /// For each Unit, a list all files produced as a triple of - /// - /// - File name that will be produced by the build process (in `deps`) - /// - If it should be linked into `target`, and what it should be called (e.g. without - /// metadata). - /// - Type of the file (library / debug symbol / else) - target_filenames: HashMap, Arc, TargetFileType)>>>, - target_metadatas: HashMap, Option>, -} - -#[derive(Clone, Default)] -struct TargetInfo { - crate_type_process: Option, - crate_types: RefCell>>, - cfg: Option>, -} - -impl TargetInfo { - 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())?) - } -} - -#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct Metadata(u64); - -impl<'a, 'cfg> Context<'a, 'cfg> { - pub fn new( - ws: &'a Workspace<'cfg>, - resolve: &'a Resolve, - packages: &'a PackageSet<'cfg>, - config: &'cfg Config, - build_config: BuildConfig, - profiles: &'a Profiles, - ) -> CargoResult> { - let dest = if build_config.release { - "release" - } else { - "debug" - }; - let host_layout = Layout::new(ws, None, dest)?; - let target_layout = match build_config.requested_target.as_ref() { - Some(target) => Some(Layout::new(ws, Some(target), dest)?), - None => None, - }; - - let incremental_env = match env::var("CARGO_INCREMENTAL") { - Ok(v) => Some(v == "1"), - Err(_) => None, - }; - - // Load up the jobserver that we'll use to manage our parallelism. This - // is the same as the GNU make implementation of a jobserver, and - // intentionally so! It's hoped that we can interact with GNU make and - // all share the same jobserver. - // - // Note that if we don't have a jobserver in our environment then we - // create our own, and we create it with `n-1` tokens because one token - // is ourself, a running process. - let jobserver = match config.jobserver_from_env() { - Some(c) => c.clone(), - None => Client::new(build_config.jobs as usize - 1) - .chain_err(|| "failed to create jobserver")?, - }; - - Ok(Context { - ws, - host: host_layout, - target: target_layout, - resolve, - packages, - config, - target_info: TargetInfo::default(), - host_info: TargetInfo::default(), - compilation: Compilation::new(config), - build_state: Arc::new(BuildState::new(&build_config)), - build_config, - fingerprints: HashMap::new(), - profiles, - compiled: HashSet::new(), - build_scripts: HashMap::new(), - build_explicit_deps: HashMap::new(), - links: Links::new(), - used_in_plugin: HashSet::new(), - incremental_env, - jobserver, - build_script_overridden: HashSet::new(), - - // TODO: Pre-Calculate these with a topo-sort, rather than lazy-calculating - target_filenames: HashMap::new(), - target_metadatas: HashMap::new(), - }) - } - - /// Prepare this context, ensuring that all filesystem directories are in - /// place. - pub fn prepare(&mut self) -> CargoResult<()> { - let _p = profile::start("preparing layout"); - - self.host - .prepare() - .chain_err(|| internal("couldn't prepare build directories"))?; - if let Some(ref mut target) = self.target { - target - .prepare() - .chain_err(|| internal("couldn't prepare build directories"))?; - } - - self.compilation.host_deps_output = self.host.deps().to_path_buf(); - - let layout = self.target.as_ref().unwrap_or(&self.host); - self.compilation.root_output = layout.dest().to_path_buf(); - self.compilation.deps_output = layout.deps().to_path_buf(); - Ok(()) - } - - /// Ensure that we've collected all target-specific information to compile - /// all the units mentioned in `units`. - pub fn probe_target_info(&mut self) -> CargoResult<()> { - debug!("probe_target_info"); - self.probe_target_info_kind(Kind::Target)?; - if self.requested_target().is_none() { - self.host_info = self.target_info.clone(); - } else { - self.probe_target_info_kind(Kind::Host)?; - } - Ok(()) - } - - fn probe_target_info_kind(&mut self, kind: Kind) -> CargoResult<()> { - let rustflags = env_args( - self.config, - &self.build_config, - self.info(&kind), - kind, - "RUSTFLAGS", - )?; - let mut process = self.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(&self.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 = with_cfg - .exec_with_output() - .or_else(|_| { - has_cfg_and_sysroot = false; - process.exec_with_output() - }) - .chain_err(|| "failed to run `rustc` to learn about target-specific information")?; - - let error = str::from_utf8(&output.stderr).unwrap(); - let output = str::from_utf8(&output.stdout).unwrap(); - 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); - } - - 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"); - } - self.compilation.host_dylib_path = Some(rustlib); - } else { - rustlib.push("lib"); - rustlib.push("rustlib"); - rustlib.push(self.target_triple()); - rustlib.push("lib"); - self.compilation.target_dylib_path = Some(rustlib); - } - } - - let cfg = if has_cfg_and_sysroot { - Some(lines.map(Cfg::from_str).collect::>()?) - } else { - None - }; - - let info = match kind { - Kind::Target => &mut self.target_info, - Kind::Host => &mut self.host_info, - }; - info.crate_type_process = Some(crate_type_process); - info.crate_types = RefCell::new(map); - info.cfg = cfg; - Ok(()) - } - - /// Builds up the `used_in_plugin` internal to this context from the list of - /// top-level units. - /// - /// This will recursively walk `units` and all of their dependencies to - /// determine which crate are going to be used in plugins or not. - pub fn build_used_in_plugin_map(&mut self, units: &[Unit<'a>]) -> CargoResult<()> { - let mut visited = HashSet::new(); - for unit in units { - self.walk_used_in_plugin_map(unit, unit.target.for_host(), &mut visited)?; - } - Ok(()) - } - - fn walk_used_in_plugin_map( - &mut self, - unit: &Unit<'a>, - is_plugin: bool, - visited: &mut HashSet<(Unit<'a>, bool)>, - ) -> CargoResult<()> { - if !visited.insert((*unit, is_plugin)) { - return Ok(()); - } - if is_plugin { - self.used_in_plugin.insert(*unit); - } - for unit in self.dep_targets(unit)? { - self.walk_used_in_plugin_map(&unit, is_plugin || unit.target.for_host(), visited)?; - } - Ok(()) - } - - /// Returns the appropriate directory layout for either a plugin or not. - fn layout(&self, kind: Kind) -> &Layout { - match kind { - Kind::Host => &self.host, - Kind::Target => self.target.as_ref().unwrap_or(&self.host), - } - } - - /// Returns the directories where Rust crate dependencies are found for the - /// specified unit. - pub fn deps_dir(&self, unit: &Unit) -> &Path { - self.layout(unit.kind).deps() - } - - /// Returns the directory for the specified unit where fingerprint - /// information is stored. - pub fn fingerprint_dir(&mut self, unit: &Unit<'a>) -> PathBuf { - let dir = self.pkg_dir(unit); - self.layout(unit.kind).fingerprint().join(dir) - } - - /// Returns the appropriate directory layout for either a plugin or not. - pub fn build_script_dir(&mut self, unit: &Unit<'a>) -> PathBuf { - assert!(unit.target.is_custom_build()); - assert!(!unit.profile.run_custom_build); - let dir = self.pkg_dir(unit); - self.layout(Kind::Host).build().join(dir) - } - - /// Returns the appropriate directory layout for either a plugin or not. - pub fn build_script_out_dir(&mut self, unit: &Unit<'a>) -> PathBuf { - assert!(unit.target.is_custom_build()); - assert!(unit.profile.run_custom_build); - let dir = self.pkg_dir(unit); - self.layout(unit.kind).build().join(dir).join("out") - } - - pub fn host_deps(&self) -> &Path { - self.host.deps() - } - - /// Return the root of the build output tree - pub fn target_root(&self) -> &Path { - self.host.dest() - } - - /// Returns the appropriate output directory for the specified package and - /// target. - pub fn out_dir(&mut self, unit: &Unit<'a>) -> PathBuf { - if unit.profile.doc { - self.layout(unit.kind).root().parent().unwrap().join("doc") - } else if unit.target.is_custom_build() { - self.build_script_dir(unit) - } else if unit.target.is_example() { - self.layout(unit.kind).examples().to_path_buf() - } else { - self.deps_dir(unit).to_path_buf() - } - } - - fn pkg_dir(&mut self, unit: &Unit<'a>) -> String { - let name = unit.pkg.package_id().name(); - match self.target_metadata(unit) { - Some(meta) => format!("{}-{}", name, meta), - None => format!("{}-{}", name, self.target_short_hash(unit)), - } - } - - /// Return the host triple for this context - pub fn host_triple(&self) -> &str { - &self.build_config.host_triple - } - - /// Return the target triple which this context is targeting. - pub fn target_triple(&self) -> &str { - self.requested_target() - .unwrap_or_else(|| self.host_triple()) - } - - /// Requested (not actual) target for the build - pub fn requested_target(&self) -> Option<&str> { - self.build_config.requested_target.as_ref().map(|s| &s[..]) - } - - /// Get the short hash based only on the PackageId - /// Used for the metadata when target_metadata returns None - pub fn target_short_hash(&self, unit: &Unit) -> String { - let hashable = unit.pkg.package_id().stable_hash(self.ws.root()); - util::short_hash(&hashable) - } - - /// Get the metadata for a target in a specific profile - /// We build to the path: "{filename}-{target_metadata}" - /// We use a linking step to link/copy to a predictable filename - /// like `target/debug/libfoo.{a,so,rlib}` and such. - pub fn target_metadata(&mut self, unit: &Unit<'a>) -> Option { - if let Some(cache) = self.target_metadatas.get(unit) { - return cache.clone(); - } - - let metadata = self.calc_target_metadata(unit); - self.target_metadatas.insert(*unit, metadata.clone()); - metadata - } - - fn calc_target_metadata(&mut self, unit: &Unit<'a>) -> Option { - // No metadata for dylibs because of a couple issues - // - OSX encodes the dylib name in the executable - // - Windows rustc multiple files of which we can't easily link all of them - // - // No metadata for bin because of an issue - // - wasm32 rustc/emcc encodes the .wasm name in the .js (rust-lang/cargo#4535) - // - // Two exceptions - // 1) Upstream dependencies (we aren't exporting + need to resolve name conflict) - // 2) __CARGO_DEFAULT_LIB_METADATA env var - // - // Note, though, that the compiler's build system at least wants - // path dependencies (eg libstd) to have hashes in filenames. To account for - // that we have an extra hack here which reads the - // `__CARGO_DEFAULT_LIB_METADATA` environment variable and creates a - // hash in the filename if that's present. - // - // This environment variable should not be relied on! It's - // just here for rustbuild. We need a more principled method - // doing this eventually. - let __cargo_default_lib_metadata = env::var("__CARGO_DEFAULT_LIB_METADATA"); - if !(unit.profile.test || unit.profile.check) - && (unit.target.is_dylib() || unit.target.is_cdylib() - || (unit.target.is_bin() && self.target_triple().starts_with("wasm32-"))) - && unit.pkg.package_id().source_id().is_path() - && !__cargo_default_lib_metadata.is_ok() - { - return None; - } - - let mut hasher = SipHasher::new_with_keys(0, 0); - - // Unique metadata per (name, source, version) triple. This'll allow us - // to pull crates from anywhere w/o worrying about conflicts - unit.pkg - .package_id() - .stable_hash(self.ws.root()) - .hash(&mut hasher); - - // Add package properties which map to environment variables - // exposed by Cargo - let manifest_metadata = unit.pkg.manifest().metadata(); - manifest_metadata.authors.hash(&mut hasher); - manifest_metadata.description.hash(&mut hasher); - manifest_metadata.homepage.hash(&mut hasher); - - // Also mix in enabled features to our metadata. This'll ensure that - // when changing feature sets each lib is separately cached. - self.resolve - .features_sorted(unit.pkg.package_id()) - .hash(&mut hasher); - - // Mix in the target-metadata of all the dependencies of this target - if let Ok(deps) = self.dep_targets(unit) { - let mut deps_metadata = deps.into_iter() - .map(|dep_unit| self.target_metadata(&dep_unit)) - .collect::>(); - deps_metadata.sort(); - deps_metadata.hash(&mut hasher); - } - - // Throw in the profile we're compiling with. This helps caching - // panic=abort and panic=unwind artifacts, additionally with various - // settings like debuginfo and whatnot. - unit.profile.hash(&mut hasher); - - // Artifacts compiled for the host should have a different metadata - // piece than those compiled for the target, so make sure we throw in - // the unit's `kind` as well - unit.kind.hash(&mut hasher); - - // Finally throw in the target name/kind. This ensures that concurrent - // compiles of targets in the same crate don't collide. - unit.target.name().hash(&mut hasher); - unit.target.kind().hash(&mut hasher); - - if let Ok(rustc) = self.config.rustc() { - rustc.verbose_version.hash(&mut hasher); - } - - // Seed the contents of __CARGO_DEFAULT_LIB_METADATA to the hasher if present. - // This should be the release channel, to get a different hash for each channel. - if let Ok(ref channel) = __cargo_default_lib_metadata { - channel.hash(&mut hasher); - } - - Some(Metadata(hasher.finish())) - } - - /// Returns the file stem for a given target/profile combo (with metadata) - pub fn file_stem(&mut self, unit: &Unit<'a>) -> String { - match self.target_metadata(unit) { - Some(ref metadata) => format!("{}-{}", unit.target.crate_name(), metadata), - None => self.bin_stem(unit), - } - } - - /// Returns the bin stem for a given target (without metadata) - fn bin_stem(&self, unit: &Unit) -> String { - if unit.target.allows_underscores() { - unit.target.name().to_string() - } else { - unit.target.crate_name() - } - } - - /// Returns a tuple with the directory and name of the hard link we expect - /// our target to be copied to. Eg, file_stem may be out_dir/deps/foo-abcdef - /// and link_stem would be out_dir/foo - /// This function returns it in two parts so the caller can add prefix/suffix - /// to filename separately - /// - /// Returns an Option because in some cases we don't want to link - /// (eg a dependent lib) - pub fn link_stem(&mut self, unit: &Unit<'a>) -> Option<(PathBuf, String)> { - let src_dir = self.out_dir(unit); - let bin_stem = self.bin_stem(unit); - let file_stem = self.file_stem(unit); - - // We currently only lift files up from the `deps` directory. If - // it was compiled into something like `example/` or `doc/` then - // we don't want to link it up. - if src_dir.ends_with("deps") { - // Don't lift up library dependencies - if self.ws.members().find(|&p| p == unit.pkg).is_none() && !unit.target.is_bin() { - None - } else { - Some(( - src_dir.parent().unwrap().to_owned(), - if unit.profile.test { - file_stem - } else { - bin_stem - }, - )) - } - } else if bin_stem == file_stem { - None - } else if src_dir.ends_with("examples") || src_dir.parent().unwrap().ends_with("build") { - Some((src_dir, bin_stem)) - } else { - None - } - } - - /// Return the filenames that the given target for the given profile will - /// generate as a list of 3-tuples (filename, link_dst, linkable) - /// - /// - filename: filename rustc compiles to. (Often has metadata suffix). - /// - link_dst: Optional file to link/copy the result to (without metadata suffix) - /// - linkable: Whether possible to link against file (eg it's a library) - pub fn target_filenames( - &mut self, - unit: &Unit<'a>, - ) -> CargoResult, TargetFileType)>>> { - if let Some(cache) = self.target_filenames.get(unit) { - return Ok(Arc::clone(cache)); - } - - let result = self.calc_target_filenames(unit); - if let Ok(ref ret) = result { - self.target_filenames.insert(*unit, Arc::clone(ret)); - } - result - } - - fn calc_target_filenames( - &mut self, - unit: &Unit<'a>, - ) -> CargoResult, TargetFileType)>>> { - let out_dir = self.out_dir(unit); - let stem = self.file_stem(unit); - let link_stem = self.link_stem(unit); - let info = if unit.target.for_host() { - &self.host_info - } else { - &self.target_info - }; - - let mut ret = Vec::new(); - let mut unsupported = Vec::new(); - { - if unit.profile.check { - let filename = out_dir.join(format!("lib{}.rmeta", stem)); - let link_dst = link_stem - .clone() - .map(|(ld, ls)| ld.join(format!("lib{}.rmeta", ls))); - ret.push((filename, link_dst, TargetFileType::Linkable)); - } else { - let mut add = |crate_type: &str, file_type: TargetFileType| -> CargoResult<()> { - let crate_type = if crate_type == "lib" { - "rlib" - } else { - crate_type - }; - let mut crate_types = info.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 = info.discover_crate_type(v.key())?; - &*v.insert(value) - } - }; - match *crate_type_info { - Some((ref prefix, ref suffix)) => { - let suffixes = add_target_specific_suffixes( - self.target_triple(), - crate_type, - unit.target.kind(), - suffix, - file_type, - ); - for (suffix, file_type, should_replace_hyphens) in suffixes { - // 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". - let conv = |s: String| { - if should_replace_hyphens { - s.replace("-", "_") - } else { - s - } - }; - let filename = out_dir.join(format!( - "{}{}{}", - prefix, - conv(stem.clone()), - suffix - )); - let link_dst = link_stem.clone().map(|(ld, ls)| { - ld.join(format!("{}{}{}", prefix, conv(ls), suffix)) - }); - ret.push((filename, link_dst, file_type)); - } - Ok(()) - } - // not supported, don't worry about it - None => { - unsupported.push(crate_type.to_string()); - Ok(()) - } - } - }; - //info!("{:?}", unit); - match *unit.target.kind() { - TargetKind::Bin - | TargetKind::CustomBuild - | TargetKind::ExampleBin - | TargetKind::Bench - | TargetKind::Test => { - add("bin", TargetFileType::Normal)?; - } - TargetKind::Lib(..) | TargetKind::ExampleLib(..) if unit.profile.test => { - add("bin", TargetFileType::Normal)?; - } - TargetKind::ExampleLib(ref kinds) | TargetKind::Lib(ref kinds) => { - for kind in kinds { - add( - kind.crate_type(), - if kind.linkable() { - TargetFileType::Linkable - } else { - TargetFileType::Normal - }, - )?; - } - } - } - } - } - if ret.is_empty() { - if !unsupported.is_empty() { - bail!( - "cannot produce {} for `{}` as the target `{}` \ - does not support these crate types", - unsupported.join(", "), - unit.pkg, - self.target_triple() - ) - } - bail!( - "cannot compile `{}` as the target `{}` does not \ - support any of the output crate types", - unit.pkg, - self.target_triple() - ); - } - info!("Target filenames: {:?}", ret); - - Ok(Arc::new(ret)) - } - - /// For a package, return all targets which are registered as dependencies - /// for that package. - pub fn dep_targets(&self, unit: &Unit<'a>) -> CargoResult>> { - if unit.profile.run_custom_build { - return self.dep_run_custom_build(unit); - } else if unit.profile.doc && !unit.profile.test { - return self.doc_deps(unit); - } - - let id = unit.pkg.package_id(); - let deps = self.resolve.deps(id); - let mut ret = deps.filter(|dep| { - unit.pkg - .dependencies() - .iter() - .filter(|d| d.name() == dep.name() && d.version_req().matches(dep.version())) - .any(|d| { - // If this target is a build command, then we only want build - // dependencies, otherwise we want everything *other than* build - // dependencies. - if unit.target.is_custom_build() != d.is_build() { - return false; - } - - // If this dependency is *not* a transitive dependency, then it - // only applies to test/example targets - if !d.is_transitive() && !unit.target.is_test() && !unit.target.is_example() - && !unit.profile.test - { - return false; - } - - // If this dependency is only available for certain platforms, - // make sure we're only enabling it for that platform. - if !self.dep_platform_activated(d, unit.kind) { - return false; - } - - // If the dependency is optional, then we're only activating it - // if the corresponding feature was activated - if d.is_optional() && !self.resolve.features(id).contains(&*d.name()) { - return false; - } - - // If we've gotten past all that, then this dependency is - // actually used! - true - }) - }).filter_map(|id| match self.get_package(id) { - Ok(pkg) => pkg.targets().iter().find(|t| t.is_lib()).map(|t| { - let unit = Unit { - pkg, - target: t, - profile: self.lib_or_check_profile(unit, t), - kind: unit.kind.for_target(t), - }; - Ok(unit) - }), - Err(e) => Some(Err(e)), - }) - .collect::>>()?; - - // If this target is a build script, then what we've collected so far is - // all we need. If this isn't a build script, then it depends on the - // build script if there is one. - if unit.target.is_custom_build() { - return Ok(ret); - } - ret.extend(self.dep_build_script(unit)); - - // If this target is a binary, test, example, etc, then it depends on - // the library of the same package. The call to `resolve.deps` above - // didn't include `pkg` in the return values, so we need to special case - // it here and see if we need to push `(pkg, pkg_lib_target)`. - if unit.target.is_lib() && !unit.profile.doc { - return Ok(ret); - } - ret.extend(self.maybe_lib(unit)); - - // Integration tests/benchmarks require binaries to be built - if unit.profile.test && (unit.target.is_test() || unit.target.is_bench()) { - ret.extend( - unit.pkg - .targets() - .iter() - .filter(|t| { - let no_required_features = Vec::new(); - - t.is_bin() && - // Skip binaries with required features that have not been selected. - t.required_features().unwrap_or(&no_required_features).iter().all(|f| { - self.resolve.features(id).contains(f) - }) - }) - .map(|t| Unit { - pkg: unit.pkg, - target: t, - profile: self.lib_or_check_profile(unit, t), - kind: unit.kind.for_target(t), - }), - ); - } - Ok(ret) - } - - /// Returns the dependencies needed to run a build script. - /// - /// The `unit` provided must represent an execution of a build script, and - /// the returned set of units must all be run before `unit` is run. - pub fn dep_run_custom_build(&self, unit: &Unit<'a>) -> CargoResult>> { - // If this build script's execution has been overridden then we don't - // actually depend on anything, we've reached the end of the dependency - // chain as we've got all the info we're gonna get. - let key = (unit.pkg.package_id().clone(), unit.kind); - if self.build_script_overridden.contains(&key) { - return Ok(Vec::new()); - } - - // When not overridden, then the dependencies to run a build script are: - // - // 1. Compiling the build script itself - // 2. For each immediate dependency of our package which has a `links` - // key, the execution of that build script. - let not_custom_build = unit.pkg - .targets() - .iter() - .find(|t| !t.is_custom_build()) - .unwrap(); - let tmp = Unit { - target: not_custom_build, - profile: &self.profiles.dev, - ..*unit - }; - let deps = self.dep_targets(&tmp)?; - Ok(deps.iter() - .filter_map(|unit| { - if !unit.target.linkable() || unit.pkg.manifest().links().is_none() { - return None; - } - self.dep_build_script(unit) - }) - .chain(Some(Unit { - profile: self.build_script_profile(unit.pkg.package_id()), - kind: Kind::Host, // build scripts always compiled for the host - ..*unit - })) - .collect()) - } - - /// Returns the dependencies necessary to document a package - fn doc_deps(&self, unit: &Unit<'a>) -> CargoResult>> { - let deps = self.resolve - .deps(unit.pkg.package_id()) - .filter(|dep| { - unit.pkg - .dependencies() - .iter() - .filter(|d| d.name() == dep.name()) - .any(|dep| match dep.kind() { - DepKind::Normal => self.dep_platform_activated(dep, unit.kind), - _ => false, - }) - }) - .map(|dep| self.get_package(dep)); - - // To document a library, we depend on dependencies actually being - // built. If we're documenting *all* libraries, then we also depend on - // the documentation of the library being built. - let mut ret = Vec::new(); - for dep in deps { - let dep = dep?; - let lib = match dep.targets().iter().find(|t| t.is_lib()) { - Some(lib) => lib, - None => continue, - }; - ret.push(Unit { - pkg: dep, - target: lib, - profile: self.lib_or_check_profile(unit, lib), - kind: unit.kind.for_target(lib), - }); - if self.build_config.doc_all { - ret.push(Unit { - pkg: dep, - target: lib, - profile: &self.profiles.doc, - kind: unit.kind.for_target(lib), - }); - } - } - - // Be sure to build/run the build script for documented libraries as - ret.extend(self.dep_build_script(unit)); - - // If we document a binary, we need the library available - if unit.target.is_bin() { - ret.extend(self.maybe_lib(unit)); - } - Ok(ret) - } - - /// If a build script is scheduled to be run for the package specified by - /// `unit`, this function will return the unit to run that build script. - /// - /// Overriding a build script simply means that the running of the build - /// script itself doesn't have any dependencies, so even in that case a unit - /// of work is still returned. `None` is only returned if the package has no - /// build script. - fn dep_build_script(&self, unit: &Unit<'a>) -> Option> { - unit.pkg - .targets() - .iter() - .find(|t| t.is_custom_build()) - .map(|t| Unit { - pkg: unit.pkg, - target: t, - profile: &self.profiles.custom_build, - kind: unit.kind, - }) - } - - fn maybe_lib(&self, unit: &Unit<'a>) -> Option> { - unit.pkg - .targets() - .iter() - .find(|t| t.linkable()) - .map(|t| Unit { - pkg: unit.pkg, - target: t, - profile: self.lib_or_check_profile(unit, t), - kind: unit.kind.for_target(t), - }) - } - - 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.host_triple(), &self.host_info), - Kind::Target => (self.target_triple(), &self.target_info), - }; - platform.matches(name, info.cfg.as_ref().map(|cfg| &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.as_ref().map(|s| &s[..]).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 lib_profile(&self) -> &'a Profile { - let (normal, test) = if self.build_config.release { - (&self.profiles.release, &self.profiles.bench_deps) - } else { - (&self.profiles.dev, &self.profiles.test_deps) - }; - if self.build_config.test { - test - } else { - normal - } - } - - pub fn lib_or_check_profile(&self, unit: &Unit, target: &Target) -> &'a Profile { - if !target.is_custom_build() && !target.for_host() - && (unit.profile.check || (unit.profile.doc && !unit.profile.test)) - { - return &self.profiles.check; - } - self.lib_profile() - } - - pub fn build_script_profile(&self, _pkg: &PackageId) -> &'a Profile { - // TODO: should build scripts always be built with the same library - // profile? How is this controlled at the CLI layer? - self.lib_profile() - } - - pub fn incremental_args(&self, unit: &Unit) -> CargoResult> { - // There's a number of ways to configure incremental compilation right - // now. In order of descending priority (first is highest priority) we - // have: - // - // * `CARGO_INCREMENTAL` - this is blanket used unconditionally to turn - // on/off incremental compilation for any cargo subcommand. We'll - // respect this if set. - // * `build.incremental` - in `.cargo/config` this blanket key can - // globally for a system configure whether incremental compilation is - // enabled. Note that setting this to `true` will not actually affect - // all builds though. For example a `true` value doesn't enable - // release incremental builds, only dev incremental builds. This can - // be useful to globally disable incremental compilation like - // `CARGO_INCREMENTAL`. - // * `profile.dev.incremental` - in `Cargo.toml` specific profiles can - // be configured to enable/disable incremental compilation. This can - // be primarily used to disable incremental when buggy for a project. - // * Finally, each profile has a default for whether it will enable - // incremental compilation or not. Primarily development profiles - // have it enabled by default while release profiles have it disabled - // by default. - let global_cfg = self.config.get_bool("build.incremental")?.map(|c| c.val); - let incremental = match (self.incremental_env, global_cfg, unit.profile.incremental) { - (Some(v), _, _) => v, - (None, Some(false), _) => false, - (None, _, other) => other, - }; - - if !incremental { - return Ok(Vec::new()); - } - - // Only enable incremental compilation for sources the user can - // modify (aka path sources). For things that change infrequently, - // non-incremental builds yield better performance in the compiler - // itself (aka crates.io / git dependencies) - // - // (see also https://github.com/rust-lang/cargo/issues/3972) - if !unit.pkg.package_id().source_id().is_path() { - return Ok(Vec::new()); - } - - let dir = self.layout(unit.kind).incremental().display(); - Ok(vec!["-C".to_string(), format!("incremental={}", dir)]) - } - - pub fn rustflags_args(&self, unit: &Unit) -> CargoResult> { - env_args( - self.config, - &self.build_config, - self.info(&unit.kind), - unit.kind, - "RUSTFLAGS", - ) - } - - pub fn rustdocflags_args(&self, unit: &Unit) -> CargoResult> { - env_args( - self.config, - &self.build_config, - self.info(&unit.kind), - 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, - } - } -} - -/// 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_info: &TargetInfo, - 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() - .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(ref target_cfg) = target_info.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()) -} - -impl fmt::Display for Metadata { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:016x}", self.0) - } -} - -/// 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()))) -} - -// (not a rustdoc) -// Return a list of 3-tuples (suffix, file_type, should_replace_hyphens). -// -// should_replace_hyphens will be used by the caller to replace "-" with "_" -// in a bin_stem. See the caller side (calc_target_filenames()) for details. -fn add_target_specific_suffixes( - target_triple: &str, - crate_type: &str, - target_kind: &TargetKind, - suffix: &str, - file_type: TargetFileType, -) -> Vec<(String, TargetFileType, bool)> { - let mut ret = vec![(suffix.to_string(), file_type, false)]; - - // rust-lang/cargo#4500 - if target_triple.ends_with("pc-windows-msvc") && crate_type.ends_with("dylib") - && suffix == ".dll" - { - ret.push((".dll.lib".to_string(), TargetFileType::Normal, false)); - } - - // rust-lang/cargo#4535 - if target_triple.starts_with("wasm32-") && crate_type == "bin" && suffix == ".js" { - ret.push((".wasm".to_string(), TargetFileType::Normal, 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 *target_kind == TargetKind::Bin { - if target_triple.contains("-apple-") { - ret.push((".dSYM".to_string(), TargetFileType::DebugInfo, false)); - } else if target_triple.ends_with("-msvc") { - ret.push((".pdb".to_string(), TargetFileType::DebugInfo, false)); - } - } - - ret -} diff --git a/src/cargo/ops/cargo_rustc/context/mod.rs b/src/cargo/ops/cargo_rustc/context/mod.rs new file mode 100644 index 000000000..d51984375 --- /dev/null +++ b/src/cargo/ops/cargo_rustc/context/mod.rs @@ -0,0 +1,1169 @@ +#![allow(deprecated)] + +use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::Entry; +use std::env; +use std::fmt; +use std::hash::{Hash, Hasher, SipHasher}; +use std::path::{Path, PathBuf}; +use std::str::{self, FromStr}; +use std::sync::Arc; +use std::cell::RefCell; + +use jobserver::Client; + +use core::{Package, PackageId, PackageSet, Profile, Resolve, Target}; +use core::{Dependency, Profiles, TargetKind, Workspace}; +use util::{self, internal, profile, Cfg, CfgExpr, Config, ProcessBuilder}; +use util::errors::{CargoResult, CargoResultExt}; + +use super::TargetConfig; +use super::custom_build::{BuildDeps, BuildScripts, BuildState}; +use super::fingerprint::Fingerprint; +use super::layout::Layout; +use super::links::Links; +use super::{BuildConfig, Compilation, Kind}; + +mod unit_dependencies; +use self::unit_dependencies::build_unit_dependencies; + +/// All information needed to define a Unit. +/// +/// A unit is an object that has enough information so that cargo knows how to build it. +/// For example, if your project has dependencies, then every dependency will be built as a library +/// unit. If your project is a library, then it will be built as a library unit as well, or if it +/// is a binary with `main.rs`, then a binary will be output. There are also separate unit types +/// for `test`ing and `check`ing, amongst others. +/// +/// The unit also holds information about all possible metadata about the package in `pkg`. +/// +/// A unit needs to know extra information in addition to the type and root source file. For +/// example, it needs to know the target architecture (OS, chip arch etc.) and it needs to know +/// whether you want a debug or release build. There is enough information in this struct to figure +/// all that out. +#[derive(Clone, Copy, Eq, PartialEq, Hash)] +pub struct Unit<'a> { + /// Information about available targets, which files to include/exclude, etc. Basically stuff in + /// `Cargo.toml`. + pub pkg: &'a Package, + /// Information about the specific target to build, out of the possible targets in `pkg`. Not + /// to be confused with *target-triple* (or *target architecture* ...), the target arch for a + /// build. + pub target: &'a Target, + /// The profile contains information about *how* the build should be run, including debug + /// level, extra args to pass to rustc, etc. + pub profile: &'a Profile, + /// Whether this compilation unit is for the host or target architecture. + /// + /// For example, when + /// cross compiling and using a custom build script, the build script needs to be compiled for + /// the host architecture so the host rustc can use it (when compiling to the target + /// architecture). + pub kind: Kind, +} + +/// Type of each file generated by a Unit. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum TargetFileType { + /// 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, +} + +/// The build context, containing all information about a build task +pub struct Context<'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, + /// Information on the compilation output + pub compilation: Compilation<'cfg>, + pub packages: &'a PackageSet<'cfg>, + pub build_state: Arc, + pub build_script_overridden: HashSet<(PackageId, Kind)>, + pub build_explicit_deps: HashMap, BuildDeps>, + pub fingerprints: HashMap, Arc>, + pub compiled: HashSet>, + pub build_config: BuildConfig, + pub build_scripts: HashMap, Arc>, + pub links: Links<'a>, + pub used_in_plugin: HashSet>, + pub jobserver: Client, + + /// The target directory layout for the host (and target if it is the same as host) + host: Layout, + /// The target directory layout for the target (if different from then host) + target: Option, + target_info: TargetInfo, + host_info: TargetInfo, + profiles: &'a Profiles, + incremental_env: Option, + + unit_dependencies: Option, Vec>>>, + /// For each Unit, a list all files produced as a triple of + /// + /// - File name that will be produced by the build process (in `deps`) + /// - If it should be linked into `target`, and what it should be called (e.g. without + /// metadata). + /// - Type of the file (library / debug symbol / else) + target_filenames: HashMap, Arc, TargetFileType)>>>, + target_metadatas: HashMap, Option>, +} + +#[derive(Clone, Default)] +struct TargetInfo { + crate_type_process: Option, + crate_types: RefCell>>, + cfg: Option>, +} + +impl TargetInfo { + 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())?) + } +} + +#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct Metadata(u64); + +impl<'a, 'cfg> Context<'a, 'cfg> { + pub fn new( + ws: &'a Workspace<'cfg>, + resolve: &'a Resolve, + packages: &'a PackageSet<'cfg>, + config: &'cfg Config, + build_config: BuildConfig, + profiles: &'a Profiles, + ) -> CargoResult> { + let dest = if build_config.release { + "release" + } else { + "debug" + }; + let host_layout = Layout::new(ws, None, dest)?; + let target_layout = match build_config.requested_target.as_ref() { + Some(target) => Some(Layout::new(ws, Some(target), dest)?), + None => None, + }; + + let incremental_env = match env::var("CARGO_INCREMENTAL") { + Ok(v) => Some(v == "1"), + Err(_) => None, + }; + + // Load up the jobserver that we'll use to manage our parallelism. This + // is the same as the GNU make implementation of a jobserver, and + // intentionally so! It's hoped that we can interact with GNU make and + // all share the same jobserver. + // + // Note that if we don't have a jobserver in our environment then we + // create our own, and we create it with `n-1` tokens because one token + // is ourself, a running process. + let jobserver = match config.jobserver_from_env() { + Some(c) => c.clone(), + None => Client::new(build_config.jobs as usize - 1) + .chain_err(|| "failed to create jobserver")?, + }; + + Ok(Context { + ws, + host: host_layout, + target: target_layout, + resolve, + packages, + config, + target_info: TargetInfo::default(), + host_info: TargetInfo::default(), + compilation: Compilation::new(config), + build_state: Arc::new(BuildState::new(&build_config)), + build_config, + fingerprints: HashMap::new(), + profiles, + compiled: HashSet::new(), + build_scripts: HashMap::new(), + build_explicit_deps: HashMap::new(), + links: Links::new(), + used_in_plugin: HashSet::new(), + incremental_env, + jobserver, + build_script_overridden: HashSet::new(), + + unit_dependencies: None, + // TODO: Pre-Calculate these with a topo-sort, rather than lazy-calculating + target_filenames: HashMap::new(), + target_metadatas: HashMap::new(), + }) + } + + /// Prepare this context, ensuring that all filesystem directories are in + /// place. + pub fn prepare(&mut self) -> CargoResult<()> { + let _p = profile::start("preparing layout"); + + self.host + .prepare() + .chain_err(|| internal("couldn't prepare build directories"))?; + if let Some(ref mut target) = self.target { + target + .prepare() + .chain_err(|| internal("couldn't prepare build directories"))?; + } + + self.compilation.host_deps_output = self.host.deps().to_path_buf(); + + let layout = self.target.as_ref().unwrap_or(&self.host); + self.compilation.root_output = layout.dest().to_path_buf(); + self.compilation.deps_output = layout.deps().to_path_buf(); + Ok(()) + } + + pub fn build_unit_dependencies(&mut self, units: &[Unit<'a>]) -> CargoResult<()> { + assert!(self.unit_dependencies.is_none()); + self.unit_dependencies = Some(build_unit_dependencies(units, self)?); + Ok(()) + } + + /// Ensure that we've collected all target-specific information to compile + /// all the units mentioned in `units`. + pub fn probe_target_info(&mut self) -> CargoResult<()> { + debug!("probe_target_info"); + self.probe_target_info_kind(Kind::Target)?; + if self.requested_target().is_none() { + self.host_info = self.target_info.clone(); + } else { + self.probe_target_info_kind(Kind::Host)?; + } + Ok(()) + } + + fn probe_target_info_kind(&mut self, kind: Kind) -> CargoResult<()> { + let rustflags = env_args( + self.config, + &self.build_config, + self.info(&kind), + kind, + "RUSTFLAGS", + )?; + let mut process = self.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(&self.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 = with_cfg + .exec_with_output() + .or_else(|_| { + has_cfg_and_sysroot = false; + process.exec_with_output() + }) + .chain_err(|| "failed to run `rustc` to learn about target-specific information")?; + + let error = str::from_utf8(&output.stderr).unwrap(); + let output = str::from_utf8(&output.stdout).unwrap(); + 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); + } + + 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"); + } + self.compilation.host_dylib_path = Some(rustlib); + } else { + rustlib.push("lib"); + rustlib.push("rustlib"); + rustlib.push(self.target_triple()); + rustlib.push("lib"); + self.compilation.target_dylib_path = Some(rustlib); + } + } + + let cfg = if has_cfg_and_sysroot { + Some(lines.map(Cfg::from_str).collect::>()?) + } else { + None + }; + + let info = match kind { + Kind::Target => &mut self.target_info, + Kind::Host => &mut self.host_info, + }; + info.crate_type_process = Some(crate_type_process); + info.crate_types = RefCell::new(map); + info.cfg = cfg; + Ok(()) + } + + /// Builds up the `used_in_plugin` internal to this context from the list of + /// top-level units. + /// + /// This will recursively walk `units` and all of their dependencies to + /// determine which crate are going to be used in plugins or not. + pub fn build_used_in_plugin_map(&mut self, units: &[Unit<'a>]) -> CargoResult<()> { + let mut visited = HashSet::new(); + for unit in units { + self.walk_used_in_plugin_map(unit, unit.target.for_host(), &mut visited)?; + } + Ok(()) + } + + fn walk_used_in_plugin_map( + &mut self, + unit: &Unit<'a>, + is_plugin: bool, + visited: &mut HashSet<(Unit<'a>, bool)>, + ) -> CargoResult<()> { + if !visited.insert((*unit, is_plugin)) { + return Ok(()); + } + if is_plugin { + self.used_in_plugin.insert(*unit); + } + for unit in self.dep_targets(unit) { + self.walk_used_in_plugin_map(&unit, is_plugin || unit.target.for_host(), visited)?; + } + Ok(()) + } + + /// Returns the appropriate directory layout for either a plugin or not. + fn layout(&self, kind: Kind) -> &Layout { + match kind { + Kind::Host => &self.host, + Kind::Target => self.target.as_ref().unwrap_or(&self.host), + } + } + + /// Returns the directories where Rust crate dependencies are found for the + /// specified unit. + pub fn deps_dir(&self, unit: &Unit) -> &Path { + self.layout(unit.kind).deps() + } + + /// Returns the directory for the specified unit where fingerprint + /// information is stored. + pub fn fingerprint_dir(&mut self, unit: &Unit<'a>) -> PathBuf { + let dir = self.pkg_dir(unit); + self.layout(unit.kind).fingerprint().join(dir) + } + + /// Returns the appropriate directory layout for either a plugin or not. + pub fn build_script_dir(&mut self, unit: &Unit<'a>) -> PathBuf { + assert!(unit.target.is_custom_build()); + assert!(!unit.profile.run_custom_build); + let dir = self.pkg_dir(unit); + self.layout(Kind::Host).build().join(dir) + } + + /// Returns the appropriate directory layout for either a plugin or not. + pub fn build_script_out_dir(&mut self, unit: &Unit<'a>) -> PathBuf { + assert!(unit.target.is_custom_build()); + assert!(unit.profile.run_custom_build); + let dir = self.pkg_dir(unit); + self.layout(unit.kind).build().join(dir).join("out") + } + + pub fn host_deps(&self) -> &Path { + self.host.deps() + } + + /// Return the root of the build output tree + pub fn target_root(&self) -> &Path { + self.host.dest() + } + + /// Returns the appropriate output directory for the specified package and + /// target. + pub fn out_dir(&mut self, unit: &Unit<'a>) -> PathBuf { + if unit.profile.doc { + self.layout(unit.kind).root().parent().unwrap().join("doc") + } else if unit.target.is_custom_build() { + self.build_script_dir(unit) + } else if unit.target.is_example() { + self.layout(unit.kind).examples().to_path_buf() + } else { + self.deps_dir(unit).to_path_buf() + } + } + + fn pkg_dir(&mut self, unit: &Unit<'a>) -> String { + let name = unit.pkg.package_id().name(); + match self.target_metadata(unit) { + Some(meta) => format!("{}-{}", name, meta), + None => format!("{}-{}", name, self.target_short_hash(unit)), + } + } + + /// Return the host triple for this context + pub fn host_triple(&self) -> &str { + &self.build_config.host_triple + } + + /// Return the target triple which this context is targeting. + pub fn target_triple(&self) -> &str { + self.requested_target() + .unwrap_or_else(|| self.host_triple()) + } + + /// Requested (not actual) target for the build + pub fn requested_target(&self) -> Option<&str> { + self.build_config.requested_target.as_ref().map(|s| &s[..]) + } + + /// Get the short hash based only on the PackageId + /// Used for the metadata when target_metadata returns None + pub fn target_short_hash(&self, unit: &Unit) -> String { + let hashable = unit.pkg.package_id().stable_hash(self.ws.root()); + util::short_hash(&hashable) + } + + /// Get the metadata for a target in a specific profile + /// We build to the path: "{filename}-{target_metadata}" + /// We use a linking step to link/copy to a predictable filename + /// like `target/debug/libfoo.{a,so,rlib}` and such. + pub fn target_metadata(&mut self, unit: &Unit<'a>) -> Option { + if let Some(cache) = self.target_metadatas.get(unit) { + return cache.clone(); + } + + let metadata = self.calc_target_metadata(unit); + self.target_metadatas.insert(*unit, metadata.clone()); + metadata + } + + fn calc_target_metadata(&mut self, unit: &Unit<'a>) -> Option { + // No metadata for dylibs because of a couple issues + // - OSX encodes the dylib name in the executable + // - Windows rustc multiple files of which we can't easily link all of them + // + // No metadata for bin because of an issue + // - wasm32 rustc/emcc encodes the .wasm name in the .js (rust-lang/cargo#4535) + // + // Two exceptions + // 1) Upstream dependencies (we aren't exporting + need to resolve name conflict) + // 2) __CARGO_DEFAULT_LIB_METADATA env var + // + // Note, though, that the compiler's build system at least wants + // path dependencies (eg libstd) to have hashes in filenames. To account for + // that we have an extra hack here which reads the + // `__CARGO_DEFAULT_LIB_METADATA` environment variable and creates a + // hash in the filename if that's present. + // + // This environment variable should not be relied on! It's + // just here for rustbuild. We need a more principled method + // doing this eventually. + let __cargo_default_lib_metadata = env::var("__CARGO_DEFAULT_LIB_METADATA"); + if !(unit.profile.test || unit.profile.check) + && (unit.target.is_dylib() || unit.target.is_cdylib() + || (unit.target.is_bin() && self.target_triple().starts_with("wasm32-"))) + && unit.pkg.package_id().source_id().is_path() + && !__cargo_default_lib_metadata.is_ok() + { + return None; + } + + let mut hasher = SipHasher::new_with_keys(0, 0); + + // Unique metadata per (name, source, version) triple. This'll allow us + // to pull crates from anywhere w/o worrying about conflicts + unit.pkg + .package_id() + .stable_hash(self.ws.root()) + .hash(&mut hasher); + + // Add package properties which map to environment variables + // exposed by Cargo + let manifest_metadata = unit.pkg.manifest().metadata(); + manifest_metadata.authors.hash(&mut hasher); + manifest_metadata.description.hash(&mut hasher); + manifest_metadata.homepage.hash(&mut hasher); + + // Also mix in enabled features to our metadata. This'll ensure that + // when changing feature sets each lib is separately cached. + self.resolve + .features_sorted(unit.pkg.package_id()) + .hash(&mut hasher); + + // Mix in the target-metadata of all the dependencies of this target + { + let mut deps_metadata = self.dep_targets(unit) + .iter() + .map(|dep_unit| self.target_metadata(dep_unit)) + .collect::>(); + deps_metadata.sort(); + deps_metadata.hash(&mut hasher); + } + + // Throw in the profile we're compiling with. This helps caching + // panic=abort and panic=unwind artifacts, additionally with various + // settings like debuginfo and whatnot. + unit.profile.hash(&mut hasher); + + // Artifacts compiled for the host should have a different metadata + // piece than those compiled for the target, so make sure we throw in + // the unit's `kind` as well + unit.kind.hash(&mut hasher); + + // Finally throw in the target name/kind. This ensures that concurrent + // compiles of targets in the same crate don't collide. + unit.target.name().hash(&mut hasher); + unit.target.kind().hash(&mut hasher); + + if let Ok(rustc) = self.config.rustc() { + rustc.verbose_version.hash(&mut hasher); + } + + // Seed the contents of __CARGO_DEFAULT_LIB_METADATA to the hasher if present. + // This should be the release channel, to get a different hash for each channel. + if let Ok(ref channel) = __cargo_default_lib_metadata { + channel.hash(&mut hasher); + } + + Some(Metadata(hasher.finish())) + } + + /// Returns the file stem for a given target/profile combo (with metadata) + pub fn file_stem(&mut self, unit: &Unit<'a>) -> String { + match self.target_metadata(unit) { + Some(ref metadata) => format!("{}-{}", unit.target.crate_name(), metadata), + None => self.bin_stem(unit), + } + } + + /// Returns the bin stem for a given target (without metadata) + fn bin_stem(&self, unit: &Unit) -> String { + if unit.target.allows_underscores() { + unit.target.name().to_string() + } else { + unit.target.crate_name() + } + } + + /// Returns a tuple with the directory and name of the hard link we expect + /// our target to be copied to. Eg, file_stem may be out_dir/deps/foo-abcdef + /// and link_stem would be out_dir/foo + /// This function returns it in two parts so the caller can add prefix/suffix + /// to filename separately + /// + /// Returns an Option because in some cases we don't want to link + /// (eg a dependent lib) + pub fn link_stem(&mut self, unit: &Unit<'a>) -> Option<(PathBuf, String)> { + let src_dir = self.out_dir(unit); + let bin_stem = self.bin_stem(unit); + let file_stem = self.file_stem(unit); + + // We currently only lift files up from the `deps` directory. If + // it was compiled into something like `example/` or `doc/` then + // we don't want to link it up. + if src_dir.ends_with("deps") { + // Don't lift up library dependencies + if self.ws.members().find(|&p| p == unit.pkg).is_none() && !unit.target.is_bin() { + None + } else { + Some(( + src_dir.parent().unwrap().to_owned(), + if unit.profile.test { + file_stem + } else { + bin_stem + }, + )) + } + } else if bin_stem == file_stem { + None + } else if src_dir.ends_with("examples") || src_dir.parent().unwrap().ends_with("build") { + Some((src_dir, bin_stem)) + } else { + None + } + } + + /// Return the filenames that the given target for the given profile will + /// generate as a list of 3-tuples (filename, link_dst, linkable) + /// + /// - filename: filename rustc compiles to. (Often has metadata suffix). + /// - link_dst: Optional file to link/copy the result to (without metadata suffix) + /// - linkable: Whether possible to link against file (eg it's a library) + pub fn target_filenames( + &mut self, + unit: &Unit<'a>, + ) -> CargoResult, TargetFileType)>>> { + if let Some(cache) = self.target_filenames.get(unit) { + return Ok(Arc::clone(cache)); + } + + let result = self.calc_target_filenames(unit); + if let Ok(ref ret) = result { + self.target_filenames.insert(*unit, Arc::clone(ret)); + } + result + } + + fn calc_target_filenames( + &mut self, + unit: &Unit<'a>, + ) -> CargoResult, TargetFileType)>>> { + let out_dir = self.out_dir(unit); + let stem = self.file_stem(unit); + let link_stem = self.link_stem(unit); + let info = if unit.target.for_host() { + &self.host_info + } else { + &self.target_info + }; + + let mut ret = Vec::new(); + let mut unsupported = Vec::new(); + { + if unit.profile.check { + let filename = out_dir.join(format!("lib{}.rmeta", stem)); + let link_dst = link_stem + .clone() + .map(|(ld, ls)| ld.join(format!("lib{}.rmeta", ls))); + ret.push((filename, link_dst, TargetFileType::Linkable)); + } else { + let mut add = |crate_type: &str, file_type: TargetFileType| -> CargoResult<()> { + let crate_type = if crate_type == "lib" { + "rlib" + } else { + crate_type + }; + let mut crate_types = info.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 = info.discover_crate_type(v.key())?; + &*v.insert(value) + } + }; + match *crate_type_info { + Some((ref prefix, ref suffix)) => { + let suffixes = add_target_specific_suffixes( + self.target_triple(), + crate_type, + unit.target.kind(), + suffix, + file_type, + ); + for (suffix, file_type, should_replace_hyphens) in suffixes { + // 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". + let conv = |s: String| { + if should_replace_hyphens { + s.replace("-", "_") + } else { + s + } + }; + let filename = out_dir.join(format!( + "{}{}{}", + prefix, + conv(stem.clone()), + suffix + )); + let link_dst = link_stem.clone().map(|(ld, ls)| { + ld.join(format!("{}{}{}", prefix, conv(ls), suffix)) + }); + ret.push((filename, link_dst, file_type)); + } + Ok(()) + } + // not supported, don't worry about it + None => { + unsupported.push(crate_type.to_string()); + Ok(()) + } + } + }; + //info!("{:?}", unit); + match *unit.target.kind() { + TargetKind::Bin + | TargetKind::CustomBuild + | TargetKind::ExampleBin + | TargetKind::Bench + | TargetKind::Test => { + add("bin", TargetFileType::Normal)?; + } + TargetKind::Lib(..) | TargetKind::ExampleLib(..) if unit.profile.test => { + add("bin", TargetFileType::Normal)?; + } + TargetKind::ExampleLib(ref kinds) | TargetKind::Lib(ref kinds) => { + for kind in kinds { + add( + kind.crate_type(), + if kind.linkable() { + TargetFileType::Linkable + } else { + TargetFileType::Normal + }, + )?; + } + } + } + } + } + if ret.is_empty() { + if !unsupported.is_empty() { + bail!( + "cannot produce {} for `{}` as the target `{}` \ + does not support these crate types", + unsupported.join(", "), + unit.pkg, + self.target_triple() + ) + } + bail!( + "cannot compile `{}` as the target `{}` does not \ + support any of the output crate types", + unit.pkg, + self.target_triple() + ); + } + info!("Target filenames: {:?}", ret); + + Ok(Arc::new(ret)) + } + + /// For a package, return all targets which are registered as dependencies + /// for that package. + // TODO: this ideally should be `-> &[Unit<'a>]` + pub fn dep_targets(&self, unit: &Unit<'a>) -> Vec> { + // If this build script's execution has been overridden then we don't + // actually depend on anything, we've reached the end of the dependency + // chain as we've got all the info we're gonna get. + // + // Note there's a subtlety about this piece of code! The + // `build_script_overridden` map here is populated in + // `custom_build::build_map` which you need to call before inspecting + // dependencies. However, that code itself calls this method and + // gets a full pre-filtered set of dependencies. This is not super + // obvious, and clear, but it does work at the moment. + if unit.profile.run_custom_build { + let key = (unit.pkg.package_id().clone(), unit.kind); + if self.build_script_overridden.contains(&key) { + return Vec::new(); + } + } + self.unit_dependencies.as_ref().unwrap()[unit].clone() + } + + 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.host_triple(), &self.host_info), + Kind::Target => (self.target_triple(), &self.target_info), + }; + platform.matches(name, info.cfg.as_ref().map(|cfg| &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.as_ref().map(|s| &s[..]).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 lib_profile(&self) -> &'a Profile { + let (normal, test) = if self.build_config.release { + (&self.profiles.release, &self.profiles.bench_deps) + } else { + (&self.profiles.dev, &self.profiles.test_deps) + }; + if self.build_config.test { + test + } else { + normal + } + } + + pub fn build_script_profile(&self, _pkg: &PackageId) -> &'a Profile { + // TODO: should build scripts always be built with the same library + // profile? How is this controlled at the CLI layer? + self.lib_profile() + } + + pub fn incremental_args(&self, unit: &Unit) -> CargoResult> { + // There's a number of ways to configure incremental compilation right + // now. In order of descending priority (first is highest priority) we + // have: + // + // * `CARGO_INCREMENTAL` - this is blanket used unconditionally to turn + // on/off incremental compilation for any cargo subcommand. We'll + // respect this if set. + // * `build.incremental` - in `.cargo/config` this blanket key can + // globally for a system configure whether incremental compilation is + // enabled. Note that setting this to `true` will not actually affect + // all builds though. For example a `true` value doesn't enable + // release incremental builds, only dev incremental builds. This can + // be useful to globally disable incremental compilation like + // `CARGO_INCREMENTAL`. + // * `profile.dev.incremental` - in `Cargo.toml` specific profiles can + // be configured to enable/disable incremental compilation. This can + // be primarily used to disable incremental when buggy for a project. + // * Finally, each profile has a default for whether it will enable + // incremental compilation or not. Primarily development profiles + // have it enabled by default while release profiles have it disabled + // by default. + let global_cfg = self.config.get_bool("build.incremental")?.map(|c| c.val); + let incremental = match (self.incremental_env, global_cfg, unit.profile.incremental) { + (Some(v), _, _) => v, + (None, Some(false), _) => false, + (None, _, other) => other, + }; + + if !incremental { + return Ok(Vec::new()); + } + + // Only enable incremental compilation for sources the user can + // modify (aka path sources). For things that change infrequently, + // non-incremental builds yield better performance in the compiler + // itself (aka crates.io / git dependencies) + // + // (see also https://github.com/rust-lang/cargo/issues/3972) + if !unit.pkg.package_id().source_id().is_path() { + return Ok(Vec::new()); + } + + let dir = self.layout(unit.kind).incremental().display(); + Ok(vec!["-C".to_string(), format!("incremental={}", dir)]) + } + + pub fn rustflags_args(&self, unit: &Unit) -> CargoResult> { + env_args( + self.config, + &self.build_config, + self.info(&unit.kind), + unit.kind, + "RUSTFLAGS", + ) + } + + pub fn rustdocflags_args(&self, unit: &Unit) -> CargoResult> { + env_args( + self.config, + &self.build_config, + self.info(&unit.kind), + 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, + } + } +} + +/// 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_info: &TargetInfo, + 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() + .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(ref target_cfg) = target_info.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()) +} + +impl fmt::Display for Metadata { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:016x}", self.0) + } +} + +/// 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()))) +} + +// (not a rustdoc) +// Return a list of 3-tuples (suffix, file_type, should_replace_hyphens). +// +// should_replace_hyphens will be used by the caller to replace "-" with "_" +// in a bin_stem. See the caller side (calc_target_filenames()) for details. +fn add_target_specific_suffixes( + target_triple: &str, + crate_type: &str, + target_kind: &TargetKind, + suffix: &str, + file_type: TargetFileType, +) -> Vec<(String, TargetFileType, bool)> { + let mut ret = vec![(suffix.to_string(), file_type, false)]; + + // rust-lang/cargo#4500 + if target_triple.ends_with("pc-windows-msvc") && crate_type.ends_with("dylib") + && suffix == ".dll" + { + ret.push((".dll.lib".to_string(), TargetFileType::Normal, false)); + } + + // rust-lang/cargo#4535 + if target_triple.starts_with("wasm32-") && crate_type == "bin" && suffix == ".js" { + ret.push((".wasm".to_string(), TargetFileType::Normal, 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 *target_kind == TargetKind::Bin { + if target_triple.contains("-apple-") { + ret.push((".dSYM".to_string(), TargetFileType::DebugInfo, false)); + } else if target_triple.ends_with("-msvc") { + ret.push((".pdb".to_string(), TargetFileType::DebugInfo, false)); + } + } + + ret +} diff --git a/src/cargo/ops/cargo_rustc/context/unit_dependencies.rs b/src/cargo/ops/cargo_rustc/context/unit_dependencies.rs new file mode 100644 index 000000000..2fe20cde0 --- /dev/null +++ b/src/cargo/ops/cargo_rustc/context/unit_dependencies.rs @@ -0,0 +1,301 @@ +//! Constructs the dependency graph for compilation. +//! +//! Rust code is typically organized as a set of Cargo packages. The +//! dependencies between the packages themselves are stored in the +//! `Resolve` struct. However, we can't use that information as is for +//! compilation! A package typically contains several targets, or crates, +//! and these targets has inter-dependencies. For example, you need to +//! compile the `lib` target before the `bin` one, and you need to compile +//! `build.rs` before either of those. +//! +//! So, we need to lower the `Resolve`, which specifies dependencies between +//! *packages*, to a graph of dependencies between their *targets*, and this +//! is exactly what this module is doing! Well, almost exactly: another +//! complication is that we might want to compile the same target several times +//! (for example, with and without tests), so we actually build a dependency +//! graph of `Unit`s, which capture these properties. + +use ops::Unit; +use std::collections::HashMap; +use CargoResult; +use core::dependency::Kind as DepKind; +use ops::{Context, Kind}; +use core::Target; +use core::Profile; + +pub fn build_unit_dependencies<'a, 'cfg>( + roots: &[Unit<'a>], + cx: &Context<'a, 'cfg>, +) -> CargoResult, Vec>>> { + let mut deps = HashMap::new(); + for unit in roots.iter() { + deps_of(unit, cx, &mut deps)?; + } + + Ok(deps) +} + +fn deps_of<'a, 'b, 'cfg>( + unit: &Unit<'a>, + cx: &Context<'a, 'cfg>, + deps: &'b mut HashMap, Vec>>, +) -> CargoResult<&'b [Unit<'a>]> { + if !deps.contains_key(unit) { + let unit_deps = compute_deps(unit, cx, deps)?; + deps.insert(*unit, unit_deps.clone()); + for unit in unit_deps { + deps_of(&unit, cx, deps)?; + } + } + Ok(deps[unit].as_ref()) +} + +/// For a package, return all targets which are registered as dependencies +/// for that package. +fn compute_deps<'a, 'b, 'cfg>( + unit: &Unit<'a>, + cx: &Context<'a, 'cfg>, + deps: &'b mut HashMap, Vec>>, +) -> CargoResult>> { + if unit.profile.run_custom_build { + return compute_deps_custom_build(unit, cx, deps); + } else if unit.profile.doc && !unit.profile.test { + return compute_deps_doc(unit, cx); + } + + let id = unit.pkg.package_id(); + let deps = cx.resolve.deps(id); + let mut ret = deps.filter(|dep| { + unit.pkg + .dependencies() + .iter() + .filter(|d| d.name() == dep.name() && d.version_req().matches(dep.version())) + .any(|d| { + // If this target is a build command, then we only want build + // dependencies, otherwise we want everything *other than* build + // dependencies. + if unit.target.is_custom_build() != d.is_build() { + return false; + } + + // If this dependency is *not* a transitive dependency, then it + // only applies to test/example targets + if !d.is_transitive() && !unit.target.is_test() && !unit.target.is_example() + && !unit.profile.test + { + return false; + } + + // If this dependency is only available for certain platforms, + // make sure we're only enabling it for that platform. + if !cx.dep_platform_activated(d, unit.kind) { + return false; + } + + // If the dependency is optional, then we're only activating it + // if the corresponding feature was activated + if d.is_optional() && !cx.resolve.features(id).contains(&*d.name()) { + return false; + } + + // If we've gotten past all that, then this dependency is + // actually used! + true + }) + }).filter_map(|id| match cx.get_package(id) { + Ok(pkg) => pkg.targets().iter().find(|t| t.is_lib()).map(|t| { + let unit = Unit { + pkg, + target: t, + profile: lib_or_check_profile(unit, t, cx), + kind: unit.kind.for_target(t), + }; + Ok(unit) + }), + Err(e) => Some(Err(e)), + }) + .collect::>>()?; + + // If this target is a build script, then what we've collected so far is + // all we need. If this isn't a build script, then it depends on the + // build script if there is one. + if unit.target.is_custom_build() { + return Ok(ret); + } + ret.extend(dep_build_script(unit, cx)); + + // If this target is a binary, test, example, etc, then it depends on + // the library of the same package. The call to `resolve.deps` above + // didn't include `pkg` in the return values, so we need to special case + // it here and see if we need to push `(pkg, pkg_lib_target)`. + if unit.target.is_lib() && !unit.profile.doc { + return Ok(ret); + } + ret.extend(maybe_lib(unit, cx)); + + // Integration tests/benchmarks require binaries to be built + if unit.profile.test && (unit.target.is_test() || unit.target.is_bench()) { + ret.extend( + unit.pkg + .targets() + .iter() + .filter(|t| { + let no_required_features = Vec::new(); + + t.is_bin() && + // Skip binaries with required features that have not been selected. + t.required_features().unwrap_or(&no_required_features).iter().all(|f| { + cx.resolve.features(id).contains(f) + }) + }) + .map(|t| Unit { + pkg: unit.pkg, + target: t, + profile: lib_or_check_profile(unit, t, cx), + kind: unit.kind.for_target(t), + }), + ); + } + Ok(ret) +} + +/// Returns the dependencies needed to run a build script. +/// +/// The `unit` provided must represent an execution of a build script, and +/// the returned set of units must all be run before `unit` is run. +fn compute_deps_custom_build<'a, 'cfg>( + unit: &Unit<'a>, + cx: &Context<'a, 'cfg>, + deps: &mut HashMap, Vec>>, +) -> CargoResult>> { + // When not overridden, then the dependencies to run a build script are: + // + // 1. Compiling the build script itcx + // 2. For each immediate dependency of our package which has a `links` + // key, the execution of that build script. + let not_custom_build = unit.pkg + .targets() + .iter() + .find(|t| !t.is_custom_build()) + .unwrap(); + let tmp = Unit { + target: not_custom_build, + profile: &cx.profiles.dev, + ..*unit + }; + let deps = deps_of(&tmp, cx, deps)?; + Ok(deps.iter() + .filter_map(|unit| { + if !unit.target.linkable() || unit.pkg.manifest().links().is_none() { + return None; + } + dep_build_script(unit, cx) + }) + .chain(Some(Unit { + profile: cx.build_script_profile(unit.pkg.package_id()), + kind: Kind::Host, // build scripts always compiled for the host + ..*unit + })) + .collect()) +} + +/// Returns the dependencies necessary to document a package +fn compute_deps_doc<'a, 'cfg>( + unit: &Unit<'a>, + cx: &Context<'a, 'cfg>, +) -> CargoResult>> { + let deps = cx.resolve + .deps(unit.pkg.package_id()) + .filter(|dep| { + unit.pkg + .dependencies() + .iter() + .filter(|d| d.name() == dep.name()) + .any(|dep| match dep.kind() { + DepKind::Normal => cx.dep_platform_activated(dep, unit.kind), + _ => false, + }) + }) + .map(|dep| cx.get_package(dep)); + + // To document a library, we depend on dependencies actually being + // built. If we're documenting *all* libraries, then we also depend on + // the documentation of the library being built. + let mut ret = Vec::new(); + for dep in deps { + let dep = dep?; + let lib = match dep.targets().iter().find(|t| t.is_lib()) { + Some(lib) => lib, + None => continue, + }; + ret.push(Unit { + pkg: dep, + target: lib, + profile: lib_or_check_profile(unit, lib, cx), + kind: unit.kind.for_target(lib), + }); + if cx.build_config.doc_all { + ret.push(Unit { + pkg: dep, + target: lib, + profile: &cx.profiles.doc, + kind: unit.kind.for_target(lib), + }); + } + } + + // Be sure to build/run the build script for documented libraries as + ret.extend(dep_build_script(unit, cx)); + + // If we document a binary, we need the library available + if unit.target.is_bin() { + ret.extend(maybe_lib(unit, cx)); + } + Ok(ret) +} + +fn maybe_lib<'a, 'cfg>(unit: &Unit<'a>, cx: &Context<'a, 'cfg>) -> Option> { + unit.pkg + .targets() + .iter() + .find(|t| t.linkable()) + .map(|t| Unit { + pkg: unit.pkg, + target: t, + profile: lib_or_check_profile(unit, t, cx), + kind: unit.kind.for_target(t), + }) +} + +/// If a build script is scheduled to be run for the package specified by +/// `unit`, this function will return the unit to run that build script. +/// +/// Overriding a build script simply means that the running of the build +/// script itself doesn't have any dependencies, so even in that case a unit +/// of work is still returned. `None` is only returned if the package has no +/// build script. +fn dep_build_script<'a, 'cfg>(unit: &Unit<'a>, cx: &Context<'a, 'cfg>) -> Option> { + unit.pkg + .targets() + .iter() + .find(|t| t.is_custom_build()) + .map(|t| Unit { + pkg: unit.pkg, + target: t, + profile: &cx.profiles.custom_build, + kind: unit.kind, + }) +} + +fn lib_or_check_profile<'a, 'cfg>( + unit: &Unit, + target: &Target, + cx: &Context<'a, 'cfg>, +) -> &'a Profile { + if !target.is_custom_build() && !target.for_host() + && (unit.profile.check || (unit.profile.doc && !unit.profile.test)) + { + return &cx.profiles.check; + } + cx.lib_profile() +} diff --git a/src/cargo/ops/cargo_rustc/custom_build.rs b/src/cargo/ops/cargo_rustc/custom_build.rs index 32fb3ef74..ce0c0efd2 100644 --- a/src/cargo/ops/cargo_rustc/custom_build.rs +++ b/src/cargo/ops/cargo_rustc/custom_build.rs @@ -101,7 +101,8 @@ pub fn prepare<'a, 'cfg>( } fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<(Work, Work)> { - let dependencies = cx.dep_run_custom_build(unit)?; + assert!(unit.profile.run_custom_build); + let dependencies = cx.dep_targets(unit); let build_script_unit = dependencies .iter() .find(|d| !d.profile.run_custom_build && d.target.is_custom_build()) @@ -581,7 +582,7 @@ pub fn build_map<'b, 'cfg>(cx: &mut Context<'b, 'cfg>, units: &[Unit<'b>]) -> Ca // to rustc invocation caching schemes, so be sure to generate the same // set of build script dependency orderings via sorting the targets that // come out of the `Context`. - let mut targets = cx.dep_targets(unit)?; + let mut targets = cx.dep_targets(unit); targets.sort_by_key(|u| u.pkg.package_id()); for unit in targets.iter() { diff --git a/src/cargo/ops/cargo_rustc/fingerprint.rs b/src/cargo/ops/cargo_rustc/fingerprint.rs index 38ac975de..bd441d358 100644 --- a/src/cargo/ops/cargo_rustc/fingerprint.rs +++ b/src/cargo/ops/cargo_rustc/fingerprint.rs @@ -431,7 +431,7 @@ fn calculate<'a, 'cfg>( // elsewhere. Also skip fingerprints of binaries because they don't actually // induce a recompile, they're just dependencies in the sense that they need // to be built. - let deps = cx.dep_targets(unit)?; + let deps = cx.dep_targets(unit); let deps = deps.iter() .filter(|u| !u.target.is_custom_build() && !u.target.is_bin()) .map(|unit| { diff --git a/src/cargo/ops/cargo_rustc/job_queue.rs b/src/cargo/ops/cargo_rustc/job_queue.rs index c71bab90e..cf4c93625 100644 --- a/src/cargo/ops/cargo_rustc/job_queue.rs +++ b/src/cargo/ops/cargo_rustc/job_queue.rs @@ -416,7 +416,7 @@ impl<'a> Key<'a> { profile: self.profile, kind: self.kind, }; - let targets = cx.dep_targets(&unit)?; + let targets = cx.dep_targets(&unit); Ok(targets .iter() .filter_map(|unit| { diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index a602c7c84..695b882c3 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -170,6 +170,7 @@ pub fn compile_targets<'a, 'cfg: 'a>( cx.prepare()?; cx.probe_target_info()?; + cx.build_unit_dependencies(&units)?; cx.build_used_in_plugin_map(&units)?; custom_build::build_map(&mut cx, &units)?; @@ -215,7 +216,7 @@ pub fn compile_targets<'a, 'cfg: 'a>( } } - for dep in cx.dep_targets(unit)?.iter() { + for dep in cx.dep_targets(unit).iter() { if !unit.target.is_lib() { continue; } @@ -333,7 +334,7 @@ fn compile<'a, 'cfg: 'a>( drop(p); // Be sure to compile all dependencies of this target as well. - for unit in cx.dep_targets(unit)?.iter() { + for unit in cx.dep_targets(unit).iter() { compile(cx, jobs, unit, exec)?; } @@ -1022,7 +1023,7 @@ fn build_deps_args<'a, 'cfg>( }); } - let dep_targets = cx.dep_targets(unit)?; + let dep_targets = cx.dep_targets(unit); // If there is not one linkable target but should, rustc fails later // on if there is an `extern crate` for it. This may turn into a hard diff --git a/src/cargo/ops/cargo_rustc/output_depinfo.rs b/src/cargo/ops/cargo_rustc/output_depinfo.rs index 7508ca9d5..a8a83425e 100644 --- a/src/cargo/ops/cargo_rustc/output_depinfo.rs +++ b/src/cargo/ops/cargo_rustc/output_depinfo.rs @@ -61,7 +61,7 @@ fn add_deps_for_unit<'a, 'b>( } // Recursively traverse all transitive dependencies - for dep_unit in &context.dep_targets(unit)? { + for dep_unit in context.dep_targets(unit).iter() { let source_id = dep_unit.pkg.package_id().source_id(); if source_id.is_path() { add_deps_for_unit(deps, context, dep_unit, visited)?;