From a3c41d7e4014d85c4b9c6014fe976a8e371ce672 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 28 Jul 2026 23:57:32 +0800 Subject: [PATCH 1/3] runtime: support systems without /proc/self/exe and fusermount FreeBSD has neither of the two Linux mechanisms the runtime relies on to find and mount itself: - there is no /proc/self/exe to readlink(), so the path of the running AppImage comes from the KERN_PROC_PATHNAME sysctl instead, resolved once at startup; - FUSE file systems are mounted by mount_fusefs(8), which libfuse invokes itself, so there is no setuid fusermount helper to search $PATH for. Both are guarded behind capability macros, leaving the Linux code paths unchanged. The three readlink() call sites that turned /proc/self/exe into a real path are folded into one helper so the difference lives in a single place; that helper also reserves room for the NUL terminator, which the open-coded readlink() calls did not, so a path filling the buffer wrote one byte past its end. FreeBSD additionally does not guarantee the mount point is populated by the time libfuse reports the file system as mounted, so the parent polls for the entrypoint before exec'ing it. --- src/runtime/runtime.c | 99 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 9 deletions(-) diff --git a/src/runtime/runtime.c b/src/runtime/runtime.c index 2ebb3c8..ef176d6 100644 --- a/src/runtime/runtime.c +++ b/src/runtime/runtime.c @@ -65,7 +65,35 @@ extern int sqfs_opt_proc(void* data, const char* arg, int key, struct fuse_args* #include #include +/* Platform capabilities. + * + * Linux exposes the path of the running executable as the magic symlink + * /proc/self/exe, and mounts FUSE file systems through a setuid fusermount + * helper that has to be located on $PATH. FreeBSD has neither: the path of + * the running executable comes from the KERN_PROC_PATHNAME sysctl, and + * libfuse mounts through mount_fusefs(8), which it invokes itself. + */ +#if defined(__linux__) +#define APPIMAGE_HAVE_PROC_SELF_EXE 1 +#define APPIMAGE_HAVE_FUSERMOUNT 1 +#endif + +/* On FreeBSD the mount point is not necessarily populated yet by the time + * libfuse reports the file system as mounted, so the parent has to wait for + * the entrypoint to show up before it can exec it. + */ +#if defined(__FreeBSD__) +#define APPIMAGE_MOUNT_VISIBLE_ASYNC 1 +#include +#endif + +#if !defined(APPIMAGE_HAVE_PROC_SELF_EXE) +#include +#endif + +#if defined(APPIMAGE_HAVE_FUSERMOUNT) const char* fusermountPath = NULL; +#endif typedef struct { uint32_t lo; @@ -414,6 +442,7 @@ int appimage_print_binary(char* fname, unsigned long offset, unsigned long lengt return 0; } +#if defined(APPIMAGE_HAVE_FUSERMOUNT) char* find_fusermount(bool verbose) { char* fusermount_base = "fusermount"; @@ -513,6 +542,7 @@ char* find_fusermount(bool verbose) { free(path_copy); return NULL; } +#endif /* Exit status to use when launching an AppImage fails. * For applications that assign meanings to exit status codes (e.g. rsync), @@ -522,6 +552,34 @@ char* find_fusermount(bool verbose) { */ #define EXIT_EXECERROR 127 /* Execution error exit status. */ +/* Resolve appimage_path into the caller's buffer. + * + * On Linux appimage_path is normally the magic symlink /proc/self/exe, which + * has to be read to obtain the path of the running AppImage. On systems + * without /proc/self/exe the path is resolved once at startup in main(), so + * here it can be copied as is. + * + * Returns false on failure, in which case fullpath is left untouched. + */ +static bool resolve_appimage_path(const char* const appimage_path, char* const fullpath, const size_t fullpath_size) { +#if defined(APPIMAGE_HAVE_PROC_SELF_EXE) + /* readlink() does not NUL-terminate, so leave room for the terminator */ + const ssize_t length = readlink(appimage_path, fullpath, fullpath_size - 1); + + if (length < 0) + return false; + + fullpath[length] = '\0'; +#else + if (strlen(appimage_path) >= fullpath_size) + return false; + + strcpy(fullpath, appimage_path); +#endif + + return true; +} + struct stat st; static ssize_t fs_offset; // The offset at which a filesystem image is expected = end of this ELF @@ -724,12 +782,10 @@ 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)); - if (length < 0) { + if (!resolve_appimage_path(appimage_path, fullpath, sizeof(fullpath))) { fprintf(stderr, "Error getting realpath for %s\n", appimage_path); exit(EXIT_FAILURE); } - fullpath[length] = '\0'; sprintf(portable_dir, "%s.%s", fullpath, name); if (!mkdir(portable_dir, S_IRWXU)) @@ -1480,7 +1536,18 @@ int main(int argc, char* argv[]) { * functionality specifically for builds used by appimaged. */ if (getenv("TARGET_APPIMAGE") == NULL) { +#if defined(APPIMAGE_HAVE_PROC_SELF_EXE) strcpy(appimage_path, "/proc/self/exe"); +#else + /* Without /proc/self/exe, ask the kernel for the path of this process */ + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + size_t path_len = sizeof(appimage_path); + + if (sysctl(mib, 4, appimage_path, &path_len, NULL, 0) != 0) { + perror("Failed to obtain absolute path"); + exit(EXIT_EXECERROR); + } +#endif strcpy(argv0_path, argv[0]); } else { strcpy(appimage_path, getenv("TARGET_APPIMAGE")); @@ -1511,12 +1578,10 @@ 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)); - if (length < 0) { + if (!resolve_appimage_path(appimage_path, fullpath, sizeof(fullpath))) { fprintf(stderr, "Error getting realpath for %s\n", appimage_path); exit(EXIT_EXECERROR); } - fullpath[length] = '\0'; print_help(fullpath); exit(0); @@ -1561,12 +1626,10 @@ 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)); - if (len < 0) { + if (!resolve_appimage_path(appimage_path, fullpath, sizeof(fullpath))) { perror("Failed to obtain absolute path"); exit(EXIT_EXECERROR); } - fullpath[len] = '\0'; } else { char* abspath = realpath(appimage_path, NULL); if (abspath == NULL) { @@ -1737,6 +1800,7 @@ int main(int argc, char* argv[]) { if (pid == 0) { /* in child */ +#if defined(APPIMAGE_HAVE_FUSERMOUNT) fusermountPath = getenv("FUSERMOUNT_PROG"); if (fusermountPath == NULL) { char* new_prog = find_fusermount(verbose); @@ -1750,6 +1814,7 @@ int main(int argc, char* argv[]) { printf("Error: No suitable fusermount binary found on the $PATH\n"); } } +#endif char* child_argv[5]; @@ -1846,6 +1911,22 @@ int main(int argc, char* argv[]) { strcpy(filename, mount_dir); strcat(filename, "/AppRun"); +#if defined(APPIMAGE_MOUNT_VISIBLE_ASYNC) + /* Poll for the entrypoint for up to a second, 10 ms at a time. + * In practice a single round is enough; the loop exits as soon as the + * mount point is populated, so a system that does not need this pays + * one stat() call. + */ + for (int wait_round = 0; wait_round < 100; ++wait_round) { + struct stat mounted_st; + if (stat(filename, &mounted_st) == 0) + break; + + const struct timespec ten_ms = {0, 10 * 1000 * 1000}; + nanosleep(&ten_ms, NULL); + } +#endif + /* TODO: Find a way to get the exit status and/or output of this */ execv(filename, real_argv); /* Error if we continue here */ From 6b099c86c91f1c2c2f3807982aadda5dd6d14865 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 28 Jul 2026 23:57:32 +0800 Subject: [PATCH 2/3] Add scripts for building the runtime natively on FreeBSD Mirrors the existing per-environment entry points under scripts/docker and scripts/chroot, with scripts/bsd/build.sh installing the packages and then calling install-dependencies.sh and build-runtime.sh. libfuse and squashfuse are built from source rather than taken from packages: FreeBSD ships both (filesystems/fusefs-libs3 and filesystems/fusefs-squashfuse) but as shared libraries only, and the runtime is statically linked. libfuse is pinned to 3.18.2 rather than the 3.15.0 the Linux build uses, because the BSD fixes are in 3.17 and later; the Linux pin is left alone. The fusermount patch in patches/libfuse does not apply and is not needed, as it patches the Linux mount backend while FreeBSD uses mount_bsd.c. The link needs GNU ld from devel/binutils. lld cannot apply the INSERT AFTER .interp in data_sections.ld, since a non-PIE static binary has no .interp section: ld: error: unable to insert .appimage after .interp binutils is a build dependency for objcopy anyway, so this costs nothing extra. No ELF OS/ABI correction is performed: it comes out as ELFOSABI_FREEBSD on x86_64 and ELFOSABI_NONE on aarch64, and both run, because what the kernel goes by is the FreeBSD ABI note, which ld.bfd emits in either case. The build scripts are separate from scripts/build-runtime.sh rather than conditional inside it because that script assumes a GNU userland throughout: nproc(1), bash at /bin/bash to detect the architecture from, GNU make, and objcopy in the base system. test-runtime.sh assembles a minimal AppImage around the freshly built runtime and runs it, so that a build which links but cannot mount or cannot find its own path is caught rather than shipped. --- BUILD.md | 25 +++++++- README.md | 4 +- scripts/bsd/build-runtime.sh | 78 +++++++++++++++++++++++++ scripts/bsd/build.sh | 35 +++++++++++ scripts/bsd/install-dependencies.sh | 90 +++++++++++++++++++++++++++++ scripts/bsd/test-runtime.sh | 58 +++++++++++++++++++ src/runtime/Makefile | 23 +++++++- 7 files changed, 309 insertions(+), 4 deletions(-) create mode 100755 scripts/bsd/build-runtime.sh create mode 100755 scripts/bsd/build.sh create mode 100755 scripts/bsd/install-dependencies.sh create mode 100755 scripts/bsd/test-runtime.sh diff --git a/BUILD.md b/BUILD.md index fbee62b..0133dd9 100644 --- a/BUILD.md +++ b/BUILD.md @@ -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. @@ -92,3 +94,24 @@ To specify commands that should be run, use the established `--` to distinguish > env 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-`, 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. + diff --git a/README.md b/README.md index 74476f5..05a757b 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/scripts/bsd/build-runtime.sh b/scripts/bsd/build-runtime.sh new file mode 100755 index 0000000..b18f361 --- /dev/null +++ b/scripts/bsd/build-runtime.sh @@ -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" diff --git a/scripts/bsd/build.sh b/scripts/bsd/build.sh new file mode 100755 index 0000000..567ce88 --- /dev/null +++ b/scripts/bsd/build.sh @@ -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 + +"$this_dir"/install-dependencies.sh +"$this_dir"/build-runtime.sh diff --git a/scripts/bsd/install-dependencies.sh b/scripts/bsd/install-dependencies.sh new file mode 100755 index 0000000..2314739 --- /dev/null +++ b/scripts/bsd/install-dependencies.sh @@ -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 diff --git a/scripts/bsd/test-runtime.sh b/scripts/bsd/test-runtime.sh new file mode 100755 index 0000000..527446d --- /dev/null +++ b/scripts/bsd/test-runtime.sh @@ -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 " >&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" diff --git a/src/runtime/Makefile b/src/runtime/Makefile index 9fd4165..591be06 100644 --- a/src/runtime/Makefile +++ b/src/runtime/Makefile @@ -1,12 +1,31 @@ GIT_COMMIT := $(shell cat version) +UNAME_S := $(shell uname -s) + +CFLAGS_COMMON = -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 + +ifeq ($(UNAME_S),FreeBSD) +# Ports install below /usr/local, which the base compiler does not search by +# default. squashfuse pulls in the full set of compressors on FreeBSD. +# +# The link needs GNU ld from devel/binutils: lld rejects the INSERT AFTER +# .interp in data_sections.ld ("unable to insert .appimage after .interp") +# because a non-PIE static binary has no .interp section. -static-pie, which +# would give it one, is not usable here either, so ld.bfd it is. +CC = cc +CFLAGS = $(CFLAGS_COMMON) -B/usr/local/bin -fuse-ld=bfd +INCLUDES = -I/usr/local/include -I/usr/local/include/squashfuse -I/usr/local/include/fuse3 +LIBS = -L/usr/local/lib -lsquashfuse -lsquashfuse_ll -lzstd -llzma -lz -llz4 -llzo2 -lfuse3 -lmimalloc -lmd -lpthread +else 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 +CFLAGS = $(CFLAGS_COMMON) -static-pie +INCLUDES = -I/usr/local/include/squashfuse -I/usr/include/fuse3 LIBS = -lsquashfuse -lsquashfuse_ll -lzstd -lz -lfuse3 -lmimalloc +endif all: runtime runtime: runtime.c - $(CC) -I/usr/local/include/squashfuse -I/usr/include/fuse3 $(CFLAGS) $^ $(LIBS) -o $@ + $(CC) $(INCLUDES) $(CFLAGS) $^ $(LIBS) -o $@ clean: rm -f runtime From 8a9e3dfc13d3c32ef5efe79a90972e7dc1ad458e Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 28 Jul 2026 23:57:33 +0800 Subject: [PATCH 3/3] Build the runtime for FreeBSD in CI Runs the native FreeBSD build in a VM provided by vmactions/freebsd-vm, since GitHub does not offer FreeBSD runners, for x86_64 and aarch64. Each job builds the runtime and then runs it, so a runtime that cannot mount or cannot resolve its own path fails the build. Kept as its own workflow so the existing Linux pipeline is untouched. The artifacts are uploaded but not wired into the release job, as the FreeBSD runtime is a separate artifact rather than a replacement for the Linux one of the same architecture. aarch64 is emulated and takes roughly four times as long as the accelerated x86_64 job, though it stays well under ten minutes because only the libfuse library is built, not its utils, examples and tests. Closes #126 --- .github/workflows/build-freebsd.yaml | 77 ++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 .github/workflows/build-freebsd.yaml diff --git a/.github/workflows/build-freebsd.yaml b/.github/workflows/build-freebsd.yaml new file mode 100644 index 0000000..2b66761 --- /dev/null +++ b/.github/workflows/build-freebsd.yaml @@ -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/*