Skip to content

Commit 67364ba

Browse files
committed
Add a workspace.exclude key
This commit adds a new key to the `Cargo.toml` manifest, `workspace.exclude`. This new key is a list of strings which is an array of directories that are excluded from the workspace explicitly. This is intended for use cases such as vendoring where path dependencies into a vendored directory don't want to pull in the workspace dependencies. There's a number of use cases mentioned on #3192 which I believe should all be covered with this strategy. At a bare minimum it should suffice to `exclude` every directory and then just explicitly whitelist crates through `members` through inclusion, and that should give precise control over the structure of a workspace. Closes #3192
1 parent 19fdb30 commit 67364ba

File tree

4 files changed

+180
-13
lines changed

4 files changed

+180
-13
lines changed

src/cargo/core/workspace.rs

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ enum MaybePackage {
6868
pub enum WorkspaceConfig {
6969
/// Indicates that `[workspace]` was present and the members were
7070
/// optionally specified as well.
71-
Root { members: Option<Vec<String>> },
71+
Root {
72+
members: Option<Vec<String>>,
73+
exclude: Vec<String>,
74+
},
7275

7376
/// Indicates that `[workspace]` was present and the `root` field is the
7477
/// optional value of `package.workspace`, if present.
@@ -269,11 +272,15 @@ impl<'cfg> Workspace<'cfg> {
269272
debug!("find_root - trying {}", manifest.display());
270273
if manifest.exists() {
271274
match *self.packages.load(&manifest)?.workspace_config() {
272-
WorkspaceConfig::Root { .. } => {
273-
debug!("find_root - found");
274-
return Ok(Some(manifest))
275+
WorkspaceConfig::Root { ref exclude, ref members } => {
276+
debug!("find_root - found a root checking exclusion");
277+
if !is_excluded(members, exclude, path, manifest_path) {
278+
debug!("find_root - found!");
279+
return Ok(Some(manifest))
280+
}
275281
}
276282
WorkspaceConfig::Member { root: Some(ref path_to_root) } => {
283+
debug!("find_root - found pointer");
277284
return Ok(Some(read_root_pointer(&manifest, path_to_root)?))
278285
}
279286
WorkspaceConfig::Member { .. } => {}
@@ -304,7 +311,7 @@ impl<'cfg> Workspace<'cfg> {
304311
let members = {
305312
let root = self.packages.load(&root_manifest)?;
306313
match *root.workspace_config() {
307-
WorkspaceConfig::Root { ref members } => members.clone(),
314+
WorkspaceConfig::Root { ref members, .. } => members.clone(),
308315
_ => bail!("root of a workspace inferred but wasn't a root: {}",
309316
root_manifest.display()),
310317
}
@@ -314,14 +321,17 @@ impl<'cfg> Workspace<'cfg> {
314321
for path in list {
315322
let root = root_manifest.parent().unwrap();
316323
let manifest_path = root.join(path).join("Cargo.toml");
317-
self.find_path_deps(&manifest_path, false)?;
324+
self.find_path_deps(&manifest_path, &root_manifest, false)?;
318325
}
319326
}
320327

321-
self.find_path_deps(&root_manifest, false)
328+
self.find_path_deps(&root_manifest, &root_manifest, false)
322329
}
323330

324-
fn find_path_deps(&mut self, manifest_path: &Path, is_path_dep: bool) -> CargoResult<()> {
331+
fn find_path_deps(&mut self,
332+
manifest_path: &Path,
333+
root_manifest: &Path,
334+
is_path_dep: bool) -> CargoResult<()> {
325335
let manifest_path = paths::normalize_path(manifest_path);
326336
if self.members.iter().any(|p| p == &manifest_path) {
327337
return Ok(())
@@ -334,6 +344,16 @@ impl<'cfg> Workspace<'cfg> {
334344
return Ok(())
335345
}
336346

347+
let root = root_manifest.parent().unwrap();
348+
match *self.packages.load(root_manifest)?.workspace_config() {
349+
WorkspaceConfig::Root { ref members, ref exclude } => {
350+
if is_excluded(members, exclude, root, &manifest_path) {
351+
return Ok(())
352+
}
353+
}
354+
_ => {}
355+
}
356+
337357
debug!("find_members - {}", manifest_path.display());
338358
self.members.push(manifest_path.clone());
339359

@@ -351,7 +371,7 @@ impl<'cfg> Workspace<'cfg> {
351371
.collect::<Vec<_>>()
352372
};
353373
for candidate in candidates {
354-
self.find_path_deps(&candidate, true)?;
374+
self.find_path_deps(&candidate, root_manifest, true)?;
355375
}
356376
Ok(())
357377
}
@@ -456,7 +476,7 @@ impl<'cfg> Workspace<'cfg> {
456476
MaybePackage::Virtual(_) => members_msg,
457477
MaybePackage::Package(ref p) => {
458478
let members = match *p.manifest().workspace_config() {
459-
WorkspaceConfig::Root { ref members } => members,
479+
WorkspaceConfig::Root { ref members, .. } => members,
460480
WorkspaceConfig::Member { .. } => unreachable!(),
461481
};
462482
if members.is_none() {
@@ -509,6 +529,27 @@ impl<'cfg> Workspace<'cfg> {
509529
}
510530
}
511531

532+
fn is_excluded(members: &Option<Vec<String>>,
533+
exclude: &[String],
534+
root_path: &Path,
535+
manifest_path: &Path) -> bool {
536+
let excluded = exclude.iter().any(|ex| {
537+
manifest_path.starts_with(root_path.join(ex))
538+
});
539+
540+
let explicit_member = match *members {
541+
Some(ref members) => {
542+
members.iter().any(|mem| {
543+
manifest_path.starts_with(root_path.join(mem))
544+
})
545+
}
546+
None => false,
547+
};
548+
549+
!explicit_member && excluded
550+
}
551+
552+
512553
impl<'cfg> Packages<'cfg> {
513554
fn get(&self, manifest_path: &Path) -> &MaybePackage {
514555
&self.packages[manifest_path.parent().unwrap()]

src/cargo/util/toml.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ pub struct TomlProject {
437437
#[derive(Deserialize)]
438438
pub struct TomlWorkspace {
439439
members: Option<Vec<String>>,
440+
exclude: Option<Vec<String>>,
440441
}
441442

442443
pub struct TomlVersion {
@@ -784,7 +785,10 @@ impl TomlManifest {
784785
let workspace_config = match (self.workspace.as_ref(),
785786
project.workspace.as_ref()) {
786787
(Some(config), None) => {
787-
WorkspaceConfig::Root { members: config.members.clone() }
788+
WorkspaceConfig::Root {
789+
members: config.members.clone(),
790+
exclude: config.exclude.clone().unwrap_or(Vec::new()),
791+
}
788792
}
789793
(None, root) => {
790794
WorkspaceConfig::Member { root: root.cloned() }
@@ -860,7 +864,10 @@ impl TomlManifest {
860864
let profiles = build_profiles(&self.profile);
861865
let workspace_config = match self.workspace {
862866
Some(ref config) => {
863-
WorkspaceConfig::Root { members: config.members.clone() }
867+
WorkspaceConfig::Root {
868+
members: config.members.clone(),
869+
exclude: config.exclude.clone().unwrap_or(Vec::new()),
870+
}
864871
}
865872
None => {
866873
bail!("virtual manifests must be configured with [workspace]");

src/doc/manifest.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ as:
388388

389389
# Optional key, inferred if not present
390390
members = ["path/to/member1", "path/to/member2"]
391+
392+
# Optional key, empty if not present
393+
exclude = ["path1", "path/to/dir2"]
391394
```
392395

393396
Workspaces were added to Cargo as part [RFC 1525] and have a number of
@@ -410,7 +413,9 @@ manifest, is responsible for defining the entire workspace. All `path`
410413
dependencies residing in the workspace directory become members. You can add
411414
additional packages to the workspace by listing them in the `members` key. Note
412415
that members of the workspaces listed explicitly will also have their path
413-
dependencies included in the workspace.
416+
dependencies included in the workspace. Finally, the `exclude` key can be used
417+
to blacklist paths from being included in a workspace. This can be useful if
418+
some path dependencies aren't desired to be in the workspace at all.
414419

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

tests/workspaces.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,3 +1264,117 @@ fn test_path_dependency_under_member() {
12641264
assert_that(&p.root().join("foo/bar/Cargo.lock"), is_not(existing_file()));
12651265
assert_that(&p.root().join("foo/bar/target"), is_not(existing_dir()));
12661266
}
1267+
1268+
#[test]
1269+
fn excluded_simple() {
1270+
let p = project("foo")
1271+
.file("Cargo.toml", r#"
1272+
[project]
1273+
name = "ws"
1274+
version = "0.1.0"
1275+
authors = []
1276+
1277+
[workspace]
1278+
exclude = ["foo"]
1279+
"#)
1280+
.file("src/lib.rs", "")
1281+
.file("foo/Cargo.toml", r#"
1282+
[project]
1283+
name = "foo"
1284+
version = "0.1.0"
1285+
authors = []
1286+
"#)
1287+
.file("foo/src/lib.rs", "");
1288+
p.build();
1289+
1290+
assert_that(p.cargo("build"),
1291+
execs().with_status(0));
1292+
assert_that(&p.root().join("target"), existing_dir());
1293+
assert_that(p.cargo("build").cwd(p.root().join("foo")),
1294+
execs().with_status(0));
1295+
assert_that(&p.root().join("foo/target"), existing_dir());
1296+
}
1297+
1298+
#[test]
1299+
fn exclude_members_preferred() {
1300+
let p = project("foo")
1301+
.file("Cargo.toml", r#"
1302+
[project]
1303+
name = "ws"
1304+
version = "0.1.0"
1305+
authors = []
1306+
1307+
[workspace]
1308+
members = ["foo/bar"]
1309+
exclude = ["foo"]
1310+
"#)
1311+
.file("src/lib.rs", "")
1312+
.file("foo/Cargo.toml", r#"
1313+
[project]
1314+
name = "foo"
1315+
version = "0.1.0"
1316+
authors = []
1317+
"#)
1318+
.file("foo/src/lib.rs", "")
1319+
.file("foo/bar/Cargo.toml", r#"
1320+
[project]
1321+
name = "bar"
1322+
version = "0.1.0"
1323+
authors = []
1324+
"#)
1325+
.file("foo/bar/src/lib.rs", "");
1326+
p.build();
1327+
1328+
assert_that(p.cargo("build"),
1329+
execs().with_status(0));
1330+
assert_that(&p.root().join("target"), existing_dir());
1331+
assert_that(p.cargo("build").cwd(p.root().join("foo")),
1332+
execs().with_status(0));
1333+
assert_that(&p.root().join("foo/target"), existing_dir());
1334+
assert_that(p.cargo("build").cwd(p.root().join("foo/bar")),
1335+
execs().with_status(0));
1336+
assert_that(&p.root().join("foo/bar/target"), is_not(existing_dir()));
1337+
}
1338+
1339+
#[test]
1340+
fn exclude_but_also_depend() {
1341+
let p = project("foo")
1342+
.file("Cargo.toml", r#"
1343+
[project]
1344+
name = "ws"
1345+
version = "0.1.0"
1346+
authors = []
1347+
1348+
[dependencies]
1349+
bar = { path = "foo/bar" }
1350+
1351+
[workspace]
1352+
exclude = ["foo"]
1353+
"#)
1354+
.file("src/lib.rs", "")
1355+
.file("foo/Cargo.toml", r#"
1356+
[project]
1357+
name = "foo"
1358+
version = "0.1.0"
1359+
authors = []
1360+
"#)
1361+
.file("foo/src/lib.rs", "")
1362+
.file("foo/bar/Cargo.toml", r#"
1363+
[project]
1364+
name = "bar"
1365+
version = "0.1.0"
1366+
authors = []
1367+
"#)
1368+
.file("foo/bar/src/lib.rs", "");
1369+
p.build();
1370+
1371+
assert_that(p.cargo("build"),
1372+
execs().with_status(0));
1373+
assert_that(&p.root().join("target"), existing_dir());
1374+
assert_that(p.cargo("build").cwd(p.root().join("foo")),
1375+
execs().with_status(0));
1376+
assert_that(&p.root().join("foo/target"), existing_dir());
1377+
assert_that(p.cargo("build").cwd(p.root().join("foo/bar")),
1378+
execs().with_status(0));
1379+
assert_that(&p.root().join("foo/bar/target"), existing_dir());
1380+
}

0 commit comments

Comments
 (0)