diff --git a/.github/ci/integration-conda.txt b/.github/ci/integration-conda.txt new file mode 100644 index 000000000..a9e14a134 --- /dev/null +++ b/.github/ci/integration-conda.txt @@ -0,0 +1,3 @@ +pip +conda-forge::dftbplus +conda-forge::xtb diff --git a/.github/ci/integration-requirements.txt b/.github/ci/integration-requirements.txt new file mode 100644 index 000000000..6533e4f07 --- /dev/null +++ b/.github/ci/integration-requirements.txt @@ -0,0 +1,5 @@ +ase +pqanalysis +pyscf==2.9.0 +pytest +pytest-cov diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml deleted file mode 100644 index b1b9469e8..000000000 --- a/.github/workflows/changelog.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Changelog Fragment Check - -on: - pull_request: - branches: - - dev - types: [opened, reopened, synchronize] - -jobs: - check-changelog-fragment: - if: >- - github.event.pull_request.head.repo.full_name != github.repository || - github.event.pull_request.head.ref != 'main' - runs-on: ubuntu-latest - - steps: - - name: Checkout pull request - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: Test changelog tooling - run: python3 -m unittest discover -s scripts/tests -p 'test_*.py' - - - name: Check changelog fragment - run: >- - python3 scripts/check_changelog_fragment.py - ${{ github.event.pull_request.base.sha }} - ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml deleted file mode 100644 index 660de56f2..000000000 --- a/.github/workflows/ci_build.yml +++ /dev/null @@ -1,271 +0,0 @@ -name: BUILD - -on: - pull_request: - branches: - - '*' - paths: - - '.github/workflows/ci_build.yml' - - '.cmake/**' - - 'apps/**' - - 'include/**' - - 'src/**' - - 'integration_tests/**' - - 'tests/**' - - 'benchmarks/CMakeLists.txt' - - 'benchmarks/src/**' - - 'CMakeLists.txt' - push: - branches: - - dev - - main - paths: - - '.github/workflows/ci_build.yml' - - '.cmake/**' - - 'apps/**' - - 'include/**' - - 'src/**' - - 'integration_tests/**' - - 'tests/**' - - 'benchmarks/CMakeLists.txt' - - 'benchmarks/src/**' - - 'CMakeLists.txt' - schedule: - - cron: '0 2 * * *' - workflow_dispatch: - -jobs: - build: - if: github.event_name != 'schedule' - strategy: - matrix: - os: [ubuntu-24.04, ubuntu-24.04-arm] - build: [Release, Debug] - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v4 - - - name: install gcc13 - run: | - sudo apt update - sudo apt install gcc-13 g++-13 mold - shell: bash - - # safe because CI builds with -DBUILD_WITH_NATIVE=Off (portable objects) - - name: setup ccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - key: ${{ matrix.os }}-${{ matrix.build }} - - - name: setup python ubuntu - uses: actions/setup-python@v3 - with: - python-version: '3.12' - - - name: install python dependencies - run: | - python -m pip install numpy - shell: bash - - - name: install coverage - if : ${{ matrix.os == 'ubuntu-24.04' && matrix.build == 'Debug' }} - run: | - python -m pip install coverage - python -m pip install gcovr - shell: bash - - - name: Build and Test Project - if : ${{ matrix.os != 'ubuntu-24.04' || (matrix.os == 'ubuntu-24.04' && matrix.build != 'Debug') }} - run: | - mkdir build - cd build - cmake -DCMAKE_BUILD_TYPE=${{ matrix.build }} -DBUILD_WITH_NATIVE=Off .. - make -j$(nproc) - make test - env: - CC: gcc-13 - CXX: g++-13 - - - name: Build and Test Project with coverage - if : ${{ matrix.os == 'ubuntu-24.04' && matrix.build == 'Debug' }} - run: | - mkdir build - cd build - cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_WITH_GCOVR=ON -DBUILD_WITH_NATIVE=Off .. - make -j$(nproc) - make coverage_xml - env: - CC: gcc-13 - CXX: g++-13 - - - name: Upload coverage to Codecov - if : ${{ matrix.os == 'ubuntu-24.04' && matrix.build == 'Debug' }} - uses: codecov/codecov-action@v3 - with: - fail_ci_if_error: true # optional (default = false) - gcov: true - gcov_executable: gcov-13 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - - name: Setup Conda Environment - if: ${{matrix.build == 'Release'}} - uses: conda-incubator/setup-miniconda@v3 - with: - auto-update-conda: true - python-version: '3.12' - activate-environment: test-env - channels: default, conda-forge - auto-activate-base: false - - - name: Cache conda env - if: ${{matrix.build == 'Release'}} - id: cache-conda-env - uses: actions/cache@v4 - with: - path: ${{ env.CONDA }}/envs/test-env - key: conda-env-${{ matrix.os }}-${{ hashFiles('.github/workflows/ci_build.yml') }} - - - name: Install Integration Test Dependencies in Conda Env - if: ${{matrix.build == 'Release' && steps.cache-conda-env.outputs.cache-hit != 'true'}} - run: | - conda install -n test-env -y pip conda-forge::dftbplus conda-forge::xtb - conda run -n test-env python -m pip install pytest pytest-cov pqanalysis ase pyscf==2.9.0 - - - name: Integration Tests - if: ${{matrix.build == 'Release' }} - run: | - conda run -n test-env pytest integration_tests -v - - benchmark: - if: github.event_name != 'schedule' - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - - - name: install gcc13 - run: | - sudo apt update - sudo apt install gcc-13 g++-13 - shell: bash - - - name: Build benchmark suite - run: | - cmake -S . -B build-benchmark \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_WITH_BENCHMARKING=On \ - -DBUILD_WITH_TESTS=Off \ - -DBUILD_WITH_ASE=Off \ - -DBUILD_WITH_DOCS=Off \ - -DBUILD_WITH_NATIVE=Off - cmake --build build-benchmark --target google_benchmarks -j$(nproc) - env: - CC: gcc-13 - CXX: g++-13 - - - name: Run benchmark smoke tests - run: | - ctest --test-dir build-benchmark -L benchmark --output-on-failure - - build-static-lto: - if: github.event_name != 'schedule' - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - - - name: Install GCC 13 - run: | - sudo apt update - sudo apt install gcc-13 g++-13 mold ninja-build - - - name: Build and test static LTO - run: | - cmake -S . -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DBUILD_WITH_ASE=OFF \ - -DBUILD_WITH_LTO=ON \ - -DBUILD_WITH_NATIVE=OFF - cmake --build build --parallel - ctest --test-dir build --output-on-failure -j1 - env: - CC: gcc-13 - CXX: g++-13 - - static-singularity-build: - if: >- - github.event_name == 'schedule' || - github.event_name == 'workflow_dispatch' - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - - - name: Install build dependencies - run: | - sudo apt update - sudo apt install gcc-13 g++-13 mold - - - name: Setup ccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - key: static-singularity - - - name: Build project - run: | - cmake -S . -B build \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DBUILD_WITH_SINGULARITY=ON \ - -DBUILD_WITH_TESTS=ON \ - -DBUILD_WITH_ASE=OFF \ - -DBUILD_WITH_NATIVE=OFF - cmake --build build --parallel "$(nproc)" - env: - CC: gcc-13 - CXX: g++-13 - - - name: Run tests - run: ctest --test-dir build --output-on-failure -j1 - - mpi-build: - if: github.event_name != 'schedule' - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - - - name: Install build dependencies - run: | - sudo apt update - sudo apt install \ - gcc-13 \ - g++-13 \ - mold \ - openmpi-bin \ - libopenmpi-dev \ - python-is-python3 - - - name: Setup ccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - key: mpi-release - - - name: Build project - run: | - cmake -S . -B build \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_WITH_MPI=ON \ - -DBUILD_WITH_ASE=OFF \ - -DBUILD_WITH_NATIVE=OFF \ - -DBUILD_WITH_TESTS=ON - cmake --build build --parallel "$(nproc)" - env: - CC: gcc-13 - CXX: g++-13 - - - name: Run tests - run: ctest --test-dir build --output-on-failure -j1 diff --git a/.github/workflows/clang_format.yml b/.github/workflows/clang_format.yml deleted file mode 100644 index cc55f415f..000000000 --- a/.github/workflows/clang_format.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Clang Format Check - -on: - pull_request: - branches: - - main - - dev - paths: - - '.clang-format' - - '.github/workflows/clang_format.yml' - - '**/*.c' - - '**/*.cc' - - '**/*.cpp' - - '**/*.cxx' - - '**/*.h' - - '**/*.hh' - - '**/*.hpp' - - '**/*.hxx' - - '!external/**' - workflow_dispatch: - -jobs: - clang-format: - runs-on: ubuntu-24.04 - name: Check Changed C++ Formatting - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install clang-format - run: python -m pip install clang-format==20.1.3 - - - name: Check formatting - run: | - base_sha="${{ github.event.pull_request.base.sha }}" - head_sha="${{ github.event.pull_request.head.sha }}" - if [[ -z "$base_sha" ]]; then - base_sha="HEAD^" - head_sha="HEAD" - fi - - clang-format --style=file --dump-config > /dev/null - git-clang-format \ - --diff \ - --binary "$(command -v clang-format)" \ - --extensions c,cc,cpp,cxx,h,hh,hpp,hxx \ - "$base_sha" \ - "$head_sha" \ - -- apps benchmarks include src tests diff --git a/.github/workflows/extended_ci.yml b/.github/workflows/extended_ci.yml new file mode 100644 index 000000000..ac381b006 --- /dev/null +++ b/.github/workflows/extended_ci.yml @@ -0,0 +1,349 @@ +name: Extended CI + +on: + push: + branches: + - dev + paths: + - '.clang-tidy' + - '.cmake/**' + - '.github/ci/**' + - '.github/workflows/extended_ci.yml' + - '.gitmodules' + - 'apps/**' + - 'benchmarks/**' + - 'external/**' + - 'include/**' + - 'integration_tests/**' + - 'scripts/**' + - 'src/**' + - 'tests/**' + - 'CMakeLists.txt' + pull_request: + branches: + - main + types: [opened, reopened, synchronize] + schedule: + - cron: '0 2 * * *' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: extended-ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name != 'schedule' }} + +jobs: + full-lint: + name: Full C++ lint + runs-on: ubuntu-24.04 + timeout-minutes: 25 + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'schedule' && 'dev' || '' }} + + - name: Install build and lint tools + run: | + sudo apt-get update + sudo apt-get install -y \ + clangd-20 gcc-13 g++-13 mold ninja-build + sudo update-alternatives \ + --install /usr/bin/clangd clangd /usr/bin/clangd-20 100 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: python -m pip install clangd-tidy numpy + + - name: Configure compile database + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_NATIVE=OFF + env: + CC: gcc-13 + CXX: g++-13 + + - name: Lint all production C++ files + run: | + set -euo pipefail + mapfile -t files < <( + git ls-files apps include src | + grep -E '\.(c|cc|cpp|cxx|h|hh|hpp|hxx|tpp)$' + ) + clangd-tidy "${files[@]}" -p=build --tqdm -j1 + + static-lto: + name: Full static LTO + runs-on: ubuntu-24.04 + timeout-minutes: 25 + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'schedule' && 'dev' || '' }} + + - name: Install build tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-13 g++-13 mold ninja-build + + - name: Set up ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: static-lto + + - name: Build and test static LTO + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_WITH_ASE=OFF \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_LTO=ON \ + -DBUILD_WITH_NATIVE=OFF + cmake --build build --parallel "$(nproc)" + ctest --test-dir build --output-on-failure + env: + CC: gcc-13 + CXX: g++-13 + + arm-debug: + name: ARM Debug + runs-on: ubuntu-24.04-arm + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'schedule' && 'dev' || '' }} + + - name: Install build tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-13 g++-13 mold ninja-build + + - name: Set up ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: extended-arm-debug + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install build dependencies + run: python -m pip install numpy + + - name: Build and run unit tests + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_NATIVE=OFF + cmake --build build --parallel "$(nproc)" + ctest --test-dir build --output-on-failure + env: + CC: gcc-13 + CXX: g++-13 + + - name: Set up Conda + uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: false + python-version: '3.12' + activate-environment: test-env + channels: default, conda-forge + auto-activate-base: false + + - name: Restore integration environment + id: conda-cache + uses: actions/cache@v4 + with: + path: ${{ env.CONDA }}/envs/test-env + key: >- + conda-integration-${{ runner.arch }}-${{ + hashFiles('.github/ci/integration-conda.txt', + '.github/ci/integration-requirements.txt') }} + + - name: Install integration dependencies + if: steps.conda-cache.outputs.cache-hit != 'true' + run: | + conda install -n test-env -y --file .github/ci/integration-conda.txt + conda run -n test-env \ + python -m pip install -r .github/ci/integration-requirements.txt + + - name: Run ARM integration tests + run: conda run -n test-env pytest integration_tests -v + + mpi: + name: MPI build and tests + runs-on: ubuntu-24.04 + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'schedule' && 'dev' || '' }} + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + gcc-13 \ + g++-13 \ + libopenmpi-dev \ + mold \ + ninja-build \ + openmpi-bin \ + python-is-python3 + + - name: Set up ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: extended-mpi-release + + - name: Build and run tests + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_WITH_ASE=OFF \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_MPI=ON \ + -DBUILD_WITH_NATIVE=OFF \ + -DBUILD_WITH_TESTS=ON + cmake --build build --parallel "$(nproc)" + ctest --test-dir build --output-on-failure + env: + CC: gcc-13 + CXX: g++-13 + + benchmarks: + name: Benchmark build and smoke + runs-on: ubuntu-24.04 + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'schedule' && 'dev' || '' }} + + - name: Install build tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-13 g++-13 ninja-build + + - name: Set up ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: extended-benchmarks + + - name: Build and run benchmark smoke tests + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_WITH_ASE=OFF \ + -DBUILD_WITH_BENCHMARKING=ON \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_NATIVE=OFF \ + -DBUILD_WITH_TESTS=OFF + cmake --build build --target google_benchmarks --parallel "$(nproc)" + ctest --test-dir build -L benchmark --output-on-failure + env: + CC: gcc-13 + CXX: g++-13 + + static-singularity: + name: Static Singularity build + if: >- + github.event_name == 'schedule' || + github.event_name == 'workflow_dispatch' + runs-on: ubuntu-24.04 + timeout-minutes: 25 + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'schedule' && 'dev' || '' }} + + - name: Install build tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-13 g++-13 mold ninja-build + + - name: Set up ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: static-singularity + + - name: Build and run tests + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_WITH_ASE=OFF \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_NATIVE=OFF \ + -DBUILD_WITH_SINGULARITY=ON \ + -DBUILD_WITH_TESTS=ON + cmake --build build --parallel "$(nproc)" + ctest --test-dir build --output-on-failure + env: + CC: gcc-13 + CXX: g++-13 + + result: + name: Extended result + if: always() + needs: + - full-lint + - static-lto + - arm-debug + - mpi + - benchmarks + - static-singularity + runs-on: ubuntu-latest + timeout-minutes: 2 + + steps: + - name: Verify extended job results + env: + ARM_DEBUG: ${{ needs.arm-debug.result }} + BENCHMARKS: ${{ needs.benchmarks.result }} + FULL_LINT: ${{ needs.full-lint.result }} + MPI: ${{ needs.mpi.result }} + STATIC_LTO: ${{ needs.static-lto.result }} + STATIC_SINGULARITY: ${{ needs.static-singularity.result }} + run: | + set -euo pipefail + + failed=false + results=( + "full-lint=$FULL_LINT" + "static-lto=$STATIC_LTO" + "arm-debug=$ARM_DEBUG" + "mpi=$MPI" + "benchmarks=$BENCHMARKS" + "static-singularity=$STATIC_SINGULARITY" + ) + + for entry in "${results[@]}"; do + name="${entry%%=*}" + result="${entry#*=}" + echo "$name: $result" + if [[ "$result" != "success" && "$result" != "skipped" ]]; then + failed=true + fi + done + + if [[ "$failed" == "true" ]]; then + exit 1 + fi diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml index 4c6d57065..b70f581a1 100644 --- a/.github/workflows/jekyll-gh-pages.yml +++ b/.github/workflows/jekyll-gh-pages.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Requirements run: sudo apt-get install -y doxygen diff --git a/.github/workflows/license_check.yml b/.github/workflows/license_check.yml deleted file mode 100644 index 69115df78..000000000 --- a/.github/workflows/license_check.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: License Header Check - -on: - pull_request: - branches: - - '*' - paths: - - '**/*.cpp' - - '**/*.hpp' - - '**/*.c' - - '**/*.h' - - '.github/workflows/license_check.yml' - - 'scripts/addLicense.sh' - - 'config/licenseHeader.txt' - push: - branches: - - main - - master - paths: - - '**/*.cpp' - - '**/*.hpp' - - '**/*.c' - - '**/*.h' - - '.github/workflows/license_check.yml' - - 'scripts/addLicense.sh' - - 'config/licenseHeader.txt' - workflow_dispatch: - -jobs: - license-check: - runs-on: ubuntu-latest - name: Check License Headers - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Check for license headers in C++ files - run: | - echo "Checking for exact GPL license headers in C++ files..." - - # Verify license header template exists - if [ ! -f "config/licenseHeader.txt" ]; then - echo "❌ License header template not found at config/licenseHeader.txt" - exit 1 - fi - - # Get the expected license header content - license_header=$(cat config/licenseHeader.txt) - header_line_count=$(wc -l < config/licenseHeader.txt) - - echo "Expected license header has $header_line_count lines" - echo "" - - # Find all C++ files in the relevant directories (excluding external dependencies) - missing_count=0 - total_files=0 - - find src/ tests/ include/ apps/ benchmarks/ -regex ".*\.\(hpp\|cpp\|c\|h\)$" -print0 2>/dev/null | while IFS= read -r -d '' file; do - total_files=$((total_files + 1)) - echo "Checking: $file" - - # Extract the first N lines from the file (where N = number of lines in license header) - file_header=$(head -n "$header_line_count" "$file" 2>/dev/null) - - # Compare the extracted header with the expected license header - if [ "$file_header" = "$license_header" ]; then - echo "✅ Exact license header match: $file" - else - echo "❌ License header mismatch: $file" - echo "$file" >> /tmp/missing_files.txt - fi - done - - # Count missing files - if [ -f /tmp/missing_files.txt ]; then - missing_count=$(wc -l < /tmp/missing_files.txt) - else - missing_count=0 - fi - - # Get total file count - total_files=$(find src/ tests/ include/ apps/ benchmarks/ -regex ".*\.\(hpp\|cpp\|c\|h\)$" 2>/dev/null | wc -l) - - echo "" - echo "=== License Check Summary ===" - echo "Total files checked: $total_files" - echo "Files with missing/invalid license headers: $missing_count" - - if [ $missing_count -gt 0 ]; then - echo "" - echo "Files with incorrect license headers:" - cat /tmp/missing_files.txt | while read -r file; do - echo " - $file" - done - echo "" - echo "To fix these issues, you can run the addLicense.sh script:" - echo " bash scripts/addLicense.sh" - echo "" - echo "Or manually ensure each file starts with the exact content from:" - echo " config/licenseHeader.txt" - exit 1 - else - echo "✅ All files have the exact license header!" - fi - shell: bash - - - name: Show license header template - run: | - echo "=== License Header Template ===" - echo "Using template from: config/licenseHeader.txt" - echo "Template content:" - cat config/licenseHeader.txt - echo "=== End of Template ===" - shell: bash diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index ea03c8beb..000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: LINT - -on: - pull_request: - branches: - - '*' - paths: - - '.github/workflows/ci_build.yml' - - '.cmake/**' - - 'apps/**' - - 'include/**' - - 'src/**' - - 'integration_tests/**' - - 'tests/**' - - 'benchmarks/CMakeLists.txt' - - 'benchmarks/src/**' - - 'CMakeLists.txt' - - '.clang-tidy' - - 'scripts/clang_tidy.sh' - - '.github/workflows/lint.yml' - push: - branches: - - dev - - main - paths: - - '.github/workflows/ci_build.yml' - - '.cmake/**' - - 'apps/**' - - 'include/**' - - 'src/**' - - 'integration_tests/**' - - 'CMakeLists.txt' - - '.clang-tidy' - - 'scripts/clang_tidy.sh' - - '.github/workflows/lint.yml' - - 'benchmarks/src/**' - - 'CMakeLists.txt' - workflow_dispatch: - -jobs: - lint: - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - - - name: Install build tools - run: | - sudo apt update - sudo apt install -y gcc-13 g++-13 mold ninja-build clangd-20 - sudo update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-20 100 - shell: bash - - - name: Setup python - uses: actions/setup-python@v3 - with: - python-version: '3.12' - - - name: install python dependencies - run: | - python -m pip install numpy - shell: bash - - - name: Install clangd-tidy - run: python -m pip install clangd-tidy - shell: bash - - - name: Configure (generate compile_commands.json) - run: | - cmake -S . -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DBUILD_WITH_NATIVE=Off - env: - CC: gcc-13 - CXX: g++-13 - - - name: Run clangd-tidy - run: | - clangd-tidy $(find src/ apps/ include/ -name "*.cpp" -o -name "*.hpp" -o -name "*.tpp" 2>/dev/null) -p=build diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml deleted file mode 100644 index ff72e7ca6..000000000 --- a/.github/workflows/perf.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: Performance Gate - -# Builds the fixed-work perf benchmarks for the PR and for the base branch, -# runs each under callgrind, and fails if any regressed by more than the -# threshold. Instruction counts are deterministic, so this is not flaky. - -on: - pull_request: - branches: - - main - - dev - # Only run when something that can actually affect instruction counts - # changes — code, benchmarks, build config, submodules, or the gate - # itself. Doc / changelog / unrelated-workflow PRs skip the gate. - paths: - - 'src/**' - - 'include/**' - - 'apps/**' - - 'benchmarks/**' - - 'external/**' - - 'CMakeLists.txt' - - '.cmake/**' - - '.github/workflows/perf.yml' - - 'scripts/perf_gate.sh' - workflow_dispatch: - -permissions: - contents: read - pull-requests: write - -jobs: - perf-regression: - runs-on: ubuntu-24.04 - steps: - - name: Checkout PR - uses: actions/checkout@v4 - with: - submodules: recursive - - # Callgrind instruction counts are a pure function of the binary, which - # is a pure function of the base commit SHA. So we cache the measured - # base Ir counts keyed on that SHA: on a hit we skip the base checkout, - # build, and base callgrind run entirely (~half the workflow). - - name: Restore cached base instruction counts - id: base-ir-cache - uses: actions/cache@v4 - with: - path: base_ir.tsv - key: perf-base-ir-${{ github.event.pull_request.base.sha || github.sha }} - - - name: Checkout base branch - if: steps.base-ir-cache.outputs.cache-hit != 'true' - uses: actions/checkout@v4 - with: - ref: ${{ github.base_ref || 'dev' }} - submodules: recursive - path: base - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y gcc-13 g++-13 valgrind mold - shell: bash - - - name: Setup ccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - key: perf-${{ github.base_ref || 'dev' }} - - - name: Build PR benchmarks - run: | - cmake -S . -B build-pr \ - -DCMAKE_BUILD_TYPE=Release -DBUILD_WITH_NATIVE=Off \ - -DBUILD_WITH_PERF_BENCH=On -DBUILD_WITH_ASE=Off \ - -DBUILD_WITH_TESTS=Off -DBUILD_WITH_DOCS=Off - cmake --build build-pr --target perf_benchmarks -j$(nproc) - env: - CC: gcc-13 - CXX: g++-13 - - - name: Build base benchmarks (baseline) - if: steps.base-ir-cache.outputs.cache-hit != 'true' - run: | - cmake -S base -B build-base \ - -DCMAKE_BUILD_TYPE=Release -DBUILD_WITH_NATIVE=Off \ - -DBUILD_WITH_PERF_BENCH=On -DBUILD_WITH_ASE=Off \ - -DBUILD_WITH_TESTS=Off -DBUILD_WITH_DOCS=Off || true - cmake --build build-base --target perf_benchmarks -j$(nproc) \ - || echo "base branch has no perf benchmarks yet - baseline skipped" - env: - CC: gcc-13 - CXX: g++-13 - - - name: Compare instruction counts (gate) - id: gate - continue-on-error: true - env: - # The script loads base counts from this file if it exists (cache - # hit) and (re)writes the freshly-known counts to it at the end so - # actions/cache@v4 saves them at job end (cache miss path). - PERF_BASE_IR_FILE: base_ir.tsv - run: bash scripts/perf_gate.sh build-pr/benchmarks/perf build-base/benchmarks/perf 2 - - - name: Mint PQ Perf Bot installation token - id: app-token - if: always() && github.event_name == 'pull_request' - continue-on-error: true - uses: actions/create-github-app-token@v1 - with: - app-id: ${{ secrets.PQ_PERF_BOT_APP_ID }} - private-key: ${{ secrets.PQ_PERF_BOT_PRIVATE_KEY }} - - - name: Post / update performance comment - if: always() && github.event_name == 'pull_request' - continue-on-error: true - uses: actions/github-script@v7 - with: - # Comment author is pq-perf-bot[bot] when the token mint succeeds, - # else falls back to github-actions[bot] so the comment still posts. - github-token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} - script: | - const fs = require('fs'); - const marker = ''; - const botLogin = 'pq-perf-bot[bot]'; - let body; - try { body = fs.readFileSync('perf_report.md', 'utf8'); } - catch (e) { core.info('no perf_report.md, skipping comment'); return; } - const { owner, repo } = context.repo; - const issue_number = context.issue.number; - const { data: comments } = - await github.rest.issues.listComments({ owner, repo, issue_number }); - const existing = comments.find(c => c.body && c.body.includes(marker)); - // GitHub fixes a comment's author at creation time; if an older - // comment exists under a different author (e.g. github-actions[bot] - // before the App was wired in), delete and recreate so the visible - // author becomes pq-perf-bot[bot]. - if (existing && existing.user.login !== botLogin) { - await github.rest.issues.deleteComment( - { owner, repo, comment_id: existing.id }); - await github.rest.issues.createComment( - { owner, repo, issue_number, body }); - } else if (existing) { - await github.rest.issues.updateComment( - { owner, repo, comment_id: existing.id, body }); - } else { - await github.rest.issues.createComment( - { owner, repo, issue_number, body }); - } - - - name: Fail on regression - if: steps.gate.outcome == 'failure' - run: | - echo "performance regression detected (see the comment / summary above)" - exit 1 diff --git a/.github/workflows/pr_gate.yml b/.github/workflows/pr_gate.yml new file mode 100644 index 000000000..6ded5912d --- /dev/null +++ b/.github/workflows/pr_gate.yml @@ -0,0 +1,642 @@ +name: PR Gate + +on: + pull_request: + branches: + - dev + - main + types: [opened, reopened, synchronize] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pr-gate-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + preflight: + name: Preflight + runs-on: ubuntu-24.04 + timeout-minutes: 5 + outputs: + base_sha: ${{ steps.changes.outputs.base_sha }} + build: ${{ steps.changes.outputs.build }} + head_sha: ${{ steps.changes.outputs.head_sha }} + lint_all: ${{ steps.changes.outputs.lint_all }} + lto: ${{ steps.changes.outputs.lto }} + perf: ${{ steps.changes.outputs.perf }} + + steps: + - name: Checkout pull request + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Classify changed paths + id: changes + env: + EVENT_BASE_SHA: ${{ github.event.pull_request.base.sha }} + EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + + base_sha="$EVENT_BASE_SHA" + head_sha="$EVENT_HEAD_SHA" + if [[ -z "$base_sha" || -z "$head_sha" ]]; then + base_sha="$(git rev-parse HEAD^)" + head_sha="$(git rev-parse HEAD)" + fi + + build=false + cpp=false + lint_all=false + lto=false + perf=false + workflow=false + changed_file="$RUNNER_TEMP/pq-changed-files.txt" + git diff --name-only "$base_sha...$head_sha" > "$changed_file" + + while IFS= read -r path; do + [[ -n "$path" ]] || continue + + case "$path" in + .github/workflows/pr_gate.yml|.github/workflows/extended_ci.yml) + build=true + cpp=true + lto=true + perf=true + ;; + esac + + case "$path" in + .github/workflows/*) + workflow=true + ;; + esac + + case "$path" in + .clang-tidy|.cmake/*|.github/ci/*|.gitmodules|apps/*|benchmarks/CMakeLists.txt|benchmarks/src/*|external/*|include/*|integration_tests/*|src/*|tests/*|CMakeLists.txt|scripts/clang_tidy.sh) + build=true + ;; + esac + + case "$path" in + .clang-tidy|scripts/clang_tidy.sh) + lint_all=true + ;; + esac + + case "$path" in + .cmake/*|.gitmodules|apps/*|external/*|include/*|src/*|CMakeLists.txt) + lto=true + ;; + esac + + case "$path" in + *.c|*.cc|*.cpp|*.cxx|*.h|*.hh|*.hpp|*.hxx|*.tpp|.clang-format|config/licenseHeader.txt|scripts/addLicense.sh) + cpp=true + ;; + esac + + case "$path" in + .cmake/*|apps/*|benchmarks/*|external/*|include/*|src/*|CMakeLists.txt|scripts/perf_gate.sh) + perf=true + ;; + esac + done < "$changed_file" + + { + echo "base_sha=$base_sha" + echo "build=$build" + echo "cpp=$cpp" + echo "head_sha=$head_sha" + echo "lint_all=$lint_all" + echo "lto=$lto" + echo "perf=$perf" + echo "workflow=$workflow" + } >> "$GITHUB_OUTPUT" + + { + echo "### Changed paths" + echo + echo "build=$build, cpp=$cpp, lint_all=$lint_all, lto=$lto, perf=$perf, workflow=$workflow" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Test changelog tooling + run: python3 -m unittest discover -s scripts/tests -p 'test_*.py' + + - name: Check workflow syntax + if: steps.changes.outputs.workflow == 'true' + uses: docker://rhysd/actionlint:1.7.12 + with: + args: >- + -color -shellcheck= + -ignore=runner.*codecov/codecov-action@v3.*too.old + + - name: Check changelog fragment + if: >- + github.event_name == 'pull_request' && + github.base_ref == 'dev' && + (github.event.pull_request.head.repo.full_name != github.repository || + github.head_ref != 'main') + env: + BASE_SHA: ${{ steps.changes.outputs.base_sha }} + HEAD_SHA: ${{ steps.changes.outputs.head_sha }} + run: python3 scripts/check_changelog_fragment.py "$BASE_SHA" "$HEAD_SHA" + + - name: Set up Python for formatting + if: steps.changes.outputs.cpp == 'true' + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install clang-format + if: steps.changes.outputs.cpp == 'true' + run: python -m pip install clang-format==20.1.3 + + - name: Check changed C++ formatting + if: steps.changes.outputs.cpp == 'true' + env: + BASE_SHA: ${{ steps.changes.outputs.base_sha }} + HEAD_SHA: ${{ steps.changes.outputs.head_sha }} + run: | + set -euo pipefail + clang-format --style=file --dump-config >/dev/null + git-clang-format \ + --diff \ + --binary "$(command -v clang-format)" \ + --extensions c,cc,cpp,cxx,h,hh,hpp,hxx,tpp \ + "$BASE_SHA" \ + "$HEAD_SHA" \ + -- apps benchmarks include src tests + + - name: Check license headers + if: steps.changes.outputs.cpp == 'true' + run: | + set -euo pipefail + + header_lines="$(wc -l < config/licenseHeader.txt)" + expected_header="$(cat config/licenseHeader.txt)" + missing=() + while IFS= read -r file; do + file_header="$(head -n "$header_lines" "$file")" + if [[ "$file_header" != "$expected_header" ]]; then + missing+=("$file") + fi + done < <( + git ls-files apps benchmarks include src tests | + grep -E '\.(c|cc|cpp|cxx|h|hh|hpp|hxx|tpp)$' + ) + + if (( ${#missing[@]} > 0 )); then + printf 'Files with missing or invalid license headers:\n' >&2 + printf ' %s\n' "${missing[@]}" >&2 + exit 1 + fi + + echo "All tracked C++ files have the expected license header." + + release: + name: x86 Release and integration + if: ${{ needs.preflight.outputs.build == 'true' }} + needs: preflight + runs-on: ubuntu-24.04 + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v4 + + - name: Install build tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-13 g++-13 mold ninja-build + + - name: Set up ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: pr-x86-release + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install build dependencies + run: python -m pip install numpy + + - name: Build and run unit tests + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_NATIVE=OFF + cmake --build build --parallel "$(nproc)" + ctest --test-dir build --output-on-failure + env: + CC: gcc-13 + CXX: g++-13 + + - name: Set up Conda + uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: false + python-version: '3.12' + activate-environment: test-env + channels: default, conda-forge + auto-activate-base: false + + - name: Restore integration environment + id: conda-cache + uses: actions/cache@v4 + with: + path: ${{ env.CONDA }}/envs/test-env + key: >- + conda-integration-${{ runner.arch }}-${{ + hashFiles('.github/ci/integration-conda.txt', + '.github/ci/integration-requirements.txt') }} + + - name: Install integration dependencies + if: steps.conda-cache.outputs.cache-hit != 'true' + run: | + conda install -n test-env -y --file .github/ci/integration-conda.txt + conda run -n test-env \ + python -m pip install -r .github/ci/integration-requirements.txt + + - name: Run integration tests + run: conda run -n test-env pytest integration_tests -v + + debug-quality: + name: x86 Debug, coverage, and changed lint + if: ${{ needs.preflight.outputs.build == 'true' }} + needs: preflight + runs-on: ubuntu-24.04 + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install build and lint tools + run: | + sudo apt-get update + sudo apt-get install -y \ + clangd-20 gcc-13 g++-13 mold + sudo update-alternatives \ + --install /usr/bin/clangd clangd /usr/bin/clangd-20 100 + + - name: Set up ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: pr-x86-debug-quality + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: python -m pip install clangd-tidy coverage gcovr numpy + + - name: Build tests and coverage + run: | + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_GCOVR=ON \ + -DBUILD_WITH_NATIVE=OFF + cmake --build build --parallel "$(nproc)" + cmake --build build --target coverage_xml + env: + CC: gcc-13 + CXX: g++-13 + + - name: Lint changed C++ files + env: + BASE_SHA: ${{ needs.preflight.outputs.base_sha }} + run: >- + scripts/clang_tidy.sh + --base "$BASE_SHA" + --build-dir build + --jobs 2 + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + fail_ci_if_error: true + gcov: true + gcov_executable: gcov-13 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + lint-config: + name: Full lint for lint configuration + if: ${{ needs.preflight.outputs.lint_all == 'true' }} + needs: preflight + runs-on: ubuntu-24.04 + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v4 + + - name: Install build and lint tools + run: | + sudo apt-get update + sudo apt-get install -y \ + clangd-20 gcc-13 g++-13 mold ninja-build + sudo update-alternatives \ + --install /usr/bin/clangd clangd /usr/bin/clangd-20 100 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: python -m pip install clangd-tidy numpy tqdm + + - name: Configure compile database + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_NATIVE=OFF + env: + CC: gcc-13 + CXX: g++-13 + + - name: Lint all production C++ files + run: >- + scripts/clang_tidy.sh + --all + --build-dir build + --jobs 2 + + arm-release: + name: ARM Release + if: ${{ needs.preflight.outputs.build == 'true' }} + needs: preflight + runs-on: ubuntu-24.04-arm + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v4 + + - name: Install build tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-13 g++-13 mold ninja-build + + - name: Set up ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: pr-arm-release + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install build dependencies + run: python -m pip install numpy + + - name: Build and run unit tests + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_NATIVE=OFF + cmake --build build --parallel "$(nproc)" + ctest --test-dir build --output-on-failure + env: + CC: gcc-13 + CXX: g++-13 + + lto-smoke: + name: Static LTO smoke + if: ${{ needs.preflight.outputs.lto == 'true' }} + needs: preflight + runs-on: ubuntu-24.04 + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v4 + + - name: Install build tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-13 g++-13 mold ninja-build + + - name: Set up ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: static-lto + + - name: Build executable with static LTO + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_WITH_ASE=OFF \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_LTO=ON \ + -DBUILD_WITH_NATIVE=OFF \ + -DBUILD_WITH_TESTS=OFF + cmake --build build --target PQ --parallel "$(nproc)" + build/apps/PQ --version + env: + CC: gcc-13 + CXX: g++-13 + + performance: + name: Performance regression + if: ${{ needs.preflight.outputs.perf == 'true' }} + needs: preflight + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout pull request + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Restore cached base instruction counts + id: base-ir-cache + uses: actions/cache@v4 + with: + path: base_ir.tsv + key: perf-base-ir-${{ needs.preflight.outputs.base_sha }} + + - name: Checkout exact base commit + if: steps.base-ir-cache.outputs.cache-hit != 'true' + uses: actions/checkout@v4 + with: + ref: ${{ needs.preflight.outputs.base_sha }} + submodules: recursive + path: base + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y gcc-13 g++-13 valgrind mold + + - name: Set up ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: perf-${{ github.base_ref || 'dev' }} + + - name: Build pull-request benchmarks + run: | + cmake -S . -B build-pr \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_WITH_ASE=OFF \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_NATIVE=OFF \ + -DBUILD_WITH_PERF_BENCH=ON \ + -DBUILD_WITH_TESTS=OFF + cmake --build build-pr --target perf_benchmarks --parallel "$(nproc)" + env: + CC: gcc-13 + CXX: g++-13 + + - name: Build base benchmarks + if: steps.base-ir-cache.outputs.cache-hit != 'true' + run: | + cmake -S base -B build-base \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_WITH_ASE=OFF \ + -DBUILD_WITH_DOCS=OFF \ + -DBUILD_WITH_NATIVE=OFF \ + -DBUILD_WITH_PERF_BENCH=ON \ + -DBUILD_WITH_TESTS=OFF || true + cmake --build build-base --target perf_benchmarks \ + --parallel "$(nproc)" || \ + echo "Base commit has no performance benchmarks; baseline skipped." + env: + CC: gcc-13 + CXX: g++-13 + + - name: Compare instruction counts + id: gate + continue-on-error: true + env: + PERF_BASE_IR_FILE: base_ir.tsv + run: >- + bash scripts/perf_gate.sh + build-pr/benchmarks/perf + build-base/benchmarks/perf + 2 + + - name: Mint PQ Perf Bot token + id: app-token + if: github.event_name == 'pull_request' + continue-on-error: true + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.PQ_PERF_BOT_APP_ID }} + private-key: ${{ secrets.PQ_PERF_BOT_PRIVATE_KEY }} + + - name: Post or update performance comment + if: github.event_name == 'pull_request' + continue-on-error: true + uses: actions/github-script@v7 + with: + github-token: >- + ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + const marker = ''; + const botLogin = 'pq-perf-bot[bot]'; + let body; + try { + body = fs.readFileSync('perf_report.md', 'utf8'); + } catch (error) { + core.info('no perf_report.md, skipping comment'); + return; + } + const { owner, repo } = context.repo; + const issue_number = context.issue.number; + const { data: comments } = + await github.rest.issues.listComments({ owner, repo, issue_number }); + const existing = comments.find( + comment => comment.body && comment.body.includes(marker) + ); + if (existing && existing.user.login !== botLogin) { + await github.rest.issues.deleteComment( + { owner, repo, comment_id: existing.id } + ); + await github.rest.issues.createComment( + { owner, repo, issue_number, body } + ); + } else if (existing) { + await github.rest.issues.updateComment( + { owner, repo, comment_id: existing.id, body } + ); + } else { + await github.rest.issues.createComment( + { owner, repo, issue_number, body } + ); + } + + - name: Fail on regression + if: steps.gate.outcome == 'failure' + run: | + echo "Performance regression detected; see the PR comment or summary." + exit 1 + + result: + name: Required result + if: always() + needs: + - preflight + - release + - debug-quality + - lint-config + - arm-release + - lto-smoke + - performance + runs-on: ubuntu-latest + timeout-minutes: 2 + + steps: + - name: Verify required job results + env: + ARM_RELEASE: ${{ needs.arm-release.result }} + DEBUG_QUALITY: ${{ needs.debug-quality.result }} + LINT_CONFIG: ${{ needs.lint-config.result }} + LTO_SMOKE: ${{ needs.lto-smoke.result }} + PERFORMANCE: ${{ needs.performance.result }} + PREFLIGHT: ${{ needs.preflight.result }} + RELEASE: ${{ needs.release.result }} + run: | + set -euo pipefail + + failed=false + results=( + "preflight=$PREFLIGHT" + "release=$RELEASE" + "debug-quality=$DEBUG_QUALITY" + "lint-config=$LINT_CONFIG" + "arm-release=$ARM_RELEASE" + "lto-smoke=$LTO_SMOKE" + "performance=$PERFORMANCE" + ) + + for entry in "${results[@]}"; do + name="${entry%%=*}" + result="${entry#*=}" + echo "$name: $result" + if [[ "$result" != "success" && "$result" != "skipped" ]]; then + failed=true + fi + done + + if [[ "$failed" == "true" ]]; then + exit 1 + fi diff --git a/changes/developer/ci.consolidate-pr-gate.md b/changes/developer/ci.consolidate-pr-gate.md new file mode 100644 index 000000000..22a7b905d --- /dev/null +++ b/changes/developer/ci.consolidate-pr-gate.md @@ -0,0 +1,3 @@ +- Consolidate pull-request checks into one cancellable gate and move exhaustive build variants to extended CI. +- Keep the required gate present for every pull request and filter heavy work in its preflight job. +- Update coverage and documentation workflows to supported action runtimes. diff --git a/codecov.yml b/codecov.yml index c75bebdc0..d33efc600 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,5 +11,11 @@ ignore: - src/engine/*.cpp # just one combining all engine methods - tested by integration tests - include/engine/*.hpp # just one combining all engine methods - tested by integration tests - src/QM/** +coverage: + status: + project: + default: + target: auto + threshold: 0.01 codecov: token: bf914a3c-fad4-4ea0-936e-5fdf8a80e211 diff --git a/scripts/clang_tidy.sh b/scripts/clang_tidy.sh index 2d1573566..ad14241b7 100755 --- a/scripts/clang_tidy.sh +++ b/scripts/clang_tidy.sh @@ -1,56 +1,103 @@ #!/usr/bin/env bash -set -o pipefail +set -eo pipefail -LOGFILE="clangd-tidy-report.log" - -# check if log file exists and make a backup if it does -if [[ -f "$LOGFILE" ]]; then - mv "$LOGFILE" "${LOGFILE}.bak" -fi +all_files=false +base_ref="origin/dev" +build_dir="." +jobs=1 -# Only stdout goes to the log file; stderr (where --tqdm draws its -# progress bar via carriage returns) stays on the terminal only, so -# the log file doesn't fill up with \r-based redraw noise. -exec > >(tee "$LOGFILE") +usage() { + cat <<'EOF' +Usage: scripts/clang_tidy.sh [options] -echo "Clangd-Tidy:" +Options: + --all Check every tracked production C/C++ file. + --base Compare HEAD with this revision (default: origin/dev). + --build-dir Directory containing compile_commands.json (default: .). + --jobs Number of concurrent clangd-tidy workers (default: 1). + -h, --help Show this help. +EOF +} -all_files=false while [[ $# -gt 0 ]]; do case "$1" in --all) all_files=true shift ;; + --base) + [[ $# -ge 2 ]] || { echo "--base requires a revision" >&2; exit 2; } + base_ref="$2" + shift 2 + ;; + --build-dir) + [[ $# -ge 2 ]] || { echo "--build-dir requires a path" >&2; exit 2; } + build_dir="$2" + shift 2 + ;; + --jobs) + [[ $# -ge 2 ]] || { echo "--jobs requires a count" >&2; exit 2; } + jobs="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; *) - echo "Unknown option: $1" - exit 1 + echo "Unknown option: $1" >&2 + usage >&2 + exit 2 ;; esac done +if [[ ! "$jobs" =~ ^[1-9][0-9]*$ ]]; then + echo "--jobs must be a positive integer" >&2 + exit 2 +fi + +if [[ ! -f "$build_dir/compile_commands.json" ]]; then + echo "Missing $build_dir/compile_commands.json; configure CMake first." >&2 + exit 2 +fi + +LOGFILE="clangd-tidy-report.log" +if [[ -f "$LOGFILE" ]]; then + mv "$LOGFILE" "${LOGFILE}.bak" +fi + +# Keep tqdm redraws on stderr instead of filling the report with carriage returns. +exec > >(tee "$LOGFILE") + +echo "Clangd-Tidy:" + files=() if $all_files; then - echo " Mode: all tracked C++ files" + echo " Mode: all tracked production C++ files" while IFS= read -r f; do + [[ "$f" =~ \.(c|cc|cpp|cxx|h|hh|hpp|hxx|tpp)$ ]] || continue [[ -f "$f" ]] && files+=("$f") - done < <(git ls-files '*.cpp' '*.cxx' '*.cc' '*.c' '*.h' '*.hpp' '*.hxx' -- ':!external') + done < <(git ls-files apps include src) else - echo " Mode: changed files since origin/dev" - while IFS=$'\t' read -r status old new; do + merge_base="$(git merge-base HEAD "$base_ref")" + echo " Mode: changed files since $base_ref" + while IFS=$'\t' read -r status first second; do case "$status" in D) ;; - R*) [[ -f "$new" ]] && files+=("$new") ;; - *) [[ -f "$old" ]] && files+=("$old") ;; + R*) [[ -f "$second" ]] && files+=("$second") ;; + *) [[ -f "$first" ]] && files+=("$first") ;; esac - done < <(git diff --name-status "$(git merge-base HEAD origin/dev)") + done < <(git diff --name-status --find-renames "$merge_base...HEAD") - # Filter to C++ files only (changed mode may include non-source files) - # and exclude anything under external/ + # Match the full-lint scope: production C++ files only. cpp_files=() for f in "${files[@]}"; do - [[ "$f" == external/* ]] && continue - [[ "$f" =~ \.(cpp|cxx|cc|c|h|hpp|hxx)$ ]] && cpp_files+=("$f") + case "$f" in + apps/*|include/*|src/*) ;; + *) continue ;; + esac + [[ "$f" =~ \.(c|cc|cpp|cxx|h|hh|hpp|hxx|tpp)$ ]] && cpp_files+=("$f") done files=("${cpp_files[@]}") fi @@ -61,4 +108,4 @@ if [[ ${#files[@]} -eq 0 ]]; then fi echo " Files: ${#files[@]}" -clangd-tidy "${files[@]}" -p=. --tqdm -j1 +clangd-tidy "${files[@]}" -p="$build_dir" --tqdm -j"$jobs"