Skip to content

Cutting a release

A release is one set of artifacts that agree with each other. Building them is mostly make release; what needs a person is coordination, because no single machine can produce all of them and the last two steps depend on every earlier one having arrived.

Read Build the payload first if you have never built one. This page assumes you have.

What a release is

Artifact Where it goes Built by
core-macos-arm64.tar.gz the origin a Mac, natively
core-linux-x86_64.tar.gz the origin a container
core-linux-arm64.tar.gz the origin a container
editors.tar.gz the origin any payload build: exactly one
fonts-core.tar.gz the origin the same one
manifest.json the origin last, over the directory
libera-X.Y.Z-py3-none-any.whl PyPI after the manifest, with a copy of it inside
Libera.app a download a Mac
libera-X.Y.Z-amd64.flatpak a download an x86_64 Linux machine, payload included
libera-X.Y.Z-arm64.flatpak a download an arm64 machine, payload included

The tarballs go to $BUILD_ROOT/out/dist/$PAYLOAD_VERSION, which is 0.1 today and not the application version 0.1.0. build/payload.version and pyproject.toml move independently on purpose: a host fix should not force everyone to re-download 170 MB; a payload rebuilt from new upstream pins should not need a host release.

Which machine can make what, and why

Three constraints have each cost somebody a build:

There is no macOS container. A Mac builds its own core natively. Everything else about a Mac release follows from that one fact.

Linux cores do not need a Linux machine. build/docker.sh exists so that a Linux payload never depends on what the host has installed. An Apple Silicon Mac builds linux/arm64 natively and linux/amd64 under Rosetta (not qemu), so both Linux cores come off the Mac at usable speed. On a Linux box, the host's own architecture is just one more container target; do not build it natively there.

Flatpak bundles cannot be cross-built. flatpak-builder runs every build command inside bubblewrap, which installs a seccomp filter compiled for the target architecture. Under emulation the kernel is still the host's, so it is rejected:

bwrap: Unable to set up system call filtering as requested:
prctl(PR_SET_SECCOMP) reported EINVAL.

Rosetta translates instructions; it leaves the kernel's idea of what architecture a BPF program is for alone. So each bundle is built on a machine of its own architecture, and collected.

Which leaves the division of labour:

Mac (Apple Silicon) x86_64 Linux arm64 Linux
core-macos-arm64 only here no no
core-linux-x86_64 yes (Rosetta) yes no
core-linux-arm64 yes (native) no yes
editors, fonts, manifest, wheel yes yes yes
Libera.app only here no no
*-arm64.flatpak yes no yes
*-amd64.flatpak no only here no

So one Mac produces everything except the amd64 Flatpak, with one x86_64 Linux machine contributing that single file. The coordination problem is that small. A release does not need an arm64 Linux box: the Mac covers arm64. Such a box is still the only place the suite gets exercised on real arm64 hardware, outside a container, which is a check to run before you ship.

core-macos-x86_64 has no machine and is deferred: Intel Macs are not a target for this beta.

Before you start

Hours, so run the dry run first. It builds nothing and prints what it would do:

make release-check

It checks the things that have each wasted a build before: a dirty tree (a release records its commit as provenance), Docker actually running, binfmt handlers for any foreign architecture, uv, iconutil, and enough disk. Disk means about 20 GB per architecture that is not already built, and 5 GB for one that is.

Fix everything it names, then read the plan it prints. It will tell you which steps are a rebuild and which are a repackage. Those are different: the core is skippable, the payload is not. A release once shipped an editors.tar.gz eighteen hours older than the licence text inside it, because the core tarball was present, the whole native half was skipped, and dist.sh (which repackages the theme, the branding and the attribution) never ran.

Tag before you build, so the provenance in the manifest is the tag and not a commit that only you have:

git tag -a v0.1.0 -m 'Libera Suite 0.1.0'

1. The Mac

make release

Which does, in order:

  1. the native core if x2t is missing (hours, since V8 alone is ~30 minutes), then the payload, smoke.sh and dist.sh;
  2. each Linux architecture in its container, exported into $DIST;
  3. the manifest, over everything present;
  4. the wheel, with the manifest stamped into it;
  5. Libera.app;
  6. the arm64 Flatpak.

A failure in one architecture is recorded, leaving the run to carry on: one platform refusing to build is no reason to throw away the other five artifacts. The run ends with a list of what it failed to produce and the relevant lines from each failing log. Read that list: it is the report.

Expect it to end saying:

    still missing: core-macos-x86_64
    still missing: libera-0.1.0-amd64.flatpak (build it on a amd64 machine)

Both are correct. The first is deferred; the second is step 2.

2. The x86_64 Linux machine

The bundle carries the payload, so this box needs one built before it can make a bundle. That is a change: it used to need only the repository, Docker and uv.

git clone https://github.com/abilian/libera-suite && cd libera-suite
git checkout v0.1.0            # the same tag, or the artifacts do not match
make payload-linux             # core and payload in the pinned container (hours, once)
make flatpak                   # stages them into the bundle, then --diagnose inside it

Or, if the Mac has already built the amd64 artifacts, copy them over and skip the hours: make flatpak takes them from $DIST; DIST=/path/to/artifacts make flatpak points it somewhere else.

Use make flatpak here. A release run works and wastes an afternoon: it repackages a $DIST that is not the release. Only the Mac's is. Expect this box to end up reporting still missing: core-macos-arm64 and the rest, which is correct: it is describing its own directory.

That leaves build/out/libera-0.1.0-amd64.flatpak. Copy it next to the Mac's:

scp build/out/libera-0.1.0-amd64.flatpak mac:path/to/local-office/build/out/

Check what else is in build/out first. Nothing cleans it. Every build writes a new filename, so bundles from earlier versions, including from before the product was renamed, sit there indefinitely. make release now lists those separately, under "NOT part of this release". Delete them, so they are not in the way at upload time.

make flatpak ends by installing the bundle and running libera --diagnose inside the sandbox, which is as far as a container goes, since there is no display in one. It proves three things: the GNOME runtime supplies GTK and WebKit, the application imports, --diagnose says payload 0.1 (bundled). make flatpak-smoke goes one further and converts a document with it.

3. An arm64 Linux machine, if you have one

No release depends on this step. It is the only place the suite runs on real arm64 hardware:

make payload-linux             # payload in the pinned container
make test-linux                # the suite, on Linux
make gui-linux                 # a real GTK/WebKitGTK window under Xvfb, screenshotted
make flatpak-check             # install the bundle, run --diagnose in the sandbox

On a small machine the build is tight on memory before it is slow. build/payload.sh and build/build.sh size themselves from the cgroup limit; a 4 GB box needs swap to finish the core (see Build the payload). The symptom is Killed signal terminated program cc1plus, and the answer is fallocate, not a smaller job count.

4. Bringing it together

The manifest describes a directory, so it has to be written after every artifact has arrived, and the wheel after the manifest, because the wheel carries a copy and verifies every download against it. Within one make release those two are already in order. Across machines they are not, so once the amd64 Flatpak is in place, run:

sh build/dist.sh --manifest-only          # or INSTALL_MANIFEST=1 ... to restamp the wheel

The Flatpak is not in the manifest, because it carries the host, so in practice the amd64 bundle arriving late costs nothing. But if you copy in a core tarball built elsewhere, the manifest and the wheel both have to be redone:

INSTALL_MANIFEST=1 sh build/dist.sh --manifest-only
rm -rf dist && uv build

make release checks this agreement at the end and says so:

    the wheel's manifest matches the artifacts

If it does not, an install would reject its own payload: a hash mismatch on a tester's machine, which is a bad place to find out.

5. Verify before publishing

make verify                    # lint, the whole suite, docs links, the patch queue, the converter
make patches                   # the queue still applies to the pinned SHAs

Two things remain that no automated check covers:

  • Read the attribution out loud. It is a licence obligation. It lives in build/theme/libera/meta/config.json and shows in Help ▸ About.
  • Install the payload the way a user will, from the artifacts, and open a document with it.

6. Publish

Publish in this order, because the parts refer to each other:

  1. The payload to https://cdn.abilian.com/libera/: the whole of $DIST, under the payload version.
  2. The upstream mirrors, at the same moment the binaries go out. The AGPL obligation is a commit plus a patch series that provably applies to it; build/pins.toml and build/patches/ are that series.
  3. The wheel to PyPI (make publish).
  4. Libera.app and both .flatpak bundles wherever the release is downloaded from.
  5. Push the tag.

When a step fails

exec format error a minute into an image build No binfmt handler for a foreign architecture. docker run --privileged --rm tonistiigi/binfmt --install all, or build that architecture on a machine that is one.
Killed signal terminated program cc1plus The OOM killer. Add swap; the build prints the fallocate/mkswap/swapon lines when it detects a small machine.
FATAL: heap out of memory in a webpack V8 sizes its heap from the cgroup limit, ignoring the work: a smaller container aims lower and dies sooner. build/payload.sh sets a floor below 6 GB; see Build the payload.
A patch no longer applies News, and something to investigate. make patches names it; read the upstream change before rebasing.
The build ran the JS pipeline unpatched The source tree was fetched before the patch existed. git -C $BUILD_ROOT/src/<repo> log --oneline answers it in one line.
INCOMPLETE: no editors.tar.gz A Linux-only run whose architectures were all already built, so nothing produced the platform-neutral half. The command to fix it is printed with the message.
FATAL: artwork in the payload that is not ours dist.sh found a logo-shaped file it does not recognise. Look at it before deciding: if it is upstream's it wants removing in build/patches/web-apps/ or writing over in payload.sh; if it is ours, its name goes in OURS in dist.sh.

Build the payload has the reasoning behind the rest: why the container is pinned the way it is, why arm64 needs clang 13 exactly, and what each of the silent failures cost.