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