diff options
52 files changed, 3756 insertions, 594 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 290f412..a558359 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,48 +1,74 @@ pipeline { agent any stages { - //////////////////////////////////////////////////// - stage('Linux gcc') { - agent { label 'linux && gcc && c++20' } - steps { - echo 'Cleaning workspace ...' - sh 'git clean -d -x -f' - echo 'Building (gcc) ...' - sh 'CXX=g++ ./bootstrap.sh' - echo 'Testing (gcc) ...' - sh './ctor check' - echo 'Testing suite (gcc) ...' - sh '(cd test/suite; CTORDIR=../../build CXX=g++ ./test.sh)' - } - post { - always { - xunit(thresholds: [ skipped(failureThreshold: '0'), - failed(failureThreshold: '0') ], - tools: [ CppUnit(pattern: 'build/test/*.xml') ]) + stage('all targets') + { + parallel { + //////////////////////////////////////////////////// + stage('MacOSX clang') { + agent { label 'macos' } + steps { + 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' + 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)' + } + post { + always { + xunit(thresholds: [ skipped(failureThreshold: '0'), + failed(failureThreshold: '0') ], + tools: [ CppUnit(pattern: 'build/test/*.xml') ]) + } + } } - } - } - //////////////////////////////////////////////////// - stage('Linux clang') { - agent { label 'linux && clang && c++20' } - steps { - echo 'Cleaning workspace ...' - sh 'git clean -d -x -f' - echo 'Building (clang) ...' - sh 'CXX=clang++ ./bootstrap.sh' - echo 'Testing (clang) ...' - sh './ctor check' - echo 'Testing suite (clang) ...' - sh '(cd test/suite; CTORDIR=../../build CXX=clang++ ./test.sh)' - } - post { - always { - xunit(thresholds: [ skipped(failureThreshold: '0'), - failed(failureThreshold: '0') ], - tools: [ CppUnit(pattern: 'build/test/*.xml') ]) + //////////////////////////////////////////////////// + stage('Linux gcc') { + agent { label 'linux && gcc && c++20' } + steps { + echo 'Cleaning workspace ...' + sh 'git clean -d -x -f' + echo 'Building (gcc) ...' + sh 'CXX=g++ ./bootstrap.sh' + echo 'Testing (gcc) ...' + sh './ctor check' + echo 'Testing suite (gcc) ...' + sh '(cd test/suite; CTORDIR=../../build CXX=g++ ./test.sh)' + } + post { + always { + xunit(thresholds: [ skipped(failureThreshold: '0'), + failed(failureThreshold: '0') ], + tools: [ CppUnit(pattern: 'build/test/*.xml') ]) + } + } + } + //////////////////////////////////////////////////// + stage('Linux clang') { + agent { label 'linux && clang && c++20' } + steps { + echo 'Cleaning workspace ...' + sh 'git clean -d -x -f' + echo 'Building (clang) ...' + sh 'CXX=clang++ ./bootstrap.sh' + echo 'Testing (clang) ...' + sh './ctor check' + echo 'Testing suite (clang) ...' + sh '(cd test/suite; CTORDIR=../../build CXX=clang++ ./test.sh)' + } + post { + always { + xunit(thresholds: [ skipped(failureThreshold: '0'), + failed(failureThreshold: '0') ], + tools: [ CppUnit(pattern: 'build/test/*.xml') ]) + } + } } + //////////////////////////////////////////////////// } } - //////////////////////////////////////////////////// } } diff --git a/bootstrap.sh b/bootstrap.sh index 9e8cdca..4dd57ae 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,13 +1,12 @@ #!/bin/sh set -e -: ${CXX:=g++} +: ${CXX:=c++} : ${BUILDDIR:=build} -CXX=$(which $CXX) echo "Bootstrapping..." -$CXX -std=c++20 -Wall -O3 -Isrc -pthread src/bootstrap.cc ctor.cc test/ctor.cc -o ctor +$CXX $LDFLAGS $CXXFLAGS -std=c++20 -Wall -O3 -Isrc -pthread src/bootstrap.cc ctor.cc -o ctor ./ctor -$CXX -std=c++20 -Wall -O3 -Isrc -pthread ctor.cc test/ctor.cc -L$BUILDDIR -lctor -o ctor +$CXX $LDFLAGS $CXXFLAGS -std=c++20 -Wall -O3 -Isrc -pthread ctor.cc test/ctor.cc -L$BUILDDIR -lctor -o ctor ./ctor configure --ctor-includedir=src --ctor-libdir=$BUILDDIR --build-dir=$BUILDDIR echo "Done. Now run ./ctor to (re)build." @@ -15,9 +15,11 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings) .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", diff --git a/src/argparser.h b/src/argparser.h new file mode 100644 index 0000000..c5337e0 --- /dev/null +++ b/src/argparser.h @@ -0,0 +1,477 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <functional> +#include <variant> +#include <optional> +#include <string> +#include <type_traits> +#include <stdexcept> +#include <iostream> +#include <limits> + +namespace arg +{ +struct noarg {}; +template<typename T> +struct Opt +{ + char shortopt; + std::string longopt; + std::function<int(T)> cb; + std::string help; + T t{}; +}; + +template<> +struct Opt<noarg> +{ + char shortopt; + std::string longopt; + std::function<int()> cb; + std::string help; + noarg t{}; +}; + +template<typename Callable, typename... Args> +auto call_if(Callable cb, Args... args) +{ + using Ret = std::invoke_result_t<decltype(cb), Args&&...>; + if constexpr (std::is_same_v<Ret, void>) + { + if(cb) + { + return cb(std::forward<Args>(args)...); + } + } + else + { + if(cb) + { + return cb(std::forward<Args>(args)...); + } + return Ret{}; + } +} + +enum class error +{ + missing_arg, + invalid_arg, + invalid_opt, +}; + +template<typename... Ts> +class Parser +{ +public: + struct missing_arg{}; + + Parser(int argc_, const char* const* argv_) + : argc(argc_) + , argv(argv_) + { + } + + int parse() const + { + bool demarcate{false}; + for(int i = 1; i < argc; ++i) // skip argv[0] which is program name + { + std::string_view arg{argv[i]}; + if(arg.size() == 0) + { + // Empty arg - This shouldn't happen + continue; + } + + if(arg[0] != '-' || demarcate) // positional arg + { + auto res = call_if(pos_cb, arg); + if(res != 0) + { + return res; + } + continue; + } + + if(arg == "--") + { + demarcate = true; + continue; + } + + bool was_handled{false}; + enum class state { handled, unhandled }; + + if(arg.size() > 1 && arg[0] == '-' && arg[1] == '-') // long + { + for(const auto& option : options) + { + auto ret = + std::visit([&](auto&& opt) -> std::pair<int, state> + { + if(opt.longopt != arg && + !arg.starts_with(opt.longopt+'=')) + { + return {0, state::unhandled}; + } + try + { + using T = std::decay_t<decltype(opt)>; + if constexpr (std::is_same_v<T, Opt<noarg>>) + { + return {opt.cb(), state::handled}; + } + else + { + return {opt.cb(convert(i, opt.t)), + state::handled}; + } + } + catch(std::invalid_argument&) + { + call_if(err_cb, error::invalid_arg, argv[i]); + return {1, state::handled}; + } + catch(missing_arg&) + { + call_if(err_cb, error::missing_arg, argv[i]); + return {1, state::handled}; + } + }, option); + if(ret.second == state::handled && ret.first != 0) + { + return ret.first; + } + was_handled |= ret.second == state::handled; + if(was_handled) + { + break; + } + } + } + else + if(arg.size() > 1 && arg[0] == '-') // short + { + for(auto index = 1u; index < arg.size(); ++index) + { + was_handled = false; + for(const auto& option : options) + { + auto ret = + std::visit([&](auto&& opt) -> std::pair<int, state> + { + char c = arg[index]; + if(opt.shortopt != c) + { + return {0, state::unhandled}; + } + try + { + using T = std::decay_t<decltype(opt)>; + if constexpr (std::is_same_v<T, Opt<noarg>>) + { + return {opt.cb(), state::handled}; + } + else + { + // Note: the rest of arg is converted to opt + auto idx = index; + // set index out of range all was eaten as arg + index = std::numeric_limits<int>::max(); + return {opt.cb(convert_short(&arg[idx], + i, opt.t)), + state::handled}; + } + } + catch(std::invalid_argument&) + { + call_if(err_cb, error::invalid_arg, argv[i]); + return {1, state::handled}; + } + catch(missing_arg&) + { + call_if(err_cb, error::missing_arg, argv[i]); + return {1, state::handled}; + } + }, option); + if(ret.second == state::handled && ret.first != 0) + { + return ret.first; + } + was_handled |= ret.second == state::handled; + if(was_handled) + { + break; + } + } + } + } + + if(!was_handled) + { + call_if(err_cb, error::invalid_opt, arg); + return 1; + } + } + return 0; + } + + template<typename T> + void add(char shortopt, + const std::string& longopt, + std::function<int(T)> cb, + const std::string& help) + { + options.emplace_back(Opt<T>{shortopt, longopt, cb, help}); + } + + void add(char shortopt, + const std::string& longopt, + std::function<int()> cb, + const std::string& help) + { + options.emplace_back(Opt<noarg>{shortopt, longopt, cb, help}); + } + + void set_pos_cb(std::function<int(std::string_view)> cb) + { + pos_cb = cb; + } + + void set_err_cb(std::function<void(error, std::string_view)> cb) + { + err_cb = cb; + } + + std::string prog_name() const + { + if(argc < 1) + { + return {}; + } + return argv[0]; + } + + void help() const + { + constexpr std::size_t width{26}; + constexpr std::size_t column_width{80}; + + for(const auto& option : options) + { + std::visit( + [&](auto&& opt) + { + std::string _args; + using T = std::decay_t<decltype(opt)>; + if constexpr (std::is_same_v<T, Opt<noarg>>) + { + } + else if constexpr (std::is_same_v<T, Opt<int>>) + { + _args = "<int>"; + } + else if constexpr (std::is_same_v<T, Opt<std::optional<int>>>) + { + _args = "[int]"; + } + else if constexpr (std::is_same_v<T, Opt<std::string>>) + { + _args = "<str>"; + } + else if constexpr (std::is_same_v<T, Opt<std::optional<std::string>>>) + { + _args = "[str]"; + } + else if constexpr (std::is_same_v<T, Opt<double>>) + { + _args = "<real>"; + } + else if constexpr (std::is_same_v<T, Opt<std::optional<double>>>) + { + _args = "[real]"; + } + else + { + static_assert(std::is_same_v<T, void>, "missing"); + } + + std::string option_str; + if(opt.shortopt != '\0' && !opt.longopt.empty()) + { + option_str = " -" + std::string(1, opt.shortopt) + ", " + + opt.longopt + " " + _args; + } + else if(opt.shortopt != '\0') + { + option_str = " -" + std::string(1, opt.shortopt) + _args; + } + else if(!opt.longopt.empty()) + { + option_str = " " + std::string(opt.longopt) + " " + _args; + } + + std::string padding; + if(option_str.size() < width) + { + padding.append(width - option_str.size(), ' '); + } + else + { + padding = "\n"; + padding.append(width, ' '); + } + + std::cout << option_str << padding; + + auto i = width; + for(auto c : opt.help) + { + if((c == '\n') || (i > column_width && (c == ' ' || c == '\t'))) + { + std::string _padding(width, ' '); + std::cout << '\n' << _padding; + i = width; + continue; + } + std::cout << c; + ++i; + } + std::cout << '\n'; + }, option); + } + } + +private: + template<typename T> + T convert(int& i, T) const + { + auto opt = convert(i, std::optional<T>{}); + if(!opt) + { + throw missing_arg{}; + } + return *opt; + } + + template<typename T> + std::optional<T> convert(int& i, std::optional<T>) const + { + std::string arg; + bool has_arg{false}; + std::string opt = argv[i]; + if(opt.starts_with("--")) + { + // long opt + auto equals_pos = opt.find('='); + if(equals_pos != std::string::npos) + { + arg = opt.substr(equals_pos + 1); + has_arg = true; + } + else if(i+1 < argc) + { + arg = argv[i+1]; + has_arg = !arg.starts_with("-"); + if(has_arg) + { + ++i; + } + } + } + + if(!has_arg) + { + return {}; + } + + if constexpr (std::is_same_v<T, int>) + { + return std::stoi(arg); + } + else if constexpr (std::is_same_v<T, double>) + { + return std::stod(arg); + } + else if constexpr (std::is_same_v<T, std::string>) + { + return arg; + } + else + { + static_assert(std::is_same_v<T, void>, "missing"); + } + return {}; + } + + template<typename T> + T convert_short(const char* arg_, int& i, T) const + { + auto opt = convert_short(arg_, i, std::optional<T>{}, false); + if(!opt) + { + throw missing_arg{}; + } + return *opt; + } + + template<typename T> + std::optional<T> convert_short(const char* arg_, int& i, + std::optional<T>, bool optional = true) const + { + std::string arg; + bool has_arg{false}; + std::string opt = arg_; + if(opt.length() > 1) + { + // arg in same token + arg = opt.substr(1); + has_arg = true; + } + else if(!optional && i+1 < argc) + { + arg = argv[i+1]; + has_arg = true;//!arg.starts_with("-"); + if(has_arg) + { + ++i; + } + } + + if(!has_arg) + { + return {}; + } + + if constexpr (std::is_same_v<T, int>) + { + return std::stoi(arg); + } + else if constexpr (std::is_same_v<T, double>) + { + return std::stod(arg); + } + else if constexpr (std::is_same_v<T, std::string>) + { + return arg; + } + else + { + static_assert(std::is_same_v<T, void>, "missing"); + } + return {}; + } + + using Opts = std::variant<Opt<noarg>, Opt<Ts>...>; + std::vector<Opts> options; + std::function<int(std::string_view)> pos_cb; + int argc; + const char* const* argv; + std::function<void(error, std::string_view)> err_cb; +}; + +} // arg:: diff --git a/src/bootstrap.cc b/src/bootstrap.cc index 471c844..836504e 100644 --- a/src/bootstrap.cc +++ b/src/bootstrap.cc @@ -19,6 +19,7 @@ #include "tasks.cc" #include "build.cc" #include "tools.cc" +#include "pointerlist.cc" const std::filesystem::path configurationFile("configuration.cc"); const std::filesystem::path configHeaderFile("config.h"); @@ -41,46 +42,69 @@ bool ctor::configuration::has(const std::string& key) const return false; } -const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const +std::string ctor::configuration::get(const std::string& key, const std::string& default_value) const { + static auto paths = get_paths(); auto cxx_env = std::getenv("CXX"); if(key == ctor::cfg::build_cxx && cxx_env) { - static std::string s = cxx_env; - return s; + static auto cxx_prog = locate(cxx_env, paths); + return cxx_prog; } auto cc_env = std::getenv("CC"); if(key == ctor::cfg::build_cc && cc_env) { - static std::string s = cc_env; - return s; + static auto cc_prog = locate(cc_env, paths); + return cc_prog; } auto ld_env = std::getenv("LD"); if(key == ctor::cfg::build_ld && ld_env) { - static std::string s = ld_env; - return s; + static auto ld_prog = locate(ld_env, paths); + return ld_prog; } auto ar_env = std::getenv("AR"); if(key == ctor::cfg::build_ar && ar_env) { - static std::string s = ar_env; - return s; + static auto ar_prog = locate(ar_env, paths); + return ar_prog; } auto builddir_env = std::getenv("BUILDDIR"); if(key == ctor::cfg::builddir && builddir_env) { - static std::string s = builddir_env; - return s; + return builddir_env; } return default_value; } +std::string ctor::configuration::getenv(const std::string& key) const +{ + auto envit = env.find(key); + if(envit != env.end()) + { + return envit->second; + } + + auto env = std::getenv(key.data()); + if(env) + { + return env; + } + + return {}; +} + +std::vector<std::string> readDeps(const std::string& depFile, + ctor::toolchain toolchain) +{ + return {}; +} + int main(int argc, char* argv[]) { auto args = std::span(argv, static_cast<std::size_t>(argc)); diff --git a/src/build.cc b/src/build.cc index 906c3ea..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()) @@ -131,17 +131,35 @@ int build(const ctor::settings& settings, return 0; } -namespace -{ -std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task) +std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task, + std::vector<std::shared_ptr<Task>> trace) { std::vector<std::shared_ptr<Task>> tasks; tasks.push_back(task); + trace.push_back(task); auto deps = task->getDependsTasks(); for(const auto& dep : deps) { - auto depSet = getDepTasks(dep); + if(std::find(trace.begin(), trace.end(), dep) != trace.end()) + { + trace.push_back(dep); + std::cerr << "Error: Cyclic dependency detected: "; + bool first{true}; + for(auto t : trace) + { + if(!first) + { + std::cerr << " -> "; + } + + first = false; + std::cerr << t->target(); + } + std::cerr << '\n'; + throw 1; + } + auto depSet = getDepTasks(dep, trace); for(const auto& dep_inner : depSet) { if(std::find(tasks.begin(), tasks.end(), dep_inner) == tasks.end()) @@ -153,7 +171,6 @@ std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task) return tasks; } -} int build(const ctor::settings& settings, const std::string& name, @@ -167,20 +184,27 @@ int build(const ctor::settings& settings, { task_found = true; - auto depSet = getDepTasks(task); - std::vector<std::shared_ptr<Task>> ts; - for(const auto& task_inner : depSet) + try { - if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + auto depSet = getDepTasks(task); + std::vector<std::shared_ptr<Task>> ts; + for(const auto& task_inner : depSet) { - ts.push_back(task_inner); + if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + { + ts.push_back(task_inner); + } } - } - auto ret = build(settings, name, ts, all_tasks, dryrun); - if(ret != 0) + auto ret = build(settings, name, ts, all_tasks, dryrun); + if(ret != 0) + { + return ret; + } + } + catch(...) { - return ret; + return 1; // cycle detected } break; @@ -214,14 +238,21 @@ int build(const ctor::settings& settings, { task_found = true; - auto depSet = getDepTasks(task); - for(const auto& task_inner : depSet) + try { - if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + auto depSet = getDepTasks(task); + for(const auto& task_inner : depSet) { - ts.push_back(task_inner); + if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + { + ts.push_back(task_inner); + } } } + catch(...) + { + return 1; // cycle detected + } } } } diff --git a/src/build.h b/src/build.h index 500fb7f..7296f76 100644 --- a/src/build.h +++ b/src/build.h @@ -33,3 +33,8 @@ int build(const ctor::settings& settings, const std::vector<Target>& targets, const std::vector<std::shared_ptr<Task>>& all_tasks, bool dryrun = false); + +// Recursively build vector of dependency tasks from source task. +// Throws if a cycle is detected. +std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task, + std::vector<std::shared_ptr<Task>> trace = {}); diff --git a/src/configure.cc b/src/configure.cc index 995e340..910b878 100644 --- a/src/configure.cc +++ b/src/configure.cc @@ -8,8 +8,7 @@ #include <fstream> #include <optional> #include <span> - -#include <getoptpp/getoptpp.hpp> +#include <cstring> #include "execute.h" #include "ctor.h" @@ -18,6 +17,7 @@ #include "externals.h" #include "tools.h" #include "util.h" +#include "argparser.h" const std::filesystem::path configurationFile("configuration.cc"); const std::filesystem::path configHeaderFile("config.h"); @@ -31,7 +31,11 @@ const ctor::configuration& __attribute__((weak)) ctor::get_configuration() static bool initialised{false}; if(!initialised) { - cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, "/usr/bin/g++")); + std::string cxx_prog{"c++"}; + get_env("CXX", cxx_prog); + + cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, cxx_prog)); + initialised = true; } return cfg; @@ -69,7 +73,8 @@ bool ctor::configuration::has(const std::string& key) const return tools.find(key) != tools.end(); } -const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const +std::string ctor::configuration::get(const std::string& key, + const std::string& default_value) const { if(key == ctor::cfg::ctor_includedir && ctor::includedir) { @@ -91,14 +96,51 @@ const std::string& ctor::configuration::get(const std::string& key, const std::s return ctor::conf_values[key]; } - if(has(key)) + if(tools.find(key) != tools.end()) { return tools.at(key); } + std::string value; + if(key == ctor::cfg::build_cxx && get_env("CXX", value)) + { + return value; + } + + if(key == ctor::cfg::build_cc && get_env("CC", value)) + { + return value; + } + + if(key == ctor::cfg::build_ld && get_env("LD", value)) + { + return value; + } + + if(key == ctor::cfg::build_ar && get_env("AR", value)) + { + return value; + } + return default_value; } +std::string ctor::configuration::getenv(const std::string& key) const +{ + auto envit = env.find(key); + if(envit != env.end()) + { + return envit->second; + } + + if(std::string value; get_env(key.data(), value)) + { + return value; + } + + return {}; +} + class Args : public std::vector<char*> { @@ -106,20 +148,16 @@ public: Args(const std::vector<std::string>& args) { resize(args.size() + 1); - (*this)[0] = strdup("./ctor"); + owning_container.push_back("./ctor"); + (*this)[0] = owning_container.back().data(); for(std::size_t i = 0; i < size() - 1; ++i) { - (*this)[i + 1] = strdup(args[i].data()); + owning_container.push_back(args[i]); + (*this)[i + 1] = owning_container.back().data(); } } - ~Args() - { - for(std::size_t i = 0; i < size(); ++i) - { - free((*this)[i]); - } - } + std::deque<std::string> owning_container; }; namespace { @@ -210,8 +248,7 @@ int regenerateCache(ctor::settings& settings, { Args vargs(args); - dg::Options opt; - int key{128}; + arg::Parser<std::string> opt(static_cast<int>(vargs.size()), vargs.data()); std::string build_arch_prefix; std::string build_path; @@ -225,97 +262,110 @@ int regenerateCache(ctor::settings& settings, std::string ctor_libdir; std::string builddir; - opt.add("build-dir", required_argument, 'b', - "Set output directory for build files (default: '" + - settings.builddir + "').", - [&]() { - settings.builddir = optarg; - builddir = optarg; + opt.add('b', "--build-dir", + std::function([&](std::string arg) + { + settings.builddir = arg; + builddir = arg; return 0; - }); + }), + "Set output directory for build files (default: '" + + settings.builddir + "')."); - opt.add("verbose", no_argument, 'v', - "Be verbose. Add multiple times for more verbosity.", - [&]() { + opt.add('v', "--verbose", + std::function([&]() + { settings.verbose++; return 0; - }); + }), + "Be verbose. Add multiple times for more verbosity."); - opt.add("cc", required_argument, key++, - "Use specified c-compiler instead of gcc.", - [&]() { - cc_prog = optarg; + opt.add({}, "--cc", + std::function([&](std::string arg) + { + cc_prog = arg; return 0; - }); + }), + "Use specified c-compiler instead of gcc."); - opt.add("cxx", required_argument, key++, - "Use specified c++-compiler instead of g++.", - [&]() { - cxx_prog = optarg; + opt.add({}, "--cxx", + std::function([&](std::string arg) + { + cxx_prog = arg; return 0; - }); + }), + "Use specified c++-compiler instead of g++."); - opt.add("ar", required_argument, key++, - "Use specified archiver instead of ar.", - [&]() { - ar_prog = optarg; + opt.add({}, "--ar", + std::function([&](std::string arg) + { + ar_prog = arg; return 0; - }); + }), + "Use specified archiver instead of ar."); - opt.add("ld", required_argument, key++, - "Use specified linker instead of ld.", - [&]() { - ld_prog = optarg; + opt.add({}, "--ld", + std::function([&](std::string arg) + { + ld_prog = arg; return 0; - }); + }), + "Use specified linker instead of ld."); - opt.add("build", required_argument, key++, - "Configure for building on specified architecture.", - [&]() { - build_arch_prefix = optarg; + opt.add({}, "--build", + std::function([&](std::string arg) + { + build_arch_prefix = arg; return 0; - }); + }), + "Configure for building on specified architecture."); - opt.add("build-path", required_argument, key++, - "Set path to build tool-chain.", - [&]() { - build_path = optarg; + opt.add({}, "--build-path", + std::function([&](std::string arg) + { + build_path = arg; return 0; - }); + }), + "Set path to build tool-chain."); - opt.add("host", required_argument, key++, - "Cross-compile to build programs to run on specified architecture.", - [&]() { - host_arch_prefix = optarg; + opt.add({}, "--host", + std::function([&](std::string arg) + { + host_arch_prefix = arg; return 0; - }); + }), + "Cross-compile to build programs to run on specified architecture."); - opt.add("host-path", required_argument, key++, - "Set path to cross-compile tool-chain.", - [&]() { - host_path = optarg; + opt.add({}, "--host-path", + std::function([&](std::string arg) + { + host_path = arg; return 0; - }); + }), + "Set path to cross-compile tool-chain."); - opt.add("ctor-includedir", required_argument, key++, - "Set path to ctor header file, used for re-compiling.", - [&]() { - ctor_includedir = optarg; + opt.add({}, "--ctor-includedir", + std::function([&](std::string arg) + { + ctor_includedir = arg; return 0; - }); + }), + "Set path to ctor header file, used for re-compiling."); - opt.add("ctor-libdir", required_argument, key++, - "Set path to ctor library file, used for re-compiling.", - [&]() { - ctor_libdir = optarg; + opt.add({}, "--ctor-libdir", + std::function([&](std::string arg) + { + ctor_libdir = arg; return 0; - }); + }), + "Set path to ctor library file, used for re-compiling."); // Resolv externals ctor::external_configurations externalConfigs; - for(std::size_t i = 0; i < numExternalConfigFiles; ++i) + const auto& externalConfigFiles = getExternalConfigFileList(); + for(const auto& externalConfigFile : externalConfigFiles) { - auto newExternalConfigs = externalConfigFiles[i].cb(settings); + auto newExternalConfigs = externalConfigFile.cb(settings); externalConfigs.insert(externalConfigs.end(), newExternalConfigs.begin(), newExternalConfigs.end()); @@ -324,19 +374,21 @@ int regenerateCache(ctor::settings& settings, auto add_path_args = [&](const std::string& arg_name) { - opt.add(arg_name + "-includedir", required_argument, key++, - "Set path to " + arg_name + " header file.", - [&]() { - external_includedir[arg_name] = optarg; + opt.add({}, "--" + arg_name + "-includedir", + std::function([&](std::string arg) + { + external_includedir[arg_name] = arg; return 0; - }); + }), + "Set path to " + arg_name + " header file."); - opt.add(arg_name + "-libdir", required_argument, key++, - "Set path to " + arg_name + " libraries.", - [&]() { - external_libdir[arg_name] = optarg; + opt.add({}, "--" + arg_name + "-libdir", + std::function([&](std::string arg) + { + external_libdir[arg_name] = arg; return 0; - }); + }), + "Set path to " + arg_name + " libraries."); }; for(const auto& ext : externalConfigs) @@ -356,18 +408,57 @@ int regenerateCache(ctor::settings& settings, } - opt.add("help", no_argument, 'h', - "Print this help text.", - [&]() { + opt.add('h', "--help", + std::function([&]() -> int + { std::cout << "Configure how to build with " << name << "\n"; std::cout << "Usage: " << name << " configure [options]\n\n"; std::cout << "Options:\n"; opt.help(); exit(0); - return 0; - }); + }), + "Print this help text."); + + opt.set_err_cb( + [&](arg::error err, std::string_view arg) + { + switch(err) + { + case arg::error::invalid_arg: + std::cerr << opt.prog_name() << + ": invalid argument for option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; - 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()) { @@ -651,6 +742,7 @@ int regenerateCache(ctor::settings& settings, { ctor::conf_values[ctor::cfg::builddir] = builddir; } + ctor::conf_values[ctor::cfg::host_cxx] = host_cxx; ctor::conf_values[ctor::cfg::build_cxx] = build_cxx; @@ -833,34 +925,45 @@ int configure(const ctor::settings& global_settings, int argc, char* argv[]) } std::map<std::string, std::string> env; - auto cc_env = getenv("CC"); - if(cc_env) + std::string value; + if(get_env("CC", value)) + { + env["CC"] = value; + } + + if(get_env("CFLAGS", value)) + { + env["CFLAGS"] = value; + } + + if(get_env("CXX", value)) + { + env["CXX"] = value; + } + + if(get_env("CXXFLAGS", value)) { - env["CC"] = cc_env; + env["CXXFLAGS"] = value; } - auto cxx_env = getenv("CXX"); - if(cxx_env) + if(get_env("AR", value)) { - env["CXX"] = cxx_env; + env["AR"] = value; } - auto ar_env = getenv("AR"); - if(ar_env) + if(get_env("LD", value)) { - env["AR"] = ar_env; + env["LD"] = value; } - auto ld_env = getenv("LD"); - if(ld_env) + if(get_env("LDFLAGS", value)) { - env["LD"] = ld_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); @@ -90,6 +90,9 @@ enum class cxx_opt output, // -o debug, // -g warn_all, // -Wall + warn_conversion, // -Wconversion + warn_shadow, // -Wshadow + warn_extra, // -Wextra warnings_as_errors, // -Werror generate_dep_tree, // -MMD no_link, // -c @@ -108,6 +111,9 @@ enum class c_opt output, // -o debug, // -g warn_all, // -Wall + warn_conversion, // -Wconversion + warn_shadow, // -Wshadow + warn_extra, // -Wextra warnings_as_errors, // -Werror generate_dep_tree, // -MMD no_link, // -c @@ -124,7 +130,6 @@ enum class ld_opt { // gcc/clang output, // -o - strip, // -s warn_all, // -Wall warnings_as_errors, // -Werror library_path, // -L<arg> @@ -186,7 +191,7 @@ using asm_flag = ctor::flag<ctor::asm_opt>; using c_flags = std::vector<ctor::c_flag>; using cxx_flags = std::vector<ctor::cxx_flag>; -using ld_flags= std::vector<ctor::ld_flag>; +using ld_flags = std::vector<ctor::ld_flag>; using ar_flags = std::vector<ctor::ar_flag>; using asm_flags = std::vector<ctor::asm_flag>; @@ -204,6 +209,7 @@ struct settings std::string builddir{"build"}; std::size_t parallel_processes{1}; int verbose{0}; // -1: completely silent, 0: normal, 1: verbose, ... + bool dry_run{false}; }; struct build_configuration; @@ -227,7 +233,7 @@ struct build_configuration using build_configurations = std::vector<build_configuration>; -int reg(ctor::build_configurations (*cb)(const ctor::settings&), +int reg(std::function<ctor::build_configurations (const ctor::settings&)> cb, const std::source_location location = std::source_location::current()); // This type will use flags verbatim @@ -246,7 +252,7 @@ struct external_configuration using external_configurations = std::vector<ctor::external_configuration>; -int reg(ctor::external_configurations (*cb)(const ctor::settings&), +int reg(std::function<ctor::external_configurations (const ctor::settings&)> cb, const std::source_location location = std::source_location::current()); // Convenience macro - ugly but keeps things simple(r) @@ -261,12 +267,12 @@ namespace cfg constexpr auto builddir = "builddir"; constexpr auto host_cc = "host-cc"; -constexpr auto host_cxx = "host-cpp"; +constexpr auto host_cxx = "host-cxx"; constexpr auto host_ar = "host-ar"; constexpr auto host_ld = "host-ld"; constexpr auto build_cc = "build-cc"; -constexpr auto build_cxx = "build-cpp"; +constexpr auto build_cxx = "build-cxx"; constexpr auto build_ar = "build-ar"; constexpr auto build_ld = "build-ld"; @@ -277,7 +283,7 @@ constexpr auto ctor_libdir = "ctor-libdir"; struct configuration { bool has(const std::string& key) const; - const std::string& get(const std::string& key, const std::string& default_value = {}) const; + std::string get(const std::string& key, const std::string& default_value = {}) const; ctor::toolchain host_toolchain{ctor::toolchain::none}; ctor::arch host_arch{ctor::arch::unknown}; @@ -286,6 +292,8 @@ struct configuration ctor::arch build_arch{ctor::arch::unknown}; std::vector<std::string> args; // vector of arguments used when last calling configure + + std::string getenv(const std::string& key) const; std::map<std::string, std::string> env; // env used when last calling configure std::map<std::string, std::string> tools; // tools diff --git a/src/deps.cc b/src/deps.cc new file mode 100644 index 0000000..9400b35 --- /dev/null +++ b/src/deps.cc @@ -0,0 +1,120 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "deps.h" + +#include "util.h" + +#include <fstream> + +namespace { +/* Format example: +build/src/libctor_a-libctor_cc.o: src/libctor.cc \ + src/getoptpp/getoptpp.hpp src/ctor.h src/configure.h src/rebuild.h \ + src/A\ B\ C.h src/task.h src/build.h src/unittest.h + */ +std::vector<std::string> readDepsMake(const std::string& dep_file) +{ + auto str = readFile(dep_file); + + std::vector<std::string> output; + std::string tmp; + + enum class State + { + pre_colon, + post_colon, + in_escape, + } state{State::pre_colon}; + + auto old_state{state}; + for(const auto& c : str) + { + if(c == '\r') + { + // just always ignore windows extra newline char + continue; + } + + switch(state) + { + case State::pre_colon: + if(c == ':') // ignore everything until the colon + { + state = State::post_colon; + continue; + } + continue; + + case State::post_colon: + if(c == '\n') + { + // done + if(!tmp.empty()) + { + output.emplace_back(tmp); + } + return output; + } + if(c == '\\') + { + old_state = state; + state = State::in_escape; + continue; + } + if(c == '\t' || c == ' ') // new token + { + if(!tmp.empty()) + { + output.emplace_back(tmp); + } + tmp.clear(); + continue; + } + break; + + case State::in_escape: + if(c == '\n') + { + // linewrap + state = old_state; + continue; + } + state = old_state; + break; + } + + tmp += c; + } + + if(!tmp.empty()) + { + output.emplace_back(tmp); + } + + return output; +} +} + +std::vector<std::string> readDeps(const std::string& dep_file, + ctor::toolchain toolchain) +{ + if(!std::filesystem::exists(dep_file)) + { + return {}; + } + + switch(toolchain) + { + case ctor::toolchain::any: + case ctor::toolchain::none: + return {}; + + // makefile .d file based: + case ctor::toolchain::gcc: + case ctor::toolchain::clang: + return readDepsMake(dep_file); + } + + return {}; +} diff --git a/src/deps.h b/src/deps.h new file mode 100644 index 0000000..be0dfb2 --- /dev/null +++ b/src/deps.h @@ -0,0 +1,12 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include "ctor.h" + +#include <vector> +#include <string> + +std::vector<std::string> readDeps(const std::string& dep_file, + ctor::toolchain toolchain); diff --git a/src/execute.cc b/src/execute.cc index cbae899..c050732 100644 --- a/src/execute.cc +++ b/src/execute.cc @@ -4,6 +4,7 @@ #include "execute.h" #include "ctor.h" +#include "pointerlist.h" #include <unistd.h> #include <cstring> @@ -22,38 +23,43 @@ https://stackoverflow.com/questions/4259629/what-is-the-difference-between-fork- namespace { -class Env - : public std::vector<char*> + +int parent_waitpid(pid_t pid) { -public: - Env(const std::vector<std::string>& args) + int status{}; + + auto rc_pid = waitpid(pid, &status, 0); + + if(rc_pid > 0) { - for(const auto& arg : args) + if(WIFEXITED(status)) { - push_back(strdup(arg.data())); + // Child exited with normally + return WEXITSTATUS(status); } - push_back(nullptr); - } - - ~Env() - { - for(auto ptr : *this) + if(WIFSIGNALED(status)) { - free(ptr); + // Child exited via signal (segfault, abort, ...) + std::cerr << strsignal(status) << '\n'; + return WTERMSIG(status); } } -}; - -int parent_waitpid(pid_t pid) -{ - int status{}; - - if(waitpid(pid, &status, 0) != pid) - { - return 1; + else + { // No PID returned, this is an error + if(errno == ECHILD) + { + // No children exist. + return 1; + } + else + { + // Unexpected error. + abort(); + } } - return WEXITSTATUS(status); + // Should never happen... + return 1; } } // namespace :: @@ -87,7 +93,7 @@ int execute(const ctor::settings& settings, cmd += arg; } - if(settings.verbose) + if(settings.verbose > 0) { std::cout << cmd << std::endl; } @@ -96,19 +102,18 @@ int execute(const ctor::settings& settings, auto pid = vfork(); if(pid == 0) { - std::vector<std::string> venv; + EnvMap envmap((const char**)environ); for(const auto& [key, value] : env) { - venv.push_back(key + "=" + value); + envmap.insert(key + "=" + value); } - - for(auto current = environ; *current; ++current) + if(settings.dry_run) { - venv.emplace_back(*current); + _exit(0); } - - Env penv(venv); - execve(command.data(), (char**)argv.data(), penv.data()); + auto [_, envv] = envmap.get(); + execve(command.data(), const_cast<char* const *>(argv.data()), + const_cast<char* const *>(envv)); std::cout << "Could not execute " << command << ": " << strerror(errno) << "\n"; _exit(1); // execve only returns if an error occurred diff --git a/src/getoptpp b/src/getoptpp deleted file mode 160000 -Subproject 5aba94355ec638c6f8612f86be309ed684979ae diff --git a/src/libctor.cc b/src/libctor.cc index b1fbaea..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("configure-cmd", no_argument, key++, - "Print commandline for last configure.", - [&]() { + 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", + 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.", - [&]() { + opt.add('h', "--help", + std::function([&]() -> int + { std::cout << "Usage: " << args[0] << " [options] [target] ...\n"; std::cout << R"_( where target can be either: @@ -174,29 +186,67 @@ Options: )_"; opt.help(); exit(0); - return 0; - }); + }), + "Print this help text."); - opt.process(argc, argv); + opt.set_err_cb( + [&](arg::error err, std::string_view arg) + { + switch(err) + { + case arg::error::invalid_arg: + std::cerr << opt.prog_name() << + ": invalid argument for option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + + case arg::error::missing_arg: + std::cerr << opt.prog_name() << ": option requires and argument '" << + arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + + case arg::error::invalid_opt: + std::cerr << opt.prog_name() << ": invalid option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + } + }); + auto res = opt.parse(); + if(res != 0) + { + return res; + } - auto verbose_env = std::getenv("V"); - if(verbose_env) + if(std::string value; get_env("V", value)) { - settings.verbose = std::atoi(verbose_env); + try + { + settings.verbose = std::stoi(value); + } + catch(...) + { + // not an integer + } } if(list_files) { no_default_build = true; std::vector<std::string> files; - for(std::size_t i = 0; i < numConfigFiles; ++i) + const auto& configFiles = getConfigFileList(); + for(const auto& configFile : configFiles) { - files.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()); @@ -289,7 +339,7 @@ Options: } bool build_all{!no_default_build}; - for(const auto& arg : opt.arguments()) + for(const auto& arg : arguments) { if(arg == "configure") { diff --git a/src/pointerlist.cc b/src/pointerlist.cc new file mode 100644 index 0000000..c0242f7 --- /dev/null +++ b/src/pointerlist.cc @@ -0,0 +1,123 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "pointerlist.h" + +#include <cstring> + +PointerList::PointerList(int argc, const char* const argv[]) +{ + for(int i = 0; i < argc; ++i) + { + push_back(argv[i]); + } +} + +std::pair<int, const char* const*> PointerList::get() +{ + argptrs.clear(); + for(const auto& arg : *this) + { + argptrs.push_back(arg.data()); + } + argptrs.push_back(nullptr); + + return {argptrs.size() - 1, // size not counting the nullptr at the end + argptrs.data()}; +} + +EnvMap::EnvMap(const char* const env[]) +{ + if(env == nullptr) + { + return; + } + + auto ptr = env; + while(*ptr) + { + insert(std::string_view(*ptr)); + ++ptr; + } +} + +EnvMap::EnvMap(const char* env) +{ + if(env == nullptr) + { + return; + } + + auto ptr = env; + while(*ptr) + { + insert(ptr); + ptr += strlen(ptr) + 1; + } +} + +std::string EnvMap::operator[](const std::string& key) const +{ + try + { + return data.at(key); + } + catch(...) + { + return {}; + } +} + +void EnvMap::clear() +{ + data.clear(); +} + +void EnvMap::insert(std::string_view key_value) +{ + auto equals_sign = key_value.find('='); + if(equals_sign == std::string::npos) + { + insert({key_value, ""}); + return; + } + std::string key{key_value.substr(0, equals_sign)}; + std::string value{key_value.substr(equals_sign + 1)}; // skip '=' + insert({key, value}); +} + +void EnvMap::insert(const std::pair<std::string_view, std::string_view>& item) +{ + data[std::string(item.first)] = item.second; +} + +bool EnvMap::contains(std::string_view key) const +{ + return data.contains(std::string{key}); +} + +std::size_t EnvMap::size() const +{ + return data.size(); +} + +std::string EnvMap::stringify() const +{ + std::string str; + for(const auto& [key, value] : data) + { + str += key + "=" + value + '\0'; + } + str += '\0'; + return str; +} + +std::pair<int, const char* const*> EnvMap::get() +{ + pointerlist.clear(); + for(const auto& [key, value] : data) + { + pointerlist.push_back(key + "=" + value + '\0'); + } + return pointerlist.get(); +} diff --git a/src/pointerlist.h b/src/pointerlist.h new file mode 100644 index 0000000..988bb26 --- /dev/null +++ b/src/pointerlist.h @@ -0,0 +1,74 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <string> +#include <vector> +#include <deque> +#include <utility> +#include <map> + +//! Maintains an (owning) list of string args and converts them to argc/argv +//! compatible arguments on request. +//! The returned pointers are guaranteed to be valid as long as the PointerList +//! object lifetime is not exceeded. +class PointerList + : public std::deque<std::string> +{ +public: + PointerList() = default; + PointerList(int argc, const char* const argv[]); + + //! Returns argc/argv pair from the current list of args + //! The argv entry after the last is a nullptr (not included in the argc) + std::pair<int, const char* const*> get(); + +private: + std::vector<const char*> argptrs; +}; + + +//! Maintains an owning map of strings representing the env. +class EnvMap +{ +public: + EnvMap() = default; + + //! Initialize from an array of pointers to key=value\0 strings terminated + //! by \0 + EnvMap(const char* const env[]); + + //! Initialize from a string of the format + //! key1=val\0key2=val\0...keyN=val\0\0 + EnvMap(const char* env); + + std::string operator[](const std::string& key) const; + + //! Clear all items in the map + void clear(); + + //! Insert string from format: key=value + void insert(std::string_view key_value); + + //! Regular map insert + void insert(const std::pair<std::string_view, std::string_view>& item); + + //! Checks if the container contains element with specific key + bool contains(std::string_view key) const; + + std::size_t size() const; + + //! Return string with the following format: + //! key1=val\0key2=val\0...keyN=val\0\0 + std::string stringify() const; + + //! Returns the map as argc/argv pair where each pointer points to a string + //! of the format key=value\0 and is terminated with a nullptr + std::pair<int, const char* const*> get(); + +private: + std::map<std::string, std::string> data{}; + + PointerList pointerlist; +}; diff --git a/src/rebuild.cc b/src/rebuild.cc index 4ca1809..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) { - std::size_t found{0}; - for(std::size_t i = 0; i < numConfigFiles;) - { - if(std::string(location) == configFiles[i].file) - { - ++found; - for(std::size_t j = i; j < numConfigFiles; ++j) - { - configFiles[j] = configFiles[j + 1]; - } - --numConfigFiles; - } - else - { - ++i; - } - } - - for(std::size_t i = 0; i < numExternalConfigFiles;) - { - if(std::string(location) == externalConfigFiles[i].file) - { - ++found; - for(std::size_t j = i; j < numExternalConfigFiles; ++j) - { - externalConfigFiles[j] = externalConfigFiles[j + 1]; - } - --numExternalConfigFiles; - } - else - { - ++i; - } - } - - return static_cast<int>(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; @@ -181,7 +157,6 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ c.get(ctor::cfg::ctor_libdir)); } config.flags.ldflags.emplace_back(ctor::ld_opt::link, "ctor"); - config.flags.ldflags.emplace_back(ctor::ld_opt::strip); config.flags.ldflags.emplace_back(ctor::ld_opt::threads); ctor::settings settings{global_settings}; @@ -202,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"; @@ -217,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 cd03fce..ef7731b 100644 --- a/src/task.cc +++ b/src/task.cc @@ -48,10 +48,11 @@ bool Task::operator==(const std::string& depStr) std::filesystem::path generated_output = sourceDir; generated_output /= target(); return - name() == depStr || - target() == depStr || - generated_output == depStr || - targetFile().string() == depStr + (!derived() && name() == depStr) || // compare to name + (!derived() && config.target == depStr) || // compare to stated target (ex. foo.a) + target() == depStr || // compare to derived (derived to foo.lib on msvc) + generated_output == depStr || // not sure what this is for?! + targetFile().string() == depStr // compare to target output file ; } @@ -32,7 +32,7 @@ public: bool operator==(const std::string& dep); - virtual std::string name() const; + std::string name() const; bool dirty(); bool ready(); int run(); diff --git a/src/task_ar.cc b/src/task_ar.cc index 09403bb..3b45cc2 100644 --- a/src/task_ar.cc +++ b/src/task_ar.cc @@ -121,7 +121,13 @@ int TaskAR::runInner() break; } - return execute(settings, tool, args, c.env); + auto res = execute(settings, tool, args, c.env); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskAR::clean() @@ -189,7 +195,6 @@ std::string TaskAR::flagsString() const flagsStr += str; } } - flagsStr += "\n"; for(const auto& dep : config.depends) { diff --git a/src/task_cc.cc b/src/task_cc.cc index 9a801b5..9628455 100644 --- a/src/task_cc.cc +++ b/src/task_cc.cc @@ -12,6 +12,7 @@ #include "execute.h" #include "util.h" #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) @@ -89,11 +90,6 @@ int TaskCC::registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& task return 0; } -std::string TaskCC::name() const -{ - return {}; -} - bool TaskCC::dirtyInner() { if(!std::filesystem::exists(sourceFile)) @@ -136,7 +132,8 @@ bool TaskCC::dirtyInner() } } - auto depList = readDeps(depsFile.string()); + auto toolchain = getToolChain(config.system); + auto depList = readDeps(depsFile.string(), toolchain); for(const auto& dep : depList) { if(!std::filesystem::exists(dep) || @@ -182,7 +179,7 @@ int TaskCC::runInner() output = "CC "; break; case ctor::language::cpp: - output = "CXX "; + output = "CXX "; break; case ctor::language::automatic: case ctor::language::assembler: @@ -194,8 +191,40 @@ int TaskCC::runInner() std::cout << output << std::flush; } + auto toolchain = getToolChain(config.system); const auto& cfg = ctor::get_configuration(); - return execute(settings, compiler(), args, cfg.env); + switch(sourceLanguage()) + { + case ctor::language::c: + { + auto cflags = cfg.getenv("CFLAGS"); + if(!cflags.empty()) + { + append(args, c_option(toolchain, ctor::c_opt::custom, cflags)); + } + } + break; + case ctor::language::cpp: + { + auto cxxflags = cfg.getenv("CXXFLAGS"); + if(!cxxflags.empty()) + { + append(args, cxx_option(toolchain, ctor::cxx_opt::custom, cxxflags)); + } + } + break; + case ctor::language::automatic: + case ctor::language::assembler: + // Only c/c++ handled by this task type. + break; + } + auto res = execute(settings, compiler(), args, cfg.env); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskCC::clean() @@ -288,6 +317,7 @@ std::vector<std::string> TaskCC::flags() const exit(1); break; } + } std::string TaskCC::flagsString() const @@ -311,7 +341,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const { case ctor::language::c: { - append(args, c_option(toolchain, ctor::c_opt::generate_dep_tree)); + append(args, c_option(toolchain, ctor::c_opt::generate_dep_tree, depsFile.string())); if(std::filesystem::path(config.target).extension() == ".so") { @@ -339,6 +369,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const path = (sourceDir / path).lexically_normal(); append(args, c_option(toolchain, ctor::c_opt::include_path, path.string())); + continue; } } break; @@ -353,7 +384,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const case ctor::language::cpp: { - append(args, cxx_option(toolchain, ctor::cxx_opt::generate_dep_tree)); + append(args, cxx_option(toolchain, ctor::cxx_opt::generate_dep_tree, depsFile.string())); if(std::filesystem::path(config.target).extension() == ".so") { @@ -381,6 +412,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const path = (sourceDir / path).lexically_normal(); append(args, cxx_option(toolchain, ctor::cxx_opt::include_path, path.string())); + continue; } } break; diff --git a/src/task_cc.h b/src/task_cc.h index 6c4683a..2299fcd 100644 --- a/src/task_cc.h +++ b/src/task_cc.h @@ -21,7 +21,6 @@ public: int registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& tasks) override; - std::string name() const override; bool dirtyInner() override; int runInner() override; diff --git a/src/task_fn.cc b/src/task_fn.cc index 1ff72f9..b6b50ea 100644 --- a/src/task_fn.cc +++ b/src/task_fn.cc @@ -74,10 +74,16 @@ int TaskFn::runInner() std::cout << output << std::flush; } - return config.function(sourceFile.string(), - targetFile().string(), - config, - settings); + auto res = config.function(sourceFile.string(), + targetFile().string(), + config, + settings); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskFn::clean() diff --git a/src/task_ld.cc b/src/task_ld.cc index 31ee8c0..03745be 100644 --- a/src/task_ld.cc +++ b/src/task_ld.cc @@ -128,8 +128,19 @@ int TaskLD::runInner() } auto tool = compiler(); - const auto& c = ctor::get_configuration(); - return execute(settings, tool, args, c.env, is_self); + const auto& cfg = ctor::get_configuration(); + auto ldflags = cfg.getenv("LDFLAGS"); + if(!ldflags.empty()) + { + append(args, ld_option(toolchain, ctor::ld_opt::custom, ldflags)); + } + auto res = execute(settings, tool, args, cfg.env, is_self); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskLD::clean() @@ -197,7 +208,6 @@ std::string TaskLD::flagsString() const flagsStr += str; } } - flagsStr += "\n"; for(const auto& dep : config.depends) { diff --git a/src/task_so.cc b/src/task_so.cc index eecd7aa..92aeefe 100644 --- a/src/task_so.cc +++ b/src/task_so.cc @@ -116,7 +116,18 @@ int TaskSO::runInner() auto tool = compiler(); const auto& cfg = ctor::get_configuration(); - return execute(settings, tool, args, cfg.env); + auto ldflags = cfg.getenv("LDFLAGS"); + if(!ldflags.empty()) + { + append(args, ld_option(toolchain, ctor::ld_opt::custom, ldflags)); + } + auto res = execute(settings, tool, args, cfg.env); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskSO::clean() @@ -184,7 +195,6 @@ std::string TaskSO::flagsString() const flagsStr += str; } } - flagsStr += "\n"; for(const auto& dep : config.depends) { diff --git a/src/tasks.cc b/src/tasks.cc index 2f9e47a..94fe269 100644 --- a/src/tasks.cc +++ b/src/tasks.cc @@ -25,7 +25,7 @@ const std::deque<Target>& getTargets(const ctor::settings& settings, bool resolve_externals) { - auto config_files = std::span(configFiles).subspan(0, numConfigFiles); + auto& config_files = getConfigFileList(); static bool initialised{false}; static std::deque<Target> targets; @@ -170,7 +170,8 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& return tasks; } -std::shared_ptr<Task> getNextTask([[maybe_unused]]const std::vector<std::shared_ptr<Task>>& allTasks, +std::shared_ptr<Task> getNextTask([[maybe_unused]]const ctor::settings& settings, + [[maybe_unused]]const std::vector<std::shared_ptr<Task>>& allTasks, std::vector<std::shared_ptr<Task>>& dirtyTasks) { for(auto dirtyTask = dirtyTasks.begin(); @@ -179,7 +180,7 @@ std::shared_ptr<Task> getNextTask([[maybe_unused]]const std::vector<std::shared_ { auto task = *dirtyTask; //std::cout << "Examining target " << (*dirtyTask)->target() << "\n"; - if(task->ready()) + if(task->ready() || settings.dry_run) { dirtyTasks.erase(dirtyTask); return task; diff --git a/src/tasks.h b/src/tasks.h index 6573784..97fc84d 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -23,7 +23,8 @@ const std::deque<Target>& getTargets(const ctor::settings& settings, //! fulfilled. //! The returned task is removed from the dirty list. //! Return nullptr if no dirty task is ready. -std::shared_ptr<Task> getNextTask(const std::vector<std::shared_ptr<Task>>& allTasks, +std::shared_ptr<Task> getNextTask(const ctor::settings& settings, + const std::vector<std::shared_ptr<Task>>& allTasks, std::vector<std::shared_ptr<Task>>& dirtyTasks); //! Get list of tasks filtered by name including each of their direct diff --git a/src/tools.cc b/src/tools.cc index eb98265..dfabdff 100644 --- a/src/tools.cc +++ b/src/tools.cc @@ -6,6 +6,7 @@ #include <filesystem> #include <iostream> #include <sstream> +#include <array> #include <cassert> #include <cstdio> @@ -20,6 +21,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt) case ctor::c_opt::output: stream << "ctor::c_opt::output"; break; case ctor::c_opt::debug: stream << "ctor::c_opt::debug"; break; case ctor::c_opt::warn_all: stream << "ctor::c_opt::warn_all"; break; + case ctor::c_opt::warn_conversion: stream << "ctor::c_opt::warn_conversion"; break; + case ctor::c_opt::warn_shadow: stream << "ctor::c_opt::warn_shadow"; break; + case ctor::c_opt::warn_extra: stream << "ctor::c_opt::warn_extra"; break; case ctor::c_opt::warnings_as_errors: stream << "ctor::c_opt::warnings_as_errors"; break; case ctor::c_opt::generate_dep_tree: stream << "ctor::c_opt::generate_dep_tree"; break; case ctor::c_opt::no_link: stream << "ctor::c_opt::no_link"; break; @@ -43,6 +47,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt) case ctor::cxx_opt::output: stream << "ctor::cxx_opt::output"; break; case ctor::cxx_opt::debug: stream << "ctor::cxx_opt::debug"; break; case ctor::cxx_opt::warn_all: stream << "ctor::cxx_opt::warn_all"; break; + case ctor::cxx_opt::warn_conversion: stream << "ctor::cxx_opt::warn_conversion"; break; + case ctor::cxx_opt::warn_shadow: stream << "ctor::cxx_opt::warn_shadow"; break; + case ctor::cxx_opt::warn_extra: stream << "ctor::cxx_opt::warn_extra"; break; case ctor::cxx_opt::warnings_as_errors: stream << "ctor::cxx_opt::warnings_as_errors"; break; case ctor::cxx_opt::generate_dep_tree: stream << "ctor::cxx_opt::generate_dep_tree"; break; case ctor::cxx_opt::no_link: stream << "ctor::cxx_opt::no_link"; break; @@ -64,7 +71,6 @@ std::ostream& operator<<(std::ostream& stream, const ctor::ld_opt& opt) switch(opt) { case ctor::ld_opt::output: stream << "ctor::ld_opt::output"; break; - case ctor::ld_opt::strip: stream << "ctor::ld_opt::strip"; break; case ctor::ld_opt::warn_all: stream << "ctor::ld_opt::warn_all"; break; case ctor::ld_opt::warnings_as_errors: stream << "ctor::ld_opt::warnings_as_errors"; break; case ctor::ld_opt::library_path: stream << "ctor::ld_opt::library_path"; break; @@ -247,6 +253,48 @@ ctor::c_flag c_option(const std::string& flag) return { ctor::c_opt::include_path, path }; } + if(flag.starts_with("-std=")) + { + std::string std = flag.substr(5); + return { ctor::c_opt::c_std, std }; + } + + if(flag.starts_with("-O")) + { + std::string opt = flag.substr(2, 1); + return { ctor::c_opt::optimization, opt }; + } + + if(flag.starts_with("-Wall")) + { + return { ctor::c_opt::warn_all }; + } + + if(flag.starts_with("-Wconversion")) + { + return { ctor::c_opt::warn_conversion}; + } + + if(flag.starts_with("-Wshadow")) + { + return { ctor::c_opt::warn_shadow}; + } + + if(flag.starts_with("-Wextra")) + { + return { ctor::c_opt::warn_extra}; + } + + if(flag.starts_with("-Werror")) + { + return { ctor::c_opt::warnings_as_errors }; + } + + if(flag.starts_with("-g")) + { + return { ctor::c_opt::debug }; + } + if(flag.starts_with("-D")) { std::string def = flag.substr(2); @@ -272,6 +320,48 @@ ctor::cxx_flag cxx_option(const std::string& flag) return { ctor::cxx_opt::include_path, path }; } + if(flag.starts_with("-std=")) + { + std::string std = flag.substr(5); + return { ctor::cxx_opt::cpp_std, std }; + } + + if(flag.starts_with("-O")) + { + std::string opt = flag.substr(2, 1); + return { ctor::cxx_opt::optimization, opt }; + } + + if(flag.starts_with("-Wall")) + { + return { ctor::cxx_opt::warn_all }; + } + + if(flag.starts_with("-Werror")) + { + return { ctor::cxx_opt::warnings_as_errors }; + } + + if(flag.starts_with("-Wconversion")) + { + return { ctor::cxx_opt::warn_conversion}; + } + + if(flag.starts_with("-Wshadow")) + { + return { ctor::cxx_opt::warn_shadow}; + } + + if(flag.starts_with("-Wextra")) + { + return { ctor::cxx_opt::warn_extra}; + } + + if(flag.starts_with("-g")) + { + return { ctor::cxx_opt::debug }; + } + if(flag.starts_with("-D")) { std::string def = flag.substr(2); @@ -298,6 +388,11 @@ ctor::ld_flag ld_option(const std::string& flag) return { ctor::ld_opt::library_path, path }; } + if(flag.starts_with("-pthread")) + { + return { ctor::ld_opt::threads }; + } + return { ctor::ld_opt::custom, flag }; } @@ -317,6 +412,12 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, return {"-g"}; case ctor::cxx_opt::warn_all: return {"-Wall"}; + case ctor::cxx_opt::warn_conversion: + return {"-Wconversion"}; + case ctor::cxx_opt::warn_shadow: + return {"-Wshadow"}; + case ctor::cxx_opt::warn_extra: + return {"-Wextra"}; case ctor::cxx_opt::warnings_as_errors: return {"-Werror"}; case ctor::cxx_opt::generate_dep_tree: @@ -340,7 +441,7 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, } return {"-D" + arg}; case ctor::cxx_opt::custom: - return {arg}; + return argsplit(arg); } std::cerr << "Unsupported compiler option.\n"; @@ -358,6 +459,12 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg, return {"-g"}; case ctor::c_opt::warn_all: return {"-Wall"}; + case ctor::c_opt::warn_conversion: + return {"-Wconversion"}; + case ctor::c_opt::warn_shadow: + return {"-Wshadow"}; + case ctor::c_opt::warn_extra: + return {"-Wextra"}; case ctor::c_opt::warnings_as_errors: return {"-Werror"}; case ctor::c_opt::generate_dep_tree: @@ -381,7 +488,7 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg, } return {"-D" + arg}; case ctor::c_opt::custom: - return {arg}; + return argsplit(arg); } std::cerr << "Unsupported compiler option.\n"; @@ -395,8 +502,6 @@ std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg, { case ctor::ld_opt::output: return {"-o", arg}; - case ctor::ld_opt::strip: - return {"-s"}; case ctor::ld_opt::warn_all: return {"-Wall"}; case ctor::ld_opt::warnings_as_errors: @@ -416,7 +521,7 @@ std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg, case ctor::ld_opt::position_independent_executable: return {"-fPIE"}; case ctor::ld_opt::custom: - return {arg}; + return argsplit(arg); } std::cerr << "Unsupported compiler option.\n"; @@ -437,7 +542,7 @@ std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg, case ctor::ar_opt::output: return {arg}; case ctor::ar_opt::custom: - return {arg}; + return argsplit(arg); } std::cerr << "Unsupported compiler option.\n"; @@ -450,7 +555,7 @@ std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg, switch(opt) { case ctor::asm_opt::custom: - return {arg}; + return argsplit(arg); } std::cerr << "Unsupported compiler option.\n"; diff --git a/src/util.cc b/src/util.cc index 73b158d..a4abd23 100644 --- a/src/util.cc +++ b/src/util.cc @@ -6,12 +6,18 @@ #include <iostream> #include <fstream> #include <algorithm> +#include <sstream> +#include <cctype> +#include <cstdlib> -std::string to_lower(const std::string& str) +std::string to_lower(std::string str) { - std::string out{str}; - std::transform(out.begin(), out.end(), out.begin(), ::tolower); - return out; + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) + { + return std::tolower(c); + }); + return str; } std::string readFile(const std::string& fileName) @@ -19,73 +25,17 @@ std::string readFile(const std::string& fileName) std::ifstream ifs(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate); - std::ifstream::pos_type fileSize = ifs.tellg(); - ifs.seekg(0, std::ios::beg); - - std::vector<char> bytes(static_cast<std::size_t>(fileSize)); - ifs.read(bytes.data(), fileSize); - - return {bytes.data(), static_cast<std::size_t>(fileSize)}; -} - -std::vector<std::string> readDeps(const std::string& depFile) -{ - if(!std::filesystem::exists(depFile)) + auto size = ifs.tellg(); + if(size < 0) { return {}; } + ifs.seekg(0, std::ios::beg); - auto str = readFile(depFile); - - std::vector<std::string> output; - std::string tmp; - bool start{false}; - bool in_whitespace{false}; - for(const auto& c : str) - { - if(c == '\\' || c == '\n') - { - continue; - } - - if(c == ':') - { - start = true; - continue; - } - - if(!start) - { - continue; - } - - if(c == ' ' || c == '\t') - { - if(in_whitespace) - { - continue; - } - - if(!tmp.empty()) - { - output.push_back(tmp); - } - tmp.clear(); - in_whitespace = true; - } - else - { - in_whitespace = false; - tmp += c; - } - } - - if(!tmp.empty()) - { - output.push_back(tmp); - } + std::string bytes(static_cast<std::size_t>(size), '\0'); + ifs.read(bytes.data(), static_cast<std::streamsize>(bytes.size())); - return output; + return bytes; } ctor::language languageFromExtension(const std::filesystem::path& file) @@ -168,8 +118,18 @@ std::string esc(const std::string& in) return out; } -std::vector<std::string> get_paths(const std::string& path_env) +std::vector<std::string> get_paths(const std::string& path_env_) { + std::string path_env; + if(!path_env_.empty()) + { + path_env = path_env_; + } + else + { + get_env("PATH", path_env); + } + std::vector<std::string> paths; #ifdef _WIN32 @@ -244,3 +204,106 @@ std::string locate(const std::string& prog, return {}; } + +std::vector<std::string> argsplit(const std::string& str) +{ + enum class state + { + normal, + in_quot, + in_apotrophe, + } state{state::normal}; + bool esc{false}; + + std::string token; + std::vector<std::string> tokens; + for(auto c : str) + { + switch(state) + { + case state::normal: + if(esc) + { + esc = false; + } + else + { + if(c == ' ') + { + tokens.push_back(token); + token.clear(); + continue; + } + if(c == '\\') + { + esc = true; + } + if(c == '"') + { + state = state::in_quot; + } + if(c == '\'') + { + state = state::in_apotrophe; + } + } + + token += c; + break; + case state::in_quot: + if(esc) + { + esc = false; + } + else + { + if(c == '\\') + { + esc = true; + } + if(c == '"') + { + state = state::normal; + } + } + + token += c; + break; + case state::in_apotrophe: + if(esc) + { + esc = false; + } + else + { + if(c == '\\') + { + esc = true; + } + if(c == '\'') + { + state = state::normal; + } + } + + token += c; + break; + } + } + if(!token.empty()) + { + tokens.push_back(token); + } + return tokens; +} + +bool get_env(std::string_view name, std::string& value) +{ + auto var = getenv(name.data()); + if(var) + { + value = var; + return true; + } + return false; +} @@ -9,10 +9,9 @@ #include <filesystem> #include <cstdlib> -std::string to_lower(const std::string& str); +std::string to_lower(std::string str); std::string readFile(const std::string& fileName); -std::vector<std::string> readDeps(const std::string& depFile); ctor::language languageFromExtension(const std::filesystem::path& file); std::string cleanUp(const std::string& path); @@ -24,8 +23,19 @@ void append(T& a, const T& b) std::string esc(const std::string& in); -std::vector<std::string> get_paths(const std::string& path_env = std::getenv("PATH")); +//! Get system paths (ie. env var PATH). +//! If path_env is provided, this search string will be used, other the PATH +//! env variable is used. +//! \returns a vector of the individual toknized paths. +std::vector<std::string> get_paths(const std::string& path_env = {}); std::string locate(const std::string& app, const std::vector<std::string>& paths, const std::string& arch = {}); + +//! Splits string into tokens adhering to quotations " and ' +std::vector<std::string> argsplit(const std::string& str); + +//! Calls the system getenv and sets the string if the env name it exists. +//! \returns true if the env name existed, false otherwise. +bool get_env(std::string_view name, std::string& value); 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/argsplit_test.cc b/test/argsplit_test.cc new file mode 100644 index 0000000..7dce561 --- /dev/null +++ b/test/argsplit_test.cc @@ -0,0 +1,203 @@ +#include <uunit.h> + +#include <util.h> + +class ArgSplitTest + : public uUnit +{ +public: + using fs = std::filesystem::path; + + ArgSplitTest() + { + uTEST(ArgSplitTest::plain_test); + uTEST(ArgSplitTest::quot_test); + uTEST(ArgSplitTest::apotrophe_test); + uTEST(ArgSplitTest::mixed_test); + uTEST(ArgSplitTest::escape_test); + } + + void plain_test() + { + using namespace std::string_literals; + + { // zero + auto res = argsplit({}); + uASSERT_EQUAL(0u, res.size()); + } + + { // one + auto res = argsplit("hello"s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("hello"s, res[0]); + } + + { // many + auto res = argsplit("hello world"s); + uASSERT_EQUAL(2u, res.size()); + uASSERT_EQUAL("hello"s, res[0]); + uASSERT_EQUAL("world"s, res[1]); + } + } + + void quot_test() + { + using namespace std::string_literals; + + { // zero + auto res = argsplit("\"\""); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\"\""s, res[0]); + } + + { // one + auto res = argsplit("\"hello\""s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\"hello\""s, res[0]); + } + + { // one with space + auto res = argsplit("\"hel lo\""s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\"hel lo\""s, res[0]); + } + + { // many + auto res = argsplit("\"hello\" \"world\""s); + uASSERT_EQUAL(2u, res.size()); + uASSERT_EQUAL("\"hello\""s, res[0]); + uASSERT_EQUAL("\"world\""s, res[1]); + } + + { // many with spaces + auto res = argsplit("\"hel lo\" \"wor ld\""s); + uASSERT_EQUAL(2u, res.size()); + uASSERT_EQUAL("\"hel lo\""s, res[0]); + uASSERT_EQUAL("\"wor ld\""s, res[1]); + } + } + + void apotrophe_test() + { + using namespace std::string_literals; + + { // zero + auto res = argsplit("\'\'"); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\'\'"s, res[0]); + } + + { // one + auto res = argsplit("\'hello\'"s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\'hello\'"s, res[0]); + } + + { // one with space + auto res = argsplit("\'hel lo\'"s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\'hel lo\'"s, res[0]); + } + + { // many + auto res = argsplit("\'hello\' \'world\'"s); + uASSERT_EQUAL(2u, res.size()); + uASSERT_EQUAL("\'hello\'"s, res[0]); + uASSERT_EQUAL("\'world\'"s, res[1]); + } + + { // many with spaces + auto res = argsplit("\'hel lo\' \'wor ld\'"s); + uASSERT_EQUAL(2u, res.size()); + uASSERT_EQUAL("\'hel lo\'"s, res[0]); + uASSERT_EQUAL("\'wor ld\'"s, res[1]); + } + } + + void mixed_test() + { + using namespace std::string_literals; + + { // zero + auto res = argsplit("\'\'"); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\'\'"s, res[0]); + } + + { // one + auto res = argsplit("\'he\"llo\'"s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\'he\"llo\'"s, res[0]); + } + + { // one + auto res = argsplit("\"he\'llo\""s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\"he\'llo\""s, res[0]); + } + + { // one with space + auto res = argsplit("\'he\"l lo\'\""s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\'he\"l lo\'\""s, res[0]); + } + + { // one with space + auto res = argsplit("\"he\'l lo\"\'"s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\"he\'l lo\"\'"s, res[0]); + } + + { // many + auto res = argsplit("\"he\'llo\" \"wor\'ld\""s); + uASSERT_EQUAL(2u, res.size()); + uASSERT_EQUAL("\"he\'llo\""s, res[0]); + uASSERT_EQUAL("\"wor\'ld\""s, res[1]); + } + + { // many with spaces + auto res = argsplit("\'hel\"lo\' \'wor\"ld\'"s); + uASSERT_EQUAL(2u, res.size()); + uASSERT_EQUAL("\'hel\"lo\'"s, res[0]); + uASSERT_EQUAL("\'wor\"ld\'"s, res[1]); + } + } + + void escape_test() + { + using namespace std::string_literals; + + { // zero + auto res = argsplit("\\"); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\\"s, res[0]); + } + + { // one + auto res = argsplit("\\\'"s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\\\'"s, res[0]); + } + + { // one + auto res = argsplit("\\\""s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\\\""s, res[0]); + } + + { // one + auto res = argsplit("\\\\"s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("\\\\"s, res[0]); + } + + { // one + auto res = argsplit("a\\ b"s); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("a\\ b"s, res[0]); + } + } +}; + +// Registers the fixture into the 'registry' +static ArgSplitTest test; diff --git a/test/ctor.cc b/test/ctor.cc index ccf1c31..0d77a3e 100644 --- a/test/ctor.cc +++ b/test/ctor.cc @@ -12,6 +12,75 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) { .type = ctor::target_type::unit_test, .system = ctor::output_system::build, + .target = "argparser_test", + .sources = { + "argparser_test.cc", + "testmain.cc", + }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"argparser\"", + }, + }, + }, + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, + .target = "argsplit_test", + .sources = { + "argsplit_test.cc", + "testmain.cc", + "../src/util.cc", + }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"argsplit\"", + }, + }, + }, + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, + .target = "pointerlist_test", + .sources = { + "pointerlist_test.cc", + "testmain.cc", + "../src/pointerlist.cc", + }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"pointerlist\"", + }, + }, + }, + { + .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", + "-I../src", "-Iuunit", + "-DOUTPUT=\"deps\"", + }, + }, + }, + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, .target = "testprog", .sources = { "testprog.cc", @@ -30,6 +99,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) "execute_test.cc", "testmain.cc", "../src/execute.cc", + "../src/pointerlist.cc", "../src/util.cc", }, .depends = { "testprog", }, @@ -63,6 +133,24 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) { .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", + "-I../src", "-Iuunit", + "-DOUTPUT=\"cycle\"", + }, + .ldflags = { "-pthread" }, + }, + }, + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, .target = "source_type_test", .sources = { "source_type_test.cc", @@ -104,7 +192,9 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) .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", diff --git a/test/cycle_test.cc b/test/cycle_test.cc new file mode 100644 index 0000000..3b45632 --- /dev/null +++ b/test/cycle_test.cc @@ -0,0 +1,87 @@ +#include <uunit.h> + +#include <ctor.h> +#include <build.h> + +namespace +{ +ctor::build_configurations ctorTestConfigsCyclic(const ctor::settings&) +{ + return + { + // No dependency + { + .target = "target0", + }, + + // Direct (self-depends) + { + .target = "target1", + .depends = { "target1" }, + }, + + // Indirect cyclic depends + { + .target = "target2", + .depends = { "target3" }, + }, + { + .target = "target3", + .depends = { "target2" }, + }, + }; +} +} + +REG(ctorTestConfigsCyclic); + +class CycleTest + : public uUnit +{ +public: + CycleTest() + { + uTEST(CycleTest::getTargets_test); + } + + void getTargets_test() + { + using namespace std::string_literals; + ctor::settings settings{}; + const auto& tasks = getTasks(settings); + uASSERT_EQUAL(4u, tasks.size()); + + uASSERT_EQUAL("target0"s, tasks[0]->target()); + uASSERT_EQUAL("target1"s, tasks[1]->target()); + uASSERT_EQUAL("target2"s, tasks[2]->target()); + uASSERT_EQUAL("target3"s, tasks[3]->target()); + + for(auto task : tasks) + { + if(task->registerDepTasks(tasks)) + { + uASSERT(false); + } + } + + bool exc; + exc = false; + try { getDepTasks(tasks[0]); } catch(...) { exc = true; } + uASSERT(!exc); + + exc = false; + try { getDepTasks(tasks[1]); } catch(...) { exc = true; } + uASSERT(exc); + + exc = false; + try { getDepTasks(tasks[2]); } catch(...) { exc = true; } + uASSERT(exc); + + exc = false; + try { getDepTasks(tasks[3]); } catch(...) { exc = true; } + uASSERT(exc); + } +}; + +// Registers the fixture into the 'registry' +static CycleTest test; diff --git a/test/deps_test.cc b/test/deps_test.cc new file mode 100644 index 0000000..762b0e5 --- /dev/null +++ b/test/deps_test.cc @@ -0,0 +1,97 @@ +#include <uunit.h> + +#include <fstream> +#include <map> +#include <vector> + +#include <deps.h> +#include <algorithm> + +#include "paths.h" +#include "tmpfile.h" + +class DepsTest + : public uUnit +{ +public: + DepsTest() + { + uTEST(DepsTest::parser_test); + } + + void parser_test() + { + using namespace std::string_literals; + + auto test_data = paths::top_srcdir / "test" / "deps_test_data"; + + auto empty = test_data / "empty.d"; + auto trivial = test_data / "trivial.d"; + auto no_newline = test_data / "no_newline.d"; + auto no_deps = test_data / "no_deps.d"; + auto trailing_whitespace = test_data / "trailing_whitespace.d"; + auto spaces = test_data / "spaces.d"; + auto multiline = test_data / "multiline.d"; + auto no_such_file = test_data / "no_such_file.d"; // doesn't exist + auto missing_colon = test_data / "missing_colon.d"; + + { + auto res = readDeps(empty.string(), ctor::toolchain::gcc); + uASSERT(res.empty()); + } + + { + auto res = readDeps(trivial.string(), ctor::toolchain::gcc); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("x.cc"s, res[0]); + } + + { + auto res = readDeps(no_newline.string(), ctor::toolchain::gcc); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("x.cc"s, res[0]); + } + + { + auto res = readDeps(no_deps.string(), ctor::toolchain::gcc); + uASSERT_EQUAL(0u, res.size()); + } + + { + auto res = readDeps(spaces.string(), ctor::toolchain::gcc); + uASSERT_EQUAL(2u, res.size()); + uASSERT_EQUAL("x y.cc"s, res[0]); + uASSERT_EQUAL("x y.h"s, res[1]); + } + + { + auto res = readDeps(multiline.string(), ctor::toolchain::gcc); + uASSERT_EQUAL(12u, res.size()); + uASSERT_EQUAL("src/configure.cc"s, res[0]); + uASSERT_EQUAL("src/configure.h"s, res[1]); + uASSERT_EQUAL("src/getoptpp/getoptpp.hpp"s, res[2]); + uASSERT_EQUAL("src/execute.h"s, res[3]); + uASSERT_EQUAL("src/ctor.h"s, res[4]); + uASSERT_EQUAL("src/tasks.h"s, res[5]); + uASSERT_EQUAL("src/task.h"s, res[6]); + uASSERT_EQUAL("src/rebuild.h"s, res[7]); + uASSERT_EQUAL("src/externals.h"s, res[8]); + uASSERT_EQUAL("src/externals_manual.h"s, res[9]); + uASSERT_EQUAL("src/tools.h"s, res[10]); + uASSERT_EQUAL("src/util.h"s, res[11]); + } + + { + auto res = readDeps(no_such_file.string(), ctor::toolchain::gcc); + uASSERT(res.empty()); + } + + { + auto res = readDeps(missing_colon.string(), ctor::toolchain::gcc); + uASSERT(res.empty()); + } + } +}; + +// Registers the fixture into the 'registry' +static DepsTest test; diff --git a/test/deps_test_data/empty.d b/test/deps_test_data/empty.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/deps_test_data/empty.d diff --git a/test/deps_test_data/missing_colon.d b/test/deps_test_data/missing_colon.d new file mode 100644 index 0000000..e46996c --- /dev/null +++ b/test/deps_test_data/missing_colon.d @@ -0,0 +1 @@ +x.cc x.h diff --git a/test/deps_test_data/multiline.d b/test/deps_test_data/multiline.d new file mode 100644 index 0000000..8862ab0 --- /dev/null +++ b/test/deps_test_data/multiline.d @@ -0,0 +1,4 @@ +build/src/libctor_a-configure_cc.o: src/configure.cc src/configure.h \ + src/getoptpp/getoptpp.hpp src/execute.h src/ctor.h src/tasks.h \ + src/task.h src/rebuild.h src/externals.h src/externals_manual.h \ + src/tools.h src/util.h diff --git a/test/deps_test_data/no_deps.d b/test/deps_test_data/no_deps.d new file mode 100644 index 0000000..e7cccf6 --- /dev/null +++ b/test/deps_test_data/no_deps.d @@ -0,0 +1 @@ +x.o: diff --git a/test/deps_test_data/no_newline.d b/test/deps_test_data/no_newline.d new file mode 100644 index 0000000..88829ea --- /dev/null +++ b/test/deps_test_data/no_newline.d @@ -0,0 +1 @@ +x.o: x.cc
\ No newline at end of file diff --git a/test/deps_test_data/spaces.d b/test/deps_test_data/spaces.d new file mode 100644 index 0000000..c53fd64 --- /dev/null +++ b/test/deps_test_data/spaces.d @@ -0,0 +1 @@ +x\ y.o: x\ y.cc x\ y.h diff --git a/test/deps_test_data/trivial.d b/test/deps_test_data/trivial.d new file mode 100644 index 0000000..15a0c29 --- /dev/null +++ b/test/deps_test_data/trivial.d @@ -0,0 +1 @@ +x.o: x.cc diff --git a/test/execute_test.cc b/test/execute_test.cc index 4c686bf..11b067f 100644 --- a/test/execute_test.cc +++ b/test/execute_test.cc @@ -35,6 +35,12 @@ public: 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); + value = execute(s, cmd, {"throw"}, {}, false); + uASSERT_EQUAL(6, value); + value = execute(s, cmd, {"abort"}, {}, false); + uASSERT_EQUAL(6, value); } void env() @@ -47,7 +53,7 @@ public: auto cmd = locate("testprog", paths); uASSERT(!cmd.empty()); - tmp_file tmp; + TmpFile tmp; std::map<std::string, std::string> env; diff --git a/test/pointerlist_test.cc b/test/pointerlist_test.cc new file mode 100644 index 0000000..4274473 --- /dev/null +++ b/test/pointerlist_test.cc @@ -0,0 +1,320 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <uunit.h> + +#include <set> +#include <algorithm> + +#include "pointerlist.h" + +class PointerListTest + : public uUnit +{ +public: + PointerListTest() + { + uTEST(PointerListTest::test_zom_pointerlist_push); + uTEST(PointerListTest::test_zom_pointerlist_from_args); + uTEST(PointerListTest::test_zom_envmap_insert); + uTEST(PointerListTest::test_zom_envmap_from_env); + uTEST(PointerListTest::test_exceptional_env); + } + + void test_zom_pointerlist_push() + { + using namespace std::string_literals; + + { // Zero + PointerList args; + uASSERT_EQUAL(0u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // One + PointerList args; + args.push_back("hello"); + uASSERT_EQUAL(1u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello"s, std::string(argv[0])); + } + + { // Many + PointerList args; + args.push_back("hello"); + args.push_back("dear"); + args.push_back("world"); + uASSERT_EQUAL(3u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(3, argc); + uASSERT_EQUAL("hello"s, std::string(argv[0])); + uASSERT_EQUAL("dear"s, std::string(argv[1])); + uASSERT_EQUAL("world"s, std::string(argv[2])); + } + } + + void test_zom_pointerlist_from_args() + { + using namespace std::string_literals; + + { // Zero + PointerList args(0, nullptr); + uASSERT_EQUAL(0u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // One + int _argc{1}; + const char* _argv[] = { "hello" }; + PointerList args(_argc, _argv); + uASSERT_EQUAL(1u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello"s, std::string(argv[0])); + } + + { // Many + int _argc{3}; + const char* _argv[] = { "hello", "dear", "world" }; + PointerList args(_argc, _argv); + uASSERT_EQUAL(3u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(3, argc); + // order must be preserved + uASSERT_EQUAL("hello"s, std::string(argv[0])); + uASSERT_EQUAL("dear"s, std::string(argv[1])); + uASSERT_EQUAL("world"s, std::string(argv[2])); + } + } + + void test_zom_envmap_insert() + { + using namespace std::string_literals; + + { // Zero + EnvMap env; + uASSERT_EQUAL(0u, env.size()); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + + auto str = env.stringify(); + uASSERT_EQUAL(1u, str.size()); + uASSERT_EQUAL('\0', str[0]); + } + + { // One (key only) + EnvMap env; + env.insert("hello"); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello="s, std::string(argv[0])); + uASSERT_EQUAL(""s, env["hello"]); + } + { // One (with value) + EnvMap env; + env.insert("hello=world"); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello=world"s, std::string(argv[0])); + uASSERT_EQUAL("world"s, env["hello"]); + + uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); + } + + { // Overwrite one + EnvMap env; + env.insert("hello=world"); + uASSERT_EQUAL(1u, env.size()); + uASSERT_EQUAL("world"s, env["hello"s]); + + env.insert("hello=foo"); + uASSERT_EQUAL(1u, env.size()); + uASSERT_EQUAL("foo"s, env["hello"s]); + } + + { // Many + EnvMap env; + env.insert("hello=world"); + env.insert("world=leader"); + env.insert("dear=boar"); + uASSERT_EQUAL(3u, env.size()); + + uASSERT_EQUAL("boar"s, env["dear"s]); + uASSERT_EQUAL("world"s, env["hello"s]); + uASSERT_EQUAL("leader"s, env["world"s]); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(3, argc); + // store and sort to verify unordered + std::vector<std::string> vals{argv[0], argv[1], argv[2]}; + std::ranges::sort(vals); + uASSERT_EQUAL("dear=boar"s, vals[0]); + uASSERT_EQUAL("hello=world"s, vals[1]); + uASSERT_EQUAL("world=leader"s, vals[2]); + + // test all combinations since ordering is not a requirement + // exactly one of the must be true (boolean XOR) + auto str = env.stringify(); + uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != + ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != + ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != + ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != + ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != + ("world=leader\0hello=world\0dear=boar\0\0"s == str)); + } + } + + void test_zom_envmap_from_env() + { + using namespace std::string_literals; + + { // Zero + const char* penv = nullptr; + EnvMap env(penv); + uASSERT_EQUAL(0u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // Zero + const char** ppenv = nullptr; + EnvMap env(ppenv); + uASSERT_EQUAL(0u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // One (key only) + const char* ptr = "hello\0\0"; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello="s, std::string(argv[0])); + uASSERT_EQUAL(""s, env["hello"]); + } + + { // One (key only) + const char* ptr[] = {"hello", nullptr}; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello="s, std::string(argv[0])); + uASSERT_EQUAL(""s, env["hello"]); + } + + { // One (with value) + const char* ptr = "hello=world\0\0"; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello=world"s, std::string(argv[0])); + uASSERT_EQUAL("world"s, env["hello"]); + + uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); + } + + { // One (with value) + const char* ptr[] = {"hello=world\0", nullptr}; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello=world"s, std::string(argv[0])); + uASSERT_EQUAL("world"s, env["hello"]); + + uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); + } + + { // Many + const char* ptr = "hello=world\0world=leader\0dear=boar\0\0"; + EnvMap env(ptr); + uASSERT_EQUAL(3u, env.size()); + + uASSERT_EQUAL("boar"s, env["dear"s]); + uASSERT_EQUAL("world"s, env["hello"s]); + uASSERT_EQUAL("leader"s, env["world"s]); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(3, argc); + // store and sort to verify unordered + std::vector<std::string> vals{argv[0], argv[1], argv[2]}; + std::ranges::sort(vals); + uASSERT_EQUAL("dear=boar"s, vals[0]); + uASSERT_EQUAL("hello=world"s, vals[1]); + uASSERT_EQUAL("world=leader"s, vals[2]); + + // test all combinations since ordering is not a requirement + // exactly one of the must be true (boolean XOR) + auto str = env.stringify(); + uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != + ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != + ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != + ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != + ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != + ("world=leader\0hello=world\0dear=boar\0\0"s == str)); + } + + { // Many + const char* ptr[] = + {"hello=world\0", "world=leader\0", "dear=boar\0", nullptr}; + EnvMap env(ptr); + uASSERT_EQUAL(3u, env.size()); + + uASSERT_EQUAL("boar"s, env["dear"s]); + uASSERT_EQUAL("world"s, env["hello"s]); + uASSERT_EQUAL("leader"s, env["world"s]); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(3, argc); + // store and sort to verify unordered + std::vector<std::string> vals{argv[0], argv[1], argv[2]}; + std::ranges::sort(vals); + uASSERT_EQUAL("dear=boar"s, vals[0]); + uASSERT_EQUAL("hello=world"s, vals[1]); + uASSERT_EQUAL("world=leader"s, vals[2]); + + // test all combinations since ordering is not a requirement + // exactly one of the must be true (boolean XOR) + auto str = env.stringify(); + uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != + ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != + ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != + ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != + ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != + ("world=leader\0hello=world\0dear=boar\0\0"s == str)); + } + } + + void test_exceptional_env() + { + using namespace std::string_literals; + + { // Zero + EnvMap env; + uASSERT_EQUAL(0u, env.size()); + uASSERT_EQUAL(""s, env["foo"]); // lookup of non-existing key + } + } +}; + +// Registers the fixture into the 'registry' +static PointerListTest test; diff --git a/test/suite/test.sh b/test/suite/test.sh index c112351..c54137a 100755 --- a/test/suite/test.sh +++ b/test/suite/test.sh @@ -17,6 +17,12 @@ function 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 @@ -26,7 +32,7 @@ echo "** ctor_files/ctor.cc.base" cp ctor_files/ctor.cc.base ctor.cc # Compile bootstrap binary -$CXX -pthread -std=c++20 -L${CTORDIR} -lctor -I../../src ctor.cc -o ctor || fail ${LINENO} +$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} @@ -55,7 +61,7 @@ ctor -v # ctor should not have been rebuilt, so md5 sum should be the same (echo $MD5 | md5sum --status -c) || fail ${LINENO} -MOD1=`stat -c %Y ${BUILDDIR}/hello-hello_cc.o` +MOD1=`stat $STAT_FORMAT ${BUILDDIR}/hello-hello_cc.o` touch hello.cc sleep 1.1 @@ -63,7 +69,7 @@ sleep 1.1 ctor -v # Object file should have been recompiled -MOD2=`stat -c %Y ${BUILDDIR}/hello-hello_cc.o` +MOD2=`stat $STAT_FORMAT ${BUILDDIR}/hello-hello_cc.o` [[ $MOD1 == $MOD2 ]] && fail ${LINENO} # Replacve -DFOO with -DBAR in foo external.cxxflags @@ -72,13 +78,13 @@ cp ctor_files/ctor.cc.bar ctor.cc MD5C=`md5sum configuration.cc` MD5=`md5sum ctor` -MOD1=`stat -c %Y build/hello-hello_cc.o` +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 -c %Y ${BUILDDIR}/hello-hello_cc.o` +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} @@ -88,13 +94,13 @@ cp ctor_files/ctor.cc.multi ctor.cc MD5C=`md5sum configuration.cc` MD5=`md5sum ctor` -MOD1=`stat -c %Y ${BUILDDIR}/hello-hello_cc.o` +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 -c %Y ${BUILDDIR}/hello-hello_cc.o` +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} @@ -102,13 +108,13 @@ MOD2=`stat -c %Y ${BUILDDIR}/hello-hello_cc.o` # now touching foobar.h, should retrigger re-configuration touch foobar.h -MOD1=`stat -c %Y ctor` +MOD1=`stat $STAT_FORMAT ctor` sleep 1.1 # Run normally to reconfigure, rebuild ctor and rebuild hello.cc ctor -v -MOD2=`stat -c %Y ctor` +MOD2=`stat $STAT_FORMAT ctor` [[ $MOD1 == $MOD2 ]] && fail ${LINENO} exit 0 diff --git a/test/tasks_test.cc b/test/tasks_test.cc index 3de6982..cbd0864 100644 --- a/test/tasks_test.cc +++ b/test/tasks_test.cc @@ -10,6 +10,7 @@ ctor::build_configurations ctorTestConfigs1(const ctor::settings&) return { { + .name = "Target1", .target = "target1", .sources = {"foo.cc", "bar.c"}, }, @@ -33,16 +34,29 @@ ctor::build_configurations ctorTestConfigs2(const ctor::settings&) } } +namespace test_global { +ctor::toolchain toolchain{}; +ctor::arch arch{}; +} +const ctor::configuration& ctor::get_configuration() +{ + static ctor::configuration cfg{}; + cfg.build_toolchain = test_global::toolchain; + cfg.build_arch = test_global::arch; + return cfg; +} + + REG(ctorTestConfigs1); REG(ctorTestConfigs2); std::size_t count(const std::vector<std::shared_ptr<Task>>& tasks, - const std::string& name) + const std::filesystem::path& name) { auto cnt{0u}; for(const auto& task : tasks) { - if(task->target() == name) + if(task->target() == name.string()) { cnt++; } @@ -54,16 +68,17 @@ class TestTask : public Task { public: - TestTask(const std::string& name, bool dirty, + TestTask(const ctor::build_configuration& config, + const ctor::settings& settings, + const std::string& name, bool dirty, const std::vector<std::string>& deps = {}) - : Task({}, {}, {}) + : Task(config, settings, {}) , task_name(name) , task_dirty(dirty) , task_deps(deps) { } - std::string name() const override { return task_name; } int clean() override { return 0; } std::vector<std::string> depends() const override { return task_deps; } std::string target() const override { return task_name; } @@ -81,11 +96,14 @@ class TasksTest : public uUnit { public: + using fs = std::filesystem::path; + TasksTest() { uTEST(TasksTest::getTargets_test); uTEST(TasksTest::getTasks_test); uTEST(TasksTest::getNextTask_test); + uTEST(TasksTest::comparison_test); } void getTargets_test() @@ -108,26 +126,29 @@ public: void getTasks_test() { + test_global::toolchain = ctor::toolchain::gcc; + test_global::arch = ctor::arch::unix; + using namespace std::string_literals; ctor::settings settings{ .builddir = "foo" }; { auto tasks = getTasks(settings); uASSERT_EQUAL(6u, tasks.size()); // Note: count() is used here because the order doesn't matter - uASSERT_EQUAL(1u, count(tasks, "target1"s)); - uASSERT_EQUAL(1u, count(tasks, "target2"s)); - uASSERT_EQUAL(1u, count(tasks, "target3"s)); - uASSERT_EQUAL(1u, count(tasks, "target4"s)); - uASSERT_EQUAL(1u, count(tasks, "test/target1-foo_cc.o"s)); - uASSERT_EQUAL(1u, count(tasks, "test/target1-bar_c.o"s)); + uASSERT_EQUAL(1u, count(tasks, fs("target1"))); + uASSERT_EQUAL(1u, count(tasks, fs("target2"))); + uASSERT_EQUAL(1u, count(tasks, fs("target3"))); + uASSERT_EQUAL(1u, count(tasks, fs("target4"))); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o")); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o")); } { auto tasks = getTasks(settings, {"target1", "target3"}); uASSERT_EQUAL(4u, tasks.size()); - uASSERT_EQUAL(1u, count(tasks, "target1"s)); - uASSERT_EQUAL(1u, count(tasks, "target3"s)); - uASSERT_EQUAL(1u, count(tasks, "test/target1-foo_cc.o"s)); - uASSERT_EQUAL(1u, count(tasks, "test/target1-bar_c.o"s)); + uASSERT_EQUAL(1u, count(tasks, fs("target1"))); + uASSERT_EQUAL(1u, count(tasks, fs("target3"))); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o")); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o")); } { auto tasks = getTasks(settings, {"no-such-target"}); @@ -149,11 +170,12 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(nullptr, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(nullptr, getNextTask({}, allTasks, dirtyTasks)); } { // Zero (One task, no dirty) - auto task1 = std::make_shared<TestTask>("task1", false); + ctor::build_configuration config; + auto task1 = std::make_shared<TestTask>(config, settings, "task1", false); std::vector<std::shared_ptr<Task>> allTasks; allTasks.push_back(task1); @@ -165,11 +187,12 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(nullptr, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(nullptr, getNextTask({}, allTasks, dirtyTasks)); } { // One (One task, one dirty) - auto task1 = std::make_shared<TestTask>("task1", true); + ctor::build_configuration config; + auto task1 = std::make_shared<TestTask>(config, settings, "task1", true); std::vector<std::shared_ptr<Task>> allTasks; allTasks.push_back(task1); @@ -182,13 +205,14 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(task1, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(task1, getNextTask({}, allTasks, dirtyTasks)); uASSERT_EQUAL(0u, dirtyTasks.size()); } { // One (Two tasks, one dirty) - auto task1 = std::make_shared<TestTask>("task1", false); - auto task2 = std::make_shared<TestTask>("task2", true); + ctor::build_configuration config; + auto task1 = std::make_shared<TestTask>(config, settings, "task1", false); + auto task2 = std::make_shared<TestTask>(config, settings, "task2", true); std::vector<std::shared_ptr<Task>> allTasks; allTasks.push_back(task1); @@ -202,15 +226,17 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(task2, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(task2, getNextTask({}, allTasks, dirtyTasks)); uASSERT_EQUAL(0u, dirtyTasks.size()); } { // One (Two tasks, one dirty which depends on the other) - auto task1 = std::make_shared<TestTask>("task1", false); + ctor::build_configuration config; + auto task1 = std::make_shared<TestTask>(config, settings, "task1", false); std::vector<std::string> deps = {"task1"}; - auto task2 = std::make_shared<TestTask>("task2", true, deps); + auto task2 = + std::make_shared<TestTask>(config, settings, "task2", true, deps); std::vector<std::shared_ptr<Task>> allTasks; allTasks.push_back(task1); @@ -224,15 +250,17 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(task2, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(task2, getNextTask({}, allTasks, dirtyTasks)); uASSERT_EQUAL(0u, dirtyTasks.size()); } { // One (Two tasks, Both dirty, one depends on the other) - auto task1 = std::make_shared<TestTask>("task1", true); + ctor::build_configuration config{}; + auto task1 = std::make_shared<TestTask>(config, settings, "task1", true); std::vector<std::string> deps = {"task1"}; - auto task2 = std::make_shared<TestTask>("task2", true, deps); + auto task2 = + std::make_shared<TestTask>(config, settings, "task2", true, deps); std::vector<std::shared_ptr<Task>> allTasks; allTasks.push_back(task2); @@ -247,10 +275,54 @@ public: uASSERT_EQUAL(0, task->registerDepTasks(allTasks)); } - uASSERT_EQUAL(task1, getNextTask(allTasks, dirtyTasks)); + uASSERT_EQUAL(task1, getNextTask({}, allTasks, dirtyTasks)); uASSERT_EQUAL(1u, dirtyTasks.size()); } + } + void comparison_test() + { + test_global::toolchain = ctor::toolchain::gcc; + test_global::arch = ctor::arch::unix; + + using namespace std::string_literals; + ctor::settings settings{ .builddir = "foo" }; + { // Test Task::operator== + auto tasks = getTasks(settings, {"target1"}); + uASSERT_EQUAL(3u, tasks.size()); + uASSERT_EQUAL(1u, count(tasks, fs("target1"))); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o")); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o")); + + int cnt1{}; + int cnt2{}; + int cnt3{}; + for(const auto& task : tasks) + { + if(task->target() == fs("target1")) + { + ++cnt1; + uASSERT(*task == "target1"); + uASSERT(*task == "Target1"); + } + if(task->target() == fs("test")/"target1-foo_cc.o") + { + ++cnt2; + uASSERT(*task != "target1"); + uASSERT(*task != "Target1"); + } + if(task->target() == fs("test")/"target1-bar_c.o") + { + ++cnt3; + uASSERT(*task != "target1"); + uASSERT(*task != "Target1"); + } + } + // Assert that we did actually perform all three tests exactly once + uASSERT_EQUAL(1, cnt1); + uASSERT_EQUAL(1, cnt2); + uASSERT_EQUAL(1, cnt3); + } } }; diff --git a/test/testprog.cc b/test/testprog.cc index dbfb665..93edc3f 100644 --- a/test/testprog.cc +++ b/test/testprog.cc @@ -1,8 +1,9 @@ #include <iostream> #include <fstream> #include <string> +#include <csignal> -extern const char **environ; // see 'man environ' +extern char **environ; int main(int argc, const char* argv[]) { @@ -40,6 +41,11 @@ int main(int argc, const char* argv[]) abort(); } + if(cmd == "segfault") + { + raise(SIGSEGV); + } + if(cmd == "throw") { throw "ouch"; 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 a428ea1..5ae04c3 100644 --- a/test/tools_test.cc +++ b/test/tools_test.cc @@ -114,33 +114,27 @@ bool operator!=(const ctor::asm_flag& a, const ctor::asm_flag& b) #include <uunit.h> -namespace { -std::string conf_host_cxx{}; -std::string conf_build_cxx{}; -} - const ctor::configuration& ctor::get_configuration() { static ctor::configuration cfg; return cfg; } -const std::string& ctor::configuration::get(const std::string& key, const std::string& defval) const +std::string ctor::configuration::get(const std::string& key, [[maybe_unused]]const std::string& default_value) const { if(key == ctor::cfg::host_cxx) { - return conf_host_cxx; + return {}; } if(key == ctor::cfg::build_cxx) { - return conf_build_cxx; + return {}; } assert(false); // bad key - static std::string res{}; - return res; + return {}; } class ToolsTest @@ -526,10 +520,6 @@ public: act = ld_option(ctor::toolchain::gcc, ctor::ld_opt::output, "foo"); uASSERT_EQUAL(exp, act); - exp = { "-s" }; - act = ld_option(ctor::toolchain::gcc, ctor::ld_opt::strip); - uASSERT_EQUAL(exp, act); - exp = { "-Wall" }; act = ld_option(ctor::toolchain::gcc, ctor::ld_opt::warn_all); uASSERT_EQUAL(exp, act); @@ -577,10 +567,6 @@ public: act = ld_option(ctor::toolchain::clang, ctor::ld_opt::output, "foo"); uASSERT_EQUAL(exp, act); - exp = { "-s" }; - act = ld_option(ctor::toolchain::clang, ctor::ld_opt::strip); - uASSERT_EQUAL(exp, act); - exp = { "-Wall" }; act = ld_option(ctor::toolchain::clang, ctor::ld_opt::warn_all); uASSERT_EQUAL(exp, act); @@ -628,10 +614,6 @@ public: act = ld_option(ctor::toolchain::any, ctor::ld_opt::output, "foo"); uASSERT_EQUAL(exp, act); - exp = { "{ctor::ld_opt::strip}" }; - act = ld_option(ctor::toolchain::any, ctor::ld_opt::strip); - uASSERT_EQUAL(exp, act); - exp = { "{ctor::ld_opt::warn_all}" }; act = ld_option(ctor::toolchain::any, ctor::ld_opt::warn_all); uASSERT_EQUAL(exp, act); @@ -914,7 +896,7 @@ public: // Mismatching toolchain (required vs actual) results in no output // otherwise to_strings is just a proxy for ld_option - act = to_strings(ctor::toolchain::gcc, {ctor::toolchain::clang, ctor::ld_opt::strip}); + act = to_strings(ctor::toolchain::gcc, {ctor::toolchain::clang, ctor::ld_opt::threads}); uASSERT_EQUAL(exp, act); } diff --git a/test/uunit b/test/uunit -Subproject bc078da645412c6b36ef59e635d6c35d11088c9 +Subproject 0f371777e02dd068f9675a05a29230221d5d6a7 |