use crate::cfg_eval::CfgEvaluator;
use crate::config::{FlagConfig, ResolvedCommandConfig, ResolvedFlags, WorkspaceConfig};
use crate::implication::PrunedCombination;
use crate::package::{Package, has_lib_target};
use crate::plan::targets::{TargetPlan, TargetPlans};
use crate::print_warning;
use crate::target::{EffectiveTarget, TargetSource, TargetTriple};
use color_eyre::eyre;
use std::collections::BTreeMap;
pub struct PlanBuildContext<'a> {
pub workspace_config: &'a WorkspaceConfig,
pub raw_command: Option<&'a str>,
pub resolved_command: Option<&'a str>,
pub default_diagnostics_allowed: bool,
pub matrix: bool,
}
pub struct ExecutionPlan<'a> {
pub target: TargetTriple,
pub package_plans: Vec<PackageExecutionPlan<'a>>,
}
pub struct PackageExecutionPlan<'a> {
pub package: &'a cargo_metadata::Package,
pub target: EffectiveTarget,
pub combinations: Vec<Vec<String>>,
pub pruned: Vec<PrunedCombination>,
pub matrix: serde_json::Map<String, serde_json::Value>,
pub flags: ResolvedFlags,
pub ignored_diagnostics_config: bool,
}
pub struct ExecutionPlanSet<'a> {
pub plans: Vec<ExecutionPlan<'a>>,
pub show_pruned: bool,
pub show_target: bool,
}
pub fn build_execution_plans<'a>(
target_plans: &TargetPlans<'a>,
cli_flags: FlagConfig,
context: &PlanBuildContext<'_>,
evaluator: &mut impl CfgEvaluator,
) -> eyre::Result<ExecutionPlanSet<'a>> {
let mut plans = Vec::with_capacity(target_plans.plans.len());
let mut config_show_pruned = false;
let mut config_show_target = false;
let mut package_status = BTreeMap::new();
for target_plan in &target_plans.plans {
let mut package_plans = Vec::with_capacity(target_plan.packages.len());
for planned in &target_plan.packages {
let resolved_config = crate::config::resolve::resolve_config_with_flag_layers(
planned.config,
&target_plan.target,
evaluator,
)?;
let config = resolved_config.config;
let command_config = resolve_package_command_config(
&resolved_config.flag_layers,
target_plan,
cli_flags,
context,
)?;
let flags = command_config.flags;
let status = package_status
.entry(planned.package.id.repr.clone())
.or_insert_with(|| PackagePlanningStatus {
name: planned.package.name.to_string(),
..PackagePlanningStatus::default()
});
let target_selection_skipped = is_configured_target_source(planned.target.source)
&& (flags.no_targets || !command_config.targets_enabled);
if target_selection_skipped {
status.target_selection_skipped = true;
continue;
}
if flags.only_packages_with_lib_target && !has_lib_target(planned.package) {
continue;
}
config_show_pruned = config_show_pruned || flags.show_pruned;
config_show_target = config_show_target || planned.show_target;
let packages_only = context.matrix && flags.packages_only;
let (combinations, pruned) = if packages_only {
(Vec::new(), Vec::new())
} else {
let all_combos = planned.package.feature_combinations(&config)?;
let prune_result = crate::implication::maybe_prune_with_resolved_flag(
all_combos,
&planned.package.features,
&config,
flags.no_prune_implied,
);
let combinations: Vec<Vec<String>> = prune_result
.keep
.into_iter()
.map(|combo| combo.into_iter().cloned().collect())
.collect();
(combinations, prune_result.pruned)
};
package_plans.push(PackageExecutionPlan {
package: planned.package,
target: planned.target.clone(),
combinations,
pruned,
matrix: config.matrix,
flags,
ignored_diagnostics_config: command_config.ignored_diagnostics_config,
});
status.kept = true;
}
if !package_plans.is_empty() {
plans.push(ExecutionPlan {
target: target_plan.target.clone(),
package_plans,
});
}
}
warn_packages_skipped_by_target_selection(&package_status);
let show_pruned = config_show_pruned;
let show_target = config_show_target || plans.len() > 1;
Ok(ExecutionPlanSet {
plans,
show_pruned,
show_target,
})
}
#[derive(Default)]
struct PackagePlanningStatus {
name: String,
kept: bool,
target_selection_skipped: bool,
}
fn warn_packages_skipped_by_target_selection(status: &BTreeMap<String, PackagePlanningStatus>) {
for entry in status.values() {
if entry.target_selection_skipped && !entry.kept {
print_warning!(
"not running package `{}` because target-scoped `no_targets` or `subcommands.<name>.targets = false` disabled all configured target assignments",
entry.name
);
}
}
}
fn is_configured_target_source(source: TargetSource) -> bool {
matches!(
source,
TargetSource::WorkspaceConfig | TargetSource::PackageConfig
)
}
fn resolve_package_command_config(
package_layers: &crate::config::resolve::PackageFlagLayers,
target_plan: &TargetPlan<'_>,
cli_flags: FlagConfig,
context: &PlanBuildContext<'_>,
) -> eyre::Result<ResolvedCommandConfig> {
crate::config::resolve_command_config(crate::config::ResolveCommandConfigArgs {
workspace: context.workspace_config,
workspace_target_flags: target_plan.workspace_target_flags,
workspace_target_subcommands: &target_plan.workspace_target_subcommands,
package_flags: package_layers.package_flags,
package_subcommands: &package_layers.package_subcommands,
package_target_flags: package_layers.target_flags,
package_target_subcommands: &package_layers.target_subcommands,
raw_command: context.raw_command,
resolved_command: context.resolved_command,
cli_flags,
default_diagnostics_allowed: context.default_diagnostics_allowed,
default_targets_enabled: true,
})
}
#[cfg(test)]
mod test {
use super::{ExecutionPlanSet, PlanBuildContext, build_execution_plans};
use crate::cfg_eval::CfgEvaluator;
use crate::config::{FlagConfig, TargetOverride};
use crate::package::Package as _;
use crate::package::test::{effective_target, package};
use crate::plan::targets::{PlannedPackage, TargetPlan, TargetPlans};
use crate::target::{EffectiveTarget, TargetSource, TargetTriple};
use color_eyre::eyre;
use similar_asserts::assert_eq as sim_assert_eq;
use std::collections::BTreeMap;
#[derive(Default)]
struct StubEval;
impl CfgEvaluator for StubEval {
fn matches(&mut self, _cfg_expr: &str, _target: &TargetTriple) -> eyre::Result<bool> {
Ok(false)
}
}
struct MatchAllEval;
impl CfgEvaluator for MatchAllEval {
fn matches(&mut self, _cfg_expr: &str, _target: &TargetTriple) -> eyre::Result<bool> {
Ok(true)
}
}
fn string_vec(values: &[&str]) -> Vec<String> {
values.iter().copied().map(String::from).collect()
}
fn test_context(workspace_config: &crate::config::WorkspaceConfig) -> PlanBuildContext<'_> {
PlanBuildContext {
workspace_config,
raw_command: None,
resolved_command: None,
default_diagnostics_allowed: false,
matrix: false,
}
}
#[test]
fn target_scoped_no_targets_skips_configured_assignments() -> eyre::Result<()> {
let package = package("a")?;
let config = crate::config::Config::default();
let target_plans = TargetPlans {
plans: vec![TargetPlan {
target: TargetTriple("configured-target".to_string()),
workspace_target_flags: FlagConfig {
no_targets: Some(true),
..FlagConfig::default()
},
workspace_target_subcommands: BTreeMap::new(),
packages: vec![PlannedPackage {
package: &package,
config: &config,
target: effective_target("configured-target"),
show_target: true,
}],
}],
contains_configured_assignments: true,
};
let mut evaluator = StubEval;
let workspace_config = crate::config::WorkspaceConfig::default();
let context = test_context(&workspace_config);
let plan_set = build_execution_plans(
&target_plans,
FlagConfig::default(),
&context,
&mut evaluator,
)?;
assert!(plan_set.plans.is_empty());
Ok(())
}
#[test]
fn target_scoped_lib_filter_false_can_reinclude_package() -> eyre::Result<()> {
let mut package = package("bin-only")?;
package.targets.clear();
let mut config = crate::config::Config::default();
config.target_overrides.insert(
"cfg(any())".to_string(),
TargetOverride {
flags: FlagConfig {
only_packages_with_lib_target: Some(false),
..FlagConfig::default()
},
..TargetOverride::default()
},
);
let target_plans = TargetPlans {
plans: vec![TargetPlan {
target: TargetTriple("configured-target".to_string()),
workspace_target_flags: FlagConfig {
only_packages_with_lib_target: Some(true),
..FlagConfig::default()
},
workspace_target_subcommands: BTreeMap::new(),
packages: vec![PlannedPackage {
package: &package,
config: &config,
target: effective_target("configured-target"),
show_target: true,
}],
}],
contains_configured_assignments: true,
};
let workspace_config = crate::config::WorkspaceConfig::default();
let context = test_context(&workspace_config);
let mut evaluator = MatchAllEval;
let plan_set = build_execution_plans(
&target_plans,
FlagConfig::default(),
&context,
&mut evaluator,
)?;
let [plan] = plan_set.plans.as_slice() else {
eyre::bail!("expected one execution plan, got {}", plan_set.plans.len());
};
assert_eq!(plan.package_plans.len(), 1);
Ok(())
}
#[test]
fn build_execution_plans_keeps_pruned_entries_for_summary() -> eyre::Result<()> {
let mut package = crate::package::test::package_with_metadata(
&["A", "B", "C"],
"cargo-fc",
&serde_json::json!({ "show_pruned": true }),
)?;
let Some(implied_features) = package.features.get_mut("B") else {
eyre::bail!("test package should contain feature B");
};
implied_features.push("A".to_string());
let config = package.config()?;
let target_plans = TargetPlans {
plans: vec![TargetPlan {
target: TargetTriple("test-target".to_string()),
workspace_target_flags: crate::config::FlagConfig::default(),
workspace_target_subcommands: BTreeMap::new(),
packages: vec![PlannedPackage {
package: &package,
config: &config,
target: EffectiveTarget {
triple: TargetTriple("test-target".to_string()),
source: TargetSource::Cli,
},
show_target: true,
}],
}],
contains_configured_assignments: false,
};
let mut evaluator = StubEval;
let workspace_config = crate::config::WorkspaceConfig::default();
let context = test_context(&workspace_config);
let plan_set = build_execution_plans(
&target_plans,
FlagConfig::default(),
&context,
&mut evaluator,
)?;
assert_pruned_plan(&plan_set)?;
Ok(())
}
fn assert_pruned_plan(plan_set: &ExecutionPlanSet<'_>) -> eyre::Result<()> {
assert!(plan_set.show_pruned);
let [plan] = plan_set.plans.as_slice() else {
eyre::bail!("expected one execution plan, got {}", plan_set.plans.len());
};
let [pp] = plan.package_plans.as_slice() else {
eyre::bail!(
"expected one package execution plan, got {}",
plan.package_plans.len()
);
};
sim_assert_eq!(
&pp.combinations,
&vec![
string_vec(&[]),
string_vec(&["A"]),
string_vec(&["A", "C"]),
string_vec(&["B"]),
string_vec(&["B", "C"]),
string_vec(&["C"]),
],
);
let pruned: Vec<_> = pp
.pruned
.iter()
.map(|p| (p.features.clone(), p.equivalent_to.clone()))
.collect();
sim_assert_eq!(
pruned,
vec![
(string_vec(&["A", "B"]), string_vec(&["B"])),
(string_vec(&["A", "B", "C"]), string_vec(&["B", "C"])),
],
);
Ok(())
}
}