use super::features_helpers::*;
pub use super::features_helpers::{
format_package_id, normalize_unit_id_without_source, parse_package_id_source,
};
use crate::adapter::rust::features::propagation::propagate_package;
use crate::adapter::rust::manifest::CargoToml;
use crate::adapter::rust::types::{RustAdapterError, RustBuildOptions};
use std::collections::{HashMap, HashSet};
mod cli;
mod propagation;
pub struct PackageFeatureContext<'a> {
pub name: &'a str,
pub version: &'a str,
pub source: Option<&'a str>,
pub is_workspace: bool,
pub is_root: bool,
pub toml: &'a CargoToml,
pub dependencies: &'a [String],
}
impl<'a> PackageFeatureContext<'a> {
pub fn id(&self) -> String {
format_package_id(self.name, self.version, self.source)
}
pub fn is_proc_macro(&self) -> bool {
self.toml.lib.as_ref().is_some_and(|l| {
l.proc_macro == Some(true) || l.crate_type.iter().any(|k| k == "proc-macro")
})
}
}
#[derive(Debug, Clone, Default)]
pub struct ResolvedFeatures {
pub target: HashMap<String, Vec<String>>,
pub host: HashMap<String, Vec<String>>,
pub active_target_packages: HashSet<String>,
pub active_host_packages: HashSet<String>,
}
impl std::ops::Deref for ResolvedFeatures {
type Target = HashMap<String, Vec<String>>;
fn deref(&self) -> &Self::Target {
&self.target
}
}
pub fn resolve_package_features(
packages: &[PackageFeatureContext],
opt: &RustBuildOptions,
) -> Result<ResolvedFeatures, RustAdapterError> {
resolve_package_features_with_resolver(packages, opt, None)
}
pub fn resolve_package_features_with_resolver(
packages: &[PackageFeatureContext],
opt: &RustBuildOptions,
ws_resolver: Option<&str>,
) -> Result<ResolvedFeatures, RustAdapterError> {
let mut index = FeatureGraphIndex::build(packages, opt);
let mut target_requested: HashMap<String, HashSet<String>> = HashMap::new();
let mut host_requested: HashMap<String, HashSet<String>> = HashMap::new();
let mut active_target_packages: HashSet<String> = HashSet::new();
let mut active_host_packages: HashSet<String> = HashSet::new();
for (idx, ctx) in packages.iter().enumerate() {
let id = &index.pkg_ids[idx];
let is_pm = ctx.is_proc_macro();
let is_all_ws = opt.all_features && ctx.is_workspace && opt.package.is_none();
if ctx.is_root || is_all_ws {
let (active_set, req_map) = if is_pm {
(&mut active_host_packages, &mut host_requested)
} else {
(&mut active_target_packages, &mut target_requested)
};
active_set.insert(id.clone());
let req_set = req_map.entry(id.clone()).or_default();
if opt.all_features {
req_set.extend(ctx.toml.features.keys().cloned());
} else if ctx.is_root {
req_set.extend(cli::features_for_root(packages, ctx, opt)?);
}
if ctx.is_root && !opt.no_default_features && ctx.toml.features.contains_key("default")
{
req_set.insert("default".to_string());
}
}
}
let is_test = opt.mode == crate::adapter::BuildMode::Test
|| opt.all_tests
|| opt.selects_benches()
|| opt.all_examples
|| !opt.example_targets.is_empty()
|| !opt.test_targets.is_empty();
let mut changed = true;
while changed {
changed = false;
for is_host_ctx in [false, true] {
for (idx, ctx) in packages.iter().enumerate() {
let active_set = if is_host_ctx {
&active_host_packages
} else {
&active_target_packages
};
if !active_set.contains(&index.pkg_ids[idx]) {
continue;
}
propagate_package(
idx,
ctx,
&mut index,
is_test,
is_host_ctx,
&mut active_target_packages,
&mut active_host_packages,
&mut target_requested,
&mut host_requested,
&mut changed,
)?;
}
}
}
let is_resolver_1 = ws_resolver.map_or_else(
|| {
packages
.iter()
.find(|p| p.toml.workspace.is_some() || p.is_root)
.is_some_and(|p| p.toml.effective_resolver() == "1")
},
|r| r == "1",
);
if is_resolver_1 {
let host_snapshot = host_requested.clone();
for (pkg_id, feats) in &target_requested {
host_requested
.entry(pkg_id.clone())
.or_default()
.extend(feats.iter().cloned());
}
for (pkg_id, feats) in &host_snapshot {
target_requested
.entry(pkg_id.clone())
.or_default()
.extend(feats.iter().cloned());
}
let mut changed = true;
while changed {
changed = false;
for is_host_ctx in [false, true] {
for (idx, ctx) in packages.iter().enumerate() {
let active_set = if is_host_ctx {
&active_host_packages
} else {
&active_target_packages
};
if !active_set.contains(&index.pkg_ids[idx]) {
continue;
}
propagate_package(
idx,
ctx,
&mut index,
is_test,
is_host_ctx,
&mut active_target_packages,
&mut active_host_packages,
&mut target_requested,
&mut host_requested,
&mut changed,
)?;
}
}
if changed {
let host_now = host_requested.clone();
for (pkg_id, feats) in &target_requested {
host_requested
.entry(pkg_id.clone())
.or_default()
.extend(feats.iter().cloned());
}
for (pkg_id, feats) in &host_now {
target_requested
.entry(pkg_id.clone())
.or_default()
.extend(feats.iter().cloned());
}
}
}
}
let target = finalize_feature_map(
packages,
&index.pkg_ids,
target_requested,
&active_target_packages,
);
let host = finalize_feature_map(
packages,
&index.pkg_ids,
host_requested,
&active_host_packages,
);
Ok(ResolvedFeatures {
target,
host,
active_target_packages,
active_host_packages,
})
}