Multi-OS (aka Base) Images
A common requirement of complex base images is the support of multiple OS versions (think: CentOS 8 and 9) for customers that have varied needs (especially during migration periods between major OS versions).
A bit of history
Historically, images were configured by their parent_layer, all the way up to
the first root image which decided the OS version of the entire child image
chain.
This required base image owners to maintain separate target graphs for every OS
version that they supported. These were often 99% the same, with the exception
of a few very minor differences (eg: CentOS 8 -> 9 going from dbus ->
dbus-broker).
These minor differences required very complex TARGETS/.bzl setups, because
the entire target graph needed to be declared for each supported OS, even if the
individual image.layer had the exact same features (because it's
parent_layer would need to be different).
A better way (default_os)
Nowadays, antlir2 allows the leaf image owner to directly decide what OS they
want to use, without requiring the base image author to explicitly create
targets for that OS. This is accomplished with a new attribute default_os
supported on image.layer and a few other "leaf" rules (such as package.* and
image_*_test).
Setting default_os reconfigures the entire parent_layer chain to build for
whatever OS the end user wanted - of course, provided that all images along the
way are compatible (no compatible_with that excludes the OS or any features
with a select that fails to cover the requested OS).
default_os is applied from the bottom-upThe leaf image being built takes over the configuration of the entire chain. In
other words, the default_os attribute of any parent_layers is ignored.
Base Image Recommendations
Target Hierarchy
Base image authors should define a single image hierarchy that covers all the OSes that they support. The OS should never appear in the target name.
Any differences should be covered by selects based on the OS being used.
For example, supporting a base image across C8 and C9 might use a select like:
feature.new(
name = "dbus",
features = [
features.rpms_install(subjects = select({
"//antlir/antlir2/os:centos8": ["dbus"],
"//antlir/antlir2/os:centos9": ["dbus-broker"],
}))
]
)
Building base image itself
The base image author is free to set default_os on their own layers so that
buck build will build whatever OS they consider the default.
PACKAGE files
A chosen default_os value can also be applied to all image targets within a
subdirectory of the repo by using PACKAGE files.
load("//antlir/antlir2/os:package.bzl", "set_default_os_for_package")
set_default_os_for_package(
default_os = "centos9"
)
(In)Compatibility
If a base image is only compatible with some OSes, the author should add a
compatible_with to their image.layer so that buck2 provides a better error
message.
For example, if an image is only compatible with CentOS 9, the following might be used.
image.layer(
name = "my-base-image",
compatible_with = ["//antlir/antlir2/os:centos9"],
)
compatible_withUnless you know that your image is only compatible with certain OSes, it is
preferred to not specify compatible_with in order to ease migration to new OS
versions that are expected to be broadly compatible feature-wise.
compatible_with can give better error messagesIf you forget compatible_with but do have a select (that does not cover any
incompatible OSes), the build will still fail if a child image uses an
incompatible OS, but compatible_with will give the end user an
easier-to-understand error.
CI for packages
See the internal page for CI structure recommendations.
Debugging
Now that layers can be reconfigured for different OSes, it can sometimes be
confusing to see a build error on some parent_layer that is normally
configured for one OS, but fails when configured for another.
You can build a layer target as if it was being brought in as a dependency of
some other target by using the --target-universe flag of buck2 build.
For example, if you have a multi-OS layer at fbcode//my_image:my_image, and it
fails to build as a dependency of a CentOS 8 package
(fbcode//my_image/centos8:package), you can reproduce just that build failure
with this command:
buck2 build --target-universe fbcode//my_image/centos8:package fbcode//my_image:my_image