Skip to main content

compatdb_core/
table.rs

1//! The table of rules, each with where it comes from.
2
3#[cfg(doc)]
4use crate::Origin;
5use crate::{ENV_VAR, Exe, FILE_ENV_VAR, Resolution, Rule, Specificity, VersionInfo, builtin};
6
7/// Where a rule of a [`Table`] comes from. It decides how the log names the
8/// rule's values (see [`Origin`]) and whether its `env` entries are defaults
9/// (see [`Origin::env_is_default`]). Never serialized.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum Source {
12    /// Compiled into `compatdb.so`: a rule of [`Table::builtin`].
13    BuiltIn,
14    /// Line `n` of [`ENV_VAR`], counted from 1 as [`Table::parse_lenient`]
15    /// reads the value, blank lines and a legacy header included.
16    Line(usize),
17    /// Line `n` of the file [`FILE_ENV_VAR`] names, counted from 1 as
18    /// [`Table::parse_file_lenient`] reads the file. The file counts its own
19    /// lines, so a rule here can share its number with a [`Source::Line`].
20    FileLine(usize),
21}
22
23/// Which of the two texts of the caller's rules a line is in. Each counts its
24/// own lines, so a line number alone does not say where a problem is.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum RulesText {
27    /// The value of [`ENV_VAR`].
28    Variable,
29    /// The file [`FILE_ENV_VAR`] names.
30    File,
31}
32
33impl RulesText {
34    /// How the log names the text: by its variable.
35    pub(crate) const fn name(self) -> &'static str {
36        match self {
37            Self::Variable => ENV_VAR,
38            Self::File => FILE_ENV_VAR,
39        }
40    }
41
42    /// The source of a rule on line `line` of the text.
43    pub(crate) const fn source(self, line: usize) -> Source {
44        match self {
45            Self::Variable => Source::Line(line),
46            Self::File => Source::FileLine(line),
47        }
48    }
49}
50
51/// The whole database: the built-in rules followed by the rules of
52/// [`ENV_VAR`] and then those of the file [`FILE_ENV_VAR`] names, in the
53/// order they were declared, each with its [`Source`].
54///
55/// A launcher builds the rules it sends with [`Table::new`] and
56/// [`Table::push`] and writes them with `to_string()`, which is the value of
57/// [`ENV_VAR`]; [`Table::with_builtin`] puts the built-in rules in front, as
58/// `compatdb.so` does, to see what a process would get.
59#[derive(Clone, Default, Debug, PartialEq, Eq)]
60pub struct Table {
61    /// The rules, in the order they were declared, which is the order
62    /// [`Table::resolve`] folds rules of equal place in its order in.
63    pub(crate) rules: Vec<Rule>,
64    /// Where each rule comes from, at the same index.
65    pub(crate) sources: Vec<Source>,
66}
67
68impl Table {
69    /// The table without rules.
70    #[must_use]
71    pub const fn new() -> Self {
72        Self {
73            rules: Vec::new(),
74            sources: Vec::new(),
75        }
76    }
77
78    /// The table of the built-in rules ([`builtin::rules`]), which
79    /// `compatdb.so` puts in front of the rules of [`ENV_VAR`].
80    #[must_use]
81    pub fn builtin() -> Self {
82        Self::of_builtin(builtin::rules())
83    }
84
85    /// The table of `rules`, each a built-in rule.
86    pub(crate) fn of_builtin(rules: Vec<Rule>) -> Self {
87        Self {
88            sources: vec![Source::BuiltIn; rules.len()],
89            rules,
90        }
91    }
92
93    /// This table with the built-in rules in front of its own, as
94    /// `compatdb.so` builds its table.
95    #[must_use]
96    pub fn with_builtin(self) -> Self {
97        let mut table = Self::builtin();
98        table.append(self);
99        table
100    }
101
102    /// Add the rules of `other` after these, with their sources, as
103    /// `compatdb.so` adds the rules of the file [`FILE_ENV_VAR`] names after
104    /// those of [`ENV_VAR`].
105    ///
106    /// A table that holds a file's rules is for [`Table::resolve`] and
107    /// [`Table::preview`]. `Display` writes those rules too, so its text is
108    /// no value for [`ENV_VAR`] beside the file: they would fold twice.
109    pub fn append(&mut self, other: Self) {
110        self.rules.extend(other.rules);
111        self.sources.extend(other.sources);
112    }
113
114    /// Add `rule` after the others, with the source `source`.
115    pub(crate) fn push_from(&mut self, rule: Rule, source: Source) {
116        self.rules.push(rule);
117        self.sources.push(source);
118    }
119
120    /// Add `rule` after the others, as a rule of [`ENV_VAR`] on the line
121    /// after the last line of that variable in the table, which for a table
122    /// built with this method alone is its line in the value the table
123    /// writes.
124    pub fn push(&mut self, rule: Rule) {
125        let last = self
126            .sources
127            .iter()
128            .filter_map(|source| match source {
129                Source::Line(n) => Some(*n),
130                Source::BuiltIn | Source::FileLine(_) => None,
131            })
132            .max()
133            .unwrap_or(0);
134        self.push_from(rule, Source::Line(last.saturating_add(1)));
135    }
136
137    /// The rules, in the order they were declared.
138    #[must_use]
139    pub fn rules(&self) -> &[Rule] {
140        &self.rules
141    }
142
143    /// Where each rule of [`Table::rules`] comes from, at the same index.
144    #[must_use]
145    pub fn sources(&self) -> &[Source] {
146        &self.sources
147    }
148
149    /// Each rule with its source, in the order they were declared.
150    pub(crate) fn entries(&self) -> impl Iterator<Item = (Source, &Rule)> {
151        self.sources.iter().copied().zip(&self.rules)
152    }
153
154    /// What a process with the image basename `exe` and the version
155    /// resource `version` would get from `compatdb.so` with this table in
156    /// [`ENV_VAR`]: the built-in rules followed by this table's own (a
157    /// built-in rule in this table is left out, since the library has it
158    /// already), resolved, with [`Resolution::fold_derived_env`] done.
159    ///
160    /// Which env entries apply also depends on the environment the process
161    /// starts with, which [`Resolution::env_steps`] takes.
162    ///
163    /// The process has no Steam app id here, so a rule for one
164    /// ([`Exe::Steam`]) does not apply.
165    #[must_use]
166    pub fn preview(&self, exe: &str, version: &VersionInfo) -> Resolution {
167        let mut table = Self::builtin();
168        for (source, rule) in self.entries() {
169            if source != Source::BuiltIn {
170                table.rules.push(rule.clone());
171                table.sources.push(source);
172            }
173        }
174        let mut resolution = table.resolve(exe, version, None);
175        resolution.fold_derived_env();
176        resolution
177    }
178
179    /// The `WINEDEBUG` entries the `debug_channels` of this table's rules
180    /// stand for, in table order. A launcher joins them with `,` after its
181    /// own entries and starts Wine with the result, since Wine reads the
182    /// variable before `compatdb.so` loads.
183    ///
184    /// An entry of a rule for an executable is scoped to it as
185    /// `<exe>:<entry>`, which is Wine's own syntax: Wine compares the prefix
186    /// with the image file name ASCII-case-insensitively and knows nothing
187    /// of fingerprints, so the entry reaches every program of that name. An
188    /// entry of a rule with `exe` `*` is unscoped. Wine lets a later entry
189    /// win for the same channel.
190    ///
191    /// An entry of a rule for a Steam app id is left out: Wine has no scope
192    /// for an app id, and without one the entry would reach every process.
193    /// [`Rule::set`] refuses such an entry.
194    #[must_use]
195    pub fn winedebug(&self) -> Vec<String> {
196        self.rules
197            .iter()
198            .flat_map(|rule| {
199                rule.debug_channels
200                    .iter()
201                    .filter_map(move |channel| match &rule.exe {
202                        Exe::Any => Some(channel.to_string()),
203                        Exe::Name(name) => Some(format!("{name}:{channel}")),
204                        Exe::Steam(_) => None,
205                    })
206            })
207            .collect()
208    }
209
210    /// Every pair of rules that matches exactly the same processes, by their
211    /// index in the table. A duplicate can only be a mistake, so the
212    /// launcher warns about it.
213    ///
214    /// Plain wildcard rules (`exe = *` with no fingerprint) are left out: they
215    /// are defaults that are meant to stack, one per setting, so several of
216    /// them matching every process is not a mistake.
217    #[must_use]
218    pub fn duplicate_matchers(&self) -> Vec<(usize, usize)> {
219        let mut pairs = Vec::new();
220        for (i, rule) in self.rules.iter().enumerate() {
221            if rule.specificity() == Specificity::Any {
222                continue;
223            }
224            for (j, other) in self.rules.iter().enumerate().skip(i.saturating_add(1)) {
225                if rule.matches_the_same_as(other) {
226                    pairs.push((i, j));
227                }
228            }
229        }
230        pairs
231    }
232}
233
234#[cfg(test)]
235mod tests;