1use crate::util::data_structures::{HashMap, HashSet};
2use std::cell::RefCell;
3use std::fmt::Write;
4
5use crate::compiler::DepKindSet;
6use crate::compiler::UserIntent;
7use crate::compiler::rustdoc::RustdocScrapeExamples;
8use crate::compiler::unit_dependencies::IsArtifact;
9use crate::compiler::{CompileKind, CompileMode, Unit};
10use crate::compiler::{RustcTargetData, UnitInterner};
11use crate::resolver::features::{self, FeaturesFor};
12use crate::resolver::{ForceAllTargets, HasDevUnits, Resolve};
13use crate::util::restricted_names::is_glob_pattern;
14use crate::util::{CargoResult, closest_msg};
15use crate::workspace::Workspace;
16use crate::workspace::dependency::DepKind;
17use crate::workspace::profiles::{Profiles, UnitFor};
18use crate::workspace::{FeatureValue, Package, PackageSet, Summary, Target};
19
20use super::Packages;
21use super::compile_filter::{CompileFilter, FilterRule, LibRule};
22use super::packages::build_glob;
23
24#[derive(Debug)]
29struct Proposal<'a> {
30 pkg: &'a Package,
31 target: &'a Target,
32 requires_features: bool,
37 mode: CompileMode,
38}
39
40pub struct UnitGenerator<'a, 'gctx> {
51 pub ws: &'a Workspace<'gctx>,
52 pub packages: &'a [&'a Package],
53 pub spec: &'a Packages,
54 pub target_data: &'a RustcTargetData<'gctx>,
55 pub filter: &'a CompileFilter,
56 pub requested_kinds: &'a [CompileKind],
57 pub explicit_host_kind: CompileKind,
58 pub intent: UserIntent,
59 pub resolve: &'a Resolve,
60 pub workspace_resolve: &'a Option<Resolve>,
61 pub resolved_features: &'a features::ResolvedFeatures,
62 pub package_set: &'a PackageSet<'gctx>,
63 pub profiles: &'a Profiles,
64 pub interner: &'a UnitInterner,
65 pub has_dev_units: HasDevUnits,
66}
67
68impl<'a> UnitGenerator<'a, '_> {
69 fn new_units(
71 &self,
72 pkg: &Package,
73 target: &Target,
74 initial_target_mode: CompileMode,
75 ) -> Vec<Unit> {
76 assert!(!target.is_custom_build());
78 let target_mode = match initial_target_mode {
79 CompileMode::Test => {
80 if target.is_example() && !self.filter.is_specific() && !target.tested() {
81 CompileMode::Build
84 } else {
85 CompileMode::Test
86 }
87 }
88 _ => initial_target_mode,
89 };
90
91 let is_local = pkg.package_id().source_id().is_path();
92
93 let features_for = FeaturesFor::from_for_host(target.proc_macro());
95 let features = self
96 .resolved_features
97 .activated_features(pkg.package_id(), features_for);
98
99 let explicit_kinds = if let Some(k) = pkg.manifest().forced_kind() {
106 vec![k]
107 } else {
108 self.requested_kinds
109 .iter()
110 .map(|kind| match kind {
111 CompileKind::Host => pkg
112 .manifest()
113 .default_kind()
114 .unwrap_or(self.explicit_host_kind),
115 CompileKind::Target(t) => CompileKind::Target(*t),
116 })
117 .collect()
118 };
119
120 explicit_kinds
121 .into_iter()
122 .map(move |kind| {
123 let unit_for = if initial_target_mode.is_any_test() {
124 UnitFor::new_test(self.ws.gctx(), kind)
143 } else if target.for_host() {
144 UnitFor::new_compiler(kind)
146 } else {
147 UnitFor::new_normal(kind)
148 };
149 let profile = self.profiles.get_profile(
150 pkg.package_id(),
151 pkg.hints(),
152 self.ws.is_member(pkg),
153 is_local,
154 unit_for,
155 kind,
156 );
157 let kind = kind.for_target(target);
158 self.interner.intern(
159 pkg,
160 target,
161 profile,
162 kind,
163 target_mode,
164 features.clone(),
165 self.target_data.info(kind).rustflags.clone(),
166 self.target_data.info(kind).rustdocflags.clone(),
167 self.target_data.target_config(kind).links_overrides.clone(),
168 false,
169 0,
170 IsArtifact::No,
171 None,
172 false,
173 )
174 })
175 .collect()
176 }
177
178 fn filter_default_targets<'b>(&self, targets: &'b [Target]) -> Vec<&'b Target> {
181 match self.intent {
182 UserIntent::Bench => targets.iter().filter(|t| t.benched()).collect(),
183 UserIntent::Test => targets
184 .iter()
185 .filter(|t| t.tested() || t.is_example())
186 .collect(),
187 UserIntent::Build | UserIntent::Check { .. } => targets
188 .iter()
189 .filter(|t| t.is_bin() || t.is_lib())
190 .collect(),
191 UserIntent::Doc { .. } => {
192 targets
194 .iter()
195 .filter(|t| {
196 t.documented()
197 && (!t.is_bin()
198 || !targets
199 .iter()
200 .any(|l| l.is_lib() && l.crate_name() == t.crate_name()))
201 })
202 .collect()
203 }
204 UserIntent::Doctest => {
205 panic!("Invalid intent {:?}", self.intent)
206 }
207 }
208 }
209
210 fn filter_targets(
212 &self,
213 predicate: impl Fn(&Target) -> bool,
214 requires_features: bool,
215 mode: CompileMode,
216 ) -> Vec<Proposal<'a>> {
217 self.packages
218 .iter()
219 .flat_map(|pkg| {
220 pkg.targets()
221 .iter()
222 .filter(|t| predicate(t))
223 .map(|target| Proposal {
224 pkg,
225 target,
226 requires_features,
227 mode,
228 })
229 })
230 .collect()
231 }
232
233 fn find_named_targets(
235 &self,
236 target_name: &str,
237 target_desc: &'static str,
238 is_expected_kind: fn(&Target) -> bool,
239 mode: CompileMode,
240 ) -> CargoResult<Vec<Proposal<'a>>> {
241 let is_glob = is_glob_pattern(target_name);
242 let pattern = build_glob(target_name)?;
243 let filter = |t: &Target| {
244 if is_glob {
245 is_expected_kind(t) && pattern.matches(t.name())
246 } else {
247 is_expected_kind(t) && t.name() == target_name
248 }
249 };
250 let proposals = self.filter_targets(filter, true, mode);
251 if proposals.is_empty() {
252 let mut targets = std::collections::BTreeMap::new();
253 for (pkg, target) in self.packages.iter().flat_map(|pkg| {
254 pkg.targets()
255 .iter()
256 .filter(|target| is_expected_kind(target))
257 .map(move |t| (pkg, t))
258 }) {
259 targets
260 .entry(target.name())
261 .or_insert_with(Vec::new)
262 .push((pkg, target));
263 }
264
265 let suggestion = closest_msg(target_name, targets.keys(), |t| t, "target");
266 let targets_elsewhere = self.get_targets_from_other_packages(filter)?;
267 let append_targets_elsewhere = |msg: &mut String| {
268 let mut available_msg = Vec::new();
269 for (package, targets) in &targets_elsewhere {
270 if !targets.is_empty() {
271 available_msg.push(format!(
272 "help: available {target_desc} in `{package}` package:"
273 ));
274 for target in targets {
275 available_msg.push(format!(" {target}"));
276 }
277 }
278 }
279 if !available_msg.is_empty() {
280 write!(msg, "\n{}", available_msg.join("\n"))?;
281 }
282 CargoResult::Ok(())
283 };
284
285 let unmatched_packages = match self.spec {
286 Packages::Default | Packages::OptOut(_) | Packages::All(_) => {
287 " in default-run packages".to_owned()
288 }
289 Packages::Packages(packages) => match packages.len() {
290 0 => String::new(),
291 1 => format!(" in `{}` package", packages[0]),
292 _ => format!(" in `{}`, ... packages", packages[0]),
293 },
294 };
295
296 let named = if is_glob { "matches pattern" } else { "named" };
297
298 let mut msg = String::new();
299 write!(
300 msg,
301 "no {target_desc} target {named} `{target_name}`{unmatched_packages}{suggestion}",
302 )?;
303 if !targets_elsewhere.is_empty() {
304 append_targets_elsewhere(&mut msg)?;
305 } else if suggestion.is_empty() && !targets.is_empty() {
306 write!(msg, "\nhelp: available {} targets:", target_desc)?;
307 for (target_name, pkgs) in targets {
308 if pkgs.len() == 1 {
309 write!(msg, "\n {target_name}")?;
310 } else {
311 for (pkg, _) in pkgs {
312 let pkg_name = pkg.name();
313 write!(msg, "\n {target_name} in package {pkg_name}")?;
314 }
315 }
316 }
317 }
318 anyhow::bail!(msg);
319 }
320 Ok(proposals)
321 }
322
323 fn get_targets_from_other_packages(
324 &self,
325 filter_fn: impl Fn(&Target) -> bool,
326 ) -> CargoResult<Vec<(&str, Vec<&str>)>> {
327 let packages = Packages::All(Vec::new()).get_packages(self.ws)?;
328 let targets = packages
329 .into_iter()
330 .filter_map(|pkg| {
331 let mut targets: Vec<_> = pkg
332 .manifest()
333 .targets()
334 .iter()
335 .filter_map(|target| filter_fn(target).then(|| target.name()))
336 .collect();
337 if targets.is_empty() {
338 None
339 } else {
340 targets.sort();
341 Some((pkg.name().as_str(), targets))
342 }
343 })
344 .collect();
345
346 Ok(targets)
347 }
348
349 fn list_rule_targets(
351 &self,
352 rule: &FilterRule,
353 target_desc: &'static str,
354 is_expected_kind: fn(&Target) -> bool,
355 mode: CompileMode,
356 ) -> CargoResult<Vec<Proposal<'a>>> {
357 let mut proposals = Vec::new();
358 match rule {
359 FilterRule::All => proposals.extend(self.filter_targets(is_expected_kind, false, mode)),
360 FilterRule::Just(names) => {
361 for name in names {
362 proposals.extend(self.find_named_targets(
363 name,
364 target_desc,
365 is_expected_kind,
366 mode,
367 )?);
368 }
369 }
370 }
371 Ok(proposals)
372 }
373
374 fn create_proposals(&self) -> CargoResult<(Vec<Proposal<'_>>, DepKindSet)> {
376 let mut proposals: Vec<Proposal<'_>> = Vec::new();
377 let mut selected_dep_kinds = DepKindSet::default();
378
379 selected_dep_kinds.build = true;
380
381 match *self.filter {
382 CompileFilter::Default {
383 required_features_filterable,
384 } => {
385 selected_dep_kinds.normal = true;
386 for pkg in self.packages {
390 let default = self.filter_default_targets(pkg.targets());
391 proposals.extend(default.into_iter().map(|target| Proposal {
392 pkg,
393 target,
394 requires_features: !required_features_filterable,
395 mode: to_compile_mode(self.intent),
396 }));
397 if matches!(self.intent, UserIntent::Test) {
398 if let Some(t) = pkg
399 .targets()
400 .iter()
401 .find(|t| t.is_lib() && t.doctested() && t.doctestable())
402 {
403 proposals.push(Proposal {
404 pkg,
405 target: t,
406 requires_features: false,
407 mode: CompileMode::Doctest,
408 });
409 }
410 }
411 }
412 }
413 CompileFilter::Only {
414 all_targets,
415 ref lib,
416 ref bins,
417 ref examples,
418 ref tests,
419 ref benches,
420 } => {
421 if *lib != LibRule::False {
422 match bins {
423 FilterRule::All => {
424 selected_dep_kinds.normal = true;
425 }
426 _ => (),
427 }
428 let mut libs = Vec::new();
431 let compile_mode = to_compile_mode(self.intent);
432 for proposal in self.filter_targets(Target::is_lib, false, compile_mode) {
433 let Proposal { target, pkg, .. } = proposal;
434 if matches!(self.intent, UserIntent::Doctest) && !target.doctestable() {
435 let types = target.rustc_crate_types();
436 let types_str: Vec<&str> = types.iter().map(|t| t.as_str()).collect();
437 self.ws.gctx().shell().warn(format!(
438 "doc tests are not supported for crate type(s) `{}` in package `{}`",
439 types_str.join(", "),
440 pkg.name()
441 ))?;
442 } else {
443 libs.push(proposal)
444 }
445 }
446 if !all_targets && libs.is_empty() && *lib == LibRule::True {
447 let names = self
448 .packages
449 .iter()
450 .map(|pkg| pkg.name())
451 .collect::<Vec<_>>();
452 if names.len() == 1 {
453 anyhow::bail!("no library targets found in package `{}`", names[0]);
454 } else {
455 anyhow::bail!(
456 "no library targets found in packages: {}",
457 names.join(", ")
458 );
459 }
460 }
461 proposals.extend(libs);
462 }
463
464 let default_mode = to_compile_mode(self.intent);
465
466 let test_filter = match tests {
469 FilterRule::All => Target::tested,
470 FilterRule::Just(_) => Target::is_test,
471 };
472 let test_mode = match self.intent {
473 UserIntent::Build => CompileMode::Test,
474 UserIntent::Check { .. } => CompileMode::Check { test: true },
475 _ => default_mode,
476 };
477 let bench_filter = match benches {
480 FilterRule::All => Target::benched,
481 FilterRule::Just(_) => Target::is_bench,
482 };
483
484 proposals.extend(self.list_rule_targets(
485 bins,
486 "bin",
487 Target::is_bin,
488 default_mode,
489 )?);
490 proposals.extend(self.list_rule_targets(
491 examples,
492 "example",
493 Target::is_example,
494 default_mode,
495 )?);
496 proposals.extend(self.list_rule_targets(tests, "test", test_filter, test_mode)?);
497 proposals.extend(self.list_rule_targets(
498 benches,
499 "bench",
500 bench_filter,
501 test_mode,
502 )?);
503 }
504 }
505
506 Ok((proposals, selected_dep_kinds))
507 }
508
509 fn create_docscrape_proposals(&self, doc_units: &[Unit]) -> CargoResult<Vec<Proposal<'a>>> {
511 let no_pkg_has_dev_deps = self.packages.iter().all(|pkg| {
524 pkg.summary()
525 .dependencies()
526 .iter()
527 .all(|dep| !matches!(dep.kind(), DepKind::Development))
528 });
529 let reqs_dev_deps = matches!(self.has_dev_units, HasDevUnits::Yes);
530 let safe_to_scrape_example_targets = no_pkg_has_dev_deps || reqs_dev_deps;
531
532 let pkgs_to_scrape = doc_units
533 .iter()
534 .filter(|unit| self.ws.unit_needs_doc_scrape(unit))
535 .map(|u| &u.pkg)
536 .collect::<HashSet<_>>();
537
538 let skipped_examples = RefCell::new(Vec::new());
539 let can_scrape = |target: &Target| {
540 match (target.doc_scrape_examples(), target.is_example()) {
541 (RustdocScrapeExamples::Disabled, _) => false,
543 (RustdocScrapeExamples::Enabled, _) => true,
545 (RustdocScrapeExamples::Unset, true) => {
548 if !safe_to_scrape_example_targets {
549 skipped_examples
550 .borrow_mut()
551 .push(target.name().to_string());
552 }
553 safe_to_scrape_example_targets
554 }
555 (RustdocScrapeExamples::Unset, false) => false,
557 }
558 };
559
560 let mut scrape_proposals = self.filter_targets(can_scrape, false, CompileMode::Docscrape);
561 scrape_proposals.retain(|proposal| pkgs_to_scrape.contains(proposal.pkg));
562
563 let skipped_examples = skipped_examples.into_inner();
564 if !skipped_examples.is_empty() {
565 let mut shell = self.ws.gctx().shell();
566 let example_str = skipped_examples.join(", ");
567 shell.warn(format!(
568 "\
569Rustdoc did not scrape the following examples because they require dev-dependencies: {example_str}
570 If you want Rustdoc to scrape these examples, then add `doc-scrape-examples = true`
571 to the [[example]] target configuration of at least one example."
572 ))?;
573 }
574
575 Ok(scrape_proposals)
576 }
577
578 fn unmatched_target_filters(&self, units: &[Unit]) -> CargoResult<()> {
583 let mut shell = self.ws.gctx().shell();
584 if let CompileFilter::Only {
585 all_targets,
586 lib: _,
587 ref bins,
588 ref examples,
589 ref tests,
590 ref benches,
591 } = *self.filter
592 {
593 if units.is_empty() {
594 let mut filters = String::new();
595 let mut miss_count = 0;
596
597 let mut append = |t: &FilterRule, s| {
598 if let FilterRule::All = *t {
599 miss_count += 1;
600 filters.push_str(s);
601 }
602 };
603
604 if all_targets {
605 filters.push_str(" `all-targets`");
606 } else {
607 append(bins, " `bins`,");
608 append(tests, " `tests`,");
609 append(examples, " `examples`,");
610 append(benches, " `benches`,");
611 filters.pop();
612 }
613
614 return shell.warn(format!(
615 "target {}{} specified, but no targets matched; this is a no-op",
616 if miss_count > 1 { "filters" } else { "filter" },
617 filters,
618 ));
619 }
620 }
621
622 Ok(())
623 }
624
625 fn validate_required_features(
630 &self,
631 target_name: &str,
632 required_features: &[String],
633 summary: &Summary,
634 ) -> CargoResult<()> {
635 let resolve = match self.workspace_resolve {
636 None => return Ok(()),
637 Some(resolve) => resolve,
638 };
639
640 let mut shell = self.ws.gctx().shell();
641 for feature in required_features {
642 let fv = FeatureValue::new(feature.into());
643 match &fv {
644 FeatureValue::Feature(f) => {
645 if !summary.features().contains_key(f) {
646 shell.warn(format!(
647 "invalid feature `{}` in required-features of target `{}`: \
648 `{}` is not present in [features] section",
649 fv, target_name, fv
650 ))?;
651 }
652 }
653 FeatureValue::Dep { .. } => {
654 anyhow::bail!(
655 "invalid feature `{}` in required-features of target `{}`: \
656 `dep:` prefixed feature values are not allowed in required-features",
657 fv,
658 target_name
659 );
660 }
661 FeatureValue::DepFeature { weak: true, .. } => {
662 anyhow::bail!(
663 "invalid feature `{}` in required-features of target `{}`: \
664 optional dependency with `?` is not allowed in required-features",
665 fv,
666 target_name
667 );
668 }
669 FeatureValue::DepFeature {
671 dep_name,
672 dep_feature,
673 weak: false,
674 } => {
675 match resolve.deps(summary.package_id()).find(|(_dep_id, deps)| {
676 deps.iter().any(|dep| dep.name_in_toml() == *dep_name)
677 }) {
678 Some((dep_id, _deps)) => {
679 let dep_summary = resolve.summary(dep_id);
680 if !dep_summary.features().contains_key(dep_feature)
681 && !dep_summary.dependencies().iter().any(|dep| {
682 dep.name_in_toml() == *dep_feature && dep.is_optional()
683 })
684 {
685 shell.warn(format!(
686 "invalid feature `{}` in required-features of target `{}`: \
687 feature `{}` does not exist in package `{}`",
688 fv, target_name, dep_feature, dep_id
689 ))?;
690 }
691 }
692 None => {
693 shell.warn(format!(
694 "invalid feature `{}` in required-features of target `{}`: \
695 dependency `{}` does not exist",
696 fv, target_name, dep_name
697 ))?;
698 }
699 }
700 }
701 }
702 }
703 Ok(())
704 }
705
706 fn proposals_to_units(&self, proposals: Vec<Proposal<'_>>) -> CargoResult<Vec<Unit>> {
708 let mut features_map = HashMap::default();
715 let mut units = HashSet::default();
719 for Proposal {
720 pkg,
721 target,
722 requires_features,
723 mode,
724 } in proposals
725 {
726 let unavailable_features = match target.required_features() {
727 Some(rf) => {
728 self.validate_required_features(target.name(), rf, pkg.summary())?;
729
730 let features = features_map.entry(pkg).or_insert_with(|| {
731 super::resolve_all_features(
732 self.resolve,
733 self.resolved_features,
734 self.package_set,
735 pkg.package_id(),
736 self.has_dev_units,
737 self.requested_kinds,
738 self.target_data,
739 ForceAllTargets::No,
740 )
741 });
742 rf.iter().filter(|f| !features.contains(*f)).collect()
743 }
744 None => Vec::new(),
745 };
746 if target.is_lib() || unavailable_features.is_empty() {
747 units.extend(self.new_units(pkg, target, mode));
748 } else if requires_features {
749 let required_features = target.required_features().unwrap();
750 let quoted_required_features: Vec<String> = required_features
751 .iter()
752 .map(|s| format!("`{}`", s))
753 .collect();
754 anyhow::bail!(
755 "target `{}` in package `{}` requires the features: {}\n\
756 Consider enabling them by passing, e.g., `--features=\"{}\"`",
757 target.name(),
758 pkg.name(),
759 quoted_required_features.join(", "),
760 required_features.join(" ")
761 );
762 }
763 }
765 let mut units: Vec<_> = units.into_iter().collect();
766 self.unmatched_target_filters(&units)?;
767
768 units.sort_unstable();
770 Ok(units)
771 }
772
773 pub fn generate_root_units(&self) -> CargoResult<(Vec<Unit>, DepKindSet)> {
778 let (proposals, selected_dep_kinds) = self.create_proposals()?;
779 let units = self.proposals_to_units(proposals)?;
780 Ok((units, selected_dep_kinds))
781 }
782
783 pub fn generate_scrape_units(&self, doc_units: &[Unit]) -> CargoResult<Vec<Unit>> {
790 let scrape_proposals = self.create_docscrape_proposals(&doc_units)?;
791 let scrape_units = self.proposals_to_units(scrape_proposals)?;
792 Ok(scrape_units)
793 }
794}
795
796fn to_compile_mode(intent: UserIntent) -> CompileMode {
798 match intent {
799 UserIntent::Test | UserIntent::Bench => CompileMode::Test,
800 UserIntent::Build => CompileMode::Build,
801 UserIntent::Check { test } => CompileMode::Check { test },
802 UserIntent::Doc { .. } => CompileMode::Doc,
803 UserIntent::Doctest => CompileMode::Doctest,
804 }
805}