1use std::collections::BTreeSet;
4
5use crate::core::{Package, PackageIdSpecQuery};
6use crate::core::{PackageIdSpec, Workspace};
7use crate::util::restricted_names::is_glob_pattern;
8use crate::util::CargoResult;
9
10use anyhow::{bail, Context as _};
11
12#[derive(PartialEq, Eq, Debug, Clone)]
17pub enum Packages {
18 Default,
20 All(Vec<String>),
25 OptOut(Vec<String>),
29 Packages(Vec<String>),
31}
32
33impl Packages {
34 pub fn from_flags(all: bool, exclude: Vec<String>, package: Vec<String>) -> CargoResult<Self> {
36 Ok(match (all, exclude.len(), package.len()) {
37 (false, 0, 0) => Packages::Default,
38 (false, 0, _) => Packages::Packages(package),
39 (false, _, _) => anyhow::bail!("--exclude can only be used together with --workspace"),
40 (true, 0, _) => Packages::All(package),
41 (true, _, _) => Packages::OptOut(exclude),
42 })
43 }
44
45 pub fn to_package_id_specs(&self, ws: &Workspace<'_>) -> CargoResult<Vec<PackageIdSpec>> {
47 let specs = match self {
48 Packages::All(packages) => {
49 emit_packages_not_found_within_workspace(ws, packages)?;
50 ws.members()
51 .map(Package::package_id)
52 .map(|id| id.to_spec())
53 .collect()
54 }
55 Packages::OptOut(opt_out) => {
56 let (mut patterns, mut ids) = opt_patterns_and_ids(opt_out)?;
57 let specs = ws
58 .members()
59 .filter(|pkg| {
60 let id = ids.iter().find(|id| id.matches(pkg.package_id())).cloned();
61 if let Some(id) = &id {
62 ids.remove(id);
63 }
64 !id.is_some() && !match_patterns(pkg, &mut patterns)
65 })
66 .map(Package::package_id)
67 .map(|id| id.to_spec())
68 .collect();
69 let warn = |e| ws.gctx().shell().warn(e);
70 let names = ids
71 .into_iter()
72 .map(|id| id.to_string())
73 .collect::<BTreeSet<_>>();
74 emit_package_not_found(ws, names, true).or_else(warn)?;
75 emit_pattern_not_found(ws, patterns, true).or_else(warn)?;
76 specs
77 }
78 Packages::Packages(packages) if packages.is_empty() => {
79 vec![ws.current()?.package_id().to_spec()]
80 }
81 Packages::Packages(opt_in) => {
82 let (mut patterns, mut specs) = opt_patterns_and_ids(opt_in)?;
83 if !patterns.is_empty() {
84 let matched_pkgs = ws
85 .members()
86 .filter(|pkg| match_patterns(pkg, &mut patterns))
87 .map(Package::package_id)
88 .map(|id| id.to_spec());
89 specs.extend(matched_pkgs);
90 }
91 emit_pattern_not_found(ws, patterns, false)?;
92 specs.into_iter().collect()
93 }
94 Packages::Default => ws
95 .default_members()
96 .map(Package::package_id)
97 .map(|id| id.to_spec())
98 .collect(),
99 };
100 if specs.is_empty() {
101 if ws.is_virtual() {
102 bail!(
103 "manifest path `{}` contains no package: The manifest is virtual, \
104 and the workspace has no members.",
105 ws.root().display()
106 )
107 }
108 bail!("no packages to compile")
109 }
110 Ok(specs)
111 }
112
113 pub fn get_packages<'ws>(&self, ws: &'ws Workspace<'_>) -> CargoResult<Vec<&'ws Package>> {
115 let packages: Vec<_> = match self {
116 Packages::Default => ws.default_members().collect(),
117 Packages::All(packages) => {
118 emit_packages_not_found_within_workspace(ws, packages)?;
119 ws.members().collect()
120 }
121 Packages::OptOut(opt_out) => {
122 let (mut patterns, mut ids) = opt_patterns_and_ids(opt_out)?;
123 let packages = ws
124 .members()
125 .filter(|pkg| {
126 let id = ids.iter().find(|id| id.matches(pkg.package_id())).cloned();
127 if let Some(id) = &id {
128 ids.remove(id);
129 }
130 !id.is_some() && !match_patterns(pkg, &mut patterns)
131 })
132 .collect();
133 let names = ids
134 .into_iter()
135 .map(|id| id.to_string())
136 .collect::<BTreeSet<_>>();
137 emit_package_not_found(ws, names, true)?;
138 emit_pattern_not_found(ws, patterns, true)?;
139 packages
140 }
141 Packages::Packages(opt_in) => {
142 let (mut patterns, mut ids) = opt_patterns_and_ids(opt_in)?;
143 let packages = ws
144 .members()
145 .filter(|pkg| {
146 let id = ids.iter().find(|id| id.matches(pkg.package_id())).cloned();
147 if let Some(id) = &id {
148 ids.remove(id);
149 }
150 id.is_some() || match_patterns(pkg, &mut patterns)
151 })
152 .collect();
153 let names = ids
154 .into_iter()
155 .map(|id| id.to_string())
156 .collect::<BTreeSet<_>>();
157 emit_package_not_found(ws, names, false)?;
158 emit_pattern_not_found(ws, patterns, false)?;
159 packages
160 }
161 };
162 Ok(packages)
163 }
164
165 pub fn needs_spec_flag(&self, ws: &Workspace<'_>) -> bool {
168 match self {
169 Packages::Default => ws.default_members().count() > 1,
170 Packages::All(_) => ws.members().count() > 1,
171 Packages::Packages(_) => true,
172 Packages::OptOut(_) => true,
173 }
174 }
175}
176
177fn emit_package_not_found(
179 ws: &Workspace<'_>,
180 opt_names: BTreeSet<String>,
181 opt_out: bool,
182) -> CargoResult<()> {
183 if !opt_names.is_empty() {
184 anyhow::bail!(
185 "{}package(s) `{}` not found in workspace `{}`",
186 if opt_out { "excluded " } else { "" },
187 opt_names.into_iter().collect::<Vec<_>>().join(", "),
188 ws.root().display(),
189 )
190 }
191 Ok(())
192}
193
194fn emit_pattern_not_found(
196 ws: &Workspace<'_>,
197 opt_patterns: Vec<(glob::Pattern, bool)>,
198 opt_out: bool,
199) -> CargoResult<()> {
200 let not_matched = opt_patterns
201 .iter()
202 .filter(|(_, matched)| !*matched)
203 .map(|(pat, _)| pat.as_str())
204 .collect::<Vec<_>>();
205 if !not_matched.is_empty() {
206 anyhow::bail!(
207 "{}package pattern(s) `{}` not found in workspace `{}`",
208 if opt_out { "excluded " } else { "" },
209 not_matched.join(", "),
210 ws.root().display(),
211 )
212 }
213 Ok(())
214}
215
216fn emit_packages_not_found_within_workspace(
217 ws: &Workspace<'_>,
218 packages: &[String],
219) -> CargoResult<()> {
220 let (mut patterns, mut ids) = opt_patterns_and_ids(packages)?;
221 let _: Vec<_> = ws
222 .members()
223 .filter(|pkg| {
224 let id = ids.iter().find(|id| id.matches(pkg.package_id())).cloned();
225 if let Some(id) = &id {
226 ids.remove(id);
227 }
228 !id.is_some() && !match_patterns(pkg, &mut patterns)
229 })
230 .map(Package::package_id)
231 .map(|id| id.to_spec())
232 .collect();
233 let names = ids
234 .into_iter()
235 .map(|id| id.to_string())
236 .collect::<BTreeSet<_>>();
237 emit_package_not_found(ws, names, false)?;
238 emit_pattern_not_found(ws, patterns, false)?;
239 Ok(())
240}
241
242fn opt_patterns_and_ids(
245 opt: &[String],
246) -> CargoResult<(Vec<(glob::Pattern, bool)>, BTreeSet<PackageIdSpec>)> {
247 let mut opt_patterns = Vec::new();
248 let mut opt_ids = BTreeSet::new();
249 for x in opt.iter() {
250 match PackageIdSpec::parse(x) {
251 Ok(spec) => {
252 opt_ids.insert(spec);
253 }
254 Err(_) if is_glob_pattern(x) => opt_patterns.push((build_glob(x)?, false)),
255 Err(e) => return Err(e.into()),
256 }
257 }
258 Ok((opt_patterns, opt_ids))
259}
260
261fn match_patterns(pkg: &Package, patterns: &mut Vec<(glob::Pattern, bool)>) -> bool {
264 patterns.iter_mut().any(|(m, matched)| {
265 let is_matched = m.matches(pkg.name().as_str());
266 *matched |= is_matched;
267 is_matched
268 })
269}
270
271pub fn build_glob(pat: &str) -> CargoResult<glob::Pattern> {
273 glob::Pattern::new(pat).with_context(|| format!("cannot build glob pattern from `{}`", pat))
274}