diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | builder.cpp | 296 | ||||
| -rw-r--r-- | builder.h | 29 | ||||
| -rw-r--r-- | xmake.cpp | 257 | 
4 files changed, 331 insertions, 253 deletions
| @@ -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; +}; @@ -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") { | 
