summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--Jenkinsfile118
-rwxr-xr-xbootstrap.sh7
-rw-r--r--ctor.cc7
-rw-r--r--src/argparser.h477
-rw-r--r--src/bootstrap.cc71
-rw-r--r--src/build.cc89
-rw-r--r--src/build.h5
-rw-r--r--src/configure.cc352
-rw-r--r--src/configure.h4
-rw-r--r--src/ctor.h177
-rw-r--r--src/deps.cc120
-rw-r--r--src/deps.h12
-rw-r--r--src/execute.cc76
-rw-r--r--src/execute.h9
-rw-r--r--src/externals_manual.cc8
m---------src/getoptpp0
-rw-r--r--src/libctor.cc217
-rw-r--r--src/pointerlist.cc123
-rw-r--r--src/pointerlist.h74
-rw-r--r--src/rebuild.cc184
-rw-r--r--src/rebuild.h18
-rw-r--r--src/task.cc38
-rw-r--r--src/task.h8
-rw-r--r--src/task_ar.cc49
-rw-r--r--src/task_ar.h1
-rw-r--r--src/task_cc.cc86
-rw-r--r--src/task_cc.h1
-rw-r--r--src/task_fn.cc207
-rw-r--r--src/task_fn.h5
-rw-r--r--src/task_ld.cc58
-rw-r--r--src/task_ld.h6
-rw-r--r--src/task_so.cc53
-rw-r--r--src/task_so.h1
-rw-r--r--src/tasks.cc53
-rw-r--r--src/tasks.h6
-rw-r--r--src/tools.cc311
-rw-r--r--src/tools.h29
-rw-r--r--src/unittest.cc2
-rw-r--r--src/util.cc197
-rw-r--r--src/util.h16
-rw-r--r--test/argparser_test.cc1019
-rw-r--r--test/argsplit_test.cc203
-rw-r--r--test/ctor.cc119
-rw-r--r--test/cycle_test.cc87
-rw-r--r--test/deps_test.cc97
-rw-r--r--test/deps_test_data/empty.d0
-rw-r--r--test/deps_test_data/missing_colon.d1
-rw-r--r--test/deps_test_data/multiline.d4
-rw-r--r--test/deps_test_data/no_deps.d1
-rw-r--r--test/deps_test_data/no_newline.d1
-rw-r--r--test/deps_test_data/spaces.d1
-rw-r--r--test/deps_test_data/trivial.d1
-rw-r--r--test/execute_test.cc22
-rw-r--r--test/generated_sources_test.cc135
-rw-r--r--test/pointerlist_test.cc320
-rw-r--r--test/suite/ctor_files/ctor.cc.generated87
-rw-r--r--test/suite/ctor_files/ctor.cc.generated286
-rw-r--r--test/suite/test.cc370
-rwxr-xr-xtest/suite/test.sh112
-rw-r--r--test/tasks_test.cc130
-rw-r--r--test/testprog.cc8
-rw-r--r--test/tmpfile.h44
-rw-r--r--test/tools_test.cc28
m---------test/uunit0
65 files changed, 5141 insertions, 1013 deletions
diff --git a/.gitmodules b/.gitmodules
index c765825..c12c38f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
-[submodule "getoptpp"]
- path = src/getoptpp
- url = git://git.drumgizmo.org/getoptpp.git
[submodule "test/uunit"]
path = test/uunit
url = git://git.drumgizmo.org/uunit.git
diff --git a/Jenkinsfile b/Jenkinsfile
index c95b0eb..aad3052 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,56 +1,74 @@
pipeline {
- agent { label 'c++20' }
-
+ agent any
stages {
- stage('Clean') {
- steps {
- echo 'Cleaning workspace ...'
- sh 'rm -Rf build*'
+ stage('all targets')
+ {
+ parallel {
+ ////////////////////////////////////////////////////
+ stage('MacOSX clang') {
+ agent { label 'macos' }
+ steps {
+ echo 'Cleaning workspace ...'
+ sh 'git clean -d -x -f'
+ echo 'Building (clang) ...'
+ sh 'CXXFLAGS=-Werror CXX=/usr/local/opt/llvm/bin/clang++ LDFLAGS="-L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib/unwind -lunwind" ./bootstrap.sh'
+ echo 'Testing (clang) ...'
+ sh './ctor check'
+ echo 'Testing suite (clang) ...'
+ sh '(cd test/suite; /usr/local/opt/llvm/bin/clang++ -std=c++20 test.cc -L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib/unwind -lunwind -o test && CTORDIR=../../build CXXFLAGS=-Werror CXX=/usr/local/opt/llvm/bin/clang++ LDFLAGS="-L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib/unwind -lunwind" ./test)'
+ }
+ post {
+ always {
+ xunit(thresholds: [ skipped(failureThreshold: '0'),
+ failed(failureThreshold: '0') ],
+ tools: [ CppUnit(pattern: 'build/test/*.xml') ])
+ }
+ }
+ }
+ ////////////////////////////////////////////////////
+ stage('Linux gcc') {
+ agent { label 'linux && gcc && c++20' }
+ steps {
+ echo 'Cleaning workspace ...'
+ sh 'git clean -d -x -f'
+ echo 'Building (gcc) ...'
+ sh 'CXXFLAGS=-Werror CXX=g++ ./bootstrap.sh'
+ echo 'Testing (gcc) ...'
+ sh './ctor check'
+ echo 'Testing suite (gcc) ...'
+ sh '(cd test/suite; g++ -std=c++20 test.cc -o test && CTORDIR=../../build CXXFLAGS=-Werror CXX=g++ ./test)'
+ }
+ post {
+ always {
+ xunit(thresholds: [ skipped(failureThreshold: '0'),
+ failed(failureThreshold: '0') ],
+ tools: [ CppUnit(pattern: 'build/test/*.xml') ])
+ }
+ }
+ }
+ ////////////////////////////////////////////////////
+ stage('Linux clang') {
+ agent { label 'linux && clang && c++20' }
+ steps {
+ echo 'Cleaning workspace ...'
+ sh 'git clean -d -x -f'
+ echo 'Building (clang) ...'
+ sh 'CXXFLAGS=-Werror CXX=clang++ ./bootstrap.sh'
+ echo 'Testing (clang) ...'
+ sh './ctor check'
+ echo 'Testing suite (clang) ...'
+ sh '(cd test/suite; clang++ -std=c++20 test.cc -o test && CTORDIR=../../build CXXFLAGS=-Werror CXX=clang++ ./test)'
+ }
+ post {
+ always {
+ xunit(thresholds: [ skipped(failureThreshold: '0'),
+ failed(failureThreshold: '0') ],
+ tools: [ CppUnit(pattern: 'build/test/*.xml') ])
+ }
+ }
+ }
+ ////////////////////////////////////////////////////
}
}
- stage('Build-gcc') {
- steps {
- echo 'Building (gcc) ...'
- sh 'BUILDDIR=build-gcc CXX=g++ ./bootstrap.sh'
- }
- }
- stage('Test-gcc') {
- steps {
- echo 'Testing (gcc) ...'
- sh './ctor check'
- }
- }
- stage('Test-suite-gcc') {
- steps {
- echo 'Testing suite (gcc) ...'
- sh '(cd test/suite; CTORDIR=../../build-gcc CXX=g++ ./test.sh)'
- }
- }
- stage('Build-clang') {
- steps {
- echo 'Building (clang) ...'
- sh 'BUILDDIR=build-clang CXX=clang++ ./bootstrap.sh'
- }
- }
- stage('Test-clang') {
- steps {
- echo 'Testing (clang) ...'
- sh './ctor check'
- }
- }
- stage('Test-suite-clang') {
- steps {
- echo 'Testing suite (clang) ...'
- sh '(cd test/suite; CTORDIR=../../build-clang CXX=clang++ ./test.sh)'
- }
- }
- }
-
- post {
- always {
- xunit(thresholds: [ skipped(failureThreshold: '0'),
- failed(failureThreshold: '0') ],
- tools: [ CppUnit(pattern: 'build-*/test/*.xml') ])
- }
}
}
diff --git a/bootstrap.sh b/bootstrap.sh
index 9e8cdca..4dd57ae 100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -1,13 +1,12 @@
#!/bin/sh
set -e
-: ${CXX:=g++}
+: ${CXX:=c++}
: ${BUILDDIR:=build}
-CXX=$(which $CXX)
echo "Bootstrapping..."
-$CXX -std=c++20 -Wall -O3 -Isrc -pthread src/bootstrap.cc ctor.cc test/ctor.cc -o ctor
+$CXX $LDFLAGS $CXXFLAGS -std=c++20 -Wall -O3 -Isrc -pthread src/bootstrap.cc ctor.cc -o ctor
./ctor
-$CXX -std=c++20 -Wall -O3 -Isrc -pthread ctor.cc test/ctor.cc -L$BUILDDIR -lctor -o ctor
+$CXX $LDFLAGS $CXXFLAGS -std=c++20 -Wall -O3 -Isrc -pthread ctor.cc test/ctor.cc -L$BUILDDIR -lctor -o ctor
./ctor configure --ctor-includedir=src --ctor-libdir=$BUILDDIR --build-dir=$BUILDDIR
echo "Done. Now run ./ctor to (re)build."
diff --git a/ctor.cc b/ctor.cc
index d0d53d8..f3b8588 100644
--- a/ctor.cc
+++ b/ctor.cc
@@ -15,9 +15,11 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings)
.sources = {
"src/build.cc",
"src/configure.cc",
+ "src/deps.cc",
"src/execute.cc",
"src/externals_manual.cc",
"src/libctor.cc",
+ "src/pointerlist.cc",
"src/rebuild.cc",
"src/task.cc",
"src/task_ar.cc",
@@ -36,7 +38,10 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings)
"-O3",
"-g",
"-Wall",
- "-Werror",
+ "-Wextra",
+ "-Wshadow",
+ "-Wconversion",
+// "-Wnrvo",
"-Isrc",
},
},
diff --git a/src/argparser.h b/src/argparser.h
new file mode 100644
index 0000000..c5337e0
--- /dev/null
+++ b/src/argparser.h
@@ -0,0 +1,477 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#pragma once
+
+#include <functional>
+#include <variant>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <stdexcept>
+#include <iostream>
+#include <limits>
+
+namespace arg
+{
+struct noarg {};
+template<typename T>
+struct Opt
+{
+ char shortopt;
+ std::string longopt;
+ std::function<int(T)> cb;
+ std::string help;
+ T t{};
+};
+
+template<>
+struct Opt<noarg>
+{
+ char shortopt;
+ std::string longopt;
+ std::function<int()> cb;
+ std::string help;
+ noarg t{};
+};
+
+template<typename Callable, typename... Args>
+auto call_if(Callable cb, Args... args)
+{
+ using Ret = std::invoke_result_t<decltype(cb), Args&&...>;
+ if constexpr (std::is_same_v<Ret, void>)
+ {
+ if(cb)
+ {
+ return cb(std::forward<Args>(args)...);
+ }
+ }
+ else
+ {
+ if(cb)
+ {
+ return cb(std::forward<Args>(args)...);
+ }
+ return Ret{};
+ }
+}
+
+enum class error
+{
+ missing_arg,
+ invalid_arg,
+ invalid_opt,
+};
+
+template<typename... Ts>
+class Parser
+{
+public:
+ struct missing_arg{};
+
+ Parser(int argc_, const char* const* argv_)
+ : argc(argc_)
+ , argv(argv_)
+ {
+ }
+
+ int parse() const
+ {
+ bool demarcate{false};
+ for(int i = 1; i < argc; ++i) // skip argv[0] which is program name
+ {
+ std::string_view arg{argv[i]};
+ if(arg.size() == 0)
+ {
+ // Empty arg - This shouldn't happen
+ continue;
+ }
+
+ if(arg[0] != '-' || demarcate) // positional arg
+ {
+ auto res = call_if(pos_cb, arg);
+ if(res != 0)
+ {
+ return res;
+ }
+ continue;
+ }
+
+ if(arg == "--")
+ {
+ demarcate = true;
+ continue;
+ }
+
+ bool was_handled{false};
+ enum class state { handled, unhandled };
+
+ if(arg.size() > 1 && arg[0] == '-' && arg[1] == '-') // long
+ {
+ for(const auto& option : options)
+ {
+ auto ret =
+ std::visit([&](auto&& opt) -> std::pair<int, state>
+ {
+ if(opt.longopt != arg &&
+ !arg.starts_with(opt.longopt+'='))
+ {
+ return {0, state::unhandled};
+ }
+ try
+ {
+ using T = std::decay_t<decltype(opt)>;
+ if constexpr (std::is_same_v<T, Opt<noarg>>)
+ {
+ return {opt.cb(), state::handled};
+ }
+ else
+ {
+ return {opt.cb(convert(i, opt.t)),
+ state::handled};
+ }
+ }
+ catch(std::invalid_argument&)
+ {
+ call_if(err_cb, error::invalid_arg, argv[i]);
+ return {1, state::handled};
+ }
+ catch(missing_arg&)
+ {
+ call_if(err_cb, error::missing_arg, argv[i]);
+ return {1, state::handled};
+ }
+ }, option);
+ if(ret.second == state::handled && ret.first != 0)
+ {
+ return ret.first;
+ }
+ was_handled |= ret.second == state::handled;
+ if(was_handled)
+ {
+ break;
+ }
+ }
+ }
+ else
+ if(arg.size() > 1 && arg[0] == '-') // short
+ {
+ for(auto index = 1u; index < arg.size(); ++index)
+ {
+ was_handled = false;
+ for(const auto& option : options)
+ {
+ auto ret =
+ std::visit([&](auto&& opt) -> std::pair<int, state>
+ {
+ char c = arg[index];
+ if(opt.shortopt != c)
+ {
+ return {0, state::unhandled};
+ }
+ try
+ {
+ using T = std::decay_t<decltype(opt)>;
+ if constexpr (std::is_same_v<T, Opt<noarg>>)
+ {
+ return {opt.cb(), state::handled};
+ }
+ else
+ {
+ // Note: the rest of arg is converted to opt
+ auto idx = index;
+ // set index out of range all was eaten as arg
+ index = std::numeric_limits<int>::max();
+ return {opt.cb(convert_short(&arg[idx],
+ i, opt.t)),
+ state::handled};
+ }
+ }
+ catch(std::invalid_argument&)
+ {
+ call_if(err_cb, error::invalid_arg, argv[i]);
+ return {1, state::handled};
+ }
+ catch(missing_arg&)
+ {
+ call_if(err_cb, error::missing_arg, argv[i]);
+ return {1, state::handled};
+ }
+ }, option);
+ if(ret.second == state::handled && ret.first != 0)
+ {
+ return ret.first;
+ }
+ was_handled |= ret.second == state::handled;
+ if(was_handled)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ if(!was_handled)
+ {
+ call_if(err_cb, error::invalid_opt, arg);
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ template<typename T>
+ void add(char shortopt,
+ const std::string& longopt,
+ std::function<int(T)> cb,
+ const std::string& help)
+ {
+ options.emplace_back(Opt<T>{shortopt, longopt, cb, help});
+ }
+
+ void add(char shortopt,
+ const std::string& longopt,
+ std::function<int()> cb,
+ const std::string& help)
+ {
+ options.emplace_back(Opt<noarg>{shortopt, longopt, cb, help});
+ }
+
+ void set_pos_cb(std::function<int(std::string_view)> cb)
+ {
+ pos_cb = cb;
+ }
+
+ void set_err_cb(std::function<void(error, std::string_view)> cb)
+ {
+ err_cb = cb;
+ }
+
+ std::string prog_name() const
+ {
+ if(argc < 1)
+ {
+ return {};
+ }
+ return argv[0];
+ }
+
+ void help() const
+ {
+ constexpr std::size_t width{26};
+ constexpr std::size_t column_width{80};
+
+ for(const auto& option : options)
+ {
+ std::visit(
+ [&](auto&& opt)
+ {
+ std::string _args;
+ using T = std::decay_t<decltype(opt)>;
+ if constexpr (std::is_same_v<T, Opt<noarg>>)
+ {
+ }
+ else if constexpr (std::is_same_v<T, Opt<int>>)
+ {
+ _args = "<int>";
+ }
+ else if constexpr (std::is_same_v<T, Opt<std::optional<int>>>)
+ {
+ _args = "[int]";
+ }
+ else if constexpr (std::is_same_v<T, Opt<std::string>>)
+ {
+ _args = "<str>";
+ }
+ else if constexpr (std::is_same_v<T, Opt<std::optional<std::string>>>)
+ {
+ _args = "[str]";
+ }
+ else if constexpr (std::is_same_v<T, Opt<double>>)
+ {
+ _args = "<real>";
+ }
+ else if constexpr (std::is_same_v<T, Opt<std::optional<double>>>)
+ {
+ _args = "[real]";
+ }
+ else
+ {
+ static_assert(std::is_same_v<T, void>, "missing");
+ }
+
+ std::string option_str;
+ if(opt.shortopt != '\0' && !opt.longopt.empty())
+ {
+ option_str = " -" + std::string(1, opt.shortopt) + ", " +
+ opt.longopt + " " + _args;
+ }
+ else if(opt.shortopt != '\0')
+ {
+ option_str = " -" + std::string(1, opt.shortopt) + _args;
+ }
+ else if(!opt.longopt.empty())
+ {
+ option_str = " " + std::string(opt.longopt) + " " + _args;
+ }
+
+ std::string padding;
+ if(option_str.size() < width)
+ {
+ padding.append(width - option_str.size(), ' ');
+ }
+ else
+ {
+ padding = "\n";
+ padding.append(width, ' ');
+ }
+
+ std::cout << option_str << padding;
+
+ auto i = width;
+ for(auto c : opt.help)
+ {
+ if((c == '\n') || (i > column_width && (c == ' ' || c == '\t')))
+ {
+ std::string _padding(width, ' ');
+ std::cout << '\n' << _padding;
+ i = width;
+ continue;
+ }
+ std::cout << c;
+ ++i;
+ }
+ std::cout << '\n';
+ }, option);
+ }
+ }
+
+private:
+ template<typename T>
+ T convert(int& i, T) const
+ {
+ auto opt = convert(i, std::optional<T>{});
+ if(!opt)
+ {
+ throw missing_arg{};
+ }
+ return *opt;
+ }
+
+ template<typename T>
+ std::optional<T> convert(int& i, std::optional<T>) const
+ {
+ std::string arg;
+ bool has_arg{false};
+ std::string opt = argv[i];
+ if(opt.starts_with("--"))
+ {
+ // long opt
+ auto equals_pos = opt.find('=');
+ if(equals_pos != std::string::npos)
+ {
+ arg = opt.substr(equals_pos + 1);
+ has_arg = true;
+ }
+ else if(i+1 < argc)
+ {
+ arg = argv[i+1];
+ has_arg = !arg.starts_with("-");
+ if(has_arg)
+ {
+ ++i;
+ }
+ }
+ }
+
+ if(!has_arg)
+ {
+ return {};
+ }
+
+ if constexpr (std::is_same_v<T, int>)
+ {
+ return std::stoi(arg);
+ }
+ else if constexpr (std::is_same_v<T, double>)
+ {
+ return std::stod(arg);
+ }
+ else if constexpr (std::is_same_v<T, std::string>)
+ {
+ return arg;
+ }
+ else
+ {
+ static_assert(std::is_same_v<T, void>, "missing");
+ }
+ return {};
+ }
+
+ template<typename T>
+ T convert_short(const char* arg_, int& i, T) const
+ {
+ auto opt = convert_short(arg_, i, std::optional<T>{}, false);
+ if(!opt)
+ {
+ throw missing_arg{};
+ }
+ return *opt;
+ }
+
+ template<typename T>
+ std::optional<T> convert_short(const char* arg_, int& i,
+ std::optional<T>, bool optional = true) const
+ {
+ std::string arg;
+ bool has_arg{false};
+ std::string opt = arg_;
+ if(opt.length() > 1)
+ {
+ // arg in same token
+ arg = opt.substr(1);
+ has_arg = true;
+ }
+ else if(!optional && i+1 < argc)
+ {
+ arg = argv[i+1];
+ has_arg = true;//!arg.starts_with("-");
+ if(has_arg)
+ {
+ ++i;
+ }
+ }
+
+ if(!has_arg)
+ {
+ return {};
+ }
+
+ if constexpr (std::is_same_v<T, int>)
+ {
+ return std::stoi(arg);
+ }
+ else if constexpr (std::is_same_v<T, double>)
+ {
+ return std::stod(arg);
+ }
+ else if constexpr (std::is_same_v<T, std::string>)
+ {
+ return arg;
+ }
+ else
+ {
+ static_assert(std::is_same_v<T, void>, "missing");
+ }
+ return {};
+ }
+
+ using Opts = std::variant<Opt<noarg>, Opt<Ts>...>;
+ std::vector<Opts> options;
+ std::function<int(std::string_view)> pos_cb;
+ int argc;
+ const char* const* argv;
+ std::function<void(error, std::string_view)> err_cb;
+};
+
+} // arg::
diff --git a/src/bootstrap.cc b/src/bootstrap.cc
index 55bd3e0..836504e 100644
--- a/src/bootstrap.cc
+++ b/src/bootstrap.cc
@@ -4,6 +4,7 @@
#include <iostream>
#include <array>
#include <cstdlib>
+#include <span>
#define BOOTSTRAP
@@ -18,9 +19,10 @@
#include "tasks.cc"
#include "build.cc"
#include "tools.cc"
+#include "pointerlist.cc"
-std::filesystem::path configurationFile("configuration.cc");
-std::filesystem::path configHeaderFile("config.h");
+const std::filesystem::path configurationFile("configuration.cc");
+const std::filesystem::path configHeaderFile("config.h");
const ctor::configuration& ctor::get_configuration()
{
@@ -40,46 +42,75 @@ bool ctor::configuration::has(const std::string& key) const
return false;
}
-const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const
+std::string ctor::configuration::get(const std::string& key, const std::string& default_value) const
{
- if(key == ctor::cfg::build_cxx && std::getenv("CXX"))
+ static auto paths = get_paths();
+ auto cxx_env = std::getenv("CXX");
+ if(key == ctor::cfg::build_cxx && cxx_env)
{
- static std::string s = std::getenv("CXX");
- return s;
+ static auto cxx_prog = locate(cxx_env, paths);
+ return cxx_prog;
}
- if(key == ctor::cfg::build_cc && std::getenv("CC"))
+ auto cc_env = std::getenv("CC");
+ if(key == ctor::cfg::build_cc && cc_env)
{
- static std::string s = std::getenv("CC");
- return s;
+ static auto cc_prog = locate(cc_env, paths);
+ return cc_prog;
}
- if(key == ctor::cfg::build_ld && std::getenv("LD"))
+ auto ld_env = std::getenv("LD");
+ if(key == ctor::cfg::build_ld && ld_env)
{
- static std::string s = std::getenv("LD");
- return s;
+ static auto ld_prog = locate(ld_env, paths);
+ return ld_prog;
}
- if(key == ctor::cfg::build_ar && std::getenv("AR"))
+ auto ar_env = std::getenv("AR");
+ if(key == ctor::cfg::build_ar && ar_env)
{
- static std::string s = std::getenv("AR");
- return s;
+ static auto ar_prog = locate(ar_env, paths);
+ return ar_prog;
}
- if(key == ctor::cfg::builddir && std::getenv("BUILDDIR"))
+ auto builddir_env = std::getenv("BUILDDIR");
+ if(key == ctor::cfg::builddir && builddir_env)
{
- static std::string s = std::getenv("BUILDDIR");
- return s;
+ return builddir_env;
}
return default_value;
}
+std::string ctor::configuration::getenv(const std::string& key) const
+{
+ auto envit = env.find(key);
+ if(envit != env.end())
+ {
+ return envit->second;
+ }
+
+ auto env = std::getenv(key.data());
+ if(env)
+ {
+ return env;
+ }
+
+ return {};
+}
+
+std::vector<std::string> readDeps(const std::string& depFile,
+ ctor::toolchain toolchain)
+{
+ return {};
+}
+
int main(int argc, char* argv[])
{
- if(argc > 1)
+ auto args = std::span(argv, static_cast<std::size_t>(argc));
+ if(args.size() > 1)
{
- std::cerr << "This is a minimal bootstrap version of " << argv[0] <<
+ std::cerr << "This is a minimal bootstrap version of " << args[0] <<
" which doesn't support any arguments\n";
return 1;
}
diff --git a/src/build.cc b/src/build.cc
index ea65656..5995fb7 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -38,7 +38,7 @@ int build(const ctor::settings& settings,
// Dry-run returns number of dirty tasks but otherwise does nothing.
if(dryrun)
{
- return dirtyTasks.size();
+ return static_cast<int>(dirtyTasks.size());
}
if(dirtyTasks.empty())
@@ -65,7 +65,7 @@ int build(const ctor::settings& settings,
break;
}
- auto task = getNextTask(all_tasks, dirtyTasks);
+ auto task = getNextTask(settings, all_tasks, dirtyTasks);
if(task == nullptr)
{
if(processes.empty() && !dirtyTasks.empty())
@@ -114,17 +114,15 @@ int build(const ctor::settings& settings,
}
}
- for(auto process = processes.begin();
- process != processes.end();
- ++process)
+ for(auto& process : processes)
{
- if(process->valid() == false)
+ if(process.valid() == false)
{
continue;
}
- process->wait();
- auto ret = process->get();
- if(ret != 0)
+ process.wait();
+ auto ret = process.get();
+ if (ret != 0)
{
return ret;
}
@@ -133,29 +131,46 @@ int build(const ctor::settings& settings,
return 0;
}
-namespace
-{
-std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task)
+std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task,
+ std::vector<std::shared_ptr<Task>> trace)
{
std::vector<std::shared_ptr<Task>> tasks;
tasks.push_back(task);
+ trace.push_back(task);
auto deps = task->getDependsTasks();
for(const auto& dep : deps)
{
- auto depSet = getDepTasks(dep);
- for(const auto& dep : depSet)
+ if(std::find(trace.begin(), trace.end(), dep) != trace.end())
{
- if(std::find(tasks.begin(), tasks.end(), dep) == tasks.end())
+ trace.push_back(dep);
+ std::cerr << "Error: Cyclic dependency detected: ";
+ bool first{true};
+ for(auto t : trace)
{
- tasks.push_back(dep);
+ if(!first)
+ {
+ std::cerr << " -> ";
+ }
+
+ first = false;
+ std::cerr << t->target();
+ }
+ std::cerr << '\n';
+ throw 1;
+ }
+ auto depSet = getDepTasks(dep, trace);
+ for(const auto& dep_inner : depSet)
+ {
+ if(std::find(tasks.begin(), tasks.end(), dep_inner) == tasks.end())
+ {
+ tasks.push_back(dep_inner);
}
}
}
return tasks;
}
-}
int build(const ctor::settings& settings,
const std::string& name,
@@ -169,20 +184,27 @@ int build(const ctor::settings& settings,
{
task_found = true;
- auto depSet = getDepTasks(task);
- std::vector<std::shared_ptr<Task>> ts;
- for(const auto& task : depSet)
+ try
{
- if(std::find(ts.begin(), ts.end(), task) == ts.end())
+ auto depSet = getDepTasks(task);
+ std::vector<std::shared_ptr<Task>> ts;
+ for(const auto& task_inner : depSet)
{
- ts.push_back(task);
+ if(std::find(ts.begin(), ts.end(), task_inner) == ts.end())
+ {
+ ts.push_back(task_inner);
+ }
}
- }
- auto ret = build(settings, name, ts, all_tasks, dryrun);
- if(ret != 0)
+ auto ret = build(settings, name, ts, all_tasks, dryrun);
+ if(ret != 0)
+ {
+ return ret;
+ }
+ }
+ catch(...)
{
- return ret;
+ return 1; // cycle detected
}
break;
@@ -216,14 +238,21 @@ int build(const ctor::settings& settings,
{
task_found = true;
- auto depSet = getDepTasks(task);
- for(const auto& task : depSet)
+ try
{
- if(std::find(ts.begin(), ts.end(), task) == ts.end())
+ auto depSet = getDepTasks(task);
+ for(const auto& task_inner : depSet)
{
- ts.push_back(task);
+ if(std::find(ts.begin(), ts.end(), task_inner) == ts.end())
+ {
+ ts.push_back(task_inner);
+ }
}
}
+ catch(...)
+ {
+ return 1; // cycle detected
+ }
}
}
}
diff --git a/src/build.h b/src/build.h
index 500fb7f..7296f76 100644
--- a/src/build.h
+++ b/src/build.h
@@ -33,3 +33,8 @@ int build(const ctor::settings& settings,
const std::vector<Target>& targets,
const std::vector<std::shared_ptr<Task>>& all_tasks,
bool dryrun = false);
+
+// Recursively build vector of dependency tasks from source task.
+// Throws if a cycle is detected.
+std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task,
+ std::vector<std::shared_ptr<Task>> trace = {});
diff --git a/src/configure.cc b/src/configure.cc
index c08ed88..910b878 100644
--- a/src/configure.cc
+++ b/src/configure.cc
@@ -7,8 +7,8 @@
#include <filesystem>
#include <fstream>
#include <optional>
-
-#include <getoptpp/getoptpp.hpp>
+#include <span>
+#include <cstring>
#include "execute.h"
#include "ctor.h"
@@ -17,9 +17,10 @@
#include "externals.h"
#include "tools.h"
#include "util.h"
+#include "argparser.h"
-std::filesystem::path configurationFile("configuration.cc");
-std::filesystem::path configHeaderFile("config.h");
+const std::filesystem::path configurationFile("configuration.cc");
+const std::filesystem::path configHeaderFile("config.h");
std::map<std::string, std::string> external_includedir;
std::map<std::string, std::string> external_libdir;
@@ -30,7 +31,11 @@ const ctor::configuration& __attribute__((weak)) ctor::get_configuration()
static bool initialised{false};
if(!initialised)
{
- cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, "/usr/bin/g++"));
+ std::string cxx_prog{"c++"};
+ get_env("CXX", cxx_prog);
+
+ cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, cxx_prog));
+
initialised = true;
}
return cfg;
@@ -68,7 +73,8 @@ bool ctor::configuration::has(const std::string& key) const
return tools.find(key) != tools.end();
}
-const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const
+std::string ctor::configuration::get(const std::string& key,
+ const std::string& default_value) const
{
if(key == ctor::cfg::ctor_includedir && ctor::includedir)
{
@@ -90,14 +96,51 @@ const std::string& ctor::configuration::get(const std::string& key, const std::s
return ctor::conf_values[key];
}
- if(has(key))
+ if(tools.find(key) != tools.end())
{
return tools.at(key);
}
+ std::string value;
+ if(key == ctor::cfg::build_cxx && get_env("CXX", value))
+ {
+ return value;
+ }
+
+ if(key == ctor::cfg::build_cc && get_env("CC", value))
+ {
+ return value;
+ }
+
+ if(key == ctor::cfg::build_ld && get_env("LD", value))
+ {
+ return value;
+ }
+
+ if(key == ctor::cfg::build_ar && get_env("AR", value))
+ {
+ return value;
+ }
+
return default_value;
}
+std::string ctor::configuration::getenv(const std::string& key) const
+{
+ auto envit = env.find(key);
+ if(envit != env.end())
+ {
+ return envit->second;
+ }
+
+ if(std::string value; get_env(key.data(), value))
+ {
+ return value;
+ }
+
+ return {};
+}
+
class Args
: public std::vector<char*>
{
@@ -105,20 +148,16 @@ public:
Args(const std::vector<std::string>& args)
{
resize(args.size() + 1);
- (*this)[0] = strdup("./ctor");
+ owning_container.push_back("./ctor");
+ (*this)[0] = owning_container.back().data();
for(std::size_t i = 0; i < size() - 1; ++i)
{
- (*this)[i + 1] = strdup(args[i].data());
+ owning_container.push_back(args[i]);
+ (*this)[i + 1] = owning_container.back().data();
}
}
- ~Args()
- {
- for(std::size_t i = 0; i < size(); ++i)
- {
- free((*this)[i]);
- }
- }
+ std::deque<std::string> owning_container;
};
namespace {
@@ -209,8 +248,7 @@ int regenerateCache(ctor::settings& settings,
{
Args vargs(args);
- dg::Options opt;
- int key{128};
+ arg::Parser<std::string> opt(static_cast<int>(vargs.size()), vargs.data());
std::string build_arch_prefix;
std::string build_path;
@@ -224,118 +262,133 @@ int regenerateCache(ctor::settings& settings,
std::string ctor_libdir;
std::string builddir;
- opt.add("build-dir", required_argument, 'b',
- "Set output directory for build files (default: '" +
- settings.builddir + "').",
- [&]() {
- settings.builddir = optarg;
- builddir = optarg;
+ opt.add('b', "--build-dir",
+ std::function([&](std::string arg)
+ {
+ settings.builddir = arg;
+ builddir = arg;
return 0;
- });
+ }),
+ "Set output directory for build files (default: '" +
+ settings.builddir + "').");
- opt.add("verbose", no_argument, 'v',
- "Be verbose. Add multiple times for more verbosity.",
- [&]() {
+ opt.add('v', "--verbose",
+ std::function([&]()
+ {
settings.verbose++;
return 0;
- });
+ }),
+ "Be verbose. Add multiple times for more verbosity.");
- opt.add("cc", required_argument, key++,
- "Use specified c-compiler instead of gcc.",
- [&]() {
- cc_prog = optarg;
+ opt.add({}, "--cc",
+ std::function([&](std::string arg)
+ {
+ cc_prog = arg;
return 0;
- });
+ }),
+ "Use specified c-compiler instead of gcc.");
- opt.add("cxx", required_argument, key++,
- "Use specified c++-compiler instead of g++.",
- [&]() {
- cxx_prog = optarg;
+ opt.add({}, "--cxx",
+ std::function([&](std::string arg)
+ {
+ cxx_prog = arg;
return 0;
- });
+ }),
+ "Use specified c++-compiler instead of g++.");
- opt.add("ar", required_argument, key++,
- "Use specified archiver instead of ar.",
- [&]() {
- ar_prog = optarg;
+ opt.add({}, "--ar",
+ std::function([&](std::string arg)
+ {
+ ar_prog = arg;
return 0;
- });
+ }),
+ "Use specified archiver instead of ar.");
- opt.add("ld", required_argument, key++,
- "Use specified linker instead of ld.",
- [&]() {
- ld_prog = optarg;
+ opt.add({}, "--ld",
+ std::function([&](std::string arg)
+ {
+ ld_prog = arg;
return 0;
- });
+ }),
+ "Use specified linker instead of ld.");
- opt.add("build", required_argument, key++,
- "Configure for building on specified architecture.",
- [&]() {
- build_arch_prefix = optarg;
+ opt.add({}, "--build",
+ std::function([&](std::string arg)
+ {
+ build_arch_prefix = arg;
return 0;
- });
+ }),
+ "Configure for building on specified architecture.");
- opt.add("build-path", required_argument, key++,
- "Set path to build tool-chain.",
- [&]() {
- build_path = optarg;
+ opt.add({}, "--build-path",
+ std::function([&](std::string arg)
+ {
+ build_path = arg;
return 0;
- });
+ }),
+ "Set path to build tool-chain.");
- opt.add("host", required_argument, key++,
- "Cross-compile to build programs to run on specified architecture.",
- [&]() {
- host_arch_prefix = optarg;
+ opt.add({}, "--host",
+ std::function([&](std::string arg)
+ {
+ host_arch_prefix = arg;
return 0;
- });
+ }),
+ "Cross-compile to build programs to run on specified architecture.");
- opt.add("host-path", required_argument, key++,
- "Set path to cross-compile tool-chain.",
- [&]() {
- host_path = optarg;
+ opt.add({}, "--host-path",
+ std::function([&](std::string arg)
+ {
+ host_path = arg;
return 0;
- });
+ }),
+ "Set path to cross-compile tool-chain.");
- opt.add("ctor-includedir", required_argument, key++,
- "Set path to ctor header file, used for re-compiling.",
- [&]() {
- ctor_includedir = optarg;
+ opt.add({}, "--ctor-includedir",
+ std::function([&](std::string arg)
+ {
+ ctor_includedir = arg;
return 0;
- });
+ }),
+ "Set path to ctor header file, used for re-compiling.");
- opt.add("ctor-libdir", required_argument, key++,
- "Set path to ctor library file, used for re-compiling.",
- [&]() {
- ctor_libdir = optarg;
+ opt.add({}, "--ctor-libdir",
+ std::function([&](std::string arg)
+ {
+ ctor_libdir = arg;
return 0;
- });
+ }),
+ "Set path to ctor library file, used for re-compiling.");
// Resolv externals
ctor::external_configurations externalConfigs;
- for(std::size_t i = 0; i < numExternalConfigFiles; ++i)
+ const auto& externalConfigFiles = getExternalConfigFileList();
+ for(const auto& externalConfigFile : externalConfigFiles)
{
- auto newExternalConfigs = externalConfigFiles[i].cb(settings);
+ auto newExternalConfigs = externalConfigFile.cb(settings);
externalConfigs.insert(externalConfigs.end(),
newExternalConfigs.begin(),
newExternalConfigs.end());
}
auto add_path_args =
- [&](const std::string& name)
+ [&](const std::string& arg_name)
{
- opt.add(name + "-includedir", required_argument, key++,
- "Set path to " + name + " header file.",
- [&]() {
- external_includedir[name] = optarg;
+ opt.add({}, "--" + arg_name + "-includedir",
+ std::function([&](std::string arg)
+ {
+ external_includedir[arg_name] = arg;
return 0;
- });
+ }),
+ "Set path to " + arg_name + " header file.");
- opt.add(name + "-libdir", required_argument, key++,
- "Set path to " + name + " libraries.",
- [&]() {
- external_libdir[name] = optarg;
+ opt.add({}, "--" + arg_name + "-libdir",
+ std::function([&](std::string arg)
+ {
+ external_libdir[arg_name] = arg;
return 0;
- });
+ }),
+ "Set path to " + arg_name + " libraries.");
};
for(const auto& ext : externalConfigs)
@@ -355,18 +408,57 @@ int regenerateCache(ctor::settings& settings,
}
- opt.add("help", no_argument, 'h',
- "Print this help text.",
- [&]() {
+ opt.add('h', "--help",
+ std::function([&]() -> int
+ {
std::cout << "Configure how to build with " << name << "\n";
std::cout << "Usage: " << name << " configure [options]\n\n";
std::cout << "Options:\n";
opt.help();
exit(0);
- return 0;
- });
+ }),
+ "Print this help text.");
+
+ opt.set_err_cb(
+ [&](arg::error err, std::string_view arg)
+ {
+ switch(err)
+ {
+ case arg::error::invalid_arg:
+ std::cerr << opt.prog_name() <<
+ ": invalid argument for option '" << arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+
+ case arg::error::missing_arg:
+ std::cerr << opt.prog_name() << ": option requires and argument '" <<
+ arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+
+ case arg::error::invalid_opt:
+ std::cerr << opt.prog_name() << ": invalid option '" << arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+ }
+ });
+
+ opt.set_pos_cb(
+ [&](std::string_view)
+ {
+ std::cerr <<
+ "The configure subcommand doesn't use positional arguments.\n";
+ return 1;
+ });
- opt.process(vargs.size(), vargs.data());
+ auto res = opt.parse();
+ if(res != 0)
+ {
+ return res;
+ }
if(host_arch_prefix.empty())
{
@@ -650,6 +742,7 @@ int regenerateCache(ctor::settings& settings,
{
ctor::conf_values[ctor::cfg::builddir] = builddir;
}
+
ctor::conf_values[ctor::cfg::host_cxx] = host_cxx;
ctor::conf_values[ctor::cfg::build_cxx] = build_cxx;
@@ -821,46 +914,59 @@ int regenerateCache(ctor::settings& settings,
int configure(const ctor::settings& global_settings, int argc, char* argv[])
{
+ auto args_span = std::span(argv, static_cast<std::size_t>(argc));
+
ctor::settings settings{global_settings};
std::vector<std::string> args;
- for(int i = 2; i < argc; ++i) // skip command and the first 'configure' arg
+ for(std::size_t i = 2; i < args_span.size(); ++i) // skip command and the first 'configure' arg
{
- args.push_back(argv[i]);
+ args.emplace_back(args_span[i]);
}
std::map<std::string, std::string> env;
- auto cc_env = getenv("CC");
- if(cc_env)
+ std::string value;
+ if(get_env("CC", value))
+ {
+ env["CC"] = value;
+ }
+
+ if(get_env("CFLAGS", value))
{
- env["CC"] = cc_env;
+ env["CFLAGS"] = value;
}
- auto cxx_env = getenv("CXX");
- if(cxx_env)
+ if(get_env("CXX", value))
{
- env["CXX"] = cxx_env;
+ env["CXX"] = value;
}
- auto ar_env = getenv("AR");
- if(ar_env)
+ if(get_env("CXXFLAGS", value))
{
- env["AR"] = ar_env;
+ env["CXXFLAGS"] = value;
}
- auto ld_env = getenv("LD");
- if(ld_env)
+ if(get_env("AR", value))
{
- env["LD"] = ld_env;
+ env["AR"] = value;
}
- auto path_env = getenv("PATH");
- if(path_env)
+ if(get_env("LD", value))
{
- env["PATH"] = path_env;
+ env["LD"] = value;
}
- auto ret = regenerateCache(settings, argv[0], args, env);
+ if(get_env("LDFLAGS", value))
+ {
+ env["LDFLAGS"] = value;
+ }
+
+ if(get_env("PATH", value))
+ {
+ env["PATH"] = value;
+ }
+
+ auto ret = regenerateCache(settings, args_span[0], args, env);
if(ret != 0)
{
return ret;
@@ -873,19 +979,21 @@ int configure(const ctor::settings& global_settings, int argc, char* argv[])
int reconfigure(const ctor::settings& global_settings, int argc, char* argv[])
{
+ auto args_span = std::span(argv, static_cast<std::size_t>(argc));
+
ctor::settings settings{global_settings};
bool no_rerun{false};
std::vector<std::string> args;
- for(int i = 2; i < argc; ++i) // skip executable name and 'reconfigure' arg
+ for(std::size_t i = 2; i < args_span.size(); ++i) // skip executable name and 'reconfigure' arg
{
- if(i == 2 && std::string(argv[i]) == "--no-rerun")
+ if(i == 2 && std::string(args_span[i]) == "--no-rerun")
{
no_rerun = true;
continue;
}
- args.push_back(argv[i]);
+ args.emplace_back(args_span[i]);
}
const auto& cfg = ctor::get_configuration();
@@ -895,14 +1003,14 @@ int reconfigure(const ctor::settings& global_settings, int argc, char* argv[])
{
std::cout << e.first << "=\"" << e.second << "\" ";
}
- std::cout << argv[0] << " configure ";
+ std::cout << args_span[0] << " configure ";
for(const auto& arg : cfg.args)
{
std::cout << arg << " ";
}
std::cout << "\n";
- auto ret = regenerateCache(settings, argv[0], cfg.args, cfg.env);
+ auto ret = regenerateCache(settings, args_span[0], cfg.args, cfg.env);
if(ret != 0)
{
return ret;
@@ -915,5 +1023,5 @@ int reconfigure(const ctor::settings& global_settings, int argc, char* argv[])
return 0; // this was originally invoked by configure, don't loop
}
- return execute(argv[0], args);
+ return execute(settings, args_span[0], args);
}
diff --git a/src/configure.h b/src/configure.h
index ac8d721..5344646 100644
--- a/src/configure.h
+++ b/src/configure.h
@@ -12,8 +12,8 @@ namespace ctor {
struct settings;
} // namespace ctor::
-extern std::filesystem::path configurationFile;;
-extern std::filesystem::path configHeaderFile;
+extern const std::filesystem::path configurationFile;
+extern const std::filesystem::path configHeaderFile;
int configure(const ctor::settings& settings, int argc, char* argv[]);
int reconfigure(const ctor::settings& settings, int argc, char* argv[]);
diff --git a/src/ctor.h b/src/ctor.h
index 3b64cb5..dddc5ef 100644
--- a/src/ctor.h
+++ b/src/ctor.h
@@ -10,6 +10,8 @@
#include <variant>
#include <cstddef>
#include <functional>
+#include <string_view>
+#include <cassert>
namespace ctor {
@@ -52,23 +54,6 @@ enum class arch
unknown, //!< Target platform architecture has not yet detected or was not possible to detect
};
-struct source
-{
- source(const char* file) : file(file) {}
- source(const std::string& file) : file(file) {}
- source(const char* file, ctor::language lang) : file(file), language(lang) {}
- source(const std::string& file, ctor::language lang) : file(file), language(lang) {}
-
- source(const char* file, const char* output) : file(file), output(output) {}
- source(const std::string& file, const std::string& output) : file(file), output(output) {}
- source(const char* file, ctor::language lang, const char* output) : file(file), language(lang), output(output) {}
- source(const std::string& file, ctor::language lang, const std::string& output) : file(file), language(lang), output(output) {}
-
- std::string file;
- ctor::language language{ctor::language::automatic};
- std::string output{};
-};
-
enum class toolchain
{
any,
@@ -77,12 +62,70 @@ enum class toolchain
clang,
};
+struct output_file
+{
+ std::string file;
+};
+
+enum class source_type
+{
+ regular,
+ generated,
+};
+
+struct source
+{
+ template <class ... Args>
+ requires ((
+ std::is_convertible_v<Args, std::string_view> ||
+ std::is_same_v<Args, ctor::toolchain> ||
+ std::is_same_v<Args, ctor::language> ||
+ std::is_same_v<Args, ctor::source_type> ||
+ std::is_same_v<Args, ctor::output_file>
+ ) && ...)
+ constexpr source(Args && ... arg)
+ {
+ ([&]
+ {
+ if constexpr(std::is_convertible_v<Args, std::string_view>)
+ {
+ file = arg;
+ }
+ else if constexpr(std::is_same_v<Args, ctor::toolchain>)
+ {
+ toolchain = arg;
+ }
+ else if constexpr(std::is_same_v<Args, ctor::language>)
+ {
+ language = arg;
+ }
+ else if constexpr(std::is_same_v<Args, ctor::output_file>)
+ {
+ output = arg.file;
+ }
+ else if constexpr(std::is_same_v<Args, ctor::source_type>)
+ {
+ source_type = arg;
+ }
+ }(), ...);
+ }
+
+ std::string file;
+ ctor::toolchain toolchain{ctor::toolchain::any};
+ ctor::language language{ctor::language::automatic};
+ std::string output{};
+ ctor::source_type source_type{ctor::source_type::regular};
+};
+
enum class cxx_opt
{
// gcc/clang
output, // -o
debug, // -g
warn_all, // -Wall
+ warn_conversion, // -Wconversion
+ warn_shadow, // -Wshadow
+ warn_extra, // -Wextra
warnings_as_errors, // -Werror
generate_dep_tree, // -MMD
no_link, // -c
@@ -91,6 +134,7 @@ enum class cxx_opt
optimization, // -O<arg>
position_independent_code, // -fPIC
position_independent_executable, // -fPIE
+ define, // -D<arg>[=<arg2>]
custom, // entire option taken verbatim from <arg>
};
@@ -100,6 +144,9 @@ enum class c_opt
output, // -o
debug, // -g
warn_all, // -Wall
+ warn_conversion, // -Wconversion
+ warn_shadow, // -Wshadow
+ warn_extra, // -Wextra
warnings_as_errors, // -Werror
generate_dep_tree, // -MMD
no_link, // -c
@@ -108,6 +155,7 @@ enum class c_opt
optimization, // -O<arg>
position_independent_code, // -fPIC
position_independent_executable, // -fPIE
+ define, // -D<arg>[=<arg2>]
custom, // entire option taken verbatim from <arg>
};
@@ -115,7 +163,6 @@ enum class ld_opt
{
// gcc/clang
output, // -o
- strip, // -s
warn_all, // -Wall
warnings_as_errors, // -Werror
library_path, // -L<arg>
@@ -149,18 +196,60 @@ template<typename T>
class flag
{
public:
- flag(const std::string& str);
- flag(const char* str);
- flag(T opt) : opt(opt) {}
- flag(T opt, const std::string& arg) : opt(opt), arg(arg) {}
- flag(T opt, const char* arg) : opt(opt), arg(arg) {}
- flag(ctor::toolchain toolchain, T opt) : toolchain(toolchain), opt(opt) {}
- flag(ctor::toolchain toolchain, T opt, const char* arg) : toolchain(toolchain), opt(opt), arg(arg) {}
- flag(ctor::toolchain toolchain, T opt, const std::string& arg) : toolchain(toolchain), opt(opt), arg(arg) {}
+ template <class ... Args>
+ requires ((
+ std::is_convertible_v<Args, std::string_view> ||
+ std::is_same_v<Args, ctor::toolchain> ||
+ std::is_same_v<Args, T>
+ ) && ...)
+ constexpr flag(Args && ... _arg)
+ {
+ constexpr std::size_t n = sizeof...(Args);
+ int state{}; // 0: opt, 1: arg1, 2: arg2, 3: error
+ ([&]
+ {
+ if constexpr(std::is_convertible_v<Args, std::string_view>)
+ {
+ if constexpr(n == 1)
+ {
+ std::string str(_arg);
+ to_flag(str);
+ }
+ else
+ {
+ assert(state > 0); // opt must be before args
+ if(state == 1)
+ {
+ this->arg = _arg;
+ }
+ else
+ {
+ assert(state == 2); // up to 2 args supported
+ this->arg2 = _arg;
+ }
+ ++state;
+ }
+ }
+ else if constexpr(std::is_same_v<Args, ctor::toolchain>)
+ {
+ toolchain = _arg;
+ }
+ else if constexpr(std::is_same_v<Args, T>)
+ {
+ assert(state == 0); // opt must be before args
+ opt = _arg;
+ ++state;
+ }
+ }(), ...);
+ }
ctor::toolchain toolchain{ctor::toolchain::any};
- T opt;
+ T opt{};
std::string arg;
+ std::string arg2;
+
+private:
+ void to_flag(std::string_view str);
};
using c_flag = ctor::flag<ctor::c_opt>;
@@ -171,7 +260,7 @@ using asm_flag = ctor::flag<ctor::asm_opt>;
using c_flags = std::vector<ctor::c_flag>;
using cxx_flags = std::vector<ctor::cxx_flag>;
-using ld_flags= std::vector<ctor::ld_flag>;
+using ld_flags = std::vector<ctor::ld_flag>;
using ar_flags = std::vector<ctor::ar_flag>;
using asm_flags = std::vector<ctor::asm_flag>;
@@ -189,13 +278,21 @@ struct settings
std::string builddir{"build"};
std::size_t parallel_processes{1};
int verbose{0}; // -1: completely silent, 0: normal, 1: verbose, ...
+ bool dry_run{false};
};
struct build_configuration;
-using GeneratorCb = std::function<int(const std::string& input,
- const std::string& output,
- const build_configuration& config,
- const ctor::settings& settings)>;
+using GeneratorOneToOne =
+ std::function<int(const std::string& input,
+ const std::string& output,
+ const build_configuration& config,
+ const ctor::settings& settings)>;
+
+using GeneratorManyToOne =
+ std::function<int(const std::vector<std::string>& input,
+ const std::string& output,
+ const ctor::build_configuration& config,
+ const ctor::settings& settings)>;
struct build_configuration
{
@@ -207,12 +304,14 @@ struct build_configuration
std::vector<std::string> depends; // internal target dependencies
ctor::flags flags;
std::vector<std::string> externals; // externals used by this configuration
- GeneratorCb function;
+ std::variant<std::monostate,
+ GeneratorOneToOne,
+ GeneratorManyToOne> function;
};
using build_configurations = std::vector<build_configuration>;
-int reg(ctor::build_configurations (*cb)(const ctor::settings&),
+int reg(std::function<ctor::build_configurations (const ctor::settings&)> cb,
const std::source_location location = std::source_location::current());
// This type will use flags verbatim
@@ -231,7 +330,7 @@ struct external_configuration
using external_configurations = std::vector<ctor::external_configuration>;
-int reg(ctor::external_configurations (*cb)(const ctor::settings&),
+int reg(std::function<ctor::external_configurations (const ctor::settings&)> cb,
const std::source_location location = std::source_location::current());
// Convenience macro - ugly but keeps things simple(r)
@@ -246,12 +345,12 @@ namespace cfg
constexpr auto builddir = "builddir";
constexpr auto host_cc = "host-cc";
-constexpr auto host_cxx = "host-cpp";
+constexpr auto host_cxx = "host-cxx";
constexpr auto host_ar = "host-ar";
constexpr auto host_ld = "host-ld";
constexpr auto build_cc = "build-cc";
-constexpr auto build_cxx = "build-cpp";
+constexpr auto build_cxx = "build-cxx";
constexpr auto build_ar = "build-ar";
constexpr auto build_ld = "build-ld";
@@ -262,7 +361,7 @@ constexpr auto ctor_libdir = "ctor-libdir";
struct configuration
{
bool has(const std::string& key) const;
- const std::string& get(const std::string& key, const std::string& default_value = {}) const;
+ std::string get(const std::string& key, const std::string& default_value = {}) const;
ctor::toolchain host_toolchain{ctor::toolchain::none};
ctor::arch host_arch{ctor::arch::unknown};
@@ -271,6 +370,8 @@ struct configuration
ctor::arch build_arch{ctor::arch::unknown};
std::vector<std::string> args; // vector of arguments used when last calling configure
+
+ std::string getenv(const std::string& key) const;
std::map<std::string, std::string> env; // env used when last calling configure
std::map<std::string, std::string> tools; // tools
diff --git a/src/deps.cc b/src/deps.cc
new file mode 100644
index 0000000..9400b35
--- /dev/null
+++ b/src/deps.cc
@@ -0,0 +1,120 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#include "deps.h"
+
+#include "util.h"
+
+#include <fstream>
+
+namespace {
+/* Format example:
+build/src/libctor_a-libctor_cc.o: src/libctor.cc \
+ src/getoptpp/getoptpp.hpp src/ctor.h src/configure.h src/rebuild.h \
+ src/A\ B\ C.h src/task.h src/build.h src/unittest.h
+ */
+std::vector<std::string> readDepsMake(const std::string& dep_file)
+{
+ auto str = readFile(dep_file);
+
+ std::vector<std::string> output;
+ std::string tmp;
+
+ enum class State
+ {
+ pre_colon,
+ post_colon,
+ in_escape,
+ } state{State::pre_colon};
+
+ auto old_state{state};
+ for(const auto& c : str)
+ {
+ if(c == '\r')
+ {
+ // just always ignore windows extra newline char
+ continue;
+ }
+
+ switch(state)
+ {
+ case State::pre_colon:
+ if(c == ':') // ignore everything until the colon
+ {
+ state = State::post_colon;
+ continue;
+ }
+ continue;
+
+ case State::post_colon:
+ if(c == '\n')
+ {
+ // done
+ if(!tmp.empty())
+ {
+ output.emplace_back(tmp);
+ }
+ return output;
+ }
+ if(c == '\\')
+ {
+ old_state = state;
+ state = State::in_escape;
+ continue;
+ }
+ if(c == '\t' || c == ' ') // new token
+ {
+ if(!tmp.empty())
+ {
+ output.emplace_back(tmp);
+ }
+ tmp.clear();
+ continue;
+ }
+ break;
+
+ case State::in_escape:
+ if(c == '\n')
+ {
+ // linewrap
+ state = old_state;
+ continue;
+ }
+ state = old_state;
+ break;
+ }
+
+ tmp += c;
+ }
+
+ if(!tmp.empty())
+ {
+ output.emplace_back(tmp);
+ }
+
+ return output;
+}
+}
+
+std::vector<std::string> readDeps(const std::string& dep_file,
+ ctor::toolchain toolchain)
+{
+ if(!std::filesystem::exists(dep_file))
+ {
+ return {};
+ }
+
+ switch(toolchain)
+ {
+ case ctor::toolchain::any:
+ case ctor::toolchain::none:
+ return {};
+
+ // makefile .d file based:
+ case ctor::toolchain::gcc:
+ case ctor::toolchain::clang:
+ return readDepsMake(dep_file);
+ }
+
+ return {};
+}
diff --git a/src/deps.h b/src/deps.h
new file mode 100644
index 0000000..be0dfb2
--- /dev/null
+++ b/src/deps.h
@@ -0,0 +1,12 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#pragma once
+
+#include "ctor.h"
+
+#include <vector>
+#include <string>
+
+std::vector<std::string> readDeps(const std::string& dep_file,
+ ctor::toolchain toolchain);
diff --git a/src/execute.cc b/src/execute.cc
index b4013d0..c050732 100644
--- a/src/execute.cc
+++ b/src/execute.cc
@@ -3,6 +3,9 @@
// See accompanying file LICENSE for details.
#include "execute.h"
+#include "ctor.h"
+#include "pointerlist.h"
+
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
@@ -20,47 +23,53 @@ https://stackoverflow.com/questions/4259629/what-is-the-difference-between-fork-
namespace
{
-class Env
- : public std::vector<char*>
+
+int parent_waitpid(pid_t pid)
{
-public:
- Env(const std::vector<std::string>& args)
+ int status{};
+
+ auto rc_pid = waitpid(pid, &status, 0);
+
+ if(rc_pid > 0)
{
- for(const auto& arg : args)
+ if(WIFEXITED(status))
{
- push_back(strdup(arg.data()));
+ // Child exited with normally
+ return WEXITSTATUS(status);
}
- push_back(nullptr);
- }
-
- ~Env()
- {
- for(auto ptr : *this)
+ if(WIFSIGNALED(status))
{
- free(ptr);
+ // Child exited via signal (segfault, abort, ...)
+ std::cerr << strsignal(status) << '\n';
+ return WTERMSIG(status);
}
}
-};
-
-int parent_waitpid(pid_t pid)
-{
- int status;
-
- if(waitpid(pid, &status, 0) != pid)
- {
- return 1;
+ else
+ { // No PID returned, this is an error
+ if(errno == ECHILD)
+ {
+ // No children exist.
+ return 1;
+ }
+ else
+ {
+ // Unexpected error.
+ abort();
+ }
}
- return WEXITSTATUS(status);
+ // Should never happen...
+ return 1;
}
} // namespace ::
extern char **environ; // see 'man environ'
-int execute(const std::string& command,
+int execute(const ctor::settings& settings,
+ const std::string& command,
const std::vector<std::string>& args,
const std::map<std::string, std::string>& env,
- bool verbose)
+ [[maybe_unused]] bool terminate)
{
std::vector<const char*> argv;
argv.push_back(command.data());
@@ -84,7 +93,7 @@ int execute(const std::string& command,
cmd += arg;
}
- if(verbose)
+ if(settings.verbose > 0)
{
std::cout << cmd << std::endl;
}
@@ -93,19 +102,18 @@ int execute(const std::string& command,
auto pid = vfork();
if(pid == 0)
{
- std::vector<std::string> venv;
+ EnvMap envmap((const char**)environ);
for(const auto& [key, value] : env)
{
- venv.push_back(key + "=" + value);
+ envmap.insert(key + "=" + value);
}
-
- for(auto current = environ; *current; ++current)
+ if(settings.dry_run)
{
- venv.push_back(*current);
+ _exit(0);
}
-
- Env penv(venv);
- execve(command.data(), (char**)argv.data(), penv.data());
+ auto [_, envv] = envmap.get();
+ execve(command.data(), const_cast<char* const *>(argv.data()),
+ const_cast<char* const *>(envv));
std::cout << "Could not execute " << command << ": " <<
strerror(errno) << "\n";
_exit(1); // execve only returns if an error occurred
diff --git a/src/execute.h b/src/execute.h
index 336c3ef..4288bb7 100644
--- a/src/execute.h
+++ b/src/execute.h
@@ -7,7 +7,12 @@
#include <vector>
#include <map>
-int execute(const std::string& command,
+namespace ctor {
+struct settings;
+}//ctor::
+
+int execute(const ctor::settings& settings,
+ const std::string& command,
const std::vector<std::string>& args = {},
const std::map<std::string, std::string>& env = {},
- bool verbose = true);
+ bool terminate = false);
diff --git a/src/externals_manual.cc b/src/externals_manual.cc
index 3b96263..0563a5e 100644
--- a/src/externals_manual.cc
+++ b/src/externals_manual.cc
@@ -13,7 +13,7 @@
extern std::map<std::string, std::string> external_includedir;
extern std::map<std::string, std::string> external_libdir;
-int resolv(const ctor::settings& settings, const ctor::external_configuration& config,
+int resolv([[maybe_unused]]const ctor::settings& settings, const ctor::external_configuration& config,
const ctor::external_manual& ext, ctor::flags& flags)
{
flags = ext.flags;
@@ -21,14 +21,14 @@ int resolv(const ctor::settings& settings, const ctor::external_configuration& c
auto inc = external_includedir.find(config.name);
if(inc != external_includedir.end())
{
- flags.cflags.push_back({ctor::c_opt::include_path, inc->second});
- flags.cxxflags.push_back({ctor::cxx_opt::include_path, inc->second});
+ flags.cflags.emplace_back(ctor::c_opt::include_path, inc->second);
+ flags.cxxflags.emplace_back(ctor::cxx_opt::include_path, inc->second);
}
auto lib = external_libdir.find(config.name);
if(lib != external_libdir.end())
{
- flags.ldflags.push_back({ctor::ld_opt::library_path, lib->second});
+ flags.ldflags.emplace_back(ctor::ld_opt::library_path, lib->second);
}
return 0;
diff --git a/src/getoptpp b/src/getoptpp
deleted file mode 160000
-Subproject 9ff20ef857429619267e3f156a4f81ad9e1eb8c
diff --git a/src/libctor.cc b/src/libctor.cc
index 3eb6c6f..2685ec0 100644
--- a/src/libctor.cc
+++ b/src/libctor.cc
@@ -15,8 +15,7 @@
#include <deque>
#include <fstream>
#include <cstdlib>
-
-#include <getoptpp/getoptpp.hpp>
+#include <span>
#include "ctor.h"
#include "configure.h"
@@ -24,9 +23,13 @@
#include "tasks.h"
#include "build.h"
#include "unittest.h"
+#include "argparser.h"
+#include "util.h"
int main(int argc, char* argv[])
{
+ auto args = std::span(argv, static_cast<std::size_t>(argc));
+
ctor::settings settings{};
const auto& c = ctor::get_configuration();
@@ -34,12 +37,12 @@ int main(int argc, char* argv[])
settings.parallel_processes =
std::max(1u, std::thread::hardware_concurrency()) * 2 - 1;
- if(argc > 1 && std::string(argv[1]) == "configure")
+ if(args.size() > 1 && std::string(args[1]) == "configure")
{
return configure(settings, argc, argv);
}
- if(argc > 1 && std::string(argv[1]) == "reconfigure")
+ if(args.size() > 1 && std::string(args[1]) == "reconfigure")
{
return reconfigure(settings, argc, argv);
}
@@ -55,108 +58,121 @@ int main(int argc, char* argv[])
bool list_targets{false};
bool no_relaunch{false}; // true means no re-launch after rebuild.
bool run_unittests{false};
-
- dg::Options opt;
- int key{128};
-
- opt.add("jobs", required_argument, 'j',
- "Number of parallel jobs. (default: cpucount * 2 - 1)",
- [&]() {
- try
- {
- settings.parallel_processes = std::stoi(optarg);
- }
- catch(...)
- {
- std::cerr << "Not a number\n";
- return 1;
- }
+ std::vector<std::string> arguments;
+ arg::Parser<int, std::string> opt(argc, argv);
+ opt.set_pos_cb(
+ [&](std::string_view arg)
+ {
+ arguments.emplace_back(std::string(arg));
+ return 0;
+ });
+
+ opt.add('j', "--jobs",
+ std::function([&](int jobs)
+ {
+ settings.parallel_processes = static_cast<std::size_t>(jobs);
return 0;
- });
+ }),
+ "Number of parallel jobs. (default: cpucount * 2 - 1)");
- opt.add("build-dir", required_argument, 'b',
- "Overload output directory for build files (default: '" +
- settings.builddir + "').",
- [&]() {
- settings.builddir = optarg;
+ opt.add('b', "--build-dir",
+ std::function([&](std::string builddir) {
+ settings.builddir = builddir;
return 0;
- });
+ }),
+ "Overload output directory for build files (default: '" +
+ settings.builddir + "').");
- opt.add("verbose", no_argument, 'v',
- "Be verbose. Add multiple times for more verbosity.",
- [&]() {
+ opt.add('v', "--verbose",
+ std::function([&]()
+ {
settings.verbose++;
return 0;
- });
+ }),
+ "Be verbose. Add multiple times for more verbosity.");
- opt.add("quiet", no_argument, 'q',
- "Be completely silent.",
- [&]() {
+ opt.add('q', "--quiet",
+ std::function([&]() {
settings.verbose = -1;
return 0;
- });
+ }),
+ "Be completely silent.");
- opt.add("add", required_argument, 'a',
- "Add specified file to the build configurations.",
- [&]() {
+ opt.add('a', "--add",
+ std::function([&](std::string filename) {
no_relaunch = true;
- add_files.push_back(optarg);
+ add_files.emplace_back(filename);
return 0;
- });
+ }),
+ "Add specified file to the build configurations.");
- opt.add("remove", required_argument, 'r',
- "Remove specified file from the build configurations.",
- [&]() {
+ opt.add('r', "--remove",
+ std::function([&](std::string filename)
+ {
no_relaunch = true;
- remove_files.push_back(optarg);
+ remove_files.emplace_back(filename);
return 0;
- });
+ }),
+ "Remove specified file from the build configurations.");
- opt.add("list-files", no_argument, 'L',
- "List files in the build configurations.",
- [&]() {
+ opt.add('L', "--list-files",
+ std::function([&]()
+ {
no_relaunch = true;
list_files = true;
return 0;
- });
+ }),
+ "List files in the build configurations.");
- opt.add("list-targets", no_argument, 'l',
- "List targets.",
- [&]() {
+ opt.add('l', "--list-targets",
+ std::function([&]()
+ {
no_relaunch = true;
list_targets = true;
return 0;
- });
+ }),
+ "List targets.");
+
+ opt.add('n', "--dry-run",
+ std::function([&]()
+ {
+ settings.dry_run = true;
+ return 0;
+ }),
+ "Print the commands to be executed, but do not execute them.");
- opt.add("configure-cmd", no_argument, key++,
- "Print commandline for last configure.",
- [&]() {
+ opt.add({}, "--configure-cmd",
+ std::function([&]()
+ {
no_relaunch = true;
print_configure_cmd = true;
return 0;
- });
+ }),
+ "Print commandline for last configure.");
- opt.add("configure-db", no_argument, key++,
- "Print entire configure parameter database.",
- [&]() {
+ opt.add({}, "--configure-db",
+ std::function([&]()
+ {
no_relaunch = true;
print_configure_db = true;
return 0;
- });
+ }),
+ "Print entire configure parameter database.");
- opt.add("database", required_argument, 'd',
- "Write compilation database json file.",
- [&]() {
+ opt.add('d', "--database",
+ std::function([&](std::string database)
+ {
no_relaunch = true;
write_compilation_database = true;
- compilation_database = optarg;
+ compilation_database = database;
return 0;
- });
+ }),
+ "Write compilation database json file.");
- opt.add("help", no_argument, 'h',
- "Print this help text.",
- [&]() {
- std::cout << "Usage: " << argv[0] << " [options] [target] ...\n";
+ opt.add('h', "--help",
+ std::function([&]() -> int
+ {
+ std::cout << "Usage: " << args[0] << " [options] [target] ...\n";
std::cout <<
R"_( where target can be either:
configure - run configuration step (cannot be used with other targets).
@@ -170,29 +186,67 @@ Options:
)_";
opt.help();
exit(0);
- return 0;
- });
+ }),
+ "Print this help text.");
- opt.process(argc, argv);
+ opt.set_err_cb(
+ [&](arg::error err, std::string_view arg)
+ {
+ switch(err)
+ {
+ case arg::error::invalid_arg:
+ std::cerr << opt.prog_name() <<
+ ": invalid argument for option '" << arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+
+ case arg::error::missing_arg:
+ std::cerr << opt.prog_name() << ": option requires and argument '" <<
+ arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+
+ case arg::error::invalid_opt:
+ std::cerr << opt.prog_name() << ": invalid option '" << arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+ }
+ });
+ auto res = opt.parse();
+ if(res != 0)
+ {
+ return res;
+ }
- auto verbose_env = std::getenv("V");
- if(verbose_env)
+ if(std::string value; get_env("V", value))
{
- settings.verbose = std::atoi(verbose_env);
+ try
+ {
+ settings.verbose = std::stoi(value);
+ }
+ catch(...)
+ {
+ // not an integer
+ }
}
if(list_files)
{
no_default_build = true;
std::vector<std::string> files;
- for(std::size_t i = 0; i < numConfigFiles; ++i)
+ const auto& configFiles = getConfigFileList();
+ for(const auto& configFile : configFiles)
{
- files.push_back(configFiles[i].file);
+ files.emplace_back(configFile.file);
}
- for(std::size_t i = 0; i < numExternalConfigFiles; ++i)
+ const auto& externalConfigFiles = getExternalConfigFileList();
+ for(const auto& externalConfigFile : externalConfigFiles)
{
- files.push_back(externalConfigFiles[i].file);
+ files.emplace_back(externalConfigFile.file);
}
std::sort(files.begin(), files.end());
@@ -270,7 +324,6 @@ Options:
if(print_configure_db)
{
no_default_build = true;
- const auto& c = ctor::get_configuration();
for(const auto& config : c.tools)
{
std::cout << config.first << ": " << config.second << "\n";
@@ -286,7 +339,7 @@ Options:
}
bool build_all{!no_default_build};
- for(const auto& arg : opt.arguments())
+ for(const auto& arg : arguments)
{
if(arg == "configure")
{
diff --git a/src/pointerlist.cc b/src/pointerlist.cc
new file mode 100644
index 0000000..c0242f7
--- /dev/null
+++ b/src/pointerlist.cc
@@ -0,0 +1,123 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#include "pointerlist.h"
+
+#include <cstring>
+
+PointerList::PointerList(int argc, const char* const argv[])
+{
+ for(int i = 0; i < argc; ++i)
+ {
+ push_back(argv[i]);
+ }
+}
+
+std::pair<int, const char* const*> PointerList::get()
+{
+ argptrs.clear();
+ for(const auto& arg : *this)
+ {
+ argptrs.push_back(arg.data());
+ }
+ argptrs.push_back(nullptr);
+
+ return {argptrs.size() - 1, // size not counting the nullptr at the end
+ argptrs.data()};
+}
+
+EnvMap::EnvMap(const char* const env[])
+{
+ if(env == nullptr)
+ {
+ return;
+ }
+
+ auto ptr = env;
+ while(*ptr)
+ {
+ insert(std::string_view(*ptr));
+ ++ptr;
+ }
+}
+
+EnvMap::EnvMap(const char* env)
+{
+ if(env == nullptr)
+ {
+ return;
+ }
+
+ auto ptr = env;
+ while(*ptr)
+ {
+ insert(ptr);
+ ptr += strlen(ptr) + 1;
+ }
+}
+
+std::string EnvMap::operator[](const std::string& key) const
+{
+ try
+ {
+ return data.at(key);
+ }
+ catch(...)
+ {
+ return {};
+ }
+}
+
+void EnvMap::clear()
+{
+ data.clear();
+}
+
+void EnvMap::insert(std::string_view key_value)
+{
+ auto equals_sign = key_value.find('=');
+ if(equals_sign == std::string::npos)
+ {
+ insert({key_value, ""});
+ return;
+ }
+ std::string key{key_value.substr(0, equals_sign)};
+ std::string value{key_value.substr(equals_sign + 1)}; // skip '='
+ insert({key, value});
+}
+
+void EnvMap::insert(const std::pair<std::string_view, std::string_view>& item)
+{
+ data[std::string(item.first)] = item.second;
+}
+
+bool EnvMap::contains(std::string_view key) const
+{
+ return data.contains(std::string{key});
+}
+
+std::size_t EnvMap::size() const
+{
+ return data.size();
+}
+
+std::string EnvMap::stringify() const
+{
+ std::string str;
+ for(const auto& [key, value] : data)
+ {
+ str += key + "=" + value + '\0';
+ }
+ str += '\0';
+ return str;
+}
+
+std::pair<int, const char* const*> EnvMap::get()
+{
+ pointerlist.clear();
+ for(const auto& [key, value] : data)
+ {
+ pointerlist.push_back(key + "=" + value + '\0');
+ }
+ return pointerlist.get();
+}
diff --git a/src/pointerlist.h b/src/pointerlist.h
new file mode 100644
index 0000000..988bb26
--- /dev/null
+++ b/src/pointerlist.h
@@ -0,0 +1,74 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#pragma once
+
+#include <string>
+#include <vector>
+#include <deque>
+#include <utility>
+#include <map>
+
+//! Maintains an (owning) list of string args and converts them to argc/argv
+//! compatible arguments on request.
+//! The returned pointers are guaranteed to be valid as long as the PointerList
+//! object lifetime is not exceeded.
+class PointerList
+ : public std::deque<std::string>
+{
+public:
+ PointerList() = default;
+ PointerList(int argc, const char* const argv[]);
+
+ //! Returns argc/argv pair from the current list of args
+ //! The argv entry after the last is a nullptr (not included in the argc)
+ std::pair<int, const char* const*> get();
+
+private:
+ std::vector<const char*> argptrs;
+};
+
+
+//! Maintains an owning map of strings representing the env.
+class EnvMap
+{
+public:
+ EnvMap() = default;
+
+ //! Initialize from an array of pointers to key=value\0 strings terminated
+ //! by \0
+ EnvMap(const char* const env[]);
+
+ //! Initialize from a string of the format
+ //! key1=val\0key2=val\0...keyN=val\0\0
+ EnvMap(const char* env);
+
+ std::string operator[](const std::string& key) const;
+
+ //! Clear all items in the map
+ void clear();
+
+ //! Insert string from format: key=value
+ void insert(std::string_view key_value);
+
+ //! Regular map insert
+ void insert(const std::pair<std::string_view, std::string_view>& item);
+
+ //! Checks if the container contains element with specific key
+ bool contains(std::string_view key) const;
+
+ std::size_t size() const;
+
+ //! Return string with the following format:
+ //! key1=val\0key2=val\0...keyN=val\0\0
+ std::string stringify() const;
+
+ //! Returns the map as argc/argv pair where each pointer points to a string
+ //! of the format key=value\0 and is terminated with a nullptr
+ std::pair<int, const char* const*> get();
+
+private:
+ std::map<std::string, std::string> data{};
+
+ PointerList pointerlist;
+};
diff --git a/src/rebuild.cc b/src/rebuild.cc
index f0d52d9..d62e998 100644
--- a/src/rebuild.cc
+++ b/src/rebuild.cc
@@ -8,6 +8,8 @@
#include <algorithm>
#include <source_location>
#include <cstring>
+#include <span>
+#include <vector>
#include "configure.h"
#include "ctor.h"
@@ -17,34 +19,38 @@
#include "tools.h"
#include "util.h"
-std::array<BuildConfigurationEntry, 1024> configFiles;
-std::size_t numConfigFiles{0};
+std::vector<BuildConfigurationEntry>& getConfigFileList()
+{
+ static std::vector<BuildConfigurationEntry> configFiles;
+ return configFiles;
+}
+
+std::vector<ExternalConfigurationEntry>& getExternalConfigFileList()
+{
+ static std::vector<ExternalConfigurationEntry> externalConfigFiles;
+ return externalConfigFiles;
+}
namespace ctor {
-int reg(ctor::build_configurations (*cb)(const ctor::settings&),
+int reg(std::function<ctor::build_configurations (const ctor::settings&)> cb,
const std::source_location location)
{
- // NOTE: std::cout cannot be used here
- if(numConfigFiles >= configFiles.size())
- {
- fprintf(stderr, "Max %d build configurations currently supported.\n",
- (int)configFiles.size());
- exit(1);
- }
+ BuildConfigurationEntry entry;
auto loc = std::filesystem::path(location.file_name());
if(loc.is_absolute())
{
auto pwd = std::filesystem::current_path();
auto rel = std::filesystem::relative(loc, pwd);
- configFiles[numConfigFiles].file = strdup(rel.string().data()); // NOTE: This intentionally leaks memory
+ entry.file = rel.string();
}
else
{
- configFiles[numConfigFiles].file = location.file_name();
+ entry.file = location.file_name();
}
- configFiles[numConfigFiles].cb = cb;
- ++numConfigFiles;
+ entry.cb = cb;
+ auto& configFiles = getConfigFileList();
+ configFiles.push_back(entry);
return 0;
}
@@ -52,80 +58,50 @@ int reg(ctor::build_configurations (*cb)(const ctor::settings&),
int reg(const char* location)
{
- // NOTE: std::cout cannot be used here
- if(numConfigFiles >= configFiles.size())
- {
- fprintf(stderr, "Max %d build configurations currently supported.\n",
- (int)configFiles.size());
- exit(1);
- }
+ BuildConfigurationEntry entry;
- configFiles[numConfigFiles].file = location;
- configFiles[numConfigFiles].cb =
- [](const ctor::settings&){ return std::vector<ctor::build_configuration>{}; };
- ++numConfigFiles;
+ entry.file = location;
+ entry.cb =
+ [](const ctor::settings&)
+ {
+ return std::vector<ctor::build_configuration>{};
+ };
+ auto& configFiles = getConfigFileList();
+ configFiles.push_back(entry);
return 0;
}
int unreg(const char* location)
{
- std::size_t found{0};
- for(std::size_t i = 0; i < numConfigFiles;)
- {
- if(std::string(location) == configFiles[i].file)
- {
- ++found;
- for(std::size_t j = i; j < numConfigFiles; ++j)
- {
- configFiles[j] = configFiles[j + 1];
- }
- --numConfigFiles;
- }
- else
- {
- ++i;
- }
- }
-
- for(std::size_t i = 0; i < numExternalConfigFiles;)
- {
- if(std::string(location) == externalConfigFiles[i].file)
- {
- ++found;
- for(std::size_t j = i; j < numExternalConfigFiles; ++j)
- {
- externalConfigFiles[j] = externalConfigFiles[j + 1];
- }
- --numExternalConfigFiles;
- }
- else
- {
- ++i;
- }
- }
-
- return found;
+ auto& configFiles = getConfigFileList();
+ auto erasedConfigs =
+ std::erase_if(configFiles,
+ [&](const BuildConfigurationEntry& entry)
+ {
+ return entry.file == location;
+ });
+
+ auto& externalConfigFiles = getExternalConfigFileList();
+ auto erasedExternals =
+ std::erase_if(externalConfigFiles,
+ [&](const ExternalConfigurationEntry& entry)
+ {
+ return entry.file == location;
+ });
+
+ return static_cast<int>(erasedConfigs) + static_cast<int>(erasedExternals);
}
-std::array<ExternalConfigurationEntry, 1024> externalConfigFiles;
-std::size_t numExternalConfigFiles{0};
-
namespace ctor {
-int reg(ctor::external_configurations (*cb)(const ctor::settings&),
+int reg(std::function<ctor::external_configurations (const ctor::settings&)> cb,
const std::source_location location)
{
- // NOTE: std::cout cannot be used here
- if(numExternalConfigFiles >= externalConfigFiles.size())
- {
- fprintf(stderr, "Max %d external configurations currently supported.\n",
- (int)externalConfigFiles.size());
- exit(1);
- }
-
- externalConfigFiles[numExternalConfigFiles].file = location.file_name();
- externalConfigFiles[numExternalConfigFiles].cb = cb;
- ++numExternalConfigFiles;
+ ExternalConfigurationEntry entry;
+ entry.file = location.file_name();
+ entry.cb = cb;
+ auto& externalConfigFiles = getExternalConfigFileList();
+ externalConfigFiles.push_back(entry);
return 0;
}
@@ -150,11 +126,14 @@ bool contains(const std::vector<ctor::source>& sources, const std::string& file)
bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[],
bool relaunch_allowed)
{
+ auto args_span = std::span(argv, static_cast<std::size_t>(argc));
+
using namespace std::string_literals;
+ const auto& configFiles = getConfigFileList();
if(global_settings.verbose > 1)
{
- std::cout << "Recompile check (" << numConfigFiles << "):\n";
+ std::cout << "Recompile check (" << configFiles.size() << "):\n";
}
ctor::build_configuration config;
@@ -163,41 +142,44 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[
config.name = "ctor";
config.system = ctor::output_system::build;
- config.flags.cxxflags.push_back({ctor::cxx_opt::optimization, "3"});
- config.flags.cxxflags.push_back({ctor::cxx_opt::cpp_std, "c++20"});
+ config.flags.cxxflags.emplace_back(ctor::cxx_opt::optimization, "3");
+ config.flags.cxxflags.emplace_back(ctor::cxx_opt::cpp_std, "c++20");
const auto& c = ctor::get_configuration();
if(c.has(ctor::cfg::ctor_includedir))
{
- config.flags.cxxflags.push_back({ctor::cxx_opt::include_path,
- c.get(ctor::cfg::ctor_includedir)});
+ config.flags.cxxflags.emplace_back(ctor::cxx_opt::include_path,
+ c.get(ctor::cfg::ctor_includedir));
}
if(c.has(ctor::cfg::ctor_libdir))
{
- config.flags.ldflags.push_back({ctor::ld_opt::library_path, c.get(ctor::cfg::ctor_libdir)});
+ config.flags.ldflags.emplace_back(ctor::ld_opt::library_path,
+ c.get(ctor::cfg::ctor_libdir));
}
- config.flags.ldflags.push_back({ctor::ld_opt::link, "ctor"});
- config.flags.ldflags.push_back({ctor::ld_opt::strip});
- config.flags.ldflags.push_back({ctor::ld_opt::threads});
+ config.flags.ldflags.emplace_back(ctor::ld_opt::link, "ctor");
+ config.flags.ldflags.emplace_back(ctor::ld_opt::threads);
ctor::settings settings{global_settings};
settings.verbose = -1; // Make check completely silent.
- settings.builddir += "/ctor"; // override builddir to use ctor subdir
+
+ // override builddir to use ctor subdir
+ auto ctor_builddir = std::filesystem::path(settings.builddir) / "ctor";
+ settings.builddir = ctor_builddir.string();
{
std::filesystem::path buildfile = settings.builddir;
- std::filesystem::path currentfile = argv[0];
+ std::filesystem::path currentfile = args_span[0];
config.target = std::filesystem::relative(currentfile, buildfile).string();
}
if(std::filesystem::exists(configurationFile))
{
- config.sources.push_back(configurationFile.string());
+ config.sources.emplace_back(configurationFile.string());
}
- for(std::size_t i = 0; i < numConfigFiles; ++i)
+ for(const auto& configFile : configFiles)
{
- std::string location = configFiles[i].file;
+ std::string location = configFile.file;
if(global_settings.verbose > 1)
{
std::cout << " - " << location << "\n";
@@ -206,13 +188,14 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[
// Ensure that files containing multiple configurations are only added once.
if(!contains(config.sources, location))
{
- config.sources.push_back(location);
+ config.sources.emplace_back(location);
}
}
- for(std::size_t i = 0; i < numExternalConfigFiles; ++i)
+ const auto& externalConfigFiles = getExternalConfigFileList();
+ for(const auto& externalConfigFile : externalConfigFiles)
{
- std::string location = externalConfigFiles[i].file;
+ std::string location = externalConfigFile.file;
if(global_settings.verbose > 1)
{
std::cout << " - " << location << "\n";
@@ -221,11 +204,11 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[
// Ensure that files containing multiple configurations are only added once.
if(!contains(config.sources, location))
{
- config.sources.push_back(location);
+ config.sources.emplace_back(location);
}
}
- auto tasks = taskFactory({config}, settings, {});
+ auto tasks = taskFactory({config}, settings, {}, true);
for(auto task : tasks)
{
@@ -261,16 +244,17 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[
if(reconfigure)
{
std::vector<std::string> args;
- args.push_back("reconfigure");
+ args.emplace_back("reconfigure");
if(!relaunch_allowed)
{
- args.push_back("--no-rerun");
+ args.emplace_back("--no-rerun");
}
- for(int i = 1; i < argc; ++i)
+ for(std::size_t i = 1; i < args_span.size(); ++i)
{
- args.push_back(argv[i]);
+ args.emplace_back(args_span[i]);
}
- auto ret = execute(argv[0], args);
+
+ auto ret = execute(settings, args_span[0], args);
//if(ret != 0)
{
exit(ret);
diff --git a/src/rebuild.h b/src/rebuild.h
index efa6d42..8e0c78a 100644
--- a/src/rebuild.h
+++ b/src/rebuild.h
@@ -5,26 +5,26 @@
#include <vector>
#include <array>
+#include <string>
+#include <functional>
#include "ctor.h"
struct BuildConfigurationEntry
{
- const char* file;
- ctor::build_configurations (*cb)(const ctor::settings&);
+ std::string file;
+ std::function<ctor::build_configurations (const ctor::settings&)> cb;
};
+std::vector<BuildConfigurationEntry>& getConfigFileList();
+
struct ExternalConfigurationEntry
{
- const char* file;
- ctor::external_configurations (*cb)(const ctor::settings&);
+ std::string file;
+ std::function<ctor::external_configurations (const ctor::settings&)> cb;
};
-extern std::array<BuildConfigurationEntry, 1024> configFiles;
-extern std::size_t numConfigFiles;
-
-extern std::array<ExternalConfigurationEntry, 1024> externalConfigFiles;
-extern std::size_t numExternalConfigFiles;
+std::vector<ExternalConfigurationEntry>& getExternalConfigFileList();
int reg(const char* location);
int unreg(const char* location);
diff --git a/src/task.cc b/src/task.cc
index 7813235..17559bf 100644
--- a/src/task.cc
+++ b/src/task.cc
@@ -3,22 +3,30 @@
// See accompanying file LICENSE for details.
#include "task.h"
-#include <unistd.h>
#include <iostream>
#include <algorithm>
+#include <utility>
-Task::Task(const ctor::build_configuration& config, const ctor::settings& settings,
- const std::string& sourceDir)
- : config(config)
+Task::Task(const ctor::build_configuration& config_, const ctor::settings& settings_,
+ std::string sourceDir_)
+ : config(config_)
, output_system(config.system)
- , settings(settings)
- , sourceDir(sourceDir)
+ , settings(settings_)
+ , sourceDir(sourceDir_)
{
}
int Task::registerDepTasks(const std::vector<std::shared_ptr<Task>>& tasks)
{
- for(const auto& depStr : depends())
+ auto dependsList = depends();
+ if(!derived())
+ {
+ for(const auto& dep : config.depends)
+ {
+ dependsList.emplace_back(dep);
+ }
+ }
+ for(const auto& depStr : dependsList)
{
bool found{false};
for(const auto& task : tasks)
@@ -45,11 +53,14 @@ int Task::registerDepTasks(const std::vector<std::shared_ptr<Task>>& tasks)
bool Task::operator==(const std::string& depStr)
{
+ std::filesystem::path generated_output = sourceDir;
+ generated_output /= target();
return
- name() == depStr ||
- target() == depStr ||
- sourceDir + "/" + target() == depStr ||
- targetFile().string() == depStr
+ (!derived() && name() == depStr) || // compare to name
+ (!derived() && config.target == depStr) || // compare to stated target (ex. foo.a)
+ target() == depStr || // compare to derived (derived to foo.lib on msvc)
+ generated_output == depStr || // not sure what this is for?!
+ targetFile().string() == depStr // compare to target output file
;
}
@@ -144,6 +155,7 @@ std::string Task::compiler() const
case ctor::output_system::build:
return c.get(ctor::cfg::build_cc, "/usr/bin/gcc");
}
+ break;
case ctor::language::cpp:
switch(outputSystem())
{
@@ -152,11 +164,15 @@ std::string Task::compiler() const
case ctor::output_system::build:
return c.get(ctor::cfg::build_cxx, "/usr/bin/g++");
}
+ break;
default:
std::cerr << "Unknown CC target type\n";
exit(1);
break;
}
+
+ std::cerr << "Unhandled compiler!\n";
+ exit(1);
}
std::vector<std::shared_ptr<Task>> Task::getDependsTasks()
diff --git a/src/task.h b/src/task.h
index 6fe1686..c884186 100644
--- a/src/task.h
+++ b/src/task.h
@@ -24,15 +24,15 @@ class Task
{
public:
Task(const ctor::build_configuration& config, const ctor::settings& settings,
- const std::string& sourceDir);
+ std::string sourceDir);
virtual ~Task() = default;
int registerDepTasks(const std::vector<std::shared_ptr<Task>>& tasks);
- virtual int registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& tasks) { return 0; }
+ virtual int registerDepTasksInner([[maybe_unused]]const std::vector<std::shared_ptr<Task>>& tasks) { return 0; }
bool operator==(const std::string& dep);
- virtual std::string name() const;
+ std::string name() const;
bool dirty();
bool ready();
int run();
@@ -79,5 +79,5 @@ protected:
ctor::language source_language{ctor::language::automatic};
ctor::output_system output_system{ctor::output_system::host};
const ctor::settings& settings;
- std::string sourceDir;
+ std::filesystem::path sourceDir;
};
diff --git a/src/task_ar.cc b/src/task_ar.cc
index 19a65ae..16e0ce8 100644
--- a/src/task_ar.cc
+++ b/src/task_ar.cc
@@ -11,20 +11,20 @@
#include "util.h"
#include "tools.h"
-TaskAR::TaskAR(const ctor::build_configuration& config,
- const ctor::settings& settings,
+TaskAR::TaskAR(const ctor::build_configuration& config_,
+ const ctor::settings& settings_,
const std::string& target,
const std::vector<std::string>& objects,
- const std::string& sourceDir)
- : Task(config, settings, sourceDir)
- , config(config)
- , settings(settings)
- , sourceDir(sourceDir)
+ const std::string& sourceDir_)
+ : Task(config_, settings_, sourceDir_)
+ , _targetFile(target)
+ , config(config_)
+ , settings(settings_)
+ , sourceDir(sourceDir_)
{
target_type = ctor::target_type::static_library;
output_system = config.system;
- _targetFile = target;
auto toolchain = getToolChain(config.system);
_targetFile = extension(toolchain, target_type, config.system, _targetFile);
for(const auto& object : objects)
@@ -34,11 +34,6 @@ TaskAR::TaskAR(const ctor::build_configuration& config,
dependsStr.push_back(objectFile.string());
}
- for(const auto& dep : config.depends)
- {
- depFiles.push_back(dep);
- }
-
flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem();
flagsFile += ".flags";
@@ -77,6 +72,15 @@ bool TaskAR::dirtyInner()
}
}
+ auto target_file_time = std::filesystem::last_write_time(targetFile());
+ for(const auto& object_file : objectFiles)
+ {
+ if(std::filesystem::last_write_time(object_file) > target_file_time)
+ {
+ return true;
+ }
+ }
+
return false;
}
@@ -105,7 +109,8 @@ int TaskAR::runInner()
if(settings.verbose == 0)
{
- std::cout << "AR => " << targetFile().string() << std::endl;
+ std::string output = "AR => " + targetFile().string() + '\n';
+ std::cout << output << std::flush;
}
const auto& c = ctor::get_configuration();
@@ -120,7 +125,13 @@ int TaskAR::runInner()
break;
}
- return execute(tool, args, {}, settings.verbose > 0);
+ auto res = execute(settings, tool, args, c.env);
+ if(res != 0)
+ {
+ std::filesystem::remove(targetFile());
+ }
+
+ return res;
}
int TaskAR::clean()
@@ -148,11 +159,6 @@ std::vector<std::string> TaskAR::depends() const
deps.push_back(objectFile.string());
}
- for(const auto& dep : config.depends)
- {
- deps.push_back(dep);
- }
-
return deps;
}
@@ -188,11 +194,10 @@ std::string TaskAR::flagsString() const
flagsStr += str;
}
}
- flagsStr += "\n";
for(const auto& dep : config.depends)
{
- if(dep != config.depends[0])
+ if(&dep != &config.depends[0])
{
flagsStr += " ";
}
diff --git a/src/task_ar.h b/src/task_ar.h
index 601afeb..69cc088 100644
--- a/src/task_ar.h
+++ b/src/task_ar.h
@@ -37,7 +37,6 @@ private:
std::string flagsString() const;
std::vector<std::filesystem::path> objectFiles;
- std::vector<std::filesystem::path> depFiles;
std::filesystem::path _targetFile;
std::filesystem::path flagsFile;
diff --git a/src/task_cc.cc b/src/task_cc.cc
index 4eb07ae..f81023f 100644
--- a/src/task_cc.cc
+++ b/src/task_cc.cc
@@ -12,16 +12,21 @@
#include "execute.h"
#include "util.h"
#include "tools.h"
-
-TaskCC::TaskCC(const ctor::build_configuration& config, const ctor::settings& settings,
- const std::string& sourceDir, const ctor::source& source)
- : Task(config, settings, sourceDir)
- , config(config)
- , settings(settings)
- , sourceDir(sourceDir)
+#include "deps.h"
+
+TaskCC::TaskCC(const ctor::build_configuration& config_, const ctor::settings& settings_,
+ const std::string& sourceDir_, const ctor::source& source)
+ : Task(config_, settings_, sourceDir_)
+ , sourceFile(sourceDir_)
+ , config(config_)
+ , settings(settings_)
+ , sourceDir(sourceDir_)
, _source(source)
{
- sourceFile = sourceDir;
+ if(source.source_type == ctor::source_type::generated)
+ {
+ sourceFile = std::filesystem::path(settings.builddir) / sourceFile;
+ }
sourceFile /= source.file;
std::filesystem::path base = sourceFile.parent_path();
@@ -89,11 +94,6 @@ int TaskCC::registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& task
return 0;
}
-std::string TaskCC::name() const
-{
- return {};
-}
-
bool TaskCC::dirtyInner()
{
if(!std::filesystem::exists(sourceFile))
@@ -136,7 +136,8 @@ bool TaskCC::dirtyInner()
}
}
- auto depList = readDeps(depsFile.string());
+ auto toolchain = getToolChain(config.system);
+ auto depList = readDeps(depsFile.string(), toolchain);
for(const auto& dep : depList)
{
if(!std::filesystem::exists(dep) ||
@@ -175,25 +176,59 @@ int TaskCC::runInner()
if(settings.verbose == 0)
{
+ std::string output;
switch(sourceLanguage())
{
case ctor::language::c:
- std::cout << "CC ";
+ output = "CC ";
break;
case ctor::language::cpp:
- std::cout << "CXX ";
+ output = "CXX ";
break;
case ctor::language::automatic:
case ctor::language::assembler:
// Only c/c++ handled by this task type.
break;
}
- std::cout <<
- sourceFile.lexically_normal().string() << " => " <<
- targetFile().lexically_normal().string() << std::endl;
+ output += sourceFile.lexically_normal().string() + " => " +
+ targetFile().lexically_normal().string() + '\n';
+ std::cout << output << std::flush;
+ }
+
+ auto toolchain = getToolChain(config.system);
+ const auto& cfg = ctor::get_configuration();
+ switch(sourceLanguage())
+ {
+ case ctor::language::c:
+ {
+ auto cflags = cfg.getenv("CFLAGS");
+ if(!cflags.empty())
+ {
+ append(args, c_option(toolchain, ctor::c_opt::custom, cflags));
+ }
+ }
+ break;
+ case ctor::language::cpp:
+ {
+ auto cxxflags = cfg.getenv("CXXFLAGS");
+ if(!cxxflags.empty())
+ {
+ append(args, cxx_option(toolchain, ctor::cxx_opt::custom, cxxflags));
+ }
+ }
+ break;
+ case ctor::language::automatic:
+ case ctor::language::assembler:
+ // Only c/c++ handled by this task type.
+ break;
+ }
+ auto res = execute(settings, compiler(), args, cfg.env);
+ if(res != 0)
+ {
+ std::filesystem::remove(targetFile());
}
- return execute(compiler(), args, {}, settings.verbose > 0);
+ return res;
}
int TaskCC::clean()
@@ -221,6 +256,10 @@ int TaskCC::clean()
std::vector<std::string> TaskCC::depends() const
{
+ if(_source.source_type == ctor::source_type::generated)
+ {
+ return {sourceFile.string()};
+ }
return {};
}
@@ -286,6 +325,7 @@ std::vector<std::string> TaskCC::flags() const
exit(1);
break;
}
+
}
std::string TaskCC::flagsString() const
@@ -309,7 +349,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const
{
case ctor::language::c:
{
- append(args, c_option(toolchain, ctor::c_opt::generate_dep_tree));
+ append(args, c_option(toolchain, ctor::c_opt::generate_dep_tree, depsFile.string()));
if(std::filesystem::path(config.target).extension() == ".so")
{
@@ -337,6 +377,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const
path = (sourceDir / path).lexically_normal();
append(args, c_option(toolchain,
ctor::c_opt::include_path, path.string()));
+ continue;
}
}
break;
@@ -351,7 +392,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const
case ctor::language::cpp:
{
- append(args, cxx_option(toolchain, ctor::cxx_opt::generate_dep_tree));
+ append(args, cxx_option(toolchain, ctor::cxx_opt::generate_dep_tree, depsFile.string()));
if(std::filesystem::path(config.target).extension() == ".so")
{
@@ -379,6 +420,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const
path = (sourceDir / path).lexically_normal();
append(args, cxx_option(toolchain,
ctor::cxx_opt::include_path, path.string()));
+ continue;
}
}
break;
diff --git a/src/task_cc.h b/src/task_cc.h
index 6c4683a..2299fcd 100644
--- a/src/task_cc.h
+++ b/src/task_cc.h
@@ -21,7 +21,6 @@ public:
int registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& tasks) override;
- std::string name() const override;
bool dirtyInner() override;
int runInner() override;
diff --git a/src/task_fn.cc b/src/task_fn.cc
index b11ff15..69e36ac 100644
--- a/src/task_fn.cc
+++ b/src/task_fn.cc
@@ -11,45 +11,129 @@
#include "execute.h"
#include "util.h"
-TaskFn::TaskFn(const ctor::build_configuration& config, const ctor::settings& settings,
- const std::string& sourceDir, const ctor::source& source)
- : Task(config, settings, sourceDir)
- , config(config)
- , settings(settings)
+TaskFn::TaskFn(const ctor::build_configuration& config_,
+ const ctor::settings& settings_,
+ const std::string& sourceDir_,
+ const ctor::source& source)
+ : Task(config_, settings_, sourceDir_)
+ , config(config_)
+ , settings(settings_)
{
- sourceFile = sourceDir;
- sourceFile /= source.file;
-
- std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceFile.parent_path());
-
+ std::filesystem::create_directories(std::filesystem::path(settings.builddir) /
+ sourceDir.parent_path());
target_type = config.type;
source_language = source.language;
- if(source.output.empty())
+ if(std::holds_alternative<ctor::GeneratorOneToOne>(config.function))
{
- std::cerr << "Missing output file for functional target\n";
- exit(1);
+ if(source.source_type == ctor::source_type::generated)
+ {
+ sourceFile = std::filesystem::path(settings.builddir);
+ }
+ sourceFile /= sourceDir / source.file;
+
+ std::filesystem::path base = sourceFile.parent_path();
+
+ if(source.output.empty())
+ {
+ std::cerr << "Missing target/output file for functional target.\n";
+ exit(1);
+ }
+ _targetFile = base / source.output;
}
+ else if(std::holds_alternative<ctor::GeneratorManyToOne>(config.function))
+ {
+ for(const auto& src : config.sources)
+ {
+ std::filesystem::path _src;
+ if(src.source_type == ctor::source_type::generated)
+ {
+ _src = std::filesystem::path(settings.builddir);
+ }
+ _src /= sourceDir / src.file;
+ sources.push_back(_src.string());
+ }
- _targetFile = source.output;
+ std::filesystem::path base = sourceDir;
+ if(config.target.empty())
+ {
+ std::cerr << "Missing target file for functional target\n";
+ exit(1);
+ }
+ _targetFile = base / config.target;
+ sourceListFile = targetFile().parent_path() / targetFile().filename();
+ sourceListFile += ".sources";
+ }
}
bool TaskFn::dirtyInner()
{
- if(!std::filesystem::exists(sourceFile))
+ if(!std::filesystem::exists(targetFile()))
{
- //std::cout << "Missing source file: " << std::string(sourceFile) << "\n";
+ //std::cout << "Missing targetFile\n";
return true;
}
- if(!std::filesystem::exists(targetFile()))
+ if(std::holds_alternative<ctor::GeneratorManyToOne>(config.function))
+ {
+ if(!std::filesystem::exists(sourceListFile))
+ {
+ //std::cout << "Missing sourceListFile\n";
+ return true;
+ }
+
+ {
+ auto lastSourceList = readFile(sourceListFile.string());
+ if(sourceListString() != lastSourceList)
+ {
+ //std::cout << "The compiler sourceList changed\n";
+ return true;
+ }
+ }
+ }
+
+ std::filesystem::file_time_type last_changed{};
+ bool missing{false};
+ if(std::holds_alternative<ctor::GeneratorOneToOne>(config.function))
+ {
+ if(!std::filesystem::exists(sourceFile))
+ {
+ missing = true;
+ }
+ last_changed = std::filesystem::last_write_time(sourceFile);
+ }
+ else if(std::holds_alternative<ctor::GeneratorManyToOne>(config.function))
+ {
+ bool first{true};
+ for(const auto& source : sources)
+ {
+ if(!std::filesystem::exists(source))
+ {
+ missing |= true;
+ }
+ else
+ {
+ if(first)
+ {
+ last_changed = std::filesystem::last_write_time(source);
+ }
+ else
+ {
+ last_changed =
+ std::max(last_changed,
+ std::filesystem::last_write_time(source));
+ }
+ }
+ first = false;
+ }
+ }
+
+ if(missing)
{
- //std::cout << "Missing targetFile\n";
return true;
}
- if(std::filesystem::last_write_time(sourceFile) >
- std::filesystem::last_write_time(targetFile()))
+ if(last_changed > std::filesystem::last_write_time(targetFile()))
{
//std::cout << "The targetFile older than sourceFile\n";
return true;
@@ -60,23 +144,48 @@ bool TaskFn::dirtyInner()
int TaskFn::runInner()
{
- if(!std::filesystem::exists(sourceFile))
+ if(std::holds_alternative<ctor::GeneratorManyToOne>(config.function))
{
- std::cout << "Missing source file: " << sourceFile.string() << "\n";
- return 1;
+ // Write sourceList to file.
+ std::ofstream sourceListStream(sourceListFile.string());
+ sourceListStream << sourceListString();
}
if(settings.verbose >= 0)
{
- std::cout << "Fn" << " " <<
- sourceFile.lexically_normal().string() << " => " <<
- targetFile().lexically_normal().string() << std::endl;
+ std::string output = "Fn " +
+ sourceFile.lexically_normal().string() + " => " +
+ targetFile().lexically_normal().string() + '\n';
+ std::cout << output << std::flush;
+ }
+
+ int res{};
+ if(std::holds_alternative<std::monostate>(config.function))
+ {
+ }
+ else if(std::holds_alternative<ctor::GeneratorOneToOne>(config.function))
+ {
+ auto func = std::get<ctor::GeneratorOneToOne>(config.function);
+ res = func(sourceFile.string(),
+ targetFile().string(),
+ config,
+ settings);
+ }
+ else if(std::holds_alternative<ctor::GeneratorManyToOne>(config.function))
+ {
+ auto func = std::get<ctor::GeneratorManyToOne>(config.function);
+ res = func(sources,
+ targetFile().string(),
+ config,
+ settings);
+ }
+
+ if(res != 0)
+ {
+ std::filesystem::remove(targetFile());
}
- return config.function(sourceFile.string(),
- targetFile().string(),
- config,
- settings);
+ return res;
}
int TaskFn::clean()
@@ -87,22 +196,54 @@ int TaskFn::clean()
std::filesystem::remove(targetFile());
}
+ if(std::holds_alternative<ctor::GeneratorManyToOne>(config.function))
+ {
+ if(std::filesystem::exists(sourceListFile))
+ {
+ std::cout << "Removing " << sourceListFile.string() << "\n";
+ std::filesystem::remove(sourceListFile);
+ }
+ }
+
return 0;
}
std::vector<std::string> TaskFn::depends() const
{
- return {};
+ std::vector<std::string> deps;
+ if(std::holds_alternative<ctor::GeneratorManyToOne>(config.function))
+ {
+ for(const auto& src : config.sources)
+ {
+ if(src.source_type == ctor::source_type::generated)
+ {
+ std::filesystem::path _src = std::filesystem::path(settings.builddir);
+ _src /= src.file;
+ deps.push_back(_src.string());
+ }
+ }
+ }
+ return deps;
}
std::string TaskFn::target() const
{
- return _targetFile;
+ return _targetFile.string();
}
std::filesystem::path TaskFn::targetFile() const
{
- return std::filesystem::path(settings.builddir) / sourceDir / _targetFile;
+ return std::filesystem::path(settings.builddir) / _targetFile;
+}
+
+std::string TaskFn::sourceListString() const
+{
+ std::string sourceListStr;
+ for(const auto& source : config.sources)
+ {
+ sourceListStr += " " + source.file;
+ }
+ return sourceListStr;
}
bool TaskFn::derived() const
diff --git a/src/task_fn.h b/src/task_fn.h
index 1bad32c..84b4c2a 100644
--- a/src/task_fn.h
+++ b/src/task_fn.h
@@ -28,6 +28,7 @@ public:
std::string target() const override;
std::filesystem::path targetFile() const override;
+ std::string sourceListString() const;
bool derived() const override;
std::string toJSON() const override;
@@ -37,8 +38,10 @@ public:
protected:
std::filesystem::path sourceFile;
std::filesystem::path _targetFile;
+ std::filesystem::path sourceListFile;
const ctor::build_configuration& config;
const ctor::settings& settings;
- std::filesystem::path sourceDir;
+
+ std::vector<std::string> sources;
};
diff --git a/src/task_ld.cc b/src/task_ld.cc
index b0aa4ae..a1cff34 100644
--- a/src/task_ld.cc
+++ b/src/task_ld.cc
@@ -11,15 +11,16 @@
#include "util.h"
#include "tools.h"
-TaskLD::TaskLD(const ctor::build_configuration& config,
- const ctor::settings& settings,
+TaskLD::TaskLD(const ctor::build_configuration& config_,
+ const ctor::settings& settings_,
const std::string& target,
const std::vector<std::string>& objects,
- const std::string& sourceDir)
- : Task(config, settings, sourceDir)
- , config(config)
- , settings(settings)
- , sourceDir(sourceDir)
+ const std::string& sourceDir_,
+ bool is_self_)
+ : Task(config_, settings_, sourceDir_)
+ , config(config_)
+ , settings(settings_)
+ , is_self(is_self_)
{
target_type = config.type;
output_system = config.system;
@@ -38,13 +39,8 @@ TaskLD::TaskLD(const ctor::build_configuration& config,
objectFiles.push_back(objectFile);
dependsStr.push_back(objectFile.string());
}
-
- for(const auto& dep : config.depends)
- {
- depFiles.push_back(dep);
- }
-
- flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem();
+ auto cleaned_source_dir = cleanUp(sourceDir.string());
+ flagsFile = std::filesystem::path(settings.builddir) / cleaned_source_dir / targetFile().stem();
flagsFile += ".flags";
source_language = ctor::language::c;
@@ -81,6 +77,15 @@ bool TaskLD::dirtyInner()
}
}
+ auto target_file_time = std::filesystem::last_write_time(targetFile());
+ for(const auto& object_file : objectFiles)
+ {
+ if(std::filesystem::last_write_time(object_file) > target_file_time)
+ {
+ return true;
+ }
+ }
+
return false;
}
@@ -121,11 +126,24 @@ int TaskLD::runInner()
if(settings.verbose == 0)
{
- std::cout << "LD => " << targetFile().string() << std::endl;
+ std::string output = "LD => " + targetFile().string() + '\n';
+ std::cout << output << std::flush;
}
auto tool = compiler();
- return execute(tool, args, {}, settings.verbose > 0);
+ const auto& cfg = ctor::get_configuration();
+ auto ldflags = cfg.getenv("LDFLAGS");
+ if(!ldflags.empty())
+ {
+ append(args, ld_option(toolchain, ctor::ld_opt::custom, ldflags));
+ }
+ auto res = execute(settings, tool, args, cfg.env, is_self);
+ if(res != 0)
+ {
+ std::filesystem::remove(targetFile());
+ }
+
+ return res;
}
int TaskLD::clean()
@@ -153,11 +171,6 @@ std::vector<std::string> TaskLD::depends() const
deps.push_back(objectFile.string());
}
- for(const auto& depFile : depFiles)
- {
- deps.push_back(depFile.string());
- }
-
return deps;
}
@@ -193,11 +206,10 @@ std::string TaskLD::flagsString() const
flagsStr += str;
}
}
- flagsStr += "\n";
for(const auto& dep : config.depends)
{
- if(dep != config.depends[0])
+ if(&dep != &config.depends[0])
{
flagsStr += " ";
}
diff --git a/src/task_ld.h b/src/task_ld.h
index dbe7db1..9dce4b4 100644
--- a/src/task_ld.h
+++ b/src/task_ld.h
@@ -18,7 +18,8 @@ public:
const ctor::settings& settings,
const std::string& target,
const std::vector<std::string>& objects,
- const std::string& _sourceDir);
+ const std::string& _sourceDir,
+ bool is_self);
virtual ~TaskLD() = default;
bool dirtyInner() override;
@@ -37,11 +38,10 @@ private:
std::string flagsString() const;
std::vector<std::filesystem::path> objectFiles;
- std::vector<std::filesystem::path> depFiles;
std::filesystem::path _targetFile;
std::filesystem::path flagsFile;
const ctor::build_configuration& config;
const ctor::settings& settings;
- std::string sourceDir;
+ bool is_self;
};
diff --git a/src/task_so.cc b/src/task_so.cc
index ba96388..090105a 100644
--- a/src/task_so.cc
+++ b/src/task_so.cc
@@ -11,15 +11,15 @@
#include "util.h"
#include "tools.h"
-TaskSO::TaskSO(const ctor::build_configuration& config,
- const ctor::settings& settings,
+TaskSO::TaskSO(const ctor::build_configuration& config_,
+ const ctor::settings& settings_,
const std::string& target,
const std::vector<std::string>& objects,
- const std::string& sourceDir)
- : Task(config, settings, sourceDir)
- , config(config)
- , settings(settings)
- , sourceDir(sourceDir)
+ const std::string& sourceDir_)
+ : Task(config_, settings_, sourceDir_)
+ , config(config_)
+ , settings(settings_)
+ , sourceDir(sourceDir_)
{
std::filesystem::path base = sourceDir;
@@ -36,11 +36,6 @@ TaskSO::TaskSO(const ctor::build_configuration& config,
dependsStr.push_back(objectFile.string());
}
- for(const auto& dep : config.depends)
- {
- depFiles.push_back(dep);
- }
-
flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem();
flagsFile += ".flags";
@@ -79,6 +74,15 @@ bool TaskSO::dirtyInner()
}
}
+ auto target_file_time = std::filesystem::last_write_time(targetFile());
+ for(const auto& object_file : objectFiles)
+ {
+ if(std::filesystem::last_write_time(object_file) > target_file_time)
+ {
+ return true;
+ }
+ }
+
return false;
}
@@ -110,11 +114,24 @@ int TaskSO::runInner()
if(settings.verbose == 0)
{
- std::cout << "LD => " << targetFile().string() << std::endl;
+ std::string output = "LD => " + targetFile().string() + '\n';
+ std::cout << output << std::flush;
}
auto tool = compiler();
- return execute(tool, args, {}, settings.verbose > 0);
+ const auto& cfg = ctor::get_configuration();
+ auto ldflags = cfg.getenv("LDFLAGS");
+ if(!ldflags.empty())
+ {
+ append(args, ld_option(toolchain, ctor::ld_opt::custom, ldflags));
+ }
+ auto res = execute(settings, tool, args, cfg.env);
+ if(res != 0)
+ {
+ std::filesystem::remove(targetFile());
+ }
+
+ return res;
}
int TaskSO::clean()
@@ -142,11 +159,6 @@ std::vector<std::string> TaskSO::depends() const
deps.push_back(objectFile.string());
}
- for(const auto& depFile : depFiles)
- {
- deps.push_back(depFile.string());
- }
-
return deps;
}
@@ -182,11 +194,10 @@ std::string TaskSO::flagsString() const
flagsStr += str;
}
}
- flagsStr += "\n";
for(const auto& dep : config.depends)
{
- if(dep != config.depends[0])
+ if(&dep != &config.depends[0])
{
flagsStr += " ";
}
diff --git a/src/task_so.h b/src/task_so.h
index 60af225..3fc2e2f 100644
--- a/src/task_so.h
+++ b/src/task_so.h
@@ -37,7 +37,6 @@ private:
std::string flagsString() const;
std::vector<std::filesystem::path> objectFiles;
- std::vector<std::filesystem::path> depFiles;
std::filesystem::path _targetFile;
std::filesystem::path flagsFile;
diff --git a/src/tasks.cc b/src/tasks.cc
index 61c130b..fb7d72b 100644
--- a/src/tasks.cc
+++ b/src/tasks.cc
@@ -8,6 +8,7 @@
#include <list>
#include <iostream>
#include <algorithm>
+#include <span>
#include "ctor.h"
#include "task.h"
@@ -24,20 +25,22 @@
const std::deque<Target>& getTargets(const ctor::settings& settings,
bool resolve_externals)
{
+ auto& config_files = getConfigFileList();
+
static bool initialised{false};
static std::deque<Target> targets;
if(!initialised)
{
const auto& externals = ctor::get_configuration().externals;
- for(std::size_t i = 0; i < numConfigFiles; ++i)
+ for(const auto& config_file : config_files)
{
std::string path =
- std::filesystem::path(configFiles[i].file).parent_path().string();
+ std::filesystem::path(config_file.file).parent_path().string();
if(settings.verbose > 1)
{
- std::cout << configFiles[i].file << " in path " << path << "\n";
+ std::cout << config_file.file << " in path " << path << "\n";
}
- auto configs = configFiles[i].cb(settings);
+ auto configs = config_file.cb(settings);
for(auto& config : configs)
{
if(resolve_externals)
@@ -72,7 +75,8 @@ const std::deque<Target>& getTargets(const ctor::settings& settings,
std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& config,
const ctor::settings& settings,
- const std::string& sourceDir)
+ const std::string& sourceDir,
+ bool is_self)
{
std::vector<std::shared_ptr<Task>> tasks;
@@ -81,7 +85,7 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration&
ctor::target_type target_type{config.type};
if(target_type == ctor::target_type::automatic)
{
- if(config.function != nullptr)
+ if(!std::holds_alternative<std::monostate>(config.function))
{
target_type = ctor::target_type::function;
}
@@ -91,25 +95,41 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration&
}
}
+ const auto& c = ctor::get_configuration();
std::vector<std::string> objects;
if(target_type != ctor::target_type::function)
{
- for(const auto& file : config.sources)
+ for(const auto& source : config.sources)
{
- auto task = std::make_shared<TaskCC>(config, settings, sourceDir, file);
- tasks.push_back(task);
- objects.push_back(task->targetFile().string());
+ if(source.toolchain == ctor::toolchain::any ||
+ (config.system == ctor::output_system::build && source.toolchain == c.build_toolchain) ||
+ (config.system == ctor::output_system::host && source.toolchain == c.host_toolchain))
+ {
+ auto task = std::make_shared<TaskCC>(config, settings, sourceDir, source);
+ tasks.push_back(task);
+ objects.push_back(task->targetFile().string());
+ }
}
}
#ifndef BOOTSTRAP
else
{
- for(const auto& file : config.sources)
+ bool multi{std::holds_alternative<ctor::GeneratorManyToOne>(config.function)};
+ if(multi)
{
- auto task = std::make_shared<TaskFn>(config, settings, sourceDir, file);
+ auto task = std::make_shared<TaskFn>(config, settings, sourceDir, "");
tasks.push_back(task);
objects.push_back(task->target());
}
+ else
+ {
+ for(const auto& source : config.sources)
+ {
+ auto task = std::make_shared<TaskFn>(config, settings, sourceDir, source);
+ tasks.push_back(task);
+ objects.push_back(task->target());
+ }
+ }
}
#endif
@@ -145,7 +165,7 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration&
case ctor::target_type::executable:
case ctor::target_type::unit_test:
tasks.push_back(std::make_shared<TaskLD>(config, settings, config.target,
- objects, sourceDir));
+ objects, sourceDir, is_self));
break;
case ctor::target_type::object:
@@ -160,7 +180,8 @@ std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration&
return tasks;
}
-std::shared_ptr<Task> getNextTask(const std::vector<std::shared_ptr<Task>>& allTasks,
+std::shared_ptr<Task> getNextTask([[maybe_unused]]const ctor::settings& settings,
+ [[maybe_unused]]const std::vector<std::shared_ptr<Task>>& allTasks,
std::vector<std::shared_ptr<Task>>& dirtyTasks)
{
for(auto dirtyTask = dirtyTasks.begin();
@@ -169,7 +190,7 @@ std::shared_ptr<Task> getNextTask(const std::vector<std::shared_ptr<Task>>& allT
{
auto task = *dirtyTask;
//std::cout << "Examining target " << (*dirtyTask)->target() << "\n";
- if(task->ready())
+ if(task->ready() || settings.dry_run)
{
dirtyTasks.erase(dirtyTask);
return task;
@@ -192,7 +213,7 @@ std::vector<std::shared_ptr<Task>> getTasks(const ctor::settings& settings,
std::find(std::begin(names), std::end(names), target.config.target) != std::end(names))
{
std::vector<std::string> objects;
- auto t = taskFactory(target.config, settings, target.path);
+ auto t = taskFactory(target.config, settings, target.path, false);
tasks.insert(tasks.end(), t.begin(), t.end());
}
}
diff --git a/src/tasks.h b/src/tasks.h
index cc34f56..97fc84d 100644
--- a/src/tasks.h
+++ b/src/tasks.h
@@ -23,7 +23,8 @@ const std::deque<Target>& getTargets(const ctor::settings& settings,
//! fulfilled.
//! The returned task is removed from the dirty list.
//! Return nullptr if no dirty task is ready.
-std::shared_ptr<Task> getNextTask(const std::vector<std::shared_ptr<Task>>& allTasks,
+std::shared_ptr<Task> getNextTask(const ctor::settings& settings,
+ const std::vector<std::shared_ptr<Task>>& allTasks,
std::vector<std::shared_ptr<Task>>& dirtyTasks);
//! Get list of tasks filtered by name including each of their direct
@@ -36,4 +37,5 @@ std::vector<std::shared_ptr<Task>> getTasks(const ctor::settings& settings,
//! link target and all its objects files (if any).
std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& config,
const ctor::settings& settings,
- const std::string& sourceDir);
+ const std::string& sourceDir,
+ bool is_self);
diff --git a/src/tools.cc b/src/tools.cc
index be94794..9dcaec6 100644
--- a/src/tools.cc
+++ b/src/tools.cc
@@ -6,6 +6,7 @@
#include <filesystem>
#include <iostream>
#include <sstream>
+#include <array>
#include <cassert>
#include <cstdio>
@@ -20,6 +21,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt)
case ctor::c_opt::output: stream << "ctor::c_opt::output"; break;
case ctor::c_opt::debug: stream << "ctor::c_opt::debug"; break;
case ctor::c_opt::warn_all: stream << "ctor::c_opt::warn_all"; break;
+ case ctor::c_opt::warn_conversion: stream << "ctor::c_opt::warn_conversion"; break;
+ case ctor::c_opt::warn_shadow: stream << "ctor::c_opt::warn_shadow"; break;
+ case ctor::c_opt::warn_extra: stream << "ctor::c_opt::warn_extra"; break;
case ctor::c_opt::warnings_as_errors: stream << "ctor::c_opt::warnings_as_errors"; break;
case ctor::c_opt::generate_dep_tree: stream << "ctor::c_opt::generate_dep_tree"; break;
case ctor::c_opt::no_link: stream << "ctor::c_opt::no_link"; break;
@@ -28,6 +32,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt)
case ctor::c_opt::optimization: stream << "ctor::c_opt::optimization"; break;
case ctor::c_opt::position_independent_code: stream << "ctor::c_opt::position_independent_code"; break;
case ctor::c_opt::position_independent_executable: stream << "ctor::c_opt::position_independent_executable"; break;
+ case ctor::c_opt::define: stream << "ctor::c_opt::define"; break;
case ctor::c_opt::custom: stream << "ctor::c_opt::custom"; break;
}
@@ -42,6 +47,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt)
case ctor::cxx_opt::output: stream << "ctor::cxx_opt::output"; break;
case ctor::cxx_opt::debug: stream << "ctor::cxx_opt::debug"; break;
case ctor::cxx_opt::warn_all: stream << "ctor::cxx_opt::warn_all"; break;
+ case ctor::cxx_opt::warn_conversion: stream << "ctor::cxx_opt::warn_conversion"; break;
+ case ctor::cxx_opt::warn_shadow: stream << "ctor::cxx_opt::warn_shadow"; break;
+ case ctor::cxx_opt::warn_extra: stream << "ctor::cxx_opt::warn_extra"; break;
case ctor::cxx_opt::warnings_as_errors: stream << "ctor::cxx_opt::warnings_as_errors"; break;
case ctor::cxx_opt::generate_dep_tree: stream << "ctor::cxx_opt::generate_dep_tree"; break;
case ctor::cxx_opt::no_link: stream << "ctor::cxx_opt::no_link"; break;
@@ -50,6 +58,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt)
case ctor::cxx_opt::optimization: stream << "ctor::cxx_opt::optimization"; break;
case ctor::cxx_opt::position_independent_code: stream << "ctor::cxx_opt::position_independent_code"; break;
case ctor::cxx_opt::position_independent_executable: stream << "ctor::cxx_opt::position_independent_executable"; break;
+ case ctor::cxx_opt::define: stream << "ctor::cxx_opt::define"; break;
case ctor::cxx_opt::custom: stream << "ctor::cxx_opt::custom"; break;
}
@@ -62,7 +71,6 @@ std::ostream& operator<<(std::ostream& stream, const ctor::ld_opt& opt)
switch(opt)
{
case ctor::ld_opt::output: stream << "ctor::ld_opt::output"; break;
- case ctor::ld_opt::strip: stream << "ctor::ld_opt::strip"; break;
case ctor::ld_opt::warn_all: stream << "ctor::ld_opt::warn_all"; break;
case ctor::ld_opt::warnings_as_errors: stream << "ctor::ld_opt::warnings_as_errors"; break;
case ctor::ld_opt::library_path: stream << "ctor::ld_opt::library_path"; break;
@@ -104,7 +112,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::asm_opt& opt)
return stream;
}
-ctor::toolchain getToolChain(const std::string& compiler)
+ctor::toolchain getToolChain(std::string_view compiler)
{
std::filesystem::path cc(compiler);
auto cc_cmd = cc.stem().string();
@@ -175,10 +183,11 @@ std::string get_arch(ctor::output_system system)
std::string arch;
while(!feof(pipe))
{
- char buf[1024];
- if(fgets(buf, sizeof(buf), pipe) != nullptr)
+ constexpr auto buffer_size{1024};
+ std::array<char, buffer_size> buf{};
+ if(fgets(buf.data(), buf.size(), pipe) != nullptr)
{
- arch = buf;
+ arch = buf.data();
if(arch.starts_with("Target:"))
{
break;
@@ -195,11 +204,12 @@ std::string get_arch(ctor::output_system system)
}
// Remove 'Target: ' prefix
- arch = arch.substr(8);
+ constexpr auto prefix_length{8};
+ arch = arch.substr(prefix_length);
return arch;
}
-ctor::arch get_arch(const std::string& str)
+ctor::arch get_arch(std::string_view str)
{
// gcc -v 2>&1 | grep Target
// Target: x86_64-apple-darwin19.6.0
@@ -234,57 +244,180 @@ ctor::arch get_arch(const std::string& str)
return ctor::arch::unknown;
}
-ctor::c_flag c_option(const std::string& flag)
+ctor::c_flag c_option(std::string_view flag)
{
if(flag.starts_with("-I"))
{
- std::string path = flag.substr(2);
+ std::string path(flag.substr(2));
path.erase(0, path.find_first_not_of(' '));
return { ctor::c_opt::include_path, path };
}
+ if(flag.starts_with("-std="))
+ {
+ auto std = flag.substr(5);
+ return { ctor::c_opt::c_std, std };
+ }
+
+ if(flag.starts_with("-O"))
+ {
+ auto opt = flag.substr(2, 1);
+ return { ctor::c_opt::optimization, opt };
+ }
+
+ if(flag.starts_with("-Wall"))
+ {
+ return { ctor::c_opt::warn_all };
+ }
+
+ if(flag.starts_with("-Wconversion"))
+ {
+ return { ctor::c_opt::warn_conversion};
+ }
+
+ if(flag.starts_with("-Wshadow"))
+ {
+ return { ctor::c_opt::warn_shadow};
+ }
+
+ if(flag.starts_with("-Wextra"))
+ {
+ return { ctor::c_opt::warn_extra};
+ }
+
+ if(flag.starts_with("-Werror"))
+ {
+ return { ctor::c_opt::warnings_as_errors };
+ }
+
+ if(flag.starts_with("-g"))
+ {
+ return { ctor::c_opt::debug };
+ }
+
+ if(flag.starts_with("-D"))
+ {
+ auto def = flag.substr(2);
+ auto pos = def.find('=');
+ if(pos != def.npos)
+ {
+ return { ctor::c_opt::define, def.substr(0, pos), def.substr(pos + 1) };
+ }
+ else
+ {
+ return { ctor::c_opt::define, def };
+ }
+ }
return { ctor::c_opt::custom, flag };
}
-ctor::cxx_flag cxx_option(const std::string& flag)
+ctor::cxx_flag cxx_option(std::string_view flag)
{
if(flag.starts_with("-I"))
{
- std::string path = flag.substr(2);
+ std::string path(flag.substr(2));
path.erase(0, path.find_first_not_of(' '));
return { ctor::cxx_opt::include_path, path };
}
+ if(flag.starts_with("-std="))
+ {
+ auto std = flag.substr(5);
+ return { ctor::cxx_opt::cpp_std, std };
+ }
+
+ if(flag.starts_with("-O"))
+ {
+ auto opt = flag.substr(2, 1);
+ return { ctor::cxx_opt::optimization, opt };
+ }
+
+ if(flag.starts_with("-Wall"))
+ {
+ return { ctor::cxx_opt::warn_all };
+ }
+
+ if(flag.starts_with("-Werror"))
+ {
+ return { ctor::cxx_opt::warnings_as_errors };
+ }
+
+ if(flag.starts_with("-Wconversion"))
+ {
+ return { ctor::cxx_opt::warn_conversion};
+ }
+
+ if(flag.starts_with("-Wshadow"))
+ {
+ return { ctor::cxx_opt::warn_shadow};
+ }
+
+ if(flag.starts_with("-Wextra"))
+ {
+ return { ctor::cxx_opt::warn_extra};
+ }
+
+ if(flag.starts_with("-g"))
+ {
+ return { ctor::cxx_opt::debug };
+ }
+
+ if(flag.starts_with("-D"))
+ {
+ auto def = flag.substr(2);
+ auto pos = def.find('=');
+ if(pos != def.npos)
+ {
+ return { ctor::cxx_opt::define, def.substr(0, pos), def.substr(pos + 1) };
+ }
+ else
+ {
+ return { ctor::cxx_opt::define, def };
+ }
+ }
+
return { ctor::cxx_opt::custom, flag };
}
-ctor::ld_flag ld_option(const std::string& flag)
+ctor::ld_flag ld_option(std::string_view flag)
{
if(flag.starts_with("-L"))
{
- std::string path = flag.substr(2);
+ std::string path(flag.substr(2));
path.erase(0, path.find_first_not_of(' '));
return { ctor::ld_opt::library_path, path };
}
+ if(flag.starts_with("-pthread"))
+ {
+ return { ctor::ld_opt::threads };
+ }
+
return { ctor::ld_opt::custom, flag };
}
-ctor::ar_flag ar_option(const std::string& flag)
+ctor::ar_flag ar_option(std::string_view flag)
{
return { ctor::ar_opt::custom, flag };
}
-std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg)
+std::vector<std::string> cxx_option(ctor::cxx_opt opt, std::string_view arg,
+ std::string_view arg2)
{
switch(opt)
{
case ctor::cxx_opt::output:
- return {"-o", arg};
+ return {"-o", std::string(arg)};
case ctor::cxx_opt::debug:
return {"-g"};
case ctor::cxx_opt::warn_all:
return {"-Wall"};
+ case ctor::cxx_opt::warn_conversion:
+ return {"-Wconversion"};
+ case ctor::cxx_opt::warn_shadow:
+ return {"-Wshadow"};
+ case ctor::cxx_opt::warn_extra:
+ return {"-Wextra"};
case ctor::cxx_opt::warnings_as_errors:
return {"-Werror"};
case ctor::cxx_opt::generate_dep_tree:
@@ -292,33 +425,46 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg)
case ctor::cxx_opt::no_link:
return {"-c"};
case ctor::cxx_opt::include_path:
- return {"-I" + arg};
+ return {"-I" + std::string(arg)};
case ctor::cxx_opt::cpp_std:
- return {"-std=" + arg};
+ return {"-std=" + std::string(arg)};
case ctor::cxx_opt::optimization:
- return {"-O" + arg};
+ return {"-O" + std::string(arg)};
case ctor::cxx_opt::position_independent_code:
return {"-fPIC"};
case ctor::cxx_opt::position_independent_executable:
return {"-fPIE"};
+ case ctor::cxx_opt::define:
+ if(!arg2.empty())
+ {
+ return {"-D" + std::string(arg) + "=" + std::string(arg2)};
+ }
+ return {"-D" + std::string(arg)};
case ctor::cxx_opt::custom:
- return {arg};
+ return argsplit(arg);
}
std::cerr << "Unsupported compiler option.\n";
return {};
}
-std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg)
+std::vector<std::string> c_option(ctor::c_opt opt, std::string_view arg,
+ std::string_view arg2)
{
switch(opt)
{
case ctor::c_opt::output:
- return {"-o", arg};
+ return {"-o", std::string(arg)};
case ctor::c_opt::debug:
return {"-g"};
case ctor::c_opt::warn_all:
return {"-Wall"};
+ case ctor::c_opt::warn_conversion:
+ return {"-Wconversion"};
+ case ctor::c_opt::warn_shadow:
+ return {"-Wshadow"};
+ case ctor::c_opt::warn_extra:
+ return {"-Wextra"};
case ctor::c_opt::warnings_as_errors:
return {"-Werror"};
case ctor::c_opt::generate_dep_tree:
@@ -326,41 +472,46 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg)
case ctor::c_opt::no_link:
return {"-c"};
case ctor::c_opt::include_path:
- return {"-I" + arg};
+ return {"-I" + std::string(arg)};
case ctor::c_opt::c_std:
- return {"-std=" + arg};
+ return {"-std=" + std::string(arg)};
case ctor::c_opt::optimization:
- return {"-O" + arg};
+ return {"-O" + std::string(arg)};
case ctor::c_opt::position_independent_code:
return {"-fPIC"};
case ctor::c_opt::position_independent_executable:
return {"-fPIE"};
+ case ctor::c_opt::define:
+ if(!arg2.empty())
+ {
+ return {"-D" + std::string(arg) + "=" + std::string(arg2)};
+ }
+ return {"-D" + std::string(arg)};
case ctor::c_opt::custom:
- return {arg};
+ return argsplit(arg);
}
std::cerr << "Unsupported compiler option.\n";
return {};
}
-std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg)
+std::vector<std::string> ld_option(ctor::ld_opt opt, std::string_view arg,
+ [[maybe_unused]]std::string_view arg2)
{
switch(opt)
{
case ctor::ld_opt::output:
- return {"-o", arg};
- case ctor::ld_opt::strip:
- return {"-s"};
+ return {"-o", std::string(arg)};
case ctor::ld_opt::warn_all:
return {"-Wall"};
case ctor::ld_opt::warnings_as_errors:
return {"-Werror"};
case ctor::ld_opt::library_path:
- return {"-L" + arg};
+ return {"-L" + std::string(arg)};
case ctor::ld_opt::link:
- return {"-l" + arg};
+ return {"-l" + std::string(arg)};
case ctor::ld_opt::cpp_std:
- return {"-std=" + arg};
+ return {"-std=" + std::string(arg)};
case ctor::ld_opt::build_shared:
return {"-shared"};
case ctor::ld_opt::threads:
@@ -370,14 +521,15 @@ std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg)
case ctor::ld_opt::position_independent_executable:
return {"-fPIE"};
case ctor::ld_opt::custom:
- return {arg};
+ return argsplit(arg);
}
std::cerr << "Unsupported compiler option.\n";
return {};
}
-std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg)
+std::vector<std::string> ar_option(ctor::ar_opt opt, std::string_view arg,
+ [[maybe_unused]]std::string_view arg2)
{
switch(opt)
{
@@ -388,21 +540,22 @@ std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg)
case ctor::ar_opt::create:
return {"-c"};
case ctor::ar_opt::output:
- return {arg};
+ return {std::string(arg)};
case ctor::ar_opt::custom:
- return {arg};
+ return argsplit(arg);
}
std::cerr << "Unsupported compiler option.\n";
return {};
}
-std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg)
+std::vector<std::string> asm_option(ctor::asm_opt opt, std::string_view arg,
+ [[maybe_unused]]std::string_view arg2)
{
switch(opt)
{
case ctor::asm_opt::custom:
- return {arg};
+ return argsplit(arg);
}
std::cerr << "Unsupported compiler option.\n";
@@ -425,7 +578,7 @@ std::string get_arch(ctor::output_system system)
return {};
}
-ctor::arch get_arch(ctor::output_system system, const std::string& str)
+ctor::arch get_arch(ctor::output_system system, std::string_view str)
{
auto toolchain = getToolChain(system);
switch(toolchain)
@@ -442,13 +595,14 @@ ctor::arch get_arch(ctor::output_system system, const std::string& str)
std::vector<std::string> c_option(ctor::toolchain toolchain,
ctor::c_opt opt,
- const std::string& arg)
+ std::string_view arg,
+ std::string_view arg2)
{
switch(toolchain)
{
case ctor::toolchain::gcc:
case ctor::toolchain::clang:
- return gcc::c_option(opt, arg);
+ return gcc::c_option(opt, arg, arg2);
case ctor::toolchain::any:
{
std::ostringstream ss;
@@ -457,6 +611,10 @@ std::vector<std::string> c_option(ctor::toolchain toolchain,
{
ss << ", \"" << arg << "\"";
}
+ if(!arg2.empty())
+ {
+ ss << ", \"" << arg2 << "\"";
+ }
ss << "}";
return { ss.str() };
}
@@ -470,13 +628,14 @@ std::vector<std::string> c_option(ctor::toolchain toolchain,
std::vector<std::string> cxx_option(ctor::toolchain toolchain,
ctor::cxx_opt opt,
- const std::string& arg)
+ std::string_view arg,
+ std::string_view arg2)
{
switch(toolchain)
{
case ctor::toolchain::gcc:
case ctor::toolchain::clang:
- return gcc::cxx_option(opt, arg);
+ return gcc::cxx_option(opt, arg, arg2);
case ctor::toolchain::any:
{
std::ostringstream ss;
@@ -485,6 +644,10 @@ std::vector<std::string> cxx_option(ctor::toolchain toolchain,
{
ss << ", \"" << arg << "\"";
}
+ if(!arg2.empty())
+ {
+ ss << ", \"" << arg2 << "\"";
+ }
ss << "}";
return { ss.str() };
}
@@ -498,13 +661,14 @@ std::vector<std::string> cxx_option(ctor::toolchain toolchain,
std::vector<std::string> ld_option(ctor::toolchain toolchain,
ctor::ld_opt opt,
- const std::string& arg)
+ std::string_view arg,
+ std::string_view arg2)
{
switch(toolchain)
{
case ctor::toolchain::gcc:
case ctor::toolchain::clang:
- return gcc::ld_option(opt, arg);
+ return gcc::ld_option(opt, arg, arg2);
case ctor::toolchain::any:
{
std::ostringstream ss;
@@ -513,6 +677,10 @@ std::vector<std::string> ld_option(ctor::toolchain toolchain,
{
ss << ", \"" << arg << "\"";
}
+ if(!arg2.empty())
+ {
+ ss << ", \"" << arg2 << "\"";
+ }
ss << "}";
return { ss.str() };
}
@@ -526,13 +694,14 @@ std::vector<std::string> ld_option(ctor::toolchain toolchain,
std::vector<std::string> ar_option(ctor::toolchain toolchain,
ctor::ar_opt opt,
- const std::string& arg)
+ std::string_view arg,
+ std::string_view arg2)
{
switch(toolchain)
{
case ctor::toolchain::gcc:
case ctor::toolchain::clang:
- return gcc::ar_option(opt, arg);
+ return gcc::ar_option(opt, arg, arg2);
case ctor::toolchain::any:
{
std::ostringstream ss;
@@ -541,6 +710,10 @@ std::vector<std::string> ar_option(ctor::toolchain toolchain,
{
ss << ", \"" << arg << "\"";
}
+ if(!arg2.empty())
+ {
+ ss << ", \"" << arg2 << "\"";
+ }
ss << "}";
return { ss.str() };
}
@@ -554,13 +727,14 @@ std::vector<std::string> ar_option(ctor::toolchain toolchain,
std::vector<std::string> asm_option(ctor::toolchain toolchain,
ctor::asm_opt opt,
- const std::string& arg)
+ std::string_view arg,
+ std::string_view arg2)
{
switch(toolchain)
{
case ctor::toolchain::gcc:
case ctor::toolchain::clang:
- return gcc::asm_option(opt, arg);
+ return gcc::asm_option(opt, arg, arg2);
case ctor::toolchain::any:
{
std::ostringstream ss;
@@ -569,6 +743,10 @@ std::vector<std::string> asm_option(ctor::toolchain toolchain,
{
ss << ", \"" << arg << "\"";
}
+ if(!arg2.empty())
+ {
+ ss << ", \"" << arg2 << "\"";
+ }
ss << "}";
return { ss.str() };
}
@@ -581,7 +759,7 @@ std::vector<std::string> asm_option(ctor::toolchain toolchain,
}
-ctor::c_flag c_option(const std::string& flag, ctor::toolchain toolchain)
+ctor::c_flag c_option(std::string_view flag, ctor::toolchain toolchain)
{
switch(toolchain)
{
@@ -596,7 +774,7 @@ ctor::c_flag c_option(const std::string& flag, ctor::toolchain toolchain)
return { ctor::c_opt::custom, flag };
}
-ctor::cxx_flag cxx_option(const std::string& flag, ctor::toolchain toolchain)
+ctor::cxx_flag cxx_option(std::string_view flag, ctor::toolchain toolchain)
{
switch(toolchain)
{
@@ -611,7 +789,7 @@ ctor::cxx_flag cxx_option(const std::string& flag, ctor::toolchain toolchain)
return { ctor::cxx_opt::custom, flag };
}
-ctor::ld_flag ld_option(const std::string& flag, ctor::toolchain toolchain)
+ctor::ld_flag ld_option(std::string_view flag, ctor::toolchain toolchain)
{
switch(toolchain)
{
@@ -626,7 +804,7 @@ ctor::ld_flag ld_option(const std::string& flag, ctor::toolchain toolchain)
return { ctor::ld_opt::custom, flag };
}
-ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain)
+ctor::ar_flag ar_option(std::string_view flag, ctor::toolchain toolchain)
{
switch(toolchain)
{
@@ -641,7 +819,7 @@ ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain)
return { ctor::ar_opt::custom, flag };
}
-ctor::asm_flag asm_option(const std::string& flag, ctor::toolchain toolchain)
+ctor::asm_flag asm_option(std::string_view flag, ctor::toolchain toolchain)
{
switch(toolchain)
{
@@ -663,7 +841,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,
if(flag.toolchain == ctor::toolchain::any ||
flag.toolchain == toolchain)
{
- return c_option(toolchain, flag.opt, flag.arg);
+ return c_option(toolchain, flag.opt, flag.arg, flag.arg2);
}
return {};
@@ -675,7 +853,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,
if(flag.toolchain == ctor::toolchain::any ||
flag.toolchain == toolchain)
{
- return cxx_option(toolchain, flag.opt, flag.arg);
+ return cxx_option(toolchain, flag.opt, flag.arg, flag.arg2);
}
return {};
@@ -687,7 +865,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,
if(flag.toolchain == ctor::toolchain::any ||
flag.toolchain == toolchain)
{
- return ld_option(toolchain, flag.opt, flag.arg);
+ return ld_option(toolchain, flag.opt, flag.arg, flag.arg2);
}
return {};
@@ -699,7 +877,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,
if(flag.toolchain == ctor::toolchain::any ||
flag.toolchain == toolchain)
{
- return ar_option(toolchain, flag.opt, flag.arg);
+ return ar_option(toolchain, flag.opt, flag.arg, flag.arg2);
}
return {};
@@ -711,14 +889,14 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,
if(flag.toolchain == ctor::toolchain::any ||
flag.toolchain == toolchain)
{
- return asm_option(toolchain, flag.opt, flag.arg);
+ return asm_option(toolchain, flag.opt, flag.arg, flag.arg2);
}
return {};
}
namespace {
-ctor::toolchain guess_toolchain(const std::string& opt)
+ctor::toolchain guess_toolchain(std::string_view opt)
{
if(opt.empty())
{
@@ -738,32 +916,33 @@ ctor::toolchain guess_toolchain(const std::string& opt)
}
}
+
template<>
-ctor::flag<ctor::c_opt>::flag(const char* str)
+void ctor::flag<ctor::c_opt>::to_flag(std::string_view str)
{
*this = c_option(str, guess_toolchain(str));
}
template<>
-ctor::flag<ctor::cxx_opt>::flag(const char* str)
+void ctor::flag<ctor::cxx_opt>::to_flag(std::string_view str)
{
*this = cxx_option(str, guess_toolchain(str));
}
template<>
-ctor::flag<ctor::ld_opt>::flag(const char* str)
+void ctor::flag<ctor::ld_opt>::to_flag(std::string_view str)
{
*this = ld_option(str, guess_toolchain(str));
}
template<>
-ctor::flag<ctor::ar_opt>::flag(const char* str)
+void ctor::flag<ctor::ar_opt>::to_flag(std::string_view str)
{
*this = ar_option(str, guess_toolchain(str));
}
template<>
-ctor::flag<ctor::asm_opt>::flag(const char* str)
+void ctor::flag<ctor::asm_opt>::to_flag(std::string_view str)
{
*this = asm_option(str, guess_toolchain(str));
}
diff --git a/src/tools.h b/src/tools.h
index 188d49f..b8df022 100644
--- a/src/tools.h
+++ b/src/tools.h
@@ -18,10 +18,10 @@ std::ostream& operator<<(std::ostream& stream, const ctor::ar_opt& opt);
std::ostream& operator<<(std::ostream& stream, const ctor::asm_opt& opt);
std::string get_arch(ctor::output_system system);
-ctor::arch get_arch(ctor::output_system system, const std::string& str);
+ctor::arch get_arch(ctor::output_system system, std::string_view str);
//! Get tool-chain type from compiler path string
-ctor::toolchain getToolChain(const std::string& compiler);
+ctor::toolchain getToolChain(std::string_view compiler);
//! Get tool-chain type from output system (via configuration)
ctor::toolchain getToolChain(ctor::output_system system);
@@ -32,58 +32,63 @@ ctor::toolchain getToolChain(ctor::output_system system);
//! tool-chain
std::vector<std::string> c_option(ctor::toolchain toolchain,
ctor::c_opt option,
- const std::string& arg = {});
+ std::string_view arg = {},
+ std::string_view arg2 = {});
//! Get tool argument(s) for specific option type matching the supplied
//! tool-chain
std::vector<std::string> cxx_option(ctor::toolchain toolchain,
ctor::cxx_opt option,
- const std::string& arg = {});
+ std::string_view arg = {},
+ std::string_view arg2 = {});
//! Get tool argument(s) for specific option type matching the supplied
//! tool-chain
std::vector<std::string> ld_option(ctor::toolchain toolchain,
ctor::ld_opt option,
- const std::string& arg = {});
+ std::string_view arg = {},
+ std::string_view arg2 = {});
//! Get tool argument(s) for specific option type matching the supplied
//! tool-chain
std::vector<std::string> ar_option(ctor::toolchain toolchain,
ctor::ar_opt option,
- const std::string& arg = {});
+ std::string_view arg = {},
+ std::string_view arg2 = {});
//! Get tool argument(s) for specific option type matching the supplied
//! tool-chain
std::vector<std::string> asm_option(ctor::toolchain toolchain,
ctor::asm_opt option,
- const std::string& arg = {});
+ std::string_view arg = {},
+ std::string_view arg2 = {});
//! Get ctor::c_opt enum value and argument from string,
//! ie. { ctor::c_opt::inlude_path, "foo/bar" } from "-Ifoo/bar"
//! Returns { ctor::c_opt::custom, flag } if unknown.
-ctor::c_flag c_option(const std::string& flag, ctor::toolchain toolchain);
+ctor::c_flag c_option(std::string_view flag, ctor::toolchain toolchain);
//! Get ctor::cxx_opt enum value and argument from string,
//! ie. { ctor::cxx_opt::inlude_path, "foo/bar" } from "-Ifoo/bar"
//! Returns { ctor::cxx_opt::custom, flag } if unknown.
-ctor::cxx_flag cxx_option(const std::string& flag, ctor::toolchain toolchain);
+ctor::cxx_flag cxx_option(std::string_view flag, ctor::toolchain toolchain);
//! Get ctor::ld_opt enum value and argument from string,
//! ie. { ctor::ld_opt::inlude_path, "foo/bar" } from "-Ifoo/bar"
//! Returns { ctor::ld_opt::custom, flag } if unknown.
-ctor::ld_flag ld_option(const std::string& flag, ctor::toolchain toolchain);
+ctor::ld_flag ld_option(std::string_view flag, ctor::toolchain toolchain);
//! Get ctor::ar_opt enum value and argument from string,
//! ie. { ctor::ar_opt::inlude_path, "foo/bar" } from "-Ifoo/bar"
//! Returns { ctor::ar_opt::custom, flag } if unknown.
-ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain);
+ctor::ar_flag ar_option(std::string_view flag, ctor::toolchain toolchain);
//! Get ctor::asm_opt enum value and argument from string,
//! ie. { ctor::asm_opt::inlude_path, "foo/bar" } from "-Ifoo/bar"
//! Returns { ctor::asm_opt::custom, flag } if unknown.
-ctor::asm_flag asm_option(const std::string& flag, ctor::toolchain toolchain);
+ctor::asm_flag asm_option(std::string_view flag, ctor::toolchain toolchain);
diff --git a/src/unittest.cc b/src/unittest.cc
index 9e85187..b95a931 100644
--- a/src/unittest.cc
+++ b/src/unittest.cc
@@ -24,7 +24,7 @@ int runUnitTests(std::vector<std::shared_ptr<Task>>& tasks,
name = task->target();
}
std::cout << name << ": " << std::flush;
- auto ret = execute(task->targetFile(), {}, {}, settings.verbose > 0);
+ auto ret = execute(settings, task->targetFile().string(), {}, {});
ok &= ret == 0;
if(ret == 0)
{
diff --git a/src/util.cc b/src/util.cc
index 76d380b..010cde3 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -6,12 +6,18 @@
#include <iostream>
#include <fstream>
#include <algorithm>
+#include <sstream>
+#include <cctype>
+#include <cstdlib>
-std::string to_lower(const std::string& str)
+std::string to_lower(std::string str)
{
- std::string out{str};
- std::transform(out.begin(), out.end(), out.begin(), ::tolower);
- return out;
+ std::transform(str.begin(), str.end(), str.begin(),
+ [](unsigned char c)
+ {
+ return std::tolower(c);
+ });
+ return str;
}
std::string readFile(const std::string& fileName)
@@ -19,73 +25,17 @@ std::string readFile(const std::string& fileName)
std::ifstream ifs(fileName.c_str(),
std::ios::in | std::ios::binary | std::ios::ate);
- std::ifstream::pos_type fileSize = ifs.tellg();
- ifs.seekg(0, std::ios::beg);
-
- std::vector<char> bytes(fileSize);
- ifs.read(bytes.data(), fileSize);
-
- return std::string(bytes.data(), fileSize);
-}
-
-std::vector<std::string> readDeps(const std::string& depFile)
-{
- if(!std::filesystem::exists(depFile))
+ auto size = ifs.tellg();
+ if(size < 0)
{
return {};
}
+ ifs.seekg(0, std::ios::beg);
- auto str = readFile(depFile);
-
- std::vector<std::string> output;
- std::string tmp;
- bool start{false};
- bool in_whitespace{false};
- for(const auto& c : str)
- {
- if(c == '\\' || c == '\n')
- {
- continue;
- }
-
- if(c == ':')
- {
- start = true;
- continue;
- }
-
- if(!start)
- {
- continue;
- }
-
- if(c == ' ' || c == '\t')
- {
- if(in_whitespace)
- {
- continue;
- }
-
- if(!tmp.empty())
- {
- output.push_back(tmp);
- }
- tmp.clear();
- in_whitespace = true;
- }
- else
- {
- in_whitespace = false;
- tmp += c;
- }
- }
-
- if(!tmp.empty())
- {
- output.push_back(tmp);
- }
+ std::string bytes(static_cast<std::size_t>(size), '\0');
+ ifs.read(bytes.data(), static_cast<std::streamsize>(bytes.size()));
- return output;
+ return bytes;
}
ctor::language languageFromExtension(const std::filesystem::path& file)
@@ -168,8 +118,18 @@ std::string esc(const std::string& in)
return out;
}
-std::vector<std::string> get_paths(const std::string& path_env)
+std::vector<std::string> get_paths(const std::string& path_env_)
{
+ std::string path_env;
+ if(!path_env_.empty())
+ {
+ path_env = path_env_;
+ }
+ else
+ {
+ get_env("PATH", path_env);
+ }
+
std::vector<std::string> paths;
#ifdef _WIN32
@@ -244,3 +204,106 @@ std::string locate(const std::string& prog,
return {};
}
+
+std::vector<std::string> argsplit(std::string_view str)
+{
+ enum class state
+ {
+ normal,
+ in_quot,
+ in_apotrophe,
+ } state{state::normal};
+ bool esc{false};
+
+ std::string token;
+ std::vector<std::string> tokens;
+ for(auto c : str)
+ {
+ switch(state)
+ {
+ case state::normal:
+ if(esc)
+ {
+ esc = false;
+ }
+ else
+ {
+ if(c == ' ')
+ {
+ tokens.push_back(token);
+ token.clear();
+ continue;
+ }
+ if(c == '\\')
+ {
+ esc = true;
+ }
+ if(c == '"')
+ {
+ state = state::in_quot;
+ }
+ if(c == '\'')
+ {
+ state = state::in_apotrophe;
+ }
+ }
+
+ token += c;
+ break;
+ case state::in_quot:
+ if(esc)
+ {
+ esc = false;
+ }
+ else
+ {
+ if(c == '\\')
+ {
+ esc = true;
+ }
+ if(c == '"')
+ {
+ state = state::normal;
+ }
+ }
+
+ token += c;
+ break;
+ case state::in_apotrophe:
+ if(esc)
+ {
+ esc = false;
+ }
+ else
+ {
+ if(c == '\\')
+ {
+ esc = true;
+ }
+ if(c == '\'')
+ {
+ state = state::normal;
+ }
+ }
+
+ token += c;
+ break;
+ }
+ }
+ if(!token.empty())
+ {
+ tokens.push_back(token);
+ }
+ return tokens;
+}
+
+bool get_env(std::string_view name, std::string& value)
+{
+ auto var = getenv(name.data());
+ if(var)
+ {
+ value = var;
+ return true;
+ }
+ return false;
+}
diff --git a/src/util.h b/src/util.h
index af5bbd6..337c1e4 100644
--- a/src/util.h
+++ b/src/util.h
@@ -9,10 +9,9 @@
#include <filesystem>
#include <cstdlib>
-std::string to_lower(const std::string& str);
+std::string to_lower(std::string str);
std::string readFile(const std::string& fileName);
-std::vector<std::string> readDeps(const std::string& depFile);
ctor::language languageFromExtension(const std::filesystem::path& file);
std::string cleanUp(const std::string& path);
@@ -24,8 +23,19 @@ void append(T& a, const T& b)
std::string esc(const std::string& in);
-std::vector<std::string> get_paths(const std::string& path_env = std::getenv("PATH"));
+//! Get system paths (ie. env var PATH).
+//! If path_env is provided, this search string will be used, other the PATH
+//! env variable is used.
+//! \returns a vector of the individual toknized paths.
+std::vector<std::string> get_paths(const std::string& path_env = {});
std::string locate(const std::string& app,
const std::vector<std::string>& paths,
const std::string& arch = {});
+
+//! Splits string into tokens adhering to quotations " and '
+std::vector<std::string> argsplit(std::string_view str);
+
+//! Calls the system getenv and sets the string if the env name it exists.
+//! \returns true if the env name existed, false otherwise.
+bool get_env(std::string_view name, std::string& value);
diff --git a/test/argparser_test.cc b/test/argparser_test.cc
new file mode 100644
index 0000000..b649e8c
--- /dev/null
+++ b/test/argparser_test.cc
@@ -0,0 +1,1019 @@
+#include <argparser.h>
+
+#include <iostream>
+#include <string>
+
+std::ostream& operator<<(std::ostream& ostr, const arg::error& err)
+{
+ switch(err)
+ {
+ case arg::error::invalid_arg:
+ ostr << "arg::error::invalid_arg";
+ break;
+ case arg::error::invalid_opt:
+ ostr << "arg::error::invalid_opt";
+ break;
+ case arg::error::missing_arg:
+ ostr << "arg::error::missing_arg";
+ break;
+ }
+ return ostr;
+}
+
+#include <uunit.h>
+
+class ArgParserTest
+ : public uUnit
+{
+public:
+ ArgParserTest()
+ {
+ uTEST(ArgParserTest::test_zero);
+ uTEST(ArgParserTest::test_one);
+ uTEST(ArgParserTest::test_many);
+ uTEST(ArgParserTest::test_exceptional);
+ uTEST(ArgParserTest::test_err_callback);
+ uTEST(ArgParserTest::test_pos_callback);
+ uTEST(ArgParserTest::test_grouped);
+ uTEST(ArgParserTest::test_nullprogram);
+ }
+
+ void test_zero()
+ {
+ const char* const argv[] = { "app-name" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int> args(argc, argv);
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ }
+
+ void test_one()
+ {
+ const char* argv[] = { "app-name", "-x", "42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(42, x);
+ }
+
+ void test_many()
+ {
+ const char* argv[] = { "app-name", "-x", "42", "-y17", "-z",
+ "--long-X=12", "--long-Y", "18" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ int y{};
+ bool z{false};
+ int X{};
+ int Y{};
+
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](int i){ y = i; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](){ z = true; return 0;}), "Help z");
+
+ args.add('X', "--long-X",
+ std::function([&](int i){ X = i; return 0;}), "Help X");
+
+ args.add('Y', "--long-Y",
+ std::function([&](int i){ Y = i; return 0;}), "Help Y");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(42, x);
+ uASSERT_EQUAL(17, y);
+ uASSERT_EQUAL(true, z);
+ uASSERT_EQUAL(12, X);
+ uASSERT_EQUAL(18, Y);
+ }
+
+ void test_exceptional()
+ {
+
+ { // Missing arg at trailing opt
+ const char* argv[] = { "app-name", "-x" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ int y{};
+
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](int i){ y = i; return 0;}), "Help y");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ }
+
+ { // Missing arg before other opt
+ const char* argv[] = { "app-name", "-x", "-y" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ int y{};
+
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](int i){ y = i; return 0;}), "Help y");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ }
+
+ { // Unknown arg
+ const char* argv[] = { "app-name", "-T" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ int y{};
+
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](int i){ y = i; return 0;}), "Help y");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ }
+ }
+
+ void test_err_callback()
+ {
+ using namespace std::string_literals;
+
+ { // Missing arg at trailing opt
+ const char* argv[] = { "app-name", "-x" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ bool called{false};
+ args.set_err_cb(
+ [&](arg::error err, std::string_view opt)
+ {
+ called = true;
+ uASSERT_EQUAL(arg::error::missing_arg, err);
+ uASSERT_EQUAL("-x"s, opt);
+ });
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ uASSERT(called);
+ }
+
+ { // Invalid arg format
+ const char* argv[] = { "app-name", "-x", "abc" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ bool called{false};
+ args.set_err_cb(
+ [&](arg::error err, std::string_view opt)
+ {
+ called = true;
+ uASSERT_EQUAL(arg::error::invalid_arg, err);
+ uASSERT_EQUAL("abc"s, opt);
+ });
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ uASSERT(called);
+ }
+
+ { // Invalid opt
+ const char* argv[] = { "app-name", "-y" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ bool called{false};
+ args.set_err_cb(
+ [&](arg::error err, std::string_view opt)
+ {
+ called = true;
+ uASSERT_EQUAL(arg::error::invalid_opt, err);
+ uASSERT_EQUAL("-y"s, opt);
+ });
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ uASSERT(called);
+ }
+ }
+
+ void test_pos_callback()
+ {
+ using namespace std::string_literals;
+ const char* argv[] =
+ { "app-name", "foo", "-x", "42", "bar", "-X43", "-Y", "42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int, std::optional<int>> args(argc, argv);
+
+ int x{};
+ int X{};
+ int Y{};
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ args.add('X', "--opt-x",
+ std::function([&](std::optional<int> i)
+ {
+ uASSERT(i.has_value());
+ X = *i;
+ return 0;
+ }),
+ "Help X");
+
+ args.add('Y', "--opt-y",
+ std::function([&](std::optional<int> i)
+ {
+ uASSERT(!i.has_value());
+ Y = 1;
+ return 0;
+ }),
+ "Help X");
+
+ std::vector<std::string> pos;
+ args.set_pos_cb(
+ [&](std::string_view sv)
+ {
+ pos.push_back(std::string(sv));
+ return 0;
+ });
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(3u, pos.size());
+ uASSERT_EQUAL("foo"s, pos[0]);
+ uASSERT_EQUAL("bar"s, pos[1]);
+ uASSERT_EQUAL("42"s, pos[2]);
+ uASSERT_EQUAL(42, x);
+ uASSERT_EQUAL(43, X);
+ uASSERT_EQUAL(1, Y);
+ }
+
+ void test_grouped()
+ {
+ {
+ const char* argv[] = { "app-name", "-xyz", "42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int> args(argc, argv);
+
+ bool x{false};
+ bool y{false};
+ int z{};
+ args.add('x', "--long-x",
+ std::function([&](){ x = true; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](){ y = true; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](int i){ z = i; return 0;}), "Help z");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT(x);
+ uASSERT(y);
+ uASSERT_EQUAL(42, z);
+ }
+
+ {
+ const char* argv[] = { "app-name", "-xyz42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int> args(argc, argv);
+
+ bool x{false};
+ bool y{false};
+ int z{};
+ args.add('x', "--long-x",
+ std::function([&](){ x = true; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](){ y = true; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](int i){ z = i; return 0;}), "Help z");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT(x);
+ uASSERT(y);
+ uASSERT_EQUAL(42, z);
+ }
+
+
+ {
+ const char* argv[] = { "app-name", "-xyz42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int, std::optional<int>> args(argc, argv);
+
+ bool x{false};
+ bool y{false};
+ int z{};
+ args.add('x', "--long-x",
+ std::function([&](){ x = true; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](){ y = true; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](std::optional<int> i)
+ {
+ uASSERT(i.has_value());
+ z = *i; return 0;
+ }), "Help z");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT(x);
+ uASSERT(y);
+ uASSERT_EQUAL(42, z);
+ }
+
+ {
+ const char* argv[] = { "app-name", "-xyz" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int, std::optional<int>> args(argc, argv);
+
+ bool x{false};
+ bool y{false};
+ int z{};
+ args.add('x', "--long-x",
+ std::function([&](){ x = true; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](){ y = true; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](std::optional<int> i)
+ {
+ uASSERT(!i.has_value());
+ z = 1; return 0;
+ }), "Help z");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT(x);
+ uASSERT(y);
+ uASSERT_EQUAL(1, z);
+ }
+
+ {
+ const char* argv[] = { "app-name", "-xyz", "42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int, std::optional<int>> args(argc, argv);
+
+ bool x{false};
+ bool y{false};
+ int z{};
+ args.add('x', "--long-x",
+ std::function([&](){ x = true; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](){ y = true; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](std::optional<int> i)
+ {
+ uASSERT(!i.has_value());
+ z = 1; return 0;
+ }), "Help z");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT(x);
+ uASSERT(y);
+ uASSERT_EQUAL(1, z);
+ }
+ }
+
+ void test_nullprogram()
+ {
+ using namespace std::string_literals;
+ // Inspired by https://nullprogram.com/blog/2020/08/01/
+
+ //
+ // Short options
+ //
+ {
+ const char* argv[] = { "program", "-a", "-b", "-c" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<char> r;
+ args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {});
+ args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(3u, r.size());
+ uASSERT_EQUAL('a', r[0]);
+ uASSERT_EQUAL('b', r[1]);
+ uASSERT_EQUAL('c', r[2]);
+ }
+
+ {
+ const char* argv[] = { "program", "-abc" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<char> r;
+ args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {});
+ args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(3u, r.size());
+ uASSERT_EQUAL('a', r[0]);
+ uASSERT_EQUAL('b', r[1]);
+ uASSERT_EQUAL('c', r[2]);
+ }
+
+ {
+ const char* argv[] = { "program", "-acb" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<char> r;
+ args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {});
+ args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(3u, r.size());
+ uASSERT_EQUAL('a', r[0]);
+ uASSERT_EQUAL('c', r[1]);
+ uASSERT_EQUAL('b', r[2]);
+ }
+
+ {
+ const char* argv[] = { "program", "-i", "input.txt", "-o", "output.txt" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('i', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+
+ args.add('o', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("input.txt"s, r[0]);
+ uASSERT_EQUAL("output.txt"s, r[1]);
+ }
+
+ {
+ const char* argv[] = { "program", "-iinput.txt", "-ooutput.txt" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('i', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+ args.add('o', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("input.txt"s, r[0]);
+ uASSERT_EQUAL("output.txt"s, r[1]);
+ }
+
+ {
+ const char* argv[] = { "program", "-abco", "output.txt" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.add('c', {}, std::function([&](){ r.push_back("c"); return 0;}), {});
+ args.add('o', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("a"s, r[0]);
+ uASSERT_EQUAL("b"s, r[1]);
+ uASSERT_EQUAL("c"s, r[2]);
+ uASSERT_EQUAL("output.txt"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "-abcooutput.txt" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.add('c', {}, std::function([&](){ r.push_back("c"); return 0;}), {});
+ args.add('o', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("a"s, r[0]);
+ uASSERT_EQUAL("b"s, r[1]);
+ uASSERT_EQUAL("c"s, r[2]);
+ uASSERT_EQUAL("output.txt"s, r[3]);
+ }
+
+ {
+ // Optional omitted
+ const char* argv[] = { "program", "-c" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('c', {},
+ std::function([&](std::optional<std::string> c)
+ {
+ uASSERT(!c.has_value());
+ r.push_back("c");
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(1u, r.size());
+ uASSERT_EQUAL("c"s, r[0]);
+ }
+
+ {
+ // Optional provided
+ const char* argv[] = { "program", "-cblue" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('c', {},
+ std::function([&](std::optional<std::string> c)
+ {
+ uASSERT(c.has_value());
+ r.push_back(*c);
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(1u, r.size());
+ uASSERT_EQUAL("blue"s, r[0]);
+ }
+
+ {
+ // Optional omitted (blue is a new argument)
+ const char* argv[] = { "program", "-c", "blue" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('c', {},
+ std::function([&](std::optional<std::string> c)
+ {
+ uASSERT(!c.has_value());
+ r.push_back("c");
+ return 0;
+ }), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("c"s, r[0]);
+ uASSERT_EQUAL("blue"s, r[1]);
+ }
+
+ {
+ // Two seperate flags
+ const char* argv[] = { "program", "-c", "-x" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('x', {}, std::function([&](){ r.push_back("x"); return 0;}), {});
+ args.add('c', {},
+ std::function([&](std::optional<std::string> c)
+ {
+ uASSERT(!c.has_value());
+ r.push_back("c");
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("c"s, r[0]);
+ uASSERT_EQUAL("x"s, r[1]);
+ }
+
+ {
+ // -c with argument "-x"
+ const char* argv[] = { "program", "-c-x" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('x', {}, std::function([&](){ r.push_back("x"); return 0;}), {});
+ args.add('c', {},
+ std::function([&](std::optional<std::string> c)
+ {
+ uASSERT(c.has_value());
+ r.push_back(*c);
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(1u, r.size());
+ uASSERT_EQUAL("-x"s, r[0]);
+ }
+
+ {
+ const char* argv[] = { "program", "-a", "-b", "foo", "bar" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("a"s, r[0]);
+ uASSERT_EQUAL("b"s, r[1]);
+ uASSERT_EQUAL("foo"s, r[2]);
+ uASSERT_EQUAL("bar"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "-b", "-a", "foo", "bar" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("b"s, r[0]);
+ uASSERT_EQUAL("a"s, r[1]);
+ uASSERT_EQUAL("foo"s, r[2]);
+ uASSERT_EQUAL("bar"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "-a", "foo", "-b", "bar" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("a"s, r[0]);
+ uASSERT_EQUAL("foo"s, r[1]);
+ uASSERT_EQUAL("b"s, r[2]);
+ uASSERT_EQUAL("bar"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "foo", "-a", "-b", "bar" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("foo"s, r[0]);
+ uASSERT_EQUAL("a"s, r[1]);
+ uASSERT_EQUAL("b"s, r[2]);
+ uASSERT_EQUAL("bar"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "foo", "bar", "-a", "-b" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("foo"s, r[0]);
+ uASSERT_EQUAL("bar"s, r[1]);
+ uASSERT_EQUAL("a"s, r[2]);
+ uASSERT_EQUAL("b"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "-a", "-b", "--", "-x", "foo", "bar" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(5u, r.size());
+ uASSERT_EQUAL("a"s, r[0]);
+ uASSERT_EQUAL("b"s, r[1]);
+ uASSERT_EQUAL("-x"s, r[2]);
+ uASSERT_EQUAL("foo"s, r[3]);
+ uASSERT_EQUAL("bar"s, r[4]);
+ }
+
+ //
+ // Long options
+ //
+ {
+ const char* argv[] = { "program", "--sort" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--sort",
+ std::function([&](){ r.push_back("sort"); return 0;}), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(1u, r.size());
+ uASSERT_EQUAL("sort"s, r[0]);
+ }
+
+ {
+ const char* argv[] = { "program", "--no-sort" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--no-sort",
+ std::function([&](){ r.push_back("no-sort"); return 0;}), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(1u, r.size());
+ uASSERT_EQUAL("no-sort"s, r[0]);
+ }
+
+ {
+ const char* argv[] =
+ { "program", "--output", "output.txt", "--block-size", "1024" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string, int> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--output",
+ std::function([&](std::string output)
+ {
+ r.push_back(output);
+ return 0;
+ }), {});
+ args.add({}, "--block-size",
+ std::function([&](int block_size)
+ {
+ r.push_back("["s + std::to_string(block_size) + "]"s);
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("output.txt"s, r[0]);
+ uASSERT_EQUAL("[1024]"s, r[1]);
+ }
+
+ {
+ const char* argv[] =
+ { "program", "--output=output.txt", "--block-size=1024" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string, int> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--output",
+ std::function([&](std::string output)
+ {
+ r.push_back(output);
+ return 0;
+ }), {});
+ args.add({}, "--block-size",
+ std::function([&](int block_size)
+ {
+ r.push_back("["s + std::to_string(block_size) + "]"s);
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("output.txt"s, r[0]);
+ uASSERT_EQUAL("[1024]"s, r[1]);
+ }
+
+ {
+ const char* argv[] =
+ { "program", "--color", "--reverse" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--color",
+ std::function([&](std::optional<std::string> color)
+ {
+ uASSERT(!color.has_value());
+ r.push_back("color");
+ return 0;
+ }), {});
+ args.add({}, "--reverse",
+ std::function([&]()
+ {
+ r.push_back("reverse");
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("color"s, r[0]);
+ uASSERT_EQUAL("reverse"s, r[1]);
+ }
+
+ {
+ const char* argv[] =
+ { "program", "--color=never", "--reverse" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--color",
+ std::function([&](std::optional<std::string> color)
+ {
+ uASSERT(color.has_value());
+ r.push_back(*color);
+ return 0;
+ }), {});
+ args.add({}, "--reverse",
+ std::function([&]()
+ {
+ r.push_back("reverse");
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("never"s, r[0]);
+ uASSERT_EQUAL("reverse"s, r[1]);
+ }
+
+ }
+};
+
+// Registers the fixture into the 'registry'
+static ArgParserTest test;
diff --git a/test/argsplit_test.cc b/test/argsplit_test.cc
new file mode 100644
index 0000000..7dce561
--- /dev/null
+++ b/test/argsplit_test.cc
@@ -0,0 +1,203 @@
+#include <uunit.h>
+
+#include <util.h>
+
+class ArgSplitTest
+ : public uUnit
+{
+public:
+ using fs = std::filesystem::path;
+
+ ArgSplitTest()
+ {
+ uTEST(ArgSplitTest::plain_test);
+ uTEST(ArgSplitTest::quot_test);
+ uTEST(ArgSplitTest::apotrophe_test);
+ uTEST(ArgSplitTest::mixed_test);
+ uTEST(ArgSplitTest::escape_test);
+ }
+
+ void plain_test()
+ {
+ using namespace std::string_literals;
+
+ { // zero
+ auto res = argsplit({});
+ uASSERT_EQUAL(0u, res.size());
+ }
+
+ { // one
+ auto res = argsplit("hello"s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("hello"s, res[0]);
+ }
+
+ { // many
+ auto res = argsplit("hello world"s);
+ uASSERT_EQUAL(2u, res.size());
+ uASSERT_EQUAL("hello"s, res[0]);
+ uASSERT_EQUAL("world"s, res[1]);
+ }
+ }
+
+ void quot_test()
+ {
+ using namespace std::string_literals;
+
+ { // zero
+ auto res = argsplit("\"\"");
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\"\""s, res[0]);
+ }
+
+ { // one
+ auto res = argsplit("\"hello\""s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\"hello\""s, res[0]);
+ }
+
+ { // one with space
+ auto res = argsplit("\"hel lo\""s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\"hel lo\""s, res[0]);
+ }
+
+ { // many
+ auto res = argsplit("\"hello\" \"world\""s);
+ uASSERT_EQUAL(2u, res.size());
+ uASSERT_EQUAL("\"hello\""s, res[0]);
+ uASSERT_EQUAL("\"world\""s, res[1]);
+ }
+
+ { // many with spaces
+ auto res = argsplit("\"hel lo\" \"wor ld\""s);
+ uASSERT_EQUAL(2u, res.size());
+ uASSERT_EQUAL("\"hel lo\""s, res[0]);
+ uASSERT_EQUAL("\"wor ld\""s, res[1]);
+ }
+ }
+
+ void apotrophe_test()
+ {
+ using namespace std::string_literals;
+
+ { // zero
+ auto res = argsplit("\'\'");
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\'\'"s, res[0]);
+ }
+
+ { // one
+ auto res = argsplit("\'hello\'"s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\'hello\'"s, res[0]);
+ }
+
+ { // one with space
+ auto res = argsplit("\'hel lo\'"s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\'hel lo\'"s, res[0]);
+ }
+
+ { // many
+ auto res = argsplit("\'hello\' \'world\'"s);
+ uASSERT_EQUAL(2u, res.size());
+ uASSERT_EQUAL("\'hello\'"s, res[0]);
+ uASSERT_EQUAL("\'world\'"s, res[1]);
+ }
+
+ { // many with spaces
+ auto res = argsplit("\'hel lo\' \'wor ld\'"s);
+ uASSERT_EQUAL(2u, res.size());
+ uASSERT_EQUAL("\'hel lo\'"s, res[0]);
+ uASSERT_EQUAL("\'wor ld\'"s, res[1]);
+ }
+ }
+
+ void mixed_test()
+ {
+ using namespace std::string_literals;
+
+ { // zero
+ auto res = argsplit("\'\'");
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\'\'"s, res[0]);
+ }
+
+ { // one
+ auto res = argsplit("\'he\"llo\'"s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\'he\"llo\'"s, res[0]);
+ }
+
+ { // one
+ auto res = argsplit("\"he\'llo\""s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\"he\'llo\""s, res[0]);
+ }
+
+ { // one with space
+ auto res = argsplit("\'he\"l lo\'\""s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\'he\"l lo\'\""s, res[0]);
+ }
+
+ { // one with space
+ auto res = argsplit("\"he\'l lo\"\'"s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\"he\'l lo\"\'"s, res[0]);
+ }
+
+ { // many
+ auto res = argsplit("\"he\'llo\" \"wor\'ld\""s);
+ uASSERT_EQUAL(2u, res.size());
+ uASSERT_EQUAL("\"he\'llo\""s, res[0]);
+ uASSERT_EQUAL("\"wor\'ld\""s, res[1]);
+ }
+
+ { // many with spaces
+ auto res = argsplit("\'hel\"lo\' \'wor\"ld\'"s);
+ uASSERT_EQUAL(2u, res.size());
+ uASSERT_EQUAL("\'hel\"lo\'"s, res[0]);
+ uASSERT_EQUAL("\'wor\"ld\'"s, res[1]);
+ }
+ }
+
+ void escape_test()
+ {
+ using namespace std::string_literals;
+
+ { // zero
+ auto res = argsplit("\\");
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\\"s, res[0]);
+ }
+
+ { // one
+ auto res = argsplit("\\\'"s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\\\'"s, res[0]);
+ }
+
+ { // one
+ auto res = argsplit("\\\""s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\\\""s, res[0]);
+ }
+
+ { // one
+ auto res = argsplit("\\\\"s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("\\\\"s, res[0]);
+ }
+
+ { // one
+ auto res = argsplit("a\\ b"s);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("a\\ b"s, res[0]);
+ }
+ }
+};
+
+// Registers the fixture into the 'registry'
+static ArgSplitTest test;
diff --git a/test/ctor.cc b/test/ctor.cc
index ccf1c31..8b89f6d 100644
--- a/test/ctor.cc
+++ b/test/ctor.cc
@@ -12,13 +12,99 @@ 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",
+ "-I../src", "-Iuunit",
+ "-DOUTPUT=\"argparser\"",
+ },
+ },
+ },
+ {
+ .type = ctor::target_type::unit_test,
+ .system = ctor::output_system::build,
+ .target = "generated_sources_test",
+ .sources = {
+ "generated_sources_test.cc",
+ "testmain.cc",
+ },
+ .depends = { "libctor_nomain.a" },
+ .flags = {
+ .cxxflags = {
+ "-std=c++20", "-O3", "-Wall",
+ "-I../src", "-Iuunit",
+ "-DOUTPUT=\"generated_sources\"",
+ },
+ },
+ },
+ {
+ .type = ctor::target_type::unit_test,
+ .system = ctor::output_system::build,
+ .target = "argsplit_test",
+ .sources = {
+ "argsplit_test.cc",
+ "testmain.cc",
+ "../src/util.cc",
+ },
+ .flags = {
+ .cxxflags = {
+ "-std=c++20", "-O3", "-Wall",
+ "-I../src", "-Iuunit",
+ "-DOUTPUT=\"argsplit\"",
+ },
+ },
+ },
+ {
+ .type = ctor::target_type::unit_test,
+ .system = ctor::output_system::build,
+ .target = "pointerlist_test",
+ .sources = {
+ "pointerlist_test.cc",
+ "testmain.cc",
+ "../src/pointerlist.cc",
+ },
+ .flags = {
+ .cxxflags = {
+ "-std=c++20", "-O3", "-Wall",
+ "-I../src", "-Iuunit",
+ "-DOUTPUT=\"pointerlist\"",
+ },
+ },
+ },
+ {
+ .type = ctor::target_type::unit_test,
+ .system = ctor::output_system::build,
+ .target = "deps_test",
+ .sources = {
+ "deps_test.cc",
+ "testmain.cc",
+ "../src/deps.cc",
+ "../src/util.cc",
+ },
+ .depends = { "testprog", },
+ .flags = {
+ .cxxflags = {
+ "-std=c++20", "-O3", "-Wall",
+ "-I../src", "-Iuunit",
+ "-DOUTPUT=\"deps\"",
+ },
+ },
+ },
+ {
+ .type = ctor::target_type::unit_test,
+ .system = ctor::output_system::build,
.target = "testprog",
.sources = {
"testprog.cc",
},
.flags = {
.cxxflags = {
- "-std=c++20", "-O3", "-Wall", "-Werror",
+ "-std=c++20", "-O3", "-Wall",
},
},
},
@@ -30,12 +116,13 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)
"execute_test.cc",
"testmain.cc",
"../src/execute.cc",
+ "../src/pointerlist.cc",
"../src/util.cc",
},
.depends = { "testprog", },
.flags = {
.cxxflags = {
- "-std=c++20", "-O3", "-Wall", "-Werror",
+ "-std=c++20", "-O3", "-Wall",
"-I../src", "-Iuunit",
"-DOUTPUT=\"execute\"",
},
@@ -53,7 +140,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)
.depends = { "libctor_nomain.a" },
.flags = {
.cxxflags = {
- "-std=c++20", "-O3", "-Wall", "-Werror",
+ "-std=c++20", "-O3", "-Wall",
"-I../src", "-Iuunit",
"-DOUTPUT=\"tasks\"",
},
@@ -63,6 +150,24 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)
{
.type = ctor::target_type::unit_test,
.system = ctor::output_system::build,
+ .target = "cycle_test",
+ .sources = {
+ "cycle_test.cc",
+ "testmain.cc",
+ },
+ .depends = { "libctor_nomain.a" },
+ .flags = {
+ .cxxflags = {
+ "-std=c++20", "-O3", "-Wall",
+ "-I../src", "-Iuunit",
+ "-DOUTPUT=\"cycle\"",
+ },
+ .ldflags = { "-pthread" },
+ },
+ },
+ {
+ .type = ctor::target_type::unit_test,
+ .system = ctor::output_system::build,
.target = "source_type_test",
.sources = {
"source_type_test.cc",
@@ -71,7 +176,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)
.depends = { "libctor_nomain.a" },
.flags = {
.cxxflags = {
- "-std=c++20", "-O3", "-Wall", "-Werror",
+ "-std=c++20", "-O3", "-Wall",
"-I../src", "-Iuunit",
"-DOUTPUT=\"source_type\"",
},
@@ -91,7 +196,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)
//.depends = { "libctor_nomain.a" },
.flags = {
.cxxflags = {
- "-std=c++20", "-O3", "-Wall", "-Werror",
+ "-std=c++20", "-O3", "-Wall",
"-I../src", "-Iuunit",
"-DOUTPUT=\"tools\"",
},
@@ -104,7 +209,9 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)
.sources = {
"../src/build.cc",
"../src/configure.cc",
+ "../src/deps.cc",
"../src/execute.cc",
+ "../src/pointerlist.cc",
"../src/rebuild.cc",
"../src/tasks.cc",
"../src/task.cc",
@@ -119,7 +226,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)
},
.flags = {
.cxxflags = {
- "-std=c++20", "-O3", "-Wall", "-Werror",
+ "-std=c++20", "-O3", "-Wall",
"-I../src",
},
.ldflags = { "-pthread" },
diff --git a/test/cycle_test.cc b/test/cycle_test.cc
new file mode 100644
index 0000000..3b45632
--- /dev/null
+++ b/test/cycle_test.cc
@@ -0,0 +1,87 @@
+#include <uunit.h>
+
+#include <ctor.h>
+#include <build.h>
+
+namespace
+{
+ctor::build_configurations ctorTestConfigsCyclic(const ctor::settings&)
+{
+ return
+ {
+ // No dependency
+ {
+ .target = "target0",
+ },
+
+ // Direct (self-depends)
+ {
+ .target = "target1",
+ .depends = { "target1" },
+ },
+
+ // Indirect cyclic depends
+ {
+ .target = "target2",
+ .depends = { "target3" },
+ },
+ {
+ .target = "target3",
+ .depends = { "target2" },
+ },
+ };
+}
+}
+
+REG(ctorTestConfigsCyclic);
+
+class CycleTest
+ : public uUnit
+{
+public:
+ CycleTest()
+ {
+ uTEST(CycleTest::getTargets_test);
+ }
+
+ void getTargets_test()
+ {
+ using namespace std::string_literals;
+ ctor::settings settings{};
+ const auto& tasks = getTasks(settings);
+ uASSERT_EQUAL(4u, tasks.size());
+
+ uASSERT_EQUAL("target0"s, tasks[0]->target());
+ uASSERT_EQUAL("target1"s, tasks[1]->target());
+ uASSERT_EQUAL("target2"s, tasks[2]->target());
+ uASSERT_EQUAL("target3"s, tasks[3]->target());
+
+ for(auto task : tasks)
+ {
+ if(task->registerDepTasks(tasks))
+ {
+ uASSERT(false);
+ }
+ }
+
+ bool exc;
+ exc = false;
+ try { getDepTasks(tasks[0]); } catch(...) { exc = true; }
+ uASSERT(!exc);
+
+ exc = false;
+ try { getDepTasks(tasks[1]); } catch(...) { exc = true; }
+ uASSERT(exc);
+
+ exc = false;
+ try { getDepTasks(tasks[2]); } catch(...) { exc = true; }
+ uASSERT(exc);
+
+ exc = false;
+ try { getDepTasks(tasks[3]); } catch(...) { exc = true; }
+ uASSERT(exc);
+ }
+};
+
+// Registers the fixture into the 'registry'
+static CycleTest test;
diff --git a/test/deps_test.cc b/test/deps_test.cc
new file mode 100644
index 0000000..762b0e5
--- /dev/null
+++ b/test/deps_test.cc
@@ -0,0 +1,97 @@
+#include <uunit.h>
+
+#include <fstream>
+#include <map>
+#include <vector>
+
+#include <deps.h>
+#include <algorithm>
+
+#include "paths.h"
+#include "tmpfile.h"
+
+class DepsTest
+ : public uUnit
+{
+public:
+ DepsTest()
+ {
+ uTEST(DepsTest::parser_test);
+ }
+
+ void parser_test()
+ {
+ using namespace std::string_literals;
+
+ auto test_data = paths::top_srcdir / "test" / "deps_test_data";
+
+ auto empty = test_data / "empty.d";
+ auto trivial = test_data / "trivial.d";
+ auto no_newline = test_data / "no_newline.d";
+ auto no_deps = test_data / "no_deps.d";
+ auto trailing_whitespace = test_data / "trailing_whitespace.d";
+ auto spaces = test_data / "spaces.d";
+ auto multiline = test_data / "multiline.d";
+ auto no_such_file = test_data / "no_such_file.d"; // doesn't exist
+ auto missing_colon = test_data / "missing_colon.d";
+
+ {
+ auto res = readDeps(empty.string(), ctor::toolchain::gcc);
+ uASSERT(res.empty());
+ }
+
+ {
+ auto res = readDeps(trivial.string(), ctor::toolchain::gcc);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("x.cc"s, res[0]);
+ }
+
+ {
+ auto res = readDeps(no_newline.string(), ctor::toolchain::gcc);
+ uASSERT_EQUAL(1u, res.size());
+ uASSERT_EQUAL("x.cc"s, res[0]);
+ }
+
+ {
+ auto res = readDeps(no_deps.string(), ctor::toolchain::gcc);
+ uASSERT_EQUAL(0u, res.size());
+ }
+
+ {
+ auto res = readDeps(spaces.string(), ctor::toolchain::gcc);
+ uASSERT_EQUAL(2u, res.size());
+ uASSERT_EQUAL("x y.cc"s, res[0]);
+ uASSERT_EQUAL("x y.h"s, res[1]);
+ }
+
+ {
+ auto res = readDeps(multiline.string(), ctor::toolchain::gcc);
+ uASSERT_EQUAL(12u, res.size());
+ uASSERT_EQUAL("src/configure.cc"s, res[0]);
+ uASSERT_EQUAL("src/configure.h"s, res[1]);
+ uASSERT_EQUAL("src/getoptpp/getoptpp.hpp"s, res[2]);
+ uASSERT_EQUAL("src/execute.h"s, res[3]);
+ uASSERT_EQUAL("src/ctor.h"s, res[4]);
+ uASSERT_EQUAL("src/tasks.h"s, res[5]);
+ uASSERT_EQUAL("src/task.h"s, res[6]);
+ uASSERT_EQUAL("src/rebuild.h"s, res[7]);
+ uASSERT_EQUAL("src/externals.h"s, res[8]);
+ uASSERT_EQUAL("src/externals_manual.h"s, res[9]);
+ uASSERT_EQUAL("src/tools.h"s, res[10]);
+ uASSERT_EQUAL("src/util.h"s, res[11]);
+ }
+
+ {
+ auto res = readDeps(no_such_file.string(), ctor::toolchain::gcc);
+ uASSERT(res.empty());
+ }
+
+ {
+ auto res = readDeps(missing_colon.string(), ctor::toolchain::gcc);
+ uASSERT(res.empty());
+ }
+ }
+};
+
+// Registers the fixture into the 'registry'
+static DepsTest test;
diff --git a/test/deps_test_data/empty.d b/test/deps_test_data/empty.d
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/deps_test_data/empty.d
diff --git a/test/deps_test_data/missing_colon.d b/test/deps_test_data/missing_colon.d
new file mode 100644
index 0000000..e46996c
--- /dev/null
+++ b/test/deps_test_data/missing_colon.d
@@ -0,0 +1 @@
+x.cc x.h
diff --git a/test/deps_test_data/multiline.d b/test/deps_test_data/multiline.d
new file mode 100644
index 0000000..8862ab0
--- /dev/null
+++ b/test/deps_test_data/multiline.d
@@ -0,0 +1,4 @@
+build/src/libctor_a-configure_cc.o: src/configure.cc src/configure.h \
+ src/getoptpp/getoptpp.hpp src/execute.h src/ctor.h src/tasks.h \
+ src/task.h src/rebuild.h src/externals.h src/externals_manual.h \
+ src/tools.h src/util.h
diff --git a/test/deps_test_data/no_deps.d b/test/deps_test_data/no_deps.d
new file mode 100644
index 0000000..e7cccf6
--- /dev/null
+++ b/test/deps_test_data/no_deps.d
@@ -0,0 +1 @@
+x.o:
diff --git a/test/deps_test_data/no_newline.d b/test/deps_test_data/no_newline.d
new file mode 100644
index 0000000..88829ea
--- /dev/null
+++ b/test/deps_test_data/no_newline.d
@@ -0,0 +1 @@
+x.o: x.cc \ No newline at end of file
diff --git a/test/deps_test_data/spaces.d b/test/deps_test_data/spaces.d
new file mode 100644
index 0000000..c53fd64
--- /dev/null
+++ b/test/deps_test_data/spaces.d
@@ -0,0 +1 @@
+x\ y.o: x\ y.cc x\ y.h
diff --git a/test/deps_test_data/trivial.d b/test/deps_test_data/trivial.d
new file mode 100644
index 0000000..15a0c29
--- /dev/null
+++ b/test/deps_test_data/trivial.d
@@ -0,0 +1 @@
+x.o: x.cc
diff --git a/test/execute_test.cc b/test/execute_test.cc
index d5d40c9..11b067f 100644
--- a/test/execute_test.cc
+++ b/test/execute_test.cc
@@ -23,26 +23,37 @@ public:
void return_value()
{
+ ctor::settings s;
auto cur_path = std::filesystem::path(paths::argv_0).parent_path();
std::vector<std::string> paths{{cur_path.string()}};
auto cmd = locate("testprog", paths);
uASSERT(!cmd.empty());
- uASSERT_EQUAL(0, execute(cmd, {"retval", "0"}, {}, false));
- uASSERT_EQUAL(1, execute(cmd, {"retval", "1"}, {}, false));
- uASSERT_EQUAL(1, execute("no-such-binary", {}, {}, false));
+ 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);
+ value = execute(s, cmd, {"throw"}, {}, false);
+ uASSERT_EQUAL(6, value);
+ value = execute(s, cmd, {"abort"}, {}, false);
+ uASSERT_EQUAL(6, value);
}
void env()
{
using namespace std::string_literals;
+ ctor::settings s;
auto cur_path = std::filesystem::path(paths::argv_0).parent_path();
std::vector<std::string> paths{{cur_path.string()}};
auto cmd = locate("testprog", paths);
uASSERT(!cmd.empty());
- tmp_file tmp;
+ TmpFile tmp;
std::map<std::string, std::string> env;
@@ -53,7 +64,8 @@ public:
// Overwrite the exiting LANG var
env["LANG"] = "foo";
- uASSERT_EQUAL(0, execute(cmd, {"envdump", tmp.get()}, env, false));
+ auto value = execute(s, cmd, {"envdump", tmp.get()}, env, false);
+ uASSERT_EQUAL(0, value);
std::vector<std::string> vars;
{
diff --git a/test/generated_sources_test.cc b/test/generated_sources_test.cc
new file mode 100644
index 0000000..8c46983
--- /dev/null
+++ b/test/generated_sources_test.cc
@@ -0,0 +1,135 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#include <uunit.h>
+
+#include <ctor.h>
+#include <tasks.h>
+#include <rebuild.h>
+
+class GeneratedSourcesTest
+ : public uUnit
+{
+public:
+ GeneratedSourcesTest()
+ {
+ uTEST(GeneratedSourcesTest::test_custom_output);
+ uTEST(GeneratedSourcesTest::test_many_to_one_output);
+ }
+
+ void setup()
+ {
+ // Make sure we start from a clean slate
+ getConfigFileList().clear();
+ }
+
+ void test_custom_output()
+ {
+ using namespace std::string_literals;
+
+ ctor::reg(
+ [](const ctor::settings&)
+ {
+ return ctor::build_configurations{
+ {
+ .target = "test1",
+ .sources = {
+ {"foo.cc", ctor::source_type::generated}
+ }
+ },
+ {
+ .target = "this_is_unused",
+ .sources = {
+ {"bar.x", ctor::output_file{"foo.cc"}},
+ {"bar.y", ctor::output_file{"bar.cc"}},
+ },
+ .function = []([[maybe_unused]]const std::string& input,
+ [[maybe_unused]]const std::string& output,
+ [[maybe_unused]]const ctor::build_configuration& config,
+ [[maybe_unused]]const ctor::settings& settings)
+ {
+ return 0;
+ }
+ },
+ };
+ });
+ ctor::settings settings{};
+ auto tasks = getTasks(settings);
+ for(auto task : tasks)
+ {
+ uASSERT(task->registerDepTasks(tasks) == 0);
+ }
+ uASSERT_EQUAL(4u, tasks.size());
+ bool found{false};
+ for(const auto& task : tasks)
+ {
+ if(task->target() == "test1")
+ {
+ auto deps_test1 = task->getDependsTasks();
+ uASSERT_EQUAL(1u, deps_test1.size());
+ auto deps_foo_o = deps_test1[0]->getDependsTasks();
+ uASSERT_EQUAL(1u, deps_foo_o.size());
+ uASSERT_EQUAL("test/bar.x"s, deps_foo_o[0]->source());
+ found = true;
+ }
+ }
+ uASSERT(found);
+ }
+
+ void test_many_to_one_output()
+ {
+ using namespace std::string_literals;
+
+ ctor::reg(
+ [](const ctor::settings&)
+ {
+ return ctor::build_configurations{
+ {
+ .target = "test1",
+ .sources = {
+ {"foo.cc", ctor::source_type::generated}
+ }
+ },
+ {
+ .target = "foo.cc",
+ .sources = {
+ {"bar.x"},
+ {"bar.y"},
+ },
+ .function = [](const std::vector<std::string>& input,
+ const std::string& output,
+ const ctor::build_configuration& config,
+ const ctor::settings& settings)
+ {
+ return 0;
+ }
+ },
+ };
+ });
+ ctor::settings settings{};
+ auto tasks = getTasks(settings);
+ for(auto task : tasks)
+ {
+ uASSERT(task->registerDepTasks(tasks) == 0);
+ }
+ uASSERT_EQUAL(4u, tasks.size());
+ bool found{false};
+ for(const auto& task : tasks)
+ {
+ if(task->target() == "test1")
+ {
+ auto deps_test1 = task->getDependsTasks();
+ uASSERT_EQUAL(1u, deps_test1.size());
+ auto deps_foo_o = deps_test1[0]->getDependsTasks();
+ uASSERT_EQUAL(1u, deps_foo_o.size());
+ uASSERT_EQUAL("test/bar.x"s, deps_foo_o[0]->source());
+ found = true;
+ }
+ }
+ uASSERT(found);
+ }
+
+};
+
+// Registers the fixture into the 'registry'
+static GeneratedSourcesTest test;
diff --git a/test/pointerlist_test.cc b/test/pointerlist_test.cc
new file mode 100644
index 0000000..4274473
--- /dev/null
+++ b/test/pointerlist_test.cc
@@ -0,0 +1,320 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#include <uunit.h>
+
+#include <set>
+#include <algorithm>
+
+#include "pointerlist.h"
+
+class PointerListTest
+ : public uUnit
+{
+public:
+ PointerListTest()
+ {
+ uTEST(PointerListTest::test_zom_pointerlist_push);
+ uTEST(PointerListTest::test_zom_pointerlist_from_args);
+ uTEST(PointerListTest::test_zom_envmap_insert);
+ uTEST(PointerListTest::test_zom_envmap_from_env);
+ uTEST(PointerListTest::test_exceptional_env);
+ }
+
+ void test_zom_pointerlist_push()
+ {
+ using namespace std::string_literals;
+
+ { // Zero
+ PointerList args;
+ uASSERT_EQUAL(0u, args.size());
+ auto [argc, argv] = args.get();
+ uASSERT_EQUAL(0, argc);
+ uASSERT(nullptr != argv);
+ uASSERT_EQUAL(nullptr, argv[0]);
+ }
+
+ { // One
+ PointerList args;
+ args.push_back("hello");
+ uASSERT_EQUAL(1u, args.size());
+ auto [argc, argv] = args.get();
+ uASSERT_EQUAL(1, argc);
+ uASSERT_EQUAL("hello"s, std::string(argv[0]));
+ }
+
+ { // Many
+ PointerList args;
+ args.push_back("hello");
+ args.push_back("dear");
+ args.push_back("world");
+ uASSERT_EQUAL(3u, args.size());
+ auto [argc, argv] = args.get();
+ uASSERT_EQUAL(3, argc);
+ uASSERT_EQUAL("hello"s, std::string(argv[0]));
+ uASSERT_EQUAL("dear"s, std::string(argv[1]));
+ uASSERT_EQUAL("world"s, std::string(argv[2]));
+ }
+ }
+
+ void test_zom_pointerlist_from_args()
+ {
+ using namespace std::string_literals;
+
+ { // Zero
+ PointerList args(0, nullptr);
+ uASSERT_EQUAL(0u, args.size());
+ auto [argc, argv] = args.get();
+ uASSERT_EQUAL(0, argc);
+ uASSERT(nullptr != argv);
+ uASSERT_EQUAL(nullptr, argv[0]);
+ }
+
+ { // One
+ int _argc{1};
+ const char* _argv[] = { "hello" };
+ PointerList args(_argc, _argv);
+ uASSERT_EQUAL(1u, args.size());
+ auto [argc, argv] = args.get();
+ uASSERT_EQUAL(1, argc);
+ uASSERT_EQUAL("hello"s, std::string(argv[0]));
+ }
+
+ { // Many
+ int _argc{3};
+ const char* _argv[] = { "hello", "dear", "world" };
+ PointerList args(_argc, _argv);
+ uASSERT_EQUAL(3u, args.size());
+ auto [argc, argv] = args.get();
+ uASSERT_EQUAL(3, argc);
+ // order must be preserved
+ uASSERT_EQUAL("hello"s, std::string(argv[0]));
+ uASSERT_EQUAL("dear"s, std::string(argv[1]));
+ uASSERT_EQUAL("world"s, std::string(argv[2]));
+ }
+ }
+
+ void test_zom_envmap_insert()
+ {
+ using namespace std::string_literals;
+
+ { // Zero
+ EnvMap env;
+ uASSERT_EQUAL(0u, env.size());
+
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(0, argc);
+ uASSERT(nullptr != argv);
+ uASSERT_EQUAL(nullptr, argv[0]);
+
+ auto str = env.stringify();
+ uASSERT_EQUAL(1u, str.size());
+ uASSERT_EQUAL('\0', str[0]);
+ }
+
+ { // One (key only)
+ EnvMap env;
+ env.insert("hello");
+ uASSERT_EQUAL(1u, env.size());
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(1, argc);
+ uASSERT_EQUAL("hello="s, std::string(argv[0]));
+ uASSERT_EQUAL(""s, env["hello"]);
+ }
+ { // One (with value)
+ EnvMap env;
+ env.insert("hello=world");
+ uASSERT_EQUAL(1u, env.size());
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(1, argc);
+ uASSERT_EQUAL("hello=world"s, std::string(argv[0]));
+ uASSERT_EQUAL("world"s, env["hello"]);
+
+ uASSERT_EQUAL("hello=world\0\0"s, env.stringify());
+ }
+
+ { // Overwrite one
+ EnvMap env;
+ env.insert("hello=world");
+ uASSERT_EQUAL(1u, env.size());
+ uASSERT_EQUAL("world"s, env["hello"s]);
+
+ env.insert("hello=foo");
+ uASSERT_EQUAL(1u, env.size());
+ uASSERT_EQUAL("foo"s, env["hello"s]);
+ }
+
+ { // Many
+ EnvMap env;
+ env.insert("hello=world");
+ env.insert("world=leader");
+ env.insert("dear=boar");
+ uASSERT_EQUAL(3u, env.size());
+
+ uASSERT_EQUAL("boar"s, env["dear"s]);
+ uASSERT_EQUAL("world"s, env["hello"s]);
+ uASSERT_EQUAL("leader"s, env["world"s]);
+
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(3, argc);
+ // store and sort to verify unordered
+ std::vector<std::string> vals{argv[0], argv[1], argv[2]};
+ std::ranges::sort(vals);
+ uASSERT_EQUAL("dear=boar"s, vals[0]);
+ uASSERT_EQUAL("hello=world"s, vals[1]);
+ uASSERT_EQUAL("world=leader"s, vals[2]);
+
+ // test all combinations since ordering is not a requirement
+ // exactly one of the must be true (boolean XOR)
+ auto str = env.stringify();
+ uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) !=
+ ("dear=boar\0world=leader\0hello=world\0\0"s == str)) !=
+ ("hello=world\0dear=boar\0world=leader\0\0"s == str)) !=
+ ("hello=world\0world=leader\0dear=boar\0\0"s == str)) !=
+ ("world=leader\0dear=boar\0hello=world\0\0"s == str)) !=
+ ("world=leader\0hello=world\0dear=boar\0\0"s == str));
+ }
+ }
+
+ void test_zom_envmap_from_env()
+ {
+ using namespace std::string_literals;
+
+ { // Zero
+ const char* penv = nullptr;
+ EnvMap env(penv);
+ uASSERT_EQUAL(0u, env.size());
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(0, argc);
+ uASSERT(nullptr != argv);
+ uASSERT_EQUAL(nullptr, argv[0]);
+ }
+
+ { // Zero
+ const char** ppenv = nullptr;
+ EnvMap env(ppenv);
+ uASSERT_EQUAL(0u, env.size());
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(0, argc);
+ uASSERT(nullptr != argv);
+ uASSERT_EQUAL(nullptr, argv[0]);
+ }
+
+ { // One (key only)
+ const char* ptr = "hello\0\0";
+ EnvMap env(ptr);
+ uASSERT_EQUAL(1u, env.size());
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(1, argc);
+ uASSERT_EQUAL("hello="s, std::string(argv[0]));
+ uASSERT_EQUAL(""s, env["hello"]);
+ }
+
+ { // One (key only)
+ const char* ptr[] = {"hello", nullptr};
+ EnvMap env(ptr);
+ uASSERT_EQUAL(1u, env.size());
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(1, argc);
+ uASSERT_EQUAL("hello="s, std::string(argv[0]));
+ uASSERT_EQUAL(""s, env["hello"]);
+ }
+
+ { // One (with value)
+ const char* ptr = "hello=world\0\0";
+ EnvMap env(ptr);
+ uASSERT_EQUAL(1u, env.size());
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(1, argc);
+ uASSERT_EQUAL("hello=world"s, std::string(argv[0]));
+ uASSERT_EQUAL("world"s, env["hello"]);
+
+ uASSERT_EQUAL("hello=world\0\0"s, env.stringify());
+ }
+
+ { // One (with value)
+ const char* ptr[] = {"hello=world\0", nullptr};
+ EnvMap env(ptr);
+ uASSERT_EQUAL(1u, env.size());
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(1, argc);
+ uASSERT_EQUAL("hello=world"s, std::string(argv[0]));
+ uASSERT_EQUAL("world"s, env["hello"]);
+
+ uASSERT_EQUAL("hello=world\0\0"s, env.stringify());
+ }
+
+ { // Many
+ const char* ptr = "hello=world\0world=leader\0dear=boar\0\0";
+ EnvMap env(ptr);
+ uASSERT_EQUAL(3u, env.size());
+
+ uASSERT_EQUAL("boar"s, env["dear"s]);
+ uASSERT_EQUAL("world"s, env["hello"s]);
+ uASSERT_EQUAL("leader"s, env["world"s]);
+
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(3, argc);
+ // store and sort to verify unordered
+ std::vector<std::string> vals{argv[0], argv[1], argv[2]};
+ std::ranges::sort(vals);
+ uASSERT_EQUAL("dear=boar"s, vals[0]);
+ uASSERT_EQUAL("hello=world"s, vals[1]);
+ uASSERT_EQUAL("world=leader"s, vals[2]);
+
+ // test all combinations since ordering is not a requirement
+ // exactly one of the must be true (boolean XOR)
+ auto str = env.stringify();
+ uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) !=
+ ("dear=boar\0world=leader\0hello=world\0\0"s == str)) !=
+ ("hello=world\0dear=boar\0world=leader\0\0"s == str)) !=
+ ("hello=world\0world=leader\0dear=boar\0\0"s == str)) !=
+ ("world=leader\0dear=boar\0hello=world\0\0"s == str)) !=
+ ("world=leader\0hello=world\0dear=boar\0\0"s == str));
+ }
+
+ { // Many
+ const char* ptr[] =
+ {"hello=world\0", "world=leader\0", "dear=boar\0", nullptr};
+ EnvMap env(ptr);
+ uASSERT_EQUAL(3u, env.size());
+
+ uASSERT_EQUAL("boar"s, env["dear"s]);
+ uASSERT_EQUAL("world"s, env["hello"s]);
+ uASSERT_EQUAL("leader"s, env["world"s]);
+
+ auto [argc, argv] = env.get();
+ uASSERT_EQUAL(3, argc);
+ // store and sort to verify unordered
+ std::vector<std::string> vals{argv[0], argv[1], argv[2]};
+ std::ranges::sort(vals);
+ uASSERT_EQUAL("dear=boar"s, vals[0]);
+ uASSERT_EQUAL("hello=world"s, vals[1]);
+ uASSERT_EQUAL("world=leader"s, vals[2]);
+
+ // test all combinations since ordering is not a requirement
+ // exactly one of the must be true (boolean XOR)
+ auto str = env.stringify();
+ uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) !=
+ ("dear=boar\0world=leader\0hello=world\0\0"s == str)) !=
+ ("hello=world\0dear=boar\0world=leader\0\0"s == str)) !=
+ ("hello=world\0world=leader\0dear=boar\0\0"s == str)) !=
+ ("world=leader\0dear=boar\0hello=world\0\0"s == str)) !=
+ ("world=leader\0hello=world\0dear=boar\0\0"s == str));
+ }
+ }
+
+ void test_exceptional_env()
+ {
+ using namespace std::string_literals;
+
+ { // Zero
+ EnvMap env;
+ uASSERT_EQUAL(0u, env.size());
+ uASSERT_EQUAL(""s, env["foo"]); // lookup of non-existing key
+ }
+ }
+};
+
+// Registers the fixture into the 'registry'
+static PointerListTest test;
diff --git a/test/suite/ctor_files/ctor.cc.generated b/test/suite/ctor_files/ctor.cc.generated
new file mode 100644
index 0000000..5bc2940
--- /dev/null
+++ b/test/suite/ctor_files/ctor.cc.generated
@@ -0,0 +1,87 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#include <ctor.h>
+#include <filesystem>
+#include <iostream>
+#include <fstream>
+
+namespace
+{
+ctor::build_configurations ctorConfigs(const ctor::settings& settings)
+{
+ return
+ {
+ {
+ .target = "world",
+ .sources = {
+ { "world.cc", ctor::source_type::generated },
+ },
+ },
+ {
+ .target = "foo",
+ .sources = {
+ { "foo.cc", ctor::source_type::generated },
+ },
+ },
+ {
+ .target = "this_is_unused",
+ .sources = {
+ {"hello.cc", ctor::output_file{"world.cc"}},
+ {"hello.cc", ctor::output_file{"foo.cc"}},
+ },
+ .function = [](const std::string& input,
+ const std::string& output,
+ const ctor::build_configuration& config,
+ const ctor::settings& settings)
+ {
+ namespace fs = std::filesystem;
+ std::cout << "Input: " << input << '\n';
+ std::cout << "Output: " << output << '\n';
+ fs::copy_file(input, output, fs::copy_options::overwrite_existing);
+ return 0;
+ }
+ },
+
+ {
+ .target = "many_to_one",
+ .sources = {
+ {"many_to_one.cc", ctor::source_type::generated}
+ }
+ },
+ {
+ .target = "many_to_one.cc",
+ .sources = {
+ {"foo.cc", ctor::source_type::generated},
+ {"hello.cc"},
+ },
+ .function = [](const std::vector<std::string>& input,
+ const std::string& output,
+ const ctor::build_configuration& config,
+ const ctor::settings& settings)
+ {
+ std::cout << "Output: " << output << '\n';
+ std::ofstream ofs(output);
+ bool comment{true};
+ for(const auto& input_file : input)
+ {
+ std::cout << "Input: " << input_file << '\n';
+ std::ifstream ifs(input_file);
+ std::string line;
+ while(std::getline(ifs, line))
+ {
+ ofs << line << '\n';
+ }
+ if(comment) ofs << "/*\n";
+ comment = false;
+ }
+ ofs << "*/\n";
+ return 0;
+ }
+ },
+
+ };
+}
+}
+
+REG(ctorConfigs);
diff --git a/test/suite/ctor_files/ctor.cc.generated2 b/test/suite/ctor_files/ctor.cc.generated2
new file mode 100644
index 0000000..c78489f
--- /dev/null
+++ b/test/suite/ctor_files/ctor.cc.generated2
@@ -0,0 +1,86 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#include <ctor.h>
+#include <filesystem>
+#include <iostream>
+#include <fstream>
+
+namespace
+{
+ctor::build_configurations ctorConfigs(const ctor::settings& settings)
+{
+ return
+ {
+ {
+ .target = "world",
+ .sources = {
+ { "world.cc", ctor::source_type::generated },
+ },
+ },
+ {
+ .target = "foo",
+ .sources = {
+ { "foo.cc", ctor::source_type::generated },
+ },
+ },
+ {
+ .target = "this_is_unused",
+ .sources = {
+ {"hello.cc", ctor::output_file{"world.cc"}},
+ {"hello.cc", ctor::output_file{"foo.cc"}},
+ },
+ .function = [](const std::string& input,
+ const std::string& output,
+ const ctor::build_configuration& config,
+ const ctor::settings& settings)
+ {
+ namespace fs = std::filesystem;
+ std::cout << "Input: " << input << '\n';
+ std::cout << "Output: " << output << '\n';
+ fs::copy_file(input, output, fs::copy_options::overwrite_existing);
+ return 0;
+ }
+ },
+
+ {
+ .target = "many_to_one",
+ .sources = {
+ {"many_to_one.cc", ctor::source_type::generated}
+ }
+ },
+ {
+ .target = "many_to_one.cc",
+ .sources = {
+ {"hello.cc"},
+ },
+ .function = [](const std::vector<std::string>& input,
+ const std::string& output,
+ const ctor::build_configuration& config,
+ const ctor::settings& settings)
+ {
+ std::cout << "Output: " << output << '\n';
+ std::ofstream ofs(output);
+ bool comment{true};
+ for(const auto& input_file : input)
+ {
+ std::cout << "Input: " << input_file << '\n';
+ std::ifstream ifs(input_file);
+ std::string line;
+ while(std::getline(ifs, line))
+ {
+ ofs << line << '\n';
+ }
+ if(comment) ofs << "/*\n";
+ comment = false;
+ }
+ ofs << "*/\n";
+ return 0;
+ }
+ },
+
+ };
+}
+}
+
+REG(ctorConfigs);
diff --git a/test/suite/test.cc b/test/suite/test.cc
new file mode 100644
index 0000000..bb62d9d
--- /dev/null
+++ b/test/suite/test.cc
@@ -0,0 +1,370 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#include "../../src/util.cc"
+#include "../../src/pointerlist.cc"
+#include "../../src/execute.cc"
+
+#include <iostream>
+#include <string>
+#include <filesystem>
+#include <thread>
+#include <chrono>
+#include <sstream>
+
+using namespace std::chrono_literals;
+
+int fail(int value = 1,
+ const std::source_location location = std::source_location::current())
+{
+ std::cout << "*** Failure at line " << location.line() << '\n';
+ exit(value);
+}
+
+const std::string ctor_exe{"./ctor"};
+const std::string obj_ext{".o"};
+
+void run_ctor(const std::vector<std::string>& args,
+ const std::source_location location = std::source_location::current())
+{
+ ctor::settings settings{.verbose = 2};
+ auto ret = execute(settings, ctor_exe, args);
+ if(ret != 0)
+ {
+ fail(ret, location);
+ }
+}
+
+void assert_not_exists(const std::string& path,
+ const std::source_location location = std::source_location::current())
+{
+ if(!std::filesystem::exists(path))
+ {
+ fail(1, location);
+ }
+}
+
+void assert_exists(const std::string& path,
+ const std::source_location location = std::source_location::current())
+{
+ if(std::filesystem::exists(path))
+ {
+ fail(1, location);
+ }
+}
+
+void copy_config(std::string cfg)
+{
+ std::cout << "** ctor_files/ctor.cc." + cfg + "\n";
+ std::filesystem::copy("ctor_files/ctor.cc." + cfg, "ctor.cc",
+ std::filesystem::copy_options::overwrite_existing);
+ if(std::filesystem::exists(ctor_exe))
+ {
+ auto ctor_exe_time = std::filesystem::last_write_time(ctor_exe);
+ std::filesystem::last_write_time("ctor.cc", ctor_exe_time + 1s);
+ }
+}
+
+class Tracker
+{
+public:
+ Tracker(const std::string& file_) : file(file_)
+ {
+ capture(); // capture initial value
+ }
+
+ void capture()
+ {
+ changed();
+ }
+
+ bool changed()
+ {
+ auto tmp = readFile(file);
+ auto content_changed = tmp != content;
+ content = tmp;
+ return content_changed;
+ }
+
+private:
+ std::string file;
+ std::string content;
+};
+
+void assert_not_changed(Tracker& tracker,
+ const std::source_location location = std::source_location::current())
+{
+ if(tracker.changed())
+ {
+ fail(1, location);
+ }
+}
+
+void assert_changed(Tracker& tracker,
+ const std::source_location location = std::source_location::current())
+{
+ if(!tracker.changed())
+ {
+ fail(1, location);
+ }
+}
+
+int main()
+{
+ ctor::settings settings{};
+ settings.verbose = 2;
+ auto paths = get_paths();
+
+ std::string CXX = "g++";
+ get_env("CXX", CXX);
+ std::string CTORDIR = "../../build";
+ get_env("CTORDIR", CTORDIR);
+ std::string BUILDDIR = "build";
+ get_env("BUILDDIR", BUILDDIR);
+ std::string CXXFLAGS;
+ get_env("CXXFLAGS", CXXFLAGS);
+ std::string LDFLAGS;
+ get_env("LDFLAGS", LDFLAGS);
+
+ // Wipe the board
+ std::filesystem::remove_all(BUILDDIR);
+ std::filesystem::remove("configuration.cc");
+ std::filesystem::remove("config.h");
+ std::filesystem::remove(ctor_exe);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // bootstrap
+ {
+ auto cxx_prog = locate(CXX, paths);
+
+ std::vector<std::string> args =
+ {"-pthread", "-std=c++20", "-L", CTORDIR, "-lctor", "-I", "../../src",
+ "ctor.cc", "-o", ctor_exe};
+ if(!CXXFLAGS.empty())
+ {
+ auto tokens = argsplit(CXXFLAGS);
+ for(const auto& token : tokens)
+ {
+ args.push_back(token);
+ }
+ }
+ if(!LDFLAGS.empty())
+ {
+ auto tokens = argsplit(LDFLAGS);
+ for(const auto& token : tokens)
+ {
+ args.push_back(token);
+ }
+ }
+
+ // Compile bootstrap binary
+ copy_config("base");
+ auto ret = execute(settings, cxx_prog, args);
+ if(ret != 0)
+ {
+ fail(ret);
+ }
+ }
+ //////////////////////////////////////////////////////////////////////////////
+ // check if source file changes are tracked
+ {
+ // No build files should have been created yet
+ assert_exists(BUILDDIR);
+
+ // capture ctor binary before configure is called
+ Tracker ctor_bin(ctor_exe);
+ run_ctor(
+ {"configure", "--ctor-includedir", "../../src",
+ "--ctor-libdir=" + CTORDIR, "--build-dir=" + BUILDDIR});
+
+ // ctor should be rebuilt at this point, so binary should have changed
+ assert_changed(ctor_bin);
+
+ // configuration.cc should have been generated now
+ assert_not_exists("configuration.cc");
+ assert_not_exists("config.h");
+
+ // Shouldn't compile anything yet - only configure
+ assert_exists(BUILDDIR + "/hello-hello_cc" + obj_ext);
+
+ ctor_bin.capture();
+
+ // Run normally to build project
+ run_ctor({"-v"});
+
+ // Compiled object should now exist
+ assert_not_exists(BUILDDIR + "/hello-hello_cc" + obj_ext);
+
+ // ctor should not have been rebuilt, so binary should be the same
+ assert_not_changed(ctor_bin);
+
+ std::this_thread::sleep_for(1100ms);
+
+ auto time =
+ std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext);
+ std::filesystem::last_write_time("hello.cc", time + 1s);
+ std::this_thread::sleep_for(1100ms);
+
+ // Run normally to rebuild hello.cc
+ run_ctor({"-v"});
+
+ // Object file should have been recompiled
+ auto time2 =
+ std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext);
+ if(time == time2)
+ {
+ fail();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // check if flags change trigger rebuild
+ {
+ // Replace -DFOO with -DBAR in foo external.cxxflags
+ copy_config("bar");
+ Tracker configuration_cc_bin("configuration.cc");
+ Tracker ctor_bin(ctor_exe);
+ auto time =
+ std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext);
+ std::this_thread::sleep_for(1100ms);
+
+ // Run normally to reconfigure, rebuild ctor and rebuild hello.cc
+ run_ctor({"-v"});
+
+ auto time2 =
+ std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext);
+ if(time == time2)
+ {
+ fail();
+ }
+
+ assert_changed(configuration_cc_bin);
+ assert_changed(ctor_bin);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // check if included files in ctor.cc is tracked for changes
+ {
+ copy_config("multi");
+ Tracker configuration_cc_bin("configuration.cc");
+ Tracker ctor_bin(ctor_exe);
+ auto time =
+ std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext);
+ std::this_thread::sleep_for(1100ms);
+
+ // Run normally to reconfigure, rebuild ctor and rebuild hello.cc
+ run_ctor({"-v"});
+
+ auto time2 =
+ std::filesystem::last_write_time(BUILDDIR + "/hello-hello_cc" + obj_ext);
+ if(time == time2)
+ {
+ fail();
+ }
+
+ assert_changed(configuration_cc_bin);
+ assert_changed(ctor_bin);
+
+ // now touching foobar.h, should retrigger re-configuration
+ time = std::filesystem::last_write_time(ctor_exe);
+ std::filesystem::last_write_time("foobar.h", time + 1s);
+ std::this_thread::sleep_for(1100ms);
+
+ // Run normally to reconfigure, rebuild ctor and rebuild hello.cc
+ run_ctor({"-v"});
+
+ time2 = std::filesystem::last_write_time(ctor_exe);
+ if(time == time2)
+ {
+ fail();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // generated part1: one-to-one generation
+ {
+ copy_config("generated");
+ std::filesystem::remove(BUILDDIR + "/world.cc");
+ std::filesystem::remove(BUILDDIR + "/foo.cc");
+
+ Tracker configuration_cc_bin("configuration.cc");
+ Tracker ctor_bin(ctor_exe);
+ std::this_thread::sleep_for(1100ms);
+
+ // Run normally to reconfigure, rebuild ctor and build world.cc
+ run_ctor({"-v", "world"});
+
+ assert_changed(configuration_cc_bin);
+ assert_changed(ctor_bin);
+
+ // foo.cc should not be generated at this point
+ assert_exists(BUILDDIR+"/foo.cc");
+
+ auto time_w = std::filesystem::last_write_time(BUILDDIR + "/world.cc");
+ auto time_wo =
+ std::filesystem::last_write_time(BUILDDIR + "/" + BUILDDIR+
+ "/world-world_cc" + obj_ext);
+ std::this_thread::sleep_for(1100ms);
+
+ // now touching hello.cc, should trigger regeneration of world.cc and
+ // rebuild
+ std::filesystem::last_write_time("hello.cc", time_w + 1s);
+ run_ctor({"-v", "world"});
+
+ auto time_w2 = std::filesystem::last_write_time(BUILDDIR + "/world.cc");
+ auto time_wo2 =
+ std::filesystem::last_write_time(BUILDDIR + "/" + BUILDDIR +
+ "/world-world_cc" + obj_ext);
+ if(time_w == time_w2)
+ {
+ fail();
+ }
+ if(time_wo == time_wo2)
+ {
+ fail();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // generated part2: many-to-one generation
+ {
+ std::filesystem::remove(BUILDDIR + "/world.cc");
+ std::filesystem::remove(BUILDDIR + "/foo.cc");
+ std::filesystem::remove(BUILDDIR + "/many_to_one.cc");
+
+ run_ctor({"-v", "many_to_one"});
+
+ // world.cc should not be generated at this point
+ assert_exists(BUILDDIR+"/world.cc");
+
+ // foo.cc should have been generated at this point
+ assert_not_exists(BUILDDIR+"/foo.cc");
+
+ // many_to_one.cc should have been generated
+ assert_not_exists(BUILDDIR+"/many_to_one.cc");
+
+ auto time = std::filesystem::last_write_time(BUILDDIR + "/many_to_one.cc");
+ std::this_thread::sleep_for(1100ms);
+
+ Tracker ctor_bin(ctor_exe);
+
+ // remove "foo.cc" from sources list, so it only contains hello.cc
+ copy_config("generated2");
+
+ // rebuild
+ run_ctor({"-v", "many_to_one"});
+
+ // Verify that the change resulted in a ctor rebuild
+ assert_changed(ctor_bin);
+
+ // Verify that source-list change triggered a new build of many_to_one.cc
+ auto time2 =
+ std::filesystem::last_write_time(BUILDDIR + "/many_to_one.cc");
+ if(time == time2)
+ {
+ fail();
+ }
+ }
+
+ return 0;
+}
diff --git a/test/suite/test.sh b/test/suite/test.sh
index c112351..4638c0d 100755
--- a/test/suite/test.sh
+++ b/test/suite/test.sh
@@ -1,114 +1,4 @@
#!/bin/bash
: ${CXX:=g++}
-: ${CTORDIR:=../../build}
-: ${BUILDDIR:=build}
-CXX=$(which $CXX)
-
-function fail
-{
- echo "*** Failure at line $1"
- exit 1
-}
-
-function ctor
-{
- echo "*** Running: ./ctor $*"
- ./ctor $*
-}
-
-# Wipe the board
-rm -Rf ${BUILDDIR}
-rm -f configuration.cc
-rm -f ctor
-
-echo "** ctor_files/ctor.cc.base"
-cp ctor_files/ctor.cc.base ctor.cc
-
-# Compile bootstrap binary
-$CXX -pthread -std=c++20 -L${CTORDIR} -lctor -I../../src ctor.cc -o ctor || fail ${LINENO}
-
-# No build files should have been created yet
-[ -d ${BUILDDIR} ] && fail ${LINENO}
-
-# capture md5 sum of ctor binary before configure is called
-MD5=`md5sum ctor`
-ctor configure --ctor-includedir ../../src --ctor-libdir=${CTORDIR} --build-dir=${BUILDDIR}
-
-# ctor should be rebuilt at this point, so md5 sum should have changed
-(echo $MD5 | md5sum --status -c) && fail ${LINENO}
-
-# configuration.cc should have been generated now
-[ ! -f configuration.cc ] && fail ${LINENO}
-
-# Shouldn't compile anything yet - only configure
-[ -f ${BUILDDIR}/hello-hello_cc.o ] && fail ${LINENO}
-
-MD5=`md5sum ctor`
-
-# Run normally to build project
-ctor -v
-
-# Compiled object should now exist
-[ ! -f ${BUILDDIR}/hello-hello_cc.o ] && fail ${LINENO}
-
-# ctor should not have been rebuilt, so md5 sum should be the same
-(echo $MD5 | md5sum --status -c) || fail ${LINENO}
-
-MOD1=`stat -c %Y ${BUILDDIR}/hello-hello_cc.o`
-touch hello.cc
-sleep 1.1
-
-# Run normally to rebuild hello.cc
-ctor -v
-
-# Object file should have been recompiled
-MOD2=`stat -c %Y ${BUILDDIR}/hello-hello_cc.o`
-[[ $MOD1 == $MOD2 ]] && fail ${LINENO}
-
-# Replacve -DFOO with -DBAR in foo external.cxxflags
-echo "** ctor_files/ctor.cc.bar"
-cp ctor_files/ctor.cc.bar ctor.cc
-
-MD5C=`md5sum configuration.cc`
-MD5=`md5sum ctor`
-MOD1=`stat -c %Y build/hello-hello_cc.o`
-sleep 1.1
-
-# Run normally to reconfigure, rebuild ctor and rebuild hello.cc
-ctor -v
-
-MOD2=`stat -c %Y ${BUILDDIR}/hello-hello_cc.o`
-[[ $MOD1 == $MOD2 ]] && fail ${LINENO}
-(echo $MD5C | md5sum --status -c) && fail ${LINENO}
-(echo $MD5 | md5sum --status -c) && fail ${LINENO}
-
-echo "** ctor_files/ctor.cc.multi"
-cp ctor_files/ctor.cc.multi ctor.cc
-
-MD5C=`md5sum configuration.cc`
-MD5=`md5sum ctor`
-MOD1=`stat -c %Y ${BUILDDIR}/hello-hello_cc.o`
-sleep 1.1
-
-# Run normally to reconfigure, rebuild ctor and rebuild hello.cc
-ctor -v
-
-MOD2=`stat -c %Y ${BUILDDIR}/hello-hello_cc.o`
-[[ $MOD1 == $MOD2 ]] && fail ${LINENO}
-(echo $MD5C | md5sum --status -c) && fail ${LINENO}
-(echo $MD5 | md5sum --status -c) && fail ${LINENO}
-
-# now touching foobar.h, should retrigger re-configuration
-touch foobar.h
-
-MOD1=`stat -c %Y ctor`
-sleep 1.1
-
-# Run normally to reconfigure, rebuild ctor and rebuild hello.cc
-ctor -v
-
-MOD2=`stat -c %Y ctor`
-[[ $MOD1 == $MOD2 ]] && fail ${LINENO}
-
-exit 0
+$CXX $LDFLAGS $CXXFLAGS -std=c++20 -Wall test.cc -o test && ./test
diff --git a/test/tasks_test.cc b/test/tasks_test.cc
index 3de6982..cbd0864 100644
--- a/test/tasks_test.cc
+++ b/test/tasks_test.cc
@@ -10,6 +10,7 @@ ctor::build_configurations ctorTestConfigs1(const ctor::settings&)
return
{
{
+ .name = "Target1",
.target = "target1",
.sources = {"foo.cc", "bar.c"},
},
@@ -33,16 +34,29 @@ ctor::build_configurations ctorTestConfigs2(const ctor::settings&)
}
}
+namespace test_global {
+ctor::toolchain toolchain{};
+ctor::arch arch{};
+}
+const ctor::configuration& ctor::get_configuration()
+{
+ static ctor::configuration cfg{};
+ cfg.build_toolchain = test_global::toolchain;
+ cfg.build_arch = test_global::arch;
+ return cfg;
+}
+
+
REG(ctorTestConfigs1);
REG(ctorTestConfigs2);
std::size_t count(const std::vector<std::shared_ptr<Task>>& tasks,
- const std::string& name)
+ const std::filesystem::path& name)
{
auto cnt{0u};
for(const auto& task : tasks)
{
- if(task->target() == name)
+ if(task->target() == name.string())
{
cnt++;
}
@@ -54,16 +68,17 @@ class TestTask
: public Task
{
public:
- TestTask(const std::string& name, bool dirty,
+ TestTask(const ctor::build_configuration& config,
+ const ctor::settings& settings,
+ const std::string& name, bool dirty,
const std::vector<std::string>& deps = {})
- : Task({}, {}, {})
+ : Task(config, settings, {})
, task_name(name)
, task_dirty(dirty)
, task_deps(deps)
{
}
- std::string name() const override { return task_name; }
int clean() override { return 0; }
std::vector<std::string> depends() const override { return task_deps; }
std::string target() const override { return task_name; }
@@ -81,11 +96,14 @@ class TasksTest
: public uUnit
{
public:
+ using fs = std::filesystem::path;
+
TasksTest()
{
uTEST(TasksTest::getTargets_test);
uTEST(TasksTest::getTasks_test);
uTEST(TasksTest::getNextTask_test);
+ uTEST(TasksTest::comparison_test);
}
void getTargets_test()
@@ -108,26 +126,29 @@ public:
void getTasks_test()
{
+ test_global::toolchain = ctor::toolchain::gcc;
+ test_global::arch = ctor::arch::unix;
+
using namespace std::string_literals;
ctor::settings settings{ .builddir = "foo" };
{
auto tasks = getTasks(settings);
uASSERT_EQUAL(6u, tasks.size());
// Note: count() is used here because the order doesn't matter
- uASSERT_EQUAL(1u, count(tasks, "target1"s));
- uASSERT_EQUAL(1u, count(tasks, "target2"s));
- uASSERT_EQUAL(1u, count(tasks, "target3"s));
- uASSERT_EQUAL(1u, count(tasks, "target4"s));
- uASSERT_EQUAL(1u, count(tasks, "test/target1-foo_cc.o"s));
- uASSERT_EQUAL(1u, count(tasks, "test/target1-bar_c.o"s));
+ uASSERT_EQUAL(1u, count(tasks, fs("target1")));
+ uASSERT_EQUAL(1u, count(tasks, fs("target2")));
+ uASSERT_EQUAL(1u, count(tasks, fs("target3")));
+ uASSERT_EQUAL(1u, count(tasks, fs("target4")));
+ uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o"));
+ uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o"));
}
{
auto tasks = getTasks(settings, {"target1", "target3"});
uASSERT_EQUAL(4u, tasks.size());
- uASSERT_EQUAL(1u, count(tasks, "target1"s));
- uASSERT_EQUAL(1u, count(tasks, "target3"s));
- uASSERT_EQUAL(1u, count(tasks, "test/target1-foo_cc.o"s));
- uASSERT_EQUAL(1u, count(tasks, "test/target1-bar_c.o"s));
+ uASSERT_EQUAL(1u, count(tasks, fs("target1")));
+ uASSERT_EQUAL(1u, count(tasks, fs("target3")));
+ uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o"));
+ uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o"));
}
{
auto tasks = getTasks(settings, {"no-such-target"});
@@ -149,11 +170,12 @@ public:
uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
}
- uASSERT_EQUAL(nullptr, getNextTask(allTasks, dirtyTasks));
+ uASSERT_EQUAL(nullptr, getNextTask({}, allTasks, dirtyTasks));
}
{ // Zero (One task, no dirty)
- auto task1 = std::make_shared<TestTask>("task1", false);
+ ctor::build_configuration config;
+ auto task1 = std::make_shared<TestTask>(config, settings, "task1", false);
std::vector<std::shared_ptr<Task>> allTasks;
allTasks.push_back(task1);
@@ -165,11 +187,12 @@ public:
uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
}
- uASSERT_EQUAL(nullptr, getNextTask(allTasks, dirtyTasks));
+ uASSERT_EQUAL(nullptr, getNextTask({}, allTasks, dirtyTasks));
}
{ // One (One task, one dirty)
- auto task1 = std::make_shared<TestTask>("task1", true);
+ ctor::build_configuration config;
+ auto task1 = std::make_shared<TestTask>(config, settings, "task1", true);
std::vector<std::shared_ptr<Task>> allTasks;
allTasks.push_back(task1);
@@ -182,13 +205,14 @@ public:
uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
}
- uASSERT_EQUAL(task1, getNextTask(allTasks, dirtyTasks));
+ uASSERT_EQUAL(task1, getNextTask({}, allTasks, dirtyTasks));
uASSERT_EQUAL(0u, dirtyTasks.size());
}
{ // One (Two tasks, one dirty)
- auto task1 = std::make_shared<TestTask>("task1", false);
- auto task2 = std::make_shared<TestTask>("task2", true);
+ ctor::build_configuration config;
+ auto task1 = std::make_shared<TestTask>(config, settings, "task1", false);
+ auto task2 = std::make_shared<TestTask>(config, settings, "task2", true);
std::vector<std::shared_ptr<Task>> allTasks;
allTasks.push_back(task1);
@@ -202,15 +226,17 @@ public:
uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
}
- uASSERT_EQUAL(task2, getNextTask(allTasks, dirtyTasks));
+ uASSERT_EQUAL(task2, getNextTask({}, allTasks, dirtyTasks));
uASSERT_EQUAL(0u, dirtyTasks.size());
}
{ // One (Two tasks, one dirty which depends on the other)
- auto task1 = std::make_shared<TestTask>("task1", false);
+ ctor::build_configuration config;
+ auto task1 = std::make_shared<TestTask>(config, settings, "task1", false);
std::vector<std::string> deps = {"task1"};
- auto task2 = std::make_shared<TestTask>("task2", true, deps);
+ auto task2 =
+ std::make_shared<TestTask>(config, settings, "task2", true, deps);
std::vector<std::shared_ptr<Task>> allTasks;
allTasks.push_back(task1);
@@ -224,15 +250,17 @@ public:
uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
}
- uASSERT_EQUAL(task2, getNextTask(allTasks, dirtyTasks));
+ uASSERT_EQUAL(task2, getNextTask({}, allTasks, dirtyTasks));
uASSERT_EQUAL(0u, dirtyTasks.size());
}
{ // One (Two tasks, Both dirty, one depends on the other)
- auto task1 = std::make_shared<TestTask>("task1", true);
+ ctor::build_configuration config{};
+ auto task1 = std::make_shared<TestTask>(config, settings, "task1", true);
std::vector<std::string> deps = {"task1"};
- auto task2 = std::make_shared<TestTask>("task2", true, deps);
+ auto task2 =
+ std::make_shared<TestTask>(config, settings, "task2", true, deps);
std::vector<std::shared_ptr<Task>> allTasks;
allTasks.push_back(task2);
@@ -247,10 +275,54 @@ public:
uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
}
- uASSERT_EQUAL(task1, getNextTask(allTasks, dirtyTasks));
+ uASSERT_EQUAL(task1, getNextTask({}, allTasks, dirtyTasks));
uASSERT_EQUAL(1u, dirtyTasks.size());
}
+ }
+ void comparison_test()
+ {
+ test_global::toolchain = ctor::toolchain::gcc;
+ test_global::arch = ctor::arch::unix;
+
+ using namespace std::string_literals;
+ ctor::settings settings{ .builddir = "foo" };
+ { // Test Task::operator==
+ auto tasks = getTasks(settings, {"target1"});
+ uASSERT_EQUAL(3u, tasks.size());
+ uASSERT_EQUAL(1u, count(tasks, fs("target1")));
+ uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o"));
+ uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o"));
+
+ int cnt1{};
+ int cnt2{};
+ int cnt3{};
+ for(const auto& task : tasks)
+ {
+ if(task->target() == fs("target1"))
+ {
+ ++cnt1;
+ uASSERT(*task == "target1");
+ uASSERT(*task == "Target1");
+ }
+ if(task->target() == fs("test")/"target1-foo_cc.o")
+ {
+ ++cnt2;
+ uASSERT(*task != "target1");
+ uASSERT(*task != "Target1");
+ }
+ if(task->target() == fs("test")/"target1-bar_c.o")
+ {
+ ++cnt3;
+ uASSERT(*task != "target1");
+ uASSERT(*task != "Target1");
+ }
+ }
+ // Assert that we did actually perform all three tests exactly once
+ uASSERT_EQUAL(1, cnt1);
+ uASSERT_EQUAL(1, cnt2);
+ uASSERT_EQUAL(1, cnt3);
+ }
}
};
diff --git a/test/testprog.cc b/test/testprog.cc
index dbfb665..93edc3f 100644
--- a/test/testprog.cc
+++ b/test/testprog.cc
@@ -1,8 +1,9 @@
#include <iostream>
#include <fstream>
#include <string>
+#include <csignal>
-extern const char **environ; // see 'man environ'
+extern char **environ;
int main(int argc, const char* argv[])
{
@@ -40,6 +41,11 @@ int main(int argc, const char* argv[])
abort();
}
+ if(cmd == "segfault")
+ {
+ raise(SIGSEGV);
+ }
+
if(cmd == "throw")
{
throw "ouch";
diff --git a/test/tmpfile.h b/test/tmpfile.h
index 5d114d0..0f83a20 100644
--- a/test/tmpfile.h
+++ b/test/tmpfile.h
@@ -3,39 +3,29 @@
// See accompanying file LICENSE for details.
#pragma once
-#include <cstdlib>
-#include <unistd.h>
+#include <cstdio>
-#ifdef _WIN32
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#endif
-
-struct tmp_file
+class TmpFile
{
- tmp_file(const std::string& data = {})
+public:
+ TmpFile(const std::string& data = {})
{
- int fd;
-#ifdef _WIN32
- char templ[] = "ctor_tmp_file-XXXXXX"; // buffer for filename
- _mktemp_s(templ, sizeof(templ));
- fd = open(templ, O_CREAT | O_RDWR);
-#else
- char templ[] = "/tmp/ctor_tmp_file-XXXXXX"; // buffer for filename
- fd = mkstemp(templ);
-#endif
- filename = templ;
- auto sz = write(fd, data.data(), data.size());
- (void)sz;
- close(fd);
+ auto tmp_dir = std::filesystem::temp_directory_path();
+ auto tmp_file_template = tmp_dir / "ctor_tmp_file-";
+ std::FILE* fp{nullptr};
+ int counter{};
+ while(!fp)
+ {
+ filename = tmp_file_template.string() + std::to_string(counter++);
+ fp = std::fopen(filename.data(), "wx");
+ }
+ std::fwrite(data.data(), data.size(), 1, fp);
+ std::fclose(fp);
}
- ~tmp_file()
+ ~TmpFile()
{
- unlink(filename.data());
+ std::filesystem::remove(filename);
}
const std::string& get() const
diff --git a/test/tools_test.cc b/test/tools_test.cc
index a428ea1..5ae04c3 100644
--- a/test/tools_test.cc
+++ b/test/tools_test.cc
@@ -114,33 +114,27 @@ bool operator!=(const ctor::asm_flag& a, const ctor::asm_flag& b)
#include <uunit.h>
-namespace {
-std::string conf_host_cxx{};
-std::string conf_build_cxx{};
-}
-
const ctor::configuration& ctor::get_configuration()
{
static ctor::configuration cfg;
return cfg;
}
-const std::string& ctor::configuration::get(const std::string& key, const std::string& defval) const
+std::string ctor::configuration::get(const std::string& key, [[maybe_unused]]const std::string& default_value) const
{
if(key == ctor::cfg::host_cxx)
{
- return conf_host_cxx;
+ return {};
}
if(key == ctor::cfg::build_cxx)
{
- return conf_build_cxx;
+ return {};
}
assert(false); // bad key
- static std::string res{};
- return res;
+ return {};
}
class ToolsTest
@@ -526,10 +520,6 @@ public:
act = ld_option(ctor::toolchain::gcc, ctor::ld_opt::output, "foo");
uASSERT_EQUAL(exp, act);
- exp = { "-s" };
- act = ld_option(ctor::toolchain::gcc, ctor::ld_opt::strip);
- uASSERT_EQUAL(exp, act);
-
exp = { "-Wall" };
act = ld_option(ctor::toolchain::gcc, ctor::ld_opt::warn_all);
uASSERT_EQUAL(exp, act);
@@ -577,10 +567,6 @@ public:
act = ld_option(ctor::toolchain::clang, ctor::ld_opt::output, "foo");
uASSERT_EQUAL(exp, act);
- exp = { "-s" };
- act = ld_option(ctor::toolchain::clang, ctor::ld_opt::strip);
- uASSERT_EQUAL(exp, act);
-
exp = { "-Wall" };
act = ld_option(ctor::toolchain::clang, ctor::ld_opt::warn_all);
uASSERT_EQUAL(exp, act);
@@ -628,10 +614,6 @@ public:
act = ld_option(ctor::toolchain::any, ctor::ld_opt::output, "foo");
uASSERT_EQUAL(exp, act);
- exp = { "{ctor::ld_opt::strip}" };
- act = ld_option(ctor::toolchain::any, ctor::ld_opt::strip);
- uASSERT_EQUAL(exp, act);
-
exp = { "{ctor::ld_opt::warn_all}" };
act = ld_option(ctor::toolchain::any, ctor::ld_opt::warn_all);
uASSERT_EQUAL(exp, act);
@@ -914,7 +896,7 @@ public:
// Mismatching toolchain (required vs actual) results in no output
// otherwise to_strings is just a proxy for ld_option
- act = to_strings(ctor::toolchain::gcc, {ctor::toolchain::clang, ctor::ld_opt::strip});
+ act = to_strings(ctor::toolchain::gcc, {ctor::toolchain::clang, ctor::ld_opt::threads});
uASSERT_EQUAL(exp, act);
}
diff --git a/test/uunit b/test/uunit
-Subproject bc078da645412c6b36ef59e635d6c35d11088c9
+Subproject 0f371777e02dd068f9675a05a29230221d5d6a7