| name: "Check amalgamation" | |
| on: | |
| pull_request: | |
| # also check develop itself: a PR can be merged before its own run of this | |
| # workflow completes (e.g. while it is still queued), leaving single_include | |
| # stale on develop without any failing check | |
| push: | |
| branches: | |
| - develop | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| save: | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-26.04 | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 | |
| with: | |
| egress-policy: audit | |
| - name: Save PR number | |
| run: | | |
| mkdir -p ./pr | |
| echo ${{ github.event.number }} > ./pr/number | |
| echo ${{ github.event.pull_request.user.login }} > ./pr/author | |
| - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| name: pr | |
| path: pr/ | |
| check: | |
| runs-on: ubuntu-26.04 | |
| env: | |
| MAIN_DIR: ${{ github.workspace }}/main | |
| INCLUDE_DIR: ${{ github.workspace }}/main/single_include/nlohmann | |
| TOOL_DIR: ${{ github.workspace }}/tools/tools/amalgamate | |
| NATVIS_TOOL_DIR: ${{ github.workspace }}/tools/tools/generate_natvis | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout pull request or pushed commit | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| path: main | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| persist-credentials: false | |
| - name: Checkout tools | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| path: tools | |
| ref: develop | |
| persist-credentials: false | |
| - name: Install astyle | |
| run: | | |
| python3 -mvenv venv | |
| venv/bin/pip3 install --require-hashes -r $MAIN_DIR/tools/astyle/requirements.txt | |
| - name: Install generate_natvis dependencies | |
| run: pip3 install --require-hashes -r $NATVIS_TOOL_DIR/requirements.txt | |
| - name: Regenerate the tools/macro_builder tables in macro_scope.hpp | |
| run: | | |
| cd $MAIN_DIR | |
| # Built from this PR's own tools/macro_builder/main.cpp, not a | |
| # develop checkout: unlike amalgamate.py, this tool has no other | |
| # source of truth to check against (its own README documents that | |
| # it must reproduce macro_scope.hpp byte for byte), so there is | |
| # nothing to gain from checking out a separate copy, and doing so | |
| # would make this step fail on a PR that adds support for a new | |
| # dispatch table until that PR itself merges to develop. | |
| TMPDIR=$(mktemp -d ./macro_builder_check.XXXXXX) | |
| c++ -std=c++11 tools/macro_builder/main.cpp -o "$TMPDIR/macro_builder" | |
| "$TMPDIR/macro_builder" > "$TMPDIR/paste.hpp" | |
| "$TMPDIR/macro_builder" type_body > "$TMPDIR/type_body.hpp" | |
| # Splice the (still unindented) generated blocks back into | |
| # macro_scope.hpp; the astyle pass below indents their | |
| # continuation lines the same way it does for the rest of | |
| # include/, so a correctly regenerated file comes out unchanged. | |
| awk -v newfile="$TMPDIR/paste.hpp" ' | |
| BEGIN { while ((getline line < newfile) > 0) { new = new line "\n" } } | |
| /^#define NLOHMANN_JSON_EXPAND\( x \) x$/ { printf "%s", new; skip=1 } | |
| skip && /^#define NLOHMANN_JSON_DOUBLE_PASTE63\(/ { skip=0; next } | |
| skip { next } | |
| { print } | |
| ' include/nlohmann/detail/macro_scope.hpp > "$TMPDIR/macro_scope_1.hpp" | |
| awk -v newfile="$TMPDIR/type_body.hpp" ' | |
| BEGIN { while ((getline line < newfile) > 0) { new = new line "\n" } } | |
| /^#define NLOHMANN_JSON_TYPE_BODY\(Prefix, \.\.\.\)/ { printf "%s", new; skip=1 } | |
| skip && /^[[:space:]]*NLOHMANN_JSON_TYPE_BODY_SENTINEL\)\)$/ { skip=0; next } | |
| skip { next } | |
| { print } | |
| ' "$TMPDIR/macro_scope_1.hpp" > "$TMPDIR/macro_scope_2.hpp" | |
| mv "$TMPDIR/macro_scope_2.hpp" include/nlohmann/detail/macro_scope.hpp | |
| rm -rf "$TMPDIR" | |
| - name: Regenerate amalgamation, formatting, and BUILD.bazel | |
| run: | | |
| cd $MAIN_DIR | |
| python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json.json -s . | |
| python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json_fwd.json -s . | |
| # the configuration of json_view.hpp comes with the pull request until | |
| # it is on develop; the tool itself is still develop's | |
| python3 $TOOL_DIR/amalgamate.py -c $MAIN_DIR/tools/amalgamate/config_json_view.json -s . | |
| # the header list of the Bazel "json" target must match the files in include/ | |
| cmake -P cmake/scripts/gen_bazel_build_file.cmake | |
| ${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \ | |
| $INCLUDE_DIR/json.hpp $INCLUDE_DIR/json_fwd.hpp $INCLUDE_DIR/json_view.hpp | |
| # fail loudly if a directory is renamed or removed: find would only warn | |
| # about the missing path and silently drop its files from the check | |
| SOURCE_DIRS="docs/mkdocs/docs/examples include tests" | |
| for DIR in $SOURCE_DIRS; do | |
| if [ ! -d "$DIR" ]; then | |
| echo "::error::source directory '$DIR' does not exist" | |
| exit 1 | |
| fi | |
| done | |
| ${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \ | |
| $(find $SOURCE_DIRS -type f \( -name '*.hpp' -o -name '*.cpp' -o -name '*.cu' \) -not -path 'tests/thirdparty/*' -not -path 'tests/abi/include/nlohmann/*' | sort) | |
| # json_literals.hpp is copied verbatim from the (now formatted) include/ | |
| # directory, like `make amalgamate` does | |
| cp include/nlohmann/json_literals.hpp $INCLUDE_DIR/json_literals.hpp | |
| - name: Regenerate nlohmann_json.natvis | |
| run: | | |
| cd $MAIN_DIR | |
| # The generator and its template are what this check verifies the | |
| # committed natvis file against (which namespaces it lists, and | |
| # what each entry looks like), so run this PR's copy rather than | |
| # the develop checkout's: with develop's, a PR that changes either | |
| # could never pass. generate_natvis.py loads the template from its | |
| # own directory. Like the macro_builder step above, this only runs | |
| # PR content in a job without credentials. | |
| python3 tools/generate_natvis/generate_natvis.py $MAIN_DIR | |
| - name: Check the nlohmann_json.natvis generator options | |
| run: | | |
| cd $MAIN_DIR | |
| # The options for configurations outside the checked-in file write | |
| # to a temporary file, so they do not affect the diff below. | |
| GENERATOR=tools/generate_natvis/generate_natvis.py | |
| python3 $GENERATOR --list-tags | |
| TAG_COUNT=$(python3 $GENERATOR --list-tags | wc -l) | |
| python3 $GENERATOR --abi-tags diag,_dp,_ns --abi-tags _diag --output $RUNNER_TEMP/abi_tags.natvis | |
| grep -q '<!-- Namespace nlohmann::json_abi_diag_dp_ns -->' $RUNNER_TEMP/abi_tags.natvis | |
| grep -q '<!-- Namespace nlohmann::json_abi_diag_dp_ns_v[0-9_]* -->' $RUNNER_TEMP/abi_tags.natvis | |
| test $(grep -c '<!-- Namespace ' $RUNNER_TEMP/abi_tags.natvis) -eq $(( $(grep -c '<!-- Namespace ' nlohmann_json.natvis) + 2 )) | |
| python3 $GENERATOR --all --output $RUNNER_TEMP/all.natvis | |
| test $(grep -c '<!-- Namespace ' $RUNNER_TEMP/all.natvis) -eq $(( (1 << TAG_COUNT) * 2 + 1 )) | |
| if python3 $GENERATOR --abi-tags _unknown --output $RUNNER_TEMP/unknown.natvis; then | |
| echo "generate_natvis.py accepted an unknown ABI tag" | |
| exit 1 | |
| fi | |
| - name: Build patch and check for differences | |
| id: diff | |
| run: | | |
| cd $MAIN_DIR | |
| mkdir -p ${{ github.workspace }}/patch | |
| git diff --patch --no-color > ${{ github.workspace }}/patch/amalgamation.patch | |
| if [ -s ${{ github.workspace }}/patch/amalgamation.patch ]; then | |
| echo "The source code has not been amalgamated/formatted correctly, or BUILD.bazel, nlohmann_json.natvis, or the macro_builder tables in macro_scope.hpp are out of date ('make amalgamate BUILD.bazel natvis' fixes all but the tables, see tools/macro_builder/README.md). Diff:" | |
| cat ${{ github.workspace }}/patch/amalgamation.patch | |
| echo "has_diff=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has_diff=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| # Uploaded so contributors can fix their PR with `git apply amalgamation.patch` | |
| # instead of installing the pinned astyle version locally. | |
| - name: Upload patch | |
| if: steps.diff.outputs.has_diff == 'true' | |
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| name: amalgamation-patch | |
| path: patch/amalgamation.patch | |
| - name: Fail if not amalgamated/formatted | |
| if: steps.diff.outputs.has_diff == 'true' | |
| run: exit 1 |