diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/argparser.h | 477 | ||||
-rw-r--r-- | src/build.cc | 2 | ||||
-rw-r--r-- | src/configure.cc | 308 | ||||
-rw-r--r-- | src/ctor.h | 5 | ||||
-rw-r--r-- | src/execute.cc | 5 | ||||
m--------- | src/getoptpp | 0 | ||||
-rw-r--r-- | src/libctor.cc | 207 | ||||
-rw-r--r-- | src/rebuild.cc | 139 | ||||
-rw-r--r-- | src/rebuild.h | 18 | ||||
-rw-r--r-- | src/tasks.cc | 7 | ||||
-rw-r--r-- | src/tasks.h | 3 | ||||
-rw-r--r-- | src/util.cc | 48 | ||||
-rw-r--r-- | src/util.h | 12 |
13 files changed, 920 insertions, 311 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/build.cc b/src/build.cc index a31f6a5..5995fb7 100644 --- a/src/build.cc +++ b/src/build.cc @@ -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()) diff --git a/src/configure.cc b/src/configure.cc index a43152f..910b878 100644 --- a/src/configure.cc +++ b/src/configure.cc @@ -8,8 +8,7 @@ #include <fstream> #include <optional> #include <span> - -#include <getoptpp/getoptpp.hpp> +#include <cstring> #include "execute.h" #include "ctor.h" @@ -18,6 +17,7 @@ #include "externals.h" #include "tools.h" #include "util.h" +#include "argparser.h" const std::filesystem::path configurationFile("configuration.cc"); const std::filesystem::path configHeaderFile("config.h"); @@ -32,11 +32,7 @@ const ctor::configuration& __attribute__((weak)) ctor::get_configuration() if(!initialised) { std::string cxx_prog{"c++"}; - auto cxx_env = std::getenv("CXX"); - if(cxx_env) - { - cxx_prog = cxx_env; - } + get_env("CXX", cxx_prog); cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, cxx_prog)); @@ -100,11 +96,32 @@ std::string ctor::configuration::get(const std::string& key, 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; } @@ -116,10 +133,9 @@ std::string ctor::configuration::getenv(const std::string& key) const return envit->second; } - auto sysenv = std::getenv(key.data()); - if(sysenv) + if(std::string value; get_env(key.data(), value)) { - return sysenv; + return value; } return {}; @@ -132,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 { @@ -236,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; @@ -251,97 +262,110 @@ 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()); @@ -350,19 +374,21 @@ int regenerateCache(ctor::settings& settings, auto add_path_args = [&](const std::string& arg_name) { - opt.add(arg_name + "-includedir", required_argument, key++, - "Set path to " + arg_name + " header file.", - [&]() { - external_includedir[arg_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(arg_name + "-libdir", required_argument, key++, - "Set path to " + arg_name + " libraries.", - [&]() { - external_libdir[arg_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) @@ -382,17 +408,57 @@ int regenerateCache(ctor::settings& settings, } - opt.add("help", no_argument, 'h', - "Print this help text.", - [&]() -> int { + 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); - }); + }), + "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; - opt.process(static_cast<int>(vargs.size()), vargs.data()); + 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; + }); + + auto res = opt.parse(); + if(res != 0) + { + return res; + } if(host_arch_prefix.empty()) { @@ -676,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; @@ -858,52 +925,45 @@ int configure(const ctor::settings& global_settings, int argc, char* argv[]) } 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"] = cc_env; + env["CC"] = value; } - auto cflags_env = getenv("CFLAGS"); - if(cflags_env) + if(get_env("CFLAGS", value)) { - env["CFLAGS"] = cflags_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 cxxflags_env = getenv("CXXFLAGS"); - if(cxxflags_env) + if(get_env("CXXFLAGS", value)) { - env["CXXFLAGS"] = cxxflags_env; + env["CXXFLAGS"] = value; } - auto ar_env = getenv("AR"); - if(ar_env) + if(get_env("AR", value)) { - env["AR"] = ar_env; + env["AR"] = value; } - auto ld_env = getenv("LD"); - if(ld_env) + if(get_env("LD", value)) { - env["LD"] = ld_env; + env["LD"] = value; } - auto ldflags_env = getenv("LDFLAGS"); - if(ldflags_env) + if(get_env("LDFLAGS", value)) { - env["LDFLAGS"] = ldflags_env; + env["LDFLAGS"] = value; } - auto path_env = getenv("PATH"); - if(path_env) + if(get_env("PATH", value)) { - env["PATH"] = path_env; + env["PATH"] = value; } auto ret = regenerateCache(settings, args_span[0], args, env); @@ -209,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; @@ -232,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 @@ -251,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) diff --git a/src/execute.cc b/src/execute.cc index ad6c2a2..c050732 100644 --- a/src/execute.cc +++ b/src/execute.cc @@ -107,7 +107,10 @@ int execute(const ctor::settings& settings, { envmap.insert(key + "=" + value); } - + if(settings.dry_run) + { + _exit(0); + } auto [_, envv] = envmap.get(); execve(command.data(), const_cast<char* const *>(argv.data()), const_cast<char* const *>(envv)); diff --git a/src/getoptpp b/src/getoptpp deleted file mode 160000 -Subproject 5aba94355ec638c6f8612f86be309ed684979ae diff --git a/src/libctor.cc b/src/libctor.cc index aaf17c9..2685ec0 100644 --- a/src/libctor.cc +++ b/src/libctor.cc @@ -17,14 +17,14 @@ #include <cstdlib> #include <span> -#include <getoptpp/getoptpp.hpp> - #include "ctor.h" #include "configure.h" #include "rebuild.h" #include "tasks.h" #include "build.h" #include "unittest.h" +#include "argparser.h" +#include "util.h" int main(int argc, char* argv[]) { @@ -58,108 +58,120 @@ 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 = - static_cast<std::size_t>(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.emplace_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.emplace_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.", - [&]() -> int { + opt.add('h', "--help", + std::function([&]() -> int + { std::cout << "Usage: " << args[0] << " [options] [target] ...\n"; std::cout << R"_( where target can be either: @@ -174,28 +186,67 @@ Options: )_"; opt.help(); exit(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.emplace_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.emplace_back(externalConfigFiles[i].file); + files.emplace_back(externalConfigFile.file); } std::sort(files.begin(), files.end()); @@ -288,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/rebuild.cc b/src/rebuild.cc index a2b7ddd..d62e998 100644 --- a/src/rebuild.cc +++ b/src/rebuild.cc @@ -9,6 +9,7 @@ #include <source_location> #include <cstring> #include <span> +#include <vector> #include "configure.h" #include "ctor.h" @@ -18,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; } @@ -53,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) { - int 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; } @@ -155,9 +130,10 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ 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; @@ -201,9 +177,9 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ 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"; @@ -216,9 +192,10 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ } } - 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"; 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/tasks.cc b/src/tasks.cc index 2f9e47a..94fe269 100644 --- a/src/tasks.cc +++ b/src/tasks.cc @@ -25,7 +25,7 @@ const std::deque<Target>& getTargets(const ctor::settings& settings, bool resolve_externals) { - auto config_files = std::span(configFiles).subspan(0, numConfigFiles); + auto& config_files = getConfigFileList(); static bool initialised{false}; static std::deque<Target> targets; @@ -170,7 +170,8 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& return tasks; } -std::shared_ptr<Task> getNextTask([[maybe_unused]]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(); @@ -179,7 +180,7 @@ std::shared_ptr<Task> getNextTask([[maybe_unused]]const std::vector<std::shared_ { auto task = *dirtyTask; //std::cout << "Examining target " << (*dirtyTask)->target() << "\n"; - if(task->ready()) + if(task->ready() || settings.dry_run) { dirtyTasks.erase(dirtyTask); return task; diff --git a/src/tasks.h b/src/tasks.h index 6573784..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 diff --git a/src/util.cc b/src/util.cc index 6fc650a..a4abd23 100644 --- a/src/util.cc +++ b/src/util.cc @@ -7,12 +7,17 @@ #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) @@ -20,13 +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(); + auto size = ifs.tellg(); + if(size < 0) + { + return {}; + } ifs.seekg(0, std::ios::beg); - std::vector<char> bytes(static_cast<std::size_t>(fileSize)); - ifs.read(bytes.data(), fileSize); + std::string bytes(static_cast<std::size_t>(size), '\0'); + ifs.read(bytes.data(), static_cast<std::streamsize>(bytes.size())); - return {bytes.data(), static_cast<std::size_t>(fileSize)}; + return bytes; } ctor::language languageFromExtension(const std::filesystem::path& file) @@ -109,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 @@ -277,3 +296,14 @@ std::vector<std::string> argsplit(const std::string& str) } 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,7 +9,7 @@ #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); ctor::language languageFromExtension(const std::filesystem::path& file); @@ -23,7 +23,11 @@ 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, @@ -31,3 +35,7 @@ std::string locate(const std::string& app, //! 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); |