Skip to content

Commit 2428a22

Browse files
committed
Handle overlapping members and exclude keys
The longest key takes precedence if there's more than one that matches. Closes #4089
1 parent ffab519 commit 2428a22

File tree

3 files changed

+88
-24
lines changed

3 files changed

+88
-24
lines changed

src/cargo/core/workspace.rs

+50-16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::path::{Path, PathBuf};
44
use std::slice;
55

66
use glob::glob;
7+
use ignore::Match;
8+
use ignore::gitignore::{Gitignore, GitignoreBuilder};
79
use url::Url;
810

911
use core::{Package, VirtualManifest, EitherManifest, SourceId};
@@ -74,7 +76,7 @@ pub enum WorkspaceConfig {
7476
/// optionally specified as well.
7577
Root {
7678
members: Option<Vec<String>>,
77-
exclude: Vec<String>,
79+
exclude: Gitignore,
7880
},
7981

8082
/// Indicates that `[workspace]` was present and the `root` field is the
@@ -340,13 +342,12 @@ impl<'cfg> Workspace<'cfg> {
340342

341343
let mut expanded_list = Vec::new();
342344
for path in list {
343-
let pathbuf = root.join(path);
344-
let expanded_paths = expand_member_path(&pathbuf)?;
345+
let expanded_paths = expand_member_path(root, &path)?;
345346

346347
// If glob does not find any valid paths, then put the original
347348
// path in the expanded list to maintain backwards compatibility.
348349
if expanded_paths.is_empty() {
349-
expanded_list.push(pathbuf);
350+
expanded_list.push(root.join(path.as_str()));
350351
} else {
351352
expanded_list.extend(expanded_paths);
352353
}
@@ -562,11 +563,13 @@ impl<'cfg> Workspace<'cfg> {
562563
}
563564
}
564565

565-
fn expand_member_path(path: &Path) -> CargoResult<Vec<PathBuf>> {
566-
let path = match path.to_str() {
566+
fn expand_member_path(root: &Path, pattern: &str) -> CargoResult<Vec<PathBuf>> {
567+
let root = root.join(pattern);
568+
let path = match root.to_str() {
567569
Some(p) => p,
568570
None => return Ok(Vec::new()),
569571
};
572+
// TODO: need to switch away from `glob` to `ignore` here.
570573
let res = glob(path).chain_err(|| {
571574
format!("could not parse pattern `{}`", &path)
572575
})?;
@@ -578,25 +581,39 @@ fn expand_member_path(path: &Path) -> CargoResult<Vec<PathBuf>> {
578581
}
579582

580583
fn is_excluded(members: &Option<Vec<String>>,
581-
exclude: &[String],
584+
exclude: &Gitignore,
582585
root_path: &Path,
583586
manifest_path: &Path) -> bool {
584-
let excluded = exclude.iter().any(|ex| {
585-
manifest_path.starts_with(root_path.join(ex))
586-
});
587+
let exclude_match = exclude.matched_path_or_any_parents(manifest_path, false);
587588

588589
let explicit_member = match *members {
589590
Some(ref members) => {
590-
members.iter().any(|mem| {
591-
manifest_path.starts_with(root_path.join(mem))
592-
})
591+
members.iter()
592+
.filter(|p| manifest_path.starts_with(root_path.join(p)))
593+
.max_by_key(|p| p.len())
593594
}
594-
None => false,
595+
None => None,
595596
};
596597

597-
!explicit_member && excluded
598-
}
598+
match explicit_member {
599+
// This `manifest_path` is explicitly included by `path` here, and
600+
// `path` is the longest specification for whether to include this,
601+
// taking the highest precedence. This is still excluded, however, if
602+
// there's any `exclude` path which is longer
603+
Some(path) => {
604+
match exclude_match {
605+
Match::Ignore(glob) => {
606+
glob.original().len() > path.as_str().len()
607+
}
608+
_ => false, // not ignored
609+
}
610+
}
599611

612+
// This `manifest_path` isn't explicitly included, so see if we're
613+
// explicitly excluded by any rules
614+
None => exclude_match.is_ignore(),
615+
}
616+
}
600617

601618
impl<'cfg> Packages<'cfg> {
602619
fn get(&self, manifest_path: &Path) -> &MaybePackage {
@@ -656,3 +673,20 @@ impl MaybePackage {
656673
}
657674
}
658675
}
676+
677+
impl WorkspaceConfig {
678+
pub fn root(root: &Path,
679+
members: Option<Vec<String>>,
680+
excludes: Option<Vec<String>>)
681+
-> CargoResult<WorkspaceConfig>
682+
{
683+
let mut builder = GitignoreBuilder::new(root);
684+
for exclude in excludes.unwrap_or(Vec::new()) {
685+
builder.add_line(None, &exclude)?;
686+
}
687+
Ok(WorkspaceConfig::Root {
688+
members: members,
689+
exclude: builder.build()?,
690+
})
691+
}
692+
}

src/cargo/util/toml/mod.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -627,10 +627,9 @@ impl TomlManifest {
627627
let workspace_config = match (me.workspace.as_ref(),
628628
project.workspace.as_ref()) {
629629
(Some(config), None) => {
630-
WorkspaceConfig::Root {
631-
members: config.members.clone(),
632-
exclude: config.exclude.clone().unwrap_or(Vec::new()),
633-
}
630+
WorkspaceConfig::root(package_root,
631+
config.members.clone(),
632+
config.exclude.clone())?
634633
}
635634
(None, root) => {
636635
WorkspaceConfig::Member { root: root.cloned() }
@@ -711,10 +710,9 @@ impl TomlManifest {
711710
let profiles = build_profiles(&me.profile);
712711
let workspace_config = match me.workspace {
713712
Some(ref config) => {
714-
WorkspaceConfig::Root {
715-
members: config.members.clone(),
716-
exclude: config.exclude.clone().unwrap_or(Vec::new()),
717-
}
713+
WorkspaceConfig::root(root,
714+
config.members.clone(),
715+
config.exclude.clone())?
718716
}
719717
None => {
720718
bail!("virtual manifests must be configured with [workspace]");

tests/workspaces.rs

+32
Original file line numberDiff line numberDiff line change
@@ -1469,3 +1469,35 @@ Caused by:
14691469
"));
14701470
}
14711471

1472+
#[test]
1473+
fn include_and_exclude() {
1474+
let p = project("foo")
1475+
.file("Cargo.toml", r#"
1476+
[workspace]
1477+
members = ["foo"]
1478+
exclude = ["foo/bar"]
1479+
"#)
1480+
.file("foo/Cargo.toml", r#"
1481+
[project]
1482+
name = "foo"
1483+
version = "0.1.0"
1484+
authors = []
1485+
"#)
1486+
.file("foo/src/lib.rs", "")
1487+
.file("foo/bar/Cargo.toml", r#"
1488+
[project]
1489+
name = "bar"
1490+
version = "0.1.0"
1491+
authors = []
1492+
"#)
1493+
.file("foo/bar/src/lib.rs", "");
1494+
p.build();
1495+
1496+
assert_that(p.cargo("build").cwd(p.root().join("foo")),
1497+
execs().with_status(0));
1498+
assert_that(&p.root().join("target"), existing_dir());
1499+
assert_that(&p.root().join("foo/target"), is_not(existing_dir()));
1500+
assert_that(p.cargo("build").cwd(p.root().join("foo/bar")),
1501+
execs().with_status(0));
1502+
assert_that(&p.root().join("foo/bar/target"), existing_dir());
1503+
}

0 commit comments

Comments
 (0)