Skip to main content

cargo/ops/cargo_compile/
unit_generator.rs

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/// A proposed target.
25///
26/// Proposed targets are later filtered into actual `Unit`s based on whether or
27/// not the target requires its features to be present.
28#[derive(Debug)]
29struct Proposal<'a> {
30    pkg: &'a Package,
31    target: &'a Target,
32    /// Indicates whether or not all required features *must* be present. If
33    /// false, and the features are not available, then it will be silently
34    /// skipped. Generally, targets specified by name (`--bin foo`) are
35    /// required, all others can be silently skipped if features are missing.
36    requires_features: bool,
37    mode: CompileMode,
38}
39
40/// The context needed for generating root units,
41/// which are packages the user has requested to compile.
42///
43/// To generate a full [`UnitGraph`],
44/// generally you need to call [`generate_root_units`] first,
45/// and then provide the output to [`build_unit_dependencies`].
46///
47/// [`generate_root_units`]: UnitGenerator::generate_root_units
48/// [`build_unit_dependencies`]: crate::core::compiler::unit_dependencies::build_unit_dependencies
49/// [`UnitGraph`]: crate::core::compiler::unit_graph::UnitGraph
50pub 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    /// Helper for creating a list of `Unit` structures
70    fn new_units(
71        &self,
72        pkg: &Package,
73        target: &Target,
74        initial_target_mode: CompileMode,
75    ) -> Vec<Unit> {
76        // Custom build units are added in `build_unit_dependencies`.
77        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                    // Examples are included as regular binaries to verify
82                    // that they compile.
83                    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        // No need to worry about build-dependencies, roots are never build dependencies.
94        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        // If `--target` has not been specified, then the unit
100        // graph is built almost like if `--target $HOST` was
101        // specified. See `rebuild_unit_graph_shared` for more on
102        // why this is done. However, if the package has its own
103        // `package.target` key, then this gets used instead of
104        // `$HOST`
105        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                    // NOTE: the `UnitFor` here is subtle. If you have a profile
125                    // with `panic` set, the `panic` flag is cleared for
126                    // tests/benchmarks and their dependencies. If this
127                    // was `normal`, then the lib would get compiled three
128                    // times (once with panic, once without, and once with
129                    // `--test`).
130                    //
131                    // This would cause a problem for doc tests, which would fail
132                    // because `rustdoc` would attempt to link with both libraries
133                    // at the same time. Also, it's probably not important (or
134                    // even desirable?) for rustdoc to link with a lib with
135                    // `panic` set.
136                    //
137                    // As a consequence, Examples and Binaries get compiled
138                    // without `panic` set. This probably isn't a bad deal.
139                    //
140                    // Forcing the lib to be compiled three times during `cargo
141                    // test` is probably also not desirable.
142                    UnitFor::new_test(self.ws.gctx(), kind)
143                } else if target.for_host() {
144                    // Proc macro / plugin should not have `panic` set.
145                    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                    /*is_std*/ false,
168                    /*dep_hash*/ 0,
169                    IsArtifact::No,
170                    None,
171                    false,
172                )
173            })
174            .collect()
175    }
176
177    /// Given a list of all targets for a package, filters out only the targets
178    /// that are automatically included when the user doesn't specify any targets.
179    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                // `doc` does lib and bins (bin with same name as lib is skipped).
192                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    /// Filters the set of all possible targets based on the provided predicate.
210    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    /// Finds the targets for a specifically named target.
233    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    /// Returns a list of proposed targets based on command-line target selection flags.
349    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    /// Create a list of proposed targets given the context in `UnitGenerator`
374    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                // can't enable `selected_dep_kinds.dev` because benches aren't enabled for
386                // UserIntent::Test
387
388                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                    // can't enable `selected_dep_kinds.dev` because doctests aren't enabled
428
429                    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                // If `--tests` was specified, add all targets that would be
466                // generated by `cargo test`.
467                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                // If `--benches` was specified, add all targets that would be
477                // generated by `cargo bench`.
478                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    /// Proposes targets from which to scrape examples for documentation
509    fn create_docscrape_proposals(&self, doc_units: &[Unit]) -> CargoResult<Vec<Proposal<'a>>> {
510        // In general, the goal is to scrape examples from (a) whatever targets
511        // the user is documenting, and (b) Example targets. However, if the user
512        // is documenting a library with dev-dependencies, those dev-deps are not
513        // needed for the library, while dev-deps are needed for the examples.
514        //
515        // If scrape-examples caused `cargo doc` to start requiring dev-deps, this
516        // would be a breaking change to crates whose dev-deps don't compile.
517        // Therefore we ONLY want to scrape Example targets if either:
518        //    (1) No package has dev-dependencies, so this is a moot issue, OR
519        //    (2) The provided CompileFilter requires dev-dependencies anyway.
520        //
521        // The next two variables represent these two conditions.
522        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                // Targets configured by the user to not be scraped should never be scraped
541                (RustdocScrapeExamples::Disabled, _) => false,
542                // Targets configured by the user to be scraped should always be scraped
543                (RustdocScrapeExamples::Enabled, _) => true,
544                // Example targets with no configuration should be conditionally scraped if
545                // it's guaranteed not to break the build
546                (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                // All other targets are ignored for now. This may change in the future!
555                (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    /// Checks if the unit list is empty and the user has passed any combination of
578    /// --tests, --examples, --benches or --bins, and we didn't match on any targets.
579    /// We want to emit a warning to make sure the user knows that this run is a no-op,
580    /// and their code remains unchecked despite cargo not returning any errors
581    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    /// Warns if a target's required-features references a feature that doesn't exist.
625    ///
626    /// This is a warning because historically this was not validated, and it
627    /// would cause too much breakage to make it an error.
628    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                // Handling of dependent_crate/dependent_crate_feature syntax
669                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    /// Converts proposals to units based on each target's required features.
706    fn proposals_to_units(&self, proposals: Vec<Proposal<'_>>) -> CargoResult<Vec<Unit>> {
707        // Only include targets that are libraries or have all required
708        // features available.
709        //
710        // `features_map` is a map of &Package -> enabled_features
711        // It is computed by the set of enabled features for the package plus
712        // every enabled feature of every enabled dependency.
713        let mut features_map = HashMap::new();
714        // This needs to be a set to de-duplicate units. Due to the way the
715        // targets are filtered, it is possible to have duplicate proposals for
716        // the same thing.
717        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            // else, silently skip target.
763        }
764        let mut units: Vec<_> = units.into_iter().collect();
765        self.unmatched_target_filters(&units)?;
766
767        // Keep the roots in a consistent order, which helps with checking test output.
768        units.sort_unstable();
769        Ok(units)
770    }
771
772    /// Generates all the base units for the packages the user has requested to
773    /// compile. Dependencies for these units are computed later in [`unit_dependencies`].
774    ///
775    /// [`unit_dependencies`]: crate::core::compiler::unit_dependencies
776    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    /// Generates units specifically for doc-scraping.
783    ///
784    /// This requires a separate entrypoint from [`generate_root_units`] because it
785    /// takes the documented units as input.
786    ///
787    /// [`generate_root_units`]: Self::generate_root_units
788    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
795/// Converts [`UserIntent`] to [`CompileMode`] for root units.
796fn 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}