diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .gitmodules | 6 | ||||
-rw-r--r-- | Jenkinsfile | 25 | ||||
-rw-r--r-- | bootstrap.bat | 25 | ||||
-rwxr-xr-x | clrun.sh | 22 | ||||
-rw-r--r-- | ctor.cc | 2 | ||||
m--------- | json | 0 | ||||
-rw-r--r-- | src/argparser.h | 477 | ||||
-rw-r--r-- | src/configure.cc | 346 | ||||
-rw-r--r-- | src/ctor.h | 6 | ||||
-rw-r--r-- | src/deps.cc | 74 | ||||
-rw-r--r-- | src/execute.cc | 246 | ||||
m--------- | src/getoptpp | 0 | ||||
-rw-r--r-- | src/libctor.cc | 208 | ||||
-rw-r--r-- | src/rebuild.cc | 164 | ||||
-rw-r--r-- | src/rebuild.h | 18 | ||||
-rw-r--r-- | src/task_ar.cc | 2 | ||||
-rw-r--r-- | src/task_cc.cc | 2 | ||||
-rw-r--r-- | src/task_ld.cc | 29 | ||||
-rw-r--r-- | src/task_so.cc | 2 | ||||
-rw-r--r-- | src/tasks.cc | 2 | ||||
-rw-r--r-- | src/tools.cc | 303 | ||||
-rw-r--r-- | src/util.cc | 91 | ||||
-rw-r--r-- | src/util.h | 12 | ||||
-rw-r--r-- | test/argparser_test.cc | 1019 | ||||
-rw-r--r-- | test/ctor.cc | 29 | ||||
-rw-r--r-- | test/execute_test.cc | 26 | ||||
-rw-r--r-- | test/source_type_test.cc | 8 | ||||
-rwxr-xr-x | test/suite/test.sh | 4 | ||||
-rw-r--r-- | test/testprog.cc | 38 | ||||
-rw-r--r-- | test/tmpfile.h | 45 | ||||
-rw-r--r-- | test/tools_test.cc | 3 |
32 files changed, 2861 insertions, 374 deletions
@@ -1,6 +1,7 @@ drumgizmo/ build/ ctor +ctor.exe *.a *.o *.d diff --git a/.gitmodules b/.gitmodules index c765825..341cbd4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[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 +[submodule "json"] + path = json + url = https://github.com/nlohmann/json.git diff --git a/Jenkinsfile b/Jenkinsfile index a558359..0ce77b5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,6 +5,31 @@ pipeline { { parallel { //////////////////////////////////////////////////// + stage('Windows msvc') { + agent { label 'win10 && msvc' } + environment + { + VSDEVCMD="C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\Common7\\Tools\\VsDevCmd.bat" + } + steps { + echo 'Cleaning workspace ...' + bat 'git clean -d -x -f' + dir ('build/test') { + writeFile file:'dummy', text:'' + } + bat '"%VSDEVCMD%" && bootstrap.bat' + echo 'Testing (msvc) ...' + bat 'ctor.exe check' + } + post { + always { + xunit(thresholds: [ skipped(failureThreshold: '0'), + failed(failureThreshold: '0') ], + tools: [ CppUnit(pattern: 'build/test/*.xml') ]) + } + } + } + //////////////////////////////////////////////////// stage('MacOSX clang') { agent { label 'macos' } steps { diff --git a/bootstrap.bat b/bootstrap.bat new file mode 100644 index 0000000..3802663 --- /dev/null +++ b/bootstrap.bat @@ -0,0 +1,25 @@ +@echo off + +set CXX=cl.exe +set CC=cl.exe +set AR=lib.exe +set LD=link.exe + +echo Bootstrapping... +%CXX% /nologo /MT /std:c++20 /D_X86_ /EHsc /Isrc /Fo:build\ ctor.cc src\bootstrap.cc /link /out:ctor.exe +@if %errorlevel% neq 0 exit /b %errorlevel% + +ctor.exe +@if %errorlevel% neq 0 exit /b %errorlevel% + +%CXX% /nologo /std:c++20 /D_X86_ /EHsc /Isrc /Fo:build\ ctor.cc /c +@if %errorlevel% neq 0 exit /b %errorlevel% +%CXX% /nologo /std:c++20 /D_X86_ /EHsc /Isrc /Fo:build\test\ test\ctor.cc /c +@if %errorlevel% neq 0 exit /b %errorlevel% +%LD% /nologo build\libctor.lib build\ctor.obj build\test\ctor.obj /subsystem:console /out:ctor.exe +@if %errorlevel% neq 0 exit /b %errorlevel% + +ctor.exe configure --ctor-includedir=src --ctor-libdir=build +@if %errorlevel% neq 0 exit /b %errorlevel% + +@echo Done. Now run ctor.exe to (re)build. diff --git a/clrun.sh b/clrun.sh new file mode 100755 index 0000000..e08849e --- /dev/null +++ b/clrun.sh @@ -0,0 +1,22 @@ +#!/bin/bash +#set -e +#set -x +export WINEDEBUG=-all + +rm -f ~/.wine/drive_c/number + +export BASE='C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30133' +export ONECORELIB="$BASE\lib\onecore\x86" +export WINEPATH="$BASE\bin\Hostx86\x86" +export INCLUDE="$BASE\include" +export UCRT="C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt" +export UCRTLIB="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\ucrt\x86" +export UM="C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um" +export UMLIB="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x86" +export SHARED="C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared" + +export CL="/I\"$BASE\include\" /I\"$UCRT\" /I\"$UM\" /I\"$SHARED\" /link /LIBPATH:\"$UMLIB\" /LIBPATH:\"$ONECORELIB\" /LIBPATH:\"$UCRTLIB\"" +export LINK="/LIBPATH:\"$UMLIB\" /LIBPATH:\"$ONECORELIB\" /LIBPATH:\"$UCRTLIB\"" +export LIB="/LIBPATH:\"$UMLIB\" /LIBPATH:\"$ONECORELIB\" /LIBPATH:\"$UCRTLIB\"" + +$* @@ -44,6 +44,8 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings) "-Wconversion", // "-Wnrvo", "-Isrc", + "-Ijson/include", + "-fexceptions", }, }, } diff --git a/json b/json new file mode 160000 +Subproject 6be4e8560023098fdb6d2047e6e6e5bc5dd5287 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/configure.cc b/src/configure.cc index a43152f..471afd5 100644 --- a/src/configure.cc +++ b/src/configure.cc @@ -8,8 +8,9 @@ #include <fstream> #include <optional> #include <span> - -#include <getoptpp/getoptpp.hpp> +#include <cstring> +#include <vector> +#include <deque> #include "execute.h" #include "ctor.h" @@ -18,6 +19,7 @@ #include "externals.h" #include "tools.h" #include "util.h" +#include "argparser.h" const std::filesystem::path configurationFile("configuration.cc"); const std::filesystem::path configHeaderFile("config.h"); @@ -25,18 +27,18 @@ const std::filesystem::path configHeaderFile("config.h"); std::map<std::string, std::string> external_includedir; std::map<std::string, std::string> external_libdir; +#if !defined(_WIN32) const ctor::configuration& __attribute__((weak)) ctor::get_configuration() +#else +const ctor::configuration& default_get_configuration() +#endif { static ctor::configuration cfg; static bool initialised{false}; if(!initialised) { std::string cxx_prog{"c++"}; - auto cxx_env = std::getenv("CXX"); - if(cxx_env) - { - cxx_prog = cxx_env; - } + get_env("CXX", cxx_prog); cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, cxx_prog)); @@ -44,6 +46,14 @@ const ctor::configuration& __attribute__((weak)) ctor::get_configuration() } return cfg; } +#if defined(_WIN32) +// Hack to make ctor::get_configuration "weak" linked +// See: +// https://stackoverflow.com/questions/2290587/gcc-style-weak-linking-in-visual-studio +// and +// https://stackoverflow.com/questions/11849853/how-to-list-functions-present-in-object-file +#pragma comment(linker, "/alternatename:?get_configuration@ctor@@YAABUconfiguration@1@XZ=?default_get_configuration@@YAABUconfiguration@ctor@@XZ") +#endif namespace ctor { std::optional<std::string> includedir; @@ -100,11 +110,32 @@ std::string ctor::configuration::get(const std::string& key, return ctor::conf_values[key]; } - if(has(key)) + if(tools.find(key) != tools.end()) { return tools.at(key); } + std::string value; + if(key == ctor::cfg::build_cxx && get_env("CXX", value)) + { + return value; + } + + if(key == ctor::cfg::build_cc && get_env("CC", value)) + { + return value; + } + + if(key == ctor::cfg::build_ld && get_env("LD", value)) + { + return value; + } + + if(key == ctor::cfg::build_ar && get_env("AR", value)) + { + return value; + } + return default_value; } @@ -116,10 +147,9 @@ std::string ctor::configuration::getenv(const std::string& key) const return envit->second; } - auto sysenv = std::getenv(key.data()); - if(sysenv) + if(std::string value; get_env(key.data(), value)) { - return sysenv; + return value; } return {}; @@ -132,20 +162,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 { @@ -162,6 +188,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::toolchain& toolchain) case ctor::toolchain::gcc: stream << "ctor::toolchain::gcc"; break; + case ctor::toolchain::msvc: + stream << "ctor::toolchain::msvc"; + break; case ctor::toolchain::clang: stream << "ctor::toolchain::clang"; break; @@ -236,8 +265,7 @@ int regenerateCache(ctor::settings& settings, { Args vargs(args); - dg::Options opt; - int key{128}; + arg::Parser<std::string> opt(static_cast<int>(vargs.size()), vargs.data()); std::string build_arch_prefix; std::string build_path; @@ -251,97 +279,110 @@ int regenerateCache(ctor::settings& settings, std::string ctor_libdir; std::string builddir; - opt.add("build-dir", required_argument, 'b', - "Set output directory for build files (default: '" + - settings.builddir + "').", - [&]() { - settings.builddir = optarg; - builddir = optarg; + opt.add('b', "--build-dir", + std::function([&](std::string arg) + { + settings.builddir = arg; + builddir = arg; return 0; - }); + }), + "Set output directory for build files (default: '" + + settings.builddir + "')."); - opt.add("verbose", no_argument, 'v', - "Be verbose. Add multiple times for more verbosity.", - [&]() { + opt.add('v', "--verbose", + std::function([&]() + { settings.verbose++; return 0; - }); + }), + "Be verbose. Add multiple times for more verbosity."); - opt.add("cc", required_argument, key++, - "Use specified c-compiler instead of gcc.", - [&]() { - cc_prog = optarg; + opt.add({}, "--cc", + std::function([&](std::string arg) + { + cc_prog = arg; return 0; - }); + }), + "Use specified c-compiler instead of gcc."); - opt.add("cxx", required_argument, key++, - "Use specified c++-compiler instead of g++.", - [&]() { - cxx_prog = optarg; + opt.add({}, "--cxx", + std::function([&](std::string arg) + { + cxx_prog = arg; return 0; - }); + }), + "Use specified c++-compiler instead of g++."); - opt.add("ar", required_argument, key++, - "Use specified archiver instead of ar.", - [&]() { - ar_prog = optarg; + opt.add({}, "--ar", + std::function([&](std::string arg) + { + ar_prog = arg; return 0; - }); + }), + "Use specified archiver instead of ar."); - opt.add("ld", required_argument, key++, - "Use specified linker instead of ld.", - [&]() { - ld_prog = optarg; + opt.add({}, "--ld", + std::function([&](std::string arg) + { + ld_prog = arg; return 0; - }); + }), + "Use specified linker instead of ld."); - opt.add("build", required_argument, key++, - "Configure for building on specified architecture.", - [&]() { - build_arch_prefix = optarg; + opt.add({}, "--build", + std::function([&](std::string arg) + { + build_arch_prefix = arg; return 0; - }); + }), + "Configure for building on specified architecture."); - opt.add("build-path", required_argument, key++, - "Set path to build tool-chain.", - [&]() { - build_path = optarg; + opt.add({}, "--build-path", + std::function([&](std::string arg) + { + build_path = arg; return 0; - }); + }), + "Set path to build tool-chain."); - opt.add("host", required_argument, key++, - "Cross-compile to build programs to run on specified architecture.", - [&]() { - host_arch_prefix = optarg; + opt.add({}, "--host", + std::function([&](std::string arg) + { + host_arch_prefix = arg; return 0; - }); + }), + "Cross-compile to build programs to run on specified architecture."); - opt.add("host-path", required_argument, key++, - "Set path to cross-compile tool-chain.", - [&]() { - host_path = optarg; + opt.add({}, "--host-path", + std::function([&](std::string arg) + { + host_path = arg; return 0; - }); + }), + "Set path to cross-compile tool-chain."); - opt.add("ctor-includedir", required_argument, key++, - "Set path to ctor header file, used for re-compiling.", - [&]() { - ctor_includedir = optarg; + opt.add({}, "--ctor-includedir", + std::function([&](std::string arg) + { + ctor_includedir = arg; return 0; - }); + }), + "Set path to ctor header file, used for re-compiling."); - opt.add("ctor-libdir", required_argument, key++, - "Set path to ctor library file, used for re-compiling.", - [&]() { - ctor_libdir = optarg; + opt.add({}, "--ctor-libdir", + std::function([&](std::string arg) + { + ctor_libdir = arg; return 0; - }); + }), + "Set path to ctor library file, used for re-compiling."); // Resolv externals ctor::external_configurations externalConfigs; - for(std::size_t i = 0; i < numExternalConfigFiles; ++i) + const auto& externalConfigFiles = getExternalConfigFileList(); + for(const auto& externalConfigFile : externalConfigFiles) { - auto newExternalConfigs = externalConfigFiles[i].cb(settings); + auto newExternalConfigs = externalConfigFile.cb(settings); externalConfigs.insert(externalConfigs.end(), newExternalConfigs.begin(), newExternalConfigs.end()); @@ -350,19 +391,21 @@ int regenerateCache(ctor::settings& settings, auto add_path_args = [&](const std::string& arg_name) { - opt.add(arg_name + "-includedir", required_argument, key++, - "Set path to " + arg_name + " header file.", - [&]() { - external_includedir[arg_name] = optarg; + opt.add({}, "--" + arg_name + "-includedir", + std::function([&](std::string arg) + { + external_includedir[arg_name] = arg; return 0; - }); + }), + "Set path to " + arg_name + " header file."); - opt.add(arg_name + "-libdir", required_argument, key++, - "Set path to " + arg_name + " libraries.", - [&]() { - external_libdir[arg_name] = optarg; + opt.add({}, "--" + arg_name + "-libdir", + std::function([&](std::string arg) + { + external_libdir[arg_name] = arg; return 0; - }); + }), + "Set path to " + arg_name + " libraries."); }; for(const auto& ext : externalConfigs) @@ -382,17 +425,57 @@ int regenerateCache(ctor::settings& settings, } - opt.add("help", no_argument, 'h', - "Print this help text.", - [&]() -> int { + opt.add('h', "--help", + std::function([&]() -> int + { std::cout << "Configure how to build with " << name << "\n"; std::cout << "Usage: " << name << " configure [options]\n\n"; std::cout << "Options:\n"; opt.help(); exit(0); - }); + }), + "Print this help text."); - opt.process(static_cast<int>(vargs.size()), vargs.data()); + opt.set_err_cb( + [&](arg::error err, std::string_view arg) + { + switch(err) + { + case arg::error::invalid_arg: + std::cerr << opt.prog_name() << + ": invalid argument for option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + + case arg::error::missing_arg: + std::cerr << opt.prog_name() << ": option requires and argument '" << + arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + + case arg::error::invalid_opt: + std::cerr << opt.prog_name() << ": invalid option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + } + }); + + opt.set_pos_cb( + [&](std::string_view) + { + std::cerr << + "The configure subcommand doesn't use positional arguments.\n"; + return 1; + }); + + auto res = opt.parse(); + if(res != 0) + { + return res; + } if(host_arch_prefix.empty()) { @@ -676,6 +759,7 @@ int regenerateCache(ctor::settings& settings, { ctor::conf_values[ctor::cfg::builddir] = builddir; } + ctor::conf_values[ctor::cfg::host_cxx] = host_cxx; ctor::conf_values[ctor::cfg::build_cxx] = build_cxx; @@ -858,52 +942,66 @@ 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["CXXFLAGS"] = value; + } + + if(get_env("AR", value)) { - env["CC"] = cc_env; + env["AR"] = value; } - auto cflags_env = getenv("CFLAGS"); - if(cflags_env) + if(get_env("LD", value)) { - env["CFLAGS"] = cflags_env; + env["LD"] = value; } - auto cxx_env = getenv("CXX"); - if(cxx_env) + if(get_env("LDFLAGS", value)) { - env["CXX"] = cxx_env; + env["LDFLAGS"] = value; } - auto cxxflags_env = getenv("CXXFLAGS"); - if(cxxflags_env) + if(get_env("PATH", value)) { - env["CXXFLAGS"] = cxxflags_env; + env["PATH"] = value; } - auto ar_env = getenv("AR"); - if(ar_env) + // Env vars for msvc + if(get_env("CL", value)) { - env["AR"] = ar_env; + env["CL"] = value; } - auto ld_env = getenv("LD"); - if(ld_env) + if(get_env("LIB", value)) { - env["LD"] = ld_env; + env["LIB"] = value; } - auto ldflags_env = getenv("LDFLAGS"); - if(ldflags_env) + if(get_env("LINK", value)) { - env["LDFLAGS"] = ldflags_env; + env["LINK"] = value; } - auto path_env = getenv("PATH"); - if(path_env) + if(get_env("INCLUDE", value)) { - env["PATH"] = path_env; + env["INCLUDE"] = value; } auto ret = regenerateCache(settings, args_span[0], args, env); @@ -59,6 +59,7 @@ enum class toolchain none, gcc, clang, + msvc, }; struct source @@ -94,6 +95,7 @@ enum class cxx_opt warn_shadow, // -Wshadow warn_extra, // -Wextra warnings_as_errors, // -Werror + exceptions, // -fexceptions generate_dep_tree, // -MMD no_link, // -c include_path, // -I<arg> @@ -233,7 +235,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 @@ -252,7 +254,7 @@ struct external_configuration using external_configurations = std::vector<ctor::external_configuration>; -int reg(ctor::external_configurations (*cb)(const ctor::settings&), +int reg(std::function<ctor::external_configurations (const ctor::settings&)> cb, const std::source_location location = std::source_location::current()); // Convenience macro - ugly but keeps things simple(r) diff --git a/src/deps.cc b/src/deps.cc index 9400b35..599be8c 100644 --- a/src/deps.cc +++ b/src/deps.cc @@ -5,6 +5,8 @@ #include "util.h" +#include <nlohmann/json.hpp> + #include <fstream> namespace { @@ -94,6 +96,74 @@ std::vector<std::string> readDepsMake(const std::string& dep_file) return output; } + +// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html +// https://devblogs.microsoft.com/cppblog/introducing-source-dependency-reporting-with-msvc-in-visual-studio-2019-version-16-7/ +/* Format examples: +{ + "Version": "1.1", + "Data": { + "Source": "z:\\home\\deva\\docs\\c\\ctor\\src\\libctor.cc", + "ProvidedModule": "", + "Includes": [ + "c:\\program files (x86)\\microsoft visual studio\\2019\\buildtools\\vc\\tools\\msvc\\14.29.30133\\include\\vector", + "c:\\program files (x86)\\microsoft visual studio\\2019\\buildtools\\vc\\tools\\msvc\\14.29.30133\\include\\yvals_core.h", + "c:\\program files (x86)\\microsoft visual studio\\2019\\buildtools\\vc\\tools\\msvc\\14.29.30133\\include\\vcruntime.h", +. +. +. + "z:\\home\\deva\\docs\\c\\ctor\\src\\unittest.h" + ], + "ImportedModules": [], + "ImportedHeaderUnits": [] + } +} +*/ +/* +{ + "Version": "1.2", + "Data": { + "Source": "c:\\jenkins\\workspace\\ctor-linux64\\src\\build.cc", + "ProvidedModule": "", + "Includes": [ + "c:\\jenkins\\workspace\\ctor-linux64\\src\\build.h", + "c:\\program files (x86)\\microsoft visual studio\\2022\\buildtools\\vc\\tools\\msvc\\14.42.34433\\include\\string", + "c:\\program files (x86)\\microsoft visual studio\\2022\\buildtools\\vc\\tools\\msvc\\14.42.34433\\include\\yvals_core.h", +. +. +. + "c:\\program files (x86)\\microsoft visual studio\\2022\\buildtools\\vc\\tools\\msvc\\14.42.34433\\include\\iostream" + ], + "ImportedModules": [], + "ImportedHeaderUnits": [] + } +} +*/ + +std::vector<std::string> readDepsJson(const std::string& dep_file) +{ + std::ifstream stream(dep_file); + auto json = nlohmann::json::parse(stream); + for(const auto& [key, value] : json.items()) + { + if(key == "Data" && value.is_object()) + { + for(const auto& [inner_key, inner_value] : value.items()) + { + if(inner_key == "Includes" && inner_value.is_array()) + { + std::vector<std::string> deps; + for(const auto& dep : inner_value) + { + deps.emplace_back(dep); + } + return deps; + } + } + } + } + return {}; +} } std::vector<std::string> readDeps(const std::string& dep_file, @@ -110,6 +180,10 @@ std::vector<std::string> readDeps(const std::string& dep_file, case ctor::toolchain::none: return {}; + // json based: + case ctor::toolchain::msvc: + return readDepsJson(dep_file); + // makefile .d file based: case ctor::toolchain::gcc: case ctor::toolchain::clang: diff --git a/src/execute.cc b/src/execute.cc index c050732..f2913a8 100644 --- a/src/execute.cc +++ b/src/execute.cc @@ -6,12 +6,24 @@ #include "ctor.h" #include "pointerlist.h" +#if !defined(_WIN32) #include <unistd.h> -#include <cstring> #include <sys/types.h> #include <sys/wait.h> #include <spawn.h> +extern char **environ; +#else +#define WINDOWS_LEAN_AND_MEAN +#include <windows.h> +#undef max +#include <thread> +#endif + #include <iostream> +#include <cstring> +#include <vector> +#include <deque> +#include <filesystem> /* https://blog.famzah.net/2009/11/20/a-much-faster-popen-and-system-implementation-for-linux/ @@ -23,7 +35,7 @@ https://stackoverflow.com/questions/4259629/what-is-the-difference-between-fork- namespace { - +#if !defined(_WIN32) int parent_waitpid(pid_t pid) { int status{}; @@ -61,9 +73,81 @@ int parent_waitpid(pid_t pid) // Should never happen... return 1; } -} // namespace :: +#endif //_WIN32 + +#if defined(_WIN32) +std::string getLastErrorAsString() +{ + DWORD errorMessageID = ::GetLastError(); + if(errorMessageID == 0) + { + return {}; + } -extern char **environ; // see 'man environ' + LPSTR message_buffer{}; + auto size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + errorMessageID, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&message_buffer, 0, nullptr); + + std::string message(message_buffer, size); + LocalFree(message_buffer); + return message; +} + +int moveSelf(const ctor::settings& settings) +{ + int cnt{0}; + char source[MAX_PATH]; + HMODULE module = GetModuleHandle(0); + GetModuleFileNameA(module, source, MAX_PATH); + + while(true) + { + if(cnt > 10) // If we need to try more than 10 times something is wrong + { + return 1; + } + + std::filesystem::path tmp_file = settings.builddir; + tmp_file /= "tmp"; + std::string target = tmp_file.string() + "-" + std::to_string(cnt); + if(MoveFileA(source, target.data())) + { + break; // success + } + + auto err = GetLastError(); + if(err == ERROR_ALREADY_EXISTS) + { + if(DeleteFileA(target.data())) + { + continue; // Try again + } + + err = GetLastError(); + if(err != ERROR_ACCESS_DENIED) + { + std::cerr << "Could not delete file\n"; + return err; + } + + cnt++; + continue; // Increment and try again + } + else + { + std::cerr << "Could not move file\n"; + return err; + } + } + return 0; +} +#endif // _WIN32 +} // namespace :: int execute(const ctor::settings& settings, const std::string& command, @@ -98,11 +182,13 @@ int execute(const ctor::settings& settings, std::cout << cmd << std::endl; } +#if !defined(_WIN32) + #if 1 auto pid = vfork(); if(pid == 0) { - EnvMap envmap((const char**)environ); + EnvMap envmap(environ); for(const auto& [key, value] : env) { envmap.insert(key + "=" + value); @@ -120,23 +206,163 @@ int execute(const ctor::settings& settings, } return parent_waitpid(pid); #elif 0 - pid_t pid; - std::vector<std::string> venv; + pid_t pid{}; + EnvMap envmap(environ); for(const auto& [key, value] : env) { - venv.push_back(key + "=" + value); + envmap.insert(key + "=" + value); } - Env penv(venv); + + auto [_, envv] = envmap.get(); if(posix_spawn(&pid, command.data(), nullptr, nullptr, - (char**)argv.data(), penv.data())) + (char**)argv.data(), const_cast<char* const *>(envv))) { return 1; } return parent_waitpid(pid); #else (void)parent_waitpid; + (void)env; // TODO: env is set set correctly so this doesn't work return system(cmd.data()); #endif +#else // _WIN32 + if(terminate) + { + auto ret = moveSelf(settings); + if(ret != 0) + { + return ret; + } + } + + auto env_strings = GetEnvironmentStrings(); + EnvMap envmap(env_strings); + FreeEnvironmentStrings(env_strings); + for(const auto& [key, value] : env) + { + envmap.insert(key + "=" + value); + } + + // TODO: Use SetDllDirectory(...) to set DLL search directory? + + SECURITY_ATTRIBUTES security_attr{}; + // Set the bInheritHandle flag so pipe handles are inherited. + security_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attr.bInheritHandle = true; + security_attr.lpSecurityDescriptor = nullptr; + + HANDLE stream_in_read{}; + HANDLE stream_in_write{}; + HANDLE stream_out_read{}; + HANDLE stream_out_write{}; + + if(!CreatePipe(&stream_out_read, &stream_out_write, &security_attr, 0)) + { + std::cerr << "Error CreatePipe (out): " << getLastErrorAsString() << "\n"; + } + + if(!SetHandleInformation(stream_out_read, HANDLE_FLAG_INHERIT, 0)) + { + std::cerr << + "Error - SetHandleInformation (out): " << getLastErrorAsString() << "\n"; + } + + // Create a pipe for the child process's STDIN. + if(!CreatePipe(&stream_in_read, &stream_in_write, &security_attr, 0)) + { + std::cerr << "Error CreatePipe (in): " << getLastErrorAsString() << "\n"; + } + + // Ensure the write handle to the pipe for STDIN is not inherited. + if(!SetHandleInformation(stream_in_write, HANDLE_FLAG_INHERIT, 0)) + { + std::cerr << + "Error - SetHandleInformation (in): " << getLastErrorAsString() << "\n"; + } + + STARTUPINFO si{}; + si.hStdInput = GetStdHandle(((DWORD)-10));//STD_INPUT_HANDLE + si.hStdOutput = stream_out_write; + si.hStdError = stream_out_write; + si.dwFlags |= STARTF_USESTDHANDLES; + + PROCESS_INFORMATION pi{}; + + si.cb = sizeof(si); + + if(!CreateProcess(nullptr, // lpApplicationName + cmd.data(), // lpCommandLine + nullptr, // lpProcessAttributes + nullptr, // lpThreadAttributes + true, // bInheritHandles + INHERIT_PARENT_AFFINITY | + 0x00000200 | // CREATE_NEW_PROCESS_GROUP + 0, // dwCreationFlags + envmap.stringify().data(), // lpEnvironment + nullptr, // lpCurrentDirectory + &si, // lpStartupInfo + &pi)) // lpProcessInformation + { + std::cerr << + "Could not execute " << command << ": " << getLastErrorAsString() << "\n"; + return 1; + } + + int ignore_lines{}; + std::filesystem::path cmd_path{command}; + if(cmd_path.filename() == "cl.exe") + { + // Ignore first line, avoiding the annoying msvc compiler of printing the + // filename of the file that is being compiled + ignore_lines = 1; + } + + std::atomic<bool> running{true}; + auto parent_waitpid = + std::thread([&]() + { + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(stream_out_write); + CloseHandle(stream_in_read); + running.store(false); + }); + + CHAR buf[1024]; + while(running.load()) + { + if(WaitForSingleObject(stream_out_read, 0) != WAIT_OBJECT_0) + { + Sleep(1); + continue; + } + DWORD read_bytes{}; + auto res = ReadFile(stream_out_read, buf, sizeof(buf), &read_bytes, nullptr); + if(res != TRUE || read_bytes == 0) + { + break; + } + std::string str; + str.append(buf, read_bytes); + if(ignore_lines == 0) + { + std::cout << str << std::flush; + } + if(str.find('\n') != std::string::npos) + { + ignore_lines = std::max(0, ignore_lines - 1); + } + } + + DWORD exit_code{}; + GetExitCodeProcess(pi.hProcess, &exit_code); + parent_waitpid.join(); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return exit_code; +#endif // _WIN32 + return 1; } 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 3bfa041..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,115 +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("dry-run", no_argument, 'n', - "Print the commands that would be executed, but do not execute them.", - [&]() { + opt.add('n', "--dry-run", + std::function([&]() + { settings.dry_run = true; return 0; - }); + }), + "Print the commands to be executed, but do not execute them."); - opt.add("configure-cmd", no_argument, key++, - "Print commandline for last configure.", - [&]() { + opt.add({}, "--configure-cmd", + std::function([&]() + { no_relaunch = true; print_configure_cmd = true; return 0; - }); + }), + "Print commandline for last configure."); - opt.add("configure-db", no_argument, key++, - "Print entire configure parameter database.", - [&]() { + opt.add({}, "--configure-db", + std::function([&]() + { no_relaunch = true; print_configure_db = true; return 0; - }); + }), + "Print entire configure parameter database."); - opt.add("database", required_argument, 'd', - "Write compilation database json file.", - [&]() { + opt.add('d', "--database", + std::function([&](std::string database) + { no_relaunch = true; write_compilation_database = true; - compilation_database = optarg; + compilation_database = database; return 0; - }); + }), + "Write compilation database json file."); - opt.add("help", no_argument, 'h', - "Print this help text.", - [&]() -> int { + opt.add('h', "--help", + std::function([&]() -> int + { std::cout << "Usage: " << args[0] << " [options] [target] ...\n"; std::cout << R"_( where target can be either: @@ -181,28 +186,67 @@ Options: )_"; opt.help(); exit(0); - }); + }), + "Print this help text."); - opt.process(argc, argv); + opt.set_err_cb( + [&](arg::error err, std::string_view arg) + { + switch(err) + { + case arg::error::invalid_arg: + std::cerr << opt.prog_name() << + ": invalid argument for option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + + case arg::error::missing_arg: + std::cerr << opt.prog_name() << ": option requires and argument '" << + arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + + case arg::error::invalid_opt: + std::cerr << opt.prog_name() << ": invalid option '" << arg << "'\n"; + std::cerr << "Type '" << opt.prog_name() << + " -h' for more information.\n"; + break; + } + }); + auto res = opt.parse(); + if(res != 0) + { + return res; + } - auto verbose_env = std::getenv("V"); - if(verbose_env) + if(std::string value; get_env("V", value)) { - settings.verbose = std::atoi(verbose_env); + try + { + settings.verbose = std::stoi(value); + } + catch(...) + { + // not an integer + } } if(list_files) { no_default_build = true; std::vector<std::string> files; - for(std::size_t i = 0; i < numConfigFiles; ++i) + const auto& configFiles = getConfigFileList(); + for(const auto& configFile : configFiles) { - files.emplace_back(configFiles[i].file); + files.emplace_back(configFile.file); } - for(std::size_t i = 0; i < numExternalConfigFiles; ++i) + const auto& externalConfigFiles = getExternalConfigFileList(); + for(const auto& externalConfigFile : externalConfigFiles) { - files.emplace_back(externalConfigFiles[i].file); + files.emplace_back(externalConfigFile.file); } std::sort(files.begin(), files.end()); @@ -295,7 +339,7 @@ Options: } bool build_all{!no_default_build}; - for(const auto& arg : opt.arguments()) + for(const auto& arg : arguments) { if(arg == "configure") { diff --git a/src/rebuild.cc b/src/rebuild.cc index a2b7ddd..afa1b6d 100644 --- a/src/rebuild.cc +++ b/src/rebuild.cc @@ -9,6 +9,7 @@ #include <source_location> #include <cstring> #include <span> +#include <vector> #include "configure.h" #include "ctor.h" @@ -18,34 +19,38 @@ #include "tools.h" #include "util.h" -std::array<BuildConfigurationEntry, 1024> configFiles; -std::size_t numConfigFiles{0}; +std::vector<BuildConfigurationEntry>& getConfigFileList() +{ + static std::vector<BuildConfigurationEntry> configFiles; + return configFiles; +} + +std::vector<ExternalConfigurationEntry>& getExternalConfigFileList() +{ + static std::vector<ExternalConfigurationEntry> externalConfigFiles; + return externalConfigFiles; +} namespace ctor { -int reg(ctor::build_configurations (*cb)(const ctor::settings&), +int reg(std::function<ctor::build_configurations (const ctor::settings&)> cb, const std::source_location location) { - // NOTE: std::cout cannot be used here - if(numConfigFiles >= configFiles.size()) - { - fprintf(stderr, "Max %d build configurations currently supported.\n", - (int)configFiles.size()); - exit(1); - } + BuildConfigurationEntry entry; auto loc = std::filesystem::path(location.file_name()); if(loc.is_absolute()) { auto pwd = std::filesystem::current_path(); auto rel = std::filesystem::relative(loc, pwd); - configFiles[numConfigFiles].file = strdup(rel.string().data()); // NOTE: This intentionally leaks memory + entry.file = rel.string(); } else { - configFiles[numConfigFiles].file = location.file_name(); + entry.file = location.file_name(); } - configFiles[numConfigFiles].cb = cb; - ++numConfigFiles; + entry.cb = cb; + auto& configFiles = getConfigFileList(); + configFiles.push_back(entry); return 0; } @@ -53,80 +58,50 @@ int reg(ctor::build_configurations (*cb)(const ctor::settings&), int reg(const char* location) { - // NOTE: std::cout cannot be used here - if(numConfigFiles >= configFiles.size()) - { - fprintf(stderr, "Max %d build configurations currently supported.\n", - (int)configFiles.size()); - exit(1); - } + BuildConfigurationEntry entry; - configFiles[numConfigFiles].file = location; - configFiles[numConfigFiles].cb = - [](const ctor::settings&){ return std::vector<ctor::build_configuration>{}; }; - ++numConfigFiles; + entry.file = location; + entry.cb = + [](const ctor::settings&) + { + return std::vector<ctor::build_configuration>{}; + }; + auto& configFiles = getConfigFileList(); + configFiles.push_back(entry); return 0; } int unreg(const char* location) { - int found{0}; - for(std::size_t i = 0; i < numConfigFiles;) - { - if(std::string(location) == configFiles[i].file) - { - ++found; - for(std::size_t j = i; j < numConfigFiles; ++j) - { - configFiles[j] = configFiles[j + 1]; - } - --numConfigFiles; - } - else - { - ++i; - } - } - - for(std::size_t i = 0; i < numExternalConfigFiles;) - { - if(std::string(location) == externalConfigFiles[i].file) - { - ++found; - for(std::size_t j = i; j < numExternalConfigFiles; ++j) - { - externalConfigFiles[j] = externalConfigFiles[j + 1]; - } - --numExternalConfigFiles; - } - else - { - ++i; - } - } - - return found; + auto& configFiles = getConfigFileList(); + auto erasedConfigs = + std::erase_if(configFiles, + [&](const BuildConfigurationEntry& entry) + { + return entry.file == location; + }); + + auto& externalConfigFiles = getExternalConfigFileList(); + auto erasedExternals = + std::erase_if(externalConfigFiles, + [&](const ExternalConfigurationEntry& entry) + { + return entry.file == location; + }); + + return static_cast<int>(erasedConfigs) + static_cast<int>(erasedExternals); } -std::array<ExternalConfigurationEntry, 1024> externalConfigFiles; -std::size_t numExternalConfigFiles{0}; - namespace ctor { -int reg(ctor::external_configurations (*cb)(const ctor::settings&), +int reg(std::function<ctor::external_configurations (const ctor::settings&)> cb, const std::source_location location) { - // NOTE: std::cout cannot be used here - if(numExternalConfigFiles >= externalConfigFiles.size()) - { - fprintf(stderr, "Max %d external configurations currently supported.\n", - (int)externalConfigFiles.size()); - exit(1); - } - - externalConfigFiles[numExternalConfigFiles].file = location.file_name(); - externalConfigFiles[numExternalConfigFiles].cb = cb; - ++numExternalConfigFiles; + ExternalConfigurationEntry entry; + entry.file = location.file_name(); + entry.cb = cb; + auto& externalConfigFiles = getExternalConfigFileList(); + externalConfigFiles.push_back(entry); return 0; } @@ -155,9 +130,10 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ using namespace std::string_literals; + const auto& configFiles = getConfigFileList(); if(global_settings.verbose > 1) { - std::cout << "Recompile check (" << numConfigFiles << "):\n"; + std::cout << "Recompile check (" << configFiles.size() << "):\n"; } ctor::build_configuration config; @@ -169,6 +145,8 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ config.flags.cxxflags.emplace_back(ctor::cxx_opt::optimization, "3"); config.flags.cxxflags.emplace_back(ctor::cxx_opt::cpp_std, "c++20"); + config.flags.cxxflags.push_back({ctor::toolchain::msvc, ctor::cxx_opt::custom, "/EHsc"}); + const auto& c = ctor::get_configuration(); if(c.has(ctor::cfg::ctor_includedir)) { @@ -180,13 +158,32 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ config.flags.ldflags.emplace_back(ctor::ld_opt::library_path, c.get(ctor::cfg::ctor_libdir)); } - config.flags.ldflags.emplace_back(ctor::ld_opt::link, "ctor"); + + // Static linking with the ctor lib is toolchain specific but to compiler + // enforce support for all toolchains a switch is used instead of a per + // target ldflags. + auto toolchain = getToolChain(config.system); + switch(toolchain) + { + case ctor::toolchain::msvc: + config.flags.ldflags.emplace_back(ctor::ld_opt::link, "libctor.lib"); + break; + case ctor::toolchain::gcc: + case ctor::toolchain::clang: + case ctor::toolchain::any: + case ctor::toolchain::none: + config.flags.ldflags.emplace_back(ctor::ld_opt::link, "ctor"); + break; + } + config.flags.ldflags.emplace_back(ctor::ld_opt::threads); + config.flags.ldflags.push_back({ctor::toolchain::msvc, ctor::ld_opt::custom, "/subsystem:console"}); + ctor::settings settings{global_settings}; settings.verbose = -1; // Make check completely silent. - // override builddir to use ctor subdir + // Override builddir to use ctor subdir auto ctor_builddir = std::filesystem::path(settings.builddir) / "ctor"; settings.builddir = ctor_builddir.string(); @@ -201,9 +198,9 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ config.sources.emplace_back(configurationFile.string()); } - for(std::size_t i = 0; i < numConfigFiles; ++i) + for(const auto& configFile : configFiles) { - std::string location = configFiles[i].file; + std::string location = configFile.file; if(global_settings.verbose > 1) { std::cout << " - " << location << "\n"; @@ -216,9 +213,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_ar.cc b/src/task_ar.cc index 3b45cc2..0365d51 100644 --- a/src/task_ar.cc +++ b/src/task_ar.cc @@ -93,6 +93,8 @@ int TaskAR::runInner() append(args, ar_option(toolchain, ctor::ar_opt::add_index)); append(args, ar_option(toolchain, ctor::ar_opt::create)); append(args, ar_option(toolchain, ctor::ar_opt::output, targetFile().string())); + append(args, to_strings(toolchain, {ctor::toolchain::msvc, ctor::ar_opt::custom, "/nologo"})); + for(const auto& task : getDependsTasks()) { args.push_back(task->targetFile().string()); diff --git a/src/task_cc.cc b/src/task_cc.cc index 9628455..b300cd6 100644 --- a/src/task_cc.cc +++ b/src/task_cc.cc @@ -305,12 +305,14 @@ std::vector<std::string> TaskCC::flags() const { append(flags, to_strings(toolchain, flag)); } + append(flags, to_strings(toolchain, {ctor::toolchain::msvc, ctor::c_opt::custom, "/nologo"})); return flags; case ctor::language::cpp: for(const auto& flag : config.flags.cxxflags) { append(flags, to_strings(toolchain, flag)); } + append(flags, to_strings(toolchain, {ctor::toolchain::msvc, ctor::cxx_opt::custom, "/nologo"})); return flags; default: std::cerr << "Unknown CC target type\n"; diff --git a/src/task_ld.cc b/src/task_ld.cc index 03745be..35c40ce 100644 --- a/src/task_ld.cc +++ b/src/task_ld.cc @@ -115,6 +115,7 @@ int TaskLD::runInner() } append(args, ld_option(toolchain, ctor::ld_opt::output, targetFile().string())); + append(args, to_strings(toolchain, {ctor::toolchain::msvc, ctor::ld_opt::custom, "/nologo"})); { // Write flags to file. std::ofstream flagsStream(flagsFile); @@ -129,11 +130,33 @@ int TaskLD::runInner() auto tool = compiler(); const auto& cfg = ctor::get_configuration(); - auto ldflags = cfg.getenv("LDFLAGS"); - if(!ldflags.empty()) + switch(toolchain) { - append(args, ld_option(toolchain, ctor::ld_opt::custom, ldflags)); + case ctor::toolchain::msvc: + switch(outputSystem()) + { + case ctor::output_system::host: + tool = cfg.get(ctor::cfg::host_ld, "link.exe"); + break; + case ctor::output_system::build: + tool = cfg.get(ctor::cfg::build_ld, "link.exe"); + break; + } + break; + case ctor::toolchain::gcc: + case ctor::toolchain::clang: + case ctor::toolchain::any: + case ctor::toolchain::none: + { + auto ldflags = cfg.getenv("LDFLAGS"); + if(!ldflags.empty()) + { + append(args, ld_option(toolchain, ctor::ld_opt::custom, ldflags)); + } + } + break; } + auto res = execute(settings, tool, args, cfg.env, is_self); if(res != 0) { diff --git a/src/task_so.cc b/src/task_so.cc index 92aeefe..db07c53 100644 --- a/src/task_so.cc +++ b/src/task_so.cc @@ -93,6 +93,8 @@ int TaskSO::runInner() append(args, ld_option(toolchain, ctor::ld_opt::output, targetFile().string())); + append(args, to_strings(toolchain, {ctor::toolchain::msvc, ctor::ld_opt::custom, "/nologo"})); + for(const auto& task : getDependsTasks()) { args.push_back(task->targetFile().string()); diff --git a/src/tasks.cc b/src/tasks.cc index e853470..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; diff --git a/src/tools.cc b/src/tools.cc index dfabdff..acfbbf7 100644 --- a/src/tools.cc +++ b/src/tools.cc @@ -6,6 +6,7 @@ #include <filesystem> #include <iostream> #include <sstream> +#include <algorithm> #include <array> #include <cassert> @@ -13,6 +14,11 @@ #include "util.h" +#if defined(_WIN32) +#define popen _popen +#define pclose _pclose +#endif + std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt) { // Adding to this enum should also imply adding to the unit-tests below @@ -51,6 +57,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt) case ctor::cxx_opt::warn_shadow: stream << "ctor::cxx_opt::warn_shadow"; break; case ctor::cxx_opt::warn_extra: stream << "ctor::cxx_opt::warn_extra"; break; case ctor::cxx_opt::warnings_as_errors: stream << "ctor::cxx_opt::warnings_as_errors"; break; + case ctor::cxx_opt::exceptions: stream << "ctor::cxx_opt::exceptions"; break; case ctor::cxx_opt::generate_dep_tree: stream << "ctor::cxx_opt::generate_dep_tree"; break; case ctor::cxx_opt::no_link: stream << "ctor::cxx_opt::no_link"; break; case ctor::cxx_opt::include_path: stream << "ctor::cxx_opt::include_path"; break; @@ -129,6 +136,10 @@ ctor::toolchain getToolChain(const std::string& compiler) { return ctor::toolchain::gcc; } + else if(to_lower(cc_cmd).find("cl") != std::string::npos) + { + return ctor::toolchain::msvc; + } std::cerr << "Unsupported output system.\n"; return ctor::toolchain::gcc; @@ -154,12 +165,258 @@ ctor::toolchain getToolChain(ctor::output_system system) return getToolChain(cfg.get(ctor::cfg::build_cxx, "g++")); } } +namespace msvc { +std::string get_arch([[maybe_unused]] ctor::output_system system) +{ + return "windows"; +} + +ctor::arch get_arch([[maybe_unused]] const std::string& str) +{ + return ctor::arch::windows; +} + +ctor::c_flag c_option(const std::string& flag) +{ + if(flag.starts_with("/I")) + { + std::string path = flag.substr(2); + path.erase(0, path.find_first_not_of(' ')); + return { ctor::c_opt::include_path, path }; + } + + return { ctor::c_opt::custom, flag }; +} + +ctor::cxx_flag cxx_option(const std::string& flag) +{ + if(flag.starts_with("/I")) + { + std::string path = flag.substr(2); + path.erase(0, path.find_first_not_of(' ')); + return { ctor::cxx_opt::include_path, path }; + } + + return { ctor::cxx_opt::custom, flag }; +} + +ctor::ld_flag ld_option(const std::string& flag) +{ + if(flag.starts_with("/L")) + { + std::string path = flag.substr(2); + path.erase(0, path.find_first_not_of(' ')); + return { ctor::ld_opt::library_path, path }; + } + + return { ctor::ld_opt::custom, flag }; +} + +ctor::ar_flag ar_option(const std::string& flag) +{ + return { ctor::ar_opt::custom, flag }; +} + +std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, + const std::string& arg2) +{ + switch(opt) + { + case ctor::cxx_opt::output: + return {"/Fo\"" + arg + "\""}; + case ctor::cxx_opt::debug: + return {"/DEBUG"}; + case ctor::cxx_opt::warn_all: + return {"/W4"}; + case ctor::cxx_opt::warn_conversion: + return {"/W4"}; // TODO: This is incorrect + case ctor::cxx_opt::warn_shadow: + return {"/W4"}; // TODO: This is incorrect + case ctor::cxx_opt::warn_extra: + return {"/W4"}; // TODO: This is incorrect + case ctor::cxx_opt::warnings_as_errors: + return {"/WX"}; + case ctor::cxx_opt::exceptions: + return {"/EHsc"}; + case ctor::cxx_opt::generate_dep_tree: + return {"/sourceDependencies", arg}; + case ctor::cxx_opt::no_link: + return {"/c"}; + case ctor::cxx_opt::include_path: + return {"/I" + arg}; + case ctor::cxx_opt::cpp_std: + return {"/std:" + arg}; + case ctor::cxx_opt::optimization: + { + int o{0}; + try + { + o = std::stoi(arg); + o = std::clamp(o, 0, 2); + } + catch(...) + { + // bad number? + } + return {"/O" + std::to_string(o)}; + } + case ctor::cxx_opt::position_independent_code: + return {}; // TODO? + case ctor::cxx_opt::position_independent_executable: + return {}; // TODO? + case ctor::cxx_opt::define: + if(!arg2.empty()) + { + return {"/D" + arg + "=" + esc(arg2)}; + } + else + { + return {"/D" + arg}; + } + case ctor::cxx_opt::custom: + return {arg}; + } + + std::cerr << "Unsupported compiler option.\n"; + return {}; +} + +std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg, + const std::string& arg2) +{ + switch(opt) + { + case ctor::c_opt::output: + return {"/Fo\"" + arg + "\""}; + case ctor::c_opt::debug: + return {"/DEBUG"}; + case ctor::c_opt::warn_all: + return {"/W4"}; + case ctor::c_opt::warn_conversion: + return {"/W4"}; // TODO: This is incorrect + case ctor::c_opt::warn_shadow: + return {"/W4"}; // TODO: This is incorrect + case ctor::c_opt::warn_extra: + return {"/W4"}; // TODO: This is incorrect + case ctor::c_opt::warnings_as_errors: + return {"/WX"}; + case ctor::c_opt::generate_dep_tree: + return {"/sourceDependencies", arg}; + case ctor::c_opt::no_link: + return {"/c"}; + case ctor::c_opt::include_path: + return {"/I" + arg}; + case ctor::c_opt::c_std: + return {"/std:" + arg}; + case ctor::c_opt::optimization: + { + int o{0}; + try + { + o = std::stoi(arg); + o = std::clamp(o, 0, 2); + } + catch(...) + { + // bad number? + } + return {"/O" + std::to_string(o)}; + } + case ctor::c_opt::position_independent_code: + return {}; // TODO? + case ctor::c_opt::position_independent_executable: + return {}; // TODO? + case ctor::c_opt::define: + if(!arg2.empty()) + { + return {"/D" + arg + "=" + esc(arg2)}; + } + else + { + return {"/D" + arg}; + } + case ctor::c_opt::custom: + return {arg}; + } + + std::cerr << "Unsupported compiler option.\n"; + return {}; +} + +std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg, + [[maybe_unused]]const std::string& arg2) +{ + switch(opt) + { + case ctor::ld_opt::output: + return {"/out:" + arg + ""}; + case ctor::ld_opt::warn_all: + return {"/Wall"}; + case ctor::ld_opt::warnings_as_errors: + return {"/WX"}; + case ctor::ld_opt::library_path: + return {"/LIBPATH:\""+arg+"\""}; + case ctor::ld_opt::link: + return {arg}; // TODO? + case ctor::ld_opt::cpp_std: + return {"/std:" + arg}; + case ctor::ld_opt::build_shared: + return {}; // TODO? + case ctor::ld_opt::threads: + return {}; // TODO? + case ctor::ld_opt::position_independent_code: + return {}; // TODO? + case ctor::ld_opt::position_independent_executable: + return {}; // TODO? + case ctor::ld_opt::custom: + return {arg}; + } + + std::cerr << "Unsupported compiler option.\n"; + return {}; +} + +std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg, + [[maybe_unused]]const std::string& arg2) +{ + switch(opt) + { + case ctor::ar_opt::replace: + return {}; + case ctor::ar_opt::add_index: + return {}; + case ctor::ar_opt::create: + return {}; + case ctor::ar_opt::output: + return {"/out:" + arg}; + case ctor::ar_opt::custom: + return {arg}; + } + + std::cerr << "Unsupported compiler option.\n"; + return {}; +} + +std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg, + [[maybe_unused]]const std::string& arg2) +{ + switch(opt) + { + case ctor::asm_opt::custom: + return {arg}; + } + + std::cerr << "Unsupported compiler option.\n"; + return {}; +} +} // msvc:: + namespace gcc { std::string get_arch(ctor::output_system system) { + std::string arch; std::string cmd; - const auto& c = ctor::get_configuration(); switch(system) { @@ -180,7 +437,6 @@ std::string get_arch(ctor::output_system system) return {};//ctor::arch::unknown; } - std::string arch; while(!feof(pipe)) { constexpr auto buffer_size{1024}; @@ -357,6 +613,11 @@ ctor::cxx_flag cxx_option(const std::string& flag) return { ctor::cxx_opt::warn_extra}; } + if(flag.starts_with("-fexceptions")) + { + return { ctor::cxx_opt::exceptions}; + } + if(flag.starts_with("-g")) { return { ctor::cxx_opt::debug }; @@ -420,6 +681,8 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, return {"-Wextra"}; case ctor::cxx_opt::warnings_as_errors: return {"-Werror"}; + case ctor::cxx_opt::exceptions: + return {"-fexceptions"}; case ctor::cxx_opt::generate_dep_tree: return {"-MMD"}; case ctor::cxx_opt::no_link: @@ -571,6 +834,8 @@ std::string get_arch(ctor::output_system system) case ctor::toolchain::clang: case ctor::toolchain::gcc: return gcc::get_arch(system); + case ctor::toolchain::msvc: + return msvc::get_arch(system); case ctor::toolchain::any: case ctor::toolchain::none: break; @@ -586,6 +851,8 @@ ctor::arch get_arch(ctor::output_system system, const std::string& str) case ctor::toolchain::clang: case ctor::toolchain::gcc: return gcc::get_arch(str); + case ctor::toolchain::msvc: + return msvc::get_arch(str); case ctor::toolchain::any: case ctor::toolchain::none: break; @@ -603,6 +870,8 @@ std::vector<std::string> c_option(ctor::toolchain toolchain, case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::c_option(opt, arg, arg2); + case ctor::toolchain::msvc: + return msvc::c_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -636,6 +905,8 @@ std::vector<std::string> cxx_option(ctor::toolchain toolchain, case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::cxx_option(opt, arg, arg2); + case ctor::toolchain::msvc: + return msvc::cxx_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -669,6 +940,8 @@ std::vector<std::string> ld_option(ctor::toolchain toolchain, case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::ld_option(opt, arg, arg2); + case ctor::toolchain::msvc: + return msvc::ld_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -702,6 +975,8 @@ std::vector<std::string> ar_option(ctor::toolchain toolchain, case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::ar_option(opt, arg, arg2); + case ctor::toolchain::msvc: + return msvc::ar_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -735,6 +1010,8 @@ std::vector<std::string> asm_option(ctor::toolchain toolchain, case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::asm_option(opt, arg, arg2); + case ctor::toolchain::msvc: + return msvc::asm_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -766,6 +1043,8 @@ ctor::c_flag c_option(const std::string& flag, ctor::toolchain toolchain) case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::c_option(flag); + case ctor::toolchain::msvc: + return msvc::c_option(flag); case ctor::toolchain::any: case ctor::toolchain::none: break; @@ -781,6 +1060,8 @@ ctor::cxx_flag cxx_option(const std::string& flag, ctor::toolchain toolchain) case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::cxx_option(flag); + case ctor::toolchain::msvc: + return msvc::cxx_option(flag); case ctor::toolchain::any: case ctor::toolchain::none: break; @@ -796,6 +1077,8 @@ ctor::ld_flag ld_option(const std::string& flag, ctor::toolchain toolchain) case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::ld_option(flag); + case ctor::toolchain::msvc: + return msvc::ld_option(flag); case ctor::toolchain::any: case ctor::toolchain::none: break; @@ -811,6 +1094,8 @@ ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain) case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::ar_option(flag); + case ctor::toolchain::msvc: + return msvc::ar_option(flag); case ctor::toolchain::any: case ctor::toolchain::none: break; @@ -825,6 +1110,7 @@ ctor::asm_flag asm_option(const std::string& flag, ctor::toolchain toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: + case ctor::toolchain::msvc: case ctor::toolchain::any: case ctor::toolchain::none: break; @@ -908,10 +1194,11 @@ ctor::toolchain guess_toolchain(const std::string& opt) return ctor::toolchain::gcc; } - //if(opt[0] == '/') - //{ - // return ctor::toolchain::msvc; - //} + if(opt[0] == '/') + { + return ctor::toolchain::msvc; + } + return ctor::toolchain::any; } } @@ -982,8 +1269,8 @@ ctor::target_type target_type_from_extension(ctor::toolchain toolchain, } } - if(toolchain == ctor::toolchain::any// || - //toolchain == ctor::toolchain::msvc || + if(toolchain == ctor::toolchain::any || + toolchain == ctor::toolchain::msvc// || //toolchain == ctor::toolchain::mingw || ) { diff --git a/src/util.cc b/src/util.cc index 6fc650a..9bb173f 100644 --- a/src/util.cc +++ b/src/util.cc @@ -7,12 +7,17 @@ #include <fstream> #include <algorithm> #include <sstream> +#include <cctype> +#include <cstdlib> -std::string to_lower(const std::string& str) +std::string to_lower(std::string str) { - std::string out{str}; - std::transform(out.begin(), out.end(), out.begin(), ::tolower); - return out; + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) + { + return static_cast<char>(std::tolower(c)); + }); + return str; } std::string readFile(const std::string& fileName) @@ -20,13 +25,17 @@ std::string readFile(const std::string& fileName) std::ifstream ifs(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate); - std::ifstream::pos_type fileSize = ifs.tellg(); + auto size = ifs.tellg(); + if(size < 0) + { + return {}; + } ifs.seekg(0, std::ios::beg); - std::vector<char> bytes(static_cast<std::size_t>(fileSize)); - ifs.read(bytes.data(), fileSize); + std::string bytes(static_cast<std::size_t>(size), '\0'); + ifs.read(bytes.data(), static_cast<std::streamsize>(bytes.size())); - return {bytes.data(), static_cast<std::size_t>(fileSize)}; + return bytes; } ctor::language languageFromExtension(const std::filesystem::path& file) @@ -71,7 +80,7 @@ namespace { bool isClean(char c) { - return c != '.' && c != '/'; + return c != '.' && c != '/' && c != '\\'; } } @@ -109,8 +118,18 @@ std::string esc(const std::string& in) return out; } -std::vector<std::string> get_paths(const std::string& path_env) +std::vector<std::string> get_paths(const std::string& path_env_) { + std::string path_env; + if(!path_env_.empty()) + { + path_env = path_env_; + } + else + { + get_env("PATH", path_env); + } + std::vector<std::string> paths; #ifdef _WIN32 @@ -170,15 +189,36 @@ std::string locate(const std::string& prog, } } + if(std::filesystem::exists(program + ".exe")) + { + if(check_executable(program + ".exe")) + { + return program + ".exe"; + } + } + for(const auto& path_str : paths) { std::filesystem::path path(path_str); - auto prog_path = path / program; - if(std::filesystem::exists(prog_path)) { - if(check_executable(prog_path)) + auto prog_path = path / program; + if(std::filesystem::exists(prog_path)) { - return prog_path.string(); + if(check_executable(prog_path)) + { + return prog_path.string(); + } + } + } + + { + auto prog_path = path / (program + ".exe"); + if(std::filesystem::exists(prog_path)) + { + if(check_executable(prog_path)) + { + return prog_path.string(); + } } } } @@ -277,3 +317,26 @@ std::vector<std::string> argsplit(const std::string& str) } return tokens; } + +bool get_env(std::string_view name, std::string& value) +{ +#if defined(_WIN32) + std::size_t size{}; + getenv_s(&size, nullptr, 0, name.data()); + if(size == 0) + { + return false; + } + value.resize(size); + getenv_s(&size, value.data(), size, name.data()); + return true; +#else + auto var = getenv(name.data()); + if(var) + { + value = var; + return true; + } + return false; +#endif +} @@ -9,7 +9,7 @@ #include <filesystem> #include <cstdlib> -std::string to_lower(const std::string& str); +std::string to_lower(std::string str); std::string readFile(const std::string& fileName); ctor::language languageFromExtension(const std::filesystem::path& file); @@ -23,7 +23,11 @@ void append(T& a, const T& b) std::string esc(const std::string& in); -std::vector<std::string> get_paths(const std::string& path_env = std::getenv("PATH")); +//! Get system paths (ie. env var PATH). +//! If path_env is provided, this search string will be used, other the PATH +//! env variable is used. +//! \returns a vector of the individual toknized paths. +std::vector<std::string> get_paths(const std::string& path_env = {}); std::string locate(const std::string& app, const std::vector<std::string>& paths, @@ -31,3 +35,7 @@ std::string locate(const std::string& app, //! Splits string into tokens adhering to quotations " and ' std::vector<std::string> argsplit(const std::string& str); + +//! Calls the system getenv and sets the string if the env name it exists. +//! \returns true if the env name existed, false otherwise. +bool get_env(std::string_view name, std::string& value); diff --git a/test/argparser_test.cc b/test/argparser_test.cc new file mode 100644 index 0000000..b649e8c --- /dev/null +++ b/test/argparser_test.cc @@ -0,0 +1,1019 @@ +#include <argparser.h> + +#include <iostream> +#include <string> + +std::ostream& operator<<(std::ostream& ostr, const arg::error& err) +{ + switch(err) + { + case arg::error::invalid_arg: + ostr << "arg::error::invalid_arg"; + break; + case arg::error::invalid_opt: + ostr << "arg::error::invalid_opt"; + break; + case arg::error::missing_arg: + ostr << "arg::error::missing_arg"; + break; + } + return ostr; +} + +#include <uunit.h> + +class ArgParserTest + : public uUnit +{ +public: + ArgParserTest() + { + uTEST(ArgParserTest::test_zero); + uTEST(ArgParserTest::test_one); + uTEST(ArgParserTest::test_many); + uTEST(ArgParserTest::test_exceptional); + uTEST(ArgParserTest::test_err_callback); + uTEST(ArgParserTest::test_pos_callback); + uTEST(ArgParserTest::test_grouped); + uTEST(ArgParserTest::test_nullprogram); + } + + void test_zero() + { + const char* const argv[] = { "app-name" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int> args(argc, argv); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + } + + void test_one() + { + const char* argv[] = { "app-name", "-x", "42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int> args(argc, argv); + + int x{}; + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(42, x); + } + + void test_many() + { + const char* argv[] = { "app-name", "-x", "42", "-y17", "-z", + "--long-X=12", "--long-Y", "18" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int> args(argc, argv); + + int x{}; + int y{}; + bool z{false}; + int X{}; + int Y{}; + + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](int i){ y = i; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](){ z = true; return 0;}), "Help z"); + + args.add('X', "--long-X", + std::function([&](int i){ X = i; return 0;}), "Help X"); + + args.add('Y', "--long-Y", + std::function([&](int i){ Y = i; return 0;}), "Help Y"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(42, x); + uASSERT_EQUAL(17, y); + uASSERT_EQUAL(true, z); + uASSERT_EQUAL(12, X); + uASSERT_EQUAL(18, Y); + } + + void test_exceptional() + { + + { // Missing arg at trailing opt + const char* argv[] = { "app-name", "-x" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + int y{}; + + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](int i){ y = i; return 0;}), "Help y"); + + auto res = args.parse(); + uASSERT_EQUAL(1, res); + } + + { // Missing arg before other opt + const char* argv[] = { "app-name", "-x", "-y" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + int y{}; + + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](int i){ y = i; return 0;}), "Help y"); + + auto res = args.parse(); + uASSERT_EQUAL(1, res); + } + + { // Unknown arg + const char* argv[] = { "app-name", "-T" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + int y{}; + + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](int i){ y = i; return 0;}), "Help y"); + + auto res = args.parse(); + uASSERT_EQUAL(1, res); + } + } + + void test_err_callback() + { + using namespace std::string_literals; + + { // Missing arg at trailing opt + const char* argv[] = { "app-name", "-x" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + bool called{false}; + args.set_err_cb( + [&](arg::error err, std::string_view opt) + { + called = true; + uASSERT_EQUAL(arg::error::missing_arg, err); + uASSERT_EQUAL("-x"s, opt); + }); + auto res = args.parse(); + uASSERT_EQUAL(1, res); + uASSERT(called); + } + + { // Invalid arg format + const char* argv[] = { "app-name", "-x", "abc" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + bool called{false}; + args.set_err_cb( + [&](arg::error err, std::string_view opt) + { + called = true; + uASSERT_EQUAL(arg::error::invalid_arg, err); + uASSERT_EQUAL("abc"s, opt); + }); + auto res = args.parse(); + uASSERT_EQUAL(1, res); + uASSERT(called); + } + + { // Invalid opt + const char* argv[] = { "app-name", "-y" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int> args(argc, argv); + + int x{}; + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + bool called{false}; + args.set_err_cb( + [&](arg::error err, std::string_view opt) + { + called = true; + uASSERT_EQUAL(arg::error::invalid_opt, err); + uASSERT_EQUAL("-y"s, opt); + }); + auto res = args.parse(); + uASSERT_EQUAL(1, res); + uASSERT(called); + } + } + + void test_pos_callback() + { + using namespace std::string_literals; + const char* argv[] = + { "app-name", "foo", "-x", "42", "bar", "-X43", "-Y", "42" }; + int argc = sizeof(argv)/sizeof(*argv); + arg::Parser<int, std::optional<int>> args(argc, argv); + + int x{}; + int X{}; + int Y{}; + args.add('x', "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add('X', "--opt-x", + std::function([&](std::optional<int> i) + { + uASSERT(i.has_value()); + X = *i; + return 0; + }), + "Help X"); + + args.add('Y', "--opt-y", + std::function([&](std::optional<int> i) + { + uASSERT(!i.has_value()); + Y = 1; + return 0; + }), + "Help X"); + + std::vector<std::string> pos; + args.set_pos_cb( + [&](std::string_view sv) + { + pos.push_back(std::string(sv)); + return 0; + }); + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(3u, pos.size()); + uASSERT_EQUAL("foo"s, pos[0]); + uASSERT_EQUAL("bar"s, pos[1]); + uASSERT_EQUAL("42"s, pos[2]); + uASSERT_EQUAL(42, x); + uASSERT_EQUAL(43, X); + uASSERT_EQUAL(1, Y); + } + + void test_grouped() + { + { + const char* argv[] = { "app-name", "-xyz", "42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int> args(argc, argv); + + bool x{false}; + bool y{false}; + int z{}; + args.add('x', "--long-x", + std::function([&](){ x = true; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](){ y = true; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](int i){ z = i; return 0;}), "Help z"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT(x); + uASSERT(y); + uASSERT_EQUAL(42, z); + } + + { + const char* argv[] = { "app-name", "-xyz42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int> args(argc, argv); + + bool x{false}; + bool y{false}; + int z{}; + args.add('x', "--long-x", + std::function([&](){ x = true; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](){ y = true; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](int i){ z = i; return 0;}), "Help z"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT(x); + uASSERT(y); + uASSERT_EQUAL(42, z); + } + + + { + const char* argv[] = { "app-name", "-xyz42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int, std::optional<int>> args(argc, argv); + + bool x{false}; + bool y{false}; + int z{}; + args.add('x', "--long-x", + std::function([&](){ x = true; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](){ y = true; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](std::optional<int> i) + { + uASSERT(i.has_value()); + z = *i; return 0; + }), "Help z"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT(x); + uASSERT(y); + uASSERT_EQUAL(42, z); + } + + { + const char* argv[] = { "app-name", "-xyz" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int, std::optional<int>> args(argc, argv); + + bool x{false}; + bool y{false}; + int z{}; + args.add('x', "--long-x", + std::function([&](){ x = true; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](){ y = true; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](std::optional<int> i) + { + uASSERT(!i.has_value()); + z = 1; return 0; + }), "Help z"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT(x); + uASSERT(y); + uASSERT_EQUAL(1, z); + } + + { + const char* argv[] = { "app-name", "-xyz", "42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<int, std::optional<int>> args(argc, argv); + + bool x{false}; + bool y{false}; + int z{}; + args.add('x', "--long-x", + std::function([&](){ x = true; return 0;}), "Help x"); + + args.add('y', "--long-y", + std::function([&](){ y = true; return 0;}), "Help y"); + + args.add('z', "--long-z", + std::function([&](std::optional<int> i) + { + uASSERT(!i.has_value()); + z = 1; return 0; + }), "Help z"); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT(x); + uASSERT(y); + uASSERT_EQUAL(1, z); + } + } + + void test_nullprogram() + { + using namespace std::string_literals; + // Inspired by https://nullprogram.com/blog/2020/08/01/ + + // + // Short options + // + { + const char* argv[] = { "program", "-a", "-b", "-c" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<char> r; + args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {}); + args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(3u, r.size()); + uASSERT_EQUAL('a', r[0]); + uASSERT_EQUAL('b', r[1]); + uASSERT_EQUAL('c', r[2]); + } + + { + const char* argv[] = { "program", "-abc" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<char> r; + args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {}); + args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(3u, r.size()); + uASSERT_EQUAL('a', r[0]); + uASSERT_EQUAL('b', r[1]); + uASSERT_EQUAL('c', r[2]); + } + + { + const char* argv[] = { "program", "-acb" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<char> r; + args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {}); + args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(3u, r.size()); + uASSERT_EQUAL('a', r[0]); + uASSERT_EQUAL('c', r[1]); + uASSERT_EQUAL('b', r[2]); + } + + { + const char* argv[] = { "program", "-i", "input.txt", "-o", "output.txt" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string> args(argc, argv); + + std::vector<std::string> r; + args.add('i', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + + args.add('o', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("input.txt"s, r[0]); + uASSERT_EQUAL("output.txt"s, r[1]); + } + + { + const char* argv[] = { "program", "-iinput.txt", "-ooutput.txt" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string> args(argc, argv); + + std::vector<std::string> r; + args.add('i', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + args.add('o', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("input.txt"s, r[0]); + uASSERT_EQUAL("output.txt"s, r[1]); + } + + { + const char* argv[] = { "program", "-abco", "output.txt" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.add('c', {}, std::function([&](){ r.push_back("c"); return 0;}), {}); + args.add('o', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("a"s, r[0]); + uASSERT_EQUAL("b"s, r[1]); + uASSERT_EQUAL("c"s, r[2]); + uASSERT_EQUAL("output.txt"s, r[3]); + } + + { + const char* argv[] = { "program", "-abcooutput.txt" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.add('c', {}, std::function([&](){ r.push_back("c"); return 0;}), {}); + args.add('o', {}, + std::function([&](std::string input) + { + r.push_back(input); + return 0; + }), {}); + + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("a"s, r[0]); + uASSERT_EQUAL("b"s, r[1]); + uASSERT_EQUAL("c"s, r[2]); + uASSERT_EQUAL("output.txt"s, r[3]); + } + + { + // Optional omitted + const char* argv[] = { "program", "-c" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add('c', {}, + std::function([&](std::optional<std::string> c) + { + uASSERT(!c.has_value()); + r.push_back("c"); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(1u, r.size()); + uASSERT_EQUAL("c"s, r[0]); + } + + { + // Optional provided + const char* argv[] = { "program", "-cblue" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add('c', {}, + std::function([&](std::optional<std::string> c) + { + uASSERT(c.has_value()); + r.push_back(*c); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(1u, r.size()); + uASSERT_EQUAL("blue"s, r[0]); + } + + { + // Optional omitted (blue is a new argument) + const char* argv[] = { "program", "-c", "blue" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add('c', {}, + std::function([&](std::optional<std::string> c) + { + uASSERT(!c.has_value()); + r.push_back("c"); + return 0; + }), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("c"s, r[0]); + uASSERT_EQUAL("blue"s, r[1]); + } + + { + // Two seperate flags + const char* argv[] = { "program", "-c", "-x" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add('x', {}, std::function([&](){ r.push_back("x"); return 0;}), {}); + args.add('c', {}, + std::function([&](std::optional<std::string> c) + { + uASSERT(!c.has_value()); + r.push_back("c"); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("c"s, r[0]); + uASSERT_EQUAL("x"s, r[1]); + } + + { + // -c with argument "-x" + const char* argv[] = { "program", "-c-x" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add('x', {}, std::function([&](){ r.push_back("x"); return 0;}), {}); + args.add('c', {}, + std::function([&](std::optional<std::string> c) + { + uASSERT(c.has_value()); + r.push_back(*c); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(1u, r.size()); + uASSERT_EQUAL("-x"s, r[0]); + } + + { + const char* argv[] = { "program", "-a", "-b", "foo", "bar" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("a"s, r[0]); + uASSERT_EQUAL("b"s, r[1]); + uASSERT_EQUAL("foo"s, r[2]); + uASSERT_EQUAL("bar"s, r[3]); + } + + { + const char* argv[] = { "program", "-b", "-a", "foo", "bar" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("b"s, r[0]); + uASSERT_EQUAL("a"s, r[1]); + uASSERT_EQUAL("foo"s, r[2]); + uASSERT_EQUAL("bar"s, r[3]); + } + + { + const char* argv[] = { "program", "-a", "foo", "-b", "bar" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("a"s, r[0]); + uASSERT_EQUAL("foo"s, r[1]); + uASSERT_EQUAL("b"s, r[2]); + uASSERT_EQUAL("bar"s, r[3]); + } + + { + const char* argv[] = { "program", "foo", "-a", "-b", "bar" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("foo"s, r[0]); + uASSERT_EQUAL("a"s, r[1]); + uASSERT_EQUAL("b"s, r[2]); + uASSERT_EQUAL("bar"s, r[3]); + } + + { + const char* argv[] = { "program", "foo", "bar", "-a", "-b" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(4u, r.size()); + uASSERT_EQUAL("foo"s, r[0]); + uASSERT_EQUAL("bar"s, r[1]); + uASSERT_EQUAL("a"s, r[2]); + uASSERT_EQUAL("b"s, r[3]); + } + + { + const char* argv[] = { "program", "-a", "-b", "--", "-x", "foo", "bar" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {}); + args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {}); + args.set_pos_cb(std::function([&](std::string_view pos) + { + r.push_back(std::string(pos)); + return 0; + })); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(5u, r.size()); + uASSERT_EQUAL("a"s, r[0]); + uASSERT_EQUAL("b"s, r[1]); + uASSERT_EQUAL("-x"s, r[2]); + uASSERT_EQUAL("foo"s, r[3]); + uASSERT_EQUAL("bar"s, r[4]); + } + + // + // Long options + // + { + const char* argv[] = { "program", "--sort" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--sort", + std::function([&](){ r.push_back("sort"); return 0;}), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(1u, r.size()); + uASSERT_EQUAL("sort"s, r[0]); + } + + { + const char* argv[] = { "program", "--no-sort" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--no-sort", + std::function([&](){ r.push_back("no-sort"); return 0;}), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(1u, r.size()); + uASSERT_EQUAL("no-sort"s, r[0]); + } + + { + const char* argv[] = + { "program", "--output", "output.txt", "--block-size", "1024" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string, int> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--output", + std::function([&](std::string output) + { + r.push_back(output); + return 0; + }), {}); + args.add({}, "--block-size", + std::function([&](int block_size) + { + r.push_back("["s + std::to_string(block_size) + "]"s); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("output.txt"s, r[0]); + uASSERT_EQUAL("[1024]"s, r[1]); + } + + { + const char* argv[] = + { "program", "--output=output.txt", "--block-size=1024" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::string, int> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--output", + std::function([&](std::string output) + { + r.push_back(output); + return 0; + }), {}); + args.add({}, "--block-size", + std::function([&](int block_size) + { + r.push_back("["s + std::to_string(block_size) + "]"s); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("output.txt"s, r[0]); + uASSERT_EQUAL("[1024]"s, r[1]); + } + + { + const char* argv[] = + { "program", "--color", "--reverse" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--color", + std::function([&](std::optional<std::string> color) + { + uASSERT(!color.has_value()); + r.push_back("color"); + return 0; + }), {}); + args.add({}, "--reverse", + std::function([&]() + { + r.push_back("reverse"); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("color"s, r[0]); + uASSERT_EQUAL("reverse"s, r[1]); + } + + { + const char* argv[] = + { "program", "--color=never", "--reverse" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser<std::optional<std::string>> args(argc, argv); + + std::vector<std::string> r; + args.add({}, "--color", + std::function([&](std::optional<std::string> color) + { + uASSERT(color.has_value()); + r.push_back(*color); + return 0; + }), {}); + args.add({}, "--reverse", + std::function([&]() + { + r.push_back("reverse"); + return 0; + }), {}); + + auto res = args.parse(); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(2u, r.size()); + uASSERT_EQUAL("never"s, r[0]); + uASSERT_EQUAL("reverse"s, r[1]); + } + + } +}; + +// Registers the fixture into the 'registry' +static ArgParserTest test; diff --git a/test/ctor.cc b/test/ctor.cc index b7bcc6d..768708c 100644 --- a/test/ctor.cc +++ b/test/ctor.cc @@ -12,6 +12,23 @@ 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\"", + "-fexceptions", + }, + }, + }, + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, .target = "argsplit_test", .sources = { "argsplit_test.cc", @@ -23,6 +40,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) "-std=c++20", "-O3", "-Wall", "-Werror", "-I../src", "-Iuunit", "-DOUTPUT=\"argsplit\"", + "-fexceptions", }, }, }, @@ -40,6 +58,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) "-std=c++20", "-O3", "-Wall", "-Werror", "-I../src", "-Iuunit", "-DOUTPUT=\"pointerlist\"", + "-fexceptions", }, }, }, @@ -58,7 +77,9 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) .cxxflags = { "-std=c++20", "-O3", "-Wall", "-Werror", "-I../src", "-Iuunit", + "-I../json/include", "-DOUTPUT=\"deps\"", + "-fexceptions", }, }, }, @@ -72,6 +93,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) .flags = { .cxxflags = { "-std=c++20", "-O3", "-Wall", "-Werror", + "-fexceptions", }, }, }, @@ -92,6 +114,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) "-std=c++20", "-O3", "-Wall", "-Werror", "-I../src", "-Iuunit", "-DOUTPUT=\"execute\"", + "-fexceptions", }, .ldflags = { "-pthread" }, }, @@ -110,6 +133,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) "-std=c++20", "-O3", "-Wall", "-Werror", "-I../src", "-Iuunit", "-DOUTPUT=\"tasks\"", + "-fexceptions", }, .ldflags = { "-pthread" }, }, @@ -128,6 +152,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) "-std=c++20", "-O3", "-Wall", "-Werror", "-I../src", "-Iuunit", "-DOUTPUT=\"cycle\"", + "-fexceptions", }, .ldflags = { "-pthread" }, }, @@ -146,6 +171,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) "-std=c++20", "-O3", "-Wall", "-Werror", "-I../src", "-Iuunit", "-DOUTPUT=\"source_type\"", + "-fexceptions", }, .ldflags = { "-pthread" }, }, @@ -166,6 +192,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) "-std=c++20", "-O3", "-Wall", "-Werror", "-I../src", "-Iuunit", "-DOUTPUT=\"tools\"", + "-fexceptions", }, }, }, @@ -195,6 +222,8 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) .cxxflags = { "-std=c++20", "-O3", "-Wall", "-Werror", "-I../src", + "-I../json/include", + "-fexceptions", }, .ldflags = { "-pthread" }, }, diff --git a/test/execute_test.cc b/test/execute_test.cc index 722b6ea..3cdb309 100644 --- a/test/execute_test.cc +++ b/test/execute_test.cc @@ -23,6 +23,13 @@ public: void return_value() { +#if defined(_WIN32) + constexpr int segfault_return_value = 3; + constexpr int exception_return_value = 0xC0000409; +#else + constexpr int segfault_return_value = 11; + constexpr int exception_return_value = 6; +#endif ctor::settings s; auto cur_path = std::filesystem::path(paths::argv_0).parent_path(); std::vector<std::string> paths{{cur_path.string()}}; @@ -31,16 +38,21 @@ public: auto value = execute(s, cmd, {"retval", "0"}, {}, false); uASSERT_EQUAL(0, value); + value = execute(s, cmd, {"retval", "1"}, {}, false); uASSERT_EQUAL(1, value); + value = execute(s, "no-such-binary", {}, {}, false); uASSERT_EQUAL(1, value); + value = execute(s, cmd, {"segfault"}, {}, false); - uASSERT_EQUAL(11, value); + uASSERT_EQUAL(segfault_return_value, value); + value = execute(s, cmd, {"throw"}, {}, false); - uASSERT_EQUAL(6, value); + uASSERT_EQUAL(exception_return_value, value); + value = execute(s, cmd, {"abort"}, {}, false); - uASSERT_EQUAL(6, value); + uASSERT_EQUAL(exception_return_value, value); } void env() @@ -53,7 +65,7 @@ public: auto cmd = locate("testprog", paths); uASSERT(!cmd.empty()); - tmp_file tmp; + TmpFile tmp; std::map<std::string, std::string> env; @@ -83,11 +95,13 @@ public: chk = std::find(vars.begin(), vars.end(), "bar=42"s); uASSERT(chk != vars.end()); - // Check the one that should have overwritten the existing one (probably LANG=en_US.UTF-8 or something) + // Check the one that should have overwritten the existing one (probably + // LANG=en_US.UTF-8 or something) chk = std::find(vars.begin(), vars.end(), "LANG=foo"s); uASSERT(chk != vars.end()); - // Check that other vars are also there (ie. the env wasn't cleared on entry) + // Check that other vars are also there (ie. the env wasn't cleared on + // entry) uASSERT(vars.size() > 3); } }; diff --git a/test/source_type_test.cc b/test/source_type_test.cc index 288f1e5..657260e 100644 --- a/test/source_type_test.cc +++ b/test/source_type_test.cc @@ -26,6 +26,14 @@ std::ostream& operator<<(std::ostream& stream, const ctor::language& lang) return stream; } +const ctor::configuration& ctor::get_configuration() +{ + static ctor::configuration cfg{}; + cfg.build_toolchain = ctor::toolchain::gcc; + cfg.build_arch = ctor::arch::unix; + return cfg; +} + class TestableTaskCC : public TaskCC { diff --git a/test/suite/test.sh b/test/suite/test.sh index 97d2551..ec603f5 100755 --- a/test/suite/test.sh +++ b/test/suite/test.sh @@ -1,4 +1,6 @@ #!/bin/bash +#set -x + : ${CXX:=g++} : ${CTORDIR:=../../build} : ${BUILDDIR:=build} @@ -70,8 +72,6 @@ ctor -v # Object file should have been recompiled MOD2=`stat $STAT_FORMAT ${BUILDDIR}/hello-hello_cc.o` -echo $MOD1 -echo $MOD2 [[ $MOD1 == $MOD2 ]] && fail ${LINENO} # Replacve -DFOO with -DBAR in foo external.cxxflags diff --git a/test/testprog.cc b/test/testprog.cc index 93edc3f..18c2c74 100644 --- a/test/testprog.cc +++ b/test/testprog.cc @@ -5,6 +5,14 @@ extern char **environ; +#if defined(_WIN32) +#define WINDOWS_LEAN_AND_MEAN +#include <windows.h> +#undef max +#else +extern char **environ; // see 'man environ' +#endif + int main(int argc, const char* argv[]) { if(argc < 2) @@ -20,11 +28,41 @@ int main(int argc, const char* argv[]) { return 0; } + std::ofstream ostrm(argv[2], std::ios::binary); + if(ostrm.bad()) + { + std::cout << "Error: Could not write to " << argv[2] << "\n"; + } +#if defined(_WIN32) + auto env_strings = GetEnvironmentStrings(); + const char* ptr = env_strings; + std::string env; + while(true) + { + if(*ptr == '\0') + { + if(env.empty()) + { + // no more + break; + } + ostrm << env << "\n"; + env.clear(); + ++ptr; + continue; + } + + env += *ptr; + ++ptr; + } + FreeEnvironmentStrings(env_strings); +#else for(auto current = environ; *current; ++current) { ostrm << (*current) << "\n"; } +#endif } if(cmd == "retval") diff --git a/test/tmpfile.h b/test/tmpfile.h index 5d114d0..5887e36 100644 --- a/test/tmpfile.h +++ b/test/tmpfile.h @@ -3,39 +3,34 @@ // 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); + 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++); + // TODO: Use std::fstream.open() with openmode noreplace when using c++23 +#if defined(_WIN32) + fopen_s(&fp, filename.data(), "wx"); #else - char templ[] = "/tmp/ctor_tmp_file-XXXXXX"; // buffer for filename - fd = mkstemp(templ); + fp = std::fopen(filename.data(), "wx"); #endif - filename = templ; - auto sz = write(fd, data.data(), data.size()); - (void)sz; - close(fd); + } + std::fwrite(data.data(), data.size(), 1, fp); + std::fclose(fp); } - ~tmp_file() + ~TmpFile() { - unlink(filename.data()); + std::filesystem::remove(filename); } const std::string& get() const diff --git a/test/tools_test.cc b/test/tools_test.cc index 5ae04c3..15270b3 100644 --- a/test/tools_test.cc +++ b/test/tools_test.cc @@ -22,6 +22,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::toolchain& toolchain) case ctor::toolchain::clang: stream << "ctor::toolchain::clang"; break; + case ctor::toolchain::msvc: + stream << "ctor::toolchain::msvc"; + break; } return stream; } |