This commit is contained in:
ProgramSnail 2022-08-04 01:22:09 +03:00
parent 4d899f64a7
commit b4a7121411
23 changed files with 681 additions and 11 deletions

14
.gitignore vendored
View file

@ -1,11 +1,3 @@
CMakeLists.txt.user lib
CMakeCache.txt bim
CMakeFiles build
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps

31
CMakeLists.txt Normal file
View file

@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.10)
project(BuildSystem)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
set(BUILD_SYSTEM_SOURCES
build_graph.cpp
builder.cpp
thread_pool.cpp
task_queue.cpp
dependency_manager.cpp
task.hpp)
set (CLI_SOURCES
main.cpp
interactive_mode.cpp
file_mode.cpp
cli_common.cpp
test_tasks.hpp)
add_library(BuildSystemLib ${BUILD_SYSTEM_SOURCES})
add_executable(BuildSystemCli ${CLI_SOURCES})
target_link_libraries(BuildSystemCli BuildSystemLib)
set_property(TARGET BuildSystemLib PROPERTY CXX_STANDARD 20)
set_property(TARGET BuildSystemCli PROPERTY CXX_STANDARD 20)

9
README.md Normal file
View file

@ -0,0 +1,9 @@
### Build System
Сборка:
```
mkdir build && cd build
cmake .. && make
```

110
build_graph.cpp Normal file
View file

@ -0,0 +1,110 @@
#include "build_graph.hpp"
#include <algorithm>
#include <new>
namespace build_system {
const size_t BuildGraph::TraversalManager::kCurrentWayEpoch = 0;
const size_t BuildGraph::TraversalManager::kNeverVisited = 1;
class BuildGraph::TraversalManager::View {
friend TraversalManager;
public:
View() = delete;
std::vector<size_t> DFS(size_t vertex) {
std::vector<size_t> visited;
traversal_manager.DFS(vertex, visited);
return visited;
}
// this function saves exsisting values in "visited" array
void DFS(size_t vertex, std::vector<size_t>& visited) {
traversal_manager.DFS(vertex, visited);
}
virtual ~View() { traversal_manager.nextEpoch(); }
private:
View(TraversalManager &traversal_manager)
: traversal_manager(traversal_manager) {}
private:
TraversalManager &traversal_manager;
};
BuildGraph::TraversalManager::View BuildGraph::TraversalManager::getView() {
return View(*this);
}
// this function saves existing values in "visited" array
void BuildGraph::TraversalManager::DFS(size_t vertex,
std::vector<size_t>& current_visited) {
if (visited_[vertex] == epoch_) {
return;
}
if (visited_[vertex] == kCurrentWayEpoch) {
throw IncorrectBuildGraph{};
}
visited_[vertex] = kCurrentWayEpoch;
for (auto &dependency : build_graph_.graph_[vertex].dependences) {
DFS(dependency, current_visited);
}
visited_[vertex] = epoch_;
current_visited.push_back(vertex);
}
BuildGraph::BuildGraph(const std::vector<Rule>& rules,
std::vector<Task>&& tasks)
: traversal_manager_(*this) {
graph_.reserve(tasks.size());
for (size_t i = 0; i < tasks.size(); ++i) {
Target target;
target.task = std::forward<Task>(tasks[i]);
target.id = i;
graph_.push_back(target);
}
for (auto &rule : rules) {
graph_[rule.target].dependences.push_back(rule.dependency);
}
traversal_manager_.initArray();
}
void BuildGraph::isCorrectOrThrow(size_t target) const {
traversal_manager_.getView().DFS(target);
}
bool BuildGraph::isCorrect(size_t target) const {
try {
isCorrectOrThrow(target);
} catch (IncorrectBuildGraph) {
return false;
}
return true;
}
std::vector<Target> BuildGraph::getRequiredTargetsOrder(size_t target) const {
std::vector<Target> order;
auto rev_topsorted_required_verticles = traversal_manager_.getView().DFS(target);
order.reserve(rev_topsorted_required_verticles.size());
for (auto &vertex_id : rev_topsorted_required_verticles) {
order.push_back(graph_[vertex_id]);
}
return order;
}
} // namespace build_system

68
build_graph.hpp Normal file
View file

@ -0,0 +1,68 @@
#pragma once
#include <cstddef>
#include <vector>
#include "task.hpp"
namespace build_system {
struct IncorrectBuildGraph {};
struct Rule {
size_t target;
size_t dependency;
};
struct Target {
size_t id;
Task task;
std::vector<size_t> dependences;
};
class BuildGraph {
private:
// manage DFS, topsort, etc. safe
class TraversalManager {
private:
class View;
public:
TraversalManager(const BuildGraph& build_graph)
: build_graph_(build_graph), epoch_(kNeverVisited + 1) {}
void initArray() {
visited_ = std::vector<size_t>(build_graph_.graph_.size(), kNeverVisited);
}
View getView();
private:
void nextEpoch() { ++epoch_; }
void DFS(size_t vertex, std::vector<size_t>& current_visited);
private:
static const size_t kCurrentWayEpoch;
static const size_t kNeverVisited;
const BuildGraph &build_graph_;
std::vector<size_t> visited_;
size_t epoch_;
};
public:
BuildGraph(const std::vector<Rule>& rules, std::vector<Task>&& tasks);
void isCorrectOrThrow(size_t target) const;
bool isCorrect(size_t target) const;
std::vector<Target> getRequiredTargetsOrder(size_t target) const;
private:
std::vector<Target> graph_;
mutable TraversalManager traversal_manager_;
};
} // namespace build_system

18
builder.cpp Normal file
View file

@ -0,0 +1,18 @@
#include "builder.hpp"
namespace build_system {
void Builder::execute(const BuildGraph& build_graph, size_t target_id) {
// can throw IncorrectBuildGraph
auto targets_order = build_graph.getRequiredTargetsOrder(target_id);
for (auto& target : targets_order) {
thread_pool_.addTarget(std::move(target.task), target.id, target.dependences);
}
thread_pool_.start();
thread_pool_.wait();
}
}; // namespace build_system

20
builder.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <cstddef>
#include "build_graph.hpp"
#include "thread_pool.hpp"
namespace build_system {
class Builder {
public:
explicit Builder(size_t num_threads) : thread_pool_(num_threads) {}
void execute(const BuildGraph& build_graph, size_t target_id);
private:
ThreadPool thread_pool_;
};
}; // namespace build_system

94
cli_common.cpp Normal file
View file

@ -0,0 +1,94 @@
#include "cli_common.hpp"
#include <chrono>
#include "test_tasks.hpp"
void print(const std::string& message, std::ostream* out) {
if (out) {
*out << message;
}
}
template <typename T>
void getParam(const std::string& message, T &param, std::istream& in, std::ostream* out) {
print(message, out);
in >> param;
}
build_system::Builder constructBuilder(std::istream& in, std::ostream* out) {
print("Builder constructor\n", out);
size_t num_threads;
getParam("num threads: ", num_threads, in, out);
return build_system::Builder(num_threads);
}
build_system::BuildGraph constructBuildGraph(std::istream& in, std::ostream* out) {
print("BuildGraph constructor\n", out);
size_t rules_count = 0;
getParam("rules count: ", rules_count, in, out);
std::vector<build_system::Rule> rules;
rules.reserve(rules_count);
print("rules (one per line, in format \"target dependency\"):\n", out);
for (size_t i = 0; i < rules_count; ++i) {
build_system::Rule rule;
in >> rule.target >> rule.dependency;
--rule.target;
--rule.dependency;
rules.push_back(rule);
}
size_t targets_count = 0;
getParam("targets count: ", targets_count, in, out);
std::vector<build_system::Task> tasks;
tasks.reserve(targets_count);
print("durations of tasks for targets (in seconds): ", out);
for (size_t i = 0; i < targets_count; ++i) {
size_t duration;
in >> duration;
tasks.push_back(constructTestTask(duration));
}
return build_system::BuildGraph(rules, std::move(tasks));
}
size_t constructTargetId(std::istream& in, std::ostream* out) {
size_t target_id;
getParam("target id: ", target_id, in, out);
--target_id;
return target_id;
}
void execute(build_system::Builder& builder,
build_system::BuildGraph& build_graph, size_t target_id,
std::ostream* out) {
try {
auto build_start_time = std::chrono::high_resolution_clock::now();
builder.execute(build_graph, target_id);
auto build_end_time = std::chrono::high_resolution_clock::now();
auto build_time_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
build_end_time - build_start_time);
if (out) {
*out << "overall time: " << build_time_ms.count() << "ms\n";
}
} catch (build_system::IncorrectBuildGraph) {
print("incorrect build graph\n", out);
}
}

26
cli_common.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <cstddef>
#include <iostream>
#include <optional>
#include "build_graph.hpp"
#include "builder.hpp"
void print(const std::string& message, std::ostream* out);
template <typename T>
void getParam(const std::string& message, T& param, std::istream &in,
std::ostream* out);
build_system::Builder constructBuilder(std::istream& in,
std::ostream* out = nullptr);
build_system::BuildGraph constructBuildGraph(std::istream& in,
std::ostream* out = nullptr);
size_t constructTargetId(std::istream& in, std::ostream* out = nullptr);
void execute(build_system::Builder& builder,
build_system::BuildGraph& build_graph, size_t target_id,
std::ostream* out = nullptr);

1
compile_flags.txt Normal file
View file

@ -0,0 +1 @@
-std=c++20

44
dependency_manager.cpp Normal file
View file

@ -0,0 +1,44 @@
#include "dependency_manager.hpp"
namespace build_system {
void DependencyManager::waitAll() {
for (size_t i = 0; i < dependency_counters_.size(); ++i) {
wait(i);
}
}
void DependencyManager::wait(size_t id) {
auto &counter = dependency_counters_[id];
size_t counter_value = counter.load();
while (counter_value != 0) {
counter.wait(counter_value);
counter_value = counter.load();
}
}
size_t DependencyManager::add(size_t id,
const std::vector<size_t>& dependences) {
size_t new_id = dependency_counters_.size();
dependency_counters_.emplace_back(dependences.size());
tasks_consequences_.emplace_back();
for (auto& dependency_id : dependences) {
tasks_consequences_[to_new_id[dependency_id]].push_back(new_id);
}
to_new_id[id] = new_id;
return new_id;
}
void DependencyManager::done(size_t id) {
for (auto &consequence_id : tasks_consequences_[id]) {
if (dependency_counters_[consequence_id].fetch_sub(1) == 1) {
dependency_counters_[consequence_id].notify_all();
}
}
}
}; // namespace build_system

29
dependency_manager.hpp Normal file
View file

@ -0,0 +1,29 @@
#pragma once
#include <atomic>
#include <cstddef>
#include <deque>
#include <unordered_map>
#include <vector>
namespace build_system {
class DependencyManager {
public:
DependencyManager() {}
void waitAll(); // async
void wait(size_t id); // async
size_t add(size_t id, const std::vector<size_t>& dependences); // sync
void done(size_t id); // async
private:
std::deque<std::atomic<size_t>> dependency_counters_;
std::vector<std::vector<size_t>> tasks_consequences_;
std::unordered_map<size_t, size_t> to_new_id;
};
}; // namespace build_system

25
file_mode.cpp Normal file
View file

@ -0,0 +1,25 @@
#include "file_mode.hpp"
#include <cstddef>
#include <iostream>
#include <fstream>
#include "build_graph.hpp"
#include "builder.hpp"
#include "cli_common.hpp"
void fileMode(const std::string& filename) {
std::ifstream in;
in.open(filename);
build_system::Builder builder = constructBuilder(in);
build_system::BuildGraph build_graph = constructBuildGraph(in);
size_t target_id = constructTargetId(in);
execute(builder, build_graph, target_id, &std::cout);
in.close();
}

5
file_mode.hpp Normal file
View file

@ -0,0 +1,5 @@
#pragma once
#include <string>
void fileMode(const std::string& filename);

20
interactive_mode.cpp Normal file
View file

@ -0,0 +1,20 @@
#include "interactive_mode.hpp"
#include <cstddef>
#include <iostream>
#include "build_graph.hpp"
#include "builder.hpp"
#include "cli_common.hpp"
void interactiveMode() {
build_system::Builder builder = constructBuilder(std::cin, &std::cout);
build_system::BuildGraph build_graph = constructBuildGraph(std::cin, &std::cout);
size_t target_id = constructTargetId(std::cin, &std::cout);
execute(builder, build_graph, target_id, &std::cout);
}

3
interactive_mode.hpp Normal file
View file

@ -0,0 +1,3 @@
#pragma once
void interactiveMode();

10
main.cpp Normal file
View file

@ -0,0 +1,10 @@
#include "interactive_mode.hpp"
#include "file_mode.hpp"
int main(int argc, char** argv) {
if (argc < 2) {
interactiveMode();
} else {
fileMode(argv[1]);
}
}

12
task.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <functional>
#include <utility>
namespace build_system {
using Task = std::function<void()>;
using IdentTask = std::pair<Task, size_t>;
}; // namespace build_system

30
task_queue.cpp Normal file
View file

@ -0,0 +1,30 @@
#include "task_queue.hpp"
namespace build_system {
bool TaskQueue::addIdentTask(IdentTask&& task) {
std::lock_guard<std::mutex> lock(mutex_);
if (is_closed_) {
return false;
}
tasks_.push_back(std::move(task));
task_exist_.notify_one();
return true;
}
std::optional<IdentTask> TaskQueue::getIdentTask() {
std::unique_lock<std::mutex> lock(mutex_);
while (tasks_.empty() && !is_closed_) {
task_exist_.wait(lock);
}
if (tasks_.empty()) {
return std::nullopt;
}
IdentTask task = tasks_.front();
tasks_.pop_front();
return std::move(task);
}
}; // namespace build_system

33
task_queue.hpp Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#include <condition_variable>
#include <deque>
#include <mutex>
#include <optional>
#include "task.hpp"
namespace build_system {
class TaskQueue {
public:
TaskQueue() : is_closed_(false) {}
bool addIdentTask(IdentTask&& task);
std::optional<IdentTask> getIdentTask();
void close() {
std::lock_guard<std::mutex> lock(mutex_);
is_closed_ = true;
task_exist_.notify_all();
}
private:
std::deque<IdentTask> tasks_;
bool is_closed_;
std::mutex mutex_;
std::condition_variable task_exist_;
};
}; // namespace build_system

13
test_tasks.hpp Normal file
View file

@ -0,0 +1,13 @@
#include <chrono>
#include <cstddef>
#include <thread>
#include <iostream>
#include "task.hpp"
inline build_system::Task constructTestTask(size_t duration) {
return [duration]() {
std::this_thread::sleep_for(std::chrono::seconds(duration));
};
}

47
thread_pool.cpp Normal file
View file

@ -0,0 +1,47 @@
#include "thread_pool.hpp"
#include <optional>
#include "task.hpp"
namespace build_system {
void ThreadPool::addTarget(Task&& task, size_t id,
const std::vector<size_t>& dependences) {
size_t new_id = dependency_manager_.add(id, dependences);
task_queue_.addIdentTask({std::move(task), new_id});
}
void ThreadPool::start() {
threads_.reserve(num_threads_);
for (size_t i = 0; i < num_threads_; ++i) {
threads_.emplace_back([this]() {
while (true) {
auto may_be_task = task_queue_.getIdentTask();
if (may_be_task == std::nullopt) {
break;
}
dependency_manager_.wait(may_be_task.value().second);
try {
may_be_task.value().first();
} catch (...) {
}
dependency_manager_.done(may_be_task.value().second);
}
});
}
}
void ThreadPool::wait() {
task_queue_.close();
dependency_manager_.waitAll();
for (auto& thread : threads_) {
thread.join();
}
}
}; // namespace build_system

30
thread_pool.hpp Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include <cstddef>
#include <functional>
#include <thread>
#include <vector>
#include "dependency_manager.hpp"
#include "task_queue.hpp"
namespace build_system {
class ThreadPool {
public:
explicit ThreadPool(size_t num_threads) : num_threads_(num_threads) {}
void addTarget(Task&& task, size_t id, const std::vector<size_t>& dependences);
void start();
void wait();
private:
size_t num_threads_;
std::vector<std::thread> threads_;
TaskQueue task_queue_;
DependencyManager dependency_manager_;
};
}; // namespace build_system