mirror of
https://github.com/nlohmann/json.git
synced 2026-10-28 00:35:43 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
087df7b0c2 | ||
|
|
eae3c8217b | ||
|
|
960c234cc6 | ||
|
|
b6e24d19a9 |
No files matched your search
@@ -154,6 +154,15 @@ and each page's title (H1) and declaration by
|
||||
[`docs/docset/generate_docset.py`](https://github.com/nlohmann/json/blob/develop/docs/docset/generate_docset.py);
|
||||
`make build` reports API pages that cannot be classified.
|
||||
|
||||
A pull request that changes only the documentation, Markdown files, GitHub metadata, or license files skips the
|
||||
compiler workflows (Ubuntu, macOS, Windows); the documentation and license checks still run. See
|
||||
[`tools/check_path_filters`](https://github.com/nlohmann/json/tree/develop/tools/check_path_filters) for the list of
|
||||
skipped files and what to do when a compiler job starts reading one of them.
|
||||
|
||||
Most compiler jobs run only once a maintainer adds the label "🚀 ready to merge" to a pull request; until then, a
|
||||
smaller set of jobs runs on every push. See
|
||||
[How CI runs](https://json.nlohmann.me/community/quality_assurance/#how-ci-runs) for the sets and when they run.
|
||||
|
||||
### Amalgamate the source code
|
||||
|
||||
The single-header files
|
||||
|
||||
@@ -7,6 +7,25 @@ on:
|
||||
- master
|
||||
- release/*
|
||||
pull_request:
|
||||
# Skip pull requests that change only files no job here reads. The list is checked by
|
||||
# tools/check_path_filters/check_path_filters.py (repository_checks.yml); change both together.
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '!**.md'
|
||||
- '!.github/*'
|
||||
- '!.github/ISSUE_TEMPLATE/**'
|
||||
- '!.github/external_ci/**'
|
||||
- '!.github/workflows/**'
|
||||
- '!.reuse/**'
|
||||
- '!LICENSES/**'
|
||||
- '!CITATION.cff'
|
||||
- '!REUSE.toml'
|
||||
# read by tools/check_build_options in the ci_meson_install job (ubuntu.yml)
|
||||
- 'docs/mkdocs/docs/integration/package_managers.md'
|
||||
# built by the swiftpm job (macos.yml)
|
||||
- 'docs/mkdocs/docs/integration/swift/**'
|
||||
- '.github/workflows/macos.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
name: Repository checks
|
||||
|
||||
# Checks that concern files the compiler workflows (ubuntu.yml, macos.yml,
|
||||
# windows.yml) skip with their path filter: the documentation, the license
|
||||
# information, and the CI configuration itself. This workflow has no path filter.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- master
|
||||
- release/*
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check_ci_configuration:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check the path filters of the compiler workflows
|
||||
run: python3 tools/check_path_filters/check_path_filters.py
|
||||
- name: Check the job sets of the compiler workflows and their documentation
|
||||
run: |
|
||||
python3 -m venv venv
|
||||
venv/bin/pip install --quiet -r tools/ci_jobs/requirements.txt
|
||||
venv/bin/python tools/ci_jobs/ci_jobs.py
|
||||
|
||||
ci_reuse_compliance:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_reuse_compliance
|
||||
|
||||
ci_test_documentation:
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# the git-revision-date-localized plugin needs the history; a shallow clone makes the strict build fail
|
||||
fetch-depth: ${{ matrix.target == 'ci_test_build_documentation' && '0' || '1' }}
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
if: matrix.target == 'ci_test_documentation_mermaid'
|
||||
with:
|
||||
node-version: 24
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
+422
-262
@@ -7,17 +7,62 @@ on:
|
||||
- master
|
||||
- release/*
|
||||
pull_request:
|
||||
# labeled: the "🚀 ready to merge" label starts the full set (see below)
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
# Skip pull requests that change only files no job here reads. The list is checked by
|
||||
# tools/check_path_filters/check_path_filters.py (repository_checks.yml); change both together.
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '!**.md'
|
||||
- '!.github/*'
|
||||
- '!.github/ISSUE_TEMPLATE/**'
|
||||
- '!.github/external_ci/**'
|
||||
- '!.github/workflows/**'
|
||||
- '!.reuse/**'
|
||||
- '!LICENSES/**'
|
||||
- '!CITATION.cff'
|
||||
- '!REUSE.toml'
|
||||
# read by tools/check_build_options in the ci_meson_install job (ubuntu.yml)
|
||||
- 'docs/mkdocs/docs/integration/package_managers.md'
|
||||
# built by the swiftpm job (macos.yml)
|
||||
- 'docs/mkdocs/docs/integration/swift/**'
|
||||
- '.github/workflows/ubuntu.yml'
|
||||
schedule:
|
||||
# the nightly set (see below), on develop
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# A run triggered by any other label gets a group of its own, so it does not
|
||||
# cancel the run of the last push; its jobs are skipped.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'schedule' && 'nightly' || github.ref || github.run_id }}${{ github.event.action == 'labeled' && github.event.label.name != '🚀 ready to merge' && format('-{0}', github.run_id) || '' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# The jobs are split into three sets (see "How CI runs" in
|
||||
# docs/mkdocs/docs/community/quality_assurance.md):
|
||||
#
|
||||
# - PR set: every pull request push and every push; chosen because these jobs
|
||||
# caught 92 % of the failing commits in Sep/Oct 2026.
|
||||
# - full set: after the PR set passed, on pushes and on pull requests with the
|
||||
# "🚀 ready to merge" label; every remaining compiler, version and standard.
|
||||
# - nightly set: slow tools and rare compilers, nightly on develop, on pushes
|
||||
# to master and release/*, and when started by hand.
|
||||
#
|
||||
# A matrix whose entries belong to different sets is split into one job per
|
||||
# set; the jobs share their steps through YAML anchors. tools/ci_jobs checks
|
||||
# that every job belongs to exactly one set.
|
||||
|
||||
jobs:
|
||||
###########################################################################
|
||||
# PR set
|
||||
###########################################################################
|
||||
|
||||
ci_test_gcc:
|
||||
if: &pr_set ${{ github.event_name != 'schedule' && (github.event.action != 'labeled' || github.event.label.name == '🚀 ready to merge') }}
|
||||
runs-on: ubuntu-latest
|
||||
container: gcc:latest
|
||||
steps:
|
||||
@@ -32,6 +77,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_gcc
|
||||
|
||||
ci_meson_install:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -76,7 +122,342 @@ jobs:
|
||||
cmake -S tests/cmake_import/project -B build-import-split -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-split
|
||||
cmake --build build-import-split
|
||||
|
||||
ci_static_analysis_ubuntu:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_cpplint]
|
||||
steps: &static_analysis_ubuntu_steps
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install Valgrind
|
||||
if: matrix.target == 'ci_test_valgrind'
|
||||
run: sudo apt-get update ; sudo apt-get install -y valgrind
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_static_analysis_clang:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:22
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_clang, ci_clang_tidy, ci_test_clang_sanitizer, ci_single_binaries]
|
||||
steps: &static_analysis_clang_steps
|
||||
- name: Install git, clang-tools, iwyu (ci_single_binaries), and unzip
|
||||
run: apt-get update ; apt-get install -y git clang-tools iwyu unzip
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_cmake_options:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:24.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noglobaludls]
|
||||
steps: &cmake_options_steps
|
||||
- name: Install build-essential
|
||||
run: apt-get update ; apt-get install -y build-essential unzip wget git
|
||||
# the CMake 3.5.0 release binary that ci_cmake_flags downloads links
|
||||
# against libidn.so.11, which Ubuntu 24.04 no longer ships
|
||||
- name: Install libidn11 for CMake 3.5.0
|
||||
if: matrix.target == 'ci_cmake_flags'
|
||||
run: |
|
||||
wget -q http://archive.ubuntu.com/ubuntu/pool/main/libi/libidn/libidn11_1.33-2.2ubuntu2_amd64.deb
|
||||
dpkg -i libidn11_1.33-2.2ubuntu2_amd64.deb
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_test_compilers_gcc_old:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['4.8']
|
||||
# official gcc:4.8/4.9/5/6 images fail to check out code (too old for
|
||||
# actions/checkout); install the old compilers on top of official ubuntu:20.04
|
||||
# instead, mirroring what the (now retired) custom json-ci image did.
|
||||
container: ubuntu:20.04
|
||||
steps: &compilers_gcc_old_steps
|
||||
- name: Install g++-${{ matrix.compiler }}
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends software-properties-common ca-certificates gnupg make git
|
||||
# add-apt-repository resolves the PPA through the Launchpad API,
|
||||
# which intermittently times out or fails the team lookup (the plain
|
||||
# "deb ..." sources below never hit Launchpad and never flake).
|
||||
# Retry with backoff so a transient Launchpad blip does not fail CI.
|
||||
for attempt in 1 2 3 4 5; do
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && break
|
||||
echo "::warning::add-apt-repository ppa:ubuntu-toolchain-r/test failed (attempt ${attempt}/5); retrying"
|
||||
sleep $((attempt * 10))
|
||||
done
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic universe"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial universe"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates universe"
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends g++-${{ matrix.compiler }}
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
|
||||
|
||||
ci_test_compilers_clang:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['3.4']
|
||||
container: silkeh/clang:${{ matrix.compiler }}
|
||||
steps: &compilers_clang_steps
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Disable filesystem support (std::filesystem is unusable) for clang 7 / 8 / 9 / 10
|
||||
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
|
||||
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_standards_gcc:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
container: gcc:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [26]
|
||||
steps: &standards_gcc_steps
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
|
||||
|
||||
ci_test_standards_clang:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [26]
|
||||
stdlib: [libcxx]
|
||||
steps: &standards_clang_steps
|
||||
- name: Install git and unzip
|
||||
run: apt-get update ; apt-get install -y git unzip
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build with libc++
|
||||
run: cmake --build build --target ci_test_clang_libcxx_cxx${{ matrix.standard }}
|
||||
if: ${{ matrix.stdlib == 'libcxx' }}
|
||||
- name: Build with libstdc++
|
||||
run: cmake --build build --target ci_test_clang_cxx${{ matrix.standard }}
|
||||
if: ${{ matrix.stdlib == 'libstdcxx' }}
|
||||
|
||||
ci_emscripten:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install emscripten
|
||||
uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
###########################################################################
|
||||
# full set: starts after the PR set passed
|
||||
###########################################################################
|
||||
|
||||
ci_static_analysis_ubuntu_full:
|
||||
needs: &pr_set_jobs [ci_test_gcc, ci_meson_install, ci_static_analysis_ubuntu, ci_static_analysis_clang, ci_cmake_options, ci_test_compilers_gcc_old, ci_test_compilers_clang, ci_test_standards_gcc, ci_test_standards_clang, ci_emscripten]
|
||||
if: &full_set ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, '🚀 ready to merge') }}
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_single_header, ci_cppcheck, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata]
|
||||
steps: *static_analysis_ubuntu_steps
|
||||
|
||||
ci_static_analysis_clang_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:22
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_clang_analyze]
|
||||
steps: *static_analysis_clang_steps
|
||||
|
||||
ci_cmake_options_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:24.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_cmake_flags, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_delete_deprecated_functions, ci_test_no_thread_local]
|
||||
steps: *cmake_options_steps
|
||||
|
||||
ci_test_compilers_gcc_old_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['4.9', '5', '6']
|
||||
container: ubuntu:20.04
|
||||
steps: *compilers_gcc_old_steps
|
||||
|
||||
ci_test_compilers_gcc_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# older GCC docker images (4, 5, 6) fail to check out code
|
||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
|
||||
container: gcc:${{ matrix.compiler }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_compilers_clang_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
||||
container: silkeh/clang:${{ matrix.compiler }}
|
||||
steps: *compilers_clang_steps
|
||||
|
||||
ci_test_standards_gcc_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
container: gcc:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [11, 14, 17, 20, 23]
|
||||
steps: *standards_gcc_steps
|
||||
|
||||
ci_test_standards_clang_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [11, 14, 17, 20, 23, 26]
|
||||
stdlib: [libcxx, libstdcxx]
|
||||
exclude:
|
||||
# in the PR set
|
||||
- standard: 26
|
||||
stdlib: libcxx
|
||||
steps: *standards_clang_steps
|
||||
|
||||
ci_module_cpp20:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
strategy:
|
||||
matrix:
|
||||
container: ['gcc:latest', 'silkeh/clang:latest']
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# The module test uses `import std;`, which needs CMake's experimental
|
||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||
- name: Get pinned CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
with:
|
||||
cmakeVersion: 4.3.4
|
||||
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
||||
# ships none), and the image's libc++ module manifest has a broken relative
|
||||
# path — repoint it at the real module sources.
|
||||
- name: Use libc++ and fix its module manifest path (Clang)
|
||||
if: matrix.container == 'silkeh/clang:latest'
|
||||
run: |
|
||||
echo "CXXFLAGS=-stdlib=libc++" >> "$GITHUB_ENV"
|
||||
mkdir -p /usr/lib/share && ln -sf /usr/lib/llvm-*/share/libc++ /usr/lib/share/libc++
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_module_cpp20
|
||||
|
||||
###########################################################################
|
||||
# nightly set
|
||||
###########################################################################
|
||||
|
||||
ci_infer:
|
||||
if: &nightly_set ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref != 'refs/heads/develop') }}
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
@@ -105,76 +486,16 @@ jobs:
|
||||
path: ${{ github.workspace }}/build/build_infer/infer-out/report.txt
|
||||
if-no-files-found: ignore
|
||||
|
||||
ci_static_analysis_ubuntu:
|
||||
ci_static_analysis_ubuntu_nightly:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install Valgrind
|
||||
if: matrix.target == 'ci_test_valgrind'
|
||||
run: sudo apt-get update ; sudo apt-get install -y valgrind
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_static_analysis_clang:
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:22
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_clang, ci_clang_tidy, ci_test_clang_sanitizer, ci_clang_analyze, ci_single_binaries]
|
||||
steps:
|
||||
- name: Install git, clang-tools, iwyu (ci_single_binaries), and unzip
|
||||
run: apt-get update ; apt-get install -y git clang-tools iwyu unzip
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_cmake_options:
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:24.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_delete_deprecated_functions, ci_test_no_thread_local]
|
||||
steps:
|
||||
- name: Install build-essential
|
||||
run: apt-get update ; apt-get install -y build-essential unzip wget git
|
||||
# the CMake 3.5.0 release binary that ci_cmake_flags downloads links
|
||||
# against libidn.so.11, which Ubuntu 24.04 no longer ships
|
||||
- name: Install libidn11 for CMake 3.5.0
|
||||
if: matrix.target == 'ci_cmake_flags'
|
||||
run: |
|
||||
wget -q http://archive.ubuntu.com/ubuntu/pool/main/libi/libidn/libidn11_1.33-2.2ubuntu2_amd64.deb
|
||||
dpkg -i libidn11_1.33-2.2ubuntu2_amd64.deb
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
target: [ci_test_valgrind]
|
||||
steps: *static_analysis_ubuntu_steps
|
||||
|
||||
ci_test_coverage:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
@@ -208,128 +529,8 @@ jobs:
|
||||
path-to-lcov: ${{ github.workspace }}/build/json.info.filtered.noexcept
|
||||
fail-on-error: false
|
||||
|
||||
ci_test_compilers_gcc_old:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['4.8', '4.9', '5', '6']
|
||||
# official gcc:4.8/4.9/5/6 images fail to check out code (too old for
|
||||
# actions/checkout); install the old compilers on top of official ubuntu:20.04
|
||||
# instead, mirroring what the (now retired) custom json-ci image did.
|
||||
container: ubuntu:20.04
|
||||
steps:
|
||||
- name: Install g++-${{ matrix.compiler }}
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends software-properties-common ca-certificates gnupg make git
|
||||
# add-apt-repository resolves the PPA through the Launchpad API,
|
||||
# which intermittently times out or fails the team lookup (the plain
|
||||
# "deb ..." sources below never hit Launchpad and never flake).
|
||||
# Retry with backoff so a transient Launchpad blip does not fail CI.
|
||||
for attempt in 1 2 3 4 5; do
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && break
|
||||
echo "::warning::add-apt-repository ppa:ubuntu-toolchain-r/test failed (attempt ${attempt}/5); retrying"
|
||||
sleep $((attempt * 10))
|
||||
done
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic universe"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial universe"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates universe"
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends g++-${{ matrix.compiler }}
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
|
||||
|
||||
ci_test_compilers_gcc:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# older GCC docker images (4, 5, 6) fail to check out code
|
||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
|
||||
container: gcc:${{ matrix.compiler }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_compilers_clang:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
||||
container: silkeh/clang:${{ matrix.compiler }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Disable filesystem support (std::filesystem is unusable) for clang 7 / 8 / 9 / 10
|
||||
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
|
||||
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_standards_gcc:
|
||||
runs-on: ubuntu-latest
|
||||
container: gcc:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [11, 14, 17, 20, 23, 26]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
|
||||
|
||||
ci_test_standards_clang:
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [11, 14, 17, 20, 23, 26]
|
||||
stdlib: [libcxx, libstdcxx]
|
||||
steps:
|
||||
- name: Install git and unzip
|
||||
run: apt-get update ; apt-get install -y git unzip
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build with libc++
|
||||
run: cmake --build build --target ci_test_clang_libcxx_cxx${{ matrix.standard }}
|
||||
if: ${{ matrix.stdlib == 'libcxx' }}
|
||||
- name: Build with libstdc++
|
||||
run: cmake --build build --target ci_test_clang_cxx${{ matrix.standard }}
|
||||
if: ${{ matrix.stdlib == 'libstdcxx' }}
|
||||
|
||||
ci_cuda_example:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -352,37 +553,8 @@ jobs:
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_cuda_example
|
||||
|
||||
ci_module_cpp20:
|
||||
strategy:
|
||||
matrix:
|
||||
container: ['gcc:latest', 'silkeh/clang:latest']
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# The module test uses `import std;`, which needs CMake's experimental
|
||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||
- name: Get pinned CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
with:
|
||||
cmakeVersion: 4.3.4
|
||||
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
||||
# ships none), and the image's libc++ module manifest has a broken relative
|
||||
# path — repoint it at the real module sources.
|
||||
- name: Use libc++ and fix its module manifest path (Clang)
|
||||
if: matrix.container == 'silkeh/clang:latest'
|
||||
run: |
|
||||
echo "CXXFLAGS=-stdlib=libc++" >> "$GITHUB_ENV"
|
||||
mkdir -p /usr/lib/share && ln -sf /usr/lib/llvm-*/share/libc++ /usr/lib/share/libc++
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_module_cpp20
|
||||
|
||||
ci_icpc:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-latest
|
||||
# Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is
|
||||
# Intel's own last officially published image that still includes it.
|
||||
@@ -402,6 +574,7 @@ jobs:
|
||||
run: cmake --build build --target ci_icpc
|
||||
|
||||
ci_icpx:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-latest
|
||||
container: intel/oneapi-hpckit:latest
|
||||
steps:
|
||||
@@ -416,6 +589,7 @@ jobs:
|
||||
run: cmake --build build --target ci_icpx
|
||||
|
||||
ci_nvhpc:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-latest
|
||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||
steps:
|
||||
@@ -429,54 +603,10 @@ jobs:
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_nvhpc
|
||||
|
||||
ci_emscripten:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install emscripten
|
||||
uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
ci_test_documentation:
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# the git-revision-date-localized plugin needs the history; a shallow clone makes the strict build fail
|
||||
fetch-depth: ${{ matrix.target == 'ci_test_build_documentation' && '0' || '1' }}
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
if: matrix.target == 'ci_test_documentation_mermaid'
|
||||
with:
|
||||
node-version: 24
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
# Native Linux ARM64 runner with the latest GCC. The ubuntu-24.04-arm label is
|
||||
# only available for public repositories.
|
||||
ci_test_arm64:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-24.04-arm
|
||||
container: gcc:latest
|
||||
steps:
|
||||
@@ -491,3 +621,33 @@ jobs:
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
# A failure of a scheduled run opens an issue, or comments on the open one.
|
||||
nightly_report:
|
||||
needs: [ci_infer, ci_static_analysis_ubuntu_nightly, ci_test_coverage, ci_cuda_example, ci_icpc, ci_icpx, ci_nvhpc, ci_test_arm64]
|
||||
if: ${{ always() && github.event_name == 'schedule' && contains(needs.*.result, 'failure') }}
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Open or update the nightly failure issue
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
WORKFLOW: ${{ github.workflow }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
title="Nightly CI failure (${WORKFLOW})"
|
||||
body="The nightly run of ${WORKFLOW} on ${SHA:0:10} failed: ${RUN_URL}"
|
||||
number=$(gh issue list --state open --search "in:title \"${title}\"" --json number,title --jq "map(select(.title == \"${title}\")) | .[0].number // empty")
|
||||
if [ -n "$number" ]; then
|
||||
gh issue comment "$number" --body "$body"
|
||||
else
|
||||
gh issue create --title "$title" --body "$body"
|
||||
fi
|
||||
@@ -7,6 +7,25 @@ on:
|
||||
- master
|
||||
- release/*
|
||||
pull_request:
|
||||
# Skip pull requests that change only files no job here reads. The list is checked by
|
||||
# tools/check_path_filters/check_path_filters.py (repository_checks.yml); change both together.
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '!**.md'
|
||||
- '!.github/*'
|
||||
- '!.github/ISSUE_TEMPLATE/**'
|
||||
- '!.github/external_ci/**'
|
||||
- '!.github/workflows/**'
|
||||
- '!.reuse/**'
|
||||
- '!LICENSES/**'
|
||||
- '!CITATION.cff'
|
||||
- '!REUSE.toml'
|
||||
# read by tools/check_build_options in the ci_meson_install job (ubuntu.yml)
|
||||
- 'docs/mkdocs/docs/integration/package_managers.md'
|
||||
# built by the swiftpm job (macos.yml)
|
||||
- 'docs/mkdocs/docs/integration/swift/**'
|
||||
- '.github/workflows/windows.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -13,6 +13,72 @@ describe each of them.
|
||||
|
||||
<p class="nj-caption" markdown>The checks grouped by the stage at which they run, from push or pull request through the merge into develop to a release. The badge on each card says whether the check runs on every push or PR, on a schedule, or continuously.</p>
|
||||
|
||||
## How CI runs
|
||||
|
||||
Not every job runs for every change. The compiler jobs on Ubuntu are split into three sets, so a pull request gets fast
|
||||
feedback and the long jobs run when they matter:
|
||||
|
||||
| Set | When it runs | What it contains |
|
||||
|-------------|-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| PR set | every push to a pull request, and every push to `develop`, `master`, and `release/*` | GCC and Clang with all warnings, Clang-Tidy, sanitizers, IWYU, cpplint, the oldest and newest GCC and Clang, common CMake options, Meson, Emscripten; with the checks that always run, they would have caught at least 92 % of the failing commits of September and October 2026 |
|
||||
| full set | after the PR set passed: on pull requests with the label :rocket: ready to merge, and on every push | every other compiler, version, C++ standard, and CMake option, the Clang Static Analyzer, Cppcheck, and the C++20 module tests |
|
||||
| nightly set | every night on `develop`, on every push to `master` and `release/*`, and when started by hand | Infer, Valgrind, coverage, the Intel and NVIDIA compilers, CUDA, and ARM64; a failure opens an issue |
|
||||
|
||||
The jobs on macOS and Windows, the documentation and license checks, and the amalgamation check run on every push and
|
||||
pull request. A pull request that changes only the documentation, Markdown files, GitHub metadata, or license files
|
||||
skips the compiler workflows (see [`tools/check_path_filters`](https://github.com/nlohmann/json/tree/develop/tools/check_path_filters)).
|
||||
|
||||
A maintainer adds the label :rocket: ready to merge when a pull request is ready; the full set then runs, and again on
|
||||
every later push to that pull request. A pull request is merged only when the full set passed.
|
||||
|
||||
??? abstract "Jobs of the compiler workflows and their sets"
|
||||
|
||||
The table is generated by [`tools/ci_jobs`](https://github.com/nlohmann/json/tree/develop/tools/ci_jobs), which
|
||||
also checks in CI that it is up to date and that every job belongs to exactly one set.
|
||||
|
||||
<!-- ci_jobs: begin -->
|
||||
| Set | Workflow | Job | Configurations |
|
||||
|-----|----------|-----|----------------|
|
||||
| PR set | ubuntu.yml | `ci_test_gcc` | - |
|
||||
| PR set | ubuntu.yml | `ci_meson_install` | - |
|
||||
| PR set | ubuntu.yml | `ci_static_analysis_ubuntu` | ci_cpplint |
|
||||
| PR set | ubuntu.yml | `ci_static_analysis_clang` | ci_test_clang; ci_clang_tidy; ci_test_clang_sanitizer; ci_single_binaries |
|
||||
| PR set | ubuntu.yml | `ci_cmake_options` | ci_test_diagnostics; ci_test_diagnostic_positions; ci_test_noexceptions; ci_test_noglobaludls |
|
||||
| PR set | ubuntu.yml | `ci_test_compilers_gcc_old` | 4.8 |
|
||||
| PR set | ubuntu.yml | `ci_test_compilers_clang` | 3.4 |
|
||||
| PR set | ubuntu.yml | `ci_test_standards_gcc` | 26 |
|
||||
| PR set | ubuntu.yml | `ci_test_standards_clang` | 26, libcxx |
|
||||
| PR set | ubuntu.yml | `ci_emscripten` | - |
|
||||
| full set | ubuntu.yml | `ci_static_analysis_ubuntu_full` | ci_test_single_header; ci_cppcheck; ci_reproducible_tests; ci_non_git_tests; ci_offline_testdata |
|
||||
| full set | ubuntu.yml | `ci_static_analysis_clang_full` | ci_clang_analyze |
|
||||
| full set | ubuntu.yml | `ci_cmake_options_full` | ci_cmake_flags; ci_test_noimplicitconversions; ci_test_legacycomparison; ci_test_disableenumserialization; ci_test_disabletuplereferenceconversion; ci_test_skiplibraryversioncheck; ci_test_simdutf; ci_test_strict_nul_handling; ci_test_delete_deprecated_functions; ci_test_no_thread_local |
|
||||
| full set | ubuntu.yml | `ci_test_compilers_gcc_old_full` | 4.9; 5; 6 |
|
||||
| full set | ubuntu.yml | `ci_test_compilers_gcc_full` | 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; latest |
|
||||
| full set | ubuntu.yml | `ci_test_compilers_clang_full` | 3.5; 3.6; 3.7; 3.8; 3.9; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15-bullseye; 16; 17; 18; 19; 20; 21; 22; latest |
|
||||
| full set | ubuntu.yml | `ci_test_standards_gcc_full` | 11; 14; 17; 20; 23 |
|
||||
| full set | ubuntu.yml | `ci_test_standards_clang_full` | 11, libcxx; 11, libstdcxx; 14, libcxx; 14, libstdcxx; 17, libcxx; 17, libstdcxx; 20, libcxx; 20, libstdcxx; 23, libcxx; 23, libstdcxx; 26, libstdcxx |
|
||||
| full set | ubuntu.yml | `ci_module_cpp20` | gcc:latest; silkeh/clang:latest |
|
||||
| nightly set | ubuntu.yml | `ci_infer` | - |
|
||||
| nightly set | ubuntu.yml | `ci_static_analysis_ubuntu_nightly` | ci_test_valgrind |
|
||||
| nightly set | ubuntu.yml | `ci_test_coverage` | - |
|
||||
| nightly set | ubuntu.yml | `ci_cuda_example` | 11.8.0; 12.1.1; 12.6.3 |
|
||||
| nightly set | ubuntu.yml | `ci_icpc` | - |
|
||||
| nightly set | ubuntu.yml | `ci_icpx` | - |
|
||||
| nightly set | ubuntu.yml | `ci_nvhpc` | - |
|
||||
| nightly set | ubuntu.yml | `ci_test_arm64` | - |
|
||||
| every push and PR | macos.yml | `macos-15` | 16.0; 16.1; 16.2; 16.3; 16.4; 26.0.1; 26.1.1; 26.2; 26.3 |
|
||||
| every push and PR | macos.yml | `macos-26` | 26.4.1; 26.5; 26.6 |
|
||||
| every push and PR | macos.yml | `xcode_standards` | 11; 14; 17; 20; 23; 26 |
|
||||
| every push and PR | macos.yml | `swiftpm` | - |
|
||||
| every push and PR | windows.yml | `mingw` | x64; x86 |
|
||||
| every push and PR | windows.yml | `msvc` | Debug, Win32, default; Debug, Win32, latest; Debug, x64, default; Debug, x64, latest; Release, Win32, default; Release, Win32, latest; Release, x64, default; Release, x64, latest |
|
||||
| every push and PR | windows.yml | `msvc-vs2026` | Debug, Win32, default; Debug, Win32, latest; Debug, x64, default; Debug, x64, latest; Release, Win32, default; Release, Win32, latest; Release, x64, default; Release, x64, latest |
|
||||
| every push and PR | windows.yml | `msvc-arm64` | Debug; Release |
|
||||
| every push and PR | windows.yml | `clang` | 11.0.1; 12.0.1; 13.0.1; 14.0.6; 15.0.7; 16.0.6; 18.1.8; 19.1.7; 20.1.8 |
|
||||
| every push and PR | windows.yml | `clang-cl` | Win32; x64 |
|
||||
| every push and PR | windows.yml | `ci_module_cpp20` | - |
|
||||
<!-- ci_jobs: end -->
|
||||
|
||||
## C++ language compliance and compiler compatibility
|
||||
|
||||
!!! success "Requirement: Compiler support"
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
<svg class="nj-fig nj-exceptions-qa nj-exceptions-qa--wide" viewBox="0 0 760 808" role="img" aria-labelledby="nj-qa-title-w nj-qa-desc-w" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="nj-fig nj-exceptions-qa nj-exceptions-qa--wide" viewBox="0 0 760 862" role="img" aria-labelledby="nj-qa-title-w nj-qa-desc-w" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="nj-qa-title-w">Quality assurance pipeline</title>
|
||||
<desc id="nj-qa-desc-w">Every push or pull request starts these checks in parallel: Build matrix (every push / PR): 50+ compilers: G​CC, Clang, M​SVC, AppleClang, Intel, NVIDIA, ... C++11, 14, 17, 20, 23, and 26 libc++ and libstdc++ warnings: Clang -Weverything, G​CC 300+ warnings; Unit tests (doctest) (every push / PR): every public A​PI function every exception id and message all Unicode code points single header and split headers custom number, string, array, and object types; Coverage (every push / PR): unit tests cover all lines of the code base; Static analysis (every push / PR, scheduled): Clang-Tidy, Cppcheck, Infer Clang Static Analyzer (52 checkers), I​WYU, Codacy CodeQL, Semgrep, Flawfinder (also scheduled); Dynamic analysis (every push / PR): runtime assertions Valgrind (Memcheck) address and undefined behavior sanitizers, integer overflow, nullability violations; Fuzzing (every PR, continuous): CIFuzz on every pull request OSS-Fuzz: billions of inputs every crash fixed with a reproducing unit test; Style (every push / PR): Artistic Style (astyle), enforced in the C​I cpplint with 61 rules; Docs and examples (every push / PR, scheduled): every documentation example is tested; changed output fails site built with warnings as errors; links checked regularly (scheduled); Integration, supply chain (every push / PR, scheduled): CMake 3.5, 3.31.6, and 4.0.0 Meson, Swift Package Manager dependency review on every PR Dependabot daily, OpenSSF Scorecard, R​EUSE. When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.</desc>
|
||||
<desc id="nj-qa-desc-w">A push or pull request starts these checks; the badges say when each runs: Build matrix (every push / PR, before merge): 50+ compilers: G​CC, Clang, M​SVC, AppleClang, Intel, NVIDIA, ... C++11, 14, 17, 20, 23, and 26 libc++ and libstdc++ warnings: Clang -Weverything, G​CC 300+ warnings a subset on every push and PR, all before merge; Intel, NVIDIA, C​UDA, and ARM64 nightly; Unit tests (doctest) (every push / PR): every public A​PI function every exception id and message all Unicode code points single header and split headers custom number, string, array, and object types; Coverage (nightly): unit tests cover all lines of the code base; Static analysis (every push / PR, scheduled): Clang-Tidy, Cppcheck, Infer Clang Static Analyzer (52 checkers), I​WYU, Codacy CodeQL, Semgrep, Flawfinder (also scheduled); Infer nightly; Dynamic analysis (every push / PR, nightly): runtime assertions Valgrind (Memcheck), nightly address and undefined behavior sanitizers, integer overflow, nullability violations; Fuzzing (every PR, continuous): CIFuzz on every pull request OSS-Fuzz: billions of inputs every crash fixed with a reproducing unit test; Style (every push / PR): Artistic Style (astyle), enforced in the C​I cpplint with 61 rules; Docs and examples (every push / PR, scheduled): every documentation example is tested; changed output fails site built with warnings as errors; links checked regularly (scheduled); Integration, supply chain (every push / PR, scheduled): CMake 3.5, 3.31.6, and 4.0.0 Meson, Swift Package Manager dependency review on every PR Dependabot daily, OpenSSF Scorecard, R​EUSE. When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.</desc>
|
||||
<defs><marker id="nj-qa-arrow-w" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fg-head" d="M0 1 L10 5 L0 9 z"/></marker></defs>
|
||||
<rect class="nj-exceptions-groupbox" x="12" y="70" width="736" height="594" rx="12"/>
|
||||
<rect class="nj-exceptions-groupbox" x="12" y="70" width="736" height="648" rx="12"/>
|
||||
<rect class="nj-exceptions-stage" x="265" y="8" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="380" y="30">push or pull request</text>
|
||||
<line class="nj-exceptions-edge" x1="380" y1="42" x2="380" y2="68" marker-end="url(#nj-qa-arrow-w)"/>
|
||||
<text class="nj-exceptions-group" x="26" y="94">checks, run in parallel on GitHub Actions and AppVeyor</text>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="110" width="228" height="184" rx="8"/>
|
||||
<rect class="nj-exceptions-box" x="26" y="110" width="228" height="238" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="135">Build matrix</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="146" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="160">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="156.5" y="146" width="91.6" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="202.3" y="160">before merge</text>
|
||||
<text class="nj-exceptions-line" x="40" y="188">50+ compilers: G​CC, Clang, M​SVC,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="206">AppleClang, Intel, NVIDIA, ...</text>
|
||||
<text class="nj-exceptions-line" x="40" y="224">C++11, 14, 17, 20, 23, and 26</text>
|
||||
<text class="nj-exceptions-line" x="40" y="242">libc++ and libstdc++</text>
|
||||
<text class="nj-exceptions-line" x="40" y="260">warnings: Clang -Weverything,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="278">G​CC 300+ warnings</text>
|
||||
<text class="nj-exceptions-line" x="40" y="296">a subset on every push and PR,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="314">all before merge; Intel, NVIDIA,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="332">C​UDA, and ARM64 nightly</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="266" y="110" width="228" height="184" rx="8"/>
|
||||
<rect class="nj-exceptions-box" x="266" y="110" width="228" height="238" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="280" y="135">Unit tests (doctest)</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="146" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="160">every push / PR</text>
|
||||
@@ -32,209 +37,218 @@
|
||||
<text class="nj-exceptions-line" x="280" y="278">and object types</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="506" y="110" width="228" height="184" rx="8"/>
|
||||
<rect class="nj-exceptions-box" x="506" y="110" width="228" height="238" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="520" y="135">Coverage</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="146" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="575.25" y="160">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="520" y="146" width="60.1" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="550.05" y="160">nightly</text>
|
||||
<text class="nj-exceptions-line" x="520" y="188">unit tests cover all lines</text>
|
||||
<text class="nj-exceptions-line" x="520" y="206">of the code base</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="306" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="331">Static analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="342" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="356">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="342" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="356">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="384">Clang-Tidy, Cppcheck, Infer</text>
|
||||
<text class="nj-exceptions-line" x="40" y="402">Clang Static Analyzer (52</text>
|
||||
<text class="nj-exceptions-line" x="40" y="420">checkers), I​WYU, Codacy</text>
|
||||
<text class="nj-exceptions-line" x="40" y="438">CodeQL, Semgrep, Flawfinder</text>
|
||||
<text class="nj-exceptions-line" x="40" y="456">(also scheduled)</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="360" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="385">Static analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="396" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="410">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="396" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="410">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="438">Clang-Tidy, Cppcheck, Infer</text>
|
||||
<text class="nj-exceptions-line" x="40" y="456">Clang Static Analyzer (52</text>
|
||||
<text class="nj-exceptions-line" x="40" y="474">checkers), I​WYU, Codacy</text>
|
||||
<text class="nj-exceptions-line" x="40" y="492">CodeQL, Semgrep, Flawfinder</text>
|
||||
<text class="nj-exceptions-line" x="40" y="510">(also scheduled); Infer nightly</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="266" y="306" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="280" y="331">Dynamic analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="342" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="356">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="280" y="384">runtime assertions</text>
|
||||
<text class="nj-exceptions-line" x="280" y="402">Valgrind (Memcheck)</text>
|
||||
<text class="nj-exceptions-line" x="280" y="420">address and undefined behavior</text>
|
||||
<text class="nj-exceptions-line" x="280" y="438">sanitizers, integer overflow,</text>
|
||||
<text class="nj-exceptions-line" x="280" y="456">nullability violations</text>
|
||||
<rect class="nj-exceptions-box" x="266" y="360" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="280" y="385">Dynamic analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="396" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="410">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="396.5" y="396" width="60.1" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="426.55" y="410">nightly</text>
|
||||
<text class="nj-exceptions-line" x="280" y="438">runtime assertions</text>
|
||||
<text class="nj-exceptions-line" x="280" y="456">Valgrind (Memcheck), nightly</text>
|
||||
<text class="nj-exceptions-line" x="280" y="474">address and undefined behavior</text>
|
||||
<text class="nj-exceptions-line" x="280" y="492">sanitizers, integer overflow,</text>
|
||||
<text class="nj-exceptions-line" x="280" y="510">nullability violations</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="506" y="306" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="520" y="331">Fuzzing</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="342" width="66.4" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="553.2" y="356">every PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--cont" x="592.4" y="342" width="79" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--cont" x="631.9" y="356">continuous</text>
|
||||
<text class="nj-exceptions-line" x="520" y="384">CIFuzz on every pull request</text>
|
||||
<text class="nj-exceptions-line" x="520" y="402">OSS-Fuzz: billions of inputs</text>
|
||||
<text class="nj-exceptions-line" x="520" y="420">every crash fixed with a</text>
|
||||
<text class="nj-exceptions-line" x="520" y="438">reproducing unit test</text>
|
||||
<rect class="nj-exceptions-box" x="506" y="360" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="520" y="385">Fuzzing</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="396" width="66.4" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="553.2" y="410">every PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--cont" x="592.4" y="396" width="79" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--cont" x="631.9" y="410">continuous</text>
|
||||
<text class="nj-exceptions-line" x="520" y="438">CIFuzz on every pull request</text>
|
||||
<text class="nj-exceptions-line" x="520" y="456">OSS-Fuzz: billions of inputs</text>
|
||||
<text class="nj-exceptions-line" x="520" y="474">every crash fixed with a</text>
|
||||
<text class="nj-exceptions-line" x="520" y="492">reproducing unit test</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="484" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="509">Style</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="520" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="534">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="562">Artistic Style (astyle),</text>
|
||||
<text class="nj-exceptions-line" x="40" y="580">enforced in the C​I</text>
|
||||
<text class="nj-exceptions-line" x="40" y="598">cpplint with 61 rules</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="538" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="563">Style</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="574" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="588">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="616">Artistic Style (astyle),</text>
|
||||
<text class="nj-exceptions-line" x="40" y="634">enforced in the C​I</text>
|
||||
<text class="nj-exceptions-line" x="40" y="652">cpplint with 61 rules</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="266" y="484" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="280" y="509">Docs and examples</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="520" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="534">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="396.5" y="520" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="432.85" y="534">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="280" y="562">every documentation example is</text>
|
||||
<text class="nj-exceptions-line" x="280" y="580">tested; changed output fails</text>
|
||||
<text class="nj-exceptions-line" x="280" y="598">site built with warnings</text>
|
||||
<text class="nj-exceptions-line" x="280" y="616">as errors; links checked</text>
|
||||
<text class="nj-exceptions-line" x="280" y="634">regularly (scheduled)</text>
|
||||
<rect class="nj-exceptions-box" x="266" y="538" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="280" y="563">Docs and examples</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="574" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="588">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="396.5" y="574" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="432.85" y="588">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="280" y="616">every documentation example is</text>
|
||||
<text class="nj-exceptions-line" x="280" y="634">tested; changed output fails</text>
|
||||
<text class="nj-exceptions-line" x="280" y="652">site built with warnings</text>
|
||||
<text class="nj-exceptions-line" x="280" y="670">as errors; links checked</text>
|
||||
<text class="nj-exceptions-line" x="280" y="688">regularly (scheduled)</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="506" y="484" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="520" y="509">Integration, supply chain</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="520" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="575.25" y="534">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="636.5" y="520" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="672.85" y="534">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="520" y="562">CMake 3.5, 3.31.6, and 4.0.0</text>
|
||||
<text class="nj-exceptions-line" x="520" y="580">Meson, Swift Package Manager</text>
|
||||
<text class="nj-exceptions-line" x="520" y="598">dependency review on every PR</text>
|
||||
<text class="nj-exceptions-line" x="520" y="616">Dependabot daily, OpenSSF</text>
|
||||
<text class="nj-exceptions-line" x="520" y="634">Scorecard, R​EUSE</text>
|
||||
<rect class="nj-exceptions-box" x="506" y="538" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="520" y="563">Integration, supply chain</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="574" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="575.25" y="588">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="636.5" y="574" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="672.85" y="588">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="520" y="616">CMake 3.5, 3.31.6, and 4.0.0</text>
|
||||
<text class="nj-exceptions-line" x="520" y="634">Meson, Swift Package Manager</text>
|
||||
<text class="nj-exceptions-line" x="520" y="652">dependency review on every PR</text>
|
||||
<text class="nj-exceptions-line" x="520" y="670">Dependabot daily, OpenSSF</text>
|
||||
<text class="nj-exceptions-line" x="520" y="688">Scorecard, R​EUSE</text>
|
||||
</g>
|
||||
<line class="nj-exceptions-edge" x1="380" y1="664" x2="380" y2="690" marker-end="url(#nj-qa-arrow-w)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="265" y="692" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="380" y="714">merge into develop</text>
|
||||
<line class="nj-exceptions-edge" x1="380" y1="726" x2="380" y2="752" marker-end="url(#nj-qa-arrow-w)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="230" y="754" width="300" height="46" rx="23"/>
|
||||
<text class="nj-exceptions-stagetext" x="380" y="776">release</text>
|
||||
<text class="nj-exceptions-stagesub" x="380" y="792">no open vulnerability alert</text>
|
||||
<line class="nj-exceptions-edge" x1="380" y1="718" x2="380" y2="744" marker-end="url(#nj-qa-arrow-w)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="265" y="746" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="380" y="768">merge into develop</text>
|
||||
<line class="nj-exceptions-edge" x1="380" y1="780" x2="380" y2="806" marker-end="url(#nj-qa-arrow-w)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="230" y="808" width="300" height="46" rx="23"/>
|
||||
<text class="nj-exceptions-stagetext" x="380" y="830">release</text>
|
||||
<text class="nj-exceptions-stagesub" x="380" y="846">no open vulnerability alert</text>
|
||||
</svg>
|
||||
<svg class="nj-fig nj-exceptions-qa nj-exceptions-qa--narrow" viewBox="0 0 360 1786" role="img" aria-labelledby="nj-qa-title-n nj-qa-desc-n" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="nj-fig nj-exceptions-qa nj-exceptions-qa--narrow" viewBox="0 0 360 1840" role="img" aria-labelledby="nj-qa-title-n nj-qa-desc-n" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="nj-qa-title-n">Quality assurance pipeline</title>
|
||||
<desc id="nj-qa-desc-n">Every push or pull request starts these checks in parallel: Build matrix (every push / PR): 50+ compilers: G​CC, Clang, M​SVC, AppleClang, Intel, NVIDIA, ... C++11, 14, 17, 20, 23, and 26 libc++ and libstdc++ warnings: Clang -Weverything, G​CC 300+ warnings; Unit tests (doctest) (every push / PR): every public A​PI function every exception id and message all Unicode code points single header and split headers custom number, string, array, and object types; Coverage (every push / PR): unit tests cover all lines of the code base; Static analysis (every push / PR, scheduled): Clang-Tidy, Cppcheck, Infer Clang Static Analyzer (52 checkers), I​WYU, Codacy CodeQL, Semgrep, Flawfinder (also scheduled); Dynamic analysis (every push / PR): runtime assertions Valgrind (Memcheck) address and undefined behavior sanitizers, integer overflow, nullability violations; Fuzzing (every PR, continuous): CIFuzz on every pull request OSS-Fuzz: billions of inputs every crash fixed with a reproducing unit test; Style (every push / PR): Artistic Style (astyle), enforced in the C​I cpplint with 61 rules; Docs and examples (every push / PR, scheduled): every documentation example is tested; changed output fails site built with warnings as errors; links checked regularly (scheduled); Integration, supply chain (every push / PR, scheduled): CMake 3.5, 3.31.6, and 4.0.0 Meson, Swift Package Manager dependency review on every PR Dependabot daily, OpenSSF Scorecard, R​EUSE. When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.</desc>
|
||||
<desc id="nj-qa-desc-n">A push or pull request starts these checks; the badges say when each runs: Build matrix (every push / PR, before merge): 50+ compilers: G​CC, Clang, M​SVC, AppleClang, Intel, NVIDIA, ... C++11, 14, 17, 20, 23, and 26 libc++ and libstdc++ warnings: Clang -Weverything, G​CC 300+ warnings a subset on every push and PR, all before merge; Intel, NVIDIA, C​UDA, and ARM64 nightly; Unit tests (doctest) (every push / PR): every public A​PI function every exception id and message all Unicode code points single header and split headers custom number, string, array, and object types; Coverage (nightly): unit tests cover all lines of the code base; Static analysis (every push / PR, scheduled): Clang-Tidy, Cppcheck, Infer Clang Static Analyzer (52 checkers), I​WYU, Codacy CodeQL, Semgrep, Flawfinder (also scheduled); Infer nightly; Dynamic analysis (every push / PR, nightly): runtime assertions Valgrind (Memcheck), nightly address and undefined behavior sanitizers, integer overflow, nullability violations; Fuzzing (every PR, continuous): CIFuzz on every pull request OSS-Fuzz: billions of inputs every crash fixed with a reproducing unit test; Style (every push / PR): Artistic Style (astyle), enforced in the C​I cpplint with 61 rules; Docs and examples (every push / PR, scheduled): every documentation example is tested; changed output fails site built with warnings as errors; links checked regularly (scheduled); Integration, supply chain (every push / PR, scheduled): CMake 3.5, 3.31.6, and 4.0.0 Meson, Swift Package Manager dependency review on every PR Dependabot daily, OpenSSF Scorecard, R​EUSE. When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.</desc>
|
||||
<defs><marker id="nj-qa-arrow-n" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fg-head" d="M0 1 L10 5 L0 9 z"/></marker></defs>
|
||||
<rect class="nj-exceptions-groupbox" x="12" y="70" width="336" height="1572" rx="12"/>
|
||||
<rect class="nj-exceptions-groupbox" x="12" y="70" width="336" height="1626" rx="12"/>
|
||||
<rect class="nj-exceptions-stage" x="65" y="8" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="180" y="30">push or pull request</text>
|
||||
<line class="nj-exceptions-edge" x1="180" y1="42" x2="180" y2="68" marker-end="url(#nj-qa-arrow-n)"/>
|
||||
<text class="nj-exceptions-group" x="26" y="94">checks, run in parallel</text>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="110" width="308" height="184" rx="8"/>
|
||||
<rect class="nj-exceptions-box" x="26" y="110" width="308" height="238" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="135">Build matrix</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="146" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="160">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="156.5" y="146" width="91.6" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="202.3" y="160">before merge</text>
|
||||
<text class="nj-exceptions-line" x="40" y="188">50+ compilers: G​CC, Clang, M​SVC,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="206">AppleClang, Intel, NVIDIA, ...</text>
|
||||
<text class="nj-exceptions-line" x="40" y="224">C++11, 14, 17, 20, 23, and 26</text>
|
||||
<text class="nj-exceptions-line" x="40" y="242">libc++ and libstdc++</text>
|
||||
<text class="nj-exceptions-line" x="40" y="260">warnings: Clang -Weverything,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="278">G​CC 300+ warnings</text>
|
||||
<text class="nj-exceptions-line" x="40" y="296">a subset on every push and PR,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="314">all before merge; Intel, NVIDIA,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="332">C​UDA, and ARM64 nightly</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="306" width="308" height="184" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="331">Unit tests (doctest)</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="342" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="356">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="384">every public A​PI function</text>
|
||||
<text class="nj-exceptions-line" x="40" y="402">every exception id and message</text>
|
||||
<text class="nj-exceptions-line" x="40" y="420">all Unicode code points</text>
|
||||
<text class="nj-exceptions-line" x="40" y="438">single header and split headers</text>
|
||||
<text class="nj-exceptions-line" x="40" y="456">custom number, string, array,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="474">and object types</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="360" width="308" height="184" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="385">Unit tests (doctest)</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="396" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="410">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="438">every public A​PI function</text>
|
||||
<text class="nj-exceptions-line" x="40" y="456">every exception id and message</text>
|
||||
<text class="nj-exceptions-line" x="40" y="474">all Unicode code points</text>
|
||||
<text class="nj-exceptions-line" x="40" y="492">single header and split headers</text>
|
||||
<text class="nj-exceptions-line" x="40" y="510">custom number, string, array,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="528">and object types</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="502" width="308" height="112" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="527">Coverage</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="538" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="552">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="580">unit tests cover all lines</text>
|
||||
<text class="nj-exceptions-line" x="40" y="598">of the code base</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="556" width="308" height="112" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="581">Coverage</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="40" y="592" width="60.1" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="70.05" y="606">nightly</text>
|
||||
<text class="nj-exceptions-line" x="40" y="634">unit tests cover all lines</text>
|
||||
<text class="nj-exceptions-line" x="40" y="652">of the code base</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="626" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="651">Static analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="662" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="676">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="662" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="676">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="704">Clang-Tidy, Cppcheck, Infer</text>
|
||||
<text class="nj-exceptions-line" x="40" y="722">Clang Static Analyzer (52</text>
|
||||
<text class="nj-exceptions-line" x="40" y="740">checkers), I​WYU, Codacy</text>
|
||||
<text class="nj-exceptions-line" x="40" y="758">CodeQL, Semgrep, Flawfinder</text>
|
||||
<text class="nj-exceptions-line" x="40" y="776">(also scheduled)</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="680" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="705">Static analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="716" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="730">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="716" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="730">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="758">Clang-Tidy, Cppcheck, Infer</text>
|
||||
<text class="nj-exceptions-line" x="40" y="776">Clang Static Analyzer (52</text>
|
||||
<text class="nj-exceptions-line" x="40" y="794">checkers), I​WYU, Codacy</text>
|
||||
<text class="nj-exceptions-line" x="40" y="812">CodeQL, Semgrep, Flawfinder</text>
|
||||
<text class="nj-exceptions-line" x="40" y="830">(also scheduled); Infer nightly</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="804" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="829">Dynamic analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="840" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="854">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="882">runtime assertions</text>
|
||||
<text class="nj-exceptions-line" x="40" y="900">Valgrind (Memcheck)</text>
|
||||
<text class="nj-exceptions-line" x="40" y="918">address and undefined behavior</text>
|
||||
<text class="nj-exceptions-line" x="40" y="936">sanitizers, integer overflow,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="954">nullability violations</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="858" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="883">Dynamic analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="894" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="908">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="894" width="60.1" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="186.55" y="908">nightly</text>
|
||||
<text class="nj-exceptions-line" x="40" y="936">runtime assertions</text>
|
||||
<text class="nj-exceptions-line" x="40" y="954">Valgrind (Memcheck), nightly</text>
|
||||
<text class="nj-exceptions-line" x="40" y="972">address and undefined behavior</text>
|
||||
<text class="nj-exceptions-line" x="40" y="990">sanitizers, integer overflow,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1008">nullability violations</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="982" width="308" height="148" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1007">Fuzzing</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1018" width="66.4" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="73.2" y="1032">every PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--cont" x="112.4" y="1018" width="79" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--cont" x="151.9" y="1032">continuous</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1060">CIFuzz on every pull request</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1078">OSS-Fuzz: billions of inputs</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1096">every crash fixed with a</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1114">reproducing unit test</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="1036" width="308" height="148" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1061">Fuzzing</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1072" width="66.4" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="73.2" y="1086">every PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--cont" x="112.4" y="1072" width="79" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--cont" x="151.9" y="1086">continuous</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1114">CIFuzz on every pull request</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1132">OSS-Fuzz: billions of inputs</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1150">every crash fixed with a</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1168">reproducing unit test</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="1142" width="308" height="130" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1167">Style</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1178" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1192">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1220">Artistic Style (astyle),</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1238">enforced in the C​I</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1256">cpplint with 61 rules</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="1196" width="308" height="130" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1221">Style</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1232" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1246">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1274">Artistic Style (astyle),</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1292">enforced in the C​I</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1310">cpplint with 61 rules</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="1284" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1309">Docs and examples</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1320" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1334">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="1320" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="1334">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1362">every documentation example is</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1380">tested; changed output fails</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1398">site built with warnings</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1416">as errors; links checked</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1434">regularly (scheduled)</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="1338" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1363">Docs and examples</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1374" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1388">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="1374" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="1388">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1416">every documentation example is</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1434">tested; changed output fails</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1452">site built with warnings</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1470">as errors; links checked</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1488">regularly (scheduled)</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="1462" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1487">Integration, supply chain</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1498" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1512">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="1498" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="1512">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1540">CMake 3.5, 3.31.6, and 4.0.0</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1558">Meson, Swift Package Manager</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1576">dependency review on every PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1594">Dependabot daily, OpenSSF</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1612">Scorecard, R​EUSE</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="1516" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1541">Integration, supply chain</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1552" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1566">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="1552" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="1566">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1594">CMake 3.5, 3.31.6, and 4.0.0</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1612">Meson, Swift Package Manager</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1630">dependency review on every PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1648">Dependabot daily, OpenSSF</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1666">Scorecard, R​EUSE</text>
|
||||
</g>
|
||||
<line class="nj-exceptions-edge" x1="180" y1="1642" x2="180" y2="1668" marker-end="url(#nj-qa-arrow-n)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="65" y="1670" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="180" y="1692">merge into develop</text>
|
||||
<line class="nj-exceptions-edge" x1="180" y1="1704" x2="180" y2="1730" marker-end="url(#nj-qa-arrow-n)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="30" y="1732" width="300" height="46" rx="23"/>
|
||||
<text class="nj-exceptions-stagetext" x="180" y="1754">release</text>
|
||||
<text class="nj-exceptions-stagesub" x="180" y="1770">no open vulnerability alert</text>
|
||||
<line class="nj-exceptions-edge" x1="180" y1="1696" x2="180" y2="1722" marker-end="url(#nj-qa-arrow-n)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="65" y="1724" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="180" y="1746">merge into develop</text>
|
||||
<line class="nj-exceptions-edge" x1="180" y1="1758" x2="180" y2="1784" marker-end="url(#nj-qa-arrow-n)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="30" y="1786" width="300" height="46" rx="23"/>
|
||||
<text class="nj-exceptions-stagetext" x="180" y="1808">release</text>
|
||||
<text class="nj-exceptions-stagesub" x="180" y="1824">no open vulnerability alert</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
@@ -22,19 +22,24 @@ P = "nj-exceptions-"
|
||||
TRIGGERS = {
|
||||
"push": ("every push / PR", "push"),
|
||||
"pr": ("every PR", "push"),
|
||||
"merge": ("before merge", "push"),
|
||||
"nightly": ("nightly", "sched"),
|
||||
"sched": ("scheduled", "sched"),
|
||||
"cont": ("continuous", "cont"),
|
||||
}
|
||||
|
||||
# title, triggers, lines (at most 31 characters, so they fit a card of the three-column drawing)
|
||||
LANES = [
|
||||
("Build matrix", ["push"], [
|
||||
("Build matrix", ["push", "merge"], [
|
||||
"50+ compilers: GCC, Clang, MSVC,",
|
||||
"AppleClang, Intel, NVIDIA, ...",
|
||||
"C++11, 14, 17, 20, 23, and 26",
|
||||
"libc++ and libstdc++",
|
||||
"warnings: Clang -Weverything,",
|
||||
"GCC 300+ warnings",
|
||||
"a subset on every push and PR,",
|
||||
"all before merge; Intel, NVIDIA,",
|
||||
"CUDA, and ARM64 nightly",
|
||||
]),
|
||||
("Unit tests (doctest)", ["push"], [
|
||||
"every public API function",
|
||||
@@ -44,7 +49,7 @@ LANES = [
|
||||
"custom number, string, array,",
|
||||
"and object types",
|
||||
]),
|
||||
("Coverage", ["push"], [
|
||||
("Coverage", ["nightly"], [
|
||||
"unit tests cover all lines",
|
||||
"of the code base",
|
||||
]),
|
||||
@@ -53,11 +58,11 @@ LANES = [
|
||||
"Clang Static Analyzer (52",
|
||||
"checkers), IWYU, Codacy",
|
||||
"CodeQL, Semgrep, Flawfinder",
|
||||
"(also scheduled)",
|
||||
"(also scheduled); Infer nightly",
|
||||
]),
|
||||
("Dynamic analysis", ["push"], [
|
||||
("Dynamic analysis", ["push", "nightly"], [
|
||||
"runtime assertions",
|
||||
"Valgrind (Memcheck)",
|
||||
"Valgrind (Memcheck), nightly",
|
||||
"address and undefined behavior",
|
||||
"sanitizers, integer overflow,",
|
||||
"nullability violations",
|
||||
@@ -178,7 +183,7 @@ def drawing(W, cols, suffix):
|
||||
defs = ('<defs><marker id="%s" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">'
|
||||
'<path class="fg-head" d="M0 1 L10 5 L0 9 z"/></marker></defs>' % mid)
|
||||
tid, did = "nj-qa-title-" + suffix, "nj-qa-desc-" + suffix
|
||||
desc = ("Every push or pull request starts these checks in parallel: " +
|
||||
desc = ("A push or pull request starts these checks; the badges say when each runs: " +
|
||||
"; ".join("%s (%s): %s" % (t, ", ".join(TRIGGERS[k][0] for k in trig), " ".join(lines)) for t, trig, lines in LANES) +
|
||||
". When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.")
|
||||
out = ['<svg class="nj-fig %sqa %sqa--%s" viewBox="0 0 %d %d" role="img" aria-labelledby="%s %s" xmlns="http://www.w3.org/2000/svg">'
|
||||
|
||||
@@ -21,10 +21,6 @@ python3 tests/benchmarks/json_view/compare.py --data <json_test_data directory>
|
||||
- The other libraries come from the system: pkg-config, or Homebrew (`brew install yyjson simdjson boost`). With
|
||||
`--download`, pinned releases are downloaded instead and checked against their SHA-256. Without Boost headers (or
|
||||
with `--no-boost`), the Boost.JSON columns are skipped, and the results say so.
|
||||
- `--aligned` additionally builds and runs everything with all functions aligned to 64 bytes (`-mllvm
|
||||
-align-all-functions=6` for Clang, `-falign-functions=64 -falign-loops=64` for GCC), and reports both result
|
||||
tables as "default build" and "aligned build". Code layout alone can move a result by a few percent; a difference
|
||||
that appears in only one of the two tables is a layout artifact. Off by default, which doubles the run time.
|
||||
- `--corpus file...` adds files to the corpus benchmark, e.g. those of
|
||||
[simdjson-data](https://github.com/simdjson/simdjson-data) or the
|
||||
[yyjson benchmark](https://github.com/ibireme/yyjson_benchmark).
|
||||
@@ -43,7 +39,7 @@ where `json_view` stands on another architecture; they are not meant for publica
|
||||
|
||||
## What is measured
|
||||
|
||||
`bench_view.cpp` runs these workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
|
||||
`bench_view.cpp` runs four workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
|
||||
JSON-RPC request (`rpc`):
|
||||
|
||||
| workload | what it does |
|
||||
@@ -52,7 +48,6 @@ JSON-RPC request (`rpc`):
|
||||
| traverse | parse, then visit every value, convert every number, touch every string and key |
|
||||
| select | parse, then read a few fields per record (e.g. id, user name, and retweet count of each tweet) |
|
||||
| dump | serialize a parsed document (compact) |
|
||||
| parse+dump | build a fresh document, then serialize it (compact), in one timed call |
|
||||
|
||||
`bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of
|
||||
documents. Its dump also writes the numbers as they are in the input: `json_view` with `number_format::source`, and
|
||||
@@ -77,32 +72,11 @@ call follows an untimed call of the same engine: otherwise the engine after `jso
|
||||
cleaning up the tens of thousands of nodes `json::parse` just freed (with glibc, this made `json_view` look 1.7 times
|
||||
slower on citm_catalog traverse).
|
||||
|
||||
`json_view` is an index into the source text, not a tree of owning nodes. Its like-for-like peers are yyjson and
|
||||
simdjson DOM (immutable, indexed, no per-node allocation). Boost.JSON and `json::parse` build mutable, owning DOMs and
|
||||
are reference points for what that costs, not equals; simdjson On-Demand is lazy and forward-only, so it is the other
|
||||
way round: it has no random access to pay for. All engines validate UTF-8 and syntax by default.
|
||||
|
||||
Number conversion differs: `json_view` converts integers while parsing but converts floating-point numbers lazily,
|
||||
when they are read or dumped; yyjson (default flags) and simdjson DOM convert every number while parsing. The cost of
|
||||
float conversion therefore appears in the traverse and dump rows for `json_view`, and in the parse row for the others.
|
||||
For float-heavy documents (canada), the fair reading is a sum of parse and use: the `traverse` workload already
|
||||
includes the parse, and `parse+dump` measures a fresh document and its dump in one timed call; the `dump` row alone
|
||||
leaves the conversion in `json_view` and out of the others. Eager conversion at parse time was prototyped as an opt-in
|
||||
and measured: canada traverse (with its parse) was 4-7 % faster, but parse+dump 11-30 % slower and parse 36-90 % slower. It was
|
||||
dropped, so lazy conversion is the only mode.
|
||||
|
||||
`json_view` can also be edited: `json_editable_document` has the same read API, and the `(editable)` rows in
|
||||
`bench_view` measure it on the same workloads. They are the fair counterpart of Boost.JSON and `json::parse`, which are
|
||||
mutable too; the plain `json_view` rows are the counterpart of yyjson and simdjson DOM, which are not. `bench_edit` adds
|
||||
the read-modify-write workloads. Read the tables with that in mind: an immutable-only library is not compared with an
|
||||
editable `json_view` unless it says so.
|
||||
|
||||
The engines do not all offer the same features, which the numbers should be read with:
|
||||
|
||||
| engine | document | random access | editable | notes |
|
||||
|---|---|---|---|---|
|
||||
| `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document |
|
||||
| `json_view` (editable) | index into the text plus edits | yes | yes, in place | `json_editable_document`; keeps member order and number spellings |
|
||||
| yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | |
|
||||
| simdjson DOM | immutable, parser reused | yes | no | "fresh" uses a new parser per parse |
|
||||
| simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select |
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
|
||||
// Same-feature-set benchmark: read-only JSON documents with random access.
|
||||
//
|
||||
// json_view nlohmann/json_view.hpp (fresh document per parse / reused;
|
||||
// "(editable)": json_editable_document, same read API)
|
||||
// yyjson yyjson_read(): immutable document, random access
|
||||
// json_view nlohmann/json_view.hpp (fresh document per parse / reused)
|
||||
// yyjson yyjson_read(): immutable document, random access
|
||||
// simdjson DOM dom::parser (reused, as recommended; "fresh": a new parser
|
||||
// per parse): immutable, random access
|
||||
// references (different feature sets):
|
||||
@@ -20,11 +19,7 @@
|
||||
//
|
||||
// Workloads: parse (build + free), traverse (visit everything, convert every
|
||||
// number, touch every string and key), select (a few fields per document),
|
||||
// dump (compact serialization of the parsed document), and parse+dump (a fresh
|
||||
// document, then its dump, in one timed call). json_view converts floating-point
|
||||
// numbers lazily (when read or dumped), yyjson and simdjson DOM while parsing:
|
||||
// for float-heavy documents, traverse (which includes the parse) and parse+dump
|
||||
// are the fair reading of the parse and dump rows.
|
||||
// dump (compact serialization of the parsed document).
|
||||
// All engines run interleaved in every round, each timed call after an untimed
|
||||
// one of the same engine; the best round is reported.
|
||||
#include <nlohmann/json_view.hpp>
|
||||
@@ -48,8 +43,6 @@
|
||||
|
||||
using nlohmann::json;
|
||||
using nlohmann::json_document;
|
||||
using nlohmann::json_editable_document;
|
||||
using nlohmann::json_editable_view;
|
||||
using nlohmann::json_view;
|
||||
|
||||
static volatile double g_sink;
|
||||
@@ -62,8 +55,7 @@ struct stats
|
||||
|
||||
// ---------------- traversal ----------------
|
||||
|
||||
template<class V>
|
||||
static void walk_view(V v, stats& st)
|
||||
static void walk(json_view v, stats& st)
|
||||
{
|
||||
++st.nodes;
|
||||
switch (v.type())
|
||||
@@ -72,42 +64,32 @@ static void walk_view(V v, stats& st)
|
||||
for (auto it = v.begin(); it != v.end(); ++it)
|
||||
{
|
||||
st.str += it.key().size();
|
||||
walk_view<V>(*it, st);
|
||||
walk(*it, st);
|
||||
}
|
||||
break;
|
||||
case json::value_t::array:
|
||||
for (const V e : v)
|
||||
for (const json_view e : v)
|
||||
{
|
||||
walk_view<V>(e, st);
|
||||
walk(e, st);
|
||||
}
|
||||
break;
|
||||
case json::value_t::string:
|
||||
st.str += v.get_string().size();
|
||||
break;
|
||||
case json::value_t::number_integer:
|
||||
st.num += static_cast<double>(v.template get<std::int64_t>());
|
||||
st.num += static_cast<double>(v.get<std::int64_t>());
|
||||
break;
|
||||
case json::value_t::number_unsigned:
|
||||
st.num += static_cast<double>(v.template get<std::uint64_t>());
|
||||
st.num += static_cast<double>(v.get<std::uint64_t>());
|
||||
break;
|
||||
case json::value_t::number_float:
|
||||
st.num += v.template get<double>();
|
||||
st.num += v.get<double>();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void walk(json_view v, stats& st)
|
||||
{
|
||||
walk_view<json_view>(v, st);
|
||||
}
|
||||
|
||||
static void walk(json_editable_view v, stats& st)
|
||||
{
|
||||
walk_view<json_editable_view>(v, st);
|
||||
}
|
||||
|
||||
static void walk(const json& j, stats& st)
|
||||
{
|
||||
++st.nodes;
|
||||
@@ -318,21 +300,20 @@ static void walk(const boost::json::value& v, stats& st)
|
||||
// status (one tweet): id, user.screen_name, retweet_count
|
||||
// rpc: method, params.minuend, id
|
||||
|
||||
template<class V>
|
||||
static double pick_view(const std::string& name, V r)
|
||||
static double pick(const std::string& name, json_view r)
|
||||
{
|
||||
double acc = 0;
|
||||
if (name == "twitter" || name == "status")
|
||||
{
|
||||
auto one = [&](V s)
|
||||
auto one = [&](json_view s)
|
||||
{
|
||||
acc += static_cast<double>(s["id"].template get<std::uint64_t>());
|
||||
acc += static_cast<double>(s["id"].get<std::uint64_t>());
|
||||
acc += static_cast<double>(s["user"]["screen_name"].get_string().size());
|
||||
acc += static_cast<double>(s["retweet_count"].template get<std::int64_t>());
|
||||
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
|
||||
};
|
||||
if (name == "twitter")
|
||||
{
|
||||
for (const V s : r["statuses"])
|
||||
for (const json_view s : r["statuses"])
|
||||
{
|
||||
one(s);
|
||||
}
|
||||
@@ -344,20 +325,20 @@ static double pick_view(const std::string& name, V r)
|
||||
}
|
||||
else if (name == "citm_catalog")
|
||||
{
|
||||
for (const V p : r["performances"])
|
||||
for (const json_view p : r["performances"])
|
||||
{
|
||||
acc += static_cast<double>(p["id"].template get<std::uint64_t>() + p["eventId"].template get<std::uint64_t>() + p["seatCategories"].size());
|
||||
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
|
||||
}
|
||||
acc += static_cast<double>(r["events"].size());
|
||||
}
|
||||
else if (name == "canada")
|
||||
{
|
||||
const V g = r["features"][0]["geometry"];
|
||||
const json_view g = r["features"][0]["geometry"];
|
||||
acc += static_cast<double>(r["type"].get_string().size() + g["type"].get_string().size() + g["coordinates"].size());
|
||||
}
|
||||
else if (name == "jeopardy")
|
||||
{
|
||||
for (const V q : r)
|
||||
for (const json_view q : r)
|
||||
{
|
||||
acc += q["round"].get_string() == "Final Jeopardy!" ? 1 : 0;
|
||||
acc += static_cast<double>(q["category"].get_string().size());
|
||||
@@ -366,21 +347,11 @@ static double pick_view(const std::string& name, V r)
|
||||
else if (name == "rpc")
|
||||
{
|
||||
acc += static_cast<double>(r["method"].get_string().size());
|
||||
acc += static_cast<double>(r["params"]["minuend"].template get<std::int64_t>() + r["id"].template get<std::int64_t>());
|
||||
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
static double pick(const std::string& name, json_view r)
|
||||
{
|
||||
return pick_view<json_view>(name, r);
|
||||
}
|
||||
|
||||
static double pick(const std::string& name, json_editable_view r)
|
||||
{
|
||||
return pick_view<json_editable_view>(name, r);
|
||||
}
|
||||
|
||||
static double pick(const std::string& name, const json& r)
|
||||
{
|
||||
double acc = 0;
|
||||
@@ -663,35 +634,15 @@ int main(int argc, char** argv)
|
||||
walk(json::parse(dc.text), a);
|
||||
auto d = json_document::parse(dc.text);
|
||||
walk(d.root(), b);
|
||||
auto ed = json_editable_document::parse(dc.text);
|
||||
stats e;
|
||||
walk(ed.root(), e);
|
||||
yyjson_doc* y = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
||||
walk(yyjson_doc_get_root(y), c);
|
||||
simdjson::dom::parser p;
|
||||
walk(p.parse(dc.text).value(), dd);
|
||||
const bool ok = a.nodes == b.nodes && a.nodes == e.nodes && a.str == e.str && std::fabs(a.num - e.num) <= 1e-9 * std::fabs(a.num) && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str
|
||||
const bool ok = a.nodes == b.nodes && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str
|
||||
&& std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num) && std::fabs(a.num - c.num) <= 1e-9 * std::fabs(a.num);
|
||||
const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pe = pick(dc.name, ed.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value());
|
||||
std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pe && pa == pc && pa == pd) ? "OK" : "MISMATCH");
|
||||
const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value());
|
||||
std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pc && pa == pd) ? "OK" : "MISMATCH");
|
||||
yyjson_doc_free(y);
|
||||
|
||||
// the dumps of the engines may format floats differently, so only: every dump is non-empty, and the
|
||||
// dump of json_view (also editable) and of json::parse parse back to the same number of nodes
|
||||
const std::string dj = json::parse(dc.text).dump();
|
||||
const std::string dv = d.root().dump();
|
||||
const std::string de = ed.root().dump();
|
||||
std::size_t yn = 0;
|
||||
yyjson_doc* y2 = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
||||
char* yo = yyjson_write(y2, 0, &yn);
|
||||
const bool dumps_ok = !dj.empty() && !dv.empty() && !de.empty() && yo != nullptr && yn > 0 && !simdjson::to_string(p.parse(dc.text).value()).empty();
|
||||
std::free(yo);
|
||||
yyjson_doc_free(y2);
|
||||
stats sj_nodes, sv_nodes, se_nodes;
|
||||
walk(json::parse(dj), sj_nodes);
|
||||
walk(json::parse(dv), sv_nodes);
|
||||
walk(json::parse(de), se_nodes);
|
||||
std::printf("check %-13s dump %s\n", dc.name.c_str(), (dumps_ok && sj_nodes.nodes == a.nodes && sv_nodes.nodes == a.nodes && se_nodes.nodes == a.nodes) ? "OK" : "MISMATCH");
|
||||
}
|
||||
|
||||
std::FILE* csv = std::fopen("bench_view.csv", "w");
|
||||
@@ -718,7 +669,6 @@ int main(int argc, char** argv)
|
||||
workloads.push_back({"parse", {
|
||||
{"json_view", [&] { auto d = json_document::parse(s); g_sink = static_cast<double>(d.node_count()); }},
|
||||
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
||||
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); g_sink = static_cast<double>(d.root().size()); }},
|
||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }},
|
||||
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||
@@ -730,7 +680,6 @@ int main(int argc, char** argv)
|
||||
workloads.push_back({"traverse", {
|
||||
{"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
||||
{"json_view (reused)", [&] { reused.read(s); stats st; walk(reused.root(), st); g_sink = st.num; }},
|
||||
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }},
|
||||
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }},
|
||||
@@ -742,31 +691,15 @@ int main(int argc, char** argv)
|
||||
workloads.push_back({"select", {
|
||||
{"json_view", [&] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }},
|
||||
{"json_view (reused)", [&] { reused.read(s); g_sink = pick(name, reused.root()); }},
|
||||
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); g_sink = pick(name, d.root()); }},
|
||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }},
|
||||
{"simdjson DOM", [&] { g_sink = pick(name, sj.parse(ps).value_unsafe()); }},
|
||||
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }},
|
||||
{"json::parse", [&] { json j = json::parse(s); g_sink = pick(name, j); }},
|
||||
}});
|
||||
// pipeline: a fresh document, then a dump of it, in one timed call
|
||||
// (simdjson DOM reuses its parser, as in the parse row); the traverse
|
||||
// workload above is already parse+traverse
|
||||
workloads.push_back({"parse+dump", {
|
||||
{"json_view", [&] { auto d = json_document::parse(s); std::string o = d.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); std::string o = d.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); std::size_t n = 0; char* o = yyjson_write(d, 0, &n); g_sink = static_cast<double>(n); std::free(o); yyjson_doc_free(d); }},
|
||||
{"simdjson DOM", [&] { std::string o = simdjson::to_string(sj.parse(ps).value_unsafe()); g_sink = static_cast<double>(o.size()); }},
|
||||
#if JSON_VIEW_BENCH_BOOST
|
||||
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); std::string o = boost::json::serialize(v); g_sink = static_cast<double>(o.size()); }},
|
||||
#endif
|
||||
{"json::parse", [&] { json j = json::parse(s); std::string o = j.dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
}});
|
||||
{
|
||||
// serialization of an already parsed document
|
||||
static json_document vd;
|
||||
vd.read(s);
|
||||
static json_editable_document ed = json_editable_document::parse(s);
|
||||
ed = json_editable_document::parse(s);
|
||||
static yyjson_doc* yd = nullptr;
|
||||
if (yd)
|
||||
{
|
||||
@@ -780,7 +713,6 @@ int main(int argc, char** argv)
|
||||
jd = json::parse(s);
|
||||
workloads.push_back({"dump", {
|
||||
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
{"json_view (editable)", [&] { std::string o = ed.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
||||
{"json::parse", [&] { std::string o = jd.dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
@@ -808,7 +740,7 @@ int main(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
const double ref = best[0];
|
||||
std::printf("%-13s %-14s", dc.name.c_str(), wl.first.c_str());
|
||||
std::printf("%-13s %-9s", dc.name.c_str(), wl.first.c_str());
|
||||
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||
{
|
||||
const double us = best[k] / 1e3;
|
||||
|
||||
@@ -239,8 +239,6 @@ def main():
|
||||
ap.add_argument('--download', action='store_true', help='use pinned downloads instead of system libraries')
|
||||
ap.add_argument('--no-boost', action='store_true', help='skip Boost.JSON')
|
||||
ap.add_argument('--native', action='store_true', help='compile for this CPU (-march=native / -mcpu=native)')
|
||||
ap.add_argument('--aligned', action='store_true',
|
||||
help='also build and run with all functions (and loops) aligned, to make code layout artifacts visible')
|
||||
ap.add_argument('--rounds', type=int, default=30, help='rounds of bench_view (default: 30)')
|
||||
ap.add_argument('--corpus', nargs='*', default=[], help='more files for bench_corpus')
|
||||
ap.add_argument('--build-dir', default=os.path.join(HERE, 'build'), help='where to build (default: build/ next to this script)')
|
||||
@@ -284,46 +282,32 @@ def main():
|
||||
+ ['-I' + d for d in lib.include])
|
||||
objects.append(obj)
|
||||
|
||||
cxx_version = output(cxx + ['--version'])
|
||||
is_clang = 'clang' in cxx_version.lower()
|
||||
# alignment flags: functions and (gcc) loops on 64-byte boundaries
|
||||
align_flags = ['-mllvm', '-align-all-functions=6'] if is_clang else ['-falign-functions=64', '-falign-loops=64']
|
||||
|
||||
# the builds: the default one and, with --aligned, one with aligned code
|
||||
variants = [('default build', args.build_dir, [])]
|
||||
if args.aligned:
|
||||
variants.append(('aligned build', os.path.join(args.build_dir, 'aligned'), align_flags))
|
||||
binaries = {}
|
||||
for bench in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
||||
exe = os.path.join(args.build_dir, bench)
|
||||
run(cxx + flags + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe])
|
||||
binaries[bench] = exe
|
||||
|
||||
# run: bench_view on its documents, bench_corpus on those and the given files
|
||||
corpus = [os.path.join(args.data, f) for f in DEFAULT_CORPUS] + args.corpus
|
||||
# label -> {benchmark: output}
|
||||
all_outputs = {}
|
||||
for label, build_dir, extra in variants:
|
||||
os.makedirs(build_dir, exist_ok=True)
|
||||
binaries = {}
|
||||
for bench in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
||||
exe = os.path.join(build_dir, bench)
|
||||
run(cxx + flags + extra + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe])
|
||||
binaries[bench] = exe
|
||||
|
||||
# run: bench_view on its documents, bench_corpus on those and the given files
|
||||
outputs = {}
|
||||
outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_decode'] = run([binaries['bench_decode'], args.data, str(max(1, args.rounds // 2))],
|
||||
cwd=build_dir, capture_output=True, text=True).stdout
|
||||
for name, text in outputs.items():
|
||||
print(text)
|
||||
all_outputs[label] = outputs
|
||||
outputs = {}
|
||||
outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=args.build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=args.build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_decode'] = run([binaries['bench_decode'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
for name, text in outputs.items():
|
||||
print(text)
|
||||
|
||||
# results with their metadata
|
||||
now = datetime.datetime.now()
|
||||
host = re.sub(r'[^A-Za-z0-9-]+', '-', platform.node().split('.')[0]) or 'host'
|
||||
stem = os.path.join(HERE, 'results', f'{now:%Y-%m-%d}-{host}')
|
||||
os.makedirs(os.path.dirname(stem), exist_ok=True)
|
||||
cxx_version = output(cxx + ['--version'])
|
||||
compiler_version = cxx_version.splitlines()[0] if cxx_version else ' '.join(cxx)
|
||||
meta = [
|
||||
('date', f'{now:%Y-%m-%d %H:%M}'),
|
||||
@@ -332,7 +316,6 @@ def main():
|
||||
('OS', f'{platform.system()} {platform.release()} ({platform.machine()})'),
|
||||
('compiler', compiler_version),
|
||||
('flags', ' '.join(flags)),
|
||||
('aligned results', ('included (flags: ' + ' '.join(align_flags) + ')') if args.aligned else 'not included'),
|
||||
('yyjson', libs['yyjson'].version),
|
||||
('simdjson', libs['simdjson'].version),
|
||||
('Boost.JSON', libs['boost'].version if with_boost else 'skipped (not found)'),
|
||||
@@ -345,20 +328,15 @@ def main():
|
||||
f.write('| | |\n|---|---|\n')
|
||||
for key, value in meta:
|
||||
f.write(f'| {key} | {value} |\n')
|
||||
for label, outputs in all_outputs.items():
|
||||
# (without --aligned there is only the default build, and no extra heading level)
|
||||
prefix = f'{label}: ' if args.aligned else ''
|
||||
for name, text in outputs.items():
|
||||
f.write(f'\n## {prefix}{name}\n\n```\n{text.rstrip()}\n```\n')
|
||||
for name, text in outputs.items():
|
||||
f.write(f'\n## {name}\n\n```\n{text.rstrip()}\n```\n')
|
||||
with open(stem + '.csv', 'w', encoding='utf-8') as out:
|
||||
out.write(''.join(f'# {key}: {value}\n' for key, value in meta))
|
||||
for label, build_dir, _ in variants:
|
||||
for name in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
||||
path = os.path.join(build_dir, name + '.csv')
|
||||
if os.path.isfile(path):
|
||||
with open(path, encoding='utf-8') as f:
|
||||
# (the default build keeps its plain section names)
|
||||
out.write(f'# {name}' + (f' ({label})' if args.aligned else '') + '\n' + f.read())
|
||||
for name in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
||||
path = os.path.join(args.build_dir, name + '.csv')
|
||||
if os.path.isfile(path):
|
||||
with open(path, encoding='utf-8') as f:
|
||||
out.write(f'# {name}\n' + f.read())
|
||||
print(f'results: {stem}.md, {stem}.csv')
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# check_path_filters
|
||||
|
||||
The compiler workflows [`ubuntu.yml`](../../.github/workflows/ubuntu.yml),
|
||||
[`macos.yml`](../../.github/workflows/macos.yml), and [`windows.yml`](../../.github/workflows/windows.yml) skip pull
|
||||
requests that change only files no compiler job reads: the documentation, Markdown files, GitHub metadata, other
|
||||
workflows, and the license files. Pushes to `develop`, `master`, and `release/*` always run them. The documentation,
|
||||
license, and path-filter checks run in [`repository_checks.yml`](../../.github/workflows/repository_checks.yml), which
|
||||
has no path filter.
|
||||
|
||||
The filter starts with `'**'` and excludes these files explicitly, so a new file runs the compiler workflows unless it
|
||||
is excluded on purpose. [`check_path_filters.py`](check_path_filters.py) holds the filter (`PATHS`) and checks that
|
||||
|
||||
- each of the three workflows has exactly this filter, with its own workflow file included,
|
||||
- the CI targets that read excluded files (`UNFILTERED_TARGETS`) do not run in these workflows, and
|
||||
- no file the filter keeps (sources, tests, build files, tools, workflows) mentions an excluded file or directory
|
||||
outside a comment line, unless the mention is listed in `KNOWN_REFERENCES` with a reason.
|
||||
|
||||
When the check reports a mention, decide whether a compiler job reads that file:
|
||||
|
||||
- If it does, include the file in `PATHS` after the exclusions (as `package_managers.md` and the Swift example are) and
|
||||
copy the new filter into the three workflows.
|
||||
- If it does not, add the mention to `KNOWN_REFERENCES` with the reason.
|
||||
|
||||
Run the check with
|
||||
|
||||
```shell
|
||||
python3 tools/check_path_filters/check_path_filters.py
|
||||
```
|
||||
|
||||
To see which workflows a pull request would run, pass the changed files:
|
||||
|
||||
```shell
|
||||
python3 tools/check_path_filters/check_path_filters.py --changed $(git diff --name-only origin/develop)
|
||||
```
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check the path filters of the compiler workflows.
|
||||
|
||||
ubuntu.yml, macos.yml and windows.yml skip pull requests that change only files
|
||||
no compiler job reads (documentation, Markdown files, GitHub metadata, license
|
||||
files). The filter starts with '**' and excludes these files explicitly, so a
|
||||
new file runs the workflows unless it is excluded on purpose.
|
||||
|
||||
The script checks that
|
||||
|
||||
- each of the three workflows has exactly the filter defined in PATHS below,
|
||||
with its own workflow file included, and
|
||||
- no tracked file that the filter keeps (sources, tests, build files, tools,
|
||||
workflows) mentions a file the filter excludes, unless the mention is listed
|
||||
in KNOWN_REFERENCES with the reason why it does not matter.
|
||||
|
||||
A mention found this way must either be added to PATHS as an included path
|
||||
(the file is read by a compiler job) or to KNOWN_REFERENCES (it is not).
|
||||
|
||||
With --changed FILE..., the script prints instead which workflows a pull
|
||||
request changing these files would run.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess # nosec B404
|
||||
import sys
|
||||
|
||||
REPO_ROOT = os.path.normpath(os.path.join(sys.path[0], '..', '..'))
|
||||
SELF = 'tools/check_path_filters/check_path_filters.py'
|
||||
|
||||
WORKFLOWS = ['.github/workflows/ubuntu.yml', '.github/workflows/macos.yml', '.github/workflows/windows.yml']
|
||||
|
||||
# The pull_request paths filter of the workflows above; '{workflow}' is replaced
|
||||
# with the workflow's own file. GitHub applies the patterns in order: a file
|
||||
# matching a later pattern is included ('pattern') or excluded ('!pattern').
|
||||
PATHS = [
|
||||
'**',
|
||||
'!docs/**',
|
||||
'!**.md',
|
||||
'!.github/*',
|
||||
'!.github/ISSUE_TEMPLATE/**',
|
||||
'!.github/external_ci/**',
|
||||
'!.github/workflows/**',
|
||||
'!.reuse/**',
|
||||
'!LICENSES/**',
|
||||
'!CITATION.cff',
|
||||
'!REUSE.toml',
|
||||
# read by tools/check_build_options in the ci_meson_install job (ubuntu.yml)
|
||||
'docs/mkdocs/docs/integration/package_managers.md',
|
||||
# built by the swiftpm job (macos.yml)
|
||||
'docs/mkdocs/docs/integration/swift/**',
|
||||
'{workflow}',
|
||||
]
|
||||
|
||||
# Mentions of excluded files that no job of the three workflows depends on:
|
||||
# (file that mentions it, the mention, reason)
|
||||
KNOWN_REFERENCES = [
|
||||
('cmake/ci.cmake', 'docs/', 'ci_test_examples; runs in repository_checks.yml, which has no path filter'),
|
||||
('cmake/ci.cmake', 'docs/mkdocs/', 'ci_test_build_documentation and ci_test_documentation_mermaid; they run in '
|
||||
'repository_checks.yml, which has no path filter'),
|
||||
('cmake/ci.cmake', 'docs/mkdocs/docs/examples/', 'indentation check of ci_test_amalgamation; the same check runs in '
|
||||
'check_amalgamation.yml, which has no path filter'),
|
||||
('Makefile', 'docs/mkdocs/docs/examples/', 'make pretty; runs in check_amalgamation.yml, which has no path filter'),
|
||||
('Makefile', 'ChangeLog.md', 'make ChangeLog.md writes the file; no CI job runs it'),
|
||||
('Makefile', 'tests/fuzzing.md', 'help text'),
|
||||
('Makefile', 'tools/generate_natvis/README.md', 'help text'),
|
||||
('tests/src/make_test_data_available.hpp', 'README.md', 'a file of the external test data repository'),
|
||||
('tools/check_build_options/check_build_options.py', 'docs/', 'builds the path of package_managers.md, which '
|
||||
'PATHS includes'),
|
||||
('tools/ci_jobs/ci_jobs.py', 'docs/mkdocs/docs/community/quality_assurance.md',
|
||||
'checks the job table; runs in repository_checks.yml, which has no path filter'),
|
||||
('tools/ci_jobs/ci_jobs.py', '.github/workflows/repository_checks.yml', 'read by --compare, a manual check'),
|
||||
('tools/ci_jobs/ci_jobs.py', '.github/workflows/check_amalgamation.yml', 'read by --compare, a manual check'),
|
||||
]
|
||||
|
||||
|
||||
# CI targets that read excluded files; they must run in a workflow without a path filter
|
||||
UNFILTERED_TARGETS = ['ci_test_examples', 'ci_test_build_documentation', 'ci_test_documentation_mermaid',
|
||||
'ci_test_amalgamation', 'ci_reuse_compliance']
|
||||
|
||||
|
||||
def git_files(root):
|
||||
# fixed command, no user input; includes new files not yet added, so a local run
|
||||
# sees what CI sees after the commit
|
||||
out = subprocess.check_output(['git', 'ls-files', '-z', '--cached', '--others', '--exclude-standard'], cwd=root) # nosec B603, B607
|
||||
return [f for f in out.decode('utf-8').split('\0') if f]
|
||||
|
||||
|
||||
def glob_to_regex(pattern):
|
||||
"""Translate a GitHub Actions path pattern to a regular expression."""
|
||||
regex = ''
|
||||
i = 0
|
||||
while i < len(pattern):
|
||||
if pattern.startswith('**', i):
|
||||
regex += '.*'
|
||||
i += 2
|
||||
elif pattern[i] == '*':
|
||||
regex += '[^/]*'
|
||||
i += 1
|
||||
elif pattern[i] == '?':
|
||||
regex += '[^/]'
|
||||
i += 1
|
||||
else:
|
||||
regex += re.escape(pattern[i])
|
||||
i += 1
|
||||
return re.compile(regex + r'\Z')
|
||||
|
||||
|
||||
def compile_filter(paths, workflow):
|
||||
return [(p.startswith('!'), glob_to_regex(p.lstrip('!').replace('{workflow}', workflow))) for p in paths]
|
||||
|
||||
|
||||
def included(compiled, path):
|
||||
result = False
|
||||
for negated, regex in compiled:
|
||||
if regex.match(path):
|
||||
result = not negated
|
||||
return result
|
||||
|
||||
|
||||
def read_filter(root, workflow):
|
||||
"""Return the pull_request paths list of a workflow file."""
|
||||
with open(os.path.join(root, workflow), encoding='utf-8') as f:
|
||||
lines = f.read().splitlines()
|
||||
paths = None
|
||||
in_pull_request = False
|
||||
for line in lines:
|
||||
if re.match(r'^ pull_request:', line):
|
||||
in_pull_request = True
|
||||
continue
|
||||
if in_pull_request:
|
||||
if re.match(r'^ \S', line) or re.match(r'^\S', line):
|
||||
break
|
||||
if re.match(r'^ paths:\s*$', line):
|
||||
paths = []
|
||||
continue
|
||||
m = re.match(r"^ - '(.*)'\s*(#.*)?$", line)
|
||||
if paths is not None and m:
|
||||
paths.append(m.group(1))
|
||||
elif paths is not None and line.strip() and not line.strip().startswith('#'):
|
||||
break
|
||||
return paths
|
||||
|
||||
|
||||
TOKEN = re.compile(r'(["\'/]?)([\w.-]+(?:/[\w.-]+)*)')
|
||||
|
||||
|
||||
COMMENT_LINE = re.compile(r'^[ \t]*(//|#|/?\*|--).*$', re.MULTILINE)
|
||||
|
||||
|
||||
class Mentions:
|
||||
"""Finds the words and paths in a text that name an excluded file or directory."""
|
||||
|
||||
def __init__(self, files, excluded, is_excluded):
|
||||
self.files = set(files)
|
||||
self.excluded = excluded
|
||||
self.dirs = {'/'.join(f.split('/')[:n]) for f in files for n in range(1, f.count('/') + 1)}
|
||||
# an excluded directory: a new file in it would be excluded
|
||||
self.excluded_dirs = {d for d in self.dirs if is_excluded(d + '/new_file')}
|
||||
# base names that only excluded files have, e.g. 'ChangeLog.md' but not 'CMakeLists.txt'
|
||||
self.names = {os.path.basename(f) for f in excluded} - {os.path.basename(f) for f in files if f not in excluded}
|
||||
|
||||
def find(self, text):
|
||||
"""Yield (offset, mention) for every word or path in text that names an excluded file or directory."""
|
||||
for m in TOKEN.finditer(text):
|
||||
parts = m.group(2).rstrip('/').split('/')
|
||||
# a single word names a directory only in a path context, e.g. 'docs' in os.path.join('docs', ...)
|
||||
path_context = len(parts) > 1 or m.group(1)
|
||||
for i in range(len(parts)):
|
||||
path = '/'.join(parts[i:])
|
||||
if path in self.files:
|
||||
if path in self.excluded:
|
||||
yield m.start(2), path
|
||||
break
|
||||
if path in self.dirs and path_context:
|
||||
if path in self.excluded_dirs:
|
||||
yield m.start(2), path + '/'
|
||||
break
|
||||
else:
|
||||
if parts[-1] in self.names and '.' in parts[-1]:
|
||||
yield m.start(2), parts[-1]
|
||||
|
||||
|
||||
def known(path, mention):
|
||||
return any(path == file and mention == known_mention for file, known_mention, _ in KNOWN_REFERENCES)
|
||||
|
||||
|
||||
def check_workflows(root):
|
||||
"""Return the errors in the filters of the workflows and in the targets they run."""
|
||||
errors = []
|
||||
for workflow in WORKFLOWS:
|
||||
expected = [p.replace('{workflow}', workflow) for p in PATHS]
|
||||
actual = read_filter(root, workflow)
|
||||
if actual != expected:
|
||||
errors.append('{}: the pull_request paths filter differs from tools/check_path_filters/check_path_filters.py\n'
|
||||
' expected: {}\n actual: {}'.format(workflow, expected, actual))
|
||||
with open(os.path.join(root, workflow), encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
errors += ['{}: runs {}, which reads excluded files; run it in repository_checks.yml'.format(workflow, target)
|
||||
for target in UNFILTERED_TARGETS if re.search(r'\b' + target + r'\b', text)]
|
||||
return errors
|
||||
|
||||
|
||||
def scanned_text(root, path):
|
||||
"""Return the text of a kept file without comment lines and without the filter itself, or None to skip it."""
|
||||
# third-party code and Markdown files read nothing; this script lists the excluded files
|
||||
if path.startswith('tests/thirdparty/') or path.endswith('.md') or path == SELF:
|
||||
return None
|
||||
try:
|
||||
with open(os.path.join(root, path), encoding='utf-8') as f:
|
||||
# a mention in a comment line reads nothing; keep the line numbers
|
||||
text = COMMENT_LINE.sub('', f.read())
|
||||
except (UnicodeDecodeError, OSError):
|
||||
return None
|
||||
if path in WORKFLOWS:
|
||||
# the filter itself lists the excluded files
|
||||
for p in PATHS:
|
||||
text = text.replace(" - '{}'".format(p.replace('{workflow}', path)), '')
|
||||
return text
|
||||
|
||||
|
||||
def check_mentions(root, files, excluded, finder):
|
||||
"""Return an error for every mention of an excluded file in a kept file that KNOWN_REFERENCES does not list."""
|
||||
errors = []
|
||||
for path in sorted(set(files) - excluded):
|
||||
text = scanned_text(root, path)
|
||||
reported = set()
|
||||
for offset, mention in finder.find(text or ''):
|
||||
if mention not in reported and not known(path, mention):
|
||||
reported.add(mention)
|
||||
line = text.count('\n', 0, offset) + 1
|
||||
errors.append('{}:{}: mentions excluded {}; include it in PATHS if a compiler job reads it, '
|
||||
'or add it to KNOWN_REFERENCES'.format(path, line, mention))
|
||||
return errors
|
||||
|
||||
|
||||
def check(root):
|
||||
files = git_files(root)
|
||||
union = [compile_filter(PATHS, w) for w in WORKFLOWS]
|
||||
|
||||
def is_excluded(path):
|
||||
return not any(included(c, path) for c in union)
|
||||
|
||||
excluded = {f for f in files if is_excluded(f)}
|
||||
errors = check_workflows(root) + check_mentions(root, files, excluded, Mentions(files, excluded, is_excluded))
|
||||
for e in errors:
|
||||
print(e)
|
||||
if not errors:
|
||||
print('path filters OK: {} of {} tracked files skip the compiler workflows'.format(len(excluded), len(files)))
|
||||
return 1 if errors else 0
|
||||
|
||||
|
||||
def explain(changed):
|
||||
for workflow in WORKFLOWS:
|
||||
compiled = compile_filter(PATHS, workflow)
|
||||
hits = [f for f in changed if included(compiled, f)]
|
||||
print('{}: {}'.format(workflow, 'runs ({})'.format(', '.join(hits[:3]) + (' ...' if len(hits) > 3 else ''))
|
||||
if hits else 'skipped'))
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument('--root', default=REPO_ROOT, help='repository root')
|
||||
parser.add_argument('--changed', nargs='+', metavar='FILE', help='show which workflows these changed files run')
|
||||
args = parser.parse_args()
|
||||
return explain(args.changed) if args.changed else check(args.root)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,27 @@
|
||||
# ci_jobs
|
||||
|
||||
The compiler jobs of [`ubuntu.yml`](../../.github/workflows/ubuntu.yml) are split into three sets, chosen by each job's
|
||||
`if:` condition (see [How CI runs](https://json.nlohmann.me/community/quality_assurance/#how-ci-runs)):
|
||||
|
||||
- **PR set**: every push to a pull request, and every push to `develop`, `master`, and `release/*`;
|
||||
- **full set**: after the PR set passed, on pull requests with the label "🚀 ready to merge" and on every push;
|
||||
- **nightly set**: every night on `develop`, on every push to `master` and `release/*`, and when started by hand.
|
||||
|
||||
A matrix whose entries belong to different sets is split into one job per set: the full-set job gets the suffix
|
||||
`_full`, the nightly-set job `_nightly`, and both share their steps with the PR-set job through YAML anchors.
|
||||
|
||||
[`ci_jobs.py`](ci_jobs.py) checks that
|
||||
|
||||
- every job of `ubuntu.yml` has the `if:` condition of exactly one set (the conditions are in `SETS`),
|
||||
- every full-set job waits for (`needs:`) exactly the PR set, and `nightly_report` for exactly the nightly set, and
|
||||
- the job table in [`quality_assurance.md`](../../docs/mkdocs/docs/community/quality_assurance.md) is up to date.
|
||||
|
||||
```shell
|
||||
python3 -m venv venv && venv/bin/pip install -r tools/ci_jobs/requirements.txt
|
||||
venv/bin/python tools/ci_jobs/ci_jobs.py # check
|
||||
venv/bin/python tools/ci_jobs/ci_jobs.py --markdown # print the job table for the documentation
|
||||
venv/bin/python tools/ci_jobs/ci_jobs.py --compare origin/develop # list configurations added or removed since develop
|
||||
```
|
||||
|
||||
Use `--compare` when changing the workflows: a configuration that disappears fails it, so no compiler, version, or
|
||||
standard is dropped by accident when jobs are split, merged, or moved between sets.
|
||||
Executable
+230
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env python3
|
||||
"""List and check the jobs of the compiler workflows and the set each job belongs to.
|
||||
|
||||
The jobs of a tiered workflow (ubuntu.yml) belong to one of three sets, chosen
|
||||
by the job's `if:` condition:
|
||||
|
||||
- PR set: every pull request push and every push,
|
||||
- full set: after the PR set passed, on pushes and on pull requests with the
|
||||
"🚀 ready to merge" label,
|
||||
- nightly set: nightly on develop, on pushes to master and release/*, and when
|
||||
started by hand.
|
||||
|
||||
The jobs of the other workflows run on every push and pull request.
|
||||
|
||||
The script checks that every job of a tiered workflow belongs to exactly one
|
||||
set, that the full set waits for the whole PR set, that the nightly report
|
||||
waits for the whole nightly set, and that the job table in
|
||||
docs/mkdocs/docs/community/quality_assurance.md is up to date.
|
||||
|
||||
--markdown print the job table for the documentation
|
||||
--compare REF list the configurations (job and matrix values) added or
|
||||
removed since the git revision REF; fails if one was removed
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
import subprocess # nosec B404
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
|
||||
REPO_ROOT = os.path.normpath(os.path.join(sys.path[0], '..', '..'))
|
||||
DOCS = 'docs/mkdocs/docs/community/quality_assurance.md'
|
||||
BEGIN, END = '<!-- ci_jobs: begin -->', '<!-- ci_jobs: end -->'
|
||||
|
||||
WORKFLOWS = ['.github/workflows/ubuntu.yml', '.github/workflows/macos.yml', '.github/workflows/windows.yml']
|
||||
TIERED = ['.github/workflows/ubuntu.yml']
|
||||
# --compare also counts the jobs of these workflows, so a job moved between workflows is not a removed one
|
||||
COMPARED = WORKFLOWS + ['.github/workflows/repository_checks.yml', '.github/workflows/check_amalgamation.yml']
|
||||
|
||||
LABEL = '🚀 ready to merge'
|
||||
SETS = collections.OrderedDict([
|
||||
('PR set', "${{ github.event_name != 'schedule' && (github.event.action != 'labeled' || "
|
||||
"github.event.label.name == '" + LABEL + "') }}"),
|
||||
('full set', "${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || "
|
||||
"contains(github.event.pull_request.labels.*.name, '" + LABEL + "') }}"),
|
||||
('nightly set', "${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || "
|
||||
"(github.event_name == 'push' && github.ref != 'refs/heads/develop') }}"),
|
||||
])
|
||||
UNTIERED = 'every push and PR'
|
||||
REPORT_JOB = 'nightly_report'
|
||||
|
||||
# a job split by set keeps its name and gets one of these suffixes
|
||||
SUFFIXES = {'full set': '_full', 'nightly set': '_nightly'}
|
||||
|
||||
|
||||
def read(root, path, ref=None):
|
||||
if ref is None:
|
||||
with open(os.path.join(root, path), encoding='utf-8') as f:
|
||||
return f.read()
|
||||
# fixed command; ref is a git revision given on the command line
|
||||
return subprocess.check_output(['git', 'show', '{}:{}'.format(ref, path)], cwd=root, # nosec B603, B607
|
||||
stderr=subprocess.DEVNULL).decode('utf-8')
|
||||
|
||||
|
||||
def add_include(combos, keys, inc):
|
||||
"""Apply one include entry: extend the matching configurations, or add it as a new one."""
|
||||
matching = [c for c in combos if all(c[k] == v for k, v in inc.items() if k in keys)]
|
||||
for c in matching:
|
||||
c.update({k: v for k, v in inc.items() if k not in keys})
|
||||
if not matching:
|
||||
combos.append(dict(inc))
|
||||
|
||||
|
||||
def expand(matrix):
|
||||
"""Return the configurations of a job's strategy.matrix like GitHub Actions does."""
|
||||
if not matrix:
|
||||
return [{}]
|
||||
keys = [k for k in matrix if k not in ('include', 'exclude')]
|
||||
combos = [dict(zip(keys, values)) for values in itertools.product(*(matrix[k] for k in keys))] if keys else []
|
||||
for ex in matrix.get('exclude') or []:
|
||||
combos = [c for c in combos if any(c.get(k) != v for k, v in ex.items())]
|
||||
for inc in matrix.get('include') or []:
|
||||
add_include(combos, keys, inc)
|
||||
return combos
|
||||
|
||||
|
||||
def jobs_of(root, workflow, ref=None):
|
||||
"""Yield (job id, set, list of configurations) for every job of a workflow."""
|
||||
data = yaml.safe_load(read(root, workflow, ref))
|
||||
for job_id, job in data['jobs'].items():
|
||||
condition = job.get('if')
|
||||
job_set = UNTIERED if condition is None else next((s for s, c in SETS.items() if c == condition), None)
|
||||
matrix = (job.get('strategy') or {}).get('matrix')
|
||||
yield job_id, job_set, job, expand(matrix)
|
||||
|
||||
|
||||
def base_id(job_id, job_set):
|
||||
suffix = SUFFIXES.get(job_set)
|
||||
return job_id[:-len(suffix)] if suffix and job_id.endswith(suffix) else job_id
|
||||
|
||||
|
||||
def label(config):
|
||||
return ', '.join(str(v) for k, v in config.items() if k != 'os' or len(config) == 1)
|
||||
|
||||
|
||||
def configurations(root, ref=None):
|
||||
"""Return a Counter of (job name without set suffix, configuration) over the compared workflows."""
|
||||
result = collections.Counter()
|
||||
for workflow in COMPARED:
|
||||
try:
|
||||
jobs = list(jobs_of(root, workflow, ref))
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
continue # the workflow does not exist in this revision
|
||||
for job_id, job_set, _, configs in jobs:
|
||||
if job_id == REPORT_JOB:
|
||||
continue
|
||||
for c in configs:
|
||||
result[(base_id(job_id, job_set), tuple(sorted((k, str(v)) for k, v in c.items())))] += 1
|
||||
return result
|
||||
|
||||
|
||||
def markdown(root):
|
||||
rows = []
|
||||
order = list(SETS) + [UNTIERED]
|
||||
for workflow in WORKFLOWS:
|
||||
for job_id, job_set, _, configs in jobs_of(root, workflow):
|
||||
if job_id == REPORT_JOB:
|
||||
continue
|
||||
labels = [label(c) for c in configs if c]
|
||||
rows.append((order.index(job_set) if job_set in order else len(order), os.path.basename(workflow), job_id,
|
||||
job_set or 'no set', '; '.join(labels) or '-'))
|
||||
rows.sort(key=lambda r: (r[0], r[1]))
|
||||
lines = ['| Set | Workflow | Job | Configurations |', '|-----|----------|-----|----------------|']
|
||||
lines += ['| {} | {} | `{}` | {} |'.format(s, w, j, c) for _, w, j, s, c in rows]
|
||||
return lines
|
||||
|
||||
|
||||
def check_sets(workflow, jobs):
|
||||
"""Return the errors in the sets of a tiered workflow's jobs and the jobs of each set."""
|
||||
errors = []
|
||||
by_set = collections.defaultdict(set)
|
||||
for job_id, job_set, _, _ in jobs:
|
||||
if job_id == REPORT_JOB:
|
||||
continue
|
||||
if job_set in SETS:
|
||||
by_set[job_set].add(job_id)
|
||||
else:
|
||||
errors.append('{}: job {} has an if: condition of no set; use one of the conditions in '
|
||||
'tools/ci_jobs/ci_jobs.py'.format(workflow, job_id))
|
||||
return errors, by_set
|
||||
|
||||
|
||||
def check_needs(workflow, jobs, by_set):
|
||||
"""Return the errors in the needs: of a tiered workflow's jobs."""
|
||||
required = {'full set': by_set['PR set'], 'PR set': set(), 'nightly set': set()}
|
||||
errors = []
|
||||
for job_id, job_set, job, _ in jobs:
|
||||
needs = job.get('needs') or []
|
||||
needs = {needs} if isinstance(needs, str) else set(needs)
|
||||
expected = by_set['nightly set'] if job_id == REPORT_JOB else required.get(job_set, needs)
|
||||
if needs != expected:
|
||||
errors.append('{}: job {} must need exactly: {}'.format(workflow, job_id, ', '.join(sorted(expected)) or 'no job'))
|
||||
return errors
|
||||
|
||||
|
||||
def check_docs(root):
|
||||
"""Return an error if the job table in the documentation is not up to date."""
|
||||
text = read(root, DOCS)
|
||||
block = re.search(re.escape(BEGIN) + r'\n(.*?)\n[ \t]*' + re.escape(END), text, re.S)
|
||||
if not block:
|
||||
return ['{}: markers {} and {} not found'.format(DOCS, BEGIN, END)]
|
||||
indent = re.match(r'[ \t]*', block.group(1)).group(0)
|
||||
if block.group(1) != '\n'.join(indent + line for line in markdown(root)):
|
||||
return ['{}: the job table is out of date; replace it with the output of '
|
||||
'python3 tools/ci_jobs/ci_jobs.py --markdown'.format(DOCS)]
|
||||
return []
|
||||
|
||||
|
||||
def check(root):
|
||||
errors = []
|
||||
for workflow in TIERED:
|
||||
jobs = list(jobs_of(root, workflow))
|
||||
set_errors, by_set = check_sets(workflow, jobs)
|
||||
errors += set_errors + check_needs(workflow, jobs, by_set)
|
||||
errors += check_docs(root)
|
||||
|
||||
for e in errors:
|
||||
print(e)
|
||||
if not errors:
|
||||
counts = collections.Counter()
|
||||
for workflow in WORKFLOWS:
|
||||
for job_id, job_set, _, configs in jobs_of(root, workflow):
|
||||
if job_id != REPORT_JOB:
|
||||
counts[job_set] += len(configs)
|
||||
print('CI jobs OK: ' + ', '.join('{} {}'.format(n, s) for s, n in counts.items()))
|
||||
return 1 if errors else 0
|
||||
|
||||
|
||||
def compare(root, ref):
|
||||
before, after = configurations(root, ref), configurations(root)
|
||||
removed, added = before - after, after - before
|
||||
for title, diff in (('removed', removed), ('added', added)):
|
||||
for job, config in sorted(diff.elements()):
|
||||
print('{}: {} ({})'.format(title, job, ', '.join('{}={}'.format(k, v) for k, v in config)))
|
||||
print('{} configurations before, {} after; {} removed, {} added'.format(
|
||||
sum(before.values()), sum(after.values()), sum(removed.values()), sum(added.values())))
|
||||
return 1 if removed else 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument('--root', default=REPO_ROOT, help='repository root')
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument('--markdown', action='store_true', help='print the job table for the documentation')
|
||||
group.add_argument('--compare', metavar='REF', help='list the configurations added or removed since REF')
|
||||
args = parser.parse_args()
|
||||
if args.markdown:
|
||||
print('\n'.join(markdown(args.root)))
|
||||
return 0
|
||||
if args.compare:
|
||||
return compare(args.root, args.compare)
|
||||
return check(args.root)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1 @@
|
||||
PyYAML==6.0.3
|
||||
Reference in new issue
Block a user