name: Windows CI

on: [push, pull_request, workflow_dispatch]

permissions: {}

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}

env:
  SPEC_SPLIT_DOTS: 160
  CI_LLVM_VERSION: "20.1.7"
  CI_LLVM_TARGETS: "X86,AArch64"
  CI_LLVM_LDFLAGS: "psapi.lib shell32.lib ole32.lib uuid.lib advapi32.lib ws2_32.lib ntdll.lib"

jobs:
  x86_64-windows-libs:
    runs-on: windows-2025
    steps:
      - name: Disable CRLF line ending substitution
        run: |
          git config --global core.autocrlf false

      - name: Enable Developer Command Prompt
        uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0

      - name: Download Crystal source
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Cache libraries
        id: cache-libs
        uses: actions/cache@v5
        with:
          path: | # openssl and llvm take much longer to build so they are cached separately
            libs/pcre2-8.lib
            libs/iconv.lib
            libs/gc.lib
            libs/ffi.lib
            libs/z.lib
            libs/mpir.lib
            libs/yaml.lib
            libs/xml2.lib
            libs/libxml_VERSION
          key: win-libs-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc
      - name: Set up Cygwin
        if: steps.cache-libs.outputs.cache-hit != 'true'
        uses: cygwin/cygwin-install-action@711d29f3da23c9f4a1798e369a6f01198c13b11a # v6
        with:
          packages: make
          install-dir: C:\cygwin64
          add-to-path: false
      - name: Build libgc
        if: steps.cache-libs.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.12 -AtomicOpsVersion 7.10.0
      - name: Build libpcre2
        if: steps.cache-libs.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.47
      - name: Build libiconv
        if: steps.cache-libs.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Version 1.19
      - name: Build libffi
        if: steps.cache-libs.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.5.1
      - name: Build zlib
        if: steps.cache-libs.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.3.1
      - name: Build mpir
        if: steps.cache-libs.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-mpir.ps1 -BuildTree deps\mpir
      - name: Build libyaml
        if: steps.cache-libs.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-yaml.ps1 -BuildTree deps\yaml -Version 0.2.5
      - name: Build libxml2
        if: steps.cache-libs.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-xml2.ps1 -BuildTree deps\xml2 -Version 2.15.3

      - name: Cache OpenSSL
        id: cache-openssl
        uses: actions/cache@v5
        with:
          path: |
            libs/crypto.lib
            libs/ssl.lib
            libs/openssl_VERSION
          key: win-openssl-libs-3.4.1-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc
      - name: Set up NASM
        if: steps.cache-openssl.outputs.cache-hit != 'true'
        uses: ilammy/setup-nasm@72793074d3c8cdda771dba85f6deafe00623038b # v1.5.2
      - name: Build OpenSSL
        if: steps.cache-openssl.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-openssl.ps1 -BuildTree deps\openssl -Version 3.5.0

  x86_64-windows-dlls:
    runs-on: windows-2025
    steps:
      - name: Disable CRLF line ending substitution
        run: |
          git config --global core.autocrlf false

      - name: Enable Developer Command Prompt
        uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0

      - name: Download Crystal source
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Cache libraries
        id: cache-dlls
        uses: actions/cache@v5
        with:
          path: | # openssl and llvm take much longer to build so they are cached separately
            libs/pcre2-8-dynamic.lib
            libs/iconv-dynamic.lib
            libs/gc-dynamic.lib
            libs/ffi-dynamic.lib
            libs/z-dynamic.lib
            libs/mpir-dynamic.lib
            libs/yaml-dynamic.lib
            libs/xml2-dynamic.lib
            dlls/pcre2-8.dll
            dlls/iconv-2.dll
            dlls/gc.dll
            dlls/libffi.dll
            dlls/zlib1.dll
            dlls/mpir.dll
            dlls/yaml.dll
            dlls/libxml2.dll
          key: win-dlls-${{ hashFiles('.github/workflows/win.yml', 'etc/win-ci/*.ps1') }}-msvc
      - name: Set up Cygwin
        if: steps.cache-dlls.outputs.cache-hit != 'true'
        uses: cygwin/cygwin-install-action@711d29f3da23c9f4a1798e369a6f01198c13b11a # v6
        with:
          packages: make
          install-dir: C:\cygwin64
          add-to-path: false
      - name: Build libgc
        if: steps.cache-dlls.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-gc.ps1 -BuildTree deps\gc -Version 8.2.12 -AtomicOpsVersion 7.10.0 -Dynamic
      - name: Build libpcre2
        if: steps.cache-dlls.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.47 -Dynamic
      - name: Build libiconv
        if: steps.cache-dlls.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Version 1.19 -Dynamic
      - name: Build libffi
        if: steps.cache-dlls.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.4.7 -Dynamic
      - name: Build zlib
        if: steps.cache-dlls.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.3.1 -Dynamic
      - name: Build mpir
        if: steps.cache-dlls.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-mpir.ps1 -BuildTree deps\mpir -Dynamic
      - name: Build libyaml
        if: steps.cache-dlls.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-yaml.ps1 -BuildTree deps\yaml -Version 0.2.5 -Dynamic
      - name: Build libxml2
        if: steps.cache-dlls.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-xml2.ps1 -BuildTree deps\xml2 -Version 2.15.3 -Dynamic

      - name: Cache OpenSSL
        id: cache-openssl-dlls
        uses: actions/cache@v5
        with:
          path: |
            libs/crypto-dynamic.lib
            libs/ssl-dynamic.lib
            dlls/libcrypto-3-x64.dll
            dlls/libssl-3-x64.dll
          key: win-openssl-dlls-3.4.1-${{ hashFiles('etc/win-ci/build-openssl.ps1') }}-msvc
      - name: Set up NASM
        if: steps.cache-openssl-dlls.outputs.cache-hit != 'true'
        uses: ilammy/setup-nasm@72793074d3c8cdda771dba85f6deafe00623038b # v1.5.2
      - name: Build OpenSSL
        if: steps.cache-openssl-dlls.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-openssl.ps1 -BuildTree deps\openssl -Version 3.4.1 -Dynamic

  x86_64-windows-llvm-dlls:
    runs-on: windows-2025
    steps:
      - name: Disable CRLF line ending substitution
        run: |
          git config --global core.autocrlf false

      - name: Enable Developer Command Prompt
        uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0

      - name: Download Crystal source
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Cache LLVM
        id: cache-llvm-dlls
        uses: actions/cache@v5
        with:
          path: |
            libs/llvm_VERSION
            libs/llvm-dynamic.lib
            dlls/LLVM-C.dll
          key: llvm-dlls-${{ env.CI_LLVM_VERSION }}-${{ hashFiles('etc/win-ci/build-llvm.ps1') }}-msvc
      - name: Build LLVM
        if: steps.cache-llvm-dlls.outputs.cache-hit != 'true'
        run: .\etc\win-ci\build-llvm.ps1 -BuildTree deps\llvm -Version ${{ env.CI_LLVM_VERSION }} -TargetsToBuild ${{ env.CI_LLVM_TARGETS }} -Dynamic

  x86_64-windows-release:
    needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm-dlls]
    uses: ./.github/workflows/win_build_portable.yml
    with:
      release: true
      llvm_version: "20.1.7"
      llvm_targets: "X86,AArch64"
      llvm_ldflags: "psapi.lib shell32.lib ole32.lib uuid.lib advapi32.lib ws2_32.lib ntdll.lib"

  x86_64-windows-test:
    runs-on: windows-2025
    needs: [x86_64-windows-release]
    steps:
      - name: Disable CRLF line ending substitution
        run: |
          git config --global core.autocrlf false

      - name: Enable Developer Command Prompt
        uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0

      - name: Download Crystal source
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Download Crystal executable
        uses: actions/download-artifact@v8
        with:
          name: crystal
          path: build

      - name: Set up environment
        run: |
          Add-Content $env:GITHUB_PATH "$(pwd)\build"
          Add-Content $env:GITHUB_ENV "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\build\crystal.exe"
          Add-Content $env:GITHUB_ENV "LLVM_VERSION=${{ env.CI_LLVM_VERSION }}"
          Add-Content $env:GITHUB_ENV "LLVM_TARGETS=${{ env.CI_LLVM_TARGETS }}"
          Add-Content $env:GITHUB_ENV "LLVM_LDFLAGS=${{ env.CI_LLVM_LDFLAGS }}"

      - name: Run stdlib specs
        run: make -f Makefile.win std_spec

      - name: Run compiler specs
        run: make -f Makefile.win compiler_spec

      - name: Run interpreter specs
        run: make -f Makefile.win interpreter_spec

      - name: Run primitives specs
        run: make -f Makefile.win -o .build\crystal.exe primitives_spec # we know the compiler is fresh; do not rebuild it here

      - name: Build samples
        run: make -f Makefile.win samples

  test-std:
    runs-on: windows-2025
    needs: [x86_64-windows-release]
    strategy:
      fail-fast: false
      matrix:
        opts:
          - flags: "-Dexecution_context -Dpreview_mt" # execution_context: concurrent
            workers: 1
          - flags: "-Dexecution_context -Dpreview_mt" # execution_context: parallel
            workers: 4
    steps:
      - name: Disable CRLF line ending substitution
        run: git config --global core.autocrlf false

      - name: Enable Developer Command Prompt
        uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0

      - name: Download Crystal source
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Download Crystal executable
        uses: actions/download-artifact@v8
        with:
          name: crystal
          path: build

      - name: Set up environment
        run: |
          Add-Content $env:GITHUB_PATH "$(pwd)\build"
          Add-Content $env:GITHUB_ENV "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\build\crystal.exe"
          Add-Content $env:GITHUB_ENV "LLVM_VERSION=${{ env.CI_LLVM_VERSION }}"
          Add-Content $env:GITHUB_ENV "LLVM_TARGETS=${{ env.CI_LLVM_TARGETS }}"
          Add-Content $env:GITHUB_ENV "LLVM_LDFLAGS=${{ env.CI_LLVM_LDFLAGS }}"

      - name: Test
        run: make -f Makefile.win std_spec FLAGS="${{ matrix.opts.flags }}"
        env:
          CRYSTAL_WORKERS: ${{ matrix.opts.workers || 1 }}

  x86_64-windows-test-interpreter:
    runs-on: windows-2025
    needs: [x86_64-windows-release]
    steps:
      - name: Disable CRLF line ending substitution
        run: |
          git config --global core.autocrlf false

      - name: Download Crystal source
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Download Crystal executable
        uses: actions/download-artifact@v8
        with:
          name: crystal
          path: build

      - name: Set up environment
        run: |
          Add-Content $env:GITHUB_PATH "$(pwd)\build"
          Add-Content $env:GITHUB_ENV "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\build\crystal.exe"
          Add-Content $env:GITHUB_ENV "LLVM_VERSION=${{ env.CI_LLVM_VERSION }}"
          Add-Content $env:GITHUB_ENV "LLVM_TARGETS=${{ env.CI_LLVM_TARGETS }}"
          Add-Content $env:GITHUB_ENV "LLVM_LDFLAGS=${{ env.CI_LLVM_LDFLAGS }}"

      - name: Run stdlib specs with interpreter
        run: bin\crystal i spec\std_spec.cr

      - name: Run primitives specs with interpreter
        run: bin\crystal i spec\primitives_spec.cr

  x86_64-windows-installer:
    if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/'))
    runs-on: windows-2025
    needs: [x86_64-windows-release]
    steps:
      - name: Disable CRLF line ending substitution
        run: |
          git config --global core.autocrlf false

      - name: Download Crystal source
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Download Crystal executable
        uses: actions/download-artifact@v8
        with:
          name: crystal
          path: etc/win-ci/portable

      - name: Set up Inno Setup
        run: |
          Invoke-WebRequest -Uri https://jrsoftware.org/download.php/is.exe -OutFile C:\is.exe
          C:\is.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART

      - name: Set up environment
        run: |
          Add-Content $env:GITHUB_PATH "$(pwd)\etc\win-ci\portable"
          Add-Content $env:GITHUB_ENV "LLVM_VERSION=${{ env.CI_LLVM_VERSION }}"
          Add-Content $env:GITHUB_ENV "LLVM_TARGETS=${{ env.CI_LLVM_TARGETS }}"
          Add-Content $env:GITHUB_ENV "LLVM_LDFLAGS=${{ env.CI_LLVM_LDFLAGS }}"

      - name: Build docs
        run: make -f Makefile.win install_docs prefix=etc\win-ci\portable

      - name: Build installer
        working-directory: ./etc/win-ci
        run: |
          & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" crystal.iss

      - name: Upload Crystal installer
        uses: actions/upload-artifact@v7
        with:
          name: crystal-installer
          path: etc/win-ci/Output/crystal-setup.exe
