cargo/ops/cargo_compile/
unit_generator.rs

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/// A proposed target.
24///
25/// Proposed targets are later filtered into actual `Unit`s based on whether or
26/// not the target requires its features to be present.
27#[derive(Debug)]
28struct Proposal<'a> {
29    pkg: &'a Package,
30    target: &'a Target,
31    /// Indicates whether or not all required features *must* be present. If
32    /// false, and the features are not available, then it will be silently
33    /// skipped. Generally, targets specified by name (`--bin foo`) are
34    /// required, all others can be silently skipped if features are missing.
35    requires_features: bool,
36    mode: CompileMode,
37}
38
39/// The context needed for generating root units,
40/// which are packages the user has requested to compile.
41///
42/// To generate a full [`UnitGraph`],
43/// generally you need to call [`generate_root_units`] first,
44/// and then provide the output to [`build_unit_dependencies`].
45///
46/// [`generate_root_units`]: UnitGenerator::generate_root_units
47/// [`build_unit_dependencies`]: crate::core::compiler::unit_dependencies::build_unit_dependencies
48/// [`UnitGraph`]: crate::core::compiler::unit_graph::UnitGraph
49pub(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    /// Helper for creating a list of `Unit` structures
69    fn new_units(
70        &self,
71        pkg: &Package,
72        target: &Target,
73        initial_target_mode: CompileMode,
74    ) -> Vec<Unit> {
75        // Custom build units are added in `build_unit_dependencies`.
76        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                    // Examples are included as regular binaries to verify
81                    // that they compile.
82                    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        // No need to worry about build-dependencies, roots are never build dependencies.
93        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        // If `--target` has not been specified, then the unit
99        // graph is built almost like if `--target $HOST` was
100        // specified. See `rebuild_unit_graph_shared` for more on
101        // why this is done. However, if the package has its own
102        // `package.target` key, then this gets used instead of
103        // `$HOST`
104        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                    // NOTE: the `UnitFor` here is subtle. If you have a profile
124                    // with `panic` set, the `panic` flag is cleared for
125                    // tests/benchmarks and their dependencies. If this
126                    // was `normal`, then the lib would get compiled three
127                    // times (once with panic, once without, and once with
128                    // `--test`).
129                    //
130                    // This would cause a problem for doc tests, which would fail
131                    // because `rustdoc` would attempt to link with both libraries
132                    // at the same time. Also, it's probably not important (or
133                    // even desirable?) for rustdoc to link with a lib with
134                    // `panic` set.
135                    //
136                    // As a consequence, Examples and Binaries get compiled
137                    // without `panic` set. This probably isn't a bad deal.
138                    //
139                    // Forcing the lib to be compiled three times during `cargo
140                    // test` is probably also not desirable.
141                    UnitFor::new_test(self.ws.gctx(), kind)
142                } else if target.for_host() {
143                    // Proc macro / plugin should not have `panic` set.
144                    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                    /*is_std*/ false,
167                    /*dep_hash*/ 0,
168                    IsArtifact::No,
169                    None,
170                )
171            })
172            .collect()
173    }
174
175    /// Given a list of all targets for a package, filters out only the targets
176    /// that are automatically included when the user doesn't specify any targets.
177    fn filter_default_targets<'b>(&self, targets: &'b [Target]) -> Vec<&'b Target> {
178        match self.intent {
179            UserIntent::Bench => targets.iter().filter(|t| t.benched()).collect(),
180            UserIntent::Test => targets
181                .iter()
182                .filter(|t| t.tested() || t.is_example())
183                .collect(),
184            UserIntent::Build | UserIntent::Check { .. } => targets
185                .iter()
186                .filter(|t| t.is_bin() || t.is_lib())
187                .collect(),
188            UserIntent::Doc { .. } => {
189                // `doc` does lib and bins (bin with same name as lib is skipped).
190                targets
191                    .iter()
192                    .filter(|t| {
193                        t.documented()
194                            && (!t.is_bin()
195                                || !targets
196                                    .iter()
197                                    .any(|l| l.is_lib() && l.crate_name() == t.crate_name()))
198                    })
199                    .collect()
200            }
201            UserIntent::Doctest => {
202                panic!("Invalid intent {:?}", self.intent)
203            }
204        }
205    }
206
207    /// Filters the set of all possible targets based on the provided predicate.
208    fn filter_targets(
209        &self,
210        predicate: impl Fn(&Target) -> bool,
211        requires_features: bool,
212        mode: CompileMode,
213    ) -> Vec<Proposal<'a>> {
214        self.packages
215            .iter()
216            .flat_map(|pkg| {
217                pkg.targets()
218                    .iter()
219                    .filter(|t| predicate(t))
220                    .map(|target| Proposal {
221                        pkg,
222                        target,
223                        requires_features,
224                        mode,
225                    })
226            })
227            .collect()
228    }
229
230    /// Finds the targets for a specifically named target.
231    fn find_named_targets(
232        &self,
233        target_name: &str,
234        target_desc: &'static str,
235        is_expected_kind: fn(&Target) -> bool,
236        mode: CompileMode,
237    ) -> CargoResult<Vec<Proposal<'a>>> {
238        let is_glob = is_glob_pattern(target_name);
239        let pattern = build_glob(target_name)?;
240        let filter = |t: &Target| {
241            if is_glob {
242                is_expected_kind(t) && pattern.matches(t.name())
243            } else {
244                is_expected_kind(t) && t.name() == target_name
245            }
246        };
247        let proposals = self.filter_targets(filter, true, mode);
248        if proposals.is_empty() {
249            let mut targets = std::collections::BTreeMap::new();
250            for (pkg, target) in self.packages.iter().flat_map(|pkg| {
251                pkg.targets()
252                    .iter()
253                    .filter(|target| is_expected_kind(target))
254                    .map(move |t| (pkg, t))
255            }) {
256                targets
257                    .entry(target.name())
258                    .or_insert_with(Vec::new)
259                    .push((pkg, target));
260            }
261
262            let suggestion = closest_msg(target_name, targets.keys(), |t| t, "target");
263            let targets_elsewhere = self.get_targets_from_other_packages(filter)?;
264            let append_targets_elsewhere = |msg: &mut String| {
265                let mut available_msg = Vec::new();
266                for (package, targets) in &targets_elsewhere {
267                    if !targets.is_empty() {
268                        available_msg.push(format!(
269                            "help: available {target_desc} in `{package}` package:"
270                        ));
271                        for target in targets {
272                            available_msg.push(format!("    {target}"));
273                        }
274                    }
275                }
276                if !available_msg.is_empty() {
277                    write!(msg, "\n{}", available_msg.join("\n"))?;
278                }
279                CargoResult::Ok(())
280            };
281
282            let unmatched_packages = match self.spec {
283                Packages::Default | Packages::OptOut(_) | Packages::All(_) => {
284                    " in default-run packages".to_owned()
285                }
286                Packages::Packages(packages) => match packages.len() {
287                    0 => String::new(),
288                    1 => format!(" in `{}` package", packages[0]),
289                    _ => format!(" in `{}`, ... packages", packages[0]),
290                },
291            };
292
293            let named = if is_glob { "matches pattern" } else { "named" };
294
295            let mut msg = String::new();
296            write!(
297                msg,
298                "no {target_desc} target {named} `{target_name}`{unmatched_packages}{suggestion}",
299            )?;
300            if !targets_elsewhere.is_empty() {
301                append_targets_elsewhere(&mut msg)?;
302            } else if suggestion.is_empty() && !targets.is_empty() {
303                write!(msg, "\nhelp: available {} targets:", target_desc)?;
304                for (target_name, pkgs) in targets {
305                    if pkgs.len() == 1 {
306                        write!(msg, "\n    {target_name}")?;
307                    } else {
308                        for (pkg, _) in pkgs {
309                            let pkg_name = pkg.name();
310                            write!(msg, "\n    {target_name} in package {pkg_name}")?;
311                        }
312                    }
313                }
314            }
315            anyhow::bail!(msg);
316        }
317        Ok(proposals)
318    }
319
320    fn get_targets_from_other_packages(
321        &self,
322        filter_fn: impl Fn(&Target) -> bool,
323    ) -> CargoResult<Vec<(&str, Vec<&str>)>> {
324        let packages = Packages::All(Vec::new()).get_packages(self.ws)?;
325        let targets = packages
326            .into_iter()
327            .filter_map(|pkg| {
328                let mut targets: Vec<_> = pkg
329                    .manifest()
330                    .targets()
331                    .iter()
332                    .filter_map(|target| filter_fn(target).then(|| target.name()))
333                    .collect();
334                if targets.is_empty() {
335                    None
336                } else {
337                    targets.sort();
338                    Some((pkg.name().as_str(), targets))
339                }
340            })
341            .collect();
342
343        Ok(targets)
344    }
345
346    /// Returns a list of proposed targets based on command-line target selection flags.
347    fn list_rule_targets(
348        &self,
349        rule: &FilterRule,
350        target_desc: &'static str,
351        is_expected_kind: fn(&Target) -> bool,
352        mode: CompileMode,
353    ) -> CargoResult<Vec<Proposal<'a>>> {
354        let mut proposals = Vec::new();
355        match rule {
356            FilterRule::All => proposals.extend(self.filter_targets(is_expected_kind, false, mode)),
357            FilterRule::Just(names) => {
358                for name in names {
359                    proposals.extend(self.find_named_targets(
360                        name,
361                        target_desc,
362                        is_expected_kind,
363                        mode,
364                    )?);
365                }
366            }
367        }
368        Ok(proposals)
369    }
370
371    /// Create a list of proposed targets given the context in `UnitGenerator`
372    fn create_proposals(&self) -> CargoResult<Vec<Proposal<'_>>> {
373        let mut proposals: Vec<Proposal<'_>> = Vec::new();
374
375        match *self.filter {
376            CompileFilter::Default {
377                required_features_filterable,
378            } => {
379                for pkg in self.packages {
380                    let default = self.filter_default_targets(pkg.targets());
381                    proposals.extend(default.into_iter().map(|target| Proposal {
382                        pkg,
383                        target,
384                        requires_features: !required_features_filterable,
385                        mode: to_compile_mode(self.intent),
386                    }));
387                    if matches!(self.intent, UserIntent::Test) {
388                        if let Some(t) = pkg
389                            .targets()
390                            .iter()
391                            .find(|t| t.is_lib() && t.doctested() && t.doctestable())
392                        {
393                            proposals.push(Proposal {
394                                pkg,
395                                target: t,
396                                requires_features: false,
397                                mode: CompileMode::Doctest,
398                            });
399                        }
400                    }
401                }
402            }
403            CompileFilter::Only {
404                all_targets,
405                ref lib,
406                ref bins,
407                ref examples,
408                ref tests,
409                ref benches,
410            } => {
411                if *lib != LibRule::False {
412                    let mut libs = Vec::new();
413                    let compile_mode = to_compile_mode(self.intent);
414                    for proposal in self.filter_targets(Target::is_lib, false, compile_mode) {
415                        let Proposal { target, pkg, .. } = proposal;
416                        if matches!(self.intent, UserIntent::Doctest) && !target.doctestable() {
417                            let types = target.rustc_crate_types();
418                            let types_str: Vec<&str> = types.iter().map(|t| t.as_str()).collect();
419                            self.ws.gctx().shell().warn(format!(
420                      "doc tests are not supported for crate type(s) `{}` in package `{}`",
421                      types_str.join(", "),
422                      pkg.name()
423                  ))?;
424                        } else {
425                            libs.push(proposal)
426                        }
427                    }
428                    if !all_targets && libs.is_empty() && *lib == LibRule::True {
429                        let names = self
430                            .packages
431                            .iter()
432                            .map(|pkg| pkg.name())
433                            .collect::<Vec<_>>();
434                        if names.len() == 1 {
435                            anyhow::bail!("no library targets found in package `{}`", names[0]);
436                        } else {
437                            anyhow::bail!(
438                                "no library targets found in packages: {}",
439                                names.join(", ")
440                            );
441                        }
442                    }
443                    proposals.extend(libs);
444                }
445
446                let default_mode = to_compile_mode(self.intent);
447
448                // If `--tests` was specified, add all targets that would be
449                // generated by `cargo test`.
450                let test_filter = match tests {
451                    FilterRule::All => Target::tested,
452                    FilterRule::Just(_) => Target::is_test,
453                };
454                let test_mode = match self.intent {
455                    UserIntent::Build => CompileMode::Test,
456                    UserIntent::Check { .. } => CompileMode::Check { test: true },
457                    _ => default_mode,
458                };
459                // If `--benches` was specified, add all targets that would be
460                // generated by `cargo bench`.
461                let bench_filter = match benches {
462                    FilterRule::All => Target::benched,
463                    FilterRule::Just(_) => Target::is_bench,
464                };
465
466                proposals.extend(self.list_rule_targets(
467                    bins,
468                    "bin",
469                    Target::is_bin,
470                    default_mode,
471                )?);
472                proposals.extend(self.list_rule_targets(
473                    examples,
474                    "example",
475                    Target::is_example,
476                    default_mode,
477                )?);
478                proposals.extend(self.list_rule_targets(tests, "test", test_filter, test_mode)?);
479                proposals.extend(self.list_rule_targets(
480                    benches,
481                    "bench",
482                    bench_filter,
483                    test_mode,
484                )?);
485            }
486        }
487
488        Ok(proposals)
489    }
490
491    /// Proposes targets from which to scrape examples for documentation
492    fn create_docscrape_proposals(&self, doc_units: &[Unit]) -> CargoResult<Vec<Proposal<'a>>> {
493        // In general, the goal is to scrape examples from (a) whatever targets
494        // the user is documenting, and (b) Example targets. However, if the user
495        // is documenting a library with dev-dependencies, those dev-deps are not
496        // needed for the library, while dev-deps are needed for the examples.
497        //
498        // If scrape-examples caused `cargo doc` to start requiring dev-deps, this
499        // would be a breaking change to crates whose dev-deps don't compile.
500        // Therefore we ONLY want to scrape Example targets if either:
501        //    (1) No package has dev-dependencies, so this is a moot issue, OR
502        //    (2) The provided CompileFilter requires dev-dependencies anyway.
503        //
504        // The next two variables represent these two conditions.
505        let no_pkg_has_dev_deps = self.packages.iter().all(|pkg| {
506            pkg.summary()
507                .dependencies()
508                .iter()
509                .all(|dep| !matches!(dep.kind(), DepKind::Development))
510        });
511        let reqs_dev_deps = matches!(self.has_dev_units, HasDevUnits::Yes);
512        let safe_to_scrape_example_targets = no_pkg_has_dev_deps || reqs_dev_deps;
513
514        let pkgs_to_scrape = doc_units
515            .iter()
516            .filter(|unit| self.ws.unit_needs_doc_scrape(unit))
517            .map(|u| &u.pkg)
518            .collect::<HashSet<_>>();
519
520        let skipped_examples = RefCell::new(Vec::new());
521        let can_scrape = |target: &Target| {
522            match (target.doc_scrape_examples(), target.is_example()) {
523                // Targets configured by the user to not be scraped should never be scraped
524                (RustdocScrapeExamples::Disabled, _) => false,
525                // Targets configured by the user to be scraped should always be scraped
526                (RustdocScrapeExamples::Enabled, _) => true,
527                // Example targets with no configuration should be conditionally scraped if
528                // it's guaranteed not to break the build
529                (RustdocScrapeExamples::Unset, true) => {
530                    if !safe_to_scrape_example_targets {
531                        skipped_examples
532                            .borrow_mut()
533                            .push(target.name().to_string());
534                    }
535                    safe_to_scrape_example_targets
536                }
537                // All other targets are ignored for now. This may change in the future!
538                (RustdocScrapeExamples::Unset, false) => false,
539            }
540        };
541
542        let mut scrape_proposals = self.filter_targets(can_scrape, false, CompileMode::Docscrape);
543        scrape_proposals.retain(|proposal| pkgs_to_scrape.contains(proposal.pkg));
544
545        let skipped_examples = skipped_examples.into_inner();
546        if !skipped_examples.is_empty() {
547            let mut shell = self.ws.gctx().shell();
548            let example_str = skipped_examples.join(", ");
549            shell.warn(format!(
550                "\
551Rustdoc did not scrape the following examples because they require dev-dependencies: {example_str}
552    If you want Rustdoc to scrape these examples, then add `doc-scrape-examples = true`
553    to the [[example]] target configuration of at least one example."
554            ))?;
555        }
556
557        Ok(scrape_proposals)
558    }
559
560    /// Checks if the unit list is empty and the user has passed any combination of
561    /// --tests, --examples, --benches or --bins, and we didn't match on any targets.
562    /// We want to emit a warning to make sure the user knows that this run is a no-op,
563    /// and their code remains unchecked despite cargo not returning any errors
564    fn unmatched_target_filters(&self, units: &[Unit]) -> CargoResult<()> {
565        let mut shell = self.ws.gctx().shell();
566        if let CompileFilter::Only {
567            all_targets,
568            lib: _,
569            ref bins,
570            ref examples,
571            ref tests,
572            ref benches,
573        } = *self.filter
574        {
575            if units.is_empty() {
576                let mut filters = String::new();
577                let mut miss_count = 0;
578
579                let mut append = |t: &FilterRule, s| {
580                    if let FilterRule::All = *t {
581                        miss_count += 1;
582                        filters.push_str(s);
583                    }
584                };
585
586                if all_targets {
587                    filters.push_str(" `all-targets`");
588                } else {
589                    append(bins, " `bins`,");
590                    append(tests, " `tests`,");
591                    append(examples, " `examples`,");
592                    append(benches, " `benches`,");
593                    filters.pop();
594                }
595
596                return shell.warn(format!(
597                    "target {}{} specified, but no targets matched; this is a no-op",
598                    if miss_count > 1 { "filters" } else { "filter" },
599                    filters,
600                ));
601            }
602        }
603
604        Ok(())
605    }
606
607    /// Warns if a target's required-features references a feature that doesn't exist.
608    ///
609    /// This is a warning because historically this was not validated, and it
610    /// would cause too much breakage to make it an error.
611    fn validate_required_features(
612        &self,
613        target_name: &str,
614        required_features: &[String],
615        summary: &Summary,
616    ) -> CargoResult<()> {
617        let resolve = match self.workspace_resolve {
618            None => return Ok(()),
619            Some(resolve) => resolve,
620        };
621
622        let mut shell = self.ws.gctx().shell();
623        for feature in required_features {
624            let fv = FeatureValue::new(feature.into());
625            match &fv {
626                FeatureValue::Feature(f) => {
627                    if !summary.features().contains_key(f) {
628                        shell.warn(format!(
629                            "invalid feature `{}` in required-features of target `{}`: \
630                      `{}` is not present in [features] section",
631                            fv, target_name, fv
632                        ))?;
633                    }
634                }
635                FeatureValue::Dep { .. } => {
636                    anyhow::bail!(
637                        "invalid feature `{}` in required-features of target `{}`: \
638                  `dep:` prefixed feature values are not allowed in required-features",
639                        fv,
640                        target_name
641                    );
642                }
643                FeatureValue::DepFeature { weak: true, .. } => {
644                    anyhow::bail!(
645                        "invalid feature `{}` in required-features of target `{}`: \
646                  optional dependency with `?` is not allowed in required-features",
647                        fv,
648                        target_name
649                    );
650                }
651                // Handling of dependent_crate/dependent_crate_feature syntax
652                FeatureValue::DepFeature {
653                    dep_name,
654                    dep_feature,
655                    weak: false,
656                } => {
657                    match resolve.deps(summary.package_id()).find(|(_dep_id, deps)| {
658                        deps.iter().any(|dep| dep.name_in_toml() == *dep_name)
659                    }) {
660                        Some((dep_id, _deps)) => {
661                            let dep_summary = resolve.summary(dep_id);
662                            if !dep_summary.features().contains_key(dep_feature)
663                                && !dep_summary.dependencies().iter().any(|dep| {
664                                    dep.name_in_toml() == *dep_feature && dep.is_optional()
665                                })
666                            {
667                                shell.warn(format!(
668                                    "invalid feature `{}` in required-features of target `{}`: \
669                              feature `{}` does not exist in package `{}`",
670                                    fv, target_name, dep_feature, dep_id
671                                ))?;
672                            }
673                        }
674                        None => {
675                            shell.warn(format!(
676                                "invalid feature `{}` in required-features of target `{}`: \
677                          dependency `{}` does not exist",
678                                fv, target_name, dep_name
679                            ))?;
680                        }
681                    }
682                }
683            }
684        }
685        Ok(())
686    }
687
688    /// Converts proposals to units based on each target's required features.
689    fn proposals_to_units(&self, proposals: Vec<Proposal<'_>>) -> CargoResult<Vec<Unit>> {
690        // Only include targets that are libraries or have all required
691        // features available.
692        //
693        // `features_map` is a map of &Package -> enabled_features
694        // It is computed by the set of enabled features for the package plus
695        // every enabled feature of every enabled dependency.
696        let mut features_map = HashMap::new();
697        // This needs to be a set to de-duplicate units. Due to the way the
698        // targets are filtered, it is possible to have duplicate proposals for
699        // the same thing.
700        let mut units = HashSet::new();
701        for Proposal {
702            pkg,
703            target,
704            requires_features,
705            mode,
706        } in proposals
707        {
708            let unavailable_features = match target.required_features() {
709                Some(rf) => {
710                    self.validate_required_features(target.name(), rf, pkg.summary())?;
711
712                    let features = features_map.entry(pkg).or_insert_with(|| {
713                        super::resolve_all_features(
714                            self.resolve,
715                            self.resolved_features,
716                            self.package_set,
717                            pkg.package_id(),
718                        )
719                    });
720                    rf.iter().filter(|f| !features.contains(*f)).collect()
721                }
722                None => Vec::new(),
723            };
724            if target.is_lib() || unavailable_features.is_empty() {
725                units.extend(self.new_units(pkg, target, mode));
726            } else if requires_features {
727                let required_features = target.required_features().unwrap();
728                let quoted_required_features: Vec<String> = required_features
729                    .iter()
730                    .map(|s| format!("`{}`", s))
731                    .collect();
732                anyhow::bail!(
733                    "target `{}` in package `{}` requires the features: {}\n\
734               Consider enabling them by passing, e.g., `--features=\"{}\"`",
735                    target.name(),
736                    pkg.name(),
737                    quoted_required_features.join(", "),
738                    required_features.join(" ")
739                );
740            }
741            // else, silently skip target.
742        }
743        let mut units: Vec<_> = units.into_iter().collect();
744        self.unmatched_target_filters(&units)?;
745
746        // Keep the roots in a consistent order, which helps with checking test output.
747        units.sort_unstable();
748        Ok(units)
749    }
750
751    /// Generates all the base units for the packages the user has requested to
752    /// compile. Dependencies for these units are computed later in [`unit_dependencies`].
753    ///
754    /// [`unit_dependencies`]: crate::core::compiler::unit_dependencies
755    pub fn generate_root_units(&self) -> CargoResult<Vec<Unit>> {
756        let proposals = self.create_proposals()?;
757        self.proposals_to_units(proposals)
758    }
759
760    /// Generates units specifically for doc-scraping.
761    ///
762    /// This requires a separate entrypoint from [`generate_root_units`] because it
763    /// takes the documented units as input.
764    ///
765    /// [`generate_root_units`]: Self::generate_root_units
766    pub fn generate_scrape_units(&self, doc_units: &[Unit]) -> CargoResult<Vec<Unit>> {
767        let scrape_proposals = self.create_docscrape_proposals(&doc_units)?;
768        let scrape_units = self.proposals_to_units(scrape_proposals)?;
769        Ok(scrape_units)
770    }
771}
772
773/// Converts [`UserIntent`] to [`CompileMode`] for root units.
774fn to_compile_mode(intent: UserIntent) -> CompileMode {
775    match intent {
776        UserIntent::Test | UserIntent::Bench => CompileMode::Test,
777        UserIntent::Build => CompileMode::Build,
778        UserIntent::Check { test } => CompileMode::Check { test },
779        UserIntent::Doc { .. } => CompileMode::Doc,
780        UserIntent::Doctest => CompileMode::Doctest,
781    }
782}