Heal duplicate config paths and clean startup logs #3507
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: C/C++ CI | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: [master] | |
| tags: | |
| - 'v*' | |
| - 'preview-v*' | |
| paths-ignore: | |
| - '**.md' | |
| - 'wiki/**' | |
| - '.github/ISSUE_TEMPLATE/**' | |
| - '.github/FUNDING.yml' | |
| - '.github/CODE_OF_CONDUCT.md' | |
| - '.github/CONTRIBUTING.md' | |
| pull_request: | |
| paths-ignore: | |
| - '**.md' | |
| - 'wiki/**' | |
| # Cancel older in-progress runs for the same branch/PR ref. | |
| # This reduces wasted CI minutes when multiple pushes happen quickly. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| # Set least-privilege defaults for all jobs | |
| permissions: | |
| contents: read | |
| jobs: | |
| build-macOS: | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| actions: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: x86_64 | |
| runner: macos-15-intel | |
| arch: intel | |
| artifact_name: amiberry-macOS-x86_64-intermediate | |
| - name: Apple-Silicon | |
| runner: macos-15 | |
| arch: apple-silicon | |
| artifact_name: amiberry-macOS-arm64-intermediate | |
| steps: | |
| - name: Check out repository code | |
| uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Cache Homebrew | |
| if: matrix.arch == 'intel' | |
| uses: actions/cache@v5 | |
| with: | |
| path: /usr/local/Homebrew | |
| key: ${{ runner.os }}-brew-${{ hashFiles('Brewfile') }} | |
| restore-keys: | | |
| ${{ runner.os }}-brew- | |
| - name: Fix homebrew in Github Runner | |
| if: matrix.arch == 'intel' | |
| run: | | |
| for f in $(find /usr/local/bin -type l -print); do \ | |
| (readlink $f | grep -q -s "/Library") && echo Removing "$f" && rm -f "$f"; \ | |
| done || exit 0 | |
| - name: Pre-install gobject-introspection from source | |
| if: matrix.arch == 'intel' | |
| run: brew install --build-from-source gobject-introspection | |
| - name: Install dependencies (Homebrew) | |
| run: | | |
| brew update | |
| brew bundle | |
| - name: Build for macOS (${{ matrix.name }}) | |
| run: | | |
| cmake -B build && cmake --build build -j$(sysctl -n hw.ncpu) | |
| - name: Archive intermediate app bundle | |
| run: | | |
| tar -C build -czf Amiberry.app.tar.gz Amiberry.app | |
| - name: Upload intermediate app bundle | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: Amiberry.app.tar.gz | |
| retention-days: 1 | |
| merge-macOS-universal: | |
| needs: [build-macOS] | |
| runs-on: macos-15 | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| actions: write | |
| steps: | |
| - name: Check out repository code | |
| uses: actions/checkout@v5 | |
| - name: Download x86_64 app bundle | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: amiberry-macOS-x86_64-intermediate | |
| path: x86_64-app | |
| - name: Download arm64 app bundle | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: amiberry-macOS-arm64-intermediate | |
| path: arm64-app | |
| - name: Extract downloaded app bundles | |
| run: | | |
| tar -xzf x86_64-app/Amiberry.app.tar.gz -C x86_64-app | |
| tar -xzf arm64-app/Amiberry.app.tar.gz -C arm64-app | |
| - name: Create Universal binary with lipo | |
| run: | | |
| # macOS ships bash 3.2 (GPLv2); install bash 5 for associative arrays | |
| if [ "${BASH_VERSINFO[0]:-0}" -lt 4 ]; then | |
| brew install bash | |
| exec "$(brew --prefix)/bin/bash" --noprofile --norc -euo pipefail "$0" "$@" | |
| fi | |
| set -euo pipefail | |
| mkdir -p universal-app/Amiberry.app | |
| # Start with the arm64 app as the base (preserves bundle structure) | |
| cp -R arm64-app/Amiberry.app/ universal-app/Amiberry.app/ | |
| # Merge the main executable | |
| lipo -create \ | |
| x86_64-app/Amiberry.app/Contents/MacOS/Amiberry \ | |
| arm64-app/Amiberry.app/Contents/MacOS/Amiberry \ | |
| -output universal-app/Amiberry.app/Contents/MacOS/Amiberry | |
| chmod +x universal-app/Amiberry.app/Contents/MacOS/Amiberry | |
| # Helper: merge a single binary with lipo, skipping if already universal | |
| merge_binary() { | |
| local x86_file="$1" arm_file="$2" output="$3" | |
| if [ ! -f "$x86_file" ] || [ ! -f "$arm_file" ]; then | |
| return | |
| fi | |
| # If a file is already universal (e.g. capsimage builds fat), just copy it | |
| local x86_archs arm_archs | |
| x86_archs=$(lipo -info "$x86_file" 2>/dev/null | sed 's/.*: //') | |
| arm_archs=$(lipo -info "$arm_file" 2>/dev/null | sed 's/.*: //') | |
| if echo "$x86_archs" | grep -q "x86_64" && echo "$x86_archs" | grep -q "arm64"; then | |
| echo " Already universal: $(basename "$output") — copying as-is" | |
| cp "$x86_file" "$output" | |
| elif [ "$x86_archs" = "$arm_archs" ]; then | |
| echo " WARNING: same arch in both — copying x86 version: $(basename "$output")" | |
| cp "$x86_file" "$output" | |
| else | |
| lipo -create "$x86_file" "$arm_file" -output "$output" | |
| fi | |
| } | |
| # Helper: extract library stem from a versioned dylib filename | |
| # For libs with -N in the name (e.g. libglib-2.0), strip only the last version component | |
| # libglib-2.0.0.dylib → libglib-2.0 | |
| # libgobject-2.0.0.dylib → libgobject-2.0 | |
| # For all other libs, strip the entire trailing version | |
| # libavif.16.4.0.dylib → libavif | |
| # libSDL3.0.6.0.dylib → libSDL3 | |
| # libpng16.16.dylib → libpng16 | |
| lib_stem() { | |
| local name="${1%.dylib}" | |
| if [[ "$name" =~ -[0-9] ]] && [[ "$name" =~ \.[0-9]+([.][0-9]+)*$ ]]; then | |
| echo "${name%.*}" | |
| else | |
| echo "$name" | sed -E 's/\.[0-9]+(\.[0-9]+)*$//' | |
| fi | |
| } | |
| X86_FW="x86_64-app/Amiberry.app/Contents/Frameworks" | |
| ARM_FW="arm64-app/Amiberry.app/Contents/Frameworks" | |
| UNI_FW="universal-app/Amiberry.app/Contents/Frameworks" | |
| # Build stem → filename maps for both architectures | |
| declare -A x86_stems arm_stems | |
| if [ -d "$X86_FW" ]; then | |
| for f in "$X86_FW"/*.dylib; do | |
| [ -f "$f" ] || continue | |
| name=$(basename "$f") | |
| stem=$(lib_stem "$name") | |
| x86_stems["$stem"]="$name" | |
| done | |
| fi | |
| if [ -d "$ARM_FW" ]; then | |
| for f in "$ARM_FW"/*.dylib; do | |
| [ -f "$f" ] || continue | |
| name=$(basename "$f") | |
| stem=$(lib_stem "$name") | |
| arm_stems["$stem"]="$name" | |
| done | |
| fi | |
| # Parity check: warn about mismatches between architectures | |
| echo "=== Checking dylib parity between x86_64 and arm64 builds ===" | |
| had_mismatch=0 | |
| for stem in "${!arm_stems[@]}"; do | |
| arm_name="${arm_stems[$stem]}" | |
| x86_name="${x86_stems[$stem]:-}" | |
| if [ -z "$x86_name" ]; then | |
| echo " WARNING: $arm_name exists only in arm64 build (no x86_64 equivalent)" | |
| had_mismatch=1 | |
| elif [ "$arm_name" != "$x86_name" ]; then | |
| echo " WARNING: version mismatch for $stem: x86_64=$x86_name vs arm64=$arm_name" | |
| had_mismatch=1 | |
| fi | |
| done | |
| for stem in "${!x86_stems[@]}"; do | |
| if [ -z "${arm_stems[$stem]:-}" ]; then | |
| echo " WARNING: ${x86_stems[$stem]} exists only in x86_64 build (no arm64 equivalent)" | |
| had_mismatch=1 | |
| fi | |
| done | |
| if [ "$had_mismatch" = "0" ]; then | |
| echo " All dylibs match between architectures" | |
| fi | |
| # Merge Frameworks dylibs using stem-aware matching | |
| echo "=== Merging Frameworks dylibs ===" | |
| declare -A merged_stems | |
| # Collect all unique stems from both architectures | |
| all_stems=() | |
| for stem in "${!arm_stems[@]}"; do all_stems+=("$stem"); done | |
| for stem in "${!x86_stems[@]}"; do | |
| if [ -z "${arm_stems[$stem]:-}" ]; then | |
| all_stems+=("$stem") | |
| fi | |
| done | |
| for stem in "${all_stems[@]}"; do | |
| [ -n "${merged_stems[$stem]:-}" ] && continue | |
| merged_stems["$stem"]=1 | |
| x86_name="${x86_stems[$stem]:-}" | |
| arm_name="${arm_stems[$stem]:-}" | |
| if [ -n "$x86_name" ] && [ -n "$arm_name" ]; then | |
| if [ "$x86_name" = "$arm_name" ]; then | |
| # Same filename — standard merge | |
| echo " Merging: $arm_name" | |
| merge_binary "$X86_FW/$x86_name" "$ARM_FW/$arm_name" "$UNI_FW/$arm_name" | |
| else | |
| # Version mismatch — merge by stem, provide both names | |
| echo " Merging with version mismatch: x86_64=$x86_name + arm64=$arm_name" | |
| merge_binary "$X86_FW/$x86_name" "$ARM_FW/$arm_name" "$UNI_FW/$arm_name" | |
| ln -sf "$arm_name" "$UNI_FW/$x86_name" | |
| echo " → Universal dylib: $arm_name, symlink: $x86_name → $arm_name" | |
| fi | |
| elif [ -n "$arm_name" ]; then | |
| echo " Keeping arm64-only: $arm_name (no x86_64 equivalent)" | |
| elif [ -n "$x86_name" ]; then | |
| echo " Copying x86_64-only: $x86_name (not in arm64 base)" | |
| cp "$X86_FW/$x86_name" "$UNI_FW/$x86_name" | |
| fi | |
| done | |
| # Merge all dylibs in plugins (arm64 base + any x86-only plugins) | |
| UNI_PLUGINS="universal-app/Amiberry.app/Contents/Resources/plugins" | |
| X86_PLUGINS="x86_64-app/Amiberry.app/Contents/Resources/plugins" | |
| ARM_PLUGINS="arm64-app/Amiberry.app/Contents/Resources/plugins" | |
| if [ -d "$UNI_PLUGINS" ]; then | |
| for dylib in "$UNI_PLUGINS"/*.dylib; do | |
| [ -f "$dylib" ] || continue | |
| bname=$(basename "$dylib") | |
| merge_binary "$X86_PLUGINS/$bname" "$ARM_PLUGINS/$bname" "$dylib" | |
| done | |
| fi | |
| if [ -d "$X86_PLUGINS" ]; then | |
| for dylib in "$X86_PLUGINS"/*.dylib; do | |
| [ -f "$dylib" ] || continue | |
| bname=$(basename "$dylib") | |
| if [ ! -f "$UNI_PLUGINS/$bname" ]; then | |
| echo " Copying x86_64-only plugin: $bname" | |
| mkdir -p "$UNI_PLUGINS" | |
| cp "$dylib" "$UNI_PLUGINS/$bname" | |
| fi | |
| done | |
| fi | |
| # Verify the main binary is universal | |
| echo "=== Universal binary info ===" | |
| lipo -info universal-app/Amiberry.app/Contents/MacOS/Amiberry | |
| file universal-app/Amiberry.app/Contents/MacOS/Amiberry | |
| # Verify all Frameworks dylibs are universal | |
| echo "=== Verifying Frameworks dylibs ===" | |
| non_universal=0 | |
| for dylib in "$UNI_FW"/*.dylib; do | |
| [ -f "$dylib" ] || continue | |
| # Skip symlinks — they point to an already-checked universal dylib | |
| [ -L "$dylib" ] && continue | |
| archs=$(lipo -info "$dylib" 2>/dev/null | sed 's/.*: //') | |
| name=$(basename "$dylib") | |
| if echo "$archs" | grep -q "x86_64" && echo "$archs" | grep -q "arm64"; then | |
| echo " OK: $name ($archs)" | |
| else | |
| echo " WARNING: $name is NOT universal ($archs)" | |
| non_universal=1 | |
| fi | |
| done | |
| if [ "$non_universal" = "1" ]; then | |
| echo "::warning::Some Frameworks dylibs are not universal — the app may crash on one architecture" | |
| fi | |
| echo "=== Verifying dylib dependencies resolve ===" | |
| missing_deps=0 | |
| MAIN_BIN="universal-app/Amiberry.app/Contents/MacOS/Amiberry" | |
| for arch in x86_64 arm64; do | |
| while read -r line; do | |
| dep_name=$(echo "$line" | sed -E 's|.*@executable_path/../Frameworks/([^ ]+).*|\1|') | |
| if [ ! -f "$UNI_FW/$dep_name" ]; then | |
| echo " MISSING ($arch): $dep_name" | |
| missing_deps=1 | |
| fi | |
| done < <(otool -arch "$arch" -L "$MAIN_BIN" 2>/dev/null | grep '@executable_path/../Frameworks/' || true) | |
| done | |
| if [ "$missing_deps" = "1" ]; then | |
| echo "::error::Some @executable_path dylib references cannot be resolved — the app will crash on load" | |
| exit 1 | |
| fi | |
| - name: Install the Apple certificate | |
| env: | |
| APPLE_DEVID_CERT_BASE64: ${{ secrets.APPLE_DEVID_CERT_BASE64 }} | |
| APPLE_DEVID_CERT_P12_PASSWORD: ${{ secrets.APPLE_DEVID_CERT_P12_PASSWORD }} | |
| APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} | |
| run: | | |
| set +x # Mask output for security | |
| CERTIFICATE_PATH=$RUNNER_TEMP/apple_devid_cert.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| echo -n "$APPLE_DEVID_CERT_BASE64" | base64 --decode -o $CERTIFICATE_PATH | |
| security create-keychain -p $APPLE_KEYCHAIN_PASSWORD $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p $APPLE_KEYCHAIN_PASSWORD $KEYCHAIN_PATH | |
| security import $CERTIFICATE_PATH -P $APPLE_DEVID_CERT_P12_PASSWORD -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| security set-key-partition-list -S apple-tool:,apple: -s -k $APPLE_KEYCHAIN_PASSWORD $KEYCHAIN_PATH | |
| security list-keychains -d user -s $KEYCHAIN_PATH | |
| - name: Codesign the dylibs | |
| env: | |
| CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }} | |
| run: | | |
| for file in universal-app/Amiberry.app/Contents/Frameworks/*.dylib; do | |
| [ -f "$file" ] || continue | |
| [ -L "$file" ] && continue | |
| codesign -s "$CODESIGN_IDENTITY" -f -o runtime,hard "$file" | |
| done | |
| for file in universal-app/Amiberry.app/Contents/Resources/plugins/*.dylib; do | |
| [ -f "$file" ] || continue | |
| [ -L "$file" ] && continue | |
| codesign -s "$CODESIGN_IDENTITY" -f -o runtime,hard "$file" | |
| done | |
| - name: Codesign the app | |
| env: | |
| CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }} | |
| run: | | |
| codesign -s "$CODESIGN_IDENTITY" -f -o runtime,hard --entitlements packaging/macos/Amiberry.entitlements universal-app/Amiberry.app | |
| - name: Verify app entitlements and signature | |
| run: | | |
| codesign -d --entitlements :- universal-app/Amiberry.app | |
| codesign --verify --deep --strict universal-app/Amiberry.app | |
| echo "Signature verification passed" | |
| - name: Create a zip to send to the notary service | |
| run: | | |
| ditto -c -k --keepParent universal-app/Amiberry.app Amiberry-${{ github.sha }}-macOS-universal.zip | |
| - name: Send the file to be notarized by Apple | |
| run: | | |
| set -uo pipefail | |
| ZIP="Amiberry-${{ github.sha }}-macOS-universal.zip" | |
| # Stream notarytool output to both the log and a file, without | |
| # letting `set -e` kill the script before we can echo the rejection. | |
| xcrun notarytool submit "$ZIP" --wait \ | |
| --apple-id "midwan@gmail.com" \ | |
| --password ${{ secrets.APPLE_NOTARY_APP_PASSWORD }} \ | |
| --team-id ${{ secrets.APPLE_TEAM_ID }} 2>&1 | tee notary.log | |
| STATUS=${PIPESTATUS[0]} | |
| SUBMISSION_ID=$(awk '/id:/ {print $2; exit}' notary.log) | |
| if [ "$STATUS" -eq 0 ] && grep -q 'status: Accepted' notary.log; then | |
| echo "Notarization accepted." | |
| else | |
| echo "::error::Notarization failed (notarytool exit=$STATUS) — fetching rejection log" | |
| if [ -n "$SUBMISSION_ID" ]; then | |
| xcrun notarytool log "$SUBMISSION_ID" \ | |
| --apple-id "midwan@gmail.com" \ | |
| --password ${{ secrets.APPLE_NOTARY_APP_PASSWORD }} \ | |
| --team-id ${{ secrets.APPLE_TEAM_ID }} || true | |
| fi | |
| exit 1 | |
| fi | |
| - name: Staple the notary receipt to the application bundle | |
| run: | | |
| xcrun stapler staple universal-app/Amiberry.app | |
| rm Amiberry-${{ github.sha }}-macOS-universal.zip | |
| - name: Create DMG package | |
| run: | | |
| set -euo pipefail | |
| # Read version from CMakeLists.txt (BSD-compatible, no grep -P) | |
| VERSION=$(sed -n 's/.*VERSION \([0-9]*\.[0-9]*\.[0-9]*\).*/\1/p' CMakeLists.txt | head -1) | |
| PRE_RELEASE=$(sed -n 's/.*VERSION_PRE_RELEASE "\([0-9]*\)".*/\1/p' CMakeLists.txt | head -1) | |
| if [ -n "$PRE_RELEASE" ]; then | |
| DMG_VERSION="${VERSION}~pre${PRE_RELEASE}" | |
| else | |
| DMG_VERSION="${VERSION}" | |
| fi | |
| DMG_NAME="Amiberry-${DMG_VERSION}-macOS-universal" | |
| # Create a staging directory for DMG contents | |
| STAGING="dmg-staging" | |
| rm -rf "$STAGING" | |
| mkdir -p "$STAGING" | |
| cp -R universal-app/Amiberry.app "$STAGING/" | |
| ln -s /Applications "$STAGING/Applications" | |
| # Copy background image | |
| mkdir -p "$STAGING/.background" | |
| cp packaging/dmg/AppDMGBackground.tiff "$STAGING/.background/background.tiff" | |
| # Create a writable DMG, apply Finder layout/background, then compress. | |
| RW_DMG="${DMG_NAME}-rw.dmg" | |
| hdiutil create -volname "Amiberry" \ | |
| -srcfolder "$STAGING" \ | |
| -ov -format UDRW \ | |
| "$RW_DMG" | |
| DEVICE="" | |
| cleanup() { | |
| if [ -n "$DEVICE" ]; then | |
| hdiutil detach "$DEVICE" || true | |
| fi | |
| } | |
| trap cleanup EXIT | |
| DEVICE=$(hdiutil attach -readwrite -noverify -noautoopen "$RW_DMG" | awk '/\/Volumes\/Amiberry/ {print $1; exit}') | |
| if [ -z "$DEVICE" ]; then | |
| echo "Failed to attach DMG." | |
| exit 1 | |
| fi | |
| osascript packaging/dmg/AppDMGSetup.scpt "Amiberry" | |
| hdiutil detach "$DEVICE" | |
| DEVICE="" | |
| trap - EXIT | |
| hdiutil convert "$RW_DMG" -format UDZO -imagekey zlib-level=9 -ov -o "${DMG_NAME}.dmg" | |
| rm -f "$RW_DMG" | |
| rm -rf "$STAGING" | |
| echo "DMG_NAME=${DMG_NAME}" >> "$GITHUB_ENV" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: amiberry-macOS-universal | |
| path: Amiberry-*.dmg | |
| retention-days: 7 | |
| build-ubuntu: | |
| runs-on: ${{ matrix.runner }} | |
| container: ${{ matrix.container }} | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| actions: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: 22.04-amd64 | |
| runner: ubuntu-latest | |
| container: ubuntu:22.04 | |
| artifact_name: amiberry-ubuntu-22.04-amd64 | |
| sdl_source: setup-sdl | |
| codename: jammy | |
| extra_cmake_args: -DBUNDLE_SDL=ON | |
| - name: 24.04-amd64 | |
| runner: ubuntu-latest | |
| container: ubuntu:24.04 | |
| artifact_name: amiberry-ubuntu-24.04-amd64 | |
| sdl_source: setup-sdl | |
| codename: noble | |
| extra_cmake_args: -DBUNDLE_SDL=ON | |
| - name: 25.10-amd64 | |
| runner: ubuntu-latest | |
| container: ubuntu:25.10 | |
| artifact_name: amiberry-ubuntu-25.10-amd64 | |
| sdl_source: setup-sdl | |
| codename: plucky | |
| extra_cmake_args: | |
| - name: 25.10-arm64 | |
| runner: ubuntu-24.04-arm | |
| container: ubuntu:25.10 | |
| artifact_name: amiberry-ubuntu-25.10-arm64 | |
| sdl_source: apt | |
| codename: plucky | |
| extra_cmake_args: | |
| steps: | |
| - name: Install prerequisites | |
| run: | | |
| apt-get update | |
| apt-get install -y sudo git gh | |
| - name: Check out repository code | |
| uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Install ccache | |
| run: sudo apt-get install -y ccache | |
| - name: Cache ccache | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.ccache | |
| key: ${{ runner.os }}-${{ matrix.name }}-ccache-${{ github.ref }}-${{ github.sha }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ matrix.name }}-ccache-${{ github.ref }}- | |
| ${{ runner.os }}-${{ matrix.name }}-ccache- | |
| - name: Cache apt packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: /var/cache/apt/archives | |
| key: ${{ runner.os }}-${{ matrix.name }}-apt-${{ hashFiles('**/CMakeLists.txt') }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ matrix.name }}-apt- | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y build-essential cmake ninja-build file zip libflac-dev libmpeg2-4-dev libpng-dev libasound2-dev libmpg123-dev libportmidi-dev libserialport-dev libenet-dev libpcap-dev libzstd-dev libcurl4-openssl-dev nlohmann-json3-dev libdbus-1-dev | |
| - name: Install SDL3 and SDL3_image | |
| if: matrix.sdl_source == 'setup-sdl' | |
| id: sdl | |
| uses: libsdl-org/setup-sdl@main | |
| with: | |
| install-linux-dependencies: true | |
| version: 3.2.8 | |
| version-sdl-image: 3.2.4 | |
| pre-release: false | |
| - name: Install SDL3 and SDL3_image from apt | |
| if: matrix.sdl_source == 'apt' | |
| run: sudo apt-get install -y libsdl3-dev libsdl3-image-dev | |
| - name: make for Ubuntu ${{ matrix.name }} with setup-sdl | |
| if: matrix.sdl_source == 'setup-sdl' | |
| env: | |
| CCACHE_DIR: ~/.ccache | |
| run: | | |
| export PATH="/usr/lib/ccache:$PATH" | |
| cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_PREFIX_PATH="${{ steps.sdl.outputs.prefix }}" -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DDISTRO_CODENAME=${{ matrix.codename }} ${{ matrix.extra_cmake_args }} && cmake --build build -j$(nproc) | |
| cpack -G DEB --config build/CPackConfig.cmake | |
| - name: make for Ubuntu ${{ matrix.name }} with apt SDL3 | |
| if: matrix.sdl_source == 'apt' | |
| env: | |
| CCACHE_DIR: ~/.ccache | |
| run: | | |
| export PATH="/usr/lib/ccache:$PATH" | |
| cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DDISTRO_CODENAME=${{ matrix.codename }} && cmake --build build -j$(nproc) | |
| cpack -G DEB --config build/CPackConfig.cmake | |
| - name: Upload artifact | |
| if: always() && github.ref_type != 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: amiberry_*.deb | |
| retention-days: 7 | |
| - name: ZIP package for release | |
| if: github.ref_type == 'tag' | |
| run: zip -r ${{ matrix.artifact_name }}.zip amiberry_*.deb | |
| - name: Upload artifact for release | |
| if: always() && github.ref_type == 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: amiberry-*.zip | |
| build-fedora: | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| actions: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: x86_64 | |
| runner: ubuntu-latest | |
| image: midwan/amiberry-fedora-x86_64:latest | |
| artifact_name: amiberry-fedora-x86_64 | |
| release_suffix: fedora-x86_64 | |
| codename: fc41 | |
| - name: aarch64 | |
| runner: ubuntu-24.04-arm | |
| image: midwan/amiberry-fedora-arm64:latest | |
| artifact_name: amiberry-fedora-aarch64 | |
| release_suffix: fedora-aarch64 | |
| codename: fc41 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Run the build process with Docker | |
| run: | | |
| docker run --rm -v ${{ github.workspace }}:/build ${{ matrix.image }} \ | |
| bash -c "cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DDISTRO_CODENAME=${{ matrix.codename }} && cmake --build build -j\$(nproc) && cpack -G RPM --config build/CPackConfig.cmake" | |
| - name: Upload artifact | |
| if: always() && github.ref_type != 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: amiberry-*.rpm | |
| retention-days: 7 | |
| - name: ZIP package for release | |
| if: github.ref_type == 'tag' | |
| run: zip -r amiberry-${{ github.ref_name }}-${{ matrix.release_suffix }}.zip amiberry-*.rpm | |
| - name: Upload artifact for release | |
| if: always() && github.ref_type == 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: amiberry-*.zip | |
| build-debian-amd64: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| actions: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: bookworm | |
| image: midwan/amiberry-debian-x86_64:bookworm | |
| artifact_name: amiberry-debian-bookworm-amd64 | |
| release_name: debian-bookworm-amd64 | |
| codename: bookworm | |
| extra_cmake_args: -DBUNDLE_SDL=ON | |
| - name: trixie | |
| image: midwan/amiberry-debian-x86_64:trixie | |
| artifact_name: amiberry-debian-trixie-amd64 | |
| release_name: debian-trixie-amd64 | |
| codename: trixie | |
| extra_cmake_args: | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Run the build process with Docker | |
| run: | | |
| docker run --rm -v ${{ github.workspace }}:/build ${{ matrix.image }} \ | |
| bash -c "cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DDISTRO_CODENAME=${{ matrix.codename }} ${{ matrix.extra_cmake_args }} && cmake --build build -j\$(nproc) && cpack -G DEB --config build/CPackConfig.cmake" | |
| - name: Upload artifact | |
| if: always() && github.ref_type != 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: "amiberry_*.deb" | |
| retention-days: 7 | |
| - name: ZIP package for release | |
| if: github.ref_type == 'tag' | |
| run: zip -r "amiberry-${{ github.ref_name }}-${{ matrix.release_name }}.zip" amiberry_*.deb | |
| - name: Upload artifact for release | |
| if: always() && github.ref_type == 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: "amiberry-*.zip" | |
| build-debian-arm64: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| actions: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: bookworm | |
| image: midwan/amiberry-debian-aarch64:bookworm | |
| artifact_name: amiberry-debian-bookworm-arm64 | |
| release_name: debian-bookworm-arm64 | |
| codename: bookworm | |
| extra_cmake_args: -DBUNDLE_SDL=ON | |
| - name: trixie | |
| image: midwan/amiberry-debian-aarch64:trixie | |
| artifact_name: amiberry-debian-trixie-arm64 | |
| release_name: debian-trixie-arm64 | |
| codename: trixie | |
| extra_cmake_args: | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Run the build process with Docker | |
| run: | | |
| docker run --rm -v ${{ github.workspace }}:/build ${{ matrix.image }} \ | |
| bash -c "cmake -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchain-aarch64-linux-gnu.cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DDISTRO_CODENAME=${{ matrix.codename }} ${{ matrix.extra_cmake_args }} && cmake --build build -j\$(nproc) && cpack -G DEB --config build/CPackConfig.cmake" | |
| - name: Upload artifact | |
| if: always() && github.ref_type != 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: "amiberry_*.deb" | |
| retention-days: 7 | |
| - name: ZIP package for release | |
| if: github.ref_type == 'tag' | |
| run: zip -r amiberry-${{ github.ref_name }}-${{ matrix.release_name }}.zip amiberry_*.deb | |
| - name: Upload artifact for release | |
| if: always() && github.ref_type == 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: "amiberry-*.zip" | |
| # Windows x64: native build on windows-latest using llvm-mingw (clang). | |
| # Shares the toolchain with build-windows-arm64 so Windows on Arm and x64 | |
| # use a single compiler stack. Replaced the previous MSYS2/MinGW-w64 GCC | |
| # build once the llvm-mingw shakeout was stable. | |
| build-windows: | |
| runs-on: windows-latest | |
| # Cold-cache runs (vcpkg + llvm-mingw downloads) finish in ~24 min, | |
| # warm-cache runs in ~10-15 min — see build-windows-arm64 for comparison. | |
| # 60 min gives ~2.5x headroom over the cold-cache observation, plenty for | |
| # SignPath round-trips and any transient slowness. | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: read | |
| actions: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: x64 | |
| artifact_name: amiberry-windows-x64 | |
| release_suffix: windows-x64 | |
| env: | |
| LLVM_MINGW_TAG: "20260421" | |
| LLVM_MINGW_FLAVOR: "ucrt-x86_64" | |
| VCPKG_DEFAULT_TRIPLET: x64-mingw-dynamic | |
| VCPKG_DEFAULT_HOST_TRIPLET: x64-mingw-dynamic | |
| steps: | |
| - name: Check out repository code | |
| uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Cache llvm-mingw toolchain | |
| id: cache-llvm-mingw | |
| uses: actions/cache@v5 | |
| with: | |
| path: C:/llvm-mingw | |
| key: llvm-mingw-${{ env.LLVM_MINGW_TAG }}-${{ env.LLVM_MINGW_FLAVOR }} | |
| - name: Install llvm-mingw | |
| if: steps.cache-llvm-mingw.outputs.cache-hit != 'true' | |
| shell: pwsh | |
| run: | | |
| $tag = "${env:LLVM_MINGW_TAG}" | |
| $flavor = "${env:LLVM_MINGW_FLAVOR}" | |
| $base = "llvm-mingw-$tag-$flavor" | |
| $url = "https://github.com/mstorsjo/llvm-mingw/releases/download/$tag/$base.zip" | |
| Write-Host "Downloading $url" | |
| Invoke-WebRequest -Uri $url -OutFile "$env:RUNNER_TEMP\llvm-mingw.zip" | |
| Expand-Archive -Path "$env:RUNNER_TEMP\llvm-mingw.zip" -DestinationPath "$env:RUNNER_TEMP\llvm-mingw" | |
| New-Item -ItemType Directory -Force -Path "C:/llvm-mingw" | Out-Null | |
| # The archive extracts into a single subdir named $base; flatten. | |
| Move-Item "$env:RUNNER_TEMP\llvm-mingw\$base\*" "C:/llvm-mingw" -Force | |
| - name: Expose llvm-mingw to subsequent steps | |
| shell: pwsh | |
| run: | | |
| "LLVM_MINGW_ROOT=C:/llvm-mingw" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| "C:/llvm-mingw/bin" | Out-File -FilePath $env:GITHUB_PATH -Append | |
| - name: Bootstrap vcpkg | |
| shell: pwsh | |
| run: | | |
| if (-not (Test-Path "C:/vcpkg/.git")) { | |
| git clone --depth 1 https://github.com/microsoft/vcpkg.git C:/vcpkg | |
| } | |
| & C:/vcpkg/bootstrap-vcpkg.bat -disableMetrics | |
| "VCPKG_ROOT=C:/vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| - name: Cache vcpkg installed packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: out/build/windows-release/vcpkg_installed | |
| # Include LLVM_MINGW_TAG so a toolchain bump invalidates the | |
| # cache: deps built with one CRT/runtime can be ABI-incompatible | |
| # with binaries linked by the new toolchain (the original | |
| # x64-mingw-dynamic + 20250910 cache showed up as | |
| # "undefined symbol: clock_gettime64" link errors). | |
| key: ${{ runner.os }}-x64-vcpkg-${{ env.LLVM_MINGW_TAG }}-${{ hashFiles('vcpkg.json', 'cmake/Toolchain-x86_64-w64-mingw32.cmake') }} | |
| restore-keys: | | |
| ${{ runner.os }}-x64-vcpkg-${{ env.LLVM_MINGW_TAG }}- | |
| - name: Configure | |
| shell: bash | |
| run: | | |
| cmake --preset windows-release | |
| - name: Build | |
| shell: bash | |
| run: | | |
| cmake --build out/build/windows-release -j$NUMBER_OF_PROCESSORS | |
| - name: Install (portable layout) | |
| shell: bash | |
| run: | | |
| rm -rf out/install/windows-release | |
| rm -rf out/package/windows-release | |
| cmake --install out/build/windows-release --prefix out/install/windows-release | |
| - name: Package (Portable ZIP) | |
| shell: bash | |
| run: | | |
| PORTABLE_ROOT="out/install/windows-release" | |
| test -f "$PORTABLE_ROOT/Amiberry.exe" | |
| : > "$PORTABLE_ROOT/amiberry.portable" | |
| if [ "${{ github.ref_type }}" = "tag" ]; then | |
| ZIP_NAME="amiberry-${{ github.ref_name }}-${{ matrix.release_suffix }}-portable.zip" | |
| else | |
| ZIP_NAME="amiberry-${{ github.sha }}-${{ matrix.release_suffix }}-portable.zip" | |
| fi | |
| PACKAGE_ROOT="out/package/windows-release/${ZIP_NAME%.zip}" | |
| mkdir -p "$(dirname "$PACKAGE_ROOT")" | |
| cmake -E copy_directory "$PORTABLE_ROOT" "$PACKAGE_ROOT" | |
| ZIP_PATH="$PWD/$ZIP_NAME" | |
| (cd out/package/windows-release && cmake -E tar cf "$ZIP_PATH" --format=zip "${ZIP_NAME%.zip}") | |
| - name: Verify Portable ZIP | |
| shell: pwsh | |
| run: | | |
| $zip = Get-ChildItem "$PWD/amiberry-*-portable.zip" | Select-Object -First 1 | |
| if (-not $zip) { | |
| throw "Portable ZIP not found." | |
| } | |
| $verifyRoot = Join-Path $PWD "portable-smoke" | |
| Remove-Item -Recurse -Force $verifyRoot -ErrorAction SilentlyContinue | |
| New-Item -ItemType Directory -Path $verifyRoot | Out-Null | |
| Expand-Archive -Path $zip.FullName -DestinationPath $verifyRoot | |
| $portableDir = Get-ChildItem $verifyRoot -Directory | Select-Object -First 1 | |
| if (-not $portableDir) { | |
| throw "Portable ZIP did not contain a top-level directory." | |
| } | |
| $exePath = Join-Path $portableDir.FullName "Amiberry.exe" | |
| if (-not (Test-Path $exePath)) { | |
| throw "Portable ZIP is missing Amiberry.exe." | |
| } | |
| $alternateWorkingDir = Join-Path $verifyRoot "alternate-cwd" | |
| New-Item -ItemType Directory -Path $alternateWorkingDir | Out-Null | |
| $stdoutFile = Join-Path $verifyRoot "dump-paths.stdout" | |
| $stderrFile = Join-Path $verifyRoot "dump-paths.stderr" | |
| $proc = Start-Process -FilePath $exePath -ArgumentList "--dump-paths" -WorkingDirectory $alternateWorkingDir -PassThru -Wait -NoNewWindow -RedirectStandardOutput $stdoutFile -RedirectStandardError $stderrFile | |
| if ($proc.ExitCode -ne 0) { | |
| Write-Host "STDOUT:" | |
| if (Test-Path $stdoutFile) { Get-Content $stdoutFile } | |
| Write-Host "STDERR:" | |
| if (Test-Path $stderrFile) { Get-Content $stderrFile } | |
| throw "--dump-paths failed with exit code $($proc.ExitCode)." | |
| } | |
| $lines = @{} | |
| Get-Content $stdoutFile | ForEach-Object { | |
| if ($_ -match '^(.*?)=(.*)$') { | |
| $lines[$matches[1]] = $matches[2] | |
| } | |
| } | |
| function Normalize-PortablePath([string]$path) { | |
| if ([string]::IsNullOrWhiteSpace($path)) { return "" } | |
| return $path.Replace('\', '/').TrimEnd('/').ToLowerInvariant() | |
| } | |
| $expectedRoot = Normalize-PortablePath $portableDir.FullName | |
| $expectedMarker = "$expectedRoot/amiberry.portable" | |
| $expectedConfig = "$expectedRoot/configurations" | |
| $expectedSettings = "$expectedRoot/settings" | |
| $expectedData = "$expectedRoot/data" | |
| $expectedPlugins = "$expectedRoot/plugins" | |
| $blockedRoots = @() | |
| if ($env:LOCALAPPDATA) { $blockedRoots += (Normalize-PortablePath $env:LOCALAPPDATA) } | |
| if ($env:USERPROFILE) { $blockedRoots += (Normalize-PortablePath $env:USERPROFILE) } | |
| function Assert-PortablePath([string]$key, [string]$expected) { | |
| $actual = Normalize-PortablePath $lines[$key] | |
| if ($actual -ne $expected) { | |
| throw "$key mismatch: '$($lines[$key])' vs '$expected'." | |
| } | |
| } | |
| function Assert-NotUnderBlockedRoots([string]$key) { | |
| $actual = Normalize-PortablePath $lines[$key] | |
| foreach ($blockedRoot in $blockedRoots) { | |
| if (-not [string]::IsNullOrWhiteSpace($blockedRoot) -and $actual.StartsWith($blockedRoot)) { | |
| throw "$key unexpectedly resolved under host profile path '$($lines[$key])'." | |
| } | |
| } | |
| } | |
| if ($lines["portable_mode"] -ne "1") { | |
| throw "portable_mode expected 1, got '$($lines["portable_mode"])'." | |
| } | |
| Assert-PortablePath "portable_root" $expectedRoot | |
| Assert-PortablePath "portable_marker_file" $expectedMarker | |
| Assert-PortablePath "home_dir" $expectedRoot | |
| Assert-PortablePath "config_path" $expectedConfig | |
| Assert-PortablePath "settings_dir" $expectedSettings | |
| Assert-PortablePath "data_dir" $expectedData | |
| Assert-PortablePath "plugins_dir" $expectedPlugins | |
| Assert-NotUnderBlockedRoots "portable_root" | |
| Assert-NotUnderBlockedRoots "portable_marker_file" | |
| Assert-NotUnderBlockedRoots "home_dir" | |
| Assert-NotUnderBlockedRoots "config_path" | |
| Assert-NotUnderBlockedRoots "settings_dir" | |
| Assert-NotUnderBlockedRoots "data_dir" | |
| Assert-NotUnderBlockedRoots "plugins_dir" | |
| if (Test-Path (Join-Path $portableDir.FullName "Settings")) { | |
| throw "--dump-paths should not create Settings in the portable tree." | |
| } | |
| if (Test-Path (Join-Path $portableDir.FullName "Configurations")) { | |
| throw "--dump-paths should not create Configurations in the portable tree." | |
| } | |
| if (Test-Path (Join-Path $portableDir.FullName "conf")) { | |
| throw "--dump-paths should not create conf in the portable tree." | |
| } | |
| # x86_64 builds include the JIT, so exercise --jit-selftest from the | |
| # portable layout. Mirrors the build-windows-arm64 smoke test. | |
| $jitStdoutFile = Join-Path $verifyRoot "jit-selftest.stdout" | |
| $jitStderrFile = Join-Path $verifyRoot "jit-selftest.stderr" | |
| $jitProc = Start-Process -FilePath $exePath -ArgumentList "--jit-selftest" ` | |
| -WorkingDirectory $portableDir.FullName -PassThru -Wait -NoNewWindow ` | |
| -RedirectStandardOutput $jitStdoutFile -RedirectStandardError $jitStderrFile | |
| if ($jitProc.ExitCode -ne 0) { | |
| Write-Host "JIT SELFTEST STDOUT:"; if (Test-Path $jitStdoutFile) { Get-Content $jitStdoutFile } | |
| Write-Host "JIT SELFTEST STDERR:"; if (Test-Path $jitStderrFile) { Get-Content $jitStderrFile } | |
| throw "--jit-selftest failed with exit code $($jitProc.ExitCode)." | |
| } | |
| Write-Host "x64 (llvm-mingw) Amiberry.exe ran --jit-selftest successfully." | |
| - name: Package (InnoSetup) | |
| shell: pwsh | |
| run: | | |
| cpack -G INNOSETUP --config out/build/windows-release/CPackConfig.cmake | |
| - name: Detect SignPath configuration | |
| id: signpath-config | |
| shell: pwsh | |
| env: | |
| SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }} | |
| SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} | |
| SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} | |
| SIGNPATH_SIGNING_POLICY_SLUG: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} | |
| SIGNPATH_ARTIFACT_CONFIGURATION_SLUG: ${{ vars.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} | |
| run: | | |
| if ($env:GITHUB_EVENT_NAME -eq "pull_request") { | |
| "enabled=false" >> $env:GITHUB_OUTPUT | |
| Write-Host "SignPath disabled for pull_request events." | |
| exit 0 | |
| } | |
| $required = @{ | |
| SIGNPATH_API_TOKEN = $env:SIGNPATH_API_TOKEN | |
| SIGNPATH_ORGANIZATION_ID = $env:SIGNPATH_ORGANIZATION_ID | |
| SIGNPATH_PROJECT_SLUG = $env:SIGNPATH_PROJECT_SLUG | |
| SIGNPATH_SIGNING_POLICY_SLUG = $env:SIGNPATH_SIGNING_POLICY_SLUG | |
| SIGNPATH_ARTIFACT_CONFIGURATION_SLUG = $env:SIGNPATH_ARTIFACT_CONFIGURATION_SLUG | |
| } | |
| $missing = @( | |
| $required.GetEnumerator() | | |
| Where-Object { [string]::IsNullOrWhiteSpace($_.Value) } | | |
| ForEach-Object { $_.Key } | | |
| Sort-Object | |
| ) | |
| if ($missing.Count -gt 0) { | |
| "enabled=false" >> $env:GITHUB_OUTPUT | |
| Write-Host "SignPath disabled because the following settings are missing: $($missing -join ', ')" | |
| exit 0 | |
| } | |
| "enabled=true" >> $env:GITHUB_OUTPUT | |
| Write-Host "SignPath enabled for this run." | |
| - name: Upload unsigned Windows artifacts for SignPath | |
| if: steps.signpath-config.outputs.enabled == 'true' | |
| id: signpath-unsigned | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }}-signpath-unsigned | |
| path: | | |
| amiberry-*.zip | |
| amiberry-*.exe | |
| if-no-files-found: error | |
| retention-days: 1 | |
| compression-level: 0 | |
| - name: Submit SignPath signing request | |
| if: steps.signpath-config.outputs.enabled == 'true' | |
| id: signpath | |
| uses: SignPath/github-action-submit-signing-request@v2 | |
| with: | |
| api-token: ${{ secrets.SIGNPATH_API_TOKEN }} | |
| organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} | |
| project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} | |
| signing-policy-slug: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} | |
| artifact-configuration-slug: ${{ vars.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} | |
| github-artifact-id: ${{ steps.signpath-unsigned.outputs.artifact-id }} | |
| wait-for-completion: true | |
| wait-for-completion-timeout-in-seconds: 1200 | |
| output-artifact-directory: signpath-signed | |
| - name: Replace unsigned artifacts with signed outputs | |
| if: steps.signpath-config.outputs.enabled == 'true' | |
| shell: pwsh | |
| run: | | |
| $downloadDir = Join-Path $PWD "signpath-signed" | |
| if (-not (Test-Path $downloadDir)) { | |
| throw "Signed artifact directory '$downloadDir' was not created." | |
| } | |
| $portableZip = Get-ChildItem $downloadDir -Recurse -File -Filter "amiberry-*-portable.zip" | Select-Object -First 1 | |
| if (-not $portableZip) { | |
| throw "Signed portable ZIP was not found in '$downloadDir'." | |
| } | |
| $installer = Get-ChildItem $downloadDir -Recurse -File -Filter "amiberry-*.exe" | Select-Object -First 1 | |
| if (-not $installer) { | |
| throw "Signed installer EXE was not found in '$downloadDir'." | |
| } | |
| Copy-Item $portableZip.FullName $PWD -Force | |
| Copy-Item $installer.FullName $PWD -Force | |
| Write-Host "Replaced workspace artifacts with SignPath-signed outputs." | |
| - name: Verify SignPath-signed artifacts | |
| if: steps.signpath-config.outputs.enabled == 'true' | |
| shell: pwsh | |
| run: | | |
| $installer = Get-ChildItem "$PWD/amiberry-*.exe" | Select-Object -First 1 | |
| if (-not $installer) { | |
| throw "Signed installer EXE not found in workspace." | |
| } | |
| $installerSignature = Get-AuthenticodeSignature $installer.FullName | |
| if ($installerSignature.Status -eq "NotSigned" -or -not $installerSignature.SignerCertificate) { | |
| throw "Installer EXE is missing an Authenticode signature." | |
| } | |
| Write-Host "Installer signature status: $($installerSignature.Status)" | |
| $portableZip = Get-ChildItem "$PWD/amiberry-*-portable.zip" | Select-Object -First 1 | |
| if (-not $portableZip) { | |
| throw "Signed portable ZIP not found in workspace." | |
| } | |
| $verifyRoot = Join-Path $PWD "portable-signed-smoke" | |
| Remove-Item -Recurse -Force $verifyRoot -ErrorAction SilentlyContinue | |
| New-Item -ItemType Directory -Path $verifyRoot | Out-Null | |
| Expand-Archive -Path $portableZip.FullName -DestinationPath $verifyRoot | |
| $portableExe = Get-ChildItem $verifyRoot -Recurse -File -Filter "Amiberry.exe" | Select-Object -First 1 | |
| if (-not $portableExe) { | |
| throw "Portable ZIP is missing Amiberry.exe after SignPath signing." | |
| } | |
| $portableSignature = Get-AuthenticodeSignature $portableExe.FullName | |
| if ($portableSignature.Status -eq "NotSigned" -or -not $portableSignature.SignerCertificate) { | |
| throw "Portable ZIP Amiberry.exe is missing an Authenticode signature." | |
| } | |
| Write-Host "Portable EXE signature status: $($portableSignature.Status)" | |
| - name: Upload artifact | |
| if: github.ref_type != 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: | | |
| amiberry-*.zip | |
| amiberry-*.exe | |
| retention-days: 7 | |
| - name: Upload artifact for release | |
| if: github.ref_type == 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: | | |
| amiberry-*.zip | |
| amiberry-*.exe | |
| retention-days: 7 | |
| # Windows ARM64: native build on the GitHub-hosted windows-11-arm | |
| # runner using the same llvm-mingw toolchain as build-windows. Kept | |
| # as a separate job because it cross-runs on a different runner image | |
| # (windows-11-arm) and needs to install InnoSetup at runtime, but the | |
| # toolchain, presets, and packaging are otherwise the same. This job | |
| # is mandatory and a failure blocks the workflow (and downstream | |
| # create-release). | |
| build-windows-arm64: | |
| runs-on: windows-11-arm | |
| # Cold-cache runs finish in ~24 min, warm-cache runs in ~8 min. The | |
| # extra time vs. x64 mostly comes from the InnoSetup-via-Chocolatey | |
| # install path (windows-11-arm doesn't preinstall ISCC). 60 min keeps | |
| # us in line with build-windows. | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: read | |
| actions: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: arm64 | |
| artifact_name: amiberry-windows-arm64 | |
| release_suffix: windows-arm64 | |
| env: | |
| LLVM_MINGW_TAG: "20260421" | |
| LLVM_MINGW_FLAVOR: "ucrt-aarch64" | |
| VCPKG_DEFAULT_TRIPLET: arm64-mingw-dynamic | |
| VCPKG_DEFAULT_HOST_TRIPLET: arm64-mingw-dynamic | |
| steps: | |
| - name: Check out repository code | |
| uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Cache llvm-mingw toolchain | |
| id: cache-llvm-mingw | |
| uses: actions/cache@v5 | |
| with: | |
| path: C:/llvm-mingw | |
| key: llvm-mingw-${{ env.LLVM_MINGW_TAG }}-${{ env.LLVM_MINGW_FLAVOR }} | |
| - name: Install llvm-mingw | |
| if: steps.cache-llvm-mingw.outputs.cache-hit != 'true' | |
| shell: pwsh | |
| run: | | |
| $tag = "${env:LLVM_MINGW_TAG}" | |
| $flavor = "${env:LLVM_MINGW_FLAVOR}" | |
| $base = "llvm-mingw-$tag-$flavor" | |
| $url = "https://github.com/mstorsjo/llvm-mingw/releases/download/$tag/$base.zip" | |
| Write-Host "Downloading $url" | |
| Invoke-WebRequest -Uri $url -OutFile "$env:RUNNER_TEMP\llvm-mingw.zip" | |
| Expand-Archive -Path "$env:RUNNER_TEMP\llvm-mingw.zip" -DestinationPath "$env:RUNNER_TEMP\llvm-mingw" | |
| New-Item -ItemType Directory -Force -Path "C:/llvm-mingw" | Out-Null | |
| # The archive extracts into a single subdir named $base; flatten. | |
| Move-Item "$env:RUNNER_TEMP\llvm-mingw\$base\*" "C:/llvm-mingw" -Force | |
| - name: Expose llvm-mingw to subsequent steps | |
| shell: pwsh | |
| run: | | |
| "LLVM_MINGW_ROOT=C:/llvm-mingw" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| "C:/llvm-mingw/bin" | Out-File -FilePath $env:GITHUB_PATH -Append | |
| - name: Bootstrap vcpkg | |
| shell: pwsh | |
| run: | | |
| if (-not (Test-Path "C:/vcpkg/.git")) { | |
| git clone --depth 1 https://github.com/microsoft/vcpkg.git C:/vcpkg | |
| } | |
| & C:/vcpkg/bootstrap-vcpkg.bat -disableMetrics | |
| "VCPKG_ROOT=C:/vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| - name: Cache vcpkg installed packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: out/build/windows-arm64-release/vcpkg_installed | |
| # Include LLVM_MINGW_TAG so a toolchain bump invalidates the | |
| # cache (avoids ABI mismatches between deps built with one | |
| # toolchain and binaries linked with another). | |
| key: ${{ runner.os }}-arm64-vcpkg-${{ env.LLVM_MINGW_TAG }}-${{ hashFiles('vcpkg.json', 'cmake/Toolchain-aarch64-w64-mingw32.cmake') }} | |
| restore-keys: | | |
| ${{ runner.os }}-arm64-vcpkg-${{ env.LLVM_MINGW_TAG }}- | |
| - name: Configure | |
| shell: bash | |
| run: | | |
| cmake --preset windows-arm64-release | |
| - name: Build | |
| shell: bash | |
| run: | | |
| cmake --build out/build/windows-arm64-release -j$NUMBER_OF_PROCESSORS | |
| - name: Install (portable layout) | |
| shell: bash | |
| run: | | |
| rm -rf out/install/windows-arm64-release | |
| rm -rf out/package/windows-arm64-release | |
| cmake --install out/build/windows-arm64-release --prefix out/install/windows-arm64-release | |
| - name: Package (Portable ZIP) | |
| shell: bash | |
| run: | | |
| PORTABLE_ROOT="out/install/windows-arm64-release" | |
| test -f "$PORTABLE_ROOT/Amiberry.exe" | |
| : > "$PORTABLE_ROOT/amiberry.portable" | |
| if [ "${{ github.ref_type }}" = "tag" ]; then | |
| ZIP_NAME="amiberry-${{ github.ref_name }}-windows-arm64-portable.zip" | |
| else | |
| ZIP_NAME="amiberry-${{ github.sha }}-windows-arm64-portable.zip" | |
| fi | |
| PACKAGE_ROOT="out/package/windows-arm64-release/${ZIP_NAME%.zip}" | |
| mkdir -p "$(dirname "$PACKAGE_ROOT")" | |
| cmake -E copy_directory "$PORTABLE_ROOT" "$PACKAGE_ROOT" | |
| ZIP_PATH="$PWD/$ZIP_NAME" | |
| (cd out/package/windows-arm64-release && cmake -E tar cf "$ZIP_PATH" --format=zip "${ZIP_NAME%.zip}") | |
| - name: Smoke test (--dump-paths, --jit-selftest) | |
| shell: pwsh | |
| run: | | |
| $zip = Get-ChildItem "$PWD/amiberry-*-windows-arm64-portable.zip" | Select-Object -First 1 | |
| if (-not $zip) { throw "Portable ZIP not found." } | |
| $verifyRoot = Join-Path $PWD "portable-smoke" | |
| Remove-Item -Recurse -Force $verifyRoot -ErrorAction SilentlyContinue | |
| New-Item -ItemType Directory -Path $verifyRoot | Out-Null | |
| Expand-Archive -Path $zip.FullName -DestinationPath $verifyRoot | |
| $portableDir = Get-ChildItem $verifyRoot -Directory | Select-Object -First 1 | |
| $exePath = Join-Path $portableDir.FullName "Amiberry.exe" | |
| if (-not (Test-Path $exePath)) { throw "Amiberry.exe missing from portable ZIP." } | |
| $stdoutFile = Join-Path $verifyRoot "dump-paths.stdout" | |
| $stderrFile = Join-Path $verifyRoot "dump-paths.stderr" | |
| $proc = Start-Process -FilePath $exePath -ArgumentList "--dump-paths" ` | |
| -WorkingDirectory $portableDir.FullName -PassThru -Wait -NoNewWindow ` | |
| -RedirectStandardOutput $stdoutFile -RedirectStandardError $stderrFile | |
| if ($proc.ExitCode -ne 0) { | |
| Write-Host "STDOUT:"; if (Test-Path $stdoutFile) { Get-Content $stdoutFile } | |
| Write-Host "STDERR:"; if (Test-Path $stderrFile) { Get-Content $stderrFile } | |
| throw "--dump-paths failed with exit code $($proc.ExitCode)." | |
| } | |
| Write-Host "ARM64 Amiberry.exe ran --dump-paths successfully." | |
| $jitStdoutFile = Join-Path $verifyRoot "jit-selftest.stdout" | |
| $jitStderrFile = Join-Path $verifyRoot "jit-selftest.stderr" | |
| $jitProc = Start-Process -FilePath $exePath -ArgumentList "--jit-selftest" ` | |
| -WorkingDirectory $portableDir.FullName -PassThru -Wait -NoNewWindow ` | |
| -RedirectStandardOutput $jitStdoutFile -RedirectStandardError $jitStderrFile | |
| if ($jitProc.ExitCode -ne 0) { | |
| Write-Host "JIT SELFTEST STDOUT:"; if (Test-Path $jitStdoutFile) { Get-Content $jitStdoutFile } | |
| Write-Host "JIT SELFTEST STDERR:"; if (Test-Path $jitStderrFile) { Get-Content $jitStderrFile } | |
| throw "--jit-selftest failed with exit code $($jitProc.ExitCode)." | |
| } | |
| Write-Host "ARM64 Amiberry.exe ran --jit-selftest successfully." | |
| - name: Ensure InnoSetup is available | |
| shell: pwsh | |
| run: | | |
| # InnoSetup ships as an x86 binary; it runs under WoA x86 emulation | |
| # on windows-11-arm runners. If ISCC is already on PATH (e.g. via | |
| # a future preinstall), use it; otherwise install via Chocolatey. | |
| $iscc = Get-Command iscc.exe -ErrorAction SilentlyContinue | |
| if ($iscc) { | |
| Write-Host "ISCC already available at $($iscc.Source)" | |
| } else { | |
| Write-Host "ISCC not found, installing InnoSetup via Chocolatey..." | |
| # Chocolatey is preinstalled on all GitHub-hosted Windows runners. | |
| choco install innosetup --no-progress --yes | Out-Null | |
| # Chocolatey shims go to C:\ProgramData\chocolatey\bin which is | |
| # already on PATH; verify ISCC is reachable. | |
| $iscc = Get-Command iscc.exe -ErrorAction SilentlyContinue | |
| if (-not $iscc) { | |
| # Fallback: add the standard install dir explicitly. | |
| $programFiles = ${env:ProgramFiles(x86)} | |
| $fallback = Join-Path $programFiles "Inno Setup 6" | |
| if (Test-Path $fallback) { | |
| "$fallback" | Out-File -FilePath $env:GITHUB_PATH -Append | |
| Write-Host "Added $fallback to PATH" | |
| } else { | |
| throw "InnoSetup install succeeded but ISCC not found on PATH or in $fallback." | |
| } | |
| } | |
| } | |
| - name: Package (InnoSetup) | |
| shell: pwsh | |
| run: | | |
| cpack -G INNOSETUP --config out/build/windows-arm64-release/CPackConfig.cmake | |
| - name: Detect SignPath configuration | |
| id: signpath-config | |
| shell: pwsh | |
| env: | |
| SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }} | |
| SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} | |
| SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} | |
| SIGNPATH_SIGNING_POLICY_SLUG: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} | |
| SIGNPATH_ARTIFACT_CONFIGURATION_SLUG: ${{ vars.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} | |
| run: | | |
| if ($env:GITHUB_EVENT_NAME -eq "pull_request") { | |
| "enabled=false" >> $env:GITHUB_OUTPUT | |
| Write-Host "SignPath disabled for pull_request events." | |
| exit 0 | |
| } | |
| $required = @{ | |
| SIGNPATH_API_TOKEN = $env:SIGNPATH_API_TOKEN | |
| SIGNPATH_ORGANIZATION_ID = $env:SIGNPATH_ORGANIZATION_ID | |
| SIGNPATH_PROJECT_SLUG = $env:SIGNPATH_PROJECT_SLUG | |
| SIGNPATH_SIGNING_POLICY_SLUG = $env:SIGNPATH_SIGNING_POLICY_SLUG | |
| SIGNPATH_ARTIFACT_CONFIGURATION_SLUG = $env:SIGNPATH_ARTIFACT_CONFIGURATION_SLUG | |
| } | |
| $missing = @( | |
| $required.GetEnumerator() | | |
| Where-Object { [string]::IsNullOrWhiteSpace($_.Value) } | | |
| ForEach-Object { $_.Key } | | |
| Sort-Object | |
| ) | |
| if ($missing.Count -gt 0) { | |
| "enabled=false" >> $env:GITHUB_OUTPUT | |
| Write-Host "SignPath disabled because the following settings are missing: $($missing -join ', ')" | |
| exit 0 | |
| } | |
| "enabled=true" >> $env:GITHUB_OUTPUT | |
| Write-Host "SignPath enabled for this run." | |
| - name: Upload unsigned Windows artifacts for SignPath | |
| if: steps.signpath-config.outputs.enabled == 'true' | |
| id: signpath-unsigned | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }}-signpath-unsigned | |
| path: | | |
| amiberry-*.zip | |
| amiberry-*.exe | |
| if-no-files-found: error | |
| retention-days: 1 | |
| compression-level: 0 | |
| - name: Submit SignPath signing request | |
| if: steps.signpath-config.outputs.enabled == 'true' | |
| id: signpath | |
| uses: SignPath/github-action-submit-signing-request@v2 | |
| with: | |
| api-token: ${{ secrets.SIGNPATH_API_TOKEN }} | |
| organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} | |
| project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} | |
| signing-policy-slug: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} | |
| artifact-configuration-slug: ${{ vars.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} | |
| github-artifact-id: ${{ steps.signpath-unsigned.outputs.artifact-id }} | |
| wait-for-completion: true | |
| wait-for-completion-timeout-in-seconds: 1200 | |
| output-artifact-directory: signpath-signed | |
| - name: Replace unsigned artifacts with signed outputs | |
| if: steps.signpath-config.outputs.enabled == 'true' | |
| shell: pwsh | |
| run: | | |
| $downloadDir = Join-Path $PWD "signpath-signed" | |
| if (-not (Test-Path $downloadDir)) { | |
| throw "Signed artifact directory '$downloadDir' was not created." | |
| } | |
| $portableZip = Get-ChildItem $downloadDir -Recurse -File -Filter "amiberry-*-portable.zip" | Select-Object -First 1 | |
| if (-not $portableZip) { | |
| throw "Signed portable ZIP was not found in '$downloadDir'." | |
| } | |
| $installer = Get-ChildItem $downloadDir -Recurse -File -Filter "amiberry-*.exe" | Select-Object -First 1 | |
| if (-not $installer) { | |
| throw "Signed installer EXE was not found in '$downloadDir'." | |
| } | |
| Copy-Item $portableZip.FullName $PWD -Force | |
| Copy-Item $installer.FullName $PWD -Force | |
| Write-Host "Replaced workspace artifacts with SignPath-signed outputs." | |
| - name: Verify SignPath-signed artifacts | |
| if: steps.signpath-config.outputs.enabled == 'true' | |
| shell: pwsh | |
| run: | | |
| $installer = Get-ChildItem "$PWD/amiberry-*.exe" | Select-Object -First 1 | |
| if (-not $installer) { | |
| throw "Signed installer EXE not found in workspace." | |
| } | |
| $installerSignature = Get-AuthenticodeSignature $installer.FullName | |
| if ($installerSignature.Status -eq "NotSigned" -or -not $installerSignature.SignerCertificate) { | |
| throw "Installer EXE is missing an Authenticode signature." | |
| } | |
| Write-Host "Installer EXE signature status: $($installerSignature.Status)" | |
| $portableZip = Get-ChildItem "$PWD/amiberry-*-windows-arm64-portable.zip" | Select-Object -First 1 | |
| if (-not $portableZip) { | |
| throw "Signed portable ZIP not found in workspace." | |
| } | |
| $verifyRoot = Join-Path $PWD "portable-signed-smoke" | |
| Remove-Item -Recurse -Force $verifyRoot -ErrorAction SilentlyContinue | |
| New-Item -ItemType Directory -Path $verifyRoot | Out-Null | |
| Expand-Archive -Path $portableZip.FullName -DestinationPath $verifyRoot | |
| $portableExe = Get-ChildItem $verifyRoot -Recurse -File -Filter "Amiberry.exe" | Select-Object -First 1 | |
| if (-not $portableExe) { | |
| throw "Portable ZIP is missing Amiberry.exe after SignPath signing." | |
| } | |
| $portableSignature = Get-AuthenticodeSignature $portableExe.FullName | |
| if ($portableSignature.Status -eq "NotSigned" -or -not $portableSignature.SignerCertificate) { | |
| throw "Portable ZIP Amiberry.exe is missing an Authenticode signature." | |
| } | |
| Write-Host "Portable EXE signature status: $($portableSignature.Status)" | |
| - name: Upload artifact | |
| if: github.ref_type != 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: | | |
| amiberry-*.zip | |
| amiberry-*.exe | |
| retention-days: 7 | |
| - name: Upload artifact for release | |
| if: github.ref_type == 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: | | |
| amiberry-*.zip | |
| amiberry-*.exe | |
| retention-days: 7 | |
| build-libretro: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| platform: unix | |
| artifact_platform: linux | |
| arch: x86_64 | |
| artifact: amiberry_libretro.so | |
| - os: ubuntu-latest | |
| platform: unix | |
| artifact_platform: linux | |
| arch: aarch64 | |
| artifact: amiberry_libretro.so | |
| - os: windows-latest | |
| platform: win | |
| artifact_platform: win | |
| arch: x86_64 | |
| artifact: amiberry_libretro.dll | |
| - os: macos-latest | |
| platform: osx | |
| artifact_platform: macOS | |
| arch: arm64 | |
| artifact: amiberry_libretro.dylib | |
| steps: | |
| - name: Check out amiberry | |
| uses: actions/checkout@v5 | |
| with: | |
| path: amiberry | |
| submodules: recursive | |
| - name: Install dependencies (Windows) | |
| if: runner.os == 'Windows' | |
| uses: msys2/setup-msys2@v2 | |
| with: | |
| # Use the CLANG64 subsystem so the libretro core is also built | |
| # with llvm-mingw / clang, matching build-windows. The | |
| # mingw-w64-clang-x86_64-toolchain package provides clang, | |
| # clang++, lld, libc++, libunwind, libwinpthread. | |
| msystem: CLANG64 | |
| update: false | |
| install: mingw-w64-clang-x86_64-toolchain mingw-w64-clang-x86_64-zlib make | |
| - name: Install dependencies (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y build-essential | |
| if [ "${{ matrix.arch }}" = "aarch64" ]; then | |
| sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu | |
| ZLIB_PREFIX="${RUNNER_TEMP}/zlib-aarch64" | |
| if sudo apt-get install -y zlib1g-dev-arm64-cross || sudo apt-get install -y libz-dev-arm64-cross; then | |
| echo "ZLIB_CFLAGS=-I/usr/aarch64-linux-gnu/include" >> "$GITHUB_ENV" | |
| echo "ZLIB_LIBS=-L/usr/aarch64-linux-gnu/lib -lz" >> "$GITHUB_ENV" | |
| else | |
| curl -fL https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz | tar xz -C "${RUNNER_TEMP}" | |
| pushd "${RUNNER_TEMP}/zlib-1.3.2" | |
| CC=aarch64-linux-gnu-gcc AR=aarch64-linux-gnu-ar RANLIB=aarch64-linux-gnu-ranlib \ | |
| ./configure --static --prefix="${ZLIB_PREFIX}" | |
| make -j$(nproc) | |
| make install | |
| popd | |
| echo "ZLIB_CFLAGS=-I${ZLIB_PREFIX}/include" >> "$GITHUB_ENV" | |
| echo "ZLIB_LIBS=${ZLIB_PREFIX}/lib/libz.a" >> "$GITHUB_ENV" | |
| fi | |
| fi | |
| - name: Build Libretro Core (Windows) | |
| if: runner.os == 'Windows' | |
| shell: msys2 {0} | |
| run: | | |
| cd amiberry/libretro | |
| make platform=${{ matrix.platform }} ARCH=${{ matrix.arch }} -j$(nproc) | |
| - name: Build Libretro Core | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| cd amiberry/libretro | |
| if [ "${{ matrix.arch }}" = "aarch64" ]; then | |
| make platform=${{ matrix.platform }} ARCH=${{ matrix.arch }} CROSS_COMPILE=aarch64-linux-gnu- CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ ZLIB_CFLAGS="${ZLIB_CFLAGS}" ZLIB_LIBS="${ZLIB_LIBS}" -j$(nproc) | |
| else | |
| make platform=${{ matrix.platform }} ARCH=${{ matrix.arch }} -j$(nproc) | |
| fi | |
| - name: Prepare libretro release artifact | |
| if: github.ref_type == 'tag' | |
| shell: bash | |
| run: | | |
| artifact="${{ matrix.artifact }}" | |
| ext="${artifact##*.}" | |
| release_artifact="amiberry-libretro-${{ matrix.artifact_platform }}-${{ matrix.arch }}.${ext}" | |
| cp "amiberry/libretro/${artifact}" "$release_artifact" | |
| echo "LIBRETRO_RELEASE_ARTIFACT=$release_artifact" >> "$GITHUB_ENV" | |
| - name: Upload artifact | |
| if: github.ref_type != 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: amiberry-libretro-${{ matrix.artifact_platform }}-${{ matrix.arch }} | |
| path: | | |
| amiberry/libretro/${{ matrix.artifact }} | |
| amiberry/libretro/amiberry_libretro.info | |
| retention-days: 7 | |
| - name: Upload artifact for release | |
| if: github.ref_type == 'tag' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: amiberry-libretro-${{ matrix.artifact_platform }}-${{ matrix.arch }} | |
| path: ${{ env.LIBRETRO_RELEASE_ARTIFACT }} | |
| - name: Upload libretro info artifact for release | |
| if: github.ref_type == 'tag' && matrix.platform == 'unix' && matrix.arch == 'x86_64' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: amiberry-libretro-info | |
| path: amiberry/libretro/amiberry_libretro.info | |
| create-release: | |
| needs: [merge-macOS-universal, build-fedora, build-ubuntu, build-debian-amd64, build-debian-arm64, build-windows, build-windows-arm64, build-libretro] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| if: github.ref_type == 'tag' && (startsWith(github.ref_name, 'v') || startsWith(github.ref_name, 'preview-v')) | |
| # Elevated permissions only where needed for creating a release | |
| permissions: | |
| contents: write | |
| actions: read | |
| pull-requests: read | |
| issues: read | |
| steps: | |
| - name: Download Build Artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Display structure of downloaded files | |
| run: ls -R artifacts | |
| - name: Generate SHA256SUMS | |
| run: | | |
| cd artifacts | |
| sha256sum amiberry-*.zip amiberry-*.exe amiberry-libretro-*.* amiberry_libretro.info Amiberry-*.dmg 2>/dev/null | tee SHA256SUMS | |
| cat SHA256SUMS | |
| - name: Create Release | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| prerelease: ${{ startsWith(github.ref_name, 'preview-v') }} | |
| allowUpdates: true | |
| omitBodyDuringUpdate: true | |
| generateReleaseNotes: true | |
| artifacts: | | |
| artifacts/amiberry-*.zip | |
| artifacts/amiberry-libretro-*.* | |
| artifacts/amiberry_libretro.info | |
| artifacts/amiberry-*.exe | |
| artifacts/Amiberry-*.dmg | |
| artifacts/SHA256SUMS | |
| update-repos: | |
| needs: create-release | |
| uses: ./.github/workflows/update-repos.yml | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| secrets: inherit | |
| upload-ppa: | |
| needs: create-release | |
| # Only upload stable releases (not preview-v* pre-releases) | |
| if: ${{ !startsWith(github.ref_name, 'preview-v') }} | |
| uses: ./.github/workflows/ppa-upload.yml | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| secrets: inherit |