diff --git a/src/cargo/core/dependency.rs b/src/cargo/core/dependency.rs index f877cbadb37..fe83cf7160c 100644 --- a/src/cargo/core/dependency.rs +++ b/src/cargo/core/dependency.rs @@ -16,6 +16,10 @@ pub struct Dependency { optional: bool, default_features: bool, features: Vec, + + // This dependency should be used only for this platform. + // `None` means *all platforms*. + only_for_platform: Option, } impl Dependency { @@ -57,6 +61,7 @@ impl Dependency { features: Vec::new(), default_features: true, specified_req: None, + only_for_platform: None, } } @@ -121,6 +126,11 @@ impl Dependency { .source_id(id.get_source_id().clone()) } + pub fn only_for_platform(mut self, platform: Option) -> Dependency { + self.only_for_platform = platform; + self + } + /// Returns false if the dependency is only used to build the local package. pub fn is_transitive(&self) -> bool { self.transitive } pub fn is_optional(&self) -> bool { self.optional } @@ -140,6 +150,21 @@ impl Dependency { (self.only_match_name || (self.req.matches(id.get_version()) && &self.source_id == id.get_source_id())) } + + /// If none, this dependencies must be built for all platforms. + /// If some, it must only be built for the specified platform. + pub fn get_only_for_platform(&self) -> Option<&str> { + self.only_for_platform.as_ref().map(|s| s.as_slice()) + } + + /// Returns true if the dependency should be built for this platform. + pub fn is_active_for_platform(&self, platform: &str) -> bool { + match self.only_for_platform { + None => true, + Some(ref p) if p.as_slice() == platform => true, + _ => false + } + } } #[deriving(PartialEq,Clone,Encodable)] diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 5179fc91ef4..5931df8c727 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -32,7 +32,8 @@ pub enum ResolveMethod<'a> { ResolveEverything, ResolveRequired(/* dev_deps = */ bool, /* features = */ &'a [String], - /* uses_default_features = */ bool), + /* uses_default_features = */ bool, + /* target_platform = */ Option<&'a str>), } impl Resolve { @@ -150,6 +151,12 @@ fn activate(mut cx: Context, parent: &Summary, method: ResolveMethod) -> CargoResult> { + // Extracting the platform request. + let platform = match method { + ResolveRequired(_, _, _, platform) => platform, + ResolveEverything => None, + }; + // First, figure out our set of dependencies based on the requsted set of // features. This also calculates what features we're going to enable for // our own dependencies. @@ -176,18 +183,19 @@ fn activate(mut cx: Context, a.len().cmp(&b.len()) }); - activate_deps(cx, registry, parent, deps.as_slice(), 0) + activate_deps(cx, registry, parent, platform, deps.as_slice(), 0) } -fn activate_deps(cx: Context, - registry: &mut R, - parent: &Summary, - deps: &[(&Dependency, Vec>, Vec)], - cur: uint) -> CargoResult> { +fn activate_deps<'a, R: Registry>(cx: Context, + registry: &mut R, + parent: &Summary, + platform: Option<&'a str>, + deps: &'a [(&Dependency, Vec>, Vec)], + cur: uint) -> CargoResult> { if cur == deps.len() { return Ok(Ok(cx)) } let (dep, ref candidates, ref features) = deps[cur]; let method = ResolveRequired(false, features.as_slice(), - dep.uses_default_features()); + dep.uses_default_features(), platform); let key = (dep.get_name().to_string(), dep.get_source_id().clone()); let prev_active = cx.activations.find(&key) @@ -269,7 +277,7 @@ fn activate_deps(cx: Context, Err(e) => { last_err = Some(e); continue } } }; - match try!(activate_deps(my_cx, registry, parent, deps, cur + 1)) { + match try!(activate_deps(my_cx, registry, parent, platform, deps, cur + 1)) { Ok(cx) => return Ok(Ok(cx)), Err(e) => { last_err = Some(e); } } @@ -341,12 +349,22 @@ fn resolve_features<'a>(cx: &mut Context, parent: &'a Summary, (&'a Dependency, Vec)>> { let dev_deps = match method { ResolveEverything => true, - ResolveRequired(dev_deps, _, _) => dev_deps, + ResolveRequired(dev_deps, _, _, _) => dev_deps, }; // First, filter by dev-dependencies let deps = parent.get_dependencies(); - let mut deps = deps.iter().filter(|d| d.is_transitive() || dev_deps); + let deps = deps.iter().filter(|d| d.is_transitive() || dev_deps); + + // Second, ignoring dependencies that should not be compiled for this platform + let mut deps = deps.filter(|d| { + match method { + ResolveRequired(_, _, _, Some(ref platform)) => { + d.is_active_for_platform(platform.as_slice()) + }, + _ => true + } + }); let (mut feature_deps, used_features) = try!(build_features(parent, method)); let mut ret = HashMap::new(); @@ -419,7 +437,7 @@ fn build_features(s: &Summary, method: ResolveMethod) &mut visited)); } } - ResolveRequired(_, requested_features, _) => { + ResolveRequired(_, requested_features, _, _) => { for feat in requested_features.iter() { try!(add_feature(s, feat.as_slice(), &mut deps, &mut used, &mut visited)); @@ -427,7 +445,7 @@ fn build_features(s: &Summary, method: ResolveMethod) } } match method { - ResolveEverything | ResolveRequired(_, _, true) => { + ResolveEverything | ResolveRequired(_, _, true, _) => { if s.get_features().find_equiv(&"default").is_some() && !visited.contains_equiv(&"default") { try!(add_feature(s, "default", &mut deps, &mut used, diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 79ae34f559c..8e44a81d810 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -86,6 +86,7 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions) let (packages, resolve_with_overrides, sources) = { let mut config = try!(Config::new(*shell, jobs, target.clone())); + let rustc_host = config.rustc_host().to_string(); let mut registry = PackageRegistry::new(&mut config); // First, resolve the package's *listed* dependencies, as well as @@ -98,8 +99,11 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions) let _p = profile::start("resolving w/ overrides..."); try!(registry.add_overrides(override_ids)); + + let platform = target.as_ref().map(|e| e.as_slice()).or(Some(rustc_host.as_slice())); let method = resolver::ResolveRequired(dev_deps, features.as_slice(), - !no_default_features); + !no_default_features, + platform); let resolved_with_overrides = try!(ops::resolve_with_previous(&mut registry, package, method, Some(&resolve), None)); diff --git a/src/cargo/ops/cargo_rustc/context.rs b/src/cargo/ops/cargo_rustc/context.rs index e3e22b3431a..4e8b833103c 100644 --- a/src/cargo/ops/cargo_rustc/context.rs +++ b/src/cargo/ops/cargo_rustc/context.rs @@ -3,7 +3,7 @@ use std::collections::hashmap::{Occupied, Vacant}; use std::str; use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target}; -use util::{mod, CargoResult, ChainError, internal, Config, profile, Require}; +use util::{mod, CargoResult, ChainError, internal, Config, profile}; use util::human; use super::{Kind, KindPlugin, KindTarget, Compilation}; @@ -17,7 +17,6 @@ pub enum PlatformRequirement { } pub struct Context<'a, 'b> { - pub rustc_version: String, pub config: &'b mut Config<'b>, pub resolve: &'a Resolve, pub sources: &'a SourceMap<'b>, @@ -27,7 +26,6 @@ pub struct Context<'a, 'b> { host: Layout, target: Option, target_triple: String, - host_triple: String, host_dylib: Option<(String, String)>, package_set: &'a PackageSet, target_dylib: Option<(String, String)>, @@ -49,13 +47,10 @@ impl<'a, 'b> Context<'a, 'b> { let (dylib, _) = try!(Context::filename_parts(None)); dylib }; - let (rustc_version, rustc_host) = try!(Context::rustc_version()); let target_triple = config.target().map(|s| s.to_string()); - let target_triple = target_triple.unwrap_or(rustc_host.clone()); + let target_triple = target_triple.unwrap_or(config.rustc_host().to_string()); Ok(Context { - rustc_version: rustc_version, target_triple: target_triple, - host_triple: rustc_host, env: env, host: host, target: target, @@ -71,28 +66,6 @@ impl<'a, 'b> Context<'a, 'b> { }) } - /// Run `rustc` to figure out what its current version string is. - /// - /// The second element of the tuple returned is the target triple that rustc - /// is a host for. - fn rustc_version() -> CargoResult<(String, String)> { - let output = try!(util::process("rustc").arg("-v").arg("verbose") - .exec_with_output()); - let output = try!(String::from_utf8(output.output).map_err(|_| { - internal("rustc -v didn't return utf8 output") - })); - let triple = { - let triple = output.as_slice().lines().filter(|l| { - l.starts_with("host: ") - }).map(|l| l.slice_from(6)).next(); - let triple = try!(triple.require(|| { - internal("rustc -v didn't have a line for `host:`") - })); - triple.to_string() - }; - Ok((output, triple)) - } - /// Run `rustc` to discover the dylib prefix/suffix for the target /// specified as well as the exe suffix fn filename_parts(target: Option<&str>) @@ -204,9 +177,9 @@ impl<'a, 'b> Context<'a, 'b> { /// otherwise it corresponds to the target platform. fn dylib(&self, kind: Kind) -> CargoResult<(&str, &str)> { let (triple, pair) = if kind == KindPlugin { - (&self.host_triple, &self.host_dylib) + (self.config.rustc_host(), &self.host_dylib) } else { - (&self.target_triple, &self.target_dylib) + (self.target_triple.as_slice(), &self.target_dylib) }; match *pair { None => return Err(human(format!("dylib outputs are not supported \ diff --git a/src/cargo/ops/cargo_rustc/fingerprint.rs b/src/cargo/ops/cargo_rustc/fingerprint.rs index 64866ab3a83..b5fc0669407 100644 --- a/src/cargo/ops/cargo_rustc/fingerprint.rs +++ b/src/cargo/ops/cargo_rustc/fingerprint.rs @@ -229,7 +229,7 @@ fn is_fresh(loc: &Path, new_fingerprint: &str) -> CargoResult { /// fingerprint. fn mk_fingerprint(cx: &Context, data: &T) -> String { let hasher = SipHasher::new_with_keys(0,0); - util::to_hex(hasher.hash(&(&cx.rustc_version, data))) + util::to_hex(hasher.hash(&(cx.config.rustc_version(), data))) } fn calculate_target_fresh(pkg: &Package, dep_info: &Path) -> CargoResult { diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index ec34c060daa..05e3c0d0a99 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -5,8 +5,8 @@ use std::io::fs::PathExtensions; use std::os; use core::{SourceMap, Package, PackageId, PackageSet, Target, Resolve}; -use util::{CargoResult, ProcessBuilder, CargoError, human, caused_human}; -use util::{Config, internal, ChainError, Fresh, profile}; +use util::{mod, CargoResult, ProcessBuilder, CargoError, human, caused_human}; +use util::{Require, Config, internal, ChainError, Fresh, profile}; use self::job::{Job, Work}; use self::job_queue as jq; @@ -28,6 +28,28 @@ mod layout; #[deriving(PartialEq, Eq)] pub enum Kind { KindPlugin, KindTarget } +/// Run `rustc` to figure out what its current version string is. +/// +/// The second element of the tuple returned is the target triple that rustc +/// is a host for. +pub fn rustc_version() -> CargoResult<(String, String)> { + let output = try!(util::process("rustc").arg("-v").arg("verbose") + .exec_with_output()); + let output = try!(String::from_utf8(output.output).map_err(|_| { + internal("rustc -v didn't return utf8 output") + })); + let triple = { + let triple = output.as_slice().lines().filter(|l| { + l.starts_with("host: ") + }).map(|l| l.slice_from(6)).next(); + let triple = try!(triple.require(|| { + internal("rustc -v didn't have a line for `host:`") + })); + triple.to_string() + }; + Ok((output, triple)) +} + // This is a temporary assert that ensures the consistency of the arguments // given the current limitations of Cargo. The long term fix is to have each // Target know the absolute path to the build location. diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index 5176ce75f29..77f43cac3e5 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -1,7 +1,7 @@ pub use self::cargo_clean::{clean, CleanOptions}; pub use self::cargo_compile::{compile, compile_pkg, CompileOptions}; pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages}; -pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind}; +pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind, rustc_version}; pub use self::cargo_rustc::{KindTarget, KindPlugin, Context, LayoutProxy}; pub use self::cargo_rustc::{PlatformRequirement, PlatformTarget}; pub use self::cargo_rustc::{PlatformPlugin, PlatformPluginAndTarget}; diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index fbef2c62917..9ffef7581c3 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -72,7 +72,7 @@ fn transmit(pkg: &Package, tarball: &Path, registry: &mut Registry) name: dep.get_name().to_string(), features: dep.get_features().to_vec(), version_req: dep.get_version_req().to_string(), - target: None, // FIXME: fill this out + target: dep.get_only_for_platform().map(|s| s.to_string()), } }).collect::>(); let manifest = pkg.get_manifest(); diff --git a/src/cargo/sources/registry.rs b/src/cargo/sources/registry.rs index f989559c3c1..a112f4e7b52 100644 --- a/src/cargo/sources/registry.rs +++ b/src/cargo/sources/registry.rs @@ -417,10 +417,11 @@ impl<'a, 'b> RegistrySource<'a, 'b> { let dep = try!(Dependency::parse(name.as_slice(), Some(req.as_slice()), &self.source_id)); - drop(target); // FIXME: pass this in + Ok(dep.optional(optional) .default_features(default_features) - .features(features)) + .features(features) + .only_for_platform(target)) } /// Actually perform network operations to update the registry diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index e0a57e0401b..20c37f3c9e3 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -7,6 +7,7 @@ use std::string; use serialize::{Encodable,Encoder}; use toml; use core::MultiShell; +use ops; use util::{CargoResult, ChainError, Require, internal, human}; use util::toml as cargo_toml; @@ -18,6 +19,9 @@ pub struct Config<'a> { target: Option, linker: Option, ar: Option, + rustc_version: string::String, + /// The current host and default target of rustc + rustc_host: string::String, } impl<'a> Config<'a> { @@ -27,6 +31,9 @@ impl<'a> Config<'a> { if jobs == Some(0) { return Err(human("jobs must be at least 1")) } + + let (rustc_version, rustc_host) = try!(ops::rustc_version()); + Ok(Config { home_path: try!(os::homedir().require(|| { human("Cargo couldn't find your home directory. \ @@ -37,6 +44,8 @@ impl<'a> Config<'a> { target: target, ar: None, linker: None, + rustc_version: rustc_version, + rustc_host: rustc_host, }) } @@ -84,6 +93,16 @@ impl<'a> Config<'a> { pub fn ar(&self) -> Option<&str> { self.ar.as_ref().map(|t| t.as_slice()) } + + /// Return the output of `rustc -v verbose` + pub fn rustc_version(&self) -> &str { + self.rustc_version.as_slice() + } + + /// Return the host platform and default target of rustc + pub fn rustc_host(&self) -> &str { + self.rustc_host.as_slice() + } } #[deriving(Eq,PartialEq,Clone,Encodable,Decodable)] diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index 27082011d64..f92d498ce80 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -211,6 +211,7 @@ pub struct TomlManifest { dependencies: Option>, dev_dependencies: Option>, features: Option>>, + target: Option>, } #[deriving(Decodable, Clone, Default)] @@ -464,8 +465,15 @@ impl TomlManifest { }; // Collect the deps - try!(process_dependencies(&mut cx, false, self.dependencies.as_ref())); - try!(process_dependencies(&mut cx, true, self.dev_dependencies.as_ref())); + try!(process_dependencies(&mut cx, false, None, self.dependencies.as_ref())); + try!(process_dependencies(&mut cx, true, None, self.dev_dependencies.as_ref())); + + if let Some(targets) = self.target.as_ref() { + for (name, platform) in targets.iter() { + try!(process_dependencies(&mut cx, false, Some(name.clone()), + platform.dependencies.as_ref())); + } + } } let build = match project.build { @@ -503,7 +511,7 @@ impl TomlManifest { } } -fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool, +fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool, platform: Option, new_deps: Option<&HashMap>) -> CargoResult<()> { let dependencies = match new_deps { @@ -544,6 +552,7 @@ fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool, .map(|v| v.as_slice()), &new_source_id)); let dep = dep.transitive(!dev) + .only_for_platform(platform.clone()) .features(details.features.unwrap_or(Vec::new())) .default_features(details.default_features.unwrap_or(true)) .optional(details.optional.unwrap_or(false)); @@ -572,6 +581,12 @@ enum TomlPathValue { TomlPath(Path), } +/// Corresponds to a `target` entry, but `TomlTarget` is already used. +#[deriving(Decodable)] +struct TomlPlatform { + dependencies: Option>, +} + impl TomlTarget { fn new() -> TomlTarget { TomlTarget { diff --git a/tests/test_cargo_compile.rs b/tests/test_cargo_compile.rs index f8b77781333..ee2388e0a33 100644 --- a/tests/test_cargo_compile.rs +++ b/tests/test_cargo_compile.rs @@ -1715,3 +1715,121 @@ Caused by: ")); }) + +#[cfg(target_os = "linux")] +test!(cargo_platform_specific_dependency { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [target.i686-unknown-linux-gnu.dependencies.bar] + path = "bar" + [target.x86_64-unknown-linux-gnu.dependencies.bar] + path = "bar" + "#) + .file("src/main.rs", + main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice()) + .file("bar/Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + "#) + .file("bar/src/lib.rs", r#" + pub fn gimme() -> String { + "test passed".to_string() + } + "#); + + p.cargo_process("build") + .exec_with_output() + .assert(); + + assert_that(&p.bin("foo"), existing_file()); + + assert_that( + cargo::util::process(p.bin("foo")), + execs().with_stdout("test passed\n")); +}) + +#[cfg(not(target_os = "linux"))] +test!(cargo_platform_specific_dependency { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [target.i686-unknown-linux-gnu.dependencies.bar] + path = "bar" + [target.x86_64-unknown-linux-gnu.dependencies.bar] + path = "bar" + "#) + .file("src/main.rs", + main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice()) + .file("bar/Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + "#) + .file("bar/src/lib.rs", r#" + extern crate baz; + + pub fn gimme() -> String { + format!("") + } + "#); + + assert_that(p.cargo_process("build"), + execs().with_status(101)); +}) + +test!(cargo_platform_specific_dependency_wrong_platform { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [target.non-existing-triplet.dependencies.bar] + path = "bar" + "#) + .file("src/main.rs", r#" + fn main() {} + "#) + .file("bar/Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + "#) + .file("bar/src/lib.rs", r#" + invalid rust file, should not be compiled + "#); + + p.cargo_process("build") + .exec_with_output() + .assert(); + + assert_that(&p.bin("foo"), existing_file()); + + assert_that( + cargo::util::process(p.bin("foo")), + execs()); + + let lockfile = p.root().join("Cargo.lock"); + let lockfile = File::open(&lockfile).read_to_string().assert(); + assert!(lockfile.as_slice().contains("bar")) +})