Configuration overrides in Mir¶
miral::ConfigFile and miral::live_config::IniFileWithOverrides together implement a
layered configuration system: a base file plus zero or more drop-in override files, loaded
in a single transaction and optionally reloaded on change.
This pattern lets distributors, site administrators, and users each supply a fragment without modifying each other’s files.
File discovery and XDG roots¶
ConfigFile accepts either a bare filename or a path.
Bare filename (e.g., "myapp.conf"): ConfigFile searches the following roots in
priority order and uses the highest-priority root that contains the file:
$XDG_CONFIG_HOME(falls back to$HOME/.config)Each entry in
$XDG_CONFIG_DIRS(falls back to/etc/xdg)
Path with a parent directory (e.g., /etc/myapp/myapp.conf): the parent directory
is used as the first root; $XDG_CONFIG_DIRS entries are still appended.
Override files are always collected from <base-filename>.d/ subdirectories under
every root, not just the root that provided the base file. Only files whose extension
matches the filter passed to ConfigFile are included.
Override ordering and precedence¶
The base file is loaded first.
Override files are collected from all roots and de-duplicated by basename: if two roots provide a file with the same basename, the higher-priority root’s copy is used.
The resulting set is sorted lexicographically by basename and applied in that order. Taking whole number prefixes into account.
Using numeric prefixes (e.g., 10-site.conf, 90-user.conf) is a conventional
way to control ordering predictably. Our sorting works such that 5-foo.conf <
10-bar.conf. Pure lexicographic sorting would place 10-bar.conf before
5-foo.conf.
When files are added or removed at runtime, priority is re-evaluated from scratch on the next reload.
Merge semantics¶
IniFileWithOverrides applies these rules across the ordered list of files:
Key type |
Rule |
|---|---|
Scalar ( |
Last file to set the key wins. |
Array ( |
Values accumulate across files. |
Array with empty assignment ( |
Clears all previously accumulated values; subsequent entries in the same or later files start fresh. |
Key absent from all files |
Handler receives the preset value (if registered with one), otherwise |
Invalid scalar value |
Handler receives the preset value (if registered with one), otherwise |
Invalid array entry |
The invalid entry is ignored (a warning is logged); valid entries in the same file are kept. If all entries in a transaction are invalid, the handler receives the preset value (if registered), otherwise |
All attribute handlers fire before on_done, and both fire exactly once per load()
call.
Reload model¶
With ConfigFile::Mode::reload_on_change, ConfigFile monitors the relevant directories.
On any change, it re-evaluates the full file list and calls the
OverrideLoader callback with an OverridesList classifying each file as:
unchanged — present in both old and new state, content unchanged
fresh — new file (added or first seen)
modified — present before and after, content changed
dropped — was present, no longer found
Relationship between ConfigFile and the live-config store¶
ConfigFile handles discovery and monitoring: it locates files using XDG rules,
assembles an OverridesList, and fires a callback on change.
IniFileWithOverrides (or IniFile for single-file use) handles parsing and merging:
it takes an OverridesList, parses each file as INI, and dispatches typed attribute
handlers.
The two are composed explicitly:
miral::live_config::IniFileWithOverrides config;
miral::ConfigFile config_file{
runner,
"myapp.conf",
miral::ConfigFile::Mode::reload_on_change,
[&config](miral::live_config::OverridesList const& changes) { config.load(changes); },
".conf"};
Use IniFile instead of IniFileWithOverrides when you have a single config file with no
drop-in overrides (ConfigFile with the single-file Loader constructor).