Skip to content

Refactor CI workflow for Ubuntu 24.04 and cross-compilation #24

Refactor CI workflow for Ubuntu 24.04 and cross-compilation

Refactor CI workflow for Ubuntu 24.04 and cross-compilation #24

Workflow file for this run

name: Build and Release
on:
push:
branches: [ main, master ]
tags: [ 'v*' ]
pull_request:
branches: [ main, master ]
workflow_dispatch:
permissions:
contents: write
jobs:
# ═══════════════════════════════════════════════════════════════════
# Linux — Ubuntu 24.04: native amd64 + cross-compile arm64/armhf
#
# Library strategy:
# apt: openssl, curl, zlib, uuid, brotli, nghttp2, zstd, psl,
# idn2, unistring, krb5 (all provide .a files)
# source: fmt, spdlog, argon2 (small, avoids version issues)
# ═══════════════════════════════════════════════════════════════════
build-linux:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
artifact: phira-mp-server-linux-amd64
deb_arch: amd64
triple: x86_64-linux-gnu
cxx: g++
cc: gcc
ar: ar
cross_pkg: ""
is_cross: false
- arch: arm64
artifact: phira-mp-server-linux-arm64
deb_arch: arm64
triple: aarch64-linux-gnu
cxx: aarch64-linux-gnu-g++
cc: aarch64-linux-gnu-gcc
ar: aarch64-linux-gnu-ar
cross_pkg: g++-aarch64-linux-gnu
is_cross: true
- arch: armhf
artifact: phira-mp-server-linux-armhf
deb_arch: armhf
triple: arm-linux-gnueabihf
cxx: arm-linux-gnueabihf-g++
cc: arm-linux-gnueabihf-gcc
ar: arm-linux-gnueabihf-ar
cross_pkg: g++-arm-linux-gnueabihf
is_cross: true
env:
SYSROOT: /home/runner/sysroot
steps:
- uses: actions/checkout@v4
- name: Set up multiarch apt (cross-compile only)
if: matrix.is_cross
run: |
sudo dpkg --add-architecture ${{ matrix.deb_arch }}
# Restrict existing sources to amd64
sudo sed -i '/^URIs:/i Architectures: amd64' /etc/apt/sources.list.d/ubuntu.sources
# Add ports for cross-arch
CODENAME=$(lsb_release -cs)
printf 'Types: deb\nURIs: http://ports.ubuntu.com/ubuntu-ports\nSuites: %s %s-updates %s-security\nComponents: main universe\nArchitectures: %s\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\n' \
"$CODENAME" "$CODENAME" "$CODENAME" "${{ matrix.deb_arch }}" \
| sudo tee /etc/apt/sources.list.d/${{ matrix.deb_arch }}-cross.sources
sudo apt-get update
- name: Install tools and cross-compiler
run: |
sudo apt-get update
sudo apt-get install -y build-essential make pkg-config cmake ninja-build
if [ -n "${{ matrix.cross_pkg }}" ]; then
sudo apt-get install -y ${{ matrix.cross_pkg }}
fi
- name: Install library dependencies from apt
run: |
S=""
if [ "${{ matrix.is_cross }}" = "true" ]; then S=":${{ matrix.deb_arch }}"; fi
# Header-only (arch-independent)
sudo apt-get install -y libboost-dev nlohmann-json3-dev
# Libraries with static archives
sudo apt-get install -y \
libssl-dev${S} \
libcurl4-openssl-dev${S} \
zlib1g-dev${S} \
uuid-dev${S} \
libbrotli-dev${S} \
libnghttp2-dev${S} \
libzstd-dev${S} \
libpsl-dev${S} \
libidn2-dev${S} \
libunistring-dev${S} \
libkrb5-dev${S} \
libldap-dev${S} \
libgssapi-krb5-2${S} \
librtmp-dev${S} 2>/dev/null || true
# Verify key static libraries
LIBDIR="/usr/lib/${{ matrix.triple }}"
echo "=== Static libraries in ${LIBDIR} ==="
for lib in ssl crypto curl z uuid brotlidec brotlicommon nghttp2 zstd psl idn2; do
if [ -f "${LIBDIR}/lib${lib}.a" ]; then echo " OK lib${lib}.a"
else echo " MISS lib${lib}.a"; fi
done
- name: Prepare sysroot
run: mkdir -p $SYSROOT/lib/pkgconfig $SYSROOT/include
- name: Build fmt from source
run: |
cd /tmp && git clone --depth 1 --branch 10.2.1 https://github.com/fmtlib/fmt.git && cd fmt
cmake -B build -G Ninja \
-DCMAKE_C_COMPILER=${{ matrix.cc }} \
-DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \
-DCMAKE_INSTALL_PREFIX=$SYSROOT \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF -DFMT_TEST=OFF -DFMT_DOC=OFF
cmake --build build -j$(nproc) && cmake --install build
- name: Build spdlog from source
run: |
cd /tmp && git clone --depth 1 --branch v1.14.1 https://github.com/gabime/spdlog.git && cd spdlog
cmake -B build -G Ninja \
-DCMAKE_C_COMPILER=${{ matrix.cc }} \
-DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \
-DCMAKE_INSTALL_PREFIX=$SYSROOT \
-DCMAKE_PREFIX_PATH=$SYSROOT \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DSPDLOG_BUILD_EXAMPLE=OFF -DSPDLOG_BUILD_TESTS=OFF \
-DSPDLOG_FMT_EXTERNAL=ON
cmake --build build -j$(nproc) && cmake --install build
- name: Build argon2 from source
run: |
cd /tmp && git clone --depth 1 https://github.com/P-H-C/phc-winner-argon2.git && cd phc-winner-argon2
${{ matrix.cc }} -std=c89 -O2 -c src/argon2.c -Iinclude -o argon2.o
${{ matrix.cc }} -std=c89 -O2 -c src/core.c -Iinclude -o core.o
${{ matrix.cc }} -std=c89 -O2 -c src/blake2/blake2b.c -Iinclude -o blake2b.o
${{ matrix.cc }} -std=c89 -O2 -c src/thread.c -Iinclude -o thread.o
${{ matrix.cc }} -std=c89 -O2 -c src/encoding.c -Iinclude -o encoding.o
${{ matrix.cc }} -std=c89 -O2 -c src/ref.c -Iinclude -o ref.o
${{ matrix.ar }} rcs $SYSROOT/lib/libargon2.a argon2.o core.o blake2b.o thread.o encoding.o ref.o
cp include/argon2.h $SYSROOT/include/
- name: Build phira-mp-server
run: |
LIBDIR="/usr/lib/${{ matrix.triple }}"
# Collect CFLAGS from pkg-config (include paths + defines)
export PKG_CONFIG_PATH="${SYSROOT}/lib/pkgconfig:${SYSROOT}/lib64/pkgconfig:${LIBDIR}/pkgconfig:/usr/share/pkgconfig"
export PKG_CONFIG_LIBDIR="${PKG_CONFIG_PATH}"
PKG_CFLAGS=$(pkg-config --cflags spdlog openssl libcurl nlohmann_json 2>/dev/null || true)
echo "PKG_CFLAGS: ${PKG_CFLAGS}"
# Determine which static libs are available for curl's deps
CURL_EXTRA=""
for lib in nghttp2 zstd brotlidec brotlicommon psl idn2 unistring; do
[ -f "${LIBDIR}/lib${lib}.a" ] && CURL_EXTRA="${CURL_EXTRA} -l${lib}"
done
# Kerberos / GSSAPI (libcurl on Ubuntu is compiled with these)
for lib in gssapi_krb5 krb5 k5crypto com_err krb5support rtmp; do
[ -f "${LIBDIR}/lib${lib}.a" ] && CURL_EXTRA="${CURL_EXTRA} -l${lib}"
done
# LDAP (try both ldap_r and ldap)
if [ -f "${LIBDIR}/libldap.a" ]; then CURL_EXTRA="${CURL_EXTRA} -lldap -llber"; fi
echo "CURL_EXTRA: ${CURL_EXTRA}"
make -j$(nproc) \
CXX="${{ matrix.cxx }}" \
TARGET=phira-mp-server \
CXXFLAGS="-std=c++20 -Wall -Wextra -O2 -pthread \
-DLOCALES_DIR=\"./locales\" \
-DSPDLOG_FMT_EXTERNAL -DSPDLOG_COMPILED_LIB \
-I${SYSROOT}/include \
${PKG_CFLAGS}" \
LDFLAGS="-static -L${SYSROOT}/lib -L${LIBDIR}" \
LIBS="-lspdlog -lfmt \
-lcurl -lssl -lcrypto \
-largon2 -luuid -lz \
${CURL_EXTRA} \
-lpthread -ldl"
file phira-mp-server
ls -lh phira-mp-server
- name: Package
run: |
mkdir -p release/${{ matrix.artifact }}
cp phira-mp-server release/${{ matrix.artifact }}/
cp -r locales release/${{ matrix.artifact }}/
cd release && tar czf ${{ matrix.artifact }}.tar.gz ${{ matrix.artifact }}/
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: release/${{ matrix.artifact }}.tar.gz
# ═══════════════════════════════════════════════════════════════════
# Windows amd64 — MSYS2 UCRT64, dynamic linking, bundle DLLs
# ═══════════════════════════════════════════════════════════════════
build-windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
install: >-
make
pkgconf
mingw-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-boost
mingw-w64-ucrt-x86_64-spdlog
mingw-w64-ucrt-x86_64-fmt
mingw-w64-ucrt-x86_64-nlohmann-json
mingw-w64-ucrt-x86_64-curl
mingw-w64-ucrt-x86_64-openssl
mingw-w64-ucrt-x86_64-ntldd
- name: Install or build argon2
run: |
pacman -S --noconfirm mingw-w64-ucrt-x86_64-argon2 2>/dev/null && exit 0
echo ">> Building argon2 from source"
cd /tmp
curl -sL https://github.com/P-H-C/phc-winner-argon2/archive/refs/tags/20190702.tar.gz | tar xz
cd phc-winner-argon2-20190702
gcc -std=c89 -O2 -c src/argon2.c -Iinclude -o argon2.o
gcc -std=c89 -O2 -c src/core.c -Iinclude -o core.o
gcc -std=c89 -O2 -c src/blake2/blake2b.c -Iinclude -o blake2b.o
gcc -std=c89 -O2 -c src/thread.c -Iinclude -o thread.o
gcc -std=c89 -O2 -c src/encoding.c -Iinclude -o encoding.o
gcc -std=c89 -O2 -c src/ref.c -Iinclude -o ref.o
ar rcs libargon2.a argon2.o core.o blake2b.o thread.o encoding.o ref.o
cp libargon2.a /ucrt64/lib/
cp include/argon2.h /ucrt64/include/
mkdir -p /ucrt64/lib/pkgconfig
printf 'prefix=/ucrt64\nlibdir=${prefix}/lib\nincludedir=${prefix}/include\n\nName: libargon2\nDescription: Argon2\nVersion: 20190702\nLibs: -L${libdir} -largon2\nCflags: -I${includedir}\n' \
> /ucrt64/lib/pkgconfig/libargon2.pc
- name: Build
run: |
make -j$(nproc) TARGET=phira-mp-server.exe LOCALES_DIR=\"./locales\"
file phira-mp-server.exe
- name: Collect DLLs and package
run: |
DEST=release/phira-mp-server-windows-amd64
mkdir -p "$DEST"
cp phira-mp-server.exe "$DEST/"
# ntldd outputs Windows-native paths (backslashes):
# libfoo.dll => D:\a\_temp\msys64\ucrt64\bin\libfoo.dll (0x...)
# Strategy: grep for 'ucrt64', take the DLL name (col 1), copy from /ucrt64/bin/
echo "=== Collecting UCRT64 DLLs ==="
ntldd -R phira-mp-server.exe | grep -i 'ucrt64' | awk '{gsub(/\t/," "); print $1}' | sort -u | while IFS= read -r dllname
do
dllname=$(echo "$dllname" | tr -d '[:space:]')
if [ -n "$dllname" ] && [ -f "/ucrt64/bin/$dllname" ]; then
echo " COPY: $dllname"
cp "/ucrt64/bin/$dllname" "$DEST/"
fi
done
cp -r locales "$DEST/"
echo ""
echo "=== Package contents ==="
ls -la "$DEST/"
- name: Create zip
shell: pwsh
run: |
Compress-Archive -Path release/phira-mp-server-windows-amd64 -DestinationPath release/phira-mp-server-windows-amd64.zip
- uses: actions/upload-artifact@v4
with:
name: phira-mp-server-windows-amd64
path: release/phira-mp-server-windows-amd64.zip
# ═══════════════════════════════════════════════════════════════════
# Release
# ═══════════════════════════════════════════════════════════════════
release:
needs: [build-linux, build-windows]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
- run: find artifacts -type f | sort
- uses: softprops/action-gh-release@v2
with:
files: |
artifacts/phira-mp-server-linux-amd64/*.tar.gz
artifacts/phira-mp-server-linux-arm64/*.tar.gz
artifacts/phira-mp-server-linux-armhf/*.tar.gz
artifacts/phira-mp-server-windows-amd64/*.zip
generate_release_notes: true