name: Continuous Integration
on: [push, pull_request]

jobs:
  cmake:
    name: CMake Buildsystem
    if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')"

    strategy:
      fail-fast: false

      matrix:
        profile:
        - ubuntu-bionic-standard-unity-makefile
        - ubuntu-bionic-coverage-ninja
        - macos-eigen-coverage-unity-xcode
        - macos-nometa-standard-makefile
        - windows-standard-unity-msvc
        - windows-nopython-nometa-standard-msvc

        include:
        - profile: ubuntu-bionic-standard-unity-makefile
          os: ubuntu-20.04
          config: Standard
          unity: YES
          generator: Unix Makefiles
          compiler: Default
          metalibs: YES
          python: YES
          eigen: NO

        - profile: ubuntu-bionic-coverage-ninja
          os: ubuntu-20.04
          config: Coverage
          unity: NO
          generator: Ninja
          compiler: Clang
          metalibs: YES
          python: YES
          eigen: NO

        - profile: macos-eigen-coverage-unity-xcode
          os: macOS-12
          config: Coverage
          unity: YES
          generator: Xcode
          compiler: Default
          metalibs: YES
          python: YES
          eigen: YES

        - profile: macos-nometa-standard-makefile
          os: macOS-12
          config: Standard
          unity: NO
          generator: Unix Makefiles
          compiler: Default
          metalibs: NO
          python: YES
          eigen: NO

        - profile: windows-standard-unity-msvc
          os: windows-2022
          config: Standard
          unity: YES
          generator: Visual Studio 17 2022
          compiler: Default
          metalibs: YES
          python: YES
          eigen: NO

        - profile: windows-nopython-nometa-standard-msvc
          os: windows-2022
          config: Standard
          unity: NO
          generator: Visual Studio 17 2022
          compiler: Default
          metalibs: NO
          python: NO
          eigen: NO

    runs-on: ${{ matrix.os }}

    steps:
    - uses: actions/checkout@v1
      with:
        fetch-depth: 10

    - name: Self-destruct makepanda
      run: python3 makepanda/selfdestruct.py --yes

    - name: Install dependencies (macOS)
      if: runner.os == 'macOS'
      run: |
        curl -O https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-mac.tar.gz
        tar -xf panda3d-1.10.14-tools-mac.tar.gz
        mv panda3d-1.10.14/thirdparty thirdparty
        rmdir panda3d-1.10.14

        # Temporary hack so that pzip can run, since we are about to remove Cg anyway.
        install_name_tool -id "$(pwd)/thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib" thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib

        brew install ccache

    - name: Install dependencies (Ubuntu)
      if: startsWith(matrix.os, 'ubuntu')
      run: >
        sudo apt-get update

        sudo apt-get install
        build-essential ninja-build clang llvm ccache
        bison flex
        libeigen3-dev libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev
        libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev
        libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
        python3-setuptools python3-tk

    - name: Cache dependencies (Windows)
      if: runner.os == 'Windows'
      uses: actions/cache@v1
      with:
        path: thirdparty
        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.14-r1
    - name: Install dependencies (Windows)
      if: runner.os == 'Windows'
      shell: powershell
      run: |
        if (!(Test-Path thirdparty/win-libs-vc14-x64)) {
          $wc = New-Object System.Net.WebClient
          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win64.zip", "thirdparty-tools.zip")
          Expand-Archive -Path thirdparty-tools.zip
          Move-Item -Path thirdparty-tools/panda3d-1.10.14/thirdparty -Destination .
        }

    - name: ccache (non-Windows)
      if: runner.os != 'Windows'
      uses: actions/cache@v1
      with:
        path: ccache
        key: ci-cmake-ccache-${{ matrix.profile }}

    - name: Configure
      shell: bash
      env:
        CMAKE_GENERATOR: "${{ matrix.generator }}"
      run: >
        mkdir -p build

        cd build

        if ${{ matrix.compiler == 'Clang' }}; then
          if [[ "$CMAKE_GENERATOR" =~ Studio.+20(19|22) ]]; then
            export CMAKE_GENERATOR_TOOLSET=ClangCL
          elif [[ "$CMAKE_GENERATOR" == *Studio* ]]; then
            export CMAKE_GENERATOR_TOOLSET=LLVM
          else
            export CC=clang CXX=clang++
          fi
        fi

        if ${{ runner.os != 'Windows' }}; then
          compilerLauncher=$(echo -DCMAKE_C{,XX}_COMPILER_LAUNCHER=ccache)
          export CCACHE_DIR="$(dirname $PWD)/ccache"
          echo "CCACHE_DIR=$(dirname $PWD)/ccache" >> $GITHUB_ENV
        fi

        cmake
        ${compilerLauncher:-}
        -D CMAKE_UNITY_BUILD=${{ matrix.unity }}
        -D CMAKE_BUILD_TYPE="${{ matrix.config }}"
        -D BUILD_METALIBS=${{ matrix.metalibs }}
        -D HAVE_PYTHON=${{ runner.os != 'Windows' && matrix.python || 'NO' }}
        -D HAVE_EIGEN=${{ matrix.eigen }}
        ..

    - name: Build (no Python)
      if: contains(matrix.python, 'NO')
      # BEGIN A
      working-directory: build
      run: cmake --build . --config ${{ matrix.config }} --parallel 4
      # END A

    - name: Setup Python (Python 3.8)
      if: contains(matrix.python, 'YES')
      uses: actions/setup-python@v4
      with:
        python-version: '3.8'
    - name: Configure (Python 3.8)
      if: contains(matrix.python, 'YES')
      working-directory: build
      shell: bash
      run: >
        cmake -DWANT_PYTHON_VERSION=3.8 -DHAVE_PYTHON=YES
        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
    - name: Build (Python 3.8)
      if: contains(matrix.python, 'YES')
      # BEGIN A
      working-directory: build
      run: cmake --build . --config ${{ matrix.config }} --parallel 4
      # END A
    - name: Test (Python 3.8)
      # BEGIN B
      if: contains(matrix.python, 'YES')
      working-directory: build
      shell: bash
      env:
        PYTHONPATH: ${{ matrix.config }}
      run: |
        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
      # END B

    - name: Setup Python (Python 3.9)
      if: contains(matrix.python, 'YES')
      uses: actions/setup-python@v4
      with:
        python-version: '3.9'
    - name: Configure (Python 3.9)
      if: contains(matrix.python, 'YES')
      working-directory: build
      shell: bash
      run: >
        cmake -DWANT_PYTHON_VERSION=3.9 -DHAVE_PYTHON=YES
        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
    - name: Build (Python 3.9)
      if: contains(matrix.python, 'YES')
      # BEGIN A
      working-directory: build
      run: cmake --build . --config ${{ matrix.config }} --parallel 4
      # END A
    - name: Test (Python 3.9)
      # BEGIN B
      if: contains(matrix.python, 'YES')
      working-directory: build
      shell: bash
      env:
        PYTHONPATH: ${{ matrix.config }}
      run: |
        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
      # END B

    - name: Setup Python (Python 3.10)
      if: contains(matrix.python, 'YES')
      uses: actions/setup-python@v4
      with:
        python-version: '3.10'
    - name: Configure (Python 3.10)
      if: contains(matrix.python, 'YES')
      working-directory: build
      shell: bash
      run: >
        cmake -DWANT_PYTHON_VERSION=3.10 -DHAVE_PYTHON=YES
        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
    - name: Build (Python 3.10)
      if: contains(matrix.python, 'YES')
      # BEGIN A
      working-directory: build
      run: cmake --build . --config ${{ matrix.config }} --parallel 4
      # END A
    - name: Test (Python 3.10)
      # BEGIN B
      if: contains(matrix.python, 'YES')
      working-directory: build
      shell: bash
      env:
        PYTHONPATH: ${{ matrix.config }}
      run: |
        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
      # END B

    - name: Setup Python (Python 3.11)
      if: contains(matrix.python, 'YES')
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
    - name: Configure (Python 3.11)
      if: contains(matrix.python, 'YES')
      working-directory: build
      shell: bash
      run: >
        cmake -DWANT_PYTHON_VERSION=3.11 -DHAVE_PYTHON=YES
        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
    - name: Build (Python 3.11)
      if: contains(matrix.python, 'YES')
      # BEGIN A
      working-directory: build
      run: cmake --build . --config ${{ matrix.config }} --parallel 4
      # END A
    - name: Test (Python 3.11)
      # BEGIN B
      if: contains(matrix.python, 'YES')
      working-directory: build
      shell: bash
      env:
        PYTHONPATH: ${{ matrix.config }}
      run: |
        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
      # END B

    - name: Setup Python (Python 3.12)
      if: contains(matrix.python, 'YES')
      uses: actions/setup-python@v4
      with:
        python-version: '3.12'
    - name: Configure (Python 3.12)
      if: contains(matrix.python, 'YES')
      working-directory: build
      shell: bash
      run: >
        cmake -DWANT_PYTHON_VERSION=3.12 -DHAVE_PYTHON=YES
        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
    - name: Build (Python 3.12)
      if: contains(matrix.python, 'YES')
      # BEGIN A
      working-directory: build
      run: cmake --build . --config ${{ matrix.config }} --parallel 4
      # END A
    - name: Test (Python 3.12)
      # BEGIN B
      if: contains(matrix.python, 'YES')
      working-directory: build
      shell: bash
      env:
        PYTHONPATH: ${{ matrix.config }}
      run: |
        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
      # END B

    - name: Upload coverage reports
      if: always() && matrix.config == 'Coverage'
      working-directory: build
      shell: bash
      env:
        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
      run: |
        shopt -s expand_aliases
        if ${{ runner.os == 'macOS' }}; then alias llvm-profdata='xcrun llvm-profdata' llvm-cov='xcrun llvm-cov'; fi

        python -m pip install coverage
        python -m coverage combine $(find . -name '.coverage.*')

        llvm-profdata merge pid-*.profraw -o coverage.profdata
        llvm-cov show $(grep -Rl LLVM_PROFILE_FILE . | sed 's/^/-object /') -instr-profile=coverage.profdata > coverage.txt
        bash <(curl -s https://codecov.io/bash) -y ../.github/codecov.yml

  makepanda:
    if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')"
    strategy:
      matrix:
        os: [ubuntu-20.04, windows-2019, macOS-12]
    runs-on: ${{ matrix.os }}
    steps:
    - uses: actions/checkout@v1
    - name: Install dependencies (Ubuntu)
      if: matrix.os == 'ubuntu-20.04'
      run: |
        sudo apt-get update
        sudo apt-get install build-essential bison flex libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
    - name: Get thirdparty packages (Windows)
      if: runner.os == 'Windows'
      shell: powershell
      run: |
        $wc = New-Object System.Net.WebClient
        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win64.zip", "thirdparty-tools.zip")
        Expand-Archive -Path thirdparty-tools.zip
        Move-Item -Path thirdparty-tools/panda3d-1.10.14/thirdparty -Destination .
    - name: Get thirdparty packages (macOS)
      if: runner.os == 'macOS'
      run: |
        curl -O https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-mac.tar.gz
        tar -xf panda3d-1.10.14-tools-mac.tar.gz
        mv panda3d-1.10.14/thirdparty thirdparty
        rmdir panda3d-1.10.14
        (cd thirdparty/darwin-libs-a && rm -rf rocket)

    - name: Set up Python 3.12
      uses: actions/setup-python@v4
      with:
        python-version: '3.12'
    - name: Build Python 3.12
      shell: bash
      run: |
        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
    - name: Test Python 3.12
      shell: bash
      run: |
        python -m pip install -r requirements-test.txt
        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest

    - name: Set up Python 3.11
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
    - name: Build Python 3.11
      shell: bash
      run: |
        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
    - name: Test Python 3.11
      shell: bash
      run: |
        python -m pip install -r requirements-test.txt
        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest

    - name: Set up Python 3.10
      uses: actions/setup-python@v4
      with:
        python-version: '3.10'
    - name: Build Python 3.10
      shell: bash
      run: |
        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
    - name: Test Python 3.10
      shell: bash
      run: |
        python -m pip install -r requirements-test.txt
        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest

    - name: Set up Python 3.9
      uses: actions/setup-python@v4
      with:
        python-version: '3.9'
    - name: Build Python 3.9
      shell: bash
      run: |
        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
    - name: Test Python 3.9
      shell: bash
      run: |
        python -m pip install -r requirements-test.txt
        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest

    - name: Set up Python 3.8
      uses: actions/setup-python@v4
      with:
        python-version: '3.8'
    - name: Build Python 3.8
      shell: bash
      run: |
        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
    - name: Test Python 3.8
      shell: bash
      run: |
        python -m pip install -r requirements-test.txt
        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest

    - name: Make installer
      run: |
        python makepanda/makepackage.py --verbose --lzma

  emscripten:
    if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')"
    runs-on: ubuntu-22.04
    steps:
    - uses: actions/checkout@v1

    - name: Install dependencies
      run: |
        sudo apt-get update
        sudo apt-get install build-essential ninja-build bison flex

    - name: Setup emsdk
      uses: mymindstorm/setup-emsdk@v14
      with:
        version: 3.1.51
        actions-cache-folder: 'emsdk-cache'

    - name: Build for Emscripten
      shell: bash
      run: |
        python3 makepanda/makepanda.py --git-commit=${{github.sha}} --target emscripten --outputdir=built --everything --no-python --verbose --threads=4
