1use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
10use core::fmt;
11use std::collections::HashMap;
12
13use crate::errors::{ProfileError, Result};
14use crate::name::{FeatureName, ProfileName};
15use crate::profile::{FeatureRef, Manifest};
16use crate::source::Source;
17
18#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub enum Enabler {
21 Target,
23 Default,
25 Requires(ProfileName),
27 Feature {
29 profile: ProfileName,
31 feature: FeatureName,
33 },
34}
35
36impl fmt::Display for Enabler {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 Self::Target => f.write_str("target"),
41 Self::Default => f.write_str("default"),
42 Self::Requires(profile) => write!(f, "{profile}"),
43 Self::Feature { profile, feature } => write!(f, "{profile}/{feature}"),
44 }
45 }
46}
47
48#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum Warning {
51 DefaultsOn {
53 profile: ProfileName,
55 by: ProfileName,
57 },
58}
59
60pub(crate) trait Load {
62 type Profile;
64
65 fn load(&mut self, source: &Source, name: &ProfileName) -> Result<Self::Profile>;
67
68 fn manifest(profile: &Self::Profile) -> &Manifest;
70}
71
72#[derive(Clone, Debug)]
74pub(crate) struct Configured<'a> {
75 pub source: &'a Source,
77 pub name: &'a ProfileName,
79 pub features: &'a [FeatureName],
81 pub defaults: bool,
83}
84
85#[derive(Debug)]
87pub(crate) struct Node<P> {
88 pub profile: P,
90 pub name: ProfileName,
92 pub source: Source,
94 pub features: BTreeMap<FeatureName, BTreeSet<Enabler>>,
96 pub required_by: BTreeSet<ProfileName>,
98 pub configured: bool,
100 defaults: bool,
102 no_defaults: bool,
104 active: BTreeSet<ProfileName>,
106}
107
108#[derive(Debug)]
110pub(crate) struct Graph<P> {
111 pub nodes: Vec<Node<P>>,
113 pub warnings: Vec<Warning>,
115}
116
117struct Request {
119 source: Option<Source>,
121 name: ProfileName,
123 by: Option<ProfileName>,
125 features: Vec<(FeatureName, Enabler)>,
127 defaults: bool,
129}
130
131type Turn = (FeatureName, Enabler);
133
134#[derive(PartialEq, Eq, PartialOrd, Ord)]
136struct Ask {
137 dep: ProfileName,
139 turn: Option<Turn>,
141 weak: bool,
143}
144
145impl<P> Graph<P> {
146 pub(crate) fn build<L: Load<Profile = P>>(
154 configured: &[Configured<'_>], load: &mut L,
155 ) -> Result<Self> {
156 let mut builder = Builder {
157 load,
158 nodes: Vec::new(),
159 index: HashMap::new(),
160 queue: VecDeque::new(),
161 warnings: Vec::new(),
162 changes: 0,
163 };
164 for layer in configured {
165 let features = layer.features.iter().map(|f| (f.clone(), Enabler::Target)).collect();
166 builder.queue.push_back(Request {
167 source: Some(layer.source.clone()),
168 name: layer.name.clone(),
169 by: None,
170 features,
171 defaults: layer.defaults,
172 });
173 }
174 loop {
177 while let Some(request) = builder.queue.pop_front() {
178 builder.serve(request)?;
179 }
180 let seen = builder.changes;
181 for at in 0..builder.nodes.len() {
182 builder.expand(at);
183 }
184 while let Some(request) = builder.queue.pop_front() {
185 builder.serve(request)?;
186 }
187 if builder.changes == seen {
188 break;
189 }
190 }
191 let order = builder.order(configured)?;
192 let Builder { nodes, warnings, .. } = builder;
193 let mut nodes: Vec<Option<Node<P>>> = nodes.into_iter().map(Some).collect();
194 let nodes = order.into_iter().filter_map(|at| nodes.get_mut(at)?.take()).collect();
195 Ok(Self { nodes, warnings })
196 }
197}
198
199struct Builder<'l, L: Load> {
201 load: &'l mut L,
203 nodes: Vec<Node<L::Profile>>,
205 index: HashMap<ProfileName, usize>,
207 queue: VecDeque<Request>,
209 warnings: Vec<Warning>,
211 changes: usize,
213}
214
215impl<L: Load> Builder<'_, L> {
216 fn serve(&mut self, request: Request) -> Result<()> {
219 let Request { source, name, by, features, defaults } = request;
220 let weak = source.is_none();
221 let met = self.index.get(&name).and_then(|&at| self.nodes.get(at));
222 let Some(source) = source.or_else(|| met.map(|met| met.source.clone())) else {
223 return Ok(());
224 };
225 let (at, new) = self.node(&source, &name)?;
226 let Some(node) = self.nodes.get_mut(at) else { return Ok(()) };
227 let mut changed = new;
228 match &by {
229 None => {
230 node.configured = true;
231 node.no_defaults = !defaults;
232 },
233 Some(requirer) if !weak => {
234 node.required_by.insert(requirer.clone());
235 },
236 Some(_) => {},
237 }
238 let declared = &L::manifest(&node.profile).features;
239 if defaults && !node.defaults {
240 node.defaults = true;
241 if let (true, Some(by)) = (node.no_defaults, &by) {
242 self.warnings.push(Warning::DefaultsOn { profile: name.clone(), by: by.clone() });
243 }
244 for feature in &declared.default {
245 changed |=
246 node.features.entry(feature.clone()).or_default().insert(Enabler::Default);
247 }
248 }
249 for (feature, enabler) in features {
250 if !declared.declared.contains_key(&feature) {
251 return Err(ProfileError::UnknownFeature {
252 profile: name,
253 feature,
254 by: by.map_or_else(|| "the target".to_owned(), |by| format!("profile {by}")),
255 known: declared.declared.keys().cloned().collect(),
256 }
257 .into());
258 }
259 changed |= node.features.entry(feature).or_default().insert(enabler);
260 }
261 if changed {
262 self.changes = self.changes.saturating_add(1);
263 self.expand(at);
264 }
265 Ok(())
266 }
267
268 fn node(&mut self, source: &Source, name: &ProfileName) -> Result<(usize, bool)> {
270 if let Some(&at) = self.index.get(name) {
271 let met = self.nodes.get(at).map(|node| &node.source);
272 return match met {
273 Some(met) if met != source => {
274 let (first, second) = (met.to_string(), source.to_string());
275 Err(if met.twin(source) {
276 ProfileError::Diverged { first, second }
277 } else {
278 ProfileError::SameName { name: name.clone(), first, second }
279 }
280 .into())
281 },
282 Some(_) | None => Ok((at, false)),
283 };
284 }
285 let profile = self.load.load(source, name)?;
286 let at = self.nodes.len();
287 self.nodes.push(Node {
288 profile,
289 name: name.clone(),
290 source: source.clone(),
291 features: BTreeMap::new(),
292 required_by: BTreeSet::new(),
293 configured: false,
294 defaults: false,
295 no_defaults: false,
296 active: BTreeSet::new(),
297 });
298 self.index.insert(name.clone(), at);
299 Ok((at, true))
300 }
301
302 fn expand(&mut self, at: usize) {
305 let Some(node) = self.nodes.get_mut(at) else { return };
306 let manifest = L::manifest(&node.profile);
307 let this = node.name.clone();
308 let mut asks: Vec<Ask> = Vec::new();
309 for (dep, requirement) in &manifest.requires {
310 if !requirement.optional && node.active.insert(dep.clone()) {
311 asks.push(Ask { dep: dep.clone(), turn: None, weak: false });
312 }
313 }
314 loop {
315 let mut changed = false;
316 let on: Vec<FeatureName> = node.features.keys().cloned().collect();
317 for feature in on {
318 let by = Enabler::Feature { profile: this.clone(), feature: feature.clone() };
319 for each in manifest.features.declared.get(&feature).into_iter().flatten() {
320 match each {
321 FeatureRef::Feature(other) => {
322 changed |=
323 node.features.entry(other.clone()).or_default().insert(by.clone());
324 },
325 FeatureRef::Dep(dep) => {
326 if node.active.insert(dep.clone()) {
327 changed = true;
328 asks.push(Ask { dep: dep.clone(), turn: None, weak: false });
329 }
330 },
331 FeatureRef::Of { profile: dep, feature: theirs, weak } => {
332 if !weak && node.active.insert(dep.clone()) {
333 changed = true;
334 asks.push(Ask { dep: dep.clone(), turn: None, weak: false });
335 }
336 let own = node.active.contains(dep);
338 if own || self.index.contains_key(dep) {
339 let turn = Some((theirs.clone(), by.clone()));
340 asks.push(Ask { dep: dep.clone(), turn, weak: !own });
341 }
342 },
343 }
344 }
345 }
346 if !changed {
347 break;
348 }
349 }
350 asks.sort();
351 asks.dedup();
352 for Ask { dep, turn, weak } in asks {
353 let Some(requirement) = manifest.requires.get(&dep) else { continue };
354 let by = Some(this.clone());
355 if weak {
356 let features = turn.into_iter().collect();
357 self.queue.push_back(Request {
358 source: None,
359 name: dep,
360 by,
361 features,
362 defaults: false,
363 });
364 continue;
365 }
366 let source = requirement.source.clone().unwrap_or_else(|| node.source.clone());
367 let mut features: Vec<Turn> = requirement
368 .features
369 .iter()
370 .map(|f| (f.clone(), Enabler::Requires(this.clone())))
371 .collect();
372 features.extend(turn);
373 let defaults = requirement.default_features;
374 self.queue.push_back(Request {
375 source: Some(source),
376 name: dep,
377 by,
378 features,
379 defaults,
380 });
381 }
382 }
383
384 fn order(&self, configured: &[Configured<'_>]) -> Result<Vec<usize>> {
387 let mut order = Vec::with_capacity(self.nodes.len());
388 let mut done = vec![false; self.nodes.len()];
389 let mut path = Vec::new();
390 for layer in configured {
391 if let Some(&at) = self.index.get(layer.name) {
392 self.visit(at, &mut done, &mut path, &mut order)?;
393 }
394 }
395 Ok(order)
396 }
397
398 fn visit(
400 &self, at: usize, done: &mut [bool], path: &mut Vec<usize>, order: &mut Vec<usize>,
401 ) -> Result<()> {
402 if done.get(at).copied().unwrap_or(true) {
403 return Ok(());
404 }
405 let name = |at: usize| self.nodes.get(at).map(|node| node.name.clone());
406 if let Some(from) = path.iter().position(|&on| on == at) {
407 let chain = path
408 .get(from..)
409 .unwrap_or_default()
410 .iter()
411 .chain([&at])
412 .filter_map(|&at| name(at))
413 .collect();
414 return Err(ProfileError::Cycle { chain }.into());
415 }
416 path.push(at);
417 let active = self.nodes.get(at).map(|node| node.active.iter()).into_iter().flatten();
418 for dep in active {
419 if let Some(&next) = self.index.get(dep) {
420 self.visit(next, done, path, order)?;
421 }
422 }
423 path.pop();
424 if let Some(visited) = done.get_mut(at) {
425 *visited = true;
426 }
427 order.push(at);
428 Ok(())
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use alloc::collections::BTreeMap;
435 use core::cell::RefCell;
436
437 use super::{Configured, Enabler, Graph, Load, Node, Warning};
438 use crate::errors::{Error, ProfileError, Result};
439 use crate::name::{FeatureName, ProfileName};
440 use crate::profile::Manifest;
441 use crate::source::Source;
442
443 struct Memory {
445 manifests: BTreeMap<String, Manifest>,
446 loaded: RefCell<Vec<String>>,
447 }
448
449 impl Load for Memory {
450 type Profile = Manifest;
451
452 fn load(&mut self, _: &Source, name: &ProfileName) -> Result<Manifest> {
453 self.loaded.borrow_mut().push(name.to_string());
454 Ok(self.manifests.get(name.as_str()).cloned().expect("a profile the test declares"))
455 }
456
457 fn manifest(profile: &Manifest) -> &Manifest {
458 profile
459 }
460 }
461
462 fn memory(profiles: &[(&str, &str)]) -> Memory {
464 let manifests = profiles
465 .iter()
466 .map(|(name, rest)| {
467 let toml = format!("[profile]\nname = \"{name}\"\n{rest}");
468 let manifest: Manifest = toml::from_str(&toml).expect("a manifest");
469 manifest.check(name).expect("a valid manifest");
470 ((*name).to_owned(), manifest)
471 })
472 .collect();
473 Memory { manifests, loaded: RefCell::new(Vec::new()) }
474 }
475
476 fn build(memory: &mut Memory, layers: &[(&str, &[&str], bool)]) -> Result<Graph<Manifest>> {
478 let source = Source::Dir("p".into());
479 let names: Vec<(ProfileName, Vec<FeatureName>, bool)> = layers
480 .iter()
481 .map(|(name, features, defaults)| {
482 let features = features.iter().map(|f| f.parse().expect("a feature")).collect();
483 (name.parse().expect("a name"), features, *defaults)
484 })
485 .collect();
486 let configured: Vec<Configured<'_>> = names
487 .iter()
488 .map(|(name, features, defaults)| Configured {
489 source: &source,
490 name,
491 features,
492 defaults: *defaults,
493 })
494 .collect();
495 Graph::build(&configured, memory)
496 }
497
498 fn features(graph: &Graph<Manifest>) -> Vec<(String, Vec<String>)> {
500 graph
501 .nodes
502 .iter()
503 .map(|node: &Node<Manifest>| {
504 let on = node.features.iter().map(|(feature, by)| {
505 let by: Vec<String> = by.iter().map(Enabler::to_string).collect();
506 format!("{feature} <- {}", by.join(", "))
507 });
508 (node.name.to_string(), on.collect())
509 })
510 .collect()
511 }
512
513 #[test]
514 fn defaults_and_implied_features() {
515 let mut memory = memory(&[(
516 "book",
517 "[features]\ndefault = [\"katex\"]\nkatex = []\napi = [\"katex\"]\nmermaid = []\n",
518 )]);
519 let graph = build(&mut memory, &[("book", &["api"], true)]).expect("a graph");
520 assert_eq!(
521 features(&graph),
522 [(
523 "book".to_owned(),
524 vec!["api <- target".to_owned(), "katex <- default, book/api".to_owned()]
525 )],
526 "the defaults, and what a feature implies"
527 );
528 let graph = build(&mut memory, &[("book", &[], false)]).expect("a graph");
529 assert_eq!(
530 features(&graph),
531 [("book".to_owned(), vec![])],
532 "no defaults when switched off"
533 );
534 }
535
536 #[test]
537 fn features_of_requirements_unify() {
538 let mut memory = memory(&[
539 ("ci", "[features]\npages = []\nnightly = []\n"),
540 ("book", "[requires]\nci = { optional = true }\n[features]\npages = [\"ci/pages\"]\n"),
541 (
542 "rust",
543 "[requires]\nci = { features = [\"nightly\"] }\nbook = {}\n[features]\ndefault = [\"docs\"]\ndocs = [\"book/pages\"]\n",
544 ),
545 ]);
546 let graph = build(&mut memory, &[("rust", &[], true)]).expect("a graph");
547 assert_eq!(
548 features(&graph),
549 [
550 (
551 "ci".to_owned(),
552 vec!["nightly <- rust".to_owned(), "pages <- book/pages".to_owned()]
553 ),
554 ("book".to_owned(), vec!["pages <- rust/docs".to_owned()]),
555 ("rust".to_owned(), vec!["docs <- default".to_owned()]),
556 ],
557 "one node per profile, the union of what each asks, requirements first"
558 );
559 let ci = graph.nodes.iter().find(|n| n.name.as_str() == "ci").expect("ci");
560 assert_eq!(ci.required_by.len(), 2, "both requirers");
561 }
562
563 #[test]
564 fn optional_and_weak_requirements() {
565 let mut memory = memory(&[
566 ("lychee", "[features]\nstrict = []\n"),
567 ("hook", "[features]\nstrict = []\n"),
568 (
569 "book",
570 "[requires]\nlychee = { optional = true }\nhook = { optional = true }\n[features]\nlinks = [\"dep:lychee\"]\nstrict = [\"lychee?/strict\", \"hook?/strict\"]\n",
571 ),
572 ]);
573 let graph = build(&mut memory, &[("book", &["strict"], true)]).expect("a graph");
574 assert_eq!(
575 features(&graph),
576 [("book".to_owned(), vec!["strict <- target".to_owned()])],
577 "a weak feature activates nothing"
578 );
579 assert_eq!(
580 *memory.loaded.borrow(),
581 ["book"],
582 "an optional requirement nobody activates is never read"
583 );
584 let graph = build(&mut memory, &[("book", &["strict", "links"], true)]).expect("a graph");
585 let names: Vec<String> = graph.nodes.iter().map(|n| n.name.to_string()).collect();
586 assert_eq!(
587 names,
588 ["lychee", "book"],
589 "`dep:` activates it, and the weak feature then reaches it"
590 );
591 assert_eq!(features(&graph)[0].1, ["strict <- book/strict"], "through the weak feature");
592 }
593
594 #[test]
595 fn a_weak_feature_reaches_a_profile_active_by_other_means() {
596 let mut memory = memory(&[
597 ("lychee", "[features]\nstrict = []\n"),
598 (
599 "book",
600 "[requires]\nlychee = { optional = true }\n[features]\nagents = [\"lychee?/strict\"]\n",
601 ),
602 ]);
603 let graph = build(&mut memory, &[("book", &["agents"], true)]).expect("a graph");
604 assert_eq!(graph.nodes.len(), 1, "alone, it activates nothing");
605 let graph = build(&mut memory, &[("book", &["agents"], true), ("lychee", &[], true)])
606 .expect("a graph");
607 let lychee = graph.nodes.iter().find(|n| n.name.as_str() == "lychee").expect("lychee");
608 let on: Vec<String> = lychee.features.keys().map(ToString::to_string).collect();
609 assert_eq!(on, ["strict"], "the target's lychee takes it, whatever the order");
610 assert!(lychee.required_by.is_empty(), "and is no requirement of book");
611 }
612
613 #[test]
614 fn defaults_a_requirer_turns_on_are_named() {
615 let mut memory = memory(&[
616 ("book", "[features]\ndefault = [\"katex\"]\nkatex = []\n"),
617 ("rust", "[requires]\nbook = {}\n"),
618 ]);
619 let graph =
620 build(&mut memory, &[("book", &[], false), ("rust", &[], true)]).expect("a graph");
621 assert_eq!(features(&graph)[0].1, ["katex <- default"], "on, since a requirer asks");
622 let warning = Warning::DefaultsOn {
623 profile: "book".parse().expect("a name"),
624 by: "rust".parse().expect("a name"),
625 };
626 assert_eq!(graph.warnings, [warning], "and the target hears who");
627 }
628
629 #[test]
630 fn what_cannot_hold_is_refused() {
631 let mut memory = memory(&[
632 ("a", "[requires]\nb = {}\n"),
633 ("b", "[requires]\na = {}\n"),
634 ("c", "[requires]\nd = { features = [\"nope\"] }\n"),
635 ("d", "[features]\nyes = []\n"),
636 ]);
637 let cycle = build(&mut memory, &[("a", &[], true)]).expect_err("a cycle");
638 assert!(
639 matches!(cycle, Error::Profile(ProfileError::Cycle { ref chain }) if chain.len() == 3),
640 "{cycle}"
641 );
642 let unknown = build(&mut memory, &[("c", &[], true)]).expect_err("an unknown feature");
643 assert!(
644 matches!(unknown, Error::Profile(ProfileError::UnknownFeature { ref by, .. }) if by == "profile c"),
645 "{unknown}"
646 );
647 let unknown = build(&mut memory, &[("d", &["no"], true)]).expect_err("an unknown feature");
648 assert!(
649 matches!(unknown, Error::Profile(ProfileError::UnknownFeature { ref by, .. }) if by == "the target"),
650 "{unknown}"
651 );
652 }
653}