name: CI Build and Test

on:
  pull_request:
    branches:
      - main
      - 'feature/**'
      - 'release/**'
  merge_group:
  workflow_dispatch:

# Cancel in-progress run when a pull request is updated
# Code taken from:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-using-a-fallback-value
concurrency:
  group: ${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

env:
  CARGO_TERM_COLOR: always
  NODE_VERSION: "18.17.1"
  PYTHON_VERSION: "3.11"
  RUST_TOOLCHAIN_VERSION: "1.75"
  RUST_TOOLCHAIN_COMPONENTS: rustfmt clippy

jobs:
  format:
    name: Format
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: "true"
      - name: Setup rust toolchain
        uses: ./.github/actions/toolchains/rust
        with:
          toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }}
          components: ${{ env.RUST_TOOLCHAIN_COMPONENTS }}
      - name: Check Formatting
        run: cargo fmt --all -- --check

  clippy:
    name: Clippy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: "true"
      - name: Setup rust toolchain
        uses: ./.github/actions/toolchains/rust
        with:
          toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }}
          components: ${{ env.RUST_TOOLCHAIN_COMPONENTS }}
      - uses: Swatinem/rust-cache@v2
      - name: Clippy Lints
        run: cargo clippy --all-targets --all-features -- -D warnings

  web-check:
    name: Check web files
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: "true"
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
      - name: npm install
        run: npm install
      - name: npm check
        run: npm run check

  build:
    name: Build and test
    strategy:
      matrix:
        os: [windows-latest, ubuntu-latest, macos-14]
        is_pr:
          - ${{ github.event_name == 'pull_request' }}
        exclude:
          - os: macos-14
            is_pr: true

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

    steps:
      - uses: actions/checkout@v3
        with:
          submodules: "true"
      - name: Setup rust toolchain
        uses: ./.github/actions/toolchains/rust
        with:
          toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }}
          components: ${{ env.RUST_TOOLCHAIN_COMPONENTS }}
      - name: Add additional Rust targets
        run: |
          rustup target add x86_64-apple-darwin
        if: matrix.os == 'macos-14'
      - uses: actions/setup-python@v4
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
      - uses: Swatinem/rust-cache@v2
      - name: Prereqs
        run: python ./prereqs.py --install
      - name: Build and Test
        run: python ./build.py --no-check

  integration-tests:
    name: Integration tests
    timeout-minutes: 15
    strategy:
      matrix:
        os: [windows-latest, ubuntu-latest, macos-14]
        is_pr:
          - ${{ github.event_name == 'pull_request' }}
        exclude:
          - os: macos-14
            is_pr: true

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

    steps:
      - uses: actions/checkout@v3
        with:
          submodules: "true"
      - name: Setup rust toolchain
        uses: ./.github/actions/toolchains/rust
        with:
          toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }}
          components: ${{ env.RUST_TOOLCHAIN_COMPONENTS }}
      - name: Add additional Rust targets
        run: |
          rustup target add x86_64-apple-darwin
        if: matrix.os == 'macos-14'
      - uses: actions/setup-python@v4
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
      - uses: Swatinem/rust-cache@v2
      - name: Prereqs
        run: python ./prereqs.py --install
      - name: Run integration tests
        run: python ./build.py --no-check --no-test --wasm --npm --vscode --pip --widgets --integration-tests

  runBenchmark:
    name: run benchmark
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v3
      - uses: boa-dev/criterion-compare-action@v3
        with:
          branchName: ${{ github.base_ref }}
          cwd: "compiler/qsc"
        if: ${{ github.base_ref != null }}

  runMemoryProfile:
    name: run memory profile
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v2
        with:
          ref: main
      - uses: Swatinem/rust-cache@v2
      - run: |
          MAIN_MEASUREMENT=$(cargo run --bin memtest)
          echo "MAIN_MEASUREMENT<<EOF" >> $GITHUB_ENV
          echo "$MAIN_MEASUREMENT" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV
      - run: |
          echo "${{env.MAIN_MEASUREMENT}}"
          echo $MAIN_MEASUREMENT

      - uses: actions/checkout@v2
      - run: |
          BRANCH_MEASUREMENT=$(cargo run --bin memtest)
          echo "BRANCH_MEASUREMENT<<EOF" >> $GITHUB_ENV
          echo "$BRANCH_MEASUREMENT" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV
      - run: |
          echo "${{env.BRANCH_MEASUREMENT}}"
          echo $BRANCH_MEASUREMENT
      - uses: actions/github-script@v6
        with:
          script: |
            if (${{ env.BRANCH_MEASUREMENT }} !== ${{ env.MAIN_MEASUREMENT }}) {
              try {
                await github.rest.issues.createComment({
                  issue_number: context.issue.number,
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  body: `_Change in memory usage detected by benchmark._
            ## Memory Report for ${{ github.sha }}

            | Test                        | This Branch | On Main  |
            |-----------------------------|-------------|----------|
            | compile core + standard lib | ${{ env.BRANCH_MEASUREMENT }} bytes | ${{ env.MAIN_MEASUREMENT }} bytes |`
                })
              } catch (err) {
                core.warning(`Failed writing comment on GitHub issue: ${err}`)
              }
            } else {
              console.log("no change in memory usage detected by benchmark");
            }
        if: ${{ github.base_ref != null }}

  status-check:
    name: Status Check
    needs: [format, clippy, web-check, build, integration-tests, runBenchmark, runMemoryProfile]
    runs-on: ubuntu-latest
    if: failure()
    steps:
      - run: |
          echo "::error Build failed"
          exit 1
