blob: 9cca2ee3099282c5aada391cf27984319d654d9f [file] [log] [blame]
//! The Cargo "compile" operation.
//!
//! This module contains the entry point for starting the compilation process
//! for commands like `build`, `test`, `doc`, `rustc`, etc.
//!
//! The `compile` function will do all the work to compile a workspace. A
//! rough outline is:
//!
//! - Resolve the dependency graph (see `ops::resolve`).
//! - Download any packages needed (see `PackageSet`).
//! - Generate a list of top-level "units" of work for the targets the user
//! requested on the command-line. Each `Unit` corresponds to a compiler
//! invocation. This is done in this module (`generate_targets`).
//! - Build the graph of `Unit` dependencies (see
//! `core::compiler::context::unit_dependencies`).
//! - Create a `Context` which will perform the following steps:
//! - Prepare the `target` directory (see `Layout`).
//! - Create a job queue (see `JobQueue`). The queue checks the
//! fingerprint of each `Unit` to determine if it should run or be
//! skipped.
//! - Execute the queue. Each leaf in the queue's dependency graph is
//! executed, and then removed from the graph when finished. This
//! repeats until the queue is empty.
use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt::Write;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use crate::core::compiler::unit_dependencies::{build_unit_dependencies, IsArtifact};
use crate::core::compiler::unit_graph::{self, UnitDep, UnitGraph};
use crate::core::compiler::{standard_lib, CrateType, TargetInfo};
use crate::core::compiler::{BuildConfig, BuildContext, Compilation, Context};
use crate::core::compiler::{CompileKind, CompileMode, CompileTarget, RustcTargetData, Unit};
use crate::core::compiler::{DefaultExecutor, Executor, UnitInterner};
use crate::core::profiles::{Profiles, UnitFor};
use crate::core::resolver::features::{self, CliFeatures, FeaturesFor};
use crate::core::resolver::{HasDevUnits, Resolve};
use crate::core::{FeatureValue, Package, PackageSet, Shell, Summary, Target};
use crate::core::{PackageId, PackageIdSpec, SourceId, TargetKind, Workspace};
use crate::drop_println;
use crate::ops;
use crate::ops::resolve::WorkspaceResolve;
use crate::util::config::Config;
use crate::util::interning::InternedString;
use crate::util::restricted_names::is_glob_pattern;
use crate::util::{closest_msg, profile, CargoResult, StableHasher};
use anyhow::{bail, Context as _};
/// Contains information about how a package should be compiled.
///
/// Note on distinction between `CompileOptions` and `BuildConfig`:
/// `BuildConfig` contains values that need to be retained after
/// `BuildContext` is created. The other fields are no longer necessary. Think
/// of it as `CompileOptions` are high-level settings requested on the
/// command-line, and `BuildConfig` are low-level settings for actually
/// driving `rustc`.
#[derive(Debug)]
pub struct CompileOptions {
/// Configuration information for a rustc build
pub build_config: BuildConfig,
/// Feature flags requested by the user.
pub cli_features: CliFeatures,
/// A set of packages to build.
pub spec: Packages,
/// Filter to apply to the root package to select which targets will be
/// built.
pub filter: CompileFilter,
/// Extra arguments to be passed to rustdoc (single target only)
pub target_rustdoc_args: Option<Vec<String>>,
/// The specified target will be compiled with all the available arguments,
/// note that this only accounts for the *final* invocation of rustc
pub target_rustc_args: Option<Vec<String>>,
/// Crate types to be passed to rustc (single target only)
pub target_rustc_crate_types: Option<Vec<String>>,
/// Extra arguments passed to all selected targets for rustdoc.
pub local_rustdoc_args: Option<Vec<String>>,
/// Whether the `--document-private-items` flags was specified and should
/// be forwarded to `rustdoc`.
pub rustdoc_document_private_items: bool,
/// Whether the build process should check the minimum Rust version
/// defined in the cargo metadata for a crate.
pub honor_rust_version: bool,
}
impl CompileOptions {
pub fn new(config: &Config, mode: CompileMode) -> CargoResult<CompileOptions> {
let jobs = None;
let keep_going = false;
Ok(CompileOptions {
build_config: BuildConfig::new(config, jobs, keep_going, &[], mode)?,
cli_features: CliFeatures::new_all(false),
spec: ops::Packages::Packages(Vec::new()),
filter: CompileFilter::Default {
required_features_filterable: false,
},
target_rustdoc_args: None,
target_rustc_args: None,
target_rustc_crate_types: None,
local_rustdoc_args: None,
rustdoc_document_private_items: false,
honor_rust_version: true,
})
}
}
#[derive(PartialEq, Eq, Debug)]
pub enum Packages {
Default,
All,
OptOut(Vec<String>),
Packages(Vec<String>),
}
impl Packages {
pub fn from_flags(all: bool, exclude: Vec<String>, package: Vec<String>) -> CargoResult<Self> {
Ok(match (all, exclude.len(), package.len()) {
(false, 0, 0) => Packages::Default,
(false, 0, _) => Packages::Packages(package),
(false, _, _) => anyhow::bail!("--exclude can only be used together with --workspace"),
(true, 0, _) => Packages::All,
(true, _, _) => Packages::OptOut(exclude),
})
}
/// Converts selected packages from a workspace to `PackageIdSpec`s.
pub fn to_package_id_specs(&self, ws: &Workspace<'_>) -> CargoResult<Vec<PackageIdSpec>> {
let specs = match self {
Packages::All => ws
.members()
.map(Package::package_id)
.map(PackageIdSpec::from_package_id)
.collect(),
Packages::OptOut(opt_out) => {
let (mut patterns, mut names) = opt_patterns_and_names(opt_out)?;
let specs = ws
.members()
.filter(|pkg| {
!names.remove(pkg.name().as_str()) && !match_patterns(pkg, &mut patterns)
})
.map(Package::package_id)
.map(PackageIdSpec::from_package_id)
.collect();
let warn = |e| ws.config().shell().warn(e);
emit_package_not_found(ws, names, true).or_else(warn)?;
emit_pattern_not_found(ws, patterns, true).or_else(warn)?;
specs
}
Packages::Packages(packages) if packages.is_empty() => {
vec![PackageIdSpec::from_package_id(ws.current()?.package_id())]
}
Packages::Packages(opt_in) => {
let (mut patterns, packages) = opt_patterns_and_names(opt_in)?;
let mut specs = packages
.iter()
.map(|p| PackageIdSpec::parse(p))
.collect::<CargoResult<Vec<_>>>()?;
if !patterns.is_empty() {
let matched_pkgs = ws
.members()
.filter(|pkg| match_patterns(pkg, &mut patterns))
.map(Package::package_id)
.map(PackageIdSpec::from_package_id);
specs.extend(matched_pkgs);
}
emit_pattern_not_found(ws, patterns, false)?;
specs
}
Packages::Default => ws
.default_members()
.map(Package::package_id)
.map(PackageIdSpec::from_package_id)
.collect(),
};
if specs.is_empty() {
if ws.is_virtual() {
bail!(
"manifest path `{}` contains no package: The manifest is virtual, \
and the workspace has no members.",
ws.root().display()
)
}
bail!("no packages to compile")
}
Ok(specs)
}
/// Gets a list of selected packages from a workspace.
pub fn get_packages<'ws>(&self, ws: &'ws Workspace<'_>) -> CargoResult<Vec<&'ws Package>> {
let packages: Vec<_> = match self {
Packages::Default => ws.default_members().collect(),
Packages::All => ws.members().collect(),
Packages::OptOut(opt_out) => {
let (mut patterns, mut names) = opt_patterns_and_names(opt_out)?;
let packages = ws
.members()
.filter(|pkg| {
!names.remove(pkg.name().as_str()) && !match_patterns(pkg, &mut patterns)
})
.collect();
emit_package_not_found(ws, names, true)?;
emit_pattern_not_found(ws, patterns, true)?;
packages
}
Packages::Packages(opt_in) => {
let (mut patterns, mut names) = opt_patterns_and_names(opt_in)?;
let packages = ws
.members()
.filter(|pkg| {
names.remove(pkg.name().as_str()) || match_patterns(pkg, &mut patterns)
})
.collect();
emit_package_not_found(ws, names, false)?;
emit_pattern_not_found(ws, patterns, false)?;
packages
}
};
Ok(packages)
}
/// Returns whether or not the user needs to pass a `-p` flag to target a
/// specific package in the workspace.
pub fn needs_spec_flag(&self, ws: &Workspace<'_>) -> bool {
match self {
Packages::Default => ws.default_members().count() > 1,
Packages::All => ws.members().count() > 1,
Packages::Packages(_) => true,
Packages::OptOut(_) => true,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum LibRule {
/// Include the library, fail if not present
True,
/// Include the library if present
Default,
/// Exclude the library
False,
}
#[derive(Debug)]
pub enum FilterRule {
All,
Just(Vec<String>),
}
#[derive(Debug)]
pub enum CompileFilter {
Default {
/// Flag whether targets can be safely skipped when required-features are not satisfied.
required_features_filterable: bool,
},
Only {
all_targets: bool,
lib: LibRule,
bins: FilterRule,
examples: FilterRule,
tests: FilterRule,
benches: FilterRule,
},
}
pub fn compile<'a>(ws: &Workspace<'a>, options: &CompileOptions) -> CargoResult<Compilation<'a>> {
let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor);
compile_with_exec(ws, options, &exec)
}
/// Like `compile` but allows specifying a custom `Executor` that will be able to intercept build
/// calls and add custom logic. `compile` uses `DefaultExecutor` which just passes calls through.
pub fn compile_with_exec<'a>(
ws: &Workspace<'a>,
options: &CompileOptions,
exec: &Arc<dyn Executor>,
) -> CargoResult<Compilation<'a>> {
ws.emit_warnings()?;
compile_ws(ws, options, exec)
}
pub fn compile_ws<'a>(
ws: &Workspace<'a>,
options: &CompileOptions,
exec: &Arc<dyn Executor>,
) -> CargoResult<Compilation<'a>> {
let interner = UnitInterner::new();
let bcx = create_bcx(ws, options, &interner)?;
if options.build_config.unit_graph {
unit_graph::emit_serialized_unit_graph(&bcx.roots, &bcx.unit_graph, ws.config())?;
return Compilation::new(&bcx);
}
let _p = profile::start("compiling");
let cx = Context::new(&bcx)?;
cx.compile(exec)
}
pub fn print<'a>(
ws: &Workspace<'a>,
options: &CompileOptions,
print_opt_value: &str,
) -> CargoResult<()> {
let CompileOptions {
ref build_config,
ref target_rustc_args,
..
} = *options;
let config = ws.config();
let rustc = config.load_global_rustc(Some(ws))?;
for (index, kind) in build_config.requested_kinds.iter().enumerate() {
if index != 0 {
drop_println!(config);
}
let target_info = TargetInfo::new(config, &build_config.requested_kinds, &rustc, *kind)?;
let mut process = rustc.process();
process.args(&target_info.rustflags);
if let Some(args) = target_rustc_args {
process.args(args);
}
if let CompileKind::Target(t) = kind {
process.arg("--target").arg(t.short_name());
}
process.arg("--print").arg(print_opt_value);
process.exec()?;
}
Ok(())
}
pub fn create_bcx<'a, 'cfg>(
ws: &'a Workspace<'cfg>,
options: &'a CompileOptions,
interner: &'a UnitInterner,
) -> CargoResult<BuildContext<'a, 'cfg>> {
let CompileOptions {
ref build_config,
ref spec,
ref cli_features,
ref filter,
ref target_rustdoc_args,
ref target_rustc_args,
ref target_rustc_crate_types,
ref local_rustdoc_args,
rustdoc_document_private_items,
honor_rust_version,
} = *options;
let config = ws.config();
// Perform some pre-flight validation.
match build_config.mode {
CompileMode::Test
| CompileMode::Build
| CompileMode::Check { .. }
| CompileMode::Bench
| CompileMode::RunCustomBuild => {
if std::env::var("RUST_FLAGS").is_ok() {
config.shell().warn(
"Cargo does not read `RUST_FLAGS` environment variable. Did you mean `RUSTFLAGS`?",
)?;
}
}
CompileMode::Doc { .. } | CompileMode::Doctest | CompileMode::Docscrape => {
if std::env::var("RUSTDOC_FLAGS").is_ok() {
config.shell().warn(
"Cargo does not read `RUSTDOC_FLAGS` environment variable. Did you mean `RUSTDOCFLAGS`?"
)?;
}
}
}
config.validate_term_config()?;
let target_data = RustcTargetData::new(ws, &build_config.requested_kinds)?;
let all_packages = &Packages::All;
let rustdoc_scrape_examples = &config.cli_unstable().rustdoc_scrape_examples;
let need_reverse_dependencies = rustdoc_scrape_examples.is_some();
let full_specs = if need_reverse_dependencies {
all_packages
} else {
spec
};
let resolve_specs = full_specs.to_package_id_specs(ws)?;
let has_dev_units = if filter.need_dev_deps(build_config.mode) || need_reverse_dependencies {
HasDevUnits::Yes
} else {
HasDevUnits::No
};
let resolve = ops::resolve_ws_with_opts(
ws,
&target_data,
&build_config.requested_kinds,
cli_features,
&resolve_specs,
has_dev_units,
crate::core::resolver::features::ForceAllTargets::No,
)?;
let WorkspaceResolve {
mut pkg_set,
workspace_resolve,
targeted_resolve: resolve,
resolved_features,
} = resolve;
let std_resolve_features = if let Some(crates) = &config.cli_unstable().build_std {
let (std_package_set, std_resolve, std_features) =
standard_lib::resolve_std(ws, &target_data, &build_config, crates)?;
pkg_set.add_set(std_package_set);
Some((std_resolve, std_features))
} else {
None
};
// Find the packages in the resolver that the user wants to build (those
// passed in with `-p` or the defaults from the workspace), and convert
// Vec<PackageIdSpec> to a Vec<PackageId>.
let specs = if need_reverse_dependencies {
spec.to_package_id_specs(ws)?
} else {
resolve_specs.clone()
};
let to_build_ids = resolve.specs_to_ids(&specs)?;
// Now get the `Package` for each `PackageId`. This may trigger a download
// if the user specified `-p` for a dependency that is not downloaded.
// Dependencies will be downloaded during build_unit_dependencies.
let mut to_builds = pkg_set.get_many(to_build_ids)?;
// The ordering here affects some error messages coming out of cargo, so
// let's be test and CLI friendly by always printing in the same order if
// there's an error.
to_builds.sort_by_key(|p| p.package_id());
for pkg in to_builds.iter() {
pkg.manifest().print_teapot(config);
if build_config.mode.is_any_test()
&& !ws.is_member(pkg)
&& pkg.dependencies().iter().any(|dep| !dep.is_transitive())
{
anyhow::bail!(
"package `{}` cannot be tested because it requires dev-dependencies \
and is not a member of the workspace",
pkg.name()
);
}
}
let (extra_args, extra_args_name) = match (target_rustc_args, target_rustdoc_args) {
(&Some(ref args), _) => (Some(args.clone()), "rustc"),
(_, &Some(ref args)) => (Some(args.clone()), "rustdoc"),
_ => (None, ""),
};
if extra_args.is_some() && to_builds.len() != 1 {
panic!(
"`{}` should not accept multiple `-p` flags",
extra_args_name
);
}
let profiles = Profiles::new(ws, build_config.requested_profile)?;
profiles.validate_packages(
ws.profiles(),
&mut config.shell(),
workspace_resolve.as_ref().unwrap_or(&resolve),
)?;
// If `--target` has not been specified, then the unit graph is built
// assuming `--target $HOST` was specified. See
// `rebuild_unit_graph_shared` for more on why this is done.
let explicit_host_kind = CompileKind::Target(CompileTarget::new(&target_data.rustc.host)?);
let explicit_host_kinds: Vec<_> = build_config
.requested_kinds
.iter()
.map(|kind| match kind {
CompileKind::Host => explicit_host_kind,
CompileKind::Target(t) => CompileKind::Target(*t),
})
.collect();
// Passing `build_config.requested_kinds` instead of
// `explicit_host_kinds` here so that `generate_targets` can do
// its own special handling of `CompileKind::Host`. It will
// internally replace the host kind by the `explicit_host_kind`
// before setting as a unit.
let mut units = generate_targets(
ws,
&to_builds,
filter,
&build_config.requested_kinds,
explicit_host_kind,
build_config.mode,
&resolve,
&workspace_resolve,
&resolved_features,
&pkg_set,
&profiles,
interner,
)?;
if let Some(args) = target_rustc_crate_types {
override_rustc_crate_types(&mut units, args, interner)?;
}
let mut scrape_units = match rustdoc_scrape_examples {
Some(arg) => {
let filter = match arg.as_str() {
"all" => CompileFilter::new_all_targets(),
"examples" => CompileFilter::new(
LibRule::False,
FilterRule::none(),
FilterRule::none(),
FilterRule::All,
FilterRule::none(),
),
_ => {
bail!(
r#"-Z rustdoc-scrape-examples must take "all" or "examples" as an argument"#
)
}
};
let to_build_ids = resolve.specs_to_ids(&resolve_specs)?;
let to_builds = pkg_set.get_many(to_build_ids)?;
let mode = CompileMode::Docscrape;
generate_targets(
ws,
&to_builds,
&filter,
&build_config.requested_kinds,
explicit_host_kind,
mode,
&resolve,
&workspace_resolve,
&resolved_features,
&pkg_set,
&profiles,
interner,
)?
.into_iter()
// Proc macros should not be scraped for functions, since they only export macros
.filter(|unit| !unit.target.proc_macro())
.collect::<Vec<_>>()
}
None => Vec::new(),
};
let std_roots = if let Some(crates) = standard_lib::std_crates(config, Some(&units)) {
let (std_resolve, std_features) = std_resolve_features.as_ref().unwrap();
standard_lib::generate_std_roots(
&crates,
std_resolve,
std_features,
&explicit_host_kinds,
&pkg_set,
interner,
&profiles,
)?
} else {
Default::default()
};
let mut unit_graph = build_unit_dependencies(
ws,
&pkg_set,
&resolve,
&resolved_features,
std_resolve_features.as_ref(),
&units,
&scrape_units,
&std_roots,
build_config.mode,
&target_data,
&profiles,
interner,
)?;
// TODO: In theory, Cargo should also dedupe the roots, but I'm uncertain
// what heuristics to use in that case.
if build_config.mode == (CompileMode::Doc { deps: true }) {
remove_duplicate_doc(build_config, &units, &mut unit_graph);
}
if build_config
.requested_kinds
.iter()
.any(CompileKind::is_host)
{
// Rebuild the unit graph, replacing the explicit host targets with
// CompileKind::Host, merging any dependencies shared with build
// dependencies.
let new_graph = rebuild_unit_graph_shared(
interner,
unit_graph,
&units,
&scrape_units,
explicit_host_kind,
);
// This would be nicer with destructuring assignment.
units = new_graph.0;
scrape_units = new_graph.1;
unit_graph = new_graph.2;
}
let mut extra_compiler_args = HashMap::new();
if let Some(args) = extra_args {
if units.len() != 1 {
anyhow::bail!(
"extra arguments to `{}` can only be passed to one \
target, consider filtering\nthe package by passing, \
e.g., `--lib` or `--bin NAME` to specify a single target",
extra_args_name
);
}
extra_compiler_args.insert(units[0].clone(), args);
}
for unit in &units {
if unit.mode.is_doc() || unit.mode.is_doc_test() {
let mut extra_args = local_rustdoc_args.clone();
// Add `--document-private-items` rustdoc flag if requested or if
// the target is a binary. Binary crates get their private items
// documented by default.
if rustdoc_document_private_items || unit.target.is_bin() {
let mut args = extra_args.take().unwrap_or_default();
args.push("--document-private-items".into());
if unit.target.is_bin() {
// This warning only makes sense if it's possible to document private items
// sometimes and ignore them at other times. But cargo consistently passes
// `--document-private-items`, so the warning isn't useful.
args.push("-Arustdoc::private-intra-doc-links".into());
}
extra_args = Some(args);
}
if let Some(args) = extra_args {
extra_compiler_args
.entry(unit.clone())
.or_default()
.extend(args);
}
}
}
if honor_rust_version {
// Remove any pre-release identifiers for easier comparison
let current_version = &target_data.rustc.version;
let untagged_version = semver::Version::new(
current_version.major,
current_version.minor,
current_version.patch,
);
for unit in unit_graph.keys() {
let version = match unit.pkg.rust_version() {
Some(v) => v,
None => continue,
};
let req = semver::VersionReq::parse(version).unwrap();
if req.matches(&untagged_version) {
continue;
}
let guidance = if ws.is_ephemeral() {
if ws.ignore_lock() {
"Try re-running cargo install with `--locked`".to_string()
} else {
String::new()
}
} else if !unit.is_local() {
format!(
"Either upgrade to rustc {} or newer, or use\n\
cargo update -p {}@{} --precise ver\n\
where `ver` is the latest version of `{}` supporting rustc {}",
version,
unit.pkg.name(),
unit.pkg.version(),
unit.pkg.name(),
current_version,
)
} else {
String::new()
};
anyhow::bail!(
"package `{}` cannot be built because it requires rustc {} or newer, \
while the currently active rustc version is {}\n{}",
unit.pkg,
version,
current_version,
guidance,
);
}
}
let bcx = BuildContext::new(
ws,
pkg_set,
build_config,
profiles,
extra_compiler_args,
target_data,
units,
unit_graph,
scrape_units,
)?;
Ok(bcx)
}
impl FilterRule {
pub fn new(targets: Vec<String>, all: bool) -> FilterRule {
if all {
FilterRule::All
} else {
FilterRule::Just(targets)
}
}
pub fn none() -> FilterRule {
FilterRule::Just(Vec::new())
}
fn matches(&self, target: &Target) -> bool {
match *self {
FilterRule::All => true,
FilterRule::Just(ref targets) => targets.iter().any(|x| *x == target.name()),
}
}
fn is_specific(&self) -> bool {
match *self {
FilterRule::All => true,
FilterRule::Just(ref targets) => !targets.is_empty(),
}
}
pub fn try_collect(&self) -> Option<Vec<String>> {
match *self {
FilterRule::All => None,
FilterRule::Just(ref targets) => Some(targets.clone()),
}
}
pub(crate) fn contains_glob_patterns(&self) -> bool {
match self {
FilterRule::All => false,
FilterRule::Just(targets) => targets.iter().any(is_glob_pattern),
}
}
}
impl CompileFilter {
/// Constructs a filter from raw command line arguments.
pub fn from_raw_arguments(
lib_only: bool,
bins: Vec<String>,
all_bins: bool,
tsts: Vec<String>,
all_tsts: bool,
exms: Vec<String>,
all_exms: bool,
bens: Vec<String>,
all_bens: bool,
all_targets: bool,
) -> CompileFilter {
if all_targets {
return CompileFilter::new_all_targets();
}
let rule_lib = if lib_only {
LibRule::True
} else {
LibRule::False
};
let rule_bins = FilterRule::new(bins, all_bins);
let rule_tsts = FilterRule::new(tsts, all_tsts);
let rule_exms = FilterRule::new(exms, all_exms);
let rule_bens = FilterRule::new(bens, all_bens);
CompileFilter::new(rule_lib, rule_bins, rule_tsts, rule_exms, rule_bens)
}
/// Constructs a filter from underlying primitives.
pub fn new(
rule_lib: LibRule,
rule_bins: FilterRule,
rule_tsts: FilterRule,
rule_exms: FilterRule,
rule_bens: FilterRule,
) -> CompileFilter {
if rule_lib == LibRule::True
|| rule_bins.is_specific()
|| rule_tsts.is_specific()
|| rule_exms.is_specific()
|| rule_bens.is_specific()
{
CompileFilter::Only {
all_targets: false,
lib: rule_lib,
bins: rule_bins,
examples: rule_exms,
benches: rule_bens,
tests: rule_tsts,
}
} else {
CompileFilter::Default {
required_features_filterable: true,
}
}
}
/// Constructs a filter that includes all targets.
pub fn new_all_targets() -> CompileFilter {
CompileFilter::Only {
all_targets: true,
lib: LibRule::Default,
bins: FilterRule::All,
examples: FilterRule::All,
benches: FilterRule::All,
tests: FilterRule::All,
}
}
/// Constructs a filter that includes all test targets.
///
/// Being different from the behavior of [`CompileFilter::Default`], this
/// function only recognizes test targets, which means cargo might compile
/// all targets with `tested` flag on, whereas [`CompileFilter::Default`]
/// may include additional example targets to ensure they can be compiled.
///
/// Note that the actual behavior is subject to `filter_default_targets`
/// and `generate_targets` though.
pub fn all_test_targets() -> Self {
Self::Only {
all_targets: false,
lib: LibRule::Default,
bins: FilterRule::none(),
examples: FilterRule::none(),
tests: FilterRule::All,
benches: FilterRule::none(),
}
}
/// Constructs a filter that includes lib target only.
pub fn lib_only() -> Self {
Self::Only {
all_targets: false,
lib: LibRule::True,
bins: FilterRule::none(),
examples: FilterRule::none(),
tests: FilterRule::none(),
benches: FilterRule::none(),
}
}
/// Constructs a filter that includes the given binary. No more. No less.
pub fn single_bin(bin: String) -> Self {
Self::Only {
all_targets: false,
lib: LibRule::False,
bins: FilterRule::new(vec![bin], false),
examples: FilterRule::none(),
tests: FilterRule::none(),
benches: FilterRule::none(),
}
}
/// Indicates if Cargo needs to build any dev dependency.
pub fn need_dev_deps(&self, mode: CompileMode) -> bool {
match mode {
CompileMode::Test | CompileMode::Doctest | CompileMode::Bench => true,
CompileMode::Check { test: true } => true,
CompileMode::Build
| CompileMode::Doc { .. }
| CompileMode::Docscrape
| CompileMode::Check { test: false } => match *self {
CompileFilter::Default { .. } => false,
CompileFilter::Only {
ref examples,
ref tests,
ref benches,
..
} => examples.is_specific() || tests.is_specific() || benches.is_specific(),
},
CompileMode::RunCustomBuild => panic!("Invalid mode"),
}
}
/// Selects targets for "cargo run". for logic to select targets for other
/// subcommands, see `generate_targets` and `filter_default_targets`.
pub fn target_run(&self, target: &Target) -> bool {
match *self {
CompileFilter::Default { .. } => true,
CompileFilter::Only {
ref lib,
ref bins,
ref examples,
ref tests,
ref benches,
..
} => {
let rule = match *target.kind() {
TargetKind::Bin => bins,
TargetKind::Test => tests,
TargetKind::Bench => benches,
TargetKind::ExampleBin | TargetKind::ExampleLib(..) => examples,
TargetKind::Lib(..) => {
return match *lib {
LibRule::True => true,
LibRule::Default => true,
LibRule::False => false,
};
}
TargetKind::CustomBuild => return false,
};
rule.matches(target)
}
}
}
pub fn is_specific(&self) -> bool {
match *self {
CompileFilter::Default { .. } => false,
CompileFilter::Only { .. } => true,
}
}
pub fn is_all_targets(&self) -> bool {
matches!(
*self,
CompileFilter::Only {
all_targets: true,
..
}
)
}
pub(crate) fn contains_glob_patterns(&self) -> bool {
match self {
CompileFilter::Default { .. } => false,
CompileFilter::Only {
bins,
examples,
tests,
benches,
..
} => {
bins.contains_glob_patterns()
|| examples.contains_glob_patterns()
|| tests.contains_glob_patterns()
|| benches.contains_glob_patterns()
}
}
}
}
/// A proposed target.
///
/// Proposed targets are later filtered into actual `Unit`s based on whether or
/// not the target requires its features to be present.
#[derive(Debug)]
struct Proposal<'a> {
pkg: &'a Package,
target: &'a Target,
/// Indicates whether or not all required features *must* be present. If
/// false, and the features are not available, then it will be silently
/// skipped. Generally, targets specified by name (`--bin foo`) are
/// required, all others can be silently skipped if features are missing.
requires_features: bool,
mode: CompileMode,
}
/// Generates all the base targets for the packages the user has requested to
/// compile. Dependencies for these targets are computed later in `unit_dependencies`.
fn generate_targets(
ws: &Workspace<'_>,
packages: &[&Package],
filter: &CompileFilter,
requested_kinds: &[CompileKind],
explicit_host_kind: CompileKind,
mode: CompileMode,
resolve: &Resolve,
workspace_resolve: &Option<Resolve>,
resolved_features: &features::ResolvedFeatures,
package_set: &PackageSet<'_>,
profiles: &Profiles,
interner: &UnitInterner,
) -> CargoResult<Vec<Unit>> {
let config = ws.config();
// Helper for creating a list of `Unit` structures
let new_unit = |units: &mut HashSet<Unit>,
pkg: &Package,
target: &Target,
initial_target_mode: CompileMode| {
// Custom build units are added in `build_unit_dependencies`.
assert!(!target.is_custom_build());
let target_mode = match initial_target_mode {
CompileMode::Test => {
if target.is_example() && !filter.is_specific() && !target.tested() {
// Examples are included as regular binaries to verify
// that they compile.
CompileMode::Build
} else {
CompileMode::Test
}
}
CompileMode::Build => match *target.kind() {
TargetKind::Test => CompileMode::Test,
TargetKind::Bench => CompileMode::Bench,
_ => CompileMode::Build,
},
// `CompileMode::Bench` is only used to inform `filter_default_targets`
// which command is being used (`cargo bench`). Afterwards, tests
// and benches are treated identically. Switching the mode allows
// de-duplication of units that are essentially identical. For
// example, `cargo build --all-targets --release` creates the units
// (lib profile:bench, mode:test) and (lib profile:bench, mode:bench)
// and since these are the same, we want them to be de-duplicated in
// `unit_dependencies`.
CompileMode::Bench => CompileMode::Test,
_ => initial_target_mode,
};
let is_local = pkg.package_id().source_id().is_path();
// No need to worry about build-dependencies, roots are never build dependencies.
let features_for = FeaturesFor::from_for_host(target.proc_macro());
let features = resolved_features.activated_features(pkg.package_id(), features_for);
// If `--target` has not been specified, then the unit
// graph is built almost like if `--target $HOST` was
// specified. See `rebuild_unit_graph_shared` for more on
// why this is done. However, if the package has its own
// `package.target` key, then this gets used instead of
// `$HOST`
let explicit_kinds = if let Some(k) = pkg.manifest().forced_kind() {
vec![k]
} else {
requested_kinds
.iter()
.map(|kind| match kind {
CompileKind::Host => {
pkg.manifest().default_kind().unwrap_or(explicit_host_kind)
}
CompileKind::Target(t) => CompileKind::Target(*t),
})
.collect()
};
for kind in explicit_kinds.iter() {
let unit_for = if initial_target_mode.is_any_test() {
// NOTE: the `UnitFor` here is subtle. If you have a profile
// with `panic` set, the `panic` flag is cleared for
// tests/benchmarks and their dependencies. If this
// was `normal`, then the lib would get compiled three
// times (once with panic, once without, and once with
// `--test`).
//
// This would cause a problem for doc tests, which would fail
// because `rustdoc` would attempt to link with both libraries
// at the same time. Also, it's probably not important (or
// even desirable?) for rustdoc to link with a lib with
// `panic` set.
//
// As a consequence, Examples and Binaries get compiled
// without `panic` set. This probably isn't a bad deal.
//
// Forcing the lib to be compiled three times during `cargo
// test` is probably also not desirable.
UnitFor::new_test(config, *kind)
} else if target.for_host() {
// Proc macro / plugin should not have `panic` set.
UnitFor::new_compiler(*kind)
} else {
UnitFor::new_normal(*kind)
};
let profile = profiles.get_profile(
pkg.package_id(),
ws.is_member(pkg),
is_local,
unit_for,
*kind,
);
let unit = interner.intern(
pkg,
target,
profile,
kind.for_target(target),
target_mode,
features.clone(),
/*is_std*/ false,
/*dep_hash*/ 0,
IsArtifact::No,
);
units.insert(unit);
}
};
// Create a list of proposed targets.
let mut proposals: Vec<Proposal<'_>> = Vec::new();
match *filter {
CompileFilter::Default {
required_features_filterable,
} => {
for pkg in packages {
let default = filter_default_targets(pkg.targets(), mode);
proposals.extend(default.into_iter().map(|target| Proposal {
pkg,
target,
requires_features: !required_features_filterable,
mode,
}));
if mode == CompileMode::Test {
if let Some(t) = pkg
.targets()
.iter()
.find(|t| t.is_lib() && t.doctested() && t.doctestable())
{
proposals.push(Proposal {
pkg,
target: t,
requires_features: false,
mode: CompileMode::Doctest,
});
}
}
}
}
CompileFilter::Only {
all_targets,
ref lib,
ref bins,
ref examples,
ref tests,
ref benches,
} => {
if *lib != LibRule::False {
let mut libs = Vec::new();
for proposal in filter_targets(packages, Target::is_lib, false, mode) {
let Proposal { target, pkg, .. } = proposal;
if mode.is_doc_test() && !target.doctestable() {
let types = target.rustc_crate_types();
let types_str: Vec<&str> = types.iter().map(|t| t.as_str()).collect();
ws.config().shell().warn(format!(
"doc tests are not supported for crate type(s) `{}` in package `{}`",
types_str.join(", "),
pkg.name()
))?;
} else {
libs.push(proposal)
}
}
if !all_targets && libs.is_empty() && *lib == LibRule::True {
let names = packages.iter().map(|pkg| pkg.name()).collect::<Vec<_>>();
if names.len() == 1 {
anyhow::bail!("no library targets found in package `{}`", names[0]);
} else {
anyhow::bail!("no library targets found in packages: {}", names.join(", "));
}
}
proposals.extend(libs);
}
// If `--tests` was specified, add all targets that would be
// generated by `cargo test`.
let test_filter = match tests {
FilterRule::All => Target::tested,
FilterRule::Just(_) => Target::is_test,
};
let test_mode = match mode {
CompileMode::Build => CompileMode::Test,
CompileMode::Check { .. } => CompileMode::Check { test: true },
_ => mode,
};
// If `--benches` was specified, add all targets that would be
// generated by `cargo bench`.
let bench_filter = match benches {
FilterRule::All => Target::benched,
FilterRule::Just(_) => Target::is_bench,
};
let bench_mode = match mode {
CompileMode::Build => CompileMode::Bench,
CompileMode::Check { .. } => CompileMode::Check { test: true },
_ => mode,
};
proposals.extend(list_rule_targets(
packages,
bins,
"bin",
Target::is_bin,
mode,
)?);
proposals.extend(list_rule_targets(
packages,
examples,
"example",
Target::is_example,
mode,
)?);
proposals.extend(list_rule_targets(
packages,
tests,
"test",
test_filter,
test_mode,
)?);
proposals.extend(list_rule_targets(
packages,
benches,
"bench",
bench_filter,
bench_mode,
)?);
}
}
// Only include targets that are libraries or have all required
// features available.
//
// `features_map` is a map of &Package -> enabled_features
// It is computed by the set of enabled features for the package plus
// every enabled feature of every enabled dependency.
let mut features_map = HashMap::new();
// This needs to be a set to de-duplicate units. Due to the way the
// targets are filtered, it is possible to have duplicate proposals for
// the same thing.
let mut units = HashSet::new();
for Proposal {
pkg,
target,
requires_features,
mode,
} in proposals
{
let unavailable_features = match target.required_features() {
Some(rf) => {
validate_required_features(
workspace_resolve,
target.name(),
rf,
pkg.summary(),
&mut config.shell(),
)?;
let features = features_map.entry(pkg).or_insert_with(|| {
resolve_all_features(resolve, resolved_features, package_set, pkg.package_id())
});
rf.iter().filter(|f| !features.contains(*f)).collect()
}
None => Vec::new(),
};
if target.is_lib() || unavailable_features.is_empty() {
new_unit(&mut units, pkg, target, mode);
} else if requires_features {
let required_features = target.required_features().unwrap();
let quoted_required_features: Vec<String> = required_features
.iter()
.map(|s| format!("`{}`", s))
.collect();
anyhow::bail!(
"target `{}` in package `{}` requires the features: {}\n\
Consider enabling them by passing, e.g., `--features=\"{}\"`",
target.name(),
pkg.name(),
quoted_required_features.join(", "),
required_features.join(" ")
);
}
// else, silently skip target.
}
let mut units: Vec<_> = units.into_iter().collect();
unmatched_target_filters(&units, filter, &mut ws.config().shell())?;
// Keep the roots in a consistent order, which helps with checking test output.
units.sort_unstable();
Ok(units)
}
/// Checks if the unit list is empty and the user has passed any combination of
/// --tests, --examples, --benches or --bins, and we didn't match on any targets.
/// We want to emit a warning to make sure the user knows that this run is a no-op,
/// and their code remains unchecked despite cargo not returning any errors
fn unmatched_target_filters(
units: &[Unit],
filter: &CompileFilter,
shell: &mut Shell,
) -> CargoResult<()> {
if let CompileFilter::Only {
all_targets,
lib: _,
ref bins,
ref examples,
ref tests,
ref benches,
} = *filter
{
if units.is_empty() {
let mut filters = String::new();
let mut miss_count = 0;
let mut append = |t: &FilterRule, s| {
if let FilterRule::All = *t {
miss_count += 1;
filters.push_str(s);
}
};
if all_targets {
filters.push_str(" `all-targets`");
} else {
append(bins, " `bins`,");
append(tests, " `tests`,");
append(examples, " `examples`,");
append(benches, " `benches`,");
filters.pop();
}
return shell.warn(format!(
"Target {}{} specified, but no targets matched. This is a no-op",
if miss_count > 1 { "filters" } else { "filter" },
filters,
));
}
}
Ok(())
}
/// Warns if a target's required-features references a feature that doesn't exist.
///
/// This is a warning because historically this was not validated, and it
/// would cause too much breakage to make it an error.
fn validate_required_features(
resolve: &Option<Resolve>,
target_name: &str,
required_features: &[String],
summary: &Summary,
shell: &mut Shell,
) -> CargoResult<()> {
let resolve = match resolve {
None => return Ok(()),
Some(resolve) => resolve,
};
for feature in required_features {
let fv = FeatureValue::new(feature.into());
match &fv {
FeatureValue::Feature(f) => {
if !summary.features().contains_key(f) {
shell.warn(format!(
"invalid feature `{}` in required-features of target `{}`: \
`{}` is not present in [features] section",
fv, target_name, fv
))?;
}
}
FeatureValue::Dep { .. } => {
anyhow::bail!(
"invalid feature `{}` in required-features of target `{}`: \
`dep:` prefixed feature values are not allowed in required-features",
fv,
target_name
);
}
FeatureValue::DepFeature { weak: true, .. } => {
anyhow::bail!(
"invalid feature `{}` in required-features of target `{}`: \
optional dependency with `?` is not allowed in required-features",
fv,
target_name
);
}
// Handling of dependent_crate/dependent_crate_feature syntax
FeatureValue::DepFeature {
dep_name,
dep_feature,
weak: false,
} => {
match resolve
.deps(summary.package_id())
.find(|(_dep_id, deps)| deps.iter().any(|dep| dep.name_in_toml() == *dep_name))
{
Some((dep_id, _deps)) => {
let dep_summary = resolve.summary(dep_id);
if !dep_summary.features().contains_key(dep_feature)
&& !dep_summary
.dependencies()
.iter()
.any(|dep| dep.name_in_toml() == *dep_feature && dep.is_optional())
{
shell.warn(format!(
"invalid feature `{}` in required-features of target `{}`: \
feature `{}` does not exist in package `{}`",
fv, target_name, dep_feature, dep_id
))?;
}
}
None => {
shell.warn(format!(
"invalid feature `{}` in required-features of target `{}`: \
dependency `{}` does not exist",
fv, target_name, dep_name
))?;
}
}
}
}
}
Ok(())
}
/// Gets all of the features enabled for a package, plus its dependencies'
/// features.
///
/// Dependencies are added as `dep_name/feat_name` because `required-features`
/// wants to support that syntax.
pub fn resolve_all_features(
resolve_with_overrides: &Resolve,
resolved_features: &features::ResolvedFeatures,
package_set: &PackageSet<'_>,
package_id: PackageId,
) -> HashSet<String> {
let mut features: HashSet<String> = resolved_features
.activated_features(package_id, FeaturesFor::NormalOrDevOrArtifactTarget(None))
.iter()
.map(|s| s.to_string())
.collect();
// Include features enabled for use by dependencies so targets can also use them with the
// required-features field when deciding whether to be built or skipped.
for (dep_id, deps) in resolve_with_overrides.deps(package_id) {
let is_proc_macro = package_set
.get_one(dep_id)
.expect("packages downloaded")
.proc_macro();
for dep in deps {
let features_for = FeaturesFor::from_for_host(is_proc_macro || dep.is_build());
for feature in resolved_features
.activated_features_unverified(dep_id, features_for)
.unwrap_or_default()
{
features.insert(format!("{}/{}", dep.name_in_toml(), feature));
}
}
}
features
}
/// Given a list of all targets for a package, filters out only the targets
/// that are automatically included when the user doesn't specify any targets.
fn filter_default_targets(targets: &[Target], mode: CompileMode) -> Vec<&Target> {
match mode {
CompileMode::Bench => targets.iter().filter(|t| t.benched()).collect(),
CompileMode::Test => targets
.iter()
.filter(|t| t.tested() || t.is_example())
.collect(),
CompileMode::Build | CompileMode::Check { .. } => targets
.iter()
.filter(|t| t.is_bin() || t.is_lib())
.collect(),
CompileMode::Doc { .. } => {
// `doc` does lib and bins (bin with same name as lib is skipped).
targets
.iter()
.filter(|t| {
t.documented()
&& (!t.is_bin()
|| !targets.iter().any(|l| l.is_lib() && l.name() == t.name()))
})
.collect()
}
CompileMode::Doctest | CompileMode::Docscrape | CompileMode::RunCustomBuild => {
panic!("Invalid mode {:?}", mode)
}
}
}
/// Returns a list of proposed targets based on command-line target selection flags.
fn list_rule_targets<'a>(
packages: &[&'a Package],
rule: &FilterRule,
target_desc: &'static str,
is_expected_kind: fn(&Target) -> bool,
mode: CompileMode,
) -> CargoResult<Vec<Proposal<'a>>> {
let mut proposals = Vec::new();
match rule {
FilterRule::All => {
proposals.extend(filter_targets(packages, is_expected_kind, false, mode))
}
FilterRule::Just(names) => {
for name in names {
proposals.extend(find_named_targets(
packages,
name,
target_desc,
is_expected_kind,
mode,
)?);
}
}
}
Ok(proposals)
}
/// Finds the targets for a specifically named target.
fn find_named_targets<'a>(
packages: &[&'a Package],
target_name: &str,
target_desc: &'static str,
is_expected_kind: fn(&Target) -> bool,
mode: CompileMode,
) -> CargoResult<Vec<Proposal<'a>>> {
let is_glob = is_glob_pattern(target_name);
let proposals = if is_glob {
let pattern = build_glob(target_name)?;
let filter = |t: &Target| is_expected_kind(t) && pattern.matches(t.name());
filter_targets(packages, filter, true, mode)
} else {
let filter = |t: &Target| t.name() == target_name && is_expected_kind(t);
filter_targets(packages, filter, true, mode)
};
if proposals.is_empty() {
let targets = packages
.iter()
.flat_map(|pkg| {
pkg.targets()
.iter()
.filter(|target| is_expected_kind(target))
})
.collect::<Vec<_>>();
let suggestion = closest_msg(target_name, targets.iter(), |t| t.name());
if !suggestion.is_empty() {
anyhow::bail!(
"no {} target {} `{}`{}",
target_desc,
if is_glob { "matches pattern" } else { "named" },
target_name,
suggestion
);
} else {
let mut msg = String::new();
writeln!(
msg,
"no {} target {} `{}`.",
target_desc,
if is_glob { "matches pattern" } else { "named" },
target_name,
)?;
if !targets.is_empty() {
writeln!(msg, "Available {} targets:", target_desc)?;
for target in targets {
writeln!(msg, " {}", target.name())?;
}
}
anyhow::bail!(msg);
}
}
Ok(proposals)
}
fn filter_targets<'a>(
packages: &[&'a Package],
predicate: impl Fn(&Target) -> bool,
requires_features: bool,
mode: CompileMode,
) -> Vec<Proposal<'a>> {
let mut proposals = Vec::new();
for pkg in packages {
for target in pkg.targets().iter().filter(|t| predicate(t)) {
proposals.push(Proposal {
pkg,
target,
requires_features,
mode,
});
}
}
proposals
}
/// This is used to rebuild the unit graph, sharing host dependencies if possible.
///
/// This will translate any unit's `CompileKind::Target(host)` to
/// `CompileKind::Host` if the kind is equal to `to_host`. This also handles
/// generating the unit `dep_hash`, and merging shared units if possible.
///
/// This is necessary because if normal dependencies used `CompileKind::Host`,
/// there would be no way to distinguish those units from build-dependency
/// units. This can cause a problem if a shared normal/build dependency needs
/// to link to another dependency whose features differ based on whether or
/// not it is a normal or build dependency. If both units used
/// `CompileKind::Host`, then they would end up being identical, causing a
/// collision in the `UnitGraph`, and Cargo would end up randomly choosing one
/// value or the other.
///
/// The solution is to keep normal and build dependencies separate when
/// building the unit graph, and then run this second pass which will try to
/// combine shared dependencies safely. By adding a hash of the dependencies
/// to the `Unit`, this allows the `CompileKind` to be changed back to `Host`
/// without fear of an unwanted collision.
fn rebuild_unit_graph_shared(
interner: &UnitInterner,
unit_graph: UnitGraph,
roots: &[Unit],
scrape_units: &[Unit],
to_host: CompileKind,
) -> (Vec<Unit>, Vec<Unit>, UnitGraph) {
let mut result = UnitGraph::new();
// Map of the old unit to the new unit, used to avoid recursing into units
// that have already been computed to improve performance.
let mut memo = HashMap::new();
let new_roots = roots
.iter()
.map(|root| {
traverse_and_share(interner, &mut memo, &mut result, &unit_graph, root, to_host)
})
.collect();
let new_scrape_units = scrape_units
.iter()
.map(|unit| memo.get(unit).unwrap().clone())
.collect();
(new_roots, new_scrape_units, result)
}
/// Recursive function for rebuilding the graph.
///
/// This walks `unit_graph`, starting at the given `unit`. It inserts the new
/// units into `new_graph`, and returns a new updated version of the given
/// unit (`dep_hash` is filled in, and `kind` switched if necessary).
fn traverse_and_share(
interner: &UnitInterner,
memo: &mut HashMap<Unit, Unit>,
new_graph: &mut UnitGraph,
unit_graph: &UnitGraph,
unit: &Unit,
to_host: CompileKind,
) -> Unit {
if let Some(new_unit) = memo.get(unit) {
// Already computed, no need to recompute.
return new_unit.clone();
}
let mut dep_hash = StableHasher::new();
let new_deps: Vec<_> = unit_graph[unit]
.iter()
.map(|dep| {
let new_dep_unit =
traverse_and_share(interner, memo, new_graph, unit_graph, &dep.unit, to_host);
new_dep_unit.hash(&mut dep_hash);
UnitDep {
unit: new_dep_unit,
..dep.clone()
}
})
.collect();
let new_dep_hash = dep_hash.finish();
let new_kind = if unit.kind == to_host {
CompileKind::Host
} else {
unit.kind
};
let new_unit = interner.intern(
&unit.pkg,
&unit.target,
unit.profile.clone(),
new_kind,
unit.mode,
unit.features.clone(),
unit.is_std,
new_dep_hash,
unit.artifact,
);
assert!(memo.insert(unit.clone(), new_unit.clone()).is_none());
new_graph.entry(new_unit.clone()).or_insert(new_deps);
new_unit
}
/// Build `glob::Pattern` with informative context.
fn build_glob(pat: &str) -> CargoResult<glob::Pattern> {
glob::Pattern::new(pat).with_context(|| format!("cannot build glob pattern from `{}`", pat))
}
/// Emits "package not found" error.
///
/// > This function should be used only in package selection processes such like
/// `Packages::to_package_id_specs` and `Packages::get_packages`.
fn emit_package_not_found(
ws: &Workspace<'_>,
opt_names: BTreeSet<&str>,
opt_out: bool,
) -> CargoResult<()> {
if !opt_names.is_empty() {
anyhow::bail!(
"{}package(s) `{}` not found in workspace `{}`",
if opt_out { "excluded " } else { "" },
opt_names.into_iter().collect::<Vec<_>>().join(", "),
ws.root().display(),
)
}
Ok(())
}
/// Emits "glob pattern not found" error.
///
/// > This function should be used only in package selection processes such like
/// `Packages::to_package_id_specs` and `Packages::get_packages`.
fn emit_pattern_not_found(
ws: &Workspace<'_>,
opt_patterns: Vec<(glob::Pattern, bool)>,
opt_out: bool,
) -> CargoResult<()> {
let not_matched = opt_patterns
.iter()
.filter(|(_, matched)| !*matched)
.map(|(pat, _)| pat.as_str())
.collect::<Vec<_>>();
if !not_matched.is_empty() {
anyhow::bail!(
"{}package pattern(s) `{}` not found in workspace `{}`",
if opt_out { "excluded " } else { "" },
not_matched.join(", "),
ws.root().display(),
)
}
Ok(())
}
/// Checks whether a package matches any of a list of glob patterns generated
/// from `opt_patterns_and_names`.
///
/// > This function should be used only in package selection processes such like
/// `Packages::to_package_id_specs` and `Packages::get_packages`.
fn match_patterns(pkg: &Package, patterns: &mut Vec<(glob::Pattern, bool)>) -> bool {
patterns.iter_mut().any(|(m, matched)| {
let is_matched = m.matches(pkg.name().as_str());
*matched |= is_matched;
is_matched
})
}
/// Given a list opt-in or opt-out package selection strings, generates two
/// collections that represent glob patterns and package names respectively.
///
/// > This function should be used only in package selection processes such like
/// `Packages::to_package_id_specs` and `Packages::get_packages`.
fn opt_patterns_and_names(
opt: &[String],
) -> CargoResult<(Vec<(glob::Pattern, bool)>, BTreeSet<&str>)> {
let mut opt_patterns = Vec::new();
let mut opt_names = BTreeSet::new();
for x in opt.iter() {
if is_glob_pattern(x) {
opt_patterns.push((build_glob(x)?, false));
} else {
opt_names.insert(String::as_str(x));
}
}
Ok((opt_patterns, opt_names))
}
/// Removes duplicate CompileMode::Doc units that would cause problems with
/// filename collisions.
///
/// Rustdoc only separates units by crate name in the file directory
/// structure. If any two units with the same crate name exist, this would
/// cause a filename collision, causing different rustdoc invocations to stomp
/// on one another's files.
///
/// Unfortunately this does not remove all duplicates, as some of them are
/// either user error, or difficult to remove. Cases that I can think of:
///
/// - Same target name in different packages. See the `collision_doc` test.
/// - Different sources. See `collision_doc_sources` test.
///
/// Ideally this would not be necessary.
fn remove_duplicate_doc(
build_config: &BuildConfig,
root_units: &[Unit],
unit_graph: &mut UnitGraph,
) {
// First, create a mapping of crate_name -> Unit so we can see where the
// duplicates are.
let mut all_docs: HashMap<String, Vec<Unit>> = HashMap::new();
for unit in unit_graph.keys() {
if unit.mode.is_doc() {
all_docs
.entry(unit.target.crate_name())
.or_default()
.push(unit.clone());
}
}
// Keep track of units to remove so that they can be efficiently removed
// from the unit_deps.
let mut removed_units: HashSet<Unit> = HashSet::new();
let mut remove = |units: Vec<Unit>, reason: &str, cb: &dyn Fn(&Unit) -> bool| -> Vec<Unit> {
let (to_remove, remaining_units): (Vec<Unit>, Vec<Unit>) = units
.into_iter()
.partition(|unit| cb(unit) && !root_units.contains(unit));
for unit in to_remove {
log::debug!(
"removing duplicate doc due to {} for package {} target `{}`",
reason,
unit.pkg,
unit.target.name()
);
unit_graph.remove(&unit);
removed_units.insert(unit);
}
remaining_units
};
// Iterate over the duplicates and try to remove them from unit_graph.
for (_crate_name, mut units) in all_docs {
if units.len() == 1 {
continue;
}
// Prefer target over host if --target was not specified.
if build_config
.requested_kinds
.iter()
.all(CompileKind::is_host)
{
// Note these duplicates may not be real duplicates, since they
// might get merged in rebuild_unit_graph_shared. Either way, it
// shouldn't hurt to remove them early (although the report in the
// log might be confusing).
units = remove(units, "host/target merger", &|unit| unit.kind.is_host());
if units.len() == 1 {
continue;
}
}
// Prefer newer versions over older.
let mut source_map: HashMap<(InternedString, SourceId, CompileKind), Vec<Unit>> =
HashMap::new();
for unit in units {
let pkg_id = unit.pkg.package_id();
// Note, this does not detect duplicates from different sources.
source_map
.entry((pkg_id.name(), pkg_id.source_id(), unit.kind))
.or_default()
.push(unit);
}
let mut remaining_units = Vec::new();
for (_key, mut units) in source_map {
if units.len() > 1 {
units.sort_by(|a, b| a.pkg.version().partial_cmp(b.pkg.version()).unwrap());
// Remove any entries with version < newest.
let newest_version = units.last().unwrap().pkg.version().clone();
let keep_units = remove(units, "older version", &|unit| {
unit.pkg.version() < &newest_version
});
remaining_units.extend(keep_units);
} else {
remaining_units.extend(units);
}
}
if remaining_units.len() == 1 {
continue;
}
// Are there other heuristics to remove duplicates that would make
// sense? Maybe prefer path sources over all others?
}
// Also remove units from the unit_deps so there aren't any dangling edges.
for unit_deps in unit_graph.values_mut() {
unit_deps.retain(|unit_dep| !removed_units.contains(&unit_dep.unit));
}
// Remove any orphan units that were detached from the graph.
let mut visited = HashSet::new();
fn visit(unit: &Unit, graph: &UnitGraph, visited: &mut HashSet<Unit>) {
if !visited.insert(unit.clone()) {
return;
}
for dep in &graph[unit] {
visit(&dep.unit, graph, visited);
}
}
for unit in root_units {
visit(unit, unit_graph, &mut visited);
}
unit_graph.retain(|unit, _| visited.contains(unit));
}
/// Override crate types for given units.
///
/// This is primarily used by `cargo rustc --crate-type`.
fn override_rustc_crate_types(
units: &mut [Unit],
args: &[String],
interner: &UnitInterner,
) -> CargoResult<()> {
if units.len() != 1 {
anyhow::bail!(
"crate types to rustc can only be passed to one \
target, consider filtering\nthe package by passing, \
e.g., `--lib` or `--example` to specify a single target"
);
}
let unit = &units[0];
let override_unit = |f: fn(Vec<CrateType>) -> TargetKind| {
let crate_types = args.iter().map(|s| s.into()).collect();
let mut target = unit.target.clone();
target.set_kind(f(crate_types));
interner.intern(
&unit.pkg,
&target,
unit.profile.clone(),
unit.kind,
unit.mode,
unit.features.clone(),
unit.is_std,
unit.dep_hash,
unit.artifact,
)
};
units[0] = match unit.target.kind() {
TargetKind::Lib(_) => override_unit(TargetKind::Lib),
TargetKind::ExampleLib(_) => override_unit(TargetKind::ExampleLib),
_ => {
anyhow::bail!(
"crate types can only be specified for libraries and example libraries.\n\
Binaries, tests, and benchmarks are always the `bin` crate type"
);
}
};
Ok(())
}