`--preset=`
-: If specified, only build the given preset. Can be specified multiple
- times to build multiple presets. If not specified, all presets are
- built. See the `Presets` section for more information.
+: If specified, only build the given presets. Can be specified multiple
+ times to build multiple presets. All the given presets and their
+ dependencies are built. If not specified, all presets are built. See
+ the `Presets` section for more information.
## Supported output formats
| `ImageVersion=` | no | yes | match fails |
| `Bootable=` | no | no | match auto feature |
+### [Preset] Section
+
+`Dependencies=`, `--dependency=`
+
+: The presets that this preset depends on specified as a comma-separated
+ list. All presets configured in this option will be built before this
+ preset and will be pulled in as dependencies of this preset when
+ `--preset` is used.
+
### [Distribution] Section
`Distribution=`, `--distribution=`, `-d`
the `.conf` extension.
When presets are found in `mkosi.presets/`, mkosi will build the
-configured presets (or all of them if none were explicitly configured
-using `--preset=`) in alphanumerical order. To enforce a certain build
-order, preset names can be numerically prefixed (e.g. `00-initrd.conf`).
-The numerical prefix will be removed from the preset name during parsing,
-along with the `.conf` suffix (`00-initrd.conf` becomes `initrd`). The
-preset name is used for display purposes and also as the default output
-name if none is explicitly configured.
+configured preset and its dependencies (or all of them if no presets
+were explicitly configured using `--preset=`). To add dependencies
+between presets, the `Dependencies=` setting can be used.
When presets are defined, mkosi will first read the global configuration
(configuration outside of the `mkosi.presets/` directory), followed by
the preset specific configuration. This means that global configuration
takes precedence over preset specific configuration.
-Later presets can refer to outputs of earlier presets. Specifically, for
-the following options, mkosi will only check whether the inputs exist
-just before building the preset:
+Presets can refer to outputs of presets they depend on. Specifically,
+for the following options, mkosi will only check whether the inputs
+exist just before building the preset:
- `BaseTrees=`
- `PackageManagerTrees=`
- `ToolsTree=`
- `Initrds=`
-To refer to outputs of earlier presets, simply configure any of these
-options with a relative path to the location of the output to use in the
-earlier preset's output directory.
+To refer to outputs of a preset's dependencies, simply configure any of
+these options with a relative path to the output to use in the output
+directory of the dependency.
A good example on how to use presets can be found in the systemd
repository: https://github.com/systemd/systemd/tree/main/mkosi.presets.
import enum
import fnmatch
import functools
+import graphlib
import inspect
import operator
import os.path
import platform
import shlex
import shutil
-import string
import subprocess
import sys
import textwrap
access the value from state.
"""
+ dependencies: tuple[str]
distribution: Distribution
release: str
mirror: Optional[str]
class MkosiConfigParser:
SETTINGS = (
+ MkosiConfigSetting(
+ dest="dependencies",
+ long="--dependency",
+ section="Preset",
+ parse=config_make_list_parser(delimiter=","),
+ help="Specify other presets that this preset depends on",
+ ),
MkosiConfigSetting(
dest="distribution",
short="-d",
"--preset",
action="append",
dest="presets",
+ metavar="PRESET",
default=[],
- help="Build the specified preset",
+ help="Build the specified presets and their dependencies",
)
parser.add_argument(
"--nspawn-keep-unit",
delattr(namespace, "cache")
print("Warning: --cache is no longer supported")
+ def resolve_deps(self, args: MkosiArgs, presets: Sequence[MkosiConfig]) -> list[MkosiConfig]:
+ graph = {p.preset: p.dependencies for p in presets}
+
+ if args.presets:
+ if any((missing := p) not in graph for p in args.presets):
+ die(f"No preset found with name {missing}")
+
+ deps = set()
+ queue = [*args.presets]
+
+ while queue:
+ if (preset := queue.pop(0)) not in deps:
+ deps.add(preset)
+ queue.extend(graph[preset])
+
+ presets = [p for p in presets if p.preset in deps]
+
+ graph = {p.preset: p.dependencies for p in presets}
+
+ try:
+ order = list(graphlib.TopologicalSorter(graph).static_order())
+ except graphlib.CycleError as e:
+ die(f"Preset dependency cycle detected: {' => '.join(e.args[1])}")
+
+ return sorted(presets, key=lambda p: order.index(p.preset))
+
def parse(self, argv: Optional[Sequence[str]] = None) -> tuple[MkosiArgs, tuple[MkosiConfig, ...]]:
presets = []
namespace = argparse.Namespace()
self.parse_config(Path("."), namespace)
if Path("mkosi.presets").exists():
- for p in sorted(Path("mkosi.presets").iterdir()):
+ for p in Path("mkosi.presets").iterdir():
if not p.is_dir() and not p.suffix == ".conf":
continue
- name = p.name.lstrip(string.digits + "-").removesuffix(".conf")
+ name = p.name.removesuffix(".conf")
if not name:
die(f"{p} is not a valid preset name")
- if args.presets and name not in args.presets:
- continue
cp = copy.deepcopy(namespace)
# infrastructure scripts rather than image-specific configuration.
self.backward_compat_stubs(namespace)
- return args, tuple(load_config(ns) for ns in presets)
+ presets = [load_config(ns) for ns in presets]
+ presets = self.resolve_deps(args, presets)
+
+ return args, tuple(presets)
def load_credentials(args: argparse.Namespace) -> dict[str, str]:
Verb: {bold(args.verb)}
Cmdline: {bold(" ".join(args.cmdline))}
+ {bold("PRESET")}:
+ Dependencies: {line_join_list(config.dependencies)}
+
{bold("DISTRIBUTION")}:
Distribution: {bold(config.distribution)}
Release: {bold(none_to_na(config.release))}