name: CI

on:
  push:
    branches: [ master, dev, ci_testing, experiments, '*.*.x' ]
    paths-ignore:
      - '**.md'
      - 'LICENSE'
      - '.gitignore'
      - 'resources/docs.html'
  pull_request:
    branches: [ master, dev ]
  workflow_dispatch:

env:
  LLVM_RELEASE_VERSION: 22.x
  LLVM_RELEASE_VERSION_LINUX_MUSL: 22
  LLVM_RELEASE_VERSION_OPENBSD: 21
  LLVM_RELEASE_VERSION_NETBSD: 21
  LLVM_DEV_VERSION: 22
  # CACHE_INVALIDATION_SEED: '%Y-%m-%d' # Daily
  CACHE_INVALIDATION_SEED: '%G-%V-1'   # Weekly

jobs:

  # Gate job: checks only the files changed in the LATEST push (not the whole
  # PR diff). This prevents CI from re-running when only docs/notes are updated.
  changes:
    runs-on: ubuntu-24.04
    outputs:
      src: ${{ steps.filter.outputs.src }}
    steps:
      - uses: actions/checkout@v7
      - uses: dorny/paths-filter@v4
        id: filter
        with:
          token: ''
          base: ${{ github.event.before }}
          predicate-quantifier: 'some-with-excludes'
          filters: |
            src:
              - '**'
              - '!**.md'
              - '!LICENSE'
              - '!.gitignore'
              - '!resources/docs.html'

  build-msvc:
    name: Windows (x64) MSVC
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: windows-2025
    timeout-minutes: 30
    defaults:
      run:
        shell: cmd
    steps:
      - uses: actions/checkout@v7

      - name: Get current date context
        id: date
        run: echo "date=$(date +'${{ env.CACHE_INVALIDATION_SEED }}')" >> $GITHUB_OUTPUT
  
      - uses: actions/cache@v6
        with:
          path: |
            build-release/_deps
            build-debug/_deps
          key: ${{ runner.os }}-llvm-${{ env.LLVM_RELEASE_VERSION }}-Release-Debug-${{ steps.date.outputs.date }}-${{ hashFiles('CMakeLists.txt', '.github/workflows/main.yml') }}

      # set up the environment for Ninja
      - uses: TheMrMilchmann/setup-msvc-dev@v4
        with:
          arch: x64

      - name: CMake Build (Release)
        run: |
          cmake -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release -DC3_FETCH_LLVM=ON
          cmake --build build-release --config Release

      - name: Bundle (Windows Release)
        shell: bash
        run: ./scripts/tools/package_build.sh "./build-release/c3c.exe" "c3-windows-Release" "zip"

      - uses: actions/upload-artifact@v7
        with:
          name: c3-windows-Release
          path: c3-windows-Release.zip

      - name: CMake Build (Debug)
        run: |
          cmake -B build-debug -G Ninja -DCMAKE_BUILD_TYPE=Debug -DC3_FETCH_LLVM=ON
          cmake --build build-debug --config Debug

      # We remove the GNU link tool so C3C picks up the MSVC link.exe
      - name: Remove GNU Link
        shell: bash
        run: rm -f /usr/bin/link

      - name: Cache FreeBSD Sysroot
        uses: actions/cache@v6
        with:
          path: .cache
          key: ${{ runner.os }}-freebsd-sysroot-15.0

      - name: Run Unified Tests
        shell: bash
        run: ./scripts/tools/ci_tests.sh "./build-debug/c3c.exe"

      - name: Bundle (Windows Debug)
        shell: bash
        run: ./scripts/tools/package_build.sh "./build-debug/c3c.exe" "c3-windows-Debug" "zip"

      - uses: actions/upload-artifact@v7
        with:
          name: c3-windows-Debug
          path: c3-windows-Debug.zip

      - name: Cache NSIS
        id: cache-nsis
        uses: actions/cache@v6
        with:
          path: C:\Program Files (x86)\NSIS
          key: ${{ runner.os }}-nsis-3.12

      - name: Setup NSIS & Compile Installers
        shell: bash
        run: |
          if [ "${{ steps.cache-nsis.outputs.cache-hit }}" != "true" ]; then
            choco install nsis -y
            curl -L -o EnVar-Plugin.zip "https://github.com/GsNSIS/EnVar/releases/download/v0.3.1/EnVar-Plugin.zip"
            7z x EnVar-Plugin.zip -o"C:\Program Files (x86)\NSIS" -y
          fi
          curl -sSfL -o MANUAL.md https://c3-lang.org/all.md
          curl -sSfL -o c3fmt.exe https://github.com/lmichaudel/c3fmt/releases/latest/download/c3fmt-windows.exe
          export PATH="/c/Program Files (x86)/NSIS/Bin:/c/Program Files (x86)/NSIS:$PATH"
          MSYS_NO_PATHCONV=1 makensis /DINSTALLER_NAME="c3-windows-setup-Release.exe" /DBUILD_DIR=build-release resources/nsis/installer.nsi
          MSYS_NO_PATHCONV=1 makensis /DINSTALLER_NAME="c3-windows-setup-Debug.exe" /DBUILD_DIR=build-debug resources/nsis/installer.nsi

      - uses: actions/upload-artifact@v7
        with:
          name: c3-windows-setup-Release
          path: resources/nsis/c3-windows-setup-Release.exe

      - uses: actions/upload-artifact@v7
        with:
          name: c3-windows-setup-Debug
          path: resources/nsis/c3-windows-setup-Debug.exe

  build-msvc-arm64:
    name: Windows (aarch64) MSVC
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: windows-11-arm
    timeout-minutes: 30
    defaults:
      run:
        shell: cmd
    steps:
      - uses: actions/checkout@v7

      - name: Get current date context
        id: date
        run: echo "date=$(date +'${{ env.CACHE_INVALIDATION_SEED }}')" >> $GITHUB_OUTPUT
  
      - uses: actions/cache@v6
        with:
          path: |
            build-release/_deps
            build-debug/_deps
          key: ${{ runner.os }}-aarch64-llvm-${{ env.LLVM_RELEASE_VERSION }}-Release-Debug-${{ steps.date.outputs.date }}-${{ hashFiles('CMakeLists.txt', '.github/workflows/main.yml') }}
          
      # set up the environment for Ninja
      - uses: TheMrMilchmann/setup-msvc-dev@v4
        with:
          arch: arm64

      - name: CMake Build (Release)
        run: |
          cmake -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release -DC3_FETCH_LLVM=ON
          cmake --build build-release --config Release

      - name: Bundle (Windows Release)
        shell: bash
        run: ./scripts/tools/package_build.sh "./build-release/c3c.exe" "c3-windows-aarch64-Release" "zip"

      - uses: actions/upload-artifact@v7
        with:
          name: c3-windows-aarch64-Release
          path: c3-windows-aarch64-Release.zip

      - name: CMake Build (Debug)
        run: |
          cmake -B build-debug -G Ninja -DCMAKE_BUILD_TYPE=Debug -DC3_FETCH_LLVM=ON
          cmake --build build-debug --config Debug

      # We remove the GNU link tool so C3C picks up the MSVC link.exe
      - name: Remove GNU Link
        shell: bash
        run: rm -f /usr/bin/link

      - name: Cache FreeBSD Sysroot
        uses: actions/cache@v6
        with:
          path: .cache
          key: ${{ runner.os }}-aarch64-freebsd-sysroot-15.0

      - name: Run Unified Tests
        shell: bash
        run: ./scripts/tools/ci_tests.sh "./build-debug/c3c.exe"

      - name: Bundle (Windows Debug)
        shell: bash
        run: ./scripts/tools/package_build.sh "./build-debug/c3c.exe" "c3-windows-aarch64-Debug" "zip"

      - uses: actions/upload-artifact@v7
        with:
          name: c3-windows-aarch64-Debug
          path: c3-windows-aarch64-Debug.zip

      - name: Cache NSIS
        id: cache-nsis
        uses: actions/cache@v6
        with:
          path: C:\Program Files (x86)\NSIS
          key: ${{ runner.os }}-aarch64-nsis-3.12

      - name: Setup NSIS & Compile Installers
        shell: bash
        run: |
          if [ "${{ steps.cache-nsis.outputs.cache-hit }}" != "true" ]; then
            choco install nsis -y
            curl -L -o EnVar-Plugin.zip "https://github.com/GsNSIS/EnVar/releases/download/v0.3.1/EnVar-Plugin.zip"
            7z x EnVar-Plugin.zip -o"C:\Program Files (x86)\NSIS" -y
          fi
          curl -sSfL -o MANUAL.md https://c3-lang.org/all.md
          curl -sSfL -o c3fmt.exe https://github.com/lmichaudel/c3fmt/releases/latest/download/c3fmt-windows-aarch64.exe
          export PATH="/c/Program Files (x86)/NSIS/Bin:/c/Program Files (x86)/NSIS:$PATH"
          MSYS_NO_PATHCONV=1 makensis /DINSTALLER_NAME="c3-windows-setup-aarch64-Release.exe" /DBUILD_DIR=build-release resources/nsis/installer.nsi
          MSYS_NO_PATHCONV=1 makensis /DINSTALLER_NAME="c3-windows-setup-aarch64-Debug.exe" /DBUILD_DIR=build-debug resources/nsis/installer.nsi

      - uses: actions/upload-artifact@v7
        with:
          name: c3-windows-setup-aarch64-Release
          path: resources/nsis/c3-windows-setup-aarch64-Release.exe

      - uses: actions/upload-artifact@v7
        with:
          name: c3-windows-setup-aarch64-Debug
          path: resources/nsis/c3-windows-setup-aarch64-Debug.exe
          
  build-msys2:
    name: Windows (${{ matrix.arch }}) ${{ matrix.sys }}
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: ${{ matrix.runner }}
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        include:
          - { arch: x64, runner: windows-2025, sys: MINGW64, prefix: /mingw64 }
          - { arch: x64, runner: windows-2025, sys: CLANG64, prefix: /clang64 }
          - { arch: aarch64, runner: windows-11-arm, sys: CLANGARM64, prefix: /clangarm64 }
    defaults:
      run:
        shell: msys2 {0}
    steps:
      - uses: actions/checkout@v7
      - uses: msys2/setup-msys2@v2
        with:
          msystem: ${{ matrix.sys }}
          update: true
          cache: true
          pacboy: >-
            cmake:p
            ninja:p
            llvm:p
            lld:p
            clang:p
            toolchain:p
            python:p
            git:p
            zlib:p
            zstd:p
            libxml2:p
      # (MINGW64) Force LLD as the linker to resolve __imp_ symbol thunking issues from
      # static LLVM archives (LNK4217), which the GNU linker (ld) cannot resolve automatically.
      - name: CMake Build
        run: |
          cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_LINKER=lld -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" -DC3_LINK_DYNAMIC=OFF -DLLVM_DIR=${{ matrix.prefix }}/lib/cmake/llvm -DC3_LLD_DIR=${{ matrix.prefix }}/lib/
          cmake --build build
      - name: Cache FreeBSD Sysroot
        uses: actions/cache@v6
        with:
          path: .cache
          key: ${{ runner.os }}-${{ matrix.arch }}-freebsd-sysroot-15.0
      - name: Run Unified Tests
        run: ./scripts/tools/ci_tests.sh "./build/c3c"


  build-linux:
    name: Linux (x64) Ubuntu
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v7
      - name: Get current date context
        id: date
        run: echo "date=$(date +'${{ env.CACHE_INVALIDATION_SEED }}')" >> $GITHUB_OUTPUT
      - name: Cache APT archives
        uses: actions/cache@v6
        with:
          path: apt-cache
          key: apt-cache-${{ runner.os }}-${{ steps.date.outputs.date }}
      - run: |
          mkdir -p apt-cache/partial
          for i in 1 2 3; do sudo apt-get update && break || sleep 2; done
          for i in 1 2 3; do sudo apt-get install -y -o dir::cache::archives="$(pwd)/apt-cache" --fix-missing zlib1g zlib1g-dev python3 ninja-build curl libcurl4-openssl-dev opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf libx11-dev libxrandr-dev libxinerama-dev libxi-dev libxxf86vm-dev libxcursor-dev && break || sleep 2; done
          sudo chown -R $USER:$USER apt-cache

      - name: CMake Build (Release)
        run: |
          cmake -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release -DC3_FETCH_LLVM=ON
          cmake --build build-release

      - name: Bundle & Upload (Linux Release)
        run: |
          bash ./scripts/tools/package_build.sh "./build-release/c3c" "c3-linux-Release" "tar"
      - uses: actions/upload-artifact@v7
        with:
          name: c3-linux-Release
          path: c3-linux-Release.tar.gz

      - name: CMake Build (Debug)
        run: |
          cmake -B build-debug -G Ninja -DCMAKE_BUILD_TYPE=Debug -DC3_FETCH_LLVM=ON
          cmake --build build-debug

      - name: Cache FreeBSD Sysroot
        uses: actions/cache@v6
        with:
          path: .cache
          key: ${{ runner.os }}-freebsd-sysroot-15.0
      - name: Run Unified Tests
        run: ./scripts/tools/ci_tests.sh "./build-debug/c3c"

      - name: Embedded/QEMU Tests
        run: |
          cd resources/examples/embedded/riscv-qemu
          make C3C_PATH=../../../../build-debug/ run

      - name: Bundle & Upload (Linux Debug)
        run: |
          bash ./scripts/tools/package_build.sh "./build-debug/c3c" "c3-linux-Debug" "tar"
      - uses: actions/upload-artifact@v7
        with:
          name: c3-linux-Debug
          path: c3-linux-Debug.tar.gz

  test-emscripten-32bit:
    name: Emscripten
    needs: build-linux
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        build_type: [Debug]
    steps:
      - uses: actions/checkout@v7

      - uses: actions/download-artifact@v8
        with:
          name: c3-linux-${{matrix.build_type}}
          path: bin

      - name: Extract C3
        run: |
          tar -xzf bin/c3-linux-${{matrix.build_type}}.tar.gz -C bin
          # The package_build.sh creates a 'c3' directory inside the tar
          chmod +x bin/c3/c3c

      - name: Setup Emscripten
        run: |
          git clone https://github.com/emscripten-core/emsdk.git
          cd emsdk
          ./emsdk install latest
          ./emsdk activate latest
          echo "$GITHUB_WORKSPACE/emsdk" >> $GITHUB_PATH
          echo "$GITHUB_WORKSPACE/emsdk/upstream/emscripten" >> $GITHUB_PATH

      - name: Run Emscripten Tests
        run: ./scripts/tools/emscripten_tests.sh "./bin/c3/c3c"

  build-linux-musl-static:
    name: Linux (x64) musl Static
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-24.04
    container:
      image: alpine:3.24
    steps:
      - name: Prepare Git
        run: |
          apk add --no-cache git
          git config --global --add safe.directory "$GITHUB_WORKSPACE"

      - uses: actions/checkout@v7

      - name: Install dependencies
        run: |
          apk update
          apk add \
            build-base bash cmake ninja python3 linux-headers \
            zlib-dev zlib-static zstd-dev zstd-static curl-dev curl-static \
            openssl-libs-static brotli-static nghttp2-static libpsl-static \
            libidn2-static libunistring-static nghttp3-static c-ares-dev \
            curl-dev openssl-dev brotli-dev libpsl-dev libidn2-dev \
            libunistring-dev nghttp2-dev nghttp3-dev c-ares-dev

      - name: CMake Build (static musl Release)
        run: |
          cmake -B build-release -G Ninja \
            -DCMAKE_BUILD_TYPE=Release \
            -DC3_FETCH_LLVM=ON \
            -DC3_LINK_DYNAMIC=OFF \
            -DBUILD_SHARED_LIBS=OFF \
            -DCMAKE_EXE_LINKER_FLAGS="-static -static-libstdc++ -static-libgcc"
          cmake --build build-release

      - name: Bundle (Release)
        run: bash ./scripts/tools/package_build.sh "./build-release/c3c" "c3-linux-static-Release" "tar"
      - uses: actions/upload-artifact@v7
        with:
          name: c3-linux-static-Release
          path: c3-linux-static-Release.tar.gz

      - name: CMake Build (static musl Debug)
        run: |
          cmake -B build-debug -G Ninja \
            -DCMAKE_BUILD_TYPE=Debug \
            -DC3_FETCH_LLVM=ON \
            -DC3_LINK_DYNAMIC=OFF \
            -DBUILD_SHARED_LIBS=OFF \
            -DCMAKE_EXE_LINKER_FLAGS="-static -static-libstdc++ -static-libgcc"
          cmake --build build-debug

      - name: Verify Static
        run: |
          ldd build-debug/c3c || true
          file build-debug/c3c
          build-debug/c3c --version

      - name: Run Unified Tests
        run: ./scripts/tools/ci_tests.sh "./build-debug/c3c"

      - name: Bundle (Debug)
        run: bash ./scripts/tools/package_build.sh "./build-debug/c3c" "c3-linux-static-Debug" "tar"
      - uses: actions/upload-artifact@v7
        with:
          name: c3-linux-static-Debug
          path: c3-linux-static-Debug.tar.gz

  build-linux-alpine:
    name: Linux (x64) Alpine Dynamic
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-24.04
    container:
      image: alpine:3.24
    steps:
      - name: Prepare Git
        run: |
          apk add --no-cache git
          git config --global --add safe.directory "$GITHUB_WORKSPACE"

      - uses: actions/checkout@v7

      - name: Get current date context
        id: date
        run: echo "date=$(date +'${{ env.CACHE_INVALIDATION_SEED }}')" >> $GITHUB_OUTPUT

      #https://wiki.alpinelinux.org/wiki/Local_APK_cache
      - name: Prepare real apk cache directory + symlink
        run: |
          mkdir -p /var/cache/apk
          ln -sf /var/cache/apk /etc/apk/cache

      - name: Cache APK downloaded packages (daily)
        uses: actions/cache@v6
        with:
          path: /var/cache/apk
          key: apk-pkgs-${{ runner.os }}-alpine-3.23-${{ steps.date.outputs.date }}
          restore-keys: |
            apk-pkgs-${{ runner.os }}-alpine-3.23-

      - name: Install dependencies
        run: |
          V=${{env.LLVM_RELEASE_VERSION_LINUX_MUSL}}
          for i in 1 2 3; do apk update && break || sleep 2; done
          for i in 1 2 3; do \
            apk add \
              build-base bash cmake ninja \
              tar zstd \
              curl-dev \
              llvm${V}-dev \
              llvm${V}-static \
              llvm${V}-gtest \
              lld${V}-dev \
              lld${V}-libs && break || sleep 2; \
          done

      - name: CMake Build (Release)
        run: |
          cmake -B build-release -G Ninja \
            -DCMAKE_BUILD_TYPE=Release \
            -DC3_FETCH_LLVM=OFF \
            -DLLVM_ENABLE_LIBXML2=OFF \
            -DC3_LINK_DYNAMIC=ON
          cmake --build build-release

      - name: Bundle & Upload (Alpine Release)
        run: bash ./scripts/tools/package_build.sh "./build-release/c3c" "c3-linux-musl-Release" "tar"
      - uses: actions/upload-artifact@v7
        with:
          name: c3-linux-musl-Release
          path: c3-linux-musl-Release.tar.gz

      - name: CMake Build (Debug)
        run: |
          cmake -B build-debug -G Ninja \
            -DCMAKE_BUILD_TYPE=Debug \
            -DC3_FETCH_LLVM=OFF \
            -DLLVM_ENABLE_LIBXML2=OFF \
            -DC3_LINK_DYNAMIC=ON
          cmake --build build-debug

      - name: Run Unified Tests
        run: ./scripts/tools/ci_tests.sh "./build-debug/c3c"

      - name: Bundle & Upload (Alpine Debug)
        run: bash ./scripts/tools/package_build.sh "./build-debug/c3c" "c3-linux-musl-Debug" "tar"
      - uses: actions/upload-artifact@v7
        with:
          name: c3-linux-musl-Debug
          path: c3-linux-musl-Debug.tar.gz

  build-mac:
    name: macOS (aarch64)
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v7
      - name: CMake Build (Release)
        run: |
          cmake -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release -DC3_FETCH_LLVM=ON
          cmake --build build-release

      - name: Bundle & Upload (Mac Release)
        run: bash ./scripts/tools/package_build.sh "./build-release/c3c" "c3-macos-Release" "zip"
      - uses: actions/upload-artifact@v7
        with:
          name: c3-macos-Release
          path: c3-macos-Release.zip

      - name: CMake Build (Debug)
        run: |
          cmake -B build-debug -G Ninja -DCMAKE_BUILD_TYPE=Debug -DC3_FETCH_LLVM=ON
          cmake --build build-debug

      - name: Cache FreeBSD Sysroot
        uses: actions/cache@v6
        with:
          path: .cache
          key: ${{ runner.os }}-freebsd-sysroot-15.0
      - name: Run Unified Tests
        run: ./scripts/tools/ci_tests.sh "./build-debug/c3c"

      - name: Build Lib (Mac Debug)
        run: |
          cd resources/testproject
          ../../build-debug/c3c build hello_world_lib -vv --trust=full

      - name: Bundle & Upload (Mac Debug)
        run: bash ./scripts/tools/package_build.sh "./build-debug/c3c" "c3-macos-Debug" "zip"
      - uses: actions/upload-artifact@v7
        with:
          name: c3-macos-Debug
          path: c3-macos-Debug.zip

  build-openbsd:
    name: OpenBSD (x64) ${{ matrix.version }}
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        version: ['7.9']
    steps:
      - uses: actions/checkout@v7

      - name: Setup OpenBSD VM
        uses: cross-platform-actions/action@master
        with:
          operating_system: openbsd
          version: ${{ matrix.version }}
          cpu_count: 4
          memory: 6G

      - name: Install Dependencies
        shell: cpa.sh {0}
        run: |
          echo "Testing OpenBSD mirrors..."
          for m in "https://cloudflare.cdn.openbsd.org/pub/OpenBSD" "https://cdn.openbsd.org/pub/OpenBSD" "https://mirror.planetunix.net/pub/OpenBSD" "https://ftp.usa.openbsd.org/pub/OpenBSD"; do
            if curl -s -f -I -L --connect-timeout 5 --max-time 10 "${m}/${{ matrix.version }}/packages/amd64/" > /dev/null; then
              echo "Selected mirror: ${m}"
              echo "${m}" | sudo tee /etc/installurl
              break
            fi
          done
          for i in 1 2 3; do
            sudo pkg_add ninja cmake llvm%${{ env.LLVM_RELEASE_VERSION_OPENBSD }} bash && break || sleep 5
          done

      - name: Build (Release)
        shell: cpa.sh {0}
        run: |
          export MALLOC_OPTIONS=j
          cmake -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
          cmake --build build-release

      - name: Build (Debug)
        shell: cpa.sh {0}
        run: |
          export MALLOC_OPTIONS=j
          cmake -B build-debug -G Ninja -DCMAKE_BUILD_TYPE=Debug
          cmake --build build-debug

      - name: Run Tests
        shell: cpa.sh {0}
        run: |
          export MALLOC_OPTIONS=j
          ulimit -d $(ulimit -Hd) || true
          ./scripts/tools/ci_tests.sh build-debug/c3c

      - name: Package
        shell: cpa.sh {0}
        run: |
          export MALLOC_OPTIONS=j
          ./scripts/tools/package_build.sh "./build-release/c3c" "c3-openbsd-Release" "tar"
          ./scripts/tools/package_build.sh "./build-debug/c3c" "c3-openbsd-Debug" "tar"

      - name: Upload Artifacts (Release)
        uses: actions/upload-artifact@v7
        with:
          name: c3-openbsd-Release
          path: c3-openbsd-Release.tar.gz

      - name: Upload Artifacts (Debug)
        uses: actions/upload-artifact@v7
        with:
          name: c3-openbsd-Debug
          path: c3-openbsd-Debug.tar.gz

  build-netbsd:
    name: NetBSD (x64) ${{ matrix.version }}
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        version: ['10.1']
    steps:
      - uses: actions/checkout@v7

      - name: Setup NetBSD VM
        uses: cross-platform-actions/action@master
        with:
          operating_system: netbsd
          version: ${{ matrix.version }}
          cpu_count: 4
          memory: 6G

      - name: Install Dependencies
        shell: cpa.sh {0}
        run: |
          # Install dependencies, select best mirror
          export PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH"
          sudo mkdir -p /usr/pkg/etc/pkgin
          MIRROR_PATH="pub/pkgsrc/packages/NetBSD/amd64/10.1/All"
          WORKING_MIRRORS=""
          echo "Testing mirrors..."
          for m in "http://cdn.NetBSD.org/$MIRROR_PATH" "http://ftp.jaist.ac.jp/$MIRROR_PATH" "http://ftp.NetBSD.org/$MIRROR_PATH" "http://ftp.fr.NetBSD.org/$MIRROR_PATH" "https://mirror.planetunix.net/$MIRROR_PATH"; do
            if curl -s -f -I -L --connect-timeout 5 --max-time 10 "$m/pkg_summary.bz2" > /dev/null; then
              echo "OK: $m"
              WORKING_MIRRORS="${WORKING_MIRRORS}${m}/\n"
            fi
          done

          if [ -n "$WORKING_MIRRORS" ]; then
            printf "$WORKING_MIRRORS" | sudo tee /usr/pkg/etc/pkgin/repositories.conf
          else
            echo "No mirrors responded, using main site as fallback."
            echo "http://ftp.NetBSD.org/$MIRROR_PATH/" | sudo tee /usr/pkg/etc/pkgin/repositories.conf
          fi

          for i in 1 2 3; do
            sudo pkgin -y update && \
            sudo pkgin -y install cmake gcc14 ninja-build bash \
              "llvm>=${{ env.LLVM_RELEASE_VERSION_NETBSD }}" \
              "clang>=${{ env.LLVM_RELEASE_VERSION_NETBSD }}" \
              "lld>=${{ env.LLVM_RELEASE_VERSION_NETBSD }}" && break || sleep 5
          done

      - name: Build (Release)
        shell: cpa.sh {0}
        run: |
          export PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH"
          export CC=clang
          export CXX=clang++

          cmake -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
          cmake --build build-release

      - name: Build (Debug)
        shell: cpa.sh {0}
        run: |
          export PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH"
          export CC=clang
          export CXX=clang++
          cmake -B build-debug -G Ninja -DCMAKE_BUILD_TYPE=Debug
          cmake --build build-debug

      - name: Run Tests
        shell: cpa.sh {0}
        run: |
          export PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH"
          ./scripts/tools/ci_tests.sh build-debug/c3c

      - name: Package
        shell: cpa.sh {0}
        run: |
          export PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH"
          ./scripts/tools/package_build.sh "./build-release/c3c" "c3-netbsd-Release" "tar"
          ./scripts/tools/package_build.sh "./build-debug/c3c" "c3-netbsd-Debug" "tar"

      - name: Upload Artifacts (Release)
        uses: actions/upload-artifact@v7
        with:
          name: c3-netbsd-Release
          path: c3-netbsd-Release.tar.gz

      - name: Upload Artifacts (Debug)
        uses: actions/upload-artifact@v7
        with:
          name: c3-netbsd-Debug
          path: c3-netbsd-Debug.tar.gz

  build-with-docker:
    name: LLVM ${{ matrix.llvm_version }} Compatibility
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        llvm_version: [20, 21]
    steps:
      - uses: actions/checkout@v7

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Build and cache Docker image
        uses: docker/build-push-action@v7
        with:
          context: ./docker
          build-args: |
            LLVM_VERSION=${{ matrix.llvm_version }}
            UBUNTU_VERSION=24.04
            CMAKE_VERSION=3.20.0
          tags: c3c-builder:latest
          outputs: type=docker,dest=/tmp/c3c-builder.tar
          cache-from: type=gha,scope=build-with-docker-${{ matrix.llvm_version }}
          cache-to: type=gha,mode=max,scope=build-with-docker-${{ matrix.llvm_version }}
        env:
          DOCKER_BUILD_SUMMARY: false

      - name: Load Docker image
        run: docker load -i /tmp/c3c-builder.tar

      - name: Build C3 in Docker
        run: |
          rm -rf build bin
          mkdir -p build bin
          chmod -R 777 build bin
          docker run --rm \
            -v "$(pwd):/home/c3c/source" \
            -w /home/c3c/source \
            c3c-builder:latest \
            bash -c "git config --global --add safe.directory /home/c3c/source && \
            cmake -S . -B build \
                    -G Ninja \
                    -DCMAKE_BUILD_TYPE=Debug \
                    -DCMAKE_C_COMPILER=clang-${{ matrix.llvm_version }} \
                    -DCMAKE_CXX_COMPILER=clang++-${{ matrix.llvm_version }} \
                    -DCMAKE_LINKER=lld-${{ matrix.llvm_version }} \
                    -DCMAKE_OBJCOPY=llvm-objcopy-${{ matrix.llvm_version }} \
                    -DCMAKE_STRIP=llvm-strip-${{ matrix.llvm_version }} \
                    -DCMAKE_DLLTOOL=llvm-dlltool-${{ matrix.llvm_version }} \
                    -DC3_LLVM_VERSION=auto && \
            cmake --build build && \
            cp -r build/c3c build/lib bin"

      - name: Run Unified Tests
        run: |
          docker run --rm \
            -u 0 \
            -v "$(pwd):/home/c3c/source" \
            -w /home/c3c/source \
            c3c-builder \
            bash -c "./scripts/tools/ci_tests.sh ./bin/c3c"

  build-linux-llvm23:
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    name: Linux (x64) LLVM 23
    runs-on: ubuntu-24.04
    timeout-minutes: 30
    continue-on-error: true

    steps:
      - uses: actions/checkout@v7

      - name: Install LLVM 23 from apt.llvm.org
        run: |
          sudo apt-get update
          sudo apt-get install -y wget lsb-release gnupg software-properties-common
          wget https://apt.llvm.org/llvm.sh
          chmod +x llvm.sh

          sudo ./llvm.sh 23 all

          sudo apt-get install -y \
            liblld-23-dev \
            libpolly-23-dev \
            zlib1g-dev \
            ninja-build \
            python3 \
            libx11-dev libxrandr-dev libxinerama-dev libxi-dev libxxf86vm-dev libxcursor-dev

      - name: CMake Build
        run: |
          cmake -B build -G Ninja \
            -DCMAKE_BUILD_TYPE=Debug
          cmake --build build

      - name: Run Unified Tests
        run: ./scripts/tools/ci_tests.sh "./build/c3c"

  build-nix:
    name: Nix (${{ matrix.nixpkgs }})
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-22.04
    strategy:
      fail-fast: false
      matrix:
        build_type: [ Debug ]
        nixpkgs: [ Lock, Latest ]
    steps:
    - uses: actions/checkout@v7
    - uses: cachix/install-nix-action@v31
      with:
        github_access_token: ${{ secrets.GITHUB_TOKEN }}
    - name: Update flake
      if: matrix.nixpkgs == 'Latest'
      run: nix flake update
    - name: CMake build and run Unified Tests
      run: |
        CHECK_NAME=".#c3c-checks"
        if [[ ${{ matrix.build_type }} = "Debug" ]]; then CHECK_NAME=".#c3c-debug-checks"; fi
        nix build -L "$CHECK_NAME"

  build-android:
    name: Android (${{ matrix.architecture }})
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    strategy:
      fail-fast: false
      matrix:
        include:
          - architecture: aarch64
            runner: ubuntu-24.04-arm
          - architecture: x86_64
            runner: ubuntu-24.04
    runs-on: ${{matrix.runner}}
    container:
      image: termux/termux-docker:${{matrix.architecture}}
      volumes:
        - /tmp/node20:/__e/node20
        - /tmp/node24:/__e/node24
    env:
      TERMUX_MAIN_PACKAGE_FORMAT: debian
      ANDROID_ROOT: /system
      ANDROID_DATA: /data
      PREFIX: /data/data/com.termux/files/usr
      HOME: /data/data/com.termux/files/home
      PATH: /data/data/com.termux/files/usr/bin
      TMPDIR: /data/data/com.termux/files/usr/tmp
      LANG: en_US.UTF-8
      TZ: UTC
    steps:
      - name: select termux mirror
        run: |
          for m in \
            "https://termux.mentality.rip/termux-main" \
            "https://ftp.agdsn.de/termux/termux-main" \
            "https://linux.domainesia.com/applications/termux/termux-main"
          do
            echo "Trying mirror: $m"
            printf "deb %s stable main\n" "$m" > "${PREFIX}/etc/apt/sources.list"
            if /entrypoint.sh pkg update -y; then
              echo "Selected mirror: $m"
              exit 0
            fi
          done
    
          echo "All mirrors failed"
          exit 1
      - name: prepare termux
        run: |
          ln -sf ${PREFIX}/etc/termux/mirrors/default ${PREFIX}/etc/termux/chosen_mirrors
          # Ensure root can execute Termux binaries (the new image has them as 700)
          chmod -R 755 ${PREFIX}/bin || true
          for i in 1 2; do /entrypoint.sh pkg update && break || sleep 5; done
          /entrypoint.sh bash -c "yes | pkg upgrade -y"
          for i in 1 2; do /entrypoint.sh pkg install -y nodejs-lts tar git && break || sleep 5; done
          # Fix permissions again for newly installed packages and add compatibility symlinks
          chmod -R 755 ${PREFIX}/bin
          mkdir -p /usr/bin /bin || true
          ln -sf ${PREFIX}/bin/env /usr/bin/env || true
          ln -sf ${PREFIX}/bin/bash /usr/bin/bash || true
          ln -sf ${PREFIX}/bin/sh /bin/sh || true
          # Ensure TMPDIR exists and is writable
          mkdir -p ${TMPDIR} /tmp || true
          chmod 1777 ${TMPDIR} || true
          # Symlink Termux node to where GHA expects it
          mkdir -p /__e/node20/bin /__e/node24/bin || true
          ln -sf ${PREFIX}/bin/node /__e/node20/bin/node || true
          ln -sf ${PREFIX}/bin/node /__e/node24/bin/node || true

      - name: Get current date context
        id: date
        run: echo "date=$(date +'${{ env.CACHE_INVALIDATION_SEED }}')" >> $GITHUB_OUTPUT

      - name: Cache Termux packages
        uses: actions/cache@v6
        with:
          path: /data/data/com.termux/cache/apt/archives/
          key: termux-cache-${{ matrix.architecture }}-${{ steps.date.outputs.date }}

      - uses: actions/checkout@v7

      - name: fix permissions after checkout
        run: chown -R 1000:1000 .

      - name: setup
        run: |
          for i in 1 2; do \
            /entrypoint.sh pkg update -y && \
            /entrypoint.sh pkg install -y llvm binutils libgc build-essential cmake git libedit zlib clang make mlir llvm-tools libpolly python libllvm-static && break || sleep 10; \
          done

      - name: build (Release)
        run: |
          cmake -B build-release -DCMAKE_BUILD_TYPE=Release
          cmake --build build-release

      - name: Bundle Release
        run: |
          chmod +x ./scripts/tools/package_build.sh
          bash ./scripts/tools/package_build.sh "./build-release/c3c" "c3-android-${{matrix.architecture}}-Release" "tar"

      - name: upload artifacts (Release)
        uses: actions/upload-artifact@v7
        with:
          name: c3-android-${{matrix.architecture}}-Release
          path: c3-android-${{matrix.architecture}}-Release.tar.gz

      - name: build (Debug)
        run: |
          cmake -B build-debug -DCMAKE_BUILD_TYPE=Debug
          cmake --build build-debug

      - name: Run Unified Tests
        run: bash ./scripts/tools/ci_tests.sh "./build-debug/c3c" "android"

      - name: Bundle Debug
        run: |
          chmod +x ./scripts/tools/package_build.sh
          bash ./scripts/tools/package_build.sh "./build-debug/c3c" "c3-android-${{matrix.architecture}}-Debug" "tar"

      - name: upload artifacts (Debug)
        uses: actions/upload-artifact@v7
        with:
          name: c3-android-${{matrix.architecture}}-Debug
          path: c3-android-${{matrix.architecture}}-Debug.tar.gz

  test-ios:
    name: iOS (aarch64)
    needs: changes
    if: needs.changes.outputs.src == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v7
      
      - name: CMake Build (Host)
        run: |
          cmake -B build-debug -G Ninja \
            -DCMAKE_BUILD_TYPE=Debug \
            -DC3_FETCH_LLVM=ON
          cmake --build build-debug

      - name: Setup and Boot iOS Simulator
        uses: futureware-tech/simulator-action@v5
        with:
          os: 'iOS'
          os_version: ">=14.0"

      - name: Run Unified Tests (iOS)
        run: |
          chmod +x ./scripts/tools/ci_tests.sh
          ./scripts/tools/ci_tests.sh ./build-debug/c3c "ios"

      - name: Compile and Inspect Device Binary
        if: always()
        run: |
          ./build-debug/c3c compile resources/examples/hello_world_many.c3 -vv --target ios-aarch64 -o helloworld
          echo "--- Binary Details ---"
          file helloworld && echo "---" && otool -hv helloworld

  release:
    runs-on: ubuntu-slim
    needs: [build-msvc, build-msvc-arm64, build-linux, build-mac, build-linux-alpine, build-openbsd, build-netbsd, build-android, build-linux-musl-static]
    if: github.ref == 'refs/heads/master' || endsWith(github.ref, '.x')
    permissions:
      contents: write
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      RELEASE_NAME: ""
      TAG_NAME: ""
    steps:
      - uses: actions/checkout@v7
      - uses: actions/download-artifact@v8

      - name: Prepare Assets
        run: |
          for config in Release Debug; do
            for platform in windows windows-aarch64 macos linux linux-musl linux-static openbsd netbsd android-aarch64 android-x86_64 windows-setup windows-setup-aarch64; do
              if [ "$platform" = "windows-setup" ] || [ "$platform" = "windows-setup-aarch64" ]; then
                ext="exe"
              # Windows and macOS produce zip, others tar.gz
              elif [ "$platform" = "windows" ] || [ "$platform" = "windows-aarch64" ] || [ "$platform" = "macos" ]; then
                ext="zip"
              else
                ext="tar.gz"
              fi

              src="c3-${platform}-${config}/c3-${platform}-${config}.${ext}"
              dst="c3-${platform}"
              [ "$config" = "Debug" ] && dst="${dst}-debug"
              dst="${dst}.${ext}"

              mv "$src" "$dst" || true
            done
          done

      - name: Set Release Names
        run: |
          if [ "${{ github.ref }}" = "refs/heads/master" ]; then
            echo "TAG_NAME=latest-prerelease-tag" >> $GITHUB_ENV
            echo "RELEASE_NAME=latest-prerelease-$(date +'%Y%m%d-%H%M')" >> $GITHUB_ENV
          else
            BRANCH_NAME="${GITHUB_REF#refs/heads/}"
            echo "TAG_NAME=${BRANCH_NAME}-latest" >> $GITHUB_ENV
            echo "RELEASE_NAME=${BRANCH_NAME}-latest" >> $GITHUB_ENV
          fi

      - run: gh release delete ${{ env.TAG_NAME }} --cleanup-tag -y || true

      - name: Create GitHub Release
        uses: ncipollo/release-action@v1
        with:
          tag: ${{ env.TAG_NAME }}
          name: ${{ env.RELEASE_NAME }}
          prerelease: true
          allowUpdates: true
          removeArtifacts: true
          artifacts: "*.zip,*.tar.gz,*.exe"