Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement cfg-based target-specific dependencies #2328

Merged
merged 1 commit into from
Feb 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 74 additions & 22 deletions src/cargo/core/dependency.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
use std::fmt;
use std::rc::Rc;
use std::str::FromStr;

use semver::VersionReq;
use rustc_serialize::{Encoder, Encodable};

use core::{SourceId, Summary, PackageId};
use std::rc::Rc;
use util::CargoResult;
use util::{CargoError, CargoResult, Cfg, CfgExpr, ChainError, human};

/// Information about a dependency requested by a Cargo manifest.
/// Cheap to copy.
#[derive(PartialEq, Clone ,Debug)]
pub struct Dependency {
inner: Rc<DependencyInner>,
}

/// The data underlying a Dependency.
#[derive(PartialEq,Clone,Debug)]
#[derive(PartialEq, Clone, Debug)]
pub struct DependencyInner {
name: String,
source_id: SourceId,
Expand All @@ -21,17 +31,15 @@ pub struct DependencyInner {

// This dependency should be used only for this platform.
// `None` means *all platforms*.
only_for_platform: Option<String>,
platform: Option<Platform>,
}

/// Information about a dependency requested by a Cargo manifest.
/// Cheap to copy.
#[derive(PartialEq,Clone,Debug)]
pub struct Dependency {
inner: Rc<DependencyInner>,
#[derive(Clone, Debug, PartialEq)]
pub enum Platform {
Name(String),
Cfg(CfgExpr),
}


#[derive(RustcEncodable)]
struct SerializedDependency<'a> {
name: &'a str,
Expand All @@ -42,7 +50,7 @@ struct SerializedDependency<'a> {
optional: bool,
uses_default_features: bool,
features: &'a [String],
target: &'a Option<&'a str>,
target: Option<&'a Platform>,
}

impl Encodable for Dependency {
Expand All @@ -55,7 +63,7 @@ impl Encodable for Dependency {
optional: self.is_optional(),
uses_default_features: self.uses_default_features(),
features: self.features(),
target: &self.only_for_platform(),
target: self.platform(),
}.encode(s)
}
}
Expand Down Expand Up @@ -106,7 +114,7 @@ impl DependencyInner {
features: Vec::new(),
default_features: true,
specified_req: None,
only_for_platform: None,
platform: None,
}
}

Expand All @@ -118,10 +126,10 @@ impl DependencyInner {
self.specified_req.as_ref().map(|s| &s[..])
}

/// If none, this dependencies must be built for all platforms.
/// If some, it must only be built for the specified platform.
pub fn only_for_platform(&self) -> Option<&str> {
self.only_for_platform.as_ref().map(|s| &s[..])
/// If none, this dependency must be built for all platforms.
/// If some, it must only be built for matching platforms.
pub fn platform(&self) -> Option<&Platform> {
self.platform.as_ref()
}

pub fn set_kind(mut self, kind: Kind) -> DependencyInner {
Expand Down Expand Up @@ -159,9 +167,9 @@ impl DependencyInner {
self
}

pub fn set_only_for_platform(mut self, platform: Option<String>)
-> DependencyInner {
self.only_for_platform = platform;
pub fn set_platform(mut self, platform: Option<Platform>)
-> DependencyInner {
self.platform = platform;
self
}

Expand Down Expand Up @@ -230,8 +238,8 @@ impl Dependency {

/// If none, this dependencies must be built for all platforms.
/// If some, it must only be built for the specified platform.
pub fn only_for_platform(&self) -> Option<&str> {
self.inner.only_for_platform()
pub fn platform(&self) -> Option<&Platform> {
self.inner.platform()
}

/// Lock this dependency to depending on the specified package id
Expand All @@ -258,3 +266,47 @@ impl Dependency {
self.inner.matches_id(id)
}
}

impl Platform {
pub fn matches(&self, name: &str, cfg: Option<&[Cfg]>) -> bool {
match *self {
Platform::Name(ref p) => p == name,
Platform::Cfg(ref p) => {
match cfg {
Some(cfg) => p.matches(cfg),
None => false,
}
}
}
}
}

impl Encodable for Platform {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
self.to_string().encode(s)
}
}

impl FromStr for Platform {
type Err = Box<CargoError>;

fn from_str(s: &str) -> CargoResult<Platform> {
if s.starts_with("cfg(") && s.ends_with(")") {
let s = &s[4..s.len()-1];
s.parse().map(Platform::Cfg).chain_error(|| {
human(format!("failed to parse `{}` as a cfg expression", s))
})
} else {
Ok(Platform::Name(s.to_string()))
}
}
}

impl fmt::Display for Platform {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Platform::Name(ref n) => n.fmt(f),
Platform::Cfg(ref e) => write!(f, "cfg({})", e),
}
}
}
100 changes: 61 additions & 39 deletions src/cargo/ops/cargo_rustc/context.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::collections::{HashSet, HashMap};
use std::path::{Path, PathBuf};
use std::str;
use std::str::{self, FromStr};
use std::sync::Arc;

use regex::Regex;

use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target, Profile};
use core::{TargetKind, LibKind, Profiles, Metadata, Dependency};
use core::dependency::Kind as DepKind;
use util::{self, CargoResult, ChainError, internal, Config, profile};
use util::{self, CargoResult, ChainError, internal, Config, profile, Cfg, human};

use super::TargetConfig;
use super::custom_build::{BuildState, BuildScripts};
Expand Down Expand Up @@ -41,16 +41,20 @@ pub struct Context<'a, 'cfg: 'a> {
host: Layout,
target: Option<Layout>,
target_triple: String,
host_dylib: Option<(String, String)>,
host_staticlib: Option<(String, String)>,
host_exe: String,
target_info: TargetInfo,
host_info: TargetInfo,
package_set: &'a PackageSet,
target_dylib: Option<(String, String)>,
target_staticlib: Option<(String, String)>,
target_exe: String,
profiles: &'a Profiles,
}

#[derive(Clone)]
struct TargetInfo {
dylib: Option<(String, String)>,
staticlib: Option<(String, String)>,
exe: String,
cfg: Option<Vec<Cfg>>,
}

impl<'a, 'cfg> Context<'a, 'cfg> {
pub fn new(resolve: &'a Resolve,
sources: &'a SourceMap<'cfg>,
Expand All @@ -62,12 +66,11 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
profiles: &'a Profiles) -> CargoResult<Context<'a, 'cfg>> {
let target = build_config.requested_target.clone();
let target = target.as_ref().map(|s| &s[..]);
let (target_dylib, target_staticlib, target_exe) = try!(Context::filename_parts(target,
config));
let (host_dylib, host_staticlib, host_exe) = if build_config.requested_target.is_none() {
(target_dylib.clone(), target_staticlib.clone(), target_exe.clone())
let target_info = try!(Context::target_info(target, config));
let host_info = if build_config.requested_target.is_none() {
target_info.clone()
} else {
try!(Context::filename_parts(None, config))
try!(Context::target_info(None, config))
};
let target_triple = target.unwrap_or_else(|| {
&config.rustc_info().host[..]
Expand All @@ -83,12 +86,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
sources: sources,
package_set: deps,
config: config,
target_dylib: target_dylib,
target_staticlib: target_staticlib,
target_exe: target_exe,
host_dylib: host_dylib,
host_staticlib: host_staticlib,
host_exe: host_exe,
target_info: target_info,
host_info: host_info,
compilation: Compilation::new(config),
build_state: Arc::new(BuildState::new(&build_config, deps)),
build_config: build_config,
Expand All @@ -103,8 +102,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> {

/// Run `rustc` to discover the dylib prefix/suffix for the target
/// specified as well as the exe suffix
fn filename_parts(target: Option<&str>, cfg: &Config)
-> CargoResult<(Option<(String, String)>, Option<(String, String)>, String)> {
fn target_info(target: Option<&str>, cfg: &Config)
-> CargoResult<TargetInfo> {
let mut process = util::process(cfg.rustc());
process.arg("-")
.arg("--crate-name").arg("_")
Expand All @@ -116,7 +115,18 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
if let Some(s) = target {
process.arg("--target").arg(s);
};
let output = try!(process.exec_with_output());

let mut with_cfg = process.clone();
with_cfg.arg("--print=cfg");

let mut has_cfg = true;
let output = try!(with_cfg.exec_with_output().or_else(|_| {
has_cfg = false;
process.exec_with_output()
}).chain_error(|| {
human(format!("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();
Expand All @@ -143,13 +153,25 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
Some((staticlib_parts[0].to_string(), staticlib_parts[1].to_string()))
};

let exe_suffix = if nobin.is_match(error) {
let exe = if nobin.is_match(error) {
String::new()
} else {
lines.next().unwrap().trim()
.split('_').skip(1).next().unwrap().to_string()
};
Ok((dylib, staticlib, exe_suffix))

let cfg = if has_cfg {
Some(try!(lines.map(Cfg::from_str).collect()))
} else {
None
};

Ok(TargetInfo {
dylib: dylib,
staticlib: staticlib,
exe: exe,
cfg: cfg,
})
}

/// Prepare this context, ensuring that all filesystem directories are in
Expand Down Expand Up @@ -207,9 +229,9 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
/// otherwise it corresponds to the target platform.
fn dylib(&self, kind: Kind) -> CargoResult<(&str, &str)> {
let (triple, pair) = if kind == Kind::Host {
(&self.config.rustc_info().host, &self.host_dylib)
(&self.config.rustc_info().host, &self.host_info.dylib)
} else {
(&self.target_triple, &self.target_dylib)
(&self.target_triple, &self.target_info.dylib)
};
match *pair {
None => bail!("dylib outputs are not supported for {}", triple),
Expand All @@ -223,9 +245,9 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
/// otherwise it corresponds to the target platform.
pub fn staticlib(&self, kind: Kind) -> CargoResult<(&str, &str)> {
let (triple, pair) = if kind == Kind::Host {
(&self.config.rustc_info().host, &self.host_staticlib)
(&self.config.rustc_info().host, &self.host_info.staticlib)
} else {
(&self.target_triple, &self.target_staticlib)
(&self.target_triple, &self.target_info.staticlib)
};
match *pair {
None => bail!("staticlib outputs are not supported for {}", triple),
Expand Down Expand Up @@ -284,9 +306,9 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
pub fn target_filenames(&self, unit: &Unit) -> CargoResult<Vec<String>> {
let stem = self.file_stem(unit);
let suffix = if unit.target.for_host() {
&self.host_exe
&self.host_info.exe
} else {
&self.target_exe
&self.target_info.exe
};

let mut ret = Vec::new();
Expand Down Expand Up @@ -532,15 +554,15 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
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.
match (dep.only_for_platform(), kind) {
(Some(ref platform), Kind::Host) => {
*platform == self.config.rustc_info().host
},
(Some(ref platform), Kind::Target) => {
*platform == self.target_triple
},
(None, _) => true
}
let platform = match dep.platform() {
Some(p) => p,
None => return true,
};
let (name, info) = match kind {
Kind::Host => (&self.config.rustc_info().host, &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.
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/ops/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fn transmit(pkg: &Package, tarball: &Path, registry: &mut Registry)
name: dep.name().to_string(),
features: dep.features().to_vec(),
version_req: dep.version_req().to_string(),
target: dep.only_for_platform().map(|s| s.to_string()),
target: dep.platform().map(|s| s.to_string()),
kind: match dep.kind() {
Kind::Normal => "normal",
Kind::Build => "build",
Expand Down
7 changes: 6 additions & 1 deletion src/cargo/sources/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,11 @@ impl<'cfg> RegistrySource<'cfg> {
_ => Kind::Normal,
};

let platform = match target {
Some(target) => Some(try!(target.parse())),
None => None,
};

// Unfortunately older versions of cargo and/or the registry ended up
// publishing lots of entries where the features array contained the
// empty feature, "", inside. This confuses the resolution process much
Expand All @@ -445,7 +450,7 @@ impl<'cfg> RegistrySource<'cfg> {
Ok(dep.set_optional(optional)
.set_default_features(default_features)
.set_features(features)
.set_only_for_platform(target)
.set_platform(platform)
.set_kind(kind)
.into_dependency())
}
Expand Down
Loading