Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,24 @@ jobs:
runner: ubuntu-24.04-arm
- appimage_arch: aarch64
runner: ubuntu-24.04-arm
- appimage_arch: riscv64
runner: ubuntu-24.04
qemu: true
- appimage_arch: ppc64le
runner: ubuntu-24.04
qemu: true

runs-on: ${{ matrix.runner }}

steps:
- name: Checkout
uses: actions/checkout@v7

# GitHub has no RISC-V and POWER runners: the build container runs emulated
- name: Set up QEMU
if: ${{ matrix.qemu }}
uses: docker/setup-qemu-action@v4

- name: Get git hash
run: |
echo -n "https://github.com/${GITHUB_REPOSITORY}/commit/" > src/runtime/version
Expand Down Expand Up @@ -102,11 +113,84 @@ jobs:
run: |
scripts/chroot/chroot_build.sh

# Architectures without Alpine Linux (big endian ppc64): built in Debian ports
# with glibc (scripts/debian-ports/build-in-debian.sh), emulated by QEMU
build-debian-ports:
strategy:
fail-fast: false
matrix:
include:
- appimage_arch: ppc64
debian_arch: ppc64
platform: linux/ppc64

runs-on: ubuntu-24.04
timeout-minutes: 240

steps:
- name: Checkout
uses: actions/checkout@v7

- name: Get git hash
run: |
echo -n "https://github.com/${GITHUB_REPOSITORY}/commit/" > src/runtime/version
git rev-parse --short HEAD | xargs >> src/runtime/version

# debian-ports has no Docker images: the root file system is made with
# debootstrap (its second stage runs emulated) and imported. The binfmt
# image of setup-qemu-action has no big endian ppc64: qemu-user-static of Ubuntu
- name: Make the Debian ports image
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends debootstrap qemu-user-static
# the keyring of Ubuntu is older than the key that signs sid today. The pool of
# Debian keeps only the current version of the package: its file name comes
# from the package index, so that a new upload of the keyring breaks nothing
POOL=$(curl -fsSL https://deb.debian.org/debian/dists/sid/main/binary-all/Packages.xz | xz -d \
| awk '/^Package: debian-ports-archive-keyring$/ { f = 1 } f && /^Filename:/ { print $2; f = 0 }')
test -n "$POOL"
KEYRING=$(basename "$POOL")
curl -fsSLO "https://deb.debian.org/debian/$POOL"
dpkg-deb -x $KEYRING keyring
# the keyring goes into the system too, for its apt-get update
sudo debootstrap --arch=${{ matrix.debian_arch }} --variant=minbase \
--include=debian-ports-archive-keyring \
--keyring=$PWD/keyring/usr/share/keyrings/debian-ports-archive-keyring.gpg \
sid rootfs http://ftp.ports.debian.org/debian-ports
sudo tar -C rootfs -c . | docker import --platform ${{ matrix.platform }} - debian-ports
sudo rm -rf rootfs keyring $KEYRING

- name: Build
run: |
docker run --rm --platform ${{ matrix.platform }} -v "$PWD":/src -w /src debian-ports \
bash scripts/debian-ports/build-in-debian.sh
sudo chown -R "$(id -u):$(id -g)" out

- name: Sign
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
# as in the build job: skipped without the secret (pull requests from forks)
if: ${{ env.SIGNING_KEY != '' }}
run: |
find out
scripts/sign.sh out/runtime-*
cp signing-pubkey.asc out/

- uses: actions/upload-artifact@v7
with:
name: artifacts ${{ matrix.appimage_arch }}
path: out/*

upload:
name: Create release and upload artifacts
runs-on: ubuntu-latest
needs:
- build
- build-debian-ports
# Debian ports is unstable by nature and its job runs emulated for hours: when
# it fails, the release still goes out with the other architectures (the
# artifacts of a failed job are not there to upload)
if: ${{ !cancelled() && needs.build.result == 'success' }}
steps:
- name: Download artifacts
uses: actions/download-artifact@v8
Expand Down
15 changes: 15 additions & 0 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ The build process has been automated completely. As a user, you just need to run

The resulting AppImages will end up in your current working directory.

`<arch>` is one of `x86_64`, `i686`, `armhf`, `aarch64`, `riscv64` and `ppc64le`. There are no GitHub runners for the last two: the CI runs their Alpine containers emulated by QEMU (see [Emulate foreign architectures](#emulate-foreign-architectures)), which is much slower.


### Architectures without Alpine Linux: big endian ppc64

Alpine Linux has no big endian 64-bit PowerPC, so `ppc64` is built in a Debian ports system (the `sid` release, the only one with this architecture), with glibc and gcc instead of musl and clang, and without static PIE (`make STATIC_PIE=`). libfuse and squashfuse are built from source as for the other architectures (`scripts/common/install-dependencies.sh`); zstd, zlib and, when Debian has its static library, mimalloc come from Debian:

```sh
# in a Debian ports (sid) ppc64 system, as root, in the repository:
> bash scripts/debian-ports/build-in-debian.sh
```

The runtime ends up in `out/runtime-ppc64`. Debian ports has no Docker images: the CI makes the system with `debootstrap` and imports it into Docker (job `build-debian-ports` of `.github/workflows/build.yaml`), and runs it with the `qemu-user-static` of Ubuntu, as the binfmt image of setup-qemu-action has no big endian ppc64.
The keyring of Debian ports is the current one of the Debian package index (the pool keeps only the latest version), the runtime is signed as the others, and the release does not wait for this job: when the emulated Debian ports build fails, the other architectures are published without `runtime-ppc64`.


### Interactive environment

Expand Down
9 changes: 8 additions & 1 deletion scripts/build-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ cp -R src "$build_dir"/
pushd "$build_dir"

pushd src/runtime/
make -j"$(nproc)" runtime
# RUNTIME_MAKE_ARGS: extra make variables, e.g. CC=gcc STATIC_PIE= for glibc builds
make -j"$(nproc)" runtime ${RUNTIME_MAKE_ARGS:-}

file runtime

Expand All @@ -42,6 +43,12 @@ elif [[ "$runtime" =~ EABI5 ]]; then
architecture=armhf
elif [[ "$runtime" =~ x86-64 ]]; then
architecture=x86_64
elif [[ "$runtime" =~ RISC-V ]]; then
architecture=riscv64
elif [[ "$runtime" =~ "64-bit MSB" ]] && [[ "$runtime" =~ PowerPC ]]; then
architecture=ppc64
elif [[ "$runtime" =~ "64-bit LSB" ]] && [[ "$runtime" =~ PowerPC ]]; then
architecture=ppc64le
else
echo "Unsupported architecture: ${runtime#* }"
exit 2
Expand Down
38 changes: 38 additions & 0 deletions scripts/debian-ports/build-in-debian.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#! /bin/bash

# Builds the runtime in a Debian (ports) system, for the architectures that
# have no Alpine Linux, e.g. big endian ppc64: glibc instead of musl, gcc
# instead of clang, and no static PIE (glibc has none on every architecture).
# libfuse and squashfuse come from source as for the other architectures
# (scripts/common/install-dependencies.sh), zstd and zlib from Debian.
#
# (systemd-dev: the udev.pc that the build of libfuse reads, without systemd itself,
# whose maintainer scripts crash under QEMU)
#
# Usage (as root, in the repository, e.g. in a container):
# bash scripts/debian-ports/build-in-debian.sh
# The runtime is written into out/.

set -euxo pipefail

export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends \
bash ca-certificates wget file patch xz-utils make gcc libc6-dev pkg-config \
meson ninja-build autoconf automake libtool \
libzstd-dev zlib1g-dev libmimalloc-dev systemd-dev

# meson installs libfuse into /usr/lib64, which pkg-config and gcc of Debian
# (multiarch: /usr/lib/<triplet>) do not search
export PKG_CONFIG_PATH=/usr/lib64/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}
export LIBRARY_PATH=/usr/lib64${LIBRARY_PATH:+:$LIBRARY_PATH}

bash scripts/common/install-dependencies.sh

# mimalloc only when Debian has its static library
make_args="CC=gcc STATIC_PIE="
if ! find /usr/lib -name libmimalloc.a | grep -q .; then
make_args="$make_args MIMALLOC="
fi

RUNTIME_MAKE_ARGS="$make_args" bash scripts/build-runtime.sh
6 changes: 6 additions & 0 deletions scripts/docker/create-build-container.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ case "${ARCH}" in
aarch64)
docker_platform=linux/arm64/v8
;;
riscv64)
docker_platform=linux/riscv64
;;
ppc64le)
docker_platform=linux/ppc64le
;;
*)
echo "Unsupported architecture: $ARCH"
exit 3
Expand Down
7 changes: 5 additions & 2 deletions src/runtime/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
GIT_COMMIT := $(shell cat version)
CC = clang
CFLAGS = -std=gnu99 -Os -D_FILE_OFFSET_BITS=64 -DGIT_COMMIT=\"$(GIT_COMMIT)\" -T data_sections.ld -ffunction-sections -fdata-sections -Wl,--gc-sections -static -Wall -Werror -static-pie
LIBS = -lsquashfuse -lsquashfuse_ll -lzstd -lz -lfuse3 -lmimalloc
# both can be emptied on the command line, e.g. for glibc targets without static PIE (make STATIC_PIE=)
STATIC_PIE = -static-pie
MIMALLOC = -lmimalloc
CFLAGS = -std=gnu99 -Os -D_FILE_OFFSET_BITS=64 -DGIT_COMMIT=\"$(GIT_COMMIT)\" -T data_sections.ld -ffunction-sections -fdata-sections -Wl,--gc-sections -static -Wall -Werror $(STATIC_PIE)
LIBS = -lsquashfuse -lsquashfuse_ll -lzstd -lz -lfuse3 $(MIMALLOC)

all: runtime

Expand Down
11 changes: 7 additions & 4 deletions src/runtime/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -724,14 +724,17 @@ void portable_option(const char* arg, const char* appimage_path, const char* nam
char portable_dir[PATH_MAX];
char fullpath[PATH_MAX];

ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath));
ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath) - 1);
if (length < 0) {
fprintf(stderr, "Error getting realpath for %s\n", appimage_path);
exit(EXIT_FAILURE);
}
fullpath[length] = '\0';

sprintf(portable_dir, "%s.%s", fullpath, name);
if (snprintf(portable_dir, sizeof(portable_dir), "%s.%s", fullpath, name) >= (int) sizeof(portable_dir)) {
fprintf(stderr, "Path of the portable %s directory too long: %s.%s\n", name, fullpath, name);
exit(EXIT_FAILURE);
}
if (!mkdir(portable_dir, S_IRWXU))
fprintf(stderr, "Portable %s directory created at %s\n", name, portable_dir);
else
Expand Down Expand Up @@ -1511,7 +1514,7 @@ int main(int argc, char* argv[]) {
if (arg && strcmp(arg, "appimage-help") == 0) {
char fullpath[PATH_MAX];

ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath));
ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath) - 1);
if (length < 0) {
fprintf(stderr, "Error getting realpath for %s\n", appimage_path);
exit(EXIT_EXECERROR);
Expand Down Expand Up @@ -1561,7 +1564,7 @@ int main(int argc, char* argv[]) {

if (getenv("TARGET_APPIMAGE") == NULL) {
// If we are operating on this file itself
ssize_t len = readlink(appimage_path, fullpath, sizeof(fullpath));
ssize_t len = readlink(appimage_path, fullpath, sizeof(fullpath) - 1);
if (len < 0) {
perror("Failed to obtain absolute path");
exit(EXIT_EXECERROR);
Expand Down
Loading