#
cargo/ops/cargo_compile/
unit_generator.rs

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