# Copyright 2022 Google LLC
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd

# A reusable workflow to build and test Packager on every supported OS and
# architecture.
name: Build

# Runs when called from another workflow.
on:
  workflow_call:
    inputs:
      ref:
        required: true
        type: string
      # If true, start a debug SSH server on failures.
      debug:
        required: false
        type: boolean
        default: false
      # If true, enable self-hosted runners in the build matrix.
      self_hosted:
        required: false
        type: boolean
        default: false

# By default, run all commands in a bash shell.  On Windows, the default would
# otherwise be powershell.
defaults:
  run:
    shell: bash

jobs:
  # Configure the build matrix based on inputs.  The list of objects in the
  # build matrix contents can't be changed by conditionals, but it can be
  # computed by another job and deserialized.  This uses inputs.self_hosted to
  # determine the build matrix, based on the metadata in build-matrix.json.
  build_matrix_config:
    name: Matrix configuration
    runs-on: ubuntu-latest
    outputs:
      INCLUDE: ${{ steps.configure.outputs.INCLUDE }}
      OS: ${{ steps.configure.outputs.OS }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false

      - name: Configure Build Matrix
        id: configure
        shell: node {0}
        run: |
          const fs = require('fs');

          const enableSelfHosted = ${{ inputs.self_hosted }};
          const buildMatrix = JSON.parse(fs.readFileSync("${{ github.workspace }}/build-matrix.json"));

          // Use enableSelfHosted to decide what the build matrix below should
          // include.
          const {hosted, selfHosted} = buildMatrix;
          const include = enableSelfHosted ? hosted.concat(selfHosted) : hosted;
          const os = include.map((config) => config.os);

          // Output JSON objects consumed by the build matrix below.
          fs.writeFileSync(process.env.GITHUB_OUTPUT,
              [
                `INCLUDE=${ JSON.stringify(include) }`,
                `OS=${ JSON.stringify(os) }`,
              ].join('\n'),
              {flag: 'a'});

          // Log the outputs, for the sake of debugging this script.
          console.log({enableSelfHosted, include, os});

  build:
    needs: build_matrix_config
    strategy:
      fail-fast: false
      matrix:
        include: ${{ fromJSON(needs.build_matrix_config.outputs.INCLUDE) }}
        os: ${{ fromJSON(needs.build_matrix_config.outputs.OS) }}
        build_type: ["Debug", "Release"]
        lib_type: ["static", "shared"]

    name: ${{ matrix.os_name }} ${{ matrix.target_arch }} ${{ matrix.build_type }} ${{ matrix.lib_type }}
    runs-on: ${{ matrix.os }}

    steps:
      - name: Configure git to preserve line endings
        # Otherwise, tests fail on Windows because "golden" test outputs will not
        # have the correct line endings.
        run: git config --global core.autocrlf false

      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          submodules: recursive
          fetch-tags: true
          persist-credentials: false

      - name: Install Linux deps
        if: runner.os == 'Linux'
        # NOTE: CMake is already installed in GitHub Actions VMs, but not
        # necessarily in a self-hosted runner.  We also need a minimum version
        # that may be greater than what is available in Ubuntu, so we set up
        # the official CMake PPA first.
        run: |
          kitware_key_url="http://apt.kitware.com/keys/kitware-archive-latest.asc"
          kitware_key_path="/usr/share/keyrings/kitware-archive-keyring.gpg"
          kitware_sources_path="/etc/apt/sources.list.d/kitware.list"

          curl -sL "$kitware_key_url" | gpg --dearmor - \
              | sudo tee "$kitware_key_path" >/dev/null

          . /etc/lsb-release  # Defines $DISTRIB_CODENAME (jammy, focal, etc)

          echo "deb [signed-by=$kitware_key_path] http://apt.kitware.com/ubuntu/ $DISTRIB_CODENAME main" \
              | sudo tee "$kitware_sources_path" >/dev/null

          sudo apt update
          sudo apt install -y \
              cmake \
              ninja-build

      - name: Install Mac deps
        if: runner.os == 'macOS'
        # NOTE: GitHub Actions VMs on Mac do not install ninja by default.
        run: |
          brew install ninja

      - name: Check Mac CPU architecture
        if: runner.os == 'macOS'
        # In case we get confused about GitHub's mac VM image labels,
        # explicitly check that the CPU type matches our expectations.
        run: |
          if [[ "${{matrix.target_arch}}" == "arm64" ]]; then
            CORRECT_LABEL="arm64"
          else
            CORRECT_LABEL="x86_64"
          fi

          LABEL=$(uname -m)
          echo "Hardware label: \"$LABEL\""

          if [[ "$LABEL" != "$CORRECT_LABEL" ]]; then
            echo "Wrong hardware label \"$LABEL\", expecting \"$CORRECT_LABEL\"."
            echo "Full uname string: $(uname -a)"
            echo "Full sysctl CPU info:"
            sysctl machdep.cpu
            exit 1
          fi

      - name: Generate build files
        run: |
          mkdir -p build/

          if [[ "${{ matrix.lib_type }}" == "shared" ]]; then
            BUILD_SHARED_LIBS="ON"
          else
            BUILD_SHARED_LIBS="OFF"
          fi

          # If set, override the default generator for the platform.
          # Not every entry in the build matrix config defines this.
          # If this is blank, CMake will choose the default generator.
          export CMAKE_GENERATOR="${{ matrix.generator }}"

          # If set, configure the build to restrict parallel operations.
          # This helps us avoid the compiler failing due to a lack of RAM
          # on our arm64 build devices (4GB RAM shared among 6 CPUs).
          if [[ "${{ matrix.low_mem }}" != "" ]]; then
            export PACKAGER_LOW_MEMORY_BUILD=yes
          fi

          # Do fully static release builds on Linux.
          BUILD_CONFIG="${{ matrix.build_type }}-${{ matrix.lib_type }}"
          if [[ "${{ runner.os }}" == "Linux" && \
                "$BUILD_CONFIG" == "Release-static" ]]; then
            # Enable build settings for fully-static.
            FULLY_STATIC="ON"

            # Use a musl toolchain, since glibc static executables are not
            # portable.
            if [[ "${{matrix.target_arch}}" == "arm64" ]]; then
              MUSL_ARCH="aarch64"
            else
              MUSL_ARCH="x86_64"
            fi
            curl -LO https://joeyparrish.github.io/musl-cc-mirror/"$MUSL_ARCH"-linux-musl-native.tgz
            tar xf "$MUSL_ARCH"-linux-musl-native.tgz
            export CC=`pwd`/"$MUSL_ARCH"-linux-musl-native/bin/"$MUSL_ARCH"-linux-musl-gcc
            export CXX=`pwd`/"$MUSL_ARCH"-linux-musl-native/bin/"$MUSL_ARCH"-linux-musl-g++
          else
            FULLY_STATIC="OFF"
          fi

          cmake \
            -DCMAKE_BUILD_TYPE="${{ matrix.build_type }}" \
            -DBUILD_SHARED_LIBS="$BUILD_SHARED_LIBS" \
            -DFULLY_STATIC="$FULLY_STATIC" \
            -S . \
            -B build/

      - name: Build
        # This is a universal build command, which will call make on Linux and
        # Visual Studio on Windows.  Note that the VS generator is what cmake
        # calls a "multi-configuration" generator, and so the desired build
        # type must be specified for Windows.
        run: cmake --build build/ --config "${{ matrix.build_type }}" --parallel

      - name: Test
        run: ctest -C "${{ matrix.build_type }}" -V --test-dir build/

      - name: Publish Test Report
        uses: mikepenz/action-junit-report@v4
        if: ${{ always() }}
        with:
          report_paths: 'junit-reports/TEST-*.xml'

      - name: Prepare artifacts (static release only)
        run: |
          BUILD_CONFIG="${{ matrix.build_type }}-${{ matrix.lib_type }}"
          if [[ "$BUILD_CONFIG" != "Release-static" ]]; then
            echo "Skipping artifacts for $BUILD_CONFIG."
            exit 0
          fi

          # Check static executables
          if [[ "${{ runner.os }}" == "Linux" ]]; then
            echo "::group::Check static executables"
            for exe in build/packager/{packager,mpd_generator}; do
              # Capture information about the executables, but also let it be
              # logged to stdout.
              ldd "$exe" | tee static.log
              # The phrase "statically linked" means we got it right.  Fail if
              # we don't find it.
              if ! cat static.log | grep -q statically; then
                exit 1
              fi
            done
            echo "::endgroup::"
          fi

          echo "::group::Prepare artifacts folder"
          mkdir artifacts
          ARTIFACTS="$GITHUB_WORKSPACE/artifacts"
          if [[ "${{ runner.os }}" == "Windows" ]]; then
            cd build/packager/Release
          else
            cd build/packager
          fi
          echo "::endgroup::"

          echo "::group::Strip executables"
          strip packager${{ matrix.exe_ext }}
          strip mpd_generator${{ matrix.exe_ext }}
          echo "::endgroup::"

          SUFFIX="-${{ matrix.os_name }}-${{ matrix.target_arch }}"
          EXE_SUFFIX="$SUFFIX${{ matrix.exe_ext}}"

          echo "::group::Copy packager"
          cp packager${{ matrix.exe_ext }} $ARTIFACTS/packager$EXE_SUFFIX
          echo "::endgroup::"

          echo "::group::Copy mpd_generator"
          cp mpd_generator${{ matrix.exe_ext }} $ARTIFACTS/mpd_generator$EXE_SUFFIX
          echo "::endgroup::"

          # The pssh-box bundle is OS and architecture independent.  So only do
          # it on this one OS and architecture, and give it a more generic
          # filename.
          if [[ '${{ matrix.os_name }}' == 'linux' && '${{ matrix.target_arch }}' == 'x64' ]]; then
            echo "::group::Tar pssh-box"
            tar -czf $ARTIFACTS/pssh-box.py.tar.gz pssh-box.py pssh-box-protos
            echo "::endgroup::"
          fi

      - name: Upload static release build artifacts
        uses: actions/upload-artifact@v4
        if: matrix.build_type == 'Release' && matrix.lib_type == 'static'
        with:
          name: artifacts-${{ matrix.os_name }}-${{ matrix.target_arch }}
          path: artifacts/*
          if-no-files-found: error
          retention-days: 5

      - name: Debug
        uses: mxschmitt/action-tmate@v3.17
        with:
          limit-access-to-actor: true
        if: failure() && inputs.debug
