From 7b3cbfff23d601706bf8257009d1d6b8277e6165 Mon Sep 17 00:00:00 2001 From: Nikita Ofitserov Date: Sun, 23 Jul 2017 23:47:15 +0300 Subject: [PATCH 1/8] Add some tests for std::move from std::initializer_list --- test/src/unit-constructor1.cpp | 119 +++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/test/src/unit-constructor1.cpp b/test/src/unit-constructor1.cpp index 7822b634..a8f5deb8 100644 --- a/test/src/unit-constructor1.cpp +++ b/test/src/unit-constructor1.cpp @@ -1034,6 +1034,125 @@ TEST_CASE("constructors") CHECK(j.type() == json::value_t::array); } } + + SECTION("move from initializer_list") + { + SECTION("string") + { + // This should break through any short string optimization in std::string + std::string source(1024, '!'); + const char* source_addr = source.data(); + + SECTION("constructor with implicit types (array)") + { + json j = {std::move(source)}; + CHECK(j[0].get_ref().data() == source_addr); + } + + SECTION("constructor with implicit types (object)") + { + json j = {{"key", std::move(source)}}; + CHECK(j["key"].get_ref().data() == source_addr); + } + + SECTION("constructor with implicit types (object key)") + { + json j = {{std::move(source), 42}}; + CHECK(j.get_ref().begin()->first.data() == source_addr); + } + } + + SECTION("array") + { + json::array_t source = {1, 2, 3}; + const json* source_addr = source.data(); + + SECTION("constructor with implicit types (array)") + { + json j {std::move(source)}; + CHECK(j[0].get_ref().data() == source_addr); + } + + SECTION("constructor with implicit types (object)") + { + json j {{"key", std::move(source)}}; + CHECK(j["key"].get_ref().data() == source_addr); + } + + SECTION("assignment with implicit types (array)") + { + json j = {std::move(source)}; + CHECK(j[0].get_ref().data() == source_addr); + } + + SECTION("assignment with implicit types (object)") + { + json j = {{"key", std::move(source)}}; + CHECK(j["key"].get_ref().data() == source_addr); + } + } + + SECTION("object") + { + json::object_t source = {{"hello", "world"}}; + const json* source_addr = &source.at("hello"); + + SECTION("constructor with implicit types (array)") + { + json j {std::move(source)}; + CHECK(&(j[0].get_ref().at("hello")) == source_addr); + } + + SECTION("constructor with implicit types (object)") + { + json j {{"key", std::move(source)}}; + CHECK(&(j["key"].get_ref().at("hello")) == source_addr); + } + + SECTION("assignment with implicit types (array)") + { + json j = {std::move(source)}; + CHECK(&(j[0].get_ref().at("hello")) == source_addr); + } + + SECTION("assignment with implicit types (object)") + { + json j = {{"key", std::move(source)}}; + CHECK(&(j["key"].get_ref().at("hello")) == source_addr); + } + } + + SECTION("json") + { + json source {1, 2, 3}; + const json* source_addr = &source[0]; + + SECTION("constructor with implicit types (array)") + { + json j {std::move(source)}; + CHECK(&j[0][0] == source_addr); + } + + SECTION("constructor with implicit types (object)") + { + json j {{"key", std::move(source)}}; + CHECK(&j["key"][0] == source_addr); + } + + SECTION("assignment with implicit types (array)") + { + json j = {std::move(source)}; + CHECK(&j[0][0] == source_addr); + } + + SECTION("assignment with implicit types (object)") + { + json j = {{"key", std::move(source)}}; + CHECK(&j["key"][0] == source_addr); + } + } + + } } SECTION("create an array of n copies of a given value") From cf3ca3b78c09eb0c2e834d2185d4f3d36f8a6ab2 Mon Sep 17 00:00:00 2001 From: Nikita Ofitserov Date: Sun, 23 Jul 2017 23:50:59 +0300 Subject: [PATCH 2/8] Optimize json construction from rvalue string_t/array_t/object_t --- src/json.hpp | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 63f5d2b8..bca6b833 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -579,6 +579,14 @@ struct external_constructor j.m_value = s; j.assert_invariant(); } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) + { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } }; template<> @@ -628,6 +636,14 @@ struct external_constructor j.assert_invariant(); } + template + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + template::value, @@ -666,6 +682,14 @@ struct external_constructor j.assert_invariant(); } + template + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + template::value, @@ -858,6 +882,12 @@ void to_json(BasicJsonType& j, const CompatibleString& s) external_constructor::construct(j, s); } +template +void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor::construct(j, std::move(s)); +} + template::value, int> = 0> void to_json(BasicJsonType& j, FloatType val) noexcept @@ -908,13 +938,25 @@ void to_json(BasicJsonType& j, const CompatibleArrayType& arr) external_constructor::construct(j, arr); } +template +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t::value, int> = 0 > -void to_json(BasicJsonType& j, const CompatibleObjectType& arr) +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { - external_constructor::construct(j, arr); + external_constructor::construct(j, obj); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor::construct(j, std::move(obj)); } template (value); } + /// constructor for rvalue strings + json_value(string_t&& value) + { + string = create(std::move(value)); + } + /// constructor for objects json_value(const object_t& value) { object = create(value); } + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create(std::move(value)); + } + /// constructor for arrays json_value(const array_t& value) { array = create(value); } + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create(std::move(value)); + } + void destroy(value_t t) { switch (t) From 09cda573096f125877b9e6195af6f11082607bc0 Mon Sep 17 00:00:00 2001 From: Nikita Ofitserov Date: Sun, 23 Jul 2017 23:57:17 +0300 Subject: [PATCH 3/8] Support moving from rvalues in an std::initializer_list This commit works around an issue in std::initializer_list design. By using a detail::json_ref proxy with a mutable value inside, rvalue-ness of an input to list initializer is remembered and used later to move from the proxy instead of copying. --- src/json.hpp | 129 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 34 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index bca6b833..79ae9978 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -7081,6 +7081,60 @@ class serializer /// the indentation string string_t indent_string; }; + +template +struct json_ref +{ + typedef BasicJsonType value_type; + + json_ref(value_type&& value) + : value_(std::move(value)) + , is_rvalue_(true) + {} + + json_ref(const value_type& value) + : value_(value) + , is_rvalue_(false) + {} + + json_ref(std::initializer_list init) + : value_(init) + , is_rvalue_(true) + {} + + template + json_ref(Args... args) + : value_(std::forward(args)...) + , is_rvalue_(true) + {} + + value_type moved() const + { + if (is_rvalue_) + { + return std::move(value_); + } + else + { + return value_; + } + } + + value_type const& operator*() const + { + return value_; + } + + value_type const* operator->() const + { + return &value_; + } + + private: + mutable value_type value_; + bool is_rvalue_; +}; + } // namespace detail /// namespace to hold default `to_json` / `from_json` functions @@ -7583,6 +7637,7 @@ class basic_json template using json_serializer = JSONSerializer; + using initializer_list_t = std::initializer_list>; //////////////// // exceptions // @@ -8614,10 +8669,10 @@ class basic_json With the rules described above, the following JSON values cannot be expressed by an initializer list: - - the empty array (`[]`): use @ref array(std::initializer_list) + - the empty array (`[]`): use @ref array(initializer_list_t) with an empty initializer list in this case - arrays whose elements satisfy rule 2: use @ref - array(std::initializer_list) with the same initializer list + array(initializer_list_t) with the same initializer list in this case @note When used without parentheses around an empty initializer list, @ref @@ -8629,8 +8684,8 @@ class basic_json @param[in] type_deduction internal parameter; when set to `true`, the type of the JSON value is deducted from the initializer list @a init; when set to `false`, the type provided via @a manual_type is forced. This mode is - used by the functions @ref array(std::initializer_list) and - @ref object(std::initializer_list). + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). @param[in] manual_type internal parameter; when @a type_deduction is set to `false`, the created JSON value will use the provided type (only @ref @@ -8641,7 +8696,7 @@ class basic_json `value_t::object`, but @a init contains an element which is not a pair whose first element is a string. In this case, the constructor could not create an object. If @a type_deduction would have be `true`, an array - would have been created. See @ref object(std::initializer_list) + would have been created. See @ref object(initializer_list_t) for an example. @complexity Linear in the size of the initializer list @a init. @@ -8649,23 +8704,23 @@ class basic_json @liveexample{The example below shows how JSON values are created from initializer lists.,basic_json__list_init_t} - @sa @ref array(std::initializer_list) -- create a JSON array + @sa @ref array(initializer_list_t) -- create a JSON array value from an initializer list - @sa @ref object(std::initializer_list) -- create a JSON object + @sa @ref object(initializer_list_t) -- create a JSON object value from an initializer list @since version 1.0.0 */ - basic_json(std::initializer_list init, + basic_json(initializer_list_t init, bool type_deduction = true, value_t manual_type = value_t::array) { // check if each element is an array with two elements whose first // element is a string bool is_an_object = std::all_of(init.begin(), init.end(), - [](const basic_json & element) + [](const detail::json_ref& element_ref) { - return element.is_array() and element.size() == 2 and element[0].is_string(); + return element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string(); }); // adjust type if type deduction is not wanted @@ -8690,16 +8745,19 @@ class basic_json m_type = value_t::object; m_value = value_t::object; - std::for_each(init.begin(), init.end(), [this](const basic_json & element) + std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) { - m_value.object->emplace(*(element[0].m_value.string), element[1]); + basic_json element = element_ref.moved(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); }); } else { // the initializer list describes an array -> create array m_type = value_t::array; - m_value.array = create(init); + m_value.array = create(init.begin(), init.end()); } assert_invariant(); @@ -8714,7 +8772,7 @@ class basic_json @note This function is only needed to express two edge cases that cannot be realized with the initializer list constructor (@ref - basic_json(std::initializer_list, bool, value_t)). These cases + basic_json(initializer_list_t, bool, value_t)). These cases are: 1. creating an array whose elements are all pairs whose first element is a string -- in this case, the initializer list constructor would create an @@ -8732,15 +8790,14 @@ class basic_json @liveexample{The following code shows an example for the `array` function.,array} - @sa @ref basic_json(std::initializer_list, bool, value_t) -- + @sa @ref basic_json(initializer_list_t, bool, value_t) -- create a JSON value from an initializer list - @sa @ref object(std::initializer_list) -- create a JSON object + @sa @ref object(initializer_list_t) -- create a JSON object value from an initializer list @since version 1.0.0 */ - static basic_json array(std::initializer_list init = - std::initializer_list()) + static basic_json array(initializer_list_t init = {}) { return basic_json(init, false, value_t::array); } @@ -8753,10 +8810,10 @@ class basic_json the initializer list is empty, the empty object `{}` is created. @note This function is only added for symmetry reasons. In contrast to the - related function @ref array(std::initializer_list), there are + related function @ref array(initializer_list_t), there are no cases which can only be expressed by this function. That is, any initializer list @a init can also be passed to the initializer list - constructor @ref basic_json(std::initializer_list, bool, value_t). + constructor @ref basic_json(initializer_list_t, bool, value_t). @param[in] init initializer list to create an object from (optional) @@ -8764,7 +8821,7 @@ class basic_json @throw type_error.301 if @a init is not a list of pairs whose first elements are strings. In this case, no object can be created. When such a - value is passed to @ref basic_json(std::initializer_list, bool, value_t), + value is passed to @ref basic_json(initializer_list_t, bool, value_t), an array would have been created from the passed initializer list @a init. See example below. @@ -8773,15 +8830,14 @@ class basic_json @liveexample{The following code shows an example for the `object` function.,object} - @sa @ref basic_json(std::initializer_list, bool, value_t) -- + @sa @ref basic_json(initializer_list_t, bool, value_t) -- create a JSON value from an initializer list - @sa @ref array(std::initializer_list) -- create a JSON array + @sa @ref array(initializer_list_t) -- create a JSON array value from an initializer list @since version 1.0.0 */ - static basic_json object(std::initializer_list init = - std::initializer_list()) + static basic_json object(initializer_list_t init = {}) { return basic_json(init, false, value_t::object); } @@ -8953,6 +9009,11 @@ class basic_json // other constructors and destructor // /////////////////////////////////////// + basic_json(const detail::json_ref& ref) + : basic_json(ref.moved()) + { + } + /*! @brief copy constructor @@ -11755,7 +11816,7 @@ class basic_json @brief add an object to an array @copydoc push_back(basic_json&&) */ - reference operator+=(basic_json&& val) + reference operator+=(basic_json && val) { push_back(std::move(val)); return *this; @@ -11870,12 +11931,12 @@ class basic_json @liveexample{The example shows how initializer lists are treated as objects when possible.,push_back__initializer_list} */ - void push_back(std::initializer_list init) + void push_back(initializer_list_t init) { - if (is_object() and init.size() == 2 and init.begin()->is_string()) + if (is_object() and init.size() == 2 and (*init.begin())->is_string()) { - const string_t key = *init.begin(); - push_back(typename object_t::value_type(key, *(init.begin() + 1))); + basic_json&& key = init.begin()->moved(); + push_back(typename object_t::value_type(std::move(key.get_ref()), (init.begin() + 1)->moved())); } else { @@ -11885,9 +11946,9 @@ class basic_json /*! @brief add an object to an object - @copydoc push_back(std::initializer_list) + @copydoc push_back(initializer_list_t) */ - reference operator+=(std::initializer_list init) + reference operator+=(initializer_list_t init) { push_back(init); return *this; @@ -12172,7 +12233,7 @@ class basic_json @since version 1.0.0 */ - iterator insert(const_iterator pos, std::initializer_list ilist) + iterator insert(const_iterator pos, initializer_list_t ilist) { // insert only works for arrays if (not is_array()) @@ -12188,7 +12249,7 @@ class basic_json // insert to array and return iterator iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); return result; } From f5cae64e525715e715fca97d0b2634a0968dc789 Mon Sep 17 00:00:00 2001 From: Nikita Ofitserov Date: Sun, 23 Jul 2017 23:59:34 +0300 Subject: [PATCH 4/8] Update tests while fixing possible UB std::initializer_list does not own the temporaries created in its initialization. Therefore, storing it in an independent stack variable is unsafe. --- test/src/unit-constructor1.cpp | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/test/src/unit-constructor1.cpp b/test/src/unit-constructor1.cpp index a8f5deb8..446bb0b3 100644 --- a/test/src/unit-constructor1.cpp +++ b/test/src/unit-constructor1.cpp @@ -842,8 +842,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l; - json j(l); + json j(json::initializer_list_t {}); CHECK(j.type() == json::value_t::object); } @@ -860,8 +859,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(json::array_t())}; - json j(l); + json j(json::initializer_list_t {json(json::array_t())}); CHECK(j.type() == json::value_t::array); } @@ -876,8 +874,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(json::object_t())}; - json j(l); + json j(json::initializer_list_t {json(json::object_t())}); CHECK(j.type() == json::value_t::array); } @@ -892,8 +889,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json("Hello world")}; - json j(l); + json j(json::initializer_list_t {json("Hello world")}); CHECK(j.type() == json::value_t::array); } @@ -908,8 +904,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(true)}; - json j(l); + json j(json::initializer_list_t {json(true)}); CHECK(j.type() == json::value_t::array); } @@ -924,8 +919,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(1)}; - json j(l); + json j(json::initializer_list_t {json(1)}); CHECK(j.type() == json::value_t::array); } @@ -940,8 +934,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(1u)}; - json j(l); + json j(json::initializer_list_t {json(1u)}); CHECK(j.type() == json::value_t::array); } @@ -956,8 +949,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(42.23)}; - json j(l); + json j(json::initializer_list_t {json(42.23)}); CHECK(j.type() == json::value_t::array); } @@ -973,8 +965,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()}; - json j(l); + json j(json::initializer_list_t {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()}); CHECK(j.type() == json::value_t::array); } From 0f4978e503eb378e5efd3f6628ec7af7ca01a3a7 Mon Sep 17 00:00:00 2001 From: Nikita Ofitserov Date: Mon, 24 Jul 2017 01:02:03 +0300 Subject: [PATCH 5/8] Fix an actually invalid test C++ overload resolution/list initialization rules are hard. --- test/src/unit-constructor1.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/unit-constructor1.cpp b/test/src/unit-constructor1.cpp index 446bb0b3..5130e505 100644 --- a/test/src/unit-constructor1.cpp +++ b/test/src/unit-constructor1.cpp @@ -1120,7 +1120,7 @@ TEST_CASE("constructors") SECTION("constructor with implicit types (array)") { - json j {std::move(source)}; + json j {std::move(source), {}}; CHECK(&j[0][0] == source_addr); } @@ -1132,7 +1132,7 @@ TEST_CASE("constructors") SECTION("assignment with implicit types (array)") { - json j = {std::move(source)}; + json j = {std::move(source), {}}; CHECK(&j[0][0] == source_addr); } From 897879bccbf3b4f1203d0c97360874ee4479dc53 Mon Sep 17 00:00:00 2001 From: Nikita Ofitserov Date: Mon, 24 Jul 2017 12:29:06 +0300 Subject: [PATCH 6/8] Make detail::json_ref do less work by deferring moves/copies --- src/json.hpp | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 79ae9978..ab725f55 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -7088,50 +7088,55 @@ struct json_ref typedef BasicJsonType value_type; json_ref(value_type&& value) - : value_(std::move(value)) + : value_ref_(&value) , is_rvalue_(true) {} json_ref(const value_type& value) - : value_(value) + : value_ref_(const_cast(&value)) , is_rvalue_(false) {} json_ref(std::initializer_list init) - : value_(init) + : owned_value_(init) , is_rvalue_(true) - {} + { + value_ref_ = &owned_value_; + } template json_ref(Args... args) - : value_(std::forward(args)...) + : owned_value_(std::forward(args)...) , is_rvalue_(true) - {} + { + value_ref_ = &owned_value_; + } - value_type moved() const + value_type moved_or_copied() const { if (is_rvalue_) { - return std::move(value_); + return std::move(*value_ref_); } else { - return value_; + return *value_ref_; } } value_type const& operator*() const { - return value_; + return *static_cast(value_ref_); } value_type const* operator->() const { - return &value_; + return static_cast(value_ref_); } private: - mutable value_type value_; + value_type * value_ref_; + mutable value_type owned_value_; bool is_rvalue_; }; @@ -8747,7 +8752,7 @@ class basic_json std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) { - basic_json element = element_ref.moved(); + basic_json element = element_ref.moved_or_copied(); m_value.object->emplace( std::move(*((*element.m_value.array)[0].m_value.string)), std::move((*element.m_value.array)[1])); @@ -9010,7 +9015,7 @@ class basic_json /////////////////////////////////////// basic_json(const detail::json_ref& ref) - : basic_json(ref.moved()) + : basic_json(ref.moved_or_copied()) { } @@ -11935,8 +11940,9 @@ class basic_json { if (is_object() and init.size() == 2 and (*init.begin())->is_string()) { - basic_json&& key = init.begin()->moved(); - push_back(typename object_t::value_type(std::move(key.get_ref()), (init.begin() + 1)->moved())); + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); } else { From 93bb71232da8be4d30b16a3c0a3b5fd596077870 Mon Sep 17 00:00:00 2001 From: Nikita Ofitserov Date: Tue, 25 Jul 2017 12:17:32 +0300 Subject: [PATCH 7/8] Move from rvalues eagerly to work around MSVC problem On MSVC compiler, temporaries that are constructed during a list initialization, are sometimes destroyed even before calling the initializing constructor, instead of at the end of the containing full-expression. This is clearly non-conforming to [class.temporary]. As the impact of this bug is silently producing incorrect JSON values, move eagerly from rvalues to be safe. See https://stackoverflow.com/questions/24586411 --- src/json.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index ab725f55..20c2f618 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -7088,9 +7088,11 @@ struct json_ref typedef BasicJsonType value_type; json_ref(value_type&& value) - : value_ref_(&value) + : owned_value_(std::move(value)) , is_rvalue_(true) - {} + { + value_ref_ = &owned_value_; + } json_ref(const value_type& value) : value_ref_(const_cast(&value)) From f434942371c2da33dc7f1f263e5c377cb6932d13 Mon Sep 17 00:00:00 2001 From: dan-42 Date: Sat, 29 Jul 2017 11:59:09 +0200 Subject: [PATCH 8/8] REFACTOR: rewrite CMakeLists.txt for better inlcude and reuse The rewrite uses more cmake build-in automatisms and build-in generates variables to allow better generic reuse. * cmake files are installed to ``` /lib/cmake/nlohmann_json/ ``` for best support on most systems * include path is set to ``` include ``` for usage as ``` #include ``` --- CMakeLists.txt | 110 +++++++++++++++++++++++++----------------- cmake/config.cmake.in | 8 +-- 2 files changed, 68 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc660c24..cc30013b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,56 +1,78 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.0.0) -# define the project -project(nlohmann_json VERSION 2.1.1 LANGUAGES CXX) +## +## PROJECT +## name and version +## +project(nlohmann_json VERSION 2.1.1) +## +## OPTIONS +## option(JSON_BuildTests "Build the unit tests" ON) -# define project variables -set(JSON_TARGET_NAME ${PROJECT_NAME}) -set(JSON_PACKAGE_NAME ${JSON_TARGET_NAME}) -set(JSON_TARGETS_FILENAME "${JSON_PACKAGE_NAME}Targets.cmake") -set(JSON_CONFIG_FILENAME "${JSON_PACKAGE_NAME}Config.cmake") -set(JSON_CONFIGVERSION_FILENAME "${JSON_PACKAGE_NAME}ConfigVersion.cmake") -set(JSON_CONFIG_DESTINATION "cmake") -set(JSON_INCLUDE_DESTINATION "include/nlohmann") +## +## CONFIGURATION +## +set(NLOHMANN_JSON_TARGET_NAME ${PROJECT_NAME}) +set(NLOHMANN_JSON_SOURCE_DIR "src/") +set(NLOHMANN_JSON_CONFIG_INSTALL_DIR "lib/cmake/${PROJECT_NAME}") +set(NLOHMANN_JSON_INCLUDE_INSTALL_DIR "include") +set(NLOHMANN_JSON_HEADER_INSTALL_DIR "${NLOHMANN_JSON_INCLUDE_INSTALL_DIR}/nlohmann") +set(NLOHMANN_JSON_TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") +set(NLOHMANN_JSON_CMAKE_CONFIG_TEMPLATE "cmake/config.cmake.in") +set(NLOHMANN_JSON_CMAKE_CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake_config") +set(NLOHMANN_JSON_CMAKE_VERSION_CONFIG_FILE "${NLOHMANN_JSON_CMAKE_CONFIG_DIR}/${PROJECT_NAME}ConfigVersion.cmake") +set(NLOHMANN_JSON_CMAKE_PROJECT_CONFIG_FILE "${NLOHMANN_JSON_CMAKE_CONFIG_DIR}/${PROJECT_NAME}Config.cmake") -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +## +## TARGET +## create target and add include path +## +add_library(${NLOHMANN_JSON_TARGET_NAME} INTERFACE) -# create and configure the library target -add_library(${JSON_TARGET_NAME} INTERFACE) -target_include_directories(${JSON_TARGET_NAME} INTERFACE - $ - $) - -# create and configure the unit test target +target_include_directories( + ${NLOHMANN_JSON_TARGET_NAME} + INTERFACE $ +) + +## +## TESTS +## create and configure the unit test target +## if(JSON_BuildTests) enable_testing() + include_directories(${NLOHMANN_JSON_SOURCE_DIR}) add_subdirectory(test) endif() -# generate a config and config version file for the package +## +## INSTALL +## install header files, generate and install cmake config files for find_package() +## include(CMakePackageConfigHelpers) -configure_package_config_file("cmake/config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIG_FILENAME}" - INSTALL_DESTINATION ${JSON_CONFIG_DESTINATION} - PATH_VARS JSON_INCLUDE_DESTINATION) -write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIGVERSION_FILENAME}" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion) - -# export the library target and store build directory in package registry -export(TARGETS ${JSON_TARGET_NAME} - FILE "${CMAKE_CURRENT_BINARY_DIR}/${JSON_TARGETS_FILENAME}") -export(PACKAGE ${JSON_PACKAGE_NAME}) - -# install library target and config files -install(TARGETS ${JSON_TARGET_NAME} - EXPORT ${JSON_PACKAGE_NAME}) -install(FILES "src/json.hpp" - DESTINATION ${JSON_INCLUDE_DESTINATION}) -install(EXPORT ${JSON_PACKAGE_NAME} - FILE ${JSON_TARGETS_FILENAME} - DESTINATION ${JSON_CONFIG_DESTINATION}) -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIG_FILENAME}" - "${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIGVERSION_FILENAME}" - DESTINATION ${JSON_CONFIG_DESTINATION}) +write_basic_package_version_file( + ${NLOHMANN_JSON_CMAKE_VERSION_CONFIG_FILE} COMPATIBILITY SameMajorVersion +) +configure_package_config_file( + ${NLOHMANN_JSON_CMAKE_CONFIG_TEMPLATE} + ${NLOHMANN_JSON_CMAKE_PROJECT_CONFIG_FILE} + INSTALL_DESTINATION ${NLOHMANN_JSON_CONFIG_INSTALL_DIR} +) +install( + DIRECTORY ${NLOHMANN_JSON_SOURCE_DIR} + DESTINATION ${NLOHMANN_JSON_HEADER_INSTALL_DIR} +) +install( + FILES ${NLOHMANN_JSON_CMAKE_PROJECT_CONFIG_FILE} ${NLOHMANN_JSON_CMAKE_VERSION_CONFIG_FILE} + DESTINATION ${NLOHMANN_JSON_CONFIG_INSTALL_DIR} +) +install( + TARGETS ${NLOHMANN_JSON_TARGET_NAME} + EXPORT ${NLOHMANN_JSON_TARGETS_EXPORT_NAME} + INCLUDES DESTINATION ${NLOHMANN_JSON_INCLUDE_INSTALL_DIR} +) +install( + EXPORT ${NLOHMANN_JSON_TARGETS_EXPORT_NAME} + DESTINATION ${NLOHMANN_JSON_CONFIG_INSTALL_DIR} +) diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in index a1e9488d..b4fd29d9 100644 --- a/cmake/config.cmake.in +++ b/cmake/config.cmake.in @@ -1,7 +1,3 @@ @PACKAGE_INIT@ -set_and_check(JSON_INCLUDE_DIR "@PACKAGE_JSON_INCLUDE_DESTINATION@") - -cmake_policy(PUSH) -cmake_policy(SET CMP0024 OLD) -include(${CMAKE_CURRENT_LIST_DIR}/@JSON_TARGETS_FILENAME@) -cmake_policy(POP) +include("${CMAKE_CURRENT_LIST_DIR}/@NLOHMANN_JSON_TARGETS_EXPORT_NAME@.cmake") +check_required_components("@PROJECT_NAME@")