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
77 changes: 77 additions & 0 deletions .github/workflows/build-freebsd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: build (FreeBSD)

on:
push:
pull_request:
workflow_dispatch:

# This ensures that jobs get canceled when force-pushing
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- appimage_arch: x86_64
vm_arch: x86_64
- appimage_arch: aarch64
vm_arch: aarch64

# the FreeBSD VM is provided by the vmactions action, so the job itself
# runs on a regular Linux runner regardless of the target architecture
runs-on: ubuntu-latest

# aarch64 is fully emulated and takes considerably longer than x86_64,
# which is accelerated; this is a backstop against a hung VM, not a target
timeout-minutes: 180

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

- name: Build in FreeBSD VM
uses: vmactions/freebsd-vm@v1
with:
release: "14.3"
arch: ${{ matrix.vm_arch }}
usesh: true
cache-after-prepare: true
prepare: |
# binutils provides objcopy and the GNU ld the runtime has to be
# linked with; the rest is the toolchain for libfuse and squashfuse
pkg install -y \
autoconf \
automake \
binutils \
gmake \
liblz4 \
libtool \
lzo2 \
meson \
mimalloc \
ninja \
pkgconf \
squashfs-tools \
zstd
run: |
set -e
# the dependencies are built in the run step rather than in prepare
# because the prepared-image cache key only covers the prepare script,
# so a change to install-dependencies.sh would otherwise be ignored
./scripts/bsd/install-dependencies.sh
./scripts/bsd/build-runtime.sh
./scripts/bsd/test-runtime.sh out/runtime-freebsd-${{ matrix.appimage_arch }}

- uses: actions/upload-artifact@v7
with:
name: artifacts freebsd ${{ matrix.appimage_arch }}
path: out/*
25 changes: 24 additions & 1 deletion BUILD.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# How to build the runtime

We maintain and provide two official ways to build the runtime:
We maintain and provide two official ways to build the runtime for Linux:

- a Docker-based setup that caches dependencies and isolates the build environment from the system
- a `chroot`-based method that can be used in isolated environments like, e.g., GitHub codespaces, if you cannot use Docker

The FreeBSD runtime is built natively instead; see [FreeBSD](#freebsd) below.

**Please note: We recommend regular users to use the Docker-based setup whenever possible!** The chroot based setup imposes a risk to break your local machine. It is meant **only** for environments that are otherwise isolated or reproducible, e.g., GitHub codespaces.


Expand Down Expand Up @@ -92,3 +94,24 @@ To specify commands that should be run, use the established `--` to distinguish
> env ARCH=<arch> scripts/create-build-container.sh -u $(id -u):(id -g) -- bash some-script.sh
```


## FreeBSD

The FreeBSD runtime is a separate binary from the Linux one and is built natively, on FreeBSD itself. There is no container or chroot involved; run the following as root, in a throwaway machine or VM, since it installs packages and libraries system-wide:

```sh
> scripts/bsd/build.sh
```

The result ends up in `out/` as `runtime-freebsd-<arch>`, alongside its separate debug symbols.

The script installs the required packages, then builds a static libfuse and squashfuse from source (FreeBSD packages both, but ships shared libraries only, and the runtime is linked statically), and finally builds the runtime itself. The three stages are also available individually as `scripts/bsd/install-dependencies.sh` and `scripts/bsd/build-runtime.sh`.

To check that a runtime you built actually works, `scripts/bsd/test-runtime.sh` assembles a minimal AppImage around it and runs it:

```sh
> scripts/bsd/test-runtime.sh out/runtime-freebsd-x86_64
```

This is what CI does, in a VM provided by [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm), as GitHub does not offer FreeBSD runners.

4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# type2-runtime ![GitHub Actions](https://github.com/AppImage/type2-runtime/actions/workflows/build.yaml/badge.svg)
# type2-runtime ![GitHub Actions](https://github.com/AppImage/type2-runtime/actions/workflows/build.yaml/badge.svg) ![GitHub Actions (FreeBSD)](https://github.com/AppImage/type2-runtime/actions/workflows/build-freebsd.yaml/badge.svg)

The runtime is the executable part of every AppImage. It mounts the payload via FUSE and executes the entrypoint.

This repository builds a statically linked runtime for type-2 AppImages in a [Alpine Linux](https://alpinelinux.org/) chroot with [musl libc](https://www.musl-libc.org/).

A separate, natively built runtime is provided for FreeBSD.

Since the runtime is linked statically, libfuse2 is no longer required on the target system.

## Notes for users
Expand Down
78 changes: 78 additions & 0 deletions scripts/bsd/build-runtime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/sh

# Builds the runtime natively on FreeBSD.
#
# The counterpart of scripts/build-runtime.sh, which assumes a GNU userland:
# there is no nproc(1), the architecture cannot be read off /bin/bash (bash is
# not part of the base system), the Makefile needs GNU make, and objcopy comes
# from devel/binutils rather than the base system.

set -eu

# we'll copy the outcome into a subdirectory out in the current working directory
out_dir="$(pwd)"/out
mkdir -p "$out_dir"

: "${MAKE:=gmake}"
: "${OBJCOPY:=/usr/local/bin/objcopy}"
: "${STRIP:=/usr/local/bin/strip}"

njobs="$(sysctl -n hw.ncpu)"

cd src/runtime

$MAKE -j"$njobs" runtime

# the ELF OS/ABI ends up as ELFOSABI_FREEBSD on x86_64 and ELFOSABI_NONE on
# aarch64; both run, since what the kernel goes by is the FreeBSD ABI note,
# which ld.bfd emits either way
file runtime

$OBJCOPY --only-keep-debug runtime runtime.debug

$STRIP --strip-debug --strip-unneeded runtime

ls -lh runtime runtime.debug

# convert uname's output to AppImage's semi-official suffix style
# unlike the Linux build there is no 32-bit-userland-on-64-bit-kernel case to
# worry about here, so the kernel architecture is the userland architecture
machine="$(uname -m)"

case "$machine" in
amd64|x86_64)
architecture=x86_64
;;
arm64|aarch64)
architecture=aarch64
;;
i386)
architecture=i686
;;
armv6|armv7)
architecture=armhf
;;
*)
echo "Unsupported architecture: $machine"
exit 2
;;
esac

# the OS is part of the name: a FreeBSD runtime is not interchangeable with the
# Linux runtime of the same architecture
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
runtime="runtime-$os-$architecture"

mv runtime "$runtime"
mv runtime.debug "$runtime".debug

$OBJCOPY --add-gnu-debuglink "$runtime".debug "$runtime"

# "classic" magic bytes which cannot be embedded with compiler magic, always do AFTER strip
# needs to be done after calls to objcopy, strip etc.
printf 'AI\002' | dd of="$runtime" bs=1 count=3 seek=8 conv=notrunc

cp "$runtime" "$out_dir"/
cp "$runtime".debug "$out_dir"/

ls -al "$out_dir"
35 changes: 35 additions & 0 deletions scripts/bsd/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/sh

# Entry point for a native FreeBSD build, the counterpart of
# scripts/docker/build-with-docker.sh and scripts/chroot/build.sh.
#
# Has to run as root, since it installs packages and the dependencies below
# /usr/local. Intended to be run in a throwaway VM.

set -eu

if [ "$(uname -s)" != FreeBSD ]; then
echo "This script has to be run on FreeBSD" >&2
exit 1
fi

this_dir="$(cd "$(dirname "$0")" && pwd)"

# binutils provides objcopy and the GNU ld the runtime has to be linked with;
# the rest is the toolchain needed to build libfuse and squashfuse
pkg install -y \
autoconf \
automake \
binutils \
gmake \
liblz4 \
libtool \
lzo2 \
meson \
mimalloc \
ninja \
pkgconf \
zstd
Comment on lines +31 to +32

"$this_dir"/install-dependencies.sh
"$this_dir"/build-runtime.sh
90 changes: 90 additions & 0 deletions scripts/bsd/install-dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/bin/sh

# Builds the static libfuse3 and squashfuse that the runtime is linked against.
#
# FreeBSD does package both of them (filesystems/fusefs-libs3 and
# filesystems/fusefs-squashfuse), but ships shared libraries only, while the
# runtime has to be statically linked, so they are built from source here.
#
# This is separate from scripts/common/install-dependencies.sh because almost
# every step differs: FreeBSD needs a much newer libfuse (3.17 and later carry
# the BSD fixes), the fusermount patch in patches/libfuse/ is Linux-only, the
# prefix is /usr/local rather than /usr, and there is no nproc(1).

set -eu

fuse_version=3.18.2
fuse_sha256=f01de85717e20adf5f98aff324acd85dd73d61a5ca3834d573dcf0bd6e54a298

squashfuse_version=0.5.2
squashfuse_sha256=db0238c5981dabbd80ee09ae15387f390091668ca060a7bc38047912491443d3

prefix=/usr/local
njobs="$(sysctl -n hw.ncpu)"

download() {
if command -v fetch > /dev/null 2>&1; then
fetch -o "$2" "$1"
else
curl -sSL -o "$2" "$1"
fi
}

sha256_of() {
if command -v sha256 > /dev/null 2>&1; then
sha256 -q "$1"
else
sha256sum "$1" | cut -d' ' -f1
fi
}

verify() {
actual="$(sha256_of "$1")"

if [ "$actual" != "$2" ]; then
echo "checksum mismatch for $1: expected $2, got $actual" >&2
exit 1
fi
}

# the build trees are deliberately left behind: this runs in a throwaway build
# VM, and keeping them makes a failed build far easier to inspect
workdir="${WORKDIR:-$(mktemp -d -t type2-runtime-deps)}"
echo "Building dependencies in $workdir"
cd "$workdir"

# libfuse
download "https://github.com/libfuse/libfuse/releases/download/fuse-$fuse_version/fuse-$fuse_version.tar.gz" \
"fuse-$fuse_version.tar.gz"
verify "fuse-$fuse_version.tar.gz" "$fuse_sha256"
tar xf "fuse-$fuse_version.tar.gz"

mkdir -p "fuse-$fuse_version"/build
cd "fuse-$fuse_version"/build
# only the library itself is needed; the helper programs are Linux-only
# (fusermount3 has no FreeBSD counterpart, FreeBSD mounts via mount_fusefs(8))
meson setup --prefix="$prefix" --default-library=static \
-Dexamples=false -Dtests=false -Dutils=false ..
ninja -v install
cd "$workdir"

# squashfuse
# minimize binary size, same as the Linux build does
CFLAGS="-ffunction-sections -fdata-sections -Os"
export CFLAGS
PKG_CONFIG_PATH="$prefix/lib/pkgconfig:$prefix/libdata/pkgconfig"
export PKG_CONFIG_PATH

download "https://github.com/vasi/squashfuse/archive/$squashfuse_version.tar.gz" \
"squashfuse-$squashfuse_version.tar.gz"
verify "squashfuse-$squashfuse_version.tar.gz" "$squashfuse_sha256"
tar xf "squashfuse-$squashfuse_version.tar.gz"

cd "squashfuse-$squashfuse_version"
./autogen.sh
./configure --prefix="$prefix" LDFLAGS="-static"
make -j"$njobs"
make install
# the runtime includes squashfuse internals (fuseprivate.h, ll.h) that are not
# part of the installed public headers
install -m 644 ./*.h "$prefix"/include/squashfuse
58 changes: 58 additions & 0 deletions scripts/bsd/test-runtime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/bin/sh

# Smoke test for a natively built FreeBSD runtime: assembles a minimal AppImage
# around it and runs it, which exercises the parts that differ from Linux --
# resolving the path of the running executable through the KERN_PROC_PATHNAME
# sysctl, and mounting the payload through mount_fusefs(8).
#
# Has to run as root: mounting requires either root or vfs.usermount=1.

set -eu

runtime="${1:-}"

if [ -z "$runtime" ]; then
echo "usage: $0 <path to runtime>" >&2
exit 1
fi

kldload -n fusefs

workdir="$(mktemp -d -t type2-runtime-test)"
appdir="$workdir"/AppDir
mkdir -p "$appdir"

cat > "$appdir"/AppRun <<'APPRUN'
#!/bin/sh
echo "AppRun running from $APPDIR"
echo "APPIMAGE is $APPIMAGE"
test -n "$APPIMAGE"
test -f "$APPIMAGE"
echo "smoke test ok"
APPRUN
chmod +x "$appdir"/AppRun

mksquashfs "$appdir" "$workdir"/payload.squashfs -root-owned -noappend

appimage="$workdir"/smoketest.AppImage
cat "$runtime" "$workdir"/payload.squashfs > "$appimage"
chmod +x "$appimage"

# --appimage-version and --appimage-help both write to stderr
echo "--- runtime reports its version ---"
"$appimage" --appimage-version 2>&1

# the path in the help output is the one the runtime resolved for itself, so
# this is what proves the KERN_PROC_PATHNAME lookup returned the right thing
echo "--- runtime prints its own path in the help output ---"
"$appimage" --appimage-help 2>&1 | grep -F "$appimage"

echo "--- mounting the payload and running AppRun ---"
"$appimage" | tee "$workdir"/output.txt

grep -q "smoke test ok" "$workdir"/output.txt

echo "--- extract-and-run fallback ---"
"$appimage" --appimage-extract-and-run | grep -q "smoke test ok"

echo "FreeBSD runtime smoke test passed"
Loading