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;
20
21#[derive(Debug)]
26struct Proposal<'a> {
27 pkg: &'a Package,
28 target: &'a Target,
29 requires_features: bool,
34 mode: CompileMode,
35}
36
37pub(super) struct UnitGenerator<'a, 'gctx> {
48 pub ws: &'a Workspace<'gctx>,
49 pub packages: &'a [&'a Package],
50 pub target_data: &'a RustcTargetData<'gctx>,
51 pub filter: &'a CompileFilter,
52 pub requested_kinds: &'a [CompileKind],
53 pub explicit_host_kind: CompileKind,
54 pub mode: CompileMode,
55 pub resolve: &'a Resolve,
56 pub workspace_resolve: &'a Option<Resolve>,
57 pub resolved_features: &'a features::ResolvedFeatures,
58 pub package_set: &'a PackageSet<'gctx>,
59 pub profiles: &'a Profiles,
60 pub interner: &'a UnitInterner,
61 pub has_dev_units: HasDevUnits,
62}
63
64impl<'a> UnitGenerator<'a, '_> {
65 fn new_units(
67 &self,
68 pkg: &Package,
69 target: &Target,
70 initial_target_mode: CompileMode,
71 ) -> Vec<Unit> {
72 assert!(!target.is_custom_build());
74 let target_mode = match initial_target_mode {
75 CompileMode::Test => {
76 if target.is_example() && !self.filter.is_specific() && !target.tested() {
77 CompileMode::Build
80 } else {
81 CompileMode::Test
82 }
83 }
84 CompileMode::Build => match *target.kind() {
85 TargetKind::Test => CompileMode::Test,
86 TargetKind::Bench => CompileMode::Bench,
87 _ => CompileMode::Build,
88 },
89 CompileMode::Bench => CompileMode::Test,
98 _ => initial_target_mode,
99 };
100
101 let is_local = pkg.package_id().source_id().is_path();
102
103 let features_for = FeaturesFor::from_for_host(target.proc_macro());
105 let features = self
106 .resolved_features
107 .activated_features(pkg.package_id(), features_for);
108
109 let explicit_kinds = if let Some(k) = pkg.manifest().forced_kind() {
116 vec![k]
117 } else {
118 self.requested_kinds
119 .iter()
120 .map(|kind| match kind {
121 CompileKind::Host => pkg
122 .manifest()
123 .default_kind()
124 .unwrap_or(self.explicit_host_kind),
125 CompileKind::Target(t) => CompileKind::Target(*t),
126 })
127 .collect()
128 };
129
130 explicit_kinds
131 .into_iter()
132 .map(move |kind| {
133 let unit_for = if initial_target_mode.is_any_test() {
134 UnitFor::new_test(self.ws.gctx(), kind)
153 } else if target.for_host() {
154 UnitFor::new_compiler(kind)
156 } else {
157 UnitFor::new_normal(kind)
158 };
159 let profile = self.profiles.get_profile(
160 pkg.package_id(),
161 self.ws.is_member(pkg),
162 is_local,
163 unit_for,
164 kind,
165 );
166 let kind = kind.for_target(target);
167 self.interner.intern(
168 pkg,
169 target,
170 profile,
171 kind,
172 target_mode,
173 features.clone(),
174 self.target_data.info(kind).rustflags.clone(),
175 self.target_data.info(kind).rustdocflags.clone(),
176 self.target_data.target_config(kind).links_overrides.clone(),
177 false,
178 0,
179 IsArtifact::No,
180 None,
181 )
182 })
183 .collect()
184 }
185
186 fn filter_default_targets<'b>(&self, targets: &'b [Target]) -> Vec<&'b Target> {
189 match self.mode {
190 CompileMode::Bench => targets.iter().filter(|t| t.benched()).collect(),
191 CompileMode::Test => targets
192 .iter()
193 .filter(|t| t.tested() || t.is_example())
194 .collect(),
195 CompileMode::Build | CompileMode::Check { .. } => targets
196 .iter()
197 .filter(|t| t.is_bin() || t.is_lib())
198 .collect(),
199 CompileMode::Doc { .. } => {
200 targets
202 .iter()
203 .filter(|t| {
204 t.documented()
205 && (!t.is_bin()
206 || !targets
207 .iter()
208 .any(|l| l.is_lib() && l.crate_name() == t.crate_name()))
209 })
210 .collect()
211 }
212 CompileMode::Doctest | CompileMode::RunCustomBuild | CompileMode::Docscrape => {
213 panic!("Invalid mode {:?}", self.mode)
214 }
215 }
216 }
217
218 fn filter_targets(
220 &self,
221 predicate: impl Fn(&Target) -> bool,
222 requires_features: bool,
223 mode: CompileMode,
224 ) -> Vec<Proposal<'a>> {
225 self.packages
226 .iter()
227 .flat_map(|pkg| {
228 pkg.targets()
229 .iter()
230 .filter(|t| predicate(t))
231 .map(|target| Proposal {
232 pkg,
233 target,
234 requires_features,
235 mode,
236 })
237 })
238 .collect()
239 }
240
241 fn find_named_targets(
243 &self,
244 target_name: &str,
245 target_desc: &'static str,
246 is_expected_kind: fn(&Target) -> bool,
247 mode: CompileMode,
248 ) -> CargoResult<Vec<Proposal<'a>>> {
249 let is_glob = is_glob_pattern(target_name);
250 let proposals = if is_glob {
251 let pattern = build_glob(target_name)?;
252 let filter = |t: &Target| is_expected_kind(t) && pattern.matches(t.name());
253 self.filter_targets(filter, true, mode)
254 } else {
255 let filter = |t: &Target| t.name() == target_name && is_expected_kind(t);
256 self.filter_targets(filter, true, mode)
257 };
258
259 if proposals.is_empty() {
260 let targets = self
261 .packages
262 .iter()
263 .flat_map(|pkg| {
264 pkg.targets()
265 .iter()
266 .filter(|target| is_expected_kind(target))
267 })
268 .collect::<Vec<_>>();
269 let suggestion = closest_msg(target_name, targets.iter(), |t| t.name(), "target");
270 if !suggestion.is_empty() {
271 anyhow::bail!(
272 "no {} target {} `{}`{}",
273 target_desc,
274 if is_glob { "matches pattern" } else { "named" },
275 target_name,
276 suggestion
277 );
278 } else {
279 let mut msg = String::new();
280 writeln!(
281 msg,
282 "no {} target {} `{}`.",
283 target_desc,
284 if is_glob { "matches pattern" } else { "named" },
285 target_name,
286 )?;
287 if !targets.is_empty() {
288 writeln!(msg, "Available {} targets:", target_desc)?;
289 for target in targets {
290 writeln!(msg, " {}", target.name())?;
291 }
292 }
293 anyhow::bail!(msg);
294 }
295 }
296 Ok(proposals)
297 }
298
299 fn list_rule_targets(
301 &self,
302 rule: &FilterRule,
303 target_desc: &'static str,
304 is_expected_kind: fn(&Target) -> bool,
305 mode: CompileMode,
306 ) -> CargoResult<Vec<Proposal<'a>>> {
307 let mut proposals = Vec::new();
308 match rule {
309 FilterRule::All => proposals.extend(self.filter_targets(is_expected_kind, false, mode)),
310 FilterRule::Just(names) => {
311 for name in names {
312 proposals.extend(self.find_named_targets(
313 name,
314 target_desc,
315 is_expected_kind,
316 mode,
317 )?);
318 }
319 }
320 }
321 Ok(proposals)
322 }
323
324 fn create_proposals(&self) -> CargoResult<Vec<Proposal<'_>>> {
326 let mut proposals: Vec<Proposal<'_>> = Vec::new();
327
328 match *self.filter {
329 CompileFilter::Default {
330 required_features_filterable,
331 } => {
332 for pkg in self.packages {
333 let default = self.filter_default_targets(pkg.targets());
334 proposals.extend(default.into_iter().map(|target| Proposal {
335 pkg,
336 target,
337 requires_features: !required_features_filterable,
338 mode: self.mode,
339 }));
340 if self.mode == CompileMode::Test {
341 if let Some(t) = pkg
342 .targets()
343 .iter()
344 .find(|t| t.is_lib() && t.doctested() && t.doctestable())
345 {
346 proposals.push(Proposal {
347 pkg,
348 target: t,
349 requires_features: false,
350 mode: CompileMode::Doctest,
351 });
352 }
353 }
354 }
355 }
356 CompileFilter::Only {
357 all_targets,
358 ref lib,
359 ref bins,
360 ref examples,
361 ref tests,
362 ref benches,
363 } => {
364 if *lib != LibRule::False {
365 let mut libs = Vec::new();
366 for proposal in self.filter_targets(Target::is_lib, false, self.mode) {
367 let Proposal { target, pkg, .. } = proposal;
368 if self.mode.is_doc_test() && !target.doctestable() {
369 let types = target.rustc_crate_types();
370 let types_str: Vec<&str> = types.iter().map(|t| t.as_str()).collect();
371 self.ws.gctx().shell().warn(format!(
372 "doc tests are not supported for crate type(s) `{}` in package `{}`",
373 types_str.join(", "),
374 pkg.name()
375 ))?;
376 } else {
377 libs.push(proposal)
378 }
379 }
380 if !all_targets && libs.is_empty() && *lib == LibRule::True {
381 let names = self
382 .packages
383 .iter()
384 .map(|pkg| pkg.name())
385 .collect::<Vec<_>>();
386 if names.len() == 1 {
387 anyhow::bail!("no library targets found in package `{}`", names[0]);
388 } else {
389 anyhow::bail!(
390 "no library targets found in packages: {}",
391 names.join(", ")
392 );
393 }
394 }
395 proposals.extend(libs);
396 }
397
398 let test_filter = match tests {
401 FilterRule::All => Target::tested,
402 FilterRule::Just(_) => Target::is_test,
403 };
404 let test_mode = match self.mode {
405 CompileMode::Build => CompileMode::Test,
406 CompileMode::Check { .. } => CompileMode::Check { test: true },
407 _ => self.mode,
408 };
409 let bench_filter = match benches {
412 FilterRule::All => Target::benched,
413 FilterRule::Just(_) => Target::is_bench,
414 };
415 let bench_mode = match self.mode {
416 CompileMode::Build => CompileMode::Bench,
417 CompileMode::Check { .. } => CompileMode::Check { test: true },
418 _ => self.mode,
419 };
420
421 proposals.extend(self.list_rule_targets(bins, "bin", Target::is_bin, self.mode)?);
422 proposals.extend(self.list_rule_targets(
423 examples,
424 "example",
425 Target::is_example,
426 self.mode,
427 )?);
428 proposals.extend(self.list_rule_targets(tests, "test", test_filter, test_mode)?);
429 proposals.extend(self.list_rule_targets(
430 benches,
431 "bench",
432 bench_filter,
433 bench_mode,
434 )?);
435 }
436 }
437
438 Ok(proposals)
439 }
440
441 fn create_docscrape_proposals(&self, doc_units: &[Unit]) -> CargoResult<Vec<Proposal<'a>>> {
443 let no_pkg_has_dev_deps = self.packages.iter().all(|pkg| {
456 pkg.summary()
457 .dependencies()
458 .iter()
459 .all(|dep| !matches!(dep.kind(), DepKind::Development))
460 });
461 let reqs_dev_deps = matches!(self.has_dev_units, HasDevUnits::Yes);
462 let safe_to_scrape_example_targets = no_pkg_has_dev_deps || reqs_dev_deps;
463
464 let pkgs_to_scrape = doc_units
465 .iter()
466 .filter(|unit| self.ws.unit_needs_doc_scrape(unit))
467 .map(|u| &u.pkg)
468 .collect::<HashSet<_>>();
469
470 let skipped_examples = RefCell::new(Vec::new());
471 let can_scrape = |target: &Target| {
472 match (target.doc_scrape_examples(), target.is_example()) {
473 (RustdocScrapeExamples::Disabled, _) => false,
475 (RustdocScrapeExamples::Enabled, _) => true,
477 (RustdocScrapeExamples::Unset, true) => {
480 if !safe_to_scrape_example_targets {
481 skipped_examples
482 .borrow_mut()
483 .push(target.name().to_string());
484 }
485 safe_to_scrape_example_targets
486 }
487 (RustdocScrapeExamples::Unset, false) => false,
489 }
490 };
491
492 let mut scrape_proposals = self.filter_targets(can_scrape, false, CompileMode::Docscrape);
493 scrape_proposals.retain(|proposal| pkgs_to_scrape.contains(proposal.pkg));
494
495 let skipped_examples = skipped_examples.into_inner();
496 if !skipped_examples.is_empty() {
497 let mut shell = self.ws.gctx().shell();
498 let example_str = skipped_examples.join(", ");
499 shell.warn(format!(
500 "\
501Rustdoc did not scrape the following examples because they require dev-dependencies: {example_str}
502 If you want Rustdoc to scrape these examples, then add `doc-scrape-examples = true`
503 to the [[example]] target configuration of at least one example."
504 ))?;
505 }
506
507 Ok(scrape_proposals)
508 }
509
510 fn unmatched_target_filters(&self, units: &[Unit]) -> CargoResult<()> {
515 let mut shell = self.ws.gctx().shell();
516 if let CompileFilter::Only {
517 all_targets,
518 lib: _,
519 ref bins,
520 ref examples,
521 ref tests,
522 ref benches,
523 } = *self.filter
524 {
525 if units.is_empty() {
526 let mut filters = String::new();
527 let mut miss_count = 0;
528
529 let mut append = |t: &FilterRule, s| {
530 if let FilterRule::All = *t {
531 miss_count += 1;
532 filters.push_str(s);
533 }
534 };
535
536 if all_targets {
537 filters.push_str(" `all-targets`");
538 } else {
539 append(bins, " `bins`,");
540 append(tests, " `tests`,");
541 append(examples, " `examples`,");
542 append(benches, " `benches`,");
543 filters.pop();
544 }
545
546 return shell.warn(format!(
547 "target {}{} specified, but no targets matched; this is a no-op",
548 if miss_count > 1 { "filters" } else { "filter" },
549 filters,
550 ));
551 }
552 }
553
554 Ok(())
555 }
556
557 fn validate_required_features(
562 &self,
563 target_name: &str,
564 required_features: &[String],
565 summary: &Summary,
566 ) -> CargoResult<()> {
567 let resolve = match self.workspace_resolve {
568 None => return Ok(()),
569 Some(resolve) => resolve,
570 };
571
572 let mut shell = self.ws.gctx().shell();
573 for feature in required_features {
574 let fv = FeatureValue::new(feature.into());
575 match &fv {
576 FeatureValue::Feature(f) => {
577 if !summary.features().contains_key(f) {
578 shell.warn(format!(
579 "invalid feature `{}` in required-features of target `{}`: \
580 `{}` is not present in [features] section",
581 fv, target_name, fv
582 ))?;
583 }
584 }
585 FeatureValue::Dep { .. } => {
586 anyhow::bail!(
587 "invalid feature `{}` in required-features of target `{}`: \
588 `dep:` prefixed feature values are not allowed in required-features",
589 fv,
590 target_name
591 );
592 }
593 FeatureValue::DepFeature { weak: true, .. } => {
594 anyhow::bail!(
595 "invalid feature `{}` in required-features of target `{}`: \
596 optional dependency with `?` is not allowed in required-features",
597 fv,
598 target_name
599 );
600 }
601 FeatureValue::DepFeature {
603 dep_name,
604 dep_feature,
605 weak: false,
606 } => {
607 match resolve.deps(summary.package_id()).find(|(_dep_id, deps)| {
608 deps.iter().any(|dep| dep.name_in_toml() == *dep_name)
609 }) {
610 Some((dep_id, _deps)) => {
611 let dep_summary = resolve.summary(dep_id);
612 if !dep_summary.features().contains_key(dep_feature)
613 && !dep_summary.dependencies().iter().any(|dep| {
614 dep.name_in_toml() == *dep_feature && dep.is_optional()
615 })
616 {
617 shell.warn(format!(
618 "invalid feature `{}` in required-features of target `{}`: \
619 feature `{}` does not exist in package `{}`",
620 fv, target_name, dep_feature, dep_id
621 ))?;
622 }
623 }
624 None => {
625 shell.warn(format!(
626 "invalid feature `{}` in required-features of target `{}`: \
627 dependency `{}` does not exist",
628 fv, target_name, dep_name
629 ))?;
630 }
631 }
632 }
633 }
634 }
635 Ok(())
636 }
637
638 fn proposals_to_units(&self, proposals: Vec<Proposal<'_>>) -> CargoResult<Vec<Unit>> {
640 let mut features_map = HashMap::new();
647 let mut units = HashSet::new();
651 for Proposal {
652 pkg,
653 target,
654 requires_features,
655 mode,
656 } in proposals
657 {
658 let unavailable_features = match target.required_features() {
659 Some(rf) => {
660 self.validate_required_features(target.name(), rf, pkg.summary())?;
661
662 let features = features_map.entry(pkg).or_insert_with(|| {
663 super::resolve_all_features(
664 self.resolve,
665 self.resolved_features,
666 self.package_set,
667 pkg.package_id(),
668 )
669 });
670 rf.iter().filter(|f| !features.contains(*f)).collect()
671 }
672 None => Vec::new(),
673 };
674 if target.is_lib() || unavailable_features.is_empty() {
675 units.extend(self.new_units(pkg, target, mode));
676 } else if requires_features {
677 let required_features = target.required_features().unwrap();
678 let quoted_required_features: Vec<String> = required_features
679 .iter()
680 .map(|s| format!("`{}`", s))
681 .collect();
682 anyhow::bail!(
683 "target `{}` in package `{}` requires the features: {}\n\
684 Consider enabling them by passing, e.g., `--features=\"{}\"`",
685 target.name(),
686 pkg.name(),
687 quoted_required_features.join(", "),
688 required_features.join(" ")
689 );
690 }
691 }
693 let mut units: Vec<_> = units.into_iter().collect();
694 self.unmatched_target_filters(&units)?;
695
696 units.sort_unstable();
698 Ok(units)
699 }
700
701 pub fn generate_root_units(&self) -> CargoResult<Vec<Unit>> {
706 let proposals = self.create_proposals()?;
707 self.proposals_to_units(proposals)
708 }
709
710 pub fn generate_scrape_units(&self, doc_units: &[Unit]) -> CargoResult<Vec<Unit>> {
717 let scrape_proposals = self.create_docscrape_proposals(&doc_units)?;
718 let scrape_units = self.proposals_to_units(scrape_proposals)?;
719 Ok(scrape_units)
720 }
721}