mirror of
https://github.com/nlohmann/json.git
synced 2026-09-14 20:27:58 +00:00
extract_api.py's own extraction wasn't deterministic across machines, which CI's drift check caught immediately: JSON_HAS_RANGES auto-detects via the standard library's __cpp_lib_ranges feature-test macro, which isn't reliably gated to C++20 mode by every stdlib -- undefined under -std=c++17 with macOS's libc++, but defined under the identical flag with the Ubuntu stdlib CI uses, so parse()/accept()/from_*() extracted different signatures purely depending on which machine ran the extraction. Pinned to -DJSON_HAS_RANGES=0: the deterministic and safe choice, since pinning to 1 was tried first and found to fail to parse on a stdlib without full <ranges> support even when the macro claims otherwise. Also found and fixed a second, independent source of the same class of drift: get_identity_name() used cursor.spelling verbatim for CONVERSION_FUNCTION cursors, which libclang renders as its own internally-canonicalized form of the return type rather than what's literally written. Confirmed for json_pointer::operator string_t() spelling differently on two machines pinned to the identical libclang==18.1.1 wheel, with the JSON_HAS_RANGES fix above ruled out as the cause. Now derived from the cursor's own raw source text instead, immune to libclang's dependent-type resolution differences and incidentally more readable than the libclang-internal forms it replaces. Bumped SURFACE_FORMAT_VERSION to 3 and regenerated all 27 history snapshots and the committed api_surface.json; both fixes are documented in tools/api_checker/history/README.md's format-history log. Also fixes diff_api.py's format_version guard, which only compared the two loaded surfaces against each other and never against SURFACE_FORMAT_VERSION (what this build actually understands) -- two surfaces on the same, newer-than-expected format_version would have silently passed the guard. Remaining fixes are the concretely actionable findings from Codacy's review of the new tools/api_checker/ files: unused imports/variables, a stray f-string with no placeholders. Left the docstring-formatting nitpicks (pydocstyle D2xx/D4xx) and generic subprocess-usage notices alone -- the former has no established convention elsewhere in this codebase's Python tooling to conform to, and the latter are inherent to a dev tool that shells out to git/clang with developer-controlled arguments, not user input. Signed-off-by: Niels Lohmann <mail@nlohmann.me> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Niels Lohmann <mail@nlohmann.me>
74 lines
5.1 KiB
Markdown
74 lines
5.1 KiB
Markdown
# API surface history
|
|
|
|
One file per released `v3.*` tag: `<tag>.json`, e.g. `v3.12.0.json`. Each is the output of
|
|
`extract_api.py --surface-output` at that tag (see `tools/api_checker/README.md` for the schema),
|
|
with additional immutable provenance in `meta`: `ref`, `commit` (the tag's resolved commit sha),
|
|
`generated_at`, and `generator`.
|
|
|
|
## Conventions
|
|
|
|
- **Immutable once committed.** Files here are never hand-edited or silently regenerated.
|
|
`snapshot_release.py` refuses to overwrite an existing file unless `--force` is passed, and that
|
|
should only happen for a deliberate, reviewed fix — the resulting diff should be inspected before
|
|
committing, same as any other source change.
|
|
- **Generated by `tools/api_checker/snapshot_release.py`**, run manually as part of cutting a
|
|
release (see `tools/api_checker/README.md`'s "Workflow: Release Checklist"). Not CI-automated.
|
|
- **`diff_api.py` uses these automatically.** `--old`/`--new` check here first (matching the ref
|
|
string to `<ref>.json`) before falling back to live `git archive` extraction — see
|
|
`tools/api_checker/README.md`'s `diff_api.py` section.
|
|
|
|
## Coverage
|
|
|
|
Backfilled: every `v3.*` tag from `v3.1.0` through the latest release at backfill time
|
|
(`v3.12.0`), covering the full public API history of the `include/nlohmann/` header layout.
|
|
|
|
## Format history
|
|
|
|
- **`format_version: 2`** (current). Fixed `extract_api.py`'s `ABI_TAG_PATTERN` to also strip the
|
|
pre-rename ABI inline-namespace form used by `v3.11.0`/`v3.11.1`: `json_v3_11_0` (no `_abi`
|
|
segment), renamed to today's `json_abi_v3_11_2`-style tag starting with `v3.11.2`. Under
|
|
`format_version: 1`, that older form wasn't recognized, so every `scope`/`signature` at
|
|
`v3.11.0`/`v3.11.1` retained the raw un-stripped namespace segment, making `diff_api.py` report
|
|
essentially the entire API (~330 of ~332 entries) as removed-and-readded across
|
|
`v3.10.5`->`v3.11.0`->`v3.11.1`->`v3.11.2` — a bug of the same shape as the earlier USR-arity
|
|
issue documented in `extract_api.py`'s `identity_key()` docstring, caught the same way: by
|
|
actually diffing real consecutive release pairs instead of trusting the extractor in isolation.
|
|
All 27 files were regenerated under `format_version: 2`; only `v3.11.0.json` and `v3.11.1.json`
|
|
actually changed content (every other tag's scopes never contained the old-style tag).
|
|
|
|
- **`format_version: 3`**. Found via a CI run on a different machine than the one that generated
|
|
`format_version: 2`, which is exactly the failure class this versioning exists to catch:
|
|
1. `JSON_HAS_RANGES` auto-detects via the standard library's `__cpp_lib_ranges` feature-test
|
|
macro. That macro isn't reliably gated to C++20 mode by every stdlib -- undefined under
|
|
`-std=c++17` with macOS's libc++, but defined under the identical flag with the Ubuntu
|
|
stdlib used in CI -- so `parse()`/`accept()`/`from_bjdata()`/etc. extracted a different
|
|
signature (with or without a `SentinelType` parameter) purely depending on which machine
|
|
ran the extraction. Now pinned to `-DJSON_HAS_RANGES=0` in `extract_api.py`'s parse
|
|
arguments: the deterministic choice, since forcing `1` was tried first and found to
|
|
actually fail to parse on a stdlib without full `<ranges>` support even when the macro
|
|
claims otherwise.
|
|
2. `get_identity_name()` used `cursor.spelling` verbatim for `CONVERSION_FUNCTION` cursors,
|
|
which libclang renders as its own internally-canonicalized form of the return type rather
|
|
than what's literally written. Confirmed for `json_pointer::operator string_t()`: spelled
|
|
`"operator nlohmann::json_pointer::string_t_helper<type-parameter-0-0>::type"` on one
|
|
machine and `"operator typename string_t_helper<type-parameter-0-0>::type"` on another,
|
|
with both machines pinned to the identical `libclang==18.1.1` wheel and the JSON_HAS_RANGES
|
|
fix above ruled out as the cause. Now derived from the cursor's own raw source text instead
|
|
(regex over `get_signature_text()`'s output), immune to libclang's dependent-type
|
|
resolution differences and incidentally more readable
|
|
(`"operator string_t"`/`"operator ValueType"` instead of the libclang-internal forms).
|
|
All 27 files were regenerated under `format_version: 3`.
|
|
|
|
## Known gaps
|
|
|
|
- **`v3.0.0`, `v3.0.1`**: not backfilled. These predate the `include/nlohmann/` directory
|
|
structure entirely — headers lived under `src/` at that point (a single `src/json.hpp`). Since
|
|
`extract_api.py` hardcodes `include/nlohmann/json.hpp` as the entry point,
|
|
`snapshot_release.py --all-tags` fails cleanly on these two refs (`git archive ... -- include`
|
|
finds nothing) rather than silently producing a wrong/empty result. Not pursued: two tags,
|
|
immediately superseded by `v3.1.0`, and supporting the pre-restructuring layout would need a
|
|
separate header/include-path convention with no other benefit. If full pre-3.1 coverage is ever
|
|
wanted, `extract_api.py` would need a `src/json.hpp`-aware mode first.
|
|
- Pre-`v3.0.0` tags (`v1.x`, `v2.x`, `v3.0.0-rc*`, etc.) were never attempted — out of scope for
|
|
this backfill; see `tools/api_checker/POLICY.md` for the stated boundary.
|