Skip to content

Commit 828a9c5

Browse files
committed
Auto merge of #3979 - hjr3:issue-3911, r=alexcrichton
Support glob syntax in workspace members Fixes #3911
2 parents b007d82 + f00c223 commit 828a9c5

File tree

4 files changed

+138
-7
lines changed

4 files changed

+138
-7
lines changed

src/cargo/core/workspace.rs

+35-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ use std::collections::BTreeMap;
33
use std::path::{Path, PathBuf};
44
use std::slice;
55

6+
use glob::glob;
7+
68
use core::{Package, VirtualManifest, EitherManifest, SourceId};
79
use core::{PackageIdSpec, Dependency, Profile, Profiles};
810
use ops;
9-
use util::{Config, CargoResult, Filesystem, human};
11+
use util::{Config, CargoResult, Filesystem, human, ChainError};
1012
use util::paths;
1113

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

318320
if let Some(list) = members {
321+
let root = root_manifest.parent().unwrap();
322+
323+
let mut expanded_list = Vec::new();
319324
for path in list {
320-
let root = root_manifest.parent().unwrap();
321-
let manifest_path = root.join(path).join("Cargo.toml");
325+
let pathbuf = root.join(path);
326+
let expanded_paths = expand_member_path(&pathbuf)?;
327+
328+
// If glob does not find any valid paths, then put the original
329+
// path in the expanded list to maintain backwards compatibility.
330+
if expanded_paths.is_empty() {
331+
expanded_list.push(pathbuf);
332+
} else {
333+
expanded_list.extend(expanded_paths);
334+
}
335+
}
336+
337+
for path in expanded_list {
338+
let manifest_path = path.join("Cargo.toml");
322339
self.find_path_deps(&manifest_path, &root_manifest, false)?;
323340
}
324341
}
@@ -527,6 +544,21 @@ impl<'cfg> Workspace<'cfg> {
527544
}
528545
}
529546

547+
fn expand_member_path(path: &Path) -> CargoResult<Vec<PathBuf>> {
548+
let path = match path.to_str() {
549+
Some(p) => p,
550+
None => return Ok(Vec::new()),
551+
};
552+
let res = glob(path).chain_error(|| {
553+
human(format!("could not parse pattern `{}`", &path))
554+
})?;
555+
res.map(|p| {
556+
p.chain_error(|| {
557+
human(format!("unable to match path to pattern `{}`", &path))
558+
})
559+
}).collect()
560+
}
561+
530562
fn is_excluded(members: &Option<Vec<String>>,
531563
exclude: &[String],
532564
root_path: &Path,

src/cargo/util/errors.rs

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use core::TargetKind;
1111

1212
use curl;
1313
use git2;
14+
use glob;
1415
use semver;
1516
use serde_json;
1617
use term;
@@ -428,6 +429,8 @@ from_error! {
428429
term::Error,
429430
num::ParseIntError,
430431
str::ParseBoolError,
432+
glob::PatternError,
433+
glob::GlobError,
431434
}
432435

433436
impl From<string::ParseError> for Box<CargoError> {
@@ -459,6 +462,8 @@ impl CargoError for ffi::NulError {}
459462
impl CargoError for term::Error {}
460463
impl CargoError for num::ParseIntError {}
461464
impl CargoError for str::ParseBoolError {}
465+
impl CargoError for glob::PatternError {}
466+
impl CargoError for glob::GlobError {}
462467

463468
// =============================================================================
464469
// Construction helpers

src/doc/manifest.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ as:
396396
[workspace]
397397

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

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

429432
The `package.workspace` manifest key (described above) is used in member crates
430433
to point at a workspace's root crate. If this key is omitted then it is inferred

tests/workspaces.rs

+91
Original file line numberDiff line numberDiff line change
@@ -1378,3 +1378,94 @@ fn exclude_but_also_depend() {
13781378
execs().with_status(0));
13791379
assert_that(&p.root().join("foo/bar/target"), existing_dir());
13801380
}
1381+
1382+
#[test]
1383+
fn glob_syntax() {
1384+
let p = project("foo")
1385+
.file("Cargo.toml", r#"
1386+
[project]
1387+
name = "foo"
1388+
version = "0.1.0"
1389+
authors = []
1390+
1391+
[workspace]
1392+
members = ["crates/*"]
1393+
exclude = ["crates/qux"]
1394+
"#)
1395+
.file("src/main.rs", "fn main() {}")
1396+
.file("crates/bar/Cargo.toml", r#"
1397+
[project]
1398+
name = "bar"
1399+
version = "0.1.0"
1400+
authors = []
1401+
workspace = "../.."
1402+
"#)
1403+
.file("crates/bar/src/main.rs", "fn main() {}")
1404+
.file("crates/baz/Cargo.toml", r#"
1405+
[project]
1406+
name = "baz"
1407+
version = "0.1.0"
1408+
authors = []
1409+
workspace = "../.."
1410+
"#)
1411+
.file("crates/baz/src/main.rs", "fn main() {}")
1412+
.file("crates/qux/Cargo.toml", r#"
1413+
[project]
1414+
name = "qux"
1415+
version = "0.1.0"
1416+
authors = []
1417+
"#)
1418+
.file("crates/qux/src/main.rs", "fn main() {}");
1419+
p.build();
1420+
1421+
assert_that(p.cargo("build"), execs().with_status(0));
1422+
assert_that(&p.bin("foo"), existing_file());
1423+
assert_that(&p.bin("bar"), is_not(existing_file()));
1424+
assert_that(&p.bin("baz"), is_not(existing_file()));
1425+
1426+
assert_that(p.cargo("build").cwd(p.root().join("crates/bar")),
1427+
execs().with_status(0));
1428+
assert_that(&p.bin("foo"), existing_file());
1429+
assert_that(&p.bin("bar"), existing_file());
1430+
1431+
assert_that(p.cargo("build").cwd(p.root().join("crates/baz")),
1432+
execs().with_status(0));
1433+
assert_that(&p.bin("foo"), existing_file());
1434+
assert_that(&p.bin("baz"), existing_file());
1435+
1436+
assert_that(p.cargo("build").cwd(p.root().join("crates/qux")),
1437+
execs().with_status(0));
1438+
assert_that(&p.bin("qux"), is_not(existing_file()));
1439+
1440+
assert_that(&p.root().join("Cargo.lock"), existing_file());
1441+
assert_that(&p.root().join("crates/bar/Cargo.lock"), is_not(existing_file()));
1442+
assert_that(&p.root().join("crates/baz/Cargo.lock"), is_not(existing_file()));
1443+
assert_that(&p.root().join("crates/qux/Cargo.lock"), existing_file());
1444+
}
1445+
1446+
#[test]
1447+
fn glob_syntax_invalid_members() {
1448+
let p = project("foo")
1449+
.file("Cargo.toml", r#"
1450+
[project]
1451+
name = "foo"
1452+
version = "0.1.0"
1453+
authors = []
1454+
1455+
[workspace]
1456+
members = ["crates/*"]
1457+
"#)
1458+
.file("src/main.rs", "fn main() {}")
1459+
.file("crates/bar/src/main.rs", "fn main() {}");
1460+
p.build();
1461+
1462+
assert_that(p.cargo("build"),
1463+
execs().with_status(101)
1464+
.with_stderr("\
1465+
error: failed to read `[..]Cargo.toml`
1466+
1467+
Caused by:
1468+
[..]
1469+
"));
1470+
}
1471+

0 commit comments

Comments
 (0)