Skip to content

Handle overlapping members and exclude keys #4297

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

Closed
wants to merge 1 commit into from
Closed
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
66 changes: 50 additions & 16 deletions src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::path::{Path, PathBuf};
use std::slice;

use glob::glob;
use ignore::Match;
use ignore::gitignore::{Gitignore, GitignoreBuilder};
use url::Url;

use core::{Package, VirtualManifest, EitherManifest, SourceId};
Expand Down Expand Up @@ -74,7 +76,7 @@ pub enum WorkspaceConfig {
/// optionally specified as well.
Root {
members: Option<Vec<String>>,
exclude: Vec<String>,
exclude: Gitignore,
},

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

let mut expanded_list = Vec::new();
for path in list {
let pathbuf = root.join(path);
let expanded_paths = expand_member_path(&pathbuf)?;
let expanded_paths = expand_member_path(root, &path)?;

// 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);
expanded_list.push(root.join(path.as_str()));
} else {
expanded_list.extend(expanded_paths);
}
Expand Down Expand Up @@ -562,11 +563,13 @@ impl<'cfg> Workspace<'cfg> {
}
}

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

fn is_excluded(members: &Option<Vec<String>>,
exclude: &[String],
exclude: &Gitignore,
root_path: &Path,
manifest_path: &Path) -> bool {
let excluded = exclude.iter().any(|ex| {
manifest_path.starts_with(root_path.join(ex))
});
let exclude_match = exclude.matched_path_or_any_parents(manifest_path, false);

let explicit_member = match *members {
Some(ref members) => {
members.iter().any(|mem| {
manifest_path.starts_with(root_path.join(mem))
})
members.iter()
.filter(|p| manifest_path.starts_with(root_path.join(p)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that members can contain globs. That is, p might be like foo/* here, and so this starts_with will fail.

.max_by_key(|p| p.len())
}
None => false,
None => None,
};

!explicit_member && excluded
}
match explicit_member {
// This `manifest_path` is explicitly included by `path` here, and
// `path` is the longest specification for whether to include this,
// taking the highest precedence. This is still excluded, however, if
// there's any `exclude` path which is longer
Some(path) => {
match exclude_match {
Match::Ignore(glob) => {
glob.original().len() > path.as_str().len()
}
_ => false, // not ignored
}
}

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

impl<'cfg> Packages<'cfg> {
fn get(&self, manifest_path: &Path) -> &MaybePackage {
Expand Down Expand Up @@ -656,3 +673,20 @@ impl MaybePackage {
}
}
}

impl WorkspaceConfig {
pub fn root(root: &Path,
members: Option<Vec<String>>,
excludes: Option<Vec<String>>)
-> CargoResult<WorkspaceConfig>
{
let mut builder = GitignoreBuilder::new(root);
for exclude in excludes.unwrap_or(Vec::new()) {
builder.add_line(None, &exclude)?;
}
Ok(WorkspaceConfig::Root {
members: members,
exclude: builder.build()?,
})
}
}
14 changes: 6 additions & 8 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,10 +627,9 @@ impl TomlManifest {
let workspace_config = match (me.workspace.as_ref(),
project.workspace.as_ref()) {
(Some(config), None) => {
WorkspaceConfig::Root {
members: config.members.clone(),
exclude: config.exclude.clone().unwrap_or(Vec::new()),
}
WorkspaceConfig::root(package_root,
config.members.clone(),
config.exclude.clone())?
}
(None, root) => {
WorkspaceConfig::Member { root: root.cloned() }
Expand Down Expand Up @@ -711,10 +710,9 @@ impl TomlManifest {
let profiles = build_profiles(&me.profile);
let workspace_config = match me.workspace {
Some(ref config) => {
WorkspaceConfig::Root {
members: config.members.clone(),
exclude: config.exclude.clone().unwrap_or(Vec::new()),
}
WorkspaceConfig::root(root,
config.members.clone(),
config.exclude.clone())?
}
None => {
bail!("virtual manifests must be configured with [workspace]");
Expand Down
32 changes: 32 additions & 0 deletions tests/workspaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,3 +1469,35 @@ Caused by:
"));
}

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

assert_that(p.cargo("build").cwd(p.root().join("foo")),
execs().with_status(0));
assert_that(&p.root().join("target"), existing_dir());
assert_that(&p.root().join("foo/target"), is_not(existing_dir()));
assert_that(p.cargo("build").cwd(p.root().join("foo/bar")),
execs().with_status(0));
assert_that(&p.root().join("foo/bar/target"), existing_dir());
}