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