Skip to main content

devset_core/
resolve.rs

1//! Resolving a target's layers into one [`Resolved`] profile: read, composed, answered, rendered.
2//!
3//! In order, each step reading only what the steps before it decided:
4//!
5//! 1. **Graph.** The configured layers and everything they require, by name, their features
6//!    unified, in the order they apply.
7//! 2. **Variables.** Every variable a layer declares, answered.
8//! 3. **Paths.** Each entry's path, and the paths and globs its gates name, rendered.
9//! 4. **Gates.** Entries whose `features`, `profiles` or `vars` do not hold are off; `exists` and
10//!    scaffolds wait for [`survey`](crate::survey()), which sees the disk.
11//! 5. **Providers.** One layer per file, or one per part, the target's overrides applied.
12//! 6. **Content.** Templates rendered with the answers and the graph; parts read out of it.
13
14use alloc::collections::{BTreeMap, BTreeSet};
15use core::{mem, slice};
16
17use semver::Version;
18use serde::Serialize;
19
20use crate::collection::{Index, label};
21use crate::digest::Digest;
22use crate::errors::{ProfileError, Result, TargetError};
23use crate::format::Format;
24use crate::gate::{self, Pattern, Reason};
25use crate::graph::{Configured, Graph, Load, Node};
26pub use crate::graph::{Enabler, Warning};
27pub use crate::merge::Driver;
28use crate::name::{FeatureName, ProfileName, ScaffoldName, SourceName};
29use crate::part::{Key, Scope, Shape, Slot, Written, display, overlap};
30use crate::path::RelPath;
31use crate::profile::{
32    FileSpec, MANIFEST, Manifest, MergeSpec, Meta, PAYLOAD, Policy, ScaffoldSpec,
33};
34pub use crate::settings::{Settings, Suggestion};
35use crate::source::{Cache, Oid, Reader, Source};
36use crate::survey::Entry;
37use crate::target::{Override, Target, from_toml};
38use crate::tree::Tree;
39pub use crate::vars::Question;
40use crate::vars::{self, Context, VarName, VarSpec};
41
42/// Which sources [`resolve`] may move off their locked commit.
43#[derive(Clone, Copy, Debug)]
44pub enum Refresh<'a> {
45    /// None: every source at its locked commit.
46    None,
47    /// Every source, to what its ref names now.
48    All,
49    /// The source with this name, or the one the layer with this name comes from.
50    Only(&'a str),
51}
52
53/// Whether a layer's content is what the lock recorded.
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
55#[serde(rename_all = "lowercase")]
56pub enum Applied {
57    /// It is.
58    Current,
59    /// Its source moved, its local directory changed, or an answer or the graph did, since it
60    /// was applied.
61    Changed,
62    /// The layer has never been applied.
63    Never,
64}
65
66/// An active profile, read from its source at one revision, and rendered for the target.
67#[derive(Debug)]
68pub struct Layer {
69    /// Where it came from.
70    source: Source,
71    /// The name the target gives its source; `None` for a source a profile requires by `git`.
72    source_name: Option<SourceName>,
73    /// Its directory in the source.
74    dir: String,
75    /// Its `[profile]` table.
76    meta: Meta,
77    /// Its `[merge]` table.
78    merge: MergeSpec,
79    /// Its variables.
80    vars: BTreeMap<VarName, VarSpec>,
81    /// Every feature it declares.
82    declared: Vec<FeatureName>,
83    /// Every feature that is on, with who turned it on.
84    features: BTreeMap<FeatureName, BTreeSet<Enabler>>,
85    /// The profiles that require it.
86    required_by: Vec<ProfileName>,
87    /// Whether the target configures it as a layer.
88    configured: bool,
89    /// The commit, for a versioned source.
90    rev: Option<Oid>,
91    /// Digest of its manifest as read.
92    manifest: Digest,
93    /// Digest of manifest, rendered payload and features together.
94    digest: Digest,
95    /// The digest the lock recorded for it.
96    locked: Option<Digest>,
97    /// Its `[files]` and `[scaffolds]`, as written, until their paths are rendered.
98    specs: Specs,
99    /// Its `[scaffolds]`: each group's sentinels, rendered.
100    scaffolds: BTreeMap<ScaffoldName, Vec<Pattern>>,
101    /// Its files, by rendered path.
102    files: BTreeMap<RelPath, LayerFile>,
103    /// Its payload as read, by path as written, until rendered.
104    written: Tree,
105    /// Each file's bytes, by rendered path, templates rendered; of the files that apply.
106    payload: Tree,
107    /// Each part's starter, by the rendered path of its file, templates rendered.
108    starters: Tree,
109}
110
111/// A profile's `[files]` and `[scaffolds]`, as written.
112pub(crate) type Specs = (BTreeMap<RelPath, FileSpec>, BTreeMap<ScaffoldName, ScaffoldSpec>);
113
114/// One of a layer's files: its entry, where its payload is, and whether its gates let it apply.
115#[derive(Clone, Debug)]
116pub struct LayerFile {
117    /// Its path as the profile wrote it: under `files/`, and before variables are answered.
118    pub written: RelPath,
119    /// Its entry.
120    pub spec: FileSpec,
121    /// The paths and globs of its `when.exists`, rendered.
122    pub(crate) exists: Vec<Pattern>,
123    /// Why it does not apply, when its `features`, `profiles` or `vars` say so.
124    pub gated: Option<Reason>,
125}
126
127impl LayerFile {
128    /// The paths and globs of its `when.exists`, rendered.
129    pub fn exists(&self) -> impl Iterator<Item = &str> {
130        self.exists.iter().map(Pattern::as_str)
131    }
132}
133
134impl Layer {
135    /// Where it came from.
136    #[must_use]
137    pub const fn source(&self) -> &Source {
138        &self.source
139    }
140
141    /// The name the target gives its source; `None` for a source a profile requires by `git`.
142    #[must_use]
143    pub const fn source_name(&self) -> Option<&SourceName> {
144        self.source_name.as_ref()
145    }
146
147    /// Its profile's name, which names the layer.
148    #[must_use]
149    pub const fn name(&self) -> &ProfileName {
150        &self.meta.name
151    }
152
153    /// `source/profile`, as the target names it; the profile's name for a source a profile
154    /// requires by `git`.
155    #[must_use]
156    pub fn qualified(&self) -> String {
157        let name = &self.meta.name;
158        self.source_name
159            .as_ref()
160            .map_or_else(|| name.to_string(), |source| format!("{source}/{name}"))
161    }
162
163    /// Its `[profile]` table.
164    #[must_use]
165    pub const fn meta(&self) -> &Meta {
166        &self.meta
167    }
168
169    /// Its `[merge]` table.
170    #[must_use]
171    pub const fn merge(&self) -> &MergeSpec {
172        &self.merge
173    }
174
175    /// The commit, for a versioned source.
176    #[must_use]
177    pub const fn rev(&self) -> Option<&Oid> {
178        self.rev.as_ref()
179    }
180
181    /// Digest of manifest, rendered payload and features together.
182    #[must_use]
183    pub const fn digest(&self) -> Digest {
184        self.digest
185    }
186
187    /// Every feature it declares, in order.
188    #[must_use]
189    pub fn declared(&self) -> &[FeatureName] {
190        &self.declared
191    }
192
193    /// Every feature that is on, with who turned it on.
194    #[must_use]
195    pub const fn features(&self) -> &BTreeMap<FeatureName, BTreeSet<Enabler>> {
196        &self.features
197    }
198
199    /// The profiles that require it.
200    #[must_use]
201    pub fn required_by(&self) -> &[ProfileName] {
202        &self.required_by
203    }
204
205    /// Whether the target configures it as a layer, rather than a profile requiring it.
206    #[must_use]
207    pub const fn configured(&self) -> bool {
208        self.configured
209    }
210
211    /// Its files, by rendered path: those that apply, and those whose gates say they do not.
212    #[must_use]
213    pub const fn files(&self) -> &BTreeMap<RelPath, LayerFile> {
214        &self.files
215    }
216
217    /// Its scaffold groups, each with its sentinels.
218    pub fn scaffolds(&self) -> impl Iterator<Item = (&ScaffoldName, impl Iterator<Item = &str>)> {
219        self.scaffolds.iter().map(|(name, unless)| (name, unless.iter().map(Pattern::as_str)))
220    }
221
222    /// Whether this content is what the lock recorded.
223    #[must_use]
224    pub fn applied(&self) -> Applied {
225        match self.locked {
226            None => Applied::Never,
227            Some(locked) if locked == self.digest => Applied::Current,
228            Some(_) => Applied::Changed,
229        }
230    }
231
232    /// Its variables.
233    pub(crate) fn vars(&self) -> impl Iterator<Item = (&VarName, &VarSpec)> {
234        self.vars.iter()
235    }
236
237    /// Its scaffold groups' sentinels, rendered.
238    pub(crate) const fn sentinels(&self) -> &BTreeMap<ScaffoldName, Vec<Pattern>> {
239        &self.scaffolds
240    }
241
242    /// Where `file`, relative to its profile, is, as users know it.
243    pub(crate) fn label(&self, file: &str) -> String {
244        if self.dir.is_empty() {
245            label(&self.source, file)
246        } else {
247            label(&self.source, &format!("{}/{file}", self.dir))
248        }
249    }
250
251    /// Its `[files]` and `[scaffolds]` as written, taken for their paths to be rendered.
252    pub(crate) fn take_specs(&mut self) -> Specs {
253        mem::take(&mut self.specs)
254    }
255
256    /// Sets its files, and its scaffolds' sentinels, rendered.
257    pub(crate) fn set_files(
258        &mut self, files: BTreeMap<RelPath, LayerFile>,
259        scaffolds: BTreeMap<ScaffoldName, Vec<Pattern>>,
260    ) {
261        self.files = files;
262        self.scaffolds = scaffolds;
263    }
264
265    /// Its files, to gate.
266    pub(crate) const fn files_mut(&mut self) -> &mut BTreeMap<RelPath, LayerFile> {
267        &mut self.files
268    }
269
270    /// Its payload as read, by path as written.
271    pub(crate) const fn written(&self) -> &Tree {
272        &self.written
273    }
274
275    /// Sets its rendered payload and starters, and digests them.
276    pub(crate) fn set_payload(&mut self, payload: Tree, starters: Tree) {
277        let mut hasher = blake3::Hasher::new();
278        hasher.update(self.manifest.as_bytes());
279        hasher.update(payload.digest().as_bytes());
280        hasher.update(starters.digest().as_bytes());
281        for feature in self.features.keys() {
282            hasher.update(feature.as_str().as_bytes());
283            hasher.update(&[0]);
284        }
285        self.digest = Digest::new(hasher.finalize());
286        self.payload = payload;
287        self.starters = starters;
288        self.written = Tree::default();
289    }
290}
291
292/// What a layer provides at one slot, overrides applied.
293#[derive(Clone, Debug)]
294pub(crate) struct Provided {
295    /// Index into the layers.
296    pub layer: usize,
297    /// How it is managed.
298    pub policy: Policy,
299    /// How a merge of its file is checked.
300    pub validate: Format,
301    /// Whether the file is written executable.
302    pub executable: bool,
303    /// Its part of the file; `None` when it provides the whole file.
304    pub portion: Option<Portion>,
305}
306
307/// One layer's part of a file.
308#[derive(Clone, Debug)]
309pub(crate) struct Portion {
310    /// How much of the file it is.
311    pub scope: Scope,
312    /// How it is found, read and written.
313    pub shape: Shape,
314    /// What the profile puts there, templates rendered, as [`Shape::read`] reads it.
315    pub content: Vec<u8>,
316    /// For keys, the payload's leaves as it writes them, which new keys follow.
317    pub payload: Written,
318    /// The payload file itself, templates rendered.
319    pub text: Vec<u8>,
320    /// Whether its starter, if it has one, is the file's.
321    pub starts: bool,
322    /// The file the target's starts from when it is absent, templates rendered.
323    pub starter: Option<Vec<u8>>,
324}
325
326impl Portion {
327    /// `file` with the part holding `content` over `keys`, as [`Shape::splice`] writes it.
328    ///
329    /// A file with nothing in it yet takes the profile's keys as the payload writes them, its
330    /// comments and layout included.
331    pub(crate) fn splice(
332        &self, file: &[u8], content: &[u8], keys: &BTreeSet<Key>, label: &str,
333    ) -> Result<Vec<u8>> {
334        let keyed = matches!(self.shape, Shape::Keys(_));
335        if keyed && content == self.content && file.trim_ascii().is_empty() {
336            return Ok(self.text.clone());
337        }
338        self.shape.splice(file, content, keys, &self.payload, label)
339    }
340}
341
342/// A target's layers, composed: each file has one provider, or one per part, and settings are
343/// decided.
344#[derive(Debug)]
345pub struct Resolved {
346    /// In the order they apply: each requirement before its requirers.
347    layers: Vec<Layer>,
348    /// Each slot's provider.
349    providers: BTreeMap<Slot, Provided>,
350    /// The settings in force.
351    settings: Settings,
352    /// Drivers the layers suggest.
353    suggestions: Vec<Suggestion>,
354    /// The answer to every declared variable.
355    answers: BTreeMap<VarName, String>,
356    /// What the graph settled that the target may not expect.
357    warnings: Vec<Warning>,
358}
359
360impl Resolved {
361    /// The layers, each requirement before its requirers.
362    #[must_use]
363    pub fn layers(&self) -> &[Layer] {
364        &self.layers
365    }
366
367    /// The layer whose profile is `name`.
368    #[must_use]
369    pub fn layer(&self, name: &str) -> Option<&Layer> {
370        self.layers.iter().find(|layer| layer.meta.name == *name)
371    }
372
373    /// The settings in force.
374    #[must_use]
375    pub const fn settings(&self) -> &Settings {
376        &self.settings
377    }
378
379    /// Merge drivers the layers suggest that the target has not configured.
380    #[must_use]
381    pub fn suggestions(&self) -> &[Suggestion] {
382        &self.suggestions
383    }
384
385    /// The answer to every declared variable.
386    #[must_use]
387    pub const fn answers(&self) -> &BTreeMap<VarName, String> {
388        &self.answers
389    }
390
391    /// What the graph settled that the target may not expect.
392    #[must_use]
393    pub fn warnings(&self) -> &[Warning] {
394        &self.warnings
395    }
396
397    /// The name of the layer that provides `entry`.
398    #[must_use]
399    pub fn provider(&self, entry: &Entry) -> Option<&ProfileName> {
400        Some(&self.layers.get(entry.want?.layer)?.meta.name)
401    }
402
403    /// The layer file behind `entry`, while a layer provides it.
404    #[must_use]
405    pub fn file(&self, entry: &Entry) -> Option<&LayerFile> {
406        self.layers.get(entry.want?.layer)?.files.get(&entry.path)
407    }
408
409    /// Every slot, its provider, and what it provides: a whole file's bytes, or a part's content.
410    pub(crate) fn provided(&self) -> impl Iterator<Item = (&Slot, &Provided, &[u8])> {
411        self.providers
412            .iter()
413            .filter_map(|(slot, provided)| Some((slot, provided, self.bytes(slot, provided)?)))
414    }
415
416    /// What the profile provides for `entry`: a whole file's bytes, or a part's content.
417    ///
418    /// # Errors
419    /// [`TargetError::NotManaged`], no layer provides it: only an entry with a
420    /// [`want`](Entry::want) has a payload.
421    pub(crate) fn payload(&self, entry: &Entry) -> Result<&[u8]> {
422        let slot = entry.slot();
423        let bytes = self.providers.get(&slot).and_then(|provided| self.bytes(&slot, provided));
424        bytes.ok_or_else(|| {
425            TargetError::NotManaged { path: entry.path.to_string(), managed: Vec::new() }.into()
426        })
427    }
428
429    /// What `provided` gives `slot`.
430    fn bytes<'a>(&'a self, slot: &Slot, provided: &'a Provided) -> Option<&'a [u8]> {
431        match &provided.portion {
432            Some(portion) => Some(&portion.content),
433            None => self.layers.get(provided.layer)?.payload.get(&slot.path),
434        }
435    }
436
437    /// `entry`'s part as its profile provides it, or provided before its gates turned it off;
438    /// `None` for a whole file.
439    pub(crate) fn portion(&self, entry: &Entry) -> Option<&Portion> {
440        self.providers.get(&entry.slot())?.portion.as_ref()
441    }
442}
443
444/// Resolves `target`'s layers into one profile, the target's overrides and settings on top.
445///
446/// Each source is read at its locked commit unless `refresh` moves it; then the graph is built,
447/// the variables answered, the paths rendered and gated, the layers composed, and their templates
448/// rendered.
449///
450/// # Errors
451/// - [`Error::Source`](crate::Error::Source), a source cannot be read.
452/// - [`Error::Profile`](crate::Error::Profile), a profile is not valid or not found, a feature is
453///   unknown, or the layers collide on a path or a setting the target does not decide.
454/// - [`Error::Target`](crate::Error::Target), a layer names no source, or an override or `refresh`
455///   names nothing.
456/// - [`VarError::Unanswered`](crate::VarError::Unanswered), a variable needs an answer; answer it
457///   with [`Target::answer`] and resolve again.
458pub fn resolve(target: &Target, cache: &Cache, refresh: Refresh<'_>) -> Result<Resolved> {
459    let moves = Moves::of(target, cache, refresh)?;
460    let (mut layers, warnings) = layers(target, cache, &moves)?;
461    let answers = vars::answers(&layers, target)?;
462    let context = Context::of(target, &layers);
463    vars::render_paths(&mut layers, &answers, &context)?;
464    gate::statics(&mut layers, &answers);
465    let config = target.config();
466    let mut providers = providers(&layers, &config.files)?;
467    vars::render(&mut layers, &answers, &context)?;
468    portions(&layers, &mut providers)?;
469    let settings = Settings::compose(&layers, &config.merge)?;
470    let suggestions = Settings::suggestions(&layers, &config.merge);
471    Ok(Resolved { layers, providers, settings, suggestions, answers, warnings })
472}
473
474/// The sources a resolution moves to what their refs name now.
475enum Moves {
476    /// None.
477    None,
478    /// Every one.
479    All,
480    /// These.
481    Only(Vec<Source>),
482}
483
484impl Moves {
485    /// What `refresh` moves in `target`: a source it names, or the source of a layer it names.
486    fn of(target: &Target, cache: &Cache, refresh: Refresh<'_>) -> Result<Self> {
487        let name = match refresh {
488            Refresh::None => return Ok(Self::None),
489            Refresh::All => return Ok(Self::All),
490            Refresh::Only(name) => name,
491        };
492        let config = target.config();
493        if let Some(source) = config.sources.iter().find(|(named, _)| named.as_str() == name) {
494            return Ok(Self::Only(vec![source.1.clone()]));
495        }
496        let configured = config.layers.iter().find(|layer| layer.profile.profile == *name);
497        if let Some(source) = configured.and_then(|layer| config.sources.get(&layer.profile.source))
498        {
499            return Ok(Self::Only(vec![source.clone()]));
500        }
501        let (layers, _) = layers(target, cache, &Self::None)?;
502        if let Some(layer) = layers.iter().find(|layer| layer.meta.name == *name) {
503            return Ok(Self::Only(vec![layer.source.clone()]));
504        }
505        let layers = layers.iter().map(|layer| layer.meta.name.to_string());
506        let names = layers.chain(config.sources.keys().map(ToString::to_string)).collect();
507        Err(TargetError::NoSuchLayer { name: name.to_owned(), names }.into())
508    }
509
510    /// Whether `source` moves.
511    fn moves(&self, source: &Source) -> bool {
512        match self {
513            Self::None => false,
514            Self::All => true,
515            Self::Only(sources) => sources.contains(source),
516        }
517    }
518}
519
520/// Every active profile of `target`, read and in order, and what the graph warns of.
521fn layers(target: &Target, cache: &Cache, moves: &Moves) -> Result<(Vec<Layer>, Vec<Warning>)> {
522    let config = target.config();
523    let configured = config
524        .layers
525        .iter()
526        .map(|layer| {
527            let source = config.sources.get(&layer.profile.source).ok_or_else(|| {
528                let sources = config.sources.keys().cloned().collect();
529                let name = layer.profile.source.clone();
530                let naming = config.layers.iter().filter(|l| l.profile.source == name);
531                let layers = naming.map(|l| l.profile.clone()).collect();
532                TargetError::NoSuchSource { name, sources, layers }
533            })?;
534            Ok(Configured {
535                source,
536                name: &layer.profile.profile,
537                features: &layer.features,
538                defaults: layer.default_features,
539            })
540        })
541        .collect::<Result<Vec<_>>>()?;
542    let mut loader = Loader { target, cache, moves, opened: Vec::new() };
543    let Graph { nodes, warnings } = Graph::build(&configured, &mut loader)?;
544    let layers = nodes.into_iter().map(|node| loader.layer(node)).collect::<Result<_>>()?;
545    Ok((layers, warnings))
546}
547
548/// Reads profiles for the graph: each source opened once, at one revision.
549struct Loader<'a> {
550    /// Whose layers.
551    target: &'a Target,
552    /// Where git sources are fetched.
553    cache: &'a Cache,
554    /// Which sources move off their locked commit.
555    moves: &'a Moves,
556    /// Every source opened so far.
557    opened: Vec<Opened>,
558}
559
560/// A source, opened at its revision, and its profiles by name.
561struct Opened {
562    /// The source.
563    source: Source,
564    /// Its files.
565    reader: Reader,
566    /// Its profiles.
567    index: Index,
568}
569
570/// A profile the graph read: where it is, and what it declares.
571pub(crate) struct Loaded {
572    /// Which opened source it is in.
573    opened: usize,
574    /// Its directory in the source.
575    dir: String,
576    /// Its manifest, parsed and checked.
577    manifest: Manifest,
578    /// Digest of its manifest as read.
579    digest: Digest,
580}
581
582impl Load for Loader<'_> {
583    type Profile = Loaded;
584
585    fn load(&mut self, source: &Source, name: &ProfileName) -> Result<Loaded> {
586        let opened = self.open(source)?;
587        let Some(Opened { reader, index, .. }) = self.opened.get(opened) else {
588            return Err(ProfileError::NoProfiles { location: source.to_string() }.into());
589        };
590        let dir = index.find(name, source)?.to_owned();
591        let path = if dir.is_empty() {
592            RelPath::new(MANIFEST)?
593        } else {
594            RelPath::new(&format!("{dir}/{MANIFEST}"))?
595        };
596        let read = reader.read("", slice::from_ref(&path))?;
597        let raw = read
598            .get(&path)
599            .ok_or_else(|| ProfileError::NoProfiles { location: source.to_string() })?;
600        let file = label(source, path.as_str());
601        let manifest: Manifest = from_toml(raw, &file)?;
602        if let Some(required) = &manifest.profile.devset
603            && !Version::parse(crate::VERSION).is_ok_and(|version| required.matches(&version))
604        {
605            let (profile, requires) = (manifest.profile.name, required.to_string());
606            return Err(ProfileError::Incompatible { profile, requires }.into());
607        }
608        manifest.check(&file)?;
609        Ok(Loaded { opened, dir, manifest, digest: Digest::of(raw) })
610    }
611
612    fn manifest(profile: &Loaded) -> &Manifest {
613        &profile.manifest
614    }
615}
616
617impl Loader<'_> {
618    /// The position of `source` among the opened: opened now, at its locked commit unless it
619    /// moves, if it was not already.
620    ///
621    /// # Errors
622    /// [`ProfileError::Diverged`], another ref of it is open: one source at two refs.
623    fn open(&mut self, source: &Source) -> Result<usize> {
624        if let Some(at) = self.opened.iter().position(|opened| opened.source == *source) {
625            return Ok(at);
626        }
627        if let Some(twin) = self.opened.iter().find(|opened| opened.source.twin(source)) {
628            let (first, second) = (twin.source.to_string(), source.to_string());
629            return Err(ProfileError::Diverged { first, second }.into());
630        }
631        let pin = if self.moves.moves(source) { None } else { self.target.pinned(source) };
632        let reader = source.open(self.target.root(), pin, self.cache)?;
633        let index = Index::read(&reader, source)?;
634        if index.names().next().is_none() {
635            return Err(ProfileError::NoProfiles { location: source.to_string() }.into());
636        }
637        self.opened.push(Opened { source: source.clone(), reader, index });
638        Ok(self.opened.len().saturating_sub(1))
639    }
640
641    /// The layer of `node`: its payload read, as written.
642    fn layer(&self, node: Node<Loaded>) -> Result<Layer> {
643        let Node { profile, name, source, features, required_by, configured, .. } = node;
644        let Loaded { opened, dir, manifest, digest } = profile;
645        let Some(Opened { reader, .. }) = self.opened.get(opened) else {
646            return Err(ProfileError::NoProfiles { location: source.to_string() }.into());
647        };
648        let paths: Vec<RelPath> = manifest.payload_paths().into_iter().collect();
649        let payload_dir =
650            if dir.is_empty() { PAYLOAD.to_owned() } else { format!("{dir}/{PAYLOAD}") };
651        let written = reader.read(&payload_dir, &paths)?;
652        if let Some(missing) = paths.into_iter().find(|path| written.get(path).is_none()) {
653            return Err(ProfileError::MissingPayload { profile: name, path: missing }.into());
654        }
655        let config = self.target.config();
656        let source_name = config
657            .sources
658            .iter()
659            .find(|(_, named)| **named == source)
660            .map(|(name, _)| name.clone());
661        let locked = self.target.locked(&source, &name);
662        let Manifest { profile: meta, merge, vars, features: declared, files, scaffolds, .. } =
663            manifest;
664        Ok(Layer {
665            rev: reader.rev().cloned(),
666            source,
667            source_name,
668            dir,
669            meta,
670            merge,
671            vars,
672            declared: declared.declared.into_keys().collect(),
673            features,
674            required_by: required_by.into_iter().collect(),
675            configured,
676            manifest: digest,
677            digest,
678            locked,
679            specs: (files, scaffolds),
680            scaffolds: BTreeMap::new(),
681            files: BTreeMap::new(),
682            written,
683            payload: Tree::default(),
684            starters: Tree::default(),
685        })
686    }
687}
688
689/// The one layer that provides each file, or each layer's part of it, with `overrides` applied.
690///
691/// Every layer's part is taken, so long as all own it in one scope, beside at most one starter: a
692/// whole `once` file, or a part's own. A target's `from` settles which layer provides the file:
693/// the starter, beside every layer's part, or the one layer that has it, whatever the scopes. A
694/// `from` naming a layer whose gates leave the file out stands aside.
695fn providers(
696    layers: &[Layer], overrides: &BTreeMap<RelPath, Override>,
697) -> Result<BTreeMap<Slot, Provided>> {
698    let mut owners: BTreeMap<&RelPath, Vec<usize>> = BTreeMap::new();
699    for (index, layer) in layers.iter().enumerate() {
700        for (path, file) in &layer.files {
701            if file.gated.is_none() {
702                owners.entry(path).or_default().push(index);
703            }
704        }
705    }
706    let listed = |path: &RelPath| layers.iter().any(|layer| layer.files.contains_key(path));
707    if let Some(path) = overrides.keys().find(|path| !listed(path)) {
708        let mut provided: Vec<RelPath> =
709            layers.iter().flat_map(|layer| layer.files.keys().cloned()).collect();
710        provided.sort();
711        provided.dedup();
712        return Err(TargetError::StaleOverride { path: path.clone(), provided }.into());
713    }
714    let mut providers = BTreeMap::new();
715    let mut folds = BTreeMap::<String, &RelPath>::new();
716    for (path, candidates) in owners {
717        providers.extend(provide(layers, path, candidates, overrides.get(path))?);
718        if let Some(other) = folds.insert(path.fold(), path)
719            && other != path
720        {
721            return Err(
722                ProfileError::FoldCollision { first: other.clone(), second: path.clone() }.into()
723            );
724        }
725    }
726    Ok(providers)
727}
728
729/// The providers of `path`, which the layers at `candidates` list and `over` overrides.
730fn provide(
731    layers: &[Layer], path: &RelPath, mut candidates: Vec<usize>, over: Option<&Override>,
732) -> Result<Vec<(Slot, Provided)>> {
733    let name = |index: usize| layers.get(index).map(|layer| layer.meta.name.clone());
734    let spec = |index: usize| {
735        layers.get(index).and_then(|layer| layer.files.get(path)).map(|file| &file.spec)
736    };
737    let policy = |index: usize| {
738        over.and_then(|o| o.policy)
739            .unwrap_or_else(|| spec(index).map_or_else(Policy::default, FileSpec::policy))
740    };
741    let starts = |index: usize| {
742        spec(index).is_some_and(|spec| match spec.scope {
743            Scope::File => policy(index) == Policy::Once,
744            Scope::Keys | Scope::Block => spec.starter.is_some(),
745        })
746    };
747    let mut starter = None;
748    if let Some(from) = over.and_then(|over| over.from.as_ref()) {
749        let listing: Vec<usize> = (0..layers.len())
750            .filter(|&i| layers.get(i).is_some_and(|l| l.files.contains_key(path)))
751            .collect();
752        if !listing.iter().any(|&i| name(i).as_ref() == Some(from)) {
753            let layers = listing.iter().filter_map(|&i| name(i)).collect();
754            return Err(ProfileError::NotProvided {
755                path: path.clone(),
756                from: from.clone(),
757                layers,
758            }
759            .into());
760        }
761        // A `from` whose layer's gates leave the file out stands aside.
762        if let Some(picked) = candidates.iter().copied().find(|&i| name(i).as_ref() == Some(from)) {
763            if starts(picked) {
764                let part = |i: usize| spec(i).is_some_and(|spec| spec.scope != Scope::File);
765                candidates.retain(|&i| i == picked || part(i));
766                starter = Some(picked);
767            } else {
768                candidates = vec![picked];
769            }
770        }
771    }
772    let starts = |index: usize| starter.map_or_else(|| starts(index), |picked| picked == index);
773    let claims: Vec<Claim> = candidates
774        .iter()
775        .filter_map(|&i| {
776            Some(Claim {
777                layer: name(i)?,
778                scope: spec(i)?.scope,
779                policy: policy(i),
780                starts: starts(i),
781            })
782        })
783        .collect();
784    share(path, &claims)?;
785    let mut provided = Vec::with_capacity(candidates.len());
786    for index in candidates {
787        let (Some(spec), Some(owner)) = (spec(index), name(index)) else { continue };
788        let validate = over.and_then(|o| o.validate).or(spec.validate);
789        let format = validate.unwrap_or_else(|| Format::of(path));
790        let shape = Shape::of(spec.scope, path, format, spec.comment, owner.as_str())?;
791        let portion = shape.map(|shape| Portion {
792            scope: spec.scope,
793            shape,
794            content: Vec::new(),
795            payload: Written::default(),
796            text: Vec::new(),
797            starts: starts(index),
798            starter: None,
799        });
800        let slot = Slot { path: path.clone(), part: portion.as_ref().map(|_| owner.to_string()) };
801        let policy = policy(index);
802        provided.push((
803            slot,
804            Provided {
805                layer: index,
806                policy,
807                validate: format,
808                executable: spec.executable,
809                portion,
810            },
811        ));
812    }
813    Ok(provided)
814}
815
816/// How one layer owns a file, for [`share`].
817struct Claim {
818    /// The layer.
819    layer: ProfileName,
820    /// How much of the file it owns.
821    scope: Scope,
822    /// How it manages it.
823    policy: Policy,
824    /// Whether it starts the file when the target has none.
825    starts: bool,
826}
827
828/// Checks that the layers `claims` names may share `path`: one of them provides it, or all own
829/// parts of it in one scope, beside at most one starter.
830///
831/// # Errors
832/// - [`ProfileError::Collision`], several own it whole.
833/// - [`ProfileError::Starters`], several would start it.
834/// - [`ProfileError::Scopes`], they own it in different scopes, or one owns it whole and is not
835///   `once`.
836fn share(path: &RelPath, claims: &[Claim]) -> Result<()> {
837    if claims.len() <= 1 {
838        return Ok(());
839    }
840    let named = |keep: &dyn Fn(&Claim) -> bool| -> Vec<ProfileName> {
841        claims.iter().filter(|claim| keep(claim)).map(|claim| claim.layer.clone()).collect()
842    };
843    let wholes = named(&|claim| claim.scope == Scope::File);
844    if wholes.len() > 1 {
845        return Err(ProfileError::Collision { path: path.clone(), layers: wholes }.into());
846    }
847    let mut parts =
848        claims.iter().filter(|claim| claim.scope != Scope::File).map(|claim| claim.scope);
849    let one_scope = parts.next().is_none_or(|first| parts.all(|scope| scope == first));
850    let owned =
851        claims.iter().any(|claim| claim.scope == Scope::File && claim.policy != Policy::Once);
852    if !one_scope || owned {
853        let layers = claims.iter().map(|claim| (claim.layer.clone(), claim.scope)).collect();
854        return Err(ProfileError::Scopes { path: path.clone(), layers }.into());
855    }
856    let starters = named(&|claim| claim.starts);
857    if starters.len() > 1 {
858        return Err(ProfileError::Starters { path: path.clone(), layers: starters }.into());
859    }
860    Ok(())
861}
862
863/// Reads each part's content, and its starter, out of its layer's rendered payload, and checks
864/// that no two layers own overlapping keys of one file.
865fn portions(layers: &[Layer], providers: &mut BTreeMap<Slot, Provided>) -> Result<()> {
866    let mut owned: Vec<(&RelPath, usize, Key)> = Vec::new();
867    for (slot, provided) in providers.iter_mut() {
868        let Some(portion) = &mut provided.portion else {
869            continue;
870        };
871        let Some(layer) = layers.get(provided.layer) else {
872            continue;
873        };
874        let bytes = layer.payload.get(&slot.path).ok_or_else(|| ProfileError::MissingPayload {
875            profile: layer.meta.name.clone(),
876            path: slot.path.clone(),
877        })?;
878        let written =
879            layer.files.get(&slot.path).map_or(slot.path.as_str(), |file| file.written.as_str());
880        let label = layer.label(&format!("{PAYLOAD}/{written}"));
881        (portion.content, portion.payload) = portion.shape.read(bytes, &label)?;
882        portion.text = bytes.to_vec();
883        portion.starter =
884            layer.starters.get(&slot.path).filter(|_| portion.starts).map(<[u8]>::to_vec);
885        let keys = portion.shape.keys(&portion.content);
886        owned.extend(keys.into_iter().map(|key| (&slot.path, provided.layer, key)));
887    }
888    owned.sort_by_key(|&(path, layer, _)| (path, layer));
889    let name = |index: usize| layers.get(index).map(|l| l.meta.name.clone());
890    for (at, (path, layer, key)) in owned.iter().enumerate() {
891        let earlier = owned.get(..at).unwrap_or_default().iter().rev();
892        let clash = earlier
893            .take_while(|(other, ..)| other == path)
894            .find(|(_, other, theirs)| other != layer && overlap(key, theirs));
895        if let Some((_, other, theirs)) = clash
896            && let (Some(first), Some(second)) = (name(*other), name(*layer))
897        {
898            let key = display(if key.len() <= theirs.len() { key } else { theirs });
899            return Err(ProfileError::Overlap { path: (*path).clone(), key, first, second }.into());
900        }
901    }
902    Ok(())
903}