name: Build with MSVC

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

on:
  push:
    branches:
      - '4.**'
      - '5.**'
      - 'trunk'
  pull_request:

# env:
  # Fully print commands executed by Make
  # MAKEFLAGS: V=1

defaults:
  run:
    shell: bash -eo pipefail -o igncr {0}

jobs:
  config:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.matrix.outputs.result}}
      skip-testsuite: ${{ steps.skip.outputs.result }}
    steps:
      - name: Compute matrix for the "build" job
        id: matrix
        uses: actions/github-script@v7
        with:
          script: |
            // # Always test cl and clang-cl
            let compilers = ['cl', 'clang-cl'];
            // # Also test i686 MSVC
            let include = [
              {cc: 'cl', arch: 'i686'}];
            // # If this is a pull request, see if the PR has the
            // # 'CI: Full matrix' label. This is done using an API request,
            // # rather than from context.payload.pull_request.labels, since we
            // # want the _current_ list of labels. This allows the labelling to
            // # be changed, and then forcing a re-run of the workflow, rather
            // # than having labelling triggering a fresh workflow event (which
            // # is wasteful).
            if (context.payload.pull_request) {
              const { data: labels } =
                await github.rest.issues.listLabelsOnIssue({...context.repo, issue_number: context.payload.pull_request.number});
              if (labels.some(label => label.name === 'CI: Full matrix')) {
                console.log('Full matrix requested');
                // # Test Cygwin as well
                compilers.push('gcc');
                // # Test bytecode-only Cygwin
                include.push({cc: 'gcc', arch: 'x86_64', config_arg: '--disable-native-compiler'});
              }
            }
            return {config_arg: [''], arch: ['x86_64'], cc: compilers, include: include};
      - name: Determine if the testsuite should be skipped
        id: skip
        uses: actions/github-script@v7
        with:
          script: |
            let skip_testsuite = false;
            if (context.payload.pull_request) {
              const { data: labels } =
                await github.rest.issues.listLabelsOnIssue({...context.repo, issue_number: context.payload.pull_request.number});
              skip_testsuite = labels.some(label => label.name === 'CI: Skip testsuite');
            }
            console.log('Skip testsuite: ' + skip_testsuite);
            return skip_testsuite;

  build:
    permissions: {}

    runs-on: windows-latest

    needs: config

    timeout-minutes: ${{ matrix.cc == 'gcc' && 90 || 60 }}

    name: ${{ matrix.cc == 'cl' && 'MSVC' || matrix.cc == 'gcc' && 'Cygwin' || 'clang-cl' }} ${{ matrix.arch }} ${{ matrix.config_arg != '' && format('({0})', matrix.config_arg) || '' }}

    strategy:
      matrix: ${{ fromJSON(needs.config.outputs.matrix) }}

    steps:

      - name: Fetch OCaml
        uses: actions/checkout@v4
        with:
          submodules: true

      - name: Restore Cygwin cache
        uses: actions/cache/restore@v4
        with:
          path: |
            C:\cygwin-packages
          key: cygwin-packages

      - name: Install Cygwin
        uses: cygwin/cygwin-install-action@v3
        with:
          packages: make,${{ matrix.cc != 'gcc' && 'mingw64-x86_64-' || 'gcc-fortran,' }}gcc-core
          install-dir: 'D:\cygwin'

      - name: Save Cygwin cache
        uses: actions/cache/save@v4
        with:
          path: |
            C:\cygwin-packages
          key: cygwin-packages

      - name: Set up MSVC
        uses: ilammy/msvc-dev-cmd@v1
        with:
          arch: ${{ matrix.arch == 'x86_64' && 'x64' || 'x86' }}
        if: matrix.cc != 'gcc'

      - name: Compute a key to cache configure results
        id: autoconf-cache-key
        env:
          HOST: ${{ format('{0}-pc-{1}', matrix.arch, (matrix.cc == 'gcc' && 'cygwin' || 'windows')) }}
        run: |
          echo "key=${{ env.HOST }}-${{ matrix.cc }}-${{ hashFiles('configure') }}" >> $GITHUB_OUTPUT

      - name: Restore Autoconf cache
        uses: actions/cache/restore@v4
        with:
          path: |
            config.cache
          key: ${{ steps.autoconf-cache-key.outputs.key }}

      - name: Configure tree
        env:
          CONFIG_ARGS: >-
            --cache-file=config.cache
            --prefix "${{ matrix.cc != 'gcc' && '$PROGRAMFILES/Бактріан🐫' || '$(cygpath "$PROGRAMFILES/Бактріан🐫")'}}"
            ${{ matrix.cc != 'gcc' && format('--host={0}-pc-windows', matrix.arch) || '' }}
            ${{ matrix.cc != 'gcc' && format('CC={0}', matrix.cc) || '' }}
            ${{ matrix.config_arg }}
        run: |
          eval $(tools/msvs-promote-path)
          if ! ./configure ${{ env.CONFIG_ARGS }} ; then
            rm -rf config.cache
            failed=0
            ./configure ${{ env.CONFIG_ARGS }} || failed=$?
            if ((failed)) ; then
              echo
              echo "::group::config.log content ($(wc -l config.log) lines)"
              cat config.log
              echo '::endgroup::'
              exit $failed
            fi
          fi

      - name: Save Autoconf cache
        uses: actions/cache/save@v4
        with:
          path: |
            config.cache
          key: ${{ steps.autoconf-cache-key.outputs.key }}

      - name: Build OCaml
        run: |
          eval $(tools/msvs-promote-path)
          make -j || failed=$?
          if ((failed)) ; then make -j1 V=1 ; exit $failed ; fi
          test -e runtime/libcamlrun.lib || tools/check-symbol-names runtime/*.a otherlibs/*/lib*.a
          runtime/ocamlrun ocamlc -config

      - name: Assemble backend with mingw-w64 GASM and compare
        run: |
          x86_64-w64-mingw32-gcc -c -I./runtime  -I ./flexdll -D__USE_MINGW_ANSI_STDIO=0 -DUNICODE -D_UNICODE -DWINDOWS_UNICODE=1 -DCAMLDLLIMPORT= -DIN_CAML_RUNTIME -DNATIVE_CODE -DTARGET_amd64 -DMODEL_default -DSYS_mingw64 -o runtime/amd64.o runtime/amd64.S
          dumpbin /disasm:nobytes runtime/amd64nt.obj > runtime/amd64nt.dump
          awk -f tools/ci/actions/canonicalize-dumpbin.awk runtime/amd64nt.dump runtime/amd64nt.dump > runtime/amd64nt.canonical
          dumpbin /disasm:nobytes runtime/amd64.o > runtime/amd64.dump
          awk -f tools/ci/actions/canonicalize-dumpbin.awk runtime/amd64.dump runtime/amd64.dump > runtime/amd64.canonical
          git diff --no-index -- runtime/amd64*.canonical
          wc -l runtime/amd64*.dump runtime/amd64*.canonical
        # ^ The final wc is there to make sure that the canonical files are
        # reasonable cleaned-up versions of the raw dumpbins and not simply
        # empty
        if: endsWith(matrix.arch, '64') && matrix.cc != 'gcc'

      - name: Run the test suite
        if: ${{ needs.config.outputs.skip-testsuite != 'true' }}
        run: |
          eval $(tools/msvs-promote-path)
          make tests

      - name: Install the compiler
        run: make install
