Skip to content

Build system integration #1229

Closed
Closed
@DaanDeMeyer

Description

@DaanDeMeyer

This description assumes #1228 is already implemented and we do our builds inside regular directories on the host filesystem.

I've been thinking quite a bit on how to integrate mkosi built initrds into mkosi itself. My initial idea was to add another build stage that builds the initrd and then uses it in the final image. This is complicated because of several reasons:

  • We have even more state to keep track of in mkosi itself, increasing overall complexity
  • We have to figure out how to handle configuring the built initrd. If we'd want to keep a single config, we'd have to add extra options to configure the initrd specifically
  • Doesn't take into account prebuilt initrds supplied via other means

Given the above points, this doesn't seem like the way to go. Instead, adding an --initrd option that takes a list of initrds to use for the final image is simpler and more generic, since the prebuilt initrd can come from any source instead of just mkosi.

The issue with the --initrd option is that we'd need to run mkosi twice to first generate the initrd first and the final image second. This led me to start thinking about how we could effectively manage multiple invocations of mkosi, with dependencies between them. This is pretty much what build systems are made to do. So I started thinking about what we could do to make mkosi integrate into popular build systems, and I came up with the following:

We should record all configuration used to build the image to the manifest

We already wanted to do this for other reasons but it's important to have a detailed description of the image and the configuration used to build it if we want to be able to build mkosi images in multiple separate invocations of mkosi

We should be able to merge instances of MkosiConfig

When building an image in multiple invocations of mkosi, we need to be able to specify only additional configuration that needs to be applied on top of the base image. We'd read the base MkosiConfig from the base image manifest, and any additional configuration to be applied on top, and merge them into the final configuration.

Also of note is that the base image manifest should also be included in the manifest as part of the config.

Mkosi should be able to use a previously built image as a base to start working from

If we want to be able to build images in multiple invocations of mkosi, we need to be able to take an existing image and extend it. We already support this in a very limited way using --base-image, but we can do much better. First, instead of taking a directory/image as an argument, --base-image should take a mkosi manifest as its argument. The manifest records where the base image was stored and the configuration used to build it. We merge it with any additional configuration for the new image and use that as the final config. The new image built only applies steps that weren't yet done in the base image build.

The base image is not modified, we always use an overlay when building (if there is no base we just use an empty overlay). We keep the existing output formats, but also add a new output format overlay, which just keeps the overlay we used when writing the image. When the base image is itself an overlay, we recurse through the base image manifests until the root or until we find a base image that is not an overlay, create a new overlayfs mount that combines all these overlays and use that as the base image.

This recursion extends to mkosi shell and mkosi boot, where we have systemd-nspawn boot into the overlay mount. For qemu, we'd need an extra step to run repart on the combined overlay to convert it into a disk image.

Note that we won't just build from any base image, the distribution, release, architecture and probably a few more things can't be changed.

We should remove --incremental and --build-script

we can remove --incremental and re-implement these outside of mkosi if we implement the above changes. The best way to explain this is by an example so I'll describe how I would use this in the systemd repo.

We replace --build-script by extending shell or adding a new verb run-script that runs a script in a given image. The verb would take the script, source directory, build directory, and maybe a few other things.

In systemd's meson build scripts, we'd have a series of targets that run mkosi to build a set of images and do things with them. I'll list the targets and their purpose:

  • base image: This is the starting image that everything else builds on. It contains the runtime dependencies of systemd. Output is format is overlay.
  • build image: This extends the base image and adds the dependencies required to build systemd, so devel packages, compilers and build systems. Output format is overlay
  • configure systemd: This target depends on the build image and runs meson in the build image, source dir and mkosi.builddir/ are mounted in as with --build-script
  • build systemd: Same as above but runs ninja to build systemd and installs it, destdir is also mounted in here
  • lsp: This target runs clangd in the build image and depends on the configure systemd target to make sure compile_commands.json is available. This would use meson's run_target() functionality (and depend on ninja 1.11 so we can silence ninja's output).
  • initrd image: This extends the base image and depends on the "build systemd" target. It adds some extra packages specific to the initrd and installs the built version of systemd on top and packages everything up as a cpio.
  • dev image: This image extends the base image and depends on the initrd image and the build systemd target. It adds all the extra tooling to make the image useful for testing and developing systemd, copies in the systemd installation directory and uses the prebuilt initrd as its initrd. Output format is overlay.
  • build-shell: This target depends on the build image and configure systemd and gives you a shell in the build image with source dir and build dir mounted.
  • shell: This target depends on the dev image and gives you a shell in it
  • boot: This target depends on the dev image and boots it with systemd-nspawn
  • disk image: Depends on the dev image and packages it into a disk image with systemd-repart. Output is a disk image
  • qemu: This target depends on the disk image and boots it with qemu

The above would be the basic setup. But we'd be able to go even further with this when we start using mkosi for systemd's integration tests. Since each integration test could be a test in meson that would depend on the dev image/disk image and run the integration test in it. If needed, for some tests we could define custom images that extend the dev/disk image even more.

Implementation wise, the image targets would all use meson's custom_target() and the interactive commands would use meson's run_target(). But this isn't specific to meson and can be adapted to any build system.

Given the above setup, there's no more need for --incremental since it's implemented in the build system. There's also no more need for the notion of build images, since that's moved to the build system as well. mkosi itself would be stripped down to building a single image based on the provided options and writing the manifest after building the image. All features that involve scheduling multiple image builds and running commands in them would be moved to the build system.

In the systemd repo, you could even have the mkosi targets depend on the repart, nspawn, and other targets to first build all these components on the host and then use these directly when building the images.

Conclusion

I think using mkosi with a build system would be a very powerful way to schedule multiple image builds and the interaction between them while simplifying mkosi itself due to not needing to deal itself with scheduling multiple image builds. In terms of breaking backwards compat, we'd be removing --incremental, and replacing --build-script with a similar verb.

We could go slightly further and require --initrd for bootable images remove dracut support and require the initrd to be supplied by the user.. But this would mean building bootable images would no longer be possible without a build system, at least not until distro prebuilt initrds become a thing.

This turned into quite a wall of text. I'm curious as to what others think of this approach.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions