summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2024-05-03 11:22:55 +0200
committerRoland Reichwein <mail@reichwein.it>2024-05-03 11:22:55 +0200
commit5ecd32cb842defe38e14dcaeb0caa2d98356a0bb (patch)
tree36d394c727b98f0d14553eaf54cb63dfc1e4dedc
parentcb2b1059904124b8122ebe490fb504c62189d153 (diff)
Factored out Builder
-rw-r--r--Makefile2
-rw-r--r--builder.cpp296
-rw-r--r--builder.h29
-rw-r--r--xmake.cpp257
4 files changed, 331 insertions, 253 deletions
diff --git a/Makefile b/Makefile
index 63816fc..5ca10a0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
PROJECTNAME=xmake
-SRC=main.cpp xmake.cpp
+SRC=main.cpp xmake.cpp builder.cpp
OBJ=$(SRC:.cpp=.o)
diff --git a/builder.cpp b/builder.cpp
new file mode 100644
index 0000000..0493bb3
--- /dev/null
+++ b/builder.cpp
@@ -0,0 +1,296 @@
+#include "builder.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <filesystem>
+#include <iostream>
+#include <iterator>
+#include <sstream>
+#include <stack>
+#include <stdexcept>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/xml_parser.hpp>
+#include <boost/process.hpp>
+
+#include <fmt/format.h>
+
+#include <libreichwein/file.h>
+#include <libreichwein/stringhelper.h>
+
+namespace fs = std::filesystem;
+namespace bp = boost::process;
+namespace pt = boost::property_tree;
+using namespace std::string_literals;
+
+namespace {
+ fs::path get_target(const pt::ptree& ptree) {
+ return ptree.get<std::string>("xmake.build.name");
+ }
+
+ std::vector<fs::path> get_sources(const pt::ptree& ptree) {
+ std::vector<fs::path> sources;
+ for (const pt::ptree::value_type &v: ptree.get_child("xmake.build")) {
+ if (v.first == "source")
+ sources.push_back(v.second.data());
+ }
+ return sources;
+ }
+
+ std::vector<fs::path> get_objects(const pt::ptree& ptree) {
+ std::vector<fs::path> objects{get_sources(ptree)};
+ for (auto &i: objects) {
+ i.replace_extension("o");
+ }
+ return objects;
+ }
+
+ // both need to exist
+ bool is_older(const fs::path& p, const fs::path& other) {
+ auto t_p{fs::last_write_time(p)};
+ auto t_other{fs::last_write_time(other)};
+ return t_p < t_other;
+ }
+
+ // outdated according to dependency file list, non-recursively
+ bool is_outdated(const fs::path& p, const std::vector<fs::path> &dependencies) {
+ if (!fs::exists(p))
+ return true;
+
+ for (const auto& dep: dependencies) {
+ if (!fs::exists(dep) || is_older(p, dep)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ std::vector<fs::path> deps_from_depfile(const fs::path& path) {
+ std::string depfile_content{Reichwein::File::getFile(path)};
+ std::vector<std::string> parts {Reichwein::Stringhelper::split(depfile_content, ":\r\n")};
+ if (parts.size() >= 2) {
+ std::vector<std::string> deps {Reichwein::Stringhelper::split(parts[1], " ")};
+ std::vector<fs::path> result;
+ std::transform(deps.cbegin(), deps.cend(), std::back_inserter(result), [](const std::string& s){ return s; });
+ return result;
+ } else {
+ throw std::runtime_error("Bad depfile contents: "s + path.string());
+ }
+ }
+
+ fs::path depfile_name_from(const fs::path& p) {
+ fs::path depfile{p};
+ depfile.replace_extension("d");
+ return depfile;
+ }
+
+ // return contained dependencies
+ // input: cpp
+ std::vector<fs::path> make_depfile_from(const fs::path& p) {
+ fs::path depfile{depfile_name_from(p)};
+
+ // check if depfile exists and if it contains up to date info
+ if (!fs::exists(depfile) || is_outdated(depfile, deps_from_depfile(depfile))) {
+ // actually create depfile
+ int result{system(fmt::format("g++ -MM -MF {} -c {}", depfile.string(), p.string()).c_str())};
+ if (result != 0) {
+ throw std::runtime_error(fmt::format("Depfile {} can't be created", depfile.string()));
+ }
+ }
+
+ return deps_from_depfile(depfile);
+ }
+
+ // type of file can be built from dependencies
+ bool is_buildable_by_extension(const fs::path& p) {
+ fs::path ext{p.extension()};
+ return ext.empty() || ext == ".o";
+ }
+
+ std::unordered_map<fs::path, std::vector<fs::path>> get_dependencies(const pt::ptree& ptree) {
+ std::unordered_map<fs::path, std::vector<fs::path>> dependencies;
+ dependencies.emplace(get_target(ptree), get_objects(ptree));
+ std::vector<fs::path> sources{get_sources(ptree)};
+ for (const auto& p: sources) {
+ fs::path p_obj{p};
+ p_obj.replace_extension("o");
+ std::vector<fs::path> deps {make_depfile_from(p)};
+ fs::remove(depfile_name_from(p));
+ dependencies.emplace(p_obj, deps);
+ }
+ return dependencies;
+ }
+
+}
+
+Builder::Builder(const pt::ptree& ptree):
+ _target{get_target(ptree)},
+ _objects{get_objects(ptree)},
+ _sources{get_sources(ptree)},
+ _dependencies{get_dependencies(ptree)}
+{
+}
+
+std::vector<fs::path> Builder::dependencies_of(const fs::path& p) {
+ try {
+ return _dependencies.at(p);
+ } catch (const std::out_of_range& ex) {
+ return {}; // empty by default
+ }
+}
+
+// outdated according to dependency tree, recursively
+bool Builder::is_outdated(const fs::path& p) {
+ if (!fs::exists(p))
+ return true;
+
+ std::vector<fs::path> deps{dependencies_of(p)};
+ for (const auto& dep: deps) {
+ if (!fs::exists(dep) || is_older(p, dep)) {
+ return true;
+ }
+
+ if (is_outdated(dep)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// build 1 file
+void Builder::build(const fs::path& p) {
+ std::string command;
+
+ if (p.extension() == ".o") {
+ // compile
+ fs::path cppfile{p};
+ cppfile.replace_extension("cpp");
+ command = fmt::format("g++ -std=c++17 -c {} -o {}", cppfile.string(), p.string());
+ } else {
+ // link
+ command = "g++";
+ std::vector<fs::path> objects{dependencies_of(p)};
+ for (auto &i: objects) {
+ command += fmt::format(" {}", i.string());
+ }
+ command += " -lfmt -lreichwein";
+ command += fmt::format(" -o {}", p.string());
+ }
+
+ std::cout << command << std::endl;
+ int result{system(command.c_str())};
+ if (result != 0) {
+ throw std::runtime_error(fmt::format("Error {}", result));
+ }
+}
+
+// build list of files
+// eats up input
+void Builder::build(std::unordered_set<fs::path>& buildlist) {
+ std::unordered_set<fs::path> activelist; // currently building
+ std::unordered_set<fs::path> donelist; // build done
+
+ if (buildlist.empty()) {
+ std::cout << "Everything up to date." << std::endl;
+ } else {
+ std::cout << "Running commands: " << std::endl;
+ }
+
+ while (!buildlist.empty()) {
+ // find file which can be built directly since its build dependencies are up to date
+ // this holds for either any member of the set (e.g. at begin()), or any of its direct or indirect dependencies
+ fs::path current {*buildlist.begin()};
+
+ bool found_outdated{}; // outdated dependencies
+
+ do {
+ found_outdated = false;
+ std::vector<fs::path> deps{dependencies_of(current)};
+ for (auto& i: deps) {
+ if (activelist.find(i) == activelist.end() && is_outdated(i)) {
+ found_outdated = true;
+ current = i;
+ break;
+ }
+ }
+ } while (found_outdated);
+
+ buildlist.erase(current);
+
+ activelist.insert(current);
+ // TODO: build in background
+ build(current);
+ activelist.erase(current);
+
+ donelist.insert(current);
+ }
+}
+
+// build everything according to specified configuration
+void Builder::build() {
+ std::cout << "Target: " << _target << std::endl;
+
+ std::cout << "Sources: " << std::endl;
+ for (auto &i: _sources) {
+ std::cout << " " << i << std::endl;
+ }
+
+ // create build list by depth-first search
+ std::cout << "Calculating build list..." << std::endl;
+ std::unordered_set<fs::path> buildlist;
+ std::stack<fs::path> container; // temporary container for search algorithm
+ container.push(_target);
+ while (!container.empty()) {
+ fs::path current{container.top()};
+ container.pop();
+
+ std::vector<fs::path> deps{dependencies_of(current)};
+ for (auto &i: deps) {
+ container.push(i);
+ }
+
+ if (is_outdated(current) && is_buildable_by_extension(current)) {
+ buildlist.insert(current);
+ }
+ }
+
+ std::cout << "Build list:" << std::endl;
+ for (auto &i: buildlist) {
+ std::cout << " " << i << std::endl;
+ }
+
+ build(buildlist);
+}
+
+void Builder::clean() {
+ std::vector<fs::path> cleanlist{_objects};
+ cleanlist.push_back(_target);
+
+ std::vector<std::string> commands;
+ for (auto &i: cleanlist) {
+ if (fs::exists(i)) {
+ commands.push_back(fmt::format("rm -f {}", i.string()));
+ }
+ fs::path dep{i};
+ dep.replace_extension("d");
+ if (fs::exists(dep)) {
+ commands.push_back(fmt::format("rm -f {}", dep.string()));
+ }
+ }
+
+ std::cout << "Running commands: " << std::endl;
+ for (auto &i: commands) {
+ std::cout << i << std::endl;
+ int result{system(i.c_str())};
+ if (result != 0) {
+ throw std::runtime_error(fmt::format("Error {}", result));
+ }
+ }
+}
+
diff --git a/builder.h b/builder.h
new file mode 100644
index 0000000..c139d2a
--- /dev/null
+++ b/builder.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <filesystem>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <boost/property_tree/ptree.hpp>
+
+class Builder
+{
+public:
+ Builder(const boost::property_tree::ptree& ptree);
+
+ void build();
+ void clean();
+
+private:
+ std::vector<std::filesystem::path> dependencies_of(const std::filesystem::path& p);
+ bool is_outdated(const std::filesystem::path& p);
+
+ void build(const std::filesystem::path& p);
+ void build(std::unordered_set<std::filesystem::path>& buildlist);
+
+ std::filesystem::path _target;
+ std::vector<std::filesystem::path> _objects;
+ std::vector<std::filesystem::path> _sources;
+ std::unordered_map<std::filesystem::path, std::vector<std::filesystem::path>> _dependencies;
+};
diff --git a/xmake.cpp b/xmake.cpp
index a1831e8..5a12bb4 100644
--- a/xmake.cpp
+++ b/xmake.cpp
@@ -1,5 +1,7 @@
#include "xmake.h"
+#include "builder.h"
+
#include <algorithm>
#include <cstdlib>
#include <filesystem>
@@ -35,256 +37,6 @@ namespace {
std::cout << "Usage: xmake <target>" << std::endl;
}
- fs::path get_target(const pt::ptree& ptree) {
- return ptree.get<std::string>("xmake.build.name");
- }
-
- std::vector<fs::path> get_sources(const pt::ptree& ptree) {
- std::vector<fs::path> sources;
- for (const pt::ptree::value_type &v: ptree.get_child("xmake.build")) {
- if (v.first == "source")
- sources.push_back(v.second.data());
- }
- return sources;
- }
-
- std::vector<fs::path> get_objects(const pt::ptree& ptree) {
- std::vector<fs::path> objects{get_sources(ptree)};
- for (auto &i: objects) {
- i.replace_extension("o");
- }
- return objects;
- }
-
- // both need to exist
- bool is_older(const fs::path& p, const fs::path& other) {
- auto t_p{fs::last_write_time(p)};
- auto t_other{fs::last_write_time(other)};
- return t_p < t_other;
- }
-
- // outdated according to dependency file list, non-recursively
- bool is_outdated(const fs::path& p, const std::vector<fs::path> &dependencies) {
- if (!fs::exists(p))
- return true;
-
- for (const auto& dep: dependencies) {
- if (!exists(dep) || is_older(p, dep)) {
- return true;
- }
- }
-
- return false;
- }
-
- std::vector<fs::path> dependencies_of(const fs::path& p, const std::unordered_map<fs::path, std::vector<fs::path>> &dependencies) {
- try {
- return dependencies.at(p);
- } catch (const std::out_of_range& ex) {
- return {}; // empty by default
- }
- }
-
- // outdated according to dependency tree, recursively
- bool is_outdated(const fs::path& p, const std::unordered_map<fs::path, std::vector<fs::path>> &dependencies) {
- if (!fs::exists(p))
- return true;
-
- std::vector<fs::path> deps{dependencies_of(p, dependencies)};
- for (const auto& dep: deps) {
- if (!exists(dep) || is_older(p, dep)) {
- return true;
- }
-
- if (is_outdated(dep, dependencies)) {
- return true;
- }
- }
-
- return false;
- }
-
- std::vector<fs::path> deps_from_depfile(const fs::path& path) {
- std::string depfile_content{Reichwein::File::getFile(path)};
- std::vector<std::string> parts {Reichwein::Stringhelper::split(depfile_content, ":\r\n")};
- if (parts.size() >= 2) {
- std::vector<std::string> deps {Reichwein::Stringhelper::split(parts[1], " ")};
- std::vector<fs::path> result;
- std::transform(deps.cbegin(), deps.cend(), std::back_inserter(result), [](const std::string& s){ return s; });
- return result;
- } else {
- throw std::runtime_error("Bad depfile contents: "s + path.string());
- }
- }
-
- // return contained dependencies
- // input: cpp
- std::vector<fs::path> make_depfile_from(const fs::path& p) {
- fs::path depfile{p};
- depfile.replace_extension("d");
-
- // check if depfile exists and if it contains up to date info
- if (!fs::exists(depfile) || is_outdated(depfile, deps_from_depfile(depfile))) {
- // actually create depfile
- int result{system(fmt::format("g++ -MM -MF {} -c {}", depfile.string(), p.string()).c_str())};
- if (result != 0) {
- throw std::runtime_error(fmt::format("Depfile {} can't be created", depfile.string()));
- }
- }
-
- return deps_from_depfile(depfile);
- }
-
- // type of file can be built from dependencies
- bool is_buildable_by_extension(const fs::path& p) {
- fs::path ext{p.extension()};
- return ext.empty() || ext == ".o";
- }
-
- // build 1 file
- void build(const fs::path& p, const std::unordered_map<fs::path, std::vector<fs::path>> &dependencies) {
- std::string command;
-
- if (p.extension() == ".o") {
- // compile
- fs::path cppfile{p};
- cppfile.replace_extension("cpp");
- command = fmt::format("g++ -std=c++17 -c {} -o {}", cppfile.string(), p.string());
- } else {
- // link
- command = "g++";
- std::vector<fs::path> objects{dependencies_of(p, dependencies)};
- for (auto &i: objects) {
- command += fmt::format(" {}", i.string());
- }
- command += " -lfmt -lreichwein";
- command += fmt::format(" -o {}", p.string());
- }
-
- std::cout << command << std::endl;
- int result{system(command.c_str())};
- if (result != 0) {
- throw std::runtime_error(fmt::format("Error {}", result));
- }
- }
-
- // build list of files
- // eats up input
- void build(std::unordered_set<fs::path>& buildlist, const std::unordered_map<fs::path, std::vector<fs::path>> &dependencies) {
- std::unordered_set<fs::path> activelist; // currently building
- std::unordered_set<fs::path> donelist; // build done
-
- if (buildlist.empty()) {
- std::cout << "Everything up to date." << std::endl;
- } else {
- std::cout << "Running commands: " << std::endl;
- }
-
- while (!buildlist.empty()) {
- // find file which can be built directly since its build dependencies are up to date
- // this holds for either any member of the set (e.g. at begin()), or any of its direct or indirect dependencies
- fs::path current {*buildlist.begin()};
-
- bool found_outdated{}; // outdated dependencies
-
- do {
- found_outdated = false;
- std::vector<fs::path> deps{dependencies_of(current, dependencies)};
- for (auto& i: deps) {
- if (activelist.find(i) == activelist.end() && is_outdated(i, dependencies)) {
- found_outdated = true;
- current = i;
- break;
- }
- }
- } while (found_outdated);
-
- buildlist.erase(current);
-
- activelist.insert(current);
- // TODO: build in background
- build(current, dependencies);
- activelist.erase(current);
-
- donelist.insert(current);
- }
- }
-
- // build everything according to specified configuration
- void build(const pt::ptree& ptree) {
- fs::path target{get_target(ptree)};
- std::vector<fs::path> objects{get_objects(ptree)};
- std::vector<fs::path> sources{get_sources(ptree)};
-
- std::cout << "Target: " << target << std::endl;
-
- std::cout << "Sources: " << std::endl;
- for (auto &i: sources) {
- std::cout << " " << i << std::endl;
- }
-
- std::cout << "Calculating dependencies..." << std::endl;
- std::unordered_map<fs::path, std::vector<fs::path>> dependencies;
- dependencies.emplace(target, objects);
- for (const auto& p: sources) {
- fs::path p_obj{p};
- p_obj.replace_extension("o");
- std::vector<fs::path> deps {make_depfile_from(p)};
- dependencies.emplace(p_obj, deps);
- }
-
- // create build list by depth-first search
- std::cout << "Calculating build list..." << std::endl;
- std::unordered_set<fs::path> buildlist;
- std::stack<fs::path> container; // temporary container for search algorithm
- container.push(target);
- while (!container.empty()) {
- fs::path current{container.top()};
- container.pop();
-
- std::vector<fs::path> deps{dependencies_of(current, dependencies)};
- for (auto &i: deps) {
- container.push(i);
- }
-
- if (is_outdated(current, dependencies) && is_buildable_by_extension(current)) {
- buildlist.insert(current);
- }
- }
-
- std::cout << "Build list:" << std::endl;
- for (auto &i: buildlist) {
- std::cout << " " << i << std::endl;
- }
-
- build(buildlist, dependencies);
- }
-
- void clean(const pt::ptree& ptree) {
- std::vector<fs::path> cleanlist{get_objects(ptree)};
- cleanlist.push_back(get_target(ptree));
-
- std::vector<std::string> commands;
- for (auto &i: cleanlist) {
- if (fs::exists(i)) {
- commands.push_back(fmt::format("rm -f {}", i.string()));
- }
- fs::path dep{i};
- dep.replace_extension("d");
- if (fs::exists(dep)) {
- commands.push_back(fmt::format("rm -f {}", dep.string()));
- }
- }
-
- std::cout << "Running commands: " << std::endl;
- for (auto &i: commands) {
- std::cout << i << std::endl;
- int result{system(i.c_str())};
- if (result != 0) {
- throw std::runtime_error(fmt::format("Error {}", result));
- }
- }
- }
}
int xmake(int argc, char* argv[])
@@ -304,10 +56,11 @@ int xmake(int argc, char* argv[])
exit(1);
}
+ Builder builder(ptree);
if (action == "default"s || action == "build") { // build
- build(ptree);
+ builder.build();
} else if (action == "clean") {
- clean(ptree);
+ builder.clean();
} else if (action == "install") {
throw std::runtime_error("unimplemented");
} else if (action == "rebuild") {