1#[cfg(feature = "cli-support")]
10mod display;
11mod simplify;
12
13#[cfg(feature = "cli-support")]
14pub use display::HakariExplainDisplay;
15
16use crate::{explain::simplify::*, Hakari};
17use guppy::{
18 graph::{cargo::BuildPlatform, feature::StandardFeatures, PackageGraph, PackageMetadata},
19 PackageId,
20};
21use std::{
22 collections::{BTreeMap, BTreeSet},
23 sync::Arc,
24};
25use target_spec::Platform;
26
27#[derive(Clone, Debug)]
31pub struct HakariExplain<'g, 'a> {
32 #[cfg_attr(not(feature = "cli-support"), allow(dead_code))]
33 graph: &'g PackageGraph,
34 metadata: PackageMetadata<'g>,
35 #[cfg_attr(not(feature = "cli-support"), allow(dead_code))]
36 platforms: &'a [Arc<Platform>],
37 target_map: ExplainMap<'g, 'a>,
38 host_map: ExplainMap<'g, 'a>,
39}
40
41type ExplainMap<'g, 'a> = BTreeMap<&'a BTreeSet<&'g str>, ExplainInner<'g>>;
42
43#[derive(Clone, Debug, Default)]
44struct ExplainInner<'g> {
45 #[cfg_attr(not(feature = "cli-support"), allow(dead_code))]
46 workspace_packages: BTreeMap<&'g PackageId, ExplainInnerValue<'g>>,
47 #[cfg_attr(not(feature = "cli-support"), allow(dead_code))]
48 fixup_platforms: Vec<Simple<Option<usize>>>,
49}
50
51#[derive(Clone, Debug)]
52#[allow(clippy::type_complexity)]
53struct ExplainInnerValue<'g> {
54 #[cfg_attr(not(feature = "cli-support"), allow(dead_code))]
55 metadata: PackageMetadata<'g>,
56 #[cfg_attr(not(feature = "cli-support"), allow(dead_code))]
57 sets: Vec<(
58 Simple<bool>,
59 Simple<StandardFeatures>,
60 Simple<Option<usize>>,
61 )>,
62}
63
64impl<'g, 'a> HakariExplain<'g, 'a> {
65 pub(crate) fn new(hakari: &'a Hakari<'g>, dep_id: &PackageId) -> Result<Self, guppy::Error> {
66 let graph = hakari.builder.graph();
67 let metadata = hakari.builder.graph().metadata(dep_id)?;
68 let intermediate = ExplainIntermediate::new(hakari, metadata.id())?;
69
70 let target_map = Self::simplify_map(hakari, intermediate.target_map);
71 let host_map = Self::simplify_map(hakari, intermediate.host_map);
72
73 Ok(Self {
74 graph,
75 metadata,
76 platforms: &hakari.builder.platforms,
77 target_map,
78 host_map,
79 })
80 }
81
82 fn simplify_map(hakari: &'a Hakari<'g>, map: IntermediateMap<'g, 'a>) -> ExplainMap<'g, 'a> {
83 const STANDARD_FEATURES_COUNT: usize = 3;
84 const INCLUDE_DEV_COUNT: usize = 2;
85 let platform_count = hakari.builder.platforms.len() + 1;
87
88 map.into_iter()
89 .map(|(features, inner)| {
90 let workspace_packages = inner
91 .workspace_packages
92 .into_iter()
93 .map(|(package_id, IntermediateInnerValue { metadata, sets })| {
94 let sets = simplify3(
95 &sets,
96 (INCLUDE_DEV_COUNT, STANDARD_FEATURES_COUNT, platform_count),
97 );
98 (package_id, ExplainInnerValue { metadata, sets })
99 })
100 .collect();
101 let fixup_platforms = simplify1(&inner.fixup_platforms, platform_count);
102 (
103 features,
104 ExplainInner {
105 workspace_packages,
106 fixup_platforms,
107 },
108 )
109 })
110 .collect()
111 }
112
113 pub fn dependency(&self) -> PackageMetadata<'g> {
116 self.metadata
117 }
118
119 #[cfg(feature = "cli-support")]
121 pub fn display<'explain>(&'explain self) -> HakariExplainDisplay<'g, 'a, 'explain> {
122 HakariExplainDisplay::new(self)
123 }
124
125 #[allow(dead_code)]
127 fn explain_maps(&self) -> [(BuildPlatform, &ExplainMap<'g, 'a>); 2] {
128 [
129 (BuildPlatform::Target, &self.target_map),
130 (BuildPlatform::Host, &self.host_map),
131 ]
132 }
133}
134
135#[derive(Debug)]
137struct ExplainIntermediate<'g, 'a> {
138 target_map: IntermediateMap<'g, 'a>,
139 host_map: IntermediateMap<'g, 'a>,
140}
141
142type IntermediateMap<'g, 'a> = BTreeMap<&'a BTreeSet<&'g str>, IntermediateInner<'g>>;
143
144#[derive(Debug, Default)]
145struct IntermediateInner<'g> {
146 workspace_packages: BTreeMap<&'g PackageId, IntermediateInnerValue<'g>>,
147 fixup_platforms: BTreeSet<Option<usize>>,
148}
149
150#[derive(Debug)]
151struct IntermediateInnerValue<'g> {
152 metadata: PackageMetadata<'g>,
153 sets: BTreeSet<(bool, StandardFeatures, Option<usize>)>,
154}
155
156impl<'g, 'a> ExplainIntermediate<'g, 'a> {
157 fn new(hakari: &'a Hakari<'g>, dep_id: &'g PackageId) -> Result<Self, guppy::Error> {
158 let mut target_map: IntermediateMap<'g, 'a> = BTreeMap::new();
159 let mut host_map: IntermediateMap<'g, 'a> = BTreeMap::new();
160
161 let platform_idxs =
163 std::iter::once(None).chain((0..=hakari.builder.platforms.len()).map(Some));
164 let map_keys = platform_idxs.map(|idx| (idx, dep_id));
165
166 for (platform, package_id) in map_keys {
167 let computed_value = match hakari.computed_map.get(&(platform, package_id)) {
168 Some(v) => v,
169 None => continue,
170 };
171
172 for (build_platform, inner_map) in computed_value.inner_maps() {
173 let map = match build_platform {
174 BuildPlatform::Target => &mut target_map,
175 BuildPlatform::Host => &mut host_map,
176 };
177
178 for (features, inner_value) in inner_map {
179 for &(workspace_package, standard_features, include_dev) in
180 &inner_value.workspace_packages
181 {
182 map.entry(features)
183 .or_default()
184 .workspace_packages
185 .entry(workspace_package.id())
186 .or_insert_with(|| IntermediateInnerValue {
187 metadata: workspace_package,
188 sets: BTreeSet::new(),
189 })
190 .sets
191 .insert((include_dev, standard_features, platform));
192 }
193
194 if inner_value.fixed_up {
195 map.entry(features)
196 .or_default()
197 .fixup_platforms
198 .insert(platform);
199 }
200 }
201 }
202 }
203
204 if target_map.is_empty() && host_map.is_empty() {
205 return Err(guppy::Error::UnknownPackageId(dep_id.clone()));
206 }
207
208 Ok(Self {
209 target_map,
210 host_map,
211 })
212 }
213}