name: CI

on:
  pull_request:
  push:

concurrency:
  group: ${{format('{0}:{1}', github.repository, github.ref)}}
  cancel-in-progress: true

env:
  BOOST_SPIRIT_BUILD_JOBS: 4
  BOOST_SPIRIT_CACHED_UPSTREAM_LIBS: --with-regex --with-thread --with-atomic --with-chrono --with-container --with-date_time --with-exception --with-math

jobs:
  changes:
    runs-on: ubuntu-latest
    outputs:
      spirit_component: ${{ steps.filter.outputs.changes }}
      karma: ${{ steps.filter.outputs.karma }}
      lex: ${{ steps.filter.outputs.lex }}
      qi: ${{ steps.filter.outputs.qi }}
      support: ${{ steps.filter.outputs.support }}
      repository: ${{ steps.filter.outputs.repository }}
      x3: ${{ steps.filter.outputs.x3 }}
    steps:
      - uses: actions/checkout@v5
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          # Note: references to "support" directory in X3 can be completely
          # eliminated as soon as those components are moved to X3's
          # self-contained location
          filters: |
            karma:
              - '.github/workflows/*.yml'
              - 'include/boost/spirit/home/support/**/*'
              - 'include/boost/spirit/home/support.hpp'
              - 'include/boost/spirit/home/karma/**/*'
              - 'include/boost/spirit/home/karma.hpp'
              - 'test/karma/**/*'
            lex:
              - '.github/workflows/*.yml'
              - 'include/boost/spirit/home/support/**/*'
              - 'include/boost/spirit/home/support.hpp'
              - 'include/boost/spirit/home/lex/**/*'
              - 'include/boost/spirit/home/lex.hpp'
              - 'test/lex/**/*'
            qi:
              - '.github/workflows/*.yml'
              - 'include/boost/spirit/home/support/**/*'
              - 'include/boost/spirit/home/support.hpp'
              - 'include/boost/spirit/home/qi/**/*'
              - 'include/boost/spirit/home/qi.hpp'
              - 'test/qi/**/*'
            support:
              - '.github/workflows/*.yml'
              - 'include/boost/spirit/home/support/**/*'
              - 'include/boost/spirit/home/support.hpp'
              - 'test/support/**/*'
            repository:
              - '.github/workflows/*.yml'
              - 'include/boost/spirit/repository/**/*'
              - 'repository/**/*'
            x3:
              - '.github/workflows/*.yml'
              - 'include/boost/spirit/home/x3/**/*'
              - 'include/boost/spirit/home/x3.hpp'
              - 'test/x3/**/*'
              - 'include/boost/spirit/home/support/char_set/**/*'
              - 'include/boost/spirit/home/support/char_encoding/**/*'

  build:
    name: "[${{ matrix.cpp_version.name }}] ${{ matrix.spirit_component }} | ${{ matrix.compiler.toolset }}-${{ matrix.compiler.version }} (${{ matrix.build_type.name }}) @ ${{ matrix.os.name }}-${{ matrix.os.version }}"

    needs: changes
    if: ${{ needs.changes.outputs.spirit_component != '[]' && needs.changes.outputs.spirit_component != '' }}

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

    strategy:
      fail-fast: false

      matrix:
        os:
          - name: ubuntu
            version: 24.04
          - name: windows
            version: 2022
        build_type:
          - name: Debug
            lowercase: debug
          - name: Release
            lowercase: release
        cpp_version:
          - name: C++11
            number: 11
          - name: C++23
            number: 23
            msvc_std: /std:c++23preview
          - name: C++26
            number: 26
            msvc_std: /std:c++latest
        compiler:
          - name: GCC
            toolset: gcc
            version: 14
            cxxflags: -fdiagnostics-color=always
          - name: Clang
            toolset: clang
            version: 22
            cxxflags: -fcolor-diagnostics
          - name: MSVC
            toolset: msvc
            version: 2022
            cxxflags: /EHsc /utf-8

        spirit_component: ${{ fromJSON(needs.changes.outputs.spirit_component) }}

        exclude:
          # Blacklist all invalid combinations of environments
          - os:
              name: windows
            cpp_version:
              number: 11 # /std:c++11 is no longer supported
          - os:
              name: windows
            compiler:
              name: GCC
          - os:
              name: windows
            compiler:
              name: Clang
          - os:
              name: ubuntu
            compiler:
              name: MSVC

          # Blacklist incompatible toolchains
          - spirit_component: karma
            cpp_version:
              number: 23
          - spirit_component: karma
            cpp_version:
              number: 26
          - spirit_component: lex
            cpp_version:
              number: 23
          - spirit_component: lex
            cpp_version:
              number: 26
          - spirit_component: qi
            cpp_version:
              number: 23
          - spirit_component: qi
            cpp_version:
              number: 26
          - spirit_component: support
            cpp_version:
              number: 23
          - spirit_component: support
            cpp_version:
              number: 26
          - spirit_component: repository
            cpp_version:
              number: 23
          - spirit_component: repository
            cpp_version:
              number: 26
          - spirit_component: x3
            cpp_version:
              number: 11

    steps:
      - uses: actions/checkout@v5
        with:
          # required for upstream Boost version detection
          # sadly not working due to upstream bug: https://github.com/actions/checkout/issues/1471
          fetch-tags: true

      - name: Fetch git tags
        run: |
          git fetch --depth=1 origin +refs/tags/*:refs/tags/*

      - name: Initialize Ubuntu
        if: matrix.os.name == 'ubuntu'
        run: |
          sudo echo "set man-db/auto-update false" | sudo debconf-communicate
          sudo dpkg-reconfigure man-db
          sudo apt-get install -y jq

      - name: Setup GCC
        if: matrix.compiler.toolset == 'gcc'
        run: |
          sudo apt-get update
          sudo apt-get install -y g++-${{ matrix.compiler.version }}

      - name: Setup Clang
        if: matrix.compiler.toolset == 'clang'
        run: |
          wget https://apt.llvm.org/llvm.sh
          chmod +x ./llvm.sh
          sudo ./llvm.sh ${{ matrix.compiler.version }}
          sudo apt-get install -y libc++-${{ matrix.compiler.version }}-dev libc++abi-${{ matrix.compiler.version }}-dev
          echo "BOOST_SPIRIT_STDLIB=stdlib=libc++" >> "$GITHUB_ENV"

      - name: Fetch Environment Info
        id: env-info
        shell: bash
        run: |
          set -e
          export BOOST_RELEASE_VERSION_NAME=$(git for-each-ref refs/tags --sort=-refname --format='%(refname:lstrip=-1)' --count=1)
          echo "BOOST_RELEASE_VERSION_NAME: $BOOST_RELEASE_VERSION_NAME"
          echo "BOOST_RELEASE_VERSION_NAME=$BOOST_RELEASE_VERSION_NAME" >> "$GITHUB_OUTPUT"

          if [ "${{ matrix.os.name }}" = "windows" ]; then
            export BOOST_ROOT=$(cygpath -w $HOME/boost)
          else
            export BOOST_ROOT=$HOME/boost
          fi
          echo "BOOST_ROOT: $BOOST_ROOT"
          echo "BOOST_ROOT=$BOOST_ROOT" >> "$GITHUB_OUTPUT"

      - name: Cache upstream Boost libraries (restore)
        id: cache-boost
        uses: actions/cache/restore@v4
        with:
          key: ${{ steps.env-info.outputs.BOOST_RELEASE_VERSION_NAME }}-${{ matrix.os.name }}-${{ matrix.os.version }}-${{ matrix.compiler.toolset }}-${{ matrix.compiler.version }}-${{ matrix.cpp_version.name }}-${{ matrix.build_type.name }}
          path: |
            ${{ steps.env-info.outputs.BOOST_ROOT }}

      - name: Clone upstream Boost libraries
        if: steps.cache-boost.outputs.cache-hit != 'true'
        run: |
          git -c advice.detachedHead=false clone --no-tags --single-branch --branch=${{ steps.env-info.outputs.BOOST_RELEASE_VERSION_NAME }} --depth=1 --quiet --recurse-submodules=":(exclude)libs/spirit" --shallow-submodules https://github.com/boostorg/boost.git ${{ steps.env-info.outputs.BOOST_ROOT }}

      - name: Build upstream Boost libraries (Ubuntu)
        if: matrix.os.name == 'ubuntu' && steps.cache-boost.outputs.cache-hit != 'true'
        env:
          BOOST_ROOT: ${{ steps.env-info.outputs.BOOST_ROOT }}
        working-directory: ${{ steps.env-info.outputs.BOOST_ROOT }}
        shell: bash
        run: |
          set -xe
          rm -rf libs/spirit
          ./bootstrap.sh --with-toolset=${{ matrix.compiler.toolset }}
          ./b2 -d0 headers
          ./b2 -d1 -j$BOOST_SPIRIT_BUILD_JOBS $BOOST_SPIRIT_CACHED_UPSTREAM_LIBS link=shared threading=multi variant=${{ matrix.build_type.lowercase }} toolset=${{ matrix.compiler.toolset }}-${{ matrix.compiler.version }} cxxstd=${{ matrix.cpp_version.number }} cxxflags="${{ matrix.compiler.cxxflags }}" $BOOST_SPIRIT_STDLIB warnings=extra warnings-as-errors=off

      - uses: TheMrMilchmann/setup-msvc-dev@v3
        if: matrix.os.name == 'windows'
        with:
          arch: x64

      - name: Build upstream Boost libraries (Windows)
        if: matrix.os.name == 'windows' && steps.cache-boost.outputs.cache-hit != 'true'
        env:
          BOOST_ROOT: ${{ steps.env-info.outputs.BOOST_ROOT }}
        working-directory: ${{ steps.env-info.outputs.BOOST_ROOT }}
        shell: cmd
        run: |
          rd /s /q libs\spirit
          .\bootstrap.bat --with-toolset=${{ matrix.compiler.toolset }}
          .\b2 -d0 headers
          .\b2 -d1 -j%BOOST_SPIRIT_BUILD_JOBS% %BOOST_SPIRIT_CACHED_UPSTREAM_LIBS% link=shared threading=multi variant=${{ matrix.build_type.lowercase }} toolset=${{ matrix.compiler.toolset }} cxxflags="${{ matrix.cpp_version.msvc_std }} ${{matrix.compiler.cxxflags}}" warnings=extra warnings-as-errors=off

      - name: Cache upstream Boost libraries
        if: steps.cache-boost.outputs.cache-hit != 'true'
        uses: actions/cache/save@v4
        with:
          key: ${{ steps.cache-boost.outputs.cache-primary-key }}
          path: |
            ${{ steps.env-info.outputs.BOOST_ROOT }}
            !${{ steps.env-info.outputs.BOOST_ROOT }}/.git

      - name: Build and test (Ubuntu)
        if: matrix.os.name == 'ubuntu'
        env:
          BOOST_ROOT: ${{ steps.env-info.outputs.BOOST_ROOT }}
        working-directory: ${{ steps.env-info.outputs.BOOST_ROOT }}
        shell: bash
        run: |
          set -xe
          cp -rp $GITHUB_WORKSPACE libs/spirit
          cd libs/spirit/test
          ln -s ../repository/test repository # workaround legacy directory structure
          cd ${{ matrix.spirit_component }}

          $BOOST_ROOT/b2 -d1 -j$BOOST_SPIRIT_BUILD_JOBS link=shared threading=multi variant=${{ matrix.build_type.lowercase }} toolset=${{ matrix.compiler.toolset }}-${{ matrix.compiler.version }} cxxstd=${{ matrix.cpp_version.number }} cxxflags="${{ matrix.compiler.cxxflags }}" $BOOST_SPIRIT_STDLIB warnings=extra warnings-as-errors=off

      - name: Build and test (Windows)
        if: matrix.os.name == 'windows'
        env:
          BOOST_ROOT: ${{ steps.env-info.outputs.BOOST_ROOT }}
        working-directory: ${{ steps.env-info.outputs.BOOST_ROOT }}
        shell: cmd
        run: |
          xcopy /S /E /Q %GITHUB_WORKSPACE% libs\spirit\
          cd libs\spirit\test
          cd ${{ matrix.spirit_component }}

          %BOOST_ROOT%\b2 -d1 -j%BOOST_SPIRIT_BUILD_JOBS% link=shared threading=multi variant=${{ matrix.build_type.lowercase }} toolset=${{ matrix.compiler.toolset }} cxxflags="${{ matrix.cpp_version.msvc_std }} ${{matrix.compiler.cxxflags}}" warnings=extra warnings-as-errors=off
