summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2024-05-09 15:37:06 +0200
committerRoland Reichwein <mail@reichwein.it>2024-05-09 15:37:06 +0200
commit242b03bc8da841a9527ad845eb60275008155afb (patch)
treeca36edb74ccfe34662c8a2b1c72295df77b50254
parent7dabda20e11138f2235b12271d569cd3256ebf3a (diff)
Run tests
-rw-r--r--Builder.cpp124
-rw-r--r--Builder.h10
-rw-r--r--LanguageSettings.cpp44
-rw-r--r--LanguageSettings.h4
-rw-r--r--ymake.cpp15
5 files changed, 132 insertions, 65 deletions
diff --git a/Builder.cpp b/Builder.cpp
index 86f6920..57f731e 100644
--- a/Builder.cpp
+++ b/Builder.cpp
@@ -17,6 +17,7 @@
#include <unordered_set>
#include <vector>
+#include <boost/process.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
@@ -25,25 +26,36 @@
#include <libreichwein/file.h>
#include <libreichwein/stringhelper.h>
+namespace bp = boost::process;
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
using namespace std::chrono_literals;
using namespace std::string_literals;
namespace {
+ const std::string topelement{"ymake"};
+
+ bool is_match(const pt::ptree::value_type& element, const std::string& target) {
+ if (target == "*") {
+ return true;
+ } else {
+ return element.first == target;
+ }
+ }
+
// get name of single target, ptree is <build> subtree
fs::path get_target(const pt::ptree& ptree) {
return ptree.get<std::string>("name");
}
// ptree is main tree
- std::vector<fs::path> get_all_targets(const pt::ptree& ptree) {
+ std::vector<fs::path> get_all_targets(const pt::ptree& ptree, const std::string& target) {
std::vector<fs::path> result;
// iterate over all <build> elements
- for (const auto& build: ptree.get_child("ymake")) {
- if (build.first == "build") {
- result.push_back(build.second.get<std::string>("name"));
+ for (const auto& element: ptree.get_child(topelement)) {
+ if (is_match(element, target)) {
+ result.push_back(element.second.get<std::string>("name"));
}
}
@@ -60,25 +72,15 @@ namespace {
return sources;
}
- // get link libs of single target, ptree is <build> subtree
- std::vector<std::string> get_link_libs_of_build(const pt::ptree& ptree) {
- std::vector<std::string> linklibs;
- for (const pt::ptree::value_type &v: ptree) {
- if (v.first == "linklib")
- linklibs.push_back(v.second.data());
- }
- return linklibs;
- }
-
// ptree is main tree
- std::vector<fs::path> get_all_sources(const pt::ptree& ptree) {
+ std::vector<fs::path> get_all_sources(const pt::ptree& ptree, const std::string& target) {
std::vector<fs::path> result;
// iterate over all <build> elements
- for (const auto& build: ptree.get_child("ymake")) {
- if (build.first == "build") {
+ for (const auto& element: ptree.get_child(topelement)) {
+ if (is_match(element, target)) {
// iterate over all <source> elements
- for (const auto& source: build.second) {
+ for (const auto& source: element.second) {
if (source.first == "source") {
result.push_back(source.second.data());
}
@@ -100,8 +102,8 @@ namespace {
}
// ptree is main tree
- std::vector<fs::path> get_all_objects(const pt::ptree& ptree) {
- std::vector<fs::path> objects{get_all_sources(ptree)};
+ std::vector<fs::path> get_all_objects(const pt::ptree& ptree, const std::string& target) {
+ std::vector<fs::path> objects{get_all_sources(ptree, target)};
for (auto &i: objects) {
i.replace_extension("o");
}
@@ -134,28 +136,44 @@ namespace {
return depfile;
}
- std::unordered_map<fs::path, std::vector<std::string>> get_link_libs(const pt::ptree& ptree) {
- std::unordered_map<fs::path, std::vector<std::string>> link_libs_map;
+ // T = std::string, fs::path
+ template<typename T>
+ // get specified subelements (as list) of single target, ptree is <build> subtree
+ std::vector<T> get_subelements_of_build(const pt::ptree& ptree, const std::string& subelement) {
+ std::vector<T> result;
+ for (const pt::ptree::value_type &v: ptree) {
+ if (v.first == subelement) {
+ result.push_back(v.second.data());
+ }
+ }
+ return result;
+ }
- for (const auto& build: ptree.get_child("ymake")) {
- if (build.first == "build") {
- std::vector<std::string> link_libs{get_link_libs_of_build(build.second)};
- if (!link_libs.empty()) {
- link_libs_map.emplace(get_target(build.second), link_libs);
+ // T = std::string, fs::path
+ template<typename T>
+ std::unordered_map<fs::path, std::vector<T>> get_subelements(const pt::ptree& ptree, const std::string& target, const std::string& subelement) {
+ std::unordered_map<fs::path, std::vector<T>> result_map;
+
+ for (const auto& element: ptree.get_child(topelement)) {
+ if (is_match(element, target)) {
+ std::vector<T> subelements{get_subelements_of_build<T>(element.second, subelement)};
+ if (!subelements.empty()) {
+ result_map.emplace(get_target(element.second), subelements);
}
}
}
- return link_libs_map;
+ return result_map;
}
-
}
-Builder::Builder(const pt::ptree& ptree):
+Builder::Builder(const pt::ptree& ptree, const std::string& target):
_ptree(ptree),
- _all_targets{get_all_targets(ptree)},
- _all_objects{get_all_objects(ptree)},
- _link_libs{get_link_libs(ptree)}
+ _target(target),
+ _all_targets{get_all_targets(ptree, target)},
+ _all_objects{get_all_objects(ptree, target)},
+ _include_paths{get_subelements<fs::path>(ptree, target, "includepath")},
+ _link_libs{get_subelements<std::string>(ptree, target, "linklib")}
{
// intentionally defer creation of _dependencies to build()
// to prevent creation of .d files in clean()
@@ -179,6 +197,15 @@ std::vector<std::string> Builder::link_libs_of(const fs::path& p) const
}
}
+std::vector<fs::path> Builder::include_paths_of(const fs::path& p) const
+{
+ try {
+ return _include_paths.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) const
{
@@ -236,11 +263,11 @@ std::unordered_map<fs::path, std::vector<fs::path>> Builder::get_dependencies(co
{
std::unordered_map<fs::path, std::vector<fs::path>> dependencies;
- for (const auto& build: ptree.get_child("ymake")) {
- if (build.first == "build") {
- dependencies.emplace(get_target(build.second), get_objects(build.second));
+ for (const auto& element: ptree.get_child(topelement)) {
+ if (is_match(element, _target)) {
+ dependencies.emplace(get_target(element.second), get_objects(element.second));
- std::vector<fs::path> sources{get_sources(build.second)};
+ std::vector<fs::path> sources{get_sources(element.second)};
for (const auto& p: sources) {
fs::path p_obj{p};
p_obj.replace_extension("o");
@@ -266,7 +293,8 @@ void Builder::build_file(const fs::path& p) {
if (it == source_files.end()) {
throw std::runtime_error(fmt::format("No source file found for {}", p.string()));
}
- command = _lang.getCompileCommand(p, *it);
+ //std::cout << "DEBUG: " << (*_include_paths.begin()).first << " " << (*_include_paths.begin()).second.size() << " " << p.string() << std::endl;
+ command = _lang.getCompileCommand(p, *it, include_paths_of(p));
} else {
// link
std::vector<fs::path> objects{dependencies_of(p)};
@@ -278,7 +306,7 @@ void Builder::build_file(const fs::path& p) {
_runner.spawn(p.string(), command.c_str());
}
-void Builder::cleanup() {
+void Builder::cleanup_buildlist() {
std::string path;
int exit_code{_runner.wait_one(path)};
_activelist.erase(fs::path{path});
@@ -322,7 +350,7 @@ void Builder::build_filelist() {
if (current.empty()) {
std::this_thread::sleep_for(10ms); // short wait before retry
if (_runner.finished() > 0) {
- cleanup();
+ cleanup_buildlist();
}
}
}
@@ -332,7 +360,7 @@ void Builder::build_filelist() {
// wait until process slot is available
while (_runner.full() || _runner.finished() > 0) {
- cleanup();
+ cleanup_buildlist();
}
build_file(current); // calls spawn() on _runner
@@ -340,7 +368,7 @@ void Builder::build_filelist() {
// final cleanup
while (_runner.finished() != 0 || _runner.running() != 0) {
- cleanup();
+ cleanup_buildlist();
}
if (!_activelist.empty()) {
@@ -409,3 +437,15 @@ void Builder::clean() const {
}
}
+// build tests and run them
+void Builder::run_tests() {
+ for (const auto& i: _all_targets) {
+ std::string command{(fs::path("./") / i).string()};
+ std::cout << "Running test: " << command << std::endl;
+ int result {bp::system(command)};
+ if (result != 0) {
+ throw std::runtime_error("Test failed: " + i.string() + ", exit code: " + std::to_string(result));
+ }
+ }
+}
+
diff --git a/Builder.h b/Builder.h
index 3169c3b..b167e65 100644
--- a/Builder.h
+++ b/Builder.h
@@ -1,6 +1,7 @@
#pragma once
#include <filesystem>
+#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -13,14 +14,17 @@
class Builder
{
public:
- Builder(const boost::property_tree::ptree& ptree);
+ Builder(const boost::property_tree::ptree& ptree, const std::string& target);
void build();
+ void run_tests();
+
void clean() const;
private:
std::unordered_map<std::filesystem::path, std::vector<std::filesystem::path>> get_dependencies(const boost::property_tree::ptree& ptree) const;
std::vector<std::filesystem::path> dependencies_of(const std::filesystem::path& p) const;
+ std::vector<std::filesystem::path> include_paths_of(const std::filesystem::path& p) const;
std::vector<std::string> link_libs_of(const std::filesystem::path& p) const;
bool is_outdated(const std::filesystem::path& p) const;
bool is_outdated(const std::filesystem::path& p, const std::vector<std::filesystem::path> &dependencies) const;
@@ -29,12 +33,14 @@ private:
void build_file(const std::filesystem::path& p);
void build_filelist();
- void cleanup();
+ void cleanup_buildlist();
const boost::property_tree::ptree _ptree;
+ std::string _target;
std::vector<std::filesystem::path> _all_targets;
std::vector<std::filesystem::path> _all_objects;
std::unordered_map<std::filesystem::path, std::vector<std::filesystem::path>> _dependencies;
+ std::unordered_map<std::filesystem::path, std::vector<std::filesystem::path>> _include_paths;
std::unordered_map<std::filesystem::path, std::vector<std::string>> _link_libs;
LanguageSettings _lang;
diff --git a/LanguageSettings.cpp b/LanguageSettings.cpp
index 3ded5cf..57a550d 100644
--- a/LanguageSettings.cpp
+++ b/LanguageSettings.cpp
@@ -1,7 +1,9 @@
#include "LanguageSettings.h"
+#include <algorithm>
#include <cstdlib>
#include <iostream>
+#include <numeric>
#include <stdexcept>
#include <string>
#include <vector>
@@ -11,6 +13,7 @@
#include <libreichwein/stringhelper.h>
namespace fs = std::filesystem;
+using namespace std::string_literals;
namespace {
std::string env_value(const std::string& key) {
@@ -65,10 +68,20 @@ LanguageSettings::LanguageSettings():
}
}
-std::string LanguageSettings::getCompileCommand(const std::filesystem::path& target, const std::filesystem::path &source) const
+std::string LanguageSettings::getCompileCommand(const std::filesystem::path& target,
+ const std::filesystem::path &source,
+ const std::vector<std::filesystem::path>& includepaths) const
{
+ std::string includes{std::accumulate(includepaths.begin(), includepaths.end(), std::string{},
+ [](const std::string& sum, const fs::path& p){ return sum + " " + p.string();})};
+
// compile: $(CXX) $(CXXFLAGS) -c $< -o $@
- return fmt::format("{} {} -c {} -o {}", CXX, CXXFLAGS, source.string(), target.string());
+ return fmt::format("{}{}{} -c {} -o {}",
+ CXX,
+ CXXFLAGS.empty() ? ""s : (" "s + CXXFLAGS),
+ includes,
+ source.string(),
+ target.string());
}
std::string LanguageSettings::getLinkCommand(const std::filesystem::path& target,
@@ -77,23 +90,20 @@ std::string LanguageSettings::getLinkCommand(const std::filesystem::path& target
{
// link: $(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LIBS) -o $@
- std::string input_string;
- for (const auto& i: inputs) {
- if (!input_string.empty()) {
- input_string += " ";
- }
- input_string += i.string();
- }
+ std::string input_string{std::accumulate(inputs.begin(), inputs.end(), std::string{},
+ [](const std::string& sum, const fs::path& p){ return sum + " " + p.string(); })};
- std::string link_libs_string;
- for (const auto& i: link_libs) {
- if (!link_libs_string.empty()) {
- link_libs_string += " ";
- }
- link_libs_string += "-l" + i;
- }
+ std::string link_libs_string{std::accumulate(link_libs.begin(), link_libs.end(), std::string(),
+ [](const std::string& sum, const std::string& i){ return sum + " -l" + i; })};
- return fmt::format("{} {} {} {} {} {} -o {}", CXX, CXXFLAGS, input_string, LDLIBS, LIBS, link_libs_string, target.string());
+ return fmt::format("{}{}{}{}{}{} -o {}",
+ CXX,
+ CXXFLAGS.empty() ? ""s : (" "s + CXXFLAGS),
+ input_string,
+ LDLIBS.empty() ? ""s : (" "s + LDLIBS),
+ LIBS.empty() ? ""s : (" "s + LIBS),
+ link_libs_string,
+ target.string());
}
std::string LanguageSettings::getDepCommand(const std::filesystem::path& target, const std::filesystem::path &source) const
diff --git a/LanguageSettings.h b/LanguageSettings.h
index 97519e3..0b4701c 100644
--- a/LanguageSettings.h
+++ b/LanguageSettings.h
@@ -9,7 +9,9 @@ class LanguageSettings
public:
LanguageSettings();
- std::string getCompileCommand(const std::filesystem::path& target, const std::filesystem::path &source) const;
+ std::string getCompileCommand(const std::filesystem::path& target,
+ const std::filesystem::path &source,
+ const std::vector<std::filesystem::path>& includepaths) const;
std::string getLinkCommand(const std::filesystem::path& target,
const std::vector<std::filesystem::path> &inputs,
const std::vector<std::string>& link_libs) const;
diff --git a/ymake.cpp b/ymake.cpp
index a3eec6a..cc3e00e 100644
--- a/ymake.cpp
+++ b/ymake.cpp
@@ -34,7 +34,11 @@ using namespace std::string_literals;
namespace {
void usage()
{
- std::cout << "Usage: ymake <target>" << std::endl;
+ std::cout << "Usage: ymake [<action>]" << std::endl;
+ std::cout << " with <action> one of:" << std::endl;
+ std::cout << " build (same as no action specified)" << std::endl;
+ std::cout << " clean" << std::endl;
+ std::cout << " test" << std::endl;
}
}
@@ -55,11 +59,16 @@ int ymake(int argc, char* argv[])
exit(1);
}
- Builder builder(ptree);
- if (action == "default"s || action == "build") { // build
+ if (action == "default" || action == "build") { // build
+ Builder builder(ptree, "build");
builder.build();
} else if (action == "clean") {
+ Builder builder(ptree, "*");
builder.clean();
+ } else if (action == "test") {
+ Builder builder(ptree, "test");
+ builder.build();
+ builder.run_tests();
} else if (action == "install") {
throw std::runtime_error("unimplemented");
} else if (action == "rebuild") {