Skip to main content

devset_core/
graph.rs

1//! The profile graph: every active profile, the features the target and its requirers turn on, and
2//! the order the profiles apply in.
3//!
4//! Features are Cargo's. They only add, so the graph is expanded until nothing changes: a request
5//! for a profile turns on the features it names, and each feature turned on may turn on more, in
6//! the profile or in what it requires. A profile reached twice is one node, its features the union.
7//! The order is topological, each requirement before its requirers.
8
9use 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/// Who turned a feature on.
19#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub enum Enabler {
21    /// The target, in the layer's `features`.
22    Target,
23    /// The profile's `default` list.
24    Default,
25    /// A profile that requires it, in its `[requires]` entry.
26    Requires(ProfileName),
27    /// A feature: of a profile that requires it, or of the profile itself.
28    Feature {
29        /// The profile the feature is of.
30        profile: ProfileName,
31        /// The feature.
32        feature: FeatureName,
33    },
34}
35
36/// `target`, `default`, `rust`, or `rust/docs`.
37impl 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/// Something the graph settled that the target may not expect.
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum Warning {
51    /// A layer sets `default-features = false`, and a profile that requires it turns them on.
52    DefaultsOn {
53        /// The layer.
54        profile: ProfileName,
55        /// The profile that turns them on.
56        by: ProfileName,
57    },
58}
59
60/// How the graph reads a profile.
61pub(crate) trait Load {
62    /// A profile, read.
63    type Profile;
64
65    /// The profile named `name` in `source`.
66    fn load(&mut self, source: &Source, name: &ProfileName) -> Result<Self::Profile>;
67
68    /// What `profile` declares.
69    fn manifest(profile: &Self::Profile) -> &Manifest;
70}
71
72/// A layer the target configures: its profile, where, and the features it turns on.
73#[derive(Clone, Debug)]
74pub(crate) struct Configured<'a> {
75    /// The profile's source.
76    pub source: &'a Source,
77    /// The profile.
78    pub name: &'a ProfileName,
79    /// The features the target turns on.
80    pub features: &'a [FeatureName],
81    /// Whether its default features are on.
82    pub defaults: bool,
83}
84
85/// One active profile.
86#[derive(Debug)]
87pub(crate) struct Node<P> {
88    /// The profile, read.
89    pub profile: P,
90    /// Its name.
91    pub name: ProfileName,
92    /// Its source.
93    pub source: Source,
94    /// Every feature that is on, with who turned it on.
95    pub features: BTreeMap<FeatureName, BTreeSet<Enabler>>,
96    /// The profiles that require it and activated it.
97    pub required_by: BTreeSet<ProfileName>,
98    /// Whether the target configures it as a layer.
99    pub configured: bool,
100    /// Whether its default features are on.
101    defaults: bool,
102    /// Whether the target switched its defaults off.
103    no_defaults: bool,
104    /// Its requirements that are active.
105    active: BTreeSet<ProfileName>,
106}
107
108/// Every active profile, in the order they apply, and what the target should hear about.
109#[derive(Debug)]
110pub(crate) struct Graph<P> {
111    /// Each requirement before its requirers; otherwise as configured.
112    pub nodes: Vec<Node<P>>,
113    /// What the graph settled that the target may not expect.
114    pub warnings: Vec<Warning>,
115}
116
117/// A request for a profile: to activate it, and to turn features on.
118struct Request {
119    /// Where it is; `None` for a weak request, which only reaches the profile already active.
120    source: Option<Source>,
121    /// Its name.
122    name: ProfileName,
123    /// Who asks: the target, or a profile.
124    by: Option<ProfileName>,
125    /// The features to turn on, each with who turns it on.
126    features: Vec<(FeatureName, Enabler)>,
127    /// Whether to turn its default features on.
128    defaults: bool,
129}
130
131/// A feature to turn on, and who turns it on.
132type Turn = (FeatureName, Enabler);
133
134/// What a profile asks of one of its requirements.
135#[derive(PartialEq, Eq, PartialOrd, Ord)]
136struct Ask {
137    /// The requirement.
138    dep: ProfileName,
139    /// A feature to turn on in it, beside what its `[requires]` entry asks.
140    turn: Option<Turn>,
141    /// Whether it only reaches the requirement through a weak feature: it activates nothing.
142    weak: bool,
143}
144
145impl<P> Graph<P> {
146    /// The graph of `configured`, the target's layers in order, read through `load`.
147    ///
148    /// # Errors
149    /// - [`ProfileError::UnknownFeature`], a feature is asked of a profile that lacks it.
150    /// - [`ProfileError::Cycle`], requirements lead back to a profile that requires them.
151    /// - [`ProfileError::SameName`] or [`ProfileError::Diverged`], two profiles have one name.
152    /// - Whatever `load` fails with.
153    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        // A weak feature waits for its profile: expanded again once nothing else changes, until
175        // nothing does.
176        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
199/// The graph being built.
200struct Builder<'l, L: Load> {
201    /// How profiles are read.
202    load: &'l mut L,
203    /// Every active profile, in the order met.
204    nodes: Vec<Node<L::Profile>>,
205    /// Each node's position, by name.
206    index: HashMap<ProfileName, usize>,
207    /// Requests not yet served.
208    queue: VecDeque<Request>,
209    /// What the target should hear about.
210    warnings: Vec<Warning>,
211    /// How many times a node has changed, so the builder knows when the graph settles.
212    changes: usize,
213}
214
215impl<L: Load> Builder<'_, L> {
216    /// Serves `request`: the profile activated, its features turned on, and what they turn on
217    /// requested in turn.
218    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    /// The node for `name` in `source`, read if new, and whether it is.
269    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    /// Turns on everything the features of the node at `at` turn on, in it and in what it
303    /// requires, until nothing more is.
304    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                            // A weak feature reaches a profile active by any other means.
337                            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    /// Every node's position in the order the profiles apply: depth first from each configured
385    /// layer, each requirement, by name, before its requirer.
386    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    /// Visits the node at `at` for [`Builder::order`]; `path` holds the nodes being visited.
399    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    /// Manifests by name, one source; every load recorded.
444    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    /// A source of `profiles`, each `(name, the rest of its profile.toml)`.
463    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    /// The graph of `layers`, each `(name, features, defaults)`.
477    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    /// Each node's name and its features, with who turned each on.
499    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}