diff --git a/src/bin/build.rs b/src/bin/build.rs index 04a108a4030..8d79c09ab7e 100644 --- a/src/bin/build.rs +++ b/src/bin/build.rs @@ -32,6 +32,7 @@ pub struct Options { flag_frozen: bool, flag_all: bool, flag_exclude: Vec, + flag_config: Vec, } pub const USAGE: &'static str = " @@ -41,32 +42,33 @@ Usage: cargo build [options] Options: - -h, --help Print this message - -p SPEC, --package SPEC ... Package to build - --all Build all packages in the workspace - --exclude SPEC ... Exclude packages from the build - -j N, --jobs N Number of parallel jobs, defaults to # of CPUs - --lib Build only this package's library - --bin NAME Build only the specified binary - --bins Build all binaries - --example NAME Build only the specified example - --examples Build all examples - --test NAME Build only the specified test target - --tests Build all tests - --bench NAME Build only the specified bench target - --benches Build all benches - --release Build artifacts in release mode, with optimizations - --features FEATURES Space-separated list of features to also build - --all-features Build all available features - --no-default-features Do not build the `default` feature - --target TRIPLE Build for the target triple - --manifest-path PATH Path to the manifest to compile - -v, --verbose ... Use verbose output (-vv very verbose/build.rs output) - -q, --quiet No output printed to stdout - --color WHEN Coloring: auto, always, never - --message-format FMT Error format: human, json [default: human] - --frozen Require Cargo.lock and cache are up to date - --locked Require Cargo.lock is up to date + -h, --help Print this message + -p SPEC, --package SPEC ... Package to build + --all Build all packages in the workspace + --exclude SPEC ... Exclude packages from the build + -j N, --jobs N Number of parallel jobs, defaults to # of CPUs + --lib Build only this package's library + --bin NAME Build only the specified binary + --bins Build all binaries + --example NAME Build only the specified example + --examples Build all examples + --test NAME Build only the specified test target + --tests Build all tests + --bench NAME Build only the specified bench target + --benches Build all benches + --release Build artifacts in release mode, with optimizations + --features FEATURES Space-separated list of features to also build + --all-features Build all available features + --no-default-features Do not build the `default` feature + --target TRIPLE Build for the target triple + --manifest-path PATH Path to the manifest to compile + -c CONFIG, --config CONFIG ... Set/override values specified in Cargo.toml + -v, --verbose ... Use verbose output (-vv very verbose/build.rs output) + -q, --quiet No output printed to stdout + --color WHEN Coloring: auto, always, never + --message-format FMT Error format: human, json [default: human] + --frozen Require Cargo.lock and cache are up to date + --locked Require Cargo.lock is up to date If the --package argument is given, then SPEC is a package id specification which indicates which package should be built. If it is not given, then the @@ -92,7 +94,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult { options.flag_locked)?; let root = find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())?; - let ws = Workspace::new(&root, config)?; + let ws = Workspace::new_with_overrides(&root, config, &options.flag_config)?; let spec = Packages::from_flags(ws.is_virtual(), options.flag_all, diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 148e1e9ff44..f3698c5c958 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -101,6 +101,12 @@ impl<'cfg> Workspace<'cfg> { /// before returning it, so `Ok` is only returned for valid workspaces. pub fn new(manifest_path: &Path, config: &'cfg Config) -> CargoResult> { + let overrides = vec!(); + Workspace::new_with_overrides(manifest_path, config, &overrides) + } + + pub fn new_with_overrides(manifest_path: &Path, config: &'cfg Config, + overrides: &Vec) -> CargoResult> { let target_dir = config.target_dir()?; let mut ws = Workspace { @@ -116,7 +122,7 @@ impl<'cfg> Workspace<'cfg> { is_ephemeral: false, require_optional_deps: true, }; - ws.root_manifest = ws.find_root(manifest_path)?; + ws.root_manifest = ws.find_root(manifest_path, &overrides)?; ws.find_members()?; ws.validate()?; Ok(ws) @@ -272,7 +278,7 @@ impl<'cfg> Workspace<'cfg> { /// /// Returns an error if `manifest_path` isn't actually a valid manifest or /// if some other transient error happens. - fn find_root(&mut self, manifest_path: &Path) + fn find_root(&mut self, manifest_path: &Path, overrides: &Vec) -> CargoResult> { fn read_root_pointer(member_manifest: &Path, root_link: &str) -> CargoResult { let path = member_manifest.parent().unwrap() @@ -283,7 +289,7 @@ impl<'cfg> Workspace<'cfg> { }; { - let current = self.packages.load(&manifest_path)?; + let current = self.packages.load(&manifest_path, &overrides)?; match *current.workspace_config() { WorkspaceConfig::Root { .. } => { debug!("find_root - is root {}", manifest_path.display()); @@ -300,7 +306,7 @@ impl<'cfg> Workspace<'cfg> { let manifest = path.join("Cargo.toml"); debug!("find_root - trying {}", manifest.display()); if manifest.exists() { - match *self.packages.load(&manifest)?.workspace_config() { + match *self.packages.load(&manifest, &overrides)?.workspace_config() { WorkspaceConfig::Root { ref exclude, ref members } => { debug!("find_root - found a root checking exclusion"); if !is_excluded(members, exclude, path, manifest_path) { @@ -337,7 +343,8 @@ impl<'cfg> Workspace<'cfg> { } }; let members = { - let root = self.packages.load(&root_manifest)?; + let unused = vec!(); + let root = self.packages.load(&root_manifest, &unused)?; match *root.workspace_config() { WorkspaceConfig::Root { ref members, .. } => members.clone(), _ => bail!("root of a workspace inferred but wasn't a root: {}", @@ -379,16 +386,17 @@ impl<'cfg> Workspace<'cfg> { if self.members.iter().any(|p| p == &manifest_path) { return Ok(()) } + let unused = vec!(); if is_path_dep && !manifest_path.parent().unwrap().starts_with(self.root()) - && self.find_root(&manifest_path)? != self.root_manifest { + && self.find_root(&manifest_path, &unused)? != self.root_manifest { // If `manifest_path` is a path dependency outside of the workspace, // don't add it, or any of its dependencies, as a members. return Ok(()) } let root = root_manifest.parent().unwrap(); - match *self.packages.load(root_manifest)?.workspace_config() { + match *self.packages.load(root_manifest, &unused)?.workspace_config() { WorkspaceConfig::Root { ref members, ref exclude } => { if is_excluded(members, exclude, root, &manifest_path) { return Ok(()) @@ -401,7 +409,7 @@ impl<'cfg> Workspace<'cfg> { self.members.push(manifest_path.clone()); let candidates = { - let pkg = match *self.packages.load(&manifest_path)? { + let pkg = match *self.packages.load(&manifest_path, &unused)? { MaybePackage::Package(ref p) => p, MaybePackage::Virtual(_) => return Ok(()), }; @@ -471,8 +479,9 @@ impl<'cfg> Workspace<'cfg> { } } + let unused = vec!(); for member in self.members.clone() { - let root = self.find_root(&member)?; + let root = self.find_root(&member, &unused)?; if root == self.root_manifest { continue } @@ -613,14 +622,14 @@ impl<'cfg> Packages<'cfg> { &self.packages[manifest_path.parent().unwrap()] } - fn load(&mut self, manifest_path: &Path) -> CargoResult<&MaybePackage> { + fn load(&mut self, manifest_path: &Path, overrides: &Vec) -> CargoResult<&MaybePackage> { let key = manifest_path.parent().unwrap(); match self.packages.entry(key.to_path_buf()) { Entry::Occupied(e) => Ok(e.into_mut()), Entry::Vacant(v) => { let source_id = SourceId::for_path(key)?; let (manifest, _nested_paths) = - read_manifest(&manifest_path, &source_id, self.config)?; + read_manifest(&manifest_path, &source_id, &overrides, self.config)?; Ok(v.insert(match manifest { EitherManifest::Real(manifest) => { MaybePackage::Package(Package::new(manifest, diff --git a/src/cargo/ops/cargo_read_manifest.rs b/src/cargo/ops/cargo_read_manifest.rs index 3cc736a2d63..d018de4f501 100644 --- a/src/cargo/ops/cargo_read_manifest.rs +++ b/src/cargo/ops/cargo_read_manifest.rs @@ -12,7 +12,8 @@ use util::toml::read_manifest; pub fn read_package(path: &Path, source_id: &SourceId, config: &Config) -> CargoResult<(Package, Vec)> { trace!("read_package; path={}; source-id={}", path.display(), source_id); - let (manifest, nested) = read_manifest(path, source_id, config)?; + let unused = vec!(); + let (manifest, nested) = read_manifest(path, source_id, &unused, config)?; let manifest = match manifest { EitherManifest::Real(manifest) => manifest, EitherManifest::Virtual(..) => { @@ -114,7 +115,8 @@ fn read_nested_packages(path: &Path, let manifest_path = find_project_manifest_exact(path, "Cargo.toml")?; - let (manifest, nested) = match read_manifest(&manifest_path, source_id, config) { + let unused = vec!(); + let (manifest, nested) = match read_manifest(&manifest_path, source_id, &unused, config) { Err(err) => { // Ignore malformed manifests found on git repositories // diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index fcf5a98d1e8..e570d3115d4 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -25,19 +25,59 @@ use util::errors::{CargoError, CargoResult, CargoResultExt}; mod targets; use self::targets::targets; -pub fn read_manifest(path: &Path, source_id: &SourceId, config: &Config) +pub fn read_manifest(path: &Path, source_id: &SourceId, overrides: &Vec, + config: &Config) -> CargoResult<(EitherManifest, Vec)> { trace!("read_manifest; path={}; source-id={}", path.display(), source_id); let contents = paths::read(path)?; - do_read_manifest(&contents, path, source_id, config).chain_err(|| { + do_read_manifest(&contents, path, source_id, &overrides, config).chain_err(|| { format!("failed to parse manifest at `{}`", path.display()) }) } +fn merge_toml(orig: &toml::Value, overrides: &toml::Value) -> CargoResult { + match (orig, overrides) { + // We've descended down some TOML path and found identically-typed scalar + // values; the override is the correct one to pick. + (&toml::Value::String(_), &toml::Value::String(_)) | + (&toml::Value::Integer(_), &toml::Value::Integer(_)) | + (&toml::Value::Float(_), &toml::Value::Float(_)) | + (&toml::Value::Boolean(_), &toml::Value::Boolean(_)) | + (&toml::Value::Datetime(_), &toml::Value::Datetime(_)) => Ok(overrides.clone()), + + // We have two tables to merge. We want to recursively merge all of the + // values in the override table into the first one. + (&toml::Value::Table(ref orig), &toml::Value::Table(ref overrides)) => { + let mut new_table = orig.clone(); + for (key, value) in overrides { + // XXX This should not be so difficult! + match orig.get(key) { + Some(ref v) => { + let merged = merge_toml(v, &value)?; + *(new_table.get_mut(key).unwrap()) = merged; + } + None => { + new_table.insert(key.clone(), value.clone()); + } + }; + } + Ok(toml::Value::Table(new_table)) + }, + + // XXX arrays are a peculiar case. Do we just accept the override array, + // or do we attempt to merge recursively? What does merging recursively even + // *mean* if the arrays don't have the same length? + // + // Anything else is just something we're not equipped to handle. + (_, _) => bail!("Can't incorporate command-line manifest modifications."), + } +} + fn do_read_manifest(contents: &str, manifest_file: &Path, source_id: &SourceId, + overrides: &Vec, config: &Config) -> CargoResult<(EitherManifest, Vec)> { let package_root = manifest_file.parent().unwrap(); @@ -45,7 +85,23 @@ fn do_read_manifest(contents: &str, let toml = { let pretty_filename = util::without_prefix(manifest_file, config.cwd()).unwrap_or(manifest_file); - parse(contents, pretty_filename, config)? + let candidate = parse(contents, pretty_filename, config)?; + + if overrides.is_empty() { + candidate + } else { + let command_line_filename = "".to_owned(); + let command_line_filename = Path::new(&command_line_filename); + // XXX need some better error handling + let toml_overrides = parse(&overrides[0], &command_line_filename, config); + let toml_overrides = overrides[1..].iter().fold(toml_overrides, |acc, ref o| { + acc.and_then(|v| { + parse(&o, &command_line_filename, config).and_then(|v2| merge_toml(&v, &v2)) + }) + })?; + + merge_toml(&candidate, &toml_overrides)? + } }; let mut unused = BTreeSet::new();