# Copyright (C) Markus Franz Xaver Johannes Oberhumer

# note: GitHub Actions runner images are frequently updated, and
# unfortunately this sometimes breaks builds in mysterious ways...
# see https://github.com/actions/runner-images.git

name: CI

on: [push, workflow_dispatch]

env:
  CMAKE_REQUIRED_QUIET: 'OFF'
  CMAKE_VERBOSE_MAKEFILE: 'ON'
  CTEST_OUTPUT_ON_FAILURE: 'ON'
  DEBIAN_FRONTEND: noninteractive
  UPX_CMAKE_BUILD_FLAGS: --verbose
  UPX_CMAKE_CONFIG_FLAGS: -Wdev --warn-uninitialized
  UPX_CONFIG_HAVE_WORKING_BUILD_RPATH: 'ON'
  UPX_DEBUG_TEST_FLOAT_DIVISION_BY_ZERO: 1
  UPX_DEBUG_TEST_LIBC_QSORT: 1
  ZSTD_CLEVEL: 9
  # 2024-08-17
  ZIG_DIST_VERSION: 0.14.0-dev.1166+bb7050106

jobs:
  job-rebuild-and-verify-stubs:
    name: Rebuild stubs
    runs-on: ubuntu-latest
    container: ubuntu:24.04 # provides glibc-2.39
    #container: debian:12-slim # also works; provides glibc-2.36
    #container: debian:testing-slim # also works; currently provides glibc-2.38
    steps:
      - name: Install packages
        run: |
          uname -a; pwd; id; umask
          dpkg --add-architecture i386
          apt-get update && apt-get upgrade -y
          # install system packages
          apt-get install -y --no-install-recommends bash ca-certificates curl git libmpc3 make perl-base tar time xz-utils libc6:i386 zlib1g:i386
          # install python2-minimal packages from Debian-11
          mkdir ../deps; cd ../deps; mkdir packages
          curl -sS -L -O https://ftp.debian.org/debian/pool/main/p/python2.7/libpython2.7-minimal_2.7.18-8+deb11u1_amd64.deb
          curl -sS -L -O https://ftp.debian.org/debian/pool/main/p/python2.7/python2.7-minimal_2.7.18-8+deb11u1_amd64.deb
          dpkg -i ./*python2*.deb && rm ./*python2*.deb && ldconfig
          ln -s -v python2.7 /usr/bin/python2
          # manually unpack and install compat libs from Ubuntu-16.04
          curl -sS -L -O https://archive.kernel.org/ubuntu-archive/ubuntu/pool/main/m/mpfr4/libmpfr4_3.1.6-1_amd64.deb
          for f in ./*.deb; do dpkg -x $f ./packages; done
          mv -v -n ./packages/usr/lib/x86_64-linux-gnu/lib* /usr/lib/x86_64-linux-gnu/
          rm -rf ./*.deb ./packages && ldconfig
          # install upx-stubtools
          curl -sS -L https://github.com/upx/upx-stubtools/releases/download/v20221212/bin-upx-20221212.tar.xz | tar -xJ
      - name: Check out code
        run: |
          git config --global --add safe.directory '*' # needed when running in a container
          git clone --branch "$GITHUB_REF_NAME" --depth 1 "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" .
          git submodule update --init -- vendor/lzma-sdk
          git fsck --strict --no-progress
      - name: Rebuild and verify stubs
        run: |
          export PATH="$(readlink -en ../deps/bin-upx-20221212):$PATH"
          make -C src/stub maintainer-clean extra-clean
          git status || true
          make -C src/stub extra-all all
          if ! git diff --quiet; then git diff; exit 1; fi
      - run: bash ./misc/scripts/check_whitespace_git.sh
      - name: Check source code formatting
        run: |
          export UPX_CLANG_FORMAT="$(readlink -en ../deps/bin-upx-20221212/clang-format-15.0.6)"
          make -C src clang-format
          if ! git diff --quiet; then git diff; exit 1; fi
      - name: Rebuild docs
        run: |
          make -C doc clean all
          if ! git diff --quiet; then git diff || true; fi # ignore diff error

  job-linux-cmake: # uses cmake + make
    if: true
    needs: [ job-rebuild-and-verify-stubs ]
    strategy:
      fail-fast: false
      matrix:
        include:
          - { os: ubuntu-20.04, use_extra: true }
          - { os: ubuntu-22.04, use_extra: true, use_wine: true }
          # GitHub ubuntu-24.04 is BETA; BUG: currently i386 programs can abort with "The futex facility returned an unexpected error code"
          #   THIS IS A KNOWN ISSUE: https://github.com/actions/runner-images/issues/9848
          # { os: ubuntu-24.04, use_extra: true, use_wine: true }
          - { os: ubuntu-24.04 } # TODO later: enable extra+wine once the GitHub VM is fixed
    name: ${{ format('{0}', matrix.os) }}
    runs-on: ${{ matrix.os }}
    steps:
      - name: Install extra 32-bit and MinGW packages
        if: ${{ matrix.use_extra }}
        run: |
          sudo dpkg --add-architecture i386
          sudo apt-get update && sudo apt-get upgrade -y
          sudo apt-get install -y g++-multilib g++-mingw-w64-i686 g++-mingw-w64-x86-64
          # make sure that we use posix-threads (pthread/winpthreads) and NOT win32-threads
          for f in i686-w64-mingw32-g++ i686-w64-mingw32-gcc x86_64-w64-mingw32-g++ x86_64-w64-mingw32-gcc; do
            if test -f /usr/bin/$f-posix; then sudo update-alternatives --set $f /usr/bin/$f-posix; fi
            ls -l /usr/bin/${f}*
          done
          ls -l /etc/alternatives/*mingw* || true
          sudo apt-get install -y libc6-dbg:i386 valgrind
      - name: Install Wine
        if: ${{ matrix.use_extra && matrix.use_wine }}
        run: |
          sudo apt-get install -y wine wine32:i386 wine64
          ls -l /usr/bin/wine*
          mkdir -p -v ~/.wine && wineboot --init
      - uses: actions/checkout@v4
        with: { submodules: true }
      - name: Check out test suite
        run: git clone --depth=1 https://github.com/upx/upx-testsuite ../deps/upx-testsuite
      - run: set -x; clang --version; clang++ --version
      - run: set -x; gcc --version; g++ --version
      - run: clang   -E -x c   -dM /dev/null # list predefined macros for C
      - run: clang++ -E -x c++ -dM /dev/null # list predefined macros for C++
      - run: gcc     -E -x c   -dM /dev/null # list predefined macros for C
      - run: g++     -E -x c++ -dM /dev/null # list predefined macros for C++
      - run: make build/extra/gcc/all
      - run: make build/extra/clang/all
      - run: make build/extra/gcc-m32/all
        if: ${{ matrix.use_extra }}
      - run: make build/extra/gcc-mx32/all
        if: ${{ matrix.use_extra }}
      - run: make build/extra/cross-windows-mingw32/all
        if: ${{ matrix.use_extra }}
      - run: make build/extra/cross-windows-mingw64/all
        if: ${{ matrix.use_extra }}
      - name: Make artifact
        run: |
          N=$(echo "upx-${GITHUB_REF_NAME}-${GITHUB_SHA:0:7}-${{ matrix.os }}" | sed 's/[^0-9a-zA-Z_.-]/-/g')
          mkdir -p "tmp/artifact/$N"
          dirs="gcc/debug gcc/release clang/debug clang/release"
          if test "${{ matrix.use_extra }}" = "true"; then
            dirs="$dirs gcc-m32/debug gcc-m32/release gcc-mx32/debug gcc-mx32/release"
            dirs="$dirs cross-windows-mingw32/debug cross-windows-mingw32/release"
            dirs="$dirs cross-windows-mingw64/debug cross-windows-mingw64/release"
          fi
          for d in $dirs; do DESTDIR="$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithDestdir/$d" cmake --install build/extra/$d; done
          for d in $dirs; do DESTDIR="$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithDestdirAndStrip/$d" cmake --install build/extra/$d --strip; done
          for d in $dirs; do cmake --install build/extra/$d --prefix "$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithPrefix/$d"; done
          for d in $dirs; do cmake --install build/extra/$d --prefix "$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithPrefixAndStrip/$d" --strip; done
          (cd build && shopt -s nullglob && cp -ai --parents */upx{,.exe} */*/*/upx{,.exe} "../tmp/artifact/$N")
          if command -v hardlink >/dev/null; then (cd "tmp/artifact/$N" && hardlink .) fi
          (cd tmp/artifact && tar --sort=name --zstd -cf "$N.tar.zst" "$N" && rm -rf "./$N" && ls -la && zstd -tq "$N.tar.zst")
          echo "artifact_name=$N" >> $GITHUB_ENV
      - name: ${{ format('Upload artifact {0}', env.artifact_name) }}
        uses: actions/upload-artifact@v4
        with: { name: '${{ env.artifact_name }}', path: tmp/artifact }
      - name: Run install tests
        run: |
          (cd build/extra/gcc/release && DESTDIR="$PWD/Install with cmake" cmake --install .)
          (cd build/extra/gcc/release && DESTDIR="$PWD/Install with make"  make install)
      - name: Run ctest tests
        run: |
          jobs="gcc/debug gcc/release clang/debug clang/release"
          test "${{ matrix.use_extra }}" = "true" && jobs="$jobs gcc-m32/debug gcc-m32/release"
          command -v wine >/dev/null && jobs="$jobs cross-windows-mingw32/debug cross-windows-mingw32/release"
          command -v wine >/dev/null && jobs="$jobs cross-windows-mingw64/debug cross-windows-mingw64/release"
          echo "===== parallel jobs: $jobs"
          CTEST_JOBS=2 parallel -kv --lb 'make build/extra/{}+test' ::: $jobs
      - name: Mimic ctest tests
        run: |
          jobs="gcc/debug gcc/release clang/debug clang/release"
          test "${{ matrix.use_extra }}" = "true" && jobs="$jobs gcc-m32/debug gcc-m32/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'cd build/extra/{} && bash ../../../../misc/testsuite/mimic_ctest.sh' ::: $jobs
      - name: Mimic ctest tests with Valgrind
        if: true # note: valgrind is SLOW
        run: |
          if command -v valgrind >/dev/null; then
            export UPX_CONFIG_DISABLE_EXHAUSTIVE_TESTS=ON # valgrind is SLOW
            export upx_exe_runner="valgrind --leak-check=no --error-exitcode=1 --quiet --gen-suppressions=all"
            # clang/debug does not work before valgrind-3.20, see https://bugs.kde.org/show_bug.cgi?id=452758
            jobs="gcc/debug gcc/release clang/release"
            test "${{ matrix.use_extra }}" = "true" && jobs="$jobs gcc-m32/debug gcc-m32/release"
            echo "===== parallel jobs: $jobs"
            parallel -kv --lb 'cd build/extra/{} && bash ../../../../misc/testsuite/mimic_ctest.sh' ::: $jobs
          fi
      - name: Run file system tests
        run: |
          jobs="gcc/debug gcc/release clang/debug clang/release"
          test "${{ matrix.use_extra }}" = "true" && jobs="$jobs gcc-m32/debug gcc-m32/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'cd build/extra/{} && bash ../../../../misc/testsuite/test_symlinks.sh' ::: $jobs
      - name: Run file system tests with Valgrind
        if: false # note: valgrind is SLOW
        run: |
          if command -v valgrind >/dev/null; then
            export upx_exe_runner="valgrind --leak-check=no --error-exitcode=1 --quiet --gen-suppressions=all"
            # clang/debug does not work before valgrind-3.20, see https://bugs.kde.org/show_bug.cgi?id=452758
            jobs="gcc/debug gcc/release clang/release"
            test "${{ matrix.use_extra }}" = "true" && jobs="$jobs gcc-m32/debug gcc-m32/release"
            echo "===== parallel jobs: $jobs"
            parallel -kv --lb 'cd build/extra/{} && bash ../../../../misc/testsuite/test_symlinks.sh' ::: $jobs
          fi
      - name: Run test suite
        run: |
          export upx_testsuite_SRCDIR="$(readlink -en ../deps/upx-testsuite)"
          jobs="gcc/debug gcc/release clang/debug clang/release"
          test "${{ matrix.use_extra }}" = "true" && jobs="$jobs gcc-m32/debug gcc-m32/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'cd build/extra/{} && bash ../../../../misc/testsuite/upx_testsuite_1.sh' ::: $jobs

  job-macos-cmake: # uses cmake + make
    if: true
    needs: [ job-rebuild-and-verify-stubs ]
    strategy:
      fail-fast: false
      matrix:
        include:
          # NOTE: Xcode updates regularly tend to break Homebrew clang/gcc; often some ld64 issue; use "xcode_version" as needed; or try "-Wl,-ld_classic"
          # NOTE: macos does not have "env -C"; only with brew coreutils
          # NOTE: macos-11 does not have "readlink -f"; only on macos >= 12 or with brew coreutils
          # { os: macos-11, gcc: gcc-10, gxx: g++-10, testsuite: true } # macos-11 is EOL; scheduled for removal from GitHub actions
          - { os: macos-12, gcc: gcc-12, gxx: g++-12, testsuite: true }
          - { os: macos-13, testsuite: true } # use default Xcode-15; NOTE: gcc-12 on macos-13 does not work with Xcode-15
          - { os: macos-13, gcc: gcc-12, gxx: g++-12, testsuite: true, xcode_version: 14.3.1 }
          # { os: macos-14, gcc: gcc-13, gxx: g++-13, testsuite: true }                        # gcc-13: INTERNAL ERROR in ld64
          # { os: macos-14, gcc: gcc-13, gxx: g++-13, testsuite: true, xcode_version: 14.3.1 } # gcc-13: MISSING HEADER FILES
          - { os: macos-14, gcc: gcc-12, gxx: g++-12, testsuite: true } # => use gcc-12 for now
    name: ${{ format('{0} {1}{2}', matrix.os, matrix.xcode_version && 'xcode-' || '',  matrix.xcode_version) }}
    runs-on: ${{ matrix.os }}
    steps:
      - uses: maxim-lobanov/setup-xcode@v1
        if: ${{ matrix.xcode_version }}
        with: { xcode-version: '${{ matrix.xcode_version }}' }
      - name: Install brew packages
        if: ${{ matrix.testsuite }}
        run: |
          test -z "$HOMEBREW_PREFIX" && HOMEBREW_PREFIX="$(brew --prefix)"
          echo "HOMEBREW_PREFIX=$HOMEBREW_PREFIX" >> $GITHUB_ENV
          # testsuite needs working "readlink -en" and "sha256sum -b"
          packages="ninja parallel util-linux"
          test -e "$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin/readlink" || packages="$packages coreutils"
          if test -n "$packages"; then
            echo "===== brew leaves:"; brew leaves; echo "===== brew list:"; brew list --versions
            # only run "brew update" if needed
            if ! brew install $packages; then echo "===== brew update" && brew update && brew install $packages; fi
          fi
          mkdir -p ~/.parallel && : > ~/.parallel/$(echo 6305-4721 | tr 0-7 leticlwi)
          echo "UPX_DEBUG_FORCE_PACK_MACOS=1" >> $GITHUB_ENV
          case "${{ matrix.os }}" in
            # TODO FIXME: UPX on macos-13+ is broken => disable run-packed for now
            macos-12 | macos-13 | macos-14) echo "UPX_CONFIG_DISABLE_RUN_PACKED_TEST=ON" >> $GITHUB_ENV ;;
          esac
      - uses: actions/checkout@v4
        with: { submodules: true }
      - name: Check out test suite
        if: ${{ matrix.testsuite }}
        run: git clone --depth=1 https://github.com/upx/upx-testsuite ../deps/upx-testsuite
      - run: set -x; xcode-select -p || true; xcodebuild -version || true
      - run: set -x; clang --version; clang++ --version
      - run: clang   -E -x c   -dM /dev/null # list predefined macros for C
      - run: clang++ -E -x c++ -dM /dev/null # list predefined macros for C++
      - run: set -x; ${{ matrix.gcc }} --version; ${{ matrix.gxx }} --version
        if: ${{ matrix.gcc }}
      - run: ${{ matrix.gcc }} -E -x c   -dM /dev/null # list predefined macros for C
        if: ${{ matrix.gcc }}
      - run: ${{ matrix.gxx }} -E -x c++ -dM /dev/null # list predefined macros for C++
        if: ${{ matrix.gcc }}
      - run: make build/extra/clang/all
      - name: Build extra/gcc/all
        if: ${{ matrix.gcc }}
        run: make build/extra/gcc/all CC="${{ matrix.gcc }} -static-libgcc" CXX="${{ matrix.gxx }} -static-libgcc -static-libstdc++"
      - name: Build xtarget/cross-darwin-arm64/all
        run: |
          make UPX_XTARGET=xtarget/cross-darwin-arm64 xtarget/all \
            CC="clang -target arm64-apple-darwin" CXX="clang++ -target arm64-apple-darwin"
      - name: Make artifact
        run: |
          X="${{ matrix.xcode_version }}"; test -n "$X" && X="-xcode-$X"
          N=$(echo "upx-${GITHUB_REF_NAME}-${GITHUB_SHA:0:7}-${{ matrix.os }}$X" | sed 's/[^0-9a-zA-Z_.-]/-/g')
          mkdir -p "tmp/artifact/$N"
          dirs="clang/debug clang/release"
          test -n "${{ matrix.gcc }}" && dirs="$dirs gcc/debug gcc/release"
          for d in $dirs; do DESTDIR="$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithDestdir/$d" cmake --install build/extra/$d; done
          for d in $dirs; do DESTDIR="$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithDestdirAndStrip/$d" cmake --install build/extra/$d --strip; done
          for d in $dirs; do cmake --install build/extra/$d --prefix "$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithPrefix/$d"; done
          for d in $dirs; do cmake --install build/extra/$d --prefix "$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithPrefixAndStrip/$d" --strip; done
          (cd build && rsync -R -a */*/*/upx "../tmp/artifact/$N/")
          hardlink="$HOMEBREW_PREFIX/opt/util-linux/bin/hardlink"
          if test -f "$hardlink"; then (cd "tmp/artifact/$N" && "$hardlink" .) fi
          (cd tmp/artifact && gtar --sort=name --zstd -cf "$N.tar.zst" "$N" && rm -rf "./$N" && ls -la && zstd -tq "$N.tar.zst")
          echo "artifact_name=$N" >> $GITHUB_ENV
      - name: ${{ format('Upload artifact {0}', env.artifact_name) }}
        uses: actions/upload-artifact@v4
        with: { name: '${{ env.artifact_name }}', path: tmp/artifact }
      - name: Run install tests
        run: |
          (cd build/extra/clang/release && DESTDIR="$PWD/Install with cmake" cmake --install .)
          (cd build/extra/clang/release && DESTDIR="$PWD/Install with make"  make install)
      - name: Run ctest tests
        run: |
          for f in ./build/extra/*/*/upx; do file $f; done
          jobs="clang/debug clang/release"
          test -n "${{ matrix.gcc }}" && jobs="$jobs gcc/debug gcc/release"
          echo "===== parallel jobs: $jobs"
          CTEST_JOBS=2 parallel -kv --lb 'make build/extra/{}+test' ::: $jobs
      - name: Mimic ctest tests
        run: |
          export PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
          jobs="clang/debug clang/release"
          test -n "${{ matrix.gcc }}" && jobs="$jobs gcc/debug gcc/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'cd build/extra/{} && bash ../../../../misc/testsuite/mimic_ctest.sh' ::: $jobs
      - name: Run file system tests
        if: ${{ matrix.testsuite }} # for coreutils readlink
        run: |
          export PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
          export upx_test_file="$PWD"/build/extra/clang/release/upx
          jobs="clang/debug clang/release"
          test -n "${{ matrix.gcc }}" && jobs="$jobs gcc/debug gcc/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'cd build/extra/{} && bash ../../../../misc/testsuite/test_symlinks.sh' ::: $jobs
      - name: Run test suite
        if: ${{ matrix.testsuite }}
        run: |
          export PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
          export upx_testsuite_SRCDIR="$(readlink -en ../deps/upx-testsuite)"
          jobs="clang/debug clang/release"
          test -n "${{ matrix.gcc }}" && jobs="$jobs gcc/debug gcc/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'cd build/extra/{} && bash ../../../../misc/testsuite/upx_testsuite_1.sh' ::: $jobs

  job-windows-cmake: # uses cmake + msbuild
    if: true
    needs: [ job-rebuild-and-verify-stubs ]
    strategy:
      fail-fast: false
      matrix:
        include:
          - { name: windows-2019-amd64, os: windows-2019, vsversion: 2019, vsarch: amd64 }
          - { name: windows-2022-amd64, os: windows-2022, vsversion: 2022, vsarch: amd64 }
    name: ${{ format('{0}', matrix.name) }}
    runs-on: ${{ matrix.os }}
    steps:
      - run: git config --global core.autocrlf false
      - uses: actions/checkout@v4
        with: { submodules: true }
      - name: Check out test suite
        run: git clone --depth=1 https://github.com/upx/upx-testsuite ../deps/upx-testsuite
      - uses: ilammy/msvc-dev-cmd@v1
        with: { vsversion: '${{ matrix.vsversion }}', arch: '${{ matrix.vsarch }}' }
      - run: make build/debug
      - run: make build/release
      - name: Make artifact
        shell: bash
        run: |
          N=$(echo "upx-${GITHUB_REF_NAME}-${GITHUB_SHA:0:7}-${{ matrix.os }}" | sed 's/[^0-9a-zA-Z_.-]/-/g')
          mkdir -p "tmp/artifact/$N"
          DESTDIR="$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithDestdir/debug"   cmake --install build/debug   --config Debug
          DESTDIR="$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithDestdir/release" cmake --install build/release --config Release
          DESTDIR="$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithDestdirAndStrip/debug"   cmake --install build/debug   --config Debug   --strip
          DESTDIR="$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithDestdirAndStrip/release" cmake --install build/release --config Release --strip
          cmake --install build/debug   --config Debug   --prefix "$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithPrefix/debug"
          cmake --install build/release --config Release --prefix "$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithPrefix/release"
          cmake --install build/debug   --config Debug   --prefix "$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithPrefixAndStrip/debug"   --strip
          cmake --install build/release --config Release --prefix "$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithPrefixAndStrip/release" --strip
          (cd build && cp -ai --parents */*/upx.exe "../tmp/artifact/$N")
          (cd tmp/artifact && tar --sort=name --zstd -cf "$N.tar.zst" "$N" && rm -rf "./$N" && ls -la && zstd -tq "$N.tar.zst")
          echo "artifact_name=$N" >> $GITHUB_ENV
      - name: ${{ format('Upload artifact {0}', env.artifact_name) }}
        uses: actions/upload-artifact@v4
        with: { name: '${{ env.artifact_name }}', path: tmp/artifact }
      - name: Run ctest tests
        if: ${{ matrix.vsarch != 'amd64_arm64' }}
        run: |
          ctest --test-dir build/debug   --parallel 8 -C Debug
          ctest --test-dir build/release --parallel 8 -C Release
      - name: Run test suite build/release
        if: ${{ matrix.vsarch != 'amd64_arm64' }}
        shell: bash
        run: |
          export upx_testsuite_SRCDIR="$(readlink -en ../deps/upx-testsuite)"
          env -C build/release/Release bash "$PWD"/misc/testsuite/upx_testsuite_1.sh

  job-windows-toolchains: # build "by hand" using cmd.exe
    if: github.repository_owner == 'upx'
    needs: [ job-rebuild-and-verify-stubs ]
    strategy:
      fail-fast: false
      matrix:
        include:
          - { name: amd64-win64-vs2019,   os: windows-2019, vsversion: 2019, vsarch: amd64 }
          - { name: amd64-win64-vs2022,   os: windows-2022, vsversion: 2022, vsarch: amd64 }
          - { name: arm64-win64-vs2019,   os: windows-2019, vsversion: 2019, vsarch: amd64_arm64 }
          - { name: arm64-win64-vs2022,   os: windows-2022, vsversion: 2022, vsarch: amd64_arm64 }
          - { name: arm64ec-win64-vs2022, os: windows-2022, vsversion: 2022, vsarch: amd64_arm64, cl_machine_flags: -arm64EC, link_machine_flags: '/machine:arm64ec' }
          # { name: arm64x-win64-vs2022,  os: windows-2022, vsversion: 2022, vsarch: amd64_arm64, cl_machine_flags: -arm64EC, link_machine_flags: '/machine:arm64x' }
          - { name: i386-win32-vs2019,    os: windows-2019, vsversion: 2019, vsarch: amd64_x86 }
          - { name: i386-win32-vs2022,    os: windows-2022, vsversion: 2022, vsarch: amd64_x86 }
    name: ${{ format('windows {0}', matrix.name) }}
    runs-on: ${{ matrix.os }}
    env:
      C: ${{ matrix.name }}
      B: release
    steps:
      - run: git config --global core.autocrlf false
      - uses: actions/checkout@v4
        with: { submodules: true }
      - name: Prepare sources and Check out test suite
        shell: bash
        run: |
          git clone --depth=1 https://github.com/upx/upx-testsuite ../deps/upx-testsuite
          mkdir -p -v build/$C/$B/{bzip2,ucl,upx,zlib,zstd}
          repository_name="${GITHUB_REPOSITORY##*/}" # basename
          echo "H=d:\\a\\$repository_name\\$repository_name" >> $GITHUB_ENV
      - uses: ilammy/msvc-dev-cmd@v1
        with: { vsversion: '${{ matrix.vsversion }}', arch: '${{ matrix.vsarch }}' }
      - name: Build by hand
        shell: cmd
        run: |
          @REM ===== set vars =====
          where cl & where link
          set RUN_CL=cl ${{ matrix.cl_machine_flags }} -MT
          set RUN_LIB=link -lib ${{ matrix.link_machine_flags }}
          @rem UPX only uses the very basic Windows API
          set DEFS=-D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0501
          set BDIR=%H%\build\%C%\%B%
          git rev-parse --short=12 HEAD > %BDIR%\upx\.GITREV.txt
          @REM ===== build bzip2 =====
          cd %BDIR%\bzip2
          @rem %RUN_CL% -J -O2 -W4 -wd4127 -wd4244 -wd4267 -WX %DEFS% -DBZ_NO_STDIO -c %H%\vendor\bzip2\*.c
          @rem %RUN_LIB% -out:bzip2.lib *.obj
          @REM ===== build UCL =====
          cd %BDIR%\ucl
          set s=%H%\vendor\ucl
          %RUN_CL% -J -O2 -W4 -WX %DEFS% -I%s%\include -I%s% -c %s%\src\*.c
          %RUN_LIB% -out:ucl.lib *.obj
          @REM ===== build zlib =====
          cd %BDIR%\zlib
          %RUN_CL% -J -O2 -W3 -WX %DEFS% -DHAVE_VSNPRINTF -c %H%\vendor\zlib\*.c
          %RUN_LIB% -out:zlib.lib *.obj
          @REM ===== build zstd =====
          cd %BDIR%\zstd
          set s=%H%\vendor\zstd\lib
          @rem %RUN_CL% -J -O2 -W4 -WX %DEFS% -DDYNAMIC_BMI2=0 -DZSTD_DISABLE_ASM -c %s%\common\*.c %s%\compress\*.c %s%\decompress\*.c
          @rem %RUN_LIB% -out:zstd.lib *.obj
          @REM ===== build UPX =====
          cd %BDIR%\upx
          set s=%H%\src
          cat .GITREV.txt
          set /p GITREV=<.GITREV.txt
          set UPX_DEFS=-DUPX_CONFIG_DISABLE_WSTRICT=0 -DUPX_CONFIG_DISABLE_WERROR=0 -DWITH_THREADS=0 -DWITH_BZIP2=0 -DWITH_ZSTD=0
          set UPX_LIBS=%BDIR%\ucl\ucl.lib %BDIR%\zlib\zlib.lib
          @rem set UPX_LIBS=%BDIR%\bzip2\bzip2.lib %BDIR%\ucl\ucl.lib %BDIR%\zlib\zlib.lib %BDIR%\zstd\zstd.lib
          set sources=%s%\*.cpp %s%\check\*.cpp %s%\compress\*.cpp %s%\console\*.cpp %s%\filter\*.cpp %s%\util\*.cpp
          %RUN_CL% -J -O2 -W4 -WX -std:c++17 -Zc:__cplusplus -EHsc -DUPX_VERSION_GITREV="""%GITREV%""" %DEFS% %UPX_DEFS% -I%H%\vendor -Feupx.exe %sources% %UPX_LIBS% /link ${{ matrix.link_machine_flags }} setargv.obj
      - name: Make artifact
        shell: bash
        run: |
          N=$(echo "upx-${GITHUB_REF_NAME}-${GITHUB_SHA:0:7}-windows-${{ matrix.name }}" | sed 's/[^0-9a-zA-Z_.-]/-/g')
          mkdir -p "tmp/artifact/$N/$B"
          cp -ai build/$C/$B/upx/upx*.exe "tmp/artifact/$N/$B"
          (cd tmp/artifact && tar --sort=name --zstd -cf "$N.tar.zst" "$N" && rm -rf "./$N" && ls -la && zstd -tq "$N.tar.zst")
          echo "artifact_name=$N" >> $GITHUB_ENV
      - name: ${{ format('Upload artifact {0}', env.artifact_name) }}
        uses: actions/upload-artifact@v4
        with: { name: '${{ env.artifact_name }}', path: tmp/artifact }
      - name: Run basic tests
        if: ${{ matrix.vsarch != 'amd64_arm64' }}
        run: |
          $ErrorActionPreference = 'stop'
          $ErrorView = 'NormalView'
          cd $Env:H\build\$Env:C\$Env:B\upx
          $Env:UPX = "--no-color --no-progress"
          .\upx.exe --version
          .\upx.exe --sysinfo -v
          .\upx.exe upx.exe -o upx_packed.exe
          dir *.exe
          .\upx.exe -l upx_packed.exe
          .\upx.exe -t upx_packed.exe
          .\upx_packed.exe --version
      - name: Run test suite
        if: ${{ matrix.vsarch != 'amd64_arm64' }}
        shell: bash
        run: |
          export upx_testsuite_SRCDIR="$(readlink -en ../deps/upx-testsuite)"
          env -C build/$C/$B/upx upx_exe=./upx.exe bash "$PWD"/misc/testsuite/upx_testsuite_1.sh

  job-linux-zigcc: # uses cmake + make
    if: github.repository_owner == 'upx'
    needs: [ job-rebuild-and-verify-stubs ]
    strategy:
      fail-fast: false
      matrix:
        include:
          # only build a few selected targets => more targets are tested in the Weekly CI
          - { zig_target: aarch64-linux-musl, qemu: qemu-aarch64 }
          - { zig_target: aarch64-macos.11.0-none }
          - { zig_target: aarch64-windows-gnu }
          - { zig_target: arm-linux-musleabihf, zig_flags: -mcpu=cortex_a5, qemu: qemu-arm }
          - { zig_target: armeb-linux-musleabihf, zig_flags: -mcpu=cortex_a5, qemu: qemu-armeb }
          - { zig_target: i386-linux-gnu.2.3.4, zig_flags: -march=i586 }
          - { zig_target: i386-linux-musl, zig_flags: -march=i586, qemu: qemu-i386 }
          - { zig_target: i386-windows-gnu }
          - { zig_target: mips-linux-musl, qemu: qemu-mips }
          - { zig_target: mips-linux-muslsf, zig_flags: -msoft-float, qemu: qemu-mips }
          - { zig_target: mipsel-linux-musl, qemu: qemu-mipsel }
          - { zig_target: mipsel-linux-muslsf, zig_flags: -msoft-float, qemu: qemu-mipsel }
          - { zig_target: powerpc-linux-musl, qemu: qemu-ppc }
          - { zig_target: powerpc64-linux-musl, qemu: qemu-ppc64 }
          - { zig_target: powerpc64le-linux-musl, qemu: qemu-ppc64le }
          - { zig_target: x86_64-linux-gnu.2.3.4, qemu: qemu-x86_64 } # can use QEMU because of gcompat
          - { zig_target: x86_64-linux-musl, qemu: qemu-x86_64 }
          - { zig_target: x86_64-macos.11.0-none }
          - { zig_target: x86_64-windows-gnu }
    name: ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }}
    runs-on: ubuntu-latest
    container: ${{ contains(matrix.qemu, 'qemu-armeb') && 'alpine:3.19' || 'alpine:3.20' }} # qemu-armeb bug
    env:
      # zig is currently missing winpthreads; see https://github.com/ziglang/zig/issues/10989
      UPX_CONFIG_EXPECT_THREADS: ${{ contains(matrix.zig_target, '-windows') && 'OFF' || 'ON' }}
      UPX_CONFIG_HAVE_WORKING_BUILD_RPATH: ''
      # for zig-cc wrapper scripts (see below):
      ZIG_CPPFLAGS: -DUPX_DOCTEST_CONFIG_MULTITHREADING
      ZIG_FLAGS: ${{ matrix.zig_flags }}
      ZIG_PIC: ${{ matrix.zig_pic }}
      ZIG_TARGET: ${{ matrix.zig_target }}
    steps:
      - name: Install Alpine Linux container packages
        if: ${{ job.container }}
        shell: sh
        run: |
          apk update && apk upgrade && apk add bash cmake curl file git make parallel tar util-linux xz zstd
          # set PATH like in Ubuntu
          echo "PATH=$HOME/.local/bin:$PATH" >> $GITHUB_ENV
          git config --global --add safe.directory '*' # needed when running in a container
          mkdir -p ~/.parallel && : > ~/.parallel/$(echo 6305-4721 | tr 0-7 leticlwi)
      - uses: actions/checkout@v4
        with: { submodules: true }
      - name: ${{ format('Install Zig {0}', env.ZIG_DIST_VERSION) }}
        shell: bash
        run: |
          rev=$(git rev-parse --short=7 HEAD)
          echo "UPX_GITREV_SHORT=$rev" >> $GITHUB_ENV
          if [[ "${{ matrix.zig_target }}" == i386-linux-gnu.2.3.4 ]]; then
            echo "UPX_CONFIG_DISABLE_SHARED_LIBS=ON" >> $GITHUB_ENV # zig problem when linking in Debug mode
          fi
          if [[ "${{ matrix.zig_target }}" == x86_64-linux-gnu.2.3.4 ]]; then
            echo "NEED_GCOMPAT=1" >> $GITHUB_ENV
            # TODO FIXME: problem with self-packed upx and musl+gcompat: "Not a valid dynamic program"
            echo "UPX_CONFIG_DISABLE_RUN_PACKED_TEST=ON" >> $GITHUB_ENV
          fi
          # install zig; note that ~/.local/bin is included in the default $PATH on Ubuntu
          mkdir -p -v ~/.local/bin
          cd ~/.local/bin
          ZIG_DIST_NAME=zig-linux-x86_64-${ZIG_DIST_VERSION}
          curl -sS -L -O https://ziglang.org/builds/${ZIG_DIST_NAME}.tar.xz
          ls -l ${ZIG_DIST_NAME}.tar.xz
          tar -xoJf ${ZIG_DIST_NAME}.tar.xz
          rm ${ZIG_DIST_NAME}.tar.xz
          ln -s -v ${ZIG_DIST_NAME}/zig zig
          #echo "PATH=$PATH" && which zig
          echo -n 'zig version: '; zig version
          # create wrapper scripts (needed for CMake)
          log=
          log='set -x\n'
          echo -e '#!/bin/sh\n'$log'exec zig ar "$@"' > zig-ar
          echo -e '#!/bin/sh\n'$log'exec zig cc  -target $ZIG_TARGET $ZIG_PIC $ZIG_FLAGS $ZIG_CPPFLAGS $ZIG_CFLAGS   "$@"' > zig-cc
          echo -e '#!/bin/sh\n'$log'exec zig c++ -target $ZIG_TARGET $ZIG_PIC $ZIG_FLAGS $ZIG_CPPFLAGS $ZIG_CXXFLAGS "$@"' > zig-cxx
          echo -e '#!/bin/sh\n'$log'exec zig ranlib "$@"' > zig-ranlib
          chmod +x zig-ar zig-cc zig-cxx zig-ranlib
          ls -la; head zig-ar zig-cc zig-cxx zig-ranlib
          # update ZIG_TARGET
          ZIG_TARGET=${ZIG_TARGET/i386-/x86-}    # i386 => x86
          ZIG_TARGET=${ZIG_TARGET/-muslsf/-musl} # muslsf => musl
          echo "ZIG_TARGET=$ZIG_TARGET" >> $GITHUB_ENV
          # -fPIE is for compilation only => also use the correct linker flag "-pie"
          # INFO: it seems the zig driver does handle this automatically(??), so not really needed
          if test "X$ZIG_PIC" = "X-fPIE"; then true;
            echo "ZIG_FLAGS=$ZIG_FLAGS --start-no-unused-arguments -pie --end-no-unused-arguments" >> $GITHUB_ENV
            echo "UPX_CONFIG_DISABLE_SHARED_LIBS=ON" >> $GITHUB_ENV # conflicts with ZIG_PIC/ZIG_FLAGS
          fi
      - run: set -x; zig version; zig-cc --version; zig-cxx --version
      - run: zig-cc  -E -x c   -dM /dev/null # list predefined macros for C
      - run: zig-cxx -E -x c++ -dM /dev/null # list predefined macros for C++
      - name: ${{ format('Build Release with zig-cc -target {0} {1}', env.ZIG_TARGET, env.ZIG_PIC) }}
        run: |
          make UPX_XTARGET=zig/${ZIG_TARGET}${ZIG_PIC} xtarget/release \
            CC="zig-cc" CXX="zig-cxx" CMAKE_AR="$HOME/.local/bin/zig-ar" CMAKE_RANLIB="$HOME/.local/bin/zig-ranlib"
          file build/zig/${ZIG_TARGET}${ZIG_PIC}/release/upx*
      - name: ${{ format('Build Debug with zig-cc -target {0} {1}', env.ZIG_TARGET, env.ZIG_PIC) }}
        run: |
          make UPX_XTARGET=zig/${ZIG_TARGET}${ZIG_PIC} xtarget/debug \
           CC="zig-cc" CXX="zig-cxx" CMAKE_AR="$HOME/.local/bin/zig-ar" CMAKE_RANLIB="$HOME/.local/bin/zig-ranlib"
          file build/zig/${ZIG_TARGET}${ZIG_PIC}/debug/upx*
      - name: ${{ format('Make artifact from upx-{0}-{1}', github.ref_name, env.UPX_GITREV_SHORT) }}
        shell: bash
        run: |
          N=$(echo "upx-${GITHUB_REF_NAME}-${GITHUB_SHA:0:7}-zigcc-${{ matrix.zig_target }}${ZIG_PIC}" | sed 's/[^0-9a-zA-Z_.-]/-/g')
          mkdir -p "tmp/artifact/$N"
          if [[ "${{ matrix.zig_target }}" == *-linux* ]]; then
            dirs="${ZIG_TARGET}${ZIG_PIC}/debug ${ZIG_TARGET}${ZIG_PIC}/release"
            for d in $dirs; do DESTDIR="$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithDestdir/$d" cmake --install build/zig/$d; done
            for d in $dirs; do DESTDIR="$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithDestdirAndStrip/$d" cmake --install build/zig/$d --strip; done
            for d in $dirs; do cmake --install build/zig/$d --prefix "$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithPrefix/$d"; done
            for d in $dirs; do cmake --install build/zig/$d --prefix "$PWD/tmp/artifact/$N/InstallWithCMake/InstallWithPrefixAndStrip/$d" --strip; done
          fi
          (cd build && shopt -s nullglob && cp -ai --parents */upx{,.exe} */*/*/upx{,.exe} "../tmp/artifact/$N")
          if command -v hardlink >/dev/null; then (cd "tmp/artifact/$N" && hardlink .) fi
          (cd tmp/artifact && tar --sort=name --zstd -cf "$N.tar.zst" "$N" && rm -rf "./$N" && ls -la && zstd -tq "$N.tar.zst")
          echo "artifact_name=$N" >> $GITHUB_ENV
      - name: ${{ format('Upload artifact {0}', env.artifact_name) }}
        uses: actions/upload-artifact@v4
        with: { name: '${{ env.artifact_name }}', path: tmp/artifact }
      - name: Run install tests
        if: ${{ contains(matrix.zig_target, '-linux') }}
        run: |
          (cd build/zig/${ZIG_TARGET}${ZIG_PIC}/release && DESTDIR="$PWD/Install with cmake" cmake --install .)
          (cd build/zig/${ZIG_TARGET}${ZIG_PIC}/release && DESTDIR="$PWD/Install with make"  make install)
      - name: Run ctest tests
        if: ${{ matrix.zig_target == 'i386-linux-musl' || matrix.zig_target == 'x86_64-linux-musl' || matrix.zig_target == 'x86_64-linux-gnu.2.3.4' }}
        run: |
          test -n "$NEED_GCOMPAT" && apk add gcompat
          jobs="${ZIG_TARGET}${ZIG_PIC}/debug ${ZIG_TARGET}${ZIG_PIC}/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'make build/zig/{}+test' ::: $jobs
      - name: Mimic ctest tests
        if: ${{ matrix.zig_target == 'i386-linux-musl' || matrix.zig_target == 'x86_64-linux-musl' || matrix.zig_target == 'x86_64-linux-gnu.2.3.4' }}
        run: |
          test -n "$NEED_GCOMPAT" && apk add gcompat
          jobs="${ZIG_TARGET}${ZIG_PIC}/debug ${ZIG_TARGET}${ZIG_PIC}/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'cd build/zig/{} && bash ../../../../misc/testsuite/mimic_ctest.sh' ::: $jobs
      - name: ${{ format('Mimic ctest tests with QEMU {0}', matrix.qemu) }}
        if: ${{ matrix.qemu }}
        run: |
          qemu="${{ matrix.qemu }}"
          apk add coreutils "${qemu%% *}"
          test -n "$NEED_GCOMPAT" && apk add gcompat
          export upx_exe_runner="$qemu"
          jobs="${ZIG_TARGET}${ZIG_PIC}/debug ${ZIG_TARGET}${ZIG_PIC}/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'cd build/zig/{} && bash ../../../../misc/testsuite/mimic_ctest.sh' ::: $jobs
      - name: Mimic ctest tests with Valgrind
        if: ${{ matrix.zig_target == 'x86_64-linux-musl' || matrix.zig_target == 'x86_64-linux-gnu.2.3.4' }}
        run: |
          export UPX_CONFIG_DISABLE_EXHAUSTIVE_TESTS=ON # valgrind is SLOW
          apk add coreutils valgrind
          test -n "$NEED_GCOMPAT" && apk add gcompat
          export upx_exe_runner="valgrind --leak-check=no --error-exitcode=1 --quiet --gen-suppressions=all"
          upx_exe_runner="$upx_exe_runner --suppressions=$PWD/misc/valgrind/musl.supp"
          jobs="${ZIG_TARGET}${ZIG_PIC}/debug ${ZIG_TARGET}${ZIG_PIC}/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'cd build/zig/{} && bash ../../../../misc/testsuite/mimic_ctest.sh' ::: $jobs
      - name: Run file system tests
        if: ${{ matrix.zig_target == 'i386-linux-musl' || matrix.zig_target == 'x86_64-linux-musl' || matrix.zig_target == 'x86_64-linux-gnu.2.3.4' }}
        run: |
          apk add coreutils sudo
          test -n "$NEED_GCOMPAT" && apk add gcompat
          jobs="${ZIG_TARGET}${ZIG_PIC}/debug ${ZIG_TARGET}${ZIG_PIC}/release"
          echo "===== parallel jobs: $jobs"
          parallel -kv --lb 'cd build/zig/{} && chmod a+w . && sudo -u nobody bash ../../../../misc/testsuite/test_symlinks.sh' ::: $jobs
