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