diff --git a/buildcc/lib/target/include/target/custom_generator.h b/buildcc/lib/target/include/target/custom_generator.h index 76e64576..316290ea 100644 --- a/buildcc/lib/target/include/target/custom_generator.h +++ b/buildcc/lib/target/include/target/custom_generator.h @@ -41,23 +41,27 @@ namespace buildcc { class CustomGeneratorContext { public: CustomGeneratorContext(const env::Command &c, const fs_unordered_set &i, - const fs_unordered_set &o) - : command(c), inputs(i), outputs(o) {} + const fs_unordered_set &o, + const std::vector &ub) + : command(c), inputs(i), outputs(o), userblob(ub) {} -public: const env::Command &command; const fs_unordered_set &inputs; const fs_unordered_set &outputs; + const std::vector &userblob; }; // clang-format off -typedef std::function GenerateCb; +using GenerateCb = std::function; -typedef std::function &)> DependencyCb; +using DependencyCb = std::function &&)>; // clang-format on class CustomBlobHandler { public: + CustomBlobHandler() = default; + virtual ~CustomBlobHandler() = default; + bool CheckChanged(const std::vector &previous, const std::vector ¤t) const { env::assert_fatal( @@ -69,7 +73,7 @@ class CustomBlobHandler { return !IsEqual(previous, current); }; - std::vector GetSerializedData() { + std::vector GetSerializedData() const { auto serialized_data = Serialize(); env::assert_fatal( Verify(serialized_data), @@ -94,12 +98,11 @@ struct UserCustomGeneratorSchema : public internal::CustomGeneratorSchema { std::unordered_map gen_info_map; void ConvertToInternal() { - for (auto &r_miter : gen_info_map) { - r_miter.second.internal_inputs = path_schema_convert( - r_miter.second.inputs, internal::Path::CreateExistingPath); - auto p = internal_gen_info_map.emplace(r_miter.first, r_miter.second); - env::assert_fatal(p.second, - fmt::format("Could not save {}", r_miter.first)); + 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); + env::assert_fatal(success, fmt::format("Could not save {}", id)); } } }; @@ -136,6 +139,10 @@ class CustomGenerator : public internal::BuilderInterface { const GenerateCb &generate_cb, std::shared_ptr blob_handler = nullptr); + void AddGroup(const std::string &group_id, + std::initializer_list ids, + const DependencyCb &dependency_cb = DependencyCb()); + // Callbacks /** * @brief Setup dependencies between Tasks using their `id` @@ -168,12 +175,16 @@ class CustomGenerator : public internal::BuilderInterface { private: void Initialize(); - template void TaskRunner(const std::string &id); + void TaskRunner(bool run, const std::string &id); + tf::Task CreateTaskRunner(tf::Subflow &subflow, bool build, + const std::string &id); void GenerateTask(); - void BuildGenerate( - std::unordered_map &gen_selected_map, - std::unordered_map &dummy_gen_selected_map); + void BuildGenerate(std::unordered_set &gen_selected_ids, + std::unordered_set &dummy_gen_selected_ids); + + void InvokeDependencyCb(std::unordered_map + &®istered_tasks) const noexcept; // Recheck states void IdRemoved(); @@ -181,7 +192,31 @@ class CustomGenerator : public internal::BuilderInterface { void IdUpdated(); protected: - env::Command command_; + const env::Command &ConstCommand() const { return command_; } + env::Command &RefCommand() { return command_; } + +private: + struct GroupMetadata { + std::vector ids; + DependencyCb dependency_cb; + + void InvokeDependencyCb(const std::string &group_id, + std::unordered_map + &®istered_tasks) const noexcept { + if (!dependency_cb) { + return; + } + try { + dependency_cb(std::move(registered_tasks)); + } catch (...) { + env::log_critical( + __FUNCTION__, + fmt::format("Dependency callback failed for group id {}", + group_id)); + env::set_task_state(env::TaskState::FAILURE); + } + } + }; private: std::string name_; @@ -190,11 +225,14 @@ class CustomGenerator : public internal::BuilderInterface { // Serialization UserCustomGeneratorSchema user_; + std::unordered_map grouped_ids_; + std::unordered_set ungrouped_ids_; std::mutex success_schema_mutex_; std::unordered_map success_schema_; // Internal + env::Command command_; tf::Taskflow tf_; // Callbacks diff --git a/buildcc/lib/target/include/target/file_generator.h b/buildcc/lib/target/include/target/file_generator.h index 08eb1d99..cc4b66b0 100644 --- a/buildcc/lib/target/include/target/file_generator.h +++ b/buildcc/lib/target/include/target/file_generator.h @@ -23,9 +23,8 @@ namespace buildcc { class FileGenerator : public CustomGenerator { public: - FileGenerator(const std::string &name, const TargetEnv &env) - : CustomGenerator(name, env) {} - virtual ~FileGenerator() = default; + using CustomGenerator::CustomGenerator; + ~FileGenerator() override = default; FileGenerator(const FileGenerator &) = delete; /** @@ -73,6 +72,7 @@ class FileGenerator : public CustomGenerator { private: using CustomGenerator::AddDependencyCb; using CustomGenerator::AddGenInfo; + using CustomGenerator::AddGroup; using CustomGenerator::Build; private: diff --git a/buildcc/lib/target/include/target/template_generator.h b/buildcc/lib/target/include/target/template_generator.h index 2d2061a5..b22d49c0 100644 --- a/buildcc/lib/target/include/target/template_generator.h +++ b/buildcc/lib/target/include/target/template_generator.h @@ -23,12 +23,13 @@ namespace buildcc { class TemplateGenerator : public CustomGenerator { public: - TemplateGenerator(const std::string &name, const TargetEnv &env) - : CustomGenerator(name, env) {} + using CustomGenerator::CustomGenerator; + ~TemplateGenerator() override = default; + TemplateGenerator(const TemplateGenerator &) = delete; void AddTemplate(const fs::path &input_filename, const fs::path &output_filename); - std::string Parse(const std::string &pattern); + std::string Parse(const std::string &pattern) const; /** * @brief Build FileGenerator Tasks @@ -41,6 +42,7 @@ class TemplateGenerator : public CustomGenerator { private: using CustomGenerator::AddDependencyCb; using CustomGenerator::AddGenInfo; + using CustomGenerator::AddGroup; using CustomGenerator::Build; private: diff --git a/buildcc/lib/target/src/custom_generator/custom_generator.cpp b/buildcc/lib/target/src/custom_generator/custom_generator.cpp index 466d43dd..c488a54b 100644 --- a/buildcc/lib/target/src/custom_generator/custom_generator.cpp +++ b/buildcc/lib/target/src/custom_generator/custom_generator.cpp @@ -17,10 +17,7 @@ #include "target/custom_generator.h" namespace { -constexpr const char *const kStartGeneratorTaskName = "Start Generator"; -constexpr const char *const kEndGeneratorTaskName = "End Generator"; -constexpr const char *const kCommandTaskName = "Command"; constexpr const char *const kGenerateTaskName = "Generate"; } // namespace @@ -34,8 +31,8 @@ void CustomGenerator::AddPattern(const std::string &identifier, void CustomGenerator::AddPatterns( const std::unordered_map &pattern_map) { - for (const auto &arg_iter : pattern_map) { - AddPattern(arg_iter.first, arg_iter.second); + for (const auto &[identifier, pattern] : pattern_map) { + AddPattern(identifier, pattern); } } @@ -67,7 +64,29 @@ void CustomGenerator::AddGenInfo( } schema.generate_cb = generate_cb; schema.blob_handler = std::move(blob_handler); - user_.gen_info_map.emplace(id, std::move(schema)); + user_.gen_info_map.try_emplace(id, std::move(schema)); + ungrouped_ids_.emplace(id); +} + +void CustomGenerator::AddGroup(const std::string &group_id, + std::initializer_list ids, + const DependencyCb &dependency_cb) { + // Verify that the ids exist + // Remove those ids from ungrouped_ids + for (const auto &id : ids) { + env::assert_fatal(user_.gen_info_map.find(id) != user_.gen_info_map.end(), + fmt::format("Id '{}' is not found", id)); + ungrouped_ids_.erase(id); + } + + env::assert_fatal(grouped_ids_.find(group_id) == grouped_ids_.end(), + fmt::format("Group Id '{}' duplicate found", group_id)); + + // Group map is used to group similar ids in a single subflow + GroupMetadata group_metadata; + group_metadata.ids = ids; + group_metadata.dependency_cb = dependency_cb; + grouped_ids_.try_emplace(group_id, std::move(group_metadata)); } void CustomGenerator::AddDependencyCb(const DependencyCb &dependency_cb) { @@ -102,10 +121,12 @@ void CustomGenerator::Initialize() { } void CustomGenerator::BuildGenerate( - std::unordered_map &gen_selected_map, - std::unordered_map &dummy_gen_selected_map) { + std::unordered_set &gen_selected_ids, + std::unordered_set &dummy_gen_selected_ids) { if (!serialization_.IsLoaded()) { - gen_selected_map = user_.gen_info_map; + std::for_each( + user_.gen_info_map.begin(), user_.gen_info_map.end(), + [&](const auto &iter) { gen_selected_ids.insert(iter.first); }); dirty_ = true; } else { // DONE, Conditionally select internal_gen_info_map depending on what has @@ -118,8 +139,7 @@ void CustomGenerator::BuildGenerate( // curr_gen_info_map If prev_gen_info_map does not exist in // curr_gen_info_map, has been removed from existing build We need this // condition to only set the dirty_ flag - for (const auto &prev_miter : prev_gen_info_map) { - const auto &id = prev_miter.first; + for (const auto &[id, _] : prev_gen_info_map) { if (curr_gen_info_map.find(id) == curr_gen_info_map.end()) { // MAP REMOVED condition IdRemoved(); @@ -131,18 +151,17 @@ void CustomGenerator::BuildGenerate( // DONE, MAP ADDED condition Check if curr_gen_info_map exists in // prev_gen_info_map If curr_gen_info_map does not exist in // prev_gen_info_map, has been added to existing build - for (const auto &curr_miter : curr_gen_info_map) { - const auto &id = curr_miter.first; + for (const auto &[id, _] : curr_gen_info_map) { if (prev_gen_info_map.find(id) == prev_gen_info_map.end()) { // MAP ADDED condition IdAdded(); - gen_selected_map.emplace(curr_miter.first, curr_miter.second); + gen_selected_ids.insert(id); dirty_ = true; } else { // MAP UPDATED condition (*checked later) // This is because tasks can have dependencies amongst each other we can // compute task level rebuilds later - dummy_gen_selected_map.emplace(curr_miter.first, curr_miter.second); + dummy_gen_selected_ids.insert(id); } } } @@ -155,51 +174,51 @@ void CustomGenerator::GenerateTask() { } try { - std::unordered_map selected_user_schema; - std::unordered_map dummy_selected_user_schema; - BuildGenerate(selected_user_schema, dummy_selected_user_schema); - - std::unordered_map task_map; - // Create task for selected schema - for (const auto &selected_miter : selected_user_schema) { - const auto &id = selected_miter.first; - // const auto ¤t_info = selected_miter.second; - tf::Task task = subflow - .emplace([&]() { - try { - TaskRunner(id); - } catch (...) { - env::set_task_state(env::TaskState::FAILURE); - } - }) - .name(id); - task_map.emplace(id, task); + std::unordered_map registered_tasks; + + // Selected ids for build + std::unordered_set selected_ids; + std::unordered_set dummy_selected_ids; + BuildGenerate(selected_ids, dummy_selected_ids); + + // Grouped tasks + for (const auto &[first, second] : grouped_ids_) { + const auto &group_id = first; + const auto &group_metadata = second; + auto group_task = subflow.emplace([&](tf::Subflow &s) { + std::unordered_map reg_tasks; + + if (env::get_task_state() != env::TaskState::SUCCESS) { + return; + } + + for (const auto &id : group_metadata.ids) { + bool build = selected_ids.count(id) == 1; + auto task = CreateTaskRunner(s, build, id); + task.name(id); + reg_tasks.try_emplace(id, task); + } + + // Dependency callback + group_metadata.InvokeDependencyCb(group_id, std::move(reg_tasks)); + + // NOTE, Do not call detach otherwise this will fail + s.join(); + }); + group_task.name(group_id); + registered_tasks.try_emplace(group_id, group_task); } - for (auto &dummy_selected_miter : dummy_selected_user_schema) { - const auto &id = dummy_selected_miter.first; - // const auto ¤t_info = dummy_selected_miter.second; - tf::Task task = subflow - .emplace([&]() { - try { - TaskRunner(id); - } catch (...) { - env::set_task_state(env::TaskState::FAILURE); - } - }) - .name(id); - task_map.emplace(id, task); + // Ungrouped tasks + for (const auto &id : ungrouped_ids_) { + bool build = selected_ids.count(id) == 1; + auto task = CreateTaskRunner(subflow, build, id); + task.name(id); + registered_tasks.try_emplace(id, task); } - // Dependencies between ids - if (dependency_cb_) { - try { - dependency_cb_(task_map); - } catch (...) { - env::log_critical(__FUNCTION__, "Dependency callback failed"); - env::set_task_state(env::TaskState::FAILURE); - } - } + // Dependencies between tasks + InvokeDependencyCb(std::move(registered_tasks)); // NOTE, Do not call detach otherwise this will fail subflow.join(); @@ -224,22 +243,47 @@ void CustomGenerator::GenerateTask() { generate_task.name(kGenerateTaskName); } -template void CustomGenerator::TaskRunner(const std::string &id) { - // Convert - { - auto ¤t_gen_info = user_.gen_info_map.at(id); - current_gen_info.internal_inputs = internal::path_schema_convert( - current_gen_info.inputs, internal::Path::CreateExistingPath); - current_gen_info.userblob = - current_gen_info.blob_handler != nullptr - ? current_gen_info.blob_handler->GetSerializedData() - : std::vector(); +void CustomGenerator::InvokeDependencyCb( + std::unordered_map &®istered_tasks) + const noexcept { + if (dependency_cb_) { + try { + dependency_cb_(std::move(registered_tasks)); + } catch (...) { + env::log_critical(__FUNCTION__, "Dependency callback failed"); + env::set_task_state(env::TaskState::FAILURE); + } } +} + +tf::Task CustomGenerator::CreateTaskRunner(tf::Subflow &subflow, bool build, + const std::string &id) { + return subflow.emplace([&, build, id]() { + if (env::get_task_state() != env::TaskState::SUCCESS) { + return; + } + try { + TaskRunner(build, id); + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + }); +} + +void CustomGenerator::TaskRunner(bool run, const std::string &id) { + // Convert + auto ¤t_gen_info = user_.gen_info_map.at(id); + current_gen_info.internal_inputs = internal::path_schema_convert( + current_gen_info.inputs, internal::Path::CreateExistingPath); + current_gen_info.userblob = + current_gen_info.blob_handler != nullptr + ? current_gen_info.blob_handler->GetSerializedData() + : std::vector(); // Run const auto ¤t_info = user_.gen_info_map.at(id); bool rerun = false; - if constexpr (run) { + if (run) { rerun = true; } else { const auto &previous_info = @@ -257,13 +301,14 @@ template void CustomGenerator::TaskRunner(const std::string &id) { if (rerun) { dirty_ = true; buildcc::CustomGeneratorContext ctx(command_, current_info.inputs, - current_info.outputs); + current_info.outputs, + current_info.userblob); bool success = current_info.generate_cb(ctx); env::assert_fatal(success, fmt::format("Generate Cb failed for id {}", id)); } - std::lock_guard guard(success_schema_mutex_); - success_schema_.emplace(id, current_info); + std::scoped_lock guard(success_schema_mutex_); + success_schema_.try_emplace(id, current_info); } } // namespace buildcc diff --git a/buildcc/lib/target/src/generator/file_generator.cpp b/buildcc/lib/target/src/generator/file_generator.cpp index ca94d9e2..1362ed32 100644 --- a/buildcc/lib/target/src/generator/file_generator.cpp +++ b/buildcc/lib/target/src/generator/file_generator.cpp @@ -24,11 +24,22 @@ namespace { -class GeneratorBlobHandler : public buildcc::CustomBlobHandler { +class FileGeneratorBlobHandler : public buildcc::CustomBlobHandler { public: - explicit GeneratorBlobHandler(const std::vector &commands) + explicit FileGeneratorBlobHandler(const std::vector &commands) : commands_(commands) {} + // serialized_data has already been verified + static std::vector + Deserialize(const std::vector &serialized_data) { + std::vector deserialized; + auto flex_vect = flexbuffers::GetRoot(serialized_data).AsVector(); + for (size_t i = 0; i < flex_vect.size(); i++) { + deserialized.emplace_back(flex_vect[i].AsString().str()); + } + return deserialized; + } + private: const std::vector &commands_; @@ -61,18 +72,24 @@ class GeneratorBlobHandler : public buildcc::CustomBlobHandler { builder.Finish(); return builder.GetBuffer(); } +}; - // serialized_data has already been verified - static std::vector - Deserialize(const std::vector &serialized_data) { - std::vector deserialized; - auto flex_vect = flexbuffers::GetRoot(serialized_data).AsVector(); - for (size_t i = 0; i < flex_vect.size(); i++) { - deserialized.emplace_back(flex_vect[i].AsString().str()); +bool FileGeneratorGenerateCb(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + bool success = true; + std::vector commands = + FileGeneratorBlobHandler::Deserialize(ctx.userblob); + for (const auto &c : commands) { + bool executed = buildcc::env::Command::Execute(c); + if (!executed) { + success = false; + buildcc::env::log_critical(__FUNCTION__, + fmt::format("Failed to run command {}", c)); + break; } - return deserialized; } -}; + return success; +} } // namespace @@ -81,7 +98,7 @@ namespace buildcc { void FileGenerator::AddInput(const std::string &absolute_input_pattern, const char *identifier) { std::string absolute_input_string = - command_.Construct(absolute_input_pattern); + RefCommand().Construct(absolute_input_pattern); const auto absolute_input_path = internal::Path::CreateNewPath(absolute_input_string); inputs_.insert(absolute_input_path.GetPathname()); @@ -94,7 +111,7 @@ void FileGenerator::AddInput(const std::string &absolute_input_pattern, void FileGenerator::AddOutput(const std::string &absolute_output_pattern, const char *identifier) { std::string absolute_output_string = - command_.Construct(absolute_output_pattern); + RefCommand().Construct(absolute_output_pattern); const auto absolute_output_path = internal::Path::CreateNewPath(absolute_output_string); outputs_.insert(absolute_output_path.GetPathname()); @@ -108,28 +125,13 @@ void FileGenerator::AddCommand( const std::string &command_pattern, const std::unordered_map &arguments) { std::string constructed_command = - command_.Construct(command_pattern, arguments); + RefCommand().Construct(command_pattern, arguments); commands_.emplace_back(std::move(constructed_command)); } void FileGenerator::Build() { - AddGenInfo( - "Generate", inputs_, outputs_, - [&](const CustomGeneratorContext &ctx) -> bool { - (void)ctx; - bool success = true; - for (const auto &c : commands_) { - bool executed = env::Command::Execute(c); - if (!executed) { - success = false; - env::log_critical(__FUNCTION__, - fmt::format("Failed to run command {}", c)); - break; - } - } - return success; - }, - std::make_shared(commands_)); + AddGenInfo("Generate", inputs_, outputs_, FileGeneratorGenerateCb, + std::make_shared(commands_)); this->CustomGenerator::Build(); } diff --git a/buildcc/lib/target/src/generator/template_generator.cpp b/buildcc/lib/target/src/generator/template_generator.cpp index c99fc541..e7c059c0 100644 --- a/buildcc/lib/target/src/generator/template_generator.cpp +++ b/buildcc/lib/target/src/generator/template_generator.cpp @@ -48,16 +48,16 @@ void TemplateGenerator::AddTemplate(const fs::path &input_filename, const fs::path &output_filename) { TemplateInfo info; info.input = internal::Path::CreateNewPath( - command_.Construct(path_as_string(input_filename))) + RefCommand().Construct(path_as_string(input_filename))) .GetPathname(); info.output = internal::Path::CreateNewPath( - command_.Construct(path_as_string(output_filename))) + RefCommand().Construct(path_as_string(output_filename))) .GetPathname(); template_infos_.emplace_back(std::move(info)); } -std::string TemplateGenerator::Parse(const std::string &pattern) { - return command_.Construct(pattern); +std::string TemplateGenerator::Parse(const std::string &pattern) const { + return ConstCommand().Construct(pattern); } /** diff --git a/buildcc/lib/target/test/target/test_custom_generator.cpp b/buildcc/lib/target/test/target/test_custom_generator.cpp index 5efa7e6f..ea3eef74 100644 --- a/buildcc/lib/target/test/target/test_custom_generator.cpp +++ b/buildcc/lib/target/test/target/test_custom_generator.cpp @@ -85,6 +85,235 @@ TEST(CustomGeneratorTestGroup, Basic_Failure) { CHECK_EQUAL(internal_map.size(), 1); } +TEST(CustomGeneratorTestGroup, Basic_Group) { + buildcc::CustomGenerator cgen("basic_group", ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, BasicGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.AddGroup("grouped_id1_and_id2", {"id1", "id2"}); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + + // Serialization check + { + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(internal_map.size(), 2); + const auto &id1_info = internal_map.at("id1"); + CHECK_EQUAL(id1_info.internal_inputs.size(), 1); + CHECK_EQUAL(id1_info.outputs.size(), 1); + + const auto &id2_info = internal_map.at("id2"); + CHECK_EQUAL(id2_info.internal_inputs.size(), 1); + CHECK_EQUAL(id2_info.outputs.size(), 0); + } +} + +TEST(CustomGeneratorTestGroup, Basic_Group_Dependency) { + buildcc::CustomGenerator cgen("basic_group_dependency", ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, BasicGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.AddGroup("grouped_id1_and_id2", {"id1", "id2"}, [](auto &&task_map) { + task_map.at("id1").precede(task_map.at("id2")); + }); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + + // Serialization check + { + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(internal_map.size(), 2); + const auto &id1_info = internal_map.at("id1"); + CHECK_EQUAL(id1_info.internal_inputs.size(), 1); + CHECK_EQUAL(id1_info.outputs.size(), 1); + + const auto &id2_info = internal_map.at("id2"); + CHECK_EQUAL(id2_info.internal_inputs.size(), 1); + CHECK_EQUAL(id2_info.outputs.size(), 0); + } +} + +TEST(CustomGeneratorTestGroup, Basic_Group_DependencyFailure) { + buildcc::CustomGenerator cgen("basic_group_dependency_failure", ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, BasicGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.AddGroup("grouped_id1_and_id2", {"id1", "id2"}, [](auto &&task_map) { + task_map.at("id1").precede(task_map.at("id2")); + buildcc::env::assert_fatal("Failure"); + }); + cgen.Build(); + + buildcc::m::CustomGeneratorRunner(cgen); + + // Serialization check + { + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(internal_map.size(), 0); + } + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); +} + +bool FailureCb(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + return false; +} +bool SuccessCb(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + return true; +} + +// An ungrouped task a dependency on a grouped task and fail the +// ungrouped task +TEST(CustomGeneratorTestGroup, Basic_Group_DependencyFailure2) { + buildcc::CustomGenerator cgen("basic_group_dependency_failure2", ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, FailureCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.cpp"}, {}, SuccessCb); + cgen.AddGroup("grouped_id2", {"id2"}); + cgen.AddDependencyCb([&](auto &&task_map) { + task_map.at("id1").precede(task_map.at("grouped_id2")); + }); + cgen.Build(); + + buildcc::m::CustomGeneratorRunner(cgen); + + // Serialization check + { + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(internal_map.size(), 0); + } + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); +} + +// Behaviour +// Initial: A | B -> Passes +// Changes: (GID:NEW)[A -> B] -> No rebuild triggered + +// Behaviour +// Initial: A | B -> Fails +// Changes: (GID:NEW)[A -> B] -> rebuild triggered due to previous failure + +// ! IMPORTANT +// * NOTE, It is users responsibility to make sure that when A -> B, A's data +// change should automatically trigger B + +// For example: Say A -> B i.e B depends on A +// In a typical scenario, B would depend on A's output +// To make sure B is triggered when A changes. Make sure you use A's output in +// B's userblob. +// In this way whenever A changes, B's userblob automatically becomes "newer" +// and triggers a rebuild as well + +// Say, A gives out "rebuild = true/false" as its output +// We can use this "rebuild" variable in B's userblob +// When A runs and "rebuild" changes from false to true, During the `TaskRunner` +// we check B's userblob and automatically invoke the `CheckChanged` virtual +// call +// TODO, Create a testcase for the above scenario (Advanced_DependencyRebuild +// scenario) + +// DONE, Make B fail because it properly depends on A +static bool rebuild_value{false}; +static bool ProperDependency1(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + mock().actualCall("ProperDependency1"); + rebuild_value = true; + return true; +} + +static bool ProperDependency2(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + mock().actualCall("ProperDependency2"); + return rebuild_value; +} + +// ProperDependency2 depends on ProperDependency1 completion +TEST(CustomGeneratorTestGroup, Basic_ProperDependency_GoodCase) { + rebuild_value = false; + + buildcc::CustomGenerator cgen("basic_proper_dependency_good_case", ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, ProperDependency1); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.cpp"}, {}, + ProperDependency2); + cgen.AddDependencyCb( + [](auto &&task_map) { task_map.at("id1").precede(task_map.at("id2")); }); + cgen.Build(); + + mock().expectOneCall("ProperDependency1"); + mock().expectOneCall("ProperDependency2"); + buildcc::m::CustomGeneratorRunner(cgen); + + // Serialization check + { + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(internal_map.size(), 2); + } + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); +} + +// ProperDependency2 depends on ProperDependency1 completion +TEST(CustomGeneratorTestGroup, Basic_ProperDependency_BadCase) { + rebuild_value = false; + + buildcc::CustomGenerator cgen("basic_proper_dependency_bad_case", ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, ProperDependency1); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.cpp"}, {}, + ProperDependency2); + cgen.AddDependencyCb( + [](auto &&task_map) { task_map.at("id2").precede(task_map.at("id1")); }); + cgen.Build(); + + mock().expectOneCall("ProperDependency2"); + buildcc::m::CustomGeneratorRunner(cgen); + + // Serialization check + { + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_gen_info_map; + CHECK_EQUAL(internal_map.size(), 0); + } + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); +} + TEST(CustomGeneratorTestGroup, DefaultArgumentUsage) { buildcc::CustomGenerator cgen("default_argument_usage", ""); cgen.AddPatterns({ @@ -183,7 +412,7 @@ static bool Dep2Cb(const buildcc::CustomGeneratorContext &ctx) { return buildcc::env::Command::Execute(""); } -static void DependencyCb(std::unordered_map &task_map) { +static void DependencyCb(std::unordered_map &&task_map) { task_map.at("id1").precede(task_map.at("id2")); } @@ -417,8 +646,10 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Basic) { mock().expectOneCall("RealGenerateCb"); buildcc::env::m::CommandExpect_Execute(1, false); - mock().expectOneCall("RealGenerateCb"); - buildcc::env::m::CommandExpect_Execute(1, true); + + // Since there is an error above, second command does not execute (note, + // this is the behaviour in a single thread that is why we can + // check sequentially) buildcc::m::CustomGeneratorRunner(cgen); CHECK_TRUE(buildcc::env::get_task_state() == @@ -427,7 +658,7 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Basic) { 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_gen_info_map.size(), 0); fs::remove_all(cgen.GetBinaryPath()); } @@ -515,6 +746,9 @@ TEST(CustomGeneratorTestGroup, RealGenerate_RemoveAndAdd) { {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.c"}, {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddDependencyCb([](auto &&task_map) { + task_map.at("id1").precede(task_map.at("id2")); + }); cgen.Build(); buildcc::m::CustomGeneratorExpect_IdAdded(1, &cgen); @@ -576,6 +810,9 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Update_Failure) { {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.cpp"}, {"{gen_build_dir}/other_dummy_main.o"}, RealGenerateCb); + cgen.AddDependencyCb([](auto &&task_map) { + task_map.at("id1").precede(task_map.at("id2")); + }); cgen.Build(); mock().expectOneCall("RealGenerateCb"); @@ -609,6 +846,9 @@ TEST(CustomGeneratorTestGroup, RealGenerate_Update_Failure) { {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.cpp"}, {"{gen_build_dir}/other_dummy_main.o"}, RealGenerateCb); + cgen.AddDependencyCb([](auto &&task_map) { + task_map.at("id1").precede(task_map.at("id2")); + }); cgen.Build(); mock().expectOneCall("RealGenerateCb");