diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 8056c7089..022f08100 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -41,8 +41,11 @@ use crate::core::{PackageId, PackageIdSpec, TargetKind, Workspace}; use crate::ops; use crate::ops::resolve::WorkspaceResolve; use crate::util::config::Config; +use crate::util::restricted_names::is_glob_pattern; use crate::util::{closest_msg, profile, CargoResult, StableHasher}; +use anyhow::Context as _; + /// Contains information about how a package should be compiled. /// /// Note on distinction between `CompileOptions` and `BuildConfig`: @@ -577,6 +580,13 @@ impl FilterRule { FilterRule::Just(ref targets) => Some(targets.clone()), } } + + pub(crate) fn contains_glob_patterns(&self) -> bool { + match self { + FilterRule::All => false, + FilterRule::Just(targets) => targets.iter().any(is_glob_pattern), + } + } } impl CompileFilter { @@ -706,6 +716,24 @@ impl CompileFilter { CompileFilter::Only { .. } => true, } } + + pub(crate) fn contains_glob_patterns(&self) -> bool { + match self { + CompileFilter::Default { .. } => false, + CompileFilter::Only { + bins, + examples, + tests, + benches, + .. + } => { + bins.contains_glob_patterns() + || examples.contains_glob_patterns() + || tests.contains_glob_patterns() + || benches.contains_glob_patterns() + } + } + } } /// A proposed target. @@ -1163,8 +1191,16 @@ fn find_named_targets<'a>( is_expected_kind: fn(&Target) -> bool, mode: CompileMode, ) -> CargoResult>> { - let filter = |t: &Target| t.name() == target_name && is_expected_kind(t); - let proposals = filter_targets(packages, filter, true, mode); + let is_glob = is_glob_pattern(target_name); + let proposals = if is_glob { + let pattern = build_glob(target_name)?; + let filter = |t: &Target| is_expected_kind(t) && pattern.matches(t.name()); + filter_targets(packages, filter, true, mode) + } else { + let filter = |t: &Target| t.name() == target_name && is_expected_kind(t); + filter_targets(packages, filter, true, mode) + }; + if proposals.is_empty() { let targets = packages.iter().flat_map(|pkg| { pkg.targets() @@ -1173,8 +1209,9 @@ fn find_named_targets<'a>( }); let suggestion = closest_msg(target_name, targets, |t| t.name()); anyhow::bail!( - "no {} target named `{}`{}", + "no {} target {} `{}`{}", target_desc, + if is_glob { "matches pattern" } else { "named" }, target_name, suggestion ); @@ -1291,3 +1328,8 @@ fn traverse_and_share( new_graph.entry(new_unit.clone()).or_insert(new_deps); new_unit } + +/// TODO: @weihanglo +fn build_glob(pat: &str) -> CargoResult { + glob::Pattern::new(pat).with_context(|| format!("Cannot build glob pattern from `{}`", pat)) +} diff --git a/src/cargo/ops/cargo_run.rs b/src/cargo/ops/cargo_run.rs index ab4667bd2..d95b01851 100644 --- a/src/cargo/ops/cargo_run.rs +++ b/src/cargo/ops/cargo_run.rs @@ -13,6 +13,10 @@ pub fn run( ) -> CargoResult<()> { let config = ws.config(); + if options.filter.contains_glob_patterns() { + anyhow::bail!("`cargo run` does not support glob patterns on target selection") + } + // We compute the `bins` here *just for diagnosis*. The actual set of // packages to be run is determined by the `ops::compile` call below. let packages = options.spec.get_packages(ws)?; diff --git a/src/cargo/util/restricted_names.rs b/src/cargo/util/restricted_names.rs index 3e1cb036d..650ae2330 100644 --- a/src/cargo/util/restricted_names.rs +++ b/src/cargo/util/restricted_names.rs @@ -83,7 +83,7 @@ pub fn validate_package_name(name: &str, what: &str, help: &str) -> CargoResult< Ok(()) } -// Check the entire path for names reserved in Windows. +/// Check the entire path for names reserved in Windows. pub fn is_windows_reserved_path(path: &Path) -> bool { path.iter() .filter_map(|component| component.to_str()) @@ -92,3 +92,8 @@ pub fn is_windows_reserved_path(path: &Path) -> bool { is_windows_reserved(stem) }) } + +/// Returns `true` if the name contains any glob pattern wildcards. +pub fn is_glob_pattern>(name: T) -> bool { + name.as_ref().contains(&['*', '?', '[', ']'][..]) +}