. "$BST_LIB" # nlohmann-json is "JSON for Modern C++": a header-only library whose whole API is # one class template, basic_json, that models a JSON document as a tagged union of # object / array / string / number / boolean / null / binary and gives it the # interface of an STL container -- at(), operator[], begin()/end(), size(), # push_back(), items() -- so a document is walked with the same idioms as a map or # a vector. Around that sit the parser (parse / sax_parse), the serializer (dump), # RFC 6901 json_pointer with RFC 6902 patch/diff and RFC 7386 merge_patch, the # five binary codecs (CBOR, MessagePack, UBJSON, BJData, BSON), and the # adl_serializer customisation point that teaches it a user's own types. # # The port is 45 headers plus the find_package(nlohmann_json) cmake files and one # pkg-config file -- ALL of it -dev. Nothing is compiled: the recipe's cmake build # produces an INTERFACE target, so the main package is empty (ALLOW_EMPTY) and # there is no archive, no shared object and no CLI. With no C++ compiler on the # image either, the shipped payload IS the deliverable, so every case here asserts # the artifact, in the shape utfcpp and the header-only libraries before it # established. # # What keeps that from being a file listing is the include-closure check below. # A header-only library is only usable if the tree it ships is SELF-CONTAINED: # json.hpp is a facade that includes 40-odd others, each of which includes more, so # one header dropped by a packaging split or a FILES glob turns every consumer's # first #include into a compile error. The check reads every # `#include ` in the INSTALLED tree and requires each target to be # installed too -- the tree asserted against itself, not against a list written # here. The version cross-check does the same for the metadata: the triple is read # off the header and the .pc and cmake files are required to agree with it, so # 3.11.3 is a fact about the port rather than about one file. # # Fork discipline (the lesson 81-cjson paid for): the runner's 60s per-suite bound # (BST_SUITE_TIMEOUT) is a budget in PROCESS SPAWNS, not in cases -- a check_out # per case is a command substitution plus an exec, and ~140 of those is a minute of # forking on the wasm32-hwjs runtime on its own. So each file is read ONCE into a # shell variable and every text assertion is a fork-free `case` glob against it: # ~140 cases for ~20 spawns. `has`/`hasfile`/`hasline` stay local to this suite so # a hot helper cannot change behaviour under the other 79 suites. inc=/usr/include/nlohmann top=/usr/include/json.hpp cm=/usr/share/cmake/nlohmann_json pc=/usr/share/pkgconfig/nlohmann_json.pc nl=' ' q='"' # hasfile -- ok iff is a non-empty regular file. # has -- ok iff contains the literal . # hasline -- ok iff has as a WHOLE line (a # substring test would let detail/json_pointer.hpp # satisfy a lookup for json_pointer.hpp). hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "missing: $2" ;; esac; } # --- the shipped header tree -------------------------------------------------- # # One find for the whole inventory; every presence case below is then a glob # against it rather than its own stat. files=$(find "$inc" -name '*.hpp' 2>/dev/null | sed "s|^$inc/||" | sort) nfiles=$(printf '%s\n' "$files" | grep -c .) # 45 headers is the full 3.11.3 multi-header install. The floor guards the checks # that follow: an empty find would otherwise let the closure check pass vacuously. if [ "$nfiles" -ge 45 ]; then bst_ok nlohmann-json/tree-count else bst_notok nlohmann-json/tree-count "installed=$nfiles want>=45"; fi # The five headers a consumer names directly: the facade, the forward # declarations (the include that keeps a header from pulling in all 200 kB), the # object type that preserves insertion order, and the two customisation points. for f in json.hpp json_fwd.hpp ordered_map.hpp adl_serializer.hpp \ byte_container_with_subtype.hpp; do hasline "nlohmann-json/hdr-${f%.hpp}" "$f" "$files" done # detail/: the implementation the facade is assembled from. It is not private in # any packaging sense -- json.hpp includes all of it -- so a missing file here # breaks the build of a consumer that only ever wrote #include . for f in detail/abi_macros.hpp detail/exceptions.hpp detail/hash.hpp \ detail/json_custom_base_class.hpp detail/json_pointer.hpp \ detail/json_ref.hpp detail/macro_scope.hpp detail/macro_unscope.hpp \ detail/string_concat.hpp detail/string_escape.hpp detail/value_t.hpp \ detail/conversions/from_json.hpp detail/conversions/to_chars.hpp \ detail/conversions/to_json.hpp \ detail/input/binary_reader.hpp detail/input/input_adapters.hpp \ detail/input/json_sax.hpp detail/input/lexer.hpp detail/input/parser.hpp \ detail/input/position_t.hpp \ detail/iterators/internal_iterator.hpp detail/iterators/iteration_proxy.hpp \ detail/iterators/iterator_traits.hpp detail/iterators/iter_impl.hpp \ detail/iterators/json_reverse_iterator.hpp \ detail/iterators/primitive_iterator.hpp \ detail/meta/cpp_future.hpp detail/meta/detected.hpp \ detail/meta/identity_tag.hpp detail/meta/is_sax.hpp detail/meta/std_fs.hpp \ detail/meta/type_traits.hpp detail/meta/void_t.hpp \ detail/meta/call_std/begin.hpp detail/meta/call_std/end.hpp \ detail/output/binary_writer.hpp detail/output/output_adapters.hpp \ detail/output/serializer.hpp \ thirdparty/hedley/hedley.hpp thirdparty/hedley/hedley_undef.hpp; do b=${f##*/} hasline "nlohmann-json/hdr-${b%.hpp}" "$f" "$files" done # The completeness check. Every nlohmann include the installed tree makes must # resolve inside the installed tree -- so the port is self-contained and a # consumer's first #include compiles. The reference list is read off the target, # not written here, so a header added or renamed upstream is covered automatically. incs=$(grep -rho '#include ]*>' "$inc" 2>/dev/null | sed 's|.*.*||' | sort -u) nincs=$(printf '%s\n' "$incs" | grep -c .) miss= for i in $incs; do case "$nl$files$nl" in *"$nl$i$nl"*) ;; *) miss="$miss $i" ;; esac done if [ "$nincs" -ge 40 ] && [ -z "$miss" ]; then bst_ok nlohmann-json/include-closure else bst_notok nlohmann-json/include-closure "refs=$nincs unresolved:$miss"; fi # The compatibility symlink the recipe adds on top of the upstream install: other # recipes commonly write #include "json.hpp", and this is what lets them. -s # follows the link, so it also proves the link is not dangling. if [ -L "$top" ]; then bst_ok nlohmann-json/compat-symlink else bst_notok nlohmann-json/compat-symlink "not a symlink: $top"; fi hasfile nlohmann-json/compat-resolves "$top" check_eq nlohmann-json/compat-target "nlohmann/json.hpp" "$(readlink "$top" 2>/dev/null)" # --- version, asserted across the three files that state it ------------------- # # nlohmann states its version as three macros; the .pc and the cmake version file # restate it as a string. Reading the triple off the header and requiring the other # two to match is what makes 3.11.3 a property of the port rather than of whichever # file happens to be read. a=$(cat "$inc/detail/abi_macros.hpp" 2>/dev/null) has nlohmann-json/version-major "#define NLOHMANN_JSON_VERSION_MAJOR 3" "$a" has nlohmann-json/version-minor "#define NLOHMANN_JSON_VERSION_MINOR 11" "$a" has nlohmann-json/version-patch "#define NLOHMANN_JSON_VERSION_PATCH 3" "$a" maj=${a#*#define NLOHMANN_JSON_VERSION_MAJOR }; maj=${maj%%[!0-9]*} min=${a#*#define NLOHMANN_JSON_VERSION_MINOR }; min=${min%%[!0-9]*} pat=${a#*#define NLOHMANN_JSON_VERSION_PATCH }; pat=${pat%%[!0-9]*} hver="$maj.$min.$pat" check_eq nlohmann-json/version-triple "3.11.3" "$hver" # The inline-namespace machinery those macros feed. nlohmann puts every symbol in # an inline namespace whose name encodes the version and the ABI-affecting build # options, so two objects compiled against different configurations fail to link # instead of corrupting each other. Its absence would mean a header edited or # replaced by something that is not upstream 3.11.3. has nlohmann-json/abi-namespace-begin "#define NLOHMANN_JSON_NAMESPACE_BEGIN" "$a" has nlohmann-json/abi-namespace-end "#define NLOHMANN_JSON_NAMESPACE_END" "$a" has nlohmann-json/abi-namespace-name "#define NLOHMANN_JSON_NAMESPACE " "$a" has nlohmann-json/abi-tags "#define NLOHMANN_JSON_ABI_TAGS" "$a" has nlohmann-json/abi-inline-ns "inline namespace" "$a" # --- the facade: json.hpp ----------------------------------------------------- # # Read once; every assertion below is a glob over the same 194 kB. h=$(cat "$inc/json.hpp" 2>/dev/null) hasfile nlohmann-json/json-present "$inc/json.hpp" has nlohmann-json/json-class "class basic_json" "$h" has nlohmann-json/json-ns-begin "NLOHMANN_JSON_NAMESPACE_BEGIN" "$h" has nlohmann-json/json-ns-end "NLOHMANN_JSON_NAMESPACE_END" "$h" # Parse and serialize -- the two directions that are the reason to link the # library. parse() builds a document, sax_parse() streams events for input too # large to hold, dump() writes it back with optional indentation. has nlohmann-json/api-parse "static basic_json parse(" "$h" has nlohmann-json/api-sax-parse "bool sax_parse(" "$h" has nlohmann-json/api-dump "string_t dump(" "$h" # The container interface. These are what make a JSON document usable with the # idioms a C++ caller already has, and items() is the proxy that makes a # range-for over an object yield key/value pairs. has nlohmann-json/api-at "reference at(" "$h" has nlohmann-json/api-at-const "const_reference at(" "$h" has nlohmann-json/api-subscript "reference operator[]" "$h" has nlohmann-json/api-begin "iterator begin(" "$h" has nlohmann-json/api-items "items()" "$h" has nlohmann-json/api-size "size_type size(" "$h" has nlohmann-json/api-contains "bool contains(" "$h" has nlohmann-json/api-push-back "void push_back(" "$h" has nlohmann-json/api-emplace-back "emplace_back" "$h" has nlohmann-json/api-init-list "basic_json(initializer_list_t" "$h" # The type tag and its predicates: a JSON value is a tagged union, and this is how # a caller checks the tag before reading the payload. has nlohmann-json/api-type "value_t type() const" "$h" has nlohmann-json/api-is-object "bool is_object()" "$h" has nlohmann-json/api-is-array "bool is_array()" "$h" has nlohmann-json/api-binary-t "binary_t" "$h" has nlohmann-json/api-number-t "number_float_t" "$h" # RFC 6901 pointers and the two patch formats built on them, plus flatten / # unflatten, which convert between a nested document and its pointer-keyed form. has nlohmann-json/api-json-pointer "using json_pointer = " "$h" has nlohmann-json/api-patch "basic_json patch(" "$h" has nlohmann-json/api-diff "static basic_json diff(" "$h" has nlohmann-json/api-merge-patch "void merge_patch(" "$h" has nlohmann-json/api-flatten "basic_json flatten(" "$h" has nlohmann-json/api-unflatten "basic_json unflatten(" "$h" # The five binary codecs, each asserted in both directions -- a writer without its # reader would ship a format the image can emit but not consume. for f in cbor msgpack ubjson bjdata bson; do has "nlohmann-json/api-to-$f" "to_$f(const basic_json" "$h" has "nlohmann-json/api-from-$f" "basic_json from_$f(" "$h" done # --- json_fwd.hpp: the declarations a consumer's own headers include ---------- f=$(cat "$inc/json_fwd.hpp" 2>/dev/null) has nlohmann-json/fwd-basic-json "class basic_json;" "$f" has nlohmann-json/fwd-json-alias "using json = basic_json<>;" "$f" has nlohmann-json/fwd-ordered "using ordered_json = basic_json;" "$f" has nlohmann-json/fwd-ns-begin "NLOHMANN_JSON_NAMESPACE_BEGIN" "$f" # --- the exception hierarchy -------------------------------------------------- # # Every failure mode of the library is one of these six, and a caller's catch # blocks name them, so the hierarchy is API. They derive from a common exception # base carrying the numeric id upstream documents each error under. e=$(cat "$inc/detail/exceptions.hpp" 2>/dev/null) has nlohmann-json/exc-base "class exception : public std::exception" "$e" has nlohmann-json/exc-parse "class parse_error : public exception" "$e" has nlohmann-json/exc-iterator "class invalid_iterator : public exception" "$e" has nlohmann-json/exc-type "class type_error : public exception" "$e" has nlohmann-json/exc-range "class out_of_range : public exception" "$e" has nlohmann-json/exc-other "class other_error : public exception" "$e" # --- the SAX interface -------------------------------------------------------- # # The streaming alternative to parse(): a caller derives from json_sax and # implements every one of these pure virtuals, so each is a member of the contract # and a missing one is a compile error in the consumer, not here. s=$(cat "$inc/detail/input/json_sax.hpp" 2>/dev/null) has nlohmann-json/sax-struct "struct json_sax" "$s" for m in null boolean number_integer number_unsigned number_float string binary \ start_object key end_object start_array end_array parse_error; do has "nlohmann-json/sax-$m" "virtual bool $m(" "$s" done # --- the code-generation macros ---------------------------------------------- # # How a caller teaches the library its own types without writing to_json/from_json # by hand. They are macros, so they exist only in the header -- nothing else in # the port stands in for them. m=$(cat "$inc/detail/macro_scope.hpp" 2>/dev/null) has nlohmann-json/macro-intrusive "#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(" "$m" has nlohmann-json/macro-intrusive-dflt "#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(" "$m" has nlohmann-json/macro-non-intrusive "#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(" "$m" has nlohmann-json/macro-enum "#define NLOHMANN_JSON_SERIALIZE_ENUM(" "$m" # --- the remaining named headers --------------------------------------------- # # One defining declaration each, so "present" means "carries its real content" # rather than "is a file of the right name". has nlohmann-json/decl-ordered-map "struct ordered_map" "$(cat "$inc/ordered_map.hpp" 2>/dev/null)" has nlohmann-json/decl-adl "struct adl_serializer" "$(cat "$inc/adl_serializer.hpp" 2>/dev/null)" has nlohmann-json/decl-byte-subtype "class byte_container_with_subtype" "$(cat "$inc/byte_container_with_subtype.hpp" 2>/dev/null)" has nlohmann-json/decl-json-pointer "class json_pointer" "$(cat "$inc/detail/json_pointer.hpp" 2>/dev/null)" has nlohmann-json/decl-value-t "enum class value_t : std::uint8_t" "$(cat "$inc/detail/value_t.hpp" 2>/dev/null)" # Hedley is the vendored portability shim json.hpp's attributes expand through. # It ships namespaced (JSON_HEDLEY_*) precisely so it cannot collide with a copy a # consumer vendors itself, and hedley_undef.hpp is what makes that guarantee hold. hd=$(cat "$inc/thirdparty/hedley/hedley.hpp" 2>/dev/null) has nlohmann-json/hedley-version "#define JSON_HEDLEY_VERSION 15" "$hd" has nlohmann-json/hedley-namespaced "JSON_HEDLEY_VERSION_ENCODE" "$hd" has nlohmann-json/hedley-undef "#undef JSON_HEDLEY_VERSION" "$(cat "$inc/thirdparty/hedley/hedley_undef.hpp" 2>/dev/null)" # --- pkg-config --------------------------------------------------------------- # # One of the two integration routes. Header-only means the whole answer is a # -I flag, and it must be the substituted absolute path rather than an unexpanded # @PLACEHOLDER@. hasfile nlohmann-json/pc-present "$pc" p=$(cat "$pc" 2>/dev/null) has nlohmann-json/pc-name "Name: nlohmann_json" "$p" has nlohmann-json/pc-description "Description: JSON for Modern C++" "$p" has nlohmann-json/pc-cflags "Cflags: -I/usr/include" "$p" pver=${p#*Version: }; pver=${pver%%[!0-9.]*} check_eq nlohmann-json/pc-version-agrees "$hver" "$pver" # --- find_package(nlohmann_json) --------------------------------------------- # # The other route, and the one that carries the header-only nature as a fact: the # imported target is INTERFACE with no IMPORTED_LOCATION, because there is no # library file to point at. A target that acquired one would mean something got # compiled and the port is not what this suite describes. hasfile nlohmann-json/cmake-config "$cm/nlohmann_jsonConfig.cmake" hasfile nlohmann-json/cmake-version "$cm/nlohmann_jsonConfigVersion.cmake" hasfile nlohmann-json/cmake-targets "$cm/nlohmann_jsonTargets.cmake" cc=$(cat "$cm/nlohmann_jsonConfig.cmake" 2>/dev/null) ct=$(cat "$cm/nlohmann_jsonTargets.cmake" 2>/dev/null) cv=$(cat "$cm/nlohmann_jsonConfigVersion.cmake" 2>/dev/null) has nlohmann-json/cmake-includes-targets "nlohmann_jsonTargets.cmake" "$cc" has nlohmann-json/cmake-interface-target "add_library(nlohmann_json::nlohmann_json INTERFACE IMPORTED)" "$ct" has nlohmann-json/cmake-cxx-std 'INTERFACE_COMPILE_FEATURES "cxx_std_11"' "$ct" has nlohmann-json/cmake-include-dirs "INTERFACE_INCLUDE_DIRECTORIES" "$ct" case "$ct" in *IMPORTED_LOCATION*) bst_notok nlohmann-json/cmake-header-only "IMPORTED_LOCATION in an INTERFACE target" ;; *) bst_ok nlohmann-json/cmake-header-only ;; esac cver=${cv#*PACKAGE_VERSION $q}; cver=${cver%%$q*} check_eq nlohmann-json/cmake-version-agrees "$hver" "$cver" # --- header-only guard -------------------------------------------------------- # # Nothing is compiled, so nothing may ship in libdir. A stray archive or shared # object here would mean the recipe built a library on an arm that has no consumer # for one -- the failure mode the -dev-only install exists to prevent. noglob_ok() { # ok iff nothing matches the glob already expanded into "$@" _id=$1; shift if [ ! -e "$1" ]; then bst_ok "$_id"; else bst_notok "$_id" "present: $1"; fi } set -- /usr/lib/libnlohmann*; noglob_ok nlohmann-json/no-library "$@" set -- /usr/lib/libnlohmann*.la; noglob_ok nlohmann-json/no-libtool "$@"