From 4289a382755de1f330c35e7aa650ab2ae7fe2511 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 1 Oct 2026 14:19:32 +0200 Subject: [PATCH] Check in CI that Meson and pkg-config offer the CMake options tools/check_build_options/check_build_options.py takes the compile definitions of the CMake target as the reference and checks that the CMake pkg-config block, meson_options.txt (name and default), meson.build, and the Meson section of the package manager docs have each option with the same definition and condition. It runs as make check_build_options in the ci_meson_install job, so a new CMake option cannot be forgotten in the Meson build again. Signed-off-by: Niels Lohmann --- .github/workflows/ubuntu.yml | 2 + FILES.md | 4 +- Makefile | 6 +- tools/check_build_options/README.md | 21 +++ .../check_build_options.cpython-311.pyc | Bin 0 -> 11241 bytes .../check_build_options.py | 144 ++++++++++++++++++ 6 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 tools/check_build_options/README.md create mode 100644 tools/check_build_options/__pycache__/check_build_options.cpython-311.pyc create mode 100755 tools/check_build_options/check_build_options.py diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 3b003b47d..7ffb41fab 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -39,6 +39,8 @@ jobs: persist-credentials: false - name: Get latest CMake and ninja uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 + - name: Check that Meson and pkg-config offer the CMake options + run: make check_build_options - name: Install Meson run: pip install meson - name: Install with Meson diff --git a/FILES.md b/FILES.md index 67c52f136..4548e3753 100644 --- a/FILES.md +++ b/FILES.md @@ -267,7 +267,9 @@ ninja -C builddir ``` `meson_options.txt` defines the options, which mirror the CMake options that change the library's target (for example, -`-DDiagnostics=true`). Meson requires this file next to `meson.build`, so it is also part of `include.zip`. +`-DDiagnostics=true`). Meson requires this file next to `meson.build`, so it is also part of `include.zip`. `make check_build_options` +([`tools/check_build_options`](tools/check_build_options/README.md)) checks in CI that both files and the pkg-config files +stay in sync with the CMake options. When installing, `meson.build` installs the headers, a pkg-config file, and the CMake package config files, so that `find_package(nlohmann_json)` works. As Meson cannot generate `nlohmann_jsonTargets.cmake` itself, it is created from diff --git a/Makefile b/Makefile index b71166d0e..87758aa1d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check +.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check check_build_options ########################################################################## # configuration @@ -124,6 +124,10 @@ macro_builder_check: diff "$$TMPDIR/paste.hpp" "$$TMPDIR/paste_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_EXPAND..NLOHMANN_JSON_DOUBLE_PASTE63) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1); \ diff "$$TMPDIR/type_body.hpp" "$$TMPDIR/type_body_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_TYPE_BODY) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1) +# check that the Meson build and the pkg-config files offer the options of the CMake target +check_build_options: + python3 tools/check_build_options/check_build_options.py . + # check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources check-amalgamation: @mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~ diff --git a/tools/check_build_options/README.md b/tools/check_build_options/README.md new file mode 100644 index 000000000..1b24a55ff --- /dev/null +++ b/tools/check_build_options/README.md @@ -0,0 +1,21 @@ +# check_build_options + +Checks that the Meson build and the pkg-config files offer the same options as the CMake target, so that a new CMake +option is not forgotten in one of them. + +The compile definitions of the CMake target (`target_compile_definitions` in [`CMakeLists.txt`](../../CMakeLists.txt)) +are the reference. When you add an option there, also add it to + +- the pkg-config block in `CMakeLists.txt` (`NLOHMANN_JSON_PKGCONFIG_CFLAGS`), +- [`meson_options.txt`](../../meson_options.txt), named without the `JSON_` prefix and with the same default, +- [`meson.build`](../../meson.build) (`json_defines`), and +- the list of Meson options in + [`docs/mkdocs/docs/integration/package_managers.md`](../../docs/mkdocs/docs/integration/package_managers.md). + +Run the check with + +```shell +make check_build_options +``` + +It needs only Python 3 and runs in the `ci_meson_install` CI job. diff --git a/tools/check_build_options/__pycache__/check_build_options.cpython-311.pyc b/tools/check_build_options/__pycache__/check_build_options.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ddee8f3338366e0035f371d33291bd0d8483edf GIT binary patch literal 11241 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:(\w+=\w+)>', line) + if m: + result[m.group(1)] = (m.group(2), False) + continue + m = re.fullmatch(r'\$<\$:(\w+=\w+)>', line) + if m: + result[m.group(1)] = (m.group(2), True) + continue + sys.exit(f'CMakeLists.txt: unexpected line in target_compile_definitions: {line}') + return result + + +def cmake_defaults(cmake): + """Return {option: 'true'/'false'} for the option() calls of the form JSON_.""" + return {m.group(1): 'true' if m.group(2) == 'ON' else 'false' + for m in re.finditer(r'^option\(JSON_(\w+)\s+"[^"]*"\s+(ON|OFF)\)', cmake, re.M)} + + +def cmake_pkgconfig_definitions(cmake): + """Return {option: (definition, add_if_on)} from the pkg-config block.""" + return {m.group(2): (m.group(3), m.group(1) is None) + for m in re.finditer(r'if \((NOT )?JSON_(\w+)\)\s*\n\s*string\(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -D(\w+=\w+)"\)', cmake)} + + +def meson_options(options): + """Return {option: default} for the boolean options in meson_options.txt.""" + result = {} + for block in re.findall(r'option\((.*?)\)', options, re.S): + name = re.search(r"'(\w+)'", block).group(1) + kind = re.search(r"type\s*:\s*'(\w+)'", block) + value = re.search(r'value\s*:\s*(\w+)', block) + result[name] = value.group(1) if kind and kind.group(1) == 'boolean' and value else None + return result + + +def meson_definitions(meson): + """Return {option: (definition, add_if_on)} from meson.build.""" + return {m.group(2): (m.group(3), m.group(1) is None) + for m in re.finditer(r"if (not )?get_option\('(\w+)'\)\s*\n\s*json_defines \+= '(\w+=\w+)'", meson)} + + +def describe(definition): + name, add_if_on = definition + return f'{name} if {"enabled" if add_if_on else "disabled"}' + + +def compare(errors, where, expected, actual): + for option, definition in expected.items(): + if option not in actual: + errors.append(f'{where}: no definition for option {option} (expected {describe(definition)})') + elif actual[option] != definition: + errors.append(f'{where}: option {option} adds {describe(actual[option])}, expected {describe(definition)}') + for option in actual.keys() - expected.keys(): + errors.append(f'{where}: definition for option {option}, which the CMake target does not have') + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.split('\n')[0]) + parser.add_argument('root', nargs='?', default=REPO_ROOT, help='repository root (default: %(default)s)') + root = parser.parse_args().root + + cmake = read(root, 'CMakeLists.txt') + reference = cmake_target_definitions(cmake) + defaults = cmake_defaults(cmake) + errors = [] + + compare(errors, 'CMakeLists.txt (pkg-config)', reference, cmake_pkgconfig_definitions(cmake)) + compare(errors, 'meson.build', reference, meson_definitions(read(root, 'meson.build'))) + + options = meson_options(read(root, 'meson_options.txt')) + for option in reference: + if option not in defaults: + errors.append(f'CMakeLists.txt: no option(JSON_{option} ... ON|OFF)') + elif option not in options: + errors.append(f'meson_options.txt: option {option} missing') + elif options[option] != defaults[option]: + errors.append(f'meson_options.txt: option {option} must be boolean with value {defaults[option]} as in CMake') + for option, default in MESON_ONLY.items(): + if options.get(option) != default: + errors.append(f'meson_options.txt: option {option} must be boolean with value {default}') + for option in options.keys() - reference.keys() - MESON_ONLY.keys(): + errors.append(f'meson_options.txt: option {option} has no counterpart in the CMake target') + + docs = read(root, DOCS) + for option in sorted(set(options) & (reference.keys() | MESON_ONLY.keys())): + if f'`{option}`' not in docs: + errors.append(f'{DOCS}: Meson option {option} not listed') + + for error in errors: + print(error, file=sys.stderr) + if errors: + print('The Meson build and the pkg-config files must offer the options of the CMake target; see ' + 'tools/check_build_options/README.md.', file=sys.stderr) + return 1 + print(f'OK: {len(reference)} options with compile definitions agree between CMake, pkg-config, and Meson.') + return 0 + + +if __name__ == '__main__': + sys.exit(main())