diff options
50 files changed, 2649 insertions, 665 deletions
diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..b483ac3 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,27 @@ +pipeline { + agent { label 'c++20' } + + stages { + stage('Build') { + steps { + echo 'Building...' + sh 'rm -Rf build' + sh './bootstrap.sh' + } + } + stage('Test') { + steps { + echo 'Testing...' + sh './ctor check' + } + } + } + + post { + always { + xunit(thresholds: [ skipped(failureThreshold: '0'), + failed(failureThreshold: '0') ], + tools: [ CppUnit(pattern: 'build/test/*.xml') ]) + } + } +}
\ No newline at end of file @@ -1,4 +1,4 @@ -# Introduction + # Introduction Have you ever wondered, if you want to compile a C++ program, why you always need to learn another language to make it build properly with dependency @@ -1,8 +0,0 @@ -Glob convenience methods -std::string glob = getFilesInDir(...); - -Add support for pre/post build hooks with conditions - -Add shell script targets to be able to generate soucre files that can -be included as sources in normal build targets. -For example imageconverter diff --git a/bootstrap.sh b/bootstrap.sh index 833d3be..3a9c454 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,4 +1,8 @@ -#!/bin/bash +#!/bin/sh +set -e echo "Bootstrapping..." -g++ -std=c++17 -I. -Isrc -pthread src/*.cc ctor.cc test/ctor.cc -o ctor && \ -echo "Done. Now run ./ctor to build." +g++ -std=c++20 -Wall -O3 -Isrc -pthread src/bootstrap.cc ctor.cc test/ctor.cc -o ctor +./ctor +g++ -std=c++20 -Wall -O3 -Isrc -pthread ctor.cc test/ctor.cc -Lbuild -lctor -o ctor +./ctor configure --ctor-includedir=src --ctor-libdir=build +echo "Done. Now run ./ctor to (re)build." @@ -1,11 +1,11 @@ // -*- c++ -*- // Distributed under the BSD 2-Clause License. // See accompanying file LICENSE for details. -#include "libctor.h" +#include <libctor.h> namespace { -BuildConfigurations ctorConfigs() +BuildConfigurations ctorConfigs(const Settings& settings) { return { @@ -16,23 +16,29 @@ BuildConfigurations ctorConfigs() "src/build.cc", "src/configure.cc", "src/execute.cc", + "src/externals_manual.cc", "src/libctor.cc", "src/rebuild.cc", "src/task.cc", "src/task_ar.cc", + "src/task_fn.cc", "src/task_cc.cc", "src/task_ld.cc", "src/task_so.cc", "src/tasks.cc", + "src/tools.cc", + "src/util.cc", "src/unittest.cc", }, - .cxxflags = { - "-std=c++17", - "-O3", - "-s", - "-Wall", - "-Werror", - "-Isrc", + .flags = { + .cxxflags = { + "-std=c++20", + "-O3", + "-g", + "-Wall", + "-Werror", + "-Isrc", + }, }, } }; diff --git a/src/bootstrap.cc b/src/bootstrap.cc new file mode 100644 index 0000000..9a3c321 --- /dev/null +++ b/src/bootstrap.cc @@ -0,0 +1,83 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <iostream> +#include <array> + +#define BOOTSTRAP + +#include "libctor.h" + +#include "util.cc" +#include "rebuild.cc" +#include "task.cc" +#include "task_cc.cc" +#include "task_ar.cc" +#include "execute.cc" +#include "tasks.cc" +#include "build.cc" +#include "tools.cc" + +std::filesystem::path configurationFile("configuration.cc"); +std::filesystem::path configHeaderFile("config.h"); + +const Configuration default_configuration{}; +const Configuration& configuration() +{ + return default_configuration; +} + +bool hasConfiguration(const std::string& key) +{ + return false; +} + +const std::string& getConfiguration(const std::string& key, + const std::string& defaultValue) +{ + return defaultValue; +} + +int main(int argc, char* argv[]) +{ + if(argc > 1) + { + std::cerr << "This is a minimal bootstrap version of " << argv[0] << + " which doesn't support any arguments\n"; + return 1; + } + + Settings settings{}; + + settings.builddir = getConfiguration(cfg::builddir, "build"); + settings.parallel_processes = + std::max(1u, std::thread::hardware_concurrency() * 2 - 1); + settings.verbose = 0; + auto all_tasks = getTasks(settings, {}, false); + for(auto task : all_tasks) + { + if(task->registerDepTasks(all_tasks)) + { + return 1; + } + } + + std::vector<Target> non_unittest_targets; + auto& targets = getTargets(settings); + for(const auto& target : targets) + { + if(target.config.type != TargetType::UnitTest && + target.config.type != TargetType::UnitTestLib) + { + non_unittest_targets.push_back(target); + } + } + + auto ret = build(settings, "all", non_unittest_targets, all_tasks); + if(ret != 0) + { + return ret; + } + + return 0; +} diff --git a/src/build.cc b/src/build.cc index 1b70c5b..98952e0 100644 --- a/src/build.cc +++ b/src/build.cc @@ -9,31 +9,44 @@ #include <chrono> #include <set> #include <thread> +#include <list> + +#include "libctor.h" using namespace std::chrono_literals; int build(const Settings& settings, const std::string& name, - const std::list<std::shared_ptr<Task>>& tasks, - const std::list<std::shared_ptr<Task>>& all_tasks) + const std::set<std::shared_ptr<Task>>& tasks, + const std::set<std::shared_ptr<Task>>& all_tasks, + bool dryrun) { if(settings.verbose > 1) { std::cout << "Building '" << name << "'\n"; } - std::list<std::shared_ptr<Task>> dirtyTasks; + std::set<std::shared_ptr<Task>> dirtyTasks; for(auto task : tasks) { if(task->dirty()) { - dirtyTasks.push_back(task); + dirtyTasks.insert(task); } } + // Dry-run returns number of dirty tasks but otherwise does nothing. + if(dryrun) + { + return dirtyTasks.size(); + } + if(dirtyTasks.empty()) { - std::cout << "Nothing to be done for '"<< name << "'\n"; + if(settings.verbose > -1) + { + std::cout << "Nothing to be done for '"<< name << "'\n"; + } return 0; } @@ -71,32 +84,33 @@ int build(const Settings& settings, return task->run(); })); started_one = true; - std::this_thread::sleep_for(2ms); + // Make sure we don't start tasks on top of each other to prevent + // straining the disk. + std::this_thread::sleep_for(50ms); } for(auto process = processes.begin(); process != processes.end(); ++process) { - if(process->valid()) + if(process->valid() == false) { - if(process->get() != 0) - { - // TODO: Wait for other processes to finish before returning - return 1; - } - processes.erase(process); - break; + continue; } - } - if(started_one) - { - std::this_thread::sleep_for(2ms); + auto ret = process->get(); + if(ret != 0) + { + // NOTE Wait for other processes to finish before returning + return ret; + } + processes.erase(process); + break; } - else + + if(!started_one) // prevent polling too fast if no task is yet ready { - std::this_thread::sleep_for(200ms); + std::this_thread::sleep_for(10ms); } } @@ -104,11 +118,15 @@ int build(const Settings& settings, process != processes.end(); ++process) { + if(process->valid() == false) + { + continue; + } process->wait(); auto ret = process->get(); if(ret != 0) { - return 1; + return ret; } } @@ -138,23 +156,24 @@ std::set<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task) int build(const Settings& settings, const std::string& name, - const std::list<std::shared_ptr<Task>>& all_tasks) + const std::set<std::shared_ptr<Task>>& all_tasks, + bool dryrun) { bool task_found{false}; for(auto task : all_tasks) { - if(task->name() == name || task->target() == name) + if(*task == name) { - std::cout << name << "\n"; task_found = true; auto depSet = getDepTasks(task); - std::list<std::shared_ptr<Task>> ts; + std::set<std::shared_ptr<Task>> ts; for(const auto& task : depSet) { - ts.push_back(task); + ts.insert(task); } - auto ret = build(settings, name, ts, all_tasks); + + auto ret = build(settings, name, ts, all_tasks, dryrun); if(ret != 0) { return ret; @@ -176,25 +195,25 @@ int build(const Settings& settings, int build(const Settings& settings, const std::string& name, const std::vector<Target>& targets, - const std::list<std::shared_ptr<Task>>& all_tasks) + const std::set<std::shared_ptr<Task>>& all_tasks, + bool dryrun) { bool task_found{false}; - std::list<std::shared_ptr<Task>> ts; + std::set<std::shared_ptr<Task>> ts; for(const auto& target : targets) { for(auto task : all_tasks) { - if(task->name() == target.config.target || - task->target() == target.config.target) + if(!task->derived() && // only consider non-derived tasks + task->buildConfig().target == target.config.target) { - std::cout << target.config.target << "\n"; task_found = true; auto depSet = getDepTasks(task); for(const auto& task : depSet) { - ts.push_back(task); + ts.insert(task); } } } @@ -206,5 +225,5 @@ int build(const Settings& settings, return 1; } - return build(settings, name, ts, all_tasks); + return build(settings, name, ts, all_tasks, dryrun); } diff --git a/src/build.h b/src/build.h index 1db3f5c..caf7a68 100644 --- a/src/build.h +++ b/src/build.h @@ -4,23 +4,30 @@ #pragma once #include <string> -#include <list> +#include <set> #include <memory> #include "task.h" -#include "settings.h" #include "tasks.h" +struct Settings; + +//! Dry-run returns number of dirty tasks but otherwise does nothing. int build(const Settings& settings, const std::string& name, - const std::list<std::shared_ptr<Task>>& tasks, - const std::list<std::shared_ptr<Task>>& all_tasks); + const std::set<std::shared_ptr<Task>>& tasks, + const std::set<std::shared_ptr<Task>>& all_tasks, + bool dryrun = false); +//! Dry-run returns number of dirty tasks but otherwise does nothing. int build(const Settings& settings, const std::string& name, - const std::list<std::shared_ptr<Task>>& all_tasks); + const std::set<std::shared_ptr<Task>>& all_tasks, + bool dryrun = false); +//! Dry-run returns number of dirty tasks but otherwise does nothing. int build(const Settings& settings, const std::string& name, const std::vector<Target>& targets, - const std::list<std::shared_ptr<Task>>& all_tasks); + const std::set<std::shared_ptr<Task>>& all_tasks, + bool dryrun = false); diff --git a/src/configure.cc b/src/configure.cc index b3517dc..a81f8ad 100644 --- a/src/configure.cc +++ b/src/configure.cc @@ -6,36 +6,67 @@ #include <iostream> #include <filesystem> #include <fstream> +#include <optional> #include <getoptpp/getoptpp.hpp> -#include "settings.h" #include "execute.h" #include "libctor.h" #include "tasks.h" +#include "rebuild.h" +#include "externals.h" std::filesystem::path configurationFile("configuration.cc"); std::filesystem::path configHeaderFile("config.h"); -const std::map<std::string, std::string> default_configuration{}; -const std::map<std::string, std::string>& __attribute__((weak)) configuration() +std::map<std::string, std::string> external_includedir; +std::map<std::string, std::string> external_libdir; + +const Configuration default_configuration{}; +const Configuration& __attribute__((weak)) configuration() { return default_configuration; } +namespace ctor +{ +std::optional<std::string> includedir; +std::optional<std::string> libdir; +} + bool hasConfiguration(const std::string& key) { + if(key == cfg::ctor_includedir && ctor::includedir) + { + return true; + } + + if(key == cfg::ctor_libdir && ctor::libdir) + { + return true; + } + const auto& c = configuration(); - return c.find(key) != c.end(); + return c.tools.find(key) != c.tools.end(); } const std::string& getConfiguration(const std::string& key, const std::string& defaultValue) { + if(key == cfg::ctor_includedir && ctor::includedir) + { + return *ctor::includedir; + } + + if(key == cfg::ctor_libdir && ctor::libdir) + { + return *ctor::libdir; + } + const auto& c = configuration(); if(hasConfiguration(key)) { - return c.at(key); + return c.tools.at(key); } return defaultValue; @@ -44,7 +75,7 @@ const std::string& getConfiguration(const std::string& key, std::string locate(const std::string& arch, const std::string& app) { std::string path_env = std::getenv("PATH"); - std::cout << path_env << "\n"; + //std::cout << path_env << "\n"; std::string program = app; if(!arch.empty()) @@ -72,15 +103,15 @@ std::string locate(const std::string& arch, const std::string& app) auto perms = std::filesystem::status(prog_path).permissions(); if((perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none) { - std::cout << " - executable by owner\n"; + //std::cout << " - executable by owner\n"; } if((perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none) { - std::cout << " - executable by group\n"; + //std::cout << " - executable by group\n"; } if((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) { - std::cout << " - executable by others\n"; + //std::cout << " - executable by others\n"; } return prog_path.string(); @@ -92,21 +123,38 @@ std::string locate(const std::string& arch, const std::string& app) return {}; } -int configure(int argc, char* argv[]) +class Args + : public std::vector<char*> { - Settings settings; - - settings.builddir = "build"; +public: + Args(const std::vector<std::string>& args) + { + resize(args.size() + 1); + (*this)[0] = strdup("./ctor"); + for(std::size_t i = 0; i < size() - 1; ++i) + { + (*this)[i + 1] = strdup(args[i].data()); + } + } - std::string cmd_str; - for(int i = 0; i < argc; ++i) + ~Args() { - if(i > 0) + for(std::size_t i = 0; i < size(); ++i) { - cmd_str += " "; + free((*this)[i]); } - cmd_str += argv[i]; } +}; + +// helper constant for the visitor +template<class> inline constexpr bool always_false_v = false; + +int regenerateCache(const Settings& default_settings, + const std::vector<std::string>& args, + const std::map<std::string, std::string>& env) +{ + Settings settings{default_settings}; + Args vargs(args); dg::Options opt; int key{128}; @@ -119,6 +167,8 @@ int configure(int argc, char* argv[]) std::string cxx_prog = "g++"; std::string ar_prog = "ar"; std::string ld_prog = "ld"; + std::string ctor_includedir; + std::string ctor_libdir; opt.add("build-dir", required_argument, 'b', "Set output directory for build files (default: '" + @@ -191,6 +241,65 @@ int configure(int argc, char* argv[]) return 0; }); + opt.add("ctor-includedir", required_argument, key++, + "Set path to ctor header file, used for re-compiling.", + [&]() { + ctor_includedir = optarg; + return 0; + }); + + opt.add("ctor-libdir", required_argument, key++, + "Set path to ctor library file, used for re-compiling.", + [&]() { + ctor_libdir = optarg; + return 0; + }); + + // Resolv externals + ExternalConfigurations externalConfigs; + for(std::size_t i = 0; i < numExternalConfigFiles; ++i) + { + auto newExternalConfigs = externalConfigFiles[i].cb(settings); + externalConfigs.insert(externalConfigs.end(), + newExternalConfigs.begin(), + newExternalConfigs.end()); + } + + auto add_path_args = + [&](const std::string& name) + { + opt.add(name + "-includedir", required_argument, key++, + "Set path to " + name + " header file.", + [&]() { + external_includedir[name] = optarg; + return 0; + }); + + opt.add(name + "-libdir", required_argument, key++, + "Set path to " + name + " libraries.", + [&]() { + external_libdir[name] = optarg; + return 0; + }); + }; + + for(const auto& ext : externalConfigs) + { + std::visit([&](auto&& arg) + { + using T = std::decay_t<decltype(arg)>; + if constexpr (std::is_same_v<T, ExternalManual>) + { + add_path_args(ext.name); + } + else + { + static_assert(always_false_v<T>, "non-exhaustive visitor!"); + } + }, ext.external); + + } + opt.add("help", no_argument, 'h', "Print this help text.", [&]() { @@ -200,14 +309,14 @@ int configure(int argc, char* argv[]) return 0; }); - opt.process(argc, argv); + opt.process(vargs.size(), vargs.data()); if(host_arch.empty()) { host_arch = build_arch; } - auto tasks = getTasks(settings); + auto tasks = getTasks(settings, {}, false); /* bool needs_cpp{false}; bool needs_c{false}; @@ -233,32 +342,29 @@ int configure(int argc, char* argv[]) } } */ - auto cc_env = getenv("CC"); - if(cc_env) + + auto cc_env = env.find("CC"); + if(cc_env != env.end()) { - cmd_str = std::string("CC=") + cc_env + " " + cmd_str; - cc_prog = cc_env; + cc_prog = cc_env->second; } - auto cxx_env = getenv("CXX"); - if(cxx_env) + auto cxx_env = env.find("CXX"); + if(cxx_env != env.end()) { - cmd_str = std::string("CXX=") + cxx_env + " " + cmd_str; - cxx_prog = cxx_env; + cxx_prog = cxx_env->second; } - auto ar_env = getenv("AR"); - if(ar_env) + auto ar_env = env.find("AR"); + if(ar_env != env.end()) { - cmd_str = std::string("AR=") + ar_env + " " + cmd_str; - ar_prog = ar_env; + ar_prog = ar_env->second; } - auto ld_env = getenv("LD"); - if(ld_env) + auto ld_env = env.find("LD"); + if(ld_env != env.end()) { - cmd_str = std::string("LD=") + ld_env + " " + cmd_str; - ld_prog = ld_env; + ld_prog = ld_env->second; } std::string host_cc = locate(host_arch, cc_prog); @@ -273,24 +379,113 @@ int configure(int argc, char* argv[]) std::cout << "Writing results to: " << configurationFile.string() << "\n"; { std::ofstream istr(configurationFile); - istr << "#include \"libctor.h\"\n\n"; - istr << "const std::map<std::string, std::string>& configuration()\n"; + istr << "#include <libctor.h>\n\n"; + istr << "const Configuration& configuration()\n"; istr << "{\n"; - istr << " static std::map<std::string, std::string> c =\n"; + istr << " static Configuration cfg =\n"; istr << " {\n"; - istr << " { \"cmd\", \"" << cmd_str << "\" },\n"; - istr << " { \"" << cfg::builddir << "\", \"" << settings.builddir << "\" },\n"; - istr << " { \"" << cfg::host_cc << "\", \"" << host_cc << "\" },\n"; - istr << " { \"" << cfg::host_cxx << "\", \"" << host_cxx << "\" },\n"; - istr << " { \"" << cfg::host_ar << "\", \"" << host_ar << "\" },\n"; - istr << " { \"" << cfg::host_ld << "\", \"" << host_ld << "\" },\n"; - istr << " { \"" << cfg::build_cc << "\", \"" << build_cc << "\" },\n"; - istr << " { \"" << cfg::build_cxx << "\", \"" << build_cxx << "\" },\n"; - istr << " { \"" << cfg::build_ar << "\", \"" << build_ar << "\" },\n"; - istr << " { \"" << cfg::build_ld << "\", \"" << build_ld << "\" },\n"; + istr << " .args = {"; + for(const auto& arg : args) + { + istr << "\"" << arg << "\","; + } + istr << "},\n"; + istr << " .env = {"; + for(const auto& e : env) + { + istr << "{\"" << e.first << "\", \"" << e.second << "\"}, "; + } + istr << "},\n"; + + istr << " .tools = {\n"; + istr << " { \"" << cfg::builddir << "\", \"" << settings.builddir << "\" },\n"; + istr << " { \"" << cfg::host_cc << "\", \"" << host_cc << "\" },\n"; + istr << " { \"" << cfg::host_cxx << "\", \"" << host_cxx << "\" },\n"; + istr << " { \"" << cfg::host_ar << "\", \"" << host_ar << "\" },\n"; + istr << " { \"" << cfg::host_ld << "\", \"" << host_ld << "\" },\n"; + istr << " { \"" << cfg::build_cc << "\", \"" << build_cc << "\" },\n"; + istr << " { \"" << cfg::build_cxx << "\", \"" << build_cxx << "\" },\n"; + istr << " { \"" << cfg::build_ar << "\", \"" << build_ar << "\" },\n"; + istr << " { \"" << cfg::build_ld << "\", \"" << build_ld << "\" },\n"; + if(!ctor_includedir.empty()) + { + istr << " { \"" << cfg::ctor_includedir << "\", \"" << ctor_includedir << "\" },\n"; + ctor::includedir = ctor_includedir; + } + if(!ctor_libdir.empty()) + { + istr << " { \"" << cfg::ctor_libdir << "\", \"" << ctor_libdir << "\" },\n"; + ctor::libdir = ctor_libdir; + } + + istr << " },\n"; + istr << " .externals = {\n"; + + for(const auto& ext : externalConfigs) + { + istr << " { \"" << ext.name << "\", {\n"; + Flags resolved_flags; + if(std::holds_alternative<ExternalManual>(ext.external)) + { + if(auto ret = resolv(settings, ext, + std::get<ExternalManual>(ext.external), + resolved_flags)) + { + return ret; + } + } + else + { + std::cout << "Unknown external type\n"; + return 1; + } + + if(!resolved_flags.cxxflags.empty()) + { + istr << " .cxxflags = {"; + for(const auto& flag : resolved_flags.cxxflags) + { + istr << "\"" << flag << "\","; + } + istr << "},\n"; + } + + if(!resolved_flags.cflags.empty()) + { + istr << " .cflags = {"; + for(const auto& flag : resolved_flags.cflags) + { + istr << "\"" << flag << "\","; + } + istr << "},\n"; + } + + if(!resolved_flags.ldflags.empty()) + { + istr << " .ldflags = {"; + for(const auto& flag : resolved_flags.ldflags) + { + istr << "\"" << flag << "\","; + } + istr << "},\n"; + } + + if(!resolved_flags.asmflags.empty()) + { + istr << " .asmflags = {"; + for(const auto& flag : resolved_flags.asmflags) + { + istr << "\"" << flag << "\","; + } + istr << "},\n"; + } + istr << " }},\n"; + } + + istr << " },\n"; istr << " };\n"; - istr << " return c;\n"; - istr << "}\n"; + istr << " return cfg;\n"; + istr << "}\n\n"; } { @@ -302,3 +497,94 @@ int configure(int argc, char* argv[]) return 0; } + +int configure(const Settings& global_settings, int argc, char* argv[]) +{ + Settings settings{global_settings}; + + std::vector<std::string> args; + for(int i = 2; i < argc; ++i) // skip command and the first 'configure' arg + { + args.push_back(argv[i]); + } + + std::map<std::string, std::string> env; + auto cc_env = getenv("CC"); + if(cc_env) + { + env["CC"] = cc_env; + } + + auto cxx_env = getenv("CXX"); + if(cxx_env) + { + env["CXX"] = cxx_env; + } + + auto ar_env = getenv("AR"); + if(ar_env) + { + env["AR"] = ar_env; + } + + auto ld_env = getenv("LD"); + if(ld_env) + { + env["LD"] = ld_env; + } + + auto ret = regenerateCache(settings, args, env); + if(ret != 0) + { + return ret; + } + + recompileCheck(settings, argc, argv, false); + + return 0; +} + +int reconfigure(const Settings& settings, int argc, char* argv[]) +{ + bool no_rerun{false}; + + std::vector<std::string> args; + for(int i = 2; i < argc; ++i) // skip executable name and 'reconfigure' arg + { + if(i == 2 && std::string(argv[i]) == "--no-rerun") + { + no_rerun = true; + continue; + } + args.push_back(argv[i]); + } + + const auto& cfg = configuration(); + + std::cout << "Re-running configure:\n"; + for(const auto& e : cfg.env) + { + std::cout << e.first << "=\"" << e.second << "\" "; + } + std::cout << argv[0] << " configure "; + for(const auto& arg : cfg.args) + { + std::cout << arg << " "; + } + std::cout << "\n"; + + auto ret = regenerateCache(settings, cfg.args, cfg.env); + if(ret != 0) + { + return ret; + } + + recompileCheck(settings, 1, argv, false); + + if(no_rerun) + { + return 0; // this was originally invoked by configure, don't loop + } + + return execute(argv[0], args); +} diff --git a/src/configure.h b/src/configure.h index de1b7e0..16499d6 100644 --- a/src/configure.h +++ b/src/configure.h @@ -6,16 +6,12 @@ #include <filesystem> #include <string> #include <map> +#include <vector> + +struct Settings; extern std::filesystem::path configurationFile;; extern std::filesystem::path configHeaderFile; -int configure(int argc, char* argv[]); - -bool hasConfiguration(const std::string& key); -const std::string& getConfiguration(const std::string& key, - const std::string& defaultValue); - -const std::map<std::string, std::string>& configuration(); - -extern const std::map<std::string, std::string> default_configuration; +int configure(const Settings& settings, int argc, char* argv[]); +int reconfigure(const Settings& settings, int argc, char* argv[]); diff --git a/src/settings.h b/src/externals.h index d71717a..7b4aa23 100644 --- a/src/settings.h +++ b/src/externals.h @@ -3,11 +3,4 @@ // See accompanying file LICENSE for details. #pragma once -#include <cstddef> - -struct Settings -{ - std::string builddir; - std::size_t parallel_processes; - int verbose{1}; -}; +#include "externals_manual.h" diff --git a/src/externals_manual.cc b/src/externals_manual.cc new file mode 100644 index 0000000..0e3cdfd --- /dev/null +++ b/src/externals_manual.cc @@ -0,0 +1,37 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "externals_manual.h" + +#include <map> + +#include "libctor.h" + +#include "util.h" +#include "tools.h" + +extern std::map<std::string, std::string> external_includedir; +extern std::map<std::string, std::string> external_libdir; + +int resolv(const Settings& settings, const ExternalConfiguration& config, + const ExternalManual& ext, Flags& flags) +{ + auto tool_chain = getToolChain(config.system); + + flags = ext.flags; + + auto inc = external_includedir.find(config.name); + if(inc != external_includedir.end()) + { + append(flags.cflags, getOption(tool_chain, opt::include_path, inc->second)); + append(flags.cxxflags, getOption(tool_chain, opt::include_path, inc->second)); + } + + auto lib = external_libdir.find(config.name); + if(lib != external_libdir.end()) + { + append(flags.ldflags, getOption(tool_chain, opt::library_path, lib->second)); + } + + return 0; +} diff --git a/src/externals_manual.h b/src/externals_manual.h new file mode 100644 index 0000000..7bd968d --- /dev/null +++ b/src/externals_manual.h @@ -0,0 +1,12 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +struct Settings; +struct ExternalConfiguration; +struct ExternalManual; +struct Flags; + +int resolv(const Settings& settings, const ExternalConfiguration& name, + const ExternalManual& ext, Flags& flags); diff --git a/src/libctor.cc b/src/libctor.cc index 4d0d6d9..d188771 100644 --- a/src/libctor.cc +++ b/src/libctor.cc @@ -20,25 +20,29 @@ #include <getoptpp/getoptpp.hpp> #include "libctor.h" -#include "settings.h" #include "configure.h" #include "rebuild.h" #include "tasks.h" #include "build.h" #include "unittest.h" + int main(int argc, char* argv[]) { + Settings settings{}; + + settings.builddir = getConfiguration(cfg::builddir, settings.builddir); + settings.parallel_processes = + std::max(1u, std::thread::hardware_concurrency()) * 2 - 1; + if(argc > 1 && std::string(argv[1]) == "configure") { - return configure(argc, argv); + return configure(settings, argc, argv); } - Settings settings{}; - - settings.builddir = getConfiguration(cfg::builddir, "build"); - settings.parallel_processes = - std::max(1u, std::thread::hardware_concurrency() * 2 - 1); - settings.verbose = 0; + if(argc > 1 && std::string(argv[1]) == "reconfigure") + { + return reconfigure(settings, argc, argv); + } bool write_compilation_database{false}; std::string compilation_database; @@ -85,6 +89,13 @@ int main(int argc, char* argv[]) return 0; }); + opt.add("quiet", no_argument, 'q', + "Be completely silent.", + [&]() { + settings.verbose = -1; + return 0; + }); + opt.add("add", required_argument, 'a', "Add specified file to the build configurations.", [&]() { @@ -148,9 +159,10 @@ int main(int argc, char* argv[]) std::cout << "Usage: " << argv[0] << " [options] [target] ...\n"; std::cout << R"_( where target can be either: - configure - run configuration step (cannot be used with other targets). - clean - clean all generated files. - all - build all targets (default) + configure - run configuration step (cannot be used with other targets). + reconfigure - rerun configuration step with the same arguments as last (cannot be used with other targets). + clean - clean all generated files. + all - build all targets (default) or the name of a target which will be built along with its dependencies. Use '-l' to see a list of possible target names. @@ -178,6 +190,11 @@ Options: files.insert(configFiles[i].file); } + for(std::size_t i = 0; i < numExternalConfigFiles; ++i) + { + files.insert(externalConfigFiles[i].file); + } + for(const auto& file : files) { std::cout << file << "\n"; @@ -189,7 +206,7 @@ Options: no_default_build = true; for(const auto& add_file : add_files) { - reg(add_file.data(), [](){ return std::vector<BuildConfiguration>{};}); + reg(add_file.data()); } for(const auto& remove_file : remove_files) @@ -198,7 +215,7 @@ Options: } // Force rebuild if files were added - recompileCheck(settings, 1, argv, true, no_relaunch == false); + recompileCheck(settings, 1, argv, no_relaunch == false); } recompileCheck(settings, argc, argv); @@ -253,7 +270,7 @@ Options: { no_default_build = true; const auto& c = configuration(); - for(const auto& config : c) + for(const auto& config : c.tools) { std::cout << config.first << ": " << config.second << "\n"; } @@ -298,7 +315,8 @@ Options: auto& targets = getTargets(settings); for(const auto& target : targets) { - if(target.config.type == TargetType::UnitTest) + if(target.config.type == TargetType::UnitTest || + target.config.type == TargetType::UnitTestLib) { unittest_targets.push_back(target); } @@ -320,7 +338,8 @@ Options: auto& targets = getTargets(settings); for(const auto& target : targets) { - if(target.config.type != TargetType::UnitTest) + if(target.config.type != TargetType::UnitTest && + target.config.type != TargetType::UnitTestLib) { non_unittest_targets.push_back(target); } @@ -349,7 +368,8 @@ Options: auto& targets = getTargets(settings); for(const auto& target : targets) { - if(target.config.type != TargetType::UnitTest) + if(target.config.type != TargetType::UnitTest && + target.config.type != TargetType::UnitTestLib) { non_unittest_targets.push_back(target); } diff --git a/src/libctor.h b/src/libctor.h index 70c62a8..96e1115 100644 --- a/src/libctor.h +++ b/src/libctor.h @@ -3,9 +3,13 @@ // See accompanying file LICENSE for details. #pragma once +#include <source_location> #include <string> #include <vector> #include <map> +#include <variant> +#include <cstddef> +#include <functional> enum class TargetType { @@ -16,6 +20,8 @@ enum class TargetType DynamicLibrary, Object, UnitTest, + UnitTestLib, + Function, }; enum class Language @@ -33,29 +39,86 @@ enum class OutputSystem Build, // Internal tool during cross-compilation }; -struct BuildConfiguration +struct Source { - TargetType type{TargetType::Auto}; + Source(const char* file) : file(file) {} + Source(const std::string& file) : file(file) {} + Source(const char* file, Language lang) : file(file), language(lang) {} + Source(const std::string& file, 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, Language lang, const char* output) : file(file), language(lang), output(output) {} + Source(const std::string& file, Language lang, const std::string& output) : file(file), language(lang), output(output) {} + + std::string file; Language language{Language::Auto}; - OutputSystem system{OutputSystem::Host}; - std::string target; - std::vector<std::string> sources; // source list - std::vector<std::string> depends; // internal dependencies + std::string output{}; +}; + +struct Flags +{ std::vector<std::string> cxxflags; // flags for c++ compiler std::vector<std::string> cflags; // flags for c compiler std::vector<std::string> ldflags; // flags for linker std::vector<std::string> asmflags; // flags for asm translator }; +struct Settings +{ + std::string builddir{"build"}; + std::size_t parallel_processes{1}; + int verbose{0}; // -1: completely silent, 0: normal, 1: verbose, ... +}; + +struct BuildConfiguration; +using GeneratorCb = std::function<int(const std::string& input, + const std::string& output, + const BuildConfiguration& config, + const Settings& settings)>; + +struct BuildConfiguration +{ + std::string name; // Name - used for referring in other configurations. + TargetType type{TargetType::Auto}; + OutputSystem system{OutputSystem::Host}; + std::string target; // Output target file for this configuration + std::vector<Source> sources; // source list + std::vector<std::string> depends; // internal target dependencies + Flags flags; + std::vector<std::string> externals; // externals used by this configuration + GeneratorCb function; +}; + using BuildConfigurations = std::vector<BuildConfiguration>; -int reg(const char* location, BuildConfigurations (*cb)()); +int reg(BuildConfigurations (*cb)(const Settings&), + const std::source_location location = std::source_location::current()); + +// This type will use flags verbatim +struct ExternalManual +{ + Flags flags; +}; + + +struct ExternalConfiguration +{ + std::string name; // Name for configuration + OutputSystem system{OutputSystem::Host}; + std::variant<ExternalManual> external; +}; + +using ExternalConfigurations = std::vector<ExternalConfiguration>; + +int reg(ExternalConfigurations (*cb)(const Settings&), + const std::source_location location = std::source_location::current()); // Convenience macro - ugly but keeps things simple(r) #define CONCAT(a, b) CONCAT_INNER(a, b) #define CONCAT_INNER(a, b) a ## b #define UNIQUE_NAME(base) CONCAT(base, __LINE__) -#define REG(cb) namespace { int UNIQUE_NAME(unique) = reg(__FILE__, cb); } +#define REG(cb) namespace { int UNIQUE_NAME(unique) = reg(cb); } // Predefined configuration keys namespace cfg @@ -71,9 +134,21 @@ constexpr auto build_cc = "build-cc"; constexpr auto build_cxx = "build-cpp"; constexpr auto build_ar = "build-ar"; constexpr auto build_ld = "build-ld"; + +constexpr auto ctor_includedir = "ctor-includedir"; +constexpr auto ctor_libdir = "ctor-libdir"; } -const std::map<std::string, std::string>& configuration(); +struct Configuration +{ + std::vector<std::string> args; // vector of arguments used when last calling configure + std::map<std::string, std::string> env; // env used when last calling configure + + std::map<std::string, std::string> tools; // tools + std::map<std::string, Flags> externals; +}; + +const Configuration& configuration(); bool hasConfiguration(const std::string& key); const std::string& getConfiguration(const std::string& key, const std::string& defaultValue = {}); diff --git a/src/rebuild.cc b/src/rebuild.cc index 353beb0..9ddf5ba 100644 --- a/src/rebuild.cc +++ b/src/rebuild.cc @@ -6,18 +6,22 @@ #include <iostream> #include <filesystem> #include <algorithm> +#include <source_location> +#include <cstring> -#include "execute.h" #include "configure.h" -#include "settings.h" #include "libctor.h" +#include "tasks.h" +#include "build.h" +#include "execute.h" +#include "tools.h" +#include "util.h" std::array<BuildConfigurationEntry, 1024> configFiles; std::size_t numConfigFiles{0}; -// TODO: Use c++20 when ready, somehing like this: -//int reg(const std::source_location location = std::source_location::current()) -int reg(const char* location, std::vector<BuildConfiguration> (*cb)()) +int reg(BuildConfigurations (*cb)(const Settings&), + const std::source_location location) { // NOTE: std::cout cannot be used here if(numConfigFiles >= configFiles.size()) @@ -27,13 +31,41 @@ int reg(const char* location, std::vector<BuildConfiguration> (*cb)()) exit(1); } - configFiles[numConfigFiles].file = location; + 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 + } + else + { + configFiles[numConfigFiles].file = location.file_name(); + } configFiles[numConfigFiles].cb = cb; ++numConfigFiles; return 0; } +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); + } + + configFiles[numConfigFiles].file = location; + configFiles[numConfigFiles].cb = + [](const Settings&){ return std::vector<BuildConfiguration>{}; }; + ++numConfigFiles; + + return 0; +} + int unreg(const char* location) { std::size_t found{0}; @@ -54,91 +86,196 @@ int unreg(const char* location) } } + 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; } -void recompileCheck(const Settings& settings, int argc, char* argv[], - bool force, bool relaunch_allowed) +std::array<ExternalConfigurationEntry, 1024> externalConfigFiles; +std::size_t numExternalConfigFiles{0}; + +int reg(ExternalConfigurations (*cb)(const Settings&), + const std::source_location location) { - bool dirty{force}; + // 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); + } - std::vector<std::string> args; - args.push_back("-s"); - args.push_back("-O3"); - args.push_back("-std=c++17"); - args.push_back("-pthread"); + externalConfigFiles[numExternalConfigFiles].file = location.file_name(); + externalConfigFiles[numExternalConfigFiles].cb = cb; + ++numExternalConfigFiles; - std::filesystem::path binFile(argv[0]); + return 0; +} - if(std::filesystem::exists(configurationFile)) +namespace +{ +bool contains(const std::vector<Source>& sources, const std::string& file) +{ + for(const auto& source : sources) { - args.push_back(configurationFile.string()); - - if(std::filesystem::last_write_time(binFile) <= - std::filesystem::last_write_time(configurationFile)) - { - dirty = true; - } - - const auto& c = configuration(); - if(&c == &default_configuration) + if(source.file == file) { - // configuration.cc exists, but currently compiled with the default one. - dirty = true; + return true; } } - if(settings.verbose > 1) + return false; +} +} + +bool recompileCheck(const Settings& global_settings, int argc, char* argv[], + bool relaunch_allowed) +{ + using namespace std::string_literals; + + if(global_settings.verbose > 1) { std::cout << "Recompile check (" << numConfigFiles << "):\n"; } + BuildConfiguration config; + + config.name = "ctor"; + config.system = OutputSystem::Build; + + auto tool_chain = getToolChain(config.system); + + append(config.flags.cxxflags, + getOption(tool_chain, opt::optimization, "3")); + append(config.flags.cxxflags, + getOption(tool_chain, opt::cpp_std, "c++20")); + if(hasConfiguration(cfg::ctor_includedir)) + { + append(config.flags.cxxflags, + getOption(tool_chain, opt::include_path, + getConfiguration(cfg::ctor_includedir))); + } + if(hasConfiguration(cfg::ctor_libdir)) + { + append(config.flags.ldflags, + getOption(tool_chain, opt::library_path, + getConfiguration(cfg::ctor_libdir))); + } + append(config.flags.ldflags, getOption(tool_chain, opt::link, "ctor")); + append(config.flags.ldflags, getOption(tool_chain, opt::threads)); + + Settings settings{global_settings}; + settings.verbose = -1; // Make check completely silent. + settings.builddir += "/ctor"; // override builddir to use ctor subdir + + { + std::filesystem::path buildfile = settings.builddir; + std::filesystem::path currentfile = argv[0]; + config.target = std::filesystem::relative(currentfile, buildfile).string(); + } + + if(std::filesystem::exists(configurationFile)) + { + config.sources.push_back(configurationFile.string()); + } + for(std::size_t i = 0; i < numConfigFiles; ++i) { std::string location = configFiles[i].file; - if(settings.verbose > 1) + if(global_settings.verbose > 1) { std::cout << " - " << location << "\n"; } - std::filesystem::path configFile(location); - if(std::filesystem::last_write_time(binFile) <= - std::filesystem::last_write_time(configFile)) + + // Ensure that files containing multiple configurations are only added once. + if(!contains(config.sources, location)) { - dirty = true; + config.sources.push_back(location); } + } - // Support adding multiple config functions from the same file - if(std::find(args.begin(), args.end(), location) == std::end(args)) + for(std::size_t i = 0; i < numExternalConfigFiles; ++i) + { + std::string location = externalConfigFiles[i].file; + if(global_settings.verbose > 1) { - args.push_back(location); + std::cout << " - " << location << "\n"; + } + + // Ensure that files containing multiple configurations are only added once. + if(!contains(config.sources, location)) + { + config.sources.push_back(location); + } + } + + auto tasks = taskFactory({config}, settings, {}); + + for(auto task : tasks) + { + if(task->registerDepTasks(tasks)) + { + return false; + } + } + + // Find out if reconfigure is needed + bool reconfigure{false}; + for(auto task : tasks) + { + if(task->dirty() && + task->source() != "" && // only look at source files + task->source() != "configuration.cc") // don't reconfigure if only configuration.cc is changed. + { + reconfigure |= true; } } - args.push_back("libctor.a"); - args.push_back("-o"); - args.push_back(binFile.string()); - if(dirty) + auto dirty_tasks = build(settings, "ctor", tasks, true); // dryrun + if(dirty_tasks) { - std::cout << "Rebuilding config\n"; - auto tool = getConfiguration(cfg::build_cxx, "/usr/bin/g++"); - auto ret = execute(tool, args, settings.verbose > 0); + std::cout << "Rebuilding config.\n"; + auto ret = build(settings, "ctor", tasks); // run for real if(ret != 0) { - std::cerr << "Failed: ." << ret << "\n"; - exit(1); + return ret; } - else + } + + if(reconfigure) + { + std::vector<std::string> args; + args.push_back("reconfigure"); + if(!relaunch_allowed) { - if(relaunch_allowed) - { - std::cout << "Re-launch\n"; - std::vector<std::string> args; - for(int i = 1; i < argc; ++i) - { - args.push_back(argv[i]); - } - exit(execute(argv[0], args, settings.verbose > 0)); - } + args.push_back("--no-rerun"); } + for(int i = 1; i < argc; ++i) + { + args.push_back(argv[i]); + } + auto ret = execute(argv[0], args); + //if(ret != 0) + { + exit(ret); + } + } + + return dirty_tasks; } diff --git a/src/rebuild.h b/src/rebuild.h index 906d089..f1255c6 100644 --- a/src/rebuild.h +++ b/src/rebuild.h @@ -13,14 +13,24 @@ class Settings; struct BuildConfigurationEntry { const char* file; - std::vector<BuildConfiguration> (*cb)(); + BuildConfigurations (*cb)(const Settings&); +}; + +struct ExternalConfigurationEntry +{ + const char* file; + ExternalConfigurations (*cb)(const Settings&); }; extern std::array<BuildConfigurationEntry, 1024> configFiles; extern std::size_t numConfigFiles; -//int reg(const char* location, std::vector<BuildConfiguration> (*cb)()); +extern std::array<ExternalConfigurationEntry, 1024> externalConfigFiles; +extern std::size_t numExternalConfigFiles; + +int reg(const char* location); int unreg(const char* location); -void recompileCheck(const Settings& settings, int argc, char* argv[], - bool force = false, bool relaunch_allowed = true); +//! Returns true of recompilation was needed. +bool recompileCheck(const Settings& settings, int argc, char* argv[], + bool relaunch_allowed = true); diff --git a/src/task.cc b/src/task.cc index 1c6c233..fb50765 100644 --- a/src/task.cc +++ b/src/task.cc @@ -6,22 +6,25 @@ #include <unistd.h> #include <iostream> -Task::Task(const BuildConfiguration& config) +Task::Task(const BuildConfiguration& config, const Settings& settings, + const std::string& sourceDir) : config(config) , output_system(config.system) + , settings(settings) + , sourceDir(sourceDir) { } -int Task::registerDepTasks(const std::list<std::shared_ptr<Task>>& tasks) +int Task::registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks) { for(const auto& depStr : depends()) { bool found{false}; for(const auto& task : tasks) { - if(task->target() == depStr) + if(*task == depStr) { - dependsTasks.push_back(task); + dependsTasks.insert(task); found = true; } } @@ -33,12 +36,22 @@ int Task::registerDepTasks(const std::list<std::shared_ptr<Task>>& tasks) } } - return 0; + return registerDepTasksInner(tasks); +} + +bool Task::operator==(const std::string& depStr) +{ + return + name() == depStr || + target() == depStr || + sourceDir + "/" + target() == depStr || + targetFile().string() == depStr + ; } std::string Task::name() const { - return config.target; + return config.name; } bool Task::dirty() @@ -141,7 +154,7 @@ std::string Task::compiler() const } } -std::list<std::shared_ptr<Task>> Task::getDependsTasks() +std::set<std::shared_ptr<Task>> Task::getDependsTasks() { return dependsTasks; } @@ -6,8 +6,9 @@ #include <vector> #include <string> #include <atomic> -#include <list> +#include <set> #include <memory> +#include <filesystem> #include "libctor.h" @@ -20,12 +21,18 @@ enum class State Error, }; +struct Settings; + class Task { public: - Task(const BuildConfiguration& config); + Task(const BuildConfiguration& config, const Settings& settings, + const std::string& sourceDir); + + int registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks); + virtual int registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) { return 0; } - int registerDepTasks(const std::list<std::shared_ptr<Task>>& tasks); + bool operator==(const std::string& dep); virtual std::string name() const; bool dirty(); @@ -34,10 +41,23 @@ public: State state() const; virtual int clean() = 0 ; virtual std::vector<std::string> depends() const = 0; + + //! Raw target name as stated in ctor.cc config file or (in case of a derived + //! target) the calculated target without builddir prefix. virtual std::string target() const = 0; + //! Target file with full path prefix + virtual std::filesystem::path targetFile() const = 0; + + //! Returns true for tasks that are non-target tasks, ie. for example derived + //! objects files from target sources. + virtual bool derived() const = 0; + virtual std::string toJSON() const { return {}; }; + //! Returns a reference to the originating build config. + //! Note: the build config of a derived task will be that of its parent + //! (target) task. const BuildConfiguration& buildConfig() const; TargetType targetType() const; @@ -45,7 +65,9 @@ public: OutputSystem outputSystem() const; std::string compiler() const; - std::list<std::shared_ptr<Task>> getDependsTasks(); + std::set<std::shared_ptr<Task>> getDependsTasks(); + + virtual std::string source() const { return {}; } protected: std::atomic<State> task_state{State::Unknown}; @@ -53,9 +75,11 @@ protected: virtual bool dirtyInner() { return false; } std::vector<std::string> dependsStr; - std::list<std::shared_ptr<Task>> dependsTasks; + std::set<std::shared_ptr<Task>> dependsTasks; const BuildConfiguration& config; TargetType target_type{TargetType::Auto}; Language source_language{Language::Auto}; OutputSystem output_system{OutputSystem::Host}; + const Settings& settings; + std::string sourceDir; }; diff --git a/src/task_ar.cc b/src/task_ar.cc index e920571..3e1746c 100644 --- a/src/task_ar.cc +++ b/src/task_ar.cc @@ -7,36 +7,22 @@ #include <fstream> #include "libctor.h" -#include "settings.h" #include "execute.h" - -namespace -{ -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); -} -} // namespace :: +#include "util.h" TaskAR::TaskAR(const BuildConfiguration& config, const Settings& settings, const std::string& target, - const std::vector<std::string>& objects) - : Task(config) + const std::vector<std::string>& objects, + const std::string& sourceDir) + : Task(config, settings, sourceDir) , config(config) , settings(settings) + , sourceDir(sourceDir) { - targetFile = settings.builddir; - targetFile /= target; + std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceDir); + + _targetFile = target; for(const auto& object : objects) { std::filesystem::path objectFile = object; @@ -46,19 +32,18 @@ TaskAR::TaskAR(const BuildConfiguration& config, for(const auto& dep : config.depends) { - std::filesystem::path depFile = settings.builddir; - depFile /= dep; - depFiles.push_back(depFile); + depFiles.push_back(dep); } - flagsFile = settings.builddir / targetFile.stem(); + flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); flagsFile += ".flags"; target_type = TargetType::StaticLibrary; source_language = Language::C; for(const auto& source : config.sources) { - std::filesystem::path sourceFile(source); + std::filesystem::path sourceFile(source.file); + // TODO: Use task languages instead if(sourceFile.extension().string() != ".c") { source_language = Language::Cpp; @@ -68,7 +53,7 @@ TaskAR::TaskAR(const BuildConfiguration& config, bool TaskAR::dirtyInner() { - if(!std::filesystem::exists(targetFile)) + if(!std::filesystem::exists(targetFile())) { return true; } @@ -78,15 +63,6 @@ bool TaskAR::dirtyInner() return true; } - for(const auto& objectFile : objectFiles) - { - if(std::filesystem::last_write_time(targetFile) <= - std::filesystem::last_write_time(objectFile)) - { - return true; - } - } - { auto lastFlags = readFile(flagsFile.string()); if(flagsString() != lastFlags) @@ -101,26 +77,12 @@ bool TaskAR::dirtyInner() int TaskAR::runInner() { - std::string objectlist; - for(const auto& objectFile : objectFiles) - { - if(!objectlist.empty()) - { - objectlist += " "; - } - objectlist += objectFile.string(); - } - std::vector<std::string> args; args.push_back("rcs"); - args.push_back(targetFile.string()); - for(const auto& objectFile : objectFiles) + args.push_back(targetFile().string()); + for(const auto& task : getDependsTasks()) { - args.push_back(objectFile.string()); - } - for(const auto& flag : config.ldflags) - { - args.push_back(flag); + args.push_back(task->targetFile().string()); } { // Write flags to file. @@ -130,7 +92,7 @@ int TaskAR::runInner() if(settings.verbose == 0) { - std::cout << "AR => " << targetFile.string() << "\n"; + std::cout << "AR => " << targetFile().string() << "\n"; } std::string tool; @@ -149,10 +111,10 @@ int TaskAR::runInner() int TaskAR::clean() { - if(std::filesystem::exists(targetFile)) + if(std::filesystem::exists(targetFile())) { - std::cout << "Removing " << targetFile.string() << "\n"; - std::filesystem::remove(targetFile); + std::cout << "Removing " << targetFile().string() << "\n"; + std::filesystem::remove(targetFile()); } if(std::filesystem::exists(flagsFile)) @@ -172,9 +134,9 @@ std::vector<std::string> TaskAR::depends() const deps.push_back(objectFile.string()); } - for(const auto& depFile : depFiles) + for(const auto& dep : config.depends) { - deps.push_back(depFile.string()); + deps.push_back(dep); } return deps; @@ -182,15 +144,25 @@ std::vector<std::string> TaskAR::depends() const std::string TaskAR::target() const { - return targetFile.string(); + return _targetFile.string(); +} + +std::filesystem::path TaskAR::targetFile() const +{ + return std::filesystem::path(settings.builddir) / sourceDir / _targetFile; +} + +bool TaskAR::derived() const +{ + return false; } std::string TaskAR::flagsString() const { std::string flagsStr; - for(const auto& flag : config.ldflags) + for(const auto& flag : config.flags.ldflags) { - if(flag != config.ldflags[0]) + if(flag != config.flags.ldflags[0]) { flagsStr += " "; } @@ -207,5 +179,12 @@ std::string TaskAR::flagsString() const flagsStr += dep; } + auto deps = depends(); + for(const auto& dep : deps) + { + flagsStr += " "; + flagsStr += dep; + } + return flagsStr; } diff --git a/src/task_ar.h b/src/task_ar.h index abdc3ae..c76a852 100644 --- a/src/task_ar.h +++ b/src/task_ar.h @@ -20,7 +20,9 @@ public: TaskAR(const BuildConfiguration& config, const Settings& settings, const std::string& target, - const std::vector<std::string>& objects); + const std::vector<std::string>& objects, + const std::string& sourceDir); + virtual ~TaskAR() = default; bool dirtyInner() override; @@ -30,15 +32,19 @@ public: std::vector<std::string> depends() const override; std::string target() const override; + std::filesystem::path targetFile() const override; + + bool derived() const override; private: std::string flagsString() const; std::vector<std::filesystem::path> objectFiles; std::vector<std::filesystem::path> depFiles; - std::filesystem::path targetFile; + std::filesystem::path _targetFile; std::filesystem::path flagsFile; const BuildConfiguration& config; const Settings& settings; + std::string sourceDir; }; diff --git a/src/task_cc.cc b/src/task_cc.cc index 8256c70..c4343b6 100644 --- a/src/task_cc.cc +++ b/src/task_cc.cc @@ -5,132 +5,85 @@ #include <iostream> #include <fstream> +#include <cassert> #include "libctor.h" -#include "settings.h" #include "execute.h" - -namespace -{ -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)) - { - return {}; - } - - 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); - } - - return output; -} -} // namespace :: +#include "util.h" +#include "tools.h" TaskCC::TaskCC(const BuildConfiguration& config, const Settings& settings, - const std::string& sourceDir, const std::string& source) - : Task(config) + const std::string& sourceDir, const Source& source) + : Task(config, settings, sourceDir) , config(config) , settings(settings) , sourceDir(sourceDir) + , _source(source) { sourceFile = sourceDir; - sourceFile /= source; + sourceFile /= source.file; + + std::filesystem::path base = sourceFile.parent_path(); + std::filesystem::create_directories(std::filesystem::path(settings.builddir) / base); - std::filesystem::path base = settings.builddir; - base /= config.target; + base /= cleanUp(config.target); base += "-"; base += sourceFile.stem(); - if(sourceFile.extension().string() == ".c") + target_type = TargetType::Object; + source_language = source.language; + if(source_language == Language::Auto) { - base += "_c"; + source_language = languageFromExtension(sourceFile); } - else + + switch(source_language) { + case Language::C: + base += "_c"; + break; + case Language::Cpp: base += "_cc"; + break; + case Language::Asm: + base += "_asm"; + break; + case Language::Auto: + assert(0 && "This should never happen"); + break; } - targetFile = base; - targetFile += ".o"; - depsFile = base; - depsFile += ".d"; - flagsFile = base; - flagsFile += ".flags"; - - target_type = TargetType::Object; - if(sourceFile.extension().string() == ".c") + if(source.output.empty()) { - source_language = Language::C; + _targetFile = base; + _targetFile += ".o"; } else { - source_language = Language::Cpp; + _targetFile = source.output; } + depsFile = targetFile().parent_path() / targetFile().stem(); + depsFile += ".d"; + flagsFile = targetFile().parent_path() / targetFile().stem(); + flagsFile += ".flags"; +} + +int TaskCC::registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) +{ + for(const auto& task : tasks) + { + if(*task == _source.file) + { + dependsTasks.insert(task); + } + } + + return 0; } std::string TaskCC::name() const { - return target(); + return {}; } bool TaskCC::dirtyInner() @@ -141,7 +94,7 @@ bool TaskCC::dirtyInner() return true; } - if(!std::filesystem::exists(targetFile)) + if(!std::filesystem::exists(targetFile())) { //std::cout << "Missing targetFile\n"; return true; @@ -179,7 +132,7 @@ bool TaskCC::dirtyInner() for(const auto& dep : depList) { if(!std::filesystem::exists(dep) || - std::filesystem::last_write_time(targetFile) < + std::filesystem::last_write_time(targetFile()) < std::filesystem::last_write_time(dep)) { //std::cout << "The targetFile older than " << std::string(dep) << "\n"; @@ -188,7 +141,7 @@ bool TaskCC::dirtyInner() } if(std::filesystem::last_write_time(sourceFile) > - std::filesystem::last_write_time(targetFile)) + std::filesystem::last_write_time(targetFile())) { //std::cout << "The targetFile older than sourceFile\n"; return true; @@ -214,9 +167,22 @@ int TaskCC::runInner() if(settings.verbose == 0) { - std::cout << compiler() << " " << + switch(sourceLanguage()) + { + case Language::C: + std::cout << "CC "; + break; + case Language::Cpp: + std::cout << "CXX "; + break; + case Language::Auto: + case Language::Asm: + // Only c/c++ handled by this task type. + break; + } + std::cout << sourceFile.lexically_normal().string() << " => " << - targetFile.lexically_normal().string() << "\n"; + targetFile().lexically_normal().string() << "\n"; } return execute(compiler(), args, settings.verbose > 0); @@ -224,10 +190,10 @@ int TaskCC::runInner() int TaskCC::clean() { - if(std::filesystem::exists(targetFile)) + if(std::filesystem::exists(targetFile())) { - std::cout << "Removing " << targetFile.string() << "\n"; - std::filesystem::remove(targetFile); + std::cout << "Removing " << targetFile().string() << "\n"; + std::filesystem::remove(targetFile()); } if(std::filesystem::exists(depsFile)) @@ -252,7 +218,17 @@ std::vector<std::string> TaskCC::depends() const std::string TaskCC::target() const { - return targetFile.string(); + return _targetFile.string(); +} + +std::filesystem::path TaskCC::targetFile() const +{ + return std::filesystem::path(settings.builddir) / _targetFile; +} + +bool TaskCC::derived() const +{ + return true; } std::string TaskCC::toJSON() const @@ -261,7 +237,7 @@ std::string TaskCC::toJSON() const json += "\t{\n"; json += "\t\t\"directory\": \"" + sourceDir.string() + "\",\n"; json += "\t\t\"file\": \"" + sourceFile.lexically_normal().string() + "\",\n"; - json += "\t\t\"output\": \"" + targetFile.string() + "\",\n"; + json += "\t\t\"output\": \"" + targetFile().string() + "\",\n"; json += "\t\t\"arguments\": [ \"" + compiler() + "\""; auto args = getCompilerArgs(); for(const auto& arg : args) @@ -273,14 +249,19 @@ std::string TaskCC::toJSON() const return json; } +std::string TaskCC::source() const +{ + return sourceFile.string(); +} + std::vector<std::string> TaskCC::flags() const { switch(sourceLanguage()) { case Language::C: - return config.cflags; + return config.flags.cflags; case Language::Cpp: - return config.cxxflags; + return config.flags.cxxflags; default: std::cerr << "Unknown CC target type\n"; exit(1); @@ -300,39 +281,41 @@ std::string TaskCC::flagsString() const std::vector<std::string> TaskCC::getCompilerArgs() const { + auto tool_chain = getToolChain(config.system); + auto compiler_flags = flags(); std::vector<std::string> args; - args.push_back("-MMD"); + append(args, getOption(tool_chain, opt::generate_dep_tree)); if(std::filesystem::path(config.target).extension() == ".so") { // Add -fPIC arg to all contained object files - args.push_back("-fPIC"); + append(args, getOption(tool_chain, opt::position_independent_code)); } - args.push_back("-c"); + append(args, getOption(tool_chain, opt::no_link)); args.push_back(sourceFile.string()); - args.push_back("-o"); - args.push_back(targetFile.string()); + append(args, getOption(tool_chain, opt::output, targetFile().string())); for(const auto& flag : compiler_flags) { - // Is arg an added include path? - if(flag.substr(0, 2) == "-I") + auto option = getOption(flag); + switch(option.first) { - std::string include_path = flag.substr(2); - include_path.erase(0, include_path.find_first_not_of(' ')); - std::filesystem::path path(include_path); - - // Is it relative? - if(path.is_relative()) + // Relative include paths has to be altered to be relative to sourceDir + case opt::include_path: { - path = (sourceDir / path).lexically_normal(); - std::string new_include_path = "-I" + path.string(); - args.push_back(new_include_path); - continue; + std::filesystem::path path(option.second); + if(path.is_relative()) + { + path = (sourceDir / path).lexically_normal(); + append(args, getOption(tool_chain, opt::include_path, path.string())); + } } + continue; + default: + break; } args.push_back(flag); diff --git a/src/task_cc.h b/src/task_cc.h index 10813a7..0a0d96a 100644 --- a/src/task_cc.h +++ b/src/task_cc.h @@ -19,7 +19,10 @@ class TaskCC public: TaskCC(const BuildConfiguration& config, const Settings& settings, - const std::string& sourceDir, const std::string& source); + const std::string& sourceDir, const Source& source); + virtual ~TaskCC() = default; + + int registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) override; std::string name() const override; bool dirtyInner() override; @@ -30,20 +33,26 @@ public: std::vector<std::string> depends() const override; std::string target() const override; + std::filesystem::path targetFile() const override; + + bool derived() const override; std::string toJSON() const override; -private: + std::string source() const override; + +protected: std::vector<std::string> flags() const; std::string flagsString() const; std::vector<std::string> getCompilerArgs() const; std::filesystem::path sourceFile; - std::filesystem::path targetFile; + std::filesystem::path _targetFile; std::filesystem::path depsFile; std::filesystem::path flagsFile; const BuildConfiguration& config; const Settings& settings; std::filesystem::path sourceDir; + const Source& _source; }; diff --git a/src/task_fn.cc b/src/task_fn.cc new file mode 100644 index 0000000..ab00fae --- /dev/null +++ b/src/task_fn.cc @@ -0,0 +1,121 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "task_fn.h" + +#include <iostream> +#include <fstream> +#include <cassert> + +#include "libctor.h" +#include "execute.h" +#include "util.h" + +TaskFn::TaskFn(const BuildConfiguration& config, const Settings& settings, + const std::string& sourceDir, const 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()); + + target_type = config.type; + source_language = source.language; + + if(source.output.empty()) + { + std::cerr << "Missing output file for functional target\n"; + exit(1); + } + + _targetFile = source.output; +} + +bool TaskFn::dirtyInner() +{ + if(!std::filesystem::exists(sourceFile)) + { + //std::cout << "Missing source file: " << std::string(sourceFile) << "\n"; + return true; + } + + if(!std::filesystem::exists(targetFile())) + { + //std::cout << "Missing targetFile\n"; + return true; + } + + if(std::filesystem::last_write_time(sourceFile) > + std::filesystem::last_write_time(targetFile())) + { + //std::cout << "The targetFile older than sourceFile\n"; + return true; + } + + return false; +} + +int TaskFn::runInner() +{ + if(!std::filesystem::exists(sourceFile)) + { + std::cout << "Missing source file: " << sourceFile.string() << "\n"; + return 1; + } + + if(settings.verbose >= 0) + { + std::cout << "Fn" << " " << + sourceFile.lexically_normal().string() << " => " << + targetFile().lexically_normal().string() << "\n"; + } + + return config.function(sourceFile.string(), + targetFile().string(), + config, + settings); +} + +int TaskFn::clean() +{ + if(std::filesystem::exists(targetFile())) + { + std::cout << "Removing " << targetFile().string() << "\n"; + std::filesystem::remove(targetFile()); + } + + return 0; +} + +std::vector<std::string> TaskFn::depends() const +{ + return {}; +} + +std::string TaskFn::target() const +{ + return _targetFile; +} + +std::filesystem::path TaskFn::targetFile() const +{ + return std::filesystem::path(settings.builddir) / sourceDir / _targetFile; +} + +bool TaskFn::derived() const +{ + return false; +} + +std::string TaskFn::toJSON() const +{ + return {}; // TODO: Not sure how to express this... +} + +std::string TaskFn::source() const +{ + return sourceFile.string(); +} diff --git a/src/task_fn.h b/src/task_fn.h new file mode 100644 index 0000000..e350395 --- /dev/null +++ b/src/task_fn.h @@ -0,0 +1,47 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include "task.h" + +#include <vector> +#include <string> +#include <future> +#include <filesystem> + +struct BuildConfiguration; +struct Settings; + +class TaskFn + : public Task +{ +public: + TaskFn(const BuildConfiguration& config, + const Settings& settings, + const std::string& sourceDir, const Source& source); + virtual ~TaskFn() = default; + + bool dirtyInner() override; + + int runInner() override; + int clean() override; + + std::vector<std::string> depends() const override; + + std::string target() const override; + std::filesystem::path targetFile() const override; + bool derived() const override; + + std::string toJSON() const override; + + std::string source() const override; + +protected: + std::filesystem::path sourceFile; + std::filesystem::path _targetFile; + + const BuildConfiguration& config; + const Settings& settings; + std::filesystem::path sourceDir; +}; diff --git a/src/task_ld.cc b/src/task_ld.cc index 1b5665e..20e823d 100644 --- a/src/task_ld.cc +++ b/src/task_ld.cc @@ -7,32 +7,19 @@ #include <fstream> #include "libctor.h" -#include "settings.h" #include "execute.h" - -namespace -{ -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); -} -} // namespace :: +#include "util.h" +#include "tools.h" TaskLD::TaskLD(const BuildConfiguration& config, const Settings& settings, const std::string& target, - const std::vector<std::string>& objects) - : Task(config) + const std::vector<std::string>& objects, + const std::string& sourceDir) + : Task(config, settings, sourceDir) , config(config) , settings(settings) + , sourceDir(sourceDir) { target_type = config.type; if(target_type == TargetType::Auto) @@ -40,8 +27,9 @@ TaskLD::TaskLD(const BuildConfiguration& config, target_type = TargetType::Executable; } - targetFile = settings.builddir; - targetFile /= target; + std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceDir); + + _targetFile = target; for(const auto& object : objects) { std::filesystem::path objectFile = object; @@ -51,18 +39,16 @@ TaskLD::TaskLD(const BuildConfiguration& config, for(const auto& dep : config.depends) { - std::filesystem::path depFile = settings.builddir; - depFile /= dep; - depFiles.push_back(depFile); + depFiles.push_back(dep); } - flagsFile = settings.builddir / targetFile.stem(); + flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); flagsFile += ".flags"; source_language = Language::C; for(const auto& source : config.sources) { - std::filesystem::path sourceFile(source); + std::filesystem::path sourceFile(source.file); if(sourceFile.extension().string() != ".c") { source_language = Language::Cpp; @@ -72,7 +58,7 @@ TaskLD::TaskLD(const BuildConfiguration& config, bool TaskLD::dirtyInner() { - if(!std::filesystem::exists(targetFile)) + if(!std::filesystem::exists(targetFile())) { return true; } @@ -82,15 +68,6 @@ bool TaskLD::dirtyInner() return true; } - for(const auto& objectFile : objectFiles) - { - if(std::filesystem::last_write_time(targetFile) <= - std::filesystem::last_write_time(objectFile)) - { - return true; - } - } - { auto lastFlags = readFile(flagsFile.string()); if(flagsString() != lastFlags) @@ -105,42 +82,27 @@ bool TaskLD::dirtyInner() int TaskLD::runInner() { - std::string objectlist; - for(const auto& objectFile : objectFiles) - { - if(!objectlist.empty()) - { - objectlist += " "; - } - objectlist += objectFile.string(); - } + auto tool_chain = getToolChain(config.system); std::vector<std::string> args; - for(const auto& objectFile : objectFiles) - { - args.push_back(objectFile.string()); - } - - for(const auto& depFile : depFiles) + for(const auto& dep : getDependsTasks()) { + auto depFile = dep->targetFile(); if(depFile.extension() == ".so") { - args.push_back(std::string("-L") + settings.builddir); + append(args, getOption(tool_chain, opt::library_path, + targetFile().parent_path().string())); auto lib = depFile.stem().string().substr(3); // strip 'lib' prefix - args.push_back(std::string("-l") + lib); + append(args, getOption(tool_chain, opt::link, lib)); } - else if(depFile.extension() == ".a") + else if(depFile.extension() == ".a" || depFile.extension() == ".o") { args.push_back(depFile.string()); } } - for(const auto& flag : config.ldflags) - { - args.push_back(flag); - } - args.push_back("-o"); - args.push_back(targetFile.string()); + append(args, config.flags.ldflags); + append(args, getOption(tool_chain, opt::output, targetFile().string())); { // Write flags to file. std::ofstream flagsStream(flagsFile); @@ -149,7 +111,7 @@ int TaskLD::runInner() if(settings.verbose == 0) { - std::cout << "LD => " << targetFile.string() << "\n"; + std::cout << "LD => " << targetFile().string() << "\n"; } auto tool = compiler(); @@ -158,10 +120,10 @@ int TaskLD::runInner() int TaskLD::clean() { - if(std::filesystem::exists(targetFile)) + if(std::filesystem::exists(targetFile())) { - std::cout << "Removing " << targetFile.string() << "\n"; - std::filesystem::remove(targetFile); + std::cout << "Removing " << targetFile().string() << "\n"; + std::filesystem::remove(targetFile()); } if(std::filesystem::exists(flagsFile)) @@ -191,15 +153,25 @@ std::vector<std::string> TaskLD::depends() const std::string TaskLD::target() const { - return targetFile.string(); + return _targetFile.string(); +} + +std::filesystem::path TaskLD::targetFile() const +{ + return std::filesystem::path(settings.builddir) / sourceDir / _targetFile; +} + +bool TaskLD::derived() const +{ + return false; } std::string TaskLD::flagsString() const { std::string flagsStr; - for(const auto& flag : config.ldflags) + for(const auto& flag : config.flags.ldflags) { - if(flag != config.ldflags[0]) + if(flag != config.flags.ldflags[0]) { flagsStr += " "; } @@ -216,5 +188,12 @@ std::string TaskLD::flagsString() const flagsStr += dep; } + auto deps = depends(); + for(const auto& dep : deps) + { + flagsStr += " "; + flagsStr += dep; + } + return flagsStr; } diff --git a/src/task_ld.h b/src/task_ld.h index 730975a..8625075 100644 --- a/src/task_ld.h +++ b/src/task_ld.h @@ -20,7 +20,9 @@ public: TaskLD(const BuildConfiguration& config, const Settings& settings, const std::string& target, - const std::vector<std::string>& objects); + const std::vector<std::string>& objects, + const std::string& _sourceDir); + virtual ~TaskLD() = default; bool dirtyInner() override; @@ -30,15 +32,19 @@ public: std::vector<std::string> depends() const override; std::string target() const override; + std::filesystem::path targetFile() const override; + + bool derived() const override; private: std::string flagsString() const; std::vector<std::filesystem::path> objectFiles; std::vector<std::filesystem::path> depFiles; - std::filesystem::path targetFile; + std::filesystem::path _targetFile; std::filesystem::path flagsFile; const BuildConfiguration& config; const Settings& settings; + std::string sourceDir; }; diff --git a/src/task_so.cc b/src/task_so.cc index 5623bcf..8c6dbd4 100644 --- a/src/task_so.cc +++ b/src/task_so.cc @@ -7,35 +7,24 @@ #include <fstream> #include "libctor.h" -#include "settings.h" #include "execute.h" - -namespace -{ -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); -} -} // namespace :: +#include "util.h" +#include "tools.h" TaskSO::TaskSO(const BuildConfiguration& config, const Settings& settings, const std::string& target, - const std::vector<std::string>& objects) - : Task(config) + const std::vector<std::string>& objects, + const std::string& sourceDir) + : Task(config, settings, sourceDir) , config(config) , settings(settings) + , sourceDir(sourceDir) { - targetFile = settings.builddir; - targetFile /= target; + std::filesystem::path base = sourceDir; + std::filesystem::create_directories(std::filesystem::path(settings.builddir) / base); + + _targetFile = base / target; for(const auto& object : objects) { std::filesystem::path objectFile = object; @@ -45,19 +34,18 @@ TaskSO::TaskSO(const BuildConfiguration& config, for(const auto& dep : config.depends) { - std::filesystem::path depFile = settings.builddir; - depFile /= dep; - depFiles.push_back(depFile); + depFiles.push_back(dep); } - flagsFile = settings.builddir / targetFile.stem(); + flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); flagsFile += ".flags"; target_type = TargetType::DynamicLibrary; source_language = Language::C; for(const auto& source : config.sources) { - std::filesystem::path sourceFile(source); + std::filesystem::path sourceFile(source.file); + // TODO: Use task languages instead if(sourceFile.extension().string() != ".c") { source_language = Language::Cpp; @@ -67,7 +55,7 @@ TaskSO::TaskSO(const BuildConfiguration& config, bool TaskSO::dirtyInner() { - if(!std::filesystem::exists(targetFile)) + if(!std::filesystem::exists(targetFile())) { return true; } @@ -77,15 +65,6 @@ bool TaskSO::dirtyInner() return true; } - for(const auto& objectFile : objectFiles) - { - if(std::filesystem::last_write_time(targetFile) <= - std::filesystem::last_write_time(objectFile)) - { - return true; - } - } - { auto lastFlags = readFile(flagsFile.string()); if(flagsString() != lastFlags) @@ -100,38 +79,21 @@ bool TaskSO::dirtyInner() int TaskSO::runInner() { - std::string objectlist; - for(const auto& objectFile : objectFiles) - { - if(!objectlist.empty()) - { - objectlist += " "; - } - objectlist += objectFile.string(); - } + auto tool_chain = getToolChain(config.system); std::vector<std::string> args; - args.push_back("-fPIC"); - args.push_back("-shared"); - - args.push_back("-o"); - args.push_back(targetFile.string()); + append(args, getOption(tool_chain, opt::position_independent_code)); + append(args, getOption(tool_chain, opt::build_shared)); - for(const auto& objectFile : objectFiles) - { - args.push_back(objectFile.string()); - } + append(args, getOption(tool_chain, opt::output, targetFile().string())); - for(const auto& depFile : depFiles) + for(const auto& task : getDependsTasks()) { - args.push_back(depFile.string()); + args.push_back(task->targetFile().string()); } - for(const auto& flag : config.ldflags) - { - args.push_back(flag); - } + append(args, config.flags.ldflags); { // Write flags to file. std::ofstream flagsStream(flagsFile); @@ -140,7 +102,7 @@ int TaskSO::runInner() if(settings.verbose == 0) { - std::cout << "LD => " << targetFile.string() << "\n"; + std::cout << "LD => " << targetFile().string() << "\n"; } auto tool = compiler(); @@ -149,10 +111,10 @@ int TaskSO::runInner() int TaskSO::clean() { - if(std::filesystem::exists(targetFile)) + if(std::filesystem::exists(targetFile())) { - std::cout << "Removing " << targetFile.string() << "\n"; - std::filesystem::remove(targetFile); + std::cout << "Removing " << targetFile().string() << "\n"; + std::filesystem::remove(targetFile()); } if(std::filesystem::exists(flagsFile)) @@ -182,13 +144,23 @@ std::vector<std::string> TaskSO::depends() const std::string TaskSO::target() const { - return targetFile.string(); + return _targetFile.string(); +} + +std::filesystem::path TaskSO::targetFile() const +{ + return std::filesystem::path(settings.builddir) / sourceDir / _targetFile; +} + +bool TaskSO::derived() const +{ + return false; } std::string TaskSO::flagsString() const { std::string flagsStr = compiler(); - for(const auto& flag : config.ldflags) + for(const auto& flag : config.flags.ldflags) { flagsStr += " " + flag; } @@ -203,5 +175,12 @@ std::string TaskSO::flagsString() const flagsStr += dep; } + auto deps = depends(); + for(const auto& dep : deps) + { + flagsStr += " "; + flagsStr += dep; + } + return flagsStr; } diff --git a/src/task_so.h b/src/task_so.h index 1e65694..fe5d2fd 100644 --- a/src/task_so.h +++ b/src/task_so.h @@ -20,7 +20,9 @@ public: TaskSO(const BuildConfiguration& config, const Settings& settings, const std::string& target, - const std::vector<std::string>& objects); + const std::vector<std::string>& objects, + const std::string& sourceDir); + virtual ~TaskSO() = default; bool dirtyInner() override; @@ -30,15 +32,19 @@ public: std::vector<std::string> depends() const override; std::string target() const override; + std::filesystem::path targetFile() const override; + + bool derived() const override; private: std::string flagsString() const; std::vector<std::filesystem::path> objectFiles; std::vector<std::filesystem::path> depFiles; - std::filesystem::path targetFile; + std::filesystem::path _targetFile; std::filesystem::path flagsFile; const BuildConfiguration& config; const Settings& settings; + std::string sourceDir; }; diff --git a/src/tasks.cc b/src/tasks.cc index a52b0be..68b2476 100644 --- a/src/tasks.cc +++ b/src/tasks.cc @@ -5,24 +5,28 @@ #include <filesystem> #include <deque> +#include <list> #include <iostream> #include <algorithm> -#include "settings.h" #include "libctor.h" #include "task.h" #include "task_cc.h" #include "task_ld.h" #include "task_ar.h" #include "task_so.h" +#include "task_fn.h" #include "rebuild.h" +#include "configure.h" -const std::deque<Target>& getTargets(const Settings& settings) +const std::deque<Target>& getTargets(const Settings& settings, + bool resolve_externals) { static bool initialised{false}; static std::deque<Target> targets; if(!initialised) { + const auto& externals = configuration().externals; for(std::size_t i = 0; i < numConfigFiles; ++i) { std::string path = @@ -31,9 +35,39 @@ const std::deque<Target>& getTargets(const Settings& settings) { std::cout << configFiles[i].file << " in path " << path << "\n"; } - auto configs = configFiles[i].cb(); - for(const auto& config : configs) + auto configs = configFiles[i].cb(settings); + for(auto& config : configs) { + if(resolve_externals) + { + // Resolv config externals + for(const auto& external : config.externals) + { + if(externals.find(external) == externals.end()) + { + std::cout << "External '" << external << + "' not found in cache - run configure.\n"; + exit(1); + } + const auto& flags = externals.at(external); + config.flags.cflags.insert(config.flags.cflags.end(), + flags.cflags.begin(), + flags.cflags.end()); + config.flags.cxxflags.insert(config.flags.cxxflags.end(), + flags.cxxflags.begin(), + flags.cxxflags.end()); + config.flags.ldflags.insert(config.flags.ldflags.end(), + flags.ldflags.begin(), + flags.ldflags.end()); + config.flags.asmflags.insert(config.flags.asmflags.end(), + flags.asmflags.begin(), + flags.asmflags.end()); + //config.libs.insert(config.libs.end(), + // libs.begin(), + // libs.end()); + } + } + targets.push_back({config, path}); } } @@ -43,18 +77,22 @@ const std::deque<Target>& getTargets(const Settings& settings) return targets; } -namespace -{ -std::list<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config, - const Settings& settings, - const std::string& sourceDir) +std::set<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config, + const Settings& settings, + const std::string& sourceDir) { + std::set<std::shared_ptr<Task>> tasks; + std::filesystem::path targetFile(config.target); TargetType target_type{config.type}; if(target_type == TargetType::Auto) { - if(targetFile.extension() == ".a") + if(config.function != nullptr) + { + target_type = TargetType::Function; + } + else if(targetFile.extension() == ".a") { target_type = TargetType::StaticLibrary; } @@ -75,13 +113,26 @@ std::list<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config, } std::vector<std::string> objects; - std::list<std::shared_ptr<Task>> tasks; - for(const auto& file : config.sources) + if(target_type != TargetType::Function) + { + for(const auto& file : config.sources) + { + auto task = std::make_shared<TaskCC>(config, settings, sourceDir, file); + tasks.insert(task); + objects.push_back(task->targetFile().string()); + } + } +#ifndef BOOTSTRAP + else { - tasks.emplace_back(std::make_shared<TaskCC>(config, settings, - sourceDir, file)); - objects.push_back(tasks.back()->target()); + for(const auto& file : config.sources) + { + auto task = std::make_shared<TaskFn>(config, settings, sourceDir, file); + tasks.insert(task); + objects.push_back(task->target()); + } } +#endif switch(target_type) { @@ -90,36 +141,42 @@ std::list<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config, break; case TargetType::StaticLibrary: - tasks.emplace_back(std::make_shared<TaskAR>(config, settings, config.target, - objects)); + case TargetType::UnitTestLib: + tasks.insert(std::make_shared<TaskAR>(config, settings, config.target, + objects, sourceDir)); break; - +#ifndef BOOTSTRAP case TargetType::DynamicLibrary: + // TODO: Use C++20 starts_with if(targetFile.stem().string().substr(0, 3) != "lib") { std::cerr << "Dynamic library target must have 'lib' prefix\n"; exit(1); } - tasks.emplace_back(std::make_shared<TaskSO>(config, settings, config.target, - objects)); + tasks.insert(std::make_shared<TaskSO>(config, settings, config.target, + objects, sourceDir)); break; case TargetType::Executable: case TargetType::UnitTest: - tasks.emplace_back(std::make_shared<TaskLD>(config, settings, config.target, - objects)); + tasks.insert(std::make_shared<TaskLD>(config, settings, config.target, + objects, sourceDir)); break; case TargetType::Object: + case TargetType::Function: + break; +#else + default: break; +#endif } return tasks; } -} -std::shared_ptr<Task> getNextTask(const std::list<std::shared_ptr<Task>>& allTasks, - std::list<std::shared_ptr<Task>>& dirtyTasks) +std::shared_ptr<Task> getNextTask(const std::set<std::shared_ptr<Task>>& allTasks, + std::set<std::shared_ptr<Task>>& dirtyTasks) { for(auto dirtyTask = dirtyTasks.begin(); dirtyTask != dirtyTasks.end(); @@ -137,11 +194,12 @@ std::shared_ptr<Task> getNextTask(const std::list<std::shared_ptr<Task>>& allTas return nullptr; } -std::list<std::shared_ptr<Task>> getTasks(const Settings& settings, - const std::vector<std::string> names) +std::set<std::shared_ptr<Task>> getTasks(const Settings& settings, + const std::vector<std::string> names, + bool resolve_externals) { - auto& targets = getTargets(settings); - std::list<std::shared_ptr<Task>> tasks; + auto& targets = getTargets(settings, resolve_externals); + std::set<std::shared_ptr<Task>> tasks; for(const auto& target : targets) { if(names.empty() || @@ -149,7 +207,7 @@ std::list<std::shared_ptr<Task>> getTasks(const Settings& settings, { std::vector<std::string> objects; auto t = taskFactory(target.config, settings, target.path); - tasks.insert(tasks.end(), t.begin(), t.end()); + tasks.insert(t.begin(), t.end()); } } diff --git a/src/tasks.h b/src/tasks.h index 54591a4..c547432 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -4,7 +4,7 @@ #pragma once #include <string> -#include <list> +#include <set> #include <memory> #include <deque> @@ -20,16 +20,24 @@ struct Target }; //! Get list of all registered targets -const std::deque<Target>& getTargets(const Settings& settings); +const std::deque<Target>& getTargets(const Settings& settings, + bool resolve_externals = true); //! Returns next dirty task from the dirtyTasks list that has all its dependencies //! 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::list<std::shared_ptr<Task>>& allTasks, - std::list<std::shared_ptr<Task>>& dirtyTasks); +std::shared_ptr<Task> getNextTask(const std::set<std::shared_ptr<Task>>& allTasks, + std::set<std::shared_ptr<Task>>& dirtyTasks); //! Get list of tasks filtered by name including each of their direct //! dependency tasks (ie. objects tasks from their sources). -std::list<std::shared_ptr<Task>> getTasks(const Settings& settings, - const std::vector<std::string> names = {}); +std::set<std::shared_ptr<Task>> getTasks(const Settings& settings, + const std::vector<std::string> names = {}, + bool resolve_externals = true); + +//! Generate list of targets from a single configuration, including the final +//! link target and all its objects files (if any). +std::set<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config, + const Settings& settings, + const std::string& sourceDir); diff --git a/src/tools.cc b/src/tools.cc new file mode 100644 index 0000000..7e8ac78 --- /dev/null +++ b/src/tools.cc @@ -0,0 +1,136 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "tools.h" + +#include <filesystem> +#include <iostream> + +#include <cassert> + +ToolChain getToolChain(OutputSystem system) +{ + std::string compiler; + switch(system) + { + case OutputSystem::Host: + compiler = getConfiguration(cfg::host_cxx, "g++"); + break; + case OutputSystem::Build: + compiler = getConfiguration(cfg::build_cxx, "g++"); + break; + } + + std::filesystem::path cc(compiler); + auto cc_cmd = cc.stem().string(); + + // Note: "g++" is a substring of "clang++" so "clang++" must be tested first. + if(cc_cmd.find("clang++") != std::string::npos) + { + return ToolChain::clang; + } + else if(cc_cmd.find("g++") != std::string::npos) + { + return ToolChain::gcc; + } + + std::cerr << "Unsupported output system.\n"; + return ToolChain::gcc; +} + +namespace +{ +std::vector<std::string> getOptionGcc(opt option, const std::string& arg) +{ + switch(option) + { + case opt::output: + return {"-o", arg}; + case opt::debug: + return {"-g"}; + case opt::strip: + return {"-s"}; + case opt::warn_all: + return {"-Wall"}; + case opt::warnings_as_errors: + return {"-Werror"}; + case opt::generate_dep_tree: + return {"-MMD"}; + case opt::no_link: + return {"-c"}; + case opt::include_path: + return {"-I" + arg}; + case opt::library_path: + return {"-L" + arg}; + case opt::link: + return {"-l" + arg}; + case opt::cpp_std: + return {"-std=" + arg}; + case opt::build_shared: + return {"-shared"}; + case opt::threads: + return {"-pthread"}; + case opt::optimization: + return {"-O" + arg}; + case opt::position_independent_code: + return {"-fPIC"}; + case opt::position_independent_executable: + return {"-fPIE"}; + case opt::custom: + return {arg}; + } + + std::cerr << "Unsupported compiler option.\n"; + return {}; +} +} + +std::vector<std::string> getOption(ToolChain tool_chain, + opt option, + const std::string& arg) +{ + switch(tool_chain) + { + case ToolChain::gcc: + case ToolChain::clang: + return getOptionGcc(option, arg); + } + + std::cerr << "Unsupported tool-chain.\n"; + return {}; +} + +namespace { +std::pair<opt, std::string> getOptionGcc(const std::string& flag) +{ + if(flag.substr(0, 2) == "-I") + { + std::string path = flag.substr(2); + path.erase(0, path.find_first_not_of(' ')); + return { opt::include_path, path }; + } + + if(flag.substr(0, 2) == "-L") + { + std::string path = flag.substr(2); + path.erase(0, path.find_first_not_of(' ')); + return { opt::library_path, path }; + } + + return { opt::custom, flag }; +} +} + +std::pair<opt, std::string> getOption(const std::string& flag, + ToolChain tool_chain) +{ + switch(tool_chain) + { + case ToolChain::gcc: + case ToolChain::clang: + return getOptionGcc(flag); + } + + std::cerr << "Unsupported tool-chain.\n"; + return { opt::custom, flag }; +} diff --git a/src/tools.h b/src/tools.h new file mode 100644 index 0000000..39118d2 --- /dev/null +++ b/src/tools.h @@ -0,0 +1,52 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <vector> +#include <string> + +#include "libctor.h" + +enum class ToolChain +{ + gcc, + clang, +}; + +enum class opt +{ + // gcc/clang + output, // -o + debug, // -g + strip, // -s + warn_all, // -Wall + warnings_as_errors, // -Werror + generate_dep_tree, // -MMD + no_link, // -c + include_path, // -I<arg> + library_path, // -L<arg> + link, // -l<arg> + cpp_std, // -std=<arg> + build_shared, // -shared + threads, // -pthread + optimization, // -O<arg> + position_independent_code, // -fPIC + position_independent_executable, // -fPIE + custom, // entire option taken verbatim from <arg> +}; + +//! Get tool-chain type from output system (via configuration) +ToolChain getToolChain(OutputSystem system); + +//! Get tool argument(s) for specific option type matching the supplied +//! tool-chain +std::vector<std::string> getOption(ToolChain tool_chain, + opt option, + const std::string& arg = {}); + +//! Get opt enum value and argument from string, +//! ie. { opt::InludePath, "foo/bar" } from "-Ifoo/bar" +//! Returns { opt::Custom, flag } if unknown. +std::pair<opt, std::string> getOption(const std::string& flag, + ToolChain tool_chain = ToolChain::gcc); diff --git a/src/unittest.cc b/src/unittest.cc index ade2d0a..f18de47 100644 --- a/src/unittest.cc +++ b/src/unittest.cc @@ -6,10 +6,9 @@ #include <iostream> #include "execute.h" -#include "settings.h" #include "task.h" -int runUnitTests(std::list<std::shared_ptr<Task>>& tasks, +int runUnitTests(std::set<std::shared_ptr<Task>>& tasks, const Settings& settings) { bool ok{true}; @@ -19,16 +18,21 @@ int runUnitTests(std::list<std::shared_ptr<Task>>& tasks, { if(task->targetType() == TargetType::UnitTest) { - std::cout << task->name() << ": "; - auto ret = execute(task->target(), {}, false); + auto name = task->name(); + if(name.empty()) + { + name = task->target(); + } + std::cout << name << ": " << std::flush; + auto ret = execute(task->targetFile(), {}, settings.verbose > 0); ok &= ret == 0; if(ret == 0) { - std::cout << "OK\n"; + std::cout << " OK\n"; } else { - std::cout << "FAILED\n"; + std::cout << " FAILED\n"; } } } diff --git a/src/unittest.h b/src/unittest.h index 7eef0e2..8dee33c 100644 --- a/src/unittest.h +++ b/src/unittest.h @@ -3,11 +3,11 @@ // See accompanying file LICENSE for details. #pragma once -#include <list> +#include <set> #include <memory> class Task; class Settings; -int runUnitTests(std::list<std::shared_ptr<Task>>& tasks, +int runUnitTests(std::set<std::shared_ptr<Task>>& tasks, const Settings& settings); diff --git a/src/util.cc b/src/util.cc new file mode 100644 index 0000000..92560b6 --- /dev/null +++ b/src/util.cc @@ -0,0 +1,137 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "util.h" + +#include <iostream> +#include <fstream> + +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)) + { + return {}; + } + + 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); + } + + return output; +} + +Language languageFromExtension(const std::filesystem::path& file) +{ + auto ext = file.extension().string(); + if(ext == ".c") + { + return Language::C; + } + + if(ext == ".C" || + ext == ".cc" || + ext == ".cpp" || + ext == ".CPP" || + ext == ".c++" || + ext == ".cp" || + ext == ".cxx") + { + return Language::Cpp; + } + + if(ext == ".s" || + ext == ".S" || + ext == ".asm") + { + return Language::Asm; + } + + std::cerr << "Could not deduce language from " << file.string() << "\n"; + exit(1); + return {}; +} + +namespace +{ +bool isClean(char c) +{ + return c != '.' && c != '/'; +} +} + +std::string cleanUp(const std::string& path) +{ + std::string cleaned; + for(const auto& c : path) + { + if(isClean(c)) + { + cleaned += c; + } + else + { + cleaned += '_'; + } + } + return cleaned; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..c8b591a --- /dev/null +++ b/src/util.h @@ -0,0 +1,20 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include "libctor.h" + +#include <string> +#include <filesystem> + +std::string readFile(const std::string& fileName); +std::vector<std::string> readDeps(const std::string& depFile); +Language languageFromExtension(const std::filesystem::path& file); +std::string cleanUp(const std::string& path); + +template<typename T> +void append(T& a, const T& b) +{ + a.insert(a.end(), b.begin(), b.end()); +} diff --git a/test/ctor.cc b/test/ctor.cc index 8080d61..9b690a2 100644 --- a/test/ctor.cc +++ b/test/ctor.cc @@ -5,7 +5,7 @@ namespace { -BuildConfigurations ctorTestConfigs() +BuildConfigurations ctorTestConfigs(const Settings& settings) { return { @@ -14,13 +14,16 @@ BuildConfigurations ctorTestConfigs() .target = "execute_test", .sources = { "execute_test.cc", - "uunit/uunit.cc", + "testmain.cc", "../src/execute.cc", }, - .cxxflags = { - "-std=c++17", "-O3", "-s", "-Wall", "-Werror", - "-I../src", "-Iuunit", - "-DOUTPUT=\"execute\"", + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-s", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"execute\"", + }, + .ldflags = { "-pthread" }, }, }, { @@ -28,13 +31,77 @@ BuildConfigurations ctorTestConfigs() .target = "tasks_test", .sources = { "tasks_test.cc", - "uunit/uunit.cc", + "testmain.cc", }, - .depends = {"libctor.a"}, - .cxxflags = { - "-std=c++17", "-O3", "-s", "-Wall", "-Werror", - "-I../src", "-Iuunit", - "-DOUTPUT=\"tasks\"", + .depends = { "libctor_nomain.a" }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-s", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"tasks\"", + }, + .ldflags = { "-pthread" }, + }, + }, + { + .type = TargetType::UnitTest, + .target = "source_type_test", + .sources = { + "source_type_test.cc", + "testmain.cc", + }, + .depends = { "libctor_nomain.a" }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-s", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"source_type\"", + }, + .ldflags = { "-pthread" }, + }, + }, + { + .type = TargetType::UnitTest, + .target = "tools_test", + .sources = { + "tools_test.cc", + "testmain.cc", + "../src/tools.cc", + }, + //.depends = { "libctor_nomain.a" }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"tools\"", + }, + }, + }, + { + .type = TargetType::UnitTestLib, + .target = "libctor_nomain.a", + .sources = { + "../src/build.cc", + "../src/configure.cc", + "../src/execute.cc", + "../src/rebuild.cc", + "../src/tasks.cc", + "../src/task.cc", + "../src/task_ar.cc", + "../src/task_cc.cc", + "../src/task_fn.cc", + "../src/task_ld.cc", + "../src/task_so.cc", + "../src/tools.cc", + "../src/util.cc", + "../src/externals_manual.cc", + }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-s", "-Wall", "-Werror", + "-I../src", + }, + .ldflags = { "-pthread" }, }, }, }; diff --git a/test/paths.h b/test/paths.h new file mode 100644 index 0000000..f149972 --- /dev/null +++ b/test/paths.h @@ -0,0 +1,15 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <string> +#include <filesystem> + +namespace paths +{ +extern std::string argv_0; +extern std::filesystem::path top_srcdir; +extern std::filesystem::path top_builddir; +extern std::filesystem::path testdir; +} diff --git a/test/source_type_test.cc b/test/source_type_test.cc new file mode 100644 index 0000000..ed7e783 --- /dev/null +++ b/test/source_type_test.cc @@ -0,0 +1,76 @@ +#include <uunit.h> + +#include <libctor.h> +#include <task_cc.h> + +std::ostream& operator<<(std::ostream& stream, const Language& lang) +{ + switch(lang) + { + case Language::Auto: + stream << "Language::Auto"; + break; + case Language::C: + stream << "Language::C"; + break; + case Language::Cpp: + stream << "Language::Cpp"; + break; + case Language::Asm: + stream << "Language::Asm"; + break; + } + + return stream; +} +class TestableTaskCC + : public TaskCC +{ +public: + TestableTaskCC(const Source& source) + : TaskCC({}, {}, "build", source) + {} + + Language language() const + { + return source_language; + } +}; + +class SourceTypeTest + : public uUnit +{ +public: + SourceTypeTest() + { + uTEST(SourceTypeTest::test); + } + + void test() + { + { // c++ + TestableTaskCC task("hello.cc"); + uASSERT_EQUAL(Language::Cpp, task.language()); + } + + { // c + TestableTaskCC task("hello.c"); + uASSERT_EQUAL(Language::C, task.language()); + } + + { // asm + TestableTaskCC task("hello.s"); + uASSERT_EQUAL(Language::Asm, task.language()); + } + + { // custom/explicit language + TestableTaskCC task( {"hello.foo", Language::Asm} ); + uASSERT_EQUAL(Language::Asm, task.language()); + } + + // Note: Failure state will result in exit(1) so cannot be tested + } +}; + +// Registers the fixture into the 'registry' +static SourceTypeTest test; diff --git a/test/suite/ctor_files/ctor.cc.bar b/test/suite/ctor_files/ctor.cc.bar new file mode 100644 index 0000000..92456cb --- /dev/null +++ b/test/suite/ctor_files/ctor.cc.bar @@ -0,0 +1,62 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <libctor.h> +//#include "config.h" + +namespace +{ +BuildConfigurations ctorConfigs() +{ + return + { + { + .name = "hello", + .target = "hello", + .sources = { + "hello.cc", + }, + .flags = { + .cxxflags = { + "-std=c++20", + "-O3", + "-g", + "-Wall", + "-Werror", + }, + }, + .externals = {"bar"}, + } + }; +} + +ExternalConfigurations ctorExtConfigs() +{ + return + { + { + .name = "bar", + .flags = { + .cxxflags = { "-D_A_", "-DBAR"}, + .cflags = { "-D_B_" }, + .ldflags = { "-D_C_" }, + .asmflags = { "-D_D_" }, + }, + // Creates --with-foo-prefix arg to configure which will be used for + // -L and -I flags. + // If not specified configure will try to find them in the system paths. + }, +// { +// .name = "bar", +// .type = TargetType::ExternalPkgConfig, +// .min_version = "0.1", +// .max_version = "0.9", +// // cflags, cxxflags and ldflags deduced by pkg-config tool (or parsed +// // directly from .pc if faster) +// }, + }; +} +} + +REG(ctorConfigs); +REG(ctorExtConfigs); diff --git a/test/suite/ctor_files/ctor.cc.base b/test/suite/ctor_files/ctor.cc.base new file mode 100644 index 0000000..6c60513 --- /dev/null +++ b/test/suite/ctor_files/ctor.cc.base @@ -0,0 +1,62 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <libctor.h> +//#include "config.h" + +namespace +{ +BuildConfigurations ctorConfigs() +{ + return + { + { + .name = "hello", + .target = "hello", + .sources = { + "hello.cc", + }, + .flags = { + .cxxflags = { + "-std=c++20", + "-O3", + "-g", + "-Wall", + "-Werror", + }, + }, + .externals = {"bar"}, + } + }; +} + +ExternalConfigurations ctorExtConfigs() +{ + return + { + { + .name = "bar", + .flags = { + .cxxflags = { "-D_A_", "-DFOO"}, + .cflags = { "-D_B_" }, + .ldflags = { "-D_C_" }, + .asmflags = { "-D_D_" }, + }, + // Creates --with-foo-prefix arg to configure which will be used for + // -L and -I flags. + // If not specified configure will try to find them in the system paths. + }, +// { +// .name = "bar", +// .type = TargetType::ExternalPkgConfig, +// .min_version = "0.1", +// .max_version = "0.9", +// // cflags, cxxflags and ldflags deduced by pkg-config tool (or parsed +// // directly from .pc if faster) +// }, + }; +} +} + +REG(ctorConfigs); +REG(ctorExtConfigs); diff --git a/test/suite/ctor_files/ctor.cc.multi b/test/suite/ctor_files/ctor.cc.multi new file mode 100644 index 0000000..9db2517 --- /dev/null +++ b/test/suite/ctor_files/ctor.cc.multi @@ -0,0 +1,64 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <libctor.h> +//#include "config.h" + +#include "foobar.h" + +namespace +{ +BuildConfigurations ctorConfigs() +{ + return + { + { + .name = "hello", + .target = "hello", + .sources = { + "hello.cc", + }, + .flags = { + .cxxflags = { + "-std=c++20", + "-O3", + "-g", + "-Wall", + "-Werror", + }, + }, + .externals = {"bar"}, + } + }; +} + +ExternalConfigurations ctorExtConfigs() +{ + return + { + { + .name = "bar", + .flags = { + .cxxflags = { "-D_A_", "-DFOO"}, + .cflags = { "-D_B_" }, + .ldflags = { "-D_C_" }, + .asmflags = { "-D_D_" }, + }, + // Creates --with-foo-prefix arg to configure which will be used for + // -L and -I flags. + // If not specified configure will try to find them in the system paths. + }, +// { +// .name = "bar", +// .type = TargetType::ExternalPkgConfig, +// .min_version = "0.1", +// .max_version = "0.9", +// // cflags, cxxflags and ldflags deduced by pkg-config tool (or parsed +// // directly from .pc if faster) +// }, + }; +} +} + +REG(ctorConfigs); +REG(ctorExtConfigs); diff --git a/test/suite/foobar.h b/test/suite/foobar.h new file mode 100644 index 0000000..e6e274e --- /dev/null +++ b/test/suite/foobar.h @@ -0,0 +1,3 @@ +#pragma once + +// Nothing to see here diff --git a/test/suite/hello.cc b/test/suite/hello.cc new file mode 100644 index 0000000..06e3deb --- /dev/null +++ b/test/suite/hello.cc @@ -0,0 +1,6 @@ +#include <iostream> + +int main() +{ + std::cout << "Hello\n"; +} diff --git a/test/suite/test.sh b/test/suite/test.sh new file mode 100755 index 0000000..c980154 --- /dev/null +++ b/test/suite/test.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +function fail +{ + echo "*** Failure at line $1" + exit 1 +} + +function ctor +{ + echo "*** Running: ./ctor $*" + ./ctor $* +} + +# Wipe the board +rm -Rf build +rm -f configuration.cc +rm -f ctor + +cp ctor_files/ctor.cc.base ctor.cc + +# Compile bootstrap binary +g++ -pthread -std=c++20 -L../../build -lctor -I../../src ctor.cc -o ctor || fail ${LINENO} + +# No build files should have been created yet +[ -d build ] && fail ${LINENO} + +# capture md5 sum of ctor binary before configure is called +MD5=`md5sum ctor` +ctor configure --ctor-includedir ../../src --ctor-libdir ../../build + +# 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 build/hello-hello_cc.o ] && fail ${LINENO} + +MD5=`md5sum ctor` + +# Run normally to build project +ctor -v + +# Compiled object should now exist +[ ! -f build/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 build/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 build/hello-hello_cc.o` +[[ $MOD1 == $MOD2 ]] && fail ${LINENO} + +# Replacve -DFOO with -DBAR in foo external.cxxflags +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 build/hello-hello_cc.o` +[[ $MOD1 == $MOD2 ]] && fail ${LINENO} +(echo $MD5C | md5sum --status -c) && fail ${LINENO} +(echo $MD5 | md5sum --status -c) && fail ${LINENO} + +cp ctor_files/ctor.cc.multi 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 build/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} diff --git a/test/tasks_test.cc b/test/tasks_test.cc index d6515b8..2e0ffc7 100644 --- a/test/tasks_test.cc +++ b/test/tasks_test.cc @@ -2,11 +2,10 @@ #include <libctor.h> #include <tasks.h> -#include <settings.h> namespace { -BuildConfigurations ctorTestConfigs1() +BuildConfigurations ctorTestConfigs1(const Settings&) { return { @@ -20,7 +19,7 @@ BuildConfigurations ctorTestConfigs1() }; } -BuildConfigurations ctorTestConfigs2() +BuildConfigurations ctorTestConfigs2(const Settings&) { return { @@ -37,6 +36,19 @@ BuildConfigurations ctorTestConfigs2() REG(ctorTestConfigs1); REG(ctorTestConfigs2); +std::size_t count(const std::set<std::shared_ptr<Task>>& tasks, + const std::string& name) +{ + auto cnt{0u}; + for(const auto& task : tasks) + { + if(task->target() == name) + { + cnt++; + } + } + return cnt; +} class TestTask : public Task @@ -44,7 +56,7 @@ class TestTask public: TestTask(const std::string& name, bool dirty, const std::vector<std::string>& deps = {}) - : Task({}) + : Task({}, {}, {}) , task_name(name) , task_dirty(dirty) , task_deps(deps) @@ -55,6 +67,8 @@ public: int clean() override { return 0; } std::vector<std::string> depends() const override { return task_deps; } std::string target() const override { return task_name; } + std::filesystem::path targetFile() const override { return {}; } + bool derived() const override { return false; } bool dirtyInner() override { return task_dirty; } private: @@ -95,34 +109,26 @@ public: void getTasks_test() { using namespace std::string_literals; - Settings settings{}; + Settings settings{ .builddir = "foo" }; { auto tasks = getTasks(settings); uASSERT_EQUAL(6u, tasks.size()); - auto task = tasks.begin(); - uASSERT_EQUAL("target1-foo_cc.o"s, (*task)->target()); - task++; - uASSERT_EQUAL("target1-bar_c.o"s, (*task)->target()); - task++; - uASSERT_EQUAL("target1"s, (*task)->target()); - task++; - uASSERT_EQUAL("target2"s, (*task)->target()); - task++; - uASSERT_EQUAL("target3"s, (*task)->target()); - task++; - uASSERT_EQUAL("target4"s, (*task)->target()); + // Note: count() is used here because the order of + // std::set<std::shared_ptr<T>> is not deterministic. + 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)); } { auto tasks = getTasks(settings, {"target1", "target3"}); uASSERT_EQUAL(4u, tasks.size()); - auto task = tasks.begin(); - uASSERT_EQUAL("target1-foo_cc.o"s, (*task)->target()); - task++; - uASSERT_EQUAL("target1-bar_c.o"s, (*task)->target()); - task++; - uASSERT_EQUAL("target1"s, (*task)->target()); - task++; - uASSERT_EQUAL("target3"s, (*task)->target()); + 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)); } { auto tasks = getTasks(settings, {"no-such-target"}); @@ -136,8 +142,8 @@ public: Settings settings{}; { // Zero (Empty) - std::list<std::shared_ptr<Task>> allTasks; - std::list<std::shared_ptr<Task>> dirtyTasks; + std::set<std::shared_ptr<Task>> allTasks; + std::set<std::shared_ptr<Task>> dirtyTasks; for(auto& task : dirtyTasks) { @@ -150,10 +156,10 @@ public: { // Zero (One task, no dirty) auto task1 = std::make_shared<TestTask>("task1", false); - std::list<std::shared_ptr<Task>> allTasks; - allTasks.push_back(task1); + std::set<std::shared_ptr<Task>> allTasks; + allTasks.insert(task1); - std::list<std::shared_ptr<Task>> dirtyTasks; + std::set<std::shared_ptr<Task>> dirtyTasks; for(auto& task : dirtyTasks) { @@ -166,11 +172,11 @@ public: { // One (One task, one dirty) auto task1 = std::make_shared<TestTask>("task1", true); - std::list<std::shared_ptr<Task>> allTasks; - allTasks.push_back(task1); + std::set<std::shared_ptr<Task>> allTasks; + allTasks.insert(task1); - std::list<std::shared_ptr<Task>> dirtyTasks; - dirtyTasks.push_back(task1); + std::set<std::shared_ptr<Task>> dirtyTasks; + dirtyTasks.insert(task1); for(auto& task : dirtyTasks) { @@ -185,12 +191,12 @@ public: auto task1 = std::make_shared<TestTask>("task1", false); auto task2 = std::make_shared<TestTask>("task2", true); - std::list<std::shared_ptr<Task>> allTasks; - allTasks.push_back(task1); - allTasks.push_back(task2); + std::set<std::shared_ptr<Task>> allTasks; + allTasks.insert(task1); + allTasks.insert(task2); - std::list<std::shared_ptr<Task>> dirtyTasks; - dirtyTasks.push_back(task2); + std::set<std::shared_ptr<Task>> dirtyTasks; + dirtyTasks.insert(task2); for(auto& task : dirtyTasks) { @@ -207,12 +213,12 @@ public: std::vector<std::string> deps = {"task1"}; auto task2 = std::make_shared<TestTask>("task2", true, deps); - std::list<std::shared_ptr<Task>> allTasks; - allTasks.push_back(task1); - allTasks.push_back(task2); + std::set<std::shared_ptr<Task>> allTasks; + allTasks.insert(task1); + allTasks.insert(task2); - std::list<std::shared_ptr<Task>> dirtyTasks; - dirtyTasks.push_back(task2); + std::set<std::shared_ptr<Task>> dirtyTasks; + dirtyTasks.insert(task2); for(auto& task : dirtyTasks) { @@ -229,13 +235,13 @@ public: std::vector<std::string> deps = {"task1"}; auto task2 = std::make_shared<TestTask>("task2", true, deps); - std::list<std::shared_ptr<Task>> allTasks; - allTasks.push_back(task2); - allTasks.push_back(task1); + std::set<std::shared_ptr<Task>> allTasks; + allTasks.insert(task2); + allTasks.insert(task1); - std::list<std::shared_ptr<Task>> dirtyTasks; - dirtyTasks.push_back(task2); - dirtyTasks.push_back(task1); + std::set<std::shared_ptr<Task>> dirtyTasks; + dirtyTasks.insert(task2); + dirtyTasks.insert(task1); for(auto& task : dirtyTasks) { diff --git a/test/testmain.cc b/test/testmain.cc new file mode 100644 index 0000000..db8e22c --- /dev/null +++ b/test/testmain.cc @@ -0,0 +1,36 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#define uUNIT_MAIN +#include "uunit.h" + +#include <fstream> +#include <string> +#include <filesystem> + +namespace paths +{ +std::string argv_0; +std::filesystem::path top_srcdir; +std::filesystem::path top_builddir; +std::filesystem::path testdir; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + + paths::argv_0 = argv[0]; + + auto cur = std::filesystem::current_path(); + paths::testdir = std::filesystem::path(paths::argv_0).parent_path(); + // assuming <builddir>/test + paths::top_builddir = paths::testdir.parent_path(); + paths::top_srcdir = std::filesystem::relative(cur, paths::testdir); + + std::filesystem::current_path(paths::testdir); + + std::ofstream xmlfile; + xmlfile.open("result_" OUTPUT ".xml"); + return uUnit::runTests(xmlfile); +} diff --git a/test/tools_test.cc b/test/tools_test.cc new file mode 100644 index 0000000..3e9de1b --- /dev/null +++ b/test/tools_test.cc @@ -0,0 +1,212 @@ +#include <uunit.h> + +#include <initializer_list> +#include <cassert> + +#include <tools.h> + +using namespace std::string_literals; + +namespace +{ +std::ostream& operator<<(std::ostream& stream, const ToolChain& tool_chain) +{ + switch(tool_chain) + { + case ToolChain::gcc: + stream << "ToolChain::gcc"; + break; + case ToolChain::clang: + stream << "ToolChain::clang"; + break; + } + return stream; +} + +std::ostream& operator<<(std::ostream& stream, const std::vector<std::string>& vs) +{ + bool first{true}; + stream << "{ "; + for(const auto& v : vs) + { + if(!first) + { + stream << ", "; + } + stream << "'" << v << "'"; + first = false; + } + stream << " }"; + + return stream; +} + +std::ostream& operator<<(std::ostream& stream, opt o) +{ + // Adding to this enum should also imply adding to the unit-tests below + switch(o) + { + case opt::output: stream << "opt::output"; break; + case opt::debug: stream << "opt::debug"; break; + case opt::strip: stream << "opt::strip"; break; + case opt::warn_all: stream << "opt::warn_all"; break; + case opt::warnings_as_errors: stream << "opt::warnings_as_errors"; break; + case opt::generate_dep_tree: stream << "opt::generate_dep_tree"; break; + case opt::no_link: stream << "opt::no_link"; break; + case opt::include_path: stream << "opt::include_path"; break; + case opt::library_path: stream << "opt::library_path"; break; + case opt::link: stream << "opt::link"; break; + case opt::cpp_std: stream << "opt::cpp_std"; break; + case opt::build_shared: stream << "opt::build_shared"; break; + case opt::threads: stream << "opt::threads"; break; + case opt::optimization: stream << "opt::optimization"; break; + case opt::position_independent_code: stream << "opt::position_independent_code"; break; + case opt::position_independent_executable: stream << "opt::position_independent_executable"; break; + case opt::custom: stream << "opt::custom"; break; + } + + return stream; +} + +std::ostream& operator<<(std::ostream& stream, const std::pair<opt, std::string>& vs) +{ + stream << "{ " << vs.first << ", " << vs.second << " }"; + + return stream; +} +} + +// Controllable getConfiguration stub: +namespace +{ +std::string conf_host_cxx{}; +std::string conf_build_cxx{}; +} +const std::string& getConfiguration(const std::string& key, + const std::string& defval) +{ + if(key == cfg::host_cxx) + { + return conf_host_cxx; + } + + if(key == cfg::build_cxx) + { + return conf_build_cxx; + } + + assert(false); // bad key + + static std::string res{}; + return res; +} + +class ToolsTest + : public uUnit +{ +public: + ToolsTest() + { + uTEST(ToolsTest::getToolChain_test); + uTEST(ToolsTest::getOption_toolchain_test); + uTEST(ToolsTest::getOption_str_test); + } + + void getToolChain_test() + { + // host + conf_host_cxx = "/usr/bin/g++"; + uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Host)); + + conf_host_cxx = "/usr/bin/g++-10"; + uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Host)); + + conf_host_cxx = "/usr/bin/x86_64-pc-linux-gnu-g++-9.3.0"; + uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Host)); + + conf_host_cxx = "/usr/bin/clang++"; + uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Host)); + + conf_host_cxx = "/usr/bin/clang++-10"; + uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Host)); + + conf_host_cxx = "/usr/lib/llvm/12/bin/i686-pc-linux-gnu-clang++-12"; + uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Host)); + + // build + conf_build_cxx = "/usr/bin/g++"; + uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Build)); + + conf_build_cxx = "/usr/bin/g++-10"; + uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Build)); + + conf_build_cxx = "/usr/bin/x86_64-pc-linux-gnu-g++-9.3.0"; + uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Build)); + + conf_build_cxx = "/usr/bin/clang++"; + uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Build)); + + conf_build_cxx = "/usr/bin/clang++-10"; + uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Build)); + + conf_build_cxx = "/usr/lib/llvm/12/bin/i686-pc-linux-gnu-clang++-12"; + uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Build)); + } + + void getOption_toolchain_test() + { + using sv = std::vector<std::string>; + + uUnit::assert_equal(sv{ "-o", "foo" }, getOption(ToolChain::clang, opt::output, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-g" }, getOption(ToolChain::clang, opt::debug), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-s" }, getOption(ToolChain::clang, opt::strip), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-Wall" }, getOption(ToolChain::clang, opt::warn_all), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-Werror" }, getOption(ToolChain::clang, opt::warnings_as_errors), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-MMD" }, getOption(ToolChain::clang, opt::generate_dep_tree), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-c" }, getOption(ToolChain::clang, opt::no_link), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-Ifoo" }, getOption(ToolChain::clang, opt::include_path, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-Lfoo" }, getOption(ToolChain::clang, opt::library_path, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-lfoo" }, getOption(ToolChain::clang, opt::link, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-std=foo" }, getOption(ToolChain::clang, opt::cpp_std, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-shared" }, getOption(ToolChain::clang, opt::build_shared), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-pthread" }, getOption(ToolChain::clang, opt::threads), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-Ofoo" }, getOption(ToolChain::clang, opt::optimization, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-fPIC" }, getOption(ToolChain::clang, opt::position_independent_code), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-fPIE" }, getOption(ToolChain::clang, opt::position_independent_executable), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-foo" }, getOption(ToolChain::clang, opt::custom, "-foo"), __FILE__, __LINE__); + + uUnit::assert_equal(sv{ "-o", "foo" }, getOption(ToolChain::gcc, opt::output, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-g" }, getOption(ToolChain::gcc, opt::debug), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-s" }, getOption(ToolChain::gcc, opt::strip), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-Wall" }, getOption(ToolChain::gcc, opt::warn_all), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-Werror" }, getOption(ToolChain::gcc, opt::warnings_as_errors), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-MMD" }, getOption(ToolChain::gcc, opt::generate_dep_tree), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-c" }, getOption(ToolChain::gcc, opt::no_link), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-Ifoo" }, getOption(ToolChain::gcc, opt::include_path, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-Lfoo" }, getOption(ToolChain::gcc, opt::library_path, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-lfoo" }, getOption(ToolChain::gcc, opt::link, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-std=foo" }, getOption(ToolChain::gcc, opt::cpp_std, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-shared" }, getOption(ToolChain::gcc, opt::build_shared), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-pthread" }, getOption(ToolChain::gcc, opt::threads), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-Ofoo" }, getOption(ToolChain::gcc, opt::optimization, "foo"), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-fPIC" }, getOption(ToolChain::gcc, opt::position_independent_code), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-fPIE" }, getOption(ToolChain::gcc, opt::position_independent_executable), __FILE__, __LINE__); + uUnit::assert_equal(sv{ "-foo" }, getOption(ToolChain::gcc, opt::custom, "-foo"), __FILE__, __LINE__); + } + + void getOption_str_test() + { + using p = std::pair<opt, std::string>; + uUnit::assert_equal(p{ opt::include_path, "foo" }, getOption("-Ifoo", ToolChain::gcc), __FILE__, __LINE__); + uUnit::assert_equal(p{ opt::library_path, "foo" }, getOption("-Lfoo", ToolChain::gcc), __FILE__, __LINE__); + + uUnit::assert_equal(p{ opt::include_path, "foo" }, getOption("-Ifoo", ToolChain::clang), __FILE__, __LINE__); + uUnit::assert_equal(p{ opt::library_path, "foo" }, getOption("-Lfoo", ToolChain::clang), __FILE__, __LINE__); + } + + + +}; + +// Registers the fixture into the 'registry' +static ToolsTest test; diff --git a/test/uunit b/test/uunit -Subproject fd83de802d05a227cc00489f66ea70fccb4dda0 +Subproject bc078da645412c6b36ef59e635d6c35d11088c9 |