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