---
name: Build and test

# README:
#
# The semantics for running shell commands in GitHub actions is non-obvious. Please read
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell
# before modifying this file. Our strategy is to rely on the built-in (unspecified) shell, and
# explicitly set the shell settings we want (with `set -eo pipefail`) at the beginning of any
# bash script. For more information on these settings, see `man bash`.
#
# GitHub Actions files can be difficult to modify with confidence, because testing changes often
# requires pushing to a branch and running CI remotely. To make this process easier, consider
# the following:
#
# 1) Use Visual Studio Code with the GitHub Actions Extension (github.vscode-github-actions).
#    This allows you to check the validity of your action schema and syntax without pushing to a
#    branch.
# 2) Use https://github.com/nektos/act to run your CI steps locally. Note this will only work with
#    steps run on Linux platforms, as `act` is implemented with Docker containers.
#
on:
  push:
    branches: [main]
    paths-ignore:
      - "Docs/**"
      - "**.md"
      - "README.md"
      - "LICENSE"
      - ".gitignore"
      - ".editorconfig"
    tags:
      - v*
  pull_request:
    branches: ["**"]
    paths-ignore:
      - "Docs/**"
      - "**.md"
      - "README.md"
      - "LICENSE"
      - ".gitignore"
      - ".editorconfig"

env:
  spm-build-options: -Xswiftc -enable-testing --explicit-target-dependency-import-check error
  spm-test-options: --parallel
  swift-version: '5.10'

jobs:
  determine-version:
    name: "Determine compiler version from git tag"
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get-tag.outputs.version }}
    steps:
      - id: get-tag
        run: |
          if [[ "$GITHUB_REF" == refs/tags/* ]]; then
            version="${GITHUB_REF#refs/tags/}"
          else
            version=""
          fi
          echo "version=$version" >> "$GITHUB_OUTPUT"

  devcontainer:
    needs: determine-version
    name: Ubuntu dev container/${{ matrix.cmake_build_type }}
    strategy:
      fail-fast: false
      matrix:
        cmake_build_type: [Debug, Release]

        include:
          - spm_configuration: debug
            cmake_build_type: Debug
            more-spm-test-options: --enable-code-coverage
            HYLO_LLVM_BUILD_TYPE: MinSizeRel

          - spm_configuration: release
            cmake_build_type: Release
            HYLO_LLVM_BUILD_TYPE: MinSizeRel

    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: true
          show-progress: false

      - name: Set compiler version
        run: ./Tools/set-hc-version.sh ${{ needs.determine-version.outputs.version }}

      - name: Initialize Dev Container
        uses: devcontainers/ci@v0.3
        with:
          runCmd: uname -a
          push: never

      - name: Build and Test via CMake
        uses: devcontainers/ci@v0.3
        env:
          HYLO_LLVM_BUILD_TYPE: ${{ matrix.HYLO_LLVM_BUILD_TYPE }}
        with:
          runCmd: >-
            cmake -GNinja -S . -B .cmake-build
            -DCMAKE_BUILD_TYPE=${{ matrix.cmake_build_type }}
            -DBUILD_TESTING=YES
            -DLLVM_DIR=/opt/llvm-${{ matrix.HYLO_LLVM_BUILD_TYPE }}/lib/cmake/llvm

            cmake --build .cmake-build

            find .cmake-build -name '*.o' -delete

            find .cmake-build -name '*.a' -delete

            ctest --output-on-failure --parallel --test-dir .cmake-build
          push: never

      - name: Reclaim disk space
        run: rm -rf .cmake-build

      - name: Restore Cache for SPM
        uses: actions/cache@v4
        with:
          path: .build
          key: devcontainer-${{ matrix.spm_configuration }}-spm-${{ hashFiles('**/Package.resolved') }}
          restore-keys: |
            ${{ runner.os }}-spm-

      - name: Build and Test via SPM
        uses: devcontainers/ci@v0.3
        with:
          runCmd: |
            llvm-cov-15 --version
            swift test -c ${{ matrix.spm_configuration }} ${{ env.spm-build-options }} ${{ env.spm-test-options }} ${{ matrix.more-spm-test-options }}
          push: never

      - name: Export Coverage
        uses: devcontainers/ci@v0.3
        if: ${{ contains(matrix.more-spm-test-options, '--enable-code-coverage') }}
        with:
          runCmd: |
            shopt -s nullglob
            dot_os=(.build/${{ matrix.spm_configuration }}/*.build/*.o .build/${{ matrix.spm_configuration }}/*.build/**/*.o)
            bin_params=("${dot_os[0]}")
            for o in "${dot_os[@]:1}"; do
              bin_params+=("-object" "${o}")
            done
            # Note: on mac using llvm-cov from Xcode might require a leading xcrun.
            llvm-cov-15 export -format="lcov" -instr-profile "$(swift test -c ${{ matrix.spm_configuration }} --show-codecov-path | xargs dirname)"/default.profdata "${bin_params[@]}" > info.lcov
          push: never

      - name: Upload coverage reports to Codecov
        if: ${{ contains(matrix.more-spm-test-options, '--enable-code-coverage') }}
        uses: codecov/codecov-action@v4.5.0
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          fail_ci_if_error: true

      - name: Output compiler version
        uses: devcontainers/ci@v0.3
        with:
          runCmd: .build/${{ matrix.spm_configuration }}/hc --version
          push: never

  native:
    needs: determine-version
    name: "Native: ${{ matrix.os }}/${{ matrix.spm_configuration }}/${{ matrix.cmake_generator }}"
    strategy:
      fail-fast: false
      matrix:
        os: [macos-14, ubuntu-latest, windows-latest]
        spm_configuration: [debug, release]
        cmake_generator: [Ninja, Xcode]

        exclude:
          - os: ubuntu-latest
            cmake_generator: Xcode
          - os: windows-latest
            cmake_generator: Xcode

        include:
          - HYLO_LLVM_BUILD_RELEASE: 20250603-162600
          - HYLO_LLVM_BUILD_TYPE: MinSizeRel
          - HYLO_LLVM_DOWNLOAD_URL: https://github.com/hylo-lang/llvm-build/releases/download
          - HYLO_LLVM_VERSION: '17.0.6'
          - llvm_package_suffix: .tar.zst
          - unpackage_command: tar -x --zstd -f
          # https://github.com/apple/swift/issues/72121
          - windows_only: '# WINDOWS ONLY:'
          - use_spm: true
          - triple_cpu: x86_64

          - os: windows-latest
            unpackage_command: 7z x -t7z
            llvm_package_suffix: .7z
            triple_suffix: unknown-windows-msvc17
            windows_only: ''

          - os: windows-latest
            use_spm: false

          - os: macos-14
            triple_suffix: apple-darwin24.1.0
            triple_cpu: arm64

          - os: ubuntu-latest
            triple_suffix: unknown-linux-gnu

          - spm_configuration: debug
            cmake_build_type: Debug

          - spm_configuration: release
            cmake_build_type: Release

          - cmake_generator: Xcode
            use_spm: false

    runs-on: ${{ matrix.os }}
    env:
      llvm_url_prefix: ${{ matrix.HYLO_LLVM_DOWNLOAD_URL }}/${{ matrix.HYLO_LLVM_BUILD_RELEASE }}
      llvm_package_basename: llvm-${{ matrix.HYLO_LLVM_VERSION }}-${{ matrix.triple_cpu }}-${{ matrix.triple_suffix }}-${{ matrix.HYLO_LLVM_BUILD_TYPE }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: true
          show-progress: false
          path: hylo

      - name: Set compiler version
        working-directory: hylo
        run: ./Tools/set-hc-version.sh ${{ needs.determine-version.outputs.version }}
        shell: bash

      - name: Set up swift (non-Windows)
        if: ${{ runner.os != 'Windows' }}
        uses: SwiftyLab/setup-swift@latest
        with:
          swift-version: ${{ env.swift-version }}

      - uses: compnerd/gha-setup-vsdevenv@main
        with:
          winsdk: "10.0.22621.0" # Workaround for this: https://forums.swift.org/t/swiftpm-plugin-doesnt-work-with-the-latest-visual-studio-version/78183/14
          # TL;DR: The Windows SDK had a change in 10.0.26100.0 that the Swift compiler didn't account for.
          # The Swift compiler team is aware of the issue and they are going to release a fix some time.
      - name: Set up swift (Windows)
        if: ${{ runner.os == 'Windows' }}
        uses: compnerd/gha-setup-swift@v0.2.2
        with:
          branch: swift-${{ env.swift-version }}-release
          tag: ${{ env.swift-version }}-RELEASE

      - name: Verify swift version
        run: swift --version && swift --version | grep -q ${{ env.swift-version }}
        shell: bash

      - name: Set up latest CMake and Ninja
        uses: lukka/get-cmake@latest
        with:
          cmakeVersion: latestrc

      - name: Install LLVM
        # 7z doesn't support decompressing from a stream or we'd do this all as one statement. Maybe
        # we should find a way to use zstd on windows.
        run: >-
          curl --no-progress-meter -L -O
          -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}"
          ${{ env.llvm_url_prefix }}/${{ env.llvm_package_basename }}${{ matrix.llvm_package_suffix }}

          ${{ matrix.unpackage_command }} ${{ env.llvm_package_basename }}${{ matrix.llvm_package_suffix }}

      - name: Configure (CMake)
        # We explicitly point to swiftc in the PATH because otherwise CMake picks up the one in XCode.
        run: >-
          cmake -G '${{ matrix.cmake_generator }}' -S . -B .cmake-build
          ${{ matrix.cmake_generator != 'Xcode' && format('-DCMAKE_BUILD_TYPE={0}', matrix.cmake_build_type) || '' }}
          -DBUILD_TESTING=YES
          -DLLVM_DIR=${{ github.workspace }}/${{ env.llvm_package_basename }}/lib/cmake/llvm
          ${{ runner.os == 'macOS' && '-D CMAKE_Swift_COMPILER=swiftc -DCMAKE_OSX_SYSROOT=$(xcrun --show-sdk-path)' || '' }}
        working-directory: hylo

      - name: Build (CMake)
        run: cmake --build hylo/.cmake-build ${{ matrix.cmake_generator == 'Xcode' && format('--config {0}', matrix.cmake_build_type) || '' }}

      - name: Test (CMake)
        run: ctest --output-on-failure --parallel --test-dir hylo/.cmake-build ${{ matrix.cmake_generator == 'Xcode' && format('-C {0}', matrix.cmake_build_type) || '' }}

      - if: ${{ matrix.use_spm }}
        name: Create LLVM pkgconfig file and make it findable
        run: >-
          set -ex -o pipefail

          mkdir pkg-config

          PATH="${{ github.workspace }}/${{ env.llvm_package_basename }}/bin:$PATH"
            hylo/Tools/make-pkgconfig.sh pkg-config/llvm.pc

          echo 'PKG_CONFIG_PATH=${{ github.workspace }}/pkg-config' >> "$GITHUB_ENV"
        shell: bash

      - if: ${{ matrix.use_spm }}
        # Workaround for https://github.com/actions/cache/issues/1541
        uses: actions/cache@v4.2.2
        name: SPM cache setup
        with:
          path: hylo/.build
          key: ${{ matrix.os }}-${{ matrix.spm_configuration }}-spm-${{ hashFiles('hylo/**/Package.resolved') }}
          restore-keys: |
            ${{ matrix.os }}-${{ matrix.spm_configuration }}-spm-

      - if: ${{ matrix.use_spm && runner.os == 'Windows' }}
        name: Build support library
        run: clang -c ./StandardLibrary/Sources/LibC.c -o HyloLibC.lib
        working-directory: hylo

      - if: ${{ matrix.use_spm && runner.os == 'Windows' }}
        name: Build the dependencies of build tools
        run: |
          echo 'SPM_BUILD_TOOL_SUPPORT_NO_REENTRANT_BUILD=1' >> $env:GITHUB_ENV
          swift build ${{ env.spm-build-options }} --target BuildToolDependencies
          # https://github.com/apple/swift/issues/72121
          if (-not $?) {
              swift build ${{ env.spm-build-options }} --target BuildToolDependencies
          }
        working-directory: hylo

      - if: ${{ matrix.use_spm }}
        name: Build
        run: |
          swift build -c ${{ matrix.spm_configuration }} ${{ env.spm-build-options }} --build-tests
          ${{ matrix.windows_only }} if (-not $?) { swift build ${{ env.spm-build-options }} --target BuildToolDependencies }

        working-directory: hylo

      - if: ${{ matrix.use_spm }}
        name: Test
        run: |
          swift test --skip-build -c ${{ matrix.spm_configuration }} ${{ env.spm-build-options }} ${{ env.spm-test-options }}
          ${{ matrix.windows_only }} if (-not $?) { swift test -c ${{ matrix.spm_configuration }} ${{ env.spm-test-options }} }
        working-directory: hylo

      - name: Output compiler version
        run: cmake --build hylo/.cmake-build --target output_compiler_version -- --quiet&