name: Build & Test

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: "-D warnings"
  CROSS_DEBUG: 1

on:
  workflow_call:
    inputs:
      ref:
        default: ${{ github.ref }}
        type: string
      run-tests:
        default: true
        type: boolean
  workflow_dispatch:
    inputs:
      run-tests:
        description: Run tests
        default: true
        type: boolean
      rust-test-threads:
        description: Number of Rust test threads
        default: ""
        type: string

jobs:
  build:
    name: ${{ matrix.platform }} (${{ matrix.target }}) (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        platform:
        - linux-arm64       #
        - linux-arm         #
        - linux-armhf       #
        - linux-armv5te     #
        - linux-armv7l      #
        - linux-x64         #
        - linux-x86         #
        - linux-i586        #
        - linux-mips        #
        - linux-mips64      #
        - linux-mipsel      #
        - linux-mips64el    #
        - linux-powerpc     #
        - linux-powerpc64   #
        - linux-powerpc64el #
        - linux-riscv64gc   #
        - linux-s390x       #
        - linux-sparc64     #
        - linux-thumbv7neon #
        - windows-arm64     #
        - windows-x64       # <-- No C library build - requires an additional adapted Makefile for `cl.exe` compiler
        - windows-x86       #     -- // --
        - macos-arm64       # <-- MacOS M1/M2 - no tests, only CLI build to be published on release artifacts
        - macos-x64         #

        include:
        # When adding a new `target`:
        # 1. Define a new platform alias above
        # 2. Add a new record to a matrix map in `cli/npm/install.js`
        - { platform: linux-arm64       , target: aarch64-unknown-linux-gnu           , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-arm         , target: arm-unknown-linux-gnueabi           , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-armhf       , target: arm-unknown-linux-gnueabihf         , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-armv5te     , target: armv5te-unknown-linux-gnueabi       , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-armv7l      , target: armv7-unknown-linux-gnueabihf       , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-x64         , target: x86_64-unknown-linux-gnu            , os: ubuntu-20.04                     } #2272
        - { platform: linux-x86         , target: i686-unknown-linux-gnu              , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-i586        , target: i586-unknown-linux-gnu              , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-mips        , target: mips-unknown-linux-gnu              , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-mips64      , target: mips64-unknown-linux-gnuabi64       , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-mipsel      , target: mipsel-unknown-linux-gnu            , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-mips64el    , target: mips64el-unknown-linux-gnuabi64     , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-powerpc     , target: powerpc-unknown-linux-gnu           , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-powerpc64   , target: powerpc64-unknown-linux-gnu         , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-powerpc64el , target: powerpc64le-unknown-linux-gnu       , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-riscv64gc   , target: riscv64gc-unknown-linux-gnu         , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-s390x       , target: s390x-unknown-linux-gnu             , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-sparc64     , target: sparc64-unknown-linux-gnu           , os: ubuntu-latest  , use-cross: true }
        - { platform: linux-thumbv7neon , target: thumbv7neon-unknown-linux-gnueabihf , os: ubuntu-latest  , use-cross: true }
        - { platform: windows-arm64     , target: aarch64-pc-windows-msvc             , os: windows-latest                   }
        - { platform: windows-x64       , target: x86_64-pc-windows-msvc              , os: windows-latest                   }
        - { platform: windows-x86       , target: i686-pc-windows-msvc                , os: windows-latest                   }
        - { platform: macos-arm64       , target: aarch64-apple-darwin                , os: macos-latest                     }
        - { platform: macos-x64         , target: x86_64-apple-darwin                 , os: macos-latest                     }

        # Cross compilers for C library
        - { platform: linux-arm64       , cc: aarch64-linux-gnu-gcc             , ar: aarch64-linux-gnu-ar           }
        - { platform: linux-arm         , cc: arm-linux-gnueabi-gcc             , ar: arm-linux-gnueabi-ar           }
        - { platform: linux-armhf       , cc: arm-unknown-linux-gnueabihf-gcc   , ar: arm-unknown-linux-gnueabihf-ar }
        - { platform: linux-armv5te     , cc: arm-linux-gnueabi-gcc             , ar: arm-linux-gnueabi-ar           }
        - { platform: linux-armv7l      , cc: arm-linux-gnueabihf-gcc           , ar: arm-linux-gnueabihf-ar         }
        - { platform: linux-x86         , cc: i686-linux-gnu-gcc                , ar: i686-linux-gnu-ar              }
        - { platform: linux-i586        , cc: i686-linux-gnu-gcc                , ar: i686-linux-gnu-ar              }
        - { platform: linux-mips        , cc: mips-linux-gnu-gcc                , ar: mips-linux-gnu-ar              }
        - { platform: linux-mips64      , cc: mips64-linux-gnuabi64-gcc         , ar: mips64-linux-gnuabi64-ar       }
        - { platform: linux-mipsel      , cc: mipsel-linux-gnu-gcc              , ar: mipsel-linux-gnu-ar            }
        - { platform: linux-mips64el    , cc: mips64el-linux-gnuabi64-gcc       , ar: mips64el-linux-gnuabi64-ar     }
        - { platform: linux-powerpc     , cc: powerpc-linux-gnu-gcc             , ar: powerpc-linux-gnu-ar           }
        - { platform: linux-powerpc64   , cc: powerpc64-linux-gnu-gcc           , ar: powerpc64-linux-gnu-ar         }
        - { platform: linux-powerpc64el , cc: powerpc64le-linux-gnu-gcc         , ar: powerpc64le-linux-gnu-ar       }
        - { platform: linux-riscv64gc   , cc: riscv64-linux-gnu-gcc             , ar: riscv64-linux-gnu-ar           }
        - { platform: linux-s390x       , cc: s390x-linux-gnu-gcc               , ar: s390x-linux-gnu-ar             }
        - { platform: linux-sparc64     , cc: sparc64-linux-gnu-gcc             , ar: sparc64-linux-gnu-ar           }
        - { platform: linux-thumbv7neon , cc: arm-linux-gnueabihf-gcc           , ar: arm-linux-gnueabihf-ar         }

        # Rust toolchains
        - { platform: linux-mips        , rust-toolchain: 1.71.1 }
        - { platform: linux-mips64      , rust-toolchain: 1.71.1 }
        - { platform: linux-mipsel      , rust-toolchain: 1.71.1 }
        - { platform: linux-mips64el    , rust-toolchain: 1.71.1 }

        # See #2041 tree-sitter issue
        - { platform: windows-x64   , rust-test-threads: 1 }
        - { platform: windows-x86   , rust-test-threads: 1 }

        # CLI only build
        - { platform: windows-arm64 , cli-only: true }
        - { platform: macos-arm64   , cli-only: true }

    env:
      BUILD_CMD: cargo
      EMSCRIPTEN_VERSION: ""
      EXE: ${{ contains(matrix.target, 'windows') && '.exe' || '' }}

    defaults:
      run:
        shell: bash

    steps:
    - name: Checkout source code
      uses: actions/checkout@v3
      with:
        ref: ${{ inputs.ref }}

    - name: Read Emscripten version
      run: |
        echo "EMSCRIPTEN_VERSION=$(cat cli/emscripten-version)" >> $GITHUB_ENV

    - name: Install Emscripten
      if: ${{ !matrix.cli-only && !matrix.use-cross }}
      uses: mymindstorm/setup-emsdk@v12
      with:
        version: ${{ env.EMSCRIPTEN_VERSION }}

    - name: Install Rust toolchain
      uses: dtolnay/rust-toolchain@stable
      with:
        targets: ${{ matrix.target }}
        toolchain: ${{ matrix.rust-toolchain || 'stable' }}

    - name: Install cross
      if: ${{ matrix.use-cross }}
      uses: taiki-e/install-action@v2
      with:
        tool: cross

    - name: Build custom cross image
      if: ${{ matrix.use-cross && matrix.os == 'ubuntu-latest' }}
      run: |
        cd ..

        target="${{ matrix.target }}"
        image=ghcr.io/cross-rs/$target:custom
        echo "CROSS_IMAGE=$image"                              >> $GITHUB_ENV

        echo "[target.$target]"                                >> Cross.toml
        echo "image = \"$image\""                              >> Cross.toml
        echo "CROSS_CONFIG=$PWD/Cross.toml"                    >> $GITHUB_ENV

        echo "FROM ghcr.io/cross-rs/$target:edge"              >> Dockerfile
        echo "ENV DEBIAN_FRONTEND=noninteractive"              >> Dockerfile
        echo "RUN apt-get update && apt-get install -y nodejs" >> Dockerfile
        docker build -t $image .

    - name: Setup env extras
      env:
        RUST_TEST_THREADS: ${{ matrix.rust-test-threads || inputs.rust-test-threads || '' }}
        USE_CROSS: ${{ matrix.use-cross }}
        TARGET: ${{ matrix.target }}
        CC: ${{ matrix.cc }}
        AR: ${{ matrix.ar }}
        IS_WINDOWS: ${{ contains(matrix.os, 'windows') }}
      run: |
        PATH="$PWD/.github/scripts:$PATH"
        echo "$PWD/.github/scripts" >> $GITHUB_PATH

        echo "TREE_SITTER=tree-sitter.sh" >> $GITHUB_ENV
        echo "TARGET=$TARGET" >> $GITHUB_ENV
        echo "ROOT=$PWD" >> $GITHUB_ENV

        [ -n "$RUST_TEST_THREADS" ] && \
        echo "RUST_TEST_THREADS=$RUST_TEST_THREADS" >> $GITHUB_ENV

        [ -n "$CC" ] && echo "CC=$CC" >> $GITHUB_ENV
        [ -n "$AR" ] && echo "AR=$AR" >> $GITHUB_ENV

        [ "$IS_WINDOWS" = "false" ] && echo "CFLAGS=-Werror" >> $GITHUB_ENV

        if [ "$USE_CROSS" == "true" ]; then
          echo "BUILD_CMD=cross" >> $GITHUB_ENV
          runner=$(BUILD_CMD=cross cross.sh bash -c "env | sed -nr '/^CARGO_TARGET_.*_RUNNER=/s///p'")
          [ -n "$runner" ] && echo "CROSS_RUNNER=$runner" >> $GITHUB_ENV
        fi

    - name: Build C library
      if: ${{ !contains(matrix.os, 'windows') }} # Requires an additional adapted Makefile for `cl.exe` compiler
      run: make.sh -j

    - name: Build wasm library
      if: ${{ !matrix.cli-only && !matrix.use-cross }} # No sense to build on the same Github runner hosts many times
      run: script/build-wasm

    - name: Build CLI
      run: $BUILD_CMD build --release --target=${{ matrix.target }}

    - name: Info about CLI
      if: ${{ startsWith(matrix.platform, 'linux') }}
      run: |
        min_glibc=$(objdump -p target/$TARGET/release/tree-sitter${{ env.EXE }} | sed -nr 's/.*(GLIBC_.+).*/\1/p' | sort -uV | tail -n1)
        echo "🔗 Minimal **glibc** version required for CLI: ${min_glibc}">> $GITHUB_STEP_SUMMARY

    - name: Fetch fixtures
      if: ${{ inputs.run-tests && !matrix.cli-only }} # Don't fetch fixtures for only CLI building targets
      run: script/fetch-fixtures

    - name: Generate fixtures
      if: ${{ inputs.run-tests && !matrix.cli-only }} # Can't natively run CLI on Github runner's host
      run: script/generate-fixtures

    - name: Generate WASM fixtures
      if: ${{ inputs.run-tests && !matrix.cli-only && !matrix.use-cross }} # See comment for the "Build wasm library" step
      run: script/generate-fixtures-wasm

    - name: Run main tests
      if: ${{ inputs.run-tests && !matrix.cli-only }} # Can't natively run CLI on Github runner's host
      run: $BUILD_CMD test --target=${{ matrix.target }}

    - name: Run wasm tests
      if: ${{ inputs.run-tests && !matrix.cli-only && !matrix.use-cross }} # See comment for the "Build wasm library" step
      run: script/test-wasm

    - name: Run benchmarks
      if: ${{ inputs.run-tests && !matrix.cli-only && !matrix.use-cross }} # Cross-compiled benchmarks make no sense
      run: $BUILD_CMD bench benchmark -p tree-sitter-cli --target=${{ matrix.target }}

    - name: Upload CLI artifact
      uses: actions/upload-artifact@v3
      with:
        name: tree-sitter.${{ matrix.platform }}
        path: target/${{ matrix.target }}/release/tree-sitter${{ env.EXE }}
        if-no-files-found: error
        retention-days: 7

    - name: Upload WASM artifacts
      if: ${{ matrix.platform == 'linux-x64' }}
      uses: actions/upload-artifact@v3
      with:
        name: tree-sitter.wasm
        path: |
          lib/binding_web/tree-sitter.js
          lib/binding_web/tree-sitter.wasm
        if-no-files-found: error
        retention-days: 7
