Merge branch 'develop' into json-view/02b-float-parser Signed-off-by: Niels Lohmann <mail@nlohmann.me> # Conflicts: # tests/src/unit-class_lexer.cpp
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 1fa2f9f..3c94bc1 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml
@@ -31,6 +31,51 @@ - name: Build run: cmake --build build --target ci_test_gcc + ci_meson_install: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + 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 + run: | + meson setup build-meson --prefix=${{ github.workspace }}/install + meson install -C build-meson + - name: Use the installed package with find_package + run: | + cmake -S tests/cmake_import/project -B build-import -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install + cmake --build build-import + - name: Install with Meson and non-default options + run: | + meson setup build-meson-options --prefix=${{ github.workspace }}/install-options -DMultipleHeaders=true -DDiagnostics=true -DGlobalUDLs=false -DDisableTupleReferenceConversion=true -DStrictBinaryUTF8=true -DDeleteDeprecatedFunctions=true + meson install -C build-meson-options + - name: Check that the options reach the installed files + run: | + test -d install-options/include/nlohmann/detail + cflags=$(PKG_CONFIG_PATH=${{ github.workspace }}/install-options/share/pkgconfig pkg-config --cflags nlohmann_json) + echo "$cflags" + echo "$cflags" | grep -q -- '-DJSON_DIAGNOSTICS=1' + echo "$cflags" | grep -q -- '-DJSON_USE_GLOBAL_UDLS=0' + echo "$cflags" | grep -q -- '-DJSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1' + echo "$cflags" | grep -q -- '-DJSON_STRICT_BINARY_UTF8=1' + echo "$cflags" | grep -q -- '-DJSON_DELETE_DEPRECATED_FUNCTIONS=1' + grep -q 'JSON_USE_GLOBAL_UDLS=0;JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1;JSON_DIAGNOSTICS=1' install-options/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake + grep -q 'JSON_STRICT_BINARY_UTF8=1;JSON_DELETE_DEPRECATED_FUNCTIONS=1' install-options/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake + cmake -S tests/cmake_import/project -B build-import-options -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-options + cmake --build build-import-options + - name: Install with Meson and the include directory outside the prefix + run: | + meson setup build-meson-split --prefix=${{ github.workspace }}/install-split --includedir=${{ github.workspace }}/install-split-dev/include + meson install -C build-meson-split + cmake -S tests/cmake_import/project -B build-import-split -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-split + cmake --build build-import-split + ci_infer: runs-on: ubuntu-latest steps:
diff --git a/CMakeLists.txt b/CMakeLists.txt index fed9e31..19f5dca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -189,7 +189,39 @@ ) endif() -# Install a pkg-config file, so other tools can find this. +# Install a pkg-config file, so other tools can find this. It carries the same +# compile definitions as the target above. +set(NLOHMANN_JSON_PKGCONFIG_CFLAGS "") +if (NOT JSON_GlobalUDLs) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_GLOBAL_UDLS=0") +endif() +if (NOT JSON_ImplicitConversions) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_IMPLICIT_CONVERSIONS=0") +endif() +if (JSON_DisableEnumSerialization) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_ENUM_SERIALIZATION=1") +endif() +if (JSON_DisableTupleReferenceConversion) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1") +endif() +if (JSON_Diagnostics) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTICS=1") +endif() +if (JSON_Diagnostic_Positions) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTIC_POSITIONS=1") +endif() +if (JSON_LegacyDiscardedValueComparison) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1") +endif() +if (JSON_StrictNulHandling) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_STRICT_NUL_HANDLING=1") +endif() +if (JSON_StrictBinaryUTF8) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_STRICT_BINARY_UTF8=1") +endif() +if (JSON_DeleteDeprecatedFunctions) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DELETE_DEPRECATED_FUNCTIONS=1") +endif() configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc"
diff --git a/FILES.md b/FILES.md index 861fdf6..fbb5781 100644 --- a/FILES.md +++ b/FILES.md
@@ -249,9 +249,31 @@ The "Check amalgamation" workflow fails if the file is out of date. -### `meson.build` +### `meson.build` and `meson_options.txt` -The build definition for the [Meson](https://mesonbuild.com) build system. +Meson build definitions suitable for use as a subproject ("wrap" in Meson terminology). + +Projects wishing to use the wrap can execute: +```sh +meson wrap install nlohmann_json +``` + +Which allows Meson to build from source when a system provided dependency isn't available. + +To build directly: +```sh +meson setup builddir +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`. `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 +the template `cmake/nlohmann_jsonTargets.cmake.in`, which is only used by Meson. ### `Package.swift`
diff --git a/Makefile b/Makefile index 11b9227..093fbb7 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 @@ -127,6 +127,10 @@ 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)~ @@ -184,7 +188,7 @@ # We use `-X` to make the resulting ZIP file reproducible, see # <https://content.pivotal.io/blog/barriers-to-deterministic-reproducible-zip-files>. include.zip: BUILD.bazel - zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel MODULE.bazel meson.build LICENSE.MIT + zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel MODULE.bazel meson.build meson_options.txt LICENSE.MIT # Create the files for a release and add signatures and hashes. release: include.zip json.tar.xz
diff --git a/cmake/nlohmann_jsonTargets.cmake.in b/cmake/nlohmann_jsonTargets.cmake.in new file mode 100644 index 0000000..b2e57a5 --- /dev/null +++ b/cmake/nlohmann_jsonTargets.cmake.in
@@ -0,0 +1,42 @@ +# Imported target for installations made with Meson (see meson.build). +# +# CMake installations generate this file with install(EXPORT ...). Meson cannot +# do that, but as the library is header-only, the target only needs an include +# directory, the C++ standard, and the compile definitions of the options that +# differ from their defaults. Paths are computed relative to this file so that +# the installation can be relocated (e.g., into a sysroot), unless includedir or +# datadir is outside the prefix. + +if(TARGET @PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@) + return() +endif() + +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_DIR}/@NLOHMANN_JSON_CONFIG_TO_PREFIX@" ABSOLUTE) +# As in CMake's generated file: avoid "//include" for an installation to "/". +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +add_library(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ INTERFACE IMPORTED) +set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "@NLOHMANN_JSON_INCLUDE_DIR@" +) +if(CMAKE_VERSION VERSION_LESS 3.8) + set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_COMPILE_FEATURES cxx_range_for + ) +else() + set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_COMPILE_FEATURES cxx_std_11 + ) +endif() + +set(_NLOHMANN_JSON_COMPILE_DEFINITIONS "@NLOHMANN_JSON_COMPILE_DEFINITIONS@") +if(_NLOHMANN_JSON_COMPILE_DEFINITIONS) + set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "${_NLOHMANN_JSON_COMPILE_DEFINITIONS}" + ) +endif() + +unset(_NLOHMANN_JSON_COMPILE_DEFINITIONS) +unset(_IMPORT_PREFIX)
diff --git a/cmake/pkg-config.pc.in b/cmake/pkg-config.pc.in index 21a91a3..70407f0 100644 --- a/cmake/pkg-config.pc.in +++ b/cmake/pkg-config.pc.in
@@ -4,4 +4,4 @@ Name: @PROJECT_NAME@ Description: JSON for Modern C++ Version: @PROJECT_VERSION@ -Cflags: -I${includedir} +Cflags: -I${includedir}@NLOHMANN_JSON_PKGCONFIG_CFLAGS@
diff --git a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md index 675a8ac..2e77813 100644 --- a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md +++ b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md
@@ -67,7 +67,7 @@ `JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`: ```cpp - using wjson = nlohmann::basic_json<std::map, std::vector, std::wstring>; + using wjson = nlohmann::json::with_string_t<std::wstring>; void load(const nlohmann::json& j);
diff --git a/docs/mkdocs/docs/examples/custom_array_type.cpp b/docs/mkdocs/docs/examples/custom_array_type.cpp index 63651ca..9765c51 100644 --- a/docs/mkdocs/docs/examples/custom_array_type.cpp +++ b/docs/mkdocs/docs/examples/custom_array_type.cpp
@@ -1,11 +1,10 @@ #include <iostream> -#include <map> #include <nlohmann/json.hpp> #include "custom_array_type.hpp" -using custom_json = nlohmann::basic_json<std::map, custom_array_type>; +using custom_json = nlohmann::json::with_array_t<custom_array_type>; int main() {
diff --git a/docs/mkdocs/docs/examples/custom_binary_type.cpp b/docs/mkdocs/docs/examples/custom_binary_type.cpp index cea34ea..0a835d4 100644 --- a/docs/mkdocs/docs/examples/custom_binary_type.cpp +++ b/docs/mkdocs/docs/examples/custom_binary_type.cpp
@@ -1,16 +1,10 @@ -#include <cstdint> #include <iostream> -#include <map> -#include <string> -#include <vector> #include <nlohmann/json.hpp> #include "custom_binary_type.hpp" -using custom_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, - std::int64_t, std::uint64_t, double, std::allocator, - nlohmann::adl_serializer, custom_binary_type>; +using custom_json = nlohmann::json::with_binary_t<custom_binary_type>; int main() {
diff --git a/docs/mkdocs/docs/examples/custom_object_type.cpp b/docs/mkdocs/docs/examples/custom_object_type.cpp index d4f99cc..9d4b43d 100644 --- a/docs/mkdocs/docs/examples/custom_object_type.cpp +++ b/docs/mkdocs/docs/examples/custom_object_type.cpp
@@ -1,12 +1,11 @@ #include <iostream> #include <type_traits> -#include <vector> #include <nlohmann/json.hpp> #include "custom_object_type.hpp" -using custom_json = nlohmann::basic_json<custom_object_type, std::vector>; +using custom_json = nlohmann::json::with_object_t<custom_object_type>; int main() {
diff --git a/docs/mkdocs/docs/examples/custom_string_type.cpp b/docs/mkdocs/docs/examples/custom_string_type.cpp index 63b798f..2e71c93 100644 --- a/docs/mkdocs/docs/examples/custom_string_type.cpp +++ b/docs/mkdocs/docs/examples/custom_string_type.cpp
@@ -1,12 +1,10 @@ #include <iostream> -#include <map> -#include <vector> #include <nlohmann/json.hpp> #include "custom_string_type.hpp" -using custom_json = nlohmann::basic_json<std::map, std::vector, custom_string_type>; +using custom_json = nlohmann::json::with_string_t<custom_string_type>; int main() {
diff --git a/docs/mkdocs/docs/features/types/number_handling.md b/docs/mkdocs/docs/features/types/number_handling.md index 8bcf2dd..7a87352 100644 --- a/docs/mkdocs/docs/features/types/number_handling.md +++ b/docs/mkdocs/docs/features/types/number_handling.md
@@ -352,9 +352,8 @@ A `basic_json` type that uses `#!c long double` as floating-point type. - ```cpp hl_lines="2" - using json_ld = nlohmann::basic_json<std::map, std::vector, std::string, bool, - std::int64_t, std::uint64_t, long double>; + ```cpp hl_lines="1" + using json_ld = nlohmann::json::with_float_t<long double>; ``` Note values should then be parsed with `json_ld::parse` rather than `json::parse` as the latter would parse
diff --git a/docs/mkdocs/docs/features/types/template_parameters.md b/docs/mkdocs/docs/features/types/template_parameters.md index 591d49c..3a24d74 100644 --- a/docs/mkdocs/docs/features/types/template_parameters.md +++ b/docs/mkdocs/docs/features/types/template_parameters.md
@@ -8,6 +8,10 @@ that are known to work for that parameter and the ones that do not, checked against Boost 1.83, Abseil 20250127.0, Folly, EASTL 3.21, `ankerl::unordered_dense`, `phmap`, `gtl`, `robin_hood`, `tsl::ordered_map`, and Qt 6. +To change a single template parameter and keep the others, use the member alias templates +[`with_*_t`](../../api/basic_json/with_t.md); for instance, `nlohmann::json::with_float_t<long double>` is `json` with +`#!cpp long double` as [`number_float_t`](../../api/basic_json/number_float_t.md). + ## How to read this page Requirements are split into two groups: @@ -143,7 +147,7 @@ using base_t::base_t; }; -using unordered_json = nlohmann::basic_json<unordered_map_object>; +using unordered_json = nlohmann::json::with_object_t<unordered_map_object>; ``` Whether `#!cpp std::unordered_map` can be instantiated at all depends on the standard library: `object_t` is formed @@ -176,7 +180,7 @@ using base_t::base_t; }; -using flat_hash_json = nlohmann::basic_json<flat_hash_object>; +using flat_hash_json = nlohmann::json::with_object_t<flat_hash_object>; ``` `absl::node_hash_map` keeps references to the mapped values valid across insertions; `absl::flat_hash_map` does not,
diff --git a/docs/mkdocs/docs/integration/migration_guide.md b/docs/mkdocs/docs/integration/migration_guide.md index 17b2eb3..bfbf850 100644 --- a/docs/mkdocs/docs/integration/migration_guide.md +++ b/docs/mkdocs/docs/integration/migration_guide.md
@@ -116,7 +116,7 @@ === "Deprecated" ```cpp - using my_json = nlohmann::basic_json<std::map, std::vector, my_string_type>; + using my_json = nlohmann::json::with_string_t<my_string_type>; nlohmann::json_pointer<my_json> ptr("/foo/bar/1"); ```
diff --git a/docs/mkdocs/docs/integration/package_managers.md b/docs/mkdocs/docs/integration/package_managers.md index d5357b8..bfa9943 100644 --- a/docs/mkdocs/docs/integration/package_managers.md +++ b/docs/mkdocs/docs/integration/package_managers.md
@@ -125,10 +125,24 @@ Please see the Meson project for any issues regarding the packaging. The provided `meson.build` can also be used as an alternative to CMake for installing `nlohmann_json` system-wide in -which case a [pkg-config](pkg-config.md) file is installed. To use it, have your build system require the -`nlohmann_json` pkg-config dependency. In Meson, it is preferred to use the -[`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a subproject fallback, rather than -using the subproject directly. +which case a [pkg-config](pkg-config.md) file and the CMake package config files are installed. To use it, have your build system require +the `nlohmann_json` pkg-config dependency, or use [`find_package(nlohmann_json)`](cmake.md#external) in CMake. In Meson, +it is preferred to use the [`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a +subproject fallback, rather than using the subproject directly. + +The options that change the library's configuration are available in Meson as well, named like the +[CMake options](cmake.md#cmake-options) without the `JSON_` prefix: `MultipleHeaders`, `GlobalUDLs`, +`ImplicitConversions`, `DisableEnumSerialization`, `DisableTupleReferenceConversion`, `Diagnostics`, +`Diagnostic_Positions`, `LegacyDiscardedValueComparison`, `StrictNulHandling`, `StrictBinaryUTF8`, and +`DeleteDeprecatedFunctions`. They have the same defaults as in CMake, except that +`MultipleHeaders` is `false`. Set them with `-D` when setting up the build, or with the subproject name as prefix when +the library is used as a subproject: + +```shell +meson setup build -Dnlohmann_json:Diagnostics=true +``` + +The resulting compile definitions are part of the Meson dependency, the pkg-config file, and the CMake target. ??? example "Example: Wrap"
diff --git a/include/nlohmann/detail/bit_ops.hpp b/include/nlohmann/detail/bit_ops.hpp index 48c5eda..c570814 100644 --- a/include/nlohmann/detail/bit_ops.hpp +++ b/include/nlohmann/detail/bit_ops.hpp
@@ -9,12 +9,15 @@ #pragma once #include <cstdint> // uint64_t +#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + #include <intrin0.h> // __umulh, _umul128 +#endif #include <nlohmann/detail/abi_macros.hpp> // Portable bit-level helpers for the number and string scanners. They use -// compiler builtins where available and plain C++ otherwise, so they need no -// platform headers and work regardless of byte order. +// compiler builtins or platform-specific intrinsics where available and plain +// C++ otherwise, so they work regardless of byte order. NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -71,6 +74,12 @@ __extension__ using uint128 = unsigned __int128; const uint128 r = static_cast<uint128>(a) * b; return {static_cast<std::uint64_t>(r), static_cast<std::uint64_t>(r >> 64u)}; +#elif defined(_MSC_VER) && defined(_M_X64) + std::uint64_t high = 0; + const std::uint64_t low = _umul128(a, b, &high); + return {low, high}; +#elif defined(_MSC_VER) && defined(_M_ARM64) + return {a * b, __umulh(a, b)}; #else const std::uint64_t a_lo = a & 0xFFFFFFFFu; const std::uint64_t a_hi = a >> 32u;
diff --git a/meson.build b/meson.build index 2800957..19f7347 100644 --- a/meson.build +++ b/meson.build
@@ -2,24 +2,120 @@ 'cpp', version : '3.12.0', license : 'MIT', + meson_version : '>= 0.64', + default_options: ['cpp_std=c++11'], ) +if get_option('MultipleHeaders') + incdir = 'include' +else + incdir = 'single_include' +endif + +# The same compile definitions as the CMake target (see target_compile_definitions +# in CMakeLists.txt): only an option that differs from its default adds one. +json_defines = [] +if not get_option('GlobalUDLs') + json_defines += 'JSON_USE_GLOBAL_UDLS=0' +endif +if not get_option('ImplicitConversions') + json_defines += 'JSON_USE_IMPLICIT_CONVERSIONS=0' +endif +if get_option('DisableEnumSerialization') + json_defines += 'JSON_DISABLE_ENUM_SERIALIZATION=1' +endif +if get_option('DisableTupleReferenceConversion') + json_defines += 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1' +endif +if get_option('Diagnostics') + json_defines += 'JSON_DIAGNOSTICS=1' +endif +if get_option('Diagnostic_Positions') + json_defines += 'JSON_DIAGNOSTIC_POSITIONS=1' +endif +if get_option('LegacyDiscardedValueComparison') + json_defines += 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1' +endif +if get_option('StrictNulHandling') + json_defines += 'JSON_STRICT_NUL_HANDLING=1' +endif +if get_option('StrictBinaryUTF8') + json_defines += 'JSON_STRICT_BINARY_UTF8=1' +endif +if get_option('DeleteDeprecatedFunctions') + json_defines += 'JSON_DELETE_DEPRECATED_FUNCTIONS=1' +endif + +cpp_args = [] +foreach define : json_defines + cpp_args += '-D' + define +endforeach + nlohmann_json_dep = declare_dependency( - include_directories: include_directories('single_include') + compile_args: cpp_args, + include_directories: include_directories(incdir) ) +meson.override_dependency('nlohmann_json', nlohmann_json_dep) +# The multi-header version under the name earlier versions of this file used nlohmann_json_multiple_headers = declare_dependency( + compile_args: cpp_args, include_directories: include_directories('include') ) if not meson.is_subproject() -install_headers('single_include/nlohmann/json.hpp', subdir: 'nlohmann') -install_headers('single_include/nlohmann/json_fwd.hpp', subdir: 'nlohmann') -install_headers('single_include/nlohmann/json_literals.hpp', subdir: 'nlohmann') + install_subdir( + incdir / 'nlohmann', + install_dir: get_option('includedir'), + install_tag: 'devel', + ) -pkgc = import('pkgconfig') -pkgc.generate(name: 'nlohmann_json', - version: meson.project_version(), - description: 'JSON for Modern C++' -) + pkgc = import('pkgconfig') + pkgc.generate(name: 'nlohmann_json', + version: meson.project_version(), + description: 'JSON for Modern C++', + extra_cflags: cpp_args, + install_dir: get_option('datadir') / 'pkgconfig', + ) + + # CMake package config files, so that find_package(nlohmann_json) works. The + # include directory is given relative to the config files, so that the + # installation can be relocated. This is not possible if includedir or datadir + # is an absolute path outside the prefix (e.g., with the separate outputs of + # Nix); then the absolute include directory is used, as CMake does. + fs = import('fs') + cmake_install_dir = get_option('datadir') / 'cmake' / meson.project_name() + cmake_to_prefix = [] + if fs.is_absolute(get_option('includedir')) or fs.is_absolute(cmake_install_dir) + cmake_include_dir = (get_option('prefix') / get_option('includedir')).replace('\\', '/') + else + foreach component : cmake_install_dir.split('/') + cmake_to_prefix += '..' + endforeach + cmake_include_dir = '${_IMPORT_PREFIX}/' + get_option('includedir') + endif + + cmake_conf = configuration_data() + cmake_conf.set('PROJECT_NAME', meson.project_name()) + cmake_conf.set('PROJECT_VERSION', meson.project_version()) + cmake_conf.set('PROJECT_VERSION_MAJOR', meson.project_version().split('.')[0]) + cmake_conf.set('NLOHMANN_JSON_TARGET_NAME', meson.project_name()) + cmake_conf.set('NLOHMANN_JSON_TARGETS_EXPORT_NAME', meson.project_name() + 'Targets') + cmake_conf.set('NLOHMANN_JSON_INCLUDE_DIR', cmake_include_dir) + cmake_conf.set('NLOHMANN_JSON_CONFIG_TO_PREFIX', '/'.join(cmake_to_prefix)) + cmake_conf.set('NLOHMANN_JSON_COMPILE_DEFINITIONS', ';'.join(json_defines)) + + foreach cmake_file : [ + ['cmake/config.cmake.in', 'nlohmann_jsonConfig.cmake'], + ['cmake/nlohmann_jsonConfigVersion.cmake.in', 'nlohmann_jsonConfigVersion.cmake'], + ['cmake/nlohmann_jsonTargets.cmake.in', 'nlohmann_jsonTargets.cmake'], + ] + configure_file( + input: cmake_file[0], + output: cmake_file[1], + configuration: cmake_conf, + format: 'cmake@', + install_dir: cmake_install_dir, + ) + endforeach endif
diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..d4bfb20 --- /dev/null +++ b/meson_options.txt
@@ -0,0 +1,66 @@ +option( + 'MultipleHeaders', + type: 'boolean', + value: false, + description: 'Use non-amalgamated version of the library', +) +option( + 'GlobalUDLs', + type: 'boolean', + value: true, + description: 'Place user-defined string literals in the global namespace', +) +option( + 'ImplicitConversions', + type: 'boolean', + value: true, + description: 'Enable implicit conversions', +) +option( + 'DisableEnumSerialization', + type: 'boolean', + value: false, + description: 'Disable default integer enum serialization', +) +option( + 'DisableTupleReferenceConversion', + type: 'boolean', + value: false, + description: 'Disable conversion from a one-element tuple of a JSON reference', +) +option( + 'Diagnostics', + type: 'boolean', + value: false, + description: 'Use extended diagnostic messages', +) +option( + 'Diagnostic_Positions', + type: 'boolean', + value: false, + description: 'Enable diagnostic positions', +) +option( + 'LegacyDiscardedValueComparison', + type: 'boolean', + value: false, + description: 'Enable legacy discarded value comparison', +) +option( + 'StrictNulHandling', + type: 'boolean', + value: false, + description: 'Enable strict NUL-byte handling', +) +option( + 'StrictBinaryUTF8', + type: 'boolean', + value: false, + description: 'Enable UTF-8 checks in the CBOR, UBJSON, BJData, and BSON writers', +) +option( + 'DeleteDeprecatedFunctions', + type: 'boolean', + value: false, + description: 'Delete the deprecated functions instead of only deprecating them', +)
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 64fa63b..93ffdd2 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp
@@ -8793,13 +8793,16 @@ #include <cstdint> // uint64_t +#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + #include <intrin0.h> // __umulh, _umul128 +#endif // #include <nlohmann/detail/abi_macros.hpp> // Portable bit-level helpers for the number and string scanners. They use -// compiler builtins where available and plain C++ otherwise, so they need no -// platform headers and work regardless of byte order. +// compiler builtins or platform-specific intrinsics where available and plain +// C++ otherwise, so they work regardless of byte order. NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -8856,6 +8859,12 @@ __extension__ using uint128 = unsigned __int128; const uint128 r = static_cast<uint128>(a) * b; return {static_cast<std::uint64_t>(r), static_cast<std::uint64_t>(r >> 64u)}; +#elif defined(_MSC_VER) && defined(_M_X64) + std::uint64_t high = 0; + const std::uint64_t low = _umul128(a, b, &high); + return {low, high}; +#elif defined(_MSC_VER) && defined(_M_ARM64) + return {a * b, __umulh(a, b)}; #else const std::uint64_t a_lo = a & 0xFFFFFFFFu; const std::uint64_t a_hi = a >> 32u;
diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index 07eb21c..4062870 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp
@@ -370,14 +370,7 @@ #if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) SECTION("std::map-backed object_t") { - using bad_alloc_json = nlohmann::basic_json<std::map, - std::vector, - std::string, - bool, - std::int64_t, - std::uint64_t, - double, - nth_alloc_fails_allocator>; + using bad_alloc_json = nlohmann::json::with_allocator_t<nth_alloc_fails_allocator>; check_deep_copy_survives_failing_allocation<bad_alloc_json>(false); check_deep_copy_survives_failing_allocation<bad_alloc_json>(true); @@ -385,14 +378,7 @@ SECTION("ordered_map-backed object_t") { - using bad_alloc_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, - std::vector, - std::string, - bool, - std::int64_t, - std::uint64_t, - double, - nth_alloc_fails_allocator>; + using bad_alloc_ordered_json = nlohmann::ordered_json::with_allocator_t<nth_alloc_fails_allocator>; check_deep_copy_survives_failing_allocation<bad_alloc_ordered_json>(false); check_deep_copy_survives_failing_allocation<bad_alloc_ordered_json>(true); @@ -450,14 +436,7 @@ TEST_CASE("deep copy uses the provided allocator") { - using counting_json = nlohmann::basic_json<std::map, - std::vector, - std::string, - bool, - std::int64_t, - std::uint64_t, - double, - scratch_counting_allocator>; + using counting_json = nlohmann::json::with_allocator_t<scratch_counting_allocator>; // deeper than the 128 levels the copy constructor descends into, so the // innermost objects are copied by the iterative deep copy @@ -516,14 +495,7 @@ // the allocator in noexcept constructors, so a failing construction crashes // the program there instead of throwing std::bad_alloc. Nothing to check. #if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) - using countdown_json = nlohmann::basic_json<std::map, - std::vector, - std::string, - bool, - std::int64_t, - std::uint64_t, - double, - countdown_allocator>; + using countdown_json = nlohmann::json::with_allocator_t<countdown_allocator>; // deeper than the 128 levels the converting constructor descends into, so // that failures land on both sides of the bound - or, built with @@ -631,14 +603,7 @@ // Since that stack could itself throw bad_alloc from inside the // noexcept destructor (#5135), destroy() no longer allocates anything: // it only ever frees what is already there. - using counting_json = nlohmann::basic_json<std::map, - std::vector, - std::string, - bool, - std::int64_t, - std::uint64_t, - double, - counting_allocator>; + using counting_json = nlohmann::json::with_allocator_t<counting_allocator>; SECTION("array") { @@ -683,14 +648,7 @@ TEST_CASE("a failed allocation leaves the value unchanged") { // create JSON type using the throwing allocator - using my_json = nlohmann::basic_json<std::map, - std::vector, - std::string, - bool, - std::int64_t, - std::uint64_t, - double, - my_allocator>; + using my_json = nlohmann::json::with_allocator_t<my_allocator>; // Each of these creates a string, array, object, or binary value. The // value must be created before the type is changed: otherwise, a failed
diff --git a/tests/src/unit-binary_formats.cpp b/tests/src/unit-binary_formats.cpp index 3e5edf7..03dbc94 100644 --- a/tests/src/unit-binary_formats.cpp +++ b/tests/src/unit-binary_formats.cpp
@@ -237,7 +237,7 @@ // the binary formats as function pointers for "Binary formats with narrow number types"; // named functions rather than lambdas, because clang 3.5 cannot convert a lambda // to a function pointer in the braced initializer of the format table -using narrow_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>; +using narrow_json = nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float>; using bytes = std::vector<std::uint8_t>; bytes encode_cbor(const json& j)
diff --git a/tests/src/unit-class_lexer.cpp b/tests/src/unit-class_lexer.cpp index 3a84208..82690a6 100644 --- a/tests/src/unit-class_lexer.cpp +++ b/tests/src/unit-class_lexer.cpp
@@ -17,6 +17,7 @@ #include <cstdio> // snprintf #include <cstdlib> // strtod #include <cstring> // memcpy +#include <limits> // numeric_limits #include <map> // map #include <random> // mt19937 #include <sstream> // stringstream @@ -1136,8 +1137,39 @@ SECTION("128-bit products and leading zeros") { + const auto check_product = [](std::uint64_t a, std::uint64_t b) + { + const auto product = nlohmann::detail::full_multiplication(a, b); + CHECK(big_from(product.high, product.low) == big_mul(big_from(0, a), big_from(0, b))); + }; + + const std::uint64_t max = (std::numeric_limits<std::uint64_t>::max)(); + const std::array<std::pair<std::uint64_t, std::uint64_t>, 13> edge_cases = + { + { + {0, 0}, + {0, 1}, + {1, 1}, + {1, max}, + {0xFFFFFFFFu, 0x100000000u}, + {0x100000000u, 0x100000000u}, + {0x100000001u, 0x100000001u}, + {max, max}, + {max, 2}, + {0xFFFFFFFF00000000u, 0x100000001u}, + {0x100000001u, 0xFFFFFFFF00000000u}, + {max, 1}, + {2, max}, + } + }; + + for (const auto& test : edge_cases) + { + check_product(test.first, test.second); + } + // whichever implementation the compiler gets (with or without a - // 128-bit integer type or a builtin) + // 128-bit integer type or a builtin / intrinsic) std::uint64_t state = 42; for (int i = 0; i < 10000; ++i) { @@ -1146,8 +1178,7 @@ state ^= state << 17u; const std::uint64_t a = state; const std::uint64_t b = (state * 0x9E3779B97F4A7C15u) >> (i % 64); - const auto product = nlohmann::detail::full_multiplication(a, b); - CHECK(big_from(product.high, product.low) == big_mul(big_from(0, a), big_from(0, b))); + check_product(a, b); const int k = i % 64; const std::uint64_t x = (std::uint64_t{1} << k) | (a & ((std::uint64_t{1} << k) - 1));
diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index b9257cb..1e409bf 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp
@@ -824,7 +824,7 @@ return !(lhs == rhs); } }; -using unordered_json = nlohmann::basic_json<unordered_object_t>; +using unordered_json = nlohmann::json::with_object_t<unordered_object_t>; // the entries "0" to "9", enumerated in ascending or in descending order unordered_json make_unordered_object(const bool descending) @@ -875,7 +875,7 @@ template<class Key, class Value, class /*Compare*/, class Allocator> using key_case_map = std::map<Key, Value, key_case_less, Allocator>; -using key_case_json = nlohmann::basic_json<key_case_map>; +using key_case_json = nlohmann::json::with_object_t<key_case_map>; // the innermost value of a chain of single-element arrays template<typename Json> @@ -905,7 +905,7 @@ template<class Key, class Value, class /*Compare*/, class Allocator> using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>; -using ci_json = nlohmann::basic_json<case_insensitive_map>; +using ci_json = nlohmann::json::with_object_t<case_insensitive_map>; } // namespace TEST_CASE("equality of objects whose entries have no fixed order")
diff --git a/tests/src/unit-custom-array-type.cpp b/tests/src/unit-custom-array-type.cpp index 7a374b6..6f015d2 100644 --- a/tests/src/unit-custom-array-type.cpp +++ b/tests/src/unit-custom-array-type.cpp
@@ -22,7 +22,7 @@ // std::deque has no capacity() member function, which the library only needs // to detect a reallocation for JSON_DIAGNOSTICS -using deque_json = nlohmann::basic_json<std::map, std::deque>; +using deque_json = nlohmann::json::with_array_t<std::deque>; // a std::vector whose at() is hidden: the library performs its own bounds // check and must not fall back to the container's checked accessor @@ -39,7 +39,7 @@ void at() = delete; }; -using no_at_json = nlohmann::basic_json<std::map, vector_without_at>; +using no_at_json = nlohmann::json::with_array_t<vector_without_at>; } // namespace
diff --git a/tests/src/unit-custom-object-type.cpp b/tests/src/unit-custom-object-type.cpp index 0eaaf2d..fdd0632 100644 --- a/tests/src/unit-custom-object-type.cpp +++ b/tests/src/unit-custom-object-type.cpp
@@ -179,7 +179,7 @@ } }; -using no_key_compare_json = nlohmann::basic_json<no_key_compare_map>; +using no_key_compare_json = nlohmann::json::with_object_t<no_key_compare_map>; // An ObjectType whose erase(iterator) returns void rather than the following // iterator, as for instance Abseil's hash maps do @@ -196,7 +196,7 @@ } }; -using void_erase_json = nlohmann::basic_json<void_erase_map>; +using void_erase_json = nlohmann::json::with_object_t<void_erase_map>; // wraps an iterator, but only offers the LegacyForwardIterator operations, // like the iterators of std::unordered_map and other hash maps @@ -388,7 +388,7 @@ } }; -using forward_only_json = nlohmann::basic_json<forward_only_map>; +using forward_only_json = nlohmann::json::with_object_t<forward_only_map>; } // namespace
diff --git a/tests/src/unit-hash.cpp b/tests/src/unit-hash.cpp index e70b39b..117bc01 100644 --- a/tests/src/unit-hash.cpp +++ b/tests/src/unit-hash.cpp
@@ -157,13 +157,13 @@ // the ends of the integer ranges, which equal floats exactly const auto int_min = (std::numeric_limits<json::number_integer_t>::min)(); const auto int_max = (std::numeric_limits<json::number_integer_t>::max)(); - const auto two_63 = json::number_unsigned_t(1) << 63U; + const auto two_63 = static_cast<json::number_unsigned_t>(1) << 63U; CHECK(json(int_min) == json(-9223372036854775808.0)); CHECK(std::hash<json> {}(json(int_min)) == std::hash<json> {}(json(-9223372036854775808.0))); CHECK(json(two_63) == json(9223372036854775808.0)); CHECK(std::hash<json> {}(json(two_63)) == std::hash<json> {}(json(9223372036854775808.0))); - CHECK(json(json::number_unsigned_t(int_max)) == json(int_max)); - CHECK(std::hash<json> {}(json(json::number_unsigned_t(int_max))) == std::hash<json> {}(json(int_max))); + CHECK(json(static_cast<json::number_unsigned_t>(int_max)) == json(int_max)); + CHECK(std::hash<json> {}(json(static_cast<json::number_unsigned_t>(int_max))) == std::hash<json> {}(json(int_max))); } TEST_CASE("hash<nlohmann::ordered_json>")
diff --git a/tests/src/unit-large_json.cpp b/tests/src/unit-large_json.cpp index 6ce5626..cd615f4 100644 --- a/tests/src/unit-large_json.cpp +++ b/tests/src/unit-large_json.cpp
@@ -426,7 +426,7 @@ { for (std::size_t depth = 120; depth <= 140; ++depth) { - CAPTURE(depth); + CAPTURE(depth) const json array = nested_array(depth, json(7)); CHECK(json::from_cbor(json::to_cbor(array)) == array); @@ -464,7 +464,7 @@ nlohmann::detail::recursion_depth_limit() + 1, nlohmann::detail::recursion_depth_limit() + 2 }) { - CAPTURE(depth); + CAPTURE(depth) const json array = nested_array(depth, json(0)); std::vector<std::uint8_t> expected_cbor(depth, 0x81); @@ -505,10 +505,22 @@ const json discarded_leaf(json::value_t::discarded); const json deep_discarded = nested_array(depth, discarded_leaf); - CHECK_THROWS_WITH_AS(json::to_cbor(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error); - CHECK_THROWS_WITH_AS(json::to_msgpack(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error); - CHECK_THROWS_WITH_AS(json::to_ubjson(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error); - CHECK_THROWS_WITH_AS(json::to_bjdata(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error); + // with diagnostics, the message names the path to the discarded leaf +#if JSON_DIAGNOSTICS + std::string path; + for (std::size_t i = 0; i < depth; ++i) + { + path += "/0"; + } + const std::string prefix = "[json.exception.type_error.321] (" + path + ") "; +#else + const std::string prefix = "[json.exception.type_error.321] "; +#endif + + CHECK_THROWS_WITH_AS(json::to_cbor(deep_discarded), (prefix + "cannot serialize discarded value to CBOR").c_str(), json::type_error); + CHECK_THROWS_WITH_AS(json::to_msgpack(deep_discarded), (prefix + "cannot serialize discarded value to MessagePack").c_str(), json::type_error); + CHECK_THROWS_WITH_AS(json::to_ubjson(deep_discarded), (prefix + "cannot serialize discarded value to UBJSON").c_str(), json::type_error); + CHECK_THROWS_WITH_AS(json::to_bjdata(deep_discarded), (prefix + "cannot serialize discarded value to BJData").c_str(), json::type_error); } SECTION("does not overflow the C++ stack")
diff --git a/tests/src/unit-locale-cpp.cpp b/tests/src/unit-locale-cpp.cpp index 5f5e78c..b665b65 100644 --- a/tests/src/unit-locale-cpp.cpp +++ b/tests/src/unit-locale-cpp.cpp
@@ -172,7 +172,7 @@ // a floating-point type that is not a float or a double is written // with snprintf, whose locale-specific decimal point and thousands // separator are undone afterwards - using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>; + using long_double_json = nlohmann::json::with_float_t<long double>; CHECK(long_double_json(12345.5L).dump() == "12345.5"); CHECK(long_double_json(1.0L).dump() == "1.0"); CHECK(long_double_json(-0.25L).dump() == "-0.25"); @@ -273,7 +273,7 @@ } text += "]"; - using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>; + using long_double_json = nlohmann::json::with_float_t<long double>; // reference values, parsed without a locale switch REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr); @@ -449,7 +449,7 @@ // long double on 64-bit Arm, where it is IEEE-754 double) takes the // locale-independent to_chars() path instead, and this test is a no-op // there. - using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>; + using long_double_json = nlohmann::json::with_float_t<long double>; using ld_limits = std::numeric_limits<long_double_json::number_float_t>; const bool is_ieee_single_or_double = (ld_limits::is_iec559 && ld_limits::digits == 24 && ld_limits::max_exponent == 128) ||
diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index b705f94..d297697 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp
@@ -2432,8 +2432,8 @@ // used to read the union member that was not the active one, writing // wrong bytes for some values; std::int64_t/std::uint64_t (the default // types, where both members have the same width) were not affected - using int32_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint64_t, double>; - using int16_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int16_t, std::uint64_t, double>; + using int32_json = nlohmann::json::with_integers_t<std::int32_t, std::uint64_t>; + using int16_json = nlohmann::json::with_integers_t<std::int16_t, std::uint64_t>; SECTION("number_integer_t = std::int32_t") {
diff --git a/tests/src/unit-regression1.cpp b/tests/src/unit-regression1.cpp index 8f0d4c7..084eb79 100644 --- a/tests/src/unit-regression1.cpp +++ b/tests/src/unit-regression1.cpp
@@ -39,7 +39,7 @@ template<class K, class V, class dummy_compare, class A> using my_workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>; -using my_json = nlohmann::basic_json<my_workaround_fifo_map>; +using my_json = nlohmann::json::with_object_t<my_workaround_fifo_map>; ///////////////////////////////////////////////////////////////////// // for #977 @@ -86,8 +86,7 @@ }; } // namespace ns -using foo_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, - std::uint64_t, double, std::allocator, ns::foo_serializer, std::vector<std::uint8_t>>; +using foo_json = nlohmann::json::with_json_serializer_t<ns::foo_serializer>; ///////////////////////////////////////////////////////////////////// // for #805 @@ -254,7 +253,7 @@ { // create JSON class with nonstandard integer number type using custom_json = - nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, uint32_t, float>; + nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float>; custom_json j; j["int_1"] = 1; CHECK(j["int_1"] == 1); @@ -470,18 +469,17 @@ // create JSON class with nonstandard float number type // float - nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, uint32_t, float> const j_float = + nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float> const j_float = 1.23e25f; CHECK(j_float.get<float>() == 1.23e25f); // double - nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, uint64_t, double> const j_double = + nlohmann::json const j_double = 1.23e35; CHECK(j_double.get<double>() == 1.23e35); // long double - nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, uint64_t, long double> - const j_long_double = 1.23e45L; + nlohmann::json::with_float_t<long double> const j_long_double = 1.23e45L; CHECK(j_long_double.get<long double>() == 1.23e45L); }
diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index d084e64..b532e75 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp
@@ -64,18 +64,7 @@ ///////////////////////////////////////////////////////////////////// // for #4804 ///////////////////////////////////////////////////////////////////// - using json_4804 = nlohmann::basic_json<std::map, // ObjectType - std::vector, // ArrayType - std::string, // StringType - bool, // BooleanType - std::int64_t, // NumberIntegerType - std::uint64_t, // NumberUnsignedType - double, // NumberFloatType - std::allocator, // AllocatorType - nlohmann::adl_serializer, // JSONSerializer - std::vector<std::byte>, // BinaryType - void // CustomBaseClass - >; + using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>; #endif #ifdef JSON_HAS_CPP_20 @@ -107,7 +96,7 @@ // for #1021 ///////////////////////////////////////////////////////////////////// -using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>; +using float_json = nlohmann::json::with_float_t<float>; #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) namespace @@ -155,10 +144,8 @@ }; }; -using failing_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, - std::int64_t, std::uint64_t, double, failing_allocator>; -using failing_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, std::vector, std::string, bool, - std::int64_t, std::uint64_t, double, failing_allocator>; +using failing_json = nlohmann::json::with_allocator_t<failing_allocator>; +using failing_ordered_json = nlohmann::ordered_json::with_allocator_t<failing_allocator>; // builds `depth` levels of nesting around a scalar, iteratively (never // recursing: each wrap only moves the previous, already-built value, which
diff --git a/tests/src/unit-regression3.cpp b/tests/src/unit-regression3.cpp index 69a95fb..f8d9740 100644 --- a/tests/src/unit-regression3.cpp +++ b/tests/src/unit-regression3.cpp
@@ -62,18 +62,7 @@ ///////////////////////////////////////////////////////////////////// // for #4804 ///////////////////////////////////////////////////////////////////// - using json_4804 = nlohmann::basic_json<std::map, // ObjectType - std::vector, // ArrayType - std::string, // StringType - bool, // BooleanType - std::int64_t, // NumberIntegerType - std::uint64_t, // NumberUnsignedType - double, // NumberFloatType - std::allocator, // AllocatorType - nlohmann::adl_serializer, // JSONSerializer - std::vector<std::byte>, // BinaryType - void // CustomBaseClass - >; + using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>; #endif #ifdef JSON_HAS_CPP_20 @@ -930,7 +919,7 @@ { // the capacity reserved for definite-length arrays must not require the // array type to have a reserve() member function - using deque_json = nlohmann::basic_json<std::map, std::deque>; + using deque_json = nlohmann::json::with_array_t<std::deque>; SECTION("std::deque") {
diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp index 1aa2111..c56c4f5 100644 --- a/tests/src/unit-serialization.cpp +++ b/tests/src/unit-serialization.cpp
@@ -367,8 +367,7 @@ // serializer::dump_float(x, std::false_type). That branch must use the // "%.*Lg" format specifier; using "%.*g" with a long double argument is // undefined behavior and corrupts the output. - using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, - bool, std::int64_t, std::uint64_t, long double>; + using long_double_json = nlohmann::json::with_float_t<long double>; SECTION("round-trip dump/parse") {
diff --git a/tools/check_build_options/README.md b/tools/check_build_options/README.md new file mode 100644 index 0000000..1b24a55 --- /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/check_build_options.py b/tools/check_build_options/check_build_options.py new file mode 100755 index 0000000..9b9debf --- /dev/null +++ b/tools/check_build_options/check_build_options.py
@@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Check that the Meson build and the pkg-config files offer the CMake options. + +The compile definitions of the CMake target (target_compile_definitions in +CMakeLists.txt) are the reference. For every option used there, the script +checks that + +- the CMake pkg-config file adds the same definition under the same condition, +- meson_options.txt has a boolean option of the same name without the "JSON_" + prefix and with the same default, +- meson.build adds the same definition under the same condition, and +- the Meson section of the package manager documentation lists the option. + +Meson's MultipleHeaders option selects the include directory and adds no +definition; it is the only Meson option without a definition. +""" + +import argparse +import os +import re +import sys + +REPO_ROOT = os.path.normpath(os.path.join(sys.path[0], '..', '..')) +DOCS = os.path.join('docs', 'mkdocs', 'docs', 'integration', 'package_managers.md') + +# Meson options that add no compile definition, with their default +MESON_ONLY = {'MultipleHeaders': 'false'} + + +def read(root, path): + with open(os.path.join(root, path), encoding='utf-8') as f: + return f.read() + + +def cmake_target_definitions(cmake): + """Return {option: (definition, add_if_on)} from target_compile_definitions.""" + block = re.search(r'target_compile_definitions\(\s*\$\{NLOHMANN_JSON_TARGET_NAME\}\s*INTERFACE(.*?)\n\)', cmake, re.S) + if not block: + sys.exit('CMakeLists.txt: target_compile_definitions of the target not found') + result = {} + for line in block.group(1).split('\n'): + line = line.strip() + if not line: + continue + m = re.fullmatch(r'\$<\$<NOT:\$<BOOL:\$\{JSON_(\w+)\}>>:(\w+=\w+)>', line) + if m: + result[m.group(1)] = (m.group(2), False) + continue + m = re.fullmatch(r'\$<\$<BOOL:\$\{JSON_(\w+)\}>:(\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_<name>.""" + 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())