Skip to content

Commit

Permalink
Auto merge of #3979 - hjr3:issue-3911, r=alexcrichton
Browse files Browse the repository at this point in the history
Support glob syntax in workspace members

Fixes #3911
  • Loading branch information
bors committed May 16, 2017
2 parents b007d82 + f00c223 commit 828a9c5
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 7 deletions.
38 changes: 35 additions & 3 deletions src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::slice;

use glob::glob;

use core::{Package, VirtualManifest, EitherManifest, SourceId};
use core::{PackageIdSpec, Dependency, Profile, Profiles};
use ops;
use util::{Config, CargoResult, Filesystem, human};
use util::{Config, CargoResult, Filesystem, human, ChainError};
use util::paths;

/// The core abstraction in Cargo for working with a workspace of crates.
Expand Down Expand Up @@ -316,9 +318,24 @@ impl<'cfg> Workspace<'cfg> {
};

if let Some(list) = members {
let root = root_manifest.parent().unwrap();

let mut expanded_list = Vec::new();
for path in list {
let root = root_manifest.parent().unwrap();
let manifest_path = root.join(path).join("Cargo.toml");
let pathbuf = root.join(path);
let expanded_paths = expand_member_path(&pathbuf)?;

// If glob does not find any valid paths, then put the original
// path in the expanded list to maintain backwards compatibility.
if expanded_paths.is_empty() {
expanded_list.push(pathbuf);
} else {
expanded_list.extend(expanded_paths);
}
}

for path in expanded_list {
let manifest_path = path.join("Cargo.toml");
self.find_path_deps(&manifest_path, &root_manifest, false)?;
}
}
Expand Down Expand Up @@ -527,6 +544,21 @@ impl<'cfg> Workspace<'cfg> {
}
}

fn expand_member_path(path: &Path) -> CargoResult<Vec<PathBuf>> {
let path = match path.to_str() {
Some(p) => p,
None => return Ok(Vec::new()),
};
let res = glob(path).chain_error(|| {
human(format!("could not parse pattern `{}`", &path))
})?;
res.map(|p| {
p.chain_error(|| {
human(format!("unable to match path to pattern `{}`", &path))
})
}).collect()
}

fn is_excluded(members: &Option<Vec<String>>,
exclude: &[String],
root_path: &Path,
Expand Down
5 changes: 5 additions & 0 deletions src/cargo/util/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use core::TargetKind;

use curl;
use git2;
use glob;
use semver;
use serde_json;
use term;
Expand Down Expand Up @@ -428,6 +429,8 @@ from_error! {
term::Error,
num::ParseIntError,
str::ParseBoolError,
glob::PatternError,
glob::GlobError,
}

impl From<string::ParseError> for Box<CargoError> {
Expand Down Expand Up @@ -459,6 +462,8 @@ impl CargoError for ffi::NulError {}
impl CargoError for term::Error {}
impl CargoError for num::ParseIntError {}
impl CargoError for str::ParseBoolError {}
impl CargoError for glob::PatternError {}
impl CargoError for glob::GlobError {}

// =============================================================================
// Construction helpers
Expand Down
11 changes: 7 additions & 4 deletions src/doc/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ as:
[workspace]

# Optional key, inferred if not present
members = ["path/to/member1", "path/to/member2"]
members = ["path/to/member1", "path/to/member2", "path/to/member3/*"]

# Optional key, empty if not present
exclude = ["path1", "path/to/dir2"]
Expand All @@ -422,9 +422,12 @@ manifest, is responsible for defining the entire workspace. All `path`
dependencies residing in the workspace directory become members. You can add
additional packages to the workspace by listing them in the `members` key. Note
that members of the workspaces listed explicitly will also have their path
dependencies included in the workspace. Finally, the `exclude` key can be used
to blacklist paths from being included in a workspace. This can be useful if
some path dependencies aren't desired to be in the workspace at all.
dependencies included in the workspace. Sometimes a project may have a lot of
workspace members and it can be onerous to keep up to date. The path dependency
can also use [globs][globs] to match multiple paths. Finally, the `exclude`
key can be used to blacklist paths from being included in a workspace. This can
be useful if some path dependencies aren't desired to be in the workspace at
all.

The `package.workspace` manifest key (described above) is used in member crates
to point at a workspace's root crate. If this key is omitted then it is inferred
Expand Down
91 changes: 91 additions & 0 deletions tests/workspaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1378,3 +1378,94 @@ fn exclude_but_also_depend() {
execs().with_status(0));
assert_that(&p.root().join("foo/bar/target"), existing_dir());
}

#[test]
fn glob_syntax() {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.1.0"
authors = []
[workspace]
members = ["crates/*"]
exclude = ["crates/qux"]
"#)
.file("src/main.rs", "fn main() {}")
.file("crates/bar/Cargo.toml", r#"
[project]
name = "bar"
version = "0.1.0"
authors = []
workspace = "../.."
"#)
.file("crates/bar/src/main.rs", "fn main() {}")
.file("crates/baz/Cargo.toml", r#"
[project]
name = "baz"
version = "0.1.0"
authors = []
workspace = "../.."
"#)
.file("crates/baz/src/main.rs", "fn main() {}")
.file("crates/qux/Cargo.toml", r#"
[project]
name = "qux"
version = "0.1.0"
authors = []
"#)
.file("crates/qux/src/main.rs", "fn main() {}");
p.build();

assert_that(p.cargo("build"), execs().with_status(0));
assert_that(&p.bin("foo"), existing_file());
assert_that(&p.bin("bar"), is_not(existing_file()));
assert_that(&p.bin("baz"), is_not(existing_file()));

assert_that(p.cargo("build").cwd(p.root().join("crates/bar")),
execs().with_status(0));
assert_that(&p.bin("foo"), existing_file());
assert_that(&p.bin("bar"), existing_file());

assert_that(p.cargo("build").cwd(p.root().join("crates/baz")),
execs().with_status(0));
assert_that(&p.bin("foo"), existing_file());
assert_that(&p.bin("baz"), existing_file());

assert_that(p.cargo("build").cwd(p.root().join("crates/qux")),
execs().with_status(0));
assert_that(&p.bin("qux"), is_not(existing_file()));

assert_that(&p.root().join("Cargo.lock"), existing_file());
assert_that(&p.root().join("crates/bar/Cargo.lock"), is_not(existing_file()));
assert_that(&p.root().join("crates/baz/Cargo.lock"), is_not(existing_file()));
assert_that(&p.root().join("crates/qux/Cargo.lock"), existing_file());
}

#[test]
fn glob_syntax_invalid_members() {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.1.0"
authors = []
[workspace]
members = ["crates/*"]
"#)
.file("src/main.rs", "fn main() {}")
.file("crates/bar/src/main.rs", "fn main() {}");
p.build();

assert_that(p.cargo("build"),
execs().with_status(101)
.with_stderr("\
error: failed to read `[..]Cargo.toml`
Caused by:
[..]
"));
}

0 comments on commit 828a9c5

Please sign in to comment.