use crate::{
CargoTomlError, HakariCargoToml, TomlOutError,
explain::HakariExplain,
registry::Registry,
toml_name_map,
toml_out::{HakariOutputOptions, write_toml},
};
use ahash::AHashMap;
use debug_ignore::DebugIgnore;
use guppy::{
PackageId,
errors::TargetSpecError,
graph::{
DependencyDirection, PackageGraph, PackageMetadata,
cargo::{BuildPlatform, CargoOptions, CargoResolverVersion, CargoSet, InitialsPlatform},
feature::{FeatureId, FeatureLabel, FeatureSet, StandardFeatures, named_feature_filter},
},
platform::{Platform, PlatformSpec, TargetFeatures},
};
use iddqd::BiHashMap;
use rayon::prelude::*;
use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet, HashSet},
fmt,
sync::Arc,
};
#[derive(Clone, Debug)]
pub struct HakariBuilder<'g> {
graph: DebugIgnore<&'g PackageGraph>,
hakari_package: Option<PackageMetadata<'g>>,
pub(crate) platforms: Vec<Arc<Platform>>,
resolver: CargoResolverVersion,
pub(crate) verify_mode: bool,
pub(crate) traversal_excludes: HashSet<&'g PackageId>,
final_excludes: HashSet<&'g PackageId>,
pub(crate) registries: BiHashMap<Registry, ahash::RandomState>,
unify_target_host: UnifyTargetHost,
output_single_feature: bool,
pub(crate) dep_format_version: DepFormatVersion,
pub(crate) workspace_hack_line_style: WorkspaceHackLineStyle,
}
impl<'g> HakariBuilder<'g> {
pub fn new(
graph: &'g PackageGraph,
hakari_id: Option<&PackageId>,
) -> Result<Self, guppy::Error> {
let hakari_package = hakari_id
.map(|package_id| {
let package = graph.metadata(package_id)?;
if !package.in_workspace() {
return Err(guppy::Error::UnknownWorkspaceName(
package.name().to_string(),
));
}
Ok(package)
})
.transpose()?;
Ok(Self {
graph: DebugIgnore(graph),
hakari_package,
platforms: vec![],
resolver: CargoResolverVersion::V2,
verify_mode: false,
traversal_excludes: HashSet::new(),
final_excludes: HashSet::new(),
registries: BiHashMap::default(),
unify_target_host: UnifyTargetHost::default(),
output_single_feature: false,
dep_format_version: DepFormatVersion::default(),
workspace_hack_line_style: WorkspaceHackLineStyle::default(),
})
}
pub fn graph(&self) -> &'g PackageGraph {
#[allow(clippy::explicit_auto_deref)]
*self.graph
}
pub fn hakari_package(&self) -> Option<&PackageMetadata<'g>> {
self.hakari_package.as_ref()
}
pub fn read_toml(&self) -> Option<Result<HakariCargoToml, CargoTomlError>> {
let hakari_package = self.hakari_package()?;
let workspace_path = hakari_package
.source()
.workspace_path()
.expect("hakari_package is in workspace");
Some(HakariCargoToml::new_relative(
self.graph.workspace().root(),
workspace_path,
))
}
pub fn set_platforms(
&mut self,
platforms: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
) -> Result<&mut Self, TargetSpecError> {
self.platforms = platforms
.into_iter()
.map(|s| Ok(Arc::new(Platform::new(s.into(), TargetFeatures::Unknown)?)))
.collect::<Result<Vec<_>, _>>()?;
Ok(self)
}
pub fn platforms(&self) -> impl ExactSizeIterator<Item = &str> + '_ {
self.platforms.iter().map(|platform| platform.triple_str())
}
pub fn set_resolver(&mut self, resolver: CargoResolverVersion) -> &mut Self {
self.resolver = resolver;
self
}
pub fn resolver(&self) -> CargoResolverVersion {
self.resolver
}
pub fn add_traversal_excludes<'b>(
&mut self,
excludes: impl IntoIterator<Item = &'b PackageId>,
) -> Result<&mut Self, guppy::Error> {
let traversal_exclude: Vec<&'g PackageId> = excludes
.into_iter()
.map(|package_id| Ok(self.graph.metadata(package_id)?.id()))
.collect::<Result<_, _>>()?;
self.traversal_excludes.extend(traversal_exclude);
Ok(self)
}
pub fn traversal_excludes<'b>(&'b self) -> impl Iterator<Item = &'g PackageId> + 'b {
let excludes = self.make_traversal_excludes();
excludes.iter()
}
pub fn is_traversal_excluded(&self, package_id: &PackageId) -> Result<bool, guppy::Error> {
self.graph.metadata(package_id)?;
let excludes = self.make_traversal_excludes();
Ok(excludes.is_excluded(package_id))
}
pub fn add_final_excludes<'b>(
&mut self,
excludes: impl IntoIterator<Item = &'b PackageId>,
) -> Result<&mut Self, guppy::Error> {
let final_excludes: Vec<&'g PackageId> = excludes
.into_iter()
.map(|package_id| Ok(self.graph.metadata(package_id)?.id()))
.collect::<Result<_, _>>()?;
self.final_excludes.extend(final_excludes);
Ok(self)
}
pub fn final_excludes<'b>(&'b self) -> impl Iterator<Item = &'g PackageId> + 'b {
self.final_excludes.iter().copied()
}
pub fn is_final_excluded(&self, package_id: &PackageId) -> Result<bool, guppy::Error> {
self.graph.metadata(package_id)?;
Ok(self.final_excludes.contains(package_id))
}
#[inline]
pub fn is_excluded(&self, package_id: &PackageId) -> Result<bool, guppy::Error> {
Ok(self.is_traversal_excluded(package_id)? || self.is_final_excluded(package_id)?)
}
pub fn add_registries(
&mut self,
registries: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> &mut Self {
self.registries
.extend(registries.into_iter().map(|(name, url)| Registry {
name: name.into(),
url: url.into(),
}));
self
}
pub fn set_unify_target_host(&mut self, unify_target_host: UnifyTargetHost) -> &mut Self {
self.unify_target_host = unify_target_host;
self
}
pub fn unify_target_host(&self) -> UnifyTargetHost {
self.unify_target_host
}
pub fn set_output_single_feature(&mut self, output_single_feature: bool) -> &mut Self {
self.output_single_feature = output_single_feature;
self
}
pub fn output_single_feature(&self) -> bool {
self.output_single_feature
}
pub fn set_dep_format_version(&mut self, dep_format_version: DepFormatVersion) -> &mut Self {
self.dep_format_version = dep_format_version;
self
}
pub fn dep_format_version(&self) -> DepFormatVersion {
self.dep_format_version
}
pub fn set_workspace_hack_line_style(
&mut self,
line_style: WorkspaceHackLineStyle,
) -> &mut Self {
self.workspace_hack_line_style = line_style;
self
}
pub fn workspace_hack_line_style(&self) -> WorkspaceHackLineStyle {
self.workspace_hack_line_style
}
pub fn compute(self) -> Hakari<'g> {
Hakari::build(self)
}
#[cfg(feature = "cli-support")]
pub(crate) fn traversal_excludes_only<'b>(
&'b self,
) -> impl Iterator<Item = &'g PackageId> + 'b {
self.traversal_excludes.iter().copied()
}
fn make_traversal_excludes<'b>(&'b self) -> TraversalExcludes<'g, 'b> {
let hakari_package = if self.verify_mode {
None
} else {
self.hakari_package.map(|package| package.id())
};
TraversalExcludes {
excludes: &self.traversal_excludes,
hakari_package,
}
}
fn make_features_only<'b>(&'b self) -> FeatureSet<'g> {
if self.verify_mode {
match &self.hakari_package {
Some(package) => package.to_package_set(),
None => self.graph.resolve_none(),
}
.to_feature_set(StandardFeatures::Default)
} else {
self.graph.feature_graph().resolve_none()
}
}
}
#[cfg(feature = "cli-support")]
mod summaries {
use super::*;
use crate::summaries::HakariBuilderSummary;
use guppy::platform::TargetFeatures;
impl<'g> HakariBuilder<'g> {
pub fn from_summary(
graph: &'g PackageGraph,
summary: &HakariBuilderSummary,
) -> Result<Self, guppy::Error> {
let hakari_package = summary
.hakari_package
.as_ref()
.map(|name| graph.workspace().member_by_name(name))
.transpose()?;
let platforms = summary
.platforms
.iter()
.map(|triple_str| {
let platform = Platform::new(triple_str.clone(), TargetFeatures::Unknown)
.map_err(|err| {
guppy::Error::TargetSpecError(
"while resolving hakari config or summary".to_owned(),
err,
)
})?;
Ok(platform.into())
})
.collect::<Result<Vec<_>, _>>()?;
let registries: BiHashMap<_, ahash::RandomState> = summary
.registries
.iter()
.map(|(name, url)| Registry {
name: name.clone(),
url: url.clone(),
})
.collect();
let traversal_excludes = summary
.traversal_excludes
.to_package_set_registry(
graph,
|name| registries.get1(name).map(|registry| registry.url.as_str()),
"resolving hakari traversal-excludes",
)?
.package_ids(DependencyDirection::Forward)
.collect();
let final_excludes = summary
.final_excludes
.to_package_set_registry(
graph,
|name| registries.get1(name).map(|registry| registry.url.as_str()),
"resolving hakari final-excludes",
)?
.package_ids(DependencyDirection::Forward)
.collect();
Ok(Self {
graph: DebugIgnore(graph),
hakari_package,
resolver: summary.resolver,
verify_mode: false,
unify_target_host: summary.unify_target_host,
output_single_feature: summary.output_single_feature,
dep_format_version: summary.dep_format_version,
workspace_hack_line_style: summary.workspace_hack_line_style,
platforms,
registries,
traversal_excludes,
final_excludes,
})
}
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "proptest1", derive(proptest_derive::Arbitrary))]
#[cfg_attr(feature = "cli-support", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "cli-support", serde(rename_all = "kebab-case"))]
#[non_exhaustive]
pub enum UnifyTargetHost {
None,
Auto,
UnifyIfBoth,
ReplicateTargetOnHost,
}
impl Default for UnifyTargetHost {
#[inline]
fn default() -> Self {
UnifyTargetHost::Auto
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "cli-support", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "proptest1", derive(proptest_derive::Arbitrary))]
#[non_exhaustive]
#[derive(Default)]
pub enum DepFormatVersion {
#[cfg_attr(feature = "cli-support", serde(rename = "1"))]
#[default]
V1,
#[cfg_attr(feature = "cli-support", serde(rename = "2"))]
V2,
#[cfg_attr(feature = "cli-support", serde(rename = "3"))]
V3,
#[cfg_attr(feature = "cli-support", serde(rename = "4"))]
V4,
}
impl DepFormatVersion {
#[inline]
pub fn latest() -> Self {
DepFormatVersion::V4
}
}
impl fmt::Display for DepFormatVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DepFormatVersion::V1 => write!(f, "1"),
DepFormatVersion::V2 => write!(f, "2"),
DepFormatVersion::V3 => write!(f, "3"),
DepFormatVersion::V4 => write!(f, "4"),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "cli-support", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "cli-support", serde(rename_all = "kebab-case"))]
#[cfg_attr(feature = "proptest1", derive(proptest_derive::Arbitrary))]
#[non_exhaustive]
#[derive(Default)]
pub enum WorkspaceHackLineStyle {
#[default]
Full,
VersionOnly,
WorkspaceDotted,
}
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct OutputKey {
pub platform_idx: Option<usize>,
pub build_platform: BuildPlatform,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Hakari<'g> {
pub(crate) builder: HakariBuilder<'g>,
pub output_map: OutputMap<'g>,
pub computed_map: ComputedMap<'g>,
}
impl<'g> Hakari<'g> {
pub fn builder(&self) -> &HakariBuilder<'g> {
&self.builder
}
pub fn read_toml(&self) -> Option<Result<HakariCargoToml, CargoTomlError>> {
self.builder.read_toml()
}
pub fn write_toml(
&self,
options: &HakariOutputOptions,
out: impl fmt::Write,
) -> Result<(), TomlOutError> {
write_toml(
&self.builder,
&self.output_map,
options,
self.builder.dep_format_version,
out,
)
}
pub fn toml_name_map(&self) -> AHashMap<Cow<'g, str>, PackageMetadata<'g>> {
toml_name_map(&self.output_map, self.builder.dep_format_version)
}
pub fn explain(
&self,
package_id: &'g PackageId,
) -> Result<HakariExplain<'g, '_>, guppy::Error> {
HakariExplain::new(self, package_id)
}
pub fn to_toml_string(&self, options: &HakariOutputOptions) -> Result<String, TomlOutError> {
let mut out = String::new();
self.write_toml(options, &mut out)?;
Ok(out)
}
fn build(builder: HakariBuilder<'g>) -> Self {
let graph = *builder.graph;
let mut computed_map_build = ComputedMapBuild::new(&builder);
let platform_specs: Vec<_> = builder
.platforms
.iter()
.map(|platform| PlatformSpec::Platform(platform.clone()))
.collect();
let unify_target_host = builder.unify_target_host.to_impl(graph);
let mut map_build: OutputMapBuild<'g> = OutputMapBuild::new(graph);
map_build.insert_all(
computed_map_build.iter(),
builder.output_single_feature,
unify_target_host,
);
if !builder.output_single_feature {
loop {
let mut add_extra = HashSet::new();
for (output_key, features) in map_build.iter_feature_sets() {
let initials_platform = match output_key.build_platform {
BuildPlatform::Target => InitialsPlatform::Standard,
BuildPlatform::Host => InitialsPlatform::Host,
};
let mut cargo_opts = CargoOptions::new();
let platform_spec = match output_key.platform_idx {
Some(idx) => platform_specs[idx].clone(),
None => PlatformSpec::Always,
};
cargo_opts
.set_include_dev(false)
.set_initials_platform(initials_platform)
.set_platform(platform_spec)
.set_resolver(builder.resolver)
.add_omitted_packages(computed_map_build.excludes.iter());
let cargo_set = features
.into_cargo_set(&cargo_opts)
.expect("into_cargo_set processed successfully");
for &(build_platform, feature_set) in cargo_set.all_features().iter() {
for feature_list in
feature_set.packages_with_features(DependencyDirection::Forward)
{
let dep = feature_list.package();
let dep_id = dep.id();
let v_mut = computed_map_build
.get_or_insert_mut(output_key.platform_idx, dep_id);
let new_key = OutputKey {
platform_idx: output_key.platform_idx,
build_platform,
};
if map_build.is_inserted(new_key, dep_id) {
continue;
}
let this_list: BTreeSet<_> = feature_list.named_features().collect();
let already_present = v_mut.contains(build_platform, &this_list);
if !already_present {
v_mut.mark_fixed_up(build_platform, this_list);
add_extra.insert((output_key.platform_idx, dep_id));
}
}
}
}
if add_extra.is_empty() {
break;
}
map_build.insert_all(
add_extra.iter().map(|&(platform_idx, dep_id)| {
let v = computed_map_build
.get(platform_idx, dep_id)
.expect("full value should be present");
(platform_idx, dep_id, v)
}),
builder.output_single_feature,
unify_target_host,
);
}
}
let computed_map = computed_map_build.computed_map;
let output_map = map_build.finish(
&builder.final_excludes,
builder.dep_format_version,
builder.output_single_feature,
);
Self {
builder,
output_map,
computed_map,
}
}
}
pub type OutputMap<'g> =
BTreeMap<OutputKey, BTreeMap<&'g PackageId, (PackageMetadata<'g>, BTreeSet<&'g str>)>>;
pub type ComputedMap<'g> = BTreeMap<(Option<usize>, &'g PackageId), ComputedValue<'g>>;
#[derive(Clone, Debug, Default)]
pub struct ComputedValue<'g> {
pub target_inner: ComputedInnerMap<'g>,
pub host_inner: ComputedInnerMap<'g>,
}
pub type ComputedInnerMap<'g> = BTreeMap<BTreeSet<&'g str>, ComputedInnerValue<'g>>;
#[derive(Clone, Debug, Default)]
pub struct ComputedInnerValue<'g> {
pub workspace_packages: Vec<(PackageMetadata<'g>, StandardFeatures, bool)>,
pub fixed_up: bool,
}
impl<'g> ComputedInnerValue<'g> {
fn extend(&mut self, other: ComputedInnerValue<'g>) {
self.workspace_packages.extend(other.workspace_packages);
self.fixed_up |= other.fixed_up;
}
#[inline]
fn push(
&mut self,
package: PackageMetadata<'g>,
features: StandardFeatures,
include_dev: bool,
) {
self.workspace_packages
.push((package, features, include_dev));
}
}
#[derive(Debug)]
struct TraversalExcludes<'g, 'b> {
excludes: &'b HashSet<&'g PackageId>,
hakari_package: Option<&'g PackageId>,
}
impl<'g, 'b> TraversalExcludes<'g, 'b> {
fn iter(&self) -> impl Iterator<Item = &'g PackageId> + 'b + use<'g, 'b> {
self.excludes.iter().copied().chain(self.hakari_package)
}
fn is_excluded(&self, package_id: &PackageId) -> bool {
self.hakari_package == Some(package_id) || self.excludes.contains(package_id)
}
}
#[derive(Debug)]
struct ComputedMapBuild<'g, 'b> {
excludes: TraversalExcludes<'g, 'b>,
computed_map: ComputedMap<'g>,
}
impl<'g, 'b> ComputedMapBuild<'g, 'b> {
fn new(builder: &'b HakariBuilder<'g>) -> Self {
let features_include_dev = [
(StandardFeatures::None, false),
(StandardFeatures::None, true),
(StandardFeatures::Default, false),
(StandardFeatures::Default, true),
(StandardFeatures::All, false),
(StandardFeatures::All, true),
];
let always_features = features_include_dev
.iter()
.map(|&(features, include_dev)| (None, PlatformSpec::Always, features, include_dev));
let specified_features =
features_include_dev
.iter()
.flat_map(|&(features, include_dev)| {
builder
.platforms
.iter()
.enumerate()
.map(move |(idx, platform)| {
(
Some(idx),
PlatformSpec::Platform(platform.clone()),
features,
include_dev,
)
})
});
let platforms_features: Vec<_> = always_features.chain(specified_features).collect();
let workspace = builder.graph.workspace();
let excludes = builder.make_traversal_excludes();
let features_only = builder.make_features_only();
let excludes_ref = &excludes;
let features_only_ref = &features_only;
let computed_map: ComputedMap<'g> = platforms_features
.into_par_iter()
.flat_map(|(idx, platform_spec, feature_filter, include_dev)| {
let mut cargo_options = CargoOptions::new();
cargo_options
.set_include_dev(include_dev)
.set_resolver(builder.resolver)
.set_platform(platform_spec)
.add_omitted_packages(excludes.iter());
workspace.par_iter().map(move |workspace_package| {
if excludes_ref.is_excluded(workspace_package.id()) {
return BTreeMap::new();
}
let initials = workspace_package
.to_package_set()
.to_feature_set(feature_filter);
let cargo_set =
CargoSet::new(initials, features_only_ref.clone(), &cargo_options)
.expect("cargo resolution should succeed");
let all_features = cargo_set.all_features();
let values = all_features.iter().flat_map(|&(build_platform, features)| {
features
.packages_with_features(DependencyDirection::Forward)
.filter_map(move |feature_list| {
let dep = feature_list.package();
if dep.in_workspace() {
return None;
}
let features: BTreeSet<&'g str> =
feature_list.named_features().collect();
Some((
idx,
build_platform,
dep.id(),
features,
workspace_package,
feature_filter,
include_dev,
))
})
});
let mut map = ComputedMap::new();
for (
platform_idx,
build_platform,
package_id,
features,
package,
feature_filter,
include_dev,
) in values
{
map.entry((platform_idx, package_id)).or_default().insert(
build_platform,
features,
package,
feature_filter,
include_dev,
);
}
map
})
})
.reduce(ComputedMap::new, |mut acc, map| {
for (k, v) in map {
acc.entry(k).or_default().merge(v);
}
acc
});
Self {
excludes,
computed_map,
}
}
fn get(
&self,
platform_idx: Option<usize>,
package_id: &'g PackageId,
) -> Option<&ComputedValue<'g>> {
self.computed_map.get(&(platform_idx, package_id))
}
fn get_or_insert_mut(
&mut self,
platform_idx: Option<usize>,
package_id: &'g PackageId,
) -> &mut ComputedValue<'g> {
self.computed_map
.entry((platform_idx, package_id))
.or_default()
}
fn iter<'a>(
&'a self,
) -> impl Iterator<Item = (Option<usize>, &'g PackageId, &'a ComputedValue<'g>)> + 'a {
self.computed_map
.iter()
.map(move |(&(platform_idx, package_id), v)| (platform_idx, package_id, v))
}
}
impl<'g> ComputedValue<'g> {
pub fn inner_maps(&self) -> [(BuildPlatform, &ComputedInnerMap<'g>); 2] {
[
(BuildPlatform::Target, &self.target_inner),
(BuildPlatform::Host, &self.host_inner),
]
}
pub fn into_inner_maps(self) -> [(BuildPlatform, ComputedInnerMap<'g>); 2] {
[
(BuildPlatform::Target, self.target_inner),
(BuildPlatform::Host, self.host_inner),
]
}
pub fn get_inner(&self, build_platform: BuildPlatform) -> &ComputedInnerMap<'g> {
match build_platform {
BuildPlatform::Target => &self.target_inner,
BuildPlatform::Host => &self.host_inner,
}
}
pub fn get_inner_mut(&mut self, build_platform: BuildPlatform) -> &mut ComputedInnerMap<'g> {
match build_platform {
BuildPlatform::Target => &mut self.target_inner,
BuildPlatform::Host => &mut self.host_inner,
}
}
fn merge(&mut self, other: ComputedValue<'g>) {
for (features, details) in other.target_inner {
self.target_inner
.entry(features)
.or_default()
.extend(details);
}
for (features, details) in other.host_inner {
self.host_inner.entry(features).or_default().extend(details);
}
}
fn contains(&mut self, build_platform: BuildPlatform, features: &BTreeSet<&'g str>) -> bool {
self.get_inner(build_platform).contains_key(features)
}
fn insert(
&mut self,
build_platform: BuildPlatform,
features: BTreeSet<&'g str>,
package: PackageMetadata<'g>,
feature_filter: StandardFeatures,
include_dev: bool,
) {
self.get_inner_mut(build_platform)
.entry(features)
.or_default()
.push(package, feature_filter, include_dev);
}
fn mark_fixed_up(&mut self, build_platform: BuildPlatform, features: BTreeSet<&'g str>) {
self.get_inner_mut(build_platform)
.entry(features)
.or_default()
.fixed_up = true;
}
fn describe<'a>(&'a self) -> ValueDescribe<'g, 'a> {
match (self.target_inner.len(), self.host_inner.len()) {
(0, 0) => ValueDescribe::None,
(0, 1) => ValueDescribe::SingleHost(&self.host_inner),
(1, 0) => ValueDescribe::SingleTarget(&self.target_inner),
(1, 1) => {
let target_features = self.target_inner.keys().next().expect("1 element");
let host_features = self.host_inner.keys().next().expect("1 element");
if target_features == host_features {
ValueDescribe::SingleMatchingBoth {
target_inner: &self.target_inner,
host_inner: &self.host_inner,
}
} else {
ValueDescribe::SingleNonMatchingBoth {
target_inner: &self.target_inner,
host_inner: &self.host_inner,
}
}
}
(_m, 0) => ValueDescribe::MultiTarget(&self.target_inner),
(_m, 1) => ValueDescribe::MultiTargetSingleHost {
target_inner: &self.target_inner,
host_inner: &self.host_inner,
},
(0, _n) => ValueDescribe::MultiHost(&self.host_inner),
(1, _n) => ValueDescribe::MultiHostSingleTarget {
target_inner: &self.target_inner,
host_inner: &self.host_inner,
},
(_m, _n) => ValueDescribe::MultiBoth {
target_inner: &self.target_inner,
host_inner: &self.host_inner,
},
}
}
}
#[derive(Copy, Clone, Debug)]
enum ValueDescribe<'g, 'a> {
None,
SingleTarget(&'a ComputedInnerMap<'g>),
SingleHost(&'a ComputedInnerMap<'g>),
MultiTarget(&'a ComputedInnerMap<'g>),
MultiHost(&'a ComputedInnerMap<'g>),
SingleMatchingBoth {
target_inner: &'a ComputedInnerMap<'g>,
host_inner: &'a ComputedInnerMap<'g>,
},
SingleNonMatchingBoth {
target_inner: &'a ComputedInnerMap<'g>,
host_inner: &'a ComputedInnerMap<'g>,
},
MultiTargetSingleHost {
target_inner: &'a ComputedInnerMap<'g>,
host_inner: &'a ComputedInnerMap<'g>,
},
MultiHostSingleTarget {
target_inner: &'a ComputedInnerMap<'g>,
host_inner: &'a ComputedInnerMap<'g>,
},
MultiBoth {
target_inner: &'a ComputedInnerMap<'g>,
host_inner: &'a ComputedInnerMap<'g>,
},
}
impl<'g, 'a> ValueDescribe<'g, 'a> {
#[allow(dead_code)]
fn description(self) -> &'static str {
match self {
ValueDescribe::None => "None",
ValueDescribe::SingleTarget(_) => "SingleTarget",
ValueDescribe::SingleHost(_) => "SingleHost",
ValueDescribe::MultiTarget(_) => "MultiTarget",
ValueDescribe::MultiHost(_) => "MultiHost",
ValueDescribe::SingleMatchingBoth { .. } => "SingleMatchingBoth",
ValueDescribe::SingleNonMatchingBoth { .. } => "SingleNonMatchingBoth",
ValueDescribe::MultiTargetSingleHost { .. } => "MultiTargetSingleHost",
ValueDescribe::MultiHostSingleTarget { .. } => "MultiHostSingleTarget",
ValueDescribe::MultiBoth { .. } => "MultiBoth",
}
}
fn insert(
self,
output_single_feature: bool,
unify_target_host: UnifyTargetHostImpl,
mut insert_cb: impl FnMut(BuildPlatform, &'a ComputedInnerMap<'g>),
) {
use BuildPlatform::*;
match self {
ValueDescribe::None => {
}
ValueDescribe::SingleTarget(target_inner) => {
if output_single_feature {
insert_cb(Target, target_inner);
if unify_target_host == UnifyTargetHostImpl::ReplicateTargetOnHost {
insert_cb(Host, target_inner);
}
}
}
ValueDescribe::SingleHost(host_inner) => {
if output_single_feature {
insert_cb(Host, host_inner);
}
}
ValueDescribe::MultiTarget(target_inner) => {
insert_cb(Target, target_inner);
if unify_target_host == UnifyTargetHostImpl::ReplicateTargetOnHost {
insert_cb(Host, target_inner);
}
}
ValueDescribe::MultiHost(host_inner) => {
insert_cb(Host, host_inner);
}
ValueDescribe::SingleMatchingBoth {
target_inner,
host_inner,
} => {
if output_single_feature {
insert_cb(Target, target_inner);
insert_cb(Host, host_inner);
}
}
ValueDescribe::SingleNonMatchingBoth {
target_inner,
host_inner,
} => {
insert_cb(Target, target_inner);
insert_cb(Host, host_inner);
if unify_target_host != UnifyTargetHostImpl::None {
insert_cb(Target, host_inner);
insert_cb(Host, target_inner);
}
}
ValueDescribe::MultiTargetSingleHost {
target_inner,
host_inner,
} => {
insert_cb(Target, target_inner);
insert_cb(Host, host_inner);
if unify_target_host != UnifyTargetHostImpl::None {
insert_cb(Target, host_inner);
insert_cb(Host, target_inner);
}
}
ValueDescribe::MultiHostSingleTarget {
target_inner,
host_inner,
} => {
insert_cb(Target, target_inner);
insert_cb(Host, host_inner);
if unify_target_host != UnifyTargetHostImpl::None {
insert_cb(Target, host_inner);
insert_cb(Host, target_inner);
}
}
ValueDescribe::MultiBoth {
target_inner,
host_inner,
} => {
insert_cb(Target, target_inner);
insert_cb(Host, host_inner);
if unify_target_host != UnifyTargetHostImpl::None {
insert_cb(Target, host_inner);
insert_cb(Host, target_inner);
}
}
}
}
}
#[derive(Debug)]
struct OutputMapBuild<'g> {
graph: &'g PackageGraph,
output_map: OutputMap<'g>,
}
impl<'g> OutputMapBuild<'g> {
fn new(graph: &'g PackageGraph) -> Self {
Self {
graph,
output_map: OutputMap::new(),
}
}
fn is_inserted(&self, output_key: OutputKey, package_id: &'g PackageId) -> bool {
match self.output_map.get(&output_key) {
Some(inner_map) => inner_map.contains_key(package_id),
None => false,
}
}
#[allow(dead_code)]
fn get(
&self,
output_key: OutputKey,
package_id: &'g PackageId,
) -> Option<&(PackageMetadata<'g>, BTreeSet<&'g str>)> {
match self.output_map.get(&output_key) {
Some(inner_map) => inner_map.get(package_id),
None => None,
}
}
fn insert_all<'a>(
&mut self,
values: impl IntoIterator<Item = (Option<usize>, &'g PackageId, &'a ComputedValue<'g>)>,
output_single_feature: bool,
unify_target_host: UnifyTargetHostImpl,
) where
'g: 'a,
{
for (platform_idx, dep_id, v) in values {
let describe = v.describe();
describe.insert(
output_single_feature,
unify_target_host,
|build_platform, inner| {
self.insert_inner(platform_idx, build_platform, dep_id, inner);
},
);
}
}
fn insert_inner(
&mut self,
platform_idx: Option<usize>,
build_platform: BuildPlatform,
package_id: &'g PackageId,
inner: &ComputedInnerMap<'g>,
) {
let output_key = OutputKey {
platform_idx,
build_platform,
};
self.insert(
output_key,
package_id,
inner.keys().flat_map(|f| f.iter().copied()),
)
}
fn insert(
&mut self,
output_key: OutputKey,
package_id: &'g PackageId,
features: impl IntoIterator<Item = &'g str>,
) {
let map = self.output_map.entry(output_key).or_default();
let graph = self.graph;
let (_, inner) = map.entry(package_id).or_insert_with(|| {
(
graph.metadata(package_id).expect("valid package ID"),
BTreeSet::new(),
)
});
inner.extend(features);
}
fn iter_feature_sets<'a>(&'a self) -> impl Iterator<Item = (OutputKey, FeatureSet<'g>)> + 'a {
self.output_map.iter().map(move |(&output_key, deps)| {
let feature_ids = deps.iter().flat_map(|(&package_id, (_, features))| {
features
.iter()
.map(move |&feature| FeatureId::new(package_id, FeatureLabel::Named(feature)))
});
(
output_key,
self.graph
.feature_graph()
.resolve_ids(feature_ids)
.expect("specified feature IDs are valid"),
)
})
}
fn finish(
mut self,
final_excludes: &HashSet<&'g PackageId>,
dep_format: DepFormatVersion,
output_single_feature: bool,
) -> OutputMap<'g> {
for &build_platform in BuildPlatform::VALUES {
let always_key = OutputKey {
platform_idx: None,
build_platform,
};
let mut always_map = match self.output_map.remove(&always_key) {
Some(always_map) => always_map,
None => {
continue;
}
};
if dep_format >= DepFormatVersion::V3 {
Self::filter_root_features(&mut always_map, output_single_feature);
}
for (key, inner_map) in &mut self.output_map {
if key.build_platform != build_platform {
continue;
}
if dep_format >= DepFormatVersion::V3 {
Self::filter_root_features(inner_map, output_single_feature);
}
for (package_id, (_always_package, always_features)) in &always_map {
let (package, remaining_features) = {
let (package, features) = match inner_map.get(package_id) {
Some(v) => v,
None => {
continue;
}
};
(*package, features - always_features)
};
if remaining_features.is_empty() {
inner_map.remove(package_id);
} else {
inner_map.insert(package_id, (package, remaining_features));
}
}
}
self.output_map.insert(always_key, always_map);
}
self.output_map.retain(|_, inner_map| {
for package_id in final_excludes {
inner_map.remove(package_id);
}
!inner_map.is_empty()
});
self.output_map
}
fn filter_root_features(
inner_map: &mut BTreeMap<&'g PackageId, (PackageMetadata<'g>, BTreeSet<&'g str>)>,
output_single_feature: bool,
) {
inner_map.retain(|_, (package, features)| {
let feature_set = package.to_feature_set(named_feature_filter(
StandardFeatures::None,
features.iter().copied(),
));
let root_features: BTreeSet<_> = feature_set
.root_ids(DependencyDirection::Forward)
.filter_map(|f| match f.label() {
FeatureLabel::Named(name) => Some(name),
FeatureLabel::Base => None,
FeatureLabel::OptionalDependency(name) => {
debug_assert!(
false,
"root features must be named or base, found optional dependency {name}",
);
None
}
})
.collect();
if root_features.is_empty() {
output_single_feature && features.is_empty()
} else {
*features = root_features;
true
}
});
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
enum UnifyTargetHostImpl {
None,
UnifyIfBoth,
ReplicateTargetOnHost,
}
impl UnifyTargetHost {
fn to_impl(self, graph: &PackageGraph) -> UnifyTargetHostImpl {
match self {
UnifyTargetHost::None => UnifyTargetHostImpl::None,
UnifyTargetHost::UnifyIfBoth => UnifyTargetHostImpl::UnifyIfBoth,
UnifyTargetHost::ReplicateTargetOnHost => UnifyTargetHostImpl::ReplicateTargetOnHost,
UnifyTargetHost::Auto => {
let workspace_set = graph.resolve_workspace();
if workspace_set
.packages(DependencyDirection::Forward)
.any(|package| package.is_proc_macro())
{
return UnifyTargetHostImpl::ReplicateTargetOnHost;
}
if workspace_set
.links(DependencyDirection::Forward)
.any(|link| link.build().is_present())
{
return UnifyTargetHostImpl::ReplicateTargetOnHost;
}
UnifyTargetHostImpl::UnifyIfBoth
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::UnifyTargetHost;
use fixtures::json::JsonFixture;
#[test]
fn unify_target_host_auto() {
let res = UnifyTargetHost::Auto.to_impl(JsonFixture::metadata_guppy_78cb7e8().graph());
assert_eq!(
res,
UnifyTargetHostImpl::UnifyIfBoth,
"no proc macros => unify if both"
);
let res = UnifyTargetHost::Auto.to_impl(JsonFixture::metadata_libra_9ffd93b().graph());
assert_eq!(
res,
UnifyTargetHostImpl::ReplicateTargetOnHost,
"proc macros => replicate target on host"
);
let res = UnifyTargetHost::Auto.to_impl(JsonFixture::metadata_builddep().graph());
assert_eq!(
res,
UnifyTargetHostImpl::ReplicateTargetOnHost,
"internal build deps => replicate target on host"
);
}
}