#include "xmake.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; namespace bp = boost::process; namespace pt = boost::property_tree; using namespace std::string_literals; namespace { const fs::path XMakefile{"XMakefile"}; void usage() { std::cout << "Usage: xmake " << std::endl; } fs::path get_target(const pt::ptree& ptree) { return ptree.get("xmake.build.name"); } std::vector get_sources(const pt::ptree& ptree) { std::vector 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 get_objects(const pt::ptree& ptree) { std::vector 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; } bool is_outdated(const fs::path& p, const std::vector &dependencies) { if (!fs::exists(p)) return true; for (auto& dep: dependencies) { if (!exists(dep) || is_older(p, dep)) { return true; } } return false; } std::vector deps_from_depfile(const fs::path& path) { std::string depfile_content{Reichwein::File::getFile(path)}; std::vector parts {Reichwein::Stringhelper::split(depfile_content, ":\r\n")}; if (parts.size() >= 2) { std::vector deps {Reichwein::Stringhelper::split(parts[1], " ")}; std::vector 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 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); } void build(const pt::ptree& ptree) { fs::path target{get_target(ptree)}; std::vector objects{get_objects(ptree)}; std::vector 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> dependencies; dependencies.emplace(target, objects); for (const auto& p: sources) { fs::path p_obj{p}; p_obj.replace_extension("o"); std::vector deps {make_depfile_from(p)}; dependencies.emplace(p_obj, deps); } std::vector commands; // compile for (auto &p: sources) { fs::path p_obj{p}; p_obj.replace_extension("o"); if (is_outdated(p_obj, std::vector{p})) { commands.push_back(fmt::format("g++ -std=c++17 -c {} -o {}", p.string(), p_obj.string())); } } // link std::vector deps{sources}; if (is_outdated(target, deps)) { std::string link_command{"g++"}; for (auto &i: objects) { link_command += fmt::format(" {}", i.string()); } link_command += " -lfmt -lreichwein"; link_command += fmt::format(" -o {}", target.string()); commands.push_back(link_command); } std::cout << "Commands: " << std::endl; for (auto &i: commands) { std::cout << " " << i << std::endl; } if (commands.size() == 0) { std::cout << "Everything up to date." << std::endl; } else { 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)); } } } } void clean(const pt::ptree& ptree) { std::vector cleanlist{get_objects(ptree)}; cleanlist.push_back(get_target(ptree)); std::vector commands; for (auto &i: cleanlist) { commands.push_back(fmt::format("rm -f {}", i.string())); fs::path dep{i}; dep.replace_extension("d"); 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[]) { try { pt::ptree ptree; pt::read_xml(XMakefile, ptree, pt::xml_parser::no_comments | pt::xml_parser::trim_whitespace); std::string action{"default"}; if (argc == 1) { // default action } else if (argc == 2) { action = argv[1]; } else { std::cerr << "Invalid arguments." << std::endl; usage; exit(1); } if (action == "default"s || action == "build") { // build build(ptree); } else if (action == "clean") { clean(ptree); } else if (action == "install") { throw std::runtime_error("unimplemented"); } else if (action == "rebuild") { throw std::runtime_error("unimplemented"); } else if (action == "run") { throw std::runtime_error("unimplemented"); } else { std::cerr << "Invalid action: " << action << std::endl; usage; exit(1); } } catch (const std::exception& ex) { std::cerr << "xmake: " << ex.what() << std::endl; return 1; } return 0; }