name: CI build

on:
  workflow_dispatch: # run manually https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow
  push:
    branches: [ "master" ] # run for these branches
  pull_request:
    branches: [ "master" ] # run for pull requests that target these branches

env:
  BUILD_TYPE: RelWithDebInfo # CMake build type (Release, Debug, RelWithDebInfo, etc.)

jobs:
  # ----------------------------------------------------------------------------------------------
  linux-amd64:
    name: Linux (amd64/x86_64)
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Install dependencies with APT (libelf, libpcap)
      uses: awalsh128/cache-apt-pkgs-action@latest
      with:
        packages: libelf-dev libpcap0.8-dev
        version: linux-amd64

    - name: Configure with CMake
      run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DDYNAMIPS_CODE=both -DBUILD_UDP_RECV=ON -DBUILD_UDP_SEND=ON -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/build/install -DCPACK_PACKAGE_FILE_NAME=dynamips-package -DCPACK_GENERATOR=ZIP

    - name: Build
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}

    - name: Test
      run: |
        set -x
        cd ${{github.workspace}}/build
        ctest --verbose --build-config ${{env.BUILD_TYPE}}
        ./stable/dynamips -e
        ./unstable/dynamips_amd64_unstable -e

    - name: Install
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target install

    - name: Package
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target package

    - name: Upload success
      if: ${{ success() }}
      uses: actions/upload-artifact@v4
      with:
        name: linux-amd64-success
        path: |
          ${{github.workspace}}/build/install/
          ${{github.workspace}}/build/dynamips-package.zip
        compression-level: 9 # maximum compression

    - name: Upload failure
      if: ${{ failure() }}
      uses: actions/upload-artifact@v4
      with:
        name: linux-amd64-failure
        path: ${{github.workspace}}/build/
        compression-level: 9 # maximum compression

  # ----------------------------------------------------------------------------------------------
  linux-x86:
    name: Linux (x86)
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Setup x86 chroot with Alpine Linux and install dependencies
      uses: jirutka/setup-alpine@v1
      with:
        arch: x86
        packages: >
          build-base
          linux-headers
          elfutils-dev
          libpcap-dev
          cmake

    - name: Configure with CMake
      shell: alpine.sh {0}
      run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DDYNAMIPS_CODE=both -DBUILD_UDP_RECV=ON -DBUILD_UDP_SEND=ON -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/build/install -DCPACK_PACKAGE_FILE_NAME=dynamips-package -DCPACK_GENERATOR=ZIP

    - name: Build
      shell: alpine.sh {0}
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}

    - name: Test
      shell: alpine.sh {0}
      run: |
        set -x
        cd ${{github.workspace}}/build
        ctest --verbose --build-config ${{env.BUILD_TYPE}}
        ./stable/dynamips -e
        ./unstable/dynamips_x86_unstable -e

    - name: Install
      shell: alpine.sh {0}
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target install

    - name: Package
      shell: alpine.sh {0}
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target package

    - name: Upload success
      if: ${{ success() }}
      uses: actions/upload-artifact@v4
      with:
        name: linux-x86-success
        path: |
          ${{github.workspace}}/build/install/
          ${{github.workspace}}/build/dynamips-package.zip
        compression-level: 9 # maximum compression

    - name: Upload failure
      if: ${{ failure() }}
      uses: actions/upload-artifact@v4
      with:
        name: linux-x86-failure
        path: ${{github.workspace}}/build/
        compression-level: 9 # maximum compression

  # ----------------------------------------------------------------------------------------------
  macos-amd64:
    name: MacOS (amd64/x86_64)
    runs-on: macos-13 # macos-13 is the last runner with an AMD64 cpu, macos-14 has an ARM cpu (Apple M1)
    steps:
    - uses: actions/checkout@v4

    - name: Install dependencies with brew
      uses: ConorMacBride/install-package@v1
      with:
        brew: libelf # libpcap is included in the SDK

    - name: Configure with CMake
      run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DDYNAMIPS_CODE=both -DBUILD_UDP_RECV=ON -DBUILD_UDP_SEND=ON -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/build/install -DCPACK_PACKAGE_FILE_NAME=dynamips-package -DCPACK_GENERATOR=ZIP

    - name: Build
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}

    - name: Test
      run: |
        set -x
        cd ${{github.workspace}}/build
        ctest --verbose --build-config ${{env.BUILD_TYPE}}
        ./stable/dynamips -e
        ./unstable/dynamips_amd64_unstable -e

    - name: Install
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target install

    - name: Package
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target package

    - name: Upload success
      if: ${{ success() }}
      uses: actions/upload-artifact@v4
      with:
        name: macos-amd64-success
        path: |
          ${{github.workspace}}/build/install/
          ${{github.workspace}}/build/dynamips-package.zip
        compression-level: 9 # maximum compression

    - name: Upload failure
      if: ${{ failure() }}
      uses: actions/upload-artifact@v4
      with:
        name: macos-amd64-failure
        path: ${{github.workspace}}/build/
        compression-level: 9 # maximum compression

  # ----------------------------------------------------------------------------------------------
  cygwin-amd64:
    name: Cygwin (amd64/x86_64)
    runs-on: windows-latest
    env:
      CYGWIN_NOWINPATH: 1 # isolate cygwin from windows
    defaults:
      run:
        shell: C:\cygwin\bin\bash.EXE --login -e -o pipefail -o igncr '{0}'
    steps:
    - name: Avoid '\r' problems
      shell: cmd
      run: git config --global core.autocrlf input

    - uses: actions/checkout@v4

    - name: Install dependencies with Cygwin
      uses: cygwin/cygwin-install-action@master
      with:
        packages: git curl p7zip cmake make gcc-core # FIXME using the libelf-devel package gets you a "missing libintl_dgettext" error, how to fix?
        platform: x86_64
        add-to-path: false

    - name: Adapt cygwin environment
      run: |
        set -x
        # avoid git error "detected dubious ownership"
        git config --global --add safe.directory '*'
        # set the start directory
        echo "cd '${{github.workspace}}'" >> ~/.bash_profile

    - name: Install dependencies (libelf)
      run: |
        set -x
        curl -O https://fossies.org/linux/misc/old/libelf-0.8.13.tar.gz
        tar xvzf libelf-0.8.13.tar.gz
        cd libelf-0.8.13
        ./configure --host x86_64-pc-cygwin
        make
        make install

    - name: Install dependencies (winpcap SDK)
      run: |
        set -x
        curl -O https://www.winpcap.org/install/bin/WpdPack_4_1_2.zip
        7za x ./WpdPack_4_1_2.zip -o.
        cp ./WpdPack/Lib/x64/Packet.lib /usr/local/lib/libpacket.a
        cp ./WpdPack/Lib/x64/wpcap.lib /usr/local/lib/libwpcap.a
        cp --verbose --force --recursive ./WpdPack/Include/* /usr/local/include/

    - name: Install dependencies (win10pcap)
      shell: cmd
      run: |
        echo on
        curl -O "http://www.win10pcap.org/download/Win10Pcap-v10.2-5002.msi"
        msiexec /i "Win10Pcap-v10.2-5002.msi" /qn /norestart /L*v "Win10Pcap-install.log"
        type "Win10Pcap-install.log"

    - name: Configure with CMake
      run: cmake -B $(pwd)/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DDYNAMIPS_CODE=both -DBUILD_UDP_RECV=ON -DBUILD_UDP_SEND=ON -DCMAKE_INSTALL_PREFIX=$(pwd)/build/install -DCPACK_PACKAGE_FILE_NAME=dynamips-package -DCPACK_GENERATOR=ZIP

    - name: Build
      run: cmake --build $(pwd)/build --config ${{env.BUILD_TYPE}}

    - name: Test
      run: |
        set -x
        cd build
        ctest --verbose --build-config ${{env.BUILD_TYPE}}
        ./stable/dynamips -e
        ./unstable/dynamips_amd64_unstable -e

    - name: Install
      run: cmake --build $(pwd)/build --config ${{env.BUILD_TYPE}} --target install

    - name: Package
      run: cmake --build $(pwd)/build --config ${{env.BUILD_TYPE}} --target package

    - name: Upload success
      if: ${{ success() }}
      uses: actions/upload-artifact@v4
      with:
        name: cygwin-amd64-success
        path: |
          ${{github.workspace}}/build/install/
          ${{github.workspace}}/build/dynamips-package.zip
        compression-level: 9 # maximum compression

    - name: Upload failure
      if: ${{ failure() }}
      uses: actions/upload-artifact@v4
      with:
        name: cygwin-amd64-failure
        path: ${{github.workspace}}/build/
        compression-level: 9 # maximum compression

  # ----------------------------------------------------------------------------------------------
  linux-ppc32:
    name: Linux (ppc32)
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Restore ArchPOWER powerpc iso
      uses: actions/cache/restore@v4
      with:
        path: |
          archpower-current-powerpc.iso
          archpower-current-powerpc.iso.sig
        key: archpower-current-powerpc-iso

    - name: Get updated ArchPOWER powerpc iso
      env:
        GH_TOKEN: ${{ github.token }}
      run: |
        set -x

        ## Get updated iso
        if [ -f archpower-current-powerpc.iso ]; then
          stat archpower-current-powerpc.iso* >cached.stat 2>/dev/null
        fi
        wget --progress=dot:giga --directory-prefix . --timestamping https://archlinuxpower.org/iso/archpower-current-powerpc.iso
        wget --progress=dot:giga --directory-prefix . --timestamping https://archlinuxpower.org/iso/archpower-current-powerpc.iso.sig
        gpg --keyserver hkps://keyserver.ubuntu.com --keyserver-options auto-key-retrieve --verify archpower-current-powerpc.iso.sig

        ## Invalidate cache if files changed
        if [ -f cached.stat ]; then
          stat archpower-current-powerpc.iso* >updated.stat 2>/dev/null
          cmp -s cached.stat updated.stat || gh cache delete archpower-current-powerpc-iso
          rm cached.stat updated.stat
        fi

    - name: Cache ArchPOWER powerpc iso
      uses: actions/cache/save@v4
      with:
        path: |
          archpower-current-powerpc.iso
          archpower-current-powerpc.iso.sig
        key: archpower-current-powerpc-iso

    - name: Create chroot
      run: |
        set -x

        ## Make sure the system can run ppc32 executables (persistent) and that sudo works (credential)
        sudo docker run --rm --privileged multiarch/qemu-user-static --reset --persistent yes --credential yes

        ## Create overlay
        mkdir -p overlay/iso overlay/sfs overlay/rw overlay/root overlay/work overlay/script
        sudo mount --read-only archpower-current-powerpc.iso overlay/iso/
        sudo mount --read-only overlay/iso/arch/ppc/airootfs.sfs overlay/sfs/
        sudo mount -t overlay -o lowerdir=overlay/sfs,upperdir=overlay/rw,workdir=overlay/work none overlay/root/
        sudo chown root:root overlay/root
        sudo mkdir overlay/root/home/$USER
        sudo mount --rbind /home/$USER overlay/root/home/$USER/
        # based on https://wiki.archlinux.org/title/Chroot#Using_chroot
        sudo mount -t proc /proc overlay/root/proc/
        sudo mount -t sysfs /sys overlay/root/sys/
        sudo mount --rbind /dev overlay/root/dev/
        sudo rm overlay/root/etc/resolv.conf
        sudo cp /etc/resolv.conf overlay/root/etc/resolv.conf
        cat overlay/root/version

        ## Create chroot scripts
        echo "#!$SHELL" > overlay/script/chroot-as-root.sh
        echo "exec sudo chroot $PWD/overlay/root \"\$@\"" >> overlay/script/chroot-as-root.sh
        cat ./overlay/script/chroot-as-root.sh
        echo "#!$SHELL" > overlay/script/chroot-as-user.sh
        echo "exec sudo chroot --userspec $USER $PWD/overlay/root \"\$@\"" >> overlay/script/chroot-as-user.sh
        cat ./overlay/script/chroot-as-user.sh
        chmod +x overlay/script/*
        echo "$PWD/overlay/script" >> "$GITHUB_PATH"

        ## Add current user to the chroot
        ./overlay/script/chroot-as-root.sh useradd -u $(id -u $USER) -G wheel $USER
        ./overlay/script/chroot-as-root.sh bash -c "echo '%wheel ALL=(ALL:ALL) NOPASSWD: ALL' >> /etc/sudoers"

    - name: Install dependencies
      shell: chroot-as-root.sh bash {0}
      run: |
        set -x
        pacman-key --init
        pacman-key --populate
        pacman -Sy --noconfirm --needed base-devel sudo git cmake zip libelf libpcap

    - name: Configure with CMake
      shell: chroot-as-user.sh bash {0}
      run: cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DDYNAMIPS_CODE=both -DBUILD_UDP_RECV=ON -DBUILD_UDP_SEND=ON -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/build/install -DCPACK_PACKAGE_FILE_NAME=dynamips-package -DCPACK_GENERATOR=ZIP

    - name: Build
      shell: chroot-as-user.sh bash {0}
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}

    - name: Test
      shell: chroot-as-user.sh bash {0}
      run: |
        set -x
        cd ${{github.workspace}}/build
        ctest --verbose --build-config ${{env.BUILD_TYPE}}
        ./stable/dynamips -e
        ./unstable/dynamips_ppc32_unstable -e

    - name: Install
      shell: chroot-as-user.sh bash {0}
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target install

    - name: Package
      shell: chroot-as-user.sh bash {0}
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target package

    - name: Upload success
      if: ${{ success() }}
      uses: actions/upload-artifact@v4
      with:
        name: linux-ppc32-success
        path: |
          ${{github.workspace}}/build/install/
          ${{github.workspace}}/build/dynamips-package.zip
        compression-level: 9 # maximum compression

    - name: Upload failure
      if: ${{ failure() }}
      uses: actions/upload-artifact@v4
      with:
        name: linux-ppc32-failure
        path: ${{github.workspace}}/build/
        compression-level: 9 # maximum compression
