diff options
-rw-r--r-- | src/build.cc | 67 | ||||
-rw-r--r-- | src/build.h | 5 | ||||
-rw-r--r-- | test/ctor.cc | 18 | ||||
-rw-r--r-- | test/cycle_test.cc | 87 |
4 files changed, 159 insertions, 18 deletions
diff --git a/src/build.cc b/src/build.cc index 906c3ea..a31f6a5 100644 --- a/src/build.cc +++ b/src/build.cc @@ -131,17 +131,35 @@ int build(const ctor::settings& settings, return 0; } -namespace -{ -std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task) +std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task, + std::vector<std::shared_ptr<Task>> trace) { std::vector<std::shared_ptr<Task>> tasks; tasks.push_back(task); + trace.push_back(task); auto deps = task->getDependsTasks(); for(const auto& dep : deps) { - auto depSet = getDepTasks(dep); + if(std::find(trace.begin(), trace.end(), dep) != trace.end()) + { + trace.push_back(dep); + std::cerr << "Error: Cyclic dependency detected: "; + bool first{true}; + for(auto t : trace) + { + if(!first) + { + std::cerr << " -> "; + } + + first = false; + std::cerr << t->target(); + } + std::cerr << '\n'; + throw 1; + } + auto depSet = getDepTasks(dep, trace); for(const auto& dep_inner : depSet) { if(std::find(tasks.begin(), tasks.end(), dep_inner) == tasks.end()) @@ -153,7 +171,6 @@ std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task) return tasks; } -} int build(const ctor::settings& settings, const std::string& name, @@ -167,20 +184,27 @@ int build(const ctor::settings& settings, { task_found = true; - auto depSet = getDepTasks(task); - std::vector<std::shared_ptr<Task>> ts; - for(const auto& task_inner : depSet) + try { - if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + auto depSet = getDepTasks(task); + std::vector<std::shared_ptr<Task>> ts; + for(const auto& task_inner : depSet) { - ts.push_back(task_inner); + if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + { + ts.push_back(task_inner); + } } - } - auto ret = build(settings, name, ts, all_tasks, dryrun); - if(ret != 0) + auto ret = build(settings, name, ts, all_tasks, dryrun); + if(ret != 0) + { + return ret; + } + } + catch(...) { - return ret; + return 1; // cycle detected } break; @@ -214,14 +238,21 @@ int build(const ctor::settings& settings, { task_found = true; - auto depSet = getDepTasks(task); - for(const auto& task_inner : depSet) + try { - if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + auto depSet = getDepTasks(task); + for(const auto& task_inner : depSet) { - ts.push_back(task_inner); + if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + { + ts.push_back(task_inner); + } } } + catch(...) + { + return 1; // cycle detected + } } } } diff --git a/src/build.h b/src/build.h index 500fb7f..7296f76 100644 --- a/src/build.h +++ b/src/build.h @@ -33,3 +33,8 @@ int build(const ctor::settings& settings, const std::vector<Target>& targets, const std::vector<std::shared_ptr<Task>>& all_tasks, bool dryrun = false); + +// Recursively build vector of dependency tasks from source task. +// Throws if a cycle is detected. +std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task, + std::vector<std::shared_ptr<Task>> trace = {}); diff --git a/test/ctor.cc b/test/ctor.cc index 1951a30..d69b1cf 100644 --- a/test/ctor.cc +++ b/test/ctor.cc @@ -82,6 +82,24 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) { .type = ctor::target_type::unit_test, .system = ctor::output_system::build, + .target = "cycle_test", + .sources = { + "cycle_test.cc", + "testmain.cc", + }, + .depends = { "libctor_nomain.a" }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"cycle\"", + }, + .ldflags = { "-pthread" }, + }, + }, + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, .target = "source_type_test", .sources = { "source_type_test.cc", diff --git a/test/cycle_test.cc b/test/cycle_test.cc new file mode 100644 index 0000000..3b45632 --- /dev/null +++ b/test/cycle_test.cc @@ -0,0 +1,87 @@ +#include <uunit.h> + +#include <ctor.h> +#include <build.h> + +namespace +{ +ctor::build_configurations ctorTestConfigsCyclic(const ctor::settings&) +{ + return + { + // No dependency + { + .target = "target0", + }, + + // Direct (self-depends) + { + .target = "target1", + .depends = { "target1" }, + }, + + // Indirect cyclic depends + { + .target = "target2", + .depends = { "target3" }, + }, + { + .target = "target3", + .depends = { "target2" }, + }, + }; +} +} + +REG(ctorTestConfigsCyclic); + +class CycleTest + : public uUnit +{ +public: + CycleTest() + { + uTEST(CycleTest::getTargets_test); + } + + void getTargets_test() + { + using namespace std::string_literals; + ctor::settings settings{}; + const auto& tasks = getTasks(settings); + uASSERT_EQUAL(4u, tasks.size()); + + uASSERT_EQUAL("target0"s, tasks[0]->target()); + uASSERT_EQUAL("target1"s, tasks[1]->target()); + uASSERT_EQUAL("target2"s, tasks[2]->target()); + uASSERT_EQUAL("target3"s, tasks[3]->target()); + + for(auto task : tasks) + { + if(task->registerDepTasks(tasks)) + { + uASSERT(false); + } + } + + bool exc; + exc = false; + try { getDepTasks(tasks[0]); } catch(...) { exc = true; } + uASSERT(!exc); + + exc = false; + try { getDepTasks(tasks[1]); } catch(...) { exc = true; } + uASSERT(exc); + + exc = false; + try { getDepTasks(tasks[2]); } catch(...) { exc = true; } + uASSERT(exc); + + exc = false; + try { getDepTasks(tasks[3]); } catch(...) { exc = true; } + uASSERT(exc); + } +}; + +// Registers the fixture into the 'registry' +static CycleTest test; |