diff options
46 files changed, 3735 insertions, 1035 deletions
diff --git a/.gitmodules b/.gitmodules index c765825..c12c38f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "getoptpp"] - path = src/getoptpp - url = git://git.drumgizmo.org/getoptpp.git [submodule "test/uunit"] path = test/uunit url = git://git.drumgizmo.org/uunit.git diff --git a/Jenkinsfile b/Jenkinsfile index a558359..d198a69 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,11 +11,11 @@ pipeline { echo 'Cleaning workspace ...' sh 'git clean -d -x -f' echo 'Building (clang) ...' - sh 'CXX=/usr/local/opt/llvm/bin/clang++ LDFLAGS="-L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib/unwind -lunwind" ./bootstrap.sh' + sh 'CXXFLAGS=-Werror CXX=/usr/local/opt/llvm/bin/clang++ LDFLAGS="-L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib/unwind -lunwind" ./bootstrap.sh' echo 'Testing (clang) ...' sh './ctor check' echo 'Testing suite (clang) ...' - sh '(cd test/suite; CTORDIR=../../build CXX=/usr/local/opt/llvm/bin/clang++ LDFLAGS="-L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib/unwind -lunwind" ./test.sh)' + sh '(cd test/suite; /usr/local/opt/llvm/bin/clang++ -std=c++20 test.cc -L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib/unwind -lunwind -o test && CTORDIR=../../build CXXFLAGS=-Werror CXX=/usr/local/opt/llvm/bin/clang++ LDFLAGS="-L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib/unwind -lunwind" ./test)' } post { always { @@ -32,11 +32,11 @@ pipeline { echo 'Cleaning workspace ...' sh 'git clean -d -x -f' echo 'Building (gcc) ...' - sh 'CXX=g++ ./bootstrap.sh' + sh 'CXXFLAGS="-Werror" CXX=g++ ./bootstrap.sh' echo 'Testing (gcc) ...' sh './ctor check' echo 'Testing suite (gcc) ...' - sh '(cd test/suite; CTORDIR=../../build CXX=g++ ./test.sh)' + sh '(cd test/suite; g++ -std=c++20 test.cc -o test && CTORDIR=../../build CXXFLAGS="-Werror" CXX=g++ ./test)' } post { always { @@ -53,11 +53,11 @@ pipeline { echo 'Cleaning workspace ...' sh 'git clean -d -x -f' echo 'Building (clang) ...' - sh 'CXX=clang++ ./bootstrap.sh' + sh 'CXXFLAGS=-Werror CXX=clang++ ./bootstrap.sh' echo 'Testing (clang) ...' sh './ctor check' echo 'Testing suite (clang) ...' - sh '(cd test/suite; CTORDIR=../../build CXX=clang++ ./test.sh)' + sh '(cd test/suite; clang++ -std=c++20 test.cc -o test && CTORDIR=../../build CXXFLAGS=-Werror CXX=clang++ ./test)' } post { always { @@ -10,41 +10,39 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings) return { { - .system = ctor::output_system::build, - .target = "libctor.a", - .sources = { - "src/build.cc", - "src/configure.cc", - "src/deps.cc", - "src/execute.cc", - "src/externals_manual.cc", - "src/libctor.cc", - "src/pointerlist.cc", - "src/rebuild.cc", - "src/task.cc", - "src/task_ar.cc", - "src/task_fn.cc", - "src/task_cc.cc", - "src/task_ld.cc", - "src/task_so.cc", - "src/tasks.cc", - "src/tools.cc", - "src/util.cc", - "src/unittest.cc", - }, - .flags = { - .cxxflags = { - "-std=c++20", - "-O3", - "-g", - "-Wall", - "-Werror", - "-Wextra", - "-Wshadow", - "-Wconversion", -// "-Wnrvo", - "-Isrc", + ctor::output_system::build, + ctor::target("libctor.a"), + ctor::sources{ + "src/build.cc", + "src/configure.cc", + "src/deps.cc", + "src/execute.cc", + "src/externals_manual.cc", + "src/libctor.cc", + "src/pointerlist.cc", + "src/rebuild.cc", + "src/task.cc", + "src/task_ar.cc", + "src/task_fn.cc", + "src/task_cc.cc", + "src/task_ld.cc", + "src/task_so.cc", + "src/tasks.cc", + "src/tools.cc", + "src/util.cc", + "src/unittest.cc", }, + ctor::cxx_flags{ + "-std=c++20", + "-O3", + "-g", + "-Wall", + "-Wextra", + "-Wshadow", + "-Wconversion", +// "-Wnrvo", + "-Isrc", + "-fexceptions", }, } }; 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..5ba87c3 100644 --- a/src/configure.cc +++ b/src/configure.cc @@ -6,10 +6,11 @@ #include <iostream> #include <filesystem> #include <fstream> +#include <sstream> #include <optional> #include <span> - -#include <getoptpp/getoptpp.hpp> +#include <cstring> +#include <functional> #include "execute.h" #include "ctor.h" @@ -18,6 +19,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"); @@ -25,18 +27,36 @@ const std::filesystem::path configHeaderFile("config.h"); std::map<std::string, std::string> external_includedir; std::map<std::string, std::string> external_libdir; -const ctor::configuration& __attribute__((weak)) ctor::get_configuration() +std::function<const ctor::configuration&()>& getConfigurationCallback() +{ + static std::function<const ctor::configuration&()> configuration_callback; + return configuration_callback; +} + +namespace ctor { +int reg(std::function<const ctor::configuration&()> cb, + [[maybe_unused]]const std::source_location location) +{ + auto& configuration_callback = getConfigurationCallback(); + configuration_callback = cb; + return {}; +} +} // ctor:: + +const ctor::configuration& ctor::get_configuration() { + auto& configuration_callback = getConfigurationCallback(); + if(configuration_callback) + { + return configuration_callback(); + } + static ctor::configuration cfg; static bool initialised{false}; 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 +120,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 +157,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 +172,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 +272,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 +286,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 +398,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 +432,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; - opt.process(static_cast<int>(vargs.size()), vargs.data()); + 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; + }); + + auto res = opt.parse(); + if(res != 0) + { + return res; + } if(host_arch_prefix.empty()) { @@ -425,7 +515,8 @@ int regenerateCache(ctor::settings& settings, { case ctor::target_type::executable: case ctor::target_type::unit_test: - case ctor::target_type::dynamic_library: + case ctor::target_type::shared_library: + case ctor::target_type::module: needs_build_ld = true; break; case ctor::target_type::static_library: @@ -462,7 +553,8 @@ int regenerateCache(ctor::settings& settings, { case ctor::target_type::executable: case ctor::target_type::unit_test: - case ctor::target_type::dynamic_library: + case ctor::target_type::shared_library: + case ctor::target_type::module: needs_host_ld = true; break; case ctor::target_type::static_library: @@ -676,14 +768,14 @@ 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; - std::cout << "Writing results to: " << configurationFile.string() << "\n"; - { - std::ofstream istr(configurationFile); + std::cout << "Generating configuration: " << configurationFile.string() << "\n"; { + std::stringstream istr; istr << "#include <ctor.h>\n\n"; - istr << "const ctor::configuration& ctor::get_configuration()\n"; + istr << "const ctor::configuration& stored_configuration()\n"; istr << "{\n"; istr << " static ctor::configuration cfg =\n"; istr << " {\n"; @@ -770,7 +862,7 @@ int regenerateCache(ctor::settings& settings, for(const auto& ext : externalConfigs) { - istr << " { \"" << esc(ext.name) << "\", {\n"; + istr << " { \"" << esc(ext.name) << "\", ctor::flags{\n"; ctor::flags resolved_flags; if(std::holds_alternative<ctor::external_manual>(ext.external)) { @@ -789,7 +881,7 @@ int regenerateCache(ctor::settings& settings, if(!resolved_flags.cflags.empty()) { - istr << " .cflags = {"; + istr << " ctor::c_flags{"; for(const auto& flag : resolved_flags.cflags) { istr << flag << ","; @@ -799,7 +891,7 @@ int regenerateCache(ctor::settings& settings, if(!resolved_flags.cxxflags.empty()) { - istr << " .cxxflags = {"; + istr << " ctor::cxx_flags{"; for(const auto& flag : resolved_flags.cxxflags) { istr << flag << ","; @@ -809,7 +901,7 @@ int regenerateCache(ctor::settings& settings, if(!resolved_flags.ldflags.empty()) { - istr << " .ldflags = {"; + istr << " ctor::ld_flags{"; for(const auto& flag : resolved_flags.ldflags) { istr << flag << ","; @@ -819,7 +911,7 @@ int regenerateCache(ctor::settings& settings, if(!resolved_flags.asmflags.empty()) { - istr << " .asmflags = {"; + istr << " ctor::asm_flags{"; for(const auto& flag : resolved_flags.asmflags) { istr << flag << ","; @@ -833,13 +925,29 @@ int regenerateCache(ctor::settings& settings, istr << " };\n"; istr << " return cfg;\n"; istr << "}\n"; + istr << "\nREG(stored_configuration);\n"; + + auto new_configuration = istr.str(); + auto current_configuration = readFile(configurationFile.string()); + if(current_configuration != new_configuration) + { + std::ofstream configuration_stream(configurationFile.string()); + configuration_stream << new_configuration; + } } { - std::ofstream istr(configHeaderFile); - istr << "#pragma once\n\n"; - istr << "#define HAS_FOO 1\n"; - istr << "//#define HAS_BAR 1\n"; + std::stringstream new_config_stream; + new_config_stream << "#pragma once\n\n"; + new_config_stream << "#define HAS_FOO 1\n"; + new_config_stream << "//#define HAS_BAR 1\n"; + auto new_config = new_config_stream.str(); + auto current_config_content = readFile(configHeaderFile.string()); + if(current_config_content != new_config) + { + std::ofstream config_file_stream(configHeaderFile.string()); + config_file_stream << new_config; + } } return 0; @@ -858,52 +966,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); @@ -11,6 +11,7 @@ #include <cstddef> #include <functional> #include <string_view> +#include <cassert> namespace ctor { @@ -20,7 +21,9 @@ enum class target_type executable, static_library, - dynamic_library, + shared_library, + dynamic_library = shared_library, + module, object, unit_test, unit_test_library, @@ -61,27 +64,59 @@ enum class toolchain clang, }; -struct source +struct output_file { - 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_) {} + std::string file; +}; - source(ctor::toolchain toolchain_, std::string_view file_, std::string_view output_) : file(file_), toolchain(toolchain_), output(output_) {} +enum class source_type +{ + regular, + generated, +}; - source(ctor::toolchain toolchain_, std::string_view file_, ctor::language lang_, std::string_view output_) : file(file_), toolchain(toolchain_), language(lang_), output(output_) {} +struct source +{ + template <class ... Args> + requires (( + std::is_convertible_v<Args, std::string_view> || + std::is_same_v<Args, ctor::toolchain> || + std::is_same_v<Args, ctor::language> || + std::is_same_v<Args, ctor::source_type> || + std::is_same_v<Args, ctor::output_file> + ) && ...) + constexpr source(Args && ... arg) + { + ([&] + { + if constexpr(std::is_convertible_v<Args, std::string_view>) + { + file = arg; + } + else if constexpr(std::is_same_v<Args, ctor::toolchain>) + { + toolchain = arg; + } + else if constexpr(std::is_same_v<Args, ctor::language>) + { + language = arg; + } + else if constexpr(std::is_same_v<Args, ctor::output_file>) + { + output = arg.file; + } + else if constexpr(std::is_same_v<Args, ctor::source_type>) + { + source_type = arg; + } + }(), ...); + } std::string file; ctor::toolchain toolchain{ctor::toolchain::any}; ctor::language language{ctor::language::automatic}; std::string output{}; + ctor::source_type source_type{ctor::source_type::regular}; }; enum class cxx_opt @@ -94,6 +129,7 @@ enum class cxx_opt warn_shadow, // -Wshadow warn_extra, // -Wextra warnings_as_errors, // -Werror + exceptions, // -fexceptions generate_dep_tree, // -MMD no_link, // -c include_path, // -I<arg> @@ -163,24 +199,60 @@ template<typename T> class flag { public: - flag(std::string_view str); - flag(const char* str); - 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_) {} + template <class ... Args> + requires (( + std::is_convertible_v<Args, std::string_view> || + std::is_same_v<Args, ctor::toolchain> || + std::is_same_v<Args, T> + ) && ...) + constexpr flag(Args && ... _arg) + { + constexpr std::size_t n = sizeof...(Args); + int state{}; // 0: opt, 1: arg1, 2: arg2, 3: error + ([&] + { + if constexpr(std::is_convertible_v<Args, std::string_view>) + { + if constexpr(n == 1) + { + std::string str(_arg); + to_flag(str); + } + else + { + assert(state > 0); // opt must be before args + if(state == 1) + { + this->arg = _arg; + } + else + { + assert(state == 2); // up to 2 args supported + this->arg2 = _arg; + } + ++state; + } + } + else if constexpr(std::is_same_v<Args, ctor::toolchain>) + { + toolchain = _arg; + } + else if constexpr(std::is_same_v<Args, T>) + { + assert(state == 0); // opt must be before args + opt = _arg; + ++state; + } + }(), ...); + } ctor::toolchain toolchain{ctor::toolchain::any}; T opt{}; std::string arg; std::string arg2; + +private: + void to_flag(std::string_view str); }; using c_flag = ctor::flag<ctor::c_opt>; @@ -197,6 +269,41 @@ using asm_flags = std::vector<ctor::asm_flag>; struct flags { + template <class ... Args> + requires (( + std::is_same_v<Args, ctor::c_flags> || + std::is_same_v<Args, ctor::cxx_flags> || + std::is_same_v<Args, ctor::ld_flags> || + std::is_same_v<Args, ctor::ar_flags> || + std::is_same_v<Args, ctor::asm_flags> + ) && ...) + constexpr flags(Args && ... arg) + { + ([&] + { + if constexpr(std::is_same_v<Args, ctor::c_flags>) + { + cflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::cxx_flags>) + { + cxxflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::ld_flags>) + { + ldflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::ar_flags>) + { + arflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::asm_flags>) + { + asmflags = arg; + } + }(), ...); + } + ctor::c_flags cflags; // flags for c compiler ctor::cxx_flags cxxflags; // flags for c++ compiler ctor::ld_flags ldflags; // flags for linker @@ -209,16 +316,126 @@ 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; -using GeneratorCb = std::function<int(const std::string& input, - const std::string& output, - const build_configuration& config, - const ctor::settings& settings)>; +using GeneratorOneToOne = + std::function<int(const std::string& input, + const std::string& output, + const build_configuration& config, + const ctor::settings& settings)>; + +using GeneratorManyToOne = + std::function<int(const std::vector<std::string>& input, + const std::string& output, + const ctor::build_configuration& config, + const ctor::settings& settings)>; + +struct name +{ + std::string name; +}; + +struct target +{ + std::string target; +}; + +using sources = std::vector<ctor::source>; + +struct depends +{ + std::vector<std::string> depends; +}; + +struct externals +{ + std::vector<std::string> externals; +}; struct build_configuration { + template <class ... Args> + requires (( + std::is_same_v<Args, ctor::name> || + std::is_same_v<Args, ctor::target_type> || + std::is_same_v<Args, ctor::output_system> || + std::is_same_v<Args, ctor::target> || + std::is_same_v<Args, ctor::sources> || + std::is_same_v<Args, ctor::depends> || + std::is_same_v<Args, ctor::externals> || + std::is_convertible_v<Args, ctor::GeneratorOneToOne> || + std::is_convertible_v<Args, ctor::GeneratorManyToOne> || + std::is_same_v<Args, ctor::c_flags> || + std::is_same_v<Args, ctor::cxx_flags> || + std::is_same_v<Args, ctor::ld_flags> || + std::is_same_v<Args, ctor::ar_flags> || + std::is_same_v<Args, ctor::asm_flags> + ) && ...) + constexpr build_configuration(Args && ... arg) + { + ([&] + { + if constexpr(std::is_same_v<Args, ctor::name>) + { + name = arg.name; + } + else if constexpr(std::is_same_v<Args, ctor::target_type>) + { + type = arg; + } + else if constexpr(std::is_same_v<Args, ctor::output_system>) + { + system = arg; + } + else if constexpr(std::is_same_v<Args, ctor::target>) + { + target = arg.target; + } + else if constexpr(std::is_same_v<Args, ctor::sources>) + { + sources = arg; + } + else if constexpr(std::is_same_v<Args, ctor::depends>) + { + depends = arg.depends; + } + else if constexpr(std::is_same_v<Args, ctor::externals>) + { + externals = arg.externals; + } + else if constexpr(std::is_convertible_v<Args, ctor::GeneratorOneToOne>) + { + function_one_to_one = arg; + } + else if constexpr(std::is_convertible_v<Args, ctor::GeneratorManyToOne>) + { + function_many_to_one = arg; + } + else if constexpr(std::is_same_v<Args, ctor::c_flags>) + { + flags.cflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::cxx_flags>) + { + flags.cxxflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::ld_flags>) + { + flags.ldflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::ar_flags>) + { + flags.arflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::asm_flags>) + { + flags.asmflags = arg; + } + }(), ...); + } + std::string name; // Name - used for referring in other configurations. ctor::target_type type{ctor::target_type::automatic}; ctor::output_system system{ctor::output_system::build}; @@ -227,23 +444,84 @@ struct build_configuration std::vector<std::string> depends; // internal target dependencies ctor::flags flags; std::vector<std::string> externals; // externals used by this configuration - GeneratorCb function; + ctor::GeneratorOneToOne function_one_to_one; + ctor::GeneratorManyToOne function_many_to_one; }; 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 struct external_manual { + template <class ... Args> + requires (( + std::is_same_v<Args, ctor::c_flags> || + std::is_same_v<Args, ctor::cxx_flags> || + std::is_same_v<Args, ctor::ld_flags> || + std::is_same_v<Args, ctor::ar_flags> || + std::is_same_v<Args, ctor::asm_flags> + ) && ...) + constexpr external_manual(Args && ... arg) + { + ([&] + { + if constexpr(std::is_same_v<Args, ctor::c_flags>) + { + flags.cflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::cxx_flags>) + { + flags.cxxflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::ld_flags>) + { + flags.ldflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::ar_flags>) + { + flags.arflags = arg; + } + else if constexpr(std::is_same_v<Args, ctor::asm_flags>) + { + flags.asmflags = arg; + } + }(), ...); + } + ctor::flags flags; }; struct external_configuration { + template <class ... Args> + requires (( + std::is_same_v<Args, ctor::name> || + std::is_same_v<Args, ctor::output_system> || + std::is_same_v<Args, ctor::external_manual> + ) && ...) + constexpr external_configuration(Args && ... arg) + { + ([&] + { + if constexpr(std::is_same_v<Args, ctor::name>) + { + name = arg.name; + } + else if constexpr(std::is_same_v<Args, ctor::output_system>) + { + system = arg; + } + else if constexpr(std::is_same_v<Args, ctor::external_manual>) + { + external = arg; + } + }(), ...); + } + std::string name; // Name for configuration ctor::output_system system{ctor::output_system::build}; std::variant<ctor::external_manual> external; @@ -251,7 +529,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) @@ -301,4 +579,7 @@ struct configuration const ctor::configuration& get_configuration(); +int reg(std::function<const ctor::configuration&()> cb, + const std::source_location location = std::source_location::current()); + } // ctor:: 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/task.cc b/src/task.cc index ef7731b..3a7b895 100644 --- a/src/task.cc +++ b/src/task.cc @@ -7,18 +7,31 @@ #include <algorithm> #include <utility> -Task::Task(const ctor::build_configuration& config_, const ctor::settings& settings_, +Task::Task(ctor::target_type resolved_target_type, + const ctor::build_configuration& config_, + const ctor::settings& settings_, std::string sourceDir_) : config(config_) , output_system(config.system) , settings(settings_) - , sourceDir(std::move(sourceDir_)) + , sourceDir(sourceDir_) { + assert(resolved_target_type != ctor::target_type::automatic); + target_type = resolved_target_type; + output_system = config.system; } int Task::registerDepTasks(const std::vector<std::shared_ptr<Task>>& tasks) { - for(const auto& depStr : depends()) + auto dependsList = depends(); + if(!derived()) + { + for(const auto& dep : config.depends) + { + dependsList.emplace_back(dep); + } + } + for(const auto& depStr : dependsList) { bool found{false}; for(const auto& task : tasks) @@ -23,7 +23,9 @@ enum class State class Task { public: - Task(const ctor::build_configuration& config, const ctor::settings& settings, + Task(ctor::target_type resolved_target_type, + const ctor::build_configuration& config, + const ctor::settings& settings, std::string sourceDir); virtual ~Task() = default; @@ -79,5 +81,5 @@ protected: ctor::language source_language{ctor::language::automatic}; ctor::output_system output_system{ctor::output_system::host}; const ctor::settings& settings; - std::string sourceDir; + std::filesystem::path sourceDir; }; diff --git a/src/task_ar.cc b/src/task_ar.cc index 3b45cc2..4123a81 100644 --- a/src/task_ar.cc +++ b/src/task_ar.cc @@ -11,20 +11,18 @@ #include "util.h" #include "tools.h" -TaskAR::TaskAR(const ctor::build_configuration& config_, +TaskAR::TaskAR(ctor::target_type resolved_target_type, + 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_) + : Task(resolved_target_type, config_, settings_, sourceDir_) , _targetFile(target) , config(config_) , settings(settings_) , sourceDir(sourceDir_) { - target_type = ctor::target_type::static_library; - output_system = config.system; - auto toolchain = getToolChain(config.system); _targetFile = extension(toolchain, target_type, config.system, _targetFile); for(const auto& object : objects) @@ -34,11 +32,6 @@ TaskAR::TaskAR(const ctor::build_configuration& config_, dependsStr.push_back(objectFile.string()); } - for(const auto& dep : config.depends) - { - depFiles.emplace_back(dep); - } - flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); flagsFile += ".flags"; @@ -77,6 +70,15 @@ bool TaskAR::dirtyInner() } } + auto target_file_time = std::filesystem::last_write_time(targetFile()); + for(const auto& object_file : objectFiles) + { + if(std::filesystem::last_write_time(object_file) > target_file_time) + { + return true; + } + } + return false; } @@ -155,11 +157,6 @@ std::vector<std::string> TaskAR::depends() const deps.push_back(objectFile.string()); } - for(const auto& dep : config.depends) - { - deps.push_back(dep); - } - return deps; } @@ -198,7 +195,7 @@ std::string TaskAR::flagsString() const for(const auto& dep : config.depends) { - if(dep != config.depends[0]) + if(&dep != &config.depends[0]) { flagsStr += " "; } diff --git a/src/task_ar.h b/src/task_ar.h index 601afeb..2bb7735 100644 --- a/src/task_ar.h +++ b/src/task_ar.h @@ -14,7 +14,8 @@ class TaskAR : public Task { public: - TaskAR(const ctor::build_configuration& config, + TaskAR(ctor::target_type resolved_target_type, + const ctor::build_configuration& config, const ctor::settings& settings, const std::string& target, const std::vector<std::string>& objects, @@ -37,7 +38,6 @@ private: std::string flagsString() const; std::vector<std::filesystem::path> objectFiles; - std::vector<std::filesystem::path> depFiles; std::filesystem::path _targetFile; std::filesystem::path flagsFile; diff --git a/src/task_cc.cc b/src/task_cc.cc index 9628455..479a453 100644 --- a/src/task_cc.cc +++ b/src/task_cc.cc @@ -14,15 +14,22 @@ #include "tools.h" #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_) +TaskCC::TaskCC(ctor::target_type resolved_target_type, + const ctor::build_configuration& config_, + const ctor::settings& settings_, + const std::string& sourceDir_, + const ctor::source& source) + : Task(resolved_target_type, config_, settings_, sourceDir_) , sourceFile(sourceDir_) , config(config_) , settings(settings_) , sourceDir(sourceDir_) , _source(source) { + if(source.source_type == ctor::source_type::generated) + { + sourceFile = std::filesystem::path(settings.builddir) / sourceFile; + } sourceFile /= source.file; std::filesystem::path base = sourceFile.parent_path(); @@ -31,8 +38,6 @@ TaskCC::TaskCC(const ctor::build_configuration& config_, const ctor::settings& s base += "-"; base += sourceFile.stem(); - target_type = ctor::target_type::object; - output_system = config.system; source_language = source.language; if(source_language == ctor::language::automatic) { @@ -252,6 +257,10 @@ int TaskCC::clean() std::vector<std::string> TaskCC::depends() const { + if(_source.source_type == ctor::source_type::generated) + { + return {sourceFile.string()}; + } return {}; } diff --git a/src/task_cc.h b/src/task_cc.h index 2299fcd..1312417 100644 --- a/src/task_cc.h +++ b/src/task_cc.h @@ -14,7 +14,8 @@ class TaskCC : public Task { public: - TaskCC(const ctor::build_configuration& config, + TaskCC(ctor::target_type resolved_target_type, + const ctor::build_configuration& config, const ctor::settings& settings, const std::string& sourceDir, const ctor::source& source); virtual ~TaskCC() = default; diff --git a/src/task_fn.cc b/src/task_fn.cc index b6b50ea..4f27cc7 100644 --- a/src/task_fn.cc +++ b/src/task_fn.cc @@ -11,45 +11,129 @@ #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_) - , sourceFile(sourceDir_) +TaskFn::TaskFn(ctor::target_type resolved_target_type, + const ctor::build_configuration& config_, + const ctor::settings& settings_, + const std::string& sourceDir_, + const ctor::source& source) + : Task(resolved_target_type, config_, settings_, sourceDir_) , config(config_) , settings(settings_) { - sourceFile /= source.file; - - std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceFile.parent_path()); - - target_type = config.type; + std::filesystem::create_directories(std::filesystem::path(settings.builddir) / + sourceDir.parent_path()); source_language = source.language; - if(source.output.empty()) + if(config.function_one_to_one) { - std::cerr << "Missing output file for functional target\n"; - exit(1); + if(source.source_type == ctor::source_type::generated) + { + sourceFile = std::filesystem::path(settings.builddir); + } + sourceFile /= sourceDir / source.file; + + std::filesystem::path base = sourceFile.parent_path(); + + if(source.output.empty()) + { + std::cerr << "Missing target/output file for functional target.\n"; + exit(1); + } + _targetFile = base / source.output; } + else if(config.function_many_to_one) + { + for(const auto& src : config.sources) + { + std::filesystem::path _src; + if(src.source_type == ctor::source_type::generated) + { + _src = std::filesystem::path(settings.builddir); + } + _src /= sourceDir / src.file; + sources.push_back(_src.string()); + } - _targetFile = source.output; + std::filesystem::path base = sourceDir; + if(config.target.empty()) + { + std::cerr << "Missing target file for functional target\n"; + exit(1); + } + _targetFile = base / config.target; + sourceListFile = targetFile().parent_path() / targetFile().filename(); + sourceListFile += ".sources"; + } } bool TaskFn::dirtyInner() { - if(!std::filesystem::exists(sourceFile)) + if(!std::filesystem::exists(targetFile())) { - //std::cout << "Missing source file: " << std::string(sourceFile) << "\n"; + //std::cout << "Missing targetFile\n"; return true; } - if(!std::filesystem::exists(targetFile())) + if(config.function_many_to_one) + { + if(!std::filesystem::exists(sourceListFile)) + { + //std::cout << "Missing sourceListFile\n"; + return true; + } + + { + auto lastSourceList = readFile(sourceListFile.string()); + if(sourceListString() != lastSourceList) + { + //std::cout << "The compiler sourceList changed\n"; + return true; + } + } + } + + std::filesystem::file_time_type last_changed{}; + bool missing{false}; + if(config.function_one_to_one) + { + if(!std::filesystem::exists(sourceFile)) + { + missing = true; + } + last_changed = std::filesystem::last_write_time(sourceFile); + } + else if(config.function_many_to_one) + { + bool first{true}; + for(const auto& source : sources) + { + if(!std::filesystem::exists(source)) + { + missing |= true; + } + else + { + if(first) + { + last_changed = std::filesystem::last_write_time(source); + } + else + { + last_changed = + std::max(last_changed, + std::filesystem::last_write_time(source)); + } + } + first = false; + } + } + + if(missing) { - //std::cout << "Missing targetFile\n"; return true; } - if(std::filesystem::last_write_time(sourceFile) > - std::filesystem::last_write_time(targetFile())) + if(last_changed > std::filesystem::last_write_time(targetFile())) { //std::cout << "The targetFile older than sourceFile\n"; return true; @@ -60,10 +144,11 @@ bool TaskFn::dirtyInner() int TaskFn::runInner() { - if(!std::filesystem::exists(sourceFile)) + if(config.function_many_to_one) { - std::cout << "Missing source file: " << sourceFile.string() << "\n"; - return 1; + // Write sourceList to file. + std::ofstream sourceListStream(sourceListFile.string()); + sourceListStream << sourceListString(); } if(settings.verbose >= 0) @@ -74,10 +159,24 @@ int TaskFn::runInner() std::cout << output << std::flush; } - auto res = config.function(sourceFile.string(), - targetFile().string(), - config, - settings); + int res{}; + if(config.function_one_to_one) + { + auto func = config.function_one_to_one; + res = func(sourceFile.string(), + targetFile().string(), + config, + settings); + } + else if(config.function_many_to_one) + { + auto func = config.function_many_to_one; + res = func(sources, + targetFile().string(), + config, + settings); + } + if(res != 0) { std::filesystem::remove(targetFile()); @@ -94,12 +193,34 @@ int TaskFn::clean() std::filesystem::remove(targetFile()); } + if(config.function_many_to_one) + { + if(std::filesystem::exists(sourceListFile)) + { + std::cout << "Removing " << sourceListFile.string() << "\n"; + std::filesystem::remove(sourceListFile); + } + } + return 0; } std::vector<std::string> TaskFn::depends() const { - return {}; + std::vector<std::string> deps; + if(config.function_many_to_one) + { + for(const auto& src : config.sources) + { + if(src.source_type == ctor::source_type::generated) + { + std::filesystem::path _src = std::filesystem::path(settings.builddir); + _src /= src.file; + deps.push_back(_src.string()); + } + } + } + return deps; } std::string TaskFn::target() const @@ -109,7 +230,17 @@ std::string TaskFn::target() const std::filesystem::path TaskFn::targetFile() const { - return std::filesystem::path(settings.builddir) / sourceDir / _targetFile; + return std::filesystem::path(settings.builddir) / _targetFile; +} + +std::string TaskFn::sourceListString() const +{ + std::string sourceListStr; + for(const auto& source : config.sources) + { + sourceListStr += " " + source.file; + } + return sourceListStr; } bool TaskFn::derived() const diff --git a/src/task_fn.h b/src/task_fn.h index 1bad32c..e8cc5ce 100644 --- a/src/task_fn.h +++ b/src/task_fn.h @@ -14,7 +14,8 @@ class TaskFn : public Task { public: - TaskFn(const ctor::build_configuration& config, + TaskFn(ctor::target_type resolved_target_type, + const ctor::build_configuration& config, const ctor::settings& settings, const std::string& sourceDir, const ctor::source& source); virtual ~TaskFn() = default; @@ -28,6 +29,7 @@ public: std::string target() const override; std::filesystem::path targetFile() const override; + std::string sourceListString() const; bool derived() const override; std::string toJSON() const override; @@ -37,8 +39,10 @@ public: protected: std::filesystem::path sourceFile; std::filesystem::path _targetFile; + std::filesystem::path sourceListFile; const ctor::build_configuration& config; const ctor::settings& settings; - std::filesystem::path sourceDir; + + std::vector<std::string> sources; }; diff --git a/src/task_ld.cc b/src/task_ld.cc index 03745be..2dceb59 100644 --- a/src/task_ld.cc +++ b/src/task_ld.cc @@ -11,26 +11,18 @@ #include "util.h" #include "tools.h" -TaskLD::TaskLD(const ctor::build_configuration& config_, +TaskLD::TaskLD(ctor::target_type resolved_target_type, + const ctor::build_configuration& config_, const ctor::settings& settings_, const std::string& target, const std::vector<std::string>& objects, const std::string& sourceDir_, bool is_self_) - : Task(config_, settings_, sourceDir_) + : Task(resolved_target_type, config_, settings_, sourceDir_) , config(config_) , settings(settings_) - , sourceDir(sourceDir_) , is_self(is_self_) { - target_type = config.type; - output_system = config.system; - - if(target_type == ctor::target_type::automatic) - { - target_type = ctor::target_type::executable; - } - _targetFile = target; auto toolchain = getToolChain(config.system); _targetFile = extension(toolchain, target_type, config.system, _targetFile); @@ -40,13 +32,8 @@ TaskLD::TaskLD(const ctor::build_configuration& config_, objectFiles.push_back(objectFile); dependsStr.push_back(objectFile.string()); } - - for(const auto& dep : config.depends) - { - depFiles.emplace_back(dep); - } - - flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); + auto cleaned_source_dir = cleanUp(sourceDir.string()); + flagsFile = std::filesystem::path(settings.builddir) / cleaned_source_dir / targetFile().stem(); flagsFile += ".flags"; source_language = ctor::language::c; @@ -83,6 +70,15 @@ bool TaskLD::dirtyInner() } } + auto target_file_time = std::filesystem::last_write_time(targetFile()); + for(const auto& object_file : objectFiles) + { + if(std::filesystem::last_write_time(object_file) > target_file_time) + { + return true; + } + } + return false; } @@ -95,7 +91,7 @@ int TaskLD::runInner() { auto depFile = dep->targetFile(); auto dep_type = target_type_from_extension(toolchain, depFile); - if(dep_type == ctor::target_type::dynamic_library) + if(dep_type == ctor::target_type::shared_library) { append(args, ld_option(toolchain, ctor::ld_opt::library_path, targetFile().parent_path().string())); @@ -168,11 +164,6 @@ std::vector<std::string> TaskLD::depends() const deps.push_back(objectFile.string()); } - for(const auto& depFile : depFiles) - { - deps.push_back(depFile.string()); - } - return deps; } @@ -211,7 +202,7 @@ std::string TaskLD::flagsString() const for(const auto& dep : config.depends) { - if(dep != config.depends[0]) + if(&dep != &config.depends[0]) { flagsStr += " "; } diff --git a/src/task_ld.h b/src/task_ld.h index c0e3ebb..d9156b2 100644 --- a/src/task_ld.h +++ b/src/task_ld.h @@ -14,7 +14,8 @@ class TaskLD : public Task { public: - TaskLD(const ctor::build_configuration& config, + TaskLD(ctor::target_type resolved_target_type, + const ctor::build_configuration& config, const ctor::settings& settings, const std::string& target, const std::vector<std::string>& objects, @@ -38,12 +39,10 @@ private: std::string flagsString() const; std::vector<std::filesystem::path> objectFiles; - std::vector<std::filesystem::path> depFiles; std::filesystem::path _targetFile; std::filesystem::path flagsFile; 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 92aeefe..e1ea6af 100644 --- a/src/task_so.cc +++ b/src/task_so.cc @@ -11,22 +11,18 @@ #include "util.h" #include "tools.h" -TaskSO::TaskSO(const ctor::build_configuration& config_, +TaskSO::TaskSO(ctor::target_type resolved_target_type, + 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_) + : Task(resolved_target_type, config_, settings_, sourceDir_) + , _targetFile(target) , config(config_) , settings(settings_) , sourceDir(sourceDir_) { - std::filesystem::path base = sourceDir; - - target_type = ctor::target_type::dynamic_library; - output_system = config.system; - - _targetFile = base / target; auto toolchain = getToolChain(config.system); _targetFile = extension(toolchain, target_type, config.system, _targetFile); for(const auto& object : objects) @@ -36,11 +32,6 @@ TaskSO::TaskSO(const ctor::build_configuration& config_, dependsStr.push_back(objectFile.string()); } - for(const auto& dep : config.depends) - { - depFiles.emplace_back(dep); - } - flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); flagsFile += ".flags"; @@ -79,6 +70,15 @@ bool TaskSO::dirtyInner() } } + auto target_file_time = std::filesystem::last_write_time(targetFile()); + for(const auto& object_file : objectFiles) + { + if(std::filesystem::last_write_time(object_file) > target_file_time) + { + return true; + } + } + return false; } @@ -90,7 +90,7 @@ int TaskSO::runInner() append(args, ld_option(toolchain, ctor::ld_opt::position_independent_code)); append(args, ld_option(toolchain, ctor::ld_opt::build_shared)); - + append(args, ld_option(toolchain, ctor::ld_opt::custom, "-Wl,-module")); append(args, ld_option(toolchain, ctor::ld_opt::output, targetFile().string())); for(const auto& task : getDependsTasks()) @@ -155,11 +155,6 @@ std::vector<std::string> TaskSO::depends() const deps.push_back(objectFile.string()); } - for(const auto& depFile : depFiles) - { - deps.push_back(depFile.string()); - } - return deps; } @@ -198,7 +193,7 @@ std::string TaskSO::flagsString() const for(const auto& dep : config.depends) { - if(dep != config.depends[0]) + if(&dep != &config.depends[0]) { flagsStr += " "; } diff --git a/src/task_so.h b/src/task_so.h index 60af225..c4283c1 100644 --- a/src/task_so.h +++ b/src/task_so.h @@ -14,7 +14,8 @@ class TaskSO : public Task { public: - TaskSO(const ctor::build_configuration& config, + TaskSO(ctor::target_type resolved_target_type, + const ctor::build_configuration& config, const ctor::settings& settings, const std::string& target, const std::vector<std::string>& objects, @@ -37,7 +38,6 @@ private: std::string flagsString() const; std::vector<std::filesystem::path> objectFiles; - std::vector<std::filesystem::path> depFiles; std::filesystem::path _targetFile; std::filesystem::path flagsFile; diff --git a/src/tasks.cc b/src/tasks.cc index 2f9e47a..5e1d235 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; @@ -82,30 +82,34 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& std::filesystem::path targetFile(config.target); - ctor::target_type target_type{config.type}; - if(target_type == ctor::target_type::automatic) + ctor::target_type resolved_target_type{config.type}; + if(resolved_target_type == ctor::target_type::automatic) { - if(config.function != nullptr) + if(config.function_one_to_one || config.function_many_to_one) { - target_type = ctor::target_type::function; + resolved_target_type = ctor::target_type::function; } else { - target_type = target_type_from_extension(ctor::toolchain::any, targetFile); + resolved_target_type = + target_type_from_extension(ctor::toolchain::any, targetFile); } } const auto& c = ctor::get_configuration(); std::vector<std::string> objects; - if(target_type != ctor::target_type::function) + if(resolved_target_type != ctor::target_type::function) { for(const auto& source : config.sources) { 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)) + (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); + auto task = + std::make_shared<TaskCC>(ctor::target_type::object, config, settings, sourceDir, source); tasks.push_back(task); objects.push_back(task->targetFile().string()); } @@ -114,16 +118,27 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& #ifndef BOOTSTRAP else { - for(const auto& file : config.sources) + if(config.function_many_to_one) { - auto task = std::make_shared<TaskFn>(config, settings, sourceDir, file); + auto task = + std::make_shared<TaskFn>(resolved_target_type, config, settings, sourceDir, ""); tasks.push_back(task); objects.push_back(task->target()); } + else + { + for(const auto& source : config.sources) + { + auto task = + std::make_shared<TaskFn>(resolved_target_type, config, settings, sourceDir, source); + tasks.push_back(task); + objects.push_back(task->target()); + } + } } #endif - switch(target_type) + switch(resolved_target_type) { case ctor::target_type::automatic: // The target_type cannot be Auto @@ -137,25 +152,30 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& case ctor::target_type::static_library: case ctor::target_type::unit_test_library: - tasks.push_back(std::make_shared<TaskAR>(config, settings, config.target, - objects, sourceDir)); + tasks.push_back(std::make_shared<TaskAR>(resolved_target_type, config, settings, + config.target, objects, sourceDir)); break; #ifndef BOOTSTRAP - case ctor::target_type::dynamic_library: + case ctor::target_type::shared_library: // TODO: Use C++20 starts_with if(targetFile.stem().string().substr(0, 3) != "lib") { std::cerr << "Dynamic library target must have 'lib' prefix\n"; exit(1); } - tasks.push_back(std::make_shared<TaskSO>(config, settings, config.target, - objects, sourceDir)); + tasks.push_back(std::make_shared<TaskSO>(resolved_target_type, config, settings, + config.target, objects, sourceDir)); + break; + + case ctor::target_type::module: + tasks.push_back(std::make_shared<TaskSO>(resolved_target_type, config, settings, + config.target, objects, sourceDir)); break; case ctor::target_type::executable: case ctor::target_type::unit_test: - tasks.push_back(std::make_shared<TaskLD>(config, settings, config.target, - objects, sourceDir, is_self)); + tasks.push_back(std::make_shared<TaskLD>(resolved_target_type, config, settings, + config.target, objects, sourceDir, is_self)); break; case ctor::target_type::object: @@ -170,7 +190,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 +200,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/tools.cc b/src/tools.cc index dfabdff..e9c9b33 100644 --- a/src/tools.cc +++ b/src/tools.cc @@ -51,6 +51,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt) 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::exceptions: stream << "ctor::cxx_opt::exceptions"; 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; case ctor::cxx_opt::include_path: stream << "ctor::cxx_opt::include_path"; break; @@ -112,7 +113,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::asm_opt& opt) return stream; } -ctor::toolchain getToolChain(const std::string& compiler) +ctor::toolchain getToolChain(std::string_view compiler) { std::filesystem::path cc(compiler); auto cc_cmd = cc.stem().string(); @@ -209,7 +210,7 @@ std::string get_arch(ctor::output_system system) return arch; } -ctor::arch get_arch(const std::string& str) +ctor::arch get_arch(std::string_view str) { // gcc -v 2>&1 | grep Target // Target: x86_64-apple-darwin19.6.0 @@ -244,24 +245,24 @@ ctor::arch get_arch(const std::string& str) return ctor::arch::unknown; } -ctor::c_flag c_option(const std::string& flag) +ctor::c_flag c_option(std::string_view flag) { if(flag.starts_with("-I")) { - std::string path = flag.substr(2); + std::string path(flag.substr(2)); path.erase(0, path.find_first_not_of(' ')); return { ctor::c_opt::include_path, path }; } if(flag.starts_with("-std=")) { - std::string std = flag.substr(5); + auto std = flag.substr(5); return { ctor::c_opt::c_std, std }; } if(flag.starts_with("-O")) { - std::string opt = flag.substr(2, 1); + auto opt = flag.substr(2, 1); return { ctor::c_opt::optimization, opt }; } @@ -297,7 +298,7 @@ ctor::c_flag c_option(const std::string& flag) if(flag.starts_with("-D")) { - std::string def = flag.substr(2); + auto def = flag.substr(2); auto pos = def.find('='); if(pos != def.npos) { @@ -311,24 +312,24 @@ ctor::c_flag c_option(const std::string& flag) return { ctor::c_opt::custom, flag }; } -ctor::cxx_flag cxx_option(const std::string& flag) +ctor::cxx_flag cxx_option(std::string_view flag) { if(flag.starts_with("-I")) { - std::string path = flag.substr(2); + std::string path(flag.substr(2)); path.erase(0, path.find_first_not_of(' ')); return { ctor::cxx_opt::include_path, path }; } if(flag.starts_with("-std=")) { - std::string std = flag.substr(5); + auto std = flag.substr(5); return { ctor::cxx_opt::cpp_std, std }; } if(flag.starts_with("-O")) { - std::string opt = flag.substr(2, 1); + auto opt = flag.substr(2, 1); return { ctor::cxx_opt::optimization, opt }; } @@ -357,6 +358,11 @@ ctor::cxx_flag cxx_option(const std::string& flag) return { ctor::cxx_opt::warn_extra}; } + if(flag.starts_with("-fexceptions")) + { + return { ctor::cxx_opt::exceptions}; + } + if(flag.starts_with("-g")) { return { ctor::cxx_opt::debug }; @@ -364,7 +370,7 @@ ctor::cxx_flag cxx_option(const std::string& flag) if(flag.starts_with("-D")) { - std::string def = flag.substr(2); + auto def = flag.substr(2); auto pos = def.find('='); if(pos != def.npos) { @@ -379,11 +385,11 @@ ctor::cxx_flag cxx_option(const std::string& flag) return { ctor::cxx_opt::custom, flag }; } -ctor::ld_flag ld_option(const std::string& flag) +ctor::ld_flag ld_option(std::string_view flag) { if(flag.starts_with("-L")) { - std::string path = flag.substr(2); + std::string path(flag.substr(2)); path.erase(0, path.find_first_not_of(' ')); return { ctor::ld_opt::library_path, path }; } @@ -396,18 +402,18 @@ ctor::ld_flag ld_option(const std::string& flag) return { ctor::ld_opt::custom, flag }; } -ctor::ar_flag ar_option(const std::string& flag) +ctor::ar_flag ar_option(std::string_view flag) { return { ctor::ar_opt::custom, flag }; } -std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, - const std::string& arg2) +std::vector<std::string> cxx_option(ctor::cxx_opt opt, std::string_view arg, + std::string_view arg2) { switch(opt) { case ctor::cxx_opt::output: - return {"-o", arg}; + return {"-o", std::string(arg)}; case ctor::cxx_opt::debug: return {"-g"}; case ctor::cxx_opt::warn_all: @@ -420,16 +426,18 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, return {"-Wextra"}; case ctor::cxx_opt::warnings_as_errors: return {"-Werror"}; + case ctor::cxx_opt::exceptions: + return {"-fexceptions"}; case ctor::cxx_opt::generate_dep_tree: return {"-MMD"}; case ctor::cxx_opt::no_link: return {"-c"}; case ctor::cxx_opt::include_path: - return {"-I" + arg}; + return {"-I" + std::string(arg)}; case ctor::cxx_opt::cpp_std: - return {"-std=" + arg}; + return {"-std=" + std::string(arg)}; case ctor::cxx_opt::optimization: - return {"-O" + arg}; + return {"-O" + std::string(arg)}; case ctor::cxx_opt::position_independent_code: return {"-fPIC"}; case ctor::cxx_opt::position_independent_executable: @@ -437,9 +445,9 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, case ctor::cxx_opt::define: if(!arg2.empty()) { - return {"-D" + arg + "=" + arg2}; + return {"-D" + std::string(arg) + "=" + std::string(arg2)}; } - return {"-D" + arg}; + return {"-D" + std::string(arg)}; case ctor::cxx_opt::custom: return argsplit(arg); } @@ -448,13 +456,13 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, return {}; } -std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg, - const std::string& arg2) +std::vector<std::string> c_option(ctor::c_opt opt, std::string_view arg, + std::string_view arg2) { switch(opt) { case ctor::c_opt::output: - return {"-o", arg}; + return {"-o", std::string(arg)}; case ctor::c_opt::debug: return {"-g"}; case ctor::c_opt::warn_all: @@ -472,11 +480,11 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg, case ctor::c_opt::no_link: return {"-c"}; case ctor::c_opt::include_path: - return {"-I" + arg}; + return {"-I" + std::string(arg)}; case ctor::c_opt::c_std: - return {"-std=" + arg}; + return {"-std=" + std::string(arg)}; case ctor::c_opt::optimization: - return {"-O" + arg}; + return {"-O" + std::string(arg)}; case ctor::c_opt::position_independent_code: return {"-fPIC"}; case ctor::c_opt::position_independent_executable: @@ -484,9 +492,9 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg, case ctor::c_opt::define: if(!arg2.empty()) { - return {"-D" + arg + "=" + arg2}; + return {"-D" + std::string(arg) + "=" + std::string(arg2)}; } - return {"-D" + arg}; + return {"-D" + std::string(arg)}; case ctor::c_opt::custom: return argsplit(arg); } @@ -495,23 +503,23 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg, return {}; } -std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg, - [[maybe_unused]]const std::string& arg2) +std::vector<std::string> ld_option(ctor::ld_opt opt, std::string_view arg, + [[maybe_unused]]std::string_view arg2) { switch(opt) { case ctor::ld_opt::output: - return {"-o", arg}; + return {"-o", std::string(arg)}; case ctor::ld_opt::warn_all: return {"-Wall"}; case ctor::ld_opt::warnings_as_errors: return {"-Werror"}; case ctor::ld_opt::library_path: - return {"-L" + arg}; + return {"-L" + std::string(arg)}; case ctor::ld_opt::link: - return {"-l" + arg}; + return {"-l" + std::string(arg)}; case ctor::ld_opt::cpp_std: - return {"-std=" + arg}; + return {"-std=" + std::string(arg)}; case ctor::ld_opt::build_shared: return {"-shared"}; case ctor::ld_opt::threads: @@ -528,8 +536,8 @@ std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg, return {}; } -std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg, - [[maybe_unused]]const std::string& arg2) +std::vector<std::string> ar_option(ctor::ar_opt opt, std::string_view arg, + [[maybe_unused]]std::string_view arg2) { switch(opt) { @@ -540,7 +548,7 @@ std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg, case ctor::ar_opt::create: return {"-c"}; case ctor::ar_opt::output: - return {arg}; + return {std::string(arg)}; case ctor::ar_opt::custom: return argsplit(arg); } @@ -549,8 +557,8 @@ std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg, return {}; } -std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg, - [[maybe_unused]]const std::string& arg2) +std::vector<std::string> asm_option(ctor::asm_opt opt, std::string_view arg, + [[maybe_unused]]std::string_view arg2) { switch(opt) { @@ -578,7 +586,7 @@ std::string get_arch(ctor::output_system system) return {}; } -ctor::arch get_arch(ctor::output_system system, const std::string& str) +ctor::arch get_arch(ctor::output_system system, std::string_view str) { auto toolchain = getToolChain(system); switch(toolchain) @@ -595,8 +603,8 @@ 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& arg2) + std::string_view arg, + std::string_view arg2) { switch(toolchain) { @@ -628,8 +636,8 @@ 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& arg2) + std::string_view arg, + std::string_view arg2) { switch(toolchain) { @@ -661,8 +669,8 @@ 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& arg2) + std::string_view arg, + std::string_view arg2) { switch(toolchain) { @@ -694,8 +702,8 @@ 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& arg2) + std::string_view arg, + std::string_view arg2) { switch(toolchain) { @@ -727,8 +735,8 @@ 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& arg2) + std::string_view arg, + std::string_view arg2) { switch(toolchain) { @@ -759,7 +767,7 @@ std::vector<std::string> asm_option(ctor::toolchain toolchain, } -ctor::c_flag c_option(const std::string& flag, ctor::toolchain toolchain) +ctor::c_flag c_option(std::string_view flag, ctor::toolchain toolchain) { switch(toolchain) { @@ -774,7 +782,7 @@ ctor::c_flag c_option(const std::string& flag, ctor::toolchain toolchain) return { ctor::c_opt::custom, flag }; } -ctor::cxx_flag cxx_option(const std::string& flag, ctor::toolchain toolchain) +ctor::cxx_flag cxx_option(std::string_view flag, ctor::toolchain toolchain) { switch(toolchain) { @@ -789,7 +797,7 @@ ctor::cxx_flag cxx_option(const std::string& flag, ctor::toolchain toolchain) return { ctor::cxx_opt::custom, flag }; } -ctor::ld_flag ld_option(const std::string& flag, ctor::toolchain toolchain) +ctor::ld_flag ld_option(std::string_view flag, ctor::toolchain toolchain) { switch(toolchain) { @@ -804,7 +812,7 @@ ctor::ld_flag ld_option(const std::string& flag, ctor::toolchain toolchain) return { ctor::ld_opt::custom, flag }; } -ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain) +ctor::ar_flag ar_option(std::string_view flag, ctor::toolchain toolchain) { switch(toolchain) { @@ -819,7 +827,7 @@ ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain) return { ctor::ar_opt::custom, flag }; } -ctor::asm_flag asm_option(const std::string& flag, ctor::toolchain toolchain) +ctor::asm_flag asm_option(std::string_view flag, ctor::toolchain toolchain) { switch(toolchain) { @@ -896,7 +904,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, } namespace { -ctor::toolchain guess_toolchain(const std::string& opt) +ctor::toolchain guess_toolchain(std::string_view opt) { if(opt.empty()) { @@ -916,32 +924,33 @@ ctor::toolchain guess_toolchain(const std::string& opt) } } + template<> -ctor::flag<ctor::c_opt>::flag(const char* str) +void ctor::flag<ctor::c_opt>::to_flag(std::string_view str) { *this = c_option(str, guess_toolchain(str)); } template<> -ctor::flag<ctor::cxx_opt>::flag(const char* str) +void ctor::flag<ctor::cxx_opt>::to_flag(std::string_view str) { *this = cxx_option(str, guess_toolchain(str)); } template<> -ctor::flag<ctor::ld_opt>::flag(const char* str) +void ctor::flag<ctor::ld_opt>::to_flag(std::string_view str) { *this = ld_option(str, guess_toolchain(str)); } template<> -ctor::flag<ctor::ar_opt>::flag(const char* str) +void ctor::flag<ctor::ar_opt>::to_flag(std::string_view str) { *this = ar_option(str, guess_toolchain(str)); } template<> -ctor::flag<ctor::asm_opt>::flag(const char* str) +void ctor::flag<ctor::asm_opt>::to_flag(std::string_view str) { *this = asm_option(str, guess_toolchain(str)); } @@ -965,7 +974,14 @@ ctor::target_type target_type_from_extension(ctor::toolchain toolchain, if(ext == ".so" || ext == ".dylib") { - return ctor::target_type::dynamic_library; + if(file.filename().string().substr(0, 3) != "lib") + { + return ctor::target_type::module; + } + else + { + return ctor::target_type::shared_library; + } } if(ext == ".o") @@ -994,7 +1010,7 @@ ctor::target_type target_type_from_extension(ctor::toolchain toolchain, if(ext == ".dll") { - return ctor::target_type::dynamic_library; + return ctor::target_type::shared_library; } if(ext == ".obj") @@ -1078,7 +1094,8 @@ std::filesystem::path extension(ctor::toolchain toolchain, break; } break; - case ctor::target_type::dynamic_library: + case ctor::target_type::shared_library: + case ctor::target_type::module: switch(arch) { case ctor::arch::unix: diff --git a/src/tools.h b/src/tools.h index 0e7fc15..b8df022 100644 --- a/src/tools.h +++ b/src/tools.h @@ -18,10 +18,10 @@ std::ostream& operator<<(std::ostream& stream, const ctor::ar_opt& opt); std::ostream& operator<<(std::ostream& stream, const ctor::asm_opt& opt); std::string get_arch(ctor::output_system system); -ctor::arch get_arch(ctor::output_system system, const std::string& str); +ctor::arch get_arch(ctor::output_system system, std::string_view str); //! Get tool-chain type from compiler path string -ctor::toolchain getToolChain(const std::string& compiler); +ctor::toolchain getToolChain(std::string_view compiler); //! Get tool-chain type from output system (via configuration) ctor::toolchain getToolChain(ctor::output_system system); @@ -32,63 +32,63 @@ 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& arg2 = {}); + std::string_view arg = {}, + std::string_view 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& arg2 = {}); + std::string_view arg = {}, + std::string_view 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& arg2 = {}); + std::string_view arg = {}, + std::string_view 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& arg2 = {}); + std::string_view arg = {}, + std::string_view 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& arg2 = {}); + std::string_view arg = {}, + std::string_view arg2 = {}); //! Get ctor::c_opt enum value and argument from string, //! ie. { ctor::c_opt::inlude_path, "foo/bar" } from "-Ifoo/bar" //! Returns { ctor::c_opt::custom, flag } if unknown. -ctor::c_flag c_option(const std::string& flag, ctor::toolchain toolchain); +ctor::c_flag c_option(std::string_view flag, ctor::toolchain toolchain); //! Get ctor::cxx_opt enum value and argument from string, //! ie. { ctor::cxx_opt::inlude_path, "foo/bar" } from "-Ifoo/bar" //! Returns { ctor::cxx_opt::custom, flag } if unknown. -ctor::cxx_flag cxx_option(const std::string& flag, ctor::toolchain toolchain); +ctor::cxx_flag cxx_option(std::string_view flag, ctor::toolchain toolchain); //! Get ctor::ld_opt enum value and argument from string, //! ie. { ctor::ld_opt::inlude_path, "foo/bar" } from "-Ifoo/bar" //! Returns { ctor::ld_opt::custom, flag } if unknown. -ctor::ld_flag ld_option(const std::string& flag, ctor::toolchain toolchain); +ctor::ld_flag ld_option(std::string_view flag, ctor::toolchain toolchain); //! Get ctor::ar_opt enum value and argument from string, //! ie. { ctor::ar_opt::inlude_path, "foo/bar" } from "-Ifoo/bar" //! Returns { ctor::ar_opt::custom, flag } if unknown. -ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain); +ctor::ar_flag ar_option(std::string_view flag, ctor::toolchain toolchain); //! Get ctor::asm_opt enum value and argument from string, //! ie. { ctor::asm_opt::inlude_path, "foo/bar" } from "-Ifoo/bar" //! Returns { ctor::asm_opt::custom, flag } if unknown. -ctor::asm_flag asm_option(const std::string& flag, ctor::toolchain toolchain); +ctor::asm_flag asm_option(std::string_view flag, ctor::toolchain toolchain); diff --git a/src/util.cc b/src/util.cc index 6fc650a..7ca9c11 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 static_cast<char>(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) @@ -75,7 +84,7 @@ bool isClean(char c) } } -std::string cleanUp(const std::string& path) +std::string cleanUp(std::string_view path) { std::string cleaned; for(const auto& c : path) @@ -92,7 +101,7 @@ std::string cleanUp(const std::string& path) return cleaned; } -std::string esc(const std::string& in) +std::string esc(std::string_view in) { std::string out; for(auto c : in) @@ -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(std::string_view 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 @@ -186,7 +205,7 @@ std::string locate(const std::string& prog, return {}; } -std::vector<std::string> argsplit(const std::string& str) +std::vector<std::string> argsplit(std::string_view str) { enum class state { @@ -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,11 +9,11 @@ #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); -std::string cleanUp(const std::string& path); +std::string cleanUp(std::string_view path); template<typename T> void append(T& a, const T& b) @@ -21,13 +21,21 @@ void append(T& a, const T& b) a.insert(a.end(), b.begin(), b.end()); } -std::string esc(const std::string& in); +std::string esc(std::string_view 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(std::string_view 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); +std::vector<std::string> argsplit(std::string_view 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); diff --git a/test/argparser_test.cc b/test/argparser_test.cc new file mode 100644 index 0000000..b649e8c --- /dev/null +++ b/test/argparser_test.cc @@ -0,0 +1,1019 @@ +#include <argparser.h> + +#include <iostream> +#include <string> + +std::ostream& operator<<(std::ostream& ostr, const arg::error& err) +{ + switch(err) + { + case arg::error::invalid_arg: + ostr << "arg::error::invalid_arg"; + break; + case arg::error::invalid_opt: + ostr << "arg::error::invalid_opt"; + break; + case arg::error::missing_arg: + ostr << "arg::error::missing_arg"; + break; + } + return ostr; +} + +#include <uunit.h> + +class ArgParserTest + : public uUnit +{ +public: + ArgParserTest() + { + uTEST(ArgParserTest::test_zero); + uTEST(ArgParserTest::test_one); + uTEST(ArgParserTest::test_many); + uTEST(ArgParserTest::test_exceptional); + uTEST(ArgParserTest::test_err_callback); + uTEST(ArgParserTest::test_pos_callback); + uTEST(ArgParserTest::test_grouped); + uTEST(ArgParserTest::test_nullprogram); + } + + void test_zero() + { + const char* const argv[] = { "app-name" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int> args(argc, argv); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + } + + void test_one() + { + const char* argv[] = { "app-name", "-x", "42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int> args(argc, argv); + + int x{}; + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(42, x); + } + + void test_many() + { + const char* argv[] = { "app-name", "-x", "42", "-y17", "-z", + "--long-X=12", "--long-Y", "18" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int> args(argc, argv); + + int x{}; + int y{}; + bool z{false}; + int X{}; + int Y{}; + + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](int i){ y = i; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](){ z = true; return 0;}), "Help z"); + + args.add('X', "--long-X", + std::function([&](int i){ X = i; return 0;}), "Help X"); + + args.add('Y', "--long-Y", + std::function([&](int i){ Y = i; return 0;}), "Help Y"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(42, x); + uASSERT_EQUAL(17, y); + uASSERT_EQUAL(true, z); + uASSERT_EQUAL(12, X); + uASSERT_EQUAL(18, Y); + } + + void test_exceptional() + { + + { // Missing arg at trailing opt + const char* argv[] = { "app-name", "-x" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + int y{}; + + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](int i){ y = i; return 0;}), "Help y"); + + auto res = args.parse(); + uASSERT_EQUAL(1, res); + } + + { // Missing arg before other opt + const char* argv[] = { "app-name", "-x", "-y" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + int y{}; + + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](int i){ y = i; return 0;}), "Help y"); + + auto res = args.parse(); + uASSERT_EQUAL(1, res); + } + + { // Unknown arg + const char* argv[] = { "app-name", "-T" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + int y{}; + + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](int i){ y = i; return 0;}), "Help y"); + + auto res = args.parse(); + uASSERT_EQUAL(1, res); + } + } + + void test_err_callback() + { + using namespace std::string_literals; + + { // Missing arg at trailing opt + const char* argv[] = { "app-name", "-x" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + bool called{false}; + args.set_err_cb( + [&](arg::error err, std::string_view opt) + { + called = true; + uASSERT_EQUAL(arg::error::missing_arg, err); + uASSERT_EQUAL("-x"s, opt); + }); + auto res = args.parse(); + uASSERT_EQUAL(1, res); + uASSERT(called); + } + + { // Invalid arg format + const char* argv[] = { "app-name", "-x", "abc" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + bool called{false}; + args.set_err_cb( + [&](arg::error err, std::string_view opt) + { + called = true; + uASSERT_EQUAL(arg::error::invalid_arg, err); + uASSERT_EQUAL("abc"s, opt); + }); + auto res = args.parse(); + uASSERT_EQUAL(1, res); + uASSERT(called); + } + + { // Invalid opt + const char* argv[] = { "app-name", "-y" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + bool called{false}; + args.set_err_cb( + [&](arg::error err, std::string_view opt) + { + called = true; + uASSERT_EQUAL(arg::error::invalid_opt, err); + uASSERT_EQUAL("-y"s, opt); + }); + auto res = args.parse(); + uASSERT_EQUAL(1, res); + uASSERT(called); + } + } + + void test_pos_callback() + { + using namespace std::string_literals; + const char* argv[] = + { "app-name", "foo", "-x", "42", "bar", "-X43", "-Y", "42" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int, std::optional<int>> args(argc, argv); + + int x{}; + int X{}; + int Y{}; + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add('X', "--opt-x", + std::function([&](std::optional<int> i) + { + uASSERT(i.has_value()); + X = *i; + return 0; + }), + "Help X"); + + args.add('Y', "--opt-y", + std::function([&](std::optional<int> i) + { + uASSERT(!i.has_value()); + Y = 1; + return 0; + }), + "Help X"); + + std::vector<std::string> pos; + args.set_pos_cb( + [&](std::string_view sv) + { + pos.push_back(std::string(sv)); + return 0; + }); + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(3u, pos.size()); + uASSERT_EQUAL("foo"s, pos[0]); + uASSERT_EQUAL("bar"s, pos[1]); + uASSERT_EQUAL("42"s, pos[2]); + uASSERT_EQUAL(42, x); + uASSERT_EQUAL(43, X); + uASSERT_EQUAL(1, Y); + } + + void test_grouped() + { + { + const char* argv[] = { "app-name", "-xyz", "42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int> args(argc, argv); + + bool x{false}; + bool y{false}; + int z{}; + args.add('x', "--long-x", + std::function([&](){ x = true; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](){ y = true; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](int i){ z = i; return 0;}), "Help z"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT(x); + uASSERT(y); + uASSERT_EQUAL(42, z); + } + + { + const char* argv[] = { "app-name", "-xyz42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int> args(argc, argv); + + bool x{false}; + bool y{false}; + int z{}; + args.add('x', "--long-x", + std::function([&](){ x = true; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](){ y = true; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](int i){ z = i; return 0;}), "Help z"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT(x); + uASSERT(y); + uASSERT_EQUAL(42, z); + } + + + { + const char* argv[] = { "app-name", "-xyz42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int, std::optional<int>> args(argc, argv); + + bool x{false}; + bool y{false}; + int z{}; + args.add('x', "--long-x", + std::function([&](){ x = true; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](){ y = true; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](std::optional<int> i) + { + uASSERT(i.has_value()); + z = *i; return 0; + }), "Help z"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT(x); + uASSERT(y); + uASSERT_EQUAL(42, z); + } + + { + const char* argv[] = { "app-name", "-xyz" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int, std::optional<int>> args(argc, argv); + + bool x{false}; + bool y{false}; + int z{}; + args.add('x', "--long-x", + std::function([&](){ x = true; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](){ y = true; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](std::optional<int> i) + { + uASSERT(!i.has_value()); + z = 1; return 0; + }), "Help z"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT(x); + uASSERT(y); + uASSERT_EQUAL(1, z); + } + + { + const char* argv[] = { "app-name", "-xyz", "42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int, std::optional<int>> args(argc, argv); + + bool x{false}; + bool y{false}; + int z{}; + args.add('x', "--long-x", + std::function([&](){ x = true; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](){ y = true; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](std::optional<int> i) + { + uASSERT(!i.has_value()); + z = 1; return 0; + }), "Help z"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT(x); + uASSERT(y); + uASSERT_EQUAL(1, z); + } + } + + void test_nullprogram() + { + using namespace std::string_literals; + // Inspired by https://nullprogram.com/blog/2020/08/01/ + + // + // Short options + // + { + const char* argv[] = { "program", "-a", "-b", "-c" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<char> r; + args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {}); + args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(3u, r.size()); + uASSERT_EQUAL('a', r[0]); + uASSERT_EQUAL('b', r[1]); + uASSERT_EQUAL('c', r[2]); + } + + { + const char* argv[] = { "program", "-abc" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<char> r; + args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {}); + args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(3u, r.size()); + uASSERT_EQUAL('a', r[0]); + uASSERT_EQUAL('b', r[1]); + uASSERT_EQUAL('c', r[2]); + } + + { + const char* argv[] = { "program", "-acb" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<char> r; + args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {}); + args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(3u, r.size()); + uASSERT_EQUAL('a', r[0]); + uASSERT_EQUAL('c', r[1]); + uASSERT_EQUAL('b', r[2]); + } + + { + const char* argv[] = { "program", "-i", "input.txt", "-o", "output.txt" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string> args(argc, argv); + + std::vector<std::string> r; + args.add('i', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + + args.add('o', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("input.txt"s, r[0]); + uASSERT_EQUAL("output.txt"s, r[1]); + } + + { + const char* argv[] = { "program", "-iinput.txt", "-ooutput.txt" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string> args(argc, argv); + + std::vector<std::string> r; + args.add('i', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + args.add('o', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("input.txt"s, r[0]); + uASSERT_EQUAL("output.txt"s, r[1]); + } + + { + const char* argv[] = { "program", "-abco", "output.txt" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.add('c', {}, std::function([&](){ r.push_back("c"); return 0;}), {}); + args.add('o', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("a"s, r[0]); + uASSERT_EQUAL("b"s, r[1]); + uASSERT_EQUAL("c"s, r[2]); + uASSERT_EQUAL("output.txt"s, r[3]); + } + + { + const char* argv[] = { "program", "-abcooutput.txt" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.add('c', {}, std::function([&](){ r.push_back("c"); return 0;}), {}); + args.add('o', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("a"s, r[0]); + uASSERT_EQUAL("b"s, r[1]); + uASSERT_EQUAL("c"s, r[2]); + uASSERT_EQUAL("output.txt"s, r[3]); + } + + { + // Optional omitted + const char* argv[] = { "program", "-c" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add('c', {}, + std::function([&](std::optional<std::string> c) + { + uASSERT(!c.has_value()); + r.push_back("c"); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(1u, r.size()); + uASSERT_EQUAL("c"s, r[0]); + } + + { + // Optional provided + const char* argv[] = { "program", "-cblue" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add('c', {}, + std::function([&](std::optional<std::string> c) + { + uASSERT(c.has_value()); + r.push_back(*c); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(1u, r.size()); + uASSERT_EQUAL("blue"s, r[0]); + } + + { + // Optional omitted (blue is a new argument) + const char* argv[] = { "program", "-c", "blue" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add('c', {}, + std::function([&](std::optional<std::string> c) + { + uASSERT(!c.has_value()); + r.push_back("c"); + return 0; + }), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("c"s, r[0]); + uASSERT_EQUAL("blue"s, r[1]); + } + + { + // Two seperate flags + const char* argv[] = { "program", "-c", "-x" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add('x', {}, std::function([&](){ r.push_back("x"); return 0;}), {}); + args.add('c', {}, + std::function([&](std::optional<std::string> c) + { + uASSERT(!c.has_value()); + r.push_back("c"); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("c"s, r[0]); + uASSERT_EQUAL("x"s, r[1]); + } + + { + // -c with argument "-x" + const char* argv[] = { "program", "-c-x" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add('x', {}, std::function([&](){ r.push_back("x"); return 0;}), {}); + args.add('c', {}, + std::function([&](std::optional<std::string> c) + { + uASSERT(c.has_value()); + r.push_back(*c); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(1u, r.size()); + uASSERT_EQUAL("-x"s, r[0]); + } + + { + const char* argv[] = { "program", "-a", "-b", "foo", "bar" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("a"s, r[0]); + uASSERT_EQUAL("b"s, r[1]); + uASSERT_EQUAL("foo"s, r[2]); + uASSERT_EQUAL("bar"s, r[3]); + } + + { + const char* argv[] = { "program", "-b", "-a", "foo", "bar" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("b"s, r[0]); + uASSERT_EQUAL("a"s, r[1]); + uASSERT_EQUAL("foo"s, r[2]); + uASSERT_EQUAL("bar"s, r[3]); + } + + { + const char* argv[] = { "program", "-a", "foo", "-b", "bar" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("a"s, r[0]); + uASSERT_EQUAL("foo"s, r[1]); + uASSERT_EQUAL("b"s, r[2]); + uASSERT_EQUAL("bar"s, r[3]); + } + + { + const char* argv[] = { "program", "foo", "-a", "-b", "bar" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("foo"s, r[0]); + uASSERT_EQUAL("a"s, r[1]); + uASSERT_EQUAL("b"s, r[2]); + uASSERT_EQUAL("bar"s, r[3]); + } + + { + const char* argv[] = { "program", "foo", "bar", "-a", "-b" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("foo"s, r[0]); + uASSERT_EQUAL("bar"s, r[1]); + uASSERT_EQUAL("a"s, r[2]); + uASSERT_EQUAL("b"s, r[3]); + } + + { + const char* argv[] = { "program", "-a", "-b", "--", "-x", "foo", "bar" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(5u, r.size()); + uASSERT_EQUAL("a"s, r[0]); + uASSERT_EQUAL("b"s, r[1]); + uASSERT_EQUAL("-x"s, r[2]); + uASSERT_EQUAL("foo"s, r[3]); + uASSERT_EQUAL("bar"s, r[4]); + } + + // + // Long options + // + { + const char* argv[] = { "program", "--sort" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--sort", + std::function([&](){ r.push_back("sort"); return 0;}), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(1u, r.size()); + uASSERT_EQUAL("sort"s, r[0]); + } + + { + const char* argv[] = { "program", "--no-sort" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--no-sort", + std::function([&](){ r.push_back("no-sort"); return 0;}), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(1u, r.size()); + uASSERT_EQUAL("no-sort"s, r[0]); + } + + { + const char* argv[] = + { "program", "--output", "output.txt", "--block-size", "1024" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string, int> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--output", + std::function([&](std::string output) + { + r.push_back(output); + return 0; + }), {}); + args.add({}, "--block-size", + std::function([&](int block_size) + { + r.push_back("["s + std::to_string(block_size) + "]"s); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("output.txt"s, r[0]); + uASSERT_EQUAL("[1024]"s, r[1]); + } + + { + const char* argv[] = + { "program", "--output=output.txt", "--block-size=1024" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string, int> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--output", + std::function([&](std::string output) + { + r.push_back(output); + return 0; + }), {}); + args.add({}, "--block-size", + std::function([&](int block_size) + { + r.push_back("["s + std::to_string(block_size) + "]"s); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("output.txt"s, r[0]); + uASSERT_EQUAL("[1024]"s, r[1]); + } + + { + const char* argv[] = + { "program", "--color", "--reverse" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--color", + std::function([&](std::optional<std::string> color) + { + uASSERT(!color.has_value()); + r.push_back("color"); + return 0; + }), {}); + args.add({}, "--reverse", + std::function([&]() + { + r.push_back("reverse"); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("color"s, r[0]); + uASSERT_EQUAL("reverse"s, r[1]); + } + + { + const char* argv[] = + { "program", "--color=never", "--reverse" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--color", + std::function([&](std::optional<std::string> color) + { + uASSERT(color.has_value()); + r.push_back(*color); + return 0; + }), {}); + args.add({}, "--reverse", + std::function([&]() + { + r.push_back("reverse"); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("never"s, r[0]); + uASSERT_EQUAL("reverse"s, r[1]); + } + + } +}; + +// Registers the fixture into the 'registry' +static ArgParserTest test; diff --git a/test/ctor.cc b/test/ctor.cc index b7bcc6d..d1c0188 100644 --- a/test/ctor.cc +++ b/test/ctor.cc @@ -8,198 +8,221 @@ namespace ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) { return - { { - .type = ctor::target_type::unit_test, - .system = ctor::output_system::build, - .target = "argsplit_test", - .sources = { - "argsplit_test.cc", - "testmain.cc", - "../src/util.cc", + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("argparser_test"), + ctor::sources{ + "argparser_test.cc", + "testmain.cc", + }, + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", + "-I../src", "-Iuunit", + "-DOUTPUT=\"argparser\"", + "-fexceptions", + }, }, - .flags = { - .cxxflags = { - "-std=c++20", "-O3", "-Wall", "-Werror", + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("generated_sources_test"), + ctor::sources{ + "generated_sources_test.cc", + "testmain.cc", + "../configuration.cc", + }, + ctor::depends({"libctor_nomain.a"}), + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", "-I../src", "-Iuunit", - "-DOUTPUT=\"argsplit\"", + "-DOUTPUT=\"generated_sources\"", + "-fexceptions", }, }, - }, - { - .type = ctor::target_type::unit_test, - .system = ctor::output_system::build, - .target = "pointerlist_test", - .sources = { - "pointerlist_test.cc", - "testmain.cc", - "../src/pointerlist.cc", + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("argsplit_test"), + ctor::sources{ + "argsplit_test.cc", + "testmain.cc", + "../src/util.cc", + }, + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", + "-I../src", "-Iuunit", + "-DOUTPUT=\"argsplit\"", + "-fexceptions", + }, }, - .flags = { - .cxxflags = { - "-std=c++20", "-O3", "-Wall", "-Werror", + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("pointerlist_test"), + ctor::sources{ + "pointerlist_test.cc", + "testmain.cc", + "../src/pointerlist.cc", + }, + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", "-I../src", "-Iuunit", "-DOUTPUT=\"pointerlist\"", + "-fexceptions", }, }, - }, - { - .type = ctor::target_type::unit_test, - .system = ctor::output_system::build, - .target = "deps_test", - .sources = { - "deps_test.cc", - "testmain.cc", - "../src/deps.cc", - "../src/util.cc", - }, - .depends = { "testprog", }, - .flags = { - .cxxflags = { - "-std=c++20", "-O3", "-Wall", "-Werror", + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("deps_test"), + ctor::sources{ + "deps_test.cc", + "testmain.cc", + "../src/deps.cc", + "../src/util.cc", + }, + ctor::depends({"testprog"}), + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", "-I../src", "-Iuunit", "-DOUTPUT=\"deps\"", + "-fexceptions", }, }, - }, - { - .type = ctor::target_type::unit_test, - .system = ctor::output_system::build, - .target = "testprog", - .sources = { - "testprog.cc", - }, - .flags = { - .cxxflags = { - "-std=c++20", "-O3", "-Wall", "-Werror", + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("testprog"), + ctor::sources{ + "testprog.cc", + }, + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", + "-fexceptions", }, }, - }, - { - .type = ctor::target_type::unit_test, - .system = ctor::output_system::build, - .target = "execute_test", - .sources = { - "execute_test.cc", - "testmain.cc", - "../src/execute.cc", - "../src/pointerlist.cc", - "../src/util.cc", - }, - .depends = { "testprog", }, - .flags = { - .cxxflags = { - "-std=c++20", "-O3", "-Wall", "-Werror", + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("execute_test"), + ctor::sources{ + "execute_test.cc", + "testmain.cc", + "../src/execute.cc", + "../src/pointerlist.cc", + "../src/util.cc", + }, + ctor::depends({"testprog"}), + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", "-I../src", "-Iuunit", "-DOUTPUT=\"execute\"", + "-fexceptions", }, - .ldflags = { "-pthread" }, - }, - }, - { - .type = ctor::target_type::unit_test, - .system = ctor::output_system::build, - .target = "tasks_test", - .sources = { - "tasks_test.cc", - "testmain.cc", - }, - .depends = { "libctor_nomain.a" }, - .flags = { - .cxxflags = { - "-std=c++20", "-O3", "-Wall", "-Werror", + ctor::ld_flags{ "-pthread" }, + }, + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("tasks_test"), + ctor::sources{ + "tasks_test.cc", + "testmain.cc", + }, + ctor::depends({"libctor_nomain.a"}), + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", "-I../src", "-Iuunit", "-DOUTPUT=\"tasks\"", + "-fexceptions", }, - .ldflags = { "-pthread" }, - }, - }, - { - .type = ctor::target_type::unit_test, - .system = ctor::output_system::build, - .target = "cycle_test", - .sources = { - "cycle_test.cc", - "testmain.cc", - }, - .depends = { "libctor_nomain.a" }, - .flags = { - .cxxflags = { - "-std=c++20", "-O3", "-Wall", "-Werror", + ctor::ld_flags{ "-pthread" }, + }, + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("cycle_test"), + ctor::sources{ + "cycle_test.cc", + "testmain.cc", + "../configuration.cc", + }, + ctor::depends({"libctor_nomain.a"}), + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", "-I../src", "-Iuunit", "-DOUTPUT=\"cycle\"", + "-fexceptions", }, - .ldflags = { "-pthread" }, - }, - }, - { - .type = ctor::target_type::unit_test, - .system = ctor::output_system::build, - .target = "source_type_test", - .sources = { - "source_type_test.cc", - "testmain.cc", - }, - .depends = { "libctor_nomain.a" }, - .flags = { - .cxxflags = { - "-std=c++20", "-O3", "-Wall", "-Werror", + ctor::ld_flags{ "-pthread" }, + }, + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("source_type_test"), + ctor::sources{ + "source_type_test.cc", + "testmain.cc", + "../configuration.cc", + }, + ctor::depends({"libctor_nomain.a"}), + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", "-I../src", "-Iuunit", "-DOUTPUT=\"source_type\"", + "-fexceptions", }, - .ldflags = { "-pthread" }, - }, - }, - { - .type = ctor::target_type::unit_test, - .system = ctor::output_system::build, - .target = "tools_test", - .sources = { - "tools_test.cc", - "testmain.cc", - "../src/util.cc", - "../src/tools.cc", - }, - //.depends = { "libctor_nomain.a" }, - .flags = { - .cxxflags = { - "-std=c++20", "-O3", "-Wall", "-Werror", + ctor::ld_flags{ "-pthread" }, + }, + { + ctor::target_type::unit_test, + ctor::output_system::build, + ctor::target("tools_test"), + ctor::sources{ + "tools_test.cc", + "testmain.cc", + "../configuration.cc", + }, + ctor::depends({"libctor_nomain.a"}), + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", "-I../src", "-Iuunit", "-DOUTPUT=\"tools\"", + "-fexceptions", }, }, - }, - { - .type = ctor::target_type::unit_test_library, - .system = ctor::output_system::build, - .target = "libctor_nomain.a", - .sources = { - "../src/build.cc", - "../src/configure.cc", - "../src/deps.cc", - "../src/execute.cc", - "../src/pointerlist.cc", - "../src/rebuild.cc", - "../src/tasks.cc", - "../src/task.cc", - "../src/task_ar.cc", - "../src/task_cc.cc", - "../src/task_fn.cc", - "../src/task_ld.cc", - "../src/task_so.cc", - "../src/tools.cc", - "../src/util.cc", - "../src/externals_manual.cc", - }, - .flags = { - .cxxflags = { - "-std=c++20", "-O3", "-Wall", "-Werror", + { + ctor::target_type::unit_test_library, + ctor::output_system::build, + ctor::target("libctor_nomain.a"), + ctor::sources{ + "../src/build.cc", + "../src/configure.cc", + "../src/deps.cc", + "../src/execute.cc", + "../src/pointerlist.cc", + "../src/rebuild.cc", + "../src/tasks.cc", + "../src/task.cc", + "../src/task_ar.cc", + "../src/task_cc.cc", + "../src/task_fn.cc", + "../src/task_ld.cc", + "../src/task_so.cc", + "../src/tools.cc", + "../src/util.cc", + "../src/externals_manual.cc", + }, + ctor::cxx_flags{ + "-std=c++20", "-O3", "-Wall", "-I../src", + "-fexceptions", }, - .ldflags = { "-pthread" }, + ctor::ld_flags{ "-pthread" }, }, - }, - }; + }; } } diff --git a/test/cycle_test.cc b/test/cycle_test.cc index 3b45632..8f4c296 100644 --- a/test/cycle_test.cc +++ b/test/cycle_test.cc @@ -11,23 +11,23 @@ ctor::build_configurations ctorTestConfigsCyclic(const ctor::settings&) { // No dependency { - .target = "target0", + ctor::target("target0"), }, // Direct (self-depends) { - .target = "target1", - .depends = { "target1" }, + ctor::target("target1"), + ctor::depends({ "target1" }), }, // Indirect cyclic depends { - .target = "target2", - .depends = { "target3" }, + ctor::target("target2"), + ctor::depends({ "target3" }), }, { - .target = "target3", - .depends = { "target2" }, + ctor::target("target3"), + ctor::depends({ "target2" }), }, }; } diff --git a/test/execute_test.cc b/test/execute_test.cc index 722b6ea..f8f902f 100644 --- a/test/execute_test.cc +++ b/test/execute_test.cc @@ -23,6 +23,8 @@ public: void return_value() { + constexpr int segfault_return_value = 11; + constexpr int exception_return_value = 6; ctor::settings s; auto cur_path = std::filesystem::path(paths::argv_0).parent_path(); std::vector<std::string> paths{{cur_path.string()}}; @@ -31,16 +33,21 @@ public: auto value = execute(s, cmd, {"retval", "0"}, {}, false); uASSERT_EQUAL(0, value); + value = execute(s, cmd, {"retval", "1"}, {}, false); uASSERT_EQUAL(1, value); + value = execute(s, "no-such-binary", {}, {}, false); uASSERT_EQUAL(1, value); + value = execute(s, cmd, {"segfault"}, {}, false); - uASSERT_EQUAL(11, value); + uASSERT_EQUAL(segfault_return_value, value); + value = execute(s, cmd, {"throw"}, {}, false); - uASSERT_EQUAL(6, value); + uASSERT_EQUAL(exception_return_value, value); + value = execute(s, cmd, {"abort"}, {}, false); - uASSERT_EQUAL(6, value); + uASSERT_EQUAL(exception_return_value, value); } void env() @@ -53,7 +60,7 @@ public: auto cmd = locate("testprog", paths); uASSERT(!cmd.empty()); - tmp_file tmp; + TmpFile tmp; std::map<std::string, std::string> env; @@ -83,11 +90,13 @@ public: chk = std::find(vars.begin(), vars.end(), "bar=42"s); uASSERT(chk != vars.end()); - // Check the one that should have overwritten the existing one (probably LANG=en_US.UTF-8 or something) + // Check the one that should have overwritten the existing one (probably + // LANG=en_US.UTF-8 or something) chk = std::find(vars.begin(), vars.end(), "LANG=foo"s); uASSERT(chk != vars.end()); - // Check that other vars are also there (ie. the env wasn't cleared on entry) + // Check that other vars are also there (ie. the env wasn't cleared on + // entry) uASSERT(vars.size() > 3); } }; diff --git a/test/generated_sources_test.cc b/test/generated_sources_test.cc new file mode 100644 index 0000000..29a48fc --- /dev/null +++ b/test/generated_sources_test.cc @@ -0,0 +1,135 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <uunit.h> + +#include <ctor.h> +#include <tasks.h> +#include <rebuild.h> + +class GeneratedSourcesTest + : public uUnit +{ +public: + GeneratedSourcesTest() + { + uTEST(GeneratedSourcesTest::test_custom_output); + uTEST(GeneratedSourcesTest::test_many_to_one_output); + } + + void setup() + { + // Make sure we start from a clean slate + getConfigFileList().clear(); + } + + void test_custom_output() + { + using namespace std::string_literals; + + ctor::reg( + [](const ctor::settings&) + { + return ctor::build_configurations{ + { + ctor::target("test1"), + ctor::sources{ + {"foo.cc", ctor::source_type::generated} + }, + }, + { + ctor::target("this_is_unused"), + ctor::sources{ + {"bar.x", ctor::output_file{"foo.cc"}}, + {"bar.y", ctor::output_file{"bar.cc"}}, + }, + []([[maybe_unused]]const std::string& input, + [[maybe_unused]]const std::string& output, + [[maybe_unused]]const ctor::build_configuration& config, + [[maybe_unused]]const ctor::settings& settings) + { + return 0; + }, + }, + }; + }); + ctor::settings settings{}; + auto tasks = getTasks(settings); + for(auto task : tasks) + { + uASSERT(task->registerDepTasks(tasks) == 0); + } + uASSERT_EQUAL(4u, tasks.size()); + bool found{false}; + for(const auto& task : tasks) + { + if(task->target() == "test1") + { + auto deps_test1 = task->getDependsTasks(); + uASSERT_EQUAL(1u, deps_test1.size()); + auto deps_foo_o = deps_test1[0]->getDependsTasks(); + uASSERT_EQUAL(1u, deps_foo_o.size()); + uASSERT_EQUAL("test/bar.x"s, deps_foo_o[0]->source()); + found = true; + } + } + uASSERT(found); + } + + void test_many_to_one_output() + { + using namespace std::string_literals; + + ctor::reg( + [](const ctor::settings&) + { + return ctor::build_configurations{ + { + ctor::target("test1"), + ctor::sources{ + {"foo.cc", ctor::source_type::generated} + }, + }, + { + ctor::target("foo.cc"), + ctor::sources{ + "bar.x", + "bar.y", + }, + []([[maybe_unused]]const std::vector<std::string>& input, + [[maybe_unused]]const std::string& output, + [[maybe_unused]]const ctor::build_configuration& config, + [[maybe_unused]]const ctor::settings& settings) + { + return 0; + }, + }, + }; + }); + ctor::settings settings{}; + auto tasks = getTasks(settings); + for(auto task : tasks) + { + uASSERT(task->registerDepTasks(tasks) == 0); + } + uASSERT_EQUAL(4u, tasks.size()); + bool found{false}; + for(const auto& task : tasks) + { + if(task->target() == "test1") + { + auto deps_test1 = task->getDependsTasks(); + uASSERT_EQUAL(1u, deps_test1.size()); + auto deps_foo_o = deps_test1[0]->getDependsTasks(); + uASSERT_EQUAL(1u, deps_foo_o.size()); + uASSERT_EQUAL("test/bar.x"s, deps_foo_o[0]->source()); + found = true; + } + } + uASSERT(found); + } + +}; + +// Registers the fixture into the 'registry' +static GeneratedSourcesTest test; diff --git a/test/source_type_test.cc b/test/source_type_test.cc index 288f1e5..345c591 100644 --- a/test/source_type_test.cc +++ b/test/source_type_test.cc @@ -26,12 +26,21 @@ std::ostream& operator<<(std::ostream& stream, const ctor::language& lang) return stream; } +const ctor::configuration& test_configuration() +{ + static ctor::configuration cfg{}; + cfg.build_toolchain = ctor::toolchain::gcc; + cfg.build_arch = ctor::arch::unix; + return cfg; +} +REG(test_configuration); + class TestableTaskCC : public TaskCC { public: TestableTaskCC(const ctor::source& source) - : TaskCC({}, {}, "build", source) + : TaskCC(ctor::target_type::object, {}, {}, "build", source) {} ctor::language language() const diff --git a/test/suite/ctor_files/ctor.cc.bar b/test/suite/ctor_files/ctor.cc.bar index 218f9cc..d77eb70 100644 --- a/test/suite/ctor_files/ctor.cc.bar +++ b/test/suite/ctor_files/ctor.cc.bar @@ -11,21 +11,20 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings) return { { - .name = "hello", - .target = "hello", - .sources = { + ctor::name("hello"), + ctor::target("hello"), + ctor::sources{ "hello.cc", }, - .flags = { - .cxxflags = { - "-std=c++20", - "-O3", - "-g", - "-Wall", - "-Werror", - }, + ctor::cxx_flags{ + "-std=c++20", + "-O3", + "-g", + "-Wall", + "-Werror", + "-fexceptions", }, - .externals = {"bar"}, + ctor::externals({"bar"}), } }; } @@ -35,14 +34,12 @@ ctor::external_configurations ctorExtConfigs(const ctor::settings& settings) return { { - .name = "bar", - .external = ctor::external_manual{ - .flags = { - .cflags = { "-D_B_" }, - .cxxflags = { "-D_A_", "-DBAR"}, - .ldflags = { "-D_C_" }, - .asmflags = { "-D_D_" }, - }, + ctor::name("bar"), + ctor::external_manual{ + ctor::c_flags{ "-D_B_" }, + ctor::cxx_flags{ "-D_A_", "-DBAR"}, + ctor::ld_flags{ "-D_C_" }, + ctor::asm_flags{ "-D_D_" }, }, // Creates --with-foo-prefix arg to configure which will be used for // -L and -I flags. diff --git a/test/suite/ctor_files/ctor.cc.base b/test/suite/ctor_files/ctor.cc.base index eab39c4..73b5cdb 100644 --- a/test/suite/ctor_files/ctor.cc.base +++ b/test/suite/ctor_files/ctor.cc.base @@ -11,21 +11,20 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings) return { { - .name = "hello", - .target = "hello", - .sources = { + ctor::name("hello"), + ctor::target("hello"), + ctor::sources{ "hello.cc", }, - .flags = { - .cxxflags = { - "-std=c++20", - "-O3", - "-g", - "-Wall", - "-Werror", - }, + ctor::cxx_flags{ + "-std=c++20", + "-O3", + "-g", + "-Wall", + "-Werror", + "-fexceptions", }, - .externals = {"bar"}, + ctor::externals({"bar"}), } }; } @@ -35,15 +34,13 @@ ctor::external_configurations ctorExtConfigs(const ctor::settings& settings) return { { - .name = "bar", - .external = ctor::external_manual + ctor::name("bar"), + ctor::external_manual { - .flags = { - .cflags = { "-D_B_" }, - .cxxflags = { "-D_A_", "-DFOO"}, - .ldflags = { "-D_C_" }, - .asmflags = { "-D_D_" }, - }, + ctor::c_flags{ "-D_B_" }, + ctor::cxx_flags{ "-D_A_", "-DFOO"}, + ctor::ld_flags{ "-D_C_" }, + ctor::asm_flags{ "-D_D_" }, }, // Creates --with-foo-prefix arg to configure which will be used for // -L and -I flags. diff --git a/test/suite/ctor_files/ctor.cc.generated b/test/suite/ctor_files/ctor.cc.generated new file mode 100644 index 0000000..96b6fb5 --- /dev/null +++ b/test/suite/ctor_files/ctor.cc.generated @@ -0,0 +1,104 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <ctor.h> +#include <filesystem> +#include <iostream> +#include <fstream> + +namespace +{ +ctor::build_configurations ctorConfigs(const ctor::settings& settings) +{ + return + { + { + ctor::target("world"), + ctor::sources{ + { "world.cc", ctor::source_type::generated }, + }, + ctor::cxx_flags{ + "-std=c++20", + "-O3", + "-g", + "-Wall", + "-Werror", + "-fexceptions", + }, + }, + { + ctor::target("foo"), + ctor::sources{ + { "foo.cc", ctor::source_type::generated }, + }, + ctor::cxx_flags{ + "-std=c++20", + "-O3", + "-g", + "-Wall", + "-Werror", + "-fexceptions", + }, + }, + { + ctor::target("this_is_unused"), + ctor::sources{ + {"hello.cc", ctor::output_file{"world.cc"}}, + {"hello.cc", ctor::output_file{"foo.cc"}}, + }, + [](const std::string& input, + const std::string& output, + const ctor::build_configuration& config, + const ctor::settings& settings) + { + namespace fs = std::filesystem; + std::cout << "Input: " << input << '\n'; + std::cout << "Output: " << output << '\n'; + fs::copy_file(input, output, fs::copy_options::overwrite_existing); + return 0; + }, + }, + + { + ctor::target("many_to_one"), + ctor::sources{ + {"many_to_one.cc", ctor::source_type::generated} + }, + ctor::cxx_flags{"-fexceptions"}, + }, + { + ctor::target("many_to_one.cc"), + ctor::sources{ + {"foo.cc", ctor::source_type::generated}, + {"hello.cc"}, + }, + [](const std::vector<std::string>& input, + const std::string& output, + const ctor::build_configuration& config, + const ctor::settings& settings) + { + std::cout << "Output: " << output << '\n'; + std::ofstream ofs(output); + bool comment{true}; + for(const auto& input_file : input) + { + std::cout << "Input: " << input_file << '\n'; + std::ifstream ifs(input_file); + std::string line; + while(std::getline(ifs, line)) + { + ofs << line << '\n'; + } + if(comment) ofs << "/*\n"; + comment = false; + } + ofs << "*/\n"; + return 0; + } + }, + + }; +} +} + +REG(ctorConfigs); diff --git a/test/suite/ctor_files/ctor.cc.generated2 b/test/suite/ctor_files/ctor.cc.generated2 new file mode 100644 index 0000000..2e36c0e --- /dev/null +++ b/test/suite/ctor_files/ctor.cc.generated2 @@ -0,0 +1,87 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <ctor.h> +#include <filesystem> +#include <iostream> +#include <fstream> + +namespace +{ +ctor::build_configurations ctorConfigs(const ctor::settings& settings) +{ + return + { + { + ctor::target("world"), + ctor::sources{ + { "world.cc", ctor::source_type::generated }, + }, + }, + { + ctor::target("foo"), + ctor::sources{ + { "foo.cc", ctor::source_type::generated }, + }, + }, + { + ctor::target("this_is_unused"), + ctor::sources{ + {"hello.cc", ctor::output_file{"world.cc"}}, + {"hello.cc", ctor::output_file{"foo.cc"}}, + }, + [](const std::string& input, + const std::string& output, + const ctor::build_configuration& config, + const ctor::settings& settings) + { + namespace fs = std::filesystem; + std::cout << "Input: " << input << '\n'; + std::cout << "Output: " << output << '\n'; + fs::copy_file(input, output, fs::copy_options::overwrite_existing); + return 0; + } + }, + + { + ctor::target("many_to_one"), + ctor::sources{ + {"many_to_one.cc", ctor::source_type::generated} + }, + ctor::cxx_flags{ "-fexceptions", }, + }, + { + ctor::target("many_to_one.cc"), + ctor::sources{ + {"hello.cc"}, + }, + [](const std::vector<std::string>& input, + const std::string& output, + const ctor::build_configuration& config, + const ctor::settings& settings) + { + std::cout << "Output: " << output << '\n'; + std::ofstream ofs(output); + bool comment{true}; + for(const auto& input_file : input) + { + std::cout << "Input: " << input_file << '\n'; + std::ifstream ifs(input_file); + std::string line; + while(std::getline(ifs, line)) + { + ofs << line << '\n'; + } + if(comment) ofs << "/*\n"; + comment = false; + } + ofs << "*/\n"; + return 0; + } + }, + + }; +} +} + +REG(ctorConfigs); diff --git a/test/suite/ctor_files/ctor.cc.multi b/test/suite/ctor_files/ctor.cc.multi index 2b88afe..fc4c7a4 100644 --- a/test/suite/ctor_files/ctor.cc.multi +++ b/test/suite/ctor_files/ctor.cc.multi @@ -13,21 +13,20 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings) return { { - .name = "hello", - .target = "hello", - .sources = { + ctor::name("hello"), + ctor::target("hello"), + ctor::sources{ "hello.cc", }, - .flags = { - .cxxflags = { - "-std=c++20", - "-O3", - "-g", - "-Wall", - "-Werror", - }, + ctor::cxx_flags{ + "-std=c++20", + "-O3", + "-g", + "-Wall", + "-Werror", + "-fexceptions", }, - .externals = {"bar"}, + ctor::externals({"bar"}), } }; } @@ -37,14 +36,12 @@ ctor::external_configurations ctorExtConfigs(const ctor::settings& settings) return { { - .name = "bar", - .external = ctor::external_manual{ - .flags = { - .cflags = { "-D_B_" }, - .cxxflags = { "-D_A_", "-DFOO"}, - .ldflags = { "-D_C_" }, - .asmflags = { "-D_D_" }, - }, + ctor::name("bar"), + ctor::external_manual{ + ctor::c_flags{ "-D_B_" }, + ctor::cxx_flags{ "-D_A_", "-DFOO"}, + ctor::ld_flags{ "-D_C_" }, + ctor::asm_flags{ "-D_D_" }, }, // Creates --with-foo-prefix arg to configure which will be used for // -L and -I flags. diff --git a/test/suite/test.cc b/test/suite/test.cc new file mode 100644 index 0000000..b096a8b --- /dev/null +++ b/test/suite/test.cc @@ -0,0 +1,370 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "../../src/util.cc" +#include "../../src/pointerlist.cc" +#include "../../src/execute.cc" + +#include <iostream> +#include <string> +#include <filesystem> +#include <thread> +#include <chrono> +#include <sstream> + +using namespace std::chrono_literals; + +int fail(int value = 1, + const std::source_location location = std::source_location::current()) +{ + std::cout << "*** Failure at line " << location.line() << '\n'; + exit(value); +} + +const std::string ctor_exe{"./ctor"}; +const std::string obj_ext{".o"}; + +void run_ctor(const std::vector<std::string>& args, + const std::source_location location = std::source_location::current()) +{ + ctor::settings settings{.verbose = 2}; + auto ret = execute(settings, ctor_exe, args); + if(ret != 0) + { + fail(ret, location); + } +} + +void assert_not_exists(const std::string& path, + const std::source_location location = std::source_location::current()) +{ + if(std::filesystem::exists(path)) + { + fail(1, location); + } +} + +void assert_exists(const std::string& path, + const std::source_location location = std::source_location::current()) +{ + if(!std::filesystem::exists(path)) + { + fail(1, location); + } +} + +void copy_config(std::string cfg) +{ + std::cout << "** ctor_files/ctor.cc." + cfg + "\n"; + std::filesystem::copy("ctor_files/ctor.cc." + cfg, "ctor.cc", + std::filesystem::copy_options::overwrite_existing); + if(std::filesystem::exists(ctor_exe)) + { + auto ctor_exe_time = std::filesystem::last_write_time(ctor_exe); + std::filesystem::last_write_time("ctor.cc", ctor_exe_time + 1s); + } +} + +class Tracker +{ +public: + Tracker(const std::string& file_) : file(file_) + { + capture(); // capture initial value + } + + void capture() + { + changed(); + } + + bool changed() + { + auto tmp = readFile(file); + auto content_changed = tmp != content; + content = tmp; + return content_changed; + } + +private: + std::string file; + std::string content; +}; + +void assert_not_changed(Tracker& tracker, + const std::source_location location = std::source_location::current()) +{ + if(tracker.changed()) + { + fail(1, location); + } +} + +void assert_changed(Tracker& tracker, + const std::source_location location = std::source_location::current()) +{ + if(!tracker.changed()) + { + fail(1, location); + } +} + +int main() +{ + ctor::settings settings{}; + settings.verbose = 2; + auto paths = get_paths(); + + std::string CXX = "g++"; + get_env("CXX", CXX); + std::string CTORDIR = "../../build"; + get_env("CTORDIR", CTORDIR); + std::string BUILDDIR = "build"; + get_env("BUILDDIR", BUILDDIR); + std::string CXXFLAGS; + get_env("CXXFLAGS", CXXFLAGS); + std::string LDFLAGS; + get_env("LDFLAGS", LDFLAGS); + + // Wipe the board + std::filesystem::remove_all(BUILDDIR); + std::filesystem::remove("configuration.cc"); + std::filesystem::remove("config.h"); + std::filesystem::remove(ctor_exe); + + ////////////////////////////////////////////////////////////////////////////// + // bootstrap + { + auto cxx_prog = locate(CXX, paths); + + std::vector<std::string> args = + {"-pthread", "-std=c++20", "-L", CTORDIR, "-lctor", "-I", "../../src", + "ctor.cc", "-o", ctor_exe}; + if(!CXXFLAGS.empty()) + { + auto tokens = argsplit(CXXFLAGS); + for(const auto& token : tokens) + { + args.push_back(token); + } + } + if(!LDFLAGS.empty()) + { + auto tokens = argsplit(LDFLAGS); + for(const auto& token : tokens) + { + args.push_back(token); + } + } + + // Compile bootstrap binary + copy_config("base"); + auto ret = execute(settings, cxx_prog, args); + if(ret != 0) + { + fail(ret); + } + } + ////////////////////////////////////////////////////////////////////////////// + // check if source file changes are tracked + { + // No build files should have been created yet + assert_not_exists(BUILDDIR); + + // capture ctor binary before configure is called + Tracker ctor_bin(ctor_exe); + run_ctor( + {"configure", "--ctor-includedir", "../../src", + "--ctor-libdir=" + CTORDIR, "--build-dir=" + BUILDDIR}); + + // ctor should be rebuilt at this point, so binary should have changed + assert_changed(ctor_bin); + + // configuration.cc should have been generated now + assert_exists("configuration.cc"); + assert_exists("config.h"); + + // Shouldn't compile anything yet - only configure + assert_not_exists(BUILDDIR + "/hello-hello_cc" + obj_ext); + + ctor_bin.capture(); + + // Run normally to build project + run_ctor({"-v"}); + + // Compiled object should now exist + assert_exists(BUILDDIR + "/hello-hello_cc" + obj_ext); + + // ctor should not have been rebuilt, so binary should be the same + assert_not_changed(ctor_bin); + + std::this_thread::sleep_for(1100ms); + + auto time = + std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext); + std::filesystem::last_write_time("hello.cc", time + 1s); + std::this_thread::sleep_for(1100ms); + + // Run normally to rebuild hello.cc + run_ctor({"-v"}); + + // Object file should have been recompiled + auto time2 = + std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext); + if(time == time2) + { + fail(); + } + } + + ////////////////////////////////////////////////////////////////////////////// + // check if flags change trigger rebuild + { + // Replace -DFOO with -DBAR in foo external.cxxflags + copy_config("bar"); + Tracker configuration_cc_bin("configuration.cc"); + Tracker ctor_bin(ctor_exe); + auto time = + std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext); + std::this_thread::sleep_for(1100ms); + + // Run normally to reconfigure, rebuild ctor and rebuild hello.cc + run_ctor({"-v"}); + + auto time2 = + std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext); + if(time == time2) + { + fail(); + } + + assert_changed(configuration_cc_bin); + assert_changed(ctor_bin); + } + + ////////////////////////////////////////////////////////////////////////////// + // check if included files in ctor.cc is tracked for changes + { + copy_config("multi"); + Tracker configuration_cc_bin("configuration.cc"); + Tracker ctor_bin(ctor_exe); + auto time = + std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext); + std::this_thread::sleep_for(1100ms); + + // Run normally to reconfigure, rebuild ctor and rebuild hello.cc + run_ctor({"-v"}); + + auto time2 = + std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext); + if(time == time2) + { + fail(); + } + + assert_changed(configuration_cc_bin); + assert_changed(ctor_bin); + + // now touching foobar.h, should retrigger re-configuration + time = std::filesystem::last_write_time(ctor_exe); + std::filesystem::last_write_time("foobar.h", time + 1s); + std::this_thread::sleep_for(1100ms); + + // Run normally to reconfigure, rebuild ctor and rebuild hello.cc + run_ctor({"-v"}); + + time2 = std::filesystem::last_write_time(ctor_exe); + if(time == time2) + { + fail(); + } + } + + ////////////////////////////////////////////////////////////////////////////// + // generated part1: one-to-one generation + { + copy_config("generated"); + std::filesystem::remove(BUILDDIR + "/world.cc"); + std::filesystem::remove(BUILDDIR + "/foo.cc"); + + Tracker configuration_cc_bin("configuration.cc"); + Tracker ctor_bin(ctor_exe); + std::this_thread::sleep_for(1100ms); + + // Run normally to reconfigure, rebuild ctor and build world.cc + run_ctor({"-v", "world"}); + + assert_changed(configuration_cc_bin); + assert_changed(ctor_bin); + + // foo.cc should not be generated at this point + assert_not_exists(BUILDDIR+"/foo.cc"); + + auto time_w = std::filesystem::last_write_time(BUILDDIR + "/world.cc"); + auto time_wo = + std::filesystem::last_write_time(BUILDDIR + "/" + BUILDDIR+ + "/world-world_cc" + obj_ext); + std::this_thread::sleep_for(1100ms); + + // now touching hello.cc, should trigger regeneration of world.cc and + // rebuild + std::filesystem::last_write_time("hello.cc", time_w + 1s); + run_ctor({"-v", "world"}); + + auto time_w2 = std::filesystem::last_write_time(BUILDDIR + "/world.cc"); + auto time_wo2 = + std::filesystem::last_write_time(BUILDDIR + "/" + BUILDDIR + + "/world-world_cc" + obj_ext); + if(time_w == time_w2) + { + fail(); + } + if(time_wo == time_wo2) + { + fail(); + } + } + + ////////////////////////////////////////////////////////////////////////////// + // generated part2: many-to-one generation + { + std::filesystem::remove(BUILDDIR + "/world.cc"); + std::filesystem::remove(BUILDDIR + "/foo.cc"); + std::filesystem::remove(BUILDDIR + "/many_to_one.cc"); + + run_ctor({"-v", "many_to_one"}); + + // world.cc should not be generated at this point + assert_not_exists(BUILDDIR+"/world.cc"); + + // foo.cc should have been generated at this point + assert_exists(BUILDDIR+"/foo.cc"); + + // many_to_one.cc should have been generated + assert_exists(BUILDDIR+"/many_to_one.cc"); + + auto time = std::filesystem::last_write_time(BUILDDIR + "/many_to_one.cc"); + std::this_thread::sleep_for(1100ms); + + Tracker ctor_bin(ctor_exe); + + // remove "foo.cc" from sources list, so it only contains hello.cc + copy_config("generated2"); + + // rebuild + run_ctor({"-v", "many_to_one"}); + + // Verify that the change resulted in a ctor rebuild + assert_changed(ctor_bin); + + // Verify that source-list change triggered a new build of many_to_one.cc + auto time2 = + std::filesystem::last_write_time(BUILDDIR + "/many_to_one.cc"); + if(time == time2) + { + fail(); + } + } + + return 0; +} diff --git a/test/suite/test.sh b/test/suite/test.sh index 97d2551..4638c0d 100755 --- a/test/suite/test.sh +++ b/test/suite/test.sh @@ -1,122 +1,4 @@ #!/bin/bash : ${CXX:=g++} -: ${CTORDIR:=../../build} -: ${BUILDDIR:=build} -CXX=$(which $CXX) - -function fail -{ - echo "*** Failure at line $1" - exit 1 -} - -function ctor -{ - echo "*** Running: ./ctor $*" - ./ctor $* -} - -STAT_FORMAT="-c %Y" -if [[ "$OSTYPE" == "darwin"* ]]; then - # Mac OSX - STAT_FORMAT="-f %B" -fi - -# Wipe the board -rm -Rf ${BUILDDIR} -rm -f configuration.cc -rm -f ctor - -echo "** ctor_files/ctor.cc.base" -cp ctor_files/ctor.cc.base ctor.cc - -# Compile bootstrap binary -$CXX -pthread $LDFLAGS $CXXFLAGS -std=c++20 -L${CTORDIR} -lctor -I../../src ctor.cc -o ctor || fail ${LINENO} - -# No build files should have been created yet -[ -d ${BUILDDIR} ] && fail ${LINENO} - -# capture md5 sum of ctor binary before configure is called -MD5=`md5sum ctor` -ctor configure --ctor-includedir ../../src --ctor-libdir=${CTORDIR} --build-dir=${BUILDDIR} - -# ctor should be rebuilt at this point, so md5 sum should have changed -(echo $MD5 | md5sum --status -c) && fail ${LINENO} - -# configuration.cc should have been generated now -[ ! -f configuration.cc ] && fail ${LINENO} - -# Shouldn't compile anything yet - only configure -[ -f ${BUILDDIR}/hello-hello_cc.o ] && fail ${LINENO} - -MD5=`md5sum ctor` - -# Run normally to build project -ctor -v - -# Compiled object should now exist -[ ! -f ${BUILDDIR}/hello-hello_cc.o ] && fail ${LINENO} - -# ctor should not have been rebuilt, so md5 sum should be the same -(echo $MD5 | md5sum --status -c) || fail ${LINENO} - -MOD1=`stat $STAT_FORMAT ${BUILDDIR}/hello-hello_cc.o` -touch hello.cc -sleep 1.1 - -# Run normally to rebuild hello.cc -ctor -v - -# Object file should have been recompiled -MOD2=`stat $STAT_FORMAT ${BUILDDIR}/hello-hello_cc.o` -echo $MOD1 -echo $MOD2 -[[ $MOD1 == $MOD2 ]] && fail ${LINENO} - -# Replacve -DFOO with -DBAR in foo external.cxxflags -echo "** ctor_files/ctor.cc.bar" -cp ctor_files/ctor.cc.bar ctor.cc - -MD5C=`md5sum configuration.cc` -MD5=`md5sum ctor` -MOD1=`stat $STAT_FORMAT build/hello-hello_cc.o` -sleep 1.1 - -# Run normally to reconfigure, rebuild ctor and rebuild hello.cc -ctor -v - -MOD2=`stat $STAT_FORMAT ${BUILDDIR}/hello-hello_cc.o` -[[ $MOD1 == $MOD2 ]] && fail ${LINENO} -(echo $MD5C | md5sum --status -c) && fail ${LINENO} -(echo $MD5 | md5sum --status -c) && fail ${LINENO} - -echo "** ctor_files/ctor.cc.multi" -cp ctor_files/ctor.cc.multi ctor.cc - -MD5C=`md5sum configuration.cc` -MD5=`md5sum ctor` -MOD1=`stat $STAT_FORMAT ${BUILDDIR}/hello-hello_cc.o` -sleep 1.1 - -# Run normally to reconfigure, rebuild ctor and rebuild hello.cc -ctor -v - -MOD2=`stat $STAT_FORMAT ${BUILDDIR}/hello-hello_cc.o` -[[ $MOD1 == $MOD2 ]] && fail ${LINENO} -(echo $MD5C | md5sum --status -c) && fail ${LINENO} -(echo $MD5 | md5sum --status -c) && fail ${LINENO} - -# now touching foobar.h, should retrigger re-configuration -touch foobar.h - -MOD1=`stat $STAT_FORMAT ctor` -sleep 1.1 - -# Run normally to reconfigure, rebuild ctor and rebuild hello.cc -ctor -v - -MOD2=`stat $STAT_FORMAT ctor` -[[ $MOD1 == $MOD2 ]] && fail ${LINENO} - -exit 0 +$CXX $LDFLAGS $CXXFLAGS -std=c++20 -Wall test.cc -o test && ./test diff --git a/test/tasks_test.cc b/test/tasks_test.cc index b444bd5..c8e4cf8 100644 --- a/test/tasks_test.cc +++ b/test/tasks_test.cc @@ -10,12 +10,12 @@ ctor::build_configurations ctorTestConfigs1(const ctor::settings&) return { { - .name = "Target1", - .target = "target1", - .sources = {"foo.cc", "bar.c"}, + ctor::name("Target1"), + ctor::target("target1"), + ctor::sources({"foo.cc", "bar.c"}), }, { - .target = "target2", + ctor::target("target2"), }, }; } @@ -25,10 +25,10 @@ ctor::build_configurations ctorTestConfigs2(const ctor::settings&) return { { - .target = "target3", + ctor::target("target3"), }, { - .target = "target4", + ctor::target("target4"), }, }; } @@ -38,14 +38,14 @@ namespace test_global { ctor::toolchain toolchain{}; ctor::arch arch{}; } -const ctor::configuration& ctor::get_configuration() +const ctor::configuration& test_configuration() { static ctor::configuration cfg{}; cfg.build_toolchain = test_global::toolchain; cfg.build_arch = test_global::arch; return cfg; } - +REG(test_configuration); REG(ctorTestConfigs1); REG(ctorTestConfigs2); @@ -72,7 +72,7 @@ public: const ctor::settings& settings, const std::string& name, bool dirty, const std::vector<std::string>& deps = {}) - : Task(config, settings, {}) + : Task(ctor::target_type::executable, config, settings, {}) , task_name(name) , task_dirty(dirty) , task_deps(deps) @@ -170,7 +170,7 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(nullptr, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(nullptr, getNextTask({}, allTasks, dirtyTasks)); } { // Zero (One task, no dirty) @@ -187,7 +187,7 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(nullptr, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(nullptr, getNextTask({}, allTasks, dirtyTasks)); } { // One (One task, one dirty) @@ -205,7 +205,7 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(task1, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(task1, getNextTask({}, allTasks, dirtyTasks)); uASSERT_EQUAL(0u, dirtyTasks.size()); } @@ -226,7 +226,7 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(task2, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(task2, getNextTask({}, allTasks, dirtyTasks)); uASSERT_EQUAL(0u, dirtyTasks.size()); } @@ -250,7 +250,7 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(task2, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(task2, getNextTask({}, allTasks, dirtyTasks)); uASSERT_EQUAL(0u, dirtyTasks.size()); } @@ -275,7 +275,7 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(task1, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(task1, getNextTask({}, allTasks, dirtyTasks)); uASSERT_EQUAL(1u, dirtyTasks.size()); } } diff --git a/test/tmpfile.h b/test/tmpfile.h index 5d114d0..0f83a20 100644 --- a/test/tmpfile.h +++ b/test/tmpfile.h @@ -3,39 +3,29 @@ // See accompanying file LICENSE for details. #pragma once -#include <cstdlib> -#include <unistd.h> +#include <cstdio> -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#endif - -struct tmp_file +class TmpFile { - tmp_file(const std::string& data = {}) +public: + TmpFile(const std::string& data = {}) { - int fd; -#ifdef _WIN32 - char templ[] = "ctor_tmp_file-XXXXXX"; // buffer for filename - _mktemp_s(templ, sizeof(templ)); - fd = open(templ, O_CREAT | O_RDWR); -#else - char templ[] = "/tmp/ctor_tmp_file-XXXXXX"; // buffer for filename - fd = mkstemp(templ); -#endif - filename = templ; - auto sz = write(fd, data.data(), data.size()); - (void)sz; - close(fd); + auto tmp_dir = std::filesystem::temp_directory_path(); + auto tmp_file_template = tmp_dir / "ctor_tmp_file-"; + std::FILE* fp{nullptr}; + int counter{}; + while(!fp) + { + filename = tmp_file_template.string() + std::to_string(counter++); + fp = std::fopen(filename.data(), "wx"); + } + std::fwrite(data.data(), data.size(), 1, fp); + std::fclose(fp); } - ~tmp_file() + ~TmpFile() { - unlink(filename.data()); + std::filesystem::remove(filename); } const std::string& get() const diff --git a/test/tools_test.cc b/test/tools_test.cc index 5ae04c3..cfbcbce 100644 --- a/test/tools_test.cc +++ b/test/tools_test.cc @@ -114,29 +114,6 @@ bool operator!=(const ctor::asm_flag& a, const ctor::asm_flag& b) #include <uunit.h> -const ctor::configuration& ctor::get_configuration() -{ - static ctor::configuration cfg; - return cfg; -} - -std::string ctor::configuration::get(const std::string& key, [[maybe_unused]]const std::string& default_value) const -{ - if(key == ctor::cfg::host_cxx) - { - return {}; - } - - if(key == ctor::cfg::build_cxx) - { - return {}; - } - - assert(false); // bad key - - return {}; -} - class ToolsTest : public uUnit { |
