diff options
-rw-r--r-- | Builder.cpp | 124 | ||||
-rw-r--r-- | Builder.h | 10 | ||||
-rw-r--r-- | LanguageSettings.cpp | 44 | ||||
-rw-r--r-- | LanguageSettings.h | 4 | ||||
-rw-r--r-- | ymake.cpp | 15 |
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)); + } + } +} + @@ -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; @@ -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") { |