path_read_text: bool = False
path_secret: bool = False
path_default: bool = True
+ specifier: str = ""
# settings for argparse
short: Optional[str] = None
dest="distribution",
short="-d",
section="Distribution",
+ specifier="d",
parse=config_make_enum_parser(Distribution),
match=config_make_enum_matcher(Distribution),
default_factory=config_default_distribution,
dest="release",
short="-r",
section="Distribution",
+ specifier="r",
parse=config_parse_string,
match=config_make_string_matcher(),
default_factory=config_default_release,
MkosiConfigSetting(
dest="architecture",
section="Distribution",
+ specifier="a",
parse=config_make_enum_parser(Architecture),
match=config_make_enum_matcher(Architecture),
default=Architecture.native(),
metavar="FORMAT",
name="Format",
section="Output",
+ specifier="t",
parse=config_make_enum_parser(OutputFormat),
match=config_make_enum_matcher(OutputFormat),
default=OutputFormat.disk,
short="-o",
metavar="NAME",
section="Output",
+ specifier="o",
parse=config_parse_filename,
help="Output name",
),
metavar="DIR",
name="OutputDirectory",
section="Output",
+ specifier="O",
parse=config_make_path_parser(required=False),
paths=("mkosi.output",),
default_factory=lambda _: Path.cwd(),
)
SETTINGS_LOOKUP_BY_NAME = {name: s for s in SETTINGS for name in [s.name, *s.compat_names]}
SETTINGS_LOOKUP_BY_DEST = {s.dest: s for s in SETTINGS}
+SETTINGS_LOOKUP_BY_SPECIFIER = {s.specifier: s for s in SETTINGS if s.specifier}
MATCHES = (
MkosiMatch(
# Compare inodes instead of paths so we can't get tricked by bind mounts and such.
parsed_includes: set[tuple[int, int]] = set()
+ def expand_specifiers(text: str, namespace: argparse.Namespace, defaults: argparse.Namespace) -> str:
+ percent = False
+ result: list[str] = []
+
+ for c in text:
+ if percent:
+ percent = False
+
+ if c == "%":
+ result += "%"
+ else:
+ s = SETTINGS_LOOKUP_BY_SPECIFIER.get(c)
+ if not s:
+ logging.warning(f"Unknown specifier '%{c}' found in {text}, ignoring")
+ continue
+
+ if (v := finalize_default(s, namespace, defaults)) is None:
+ logging.warning(f"Setting {s.name} specified by specifier '%{c}' in {text} is not yet set, ignoring")
+ continue
+
+ result += str(v)
+ elif c == "%":
+ percent = True
+ else:
+ result += c
+
+ if percent:
+ result += "%"
+
+ return "".join(result)
+
@contextlib.contextmanager
def parse_new_includes(
namespace: argparse.Namespace,
canonical = s.name if k == name else f"@{s.name}"
logging.warning(f"Setting {k} is deprecated, please use {canonical} instead.")
+ v = expand_specifiers(v, namespace, defaults)
+
with parse_new_includes(namespace, defaults):
setattr(ns, s.dest, s.parse(v, getattr(ns, s.dest, None)))
Additionally, the suffixes `K`, `M` and `G` can be used to specify a
size in kilobytes, megabytes and gigabytes respectively.
+## Specifiers
+
+The current value of various settings can be accessed when parsing
+configuration files by using specifiers. To write a literal `%`
+character in a configuration file without treating it as a specifier,
+use `%%`. The following specifiers are understood:
+
+| Setting | Specifier |
+|--------------------|-----------|
+| `Distribution=` | `%d` |
+| `Release=` | `%r` |
+| `Architecture=` | `%a` |
+| `Format=` | `%t` |
+| `Output=` | `%o` |
+| `OutputDirectory=` | `%O` |
+
## Supported distributions
Images may be created containing installations of the following
config_parse_bytes("-3M")
with pytest.raises(SystemExit):
config_parse_bytes("-4G")
+
+
+def test_specifiers(tmp_path: Path) -> None:
+ d = tmp_path
+
+ (d / "mkosi.conf").write_text(
+ """\
+ [Distribution]
+ Distribution=ubuntu
+ Release=lunar
+ Architecture=arm64
+
+ [Output]
+ OutputDirectory=abcde
+ Output=test
+
+ [Distribution]
+ ImageId=%d~%r~%a
+
+ [Output]
+ CacheDirectory=%O/%o
+ """
+ )
+
+ with chdir(d):
+ _, [config] = parse_config()
+ assert config.distribution == Distribution.ubuntu
+ assert config.image_id == "ubuntu~lunar~arm64"
+ assert config.cache_dir == Path.cwd() / "abcde/test" / config.image_id