diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bootstrap.cc | 83 | ||||
-rw-r--r-- | src/build.cc | 87 | ||||
-rw-r--r-- | src/build.h | 19 | ||||
-rw-r--r-- | src/configure.cc | 388 | ||||
-rw-r--r-- | src/configure.h | 14 | ||||
-rw-r--r-- | src/externals.h (renamed from src/settings.h) | 9 | ||||
-rw-r--r-- | src/externals_manual.cc | 37 | ||||
-rw-r--r-- | src/externals_manual.h | 12 | ||||
-rw-r--r-- | src/libctor.cc | 54 | ||||
-rw-r--r-- | src/libctor.h | 93 | ||||
-rw-r--r-- | src/rebuild.cc | 251 | ||||
-rw-r--r-- | src/rebuild.h | 18 | ||||
-rw-r--r-- | src/task.cc | 27 | ||||
-rw-r--r-- | src/task.h | 34 | ||||
-rw-r--r-- | src/task_ar.cc | 105 | ||||
-rw-r--r-- | src/task_ar.h | 10 | ||||
-rw-r--r-- | src/task_cc.cc | 235 | ||||
-rw-r--r-- | src/task_cc.h | 15 | ||||
-rw-r--r-- | src/task_fn.cc | 121 | ||||
-rw-r--r-- | src/task_fn.h | 47 | ||||
-rw-r--r-- | src/task_ld.cc | 113 | ||||
-rw-r--r-- | src/task_ld.h | 10 | ||||
-rw-r--r-- | src/task_so.cc | 111 | ||||
-rw-r--r-- | src/task_so.h | 10 | ||||
-rw-r--r-- | src/tasks.cc | 118 | ||||
-rw-r--r-- | src/tasks.h | 20 | ||||
-rw-r--r-- | src/tools.cc | 136 | ||||
-rw-r--r-- | src/tools.h | 52 | ||||
-rw-r--r-- | src/unittest.cc | 16 | ||||
-rw-r--r-- | src/unittest.h | 4 | ||||
-rw-r--r-- | src/util.cc | 137 | ||||
-rw-r--r-- | src/util.h | 20 |
32 files changed, 1824 insertions, 582 deletions
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()); +} |