ModernUO/.github/workflows/build-test.yml
Kamron Batman 9d65439ff4
fix: require only the runtime native libraries on Linux
ModernUO mandated -dev packages on production servers for one reason:
DllImport never asks for a versioned SONAME, so libdeflate.so.0 and
libargon2.so.1 sitting in /usr/lib went unfound and the -dev package's
unversioned symlink was the only thing making resolution work. That is
fixed in the binding packages, so pick them up and stop asking for
build tooling on machines that compile nothing.

  LibDeflate.Bindings 1.0.3 -> 1.0.4
  Argon2.Bindings     1.17.0 -> 1.19.0

Also drops zstd, on every platform including macOS. ZstdNet bundles
libzstd for linux-x64, linux-arm64, osx-x64, osx-arm64 and win, and
nothing shells out to the CLI. Verified: the 15 ManagedArchive
round-trip tests pass in a container with no zstd package installed.

NativeLibraryChecker now asks whether the loader can find each library
rather than whether a named package is installed. Package-name checks
are what forced -dev in the first place, and no hardcoded name works
for ICU anyway: its apt package is release-specific (libicu74 on 24.04,
libicu76 on Alpine, libicu77 on Fedora). ldconfig -p is version
agnostic and is the loader's own cache, so one code path replaces the
apt/dnf/generic split.

ldconfig is treated as a positive signal only. musl's exits 0 while
producing no usable cache, so trusting a negative reported every
library missing on Alpine when all were installed; anything it does not
vouch for is now dlopen'd before being called missing. That fallback
bounds the .so.N probe per library, because a single small bound
reports ICU missing when it is present.

CI installs runtime packages only, deliberately: installing -dev there
would restore the unversioned symlink and mask the very resolution this
depends on, so a regression would sail through.

Adds --check-prereqs. The interactive flow was the only path that ran
these checks, so there was no way to verify a deployment target from a
script or a container.

Audited the rest of the codebase for the same hazard. ModernUO's only
other native imports are ws2_32.dll, which is always present on
Windows, and libc in SocketHelper. libc is not affected even though
libc.so is a libc6-dev linker script: DllImport("libc") was verified to
resolve and call successfully on both glibc and musl with no -dev
package installed. Nothing else P/Invokes, and no code here registers a
DllImportResolver.

Verified with 1.0.4 and 1.19.0: build plus 810 Server.Tests and 642
UOContent.Tests, and --check-prereqs reporting correctly on Debian,
Ubuntu, Fedora and Alpine with only the runtime packages installed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 22:04:06 -07:00

160 lines
5.7 KiB
YAML

name: Build
on:
push:
branches: [main]
paths:
- '.config/dotnet-tools.json'
- 'Projects/**'
- 'Directory.Build.props'
- 'global.json'
- 'version.json'
- '*.slnx'
pull_request:
branches: [main]
paths:
- '.config/dotnet-tools.json'
- 'Projects/**'
- 'Directory.Build.props'
- 'global.json'
- 'version.json'
- '*.slnx'
jobs:
build-macos:
runs-on: ${{ matrix.os }}
# A hung run otherwise bills the full 360-minute default before GitHub kills it.
timeout-minutes: 30
name: Build (${{ matrix.name }})
strategy:
fail-fast: false
matrix:
include:
- os: macos-15
name: MacOS 15
- os: macos-26
name: MacOS 26
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
- name: Install .NET
uses: actions/setup-dotnet@v6
with:
global-json-file: global.json
- name: Install Prerequisites
run: |
brew update
brew install icu4c libdeflate argon2
- name: Set Library Path
run: echo "DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH" >> $GITHUB_ENV
- name: Build
run: dotnet run --project Projects/BuildTool -- --config Release --skip-prereqs
- name: Migration Changes
run: git diff --exit-code ./**/Migrations/*.v*.json
- name: Test
# blame-hang kills a stuck test host after 10 minutes and reports the in-flight
# tests plus a process dump instead of hanging until the job timeout.
run: |
dotnet test --logger trx --results-directory ./TestResults --blame-hang --blame-hang-timeout 10m --blame-hang-dump-type full
if [ -z "$(find ./TestResults -name '*.trx' 2>/dev/null)" ]; then
echo "::error::No test result files were produced - no test projects ran. Failing to avoid masking failures."
exit 1
fi
- name: Upload test results on failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: TestResults-${{ matrix.name }}
path: ./TestResults
if-no-files-found: ignore
build-linux:
runs-on: ubuntu-latest
# A hung run otherwise bills the full 360-minute default before GitHub kills it.
timeout-minutes: 30
container:
image: ${{ matrix.container }}
options: --security-opt seccomp=unconfined
name: Build (${{ matrix.name }})
strategy:
fail-fast: false
matrix:
include:
- container: ubuntu:26.04
name: Ubuntu 26
packageManager: apt
- container: ubuntu:noble
name: Ubuntu 24
packageManager: apt
- container: ubuntu:jammy
name: Ubuntu 22
packageManager: apt
- container: debian:trixie
name: Debian 13
packageManager: apt
- container: debian:bookworm
name: Debian 12
packageManager: apt
- container: fedora:44
name: Fedora 44
packageManager: dnf
- container: quay.io/centos/centos:stream9
name: CentOS 9 Stream
packageManager: dnf
epel: true
- container: quay.io/centos/centos:stream10
name: CentOS 10 Stream
packageManager: dnf
epel: true
- container: almalinux:10
name: AlmaLinux 10
packageManager: dnf
epel: true
steps:
# Enable CRB before EPEL, per the EPEL quickstart. epel-next is not installed:
# none of the prerequisites need it, EPEL 10 does not have it, and it is one more
# mirrorlist to fetch.
- name: Enable EPEL and CRB
run: |
dnf upgrade --refresh -y
dnf install -y dnf-plugins-core
dnf config-manager --set-enabled crb
dnf install -y epel-release
if: ${{ matrix.epel }}
# Runtime packages only, deliberately. Installing the -dev packages here would add the
# unversioned .so symlink and mask the very thing the binding packages now probe for, so a
# regression in versioned-SONAME resolution would sail through CI.
- name: Install Prerequisites using dnf
run: dnf makecache --refresh && dnf install -y findutils libicu libdeflate libargon2
if: ${{ matrix.packageManager == 'dnf' }}
- name: Install Prerequisites using apt
run: apt-get update -y && apt-get install -y curl libicu-dev libdeflate0 libargon2-1 tzdata
if: ${{ matrix.packageManager == 'apt' }}
- uses: actions/checkout@v7
with:
fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
- name: Install .NET
uses: actions/setup-dotnet@v6
with:
global-json-file: global.json
- name: Build
run: dotnet run --project Projects/BuildTool -- --config Release --skip-prereqs
- name: Test
# blame-hang kills a stuck test host after 10 minutes and reports the in-flight
# tests plus a process dump instead of hanging until the job timeout.
run: |
dotnet test --logger trx --results-directory ./TestResults --blame-hang --blame-hang-timeout 10m --blame-hang-dump-type full
if [ -z "$(find ./TestResults -name '*.trx' 2>/dev/null)" ]; then
echo "::error::No test result files were produced - no test projects ran. Failing to avoid masking failures."
exit 1
fi
- name: Upload test results on failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: TestResults-${{ matrix.name }}
path: ./TestResults
if-no-files-found: ignore