Add a workspace.default-members config that overrides implied --all
authorSimon Sapin <simon.sapin@exyr.org>
Thu, 23 Nov 2017 15:01:29 +0000 (16:01 +0100)
committerSimon Sapin <simon.sapin@exyr.org>
Wed, 29 Nov 2017 09:59:19 +0000 (10:59 +0100)
When no package is selected with `-p`, the default behavior
for virtual workspaces is to select every member
as if `--all` were used.

src/bin/bench.rs
src/bin/build.rs
src/bin/check.rs
src/bin/test.rs
src/cargo/core/workspace.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_run.rs
src/cargo/util/toml/mod.rs
tests/workspaces.rs

index 1aa82dd9f877b5b445f0768f277b15f7e1ff5720..f871932d9eca5a1cb42be33f09eab60238f3f1e8 100644 (file)
@@ -109,8 +109,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult {
     let root = find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())?;
     let ws = Workspace::new(&root, config)?;
 
-    let spec = Packages::from_flags(ws.is_virtual(),
-                                    options.flag_all,
+    let spec = Packages::from_flags(options.flag_all,
                                     &options.flag_exclude,
                                     &options.flag_package)?;
 
index 883e30db6b15fa8e3adccb68b1a469b98966be53..889052068e477e5cb4bfcfd295d37cd9eb04efed 100644 (file)
@@ -100,8 +100,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult {
     let root = find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())?;
     let ws = Workspace::new(&root, config)?;
 
-    let spec = Packages::from_flags(ws.is_virtual(),
-                                    options.flag_all,
+    let spec = Packages::from_flags(options.flag_all,
                                     &options.flag_exclude,
                                     &options.flag_package)?;
 
index 8a2ab42dd22067323c37e61d3988ed35636603f9..d6d1795013691a0185f39734b5d4bc300f7cbdb5 100644 (file)
@@ -106,8 +106,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult {
     let root = find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())?;
     let ws = Workspace::new(&root, config)?;
 
-    let spec = Packages::from_flags(ws.is_virtual(),
-                                    options.flag_all,
+    let spec = Packages::from_flags(options.flag_all,
                                     &options.flag_exclude,
                                     &options.flag_package)?;
 
index a663e06091b03893c37b735db72d40cb7aa36400..58a616ad92b0b61d316c0d2c050604683dd55bcd 100644 (file)
@@ -147,8 +147,7 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult {
                                          options.flag_all_targets);
     }
 
-    let spec = Packages::from_flags(ws.is_virtual(),
-                                    options.flag_all,
+    let spec = Packages::from_flags(options.flag_all,
                                     &options.flag_exclude,
                                     &options.flag_package)?;
 
index d63ded77140cd82a9273ef04033107201f36e3f8..856b2597f429fb3248fce2d7199ede53af2edc9c 100644 (file)
@@ -46,6 +46,9 @@ pub struct Workspace<'cfg> {
     // set above.
     members: Vec<PathBuf>,
 
+    // The subset of `members` that are “default”.
+    default_members: Vec<PathBuf>,
+
     // True, if this is a temporary workspace created for the purposes of
     // cargo install or cargo package.
     is_ephemeral: bool,
@@ -90,6 +93,7 @@ pub enum WorkspaceConfig {
 pub struct WorkspaceRootConfig {
     root_dir: PathBuf,
     members: Option<Vec<String>>,
+    default_members: Option<Vec<String>>,
     exclude: Vec<String>,
 }
 
@@ -121,6 +125,7 @@ impl<'cfg> Workspace<'cfg> {
             root_manifest: None,
             target_dir: target_dir,
             members: Vec::new(),
+            default_members: Vec::new(),
             is_ephemeral: false,
             require_optional_deps: true,
         };
@@ -157,6 +162,7 @@ impl<'cfg> Workspace<'cfg> {
             root_manifest: None,
             target_dir: None,
             members: Vec::new(),
+            default_members: Vec::new(),
             is_ephemeral: true,
             require_optional_deps: require_optional_deps,
         };
@@ -170,6 +176,7 @@ impl<'cfg> Workspace<'cfg> {
                 ws.config.target_dir()?
             };
             ws.members.push(ws.current_manifest.clone());
+            ws.default_members.push(ws.current_manifest.clone());
         }
         Ok(ws)
     }
@@ -267,6 +274,14 @@ impl<'cfg> Workspace<'cfg> {
         }
     }
 
+    /// Returns an iterator over default packages in this workspace
+    pub fn default_members<'a>(&'a self) -> Members<'a, 'cfg> {
+        Members {
+            ws: self,
+            iter: self.default_members.iter(),
+        }
+    }
+
     pub fn is_ephemeral(&self) -> bool {
         self.is_ephemeral
     }
@@ -345,23 +360,49 @@ impl<'cfg> Workspace<'cfg> {
             None => {
                 debug!("find_members - only me as a member");
                 self.members.push(self.current_manifest.clone());
+                self.default_members.push(self.current_manifest.clone());
                 return Ok(())
             }
         };
 
-        let members_paths = {
+        let members_paths;
+        let default_members_paths;
+        {
             let root_package = self.packages.load(&root_manifest_path)?;
             match *root_package.workspace_config() {
-                WorkspaceConfig::Root(ref root_config) => root_config.members_paths()?,
+                WorkspaceConfig::Root(ref root_config) => {
+                    members_paths = root_config.members_paths(
+                        root_config.members.as_ref().unwrap_or(&vec![])
+                    )?;
+                    default_members_paths = if let Some(ref default) = root_config.default_members {
+                        Some(root_config.members_paths(default)?)
+                    } else {
+                        None
+                    }
+                }
                 _ => bail!("root of a workspace inferred but wasn't a root: {}",
                            root_manifest_path.display()),
             }
-        };
+        }
 
         for path in members_paths {
             self.find_path_deps(&path.join("Cargo.toml"), &root_manifest_path, false)?;
         }
 
+        if let Some(default) = default_members_paths {
+            for path in default {
+                let manifest_path = paths::normalize_path(&path.join("Cargo.toml"));
+                if !self.members.contains(&manifest_path) {
+                    bail!("package `{}` is listed in workspace’s default-members \
+                           but is not a member.",
+                          path.display())
+                }
+                self.default_members.push(manifest_path)
+            }
+        } else {
+            self.default_members = self.members.clone()
+        }
+
         self.find_path_deps(&root_manifest_path, &root_manifest_path, false)
     }
 
@@ -370,7 +411,7 @@ impl<'cfg> Workspace<'cfg> {
                       root_manifest: &Path,
                       is_path_dep: bool) -> CargoResult<()> {
         let manifest_path = paths::normalize_path(manifest_path);
-        if self.members.iter().any(|p| p == &manifest_path) {
+        if self.members.contains(&manifest_path) {
             return Ok(())
         }
         if is_path_dep
@@ -632,11 +673,13 @@ impl WorkspaceRootConfig {
     pub fn new(
         root_dir: &Path,
         members: &Option<Vec<String>>,
+        default_members: &Option<Vec<String>>,
         exclude: &Option<Vec<String>>,
     ) -> WorkspaceRootConfig {
         WorkspaceRootConfig {
             root_dir: root_dir.to_path_buf(),
             members: members.clone(),
+            default_members: default_members.clone(),
             exclude: exclude.clone().unwrap_or_default(),
         }
     }
@@ -665,21 +708,19 @@ impl WorkspaceRootConfig {
         self.members.is_some()
     }
 
-    fn members_paths(&self) -> CargoResult<Vec<PathBuf>> {
+    fn members_paths(&self, globs: &[String]) -> CargoResult<Vec<PathBuf>> {
         let mut expanded_list = Vec::new();
 
-        if let Some(globs) = self.members.clone() {
-            for glob in globs {
-                let pathbuf = self.root_dir.join(glob);
-                let expanded_paths = Self::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 glob in globs {
+            let pathbuf = self.root_dir.join(glob);
+            let expanded_paths = Self::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);
             }
         }
 
index ff8249a60cbe898c60fd348848b8b5d85eefbb1b..9ef05f41d7a87ea8841864feaf6b7a73209009c6 100644 (file)
@@ -106,26 +106,23 @@ pub enum MessageFormat {
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
 pub enum Packages<'a> {
+    Default,
     All,
     OptOut(&'a [String]),
     Packages(&'a [String]),
 }
 
 impl<'a> Packages<'a> {
-    pub fn from_flags(virtual_ws: bool, all: bool, exclude: &'a [String], package: &'a [String])
+    pub fn from_flags(all: bool, exclude: &'a [String], package: &'a [String])
         -> CargoResult<Self>
     {
-        let all = all || (virtual_ws && package.is_empty());
-
-        let packages = match (all, &exclude) {
-            (true, exclude) if exclude.is_empty() => Packages::All,
-            (true, exclude) => Packages::OptOut(exclude),
-            (false, exclude) if !exclude.is_empty() => bail!("--exclude can only be used together \
-                                                           with --all"),
-            _ => Packages::Packages(package),
-        };
-
-        Ok(packages)
+        Ok(match (all, exclude.len(), package.len()) {
+            (false, 0, 0) => Packages::Default,
+            (false, 0, _) => Packages::Packages(package),
+            (false, _, _) => bail!("--exclude can only be used together with --all"),
+            (true, 0, _) => Packages::All,
+            (true, _, _) => Packages::OptOut(exclude),
+        })
     }
 
     pub fn into_package_id_specs(self, ws: &Workspace) -> CargoResult<Vec<PackageIdSpec>> {
@@ -152,6 +149,18 @@ impl<'a> Packages<'a> {
             Packages::Packages(packages) => {
                 packages.iter().map(|p| PackageIdSpec::parse(p)).collect::<CargoResult<Vec<_>>>()?
             }
+            Packages::Default if ws.is_virtual() => {
+                ws.default_members()
+                    .map(Package::package_id)
+                    .map(PackageIdSpec::from_package_id)
+                    .collect()
+            }
+            Packages::Default => {
+                ws.current_opt()
+                    .map(Package::package_id)
+                    .map(PackageIdSpec::from_package_id)
+                    .into_iter().collect()
+            }
         };
         Ok(specs)
     }
index c20d7bf165f83238cf68da6d3bab175b758cb294..71d90470fa6bb48a226e52cb91d0b0cf3e6de1ee 100644 (file)
@@ -11,7 +11,8 @@ pub fn run(ws: &Workspace,
     let config = ws.config();
 
     let pkg = match options.spec {
-        Packages::All => unreachable!("cargo run supports single package only"),
+        Packages::All |
+        Packages::Default |
         Packages::OptOut(_) => unreachable!("cargo run supports single package only"),
         Packages::Packages(xs) => match xs.len() {
             0 => ws.current()?,
index a2b33bfb1964d554cb23aa9113f0bab0a02af01f..c5d0ddf017805b21c480ebcdff68a96fb632fd8a 100644 (file)
@@ -450,6 +450,8 @@ pub struct TomlProject {
 #[derive(Debug, Deserialize, Serialize)]
 pub struct TomlWorkspace {
     members: Option<Vec<String>>,
+    #[serde(rename = "default-members")]
+    default_members: Option<Vec<String>>,
     exclude: Option<Vec<String>>,
 }
 
@@ -681,7 +683,9 @@ impl TomlManifest {
                                       project.workspace.as_ref()) {
             (Some(config), None) => {
                 WorkspaceConfig::Root(
-                    WorkspaceRootConfig::new(&package_root, &config.members, &config.exclude)
+                    WorkspaceRootConfig::new(
+                        &package_root, &config.members, &config.default_members, &config.exclude,
+                    )
                 )
             }
             (None, root) => {
@@ -785,7 +789,9 @@ impl TomlManifest {
         let workspace_config = match me.workspace {
             Some(ref config) => {
                 WorkspaceConfig::Root(
-                    WorkspaceRootConfig::new(&root, &config.members, &config.exclude)
+                    WorkspaceRootConfig::new(
+                        &root, &config.members, &config.default_members, &config.exclude,
+                    )
                 )
             }
             None => {
index 093c9d1de45f661cdc0d44eeb4775c04894fcf2b..4f5b63cca2db291eeeab61fb63f64c0278d31e3f 100644 (file)
@@ -691,6 +691,59 @@ fn virtual_build_all_implied() {
                 execs().with_status(0));
 }
 
+#[test]
+fn virtual_default_members() {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [workspace]
+            members = ["bar", "baz"]
+            default-members = ["bar"]
+        "#)
+        .file("bar/Cargo.toml", r#"
+            [project]
+            name = "bar"
+            version = "0.1.0"
+            authors = []
+        "#)
+        .file("baz/Cargo.toml", r#"
+            [project]
+            name = "baz"
+            version = "0.1.0"
+            authors = []
+        "#)
+        .file("bar/src/main.rs", "fn main() {}")
+        .file("baz/src/main.rs", "fn main() {}");
+    let p = p.build();
+    assert_that(p.cargo("build"),
+                execs().with_status(0));
+    assert_that(&p.bin("bar"), existing_file());
+    assert_that(&p.bin("baz"), is_not(existing_file()));
+}
+
+#[test]
+fn virtual_default_member_is_not_a_member() {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [workspace]
+            members = ["bar"]
+            default-members = ["something-else"]
+        "#)
+        .file("bar/Cargo.toml", r#"
+            [project]
+            name = "bar"
+            version = "0.1.0"
+            authors = []
+        "#)
+        .file("bar/src/main.rs", "fn main() {}");
+    let p = p.build();
+    assert_that(p.cargo("build"),
+                execs().with_status(101)
+                       .with_stderr("\
+error: package `[..]something-else` is listed in workspace’s default-members \
+but is not a member.
+"));
+}
+
 #[test]
 fn virtual_build_no_members() {
     let p = project("foo")