summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/build.cc67
-rw-r--r--src/build.h5
-rw-r--r--test/ctor.cc18
-rw-r--r--test/cycle_test.cc87
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;