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