#
cargo/ops/cargo_compile/
packages.rs

    1//! See [`Packages`].
    2
    3use std::collections::BTreeSet;
    4
    5use crate::core::{Package, PackageIdSpecQuery};
    6use crate::core::{PackageIdSpec, Workspace};
    7use crate::util::CargoResult;
    8use crate::util::restricted_names::is_glob_pattern;
    9
    10use anyhow::{Context as _, bail};
    11
    12/// Represents the selected packages that will be built.
    13///
    14/// Generally, it represents the combination of all `-p` flag. When working within
    15/// a workspace, `--exclude` and `--workspace` flags also contribute to it.
    16#[derive(PartialEq, Eq, Debug, Clone)]
    17pub enum Packages {
    18    /// Packages selected by default. Usually means no flag provided.
    19    Default,
    20    /// Opt in all packages.
    21    ///
    22    /// As of the time of this writing, it only works on opting in all workspace members.
    23    /// Keeps the packages passed in to verify that they exist in the workspace.
    24    All(Vec<String>),
    25    /// Opt out of packages passed in.
    26    ///
    27    /// As of the time of this writing, it only works on opting out workspace members.
    28    OptOut(Vec<String>),
    29    /// A sequence of hand-picked packages that will be built. Normally done by `-p` flag.
    30    Packages(Vec<String>),
    31}
    32
    33impl Packages {
    34    /// Creates a `Packages` from flags which are generally equivalent to command line flags.
    35    pub fn from_flags(all: bool, exclude: Vec<String>, package: Vec<String>) -> CargoResult<Self> {
    36        Ok(match (all, exclude.len(), package.len()) {
    37            (false, 0, 0) => Packages::Default,
    38            (false, 0, _) => Packages::Packages(package),
    39            (false, _, _) => anyhow::bail!("--exclude can only be used together with --workspace"),
    40            (true, 0, _) => Packages::All(package),
    41            (true, _, _) => Packages::OptOut(exclude),
    42        })
    43    }
    44
    45    /// Converts selected packages to [`PackageIdSpec`]s.
    46    pub fn to_package_id_specs(&self, ws: &Workspace<'_>) -> CargoResult<Vec<PackageIdSpec>> {
    47        let specs = match self {
    48            Packages::All(packages) => {
    49                emit_packages_not_found_within_workspace(ws, packages)?;
    50                ws.members()
    51                    .map(Package::package_id)
    52                    .map(|id| id.to_spec())
    53                    .collect()
    54            }
    55            Packages::OptOut(opt_out) => {
    56                let (mut patterns, mut ids) = opt_patterns_and_ids(opt_out)?;
    57                let specs = ws
    58                    .members()
    59                    .filter(|pkg| {
    60                        let id = ids.iter().find(|id| id.matches(pkg.package_id())).cloned();
    61                        if let Some(id) = &id {
    62                            ids.remove(id);
    63                        }
    64                        !id.is_some() && !match_patterns(pkg, &mut patterns)
    65                    })
    66                    .map(Package::package_id)
    67                    .map(|id| id.to_spec())
    68                    .collect();
    69                let warn = |e| ws.gctx().shell().warn(e);
    70                let names = ids
    71                    .into_iter()
    72                    .map(|id| id.to_string())
    73                    .collect::<BTreeSet<_>>();
    74                emit_package_not_found(ws, names, true).or_else(warn)?;
    75                emit_pattern_not_found(ws, patterns, true).or_else(warn)?;
    76                specs
    77            }
    78            Packages::Packages(packages) if packages.is_empty() => {
    79                vec![ws.current()?.package_id().to_spec()]
    80            }
    81            Packages::Packages(opt_in) => {
    82                let (mut patterns, mut specs) = opt_patterns_and_ids(opt_in)?;
    83                if !patterns.is_empty() {
    84                    let matched_pkgs = ws
    85                        .members()
    86                        .filter(|pkg| match_patterns(pkg, &mut patterns))
    87                        .map(Package::package_id)
    88                        .map(|id| id.to_spec());
    89                    specs.extend(matched_pkgs);
    90                }
    91                emit_pattern_not_found(ws, patterns, false)?;
    92                specs.into_iter().collect()
    93            }
    94            Packages::Default => ws
    95                .default_members()
    96                .map(Package::package_id)
    97                .map(|id| id.to_spec())
    98                .collect(),
    99        };
    100        if specs.is_empty() {
    101            if ws.is_virtual() {
    102                bail!(
    103                    "manifest path `{}` contains no package: The manifest is virtual, \
    104                     and the workspace has no members.",
    105                    ws.root().display()
    106                )
    107            }
    108            bail!("no packages to compile")
    109        }
    110        Ok(specs)
    111    }
    112
    113    /// Gets a list of selected [`Package`]s.
    114    pub fn get_packages<'ws>(&self, ws: &'ws Workspace<'_>) -> CargoResult<Vec<&'ws Package>> {
    115        let packages: Vec<_> = match self {
    116            Packages::Default => ws.default_members().collect(),
    117            Packages::All(packages) => {
    118                emit_packages_not_found_within_workspace(ws, packages)?;
    119                ws.members().collect()
    120            }
    121            Packages::OptOut(opt_out) => {
    122                let (mut patterns, mut ids) = opt_patterns_and_ids(opt_out)?;
    123                let packages = ws
    124                    .members()
    125                    .filter(|pkg| {
    126                        let id = ids.iter().find(|id| id.matches(pkg.package_id())).cloned();
    127                        if let Some(id) = &id {
    128                            ids.remove(id);
    129                        }
    130                        !id.is_some() && !match_patterns(pkg, &mut patterns)
    131                    })
    132                    .collect();
    133                let names = ids
    134                    .into_iter()
    135                    .map(|id| id.to_string())
    136                    .collect::<BTreeSet<_>>();
    137                emit_package_not_found(ws, names, true)?;
    138                emit_pattern_not_found(ws, patterns, true)?;
    139                packages
    140            }
    141            Packages::Packages(opt_in) => {
    142                let (mut patterns, mut ids) = opt_patterns_and_ids(opt_in)?;
    143                let packages = ws
    144                    .members()
    145                    .filter(|pkg| {
    146                        let id = ids.iter().find(|id| id.matches(pkg.package_id())).cloned();
    147                        if let Some(id) = &id {
    148                            ids.remove(id);
    149                        }
    150                        id.is_some() || match_patterns(pkg, &mut patterns)
    151                    })
    152                    .collect();
    153                let names = ids
    154                    .into_iter()
    155                    .map(|id| id.to_string())
    156                    .collect::<BTreeSet<_>>();
    157                emit_package_not_found(ws, names, false)?;
    158                emit_pattern_not_found(ws, patterns, false)?;
    159                packages
    160            }
    161        };
    162        Ok(packages)
    163    }
    164
    165    /// Returns whether or not the user needs to pass a `-p` flag to target a
    166    /// specific package in the workspace.
    167    pub fn needs_spec_flag(&self, ws: &Workspace<'_>) -> bool {
    168        match self {
    169            Packages::Default => ws.default_members().count() > 1,
    170            Packages::All(_) => ws.members().count() > 1,
    171            Packages::Packages(_) => true,
    172            Packages::OptOut(_) => true,
    173        }
    174    }
    175}
    176
    177/// Emits "package not found" error.
    178fn emit_package_not_found(
    179    ws: &Workspace<'_>,
    180    opt_names: BTreeSet<String>,
    181    opt_out: bool,
    182) -> CargoResult<()> {
    183    if !opt_names.is_empty() {
    184        anyhow::bail!(
    185            "{}package(s) `{}` not found in workspace `{}`",
    186            if opt_out { "excluded " } else { "" },
    187            opt_names.into_iter().collect::<Vec<_>>().join(", "),
    188            ws.root().display(),
    189        )
    190    }
    191    Ok(())
    192}
    193
    194/// Emits "glob pattern not found" error.
    195fn emit_pattern_not_found(
    196    ws: &Workspace<'_>,
    197    opt_patterns: Vec<(glob::Pattern, bool)>,
    198    opt_out: bool,
    199) -> CargoResult<()> {
    200    let not_matched = opt_patterns
    201        .iter()
    202        .filter(|(_, matched)| !*matched)
    203        .map(|(pat, _)| pat.as_str())
    204        .collect::<Vec<_>>();
    205    if !not_matched.is_empty() {
    206        anyhow::bail!(
    207            "{}package pattern(s) `{}` not found in workspace `{}`",
    208            if opt_out { "excluded " } else { "" },
    209            not_matched.join(", "),
    210            ws.root().display(),
    211        )
    212    }
    213    Ok(())
    214}
    215
    216fn emit_packages_not_found_within_workspace(
    217    ws: &Workspace<'_>,
    218    packages: &[String],
    219) -> CargoResult<()> {
    220    let (mut patterns, mut ids) = opt_patterns_and_ids(packages)?;
    221    let _: Vec<_> = ws
    222        .members()
    223        .filter(|pkg| {
    224            let id = ids.iter().find(|id| id.matches(pkg.package_id())).cloned();
    225            if let Some(id) = &id {
    226                ids.remove(id);
    227            }
    228            !id.is_some() && !match_patterns(pkg, &mut patterns)
    229        })
    230        .map(Package::package_id)
    231        .map(|id| id.to_spec())
    232        .collect();
    233    let names = ids
    234        .into_iter()
    235        .map(|id| id.to_string())
    236        .collect::<BTreeSet<_>>();
    237    emit_package_not_found(ws, names, false)?;
    238    emit_pattern_not_found(ws, patterns, false)?;
    239    Ok(())
    240}
    241
    242/// Given a list opt-in or opt-out package selection strings, generates two
    243/// collections that represent glob patterns and package id specs respectively.
    244fn opt_patterns_and_ids(
    245    opt: &[String],
    246) -> CargoResult<(Vec<(glob::Pattern, bool)>, BTreeSet<PackageIdSpec>)> {
    247    let mut opt_patterns = Vec::new();
    248    let mut opt_ids = BTreeSet::new();
    249    for x in opt.iter() {
    250        match PackageIdSpec::parse(x) {
    251            Ok(spec) => {
    252                opt_ids.insert(spec);
    253            }
    254            Err(_) if is_glob_pattern(x) => opt_patterns.push((build_glob(x)?, false)),
    255            Err(e) => return Err(e.into()),
    256        }
    257    }
    258    Ok((opt_patterns, opt_ids))
    259}
    260
    261/// Checks whether a package matches any of a list of glob patterns generated
    262/// from `opt_patterns_and_names`.
    263fn match_patterns(pkg: &Package, patterns: &mut Vec<(glob::Pattern, bool)>) -> bool {
    264    patterns.iter_mut().any(|(m, matched)| {
    265        let is_matched = m.matches(pkg.name().as_str());
    266        *matched |= is_matched;
    267        is_matched
    268    })
    269}
    270
    271/// Build [`glob::Pattern`] with informative context.
    272pub fn build_glob(pat: &str) -> CargoResult<glob::Pattern> {
    273    glob::Pattern::new(pat).with_context(|| format!("cannot build glob pattern from `{}`", pat))
    274}