diff options
author | Roland Reichwein <mail@reichwein.it> | 2024-05-05 17:15:49 +0200 |
---|---|---|
committer | Roland Reichwein <mail@reichwein.it> | 2024-05-05 17:15:49 +0200 |
commit | 81bcfee54362c736e865ebeb638aeadfa9cc9e24 (patch) | |
tree | 4da9b8f2f233dacf4c0da72e1f26b55181a58be3 | |
parent | 28609f436966f731f91e84d10c1d7d0621b4abe8 (diff) |
Factored out LanguageSettings
-rw-r--r-- | Builder.cpp | 147 | ||||
-rw-r--r-- | Builder.h | 7 | ||||
-rw-r--r-- | LanguageSettings.cpp | 104 | ||||
-rw-r--r-- | LanguageSettings.h | 26 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | test-ymake.cpp | 1 | ||||
-rw-r--r-- | ymake.cpp | 4 |
7 files changed, 233 insertions, 58 deletions
diff --git a/Builder.cpp b/Builder.cpp index 51ac90f..86f6920 100644 --- a/Builder.cpp +++ b/Builder.cpp @@ -60,6 +60,16 @@ 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> result; @@ -105,20 +115,6 @@ namespace { 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")}; @@ -138,43 +134,19 @@ namespace { 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); - } - - 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; + 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; for (const auto& build: ptree.get_child("ymake")) { if (build.first == "build") { - dependencies.emplace(get_target(build.second), get_objects(build.second)); - - std::vector<fs::path> sources{get_sources(build.second)}; - for (const auto& p: sources) { - fs::path p_obj{p}; - p_obj.replace_extension("o"); - std::vector<fs::path> deps {make_depfile_from(p)}; - // keep .d files for now to speed dependencies detection on following runs - //fs::remove(depfile_name_from(p)); - dependencies.emplace(p_obj, deps); + 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); } } } - return dependencies; + return link_libs_map; } } @@ -182,7 +154,8 @@ namespace { Builder::Builder(const pt::ptree& ptree): _ptree(ptree), _all_targets{get_all_targets(ptree)}, - _all_objects{get_all_objects(ptree)} + _all_objects{get_all_objects(ptree)}, + _link_libs{get_link_libs(ptree)} { // intentionally defer creation of _dependencies to build() // to prevent creation of .d files in clean() @@ -197,6 +170,15 @@ std::vector<fs::path> Builder::dependencies_of(const fs::path& p) const } } +std::vector<std::string> Builder::link_libs_of(const fs::path& p) const +{ + try { + return _link_libs.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 { @@ -217,24 +199,79 @@ bool Builder::is_outdated(const fs::path& p) const return false; } +// outdated according to dependency file list, non-recursively +bool Builder::is_outdated(const fs::path& p, const std::vector<fs::path> &dependencies) const +{ + if (!fs::exists(p)) + return true; + + for (const auto& dep: dependencies) { + if (!fs::exists(dep) || is_older(p, dep)) { + return true; + } + } + + return false; +} + +// return contained dependencies +// input: cpp +std::vector<fs::path> Builder::make_depfile_from(const fs::path& p) const +{ + 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(_lang.getDepCommand(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); +} + +std::unordered_map<fs::path, std::vector<fs::path>> Builder::get_dependencies(const pt::ptree& ptree) const +{ + 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)); + + std::vector<fs::path> sources{get_sources(build.second)}; + for (const auto& p: sources) { + fs::path p_obj{p}; + p_obj.replace_extension("o"); + std::vector<fs::path> deps {make_depfile_from(p)}; + // keep .d files for now to speed dependencies detection on following runs + //fs::remove(depfile_name_from(p)); + dependencies.emplace(p_obj, deps); + } + } + } + + return dependencies; +} + // build 1 file void Builder::build_file(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()); + // compile + std::vector<fs::path> source_files{dependencies_of(p)}; + auto it{std::find_if(source_files.begin(), source_files.end(), is_compile_unit_source_by_extension)}; + if (it == source_files.end()) { + throw std::runtime_error(fmt::format("No source file found for {}", p.string())); + } + command = _lang.getCompileCommand(p, *it); } else { // link - command = "g++"; std::vector<fs::path> objects{dependencies_of(p)}; - for (auto &i: objects) { - command += fmt::format(" {}", i.string()); - } - command += " -lreichwein -lfmt"; - command += fmt::format(" -o {}", p.string()); + std::vector<std::string> link_libs{link_libs_of(p)}; + command = _lang.getLinkCommand(p, objects, link_libs); } std::cout << command << std::endl; @@ -7,6 +7,7 @@ #include <boost/property_tree/ptree.hpp> +#include "LanguageSettings.h" #include "ProcessRunner.h" class Builder @@ -18,8 +19,12 @@ public: 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::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; + std::vector<std::filesystem::path> make_depfile_from(const std::filesystem::path& p) const; void build_file(const std::filesystem::path& p); void build_filelist(); @@ -30,7 +35,9 @@ private: 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::string>> _link_libs; + LanguageSettings _lang; ProcessRunner _runner; std::unordered_set<std::filesystem::path> _buildlist; // build done diff --git a/LanguageSettings.cpp b/LanguageSettings.cpp new file mode 100644 index 0000000..3ded5cf --- /dev/null +++ b/LanguageSettings.cpp @@ -0,0 +1,104 @@ +#include "LanguageSettings.h" + +#include <cstdlib> +#include <iostream> +#include <stdexcept> +#include <string> +#include <vector> + +#include <fmt/format.h> + +#include <libreichwein/stringhelper.h> + +namespace fs = std::filesystem; + +namespace { + std::string env_value(const std::string& key) { + char* value_ptr{std::getenv(key.c_str())}; + if (value_ptr == nullptr) { + return {}; + } else { + return value_ptr; + } + } + + // returns "" if not found + std::string find_executable(const std::vector<std::string>& list) { + std::vector<std::string> paths {Reichwein::Stringhelper::split(env_value("PATH"), ":")}; + + for (const auto& i: list) { + for (const auto& j: paths) { + if (fs::exists(j + "/" + i)) { + return i; + } + } + } + + return {}; + } + +} + +LanguageSettings::LanguageSettings(): + // C++ + CXX{env_value("CXX")}, + CXXFLAGS{env_value("CXXFLAGS")}, + LDFLAGS{env_value("LDFLAGS")}, + LDLIBS{env_value("LDLIBS")}, + LIBS{env_value("LIBS")} + // C + // CFLAGS +{ + std::string _default_compiler(find_executable({"g++", "clang++"})); + + if (_default_compiler.empty()) { + throw std::runtime_error("No default C++ compiler found."); + } + + // set defaults + if (CXX.empty()) { + CXX = _default_compiler; + } + + if (CXXFLAGS.empty()) { + CXXFLAGS = "-std=c++17"; + } +} + +std::string LanguageSettings::getCompileCommand(const std::filesystem::path& target, const std::filesystem::path &source) const +{ + // compile: $(CXX) $(CXXFLAGS) -c $< -o $@ + return fmt::format("{} {} -c {} -o {}", CXX, CXXFLAGS, source.string(), target.string()); +} + +std::string LanguageSettings::getLinkCommand(const std::filesystem::path& target, + const std::vector<std::filesystem::path> &inputs, + const std::vector<std::string>& link_libs) const +{ + // 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 link_libs_string; + for (const auto& i: link_libs) { + if (!link_libs_string.empty()) { + link_libs_string += " "; + } + link_libs_string += "-l" + i; + } + + return fmt::format("{} {} {} {} {} {} -o {}", CXX, CXXFLAGS, input_string, LDLIBS, LIBS, link_libs_string, target.string()); +} + +std::string LanguageSettings::getDepCommand(const std::filesystem::path& target, const std::filesystem::path &source) const +{ + // g++ -MM -MF <depfile> -c <cppfile> + return fmt::format("{} -MM -MF {} -c {}", CXX, target.string(), source.string()); +} + diff --git a/LanguageSettings.h b/LanguageSettings.h new file mode 100644 index 0000000..97519e3 --- /dev/null +++ b/LanguageSettings.h @@ -0,0 +1,26 @@ +#pragma once + +#include <filesystem> +#include <string> +#include <vector> + +class LanguageSettings +{ +public: + LanguageSettings(); + + std::string getCompileCommand(const std::filesystem::path& target, const std::filesystem::path &source) const; + std::string getLinkCommand(const std::filesystem::path& target, + const std::vector<std::filesystem::path> &inputs, + const std::vector<std::string>& link_libs) const; + std::string getDepCommand(const std::filesystem::path& target, const std::filesystem::path &source) const; + +private: + std::string CXX; + std::string CXXFLAGS; + + std::string LDFLAGS; + std::string LDLIBS; + std::string LIBS; +}; + @@ -1,6 +1,6 @@ PROJECTNAME=ymake -SRC=main.cpp ymake.cpp Builder.cpp ProcessRunner.cpp file.cpp +SRC=main.cpp ymake.cpp Builder.cpp ProcessRunner.cpp file.cpp LanguageSettings.cpp OBJ=$(SRC:.cpp=.o) YSCAN=yscan diff --git a/test-ymake.cpp b/test-ymake.cpp index fcd424f..a6edc5f 100644 --- a/test-ymake.cpp +++ b/test-ymake.cpp @@ -197,6 +197,7 @@ TEST_F(ymakeTest, build_three_cpp) // TODO: test .c .cc .cpp // TODO: multiple builds // TODO: test tests +// TODO: test g++/clang++ TEST_F(yscanTest, no_cpp_file) { @@ -51,7 +51,7 @@ int ymake(int argc, char* argv[]) action = argv[1]; } else { std::cerr << "Invalid arguments." << std::endl; - usage; + usage(); exit(1); } @@ -68,7 +68,7 @@ int ymake(int argc, char* argv[]) throw std::runtime_error("unimplemented"); } else { std::cerr << "Invalid action: " << action << std::endl; - usage; + usage(); exit(1); } |