diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/argparser.h | 477 | ||||
-rw-r--r-- | src/bootstrap.cc | 71 | ||||
-rw-r--r-- | src/build.cc | 89 | ||||
-rw-r--r-- | src/build.h | 5 | ||||
-rw-r--r-- | src/configure.cc | 352 | ||||
-rw-r--r-- | src/configure.h | 4 | ||||
-rw-r--r-- | src/ctor.h | 85 | ||||
-rw-r--r-- | src/deps.cc | 120 | ||||
-rw-r--r-- | src/deps.h | 12 | ||||
-rw-r--r-- | src/execute.cc | 76 | ||||
-rw-r--r-- | src/execute.h | 9 | ||||
-rw-r--r-- | src/externals_manual.cc | 8 | ||||
m--------- | src/getoptpp | 0 | ||||
-rw-r--r-- | src/libctor.cc | 217 | ||||
-rw-r--r-- | src/pointerlist.cc | 123 | ||||
-rw-r--r-- | src/pointerlist.h | 74 | ||||
-rw-r--r-- | src/rebuild.cc | 184 | ||||
-rw-r--r-- | src/rebuild.h | 18 | ||||
-rw-r--r-- | src/task.cc | 28 | ||||
-rw-r--r-- | src/task.h | 6 | ||||
-rw-r--r-- | src/task_ar.cc | 30 | ||||
-rw-r--r-- | src/task_cc.cc | 78 | ||||
-rw-r--r-- | src/task_cc.h | 1 | ||||
-rw-r--r-- | src/task_fn.cc | 35 | ||||
-rw-r--r-- | src/task_ld.cc | 36 | ||||
-rw-r--r-- | src/task_ld.h | 4 | ||||
-rw-r--r-- | src/task_so.cc | 34 | ||||
-rw-r--r-- | src/tasks.cc | 37 | ||||
-rw-r--r-- | src/tasks.h | 6 | ||||
-rw-r--r-- | src/tools.cc | 242 | ||||
-rw-r--r-- | src/tools.h | 15 | ||||
-rw-r--r-- | src/unittest.cc | 2 | ||||
-rw-r--r-- | src/util.cc | 197 | ||||
-rw-r--r-- | src/util.h | 16 |
34 files changed, 2047 insertions, 644 deletions
diff --git a/src/argparser.h b/src/argparser.h new file mode 100644 index 0000000..c5337e0 --- /dev/null +++ b/src/argparser.h @@ -0,0 +1,477 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <functional> +#include <variant> +#include <optional> +#include <string> +#include <type_traits> +#include <stdexcept> +#include <iostream> +#include <limits> + +namespace arg +{ +struct noarg {}; +template<typename T> +struct Opt +{ + char shortopt; + std::string longopt; + std::function<int(T)> cb; + std::string help; + T t{}; +}; + +template<> +struct Opt<noarg> +{ + char shortopt; + std::string longopt; + std::function<int()> cb; + std::string help; + noarg t{}; +}; + +template<typename Callable, typename... Args> +auto call_if(Callable cb, Args... args) +{ + using Ret = std::invoke_result_t<decltype(cb), Args&&...>; + if constexpr (std::is_same_v<Ret, void>) + { + if(cb) + { + return cb(std::forward<Args>(args)...); + } + } + else + { + if(cb) + { + return cb(std::forward<Args>(args)...); + } + return Ret{}; + } +} + +enum class error +{ + missing_arg, + invalid_arg, + invalid_opt, +}; + +template<typename... Ts> +class Parser +{ +public: + struct missing_arg{}; + + Parser(int argc_, const char* const* argv_) + : argc(argc_) + , argv(argv_) + { + } + + int parse() const + { + bool demarcate{false}; + for(int i = 1; i < argc; ++i) // skip argv[0] which is program name + { + std::string_view arg{argv[i]}; + if(arg.size() == 0) + { + // Empty arg - This shouldn't happen + continue; + } + + if(arg[0] != '-' || demarcate) // positional arg + { + auto res = call_if(pos_cb, arg); + if(res != 0) + { + return res; + } + continue; + } + + if(arg == "--") + { + demarcate = true; + continue; + } + + bool was_handled{false}; + enum class state { handled, unhandled }; + + if(arg.size() > 1 && arg[0] == '-' && arg[1] == '-') // long + { + for(const auto& option : options) + { + auto ret = + std::visit([&](auto&& opt) -> std::pair<int, state> + { + if(opt.longopt != arg && + !arg.starts_with(opt.longopt+'=')) + { + return {0, state::unhandled}; + } + try + { + using T = std::decay_t<decltype(opt)>; + if constexpr (std::is_same_v<T, Opt<noarg>>) + { + return {opt.cb(), state::handled}; + } + else + { + return {opt.cb(convert(i, opt.t)), + state::handled}; + } + } + catch(std::invalid_argument&) + { + call_if(err_cb, error::invalid_arg, argv[i]); + return {1, state::handled}; + } + catch(missing_arg&) + { + call_if(err_cb, error::missing_arg, argv[i]); + return {1, state::handled}; + } + }, option); + if(ret.second == state::handled && ret.first != 0) + { + return ret.first; + } + was_handled |= ret.second == state::handled; + if(was_handled) + { + break; + } + } + } + else + if(arg.size() > 1 && arg[0] == '-') // short + { + for(auto index = 1u; index < arg.size(); ++index) + { + was_handled = false; + for(const auto& option : options) + { + auto ret = + std::visit([&](auto&& opt) -> std::pair<int, state> + { + char c = arg[index]; + if(opt.shortopt != c) + { + return {0, state::unhandled}; + } + try + { + using T = std::decay_t<decltype(opt)>; + if constexpr (std::is_same_v<T, Opt<noarg>>) + { + return {opt.cb(), state::handled}; + } + else + { + // Note: the rest of arg is converted to opt + auto idx = index; + // set index out of range all was eaten as arg + index = std::numeric_limits<int>::max(); + return {opt.cb(convert_short(&arg[idx], + i, opt.t)), + state::handled}; + } + } + catch(std::invalid_argument&) + { + call_if(err_cb, error::invalid_arg, argv[i]); + return {1, state::handled}; + } + catch(missing_arg&) + { + call_if(err_cb, error::missing_arg, argv[i]); + return {1, state::handled}; + } + }, option); + if(ret.second == state::handled && ret.first != 0) + { + return ret.first; + } + was_handled |= ret.second == state::handled; + if(was_handled) + { + break; + } + } + } + } + + if(!was_handled) + { + call_if(err_cb, error::invalid_opt, arg); + return 1; + } + } + return 0; + } + + template<typename T> + void add(char shortopt, + const std::string& longopt, + std::function<int(T)> cb, + const std::string& help) + { + options.emplace_back(Opt<T>{shortopt, longopt, cb, help}); + } + + void add(char shortopt, + const std::string& longopt, + std::function<int()> cb, + const std::string& help) + { + options.emplace_back(Opt<noarg>{shortopt, longopt, cb, help}); + } + + void set_pos_cb(std::function<int(std::string_view)> cb) + { + pos_cb = cb; + } + + void set_err_cb(std::function<void(error, std::string_view)> cb) + { + err_cb = cb; + } + + std::string prog_name() const + { + if(argc < 1) + { + return {}; + } + return argv[0]; + } + + void help() const + { + constexpr std::size_t width{26}; + constexpr std::size_t column_width{80}; + + for(const auto& option : options) + { + std::visit( + [&](auto&& opt) + { + std::string _args; + using T = std::decay_t<decltype(opt)>; + if constexpr (std::is_same_v<T, Opt<noarg>>) + { + } + else if constexpr (std::is_same_v<T, Opt<int>>) + { + _args = "<int>"; + } + else if constexpr (std::is_same_v<T, Opt<std::optional<int>>>) + { + _args = "[int]"; + } + else if constexpr (std::is_same_v<T, Opt<std::string>>) + { + _args = "<str>"; + } + else if constexpr (std::is_same_v<T, Opt<std::optional<std::string>>>) + { + _args = "[str]"; + } + else if constexpr (std::is_same_v<T, Opt<double>>) + { + _args = "<real>"; + } + else if constexpr (std::is_same_v<T, Opt<std::optional<double>>>) + { + _args = "[real]"; + } + else + { + static_assert(std::is_same_v<T, void>, "missing"); + } + + std::string option_str; + if(opt.shortopt != '\0' && !opt.longopt.empty()) + { + option_str = " -" + std::string(1, opt.shortopt) + ", " + + opt.longopt + " " + _args; + } + else if(opt.shortopt != '\0') + { + option_str = " -" + std::string(1, opt.shortopt) + _args; + } + else if(!opt.longopt.empty()) + { + option_str = " " + std::string(opt.longopt) + " " + _args; + } + + std::string padding; + if(option_str.size() < width) + { + padding.append(width - option_str.size(), ' '); + } + else + { + padding = "\n"; + padding.append(width, ' '); + } + + std::cout << option_str << padding; + + auto i = width; + for(auto c : opt.help) + { + if((c == '\n') || (i > column_width && (c == ' ' || c == '\t'))) + { + std::string _padding(width, ' '); + std::cout << '\n' << _padding; + i = width; + continue; + } + std::cout << c; + ++i; + } + std::cout << '\n'; + }, option); + } + } + +private: + template<typename T> + T convert(int& i, T) const + { + auto opt = convert(i, std::optional<T>{}); + if(!opt) + { + throw missing_arg{}; + } + return *opt; + } + + template<typename T> + std::optional<T> convert(int& i, std::optional<T>) const + { + std::string arg; + bool has_arg{false}; + std::string opt = argv[i]; + if(opt.starts_with("--")) + { + // long opt + auto equals_pos = opt.find('='); + if(equals_pos != std::string::npos) + { + arg = opt.substr(equals_pos + 1); + has_arg = true; + } + else if(i+1 < argc) + { + arg = argv[i+1]; + has_arg = !arg.starts_with("-"); + if(has_arg) + { + ++i; + } + } + } + + if(!has_arg) + { + return {}; + } + + if constexpr (std::is_same_v<T, int>) + { + return std::stoi(arg); + } + else if constexpr (std::is_same_v<T, double>) + { + return std::stod(arg); + } + else if constexpr (std::is_same_v<T, std::string>) + { + return arg; + } + else + { + static_assert(std::is_same_v<T, void>, "missing"); + } + return {}; + } + + template<typename T> + T convert_short(const char* arg_, int& i, T) const + { + auto opt = convert_short(arg_, i, std::optional<T>{}, false); + if(!opt) + { + throw missing_arg{}; + } + return *opt; + } + + template<typename T> + std::optional<T> convert_short(const char* arg_, int& i, + std::optional<T>, bool optional = true) const + { + std::string arg; + bool has_arg{false}; + std::string opt = arg_; + if(opt.length() > 1) + { + // arg in same token + arg = opt.substr(1); + has_arg = true; + } + else if(!optional && i+1 < argc) + { + arg = argv[i+1]; + has_arg = true;//!arg.starts_with("-"); + if(has_arg) + { + ++i; + } + } + + if(!has_arg) + { + return {}; + } + + if constexpr (std::is_same_v<T, int>) + { + return std::stoi(arg); + } + else if constexpr (std::is_same_v<T, double>) + { + return std::stod(arg); + } + else if constexpr (std::is_same_v<T, std::string>) + { + return arg; + } + else + { + static_assert(std::is_same_v<T, void>, "missing"); + } + return {}; + } + + using Opts = std::variant<Opt<noarg>, Opt<Ts>...>; + std::vector<Opts> options; + std::function<int(std::string_view)> pos_cb; + int argc; + const char* const* argv; + std::function<void(error, std::string_view)> err_cb; +}; + +} // arg:: diff --git a/src/bootstrap.cc b/src/bootstrap.cc index 55bd3e0..836504e 100644 --- a/src/bootstrap.cc +++ b/src/bootstrap.cc @@ -4,6 +4,7 @@ #include <iostream> #include <array> #include <cstdlib> +#include <span> #define BOOTSTRAP @@ -18,9 +19,10 @@ #include "tasks.cc" #include "build.cc" #include "tools.cc" +#include "pointerlist.cc" -std::filesystem::path configurationFile("configuration.cc"); -std::filesystem::path configHeaderFile("config.h"); +const std::filesystem::path configurationFile("configuration.cc"); +const std::filesystem::path configHeaderFile("config.h"); const ctor::configuration& ctor::get_configuration() { @@ -40,46 +42,75 @@ bool ctor::configuration::has(const std::string& key) const return false; } -const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const +std::string ctor::configuration::get(const std::string& key, const std::string& default_value) const { - if(key == ctor::cfg::build_cxx && std::getenv("CXX")) + static auto paths = get_paths(); + auto cxx_env = std::getenv("CXX"); + if(key == ctor::cfg::build_cxx && cxx_env) { - static std::string s = std::getenv("CXX"); - return s; + static auto cxx_prog = locate(cxx_env, paths); + return cxx_prog; } - if(key == ctor::cfg::build_cc && std::getenv("CC")) + auto cc_env = std::getenv("CC"); + if(key == ctor::cfg::build_cc && cc_env) { - static std::string s = std::getenv("CC"); - return s; + static auto cc_prog = locate(cc_env, paths); + return cc_prog; } - if(key == ctor::cfg::build_ld && std::getenv("LD")) + auto ld_env = std::getenv("LD"); + if(key == ctor::cfg::build_ld && ld_env) { - static std::string s = std::getenv("LD"); - return s; + static auto ld_prog = locate(ld_env, paths); + return ld_prog; } - if(key == ctor::cfg::build_ar && std::getenv("AR")) + auto ar_env = std::getenv("AR"); + if(key == ctor::cfg::build_ar && ar_env) { - static std::string s = std::getenv("AR"); - return s; + static auto ar_prog = locate(ar_env, paths); + return ar_prog; } - if(key == ctor::cfg::builddir && std::getenv("BUILDDIR")) + auto builddir_env = std::getenv("BUILDDIR"); + if(key == ctor::cfg::builddir && builddir_env) { - static std::string s = std::getenv("BUILDDIR"); - return s; + return builddir_env; } return default_value; } +std::string ctor::configuration::getenv(const std::string& key) const +{ + auto envit = env.find(key); + if(envit != env.end()) + { + return envit->second; + } + + auto env = std::getenv(key.data()); + if(env) + { + return env; + } + + return {}; +} + +std::vector<std::string> readDeps(const std::string& depFile, + ctor::toolchain toolchain) +{ + return {}; +} + int main(int argc, char* argv[]) { - if(argc > 1) + auto args = std::span(argv, static_cast<std::size_t>(argc)); + if(args.size() > 1) { - std::cerr << "This is a minimal bootstrap version of " << argv[0] << + std::cerr << "This is a minimal bootstrap version of " << args[0] << " which doesn't support any arguments\n"; return 1; } diff --git a/src/build.cc b/src/build.cc index ea65656..5995fb7 100644 --- a/src/build.cc +++ b/src/build.cc @@ -38,7 +38,7 @@ int build(const ctor::settings& settings, // Dry-run returns number of dirty tasks but otherwise does nothing. if(dryrun) { - return dirtyTasks.size(); + return static_cast<int>(dirtyTasks.size()); } if(dirtyTasks.empty()) @@ -65,7 +65,7 @@ int build(const ctor::settings& settings, break; } - auto task = getNextTask(all_tasks, dirtyTasks); + auto task = getNextTask(settings, all_tasks, dirtyTasks); if(task == nullptr) { if(processes.empty() && !dirtyTasks.empty()) @@ -114,17 +114,15 @@ int build(const ctor::settings& settings, } } - for(auto process = processes.begin(); - process != processes.end(); - ++process) + for(auto& process : processes) { - if(process->valid() == false) + if(process.valid() == false) { continue; } - process->wait(); - auto ret = process->get(); - if(ret != 0) + process.wait(); + auto ret = process.get(); + if (ret != 0) { return ret; } @@ -133,29 +131,46 @@ int build(const ctor::settings& settings, return 0; } -namespace -{ -std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task) +std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task, + std::vector<std::shared_ptr<Task>> trace) { std::vector<std::shared_ptr<Task>> tasks; tasks.push_back(task); + trace.push_back(task); auto deps = task->getDependsTasks(); for(const auto& dep : deps) { - auto depSet = getDepTasks(dep); - for(const auto& dep : depSet) + if(std::find(trace.begin(), trace.end(), dep) != trace.end()) { - if(std::find(tasks.begin(), tasks.end(), dep) == tasks.end()) + trace.push_back(dep); + std::cerr << "Error: Cyclic dependency detected: "; + bool first{true}; + for(auto t : trace) { - tasks.push_back(dep); + if(!first) + { + std::cerr << " -> "; + } + + first = false; + std::cerr << t->target(); + } + std::cerr << '\n'; + throw 1; + } + auto depSet = getDepTasks(dep, trace); + for(const auto& dep_inner : depSet) + { + if(std::find(tasks.begin(), tasks.end(), dep_inner) == tasks.end()) + { + tasks.push_back(dep_inner); } } } return tasks; } -} int build(const ctor::settings& settings, const std::string& name, @@ -169,20 +184,27 @@ int build(const ctor::settings& settings, { task_found = true; - auto depSet = getDepTasks(task); - std::vector<std::shared_ptr<Task>> ts; - for(const auto& task : depSet) + try { - if(std::find(ts.begin(), ts.end(), task) == ts.end()) + auto depSet = getDepTasks(task); + std::vector<std::shared_ptr<Task>> ts; + for(const auto& task_inner : depSet) { - ts.push_back(task); + if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + { + ts.push_back(task_inner); + } } - } - auto ret = build(settings, name, ts, all_tasks, dryrun); - if(ret != 0) + auto ret = build(settings, name, ts, all_tasks, dryrun); + if(ret != 0) + { + return ret; + } + } + catch(...) { - return ret; + return 1; // cycle detected } break; @@ -216,14 +238,21 @@ int build(const ctor::settings& settings, { task_found = true; - auto depSet = getDepTasks(task); - for(const auto& task : depSet) + try { - if(std::find(ts.begin(), ts.end(), task) == ts.end()) + auto depSet = getDepTasks(task); + for(const auto& task_inner : depSet) { - ts.push_back(task); + if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + { + ts.push_back(task_inner); + } } } + catch(...) + { + return 1; // cycle detected + } } } } diff --git a/src/build.h b/src/build.h index 500fb7f..7296f76 100644 --- a/src/build.h +++ b/src/build.h @@ -33,3 +33,8 @@ int build(const ctor::settings& settings, const std::vector<Target>& targets, const std::vector<std::shared_ptr<Task>>& all_tasks, bool dryrun = false); + +// Recursively build vector of dependency tasks from source task. +// Throws if a cycle is detected. +std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task, + std::vector<std::shared_ptr<Task>> trace = {}); diff --git a/src/configure.cc b/src/configure.cc index c08ed88..910b878 100644 --- a/src/configure.cc +++ b/src/configure.cc @@ -7,8 +7,8 @@ #include <filesystem> #include <fstream> #include <optional> - -#include <getoptpp/getoptpp.hpp> +#include <span> +#include <cstring> #include "execute.h" #include "ctor.h" @@ -17,9 +17,10 @@ #include "externals.h" #include "tools.h" #include "util.h" +#include "argparser.h" -std::filesystem::path configurationFile("configuration.cc"); -std::filesystem::path configHeaderFile("config.h"); +const std::filesystem::path configurationFile("configuration.cc"); +const std::filesystem::path configHeaderFile("config.h"); std::map<std::string, std::string> external_includedir; std::map<std::string, std::string> external_libdir; @@ -30,7 +31,11 @@ const ctor::configuration& __attribute__((weak)) ctor::get_configuration() static bool initialised{false}; if(!initialised) { - cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, "/usr/bin/g++")); + std::string cxx_prog{"c++"}; + get_env("CXX", cxx_prog); + + cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, cxx_prog)); + initialised = true; } return cfg; @@ -68,7 +73,8 @@ bool ctor::configuration::has(const std::string& key) const return tools.find(key) != tools.end(); } -const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const +std::string ctor::configuration::get(const std::string& key, + const std::string& default_value) const { if(key == ctor::cfg::ctor_includedir && ctor::includedir) { @@ -90,14 +96,51 @@ const std::string& ctor::configuration::get(const std::string& key, const std::s return ctor::conf_values[key]; } - if(has(key)) + if(tools.find(key) != tools.end()) { return tools.at(key); } + std::string value; + if(key == ctor::cfg::build_cxx && get_env("CXX", value)) + { + return value; + } + + if(key == ctor::cfg::build_cc && get_env("CC", value)) + { + return value; + } + + if(key == ctor::cfg::build_ld && get_env("LD", value)) + { + return value; + } + + if(key == ctor::cfg::build_ar && get_env("AR", value)) + { + return value; + } + return default_value; } +std::string ctor::configuration::getenv(const std::string& key) const +{ + auto envit = env.find(key); + if(envit != env.end()) + { + return envit->second; + } + + if(std::string value; get_env(key.data(), value)) + { + return value; + } + + return {}; +} + class Args : public std::vector<char*> { @@ -105,20 +148,16 @@ public: Args(const std::vector<std::string>& args) { resize(args.size() + 1); - (*this)[0] = strdup("./ctor"); + owning_container.push_back("./ctor"); + (*this)[0] = owning_container.back().data(); for(std::size_t i = 0; i < size() - 1; ++i) { - (*this)[i + 1] = strdup(args[i].data()); + owning_container.push_back(args[i]); + (*this)[i + 1] = owning_container.back().data(); } } - ~Args() - { - for(std::size_t i = 0; i < size(); ++i) - { - free((*this)[i]); - } - } + std::deque<std::string> owning_container; }; namespace { @@ -209,8 +248,7 @@ int regenerateCache(ctor::settings& settings, { Args vargs(args); - dg::Options opt; - int key{128}; + arg::Parser<std::string> opt(static_cast<int>(vargs.size()), vargs.data()); std::string build_arch_prefix; std::string build_path; @@ -224,118 +262,133 @@ int regenerateCache(ctor::settings& settings, std::string ctor_libdir; std::string builddir; - opt.add("build-dir", required_argument, 'b', - "Set output directory for build files (default: '" + - settings.builddir + "').", - [&]() { - settings.builddir = optarg; - builddir = optarg; + opt.add('b', "--build-dir", + std::function([&](std::string arg) + { + settings.builddir = arg; + builddir = arg; return 0; - }); + }), + "Set output directory for build files (default: '" + + settings.builddir + "')."); - opt.add("verbose", no_argument, 'v', - "Be verbose. Add multiple times for more verbosity.", - [&]() { + opt.add('v', "--verbose", + std::function([&]() + { settings.verbose++; return 0; - }); + }), + "Be verbose. Add multiple times for more verbosity."); - opt.add("cc", required_argument, key++, - "Use specified c-compiler instead of gcc.", - [&]() { - cc_prog = optarg; + opt.add({}, "--cc", + std::function([&](std::string arg) + { + cc_prog = arg; return 0; - }); + }), + "Use specified c-compiler instead of gcc."); - opt.add("cxx", required_argument, key++, - "Use specified c++-compiler instead of g++.", - [&]() { - cxx_prog = optarg; + opt.add({}, "--cxx", + std::function([&](std::string arg) + { + cxx_prog = arg; return 0; - }); + }), + "Use specified c++-compiler instead of g++."); - opt.add("ar", required_argument, key++, - "Use specified archiver instead of ar.", - [&]() { - ar_prog = optarg; + opt.add({}, "--ar", + std::function([&](std::string arg) + { + ar_prog = arg; return 0; - }); + }), + "Use specified archiver instead of ar."); - opt.add("ld", required_argument, key++, - "Use specified linker instead of ld.", - [&]() { - ld_prog = optarg; + opt.add({}, "--ld", + std::function([&](std::string arg) + { + ld_prog = arg; return 0; - }); + }), + "Use specified linker instead of ld."); - opt.add("build", required_argument, key++, - "Configure for building on specified architecture.", - [&]() { - build_arch_prefix = optarg; + opt.add({}, "--build", + std::function([&](std::string arg) + { + build_arch_prefix = arg; return 0; - }); + }), + "Configure for building on specified architecture."); - opt.add("build-path", required_argument, key++, - "Set path to build tool-chain.", - [&]() { - build_path = optarg; + opt.add({}, "--build-path", + std::function([&](std::string arg) + { + build_path = arg; return 0; - }); + }), + "Set path to build tool-chain."); - opt.add("host", required_argument, key++, - "Cross-compile to build programs to run on specified architecture.", - [&]() { - host_arch_prefix = optarg; + opt.add({}, "--host", + std::function([&](std::string arg) + { + host_arch_prefix = arg; return 0; - }); + }), + "Cross-compile to build programs to run on specified architecture."); - opt.add("host-path", required_argument, key++, - "Set path to cross-compile tool-chain.", - [&]() { - host_path = optarg; + opt.add({}, "--host-path", + std::function([&](std::string arg) + { + host_path = arg; return 0; - }); + }), + "Set path to cross-compile tool-chain."); - opt.add("ctor-includedir", required_argument, key++, - "Set path to ctor header file, used for re-compiling.", - [&]() { - ctor_includedir = optarg; + opt.add({}, "--ctor-includedir", + std::function([&](std::string arg) + { + ctor_includedir = arg; return 0; - }); + }), + "Set path to ctor header file, used for re-compiling."); - opt.add("ctor-libdir", required_argument, key++, - "Set path to ctor library file, used for re-compiling.", - [&]() { - ctor_libdir = optarg; + opt.add({}, "--ctor-libdir", + std::function([&](std::string arg) + { + ctor_libdir = arg; return 0; - }); + }), + "Set path to ctor library file, used for re-compiling."); // Resolv externals ctor::external_configurations externalConfigs; - for(std::size_t i = 0; i < numExternalConfigFiles; ++i) + const auto& externalConfigFiles = getExternalConfigFileList(); + for(const auto& externalConfigFile : externalConfigFiles) { - auto newExternalConfigs = externalConfigFiles[i].cb(settings); + auto newExternalConfigs = externalConfigFile.cb(settings); externalConfigs.insert(externalConfigs.end(), newExternalConfigs.begin(), newExternalConfigs.end()); } auto add_path_args = - [&](const std::string& name) + [&](const std::string& arg_name) { - opt.add(name + "-includedir", required_argument, key++, - "Set path to " + name + " header file.", - [&]() { - external_includedir[name] = optarg; + opt.add({}, "--" + arg_name + "-includedir", + std::function([&](std::string arg) + { + external_includedir[arg_name] = arg; return 0; - }); + }), + "Set path to " + arg_name + " header file."); - opt.add(name + "-libdir", required_argument, key++, - "Set path to " + name + " libraries.", - [&]() { - external_libdir[name] = optarg; + opt.add({}, "--" + arg_name + "-libdir", + std::function([&](std::string arg) + { + external_libdir[arg_name] = arg; return 0; - }); + }), + "Set path to " + arg_name + " libraries."); }; for(const auto& ext : externalConfigs) @@ -355,18 +408,57 @@ int regenerateCache(ctor::settings& settings, } - opt.add("help", no_argument, 'h', - "Print this help text.", - [&]() { + opt.add('h', "--help", + std::function([&]() -> int + { std::cout << "Configure how to build with " << name << "\n"; std::cout << "Usage: " << name << " configure [options]\n\n"; std::cout << "Options:\n"; opt.help(); exit(0); - return 0; - }); + }), + "Print this help text."); + + opt.set_err_cb( + [&](arg::error err, std::string_view arg) + { + switch(err) + { + case arg::error::invalid_arg: + std::cerr << opt.prog_name() << + ": invalid argument for option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + + case arg::error::missing_arg: + std::cerr << opt.prog_name() << ": option requires and argument '" << + arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + + case arg::error::invalid_opt: + std::cerr << opt.prog_name() << ": invalid option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + } + }); + + opt.set_pos_cb( + [&](std::string_view) + { + std::cerr << + "The configure subcommand doesn't use positional arguments.\n"; + return 1; + }); - opt.process(vargs.size(), vargs.data()); + auto res = opt.parse(); + if(res != 0) + { + return res; + } if(host_arch_prefix.empty()) { @@ -650,6 +742,7 @@ int regenerateCache(ctor::settings& settings, { ctor::conf_values[ctor::cfg::builddir] = builddir; } + ctor::conf_values[ctor::cfg::host_cxx] = host_cxx; ctor::conf_values[ctor::cfg::build_cxx] = build_cxx; @@ -821,46 +914,59 @@ int regenerateCache(ctor::settings& settings, int configure(const ctor::settings& global_settings, int argc, char* argv[]) { + auto args_span = std::span(argv, static_cast<std::size_t>(argc)); + ctor::settings settings{global_settings}; std::vector<std::string> args; - for(int i = 2; i < argc; ++i) // skip command and the first 'configure' arg + for(std::size_t i = 2; i < args_span.size(); ++i) // skip command and the first 'configure' arg { - args.push_back(argv[i]); + args.emplace_back(args_span[i]); } std::map<std::string, std::string> env; - auto cc_env = getenv("CC"); - if(cc_env) + std::string value; + if(get_env("CC", value)) + { + env["CC"] = value; + } + + if(get_env("CFLAGS", value)) { - env["CC"] = cc_env; + env["CFLAGS"] = value; } - auto cxx_env = getenv("CXX"); - if(cxx_env) + if(get_env("CXX", value)) { - env["CXX"] = cxx_env; + env["CXX"] = value; } - auto ar_env = getenv("AR"); - if(ar_env) + if(get_env("CXXFLAGS", value)) { - env["AR"] = ar_env; + env["CXXFLAGS"] = value; } - auto ld_env = getenv("LD"); - if(ld_env) + if(get_env("AR", value)) { - env["LD"] = ld_env; + env["AR"] = value; } - auto path_env = getenv("PATH"); - if(path_env) + if(get_env("LD", value)) { - env["PATH"] = path_env; + env["LD"] = value; } - auto ret = regenerateCache(settings, argv[0], args, env); + if(get_env("LDFLAGS", value)) + { + env["LDFLAGS"] = value; + } + + if(get_env("PATH", value)) + { + env["PATH"] = value; + } + + auto ret = regenerateCache(settings, args_span[0], args, env); if(ret != 0) { return ret; @@ -873,19 +979,21 @@ int configure(const ctor::settings& global_settings, int argc, char* argv[]) int reconfigure(const ctor::settings& global_settings, int argc, char* argv[]) { + auto args_span = std::span(argv, static_cast<std::size_t>(argc)); + ctor::settings settings{global_settings}; bool no_rerun{false}; std::vector<std::string> args; - for(int i = 2; i < argc; ++i) // skip executable name and 'reconfigure' arg + for(std::size_t i = 2; i < args_span.size(); ++i) // skip executable name and 'reconfigure' arg { - if(i == 2 && std::string(argv[i]) == "--no-rerun") + if(i == 2 && std::string(args_span[i]) == "--no-rerun") { no_rerun = true; continue; } - args.push_back(argv[i]); + args.emplace_back(args_span[i]); } const auto& cfg = ctor::get_configuration(); @@ -895,14 +1003,14 @@ int reconfigure(const ctor::settings& global_settings, int argc, char* argv[]) { std::cout << e.first << "=\"" << e.second << "\" "; } - std::cout << argv[0] << " configure "; + std::cout << args_span[0] << " configure "; for(const auto& arg : cfg.args) { std::cout << arg << " "; } std::cout << "\n"; - auto ret = regenerateCache(settings, argv[0], cfg.args, cfg.env); + auto ret = regenerateCache(settings, args_span[0], cfg.args, cfg.env); if(ret != 0) { return ret; @@ -915,5 +1023,5 @@ int reconfigure(const ctor::settings& global_settings, int argc, char* argv[]) return 0; // this was originally invoked by configure, don't loop } - return execute(argv[0], args); + return execute(settings, args_span[0], args); } diff --git a/src/configure.h b/src/configure.h index ac8d721..5344646 100644 --- a/src/configure.h +++ b/src/configure.h @@ -12,8 +12,8 @@ namespace ctor { struct settings; } // namespace ctor:: -extern std::filesystem::path configurationFile;; -extern std::filesystem::path configHeaderFile; +extern const std::filesystem::path configurationFile; +extern const std::filesystem::path configHeaderFile; int configure(const ctor::settings& settings, int argc, char* argv[]); int reconfigure(const ctor::settings& settings, int argc, char* argv[]); @@ -10,6 +10,7 @@ #include <variant> #include <cstddef> #include <functional> +#include <string_view> namespace ctor { @@ -52,37 +53,46 @@ enum class arch unknown, //!< Target platform architecture has not yet detected or was not possible to detect }; +enum class toolchain +{ + any, + none, + gcc, + clang, +}; + struct source { - source(const char* file) : file(file) {} - source(const std::string& file) : file(file) {} - source(const char* file, ctor::language lang) : file(file), language(lang) {} - source(const std::string& file, ctor::language lang) : file(file), language(lang) {} + source(const char* file_) : file(file_) {} // convenience ctor + + source(std::string_view file_) : source(file_, ctor::language::automatic) {} + source(std::string_view file_, ctor::language lang_) : file(file_), language(lang_) {} + + source(std::string_view file_, std::string_view output_) : file(file_), output(output_) {} + source(std::string_view file_, ctor::language lang_, std::string_view output_) : file(file_), language(lang_), output(output_) {} + + source(ctor::toolchain toolchain_, std::string_view file_) : file(file_), toolchain(toolchain_) {} + source(ctor::toolchain toolchain_, std::string_view file_, ctor::language lang_) : file(file_), toolchain(toolchain_), language(lang_) {} - source(const char* file, const char* output) : file(file), output(output) {} - source(const std::string& file, const std::string& output) : file(file), output(output) {} - source(const char* file, ctor::language lang, const char* output) : file(file), language(lang), output(output) {} - source(const std::string& file, ctor::language lang, const std::string& output) : file(file), language(lang), output(output) {} + source(ctor::toolchain toolchain_, std::string_view file_, std::string_view output_) : file(file_), toolchain(toolchain_), output(output_) {} + + source(ctor::toolchain toolchain_, std::string_view file_, ctor::language lang_, std::string_view output_) : file(file_), toolchain(toolchain_), language(lang_), output(output_) {} std::string file; + ctor::toolchain toolchain{ctor::toolchain::any}; ctor::language language{ctor::language::automatic}; std::string output{}; }; -enum class toolchain -{ - any, - none, - gcc, - clang, -}; - enum class cxx_opt { // gcc/clang output, // -o debug, // -g warn_all, // -Wall + warn_conversion, // -Wconversion + warn_shadow, // -Wshadow + warn_extra, // -Wextra warnings_as_errors, // -Werror generate_dep_tree, // -MMD no_link, // -c @@ -91,6 +101,7 @@ enum class cxx_opt optimization, // -O<arg> position_independent_code, // -fPIC position_independent_executable, // -fPIE + define, // -D<arg>[=<arg2>] custom, // entire option taken verbatim from <arg> }; @@ -100,6 +111,9 @@ enum class c_opt output, // -o debug, // -g warn_all, // -Wall + warn_conversion, // -Wconversion + warn_shadow, // -Wshadow + warn_extra, // -Wextra warnings_as_errors, // -Werror generate_dep_tree, // -MMD no_link, // -c @@ -108,6 +122,7 @@ enum class c_opt optimization, // -O<arg> position_independent_code, // -fPIC position_independent_executable, // -fPIE + define, // -D<arg>[=<arg2>] custom, // entire option taken verbatim from <arg> }; @@ -115,7 +130,6 @@ enum class ld_opt { // gcc/clang output, // -o - strip, // -s warn_all, // -Wall warnings_as_errors, // -Werror library_path, // -L<arg> @@ -149,18 +163,24 @@ template<typename T> class flag { public: - flag(const std::string& str); + flag(std::string_view str); flag(const char* str); - flag(T opt) : opt(opt) {} - flag(T opt, const std::string& arg) : opt(opt), arg(arg) {} - flag(T opt, const char* arg) : opt(opt), arg(arg) {} - flag(ctor::toolchain toolchain, T opt) : toolchain(toolchain), opt(opt) {} - flag(ctor::toolchain toolchain, T opt, const char* arg) : toolchain(toolchain), opt(opt), arg(arg) {} - flag(ctor::toolchain toolchain, T opt, const std::string& arg) : toolchain(toolchain), opt(opt), arg(arg) {} + flag(T opt_) : opt(opt_) {} + flag(T opt_, std::string_view arg_, std::string_view arg2_ = "") + : opt(opt_), arg(arg_), arg2(arg2_) {} + flag(T opt_, const char* arg_, const char* arg2_ = "") + : opt(opt_), arg(arg_), arg2(arg2_) {} + flag(ctor::toolchain toolchain_, T opt_) + : toolchain(toolchain_), opt(opt_) {} + flag(ctor::toolchain toolchain_, T opt_, const char* arg_, const char* arg2_ = "") + : toolchain(toolchain_), opt(opt_), arg(arg_), arg2(arg2_) {} + flag(ctor::toolchain toolchain_, T opt_, std::string_view arg_, std::string_view arg2_ = "") + : toolchain(toolchain_), opt(opt_), arg(arg_), arg2(arg2_) {} ctor::toolchain toolchain{ctor::toolchain::any}; - T opt; + T opt{}; std::string arg; + std::string arg2; }; using c_flag = ctor::flag<ctor::c_opt>; @@ -171,7 +191,7 @@ using asm_flag = ctor::flag<ctor::asm_opt>; using c_flags = std::vector<ctor::c_flag>; using cxx_flags = std::vector<ctor::cxx_flag>; -using ld_flags= std::vector<ctor::ld_flag>; +using ld_flags = std::vector<ctor::ld_flag>; using ar_flags = std::vector<ctor::ar_flag>; using asm_flags = std::vector<ctor::asm_flag>; @@ -189,6 +209,7 @@ struct settings std::string builddir{"build"}; std::size_t parallel_processes{1}; int verbose{0}; // -1: completely silent, 0: normal, 1: verbose, ... + bool dry_run{false}; }; struct build_configuration; @@ -212,7 +233,7 @@ struct build_configuration using build_configurations = std::vector<build_configuration>; -int reg(ctor::build_configurations (*cb)(const ctor::settings&), +int reg(std::function<ctor::build_configurations (const ctor::settings&)> cb, const std::source_location location = std::source_location::current()); // This type will use flags verbatim @@ -231,7 +252,7 @@ struct external_configuration using external_configurations = std::vector<ctor::external_configuration>; -int reg(ctor::external_configurations (*cb)(const ctor::settings&), +int reg(std::function<ctor::external_configurations (const ctor::settings&)> cb, const std::source_location location = std::source_location::current()); // Convenience macro - ugly but keeps things simple(r) @@ -246,12 +267,12 @@ namespace cfg constexpr auto builddir = "builddir"; constexpr auto host_cc = "host-cc"; -constexpr auto host_cxx = "host-cpp"; +constexpr auto host_cxx = "host-cxx"; constexpr auto host_ar = "host-ar"; constexpr auto host_ld = "host-ld"; constexpr auto build_cc = "build-cc"; -constexpr auto build_cxx = "build-cpp"; +constexpr auto build_cxx = "build-cxx"; constexpr auto build_ar = "build-ar"; constexpr auto build_ld = "build-ld"; @@ -262,7 +283,7 @@ constexpr auto ctor_libdir = "ctor-libdir"; struct configuration { bool has(const std::string& key) const; - const std::string& get(const std::string& key, const std::string& default_value = {}) const; + std::string get(const std::string& key, const std::string& default_value = {}) const; ctor::toolchain host_toolchain{ctor::toolchain::none}; ctor::arch host_arch{ctor::arch::unknown}; @@ -271,6 +292,8 @@ struct configuration ctor::arch build_arch{ctor::arch::unknown}; std::vector<std::string> args; // vector of arguments used when last calling configure + + std::string getenv(const std::string& key) const; std::map<std::string, std::string> env; // env used when last calling configure std::map<std::string, std::string> tools; // tools diff --git a/src/deps.cc b/src/deps.cc new file mode 100644 index 0000000..9400b35 --- /dev/null +++ b/src/deps.cc @@ -0,0 +1,120 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "deps.h" + +#include "util.h" + +#include <fstream> + +namespace { +/* Format example: +build/src/libctor_a-libctor_cc.o: src/libctor.cc \ + src/getoptpp/getoptpp.hpp src/ctor.h src/configure.h src/rebuild.h \ + src/A\ B\ C.h src/task.h src/build.h src/unittest.h + */ +std::vector<std::string> readDepsMake(const std::string& dep_file) +{ + auto str = readFile(dep_file); + + std::vector<std::string> output; + std::string tmp; + + enum class State + { + pre_colon, + post_colon, + in_escape, + } state{State::pre_colon}; + + auto old_state{state}; + for(const auto& c : str) + { + if(c == '\r') + { + // just always ignore windows extra newline char + continue; + } + + switch(state) + { + case State::pre_colon: + if(c == ':') // ignore everything until the colon + { + state = State::post_colon; + continue; + } + continue; + + case State::post_colon: + if(c == '\n') + { + // done + if(!tmp.empty()) + { + output.emplace_back(tmp); + } + return output; + } + if(c == '\\') + { + old_state = state; + state = State::in_escape; + continue; + } + if(c == '\t' || c == ' ') // new token + { + if(!tmp.empty()) + { + output.emplace_back(tmp); + } + tmp.clear(); + continue; + } + break; + + case State::in_escape: + if(c == '\n') + { + // linewrap + state = old_state; + continue; + } + state = old_state; + break; + } + + tmp += c; + } + + if(!tmp.empty()) + { + output.emplace_back(tmp); + } + + return output; +} +} + +std::vector<std::string> readDeps(const std::string& dep_file, + ctor::toolchain toolchain) +{ + if(!std::filesystem::exists(dep_file)) + { + return {}; + } + + switch(toolchain) + { + case ctor::toolchain::any: + case ctor::toolchain::none: + return {}; + + // makefile .d file based: + case ctor::toolchain::gcc: + case ctor::toolchain::clang: + return readDepsMake(dep_file); + } + + return {}; +} diff --git a/src/deps.h b/src/deps.h new file mode 100644 index 0000000..be0dfb2 --- /dev/null +++ b/src/deps.h @@ -0,0 +1,12 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include "ctor.h" + +#include <vector> +#include <string> + +std::vector<std::string> readDeps(const std::string& dep_file, + ctor::toolchain toolchain); diff --git a/src/execute.cc b/src/execute.cc index b4013d0..c050732 100644 --- a/src/execute.cc +++ b/src/execute.cc @@ -3,6 +3,9 @@ // See accompanying file LICENSE for details. #include "execute.h" +#include "ctor.h" +#include "pointerlist.h" + #include <unistd.h> #include <cstring> #include <sys/types.h> @@ -20,47 +23,53 @@ https://stackoverflow.com/questions/4259629/what-is-the-difference-between-fork- namespace { -class Env - : public std::vector<char*> + +int parent_waitpid(pid_t pid) { -public: - Env(const std::vector<std::string>& args) + int status{}; + + auto rc_pid = waitpid(pid, &status, 0); + + if(rc_pid > 0) { - for(const auto& arg : args) + if(WIFEXITED(status)) { - push_back(strdup(arg.data())); + // Child exited with normally + return WEXITSTATUS(status); } - push_back(nullptr); - } - - ~Env() - { - for(auto ptr : *this) + if(WIFSIGNALED(status)) { - free(ptr); + // Child exited via signal (segfault, abort, ...) + std::cerr << strsignal(status) << '\n'; + return WTERMSIG(status); } } -}; - -int parent_waitpid(pid_t pid) -{ - int status; - - if(waitpid(pid, &status, 0) != pid) - { - return 1; + else + { // No PID returned, this is an error + if(errno == ECHILD) + { + // No children exist. + return 1; + } + else + { + // Unexpected error. + abort(); + } } - return WEXITSTATUS(status); + // Should never happen... + return 1; } } // namespace :: extern char **environ; // see 'man environ' -int execute(const std::string& command, +int execute(const ctor::settings& settings, + const std::string& command, const std::vector<std::string>& args, const std::map<std::string, std::string>& env, - bool verbose) + [[maybe_unused]] bool terminate) { std::vector<const char*> argv; argv.push_back(command.data()); @@ -84,7 +93,7 @@ int execute(const std::string& command, cmd += arg; } - if(verbose) + if(settings.verbose > 0) { std::cout << cmd << std::endl; } @@ -93,19 +102,18 @@ int execute(const std::string& command, auto pid = vfork(); if(pid == 0) { - std::vector<std::string> venv; + EnvMap envmap((const char**)environ); for(const auto& [key, value] : env) { - venv.push_back(key + "=" + value); + envmap.insert(key + "=" + value); } - - for(auto current = environ; *current; ++current) + if(settings.dry_run) { - venv.push_back(*current); + _exit(0); } - - Env penv(venv); - execve(command.data(), (char**)argv.data(), penv.data()); + auto [_, envv] = envmap.get(); + execve(command.data(), const_cast<char* const *>(argv.data()), + const_cast<char* const *>(envv)); std::cout << "Could not execute " << command << ": " << strerror(errno) << "\n"; _exit(1); // execve only returns if an error occurred diff --git a/src/execute.h b/src/execute.h index 336c3ef..4288bb7 100644 --- a/src/execute.h +++ b/src/execute.h @@ -7,7 +7,12 @@ #include <vector> #include <map> -int execute(const std::string& command, +namespace ctor { +struct settings; +}//ctor:: + +int execute(const ctor::settings& settings, + const std::string& command, const std::vector<std::string>& args = {}, const std::map<std::string, std::string>& env = {}, - bool verbose = true); + bool terminate = false); diff --git a/src/externals_manual.cc b/src/externals_manual.cc index 3b96263..0563a5e 100644 --- a/src/externals_manual.cc +++ b/src/externals_manual.cc @@ -13,7 +13,7 @@ extern std::map<std::string, std::string> external_includedir; extern std::map<std::string, std::string> external_libdir; -int resolv(const ctor::settings& settings, const ctor::external_configuration& config, +int resolv([[maybe_unused]]const ctor::settings& settings, const ctor::external_configuration& config, const ctor::external_manual& ext, ctor::flags& flags) { flags = ext.flags; @@ -21,14 +21,14 @@ int resolv(const ctor::settings& settings, const ctor::external_configuration& c auto inc = external_includedir.find(config.name); if(inc != external_includedir.end()) { - flags.cflags.push_back({ctor::c_opt::include_path, inc->second}); - flags.cxxflags.push_back({ctor::cxx_opt::include_path, inc->second}); + flags.cflags.emplace_back(ctor::c_opt::include_path, inc->second); + flags.cxxflags.emplace_back(ctor::cxx_opt::include_path, inc->second); } auto lib = external_libdir.find(config.name); if(lib != external_libdir.end()) { - flags.ldflags.push_back({ctor::ld_opt::library_path, lib->second}); + flags.ldflags.emplace_back(ctor::ld_opt::library_path, lib->second); } return 0; diff --git a/src/getoptpp b/src/getoptpp deleted file mode 160000 -Subproject 9ff20ef857429619267e3f156a4f81ad9e1eb8c diff --git a/src/libctor.cc b/src/libctor.cc index 3eb6c6f..2685ec0 100644 --- a/src/libctor.cc +++ b/src/libctor.cc @@ -15,8 +15,7 @@ #include <deque> #include <fstream> #include <cstdlib> - -#include <getoptpp/getoptpp.hpp> +#include <span> #include "ctor.h" #include "configure.h" @@ -24,9 +23,13 @@ #include "tasks.h" #include "build.h" #include "unittest.h" +#include "argparser.h" +#include "util.h" int main(int argc, char* argv[]) { + auto args = std::span(argv, static_cast<std::size_t>(argc)); + ctor::settings settings{}; const auto& c = ctor::get_configuration(); @@ -34,12 +37,12 @@ int main(int argc, char* argv[]) settings.parallel_processes = std::max(1u, std::thread::hardware_concurrency()) * 2 - 1; - if(argc > 1 && std::string(argv[1]) == "configure") + if(args.size() > 1 && std::string(args[1]) == "configure") { return configure(settings, argc, argv); } - if(argc > 1 && std::string(argv[1]) == "reconfigure") + if(args.size() > 1 && std::string(args[1]) == "reconfigure") { return reconfigure(settings, argc, argv); } @@ -55,108 +58,121 @@ int main(int argc, char* argv[]) bool list_targets{false}; bool no_relaunch{false}; // true means no re-launch after rebuild. bool run_unittests{false}; - - dg::Options opt; - int key{128}; - - opt.add("jobs", required_argument, 'j', - "Number of parallel jobs. (default: cpucount * 2 - 1)", - [&]() { - try - { - settings.parallel_processes = std::stoi(optarg); - } - catch(...) - { - std::cerr << "Not a number\n"; - return 1; - } + std::vector<std::string> arguments; + arg::Parser<int, std::string> opt(argc, argv); + opt.set_pos_cb( + [&](std::string_view arg) + { + arguments.emplace_back(std::string(arg)); + return 0; + }); + + opt.add('j', "--jobs", + std::function([&](int jobs) + { + settings.parallel_processes = static_cast<std::size_t>(jobs); return 0; - }); + }), + "Number of parallel jobs. (default: cpucount * 2 - 1)"); - opt.add("build-dir", required_argument, 'b', - "Overload output directory for build files (default: '" + - settings.builddir + "').", - [&]() { - settings.builddir = optarg; + opt.add('b', "--build-dir", + std::function([&](std::string builddir) { + settings.builddir = builddir; return 0; - }); + }), + "Overload output directory for build files (default: '" + + settings.builddir + "')."); - opt.add("verbose", no_argument, 'v', - "Be verbose. Add multiple times for more verbosity.", - [&]() { + opt.add('v', "--verbose", + std::function([&]() + { settings.verbose++; return 0; - }); + }), + "Be verbose. Add multiple times for more verbosity."); - opt.add("quiet", no_argument, 'q', - "Be completely silent.", - [&]() { + opt.add('q', "--quiet", + std::function([&]() { settings.verbose = -1; return 0; - }); + }), + "Be completely silent."); - opt.add("add", required_argument, 'a', - "Add specified file to the build configurations.", - [&]() { + opt.add('a', "--add", + std::function([&](std::string filename) { no_relaunch = true; - add_files.push_back(optarg); + add_files.emplace_back(filename); return 0; - }); + }), + "Add specified file to the build configurations."); - opt.add("remove", required_argument, 'r', - "Remove specified file from the build configurations.", - [&]() { + opt.add('r', "--remove", + std::function([&](std::string filename) + { no_relaunch = true; - remove_files.push_back(optarg); + remove_files.emplace_back(filename); return 0; - }); + }), + "Remove specified file from the build configurations."); - opt.add("list-files", no_argument, 'L', - "List files in the build configurations.", - [&]() { + opt.add('L', "--list-files", + std::function([&]() + { no_relaunch = true; list_files = true; return 0; - }); + }), + "List files in the build configurations."); - opt.add("list-targets", no_argument, 'l', - "List targets.", - [&]() { + opt.add('l', "--list-targets", + std::function([&]() + { no_relaunch = true; list_targets = true; return 0; - }); + }), + "List targets."); + + opt.add('n', "--dry-run", + std::function([&]() + { + settings.dry_run = true; + return 0; + }), + "Print the commands to be executed, but do not execute them."); - opt.add("configure-cmd", no_argument, key++, - "Print commandline for last configure.", - [&]() { + opt.add({}, "--configure-cmd", + std::function([&]() + { no_relaunch = true; print_configure_cmd = true; return 0; - }); + }), + "Print commandline for last configure."); - opt.add("configure-db", no_argument, key++, - "Print entire configure parameter database.", - [&]() { + opt.add({}, "--configure-db", + std::function([&]() + { no_relaunch = true; print_configure_db = true; return 0; - }); + }), + "Print entire configure parameter database."); - opt.add("database", required_argument, 'd', - "Write compilation database json file.", - [&]() { + opt.add('d', "--database", + std::function([&](std::string database) + { no_relaunch = true; write_compilation_database = true; - compilation_database = optarg; + compilation_database = database; return 0; - }); + }), + "Write compilation database json file."); - opt.add("help", no_argument, 'h', - "Print this help text.", - [&]() { - std::cout << "Usage: " << argv[0] << " [options] [target] ...\n"; + opt.add('h', "--help", + std::function([&]() -> int + { + std::cout << "Usage: " << args[0] << " [options] [target] ...\n"; std::cout << R"_( where target can be either: configure - run configuration step (cannot be used with other targets). @@ -170,29 +186,67 @@ Options: )_"; opt.help(); exit(0); - return 0; - }); + }), + "Print this help text."); - opt.process(argc, argv); + opt.set_err_cb( + [&](arg::error err, std::string_view arg) + { + switch(err) + { + case arg::error::invalid_arg: + std::cerr << opt.prog_name() << + ": invalid argument for option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + + case arg::error::missing_arg: + std::cerr << opt.prog_name() << ": option requires and argument '" << + arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + + case arg::error::invalid_opt: + std::cerr << opt.prog_name() << ": invalid option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + } + }); + auto res = opt.parse(); + if(res != 0) + { + return res; + } - auto verbose_env = std::getenv("V"); - if(verbose_env) + if(std::string value; get_env("V", value)) { - settings.verbose = std::atoi(verbose_env); + try + { + settings.verbose = std::stoi(value); + } + catch(...) + { + // not an integer + } } if(list_files) { no_default_build = true; std::vector<std::string> files; - for(std::size_t i = 0; i < numConfigFiles; ++i) + const auto& configFiles = getConfigFileList(); + for(const auto& configFile : configFiles) { - files.push_back(configFiles[i].file); + files.emplace_back(configFile.file); } - for(std::size_t i = 0; i < numExternalConfigFiles; ++i) + const auto& externalConfigFiles = getExternalConfigFileList(); + for(const auto& externalConfigFile : externalConfigFiles) { - files.push_back(externalConfigFiles[i].file); + files.emplace_back(externalConfigFile.file); } std::sort(files.begin(), files.end()); @@ -270,7 +324,6 @@ Options: if(print_configure_db) { no_default_build = true; - const auto& c = ctor::get_configuration(); for(const auto& config : c.tools) { std::cout << config.first << ": " << config.second << "\n"; @@ -286,7 +339,7 @@ Options: } bool build_all{!no_default_build}; - for(const auto& arg : opt.arguments()) + for(const auto& arg : arguments) { if(arg == "configure") { diff --git a/src/pointerlist.cc b/src/pointerlist.cc new file mode 100644 index 0000000..c0242f7 --- /dev/null +++ b/src/pointerlist.cc @@ -0,0 +1,123 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "pointerlist.h" + +#include <cstring> + +PointerList::PointerList(int argc, const char* const argv[]) +{ + for(int i = 0; i < argc; ++i) + { + push_back(argv[i]); + } +} + +std::pair<int, const char* const*> PointerList::get() +{ + argptrs.clear(); + for(const auto& arg : *this) + { + argptrs.push_back(arg.data()); + } + argptrs.push_back(nullptr); + + return {argptrs.size() - 1, // size not counting the nullptr at the end + argptrs.data()}; +} + +EnvMap::EnvMap(const char* const env[]) +{ + if(env == nullptr) + { + return; + } + + auto ptr = env; + while(*ptr) + { + insert(std::string_view(*ptr)); + ++ptr; + } +} + +EnvMap::EnvMap(const char* env) +{ + if(env == nullptr) + { + return; + } + + auto ptr = env; + while(*ptr) + { + insert(ptr); + ptr += strlen(ptr) + 1; + } +} + +std::string EnvMap::operator[](const std::string& key) const +{ + try + { + return data.at(key); + } + catch(...) + { + return {}; + } +} + +void EnvMap::clear() +{ + data.clear(); +} + +void EnvMap::insert(std::string_view key_value) +{ + auto equals_sign = key_value.find('='); + if(equals_sign == std::string::npos) + { + insert({key_value, ""}); + return; + } + std::string key{key_value.substr(0, equals_sign)}; + std::string value{key_value.substr(equals_sign + 1)}; // skip '=' + insert({key, value}); +} + +void EnvMap::insert(const std::pair<std::string_view, std::string_view>& item) +{ + data[std::string(item.first)] = item.second; +} + +bool EnvMap::contains(std::string_view key) const +{ + return data.contains(std::string{key}); +} + +std::size_t EnvMap::size() const +{ + return data.size(); +} + +std::string EnvMap::stringify() const +{ + std::string str; + for(const auto& [key, value] : data) + { + str += key + "=" + value + '\0'; + } + str += '\0'; + return str; +} + +std::pair<int, const char* const*> EnvMap::get() +{ + pointerlist.clear(); + for(const auto& [key, value] : data) + { + pointerlist.push_back(key + "=" + value + '\0'); + } + return pointerlist.get(); +} diff --git a/src/pointerlist.h b/src/pointerlist.h new file mode 100644 index 0000000..988bb26 --- /dev/null +++ b/src/pointerlist.h @@ -0,0 +1,74 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <string> +#include <vector> +#include <deque> +#include <utility> +#include <map> + +//! Maintains an (owning) list of string args and converts them to argc/argv +//! compatible arguments on request. +//! The returned pointers are guaranteed to be valid as long as the PointerList +//! object lifetime is not exceeded. +class PointerList + : public std::deque<std::string> +{ +public: + PointerList() = default; + PointerList(int argc, const char* const argv[]); + + //! Returns argc/argv pair from the current list of args + //! The argv entry after the last is a nullptr (not included in the argc) + std::pair<int, const char* const*> get(); + +private: + std::vector<const char*> argptrs; +}; + + +//! Maintains an owning map of strings representing the env. +class EnvMap +{ +public: + EnvMap() = default; + + //! Initialize from an array of pointers to key=value\0 strings terminated + //! by \0 + EnvMap(const char* const env[]); + + //! Initialize from a string of the format + //! key1=val\0key2=val\0...keyN=val\0\0 + EnvMap(const char* env); + + std::string operator[](const std::string& key) const; + + //! Clear all items in the map + void clear(); + + //! Insert string from format: key=value + void insert(std::string_view key_value); + + //! Regular map insert + void insert(const std::pair<std::string_view, std::string_view>& item); + + //! Checks if the container contains element with specific key + bool contains(std::string_view key) const; + + std::size_t size() const; + + //! Return string with the following format: + //! key1=val\0key2=val\0...keyN=val\0\0 + std::string stringify() const; + + //! Returns the map as argc/argv pair where each pointer points to a string + //! of the format key=value\0 and is terminated with a nullptr + std::pair<int, const char* const*> get(); + +private: + std::map<std::string, std::string> data{}; + + PointerList pointerlist; +}; diff --git a/src/rebuild.cc b/src/rebuild.cc index f0d52d9..d62e998 100644 --- a/src/rebuild.cc +++ b/src/rebuild.cc @@ -8,6 +8,8 @@ #include <algorithm> #include <source_location> #include <cstring> +#include <span> +#include <vector> #include "configure.h" #include "ctor.h" @@ -17,34 +19,38 @@ #include "tools.h" #include "util.h" -std::array<BuildConfigurationEntry, 1024> configFiles; -std::size_t numConfigFiles{0}; +std::vector<BuildConfigurationEntry>& getConfigFileList() +{ + static std::vector<BuildConfigurationEntry> configFiles; + return configFiles; +} + +std::vector<ExternalConfigurationEntry>& getExternalConfigFileList() +{ + static std::vector<ExternalConfigurationEntry> externalConfigFiles; + return externalConfigFiles; +} namespace ctor { -int reg(ctor::build_configurations (*cb)(const ctor::settings&), +int reg(std::function<ctor::build_configurations (const ctor::settings&)> cb, const std::source_location location) { - // NOTE: std::cout cannot be used here - if(numConfigFiles >= configFiles.size()) - { - fprintf(stderr, "Max %d build configurations currently supported.\n", - (int)configFiles.size()); - exit(1); - } + BuildConfigurationEntry entry; auto loc = std::filesystem::path(location.file_name()); if(loc.is_absolute()) { auto pwd = std::filesystem::current_path(); auto rel = std::filesystem::relative(loc, pwd); - configFiles[numConfigFiles].file = strdup(rel.string().data()); // NOTE: This intentionally leaks memory + entry.file = rel.string(); } else { - configFiles[numConfigFiles].file = location.file_name(); + entry.file = location.file_name(); } - configFiles[numConfigFiles].cb = cb; - ++numConfigFiles; + entry.cb = cb; + auto& configFiles = getConfigFileList(); + configFiles.push_back(entry); return 0; } @@ -52,80 +58,50 @@ int reg(ctor::build_configurations (*cb)(const ctor::settings&), int reg(const char* location) { - // NOTE: std::cout cannot be used here - if(numConfigFiles >= configFiles.size()) - { - fprintf(stderr, "Max %d build configurations currently supported.\n", - (int)configFiles.size()); - exit(1); - } + BuildConfigurationEntry entry; - configFiles[numConfigFiles].file = location; - configFiles[numConfigFiles].cb = - [](const ctor::settings&){ return std::vector<ctor::build_configuration>{}; }; - ++numConfigFiles; + entry.file = location; + entry.cb = + [](const ctor::settings&) + { + return std::vector<ctor::build_configuration>{}; + }; + auto& configFiles = getConfigFileList(); + configFiles.push_back(entry); return 0; } int unreg(const char* location) { - std::size_t found{0}; - for(std::size_t i = 0; i < numConfigFiles;) - { - if(std::string(location) == configFiles[i].file) - { - ++found; - for(std::size_t j = i; j < numConfigFiles; ++j) - { - configFiles[j] = configFiles[j + 1]; - } - --numConfigFiles; - } - else - { - ++i; - } - } - - for(std::size_t i = 0; i < numExternalConfigFiles;) - { - if(std::string(location) == externalConfigFiles[i].file) - { - ++found; - for(std::size_t j = i; j < numExternalConfigFiles; ++j) - { - externalConfigFiles[j] = externalConfigFiles[j + 1]; - } - --numExternalConfigFiles; - } - else - { - ++i; - } - } - - return found; + auto& configFiles = getConfigFileList(); + auto erasedConfigs = + std::erase_if(configFiles, + [&](const BuildConfigurationEntry& entry) + { + return entry.file == location; + }); + + auto& externalConfigFiles = getExternalConfigFileList(); + auto erasedExternals = + std::erase_if(externalConfigFiles, + [&](const ExternalConfigurationEntry& entry) + { + return entry.file == location; + }); + + return static_cast<int>(erasedConfigs) + static_cast<int>(erasedExternals); } -std::array<ExternalConfigurationEntry, 1024> externalConfigFiles; -std::size_t numExternalConfigFiles{0}; - namespace ctor { -int reg(ctor::external_configurations (*cb)(const ctor::settings&), +int reg(std::function<ctor::external_configurations (const ctor::settings&)> cb, const std::source_location location) { - // NOTE: std::cout cannot be used here - if(numExternalConfigFiles >= externalConfigFiles.size()) - { - fprintf(stderr, "Max %d external configurations currently supported.\n", - (int)externalConfigFiles.size()); - exit(1); - } - - externalConfigFiles[numExternalConfigFiles].file = location.file_name(); - externalConfigFiles[numExternalConfigFiles].cb = cb; - ++numExternalConfigFiles; + ExternalConfigurationEntry entry; + entry.file = location.file_name(); + entry.cb = cb; + auto& externalConfigFiles = getExternalConfigFileList(); + externalConfigFiles.push_back(entry); return 0; } @@ -150,11 +126,14 @@ bool contains(const std::vector<ctor::source>& sources, const std::string& file) bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[], bool relaunch_allowed) { + auto args_span = std::span(argv, static_cast<std::size_t>(argc)); + using namespace std::string_literals; + const auto& configFiles = getConfigFileList(); if(global_settings.verbose > 1) { - std::cout << "Recompile check (" << numConfigFiles << "):\n"; + std::cout << "Recompile check (" << configFiles.size() << "):\n"; } ctor::build_configuration config; @@ -163,41 +142,44 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ config.name = "ctor"; config.system = ctor::output_system::build; - config.flags.cxxflags.push_back({ctor::cxx_opt::optimization, "3"}); - config.flags.cxxflags.push_back({ctor::cxx_opt::cpp_std, "c++20"}); + config.flags.cxxflags.emplace_back(ctor::cxx_opt::optimization, "3"); + config.flags.cxxflags.emplace_back(ctor::cxx_opt::cpp_std, "c++20"); const auto& c = ctor::get_configuration(); if(c.has(ctor::cfg::ctor_includedir)) { - config.flags.cxxflags.push_back({ctor::cxx_opt::include_path, - c.get(ctor::cfg::ctor_includedir)}); + config.flags.cxxflags.emplace_back(ctor::cxx_opt::include_path, + c.get(ctor::cfg::ctor_includedir)); } if(c.has(ctor::cfg::ctor_libdir)) { - config.flags.ldflags.push_back({ctor::ld_opt::library_path, c.get(ctor::cfg::ctor_libdir)}); + config.flags.ldflags.emplace_back(ctor::ld_opt::library_path, + c.get(ctor::cfg::ctor_libdir)); } - config.flags.ldflags.push_back({ctor::ld_opt::link, "ctor"}); - config.flags.ldflags.push_back({ctor::ld_opt::strip}); - config.flags.ldflags.push_back({ctor::ld_opt::threads}); + config.flags.ldflags.emplace_back(ctor::ld_opt::link, "ctor"); + config.flags.ldflags.emplace_back(ctor::ld_opt::threads); ctor::settings settings{global_settings}; settings.verbose = -1; // Make check completely silent. - settings.builddir += "/ctor"; // override builddir to use ctor subdir + + // override builddir to use ctor subdir + auto ctor_builddir = std::filesystem::path(settings.builddir) / "ctor"; + settings.builddir = ctor_builddir.string(); { std::filesystem::path buildfile = settings.builddir; - std::filesystem::path currentfile = argv[0]; + std::filesystem::path currentfile = args_span[0]; config.target = std::filesystem::relative(currentfile, buildfile).string(); } if(std::filesystem::exists(configurationFile)) { - config.sources.push_back(configurationFile.string()); + config.sources.emplace_back(configurationFile.string()); } - for(std::size_t i = 0; i < numConfigFiles; ++i) + for(const auto& configFile : configFiles) { - std::string location = configFiles[i].file; + std::string location = configFile.file; if(global_settings.verbose > 1) { std::cout << " - " << location << "\n"; @@ -206,13 +188,14 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ // Ensure that files containing multiple configurations are only added once. if(!contains(config.sources, location)) { - config.sources.push_back(location); + config.sources.emplace_back(location); } } - for(std::size_t i = 0; i < numExternalConfigFiles; ++i) + const auto& externalConfigFiles = getExternalConfigFileList(); + for(const auto& externalConfigFile : externalConfigFiles) { - std::string location = externalConfigFiles[i].file; + std::string location = externalConfigFile.file; if(global_settings.verbose > 1) { std::cout << " - " << location << "\n"; @@ -221,11 +204,11 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ // Ensure that files containing multiple configurations are only added once. if(!contains(config.sources, location)) { - config.sources.push_back(location); + config.sources.emplace_back(location); } } - auto tasks = taskFactory({config}, settings, {}); + auto tasks = taskFactory({config}, settings, {}, true); for(auto task : tasks) { @@ -261,16 +244,17 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ if(reconfigure) { std::vector<std::string> args; - args.push_back("reconfigure"); + args.emplace_back("reconfigure"); if(!relaunch_allowed) { - args.push_back("--no-rerun"); + args.emplace_back("--no-rerun"); } - for(int i = 1; i < argc; ++i) + for(std::size_t i = 1; i < args_span.size(); ++i) { - args.push_back(argv[i]); + args.emplace_back(args_span[i]); } - auto ret = execute(argv[0], args); + + auto ret = execute(settings, args_span[0], args); //if(ret != 0) { exit(ret); diff --git a/src/rebuild.h b/src/rebuild.h index efa6d42..8e0c78a 100644 --- a/src/rebuild.h +++ b/src/rebuild.h @@ -5,26 +5,26 @@ #include <vector> #include <array> +#include <string> +#include <functional> #include "ctor.h" struct BuildConfigurationEntry { - const char* file; - ctor::build_configurations (*cb)(const ctor::settings&); + std::string file; + std::function<ctor::build_configurations (const ctor::settings&)> cb; }; +std::vector<BuildConfigurationEntry>& getConfigFileList(); + struct ExternalConfigurationEntry { - const char* file; - ctor::external_configurations (*cb)(const ctor::settings&); + std::string file; + std::function<ctor::external_configurations (const ctor::settings&)> cb; }; -extern std::array<BuildConfigurationEntry, 1024> configFiles; -extern std::size_t numConfigFiles; - -extern std::array<ExternalConfigurationEntry, 1024> externalConfigFiles; -extern std::size_t numExternalConfigFiles; +std::vector<ExternalConfigurationEntry>& getExternalConfigFileList(); int reg(const char* location); int unreg(const char* location); diff --git a/src/task.cc b/src/task.cc index 7813235..ef7731b 100644 --- a/src/task.cc +++ b/src/task.cc @@ -3,16 +3,16 @@ // See accompanying file LICENSE for details. #include "task.h" -#include <unistd.h> #include <iostream> #include <algorithm> +#include <utility> -Task::Task(const ctor::build_configuration& config, const ctor::settings& settings, - const std::string& sourceDir) - : config(config) +Task::Task(const ctor::build_configuration& config_, const ctor::settings& settings_, + std::string sourceDir_) + : config(config_) , output_system(config.system) - , settings(settings) - , sourceDir(sourceDir) + , settings(settings_) + , sourceDir(std::move(sourceDir_)) { } @@ -45,11 +45,14 @@ int Task::registerDepTasks(const std::vector<std::shared_ptr<Task>>& tasks) bool Task::operator==(const std::string& depStr) { + std::filesystem::path generated_output = sourceDir; + generated_output /= target(); return - name() == depStr || - target() == depStr || - sourceDir + "/" + target() == depStr || - targetFile().string() == depStr + (!derived() && name() == depStr) || // compare to name + (!derived() && config.target == depStr) || // compare to stated target (ex. foo.a) + target() == depStr || // compare to derived (derived to foo.lib on msvc) + generated_output == depStr || // not sure what this is for?! + targetFile().string() == depStr // compare to target output file ; } @@ -144,6 +147,7 @@ std::string Task::compiler() const case ctor::output_system::build: return c.get(ctor::cfg::build_cc, "/usr/bin/gcc"); } + break; case ctor::language::cpp: switch(outputSystem()) { @@ -152,11 +156,15 @@ std::string Task::compiler() const case ctor::output_system::build: return c.get(ctor::cfg::build_cxx, "/usr/bin/g++"); } + break; default: std::cerr << "Unknown CC target type\n"; exit(1); break; } + + std::cerr << "Unhandled compiler!\n"; + exit(1); } std::vector<std::shared_ptr<Task>> Task::getDependsTasks() @@ -24,15 +24,15 @@ class Task { public: Task(const ctor::build_configuration& config, const ctor::settings& settings, - const std::string& sourceDir); + std::string sourceDir); virtual ~Task() = default; int registerDepTasks(const std::vector<std::shared_ptr<Task>>& tasks); - virtual int registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& tasks) { return 0; } + virtual int registerDepTasksInner([[maybe_unused]]const std::vector<std::shared_ptr<Task>>& tasks) { return 0; } bool operator==(const std::string& dep); - virtual std::string name() const; + std::string name() const; bool dirty(); bool ready(); int run(); diff --git a/src/task_ar.cc b/src/task_ar.cc index 19a65ae..3b45cc2 100644 --- a/src/task_ar.cc +++ b/src/task_ar.cc @@ -11,20 +11,20 @@ #include "util.h" #include "tools.h" -TaskAR::TaskAR(const ctor::build_configuration& config, - const ctor::settings& settings, +TaskAR::TaskAR(const ctor::build_configuration& config_, + const ctor::settings& settings_, const std::string& target, const std::vector<std::string>& objects, - const std::string& sourceDir) - : Task(config, settings, sourceDir) - , config(config) - , settings(settings) - , sourceDir(sourceDir) + const std::string& sourceDir_) + : Task(config_, settings_, sourceDir_) + , _targetFile(target) + , config(config_) + , settings(settings_) + , sourceDir(sourceDir_) { target_type = ctor::target_type::static_library; output_system = config.system; - _targetFile = target; auto toolchain = getToolChain(config.system); _targetFile = extension(toolchain, target_type, config.system, _targetFile); for(const auto& object : objects) @@ -36,7 +36,7 @@ TaskAR::TaskAR(const ctor::build_configuration& config, for(const auto& dep : config.depends) { - depFiles.push_back(dep); + depFiles.emplace_back(dep); } flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); @@ -105,7 +105,8 @@ int TaskAR::runInner() if(settings.verbose == 0) { - std::cout << "AR => " << targetFile().string() << std::endl; + std::string output = "AR => " + targetFile().string() + '\n'; + std::cout << output << std::flush; } const auto& c = ctor::get_configuration(); @@ -120,7 +121,13 @@ int TaskAR::runInner() break; } - return execute(tool, args, {}, settings.verbose > 0); + auto res = execute(settings, tool, args, c.env); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskAR::clean() @@ -188,7 +195,6 @@ std::string TaskAR::flagsString() const flagsStr += str; } } - flagsStr += "\n"; for(const auto& dep : config.depends) { diff --git a/src/task_cc.cc b/src/task_cc.cc index 4eb07ae..9628455 100644 --- a/src/task_cc.cc +++ b/src/task_cc.cc @@ -12,16 +12,17 @@ #include "execute.h" #include "util.h" #include "tools.h" - -TaskCC::TaskCC(const ctor::build_configuration& config, const ctor::settings& settings, - const std::string& sourceDir, const ctor::source& source) - : Task(config, settings, sourceDir) - , config(config) - , settings(settings) - , sourceDir(sourceDir) +#include "deps.h" + +TaskCC::TaskCC(const ctor::build_configuration& config_, const ctor::settings& settings_, + const std::string& sourceDir_, const ctor::source& source) + : Task(config_, settings_, sourceDir_) + , sourceFile(sourceDir_) + , config(config_) + , settings(settings_) + , sourceDir(sourceDir_) , _source(source) { - sourceFile = sourceDir; sourceFile /= source.file; std::filesystem::path base = sourceFile.parent_path(); @@ -89,11 +90,6 @@ int TaskCC::registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& task return 0; } -std::string TaskCC::name() const -{ - return {}; -} - bool TaskCC::dirtyInner() { if(!std::filesystem::exists(sourceFile)) @@ -136,7 +132,8 @@ bool TaskCC::dirtyInner() } } - auto depList = readDeps(depsFile.string()); + auto toolchain = getToolChain(config.system); + auto depList = readDeps(depsFile.string(), toolchain); for(const auto& dep : depList) { if(!std::filesystem::exists(dep) || @@ -175,25 +172,59 @@ int TaskCC::runInner() if(settings.verbose == 0) { + std::string output; switch(sourceLanguage()) { case ctor::language::c: - std::cout << "CC "; + output = "CC "; break; case ctor::language::cpp: - std::cout << "CXX "; + output = "CXX "; break; case ctor::language::automatic: case ctor::language::assembler: // Only c/c++ handled by this task type. break; } - std::cout << - sourceFile.lexically_normal().string() << " => " << - targetFile().lexically_normal().string() << std::endl; + output += sourceFile.lexically_normal().string() + " => " + + targetFile().lexically_normal().string() + '\n'; + std::cout << output << std::flush; + } + + auto toolchain = getToolChain(config.system); + const auto& cfg = ctor::get_configuration(); + switch(sourceLanguage()) + { + case ctor::language::c: + { + auto cflags = cfg.getenv("CFLAGS"); + if(!cflags.empty()) + { + append(args, c_option(toolchain, ctor::c_opt::custom, cflags)); + } + } + break; + case ctor::language::cpp: + { + auto cxxflags = cfg.getenv("CXXFLAGS"); + if(!cxxflags.empty()) + { + append(args, cxx_option(toolchain, ctor::cxx_opt::custom, cxxflags)); + } + } + break; + case ctor::language::automatic: + case ctor::language::assembler: + // Only c/c++ handled by this task type. + break; + } + auto res = execute(settings, compiler(), args, cfg.env); + if(res != 0) + { + std::filesystem::remove(targetFile()); } - return execute(compiler(), args, {}, settings.verbose > 0); + return res; } int TaskCC::clean() @@ -286,6 +317,7 @@ std::vector<std::string> TaskCC::flags() const exit(1); break; } + } std::string TaskCC::flagsString() const @@ -309,7 +341,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const { case ctor::language::c: { - append(args, c_option(toolchain, ctor::c_opt::generate_dep_tree)); + append(args, c_option(toolchain, ctor::c_opt::generate_dep_tree, depsFile.string())); if(std::filesystem::path(config.target).extension() == ".so") { @@ -337,6 +369,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const path = (sourceDir / path).lexically_normal(); append(args, c_option(toolchain, ctor::c_opt::include_path, path.string())); + continue; } } break; @@ -351,7 +384,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const case ctor::language::cpp: { - append(args, cxx_option(toolchain, ctor::cxx_opt::generate_dep_tree)); + append(args, cxx_option(toolchain, ctor::cxx_opt::generate_dep_tree, depsFile.string())); if(std::filesystem::path(config.target).extension() == ".so") { @@ -379,6 +412,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const path = (sourceDir / path).lexically_normal(); append(args, cxx_option(toolchain, ctor::cxx_opt::include_path, path.string())); + continue; } } break; diff --git a/src/task_cc.h b/src/task_cc.h index 6c4683a..2299fcd 100644 --- a/src/task_cc.h +++ b/src/task_cc.h @@ -21,7 +21,6 @@ public: int registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& tasks) override; - std::string name() const override; bool dirtyInner() override; int runInner() override; diff --git a/src/task_fn.cc b/src/task_fn.cc index b11ff15..b6b50ea 100644 --- a/src/task_fn.cc +++ b/src/task_fn.cc @@ -11,13 +11,13 @@ #include "execute.h" #include "util.h" -TaskFn::TaskFn(const ctor::build_configuration& config, const ctor::settings& settings, - const std::string& sourceDir, const ctor::source& source) - : Task(config, settings, sourceDir) - , config(config) - , settings(settings) +TaskFn::TaskFn(const ctor::build_configuration& config_, const ctor::settings& settings_, + const std::string& sourceDir_, const ctor::source& source) + : Task(config_, settings_, sourceDir_) + , sourceFile(sourceDir_) + , config(config_) + , settings(settings_) { - sourceFile = sourceDir; sourceFile /= source.file; std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceFile.parent_path()); @@ -68,15 +68,22 @@ int TaskFn::runInner() if(settings.verbose >= 0) { - std::cout << "Fn" << " " << - sourceFile.lexically_normal().string() << " => " << - targetFile().lexically_normal().string() << std::endl; + std::string output = "Fn " + + sourceFile.lexically_normal().string() + " => " + + targetFile().lexically_normal().string() + '\n'; + std::cout << output << std::flush; } - return config.function(sourceFile.string(), - targetFile().string(), - config, - settings); + auto res = config.function(sourceFile.string(), + targetFile().string(), + config, + settings); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskFn::clean() @@ -97,7 +104,7 @@ std::vector<std::string> TaskFn::depends() const std::string TaskFn::target() const { - return _targetFile; + return _targetFile.string(); } std::filesystem::path TaskFn::targetFile() const diff --git a/src/task_ld.cc b/src/task_ld.cc index b0aa4ae..03745be 100644 --- a/src/task_ld.cc +++ b/src/task_ld.cc @@ -11,15 +11,17 @@ #include "util.h" #include "tools.h" -TaskLD::TaskLD(const ctor::build_configuration& config, - const ctor::settings& settings, +TaskLD::TaskLD(const ctor::build_configuration& config_, + const ctor::settings& settings_, const std::string& target, const std::vector<std::string>& objects, - const std::string& sourceDir) - : Task(config, settings, sourceDir) - , config(config) - , settings(settings) - , sourceDir(sourceDir) + const std::string& sourceDir_, + bool is_self_) + : Task(config_, settings_, sourceDir_) + , config(config_) + , settings(settings_) + , sourceDir(sourceDir_) + , is_self(is_self_) { target_type = config.type; output_system = config.system; @@ -41,7 +43,7 @@ TaskLD::TaskLD(const ctor::build_configuration& config, for(const auto& dep : config.depends) { - depFiles.push_back(dep); + depFiles.emplace_back(dep); } flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); @@ -121,11 +123,24 @@ int TaskLD::runInner() if(settings.verbose == 0) { - std::cout << "LD => " << targetFile().string() << std::endl; + std::string output = "LD => " + targetFile().string() + '\n'; + std::cout << output << std::flush; } auto tool = compiler(); - return execute(tool, args, {}, settings.verbose > 0); + const auto& cfg = ctor::get_configuration(); + auto ldflags = cfg.getenv("LDFLAGS"); + if(!ldflags.empty()) + { + append(args, ld_option(toolchain, ctor::ld_opt::custom, ldflags)); + } + auto res = execute(settings, tool, args, cfg.env, is_self); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskLD::clean() @@ -193,7 +208,6 @@ std::string TaskLD::flagsString() const flagsStr += str; } } - flagsStr += "\n"; for(const auto& dep : config.depends) { diff --git a/src/task_ld.h b/src/task_ld.h index dbe7db1..c0e3ebb 100644 --- a/src/task_ld.h +++ b/src/task_ld.h @@ -18,7 +18,8 @@ public: const ctor::settings& settings, const std::string& target, const std::vector<std::string>& objects, - const std::string& _sourceDir); + const std::string& _sourceDir, + bool is_self); virtual ~TaskLD() = default; bool dirtyInner() override; @@ -44,4 +45,5 @@ private: const ctor::build_configuration& config; const ctor::settings& settings; std::string sourceDir; + bool is_self; }; diff --git a/src/task_so.cc b/src/task_so.cc index ba96388..92aeefe 100644 --- a/src/task_so.cc +++ b/src/task_so.cc @@ -11,15 +11,15 @@ #include "util.h" #include "tools.h" -TaskSO::TaskSO(const ctor::build_configuration& config, - const ctor::settings& settings, +TaskSO::TaskSO(const ctor::build_configuration& config_, + const ctor::settings& settings_, const std::string& target, const std::vector<std::string>& objects, - const std::string& sourceDir) - : Task(config, settings, sourceDir) - , config(config) - , settings(settings) - , sourceDir(sourceDir) + const std::string& sourceDir_) + : Task(config_, settings_, sourceDir_) + , config(config_) + , settings(settings_) + , sourceDir(sourceDir_) { std::filesystem::path base = sourceDir; @@ -38,7 +38,7 @@ TaskSO::TaskSO(const ctor::build_configuration& config, for(const auto& dep : config.depends) { - depFiles.push_back(dep); + depFiles.emplace_back(dep); } flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); @@ -110,11 +110,24 @@ int TaskSO::runInner() if(settings.verbose == 0) { - std::cout << "LD => " << targetFile().string() << std::endl; + std::string output = "LD => " + targetFile().string() + '\n'; + std::cout << output << std::flush; } auto tool = compiler(); - return execute(tool, args, {}, settings.verbose > 0); + const auto& cfg = ctor::get_configuration(); + auto ldflags = cfg.getenv("LDFLAGS"); + if(!ldflags.empty()) + { + append(args, ld_option(toolchain, ctor::ld_opt::custom, ldflags)); + } + auto res = execute(settings, tool, args, cfg.env); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskSO::clean() @@ -182,7 +195,6 @@ std::string TaskSO::flagsString() const flagsStr += str; } } - flagsStr += "\n"; for(const auto& dep : config.depends) { diff --git a/src/tasks.cc b/src/tasks.cc index 61c130b..94fe269 100644 --- a/src/tasks.cc +++ b/src/tasks.cc @@ -8,6 +8,7 @@ #include <list> #include <iostream> #include <algorithm> +#include <span> #include "ctor.h" #include "task.h" @@ -24,20 +25,22 @@ const std::deque<Target>& getTargets(const ctor::settings& settings, bool resolve_externals) { + auto& config_files = getConfigFileList(); + static bool initialised{false}; static std::deque<Target> targets; if(!initialised) { const auto& externals = ctor::get_configuration().externals; - for(std::size_t i = 0; i < numConfigFiles; ++i) + for(const auto& config_file : config_files) { std::string path = - std::filesystem::path(configFiles[i].file).parent_path().string(); + std::filesystem::path(config_file.file).parent_path().string(); if(settings.verbose > 1) { - std::cout << configFiles[i].file << " in path " << path << "\n"; + std::cout << config_file.file << " in path " << path << "\n"; } - auto configs = configFiles[i].cb(settings); + auto configs = config_file.cb(settings); for(auto& config : configs) { if(resolve_externals) @@ -72,7 +75,8 @@ const std::deque<Target>& getTargets(const ctor::settings& settings, std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& config, const ctor::settings& settings, - const std::string& sourceDir) + const std::string& sourceDir, + bool is_self) { std::vector<std::shared_ptr<Task>> tasks; @@ -91,14 +95,20 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& } } + const auto& c = ctor::get_configuration(); std::vector<std::string> objects; if(target_type != ctor::target_type::function) { - for(const auto& file : config.sources) + for(const auto& source : config.sources) { - auto task = std::make_shared<TaskCC>(config, settings, sourceDir, file); - tasks.push_back(task); - objects.push_back(task->targetFile().string()); + if(source.toolchain == ctor::toolchain::any || + (config.system == ctor::output_system::build && source.toolchain == c.build_toolchain) || + (config.system == ctor::output_system::host && source.toolchain == c.host_toolchain)) + { + auto task = std::make_shared<TaskCC>(config, settings, sourceDir, source); + tasks.push_back(task); + objects.push_back(task->targetFile().string()); + } } } #ifndef BOOTSTRAP @@ -145,7 +155,7 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& case ctor::target_type::executable: case ctor::target_type::unit_test: tasks.push_back(std::make_shared<TaskLD>(config, settings, config.target, - objects, sourceDir)); + objects, sourceDir, is_self)); break; case ctor::target_type::object: @@ -160,7 +170,8 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& return tasks; } -std::shared_ptr<Task> getNextTask(const std::vector<std::shared_ptr<Task>>& allTasks, +std::shared_ptr<Task> getNextTask([[maybe_unused]]const ctor::settings& settings, + [[maybe_unused]]const std::vector<std::shared_ptr<Task>>& allTasks, std::vector<std::shared_ptr<Task>>& dirtyTasks) { for(auto dirtyTask = dirtyTasks.begin(); @@ -169,7 +180,7 @@ std::shared_ptr<Task> getNextTask(const std::vector<std::shared_ptr<Task>>& allT { auto task = *dirtyTask; //std::cout << "Examining target " << (*dirtyTask)->target() << "\n"; - if(task->ready()) + if(task->ready() || settings.dry_run) { dirtyTasks.erase(dirtyTask); return task; @@ -192,7 +203,7 @@ std::vector<std::shared_ptr<Task>> getTasks(const ctor::settings& settings, std::find(std::begin(names), std::end(names), target.config.target) != std::end(names)) { std::vector<std::string> objects; - auto t = taskFactory(target.config, settings, target.path); + auto t = taskFactory(target.config, settings, target.path, false); tasks.insert(tasks.end(), t.begin(), t.end()); } } diff --git a/src/tasks.h b/src/tasks.h index cc34f56..97fc84d 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -23,7 +23,8 @@ const std::deque<Target>& getTargets(const ctor::settings& settings, //! fulfilled. //! The returned task is removed from the dirty list. //! Return nullptr if no dirty task is ready. -std::shared_ptr<Task> getNextTask(const std::vector<std::shared_ptr<Task>>& allTasks, +std::shared_ptr<Task> getNextTask(const ctor::settings& settings, + const std::vector<std::shared_ptr<Task>>& allTasks, std::vector<std::shared_ptr<Task>>& dirtyTasks); //! Get list of tasks filtered by name including each of their direct @@ -36,4 +37,5 @@ std::vector<std::shared_ptr<Task>> getTasks(const ctor::settings& settings, //! link target and all its objects files (if any). std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& config, const ctor::settings& settings, - const std::string& sourceDir); + const std::string& sourceDir, + bool is_self); diff --git a/src/tools.cc b/src/tools.cc index be94794..dfabdff 100644 --- a/src/tools.cc +++ b/src/tools.cc @@ -6,6 +6,7 @@ #include <filesystem> #include <iostream> #include <sstream> +#include <array> #include <cassert> #include <cstdio> @@ -20,6 +21,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt) case ctor::c_opt::output: stream << "ctor::c_opt::output"; break; case ctor::c_opt::debug: stream << "ctor::c_opt::debug"; break; case ctor::c_opt::warn_all: stream << "ctor::c_opt::warn_all"; break; + case ctor::c_opt::warn_conversion: stream << "ctor::c_opt::warn_conversion"; break; + case ctor::c_opt::warn_shadow: stream << "ctor::c_opt::warn_shadow"; break; + case ctor::c_opt::warn_extra: stream << "ctor::c_opt::warn_extra"; break; case ctor::c_opt::warnings_as_errors: stream << "ctor::c_opt::warnings_as_errors"; break; case ctor::c_opt::generate_dep_tree: stream << "ctor::c_opt::generate_dep_tree"; break; case ctor::c_opt::no_link: stream << "ctor::c_opt::no_link"; break; @@ -28,6 +32,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt) case ctor::c_opt::optimization: stream << "ctor::c_opt::optimization"; break; case ctor::c_opt::position_independent_code: stream << "ctor::c_opt::position_independent_code"; break; case ctor::c_opt::position_independent_executable: stream << "ctor::c_opt::position_independent_executable"; break; + case ctor::c_opt::define: stream << "ctor::c_opt::define"; break; case ctor::c_opt::custom: stream << "ctor::c_opt::custom"; break; } @@ -42,6 +47,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt) case ctor::cxx_opt::output: stream << "ctor::cxx_opt::output"; break; case ctor::cxx_opt::debug: stream << "ctor::cxx_opt::debug"; break; case ctor::cxx_opt::warn_all: stream << "ctor::cxx_opt::warn_all"; break; + case ctor::cxx_opt::warn_conversion: stream << "ctor::cxx_opt::warn_conversion"; break; + case ctor::cxx_opt::warn_shadow: stream << "ctor::cxx_opt::warn_shadow"; break; + case ctor::cxx_opt::warn_extra: stream << "ctor::cxx_opt::warn_extra"; break; case ctor::cxx_opt::warnings_as_errors: stream << "ctor::cxx_opt::warnings_as_errors"; break; case ctor::cxx_opt::generate_dep_tree: stream << "ctor::cxx_opt::generate_dep_tree"; break; case ctor::cxx_opt::no_link: stream << "ctor::cxx_opt::no_link"; break; @@ -50,6 +58,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt) case ctor::cxx_opt::optimization: stream << "ctor::cxx_opt::optimization"; break; case ctor::cxx_opt::position_independent_code: stream << "ctor::cxx_opt::position_independent_code"; break; case ctor::cxx_opt::position_independent_executable: stream << "ctor::cxx_opt::position_independent_executable"; break; + case ctor::cxx_opt::define: stream << "ctor::cxx_opt::define"; break; case ctor::cxx_opt::custom: stream << "ctor::cxx_opt::custom"; break; } @@ -62,7 +71,6 @@ std::ostream& operator<<(std::ostream& stream, const ctor::ld_opt& opt) switch(opt) { case ctor::ld_opt::output: stream << "ctor::ld_opt::output"; break; - case ctor::ld_opt::strip: stream << "ctor::ld_opt::strip"; break; case ctor::ld_opt::warn_all: stream << "ctor::ld_opt::warn_all"; break; case ctor::ld_opt::warnings_as_errors: stream << "ctor::ld_opt::warnings_as_errors"; break; case ctor::ld_opt::library_path: stream << "ctor::ld_opt::library_path"; break; @@ -175,10 +183,11 @@ std::string get_arch(ctor::output_system system) std::string arch; while(!feof(pipe)) { - char buf[1024]; - if(fgets(buf, sizeof(buf), pipe) != nullptr) + constexpr auto buffer_size{1024}; + std::array<char, buffer_size> buf{}; + if(fgets(buf.data(), buf.size(), pipe) != nullptr) { - arch = buf; + arch = buf.data(); if(arch.starts_with("Target:")) { break; @@ -195,7 +204,8 @@ std::string get_arch(ctor::output_system system) } // Remove 'Target: ' prefix - arch = arch.substr(8); + constexpr auto prefix_length{8}; + arch = arch.substr(prefix_length); return arch; } @@ -243,6 +253,61 @@ ctor::c_flag c_option(const std::string& flag) return { ctor::c_opt::include_path, path }; } + if(flag.starts_with("-std=")) + { + std::string std = flag.substr(5); + return { ctor::c_opt::c_std, std }; + } + + if(flag.starts_with("-O")) + { + std::string opt = flag.substr(2, 1); + return { ctor::c_opt::optimization, opt }; + } + + if(flag.starts_with("-Wall")) + { + return { ctor::c_opt::warn_all }; + } + + if(flag.starts_with("-Wconversion")) + { + return { ctor::c_opt::warn_conversion}; + } + + if(flag.starts_with("-Wshadow")) + { + return { ctor::c_opt::warn_shadow}; + } + + if(flag.starts_with("-Wextra")) + { + return { ctor::c_opt::warn_extra}; + } + + if(flag.starts_with("-Werror")) + { + return { ctor::c_opt::warnings_as_errors }; + } + + if(flag.starts_with("-g")) + { + return { ctor::c_opt::debug }; + } + + if(flag.starts_with("-D")) + { + std::string def = flag.substr(2); + auto pos = def.find('='); + if(pos != def.npos) + { + return { ctor::c_opt::define, def.substr(0, pos), def.substr(pos + 1) }; + } + else + { + return { ctor::c_opt::define, def }; + } + } return { ctor::c_opt::custom, flag }; } @@ -255,6 +320,62 @@ ctor::cxx_flag cxx_option(const std::string& flag) return { ctor::cxx_opt::include_path, path }; } + if(flag.starts_with("-std=")) + { + std::string std = flag.substr(5); + return { ctor::cxx_opt::cpp_std, std }; + } + + if(flag.starts_with("-O")) + { + std::string opt = flag.substr(2, 1); + return { ctor::cxx_opt::optimization, opt }; + } + + if(flag.starts_with("-Wall")) + { + return { ctor::cxx_opt::warn_all }; + } + + if(flag.starts_with("-Werror")) + { + return { ctor::cxx_opt::warnings_as_errors }; + } + + if(flag.starts_with("-Wconversion")) + { + return { ctor::cxx_opt::warn_conversion}; + } + + if(flag.starts_with("-Wshadow")) + { + return { ctor::cxx_opt::warn_shadow}; + } + + if(flag.starts_with("-Wextra")) + { + return { ctor::cxx_opt::warn_extra}; + } + + if(flag.starts_with("-g")) + { + return { ctor::cxx_opt::debug }; + } + + if(flag.starts_with("-D")) + { + std::string def = flag.substr(2); + auto pos = def.find('='); + if(pos != def.npos) + { + return { ctor::cxx_opt::define, def.substr(0, pos), def.substr(pos + 1) }; + } + else + { + return { ctor::cxx_opt::define, def }; + } + } + return { ctor::cxx_opt::custom, flag }; } @@ -267,6 +388,11 @@ ctor::ld_flag ld_option(const std::string& flag) return { ctor::ld_opt::library_path, path }; } + if(flag.starts_with("-pthread")) + { + return { ctor::ld_opt::threads }; + } + return { ctor::ld_opt::custom, flag }; } @@ -275,7 +401,8 @@ ctor::ar_flag ar_option(const std::string& flag) return { ctor::ar_opt::custom, flag }; } -std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg) +std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, + const std::string& arg2) { switch(opt) { @@ -285,6 +412,12 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg) return {"-g"}; case ctor::cxx_opt::warn_all: return {"-Wall"}; + case ctor::cxx_opt::warn_conversion: + return {"-Wconversion"}; + case ctor::cxx_opt::warn_shadow: + return {"-Wshadow"}; + case ctor::cxx_opt::warn_extra: + return {"-Wextra"}; case ctor::cxx_opt::warnings_as_errors: return {"-Werror"}; case ctor::cxx_opt::generate_dep_tree: @@ -301,15 +434,22 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg) return {"-fPIC"}; case ctor::cxx_opt::position_independent_executable: return {"-fPIE"}; + case ctor::cxx_opt::define: + if(!arg2.empty()) + { + return {"-D" + arg + "=" + arg2}; + } + return {"-D" + arg}; case ctor::cxx_opt::custom: - return {arg}; + return argsplit(arg); } std::cerr << "Unsupported compiler option.\n"; return {}; } -std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg) +std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg, + const std::string& arg2) { switch(opt) { @@ -319,6 +459,12 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg) return {"-g"}; case ctor::c_opt::warn_all: return {"-Wall"}; + case ctor::c_opt::warn_conversion: + return {"-Wconversion"}; + case ctor::c_opt::warn_shadow: + return {"-Wshadow"}; + case ctor::c_opt::warn_extra: + return {"-Wextra"}; case ctor::c_opt::warnings_as_errors: return {"-Werror"}; case ctor::c_opt::generate_dep_tree: @@ -335,22 +481,27 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg) return {"-fPIC"}; case ctor::c_opt::position_independent_executable: return {"-fPIE"}; + case ctor::c_opt::define: + if(!arg2.empty()) + { + return {"-D" + arg + "=" + arg2}; + } + return {"-D" + arg}; case ctor::c_opt::custom: - return {arg}; + return argsplit(arg); } std::cerr << "Unsupported compiler option.\n"; return {}; } -std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg) +std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg, + [[maybe_unused]]const std::string& arg2) { switch(opt) { case ctor::ld_opt::output: return {"-o", arg}; - case ctor::ld_opt::strip: - return {"-s"}; case ctor::ld_opt::warn_all: return {"-Wall"}; case ctor::ld_opt::warnings_as_errors: @@ -370,14 +521,15 @@ std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg) case ctor::ld_opt::position_independent_executable: return {"-fPIE"}; case ctor::ld_opt::custom: - return {arg}; + return argsplit(arg); } std::cerr << "Unsupported compiler option.\n"; return {}; } -std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg) +std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg, + [[maybe_unused]]const std::string& arg2) { switch(opt) { @@ -390,19 +542,20 @@ std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg) case ctor::ar_opt::output: return {arg}; case ctor::ar_opt::custom: - return {arg}; + return argsplit(arg); } std::cerr << "Unsupported compiler option.\n"; return {}; } -std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg) +std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg, + [[maybe_unused]]const std::string& arg2) { switch(opt) { case ctor::asm_opt::custom: - return {arg}; + return argsplit(arg); } std::cerr << "Unsupported compiler option.\n"; @@ -442,13 +595,14 @@ ctor::arch get_arch(ctor::output_system system, const std::string& str) std::vector<std::string> c_option(ctor::toolchain toolchain, ctor::c_opt opt, - const std::string& arg) + const std::string& arg, + const std::string& arg2) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: - return gcc::c_option(opt, arg); + return gcc::c_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -457,6 +611,10 @@ std::vector<std::string> c_option(ctor::toolchain toolchain, { ss << ", \"" << arg << "\""; } + if(!arg2.empty()) + { + ss << ", \"" << arg2 << "\""; + } ss << "}"; return { ss.str() }; } @@ -470,13 +628,14 @@ std::vector<std::string> c_option(ctor::toolchain toolchain, std::vector<std::string> cxx_option(ctor::toolchain toolchain, ctor::cxx_opt opt, - const std::string& arg) + const std::string& arg, + const std::string& arg2) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: - return gcc::cxx_option(opt, arg); + return gcc::cxx_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -485,6 +644,10 @@ std::vector<std::string> cxx_option(ctor::toolchain toolchain, { ss << ", \"" << arg << "\""; } + if(!arg2.empty()) + { + ss << ", \"" << arg2 << "\""; + } ss << "}"; return { ss.str() }; } @@ -498,13 +661,14 @@ std::vector<std::string> cxx_option(ctor::toolchain toolchain, std::vector<std::string> ld_option(ctor::toolchain toolchain, ctor::ld_opt opt, - const std::string& arg) + const std::string& arg, + const std::string& arg2) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: - return gcc::ld_option(opt, arg); + return gcc::ld_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -513,6 +677,10 @@ std::vector<std::string> ld_option(ctor::toolchain toolchain, { ss << ", \"" << arg << "\""; } + if(!arg2.empty()) + { + ss << ", \"" << arg2 << "\""; + } ss << "}"; return { ss.str() }; } @@ -526,13 +694,14 @@ std::vector<std::string> ld_option(ctor::toolchain toolchain, std::vector<std::string> ar_option(ctor::toolchain toolchain, ctor::ar_opt opt, - const std::string& arg) + const std::string& arg, + const std::string& arg2) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: - return gcc::ar_option(opt, arg); + return gcc::ar_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -541,6 +710,10 @@ std::vector<std::string> ar_option(ctor::toolchain toolchain, { ss << ", \"" << arg << "\""; } + if(!arg2.empty()) + { + ss << ", \"" << arg2 << "\""; + } ss << "}"; return { ss.str() }; } @@ -554,13 +727,14 @@ std::vector<std::string> ar_option(ctor::toolchain toolchain, std::vector<std::string> asm_option(ctor::toolchain toolchain, ctor::asm_opt opt, - const std::string& arg) + const std::string& arg, + const std::string& arg2) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: - return gcc::asm_option(opt, arg); + return gcc::asm_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -569,6 +743,10 @@ std::vector<std::string> asm_option(ctor::toolchain toolchain, { ss << ", \"" << arg << "\""; } + if(!arg2.empty()) + { + ss << ", \"" << arg2 << "\""; + } ss << "}"; return { ss.str() }; } @@ -663,7 +841,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { - return c_option(toolchain, flag.opt, flag.arg); + return c_option(toolchain, flag.opt, flag.arg, flag.arg2); } return {}; @@ -675,7 +853,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { - return cxx_option(toolchain, flag.opt, flag.arg); + return cxx_option(toolchain, flag.opt, flag.arg, flag.arg2); } return {}; @@ -687,7 +865,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { - return ld_option(toolchain, flag.opt, flag.arg); + return ld_option(toolchain, flag.opt, flag.arg, flag.arg2); } return {}; @@ -699,7 +877,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { - return ar_option(toolchain, flag.opt, flag.arg); + return ar_option(toolchain, flag.opt, flag.arg, flag.arg2); } return {}; @@ -711,7 +889,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { - return asm_option(toolchain, flag.opt, flag.arg); + return asm_option(toolchain, flag.opt, flag.arg, flag.arg2); } return {}; diff --git a/src/tools.h b/src/tools.h index 188d49f..0e7fc15 100644 --- a/src/tools.h +++ b/src/tools.h @@ -32,31 +32,36 @@ ctor::toolchain getToolChain(ctor::output_system system); //! tool-chain std::vector<std::string> c_option(ctor::toolchain toolchain, ctor::c_opt option, - const std::string& arg = {}); + const std::string& arg = {}, + const std::string& arg2 = {}); //! Get tool argument(s) for specific option type matching the supplied //! tool-chain std::vector<std::string> cxx_option(ctor::toolchain toolchain, ctor::cxx_opt option, - const std::string& arg = {}); + const std::string& arg = {}, + const std::string& arg2 = {}); //! Get tool argument(s) for specific option type matching the supplied //! tool-chain std::vector<std::string> ld_option(ctor::toolchain toolchain, ctor::ld_opt option, - const std::string& arg = {}); + const std::string& arg = {}, + const std::string& arg2 = {}); //! Get tool argument(s) for specific option type matching the supplied //! tool-chain std::vector<std::string> ar_option(ctor::toolchain toolchain, ctor::ar_opt option, - const std::string& arg = {}); + const std::string& arg = {}, + const std::string& arg2 = {}); //! Get tool argument(s) for specific option type matching the supplied //! tool-chain std::vector<std::string> asm_option(ctor::toolchain toolchain, ctor::asm_opt option, - const std::string& arg = {}); + const std::string& arg = {}, + const std::string& arg2 = {}); diff --git a/src/unittest.cc b/src/unittest.cc index 9e85187..b95a931 100644 --- a/src/unittest.cc +++ b/src/unittest.cc @@ -24,7 +24,7 @@ int runUnitTests(std::vector<std::shared_ptr<Task>>& tasks, name = task->target(); } std::cout << name << ": " << std::flush; - auto ret = execute(task->targetFile(), {}, {}, settings.verbose > 0); + auto ret = execute(settings, task->targetFile().string(), {}, {}); ok &= ret == 0; if(ret == 0) { diff --git a/src/util.cc b/src/util.cc index 76d380b..a4abd23 100644 --- a/src/util.cc +++ b/src/util.cc @@ -6,12 +6,18 @@ #include <iostream> #include <fstream> #include <algorithm> +#include <sstream> +#include <cctype> +#include <cstdlib> -std::string to_lower(const std::string& str) +std::string to_lower(std::string str) { - std::string out{str}; - std::transform(out.begin(), out.end(), out.begin(), ::tolower); - return out; + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) + { + return std::tolower(c); + }); + return str; } std::string readFile(const std::string& fileName) @@ -19,73 +25,17 @@ std::string readFile(const std::string& fileName) std::ifstream ifs(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate); - std::ifstream::pos_type fileSize = ifs.tellg(); - ifs.seekg(0, std::ios::beg); - - std::vector<char> bytes(fileSize); - ifs.read(bytes.data(), fileSize); - - return std::string(bytes.data(), fileSize); -} - -std::vector<std::string> readDeps(const std::string& depFile) -{ - if(!std::filesystem::exists(depFile)) + auto size = ifs.tellg(); + if(size < 0) { return {}; } + ifs.seekg(0, std::ios::beg); - auto str = readFile(depFile); - - std::vector<std::string> output; - std::string tmp; - bool start{false}; - bool in_whitespace{false}; - for(const auto& c : str) - { - if(c == '\\' || c == '\n') - { - continue; - } - - if(c == ':') - { - start = true; - continue; - } - - if(!start) - { - continue; - } - - if(c == ' ' || c == '\t') - { - if(in_whitespace) - { - continue; - } - - if(!tmp.empty()) - { - output.push_back(tmp); - } - tmp.clear(); - in_whitespace = true; - } - else - { - in_whitespace = false; - tmp += c; - } - } - - if(!tmp.empty()) - { - output.push_back(tmp); - } + std::string bytes(static_cast<std::size_t>(size), '\0'); + ifs.read(bytes.data(), static_cast<std::streamsize>(bytes.size())); - return output; + return bytes; } ctor::language languageFromExtension(const std::filesystem::path& file) @@ -168,8 +118,18 @@ std::string esc(const std::string& in) return out; } -std::vector<std::string> get_paths(const std::string& path_env) +std::vector<std::string> get_paths(const std::string& path_env_) { + std::string path_env; + if(!path_env_.empty()) + { + path_env = path_env_; + } + else + { + get_env("PATH", path_env); + } + std::vector<std::string> paths; #ifdef _WIN32 @@ -244,3 +204,106 @@ std::string locate(const std::string& prog, return {}; } + +std::vector<std::string> argsplit(const std::string& str) +{ + enum class state + { + normal, + in_quot, + in_apotrophe, + } state{state::normal}; + bool esc{false}; + + std::string token; + std::vector<std::string> tokens; + for(auto c : str) + { + switch(state) + { + case state::normal: + if(esc) + { + esc = false; + } + else + { + if(c == ' ') + { + tokens.push_back(token); + token.clear(); + continue; + } + if(c == '\\') + { + esc = true; + } + if(c == '"') + { + state = state::in_quot; + } + if(c == '\'') + { + state = state::in_apotrophe; + } + } + + token += c; + break; + case state::in_quot: + if(esc) + { + esc = false; + } + else + { + if(c == '\\') + { + esc = true; + } + if(c == '"') + { + state = state::normal; + } + } + + token += c; + break; + case state::in_apotrophe: + if(esc) + { + esc = false; + } + else + { + if(c == '\\') + { + esc = true; + } + if(c == '\'') + { + state = state::normal; + } + } + + token += c; + break; + } + } + if(!token.empty()) + { + tokens.push_back(token); + } + return tokens; +} + +bool get_env(std::string_view name, std::string& value) +{ + auto var = getenv(name.data()); + if(var) + { + value = var; + return true; + } + return false; +} @@ -9,10 +9,9 @@ #include <filesystem> #include <cstdlib> -std::string to_lower(const std::string& str); +std::string to_lower(std::string str); std::string readFile(const std::string& fileName); -std::vector<std::string> readDeps(const std::string& depFile); ctor::language languageFromExtension(const std::filesystem::path& file); std::string cleanUp(const std::string& path); @@ -24,8 +23,19 @@ void append(T& a, const T& b) std::string esc(const std::string& in); -std::vector<std::string> get_paths(const std::string& path_env = std::getenv("PATH")); +//! Get system paths (ie. env var PATH). +//! If path_env is provided, this search string will be used, other the PATH +//! env variable is used. +//! \returns a vector of the individual toknized paths. +std::vector<std::string> get_paths(const std::string& path_env = {}); std::string locate(const std::string& app, const std::vector<std::string>& paths, const std::string& arch = {}); + +//! Splits string into tokens adhering to quotations " and ' +std::vector<std::string> argsplit(const std::string& str); + +//! Calls the system getenv and sets the string if the env name it exists. +//! \returns true if the env name existed, false otherwise. +bool get_env(std::string_view name, std::string& value); |