Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf9bc9f71b | ||
|
|
9e1d35eda9 | ||
|
|
4b538d80f6 | ||
|
|
91a8503f83 | ||
|
|
66ee5d1b84 | ||
|
|
7a4e6b7a5d | ||
|
|
a4c4e92dbd | ||
|
|
ae57b09bc6 | ||
|
|
fd37b04813 | ||
|
|
e89d5fdbdd | ||
|
|
d587214ab2 | ||
|
|
bd442d5d07 | ||
|
|
4c3e92b96e | ||
|
|
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);
|
[`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.
|
`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
|
### Amalgamate the source code
|
||||||
|
|
||||||
The single-header files
|
The single-header files
|
||||||
|
|||||||
@@ -7,25 +7,60 @@ on:
|
|||||||
- master
|
- master
|
||||||
- release/*
|
- release/*
|
||||||
pull_request:
|
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/macos.yml'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# 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:
|
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
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
|
||||||
|
# The jobs are split into a PR set and a full set like in ubuntu.yml (see "How
|
||||||
|
# CI runs" in docs/mkdocs/docs/community/quality_assurance.md): the PR set runs
|
||||||
|
# on every push; the full set after the PR set passed, on pushes and on pull
|
||||||
|
# requests with the "🚀 ready to merge" label. tools/ci_jobs checks that every
|
||||||
|
# job belongs to exactly one set.
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
###########################################################################
|
||||||
|
# PR set
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
macos-15:
|
macos-15:
|
||||||
|
if: &pr_set ${{ github.event_name != 'schedule' && (github.event.action != 'labeled' || github.event.label.name == '🚀 ready to merge') }}
|
||||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
|
xcode: ['16.0', '16.4', '26.3']
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
steps:
|
steps: &xcode_steps
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -37,10 +72,11 @@ jobs:
|
|||||||
run: cd build ; ctest -j 10 --output-on-failure
|
run: cd build ; ctest -j 10 --output-on-failure
|
||||||
|
|
||||||
macos-26:
|
macos-26:
|
||||||
|
if: *pr_set
|
||||||
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode: ['26.4.1', '26.5', '26.6']
|
xcode: ['26.6']
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
@@ -56,12 +92,13 @@ jobs:
|
|||||||
run: cd build ; ctest -j 10 --output-on-failure
|
run: cd build ; ctest -j 10 --output-on-failure
|
||||||
|
|
||||||
xcode_standards:
|
xcode_standards:
|
||||||
|
if: *pr_set
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
standard: [11, 14, 17, 20, 23, 26]
|
standard: [14]
|
||||||
|
|
||||||
steps:
|
steps: &xcode_standards_steps
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -73,6 +110,7 @@ jobs:
|
|||||||
run: cd build ; ctest -j 10 --output-on-failure
|
run: cd build ; ctest -j 10 --output-on-failure
|
||||||
|
|
||||||
swiftpm:
|
swiftpm:
|
||||||
|
if: *pr_set
|
||||||
runs-on: macos-15
|
runs-on: macos-15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
@@ -106,3 +144,42 @@ jobs:
|
|||||||
EOF
|
EOF
|
||||||
cd /tmp/json-swiftpm-consumer
|
cd /tmp/json-swiftpm-consumer
|
||||||
swift build
|
swift build
|
||||||
|
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
# full set: starts after the PR set passed
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
macos-15_full:
|
||||||
|
needs: &pr_set_jobs [macos-15, macos-26, xcode_standards, swiftpm]
|
||||||
|
if: &full_set ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, '🚀 ready to merge') }}
|
||||||
|
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
xcode: ['16.1', '16.2', '16.3', '26.0.1', '26.1.1', '26.2']
|
||||||
|
env:
|
||||||
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
|
steps: *xcode_steps
|
||||||
|
|
||||||
|
macos-26_full:
|
||||||
|
needs: *pr_set_jobs
|
||||||
|
if: *full_set
|
||||||
|
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
xcode: ['26.4.1', '26.5']
|
||||||
|
env:
|
||||||
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
|
steps: *xcode_steps
|
||||||
|
|
||||||
|
xcode_standards_full:
|
||||||
|
needs: *pr_set_jobs
|
||||||
|
if: *full_set
|
||||||
|
runs-on: macos-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
standard: [11, 17, 20, 23, 26]
|
||||||
|
|
||||||
|
steps: *xcode_standards_steps
|
||||||
@@ -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@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
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@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
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@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
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
|
- master
|
||||||
- release/*
|
- release/*
|
||||||
pull_request:
|
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:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
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:
|
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
|
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:
|
jobs:
|
||||||
|
###########################################################################
|
||||||
|
# PR set
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
ci_test_gcc:
|
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
|
runs-on: ubuntu-latest
|
||||||
container: gcc:latest
|
container: gcc:latest
|
||||||
steps:
|
steps:
|
||||||
@@ -32,6 +77,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_test_gcc
|
run: cmake --build build --target ci_test_gcc
|
||||||
|
|
||||||
ci_meson_install:
|
ci_meson_install:
|
||||||
|
if: *pr_set
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-26.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- 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 -S tests/cmake_import/project -B build-import-split -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-split
|
||||||
cmake --build build-import-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@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
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@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
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:
|
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
|
runs-on: ubuntu-26.04
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
@@ -105,76 +486,16 @@ jobs:
|
|||||||
path: ${{ github.workspace }}/build/build_infer/infer-out/report.txt
|
path: ${{ github.workspace }}/build/build_infer/infer-out/report.txt
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
|
|
||||||
ci_static_analysis_ubuntu:
|
ci_static_analysis_ubuntu_nightly:
|
||||||
|
if: *nightly_set
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-26.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
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]
|
target: [ci_test_valgrind]
|
||||||
steps:
|
steps: *static_analysis_ubuntu_steps
|
||||||
- name: Harden Runner
|
|
||||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
|
||||||
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 }}
|
|
||||||
|
|
||||||
ci_test_coverage:
|
ci_test_coverage:
|
||||||
|
if: *nightly_set
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-26.04
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
@@ -208,128 +529,8 @@ jobs:
|
|||||||
path-to-lcov: ${{ github.workspace }}/build/json.info.filtered.noexcept
|
path-to-lcov: ${{ github.workspace }}/build/json.info.filtered.noexcept
|
||||||
fail-on-error: false
|
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:
|
ci_cuda_example:
|
||||||
|
if: *nightly_set
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -352,37 +553,8 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --target ci_cuda_example
|
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:
|
ci_icpc:
|
||||||
|
if: *nightly_set
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is
|
# Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is
|
||||||
# Intel's own last officially published image that still includes it.
|
# Intel's own last officially published image that still includes it.
|
||||||
@@ -402,6 +574,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_icpc
|
run: cmake --build build --target ci_icpc
|
||||||
|
|
||||||
ci_icpx:
|
ci_icpx:
|
||||||
|
if: *nightly_set
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: intel/oneapi-hpckit:latest
|
container: intel/oneapi-hpckit:latest
|
||||||
steps:
|
steps:
|
||||||
@@ -416,6 +589,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_icpx
|
run: cmake --build build --target ci_icpx
|
||||||
|
|
||||||
ci_nvhpc:
|
ci_nvhpc:
|
||||||
|
if: *nightly_set
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||||
steps:
|
steps:
|
||||||
@@ -429,54 +603,10 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --target ci_nvhpc
|
run: cmake --build build --target ci_nvhpc
|
||||||
|
|
||||||
ci_emscripten:
|
|
||||||
runs-on: ubuntu-26.04
|
|
||||||
steps:
|
|
||||||
- name: Harden Runner
|
|
||||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
|
||||||
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@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
|
||||||
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
|
# Native Linux ARM64 runner with the latest GCC. The ubuntu-24.04-arm label is
|
||||||
# only available for public repositories.
|
# only available for public repositories.
|
||||||
ci_test_arm64:
|
ci_test_arm64:
|
||||||
|
if: *nightly_set
|
||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-24.04-arm
|
||||||
container: gcc:latest
|
container: gcc:latest
|
||||||
steps:
|
steps:
|
||||||
@@ -491,3 +621,33 @@ jobs:
|
|||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --target ci_test_compiler_default
|
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@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
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
|
||||||
+214
-32
@@ -7,23 +7,62 @@ on:
|
|||||||
- master
|
- master
|
||||||
- release/*
|
- release/*
|
||||||
pull_request:
|
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/windows.yml'
|
||||||
|
schedule:
|
||||||
|
# the nightly set (see below), on develop
|
||||||
|
- cron: '0 2 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
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:
|
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
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
|
||||||
|
# The jobs are split into three sets like in ubuntu.yml (see "How CI runs" in
|
||||||
|
# docs/mkdocs/docs/community/quality_assurance.md): the PR set runs on every
|
||||||
|
# push; the full set after the PR set passed, on pushes and on pull requests
|
||||||
|
# with the "🚀 ready to merge" label; the nightly set nightly on develop, on
|
||||||
|
# pushes to master and release/*, and when started by hand. tools/ci_jobs
|
||||||
|
# checks that every job belongs to exactly one set.
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
###########################################################################
|
||||||
|
# PR set
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
mingw:
|
mingw:
|
||||||
|
if: &pr_set ${{ github.event_name != 'schedule' && (github.event.action != 'labeled' || github.event.label.name == '🚀 ready to merge') }}
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
architecture: [x64, x86]
|
architecture: [x86]
|
||||||
|
|
||||||
steps:
|
steps: &mingw_steps
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -40,15 +79,26 @@ jobs:
|
|||||||
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
||||||
|
|
||||||
msvc:
|
msvc:
|
||||||
|
if: *pr_set
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
build_type: [Debug, Release]
|
include:
|
||||||
architecture: [Win32, x64]
|
- build_type: Debug
|
||||||
std_version: [default, latest]
|
architecture: x64
|
||||||
|
std_version: default
|
||||||
|
- build_type: Release
|
||||||
|
architecture: x64
|
||||||
|
std_version: default
|
||||||
|
- build_type: Debug
|
||||||
|
architecture: Win32
|
||||||
|
std_version: default
|
||||||
|
- build_type: Release
|
||||||
|
architecture: Win32
|
||||||
|
std_version: latest
|
||||||
|
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
|
||||||
steps:
|
steps: &msvc_steps
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -77,15 +127,20 @@ jobs:
|
|||||||
# Visual Studio 2026 (v145 toolset) on the windows-2025 image. The "Visual Studio
|
# Visual Studio 2026 (v145 toolset) on the windows-2025 image. The "Visual Studio
|
||||||
# 18 2026" generator requires CMake 4.2+, so a recent CMake is fetched explicitly.
|
# 18 2026" generator requires CMake 4.2+, so a recent CMake is fetched explicitly.
|
||||||
msvc-vs2026:
|
msvc-vs2026:
|
||||||
|
if: *pr_set
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
build_type: [Debug, Release]
|
include:
|
||||||
architecture: [Win32, x64]
|
- build_type: Debug
|
||||||
std_version: [default, latest]
|
architecture: x64
|
||||||
|
std_version: latest
|
||||||
|
- build_type: Release
|
||||||
|
architecture: Win32
|
||||||
|
std_version: default
|
||||||
|
|
||||||
runs-on: windows-2025
|
runs-on: windows-2025
|
||||||
|
|
||||||
steps:
|
steps: &msvc_vs2026_steps
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -117,13 +172,14 @@ jobs:
|
|||||||
# Native ARM64 Windows runner with the MSVC ARM64 toolset. The windows-11-arm
|
# Native ARM64 Windows runner with the MSVC ARM64 toolset. The windows-11-arm
|
||||||
# label is only available for public repositories.
|
# label is only available for public repositories.
|
||||||
msvc-arm64:
|
msvc-arm64:
|
||||||
|
if: *pr_set
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
build_type: [Debug, Release]
|
build_type: [Debug]
|
||||||
|
|
||||||
runs-on: windows-11-arm
|
runs-on: windows-11-arm
|
||||||
|
|
||||||
steps:
|
steps: &msvc_arm64_steps
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -141,12 +197,13 @@ jobs:
|
|||||||
run: cd build ; ctest -j 10 -C ${{ matrix.build_type }} --output-on-failure
|
run: cd build ; ctest -j 10 -C ${{ matrix.build_type }} --output-on-failure
|
||||||
|
|
||||||
clang:
|
clang:
|
||||||
|
if: *pr_set
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
version: [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]
|
version: [20.1.8]
|
||||||
|
|
||||||
steps:
|
steps: &clang_steps
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -189,24 +246,8 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
||||||
|
|
||||||
clang-cl:
|
|
||||||
runs-on: windows-2022
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
architecture: [Win32, x64]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: Run CMake
|
|
||||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ${{ matrix.architecture }} -T ClangCL -DJSON_BuildTests=On
|
|
||||||
- name: Build
|
|
||||||
run: cmake --build build --config Debug --parallel 10
|
|
||||||
- name: Test
|
|
||||||
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
|
||||||
|
|
||||||
ci_module_cpp20:
|
ci_module_cpp20:
|
||||||
|
if: *pr_set
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
@@ -226,3 +267,144 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --config Debug --target ci_module_cpp20
|
run: cmake --build build --config Debug --target ci_module_cpp20
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
# full set: starts after the PR set passed
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msvc_full:
|
||||||
|
needs: &pr_set_jobs [mingw, msvc, msvc-vs2026, msvc-arm64, clang, ci_module_cpp20]
|
||||||
|
if: &full_set ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, '🚀 ready to merge') }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build_type: [Debug, Release]
|
||||||
|
architecture: [Win32, x64]
|
||||||
|
std_version: [default, latest]
|
||||||
|
exclude:
|
||||||
|
# in the PR set
|
||||||
|
- build_type: Debug
|
||||||
|
architecture: x64
|
||||||
|
std_version: default
|
||||||
|
- build_type: Release
|
||||||
|
architecture: x64
|
||||||
|
std_version: default
|
||||||
|
- build_type: Debug
|
||||||
|
architecture: Win32
|
||||||
|
std_version: default
|
||||||
|
- build_type: Release
|
||||||
|
architecture: Win32
|
||||||
|
std_version: latest
|
||||||
|
|
||||||
|
runs-on: windows-2022
|
||||||
|
|
||||||
|
steps: *msvc_steps
|
||||||
|
|
||||||
|
# Visual Studio 2026 (v145 toolset) on the windows-2025 image. The "Visual Studio
|
||||||
|
# 18 2026" generator requires CMake 4.2+, so a recent CMake is fetched explicitly.
|
||||||
|
msvc-vs2026_full:
|
||||||
|
needs: *pr_set_jobs
|
||||||
|
if: *full_set
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build_type: [Debug, Release]
|
||||||
|
architecture: [Win32, x64]
|
||||||
|
std_version: [default, latest]
|
||||||
|
exclude:
|
||||||
|
# in the PR set
|
||||||
|
- build_type: Debug
|
||||||
|
architecture: x64
|
||||||
|
std_version: latest
|
||||||
|
- build_type: Release
|
||||||
|
architecture: Win32
|
||||||
|
std_version: default
|
||||||
|
|
||||||
|
runs-on: windows-2025
|
||||||
|
|
||||||
|
steps: *msvc_vs2026_steps
|
||||||
|
|
||||||
|
# Native ARM64 Windows runner with the MSVC ARM64 toolset. The windows-11-arm
|
||||||
|
# label is only available for public repositories.
|
||||||
|
msvc-arm64_full:
|
||||||
|
needs: *pr_set_jobs
|
||||||
|
if: *full_set
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build_type: [Release]
|
||||||
|
|
||||||
|
runs-on: windows-11-arm
|
||||||
|
|
||||||
|
steps: *msvc_arm64_steps
|
||||||
|
|
||||||
|
clang_full:
|
||||||
|
needs: *pr_set_jobs
|
||||||
|
if: *full_set
|
||||||
|
runs-on: windows-2022
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
version: [11.0.1, 12.0.1, 13.0.1, 14.0.6, 15.0.7, 16.0.6, 18.1.8, 19.1.7]
|
||||||
|
|
||||||
|
steps: *clang_steps
|
||||||
|
|
||||||
|
clang-cl_full:
|
||||||
|
needs: *pr_set_jobs
|
||||||
|
if: *full_set
|
||||||
|
runs-on: windows-2022
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
architecture: [Win32, x64]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Run CMake
|
||||||
|
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ${{ matrix.architecture }} -T ClangCL -DJSON_BuildTests=On
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build build --config Debug --parallel 10
|
||||||
|
- name: Test
|
||||||
|
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
||||||
|
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
# nightly set
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
mingw_nightly:
|
||||||
|
if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref != 'refs/heads/develop') }}
|
||||||
|
runs-on: windows-2022
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
architecture: [x64]
|
||||||
|
|
||||||
|
steps: *mingw_steps
|
||||||
|
|
||||||
|
# A failure of a scheduled run opens an issue, or comments on the open one.
|
||||||
|
nightly_report:
|
||||||
|
needs: [mingw_nightly]
|
||||||
|
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
|
||||||
@@ -68,10 +68,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved
|
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved
|
||||||
because a reference token is used on a primitive value.
|
because a reference token is used on a primitive value.
|
||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||||
Pointer path of the value within the document, and with
|
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
`JSON_DIAGNOSTICS` enabled.
|
||||||
[`materialize()`](materialize.md) sets them).
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
||||||
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
`JSON_DIAGNOSTICS` enabled.
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
||||||
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
`JSON_DIAGNOSTICS` enabled.
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -80,13 +80,14 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
- For every other `T`: whatever [`materialize().get<T>()`](../basic_json/get.md) throws — typically `type_error.302`,
|
- For every other `T`: whatever [`materialize().get<T>()`](../basic_json/get.md) throws — typically `type_error.302`,
|
||||||
or whatever a user-provided `from_json()` throws.
|
or whatever a user-provided `from_json()` throws.
|
||||||
|
|
||||||
The exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four bullets
|
None of the exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four
|
||||||
above) carry the context of those of `BasicJsonType`: with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the
|
bullets above) carry a
|
||||||
JSON Pointer path of the value within the document, and with
|
[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An
|
||||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text. An exception
|
exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real
|
||||||
thrown while converting through `materialize()` (the last bullet) is thrown by the materialized `BasicJsonType` value
|
`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled.
|
||||||
instead: its path is relative to the root of the view's subtree, because `materialize()` builds only the subtree
|
That path is relative to the root of the view's subtree, because `materialize()` builds only the subtree (with parent
|
||||||
(with parent pointers), so it is empty if the failing value is the subtree's root itself.
|
pointers): it is empty if the failing value is the subtree's root itself, and it never is the path within the whole
|
||||||
|
document.
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example:
|
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example:
|
||||||
`"type must be string, but is array"`.
|
`"type must be string, but is array"`.
|
||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
`JSON_DIAGNOSTICS` enabled.
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example:
|
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example:
|
||||||
`"type must be number, but is string"`.
|
`"type must be number, but is string"`.
|
||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
`JSON_DIAGNOSTICS` enabled.
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -73,10 +73,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
- [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a
|
- [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a
|
||||||
number.
|
number.
|
||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||||
Pointer path of the value within the document, and with
|
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
`JSON_DIAGNOSTICS` enabled.
|
||||||
[`materialize()`](materialize.md) sets them).
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -61,10 +61,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
`default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches
|
`default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches
|
||||||
`out_of_range` and returns `default_value`.
|
`out_of_range` and returns `default_value`.
|
||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||||
Pointer path of the value within the document, and with
|
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
`JSON_DIAGNOSTICS` enabled.
|
||||||
[`materialize()`](materialize.md) sets them).
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,80 @@ 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>
|
<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, macOS, and Windows 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, four Visual Studio 2022 and two Visual Studio 2026 configurations, MinGW, Clang on Windows, ARM64 Windows, five Xcode versions, Swift Package Manager; 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, ARM64 Linux, and 64-bit MinGW; a failure opens an issue |
|
||||||
|
|
||||||
|
The documentation and license checks and the amalgamation check run on every push and pull request; AppVeyor (Visual
|
||||||
|
Studio 2015, 2017, and 2019) has its own schedule. 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 | macos.yml | `macos-15` | 16.0; 16.4; 26.3 |
|
||||||
|
| PR set | macos.yml | `macos-26` | 26.6 |
|
||||||
|
| PR set | macos.yml | `xcode_standards` | 14 |
|
||||||
|
| PR set | macos.yml | `swiftpm` | - |
|
||||||
|
| 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` | - |
|
||||||
|
| PR set | windows.yml | `mingw` | x86 |
|
||||||
|
| PR set | windows.yml | `msvc` | Debug, x64, default; Release, x64, default; Debug, Win32, default; Release, Win32, latest |
|
||||||
|
| PR set | windows.yml | `msvc-vs2026` | Debug, x64, latest; Release, Win32, default |
|
||||||
|
| PR set | windows.yml | `msvc-arm64` | Debug |
|
||||||
|
| PR set | windows.yml | `clang` | 20.1.8 |
|
||||||
|
| PR set | windows.yml | `ci_module_cpp20` | - |
|
||||||
|
| full set | macos.yml | `macos-15_full` | 16.1; 16.2; 16.3; 26.0.1; 26.1.1; 26.2 |
|
||||||
|
| full set | macos.yml | `macos-26_full` | 26.4.1; 26.5 |
|
||||||
|
| full set | macos.yml | `xcode_standards_full` | 11; 17; 20; 23; 26 |
|
||||||
|
| 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 |
|
||||||
|
| full set | windows.yml | `msvc_full` | Debug, Win32, latest; Debug, x64, latest; Release, Win32, default; Release, x64, latest |
|
||||||
|
| full set | windows.yml | `msvc-vs2026_full` | Debug, Win32, default; Debug, Win32, latest; Debug, x64, default; Release, Win32, latest; Release, x64, default; Release, x64, latest |
|
||||||
|
| full set | windows.yml | `msvc-arm64_full` | Release |
|
||||||
|
| full set | windows.yml | `clang_full` | 11.0.1; 12.0.1; 13.0.1; 14.0.6; 15.0.7; 16.0.6; 18.1.8; 19.1.7 |
|
||||||
|
| full set | windows.yml | `clang-cl_full` | Win32; x64 |
|
||||||
|
| 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` | - |
|
||||||
|
| nightly set | windows.yml | `mingw_nightly` | x64 |
|
||||||
|
<!-- ci_jobs: end -->
|
||||||
|
|
||||||
## C++ language compliance and compiler compatibility
|
## C++ language compliance and compiler compatibility
|
||||||
|
|
||||||
!!! success "Requirement: Compiler support"
|
!!! success "Requirement: Compiler support"
|
||||||
|
|||||||
@@ -134,21 +134,9 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
|||||||
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and
|
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and
|
||||||
[`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md)
|
[`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md)
|
||||||
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
|
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
|
||||||
how it reports errors. Two more macros add context to the exceptions of views, as they do for those of
|
how it reports errors. The others either do not apply ([`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md)
|
||||||
`#!cpp basic_json`:
|
needs parent pointers in `basic_json` values) or only affect the values `materialize()` produces, through
|
||||||
|
`BasicJsonType`.
|
||||||
- With [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md), an exception names the JSON Pointer path of the
|
|
||||||
value within the document.
|
|
||||||
- With [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), an exception names the bytes of
|
|
||||||
the value in the source text.
|
|
||||||
|
|
||||||
A view has neither parent pointers nor stored positions, so both are found from the document's index when the
|
|
||||||
exception is thrown. This takes time linear in the size of the document. One exception has neither:
|
|
||||||
`key()` of an iterator over an array or a primitive value
|
|
||||||
([`invalid_iterator.207`](../home/exceptions.md#jsonexceptioninvalid_iterator207)), because a view's iterator does
|
|
||||||
not know the value it iterates over.
|
|
||||||
|
|
||||||
The other macros only affect the values `materialize()` produces, through `BasicJsonType`.
|
|
||||||
|
|
||||||
## What is different
|
## What is different
|
||||||
|
|
||||||
@@ -179,6 +167,10 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
|||||||
[`at`](../api/basic_json_view/at.md) throws for every missing value.
|
[`at`](../api/basic_json_view/at.md) throws for every missing value.
|
||||||
- **Duplicate keys: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys)
|
- **Duplicate keys: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys)
|
||||||
below.
|
below.
|
||||||
|
- **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own
|
||||||
|
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
|
||||||
|
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
|
||||||
|
`BasicJsonType` was built.
|
||||||
- **Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`.
|
- **Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`.
|
||||||
[`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are
|
[`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are
|
||||||
provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
|
provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
|
||||||
@@ -337,10 +329,14 @@ Its body may only use what views and `basic_json` have in common: the type check
|
|||||||
a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is
|
a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is
|
||||||
converted through `materialize()` as well.
|
converted through `materialize()` as well.
|
||||||
|
|
||||||
One thing differs when a `from_json()` reads the view: members looked up by name (`at()`, `value()`, ... and thus
|
Two things differ when a `from_json()` reads the view:
|
||||||
the macros) find the **first** member with a repeated key, see [Duplicate keys](#duplicate-keys);
|
|
||||||
`#!cpp materialize().get<T>()` would see the last one. Containers built from an object, such as `#!cpp std::map`,
|
- **Duplicate keys.** Members looked up by name (`at()`, `value()`, ... and thus the macros) find the **first** member
|
||||||
keep the **last** value either way, as [`parse()`](../api/basic_json/parse.md) does.
|
with a repeated key, see [Duplicate keys](#duplicate-keys); `#!cpp materialize().get<T>()` would see the last one.
|
||||||
|
Containers built from an object, such as `#!cpp std::map`, keep the **last** value either way, as
|
||||||
|
[`parse()`](../api/basic_json/parse.md) does.
|
||||||
|
- **[`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md).** Exceptions thrown by the view carry no path to the
|
||||||
|
value.
|
||||||
|
|
||||||
`to_json()` is unaffected: it writes `basic_json` values, never views.
|
`to_json()` is unaffected: it writes `basic_json` values, never views.
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
<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>
|
<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"/>
|
<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>
|
<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)"/>
|
<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>
|
<text class="nj-exceptions-group" x="26" y="94">checks, run in parallel on GitHub Actions and AppVeyor</text>
|
||||||
<g class="nj-exceptions-card">
|
<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>
|
<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"/>
|
<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>
|
<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="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="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="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="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="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="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>
|
||||||
<g class="nj-exceptions-card">
|
<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>
|
<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"/>
|
<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>
|
<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>
|
<text class="nj-exceptions-line" x="280" y="278">and object types</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<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>
|
<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"/>
|
<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--push" x="575.25" y="160">every push / PR</text>
|
<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="188">unit tests cover all lines</text>
|
||||||
<text class="nj-exceptions-line" x="520" y="206">of the code base</text>
|
<text class="nj-exceptions-line" x="520" y="206">of the code base</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="26" y="306" width="228" height="166" rx="8"/>
|
<rect class="nj-exceptions-box" x="26" y="360" width="228" height="166" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="40" y="331">Static analysis</text>
|
<text class="nj-exceptions-ctitle" x="40" y="385">Static analysis</text>
|
||||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="342" width="110.5" height="20" rx="10"/>
|
<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="356">every push / PR</text>
|
<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="342" width="72.7" height="20" rx="10"/>
|
<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="356">scheduled</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="410">scheduled</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="384">Clang-Tidy, Cppcheck, Infer</text>
|
<text class="nj-exceptions-line" x="40" y="438">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="456">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="474">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="492">CodeQL, Semgrep, Flawfinder</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="456">(also scheduled)</text>
|
<text class="nj-exceptions-line" x="40" y="510">(also scheduled); Infer nightly</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="266" y="306" width="228" height="166" rx="8"/>
|
<rect class="nj-exceptions-box" x="266" y="360" width="228" height="166" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="280" y="331">Dynamic analysis</text>
|
<text class="nj-exceptions-ctitle" x="280" y="385">Dynamic analysis</text>
|
||||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="342" width="110.5" height="20" rx="10"/>
|
<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="356">every push / PR</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="410">every push / PR</text>
|
||||||
<text class="nj-exceptions-line" x="280" y="384">runtime assertions</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-line" x="280" y="402">Valgrind (Memcheck)</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="426.55" y="410">nightly</text>
|
||||||
<text class="nj-exceptions-line" x="280" y="420">address and undefined behavior</text>
|
<text class="nj-exceptions-line" x="280" y="438">runtime assertions</text>
|
||||||
<text class="nj-exceptions-line" x="280" y="438">sanitizers, integer overflow,</text>
|
<text class="nj-exceptions-line" x="280" y="456">Valgrind (Memcheck), nightly</text>
|
||||||
<text class="nj-exceptions-line" x="280" y="456">nullability violations</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>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="506" y="306" width="228" height="166" rx="8"/>
|
<rect class="nj-exceptions-box" x="506" y="360" width="228" height="166" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="520" y="331">Fuzzing</text>
|
<text class="nj-exceptions-ctitle" x="520" y="385">Fuzzing</text>
|
||||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="342" width="66.4" height="20" rx="10"/>
|
<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="356">every PR</text>
|
<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="342" width="79" height="20" rx="10"/>
|
<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="356">continuous</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--cont" x="631.9" y="410">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="438">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="456">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="474">every crash fixed with a</text>
|
||||||
<text class="nj-exceptions-line" x="520" y="438">reproducing unit test</text>
|
<text class="nj-exceptions-line" x="520" y="492">reproducing unit test</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="26" y="484" width="228" height="166" rx="8"/>
|
<rect class="nj-exceptions-box" x="26" y="538" width="228" height="166" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="40" y="509">Style</text>
|
<text class="nj-exceptions-ctitle" x="40" y="563">Style</text>
|
||||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="520" width="110.5" height="20" rx="10"/>
|
<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="534">every push / PR</text>
|
<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="562">Artistic Style (astyle),</text>
|
<text class="nj-exceptions-line" x="40" y="616">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="634">enforced in the C​I</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="598">cpplint with 61 rules</text>
|
<text class="nj-exceptions-line" x="40" y="652">cpplint with 61 rules</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="266" y="484" width="228" height="166" rx="8"/>
|
<rect class="nj-exceptions-box" x="266" y="538" width="228" height="166" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="280" y="509">Docs and examples</text>
|
<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="520" width="110.5" height="20" rx="10"/>
|
<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="534">every push / PR</text>
|
<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="520" width="72.7" height="20" rx="10"/>
|
<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="534">scheduled</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="432.85" y="588">scheduled</text>
|
||||||
<text class="nj-exceptions-line" x="280" y="562">every documentation example is</text>
|
<text class="nj-exceptions-line" x="280" y="616">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="634">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="652">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="670">as errors; links checked</text>
|
||||||
<text class="nj-exceptions-line" x="280" y="634">regularly (scheduled)</text>
|
<text class="nj-exceptions-line" x="280" y="688">regularly (scheduled)</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="506" y="484" width="228" height="166" rx="8"/>
|
<rect class="nj-exceptions-box" x="506" y="538" width="228" height="166" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="520" y="509">Integration, supply chain</text>
|
<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="520" width="110.5" height="20" rx="10"/>
|
<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="534">every push / PR</text>
|
<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="520" width="72.7" height="20" rx="10"/>
|
<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="534">scheduled</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="672.85" y="588">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="616">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="634">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="652">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="670">Dependabot daily, OpenSSF</text>
|
||||||
<text class="nj-exceptions-line" x="520" y="634">Scorecard, R​EUSE</text>
|
<text class="nj-exceptions-line" x="520" y="688">Scorecard, R​EUSE</text>
|
||||||
</g>
|
</g>
|
||||||
<line class="nj-exceptions-edge" x1="380" y1="664" x2="380" y2="690" marker-end="url(#nj-qa-arrow-w)"/>
|
<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="692" width="230" height="34" rx="17"/>
|
<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="714">merge into develop</text>
|
<text class="nj-exceptions-stagetext" x="380" y="768">merge into develop</text>
|
||||||
<line class="nj-exceptions-edge" x1="380" y1="726" x2="380" y2="752" marker-end="url(#nj-qa-arrow-w)"/>
|
<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="754" width="300" height="46" rx="23"/>
|
<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="776">release</text>
|
<text class="nj-exceptions-stagetext" x="380" y="830">release</text>
|
||||||
<text class="nj-exceptions-stagesub" x="380" y="792">no open vulnerability alert</text>
|
<text class="nj-exceptions-stagesub" x="380" y="846">no open vulnerability alert</text>
|
||||||
</svg>
|
</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>
|
<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>
|
<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"/>
|
<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>
|
<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)"/>
|
<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>
|
<text class="nj-exceptions-group" x="26" y="94">checks, run in parallel</text>
|
||||||
<g class="nj-exceptions-card">
|
<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>
|
<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"/>
|
<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>
|
<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="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="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="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="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="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="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>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="26" y="306" width="308" height="184" rx="8"/>
|
<rect class="nj-exceptions-box" x="26" y="360" width="308" height="184" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="40" y="331">Unit tests (doctest)</text>
|
<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="342" width="110.5" height="20" rx="10"/>
|
<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="356">every push / PR</text>
|
<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="384">every public A​PI function</text>
|
<text class="nj-exceptions-line" x="40" y="438">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="456">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="474">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="492">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="510">custom number, string, array,</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="474">and object types</text>
|
<text class="nj-exceptions-line" x="40" y="528">and object types</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="26" y="502" width="308" height="112" rx="8"/>
|
<rect class="nj-exceptions-box" x="26" y="556" width="308" height="112" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="40" y="527">Coverage</text>
|
<text class="nj-exceptions-ctitle" x="40" y="581">Coverage</text>
|
||||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="538" width="110.5" height="20" rx="10"/>
|
<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--push" x="95.25" y="552">every push / PR</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="70.05" y="606">nightly</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="580">unit tests cover all lines</text>
|
<text class="nj-exceptions-line" x="40" y="634">unit tests cover all lines</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="598">of the code base</text>
|
<text class="nj-exceptions-line" x="40" y="652">of the code base</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="26" y="626" width="308" height="166" rx="8"/>
|
<rect class="nj-exceptions-box" x="26" y="680" width="308" height="166" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="40" y="651">Static analysis</text>
|
<text class="nj-exceptions-ctitle" x="40" y="705">Static analysis</text>
|
||||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="662" width="110.5" height="20" rx="10"/>
|
<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="676">every push / PR</text>
|
<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="662" width="72.7" height="20" rx="10"/>
|
<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="676">scheduled</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="730">scheduled</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="704">Clang-Tidy, Cppcheck, Infer</text>
|
<text class="nj-exceptions-line" x="40" y="758">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="776">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="794">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="812">CodeQL, Semgrep, Flawfinder</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="776">(also scheduled)</text>
|
<text class="nj-exceptions-line" x="40" y="830">(also scheduled); Infer nightly</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="26" y="804" width="308" height="166" rx="8"/>
|
<rect class="nj-exceptions-box" x="26" y="858" width="308" height="166" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="40" y="829">Dynamic analysis</text>
|
<text class="nj-exceptions-ctitle" x="40" y="883">Dynamic analysis</text>
|
||||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="840" width="110.5" height="20" rx="10"/>
|
<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="854">every push / PR</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="908">every push / PR</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="882">runtime assertions</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-line" x="40" y="900">Valgrind (Memcheck)</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="186.55" y="908">nightly</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="918">address and undefined behavior</text>
|
<text class="nj-exceptions-line" x="40" y="936">runtime assertions</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="936">sanitizers, integer overflow,</text>
|
<text class="nj-exceptions-line" x="40" y="954">Valgrind (Memcheck), nightly</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="954">nullability violations</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>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="26" y="982" width="308" height="148" rx="8"/>
|
<rect class="nj-exceptions-box" x="26" y="1036" width="308" height="148" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="40" y="1007">Fuzzing</text>
|
<text class="nj-exceptions-ctitle" x="40" y="1061">Fuzzing</text>
|
||||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1018" width="66.4" height="20" rx="10"/>
|
<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="1032">every PR</text>
|
<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="1018" width="79" height="20" rx="10"/>
|
<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="1032">continuous</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--cont" x="151.9" y="1086">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="1114">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="1132">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="1150">every crash fixed with a</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="1114">reproducing unit test</text>
|
<text class="nj-exceptions-line" x="40" y="1168">reproducing unit test</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="26" y="1142" width="308" height="130" rx="8"/>
|
<rect class="nj-exceptions-box" x="26" y="1196" width="308" height="130" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="40" y="1167">Style</text>
|
<text class="nj-exceptions-ctitle" x="40" y="1221">Style</text>
|
||||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1178" width="110.5" height="20" rx="10"/>
|
<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="1192">every push / PR</text>
|
<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="1220">Artistic Style (astyle),</text>
|
<text class="nj-exceptions-line" x="40" y="1274">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="1292">enforced in the C​I</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="1256">cpplint with 61 rules</text>
|
<text class="nj-exceptions-line" x="40" y="1310">cpplint with 61 rules</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="26" y="1284" width="308" height="166" rx="8"/>
|
<rect class="nj-exceptions-box" x="26" y="1338" width="308" height="166" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="40" y="1309">Docs and examples</text>
|
<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="1320" width="110.5" height="20" rx="10"/>
|
<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="1334">every push / PR</text>
|
<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="1320" width="72.7" height="20" rx="10"/>
|
<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="1334">scheduled</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="1388">scheduled</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="1362">every documentation example is</text>
|
<text class="nj-exceptions-line" x="40" y="1416">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="1434">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="1452">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="1470">as errors; links checked</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="1434">regularly (scheduled)</text>
|
<text class="nj-exceptions-line" x="40" y="1488">regularly (scheduled)</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="nj-exceptions-card">
|
<g class="nj-exceptions-card">
|
||||||
<rect class="nj-exceptions-box" x="26" y="1462" width="308" height="166" rx="8"/>
|
<rect class="nj-exceptions-box" x="26" y="1516" width="308" height="166" rx="8"/>
|
||||||
<text class="nj-exceptions-ctitle" x="40" y="1487">Integration, supply chain</text>
|
<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="1498" width="110.5" height="20" rx="10"/>
|
<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="1512">every push / PR</text>
|
<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="1498" width="72.7" height="20" rx="10"/>
|
<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="1512">scheduled</text>
|
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="1566">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="1594">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="1612">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="1630">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="1648">Dependabot daily, OpenSSF</text>
|
||||||
<text class="nj-exceptions-line" x="40" y="1612">Scorecard, R​EUSE</text>
|
<text class="nj-exceptions-line" x="40" y="1666">Scorecard, R​EUSE</text>
|
||||||
</g>
|
</g>
|
||||||
<line class="nj-exceptions-edge" x1="180" y1="1642" x2="180" y2="1668" marker-end="url(#nj-qa-arrow-n)"/>
|
<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="1670" width="230" height="34" rx="17"/>
|
<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="1692">merge into develop</text>
|
<text class="nj-exceptions-stagetext" x="180" y="1746">merge into develop</text>
|
||||||
<line class="nj-exceptions-edge" x1="180" y1="1704" x2="180" y2="1730" marker-end="url(#nj-qa-arrow-n)"/>
|
<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="1732" width="300" height="46" rx="23"/>
|
<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="1754">release</text>
|
<text class="nj-exceptions-stagetext" x="180" y="1808">release</text>
|
||||||
<text class="nj-exceptions-stagesub" x="180" y="1770">no open vulnerability alert</text>
|
<text class="nj-exceptions-stagesub" x="180" y="1824">no open vulnerability alert</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
@@ -22,19 +22,24 @@ P = "nj-exceptions-"
|
|||||||
TRIGGERS = {
|
TRIGGERS = {
|
||||||
"push": ("every push / PR", "push"),
|
"push": ("every push / PR", "push"),
|
||||||
"pr": ("every PR", "push"),
|
"pr": ("every PR", "push"),
|
||||||
|
"merge": ("before merge", "push"),
|
||||||
|
"nightly": ("nightly", "sched"),
|
||||||
"sched": ("scheduled", "sched"),
|
"sched": ("scheduled", "sched"),
|
||||||
"cont": ("continuous", "cont"),
|
"cont": ("continuous", "cont"),
|
||||||
}
|
}
|
||||||
|
|
||||||
# title, triggers, lines (at most 31 characters, so they fit a card of the three-column drawing)
|
# title, triggers, lines (at most 31 characters, so they fit a card of the three-column drawing)
|
||||||
LANES = [
|
LANES = [
|
||||||
("Build matrix", ["push"], [
|
("Build matrix", ["push", "merge"], [
|
||||||
"50+ compilers: GCC, Clang, MSVC,",
|
"50+ compilers: GCC, Clang, MSVC,",
|
||||||
"AppleClang, Intel, NVIDIA, ...",
|
"AppleClang, Intel, NVIDIA, ...",
|
||||||
"C++11, 14, 17, 20, 23, and 26",
|
"C++11, 14, 17, 20, 23, and 26",
|
||||||
"libc++ and libstdc++",
|
"libc++ and libstdc++",
|
||||||
"warnings: Clang -Weverything,",
|
"warnings: Clang -Weverything,",
|
||||||
"GCC 300+ warnings",
|
"GCC 300+ warnings",
|
||||||
|
"a subset on every push and PR,",
|
||||||
|
"all before merge; Intel, NVIDIA,",
|
||||||
|
"CUDA, and ARM64 nightly",
|
||||||
]),
|
]),
|
||||||
("Unit tests (doctest)", ["push"], [
|
("Unit tests (doctest)", ["push"], [
|
||||||
"every public API function",
|
"every public API function",
|
||||||
@@ -44,7 +49,7 @@ LANES = [
|
|||||||
"custom number, string, array,",
|
"custom number, string, array,",
|
||||||
"and object types",
|
"and object types",
|
||||||
]),
|
]),
|
||||||
("Coverage", ["push"], [
|
("Coverage", ["nightly"], [
|
||||||
"unit tests cover all lines",
|
"unit tests cover all lines",
|
||||||
"of the code base",
|
"of the code base",
|
||||||
]),
|
]),
|
||||||
@@ -53,11 +58,11 @@ LANES = [
|
|||||||
"Clang Static Analyzer (52",
|
"Clang Static Analyzer (52",
|
||||||
"checkers), IWYU, Codacy",
|
"checkers), IWYU, Codacy",
|
||||||
"CodeQL, Semgrep, Flawfinder",
|
"CodeQL, Semgrep, Flawfinder",
|
||||||
"(also scheduled)",
|
"(also scheduled); Infer nightly",
|
||||||
]),
|
]),
|
||||||
("Dynamic analysis", ["push"], [
|
("Dynamic analysis", ["push", "nightly"], [
|
||||||
"runtime assertions",
|
"runtime assertions",
|
||||||
"Valgrind (Memcheck)",
|
"Valgrind (Memcheck), nightly",
|
||||||
"address and undefined behavior",
|
"address and undefined behavior",
|
||||||
"sanitizers, integer overflow,",
|
"sanitizers, integer overflow,",
|
||||||
"nullability violations",
|
"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">'
|
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)
|
'<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
|
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) +
|
"; ".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.")
|
". 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">'
|
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">'
|
||||||
|
|||||||
@@ -431,28 +431,6 @@ std::nullptr_t exception_context(const View& /*unused*/) noexcept
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace view
|
|
||||||
{
|
|
||||||
// the path and bytes of the value of a view (defined in <nlohmann/detail/view/errors.hpp>)
|
|
||||||
template<typename View>
|
|
||||||
std::string diagnostics_of(const View& v);
|
|
||||||
} // namespace view
|
|
||||||
|
|
||||||
// what goes in front of the message of an exception about j: nothing for a
|
|
||||||
// basic_json value (exception_context() passes the value itself), the context
|
|
||||||
// basic_json would add (JSON_DIAGNOSTICS, JSON_DIAGNOSTIC_POSITIONS) for a view
|
|
||||||
template<typename BasicJsonType, enable_if_t<is_basic_json<BasicJsonType>::value, int> = 0>
|
|
||||||
const char* exception_prefix(const BasicJsonType& /*unused*/) noexcept
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
|
|
||||||
std::string exception_prefix(const View& v)
|
|
||||||
{
|
|
||||||
return view::diagnostics_of(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief throws because @a j does not have the type a conversion expects
|
@brief throws because @a j does not have the type a conversion expects
|
||||||
@param[in] expected the expected type(s), e.g. "array" or "binary or array"
|
@param[in] expected the expected type(s), e.g. "array" or "binary or array"
|
||||||
@@ -464,7 +442,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha
|
|||||||
{
|
{
|
||||||
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
|
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
|
||||||
static_cast<void>(j);
|
static_cast<void>(j);
|
||||||
JSON_THROW(type_error::create(exception_id::type_mismatch, concat(exception_prefix(j), "type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
|
JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -347,7 +347,7 @@
|
|||||||
return ej_pair.second == j; \
|
return ej_pair.second == j; \
|
||||||
}); \
|
}); \
|
||||||
if (it != std::end(m)) e = it->first; \
|
if (it != std::end(m)) e = it->first; \
|
||||||
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat(nlohmann::detail::exception_prefix(j), "enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \
|
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
|
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class editor
|
|||||||
node* const o = own(object);
|
node* const o = own(object);
|
||||||
if (o->kind != node_kind::object && o->kind != node_kind::null)
|
if (o->kind != node_kind::object && o->kind != node_kind::null)
|
||||||
{
|
{
|
||||||
throw_type_error(site_of(object), exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
||||||
}
|
}
|
||||||
check_utf8(key.data(), key.size());
|
check_utf8(key.data(), key.size());
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
@@ -208,9 +208,9 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != node_kind::array)
|
if (a->kind != node_kind::array)
|
||||||
{
|
{
|
||||||
throw_type_error(site_of(array), exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(a, idx, a->len);
|
check_index(idx, a->len);
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
node* const slot = const_cast<node*>(nav::value(element_at<true>(m_doc, a, idx))); // NOLINT(cppcoreguidelines-pro-type-const-cast)
|
node* const slot = const_cast<node*>(nav::value(element_at<true>(m_doc, a, idx))); // NOLINT(cppcoreguidelines-pro-type-const-cast)
|
||||||
assign(slot, e, a, true);
|
assign(slot, e, a, true);
|
||||||
@@ -225,7 +225,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != node_kind::array && a->kind != node_kind::null)
|
if (a->kind != node_kind::array && a->kind != node_kind::null)
|
||||||
{
|
{
|
||||||
throw_type_error(site_of(array), exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
||||||
}
|
}
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
if (a->kind == node_kind::null)
|
if (a->kind == node_kind::null)
|
||||||
@@ -249,9 +249,9 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != node_kind::array)
|
if (a->kind != node_kind::array)
|
||||||
{
|
{
|
||||||
throw_type_error(site_of(array), exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(a, idx, a->len + 1);
|
check_index(idx, a->len + 1);
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
node* const slot = new_slot(e);
|
node* const slot = new_slot(e);
|
||||||
node* const h = block_of(m_doc, a, 1);
|
node* const h = block_of(m_doc, a, 1);
|
||||||
@@ -269,7 +269,7 @@ class editor
|
|||||||
node* const o = own(object);
|
node* const o = own(object);
|
||||||
if (o->kind != node_kind::object)
|
if (o->kind != node_kind::object)
|
||||||
{
|
{
|
||||||
throw_type_error(site_of(object), exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
||||||
}
|
}
|
||||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||||
{
|
{
|
||||||
@@ -287,9 +287,9 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != node_kind::array)
|
if (a->kind != node_kind::array)
|
||||||
{
|
{
|
||||||
throw_type_error(site_of(array), exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(a, idx, a->len);
|
check_index(idx, a->len);
|
||||||
node* const h = block_of(m_doc, a, 0);
|
node* const h = block_of(m_doc, a, 0);
|
||||||
std::memmove(h + 1 + idx, h + 2 + idx, (h->next - 2 - idx) * sizeof(node));
|
std::memmove(h + 1 + idx, h + 2 + idx, (h->next - 2 - idx) * sizeof(node));
|
||||||
--h->next;
|
--h->next;
|
||||||
@@ -316,11 +316,11 @@ class editor
|
|||||||
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||||
}
|
}
|
||||||
|
|
||||||
void check_index(const node* array, std::size_t idx, std::size_t limit) const
|
static void check_index(std::size_t idx, std::size_t limit)
|
||||||
{
|
{
|
||||||
if (idx >= limit)
|
if (idx >= limit)
|
||||||
{
|
{
|
||||||
throw_array_index_out_of_range(error_site(&m_doc, array), idx);
|
throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,11 @@
|
|||||||
|
|
||||||
#include <algorithm> // min
|
#include <algorithm> // min
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <string> // string, to_string
|
#include <string> // string
|
||||||
#include <vector> // vector
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <nlohmann/detail/view/builder.hpp>
|
#include <nlohmann/detail/view/builder.hpp>
|
||||||
#include <nlohmann/detail/view/document_data.hpp>
|
|
||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/view/materialize.hpp>
|
|
||||||
#include <nlohmann/detail/view/node.hpp>
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -26,292 +22,8 @@ namespace detail
|
|||||||
namespace view
|
namespace view
|
||||||
{
|
{
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief the value an exception is about
|
|
||||||
|
|
||||||
basic_json passes the value to the exception, which then names its path
|
|
||||||
(JSON_DIAGNOSTICS) and its bytes in the source (JSON_DIAGNOSTIC_POSITIONS). A
|
|
||||||
view's value has no parent pointer and no stored positions: both are found
|
|
||||||
from the document and the node, when the exception is thrown (so only the
|
|
||||||
error path pays for them). Without the two macros, a site is empty.
|
|
||||||
*/
|
|
||||||
class error_site
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
error_site() noexcept = default;
|
|
||||||
|
|
||||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
error_site(const document_data* d, const node* n) noexcept
|
|
||||||
: m_doc(d), m_node(n)
|
|
||||||
{}
|
|
||||||
|
|
||||||
const document_data* doc() const noexcept
|
|
||||||
{
|
|
||||||
return m_doc;
|
|
||||||
}
|
|
||||||
const node* value() const noexcept
|
|
||||||
{
|
|
||||||
return m_node;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const document_data* m_doc = nullptr;
|
|
||||||
const node* m_node = nullptr;
|
|
||||||
#else
|
|
||||||
error_site(const document_data* /*d*/, const node* /*n*/) noexcept {}
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
/// the site of a view's value (the views make this a friend)
|
|
||||||
struct view_access
|
|
||||||
{
|
|
||||||
template<typename View>
|
|
||||||
static error_site site(const View& v) noexcept
|
|
||||||
{
|
|
||||||
return error_site(v.m_doc, v.m_node);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename View>
|
|
||||||
error_site site_of(const View& v) noexcept
|
|
||||||
{
|
|
||||||
static_assert(is_json_view<View>::value, "the site of a value is only defined for views");
|
|
||||||
return view_access::site(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if JSON_DIAGNOSTICS
|
|
||||||
/*!
|
|
||||||
@brief the JSON pointer tokens from the root of document @a d to node @a target
|
|
||||||
|
|
||||||
Nodes have no parent index, so the path is found by descending from the root:
|
|
||||||
before the first edit, only into the subtree that contains the target (a
|
|
||||||
container spans the next `next` nodes); after edits, elements can live in
|
|
||||||
other sequences, so every container is searched. Iterative (the nesting depth
|
|
||||||
is limited by memory only). False if the target is not found (then tokens is
|
|
||||||
unspecified).
|
|
||||||
*/
|
|
||||||
template<bool Editable>
|
|
||||||
bool value_path(const document_data& d, const node* target, std::vector<std::string>& tokens)
|
|
||||||
{
|
|
||||||
using nav = navigation<Editable>;
|
|
||||||
struct frame
|
|
||||||
{
|
|
||||||
const node* pos;
|
|
||||||
const node* end;
|
|
||||||
std::size_t index;
|
|
||||||
bool object;
|
|
||||||
};
|
|
||||||
const node* const root = d.nodes;
|
|
||||||
if (target == root)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!is_container(*root))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::vector<frame> stack;
|
|
||||||
stack.push_back(frame{nav::first(d, root), nav::end(d, root), 0, root->kind == node_kind::object});
|
|
||||||
// (tokens[i] leads from stack[i] to stack[i + 1], or to the target)
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
// (f is not used after the stack changes: pop_back() below, push_back() at the end)
|
|
||||||
frame& f = stack.back();
|
|
||||||
if (f.pos == f.end)
|
|
||||||
{
|
|
||||||
stack.pop_back();
|
|
||||||
if (stack.empty())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
tokens.pop_back();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const node* entry = f.pos;
|
|
||||||
std::string token;
|
|
||||||
if (f.object)
|
|
||||||
{
|
|
||||||
// (as basic_json: the key up to a null byte)
|
|
||||||
token.assign(d.str(*entry), entry->len);
|
|
||||||
const std::size_t nul = token.find('\0');
|
|
||||||
if (nul != std::string::npos)
|
|
||||||
{
|
|
||||||
token.resize(nul);
|
|
||||||
}
|
|
||||||
++entry;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
token = std::to_string(f.index);
|
|
||||||
}
|
|
||||||
const node* const child = nav::value(entry);
|
|
||||||
if (child == target)
|
|
||||||
{
|
|
||||||
tokens.push_back(token);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
f.pos = document_data::after(entry);
|
|
||||||
++f.index;
|
|
||||||
if (is_container(*child) && (nav::editable() || (target > child && target < child + child->next)))
|
|
||||||
{
|
|
||||||
tokens.push_back(token);
|
|
||||||
stack.push_back(frame{nav::first(d, child), nav::end(d, child), 0, child->kind == node_kind::object});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
/*!
|
|
||||||
@brief the bytes [start, end) of node @a n in the source, as materialize() sets them
|
|
||||||
|
|
||||||
Scalars as position_source::set_scalar; an array or object from its opening
|
|
||||||
bracket to its closing bracket, which is found by following the chain of last
|
|
||||||
elements to a scalar (or an empty container) and scanning from there to the
|
|
||||||
closing brackets. None (npos) for values written by edits, and for containers
|
|
||||||
whose last element has none, as with materialize().
|
|
||||||
*/
|
|
||||||
template<bool Editable>
|
|
||||||
void value_range(const document_data& d, const node* n, std::size_t& start, std::size_t& end)
|
|
||||||
{
|
|
||||||
using nav = navigation<Editable>;
|
|
||||||
position_source p;
|
|
||||||
start = std::string::npos;
|
|
||||||
end = std::string::npos;
|
|
||||||
if (!is_container(*n))
|
|
||||||
{
|
|
||||||
p.set_scalar(d, *n);
|
|
||||||
start = p.start;
|
|
||||||
end = p.end;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// the open brackets: n, then the last element as long as it is a non-empty container
|
|
||||||
std::size_t closing = 0;
|
|
||||||
std::size_t pos = std::string::npos;
|
|
||||||
for (const node* c = n;;)
|
|
||||||
{
|
|
||||||
if (!is_container(*c))
|
|
||||||
{
|
|
||||||
p.set_scalar(d, *c);
|
|
||||||
pos = p.end;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ((c->flags & node_flags::is_new) != 0 || c->off >= d.size)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
++closing;
|
|
||||||
const node* const first = nav::first(d, c);
|
|
||||||
const node* const last_end = nav::end(d, c);
|
|
||||||
if (first == last_end)
|
|
||||||
{
|
|
||||||
pos = std::size_t{c->off} + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const std::size_t value_offset = c->kind == node_kind::object ? 1 : 0;
|
|
||||||
const node* last = first;
|
|
||||||
for (const node* e = document_data::after(first + value_offset); e != last_end; e = document_data::after(e + value_offset))
|
|
||||||
{
|
|
||||||
last = e;
|
|
||||||
}
|
|
||||||
c = nav::value(last + value_offset);
|
|
||||||
}
|
|
||||||
for (; closing != 0 && pos != std::string::npos; --closing)
|
|
||||||
{
|
|
||||||
pos = position_source::container_end(d.base[0], d.size, pos);
|
|
||||||
}
|
|
||||||
if (pos != std::string::npos)
|
|
||||||
{
|
|
||||||
start = n->off;
|
|
||||||
end = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// the context basic_json puts in front of the message of an exception about
|
|
||||||
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
|
||||||
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only
|
|
||||||
/// if there is a path)
|
|
||||||
///
|
|
||||||
/// This and the throw helpers that take an error_site are templates, so that
|
|
||||||
/// the path and range code is instantiated only where an exception can be
|
|
||||||
/// thrown: instantiated in every translation unit (as a non-template inline
|
|
||||||
/// function makes it), it changed GCC's early inlining of unrelated view
|
|
||||||
/// functions, so that GCC 13 no longer inlined a recursive walk over a view
|
|
||||||
/// into itself (2.7 times the instructions).
|
|
||||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
template<typename Unused = void>
|
|
||||||
std::string diagnostics(const error_site site)
|
|
||||||
{
|
|
||||||
if (site.doc() == nullptr || site.value() == nullptr)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
const document_data& d = *site.doc();
|
|
||||||
const bool edited = d.edits != nullptr;
|
|
||||||
std::string context;
|
|
||||||
#if JSON_DIAGNOSTICS
|
|
||||||
std::vector<std::string> tokens;
|
|
||||||
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
|
|
||||||
if (!found || tokens.empty())
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
std::string path;
|
|
||||||
for (const std::string& token : tokens)
|
|
||||||
{
|
|
||||||
path.push_back('/');
|
|
||||||
path += detail::escape(token);
|
|
||||||
}
|
|
||||||
context = concat('(', path, ") ");
|
|
||||||
#endif
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
std::size_t start = 0;
|
|
||||||
std::size_t end = 0;
|
|
||||||
if (edited)
|
|
||||||
{
|
|
||||||
value_range<true>(d, site.value(), start, end);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value_range<false>(d, site.value(), start, end);
|
|
||||||
}
|
|
||||||
if (start != std::string::npos && end != std::string::npos)
|
|
||||||
{
|
|
||||||
context += concat("(bytes ", std::to_string(start), "-", std::to_string(end), ") ");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
template<typename Unused = void>
|
|
||||||
std::string diagnostics(const error_site /*site*/)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// the context of an exception about view @a v (see exception_prefix())
|
|
||||||
template<typename View>
|
|
||||||
std::string diagnostics_of(const View& v)
|
|
||||||
{
|
|
||||||
return diagnostics<>(site_of(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
||||||
// small enough to be inlined. The overloads with an error_site put its context
|
// small enough to be inlined.
|
||||||
// in front of the message.
|
|
||||||
//
|
|
||||||
// The overloads without one are for exceptions that are not about a value of a
|
|
||||||
// document: limits and invalid input of the editing and image functions, a
|
|
||||||
// discarded view (it has no node), a view of another document (its path would
|
|
||||||
// be one in that document), and those that basic_json creates without a
|
|
||||||
// context too (the errors of json_pointer, invalid UTF-8). The one exception
|
|
||||||
// about a value that has no site is key(), see view_iterator::key().
|
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
||||||
{
|
{
|
||||||
@@ -333,24 +45,6 @@ std::string diagnostics_of(const View& v)
|
|||||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Unused = void>
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_type_error(const error_site site, exception_id id, const char* prefix, const char* type)
|
|
||||||
{
|
|
||||||
NLOHMANN_VIEW_THROW(type_error::create(id, concat(diagnostics<>(site), prefix, type), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Unused = void>
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_out_of_range(const error_site site, exception_id id, const std::string& msg)
|
|
||||||
{
|
|
||||||
NLOHMANN_VIEW_THROW(out_of_range::create(id, concat(diagnostics<>(site), msg), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Unused = void>
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_invalid_iterator(const error_site site, exception_id id, const char* msg)
|
|
||||||
{
|
|
||||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, concat(diagnostics<>(site), msg), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a parse error without a position (as those of json_pointer)
|
/// a parse error without a position (as those of json_pointer)
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
|
||||||
{
|
{
|
||||||
@@ -359,23 +53,22 @@ template<typename Unused = void>
|
|||||||
|
|
||||||
/// out_of_range.401: the array index @a idx is not below the size
|
/// out_of_range.401: the array index @a idx is not below the size
|
||||||
template<typename Index>
|
template<typename Index>
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const error_site site, const Index idx)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
|
||||||
{
|
{
|
||||||
throw_out_of_range(site, exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
|
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
|
||||||
template<typename Unused = void>
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_past_end(const error_site site, std::size_t size)
|
|
||||||
{
|
{
|
||||||
throw_out_of_range(site, exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// out_of_range.403: the object has no member @a key
|
/// out_of_range.403: the object has no member @a key
|
||||||
template<typename Key>
|
template<typename Key>
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const error_site site, const Key& key)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
|
||||||
{
|
{
|
||||||
throw_out_of_range(site, exception_id::key_not_found, concat("key '", key, "' not found"));
|
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SAX handler of the error replay: accepts every event without storing it,
|
/// SAX handler of the error replay: accepts every event without storing it,
|
||||||
|
|||||||
@@ -105,10 +105,7 @@ class view_iterator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// the key of the current object member; throws invalid_iterator.207 for
|
/// the key of the current object member; throws invalid_iterator.207 for
|
||||||
/// other iterators, like basic_json's iterators (but without the context
|
/// other iterators, like basic_json's iterators
|
||||||
/// JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS add: the iterator does not
|
|
||||||
/// know the value it iterates over, and keeping it would make iterators
|
|
||||||
/// larger, which keeps GCC from inlining recursive walks)
|
|
||||||
string_view_t key() const
|
string_view_t key() const
|
||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// throws the exception json_pointer::array_index throws for this status
|
/// throws the exception json_pointer::array_index throws for this status
|
||||||
/// (without a context, as json_pointer::array_index)
|
|
||||||
template<typename StringType>
|
template<typename StringType>
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
||||||
{
|
{
|
||||||
@@ -84,7 +83,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (mode == pointer_mode::checked)
|
if (mode == pointer_mode::checked)
|
||||||
{
|
{
|
||||||
throw_key_not_found(site_of(cur), token);
|
throw_key_not_found(token);
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -96,7 +95,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (throwing)
|
if (throwing)
|
||||||
{
|
{
|
||||||
throw_array_index_past_end(site_of(cur), cur.size());
|
throw_array_index_past_end(cur.size());
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -115,7 +114,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (mode == pointer_mode::checked)
|
if (mode == pointer_mode::checked)
|
||||||
{
|
{
|
||||||
throw_array_index_out_of_range(site_of(cur), idx);
|
throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -125,7 +124,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (throwing)
|
if (throwing)
|
||||||
{
|
{
|
||||||
throw_out_of_range(site_of(cur), exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
|
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
|
|||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_type_error(error_site(&d, &n), exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ std::vector<U, A> vector_value(const View& v)
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
||||||
{
|
{
|
||||||
throw_type_error(site_of(v), exception_id::type_mismatch, "type must be array, but is ", v.type_name());
|
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name());
|
||||||
}
|
}
|
||||||
std::vector<U, A> r;
|
std::vector<U, A> r;
|
||||||
r.reserve(v.size());
|
r.reserve(v.size());
|
||||||
@@ -88,7 +88,7 @@ Map map_value(const View& v)
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
||||||
{
|
{
|
||||||
throw_type_error(site_of(v), exception_id::type_mismatch, "type must be object, but is ", v.type_name());
|
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name());
|
||||||
}
|
}
|
||||||
Map r;
|
Map r;
|
||||||
for (auto it = v.begin(); it != v.end(); ++it)
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
|
||||||
}
|
}
|
||||||
return lookup(key);
|
return lookup(key);
|
||||||
}
|
}
|
||||||
@@ -311,7 +311,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
|
||||||
}
|
}
|
||||||
return idx < m_node->len ? element(idx) : basic_json_view();
|
return idx < m_node->len ? element(idx) : basic_json_view();
|
||||||
}
|
}
|
||||||
@@ -344,12 +344,12 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = lookup(key);
|
const basic_json_view r = lookup(key);
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
||||||
{
|
{
|
||||||
detail::view::throw_key_not_found(site(), std::string(key.data(), key.size()));
|
detail::view::throw_key_not_found(std::string(key.data(), key.size()));
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -370,11 +370,11 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
||||||
{
|
{
|
||||||
detail::view::throw_array_index_out_of_range(site(), idx);
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return element(idx);
|
return element(idx);
|
||||||
}
|
}
|
||||||
@@ -402,7 +402,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = lookup(key);
|
const basic_json_view r = lookup(key);
|
||||||
return r.is_discarded() ? default_value : r.template get<T>();
|
return r.is_discarded() ? default_value : r.template get<T>();
|
||||||
@@ -412,7 +412,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||||
}
|
}
|
||||||
// (the default is made only if there is no such member)
|
// (the default is made only if there is no such member)
|
||||||
const basic_json_view r = lookup(key);
|
const basic_json_view r = lookup(key);
|
||||||
@@ -427,7 +427,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||||
return r.is_discarded() ? default_value : r.template get<T>();
|
return r.is_discarded() ? default_value : r.template get<T>();
|
||||||
@@ -437,7 +437,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
||||||
@@ -450,7 +450,7 @@ class basic_json_view
|
|||||||
const iterator it = begin();
|
const iterator it = begin();
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
||||||
{
|
{
|
||||||
detail::view::throw_invalid_iterator(site(), detail::exception_id::iterator_value_unavailable, "cannot get value");
|
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value");
|
||||||
}
|
}
|
||||||
return *it;
|
return *it;
|
||||||
}
|
}
|
||||||
@@ -606,7 +606,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
|
||||||
}
|
}
|
||||||
return {m_doc->str(*m_node), m_node->len};
|
return {m_doc->str(*m_node), m_node->len};
|
||||||
}
|
}
|
||||||
@@ -618,7 +618,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||||
}
|
}
|
||||||
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
||||||
}
|
}
|
||||||
@@ -764,19 +764,15 @@ class basic_json_view
|
|||||||
template<typename, view_mode> friend class basic_json_view;
|
template<typename, view_mode> friend class basic_json_view;
|
||||||
template<typename, typename> friend class detail::view::editor;
|
template<typename, typename> friend class detail::view::editor;
|
||||||
template<typename, typename> friend class detail::view::view_side;
|
template<typename, typename> friend class detail::view::view_side;
|
||||||
friend struct detail::view::view_access;
|
|
||||||
friend iterator;
|
friend iterator;
|
||||||
|
|
||||||
|
/// whether the view is one of an editable document
|
||||||
|
static constexpr bool is_editable = Editable;
|
||||||
|
|
||||||
basic_json_view(const document_data* d, const node* n) noexcept
|
basic_json_view(const document_data* d, const node* n) noexcept
|
||||||
: m_doc(d), m_node(n)
|
: m_doc(d), m_node(n)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// this value as the context of an exception
|
|
||||||
detail::view::error_site site() const noexcept
|
|
||||||
{
|
|
||||||
return detail::view::error_site(m_doc, m_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
using json_side_t = detail::view::json_side<BasicJsonType, string_view_t>;
|
using json_side_t = detail::view::json_side<BasicJsonType, string_view_t>;
|
||||||
|
|
||||||
static detail::view::view_side<BasicJsonType, basic_json_view> side(const basic_json_view& v) noexcept
|
static detail::view::view_side<BasicJsonType, basic_json_view> side(const basic_json_view& v) noexcept
|
||||||
@@ -883,7 +879,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
|
||||||
}
|
}
|
||||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||||
}
|
}
|
||||||
@@ -893,7 +889,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||||
}
|
}
|
||||||
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
||||||
}
|
}
|
||||||
@@ -902,7 +898,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -1316,7 +1312,7 @@ class basic_json_document
|
|||||||
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||||
view_type set(view_type array, I idx, V && value)
|
view_type set(view_type array, I idx, V && value)
|
||||||
{
|
{
|
||||||
return editor().set(array, index(array, idx), std::forward<V>(value));
|
return editor().set(array, index(idx), std::forward<V>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// set the value at a JSON pointer: its parent must exist; an object
|
/// set the value at a JSON pointer: its parent must exist; an object
|
||||||
@@ -1353,7 +1349,7 @@ class basic_json_document
|
|||||||
const std::size_t idx = pointer_index(token);
|
const std::size_t idx = pointer_index(token);
|
||||||
if (idx >= 0xFFFFFFFFu)
|
if (idx >= 0xFFFFFFFFu)
|
||||||
{
|
{
|
||||||
detail::view::throw_array_index_out_of_range(detail::view::site_of(parent), idx);
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < idx; ++i)
|
for (std::size_t i = 0; i < idx; ++i)
|
||||||
{
|
{
|
||||||
@@ -1387,7 +1383,7 @@ class basic_json_document
|
|||||||
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||||
view_type insert(view_type array, I idx, V && value)
|
view_type insert(view_type array, I idx, V && value)
|
||||||
{
|
{
|
||||||
return editor().insert(array, index(array, idx), std::forward<V>(value));
|
return editor().insert(array, index(idx), std::forward<V>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// remove all members with this key; returns their number
|
/// remove all members with this key; returns their number
|
||||||
@@ -1400,7 +1396,7 @@ class basic_json_document
|
|||||||
template<typename I, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
template<typename I, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||||
void erase(view_type array, I idx)
|
void erase(view_type array, I idx)
|
||||||
{
|
{
|
||||||
editor().erase(array, index(array, idx));
|
editor().erase(array, index(idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// remove the value at a JSON pointer; returns the number of removed
|
/// remove the value at a JSON pointer; returns the number of removed
|
||||||
@@ -1434,25 +1430,25 @@ class basic_json_document
|
|||||||
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// an index into @a array (out_of_range.401 if negative)
|
/// an index (out_of_range.401 if negative)
|
||||||
template<typename I>
|
template<typename I>
|
||||||
static std::size_t index(const view_type& array, I idx)
|
static std::size_t index(I idx)
|
||||||
{
|
{
|
||||||
return index(array, idx, std::is_signed<I> {});
|
return index(idx, std::is_signed<I> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename I>
|
template<typename I>
|
||||||
static std::size_t index(const view_type& array, I idx, std::true_type /*signed*/)
|
static std::size_t index(I idx, std::true_type /*signed*/)
|
||||||
{
|
{
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
{
|
{
|
||||||
detail::view::throw_array_index_out_of_range(detail::view::site_of(array), idx);
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return static_cast<std::size_t>(idx);
|
return static_cast<std::size_t>(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename I>
|
template<typename I>
|
||||||
static std::size_t index(const view_type& /*array*/, I idx, std::false_type /*unsigned*/)
|
static std::size_t index(I idx, std::false_type /*unsigned*/)
|
||||||
{
|
{
|
||||||
return static_cast<std::size_t>(idx);
|
return static_cast<std::size_t>(idx);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-24
@@ -2720,7 +2720,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP
|
|||||||
return ej_pair.second == j; \
|
return ej_pair.second == j; \
|
||||||
}); \
|
}); \
|
||||||
if (it != std::end(m)) e = it->first; \
|
if (it != std::end(m)) e = it->first; \
|
||||||
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat(nlohmann::detail::exception_prefix(j), "enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \
|
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
|
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
|
||||||
@@ -5928,28 +5928,6 @@ std::nullptr_t exception_context(const View& /*unused*/) noexcept
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace view
|
|
||||||
{
|
|
||||||
// the path and bytes of the value of a view (defined in <nlohmann/detail/view/errors.hpp>)
|
|
||||||
template<typename View>
|
|
||||||
std::string diagnostics_of(const View& v);
|
|
||||||
} // namespace view
|
|
||||||
|
|
||||||
// what goes in front of the message of an exception about j: nothing for a
|
|
||||||
// basic_json value (exception_context() passes the value itself), the context
|
|
||||||
// basic_json would add (JSON_DIAGNOSTICS, JSON_DIAGNOSTIC_POSITIONS) for a view
|
|
||||||
template<typename BasicJsonType, enable_if_t<is_basic_json<BasicJsonType>::value, int> = 0>
|
|
||||||
const char* exception_prefix(const BasicJsonType& /*unused*/) noexcept
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
|
|
||||||
std::string exception_prefix(const View& v)
|
|
||||||
{
|
|
||||||
return view::diagnostics_of(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief throws because @a j does not have the type a conversion expects
|
@brief throws because @a j does not have the type a conversion expects
|
||||||
@param[in] expected the expected type(s), e.g. "array" or "binary or array"
|
@param[in] expected the expected type(s), e.g. "array" or "binary or array"
|
||||||
@@ -5961,7 +5939,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha
|
|||||||
{
|
{
|
||||||
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
|
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
|
||||||
static_cast<void>(j);
|
static_cast<void>(j);
|
||||||
JSON_THROW(type_error::create(exception_id::type_mismatch, concat(exception_prefix(j), "type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
|
JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
Generated
+508
-826
File diff suppressed because it is too large.
Load diff
@@ -15,7 +15,6 @@
|
|||||||
#include <nlohmann/json_view.hpp>
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
#include <algorithm> // any_of
|
#include <algorithm> // any_of
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <functional> // function
|
#include <functional> // function
|
||||||
#include <random> // mt19937
|
#include <random> // mt19937
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
@@ -89,26 +88,6 @@ inline bool has_duplicate_keys(const nlohmann::ordered_json_view& v)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// the message of an exception about the value at path (a JSON pointer, "" for
|
|
||||||
// the root) whose source bytes are [start, end): the context basic_json adds
|
|
||||||
// with JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS, if enabled, then what
|
|
||||||
inline std::string message(const std::string& id, const std::string& path, std::size_t start, std::size_t end, const std::string& what)
|
|
||||||
{
|
|
||||||
std::string context;
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
context = "(bytes " + std::to_string(start) + "-" + std::to_string(end) + ") ";
|
|
||||||
#else
|
|
||||||
static_cast<void>(start);
|
|
||||||
static_cast<void>(end);
|
|
||||||
#endif
|
|
||||||
#if JSON_DIAGNOSTICS
|
|
||||||
context = path.empty() ? "" : "(" + path + ") " + context;
|
|
||||||
#else
|
|
||||||
static_cast<void>(path);
|
|
||||||
#endif
|
|
||||||
return "[json.exception." + id + "] " + context + what;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
// the exception a call throws, or "" if it throws none
|
// the exception a call throws, or "" if it throws none
|
||||||
inline std::string exception_of_call(const std::function<void()>& f)
|
inline std::string exception_of_call(const std::function<void()>& f)
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ TEST_CASE("json_view")
|
|||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
// the parents are set, so errors name the path
|
// the parents are set, so errors name the path
|
||||||
const json m = materialized<json_document>(R"({"a":{"b":[1]}})");
|
const json m = materialized<json_document>(R"({"a":{"b":[1]}})");
|
||||||
CHECK_THROWS_WITH_AS(m.at("a").at("b").at(0).at("x"), json_view_test::message("type_error.304", "/a/b/0", 11, 12, "cannot use at() with number").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(m.at("a").at("b").at(0).at("x"), "[json.exception.type_error.304] (/a/b/0) cannot use at() with number", json::type_error&);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,6 +750,19 @@ std::string exception_of(F f)
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the exception a call on a basic_json value throws, without the byte range of
|
||||||
|
// the value (JSON_DIAGNOSTIC_POSITIONS), which the exceptions of views lack
|
||||||
|
template<typename F>
|
||||||
|
std::string json_exception_of(F f)
|
||||||
|
{
|
||||||
|
std::string what = exception_of(f);
|
||||||
|
const std::size_t at = what.find("] (bytes ");
|
||||||
|
if (at != std::string::npos)
|
||||||
|
{
|
||||||
|
what.erase(at + 2, what.find(") ", at) - at);
|
||||||
|
}
|
||||||
|
return what;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// compares a view with the ordered_json value materialize() gives for it:
|
// compares a view with the ordered_json value materialize() gives for it:
|
||||||
@@ -958,25 +971,24 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
if (!j.is_object())
|
if (!j.is_object())
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["a"]); }) == exception_of([&] { static_cast<void>(j["a"]); }));
|
CHECK(exception_of([&] { static_cast<void>(v["a"]); }) == json_exception_of([&] { static_cast<void>(j["a"]); }));
|
||||||
}
|
}
|
||||||
if (!j.is_array())
|
if (!j.is_array())
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v[0]); }) == exception_of([&] { static_cast<void>(j[0]); }));
|
CHECK(exception_of([&] { static_cast<void>(v[0]); }) == json_exception_of([&] { static_cast<void>(j[0]); }));
|
||||||
}
|
}
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.at("a")); }) == exception_of([&] { static_cast<void>(j.at("a")); }));
|
CHECK(exception_of([&] { static_cast<void>(v.at("a")); }) == json_exception_of([&] { static_cast<void>(j.at("a")); }));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.at("missing")); }) == exception_of([&] { static_cast<void>(j.at("missing")); }));
|
CHECK(exception_of([&] { static_cast<void>(v.at("missing")); }) == json_exception_of([&] { static_cast<void>(j.at("missing")); }));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.at(0)); }) == exception_of([&] { static_cast<void>(j.at(0)); }));
|
CHECK(exception_of([&] { static_cast<void>(v.at(0)); }) == json_exception_of([&] { static_cast<void>(j.at(0)); }));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.at(5)); }) == exception_of([&] { static_cast<void>(j.at(5)); }));
|
CHECK(exception_of([&] { static_cast<void>(v.at(5)); }) == json_exception_of([&] { static_cast<void>(j.at(5)); }));
|
||||||
if (!(j.is_object() && j.empty())) // (key() of an end iterator)
|
if (!(j.is_object() && j.empty())) // (key() of an end iterator)
|
||||||
{
|
{
|
||||||
// (without the context of basic_json: view iterators do not know the value they iterate over)
|
CHECK(exception_of([&] { static_cast<void>(v.begin().key()); }) == json_exception_of([&] { static_cast<void>(j.begin().key()); }));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.begin().key()); }) == (j.is_object() ? "" : "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators"));
|
|
||||||
}
|
}
|
||||||
if (!j.empty() || j.is_null())
|
if (!j.empty() || j.is_null())
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.front()); }) == exception_of([&] { static_cast<void>(j.front()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.front()); }) == json_exception_of([&] { static_cast<void>(j.front()); }));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.back()); }) == exception_of([&] { static_cast<void>(j.back()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.back()); }) == json_exception_of([&] { static_cast<void>(j.back()); }));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
CHECK(v.contains("a") == j.contains("a"));
|
CHECK(v.contains("a") == j.contains("a"));
|
||||||
@@ -988,8 +1000,8 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
const json_document d = json_document::parse(R"({"a":[]})");
|
const json_document d = json_document::parse(R"({"a":[]})");
|
||||||
CHECK(d.root()["b"].is_discarded());
|
CHECK(d.root()["b"].is_discarded());
|
||||||
CHECK(d.root()["a"][0].is_discarded());
|
CHECK(d.root()["a"][0].is_discarded());
|
||||||
CHECK_THROWS_WITH_AS(d.root()["a"].front(), json_view_test::message("invalid_iterator.214", "/a", 5, 7, "cannot get value").c_str(), json::invalid_iterator&);
|
CHECK_THROWS_WITH_AS(d.root()["a"].front(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
|
||||||
CHECK_THROWS_WITH_AS(d.root()["a"].back(), json_view_test::message("invalid_iterator.214", "/a", 5, 7, "cannot get value").c_str(), json::invalid_iterator&);
|
CHECK_THROWS_WITH_AS(d.root()["a"].back(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
|
||||||
const json_view invalid{};
|
const json_view invalid{};
|
||||||
CHECK(invalid.begin() == invalid.end());
|
CHECK(invalid.begin() == invalid.end());
|
||||||
CHECK(std::string(invalid.type_name()) == "discarded");
|
CHECK(std::string(invalid.type_name()) == "discarded");
|
||||||
@@ -1020,10 +1032,10 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
CHECK(v[json_view::json_pointer("/a/b/5")][0].is_discarded());
|
CHECK(v[json_view::json_pointer("/a/b/5")][0].is_discarded());
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
// type errors on values that are not discarded stay
|
// type errors on values that are not discarded stay
|
||||||
CHECK_THROWS_WITH_AS(v[0], json_view_test::message("type_error.305", "", 0, 29, "cannot use operator[] with a numeric argument with object").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(v[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with object", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(v["a"]["b"]["c"], json_view_test::message("type_error.305", "/a/b", 10, 17, "cannot use operator[] with a string argument with array").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(v["a"]["b"]["c"], "[json.exception.type_error.305] cannot use operator[] with a string argument with array", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(v["s"]["c"], json_view_test::message("type_error.305", "/s", 23, 28, "cannot use operator[] with a string argument with string").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(v["s"]["c"], "[json.exception.type_error.305] cannot use operator[] with a string argument with string", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(v["s"][0], json_view_test::message("type_error.305", "/s", 23, 28, "cannot use operator[] with a numeric argument with string").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(v["s"][0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with string", json::type_error&);
|
||||||
CHECK_THROWS_AS(v["a"]["b"][0]["c"], json::type_error&);
|
CHECK_THROWS_AS(v["a"]["b"][0]["c"], json::type_error&);
|
||||||
CHECK_THROWS_AS(v["s"][json_view::json_pointer("/x")], json::out_of_range&);
|
CHECK_THROWS_AS(v["s"][json_view::json_pointer("/x")], json::out_of_range&);
|
||||||
// at() keeps throwing on a discarded view
|
// at() keeps throwing on a discarded view
|
||||||
@@ -1098,9 +1110,9 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
CHECK(v[(std::numeric_limits<std::size_t>::max)()].is_discarded());
|
CHECK(v[(std::numeric_limits<std::size_t>::max)()].is_discarded());
|
||||||
CHECK(v[std::numeric_limits<int>::max()].is_discarded());
|
CHECK(v[std::numeric_limits<int>::max()].is_discarded());
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
CHECK_THROWS_WITH_AS(v.at(3), json_view_test::message("out_of_range.401", "", 0, 10, "array index 3 is out of range").c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(v.at(3), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(v.at(3u), json_view_test::message("out_of_range.401", "", 0, 10, "array index 3 is out of range").c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(v.at(3u), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(v.at(std::int64_t(3)), json_view_test::message("out_of_range.401", "", 0, 10, "array index 3 is out of range").c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(v.at(std::int64_t(3)), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
|
||||||
CHECK_THROWS_AS(v.at(-1), json::out_of_range&);
|
CHECK_THROWS_AS(v.at(-1), json::out_of_range&);
|
||||||
CHECK_THROWS_AS(v.at(-1L), json::out_of_range&);
|
CHECK_THROWS_AS(v.at(-1L), json::out_of_range&);
|
||||||
CHECK_THROWS_AS(v.at(std::int64_t(-1)), json::out_of_range&);
|
CHECK_THROWS_AS(v.at(std::int64_t(-1)), json::out_of_range&);
|
||||||
@@ -1234,6 +1246,26 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// an exception message without the context that basic_json adds with
|
||||||
|
// JSON_DIAGNOSTICS ("(/path) ") and JSON_DIAGNOSTIC_POSITIONS ("(bytes 1-2) ");
|
||||||
|
// the view's exceptions have no such context
|
||||||
|
std::string without_path(std::string msg)
|
||||||
|
{
|
||||||
|
for (const char* prefix :
|
||||||
|
{"] (/", "] (bytes "
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::size_t open = msg.find(prefix);
|
||||||
|
if (open != std::string::npos)
|
||||||
|
{
|
||||||
|
msg.erase(open + 2, msg.find(") ", open) + 2 - (open + 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// the bits of a float, to compare values bit for bit
|
// the bits of a float, to compare values bit for bit
|
||||||
std::uint64_t bits(double x)
|
std::uint64_t bits(double x)
|
||||||
{
|
{
|
||||||
@@ -1304,18 +1336,18 @@ void check_values(const ordered_json_view& v, const ordered_json& j, const std::
|
|||||||
// conversions to the wrong type throw what basic_json throws
|
// conversions to the wrong type throw what basic_json throws
|
||||||
if (!j.is_number())
|
if (!j.is_number())
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.get<int>()); }) == exception_of([&] { static_cast<void>(j.get<int>()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.get<int>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<int>()); })));
|
||||||
}
|
}
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.get<bool>()); }) == exception_of([&] { static_cast<void>(j.get<bool>()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.get<bool>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<bool>()); })));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::string>()); }) == exception_of([&] { static_cast<void>(j.get<std::string>()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.get<std::string>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::string>()); })));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::nullptr_t>()); }) == exception_of([&] { static_cast<void>(j.get<std::nullptr_t>()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.get<std::nullptr_t>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::nullptr_t>()); })));
|
||||||
if (!j.is_array())
|
if (!j.is_array())
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::vector<int>>()); }) == exception_of([&] { static_cast<void>(j.get<std::vector<int>>()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.get<std::vector<int>>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::vector<int>>()); })));
|
||||||
}
|
}
|
||||||
if (!j.is_object())
|
if (!j.is_object())
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::map<std::string, int>>()); }) == exception_of([&] { static_cast<void>(j.get<std::map<std::string, int>>()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.get<std::map<std::string, int>>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::map<std::string, int>>()); })));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1458,8 +1490,8 @@ TEST_CASE("json_view values")
|
|||||||
CHECK(v[3].number_token() == "123456789012345678901234567890");
|
CHECK(v[3].number_token() == "123456789012345678901234567890");
|
||||||
CHECK(v[4].number_token() == "-12");
|
CHECK(v[4].number_token() == "-12");
|
||||||
CHECK(v[5].number_token() == "7");
|
CHECK(v[5].number_token() == "7");
|
||||||
CHECK_THROWS_WITH_AS(v[6].number_token(), json_view_test::message("type_error.302", "/6", 56, 59, "type must be number, but is string").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(v[6].number_token(), "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(v.get_string(), json_view_test::message("type_error.302", "", 0, 60, "type must be string, but is array").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(v.get_string(), "[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("conversions")
|
SECTION("conversions")
|
||||||
@@ -1533,15 +1565,15 @@ TEST_CASE("json_view values")
|
|||||||
CHECK(v["o"]["x"].value(json::json_pointer("/1"), 0) == j["o"]["x"].value(json::json_pointer("/1"), 0));
|
CHECK(v["o"]["x"].value(json::json_pointer("/1"), 0) == j["o"]["x"].value(json::json_pointer("/1"), 0));
|
||||||
CHECK(v["o"]["x"].value(json::json_pointer("/7"), 3) == j["o"]["x"].value(json::json_pointer("/7"), 3));
|
CHECK(v["o"]["x"].value(json::json_pointer("/7"), 3) == j["o"]["x"].value(json::json_pointer("/7"), 3));
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["o"]["x"].value("k", 0)); }) == exception_of([&] { static_cast<void>(j["o"]["x"].value("k", 0)); }));
|
CHECK(exception_of([&] { static_cast<void>(v["o"]["x"].value("k", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["o"]["x"].value("k", 0)); })));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == exception_of([&] { static_cast<void>(j.value("s", 0)); }));
|
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == exception_of([&] { static_cast<void>(j["n"].value("x", 0)); }));
|
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); }));
|
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
|
||||||
// string defaults: the member is not a string, or the value is neither an object nor an array
|
// string defaults: the member is not a string, or the value is neither an object nor an array
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.value("n", "default")); }) == exception_of([&] { static_cast<void>(j.value("n", "default")); }));
|
CHECK(exception_of([&] { static_cast<void>(v.value("n", "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value("n", "default")); })));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", "default")); }) == exception_of([&] { static_cast<void>(j["n"].value("x", "default")); }));
|
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", "default")); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", "default")); })));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.value(json::json_pointer("/n"), "default")); }) == exception_of([&] { static_cast<void>(j.value(json::json_pointer("/n"), "default")); }));
|
CHECK(exception_of([&] { static_cast<void>(v.value(json::json_pointer("/n"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value(json::json_pointer("/n"), "default")); })));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["s"].value(json::json_pointer("/x"), "default")); }) == exception_of([&] { static_cast<void>(j["s"].value(json::json_pointer("/x"), "default")); }));
|
CHECK(exception_of([&] { static_cast<void>(v["s"].value(json::json_pointer("/x"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j["s"].value(json::json_pointer("/x"), "default")); })));
|
||||||
#endif
|
#endif
|
||||||
// (defaults and members longer than the short string optimization)
|
// (defaults and members longer than the short string optimization)
|
||||||
CHECK(v.value("s", "a default longer than fifteen characters") == "text");
|
CHECK(v.value("s", "a default longer than fifteen characters") == "text");
|
||||||
@@ -1600,7 +1632,7 @@ TEST_CASE("json_view JSON pointers")
|
|||||||
{
|
{
|
||||||
CAPTURE(pointer)
|
CAPTURE(pointer)
|
||||||
const json::json_pointer p(pointer);
|
const json::json_pointer p(pointer);
|
||||||
const std::string at_error = exception_of([&] { static_cast<void>(j.at(p)); });
|
const std::string at_error = without_path(exception_of([&] { static_cast<void>(j.at(p)); }));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.at(p)); }) == at_error);
|
CHECK(exception_of([&] { static_cast<void>(v.at(p)); }) == at_error);
|
||||||
if (at_error.empty())
|
if (at_error.empty())
|
||||||
{
|
{
|
||||||
@@ -1614,7 +1646,7 @@ TEST_CASE("json_view JSON pointers")
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v[p]); }) == exception_of([&] { static_cast<void>(j[p]); }));
|
CHECK(exception_of([&] { static_cast<void>(v[p]); }) == without_path(exception_of([&] { static_cast<void>(j[p]); })));
|
||||||
}
|
}
|
||||||
// (basic_json::contains() throws out_of_range.404 for an empty
|
// (basic_json::contains() throws out_of_range.404 for an empty
|
||||||
// array index token, although it is not meant to throw; the view
|
// array index token, although it is not meant to throw; the view
|
||||||
@@ -1625,7 +1657,7 @@ TEST_CASE("json_view JSON pointers")
|
|||||||
static_cast<void>(found);
|
static_cast<void>(found);
|
||||||
});
|
});
|
||||||
CHECK(v.contains(p) == (contains_error.empty() && j.contains(p)));
|
CHECK(v.contains(p) == (contains_error.empty() && j.contains(p)));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.value(p, 5)); }) == exception_of([&] { static_cast<void>(j.value(p, 5)); }));
|
CHECK(exception_of([&] { static_cast<void>(v.value(p, 5)); }) == without_path(exception_of([&] { static_cast<void>(j.value(p, 5)); })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ TEST_CASE("json_view large objects")
|
|||||||
CHECK(v[""].get_string() == "empty key");
|
CHECK(v[""].get_string() == "empty key");
|
||||||
CHECK(v["k1"].get<int>() == 1); // the first of duplicate keys, as for small objects
|
CHECK(v["k1"].get<int>() == 1); // the first of duplicate keys, as for small objects
|
||||||
CHECK(!v.contains("missing"));
|
CHECK(!v.contains("missing"));
|
||||||
CHECK_THROWS_WITH_AS(v.at("missing"), json_view_test::message("out_of_range.403", "", 0, text.size(), "key 'missing' not found").c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(v.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&);
|
||||||
CHECK(v == j);
|
CHECK(v == j);
|
||||||
CHECK(v.materialize() == j);
|
CHECK(v.materialize() == j);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -532,18 +532,18 @@ TEST_CASE("json_view edits: errors")
|
|||||||
|
|
||||||
CHECK_THROWS_WITH_AS(d.set(other_editable.root(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&);
|
CHECK_THROWS_WITH_AS(d.set(other_editable.root(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&);
|
||||||
CHECK_THROWS_WITH_AS(d.set(json_editable_view(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&);
|
CHECK_THROWS_WITH_AS(d.set(json_editable_view(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&);
|
||||||
CHECK_THROWS_WITH_AS(d.set(root["a"], "k", 1), json_view_test::message("type_error.305", "/a", 21, 27, "cannot use operator[] with a string argument with array").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(d.set(root["a"], "k", 1), "[json.exception.type_error.305] cannot use operator[] with a string argument with array", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(d.set(root["o"], 0, 1), json_view_test::message("type_error.305", "/o", 6, 14, "cannot use operator[] with a numeric argument with object").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(d.set(root["o"], 0, 1), "[json.exception.type_error.305] cannot use operator[] with a numeric argument with object", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(d.set(root["a"], 2, 1), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index 2 is out of range").c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(d.set(root["a"], 2, 1), "[json.exception.out_of_range.401] array index 2 is out of range", json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(d.set(root["a"], -1, 1), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index -1 is out of range").c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(d.set(root["a"], -1, 1), "[json.exception.out_of_range.401] array index -1 is out of range", json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(d.push_back(root["o"], 1), json_view_test::message("type_error.308", "/o", 6, 14, "cannot use push_back() with object").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(d.push_back(root["o"], 1), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(d.insert(root["n"], 0, 1), json_view_test::message("type_error.309", "/n", 34, 35, "cannot use insert() with number").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(d.insert(root["n"], 0, 1), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(d.insert(root["a"], 3, 1), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index 3 is out of range").c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(d.insert(root["a"], 3, 1), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(d.erase(root["n"], "k"), json_view_test::message("type_error.307", "/n", 34, 35, "cannot use erase() with number").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(d.erase(root["n"], "k"), "[json.exception.type_error.307] cannot use erase() with number", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(d.erase(root["o"], 0), json_view_test::message("type_error.307", "/o", 6, 14, "cannot use erase() with object").c_str(), json::type_error&);
|
CHECK_THROWS_WITH_AS(d.erase(root["o"], 0), "[json.exception.type_error.307] cannot use erase() with object", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(d.erase(root["a"], 2), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index 2 is out of range").c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(d.erase(root["a"], 2), "[json.exception.out_of_range.401] array index 2 is out of range", json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(d.erase(json::json_pointer("")), "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
|
CHECK_THROWS_WITH_AS(d.erase(json::json_pointer("")), "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(d.erase(json::json_pointer("/missing/x")), json_view_test::message("out_of_range.403", "", 0, 47, "key 'missing' not found").c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(d.erase(json::json_pointer("/missing/x")), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(d.set(json::json_pointer("/a/01"), 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
|
CHECK_THROWS_WITH_AS(d.set(json::json_pointer("/a/01"), 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(d.set(root, json_editable_view()), "[json.exception.type_error.302] type must be a value, but is discarded", json::type_error&);
|
CHECK_THROWS_WITH_AS(d.set(root, json_editable_view()), "[json.exception.type_error.302] type must be a value, but is discarded", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(d.set(root, json::binary({1, 2})), "[json.exception.type_error.319] cannot store a binary value in a json_document", json::type_error&);
|
CHECK_THROWS_WITH_AS(d.set(root, json::binary({1, 2})), "[json.exception.type_error.319] cannot store a binary value in a json_document", json::type_error&);
|
||||||
|
|||||||
@@ -337,7 +337,8 @@ TEST_CASE("json_view from_json")
|
|||||||
CHECK(d.root().get<std::unordered_map<std::string, int>>().at("a") == 3);
|
CHECK(d.root().get<std::unordered_map<std::string, int>>().at("a") == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
// (the exceptions of views have neither the path nor the byte range of the value)
|
||||||
|
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
|
||||||
SECTION("std types: the same errors as from the materialized value")
|
SECTION("std types: the same errors as from the materialized value")
|
||||||
{
|
{
|
||||||
const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])");
|
const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])");
|
||||||
@@ -402,10 +403,10 @@ TEST_CASE("json_view from_json")
|
|||||||
CHECK(d.root()[4].get<strict_color>() == strict_color::red);
|
CHECK(d.root()[4].get<strict_color>() == strict_color::red);
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
const json j = d.root().materialize();
|
const json j = d.root().materialize();
|
||||||
#if !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()[2].get<strict_color>()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\"");
|
CHECK(exception_of_call([&] { static_cast<void>(d.root()[2].get<strict_color>()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\"");
|
||||||
#endif
|
#if !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()[2].get<strict_color>()); }) == exception_of_call([&] { static_cast<void>(j[2].get<strict_color>()); }));
|
CHECK(exception_of_call([&] { static_cast<void>(d.root()[2].get<strict_color>()); }) == exception_of_call([&] { static_cast<void>(j[2].get<strict_color>()); }));
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,9 +452,11 @@ TEST_CASE("json_view from_json")
|
|||||||
CHECK(o.root().get<shape>().center.y == 2);
|
CHECK(o.root().get<shape>().center.y == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
|
||||||
SECTION("errors are those of the same conversion of a json value")
|
SECTION("errors are those of the same conversion of a json value")
|
||||||
{
|
{
|
||||||
|
// (with JSON_DIAGNOSTICS, those of json name the path of the value,
|
||||||
|
// and with JSON_DIAGNOSTIC_POSITIONS its bytes)
|
||||||
const json_document d = json_document::parse(R"([{"x": 1}, {"x": "1", "y": 2}, [1, 2], {"id": "7", "c": "red"}])");
|
const json_document d = json_document::parse(R"([{"x": 1}, {"x": "1", "y": 2}, [1, 2], {"id": "7", "c": "red"}])");
|
||||||
const json j = d.root().materialize();
|
const json j = d.root().materialize();
|
||||||
for (std::size_t i = 0; i < 3; ++i)
|
for (std::size_t i = 0; i < 3; ++i)
|
||||||
|
|||||||
@@ -81,112 +81,6 @@ std::string range(const json& j)
|
|||||||
{
|
{
|
||||||
return j.start_pos() == npos && j.end_pos() == npos ? "none" : std::to_string(j.start_pos()) + "-" + std::to_string(j.end_pos());
|
return j.start_pos() == npos && j.end_pos() == npos ? "none" : std::to_string(j.start_pos()) + "-" + std::to_string(j.end_pos());
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
using json_view_test::exception_of_call;
|
|
||||||
|
|
||||||
// the exceptions about every value of v are those about the value at the same
|
|
||||||
// place in j, a value of the whole document from parse() or materialize() (so
|
|
||||||
// that its parents and positions are set); members whose key repeats are
|
|
||||||
// skipped (the view finds the first one, j holds the last)
|
|
||||||
template<typename View>
|
|
||||||
void check_exceptions(const View& v, const json& j)
|
|
||||||
{
|
|
||||||
CAPTURE(j.dump())
|
|
||||||
if (!v.is_number())
|
|
||||||
{
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v.template get<int>()); }) == exception_of_call([&] { static_cast<void>(j.get<int>()); }));
|
|
||||||
}
|
|
||||||
if (!v.is_string())
|
|
||||||
{
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v.template get<std::string>()); }) == exception_of_call([&] { static_cast<void>(j.get<std::string>()); }));
|
|
||||||
}
|
|
||||||
if (!v.is_array())
|
|
||||||
{
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v.template get<std::vector<int>>()); }) == exception_of_call([&] { static_cast<void>(j.get<std::vector<int>>()); }));
|
|
||||||
}
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v.at("missing")); }) == exception_of_call([&] { static_cast<void>(j.at("missing")); }));
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v.at(1000)); }) == exception_of_call([&] { static_cast<void>(j.at(1000)); }));
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v.at(json::json_pointer("/missing"))); }) == exception_of_call([&] { static_cast<void>(j.at(json::json_pointer("/missing"))); }));
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v.at(json::json_pointer("/-"))); }) == exception_of_call([&] { static_cast<void>(j.at(json::json_pointer("/-"))); }));
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v.value("k", 0)); }) == exception_of_call([&] { static_cast<void>(j.value("k", 0)); }));
|
|
||||||
if (!v.is_object())
|
|
||||||
{
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v["k"]); }) == exception_of_call([&] { static_cast<void>(j["k"]); }));
|
|
||||||
// (without the context of basic_json: view iterators do not know the value they iterate over)
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v.begin().key()); }) == "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
|
|
||||||
}
|
|
||||||
if (!v.is_array())
|
|
||||||
{
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v[0]); }) == exception_of_call([&] { static_cast<void>(j[0]); }));
|
|
||||||
}
|
|
||||||
if (v.is_null())
|
|
||||||
{
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(v.front()); }) == exception_of_call([&] { static_cast<void>(j.front()); }));
|
|
||||||
}
|
|
||||||
if (v.is_object())
|
|
||||||
{
|
|
||||||
for (auto it = v.begin(); it != v.end(); ++it)
|
|
||||||
{
|
|
||||||
const std::string key(it.key().data(), it.key().size());
|
|
||||||
if (v.count(key) == 1)
|
|
||||||
{
|
|
||||||
check_exceptions(it.value(), j.at(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (v.is_array())
|
|
||||||
{
|
|
||||||
for (std::size_t i = 0; i < v.size(); ++i)
|
|
||||||
{
|
|
||||||
check_exceptions(v[i], j[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// as check_exceptions, for edits that fail (they change neither d nor j)
|
|
||||||
void check_edit_exceptions(json_editable_document& d, const json_editable_document::view_type& v, json& j)
|
|
||||||
{
|
|
||||||
CAPTURE(j.dump())
|
|
||||||
if (!v.is_object() && !v.is_null())
|
|
||||||
{
|
|
||||||
CHECK(exception_of_call([&] { d.set(v, "k", 1); }) == exception_of_call([&] { j["k"] = 1; }));
|
|
||||||
CHECK(exception_of_call([&] { d.erase(v, "k"); }) == exception_of_call([&] { j.erase("k"); }));
|
|
||||||
}
|
|
||||||
if (!v.is_array() && !v.is_null())
|
|
||||||
{
|
|
||||||
CHECK(exception_of_call([&] { d.set(v, 0, 1); }) == exception_of_call([&] { j[0] = 1; }));
|
|
||||||
CHECK(exception_of_call([&] { d.push_back(v, 1); }) == exception_of_call([&] { j.push_back(1); }));
|
|
||||||
}
|
|
||||||
if (!v.is_array())
|
|
||||||
{
|
|
||||||
CHECK(exception_of_call([&] { d.insert(v, 0, 1); }) == exception_of_call([&] { j.insert(j.cbegin(), 1); }));
|
|
||||||
CHECK(exception_of_call([&] { d.erase(v, 0); }) == exception_of_call([&] { j.erase(0); }));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CHECK(exception_of_call([&] { d.erase(v, 1000); }) == exception_of_call([&] { j.erase(1000); }));
|
|
||||||
}
|
|
||||||
if (v.is_object())
|
|
||||||
{
|
|
||||||
for (auto it = v.begin(); it != v.end(); ++it)
|
|
||||||
{
|
|
||||||
const std::string key(it.key().data(), it.key().size());
|
|
||||||
if (v.count(key) == 1)
|
|
||||||
{
|
|
||||||
check_edit_exceptions(d, it.value(), j.at(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (v.is_array())
|
|
||||||
{
|
|
||||||
for (std::size_t i = 0; i < v.size(); ++i)
|
|
||||||
{
|
|
||||||
check_edit_exceptions(d, v[i], j[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("json_view materialize() source positions")
|
TEST_CASE("json_view materialize() source positions")
|
||||||
@@ -451,123 +345,3 @@ TEST_CASE("json_view materialize() source positions")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
|
||||||
{
|
|
||||||
SECTION("as those of basic_json")
|
|
||||||
{
|
|
||||||
const std::vector<std::string> texts =
|
|
||||||
{
|
|
||||||
"null", "true", "42", "\"s\"", "[]", "{}", "[1, [2, [3, []]]]", R"({"a": {"b": [1, {"c": null}]}, "d": "e"})",
|
|
||||||
R"({"a/b": {"~": [1]}, "\u0041\n": {"x": "\t"}, "": [{}]})",
|
|
||||||
R"([1 /* ] */, {"a": [] // }
|
|
||||||
, "b": {} } , ])",
|
|
||||||
"\n\t[\r\n 1 ,\n {\n \"a\" : [ ]\n }\n]\n",
|
|
||||||
};
|
|
||||||
for (const auto& text : texts)
|
|
||||||
{
|
|
||||||
CAPTURE(text)
|
|
||||||
const json j = json::parse(text, nullptr, true, true, true);
|
|
||||||
const json_document d = json_document::parse(text, true, true, true);
|
|
||||||
check_exceptions(d.root(), j);
|
|
||||||
json_editable_document e = json_editable_document::parse(text, true, true, true);
|
|
||||||
check_exceptions(e.root(), j);
|
|
||||||
json m = e.root().materialize();
|
|
||||||
check_edit_exceptions(e, e.root(), m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("generated documents")
|
|
||||||
{
|
|
||||||
json_view_test::generator gen;
|
|
||||||
for (int i = 0; i < 100; ++i)
|
|
||||||
{
|
|
||||||
std::string text;
|
|
||||||
gen.value(text, 0);
|
|
||||||
CAPTURE(text)
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
check_exceptions(d.root(), json::parse(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("edited documents: as those of materialize()")
|
|
||||||
{
|
|
||||||
const std::string text = R"({"a": [1, 2], "b": {"c": "\n", "d": true}, "e": [3, [4]], "f": "x"})";
|
|
||||||
json_editable_document d = json_editable_document::parse(text);
|
|
||||||
d.set(d.root()["f"], 5);
|
|
||||||
d.push_back(d.root()["a"], json::parse(R"({"n": [null]})"));
|
|
||||||
d.erase(d.root()["e"], 0);
|
|
||||||
d.set(d.root()["b"], "c2", d.root()["e"]);
|
|
||||||
d.insert(d.root()["e"], 0, "new");
|
|
||||||
json m = d.root().materialize();
|
|
||||||
check_exceptions(d.root(), m);
|
|
||||||
check_edit_exceptions(d, d.root(), m);
|
|
||||||
// (a value inside a new container: a path, but no bytes)
|
|
||||||
std::string expected = "[json.exception.type_error.302] ";
|
|
||||||
#if JSON_DIAGNOSTICS
|
|
||||||
expected += "(/a/2/n/0) ";
|
|
||||||
#endif
|
|
||||||
expected += "type must be number, but is null";
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"][2]["n"][0].get<int>()); }) == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("deep documents with siblings after every nested container")
|
|
||||||
{
|
|
||||||
// the search for the path pushes and pops one frame per level (the
|
|
||||||
// stack reallocates while it grows, and shrinks again at every
|
|
||||||
// sibling): run under a sanitizer, a reference to a popped or moved
|
|
||||||
// frame would show here
|
|
||||||
std::string text = "7";
|
|
||||||
for (int level = 0; level < 40; ++level)
|
|
||||||
{
|
|
||||||
text = R"([[1, [2]], )" + text + R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
|
|
||||||
}
|
|
||||||
CAPTURE(text)
|
|
||||||
const json j = json::parse(text);
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
check_exceptions(d.root(), j);
|
|
||||||
json_editable_document e = json_editable_document::parse(text);
|
|
||||||
e.set(e.root()[0][0], 9);
|
|
||||||
check_exceptions(e.root(), e.root().materialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("examples")
|
|
||||||
{
|
|
||||||
const std::string text = R"({"people": [{"name": "a", "age": 1}, {"name": "c", "age": "x"}], "o": {"k": 1}})";
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
std::string expected_age = "[json.exception.type_error.302] ";
|
|
||||||
#if JSON_DIAGNOSTICS
|
|
||||||
expected_age += "(/people/1/age) ";
|
|
||||||
#endif
|
|
||||||
expected_age += "(bytes 58-61) type must be number, but is string";
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()["people"][1]["age"].get<int>()); }) == expected_age);
|
|
||||||
|
|
||||||
std::string expected_zz = "[json.exception.out_of_range.403] ";
|
|
||||||
#if JSON_DIAGNOSTICS
|
|
||||||
expected_zz += "(/people/0) ";
|
|
||||||
#endif
|
|
||||||
expected_zz += "(bytes 12-35) key 'zz' not found";
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root().at(json::json_pointer("/people/0/zz"))); }) == expected_zz);
|
|
||||||
|
|
||||||
// the root has no path, and with JSON_DIAGNOSTICS then no bytes either (as basic_json)
|
|
||||||
std::string expected_missing = "[json.exception.out_of_range.403] ";
|
|
||||||
#if !JSON_DIAGNOSTICS
|
|
||||||
expected_missing += "(bytes 0-79) ";
|
|
||||||
#endif
|
|
||||||
expected_missing += "key 'missing' not found";
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root().at("missing")); }) == expected_missing);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("duplicate keys: the value the view finds")
|
|
||||||
{
|
|
||||||
const json_document d = json_document::parse(R"({"x": "1", "x": "22"})");
|
|
||||||
std::string expected = "[json.exception.type_error.302] ";
|
|
||||||
#if JSON_DIAGNOSTICS
|
|
||||||
expected += "(/x) ";
|
|
||||||
#endif
|
|
||||||
expected += "(bytes 6-9) type must be number, but is string";
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()["x"].get<int>()); }) == expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -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,30 @@
|
|||||||
|
# ci_jobs
|
||||||
|
|
||||||
|
The jobs of the compiler workflows [`ubuntu.yml`](../../.github/workflows/ubuntu.yml),
|
||||||
|
[`macos.yml`](../../.github/workflows/macos.yml), and [`windows.yml`](../../.github/workflows/windows.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 has the `if:` condition of exactly one set (the conditions are in `SETS`),
|
||||||
|
- every full-set job waits for (`needs:`) exactly the PR set of its workflow, 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
+231
@@ -0,0 +1,231 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""List and check the jobs of the compiler workflows and the set each job belongs to.
|
||||||
|
|
||||||
|
The jobs of the compiler workflows 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 script checks that every job belongs to exactly one set, that the full set
|
||||||
|
of a workflow waits for its 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 = WORKFLOWS
|
||||||
|
# --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')]
|
||||||
|
if not keys:
|
||||||
|
# a matrix of only include entries runs each entry
|
||||||
|
return [dict(inc) for inc in matrix.get('include') or []]
|
||||||
|
combos = [dict(zip(keys, values)) for values in itertools.product(*(matrix[k] for k in keys))]
|
||||||
|
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