#
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                    " in default-run packages".to_owned()
    298                }
    299                Packages::Packages(packages) => match packages.len() {
    300                    0 => String::new(),
    301                    1 => format!(" in `{}` package", packages[0]),
    302                    _ => format!(" in `{}`, ... packages", packages[0]),
    303                },
    304            };
    305
    306            let named = if is_glob { "matches pattern" } else { "named" };
    307
    308            let mut msg = String::new();
    309            write!(
    310                msg,
    311                "no {target_desc} target {named} `{target_name}`{unmatched_packages}{suggestion}",
    312            )?;
    313            if !targets_elsewhere.is_empty() {
    314                append_targets_elsewhere(&mut msg)?;
    315            } else if suggestion.is_empty() && !targets.is_empty() {
    316                write!(msg, "\nhelp: available {} targets:", target_desc)?;
    317                for (target_name, pkgs) in targets {
    318                    if pkgs.len() == 1 {
    319                        write!(msg, "\n    {target_name}")?;
    320                    } else {
    321                        for (pkg, _) in pkgs {
    322                            let pkg_name = pkg.name();
    323                            write!(msg, "\n    {target_name} in package {pkg_name}")?;
    324                        }
    325                    }
    326                }
    327            }
    328            anyhow::bail!(msg);
    329        }
    330        Ok(proposals)
    331    }
    332
    333    fn get_targets_from_other_packages(
    334        &self,
    335        filter_fn: impl Fn(&Target) -> bool,
    336    ) -> CargoResult<Vec<(&str, Vec<&str>)>> {
    337        let packages = Packages::All(Vec::new()).get_packages(self.ws)?;
    338        let targets = packages
    339            .into_iter()
    340            .filter_map(|pkg| {
    341                let mut targets: Vec<_> = pkg
    342                    .manifest()
    343                    .targets()
    344                    .iter()
    345                    .filter_map(|target| filter_fn(target).then(|| target.name()))
    346                    .collect();
    347                if targets.is_empty() {
    348                    None
    349                } else {
    350                    targets.sort();
    351                    Some((pkg.name().as_str(), targets))
    352                }
    353            })
    354            .collect();
    355
    356        Ok(targets)
    357    }
    358
    359    /// Returns a list of proposed targets based on command-line target selection flags.
    360    fn list_rule_targets(
    361        &self,
    362        rule: &FilterRule,
    363        target_desc: &'static str,
    364        is_expected_kind: fn(&Target) -> bool,
    365        mode: CompileMode,
    366    ) -> CargoResult<Vec<Proposal<'a>>> {
    367        let mut proposals = Vec::new();
    368        match rule {
    369            FilterRule::All => proposals.extend(self.filter_targets(is_expected_kind, false, mode)),
    370            FilterRule::Just(names) => {
    371                for name in names {
    372                    proposals.extend(self.find_named_targets(
    373                        name,
    374                        target_desc,
    375                        is_expected_kind,
    376                        mode,
    377                    )?);
    378                }
    379            }
    380        }
    381        Ok(proposals)
    382    }
    383
    384    /// Create a list of proposed targets given the context in `UnitGenerator`
    385    fn create_proposals(&self) -> CargoResult<Vec<Proposal<'_>>> {
    386        let mut proposals: Vec<Proposal<'_>> = Vec::new();
    387
    388        match *self.filter {
    389            CompileFilter::Default {
    390                required_features_filterable,
    391            } => {
    392                for pkg in self.packages {
    393                    let default = self.filter_default_targets(pkg.targets());
    394                    proposals.extend(default.into_iter().map(|target| Proposal {
    395                        pkg,
    396                        target,
    397                        requires_features: !required_features_filterable,
    398                        mode: self.mode,
    399                    }));
    400                    if self.mode == CompileMode::Test {
    401                        if let Some(t) = pkg
    402                            .targets()
    403                            .iter()
    404                            .find(|t| t.is_lib() && t.doctested() && t.doctestable())
    405                        {
    406                            proposals.push(Proposal {
    407                                pkg,
    408                                target: t,
    409                                requires_features: false,
    410                                mode: CompileMode::Doctest,
    411                            });
    412                        }
    413                    }
    414                }
    415            }
    416            CompileFilter::Only {
    417                all_targets,
    418                ref lib,
    419                ref bins,
    420                ref examples,
    421                ref tests,
    422                ref benches,
    423            } => {
    424                if *lib != LibRule::False {
    425                    let mut libs = Vec::new();
    426                    for proposal in self.filter_targets(Target::is_lib, false, self.mode) {
    427                        let Proposal { target, pkg, .. } = proposal;
    428                        if self.mode.is_doc_test() && !target.doctestable() {
    429                            let types = target.rustc_crate_types();
    430                            let types_str: Vec<&str> = types.iter().map(|t| t.as_str()).collect();
    431                            self.ws.gctx().shell().warn(format!(
    432                      "doc tests are not supported for crate type(s) `{}` in package `{}`",
    433                      types_str.join(", "),
    434                      pkg.name()
    435                  ))?;
    436                        } else {
    437                            libs.push(proposal)
    438                        }
    439                    }
    440                    if !all_targets && libs.is_empty() && *lib == LibRule::True {
    441                        let names = self
    442                            .packages
    443                            .iter()
    444                            .map(|pkg| pkg.name())
    445                            .collect::<Vec<_>>();
    446                        if names.len() == 1 {
    447                            anyhow::bail!("no library targets found in package `{}`", names[0]);
    448                        } else {
    449                            anyhow::bail!(
    450                                "no library targets found in packages: {}",
    451                                names.join(", ")
    452                            );
    453                        }
    454                    }
    455                    proposals.extend(libs);
    456                }
    457
    458                // If `--tests` was specified, add all targets that would be
    459                // generated by `cargo test`.
    460                let test_filter = match tests {
    461                    FilterRule::All => Target::tested,
    462                    FilterRule::Just(_) => Target::is_test,
    463                };
    464                let test_mode = match self.mode {
    465                    CompileMode::Build => CompileMode::Test,
    466                    CompileMode::Check { .. } => CompileMode::Check { test: true },
    467                    _ => self.mode,
    468                };
    469                // If `--benches` was specified, add all targets that would be
    470                // generated by `cargo bench`.
    471                let bench_filter = match benches {
    472                    FilterRule::All => Target::benched,
    473                    FilterRule::Just(_) => Target::is_bench,
    474                };
    475                let bench_mode = match self.mode {
    476                    CompileMode::Build => CompileMode::Bench,
    477                    CompileMode::Check { .. } => CompileMode::Check { test: true },
    478                    _ => self.mode,
    479                };
    480
    481                proposals.extend(self.list_rule_targets(bins, "bin", Target::is_bin, self.mode)?);
    482                proposals.extend(self.list_rule_targets(
    483                    examples,
    484                    "example",
    485                    Target::is_example,
    486                    self.mode,
    487                )?);
    488                proposals.extend(self.list_rule_targets(tests, "test", test_filter, test_mode)?);
    489                proposals.extend(self.list_rule_targets(
    490                    benches,
    491                    "bench",
    492                    bench_filter,
    493                    bench_mode,
    494                )?);
    495            }
    496        }
    497
    498        Ok(proposals)
    499    }
    500
    501    /// Proposes targets from which to scrape examples for documentation
    502    fn create_docscrape_proposals(&self, doc_units: &[Unit]) -> CargoResult<Vec<Proposal<'a>>> {
    503        // In general, the goal is to scrape examples from (a) whatever targets
    504        // the user is documenting, and (b) Example targets. However, if the user
    505        // is documenting a library with dev-dependencies, those dev-deps are not
    506        // needed for the library, while dev-deps are needed for the examples.
    507        //
    508        // If scrape-examples caused `cargo doc` to start requiring dev-deps, this
    509        // would be a breaking change to crates whose dev-deps don't compile.
    510        // Therefore we ONLY want to scrape Example targets if either:
    511        //    (1) No package has dev-dependencies, so this is a moot issue, OR
    512        //    (2) The provided CompileFilter requires dev-dependencies anyway.
    513        //
    514        // The next two variables represent these two conditions.
    515        let no_pkg_has_dev_deps = self.packages.iter().all(|pkg| {
    516            pkg.summary()
    517                .dependencies()
    518                .iter()
    519                .all(|dep| !matches!(dep.kind(), DepKind::Development))
    520        });
    521        let reqs_dev_deps = matches!(self.has_dev_units, HasDevUnits::Yes);
    522        let safe_to_scrape_example_targets = no_pkg_has_dev_deps || reqs_dev_deps;
    523
    524        let pkgs_to_scrape = doc_units
    525            .iter()
    526            .filter(|unit| self.ws.unit_needs_doc_scrape(unit))
    527            .map(|u| &u.pkg)
    528            .collect::<HashSet<_>>();
    529
    530        let skipped_examples = RefCell::new(Vec::new());
    531        let can_scrape = |target: &Target| {
    532            match (target.doc_scrape_examples(), target.is_example()) {
    533                // Targets configured by the user to not be scraped should never be scraped
    534                (RustdocScrapeExamples::Disabled, _) => false,
    535                // Targets configured by the user to be scraped should always be scraped
    536                (RustdocScrapeExamples::Enabled, _) => true,
    537                // Example targets with no configuration should be conditionally scraped if
    538                // it's guaranteed not to break the build
    539                (RustdocScrapeExamples::Unset, true) => {
    540                    if !safe_to_scrape_example_targets {
    541                        skipped_examples
    542                            .borrow_mut()
    543                            .push(target.name().to_string());
    544                    }
    545                    safe_to_scrape_example_targets
    546                }
    547                // All other targets are ignored for now. This may change in the future!
    548                (RustdocScrapeExamples::Unset, false) => false,
    549            }
    550        };
    551
    552        let mut scrape_proposals = self.filter_targets(can_scrape, false, CompileMode::Docscrape);
    553        scrape_proposals.retain(|proposal| pkgs_to_scrape.contains(proposal.pkg));
    554
    555        let skipped_examples = skipped_examples.into_inner();
    556        if !skipped_examples.is_empty() {
    557            let mut shell = self.ws.gctx().shell();
    558            let example_str = skipped_examples.join(", ");
    559            shell.warn(format!(
    560                "\
    561Rustdoc did not scrape the following examples because they require dev-dependencies: {example_str}
    562    If you want Rustdoc to scrape these examples, then add `doc-scrape-examples = true`
    563    to the [[example]] target configuration of at least one example."
    564            ))?;
    565        }
    566
    567        Ok(scrape_proposals)
    568    }
    569
    570    /// Checks if the unit list is empty and the user has passed any combination of
    571    /// --tests, --examples, --benches or --bins, and we didn't match on any targets.
    572    /// We want to emit a warning to make sure the user knows that this run is a no-op,
    573    /// and their code remains unchecked despite cargo not returning any errors
    574    fn unmatched_target_filters(&self, units: &[Unit]) -> CargoResult<()> {
    575        let mut shell = self.ws.gctx().shell();
    576        if let CompileFilter::Only {
    577            all_targets,
    578            lib: _,
    579            ref bins,
    580            ref examples,
    581            ref tests,
    582            ref benches,
    583        } = *self.filter
    584        {
    585            if units.is_empty() {
    586                let mut filters = String::new();
    587                let mut miss_count = 0;
    588
    589                let mut append = |t: &FilterRule, s| {
    590                    if let FilterRule::All = *t {
    591                        miss_count += 1;
    592                        filters.push_str(s);
    593                    }
    594                };
    595
    596                if all_targets {
    597                    filters.push_str(" `all-targets`");
    598                } else {
    599                    append(bins, " `bins`,");
    600                    append(tests, " `tests`,");
    601                    append(examples, " `examples`,");
    602                    append(benches, " `benches`,");
    603                    filters.pop();
    604                }
    605
    606                return shell.warn(format!(
    607                    "target {}{} specified, but no targets matched; this is a no-op",
    608                    if miss_count > 1 { "filters" } else { "filter" },
    609                    filters,
    610                ));
    611            }
    612        }
    613
    614        Ok(())
    615    }
    616
    617    /// Warns if a target's required-features references a feature that doesn't exist.
    618    ///
    619    /// This is a warning because historically this was not validated, and it
    620    /// would cause too much breakage to make it an error.
    621    fn validate_required_features(
    622        &self,
    623        target_name: &str,
    624        required_features: &[String],
    625        summary: &Summary,
    626    ) -> CargoResult<()> {
    627        let resolve = match self.workspace_resolve {
    628            None => return Ok(()),
    629            Some(resolve) => resolve,
    630        };
    631
    632        let mut shell = self.ws.gctx().shell();
    633        for feature in required_features {
    634            let fv = FeatureValue::new(feature.into());
    635            match &fv {
    636                FeatureValue::Feature(f) => {
    637                    if !summary.features().contains_key(f) {
    638                        shell.warn(format!(
    639                            "invalid feature `{}` in required-features of target `{}`: \
    640                      `{}` is not present in [features] section",
    641                            fv, target_name, fv
    642                        ))?;
    643                    }
    644                }
    645                FeatureValue::Dep { .. } => {
    646                    anyhow::bail!(
    647                        "invalid feature `{}` in required-features of target `{}`: \
    648                  `dep:` prefixed feature values are not allowed in required-features",
    649                        fv,
    650                        target_name
    651                    );
    652                }
    653                FeatureValue::DepFeature { weak: true, .. } => {
    654                    anyhow::bail!(
    655                        "invalid feature `{}` in required-features of target `{}`: \
    656                  optional dependency with `?` is not allowed in required-features",
    657                        fv,
    658                        target_name
    659                    );
    660                }
    661                // Handling of dependent_crate/dependent_crate_feature syntax
    662                FeatureValue::DepFeature {
    663                    dep_name,
    664                    dep_feature,
    665                    weak: false,
    666                } => {
    667                    match resolve.deps(summary.package_id()).find(|(_dep_id, deps)| {
    668                        deps.iter().any(|dep| dep.name_in_toml() == *dep_name)
    669                    }) {
    670                        Some((dep_id, _deps)) => {
    671                            let dep_summary = resolve.summary(dep_id);
    672                            if !dep_summary.features().contains_key(dep_feature)
    673                                && !dep_summary.dependencies().iter().any(|dep| {
    674                                    dep.name_in_toml() == *dep_feature && dep.is_optional()
    675                                })
    676                            {
    677                                shell.warn(format!(
    678                                    "invalid feature `{}` in required-features of target `{}`: \
    679                              feature `{}` does not exist in package `{}`",
    680                                    fv, target_name, dep_feature, dep_id
    681                                ))?;
    682                            }
    683                        }
    684                        None => {
    685                            shell.warn(format!(
    686                                "invalid feature `{}` in required-features of target `{}`: \
    687                          dependency `{}` does not exist",
    688                                fv, target_name, dep_name
    689                            ))?;
    690                        }
    691                    }
    692                }
    693            }
    694        }
    695        Ok(())
    696    }
    697
    698    /// Converts proposals to units based on each target's required features.
    699    fn proposals_to_units(&self, proposals: Vec<Proposal<'_>>) -> CargoResult<Vec<Unit>> {
    700        // Only include targets that are libraries or have all required
    701        // features available.
    702        //
    703        // `features_map` is a map of &Package -> enabled_features
    704        // It is computed by the set of enabled features for the package plus
    705        // every enabled feature of every enabled dependency.
    706        let mut features_map = HashMap::new();
    707        // This needs to be a set to de-duplicate units. Due to the way the
    708        // targets are filtered, it is possible to have duplicate proposals for
    709        // the same thing.
    710        let mut units = HashSet::new();
    711        for Proposal {
    712            pkg,
    713            target,
    714            requires_features,
    715            mode,
    716        } in proposals
    717        {
    718            let unavailable_features = match target.required_features() {
    719                Some(rf) => {
    720                    self.validate_required_features(target.name(), rf, pkg.summary())?;
    721
    722                    let features = features_map.entry(pkg).or_insert_with(|| {
    723                        super::resolve_all_features(
    724                            self.resolve,
    725                            self.resolved_features,
    726                            self.package_set,
    727                            pkg.package_id(),
    728                        )
    729                    });
    730                    rf.iter().filter(|f| !features.contains(*f)).collect()
    731                }
    732                None => Vec::new(),
    733            };
    734            if target.is_lib() || unavailable_features.is_empty() {
    735                units.extend(self.new_units(pkg, target, mode));
    736            } else if requires_features {
    737                let required_features = target.required_features().unwrap();
    738                let quoted_required_features: Vec<String> = required_features
    739                    .iter()
    740                    .map(|s| format!("`{}`", s))
    741                    .collect();
    742                anyhow::bail!(
    743                    "target `{}` in package `{}` requires the features: {}\n\
    744               Consider enabling them by passing, e.g., `--features=\"{}\"`",
    745                    target.name(),
    746                    pkg.name(),
    747                    quoted_required_features.join(", "),
    748                    required_features.join(" ")
    749                );
    750            }
    751            // else, silently skip target.
    752        }
    753        let mut units: Vec<_> = units.into_iter().collect();
    754        self.unmatched_target_filters(&units)?;
    755
    756        // Keep the roots in a consistent order, which helps with checking test output.
    757        units.sort_unstable();
    758        Ok(units)
    759    }
    760
    761    /// Generates all the base units for the packages the user has requested to
    762    /// compile. Dependencies for these units are computed later in [`unit_dependencies`].
    763    ///
    764    /// [`unit_dependencies`]: crate::core::compiler::unit_dependencies
    765    pub fn generate_root_units(&self) -> CargoResult<Vec<Unit>> {
    766        let proposals = self.create_proposals()?;
    767        self.proposals_to_units(proposals)
    768    }
    769
    770    /// Generates units specifically for doc-scraping.
    771    ///
    772    /// This requires a separate entrypoint from [`generate_root_units`] because it
    773    /// takes the documented units as input.
    774    ///
    775    /// [`generate_root_units`]: Self::generate_root_units
    776    pub fn generate_scrape_units(&self, doc_units: &[Unit]) -> CargoResult<Vec<Unit>> {
    777        let scrape_proposals = self.create_docscrape_proposals(&doc_units)?;
    778        let scrape_units = self.proposals_to_units(scrape_proposals)?;
    779        Ok(scrape_units)
    780    }
    781}