diff --git a/.gitmodules b/.gitmodules index 6c29f7ec..081a123b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "optional"] path = third_party/tl_optional url = https://github.com/TartanLlama/optional.git +[submodule "json"] + path = third_party/json + url = https://github.com/nlohmann/json.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 76d263e8..ec6ce113 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,8 @@ include(cmake/tool/doxygen.cmake) # Libraries include(cmake/target/flatbuffers.cmake) +include(cmake/target/json.cmake) + include(cmake/target/fmt.cmake) include(cmake/target/spdlog.cmake) include(cmake/target/cli11.cmake) diff --git a/README.md b/README.md index 8feeb09b..725ca12c 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Build C, C++ and ASM files in C++ - `C++11 thread` library support - Third Party Libraries (See License below) - Flatbuffers v2.0.0 + - Nlohmann::Json v3.11.2 - Taskflow v3.1.0 - CLI11 v2.1.0 - Tiny Process Library v2.0.4 @@ -176,6 +177,7 @@ _BuildCC_ is licensed under the Apache License, Version 2.0. See [LICENSE](LICEN - [Tiny Process Library](https://gitlab.com/eidheim/tiny-process-library) (Process handling) [MIT License] - [Taskflow](https://github.com/taskflow/taskflow) (Parallel Programming) [MIT License] [Header Only] - See also [3rd-Party](https://github.com/taskflow/taskflow/tree/master/3rd-party) used by Taskflow +- [Nlohmann::Json](https://github.com/nlohmann/json) (JSON Serialization) [MIT License] [Header Only] - [Flatbuffers](https://github.com/google/flatbuffers) (Serialization) [Apache-2.0 License] - [CLI11](https://github.com/CLIUtils/CLI11) (Argument Parsing) [BSD-3-Clause License] [Header Only] - [CppUTest](https://github.com/cpputest/cpputest) (Unit Testing/Mocking) [BSD-3-Clause License] diff --git a/bootstrap/CMakeLists.txt b/bootstrap/CMakeLists.txt index f07f53d9..3c50e1b8 100644 --- a/bootstrap/CMakeLists.txt +++ b/bootstrap/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable(buildcc_lib_bootstrap ) target_sources(buildcc_lib_bootstrap PRIVATE src/build_flatbuffers.cpp + src/build_nlohmann_json.cpp src/build_cli11.cpp src/build_fmtlib.cpp src/build_spdlog.cpp diff --git a/bootstrap/include/bootstrap/build_buildcc.h b/bootstrap/include/bootstrap/build_buildcc.h index a4026f7a..e8fb8110 100644 --- a/bootstrap/include/bootstrap/build_buildcc.h +++ b/bootstrap/include/bootstrap/build_buildcc.h @@ -22,6 +22,7 @@ #include "build_cli11.h" #include "build_flatbuffers.h" #include "build_fmtlib.h" +#include "build_nlohmann_json.h" #include "build_spdlog.h" #include "build_taskflow.h" #include "build_tl_optional.h" @@ -32,7 +33,8 @@ namespace buildcc { void schema_gen_cb(FileGenerator &generator, const BaseTarget &flatc_exe); void buildcc_cb(BaseTarget &target, const FileGenerator &schema_gen, - const TargetInfo &flatbuffers_ho, const TargetInfo &fmt_ho, + const TargetInfo &flatbuffers_ho, + const TargetInfo &nlohmann_json_ho, const TargetInfo &fmt_ho, const TargetInfo &spdlog_ho, const TargetInfo &cli11_ho, const TargetInfo &taskflow_ho, const TargetInfo &tl_optional_ho, const BaseTarget &tpl); @@ -45,6 +47,7 @@ class BuildBuildCC { public: // TargetInfo / Header Only static constexpr const char *const kFlatbuffersHoName = "flatbuffers_ho"; + static constexpr const char *const kNlohmannJsonHoName = "nlohmann_json_ho"; static constexpr const char *const kCli11HoName = "cli11_ho"; static constexpr const char *const kFmtHoName = "fmtlib_ho"; static constexpr const char *const kSpdlogHoName = "spdlog_ho"; @@ -89,6 +92,9 @@ class BuildBuildCC { TargetInfo &GetFlatbuffersHo() { return storage_.Ref(kFlatbuffersHoName); } + TargetInfo &GetNlohmannJsonHo() { + return storage_.Ref(kNlohmannJsonHoName); + } TargetInfo &GetCli11Ho() { return storage_.Ref(kCli11HoName); } TargetInfo &GetFmtHo() { return storage_.Ref(kFmtHoName); } TargetInfo &GetSpdlogHo() { return storage_.Ref(kSpdlogHoName); } diff --git a/bootstrap/include/bootstrap/build_nlohmann_json.h b/bootstrap/include/bootstrap/build_nlohmann_json.h new file mode 100644 index 00000000..135275f0 --- /dev/null +++ b/bootstrap/include/bootstrap/build_nlohmann_json.h @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BOOTSTRAP_BUILD_NLOHMANN_JSON_H_ +#define BOOTSTRAP_BUILD_NLOHMANN_JSON_H_ + +#include "buildcc.h" + +namespace buildcc { + +void nlohmann_json_ho_cb(TargetInfo &info); + +} // namespace buildcc + +#endif diff --git a/bootstrap/src/build_buildcc.cpp b/bootstrap/src/build_buildcc.cpp index 1e70b401..8e72841d 100644 --- a/bootstrap/src/build_buildcc.cpp +++ b/bootstrap/src/build_buildcc.cpp @@ -20,16 +20,12 @@ namespace buildcc { void schema_gen_cb(FileGenerator &generator, const BaseTarget &flatc_exe) { generator.AddPattern("path_fbs", "{current_root_dir}/path.fbs"); - generator.AddPattern("custom_generator_fbs", - "{current_root_dir}/custom_generator.fbs"); generator.AddPattern("target_fbs", "{current_root_dir}/target.fbs"); generator.AddInput("{path_fbs}"); - generator.AddInput("{custom_generator_fbs}"); generator.AddInput("{target_fbs}"); generator.AddOutput("{current_build_dir}/path_generated.h"); - generator.AddOutput("{current_build_dir}/custom_generator_generated.h"); generator.AddOutput("{current_build_dir}/target_generated.h"); generator.AddPatterns({ @@ -38,13 +34,14 @@ void schema_gen_cb(FileGenerator &generator, const BaseTarget &flatc_exe) { // generator.AddCommand("{flatc_compiler} --help"); generator.AddCommand("{flatc_compiler} -o {current_build_dir} -I " "{current_root_dir} --gen-object-api " - "--cpp {path_fbs} {custom_generator_fbs} {target_fbs}"); + "--cpp {path_fbs} {target_fbs}"); generator.Build(); } void buildcc_cb(BaseTarget &target, const FileGenerator &schema_gen, - const TargetInfo &flatbuffers_ho, const TargetInfo &fmt_ho, + const TargetInfo &flatbuffers_ho, + const TargetInfo &nlohmann_json_ho, const TargetInfo &fmt_ho, const TargetInfo &spdlog_ho, const TargetInfo &cli11_ho, const TargetInfo &taskflow_ho, const TargetInfo &tl_optional_ho, const BaseTarget &tpl) { @@ -119,6 +116,9 @@ void buildcc_cb(BaseTarget &target, const FileGenerator &schema_gen, // FLATBUFFERS HO target.Insert(flatbuffers_ho, kInsertOptions); + // NLOHMANN JSON HO + target.Insert(nlohmann_json_ho, kInsertOptions); + // FMT HO target.Insert(fmt_ho, kInsertOptions); @@ -217,6 +217,12 @@ void BuildBuildCC::Initialize() { TargetEnv(env_.GetTargetRootDir() / "third_party" / "flatbuffers", env_.GetTargetBuildDir())); + // Nlohmann json HO lib + (void)storage_.Add( + kNlohmannJsonHoName, toolchain_, + TargetEnv(env_.GetTargetRootDir() / "third_party" / "json", + env_.GetTargetBuildDir())); + // CLI11 HO lib (void)storage_.Add( kCli11HoName, toolchain_, @@ -268,6 +274,7 @@ void BuildBuildCC::Setup(const ArgToolchainState &state) { auto &flatc_exe = GetFlatc(); auto &schema_gen = GetSchemaGen(); auto &flatbuffers_ho_lib = GetFlatbuffersHo(); + auto &nlohmann_json_ho_lib = GetNlohmannJsonHo(); auto &cli11_ho_lib = GetCli11Ho(); auto &fmt_ho_lib = GetFmtHo(); auto &spdlog_ho_lib = GetSpdlogHo(); @@ -281,6 +288,7 @@ void BuildBuildCC::Setup(const ArgToolchainState &state) { .Build(schema_gen_cb, schema_gen, flatc_exe) .Dep(schema_gen, flatc_exe) .Func(flatbuffers_ho_cb, flatbuffers_ho_lib) + .Func(nlohmann_json_ho_cb, nlohmann_json_ho_lib) .Func(cli11_ho_cb, cli11_ho_lib) .Func(fmt_ho_cb, fmt_ho_lib) .Func(spdlog_ho_cb, spdlog_ho_lib) @@ -290,8 +298,8 @@ void BuildBuildCC::Setup(const ArgToolchainState &state) { .Build(tpl_cb, tpl_lib) .Func(global_flags_cb, buildcc_lib, toolchain_) .Build(buildcc_cb, buildcc_lib, schema_gen, flatbuffers_ho_lib, - fmt_ho_lib, spdlog_ho_lib, cli11_ho_lib, taskflow_ho_lib, - tl_optional_ho_lib, tpl_lib) + nlohmann_json_ho_lib, fmt_ho_lib, spdlog_ho_lib, cli11_ho_lib, + taskflow_ho_lib, tl_optional_ho_lib, tpl_lib) .Dep(buildcc_lib, schema_gen) .Dep(buildcc_lib, tpl_lib); } diff --git a/bootstrap/src/build_nlohmann_json.cpp b/bootstrap/src/build_nlohmann_json.cpp new file mode 100644 index 00000000..7bf5ce17 --- /dev/null +++ b/bootstrap/src/build_nlohmann_json.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootstrap/build_nlohmann_json.h" + +namespace buildcc { + +void nlohmann_json_ho_cb(TargetInfo &info) { + info.AddIncludeDir("include"); + info.GlobHeaders("include/nlohmann"); + info.GlobHeaders("include/nlohmann/thirdparty/hedley"); + info.GlobHeaders("include/nlohmann/detail"); + info.GlobHeaders("include/nlohmann/detail/conversions"); + info.GlobHeaders("include/nlohmann/detail/input"); + info.GlobHeaders("include/nlohmann/detail/iterators"); + info.GlobHeaders("include/nlohmann/detail/meta"); + info.GlobHeaders("include/nlohmann/detail/meta/call_std"); + info.GlobHeaders("include/nlohmann/detail/output"); +} + +} // namespace buildcc diff --git a/buildcc/CMakeLists.txt b/buildcc/CMakeLists.txt index a034aaef..eede4309 100644 --- a/buildcc/CMakeLists.txt +++ b/buildcc/CMakeLists.txt @@ -14,6 +14,7 @@ if(${BUILDCC_BUILD_AS_SINGLE_LIB}) fmt::fmt tl::optional flatbuffers + nlohmann_json::nlohmann_json Taskflow CLI11::CLI11 ) diff --git a/buildcc/lib/target/include/target/custom_generator.h b/buildcc/lib/target/include/target/custom_generator.h index 327cd914..c7558757 100644 --- a/buildcc/lib/target/include/target/custom_generator.h +++ b/buildcc/lib/target/include/target/custom_generator.h @@ -86,7 +86,7 @@ class CustomBlobHandler { virtual std::vector Serialize() const = 0; }; -struct UserGenInfo : internal::GenInfo { +struct UserGenInfo : internal::CustomGeneratorSchema::IdInfo { fs_unordered_set inputs; GenerateCb generate_cb; std::shared_ptr blob_handler{nullptr}; @@ -99,7 +99,7 @@ struct UserCustomGeneratorSchema : public internal::CustomGeneratorSchema { for (auto &[id, gen_info] : gen_info_map) { gen_info.internal_inputs = path_schema_convert( gen_info.inputs, internal::Path::CreateExistingPath); - auto [_, success] = internal_gen_info_map.try_emplace(id, gen_info); + auto [_, success] = internal_ids.try_emplace(id, gen_info); env::assert_fatal(success, fmt::format("Could not save {}", id)); } } diff --git a/buildcc/lib/target/src/custom_generator/custom_generator.cpp b/buildcc/lib/target/src/custom_generator/custom_generator.cpp index 7ab3065a..6e466b1e 100644 --- a/buildcc/lib/target/src/custom_generator/custom_generator.cpp +++ b/buildcc/lib/target/src/custom_generator/custom_generator.cpp @@ -136,10 +136,9 @@ void CustomGenerator::BuildGenerate( [&](const auto &iter) { gen_selected_ids.insert(iter.first); }); dirty_ = true; } else { - // DONE, Conditionally select internal_gen_info_map depending on what has + // DONE, Conditionally select internal_ids depending on what has // changed - const auto &prev_gen_info_map = - serialization_.GetLoad().internal_gen_info_map; + const auto &prev_gen_info_map = serialization_.GetLoad().internal_ids; const auto &curr_gen_info_map = user_.gen_info_map; // DONE, MAP REMOVED condition Check if prev_gen_info_map exists in @@ -293,8 +292,7 @@ void CustomGenerator::TaskRunner(bool run, const std::string &id) { if (run) { rerun = true; } else { - const auto &previous_info = - serialization_.GetLoad().internal_gen_info_map.at(id); + const auto &previous_info = serialization_.GetLoad().internal_ids.at(id); rerun = internal::CheckPaths(previous_info.internal_inputs, current_info.internal_inputs) != internal::PathState::kNoChange || diff --git a/buildcc/lib/target/test/target/test_custom_generator.cpp b/buildcc/lib/target/test/target/test_custom_generator.cpp index 3c919d30..0703046d 100644 --- a/buildcc/lib/target/test/target/test_custom_generator.cpp +++ b/buildcc/lib/target/test/target/test_custom_generator.cpp @@ -51,7 +51,7 @@ TEST(CustomGeneratorTestGroup, Basic) { cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + const auto &internal_map = serialization.GetLoad().internal_ids; CHECK_EQUAL(internal_map.size(), 2); const auto &id1_info = internal_map.at("id1"); CHECK_EQUAL(id1_info.internal_inputs.size(), 1); @@ -82,7 +82,7 @@ TEST(CustomGeneratorTestGroup, Basic_Failure) { cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + const auto &internal_map = serialization.GetLoad().internal_ids; CHECK_EQUAL(internal_map.size(), 1); } @@ -105,7 +105,7 @@ TEST(CustomGeneratorTestGroup, Basic_Group) { cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + const auto &internal_map = serialization.GetLoad().internal_ids; CHECK_EQUAL(internal_map.size(), 2); const auto &id1_info = internal_map.at("id1"); CHECK_EQUAL(id1_info.internal_inputs.size(), 1); @@ -138,7 +138,7 @@ TEST(CustomGeneratorTestGroup, Basic_Group_Dependency) { cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + const auto &internal_map = serialization.GetLoad().internal_ids; CHECK_EQUAL(internal_map.size(), 2); const auto &id1_info = internal_map.at("id1"); CHECK_EQUAL(id1_info.internal_inputs.size(), 1); @@ -170,7 +170,7 @@ TEST(CustomGeneratorTestGroup, Basic_Group_DependencyFailure) { cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + const auto &internal_map = serialization.GetLoad().internal_ids; CHECK_EQUAL(internal_map.size(), 0); } @@ -207,7 +207,7 @@ TEST(CustomGeneratorTestGroup, Basic_Group_DependencyFailure2) { cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + const auto &internal_map = serialization.GetLoad().internal_ids; CHECK_EQUAL(internal_map.size(), 0); } @@ -279,7 +279,7 @@ TEST(CustomGeneratorTestGroup, Basic_ProperDependency_GoodCase) { cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + const auto &internal_map = serialization.GetLoad().internal_ids; CHECK_EQUAL(internal_map.size(), 2); } @@ -308,7 +308,7 @@ TEST(CustomGeneratorTestGroup, Basic_ProperDependency_BadCase) { cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + const auto &internal_map = serialization.GetLoad().internal_ids; CHECK_EQUAL(internal_map.size(), 0); } @@ -337,7 +337,7 @@ TEST(CustomGeneratorTestGroup, DefaultArgumentUsage) { cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + const auto &internal_map = serialization.GetLoad().internal_ids; CHECK_EQUAL(internal_map.size(), 2); const auto &id1_info = internal_map.at("id1"); CHECK_EQUAL(id1_info.internal_inputs.size(), 1); @@ -437,7 +437,7 @@ TEST(CustomGeneratorTestGroup, AddDependency_BasicCheck) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); } } @@ -475,7 +475,7 @@ TEST(CustomGeneratorTestGroup, AddDependency_FileDep) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); } } @@ -504,7 +504,7 @@ TEST(CustomGeneratorTestGroup, AddDependency_FileDep_WithRebuild) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); } @@ -524,7 +524,7 @@ TEST(CustomGeneratorTestGroup, AddDependency_FileDep_WithRebuild) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); } @@ -548,7 +548,7 @@ TEST(CustomGeneratorTestGroup, AddDependency_FileDep_WithRebuild) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 0); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 0); CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); } @@ -576,7 +576,7 @@ TEST(CustomGeneratorTestGroup, AddDependency_FileDep_WithRebuild) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); } @@ -606,7 +606,7 @@ TEST(CustomGeneratorTestGroup, AddDependency_FileDep_WithRebuild) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); } @@ -639,7 +639,7 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Basic) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); fs::remove_all(cgen.GetBinaryPath()); } @@ -666,7 +666,7 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Basic) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 0); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 0); fs::remove_all(cgen.GetBinaryPath()); } @@ -693,8 +693,8 @@ TEST(CustomGeneratorTestGroup, RealGenerate_RemoveAndAdd) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); - auto imap = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + auto imap = serialization.GetLoad().internal_ids; CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); @@ -716,8 +716,8 @@ TEST(CustomGeneratorTestGroup, RealGenerate_RemoveAndAdd) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); - auto imap = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + auto imap = serialization.GetLoad().internal_ids; CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); @@ -739,8 +739,8 @@ TEST(CustomGeneratorTestGroup, RealGenerate_RemoveAndAdd) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 1); - auto imap = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 1); + auto imap = serialization.GetLoad().internal_ids; CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); CHECK_EQUAL(imap.at("id1").outputs.size(), 1); @@ -767,8 +767,8 @@ TEST(CustomGeneratorTestGroup, RealGenerate_RemoveAndAdd) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 1); - auto imap = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 1); + auto imap = serialization.GetLoad().internal_ids; CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); CHECK_EQUAL(imap.at("id1").outputs.size(), 1); CHECK_THROWS(std::out_of_range, imap.at("id2")); @@ -794,8 +794,8 @@ TEST(CustomGeneratorTestGroup, RealGenerate_RemoveAndAdd) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); - auto imap = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + auto imap = serialization.GetLoad().internal_ids; CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); @@ -832,8 +832,8 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Update_Failure) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); - auto imap = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + auto imap = serialization.GetLoad().internal_ids; CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); @@ -866,8 +866,8 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Update_Failure) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 1); - auto imap = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 1); + auto imap = serialization.GetLoad().internal_ids; CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); CHECK_EQUAL(imap.at("id1").outputs.size(), 1); @@ -902,8 +902,8 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Update_Success) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); - auto imap = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + auto imap = serialization.GetLoad().internal_ids; CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); @@ -938,8 +938,8 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Update_Success) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); - auto imap = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + auto imap = serialization.GetLoad().internal_ids; CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); CHECK_EQUAL(imap.at("id1").outputs.size(), 1); @@ -972,8 +972,8 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Update_Success) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 2); - auto imap = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 2); + auto imap = serialization.GetLoad().internal_ids; CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); CHECK_EQUAL(imap.at("id1").outputs.size(), 1); @@ -1031,7 +1031,7 @@ TEST(CustomGeneratorTestGroup, RealGenerate_BasicBlobRecheck) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 1); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 1); } // Rebuild @@ -1049,7 +1049,7 @@ TEST(CustomGeneratorTestGroup, RealGenerate_BasicBlobRecheck) { buildcc::internal::CustomGeneratorSerialization serialization( cgen.GetBinaryPath()); CHECK_TRUE(serialization.LoadFromFile()); - CHECK_EQUAL(serialization.GetLoad().internal_gen_info_map.size(), 1); + CHECK_EQUAL(serialization.GetLoad().internal_ids.size(), 1); } buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); diff --git a/buildcc/schema/cmake/schema.cmake b/buildcc/schema/cmake/schema.cmake index 207c6abc..a2c8c710 100644 --- a/buildcc/schema/cmake/schema.cmake +++ b/buildcc/schema/cmake/schema.cmake @@ -20,6 +20,7 @@ if (${TESTING}) target_link_libraries(mock_schema PUBLIC mock_env flatbuffers + nlohmann_json::nlohmann_json CppUTest CppUTestExt @@ -80,6 +81,7 @@ if(${BUILDCC_BUILD_AS_INTERFACE}) target_link_libraries(schema PUBLIC env flatbuffers + nlohmann_json::nlohmann_json ) target_include_directories(schema PRIVATE ${SCHEMA_BUILD_DIR} diff --git a/buildcc/schema/cmake/schema_generate.cmake b/buildcc/schema/cmake/schema_generate.cmake index 9b6eadf5..c1a6b604 100644 --- a/buildcc/schema/cmake/schema_generate.cmake +++ b/buildcc/schema/cmake/schema_generate.cmake @@ -3,12 +3,10 @@ set(SCHEMA_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated CACHE PATH "Generate set(FBS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/path.fbs - ${CMAKE_CURRENT_SOURCE_DIR}/custom_generator.fbs ${CMAKE_CURRENT_SOURCE_DIR}/target.fbs ) set(FBS_GEN_FILES ${SCHEMA_BUILD_DIR}/path_generated.h - ${SCHEMA_BUILD_DIR}/custom_generator_generated.h ${SCHEMA_BUILD_DIR}/target_generated.h ) set(FBS_GEN_OPTIONS diff --git a/buildcc/schema/custom_generator.fbs b/buildcc/schema/custom_generator.fbs deleted file mode 100644 index 3c23fd75..00000000 --- a/buildcc/schema/custom_generator.fbs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2021-2022 Niket Naidu. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -include "path.fbs"; - -namespace schema.internal; - -// Relational input and output files -table GenInfo { - id:string (key); - inputs:[Path]; - outputs:[string]; - userblob:[ubyte]; -} - -// Custom generator regenerate conditions -table CustomGenerator { - name:string (key); - info:[GenInfo]; -} - -root_type CustomGenerator; diff --git a/buildcc/schema/include/schema/custom_generator_serialization.h b/buildcc/schema/include/schema/custom_generator_serialization.h index 5c51746e..13f66ba0 100644 --- a/buildcc/schema/include/schema/custom_generator_serialization.h +++ b/buildcc/schema/include/schema/custom_generator_serialization.h @@ -27,20 +27,67 @@ namespace buildcc::internal { -struct GenInfo { - path_unordered_set internal_inputs; - fs_unordered_set outputs; - std::vector userblob; -}; - struct CustomGeneratorSchema { +private: + static constexpr const char *const kSchemaName = "name"; + static constexpr const char *const kIdsName = "ids"; + static constexpr const char *const kGroupsName = "groups"; + +public: + using IdKey = std::string; + using GroupKey = std::string; + struct IdInfo { + private: + static constexpr const char *const kInputsName = "inputs"; + static constexpr const char *const kOutputsName = "outputs"; + static constexpr const char *const kUserblobName = "userblob"; + + public: + path_unordered_set internal_inputs; + fs_unordered_set outputs; + std::vector userblob; + + friend void to_json(json &j, const IdInfo &info) { + j = json{ + {kInputsName, info.internal_inputs}, + {kOutputsName, info.outputs}, + {kUserblobName, info.userblob}, + }; + } + + friend void from_json(const json &j, IdInfo &info) { + j.at(kInputsName).get_to(info.internal_inputs); + j.at(kOutputsName).get_to(info.outputs); + j.at(kUserblobName).get_to(info.userblob); + } + }; + + using IdPair = std::pair; + using GroupInfo = std::unordered_set; + using GroupPair = std::pair; + std::string name; - std::unordered_map internal_gen_info_map; + std::unordered_map internal_ids; + std::unordered_map internal_groups; + + friend void to_json(json &j, const CustomGeneratorSchema &schema) { + j = json{ + {kSchemaName, schema.name}, + {kIdsName, schema.internal_ids}, + {kGroupsName, schema.internal_groups}, + }; + } + + friend void from_json(const json &j, CustomGeneratorSchema &schema) { + j.at(kSchemaName).get_to(schema.name); + j.at(kIdsName).get_to(schema.internal_ids); + j.at(kGroupsName).get_to(schema.internal_groups); + } }; class CustomGeneratorSerialization : public SerializationInterface { public: - CustomGeneratorSerialization(const fs::path &serialized_file) + explicit CustomGeneratorSerialization(const fs::path &serialized_file) : SerializationInterface(serialized_file) {} void UpdateStore(const CustomGeneratorSchema &store) { store_ = store; } diff --git a/buildcc/schema/include/schema/interface/serialization_interface.h b/buildcc/schema/include/schema/interface/serialization_interface.h index df30f978..c09bdde2 100644 --- a/buildcc/schema/include/schema/interface/serialization_interface.h +++ b/buildcc/schema/include/schema/interface/serialization_interface.h @@ -30,9 +30,9 @@ namespace buildcc::internal { class SerializationInterface { public: - SerializationInterface(const fs::path &serialized_file) + explicit SerializationInterface(const fs::path &serialized_file) : serialized_file_(serialized_file) {} - virtual ~SerializationInterface() {} + virtual ~SerializationInterface() = default; bool LoadFromFile() { std::string buffer; diff --git a/buildcc/schema/include/schema/path.h b/buildcc/schema/include/schema/path.h index a98c3d2f..12406600 100644 --- a/buildcc/schema/include/schema/path.h +++ b/buildcc/schema/include/schema/path.h @@ -27,13 +27,25 @@ // Third party #include "fmt/format.h" +#include "nlohmann/json.hpp" namespace fs = std::filesystem; +using json = nlohmann::ordered_json; namespace buildcc::internal { class Path { +private: + static constexpr const char *const kPathName = "path"; + static constexpr const char *const kHashName = "hash"; + public: + Path() = default; + explicit Path(const fs::path &pathname, std::uint64_t last_write_timestamp) + : pathname_(pathname), last_write_timestamp_(last_write_timestamp) { + pathname_.lexically_normal().make_preferred(); + } + /** * @brief Create a Existing Path object and sets last_write_timstamp to file * timestamp @@ -103,12 +115,21 @@ class Path { return GetPathname() == pathname; } -private: - explicit Path(const fs::path &pathname, std::uint64_t last_write_timestamp) - : pathname_(pathname), last_write_timestamp_(last_write_timestamp) { - pathname_.lexically_normal().make_preferred(); + // JSON specialization + + friend void to_json(json &j, const Path &p) { + j = json{ + {kPathName, p.pathname_}, + {kHashName, p.last_write_timestamp_}, + }; } + friend void from_json(const json &j, Path &p) { + j.at(kPathName).get_to(p.pathname_); + j.at(kHashName).get_to(p.last_write_timestamp_); + } + +private: std::string Quote(const std::string &str) const { if (str.find(" ") == std::string::npos) { return str; diff --git a/buildcc/schema/src/custom_generator_serialization.cpp b/buildcc/schema/src/custom_generator_serialization.cpp index e6b8e119..8cc22aa6 100644 --- a/buildcc/schema/src/custom_generator_serialization.cpp +++ b/buildcc/schema/src/custom_generator_serialization.cpp @@ -17,71 +17,35 @@ #include "schema/custom_generator_serialization.h" // Third party -#include "flatbuffers/flatbuffers.h" - -// Private -#include "schema/private/schema_util.h" - -// Schema generated -#include "custom_generator_generated.h" +#include "nlohmann/json.hpp" namespace buildcc::internal { // PRIVATE bool CustomGeneratorSerialization::Verify(const std::string &serialized_data) { - flatbuffers::Verifier verifier((const uint8_t *)serialized_data.c_str(), - serialized_data.length()); - return fbs::VerifyCustomGeneratorBuffer(verifier); + (void)serialized_data; + return true; } bool CustomGeneratorSerialization::Load(const std::string &serialized_data) { - const auto *custom_generator = - fbs::GetCustomGenerator((const void *)serialized_data.c_str()); - // Verified, does not need a nullptr check - - const auto *fbs_gen_info = custom_generator->info(); - if (fbs_gen_info == nullptr) { - return false; - } - - // gen_info->id is a required parameter, hence gen_info cannot be nullptr - for (const auto *gen_info : *fbs_gen_info) { - GenInfo current_info; - extract_path(gen_info->inputs(), current_info.internal_inputs); - extract(gen_info->outputs(), current_info.outputs); - extract(gen_info->userblob(), current_info.userblob); - load_.internal_gen_info_map.emplace(gen_info->id()->c_str(), - std::move(current_info)); + bool is_loaded = true; + try { + json j = json::parse(serialized_data, nullptr, true, true); + load_ = j.get(); + } catch (const std::exception &e) { + env::log_critical(__FUNCTION__, e.what()); + is_loaded = false; } - return true; + return is_loaded; } bool CustomGeneratorSerialization::Store( const fs::path &absolute_serialized_file) { - flatbuffers::FlatBufferBuilder builder; - - std::vector> fbs_gen_info; - for (const auto &gen_info_iter : store_.internal_gen_info_map) { - const auto &id = gen_info_iter.first; - const auto &gen_info = gen_info_iter.second; - auto fbs_internal_inputs = - internal::create_fbs_vector_path(builder, gen_info.internal_inputs); - auto fbs_outputs = - internal::create_fbs_vector_string(builder, gen_info.outputs); - auto fbs_current_info = - fbs::CreateGenInfoDirect(builder, id.c_str(), &fbs_internal_inputs, - &fbs_outputs, &gen_info.userblob); - fbs_gen_info.push_back(fbs_current_info); - } - - auto fbs_generator = fbs::CreateCustomGeneratorDirect( - builder, store_.name.c_str(), &fbs_gen_info); - fbs::FinishCustomGeneratorBuffer(builder, fbs_generator); - - return env::save_file(path_as_string(absolute_serialized_file).c_str(), - (const char *)builder.GetBufferPointer(), - builder.GetSize(), true); + json j = store_; + auto data = j.dump(4); + return env::save_file(path_as_string(absolute_serialized_file).c_str(), data, + false); } } // namespace buildcc::internal diff --git a/buildcc/schema/test/.gitignore b/buildcc/schema/test/.gitignore index a8a0dcec..7628df5e 100644 --- a/buildcc/schema/test/.gitignore +++ b/buildcc/schema/test/.gitignore @@ -1 +1,2 @@ *.bin +*.json diff --git a/buildcc/schema/test/test_custom_generator_serialization.cpp b/buildcc/schema/test/test_custom_generator_serialization.cpp index 909b980b..e92f69d3 100644 --- a/buildcc/schema/test/test_custom_generator_serialization.cpp +++ b/buildcc/schema/test/test_custom_generator_serialization.cpp @@ -4,8 +4,6 @@ #include "flatbuffers/flatbuffers.h" -#include "custom_generator_generated.h" - // NOTE, Make sure all these includes are AFTER the system and header includes #include "CppUTest/CommandLineTestRunner.h" #include "CppUTest/MemoryLeakDetectorNewMacros.h" @@ -24,56 +22,24 @@ TEST_GROUP(CustomGeneratorSerializationTestGroup) TEST(CustomGeneratorSerializationTestGroup, FormatEmptyCheck) { buildcc::internal::CustomGeneratorSerialization serialization( - "dump/FormatEmptyCheck.bin"); - - { - flatbuffers::FlatBufferBuilder builder; - // Entire std::vector is nullptr - auto fbs_generator = - schema::internal::CreateCustomGeneratorDirect(builder, "", nullptr); - schema::internal::FinishCustomGeneratorBuffer(builder, fbs_generator); - - CHECK_TRUE(buildcc::env::save_file( - serialization.GetSerializedFile().string().c_str(), - (const char *)builder.GetBufferPointer(), builder.GetSize(), true)); + "dump/FormatEmptyCheck.json"); - CHECK_FALSE(serialization.LoadFromFile()); - - fs::remove_all(serialization.GetSerializedFile()); - } - - { - flatbuffers::FlatBufferBuilder builder; - // RelInputOutput in nullptr - auto gen_info = schema::internal::CreateGenInfoDirect(builder, ""); - std::vector v{gen_info}; - auto fbs_generator = - schema::internal::CreateCustomGeneratorDirect(builder, "", &v); - schema::internal::FinishCustomGeneratorBuffer(builder, fbs_generator); - - CHECK_TRUE(buildcc::env::save_file( - serialization.GetSerializedFile().string().c_str(), - (const char *)builder.GetBufferPointer(), builder.GetSize(), true)); - - CHECK_TRUE(serialization.LoadFromFile()); - - fs::remove_all(serialization.GetSerializedFile()); - } + bool stored = serialization.StoreToFile(); + CHECK_TRUE(stored); } TEST(CustomGeneratorSerializationTestGroup, EmptyFile_Failure) { { buildcc::internal::CustomGeneratorSerialization serialization( - "dump/empty_file.bin"); + "dump/EmptyFile.json"); CHECK_FALSE(serialization.LoadFromFile()); } { buildcc::internal::CustomGeneratorSerialization serialization( - "dump/empty_file.bin"); - char data[] = {0}; + "dump/EmptyFile.json"); buildcc::env::save_file(serialization.GetSerializedFile().string().c_str(), - (const char *)data, 1, true); + "", false); CHECK_FALSE(serialization.LoadFromFile()); } } diff --git a/buildexe/CMakeLists.txt b/buildexe/CMakeLists.txt index 6d4f9a9e..73bc7d2d 100644 --- a/buildexe/CMakeLists.txt +++ b/buildexe/CMakeLists.txt @@ -17,6 +17,7 @@ target_sources(buildexe PRIVATE ../bootstrap/src/build_buildcc.cpp ../bootstrap/src/build_cli11.cpp ../bootstrap/src/build_flatbuffers.cpp + ../bootstrap/src/build_nlohmann_json.cpp ../bootstrap/src/build_fmtlib.cpp ../bootstrap/src/build_spdlog.cpp ../bootstrap/src/build_taskflow.cpp diff --git a/cmake/target/json.cmake b/cmake/target/json.cmake new file mode 100644 index 00000000..bc524c55 --- /dev/null +++ b/cmake/target/json.cmake @@ -0,0 +1,3 @@ +set(JSON_BuildTests OFF CACHE BOOL "JSON Unit tests") +set(JSON_Install ON CACHE BOOL "JSON Install") +add_subdirectory(third_party/json) diff --git a/doc/software_architecture/buildcc_interface_lib.PNG b/doc/software_architecture/buildcc_interface_lib.PNG index ec3e1243..332d0398 100644 Binary files a/doc/software_architecture/buildcc_interface_lib.PNG and b/doc/software_architecture/buildcc_interface_lib.PNG differ diff --git a/doc/software_architecture/buildcc_single_lib.PNG b/doc/software_architecture/buildcc_single_lib.PNG index 9038de0b..24cf51c9 100644 Binary files a/doc/software_architecture/buildcc_single_lib.PNG and b/doc/software_architecture/buildcc_single_lib.PNG differ diff --git a/example/gcc/AfterInstall/CMakeLists.txt b/example/gcc/AfterInstall/CMakeLists.txt index 5931a007..04a8cd7a 100644 --- a/example/gcc/AfterInstall/CMakeLists.txt +++ b/example/gcc/AfterInstall/CMakeLists.txt @@ -13,6 +13,7 @@ project(simple) find_package(fmt_package NAMES "fmt" REQUIRED) find_package(spdlog_package NAMES "spdlog" REQUIRED) find_package(flatbuffers_package NAMES "Flatbuffers" "flatbuffers" REQUIRED) +find_package(nlohmann_json_package NAMES "nlohmann_json" REQUIRED) find_package(taskflow_package NAMES "Taskflow" "taskflow" REQUIRED) find_package(tl_optional_package NAMES "tl-optional" REQUIRED) find_package(CLI11_package NAMES "CLI11" REQUIRED) @@ -23,6 +24,7 @@ find_package(buildcc_package NAMES "buildcc" REQUIRED) message("Find package: ${fmt_package_DIR}") message("Find package: ${spdlog_package_DIR}") message("Find package: ${flatbuffers_package_DIR}") +message("Find package: ${nlohmann_json_package_DIR}") message("Find package: ${tl_optional_package_DIR}") message("Find package: ${taskflow_package_DIR}") message("Find package: ${CLI11_package_DIR}") diff --git a/third_party/json b/third_party/json new file mode 160000 index 00000000..bc889afb --- /dev/null +++ b/third_party/json @@ -0,0 +1 @@ +Subproject commit bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d