diff options
54 files changed, 2932 insertions, 1058 deletions
diff --git a/Jenkinsfile b/Jenkinsfile index c95b0eb..290f412 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,56 +1,48 @@ pipeline { - agent { label 'c++20' } - + agent any stages { - stage('Clean') { + //////////////////////////////////////////////////// + stage('Linux gcc') { + agent { label 'linux && gcc && c++20' } steps { echo 'Cleaning workspace ...' - sh 'rm -Rf build*' - } - } - stage('Build-gcc') { - steps { + sh 'git clean -d -x -f' echo 'Building (gcc) ...' - sh 'BUILDDIR=build-gcc CXX=g++ ./bootstrap.sh' - } - } - stage('Test-gcc') { - steps { + sh 'CXX=g++ ./bootstrap.sh' echo 'Testing (gcc) ...' sh './ctor check' - } - } - stage('Test-suite-gcc') { - steps { echo 'Testing suite (gcc) ...' - sh '(cd test/suite; CTORDIR=../../build-gcc CXX=g++ ./test.sh)' + sh '(cd test/suite; CTORDIR=../../build CXX=g++ ./test.sh)' } - } - stage('Build-clang') { - steps { - echo 'Building (clang) ...' - sh 'BUILDDIR=build-clang CXX=clang++ ./bootstrap.sh' + post { + always { + xunit(thresholds: [ skipped(failureThreshold: '0'), + failed(failureThreshold: '0') ], + tools: [ CppUnit(pattern: 'build/test/*.xml') ]) + } } } - stage('Test-clang') { + //////////////////////////////////////////////////// + stage('Linux clang') { + agent { label 'linux && clang && c++20' } steps { + echo 'Cleaning workspace ...' + sh 'git clean -d -x -f' + echo 'Building (clang) ...' + sh 'CXX=clang++ ./bootstrap.sh' echo 'Testing (clang) ...' sh './ctor check' - } - } - stage('Test-suite-clang') { - steps { echo 'Testing suite (clang) ...' - sh '(cd test/suite; CTORDIR=../../build-clang CXX=clang++ ./test.sh)' + sh '(cd test/suite; CTORDIR=../../build CXX=clang++ ./test.sh)' + } + post { + always { + xunit(thresholds: [ skipped(failureThreshold: '0'), + failed(failureThreshold: '0') ], + tools: [ CppUnit(pattern: 'build/test/*.xml') ]) + } } } - } - - post { - always { - xunit(thresholds: [ skipped(failureThreshold: '0'), - failed(failureThreshold: '0') ], - tools: [ CppUnit(pattern: 'build-*/test/*.xml') ]) - } + //////////////////////////////////////////////////// } } diff --git a/bootstrap.sh b/bootstrap.sh index 76503bb..d10f54b 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,11 +1,12 @@ #!/bin/sh +set -e + : ${CXX:=g++} : ${BUILDDIR:=build} -CXX=$(which $CXX) echo "Bootstrapping..." -$CXX -std=c++20 -Wall -O3 -Isrc -pthread src/bootstrap.cc ctor.cc test/ctor.cc -o ctor && \ -./ctor && \ -$CXX -std=c++20 -Wall -O3 -Isrc -pthread ctor.cc test/ctor.cc -L$BUILDDIR -lctor -o ctor && \ -./ctor configure --ctor-includedir=src --ctor-libdir=$BUILDDIR --build-dir=$BUILDDIR&& \ +$CXX -std=c++20 -Wall -O3 -Isrc -pthread src/bootstrap.cc ctor.cc -o ctor +./ctor +$CXX -std=c++20 -Wall -O3 -Isrc -pthread ctor.cc test/ctor.cc -L$BUILDDIR -lctor -o ctor +./ctor configure --ctor-includedir=src --ctor-libdir=$BUILDDIR --build-dir=$BUILDDIR echo "Done. Now run ./ctor to (re)build." @@ -10,14 +10,16 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings) return { { - .type = ctor::target_type::static_library, + .system = ctor::output_system::build, .target = "libctor.a", .sources = { "src/build.cc", "src/configure.cc", + "src/deps.cc", "src/execute.cc", "src/externals_manual.cc", "src/libctor.cc", + "src/pointerlist.cc", "src/rebuild.cc", "src/task.cc", "src/task_ar.cc", @@ -37,6 +39,10 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings) "-g", "-Wall", "-Werror", + "-Wextra", + "-Wshadow", + "-Wconversion", +// "-Wnrvo", "-Isrc", }, }, diff --git a/src/bootstrap.cc b/src/bootstrap.cc index 0604527..be1b5ed 100644 --- a/src/bootstrap.cc +++ b/src/bootstrap.cc @@ -4,6 +4,7 @@ #include <iostream> #include <array> #include <cstdlib> +#include <span> #define BOOTSTRAP @@ -18,9 +19,10 @@ #include "tasks.cc" #include "build.cc" #include "tools.cc" +#include "pointerlist.cc" -std::filesystem::path configurationFile("configuration.cc"); -std::filesystem::path configHeaderFile("config.h"); +const std::filesystem::path configurationFile("configuration.cc"); +const std::filesystem::path configHeaderFile("config.h"); const ctor::configuration& ctor::get_configuration() { @@ -28,7 +30,7 @@ const ctor::configuration& ctor::get_configuration() static bool initialised{false}; if(!initialised) { - cfg.host_toolchain = getToolChain(cfg.get(ctor::cfg::host_cxx, "/usr/bin/g++")); + cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, "/usr/bin/g++")); initialised = true; } @@ -40,28 +42,58 @@ bool ctor::configuration::has(const std::string& key) const return false; } -const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const +std::string ctor::configuration::get(const std::string& key, const std::string& default_value) const { - if(key == ctor::cfg::host_cxx && std::getenv("CXX")) + static auto paths = get_paths(); + auto cxx_env = std::getenv("CXX"); + if(key == ctor::cfg::build_cxx && cxx_env) { - static std::string s = std::getenv("CXX"); - return s; + static auto cxx_prog = locate(cxx_env, paths); + return cxx_prog; } - if(key == ctor::cfg::builddir && std::getenv("BUILDDIR")) + auto cc_env = std::getenv("CC"); + if(key == ctor::cfg::build_cc && cc_env) { - static std::string s = std::getenv("BUILDDIR"); - return s; + static auto cc_prog = locate(cc_env, paths); + return cc_prog; + } + + auto ld_env = std::getenv("LD"); + if(key == ctor::cfg::build_ld && ld_env) + { + static auto ld_prog = locate(ld_env, paths); + return ld_prog; + } + + auto ar_env = std::getenv("AR"); + if(key == ctor::cfg::build_ar && ar_env) + { + static auto ar_prog = locate(ar_env, paths); + return ar_prog; + } + + auto builddir_env = std::getenv("BUILDDIR"); + if(key == ctor::cfg::builddir && builddir_env) + { + return builddir_env; } return default_value; } +std::vector<std::string> readDeps(const std::string& depFile, + ctor::toolchain toolchain) +{ + return {}; +} + int main(int argc, char* argv[]) { - if(argc > 1) + auto args = std::span(argv, static_cast<std::size_t>(argc)); + if(args.size() > 1) { - std::cerr << "This is a minimal bootstrap version of " << argv[0] << + std::cerr << "This is a minimal bootstrap version of " << args[0] << " which doesn't support any arguments\n"; return 1; } diff --git a/src/build.cc b/src/build.cc index ad30719..a31f6a5 100644 --- a/src/build.cc +++ b/src/build.cc @@ -4,12 +4,11 @@ #include "build.h" #include <future> -#include <vector> #include <iostream> #include <chrono> -#include <set> #include <thread> #include <list> +#include <algorithm> #include "ctor.h" @@ -17,8 +16,8 @@ using namespace std::chrono_literals; int build(const ctor::settings& settings, const std::string& name, - const std::set<std::shared_ptr<Task>>& tasks, - const std::set<std::shared_ptr<Task>>& all_tasks, + const std::vector<std::shared_ptr<Task>>& tasks, + const std::vector<std::shared_ptr<Task>>& all_tasks, bool dryrun) { if(settings.verbose > 1) @@ -26,19 +25,20 @@ int build(const ctor::settings& settings, std::cout << "Building '" << name << "'\n"; } - std::set<std::shared_ptr<Task>> dirtyTasks; + std::vector<std::shared_ptr<Task>> dirtyTasks; for(auto task : tasks) { - if(task->dirty()) + if(task->dirty() && + std::find(dirtyTasks.begin(), dirtyTasks.end(), task) == dirtyTasks.end()) { - dirtyTasks.insert(task); + dirtyTasks.push_back(task); } } // Dry-run returns number of dirty tasks but otherwise does nothing. if(dryrun) { - return dirtyTasks.size(); + return static_cast<int>(dirtyTasks.size()); } if(dirtyTasks.empty()) @@ -114,17 +114,15 @@ int build(const ctor::settings& settings, } } - for(auto process = processes.begin(); - process != processes.end(); - ++process) + for(auto& process : processes) { - if(process->valid() == false) + if(process.valid() == false) { continue; } - process->wait(); - auto ret = process->get(); - if(ret != 0) + process.wait(); + auto ret = process.get(); + if (ret != 0) { return ret; } @@ -133,30 +131,50 @@ int build(const ctor::settings& settings, return 0; } -namespace +std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task, + std::vector<std::shared_ptr<Task>> trace) { -std::set<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task) -{ - std::set<std::shared_ptr<Task>> tasks; - tasks.insert(task); + std::vector<std::shared_ptr<Task>> tasks; + tasks.push_back(task); + trace.push_back(task); auto deps = task->getDependsTasks(); for(const auto& dep : deps) { - auto depSet = getDepTasks(dep); - for(const auto& dep : depSet) + if(std::find(trace.begin(), trace.end(), dep) != trace.end()) + { + trace.push_back(dep); + std::cerr << "Error: Cyclic dependency detected: "; + bool first{true}; + for(auto t : trace) + { + if(!first) + { + std::cerr << " -> "; + } + + first = false; + std::cerr << t->target(); + } + std::cerr << '\n'; + throw 1; + } + auto depSet = getDepTasks(dep, trace); + for(const auto& dep_inner : depSet) { - tasks.insert(dep); + if(std::find(tasks.begin(), tasks.end(), dep_inner) == tasks.end()) + { + tasks.push_back(dep_inner); + } } } return tasks; } -} int build(const ctor::settings& settings, const std::string& name, - const std::set<std::shared_ptr<Task>>& all_tasks, + const std::vector<std::shared_ptr<Task>>& all_tasks, bool dryrun) { bool task_found{false}; @@ -166,17 +184,27 @@ int build(const ctor::settings& settings, { task_found = true; - auto depSet = getDepTasks(task); - std::set<std::shared_ptr<Task>> ts; - for(const auto& task : depSet) + try { - ts.insert(task); - } + auto depSet = getDepTasks(task); + std::vector<std::shared_ptr<Task>> ts; + for(const auto& task_inner : depSet) + { + if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + { + ts.push_back(task_inner); + } + } - auto ret = build(settings, name, ts, all_tasks, dryrun); - if(ret != 0) + auto ret = build(settings, name, ts, all_tasks, dryrun); + if(ret != 0) + { + return ret; + } + } + catch(...) { - return ret; + return 1; // cycle detected } break; @@ -195,11 +223,11 @@ int build(const ctor::settings& settings, int build(const ctor::settings& settings, const std::string& name, const std::vector<Target>& targets, - const std::set<std::shared_ptr<Task>>& all_tasks, + const std::vector<std::shared_ptr<Task>>& all_tasks, bool dryrun) { bool task_found{false}; - std::set<std::shared_ptr<Task>> ts; + std::vector<std::shared_ptr<Task>> ts; for(const auto& target : targets) { @@ -210,10 +238,20 @@ int build(const ctor::settings& settings, { task_found = true; - auto depSet = getDepTasks(task); - for(const auto& task : depSet) + try + { + auto depSet = getDepTasks(task); + for(const auto& task_inner : depSet) + { + if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) + { + ts.push_back(task_inner); + } + } + } + catch(...) { - ts.insert(task); + return 1; // cycle detected } } } diff --git a/src/build.h b/src/build.h index d74642c..7296f76 100644 --- a/src/build.h +++ b/src/build.h @@ -4,7 +4,7 @@ #pragma once #include <string> -#include <set> +#include <vector> #include <memory> #include "task.h" @@ -17,19 +17,24 @@ struct settings; //! Dry-run returns number of dirty tasks but otherwise does nothing. int build(const ctor::settings& settings, const std::string& name, - const std::set<std::shared_ptr<Task>>& tasks, - const std::set<std::shared_ptr<Task>>& all_tasks, + const std::vector<std::shared_ptr<Task>>& tasks, + const std::vector<std::shared_ptr<Task>>& all_tasks, bool dryrun = false); //! Dry-run returns number of dirty tasks but otherwise does nothing. int build(const ctor::settings& settings, const std::string& name, - const std::set<std::shared_ptr<Task>>& all_tasks, + const std::vector<std::shared_ptr<Task>>& all_tasks, bool dryrun = false); //! Dry-run returns number of dirty tasks but otherwise does nothing. int build(const ctor::settings& settings, const std::string& name, const std::vector<Target>& targets, - const std::set<std::shared_ptr<Task>>& all_tasks, + const std::vector<std::shared_ptr<Task>>& all_tasks, bool dryrun = false); + +// Recursively build vector of dependency tasks from source task. +// Throws if a cycle is detected. +std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task, + std::vector<std::shared_ptr<Task>> trace = {}); diff --git a/src/configure.cc b/src/configure.cc index 8e88092..7a68f03 100644 --- a/src/configure.cc +++ b/src/configure.cc @@ -7,6 +7,7 @@ #include <filesystem> #include <fstream> #include <optional> +#include <span> #include <getoptpp/getoptpp.hpp> @@ -16,9 +17,10 @@ #include "rebuild.h" #include "externals.h" #include "tools.h" +#include "util.h" -std::filesystem::path configurationFile("configuration.cc"); -std::filesystem::path configHeaderFile("config.h"); +const std::filesystem::path configurationFile("configuration.cc"); +const std::filesystem::path configHeaderFile("config.h"); std::map<std::string, std::string> external_includedir; std::map<std::string, std::string> external_libdir; @@ -29,7 +31,7 @@ const ctor::configuration& __attribute__((weak)) ctor::get_configuration() static bool initialised{false}; if(!initialised) { - cfg.host_toolchain = getToolChain(cfg.get(ctor::cfg::host_cxx, "g++")); + cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, "/usr/bin/g++")); initialised = true; } return cfg; @@ -67,7 +69,7 @@ bool ctor::configuration::has(const std::string& key) const return tools.find(key) != tools.end(); } -const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const +std::string ctor::configuration::get(const std::string& key, const std::string& default_value) const { if(key == ctor::cfg::ctor_includedir && ctor::includedir) { @@ -97,57 +99,6 @@ const std::string& ctor::configuration::get(const std::string& key, const std::s return default_value; } -std::string locate(const std::string& arch, const std::string& app) -{ - std::string path_env = std::getenv("PATH"); - //std::cout << path_env << "\n"; - - std::string program = app; - if(!arch.empty()) - { - program = arch + "-" + app; - } - std::cout << "Looking for: " << program << "\n"; - std::vector<std::string> paths; - - { - std::stringstream ss(path_env); - std::string path; - while (std::getline(ss, path, ':')) - { - paths.push_back(path); - } - } - for(const auto& path_str : paths) - { - std::filesystem::path path(path_str); - auto prog_path = path / program; - if(std::filesystem::exists(prog_path)) - { - std::cout << "Found file " << app << " in path: " << path << "\n"; - 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"; - } - if((perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none) - { - //std::cout << " - executable by group\n"; - } - if((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) - { - //std::cout << " - executable by others\n"; - } - - return prog_path.string(); - } - } - - std::cerr << "Could not locate " << app << " for the " << arch << " architecture\n"; - exit(1); - return {}; -} - class Args : public std::vector<char*> { @@ -192,6 +143,26 @@ std::ostream& operator<<(std::ostream& stream, const ctor::toolchain& toolchain) return stream; } +std::ostream& operator<<(std::ostream& stream, const ctor::arch& arch) +{ + switch(arch) + { + case ctor::arch::unix: + stream << "ctor::arch::unix"; + break; + case ctor::arch::apple: + stream << "ctor::arch::apple"; + break; + case ctor::arch::windows: + stream << "ctor::arch::windows"; + break; + case ctor::arch::unknown: + stream << "ctor::arch::unknown"; + break; + } + return stream; +} + std::ostream& operator<<(std::ostream& ostr, const ctor::c_flag& flag) { for(const auto& s : to_strings(ctor::toolchain::any, flag)) @@ -233,6 +204,7 @@ std::ostream& operator<<(std::ostream& ostr, const ctor::asm_flag& flag) template<class> inline constexpr bool always_false_v = false; int regenerateCache(ctor::settings& settings, + const std::string& name, const std::vector<std::string>& args, const std::map<std::string, std::string>& env) { @@ -241,9 +213,9 @@ int regenerateCache(ctor::settings& settings, dg::Options opt; int key{128}; - std::string build_arch; + std::string build_arch_prefix; std::string build_path; - std::string host_arch; + std::string host_arch_prefix; std::string host_path; std::string cc_prog = "gcc"; std::string cxx_prog = "g++"; @@ -300,7 +272,7 @@ int regenerateCache(ctor::settings& settings, opt.add("build", required_argument, key++, "Configure for building on specified architecture.", [&]() { - build_arch = optarg; + build_arch_prefix = optarg; return 0; }); @@ -314,7 +286,7 @@ int regenerateCache(ctor::settings& settings, opt.add("host", required_argument, key++, "Cross-compile to build programs to run on specified architecture.", [&]() { - host_arch = optarg; + host_arch_prefix = optarg; return 0; }); @@ -350,19 +322,19 @@ int regenerateCache(ctor::settings& settings, } auto add_path_args = - [&](const std::string& name) + [&](const std::string& arg_name) { - opt.add(name + "-includedir", required_argument, key++, - "Set path to " + name + " header file.", + opt.add(arg_name + "-includedir", required_argument, key++, + "Set path to " + arg_name + " header file.", [&]() { - external_includedir[name] = optarg; + external_includedir[arg_name] = optarg; return 0; }); - opt.add(name + "-libdir", required_argument, key++, - "Set path to " + name + " libraries.", + opt.add(arg_name + "-libdir", required_argument, key++, + "Set path to " + arg_name + " libraries.", [&]() { - external_libdir[name] = optarg; + external_libdir[arg_name] = optarg; return 0; }); }; @@ -386,46 +358,117 @@ int regenerateCache(ctor::settings& settings, opt.add("help", no_argument, 'h', "Print this help text.", - [&]() { - std::cout << "configure usage stuff\n"; + [&]() -> int { + std::cout << "Configure how to build with " << name << "\n"; + std::cout << "Usage: " << name << " configure [options]\n\n"; + std::cout << "Options:\n"; opt.help(); exit(0); - return 0; }); - opt.process(vargs.size(), vargs.data()); + opt.process(static_cast<int>(vargs.size()), vargs.data()); - if(host_arch.empty()) + if(host_arch_prefix.empty()) { - host_arch = build_arch; + host_arch_prefix = build_arch_prefix; } auto tasks = getTasks(settings, {}, false); -/* - bool needs_cpp{false}; - bool needs_c{false}; - bool needs_ar{false}; - bool needs_asm{false}; + + bool needs_build{true}; // we always need to compile ctor itself + bool needs_build_c{false}; + bool needs_build_cxx{true}; // we always need to compile ctor itself + bool needs_build_ld{true}; // we always need to compile ctor itself + bool needs_build_ar{false}; + bool needs_build_asm{false}; + + bool needs_host_c{false}; + bool needs_host{false}; + bool needs_host_cxx{false}; + bool needs_host_ld{false}; + bool needs_host_ar{false}; + bool needs_host_asm{false}; + for(const auto& task :tasks) { - switch(task->sourceLanguage()) + switch(task->outputSystem()) { - case Language::Auto: - std::cerr << "TargetLanguage not deduced!\n"; - exit(1); - break; - case Language::C: - needs_cpp = false; - break; - case Language::Cpp: - needs_c = true; + case ctor::output_system::build: + needs_build = true; + switch(task->targetType()) + { + case ctor::target_type::executable: + case ctor::target_type::unit_test: + case ctor::target_type::dynamic_library: + needs_build_ld = true; + break; + case ctor::target_type::static_library: + case ctor::target_type::unit_test_library: + needs_build_ar = true; + break; + case ctor::target_type::object: + switch(task->sourceLanguage()) + { + case ctor::language::automatic: + std::cerr << "TargetLanguage not deduced!\n"; + exit(1); + break; + case ctor::language::c: + needs_build_c = true; + break; + case ctor::language::cpp: + needs_build_cxx = true; + break; + case ctor::language::assembler: + needs_build_asm = true; + break; + } + break; + case ctor::target_type::function: + case ctor::target_type::automatic: + case ctor::target_type::unknown: + break; + } break; - case Language::Asm: - needs_asm = true; + case ctor::output_system::host: + needs_host = true; + switch(task->targetType()) + { + case ctor::target_type::executable: + case ctor::target_type::unit_test: + case ctor::target_type::dynamic_library: + needs_host_ld = true; + break; + case ctor::target_type::static_library: + case ctor::target_type::unit_test_library: + needs_host_ar = true; + break; + case ctor::target_type::object: + switch(task->sourceLanguage()) + { + case ctor::language::automatic: + std::cerr << "TargetLanguage not deduced!\n"; + exit(1); + break; + case ctor::language::c: + needs_host_c = true; + break; + case ctor::language::cpp: + needs_host_cxx = true; + break; + case ctor::language::assembler: + needs_host_asm = true; + break; + } + break; + case ctor::target_type::function: + case ctor::target_type::automatic: + case ctor::target_type::unknown: + break; + } break; } } -*/ auto cc_env = env.find("CC"); if(cc_env != env.end()) @@ -451,21 +494,149 @@ int regenerateCache(ctor::settings& settings, ld_prog = ld_env->second; } - std::string host_cc = locate(host_arch, cc_prog); - std::string host_cxx = locate(host_arch, cxx_prog); - std::string host_ar = locate(host_arch, ar_prog); - std::string host_ld = locate(host_arch, ld_prog); - std::string build_cc = locate(build_arch, cc_prog); - std::string build_cxx = locate(build_arch, cxx_prog); - std::string build_ar = locate(build_arch, ar_prog); - std::string build_ld = locate(build_arch, ld_prog); + auto paths = get_paths(); + + auto path_env = env.find("PATH"); + if(path_env != env.end()) + { + paths = get_paths(path_env->second); + } + + std::string host_cc; + std::string host_cxx; + std::string host_ld; + std::string host_ar; + ctor::toolchain host_toolchain{ctor::toolchain::none}; + ctor::arch host_arch{ctor::arch::unknown}; + if(needs_host) + { + // Host detection + if(needs_host_c) + { + host_cc = locate(cc_prog, paths, host_arch_prefix); + if(host_cc.empty()) + { + std::cerr << "Could not locate host_cc prog" << std::endl; + return 1; + } + } + + if(needs_host_cxx) + { + host_cxx = locate(cxx_prog, paths, host_arch_prefix); + if(host_cxx.empty()) + { + std::cerr << "Could not locate host_cxx prog" << std::endl; + return 1; + } + } + + if(needs_host_ar) + { + host_ar = locate(ar_prog, paths, host_arch_prefix); + if(host_ar.empty()) + { + std::cerr << "Could not locate host_ar prog" << std::endl; + return 1; + } + } + + if(needs_host_ld) + { + host_ld = locate(ld_prog, paths, host_arch_prefix); + if(host_ld.empty()) + { + std::cerr << "Could not locate host_ld prog" << std::endl; + return 1; + } + } + + if(needs_host_asm) + { + // TODO + } + + host_toolchain = getToolChain(host_cxx); + auto host_arch_str = get_arch(ctor::output_system::host); + host_arch = get_arch(ctor::output_system::host, host_arch_str); + + std::cout << "** Host architecture '" << host_arch_str << "': " << host_arch << std::endl; - if(!host_cxx.empty()) + if(host_arch == ctor::arch::unknown) + { + std::cerr << "Could not detect host architecture" << std::endl; + return 1; + } + } + + std::string build_cc; + std::string build_cxx; + std::string build_ld; + std::string build_ar; + ctor::toolchain build_toolchain{ctor::toolchain::none}; + ctor::arch build_arch{ctor::arch::unknown}; + if(needs_build) { - // This is needed for bootstrapping (when running configure for the first time) - ctor::conf_values[ctor::cfg::host_cxx] = host_cxx; + // Build detection + if(needs_build_c) + { + build_cc = locate(cc_prog, paths, build_arch_prefix); + if(build_cc.empty()) + { + std::cerr << "Could not locate build_cc prog" << std::endl; + return 1; + } + } + + if(needs_build_cxx) + { + build_cxx = locate(cxx_prog, paths, build_arch_prefix); + if(build_cxx.empty()) + { + std::cerr << "Could not locate build_cxx prog" << std::endl; + return 1; + } + } + + if(needs_build_ar) + { + build_ar = locate(ar_prog, paths, build_arch_prefix); + if(build_ar.empty()) + { + std::cerr << "Could not locate build_ar prog" << std::endl; + return 1; + } + } + + if(needs_build_ld) + { + build_ld = locate(ld_prog, paths, build_arch_prefix); + if(build_ld.empty()) + { + std::cerr << "Could not locate build_ld prog" << std::endl; + return 1; + } + } + + if(needs_build_asm) + { + // TODO + } + + build_toolchain = getToolChain(build_cxx); + auto build_arch_str = get_arch(ctor::output_system::build); + build_arch = get_arch(ctor::output_system::build, build_arch_str); + + std::cout << "** Build architecture '" << build_arch_str << "': " << build_arch << std::endl; + + if(build_arch == ctor::arch::unknown) + { + std::cerr << "Could not detect build architecture" << std::endl; + return 1; + } } + // Store current values for execution in this execution context. if(!ctor_includedir.empty()) { @@ -490,43 +661,81 @@ int regenerateCache(ctor::settings& settings, istr << "{\n"; istr << " static ctor::configuration cfg =\n"; istr << " {\n"; - istr << " .host_toolchain = " << getToolChain(host_cxx) << ",\n"; - istr << " .build_toolchain = " << getToolChain(build_cxx) << ",\n"; + if(needs_host) + { + istr << " .host_toolchain = " << host_toolchain << ",\n"; + istr << " .host_arch = " << host_arch << ",\n"; + } + if(needs_build) + { + istr << " .build_toolchain = " << build_toolchain << ",\n"; + istr << " .build_arch = " << build_arch << ",\n"; + } istr << " .args = {"; for(const auto& arg : args) { - istr << "\"" << arg << "\","; + istr << "\"" << esc(arg) << "\","; } istr << "},\n"; - istr << " .env = {"; + istr << " .env = {\n"; for(const auto& e : env) { - istr << "{\"" << e.first << "\", \"" << e.second << "\"}, "; + istr << " {\"" << esc(e.first) << "\", \"" << esc(e.second) << "\"},\n"; } - istr << "},\n"; + istr << " },\n"; istr << " .tools = {\n"; if(!builddir.empty()) { - istr << " { \"" << ctor::cfg::builddir << "\", \"" << builddir << "\" },\n"; + istr << " { \"" << ctor::cfg::builddir << "\", \"" << esc(builddir) << "\" },\n"; ctor::builddir = builddir; } - istr << " { \"" << ctor::cfg::host_cc << "\", \"" << host_cc << "\" },\n"; - istr << " { \"" << ctor::cfg::host_cxx << "\", \"" << host_cxx << "\" },\n"; - istr << " { \"" << ctor::cfg::host_ar << "\", \"" << host_ar << "\" },\n"; - istr << " { \"" << ctor::cfg::host_ld << "\", \"" << host_ld << "\" },\n"; - istr << " { \"" << ctor::cfg::build_cc << "\", \"" << build_cc << "\" },\n"; - istr << " { \"" << ctor::cfg::build_cxx << "\", \"" << build_cxx << "\" },\n"; - istr << " { \"" << ctor::cfg::build_ar << "\", \"" << build_ar << "\" },\n"; - istr << " { \"" << ctor::cfg::build_ld << "\", \"" << build_ld << "\" },\n"; + if(needs_host) + { + if(needs_host_c) + { + istr << " { \"" << ctor::cfg::host_cc << "\", \"" << esc(host_cc) << "\" },\n"; + } + if(needs_host_cxx) + { + istr << " { \"" << ctor::cfg::host_cxx << "\", \"" << esc(host_cxx) << "\" },\n"; + } + if(needs_host_ar) + { + istr << " { \"" << ctor::cfg::host_ar << "\", \"" << esc(host_ar) << "\" },\n"; + } + if(needs_host_ld) + { + istr << " { \"" << ctor::cfg::host_ld << "\", \"" << esc(host_ld) << "\" },\n"; + } + } + if(needs_build) + { + if(needs_build_c) + { + istr << " { \"" << ctor::cfg::build_cc << "\", \"" << esc(build_cc) << "\" },\n"; + } + if(needs_build_cxx) + { + istr << " { \"" << ctor::cfg::build_cxx << "\", \"" << esc(build_cxx) << "\" },\n"; + } + if(needs_build_ar) + { + istr << " { \"" << ctor::cfg::build_ar << "\", \"" << esc(build_ar) << "\" },\n"; + } + if(needs_build_ld) + { + istr << " { \"" << ctor::cfg::build_ld << "\", \"" << esc(build_ld) << "\" },\n"; + } + } if(!ctor_includedir.empty()) { - istr << " { \"" << ctor::cfg::ctor_includedir << "\", \"" << ctor_includedir << "\" },\n"; + istr << " { \"" << ctor::cfg::ctor_includedir << "\", \"" << esc(ctor_includedir) << "\" },\n"; ctor::includedir = ctor_includedir; } if(!ctor_libdir.empty()) { - istr << " { \"" << ctor::cfg::ctor_libdir << "\", \"" << ctor_libdir << "\" },\n"; + istr << " { \"" << ctor::cfg::ctor_libdir << "\", \"" << esc(ctor_libdir) << "\" },\n"; ctor::libdir = ctor_libdir; } @@ -535,7 +744,7 @@ int regenerateCache(ctor::settings& settings, for(const auto& ext : externalConfigs) { - istr << " { \"" << ext.name << "\", {\n"; + istr << " { \"" << esc(ext.name) << "\", {\n"; ctor::flags resolved_flags; if(std::holds_alternative<ctor::external_manual>(ext.external)) { @@ -612,12 +821,14 @@ int regenerateCache(ctor::settings& settings, int configure(const ctor::settings& global_settings, int argc, char* argv[]) { + auto args_span = std::span(argv, static_cast<std::size_t>(argc)); + ctor::settings settings{global_settings}; std::vector<std::string> args; - for(int i = 2; i < argc; ++i) // skip command and the first 'configure' arg + for(std::size_t i = 2; i < args_span.size(); ++i) // skip command and the first 'configure' arg { - args.push_back(argv[i]); + args.emplace_back(args_span[i]); } std::map<std::string, std::string> env; @@ -645,7 +856,13 @@ int configure(const ctor::settings& global_settings, int argc, char* argv[]) env["LD"] = ld_env; } - auto ret = regenerateCache(settings, args, env); + auto path_env = getenv("PATH"); + if(path_env) + { + env["PATH"] = path_env; + } + + auto ret = regenerateCache(settings, args_span[0], args, env); if(ret != 0) { return ret; @@ -658,19 +875,21 @@ int configure(const ctor::settings& global_settings, int argc, char* argv[]) int reconfigure(const ctor::settings& global_settings, int argc, char* argv[]) { + auto args_span = std::span(argv, static_cast<std::size_t>(argc)); + ctor::settings settings{global_settings}; bool no_rerun{false}; std::vector<std::string> args; - for(int i = 2; i < argc; ++i) // skip executable name and 'reconfigure' arg + for(std::size_t i = 2; i < args_span.size(); ++i) // skip executable name and 'reconfigure' arg { - if(i == 2 && std::string(argv[i]) == "--no-rerun") + if(i == 2 && std::string(args_span[i]) == "--no-rerun") { no_rerun = true; continue; } - args.push_back(argv[i]); + args.emplace_back(args_span[i]); } const auto& cfg = ctor::get_configuration(); @@ -680,14 +899,14 @@ int reconfigure(const ctor::settings& global_settings, int argc, char* argv[]) { std::cout << e.first << "=\"" << e.second << "\" "; } - std::cout << argv[0] << " configure "; + std::cout << args_span[0] << " configure "; for(const auto& arg : cfg.args) { std::cout << arg << " "; } std::cout << "\n"; - auto ret = regenerateCache(settings, cfg.args, cfg.env); + auto ret = regenerateCache(settings, args_span[0], cfg.args, cfg.env); if(ret != 0) { return ret; @@ -700,5 +919,5 @@ int reconfigure(const ctor::settings& global_settings, int argc, char* argv[]) return 0; // this was originally invoked by configure, don't loop } - return execute(argv[0], args); + return execute(settings, args_span[0], args); } diff --git a/src/configure.cc.bak b/src/configure.cc.bak deleted file mode 100644 index bcbeea9..0000000 --- a/src/configure.cc.bak +++ /dev/null @@ -1,387 +0,0 @@ -// -*- c++ -*- -// Distributed under the BSD 2-Clause License. -// See accompanying file LICENSE for details. -#include "configure.h" - -#include <iostream> -#include <filesystem> -#include <fstream> - -#include <getoptpp/getoptpp.hpp> - -#include "settings.h" -#include "execute.h" -#include "libcppbuild.h" -#include "tasks.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() -{ - return default_configuration; -} - -bool hasConfiguration(const std::string& key) -{ - const auto& c = configuration(); - return c.find(key) != c.end(); -} - -const std::string& getConfiguration(const std::string& key, - const std::string& defaultValue) -{ - const auto& c = configuration(); - if(hasConfiguration(key)) - { - return c.at(key); - } - - return defaultValue; -} - - -/* -Configuration: - -h, --help display this help and exit - --help=short display options specific to this package - --help=recursive display the short help of all the included packages - -V, --version display version information and exit - -q, --quiet, --silent do not print `checking ...' messages - --cache-file=FILE cache test results in FILE [disabled] - -C, --config-cache alias for `--cache-file=config.cache' - -n, --no-create do not create output files - --srcdir=DIR find the sources in DIR [configure dir or `..'] - -Installation directories: - --prefix=PREFIX install architecture-independent files in PREFIX - [/usr/local] - --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX - [PREFIX] - -By default, `make install' will install all the files in -`/usr/local/bin', `/usr/local/lib' etc. You can specify -an installation prefix other than `/usr/local' using `--prefix', -for instance `--prefix=$HOME'. - -For better control, use the options below. - -Fine tuning of the installation directories: - --bindir=DIR user executables [EPREFIX/bin] - --sbindir=DIR system admin executables [EPREFIX/sbin] - --libexecdir=DIR program executables [EPREFIX/libexec] - --sysconfdir=DIR read-only single-machine data [PREFIX/etc] - --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] - --localstatedir=DIR modifiable single-machine data [PREFIX/var] - --libdir=DIR object code libraries [EPREFIX/lib] - --includedir=DIR C header files [PREFIX/include] - --oldincludedir=DIR C header files for non-gcc [/usr/include] - --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] - --datadir=DIR read-only architecture-independent data [DATAROOTDIR] - --infodir=DIR info documentation [DATAROOTDIR/info] - --localedir=DIR locale-dependent data [DATAROOTDIR/locale] - --mandir=DIR man documentation [DATAROOTDIR/man] - --docdir=DIR documentation root [DATAROOTDIR/doc/drumgizmo] - --htmldir=DIR html documentation [DOCDIR] - --dvidir=DIR dvi documentation [DOCDIR] - --pdfdir=DIR pdf documentation [DOCDIR] - --psdir=DIR ps documentation [DOCDIR] - -Program names: - --program-prefix=PREFIX prepend PREFIX to installed program names - --program-suffix=SUFFIX append SUFFIX to installed program names - --program-transform-name=PROGRAM run sed PROGRAM on installed program names - -System types: - --build=BUILD configure for building on BUILD [guessed] - --host=HOST cross-compile to build programs to run on HOST [BUILD] - -Optional Features: - --disable-option-checking ignore unrecognized --enable/--with options - --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) - --enable-FEATURE[=ARG] include FEATURE [ARG=yes] - --enable-silent-rules less verbose build output (undo: "make V=1") - --disable-silent-rules verbose build output (undo: "make V=0") - --enable-dependency-tracking - do not reject slow dependency extractors - --disable-dependency-tracking - speeds up one-time build - --enable-shared[=PKGS] build shared libraries [default=yes] - --enable-static[=PKGS] build static libraries [default=yes] - --enable-fast-install[=PKGS] - optimize for fast installation [default=yes] - --disable-libtool-lock avoid locking (might break parallel builds) - --disable-largefile omit support for large files - --enable-gui=backend Use specified gui backend. Can be x11, win32, cocoa, - pugl-x11, pugl-win32, pugl-cocoa or auto - [default=auto] - --enable-custom-channel-count=count - Compile with specified number of output channels - [default=16] - --enable-lv2 Compile the LV2 plugin [default=no] - --enable-vst Compile the VST plugin [default=no] - --enable-cli Compile the command line interface [default=yes] - --disable-input-dummy Disable input dummy plugin [default=enabled] - --disable-input-test Disable input test plugin [default=enabled] - --disable-input-jackmidi - Disable input jackmidi plugin [default=enabled] - --disable-input-alsamidi - Disable input alsamidi plugin [default=enabled] - --disable-input-midifile - Disable input midifile plugin [default=enabled] - --disable-input-oss Disable input oss plugin [enabled by default on - FreeBSD, disabled otherwise] - --disable-output-dummy Disable output dummy plugin [default=enabled] - --disable-output-jackaudio - Disable output jack plugin [default=enabled] - --disable-output-alsa Disable output alsa plugin [default=enabled] - --disable-output-wavfile - Disable output wavfile plugin [default=enabled] - --disable-output-oss Disable output oss plugin [enabled by default on - FreeBSD, disabled otherwise] - --enable-sse=level Enable SSE Level 1, 2, 3 or auto [default=auto] - -Optional Packages: - --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] - --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) - --with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use - both] - --with-aix-soname=aix|svr4|both - shared library versioning (aka "SONAME") variant to - provide on AIX, [default=aix]. - --with-gnu-ld assume the C compiler uses GNU ld [default=no] - --with-sysroot[=DIR] Search for dependent libraries within DIR (or the - compiler's sysroot if not specified). - --with-debug Build with debug support - --with-nls Build with nls support (default nls enabled) - --with-test Build unit tests - --with-lv2dir=DIR Use DIR as the lv2 plugin directory - [default=LIBDIR/lv2] - --with-vst-sources Point this to the vstsdk24 directory - -Some influential environment variables: - CXX C++ compiler command - CXXFLAGS C++ compiler flags - LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a - nonstandard directory <lib dir> - LIBS libraries to pass to the linker, e.g. -l<library> - CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if - you have headers in a nonstandard directory <include dir> - OBJC Objective C compiler command - OBJCFLAGS Objective C compiler flags - OBJCXX Objective C++ compiler command - OBJCXXFLAGS Objective C++ compiler flags - CC C compiler command - CFLAGS C compiler flags - LT_SYS_LIBRARY_PATH - User-defined run-time library search path. - CPP C preprocessor - CXXCPP C++ preprocessor - PKG_CONFIG path to pkg-config utility - PKG_CONFIG_PATH - directories to add to pkg-config's search path - PKG_CONFIG_LIBDIR - path overriding pkg-config's built-in search path - X11_CFLAGS C compiler flags for X11, overriding pkg-config - X11_LIBS linker flags for X11, overriding pkg-config - XEXT_CFLAGS C compiler flags for XEXT, overriding pkg-config - XEXT_LIBS linker flags for XEXT, overriding pkg-config - LV2_CFLAGS C compiler flags for LV2, overriding pkg-config - LV2_LIBS linker flags for LV2, overriding pkg-config - SMF_CFLAGS C compiler flags for SMF, overriding pkg-config - SMF_LIBS linker flags for SMF, overriding pkg-config - SNDFILE_CFLAGS - C compiler flags for SNDFILE, overriding pkg-config - SNDFILE_LIBS - linker flags for SNDFILE, overriding pkg-config - JACK_CFLAGS C compiler flags for JACK, overriding pkg-config - JACK_LIBS linker flags for JACK, overriding pkg-config - ALSA_CFLAGS C compiler flags for ALSA, overriding pkg-config - ALSA_LIBS linker flags for ALSA, overriding pkg-config - -Use these variables to override the choices made by `configure' or to help -it to find libraries and programs with nonstandard names/locations. - -Report bugs to the package provider. -*/ -int configure(int argc, char* argv[]) -{ - Settings settings; - - settings.builddir = "build"; - - std::string cmd_str; - for(int i = 0; i < argc; ++i) - { - if(i > 0) - { - cmd_str += " "; - } - cmd_str += argv[i]; - } - - dg::Options opt; - int key{256}; - - std::string build_arch; - std::string build_path; - std::string host_arch; - std::string host_path; - - opt.add("build-dir", required_argument, 'b', - "Set output directory for build files (default: '" + - settings.builddir + "').", - [&]() { - settings.builddir = optarg; - return 0; - }); - - opt.add("verbose", no_argument, 'v', - "Be verbose. Add multiple times for more verbosity.", - [&]() { - settings.verbose++; - return 0; - }); - - opt.add("build", required_argument, key++, - "Configure for building on specified architecture.", - [&]() { - build_arch = optarg; - return 0; - }); - - opt.add("build-path", required_argument, key++, - "Set path to build tool-chain.", - [&]() { - build_path = optarg; - return 0; - }); - - opt.add("host", required_argument, key++, - "Cross-compile to build programs to run on specified architecture.", - [&]() { - host_arch = optarg; - return 0; - }); - - opt.add("host-path", required_argument, key++, - "Set path to cross-compile tool-chain.", - [&]() { - host_path = optarg; - return 0; - }); - - opt.add("help", no_argument, 'h', - "Print this help text.", - [&]() { - std::cout << "configure usage stuff\n"; - opt.help(); - exit(0); - return 0; - }); - - opt.process(argc, argv); - - if(host_arch.empty()) - { - host_arch = build_arch; - } - - auto tasks = getTasks(settings); - - bool needs_cpp{false}; - bool needs_c{false}; - bool needs_ar{false}; - bool needs_asm{false}; - for(const auto& task :tasks) - { - switch(task->sourceLanguage()) - { - case Language::Auto: - std::cerr << "TargetLanguage not deduced!\n"; - exit(1); - break; - case Language::C: - needs_cpp = false; - break; - case Language::Cpp: - needs_c = true; - break; - case Language::Asm: - needs_asm = true; - break; - } - } - - // CC=clang - // CXX=clang++ - - std::string path_env = std::getenv("PATH"); - std::cout << path_env << "\n"; - - std::vector<std::string> paths; - - { - std::stringstream ss(path_env); - std::string path; - while (std::getline(ss, path, ':')) - { - paths.push_back(path); - } - } - for(const auto& path_str : paths) - { - std::filesystem::path path(path_str); - auto gcc = path / "gcc"; - if(std::filesystem::exists(gcc)) - { - std::cout << "Found file gcc in path: " << path << "\n"; - auto perms = std::filesystem::status(gcc).permissions(); - if((perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none) - { - std::cout << " - executable by owner\n"; - } - if((perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none) - { - std::cout << " - executable by group\n"; - } - if((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) - { - std::cout << " - executable by others\n"; - } - } - } - exit(0); - - { - std::ofstream istr(configurationFile); - istr << "#include \"libcppbuild.h\"\n\n"; - istr << "const std::map<std::string, std::string>& configuration()\n"; - istr << "{\n"; - istr << " static std::map<std::string, std::string> c =\n"; - istr << " {\n"; - istr << " { \"cmd\", \"" << cmd_str << "\" },\n"; - istr << " { \"" << cfg::builddir << "\", \"" << settings.builddir << "\" },\n"; - istr << " { \"" << cfg::target_cc << "\", \"/usr/bin/gcc\" },\n"; - istr << " { \"" << cfg::target_cpp << "\", \"/usr/bin/g++\" },\n"; - istr << " { \"" << cfg::target_ar << "\", \"/usr/bin/ar\" },\n"; - istr << " { \"" << cfg::target_ld << "\", \"/usr/bin/ld\" },\n"; - istr << " { \"" << cfg::host_cc << "\", \"/usr/bin/gcc\" },\n"; - istr << " { \"" << cfg::host_cpp << "\", \"/usr/bin/g++\" },\n"; - istr << " { \"" << cfg::host_ar << "\", \"/usr/bin/ar\" },\n"; - istr << " { \"" << cfg::host_ld << "\", \"/usr/bin/ld\" },\n"; - istr << " };\n"; - istr << " return c;\n"; - istr << "}\n"; - } - - { - std::ofstream istr(configHeaderFile); - istr << "#pragma once\n\n"; - istr << "#define HAS_FOO 1\n"; - istr << "//#define HAS_BAR 1\n"; - } - - return 0; -} diff --git a/src/configure.h b/src/configure.h index ac8d721..5344646 100644 --- a/src/configure.h +++ b/src/configure.h @@ -12,8 +12,8 @@ namespace ctor { struct settings; } // namespace ctor:: -extern std::filesystem::path configurationFile;; -extern std::filesystem::path configHeaderFile; +extern const std::filesystem::path configurationFile; +extern const std::filesystem::path configHeaderFile; int configure(const ctor::settings& settings, int argc, char* argv[]); int reconfigure(const ctor::settings& settings, int argc, char* argv[]); @@ -10,6 +10,7 @@ #include <variant> #include <cstddef> #include <functional> +#include <string_view> namespace ctor { @@ -24,6 +25,8 @@ enum class target_type unit_test, unit_test_library, function, + + unknown, }; enum class language @@ -41,21 +44,13 @@ enum class output_system build, // Internal tool during cross-compilation }; -struct source +enum class arch { - source(const char* file) : file(file) {} - source(const std::string& file) : file(file) {} - source(const char* file, ctor::language lang) : file(file), language(lang) {} - source(const std::string& file, ctor::language lang) : file(file), language(lang) {} - - source(const char* file, const char* output) : file(file), output(output) {} - source(const std::string& file, const std::string& output) : file(file), output(output) {} - source(const char* file, ctor::language lang, const char* output) : file(file), language(lang), output(output) {} - source(const std::string& file, ctor::language lang, const std::string& output) : file(file), language(lang), output(output) {} + unix, //!< Target platform architecture is unix-based (ie. linux, bsd, etc) + apple, //!< Target platform architecture is macos + windows, //!< Target platform architecture is windows - std::string file; - ctor::language language{ctor::language::automatic}; - std::string output{}; + unknown, //!< Target platform architecture has not yet detected or was not possible to detect }; enum class toolchain @@ -66,12 +61,38 @@ enum class toolchain clang, }; +struct source +{ + source(const char* file_) : file(file_) {} // convenience ctor + + source(std::string_view file_) : source(file_, ctor::language::automatic) {} + source(std::string_view file_, ctor::language lang_) : file(file_), language(lang_) {} + + source(std::string_view file_, std::string_view output_) : file(file_), output(output_) {} + source(std::string_view file_, ctor::language lang_, std::string_view output_) : file(file_), language(lang_), output(output_) {} + + source(ctor::toolchain toolchain_, std::string_view file_) : file(file_), toolchain(toolchain_) {} + source(ctor::toolchain toolchain_, std::string_view file_, ctor::language lang_) : file(file_), toolchain(toolchain_), language(lang_) {} + + source(ctor::toolchain toolchain_, std::string_view file_, std::string_view output_) : file(file_), toolchain(toolchain_), output(output_) {} + + source(ctor::toolchain toolchain_, std::string_view file_, ctor::language lang_, std::string_view output_) : file(file_), toolchain(toolchain_), language(lang_), output(output_) {} + + std::string file; + ctor::toolchain toolchain{ctor::toolchain::any}; + ctor::language language{ctor::language::automatic}; + std::string output{}; +}; + enum class cxx_opt { // gcc/clang output, // -o debug, // -g warn_all, // -Wall + warn_conversion, // -Wconversion + warn_shadow, // -Wshadow + warn_extra, // -Wextra warnings_as_errors, // -Werror generate_dep_tree, // -MMD no_link, // -c @@ -80,6 +101,7 @@ enum class cxx_opt optimization, // -O<arg> position_independent_code, // -fPIC position_independent_executable, // -fPIE + define, // -D<arg>[=<arg2>] custom, // entire option taken verbatim from <arg> }; @@ -89,6 +111,9 @@ enum class c_opt output, // -o debug, // -g warn_all, // -Wall + warn_conversion, // -Wconversion + warn_shadow, // -Wshadow + warn_extra, // -Wextra warnings_as_errors, // -Werror generate_dep_tree, // -MMD no_link, // -c @@ -97,6 +122,7 @@ enum class c_opt optimization, // -O<arg> position_independent_code, // -fPIC position_independent_executable, // -fPIE + define, // -D<arg>[=<arg2>] custom, // entire option taken verbatim from <arg> }; @@ -120,6 +146,11 @@ enum class ld_opt enum class ar_opt { // gcc/clang + replace, // -r + add_index, // -s + create, // -c + output, // <arg> + custom, // entire option taken verbatim from <arg> }; @@ -133,18 +164,24 @@ template<typename T> class flag { public: - flag(const std::string& str); + flag(std::string_view str); flag(const char* str); - flag(T opt) : opt(opt) {} - flag(T opt, const std::string& arg) : opt(opt), arg(arg) {} - flag(T opt, const char* arg) : opt(opt), arg(arg) {} - flag(ctor::toolchain toolchain, T opt) : toolchain(toolchain), opt(opt) {} - flag(ctor::toolchain toolchain, T opt, const char* arg) : toolchain(toolchain), opt(opt), arg(arg) {} - flag(ctor::toolchain toolchain, T opt, const std::string& arg) : toolchain(toolchain), opt(opt), arg(arg) {} + flag(T opt_) : opt(opt_) {} + flag(T opt_, std::string_view arg_, std::string_view arg2_ = "") + : opt(opt_), arg(arg_), arg2(arg2_) {} + flag(T opt_, const char* arg_, const char* arg2_ = "") + : opt(opt_), arg(arg_), arg2(arg2_) {} + flag(ctor::toolchain toolchain_, T opt_) + : toolchain(toolchain_), opt(opt_) {} + flag(ctor::toolchain toolchain_, T opt_, const char* arg_, const char* arg2_ = "") + : toolchain(toolchain_), opt(opt_), arg(arg_), arg2(arg2_) {} + flag(ctor::toolchain toolchain_, T opt_, std::string_view arg_, std::string_view arg2_ = "") + : toolchain(toolchain_), opt(opt_), arg(arg_), arg2(arg2_) {} ctor::toolchain toolchain{ctor::toolchain::any}; - T opt; + T opt{}; std::string arg; + std::string arg2; }; using c_flag = ctor::flag<ctor::c_opt>; @@ -185,7 +222,7 @@ struct build_configuration { std::string name; // Name - used for referring in other configurations. ctor::target_type type{ctor::target_type::automatic}; - ctor::output_system system{ctor::output_system::host}; + ctor::output_system system{ctor::output_system::build}; std::string target; // Output target file for this configuration std::vector<ctor::source> sources; // source list std::vector<std::string> depends; // internal target dependencies @@ -209,7 +246,7 @@ struct external_manual struct external_configuration { std::string name; // Name for configuration - ctor::output_system system{ctor::output_system::host}; + ctor::output_system system{ctor::output_system::build}; std::variant<ctor::external_manual> external; }; @@ -219,10 +256,10 @@ int reg(ctor::external_configurations (*cb)(const ctor::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(cb); } +#define CTOR_CONCAT(a, b) CTOR_CONCAT_INNER(a, b) +#define CTOR_CONCAT_INNER(a, b) a ## b +#define CTOR_UNIQUE_NAME(base) CTOR_CONCAT(base, __LINE__) +#define REG(cb) namespace { int CTOR_UNIQUE_NAME(unique) = reg(cb); } // Predefined configuration keys namespace cfg @@ -230,12 +267,12 @@ namespace cfg constexpr auto builddir = "builddir"; constexpr auto host_cc = "host-cc"; -constexpr auto host_cxx = "host-cpp"; +constexpr auto host_cxx = "host-cxx"; constexpr auto host_ar = "host-ar"; constexpr auto host_ld = "host-ld"; constexpr auto build_cc = "build-cc"; -constexpr auto build_cxx = "build-cpp"; +constexpr auto build_cxx = "build-cxx"; constexpr auto build_ar = "build-ar"; constexpr auto build_ld = "build-ld"; @@ -246,10 +283,13 @@ constexpr auto ctor_libdir = "ctor-libdir"; struct configuration { bool has(const std::string& key) const; - const std::string& get(const std::string& key, const std::string& default_value = {}) const; + std::string get(const std::string& key, const std::string& default_value = {}) const; ctor::toolchain host_toolchain{ctor::toolchain::none}; + ctor::arch host_arch{ctor::arch::unknown}; + ctor::toolchain build_toolchain{ctor::toolchain::none}; + ctor::arch build_arch{ctor::arch::unknown}; 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 diff --git a/src/deps.cc b/src/deps.cc new file mode 100644 index 0000000..9400b35 --- /dev/null +++ b/src/deps.cc @@ -0,0 +1,120 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "deps.h" + +#include "util.h" + +#include <fstream> + +namespace { +/* Format example: +build/src/libctor_a-libctor_cc.o: src/libctor.cc \ + src/getoptpp/getoptpp.hpp src/ctor.h src/configure.h src/rebuild.h \ + src/A\ B\ C.h src/task.h src/build.h src/unittest.h + */ +std::vector<std::string> readDepsMake(const std::string& dep_file) +{ + auto str = readFile(dep_file); + + std::vector<std::string> output; + std::string tmp; + + enum class State + { + pre_colon, + post_colon, + in_escape, + } state{State::pre_colon}; + + auto old_state{state}; + for(const auto& c : str) + { + if(c == '\r') + { + // just always ignore windows extra newline char + continue; + } + + switch(state) + { + case State::pre_colon: + if(c == ':') // ignore everything until the colon + { + state = State::post_colon; + continue; + } + continue; + + case State::post_colon: + if(c == '\n') + { + // done + if(!tmp.empty()) + { + output.emplace_back(tmp); + } + return output; + } + if(c == '\\') + { + old_state = state; + state = State::in_escape; + continue; + } + if(c == '\t' || c == ' ') // new token + { + if(!tmp.empty()) + { + output.emplace_back(tmp); + } + tmp.clear(); + continue; + } + break; + + case State::in_escape: + if(c == '\n') + { + // linewrap + state = old_state; + continue; + } + state = old_state; + break; + } + + tmp += c; + } + + if(!tmp.empty()) + { + output.emplace_back(tmp); + } + + return output; +} +} + +std::vector<std::string> readDeps(const std::string& dep_file, + ctor::toolchain toolchain) +{ + if(!std::filesystem::exists(dep_file)) + { + return {}; + } + + switch(toolchain) + { + case ctor::toolchain::any: + case ctor::toolchain::none: + return {}; + + // makefile .d file based: + case ctor::toolchain::gcc: + case ctor::toolchain::clang: + return readDepsMake(dep_file); + } + + return {}; +} diff --git a/src/deps.h b/src/deps.h new file mode 100644 index 0000000..be0dfb2 --- /dev/null +++ b/src/deps.h @@ -0,0 +1,12 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include "ctor.h" + +#include <vector> +#include <string> + +std::vector<std::string> readDeps(const std::string& dep_file, + ctor::toolchain toolchain); diff --git a/src/execute.cc b/src/execute.cc index 20a4a02..ad6c2a2 100644 --- a/src/execute.cc +++ b/src/execute.cc @@ -3,6 +3,9 @@ // See accompanying file LICENSE for details. #include "execute.h" +#include "ctor.h" +#include "pointerlist.h" + #include <unistd.h> #include <cstring> #include <sys/types.h> @@ -20,22 +23,53 @@ https://stackoverflow.com/questions/4259629/what-is-the-difference-between-fork- namespace { + int parent_waitpid(pid_t pid) { - int status; + int status{}; + + auto rc_pid = waitpid(pid, &status, 0); - if(waitpid(pid, &status, 0) != pid) + if(rc_pid > 0) { - return 1; + if(WIFEXITED(status)) + { + // Child exited with normally + return WEXITSTATUS(status); + } + if(WIFSIGNALED(status)) + { + // Child exited via signal (segfault, abort, ...) + std::cerr << strsignal(status) << '\n'; + return WTERMSIG(status); + } + } + else + { // No PID returned, this is an error + if(errno == ECHILD) + { + // No children exist. + return 1; + } + else + { + // Unexpected error. + abort(); + } } - return WEXITSTATUS(status); + // Should never happen... + return 1; } } // namespace :: -int execute(const std::string& command, +extern char **environ; // see 'man environ' + +int execute(const ctor::settings& settings, + const std::string& command, const std::vector<std::string>& args, - bool verbose) + const std::map<std::string, std::string>& env, + [[maybe_unused]] bool terminate) { std::vector<const char*> argv; argv.push_back(command.data()); @@ -45,22 +79,22 @@ int execute(const std::string& command, } argv.push_back(nullptr); - if(verbose) + std::string cmd; + for(const auto& arg : argv) { - std::string cmd; - for(const auto& arg : argv) + if(arg == nullptr) + { + break; + } + if(!cmd.empty()) { - if(arg == nullptr) - { - break; - } - if(!cmd.empty()) - { - cmd += " "; - } - cmd += arg; + cmd += " "; } + cmd += arg; + } + if(settings.verbose > 0) + { std::cout << cmd << std::endl; } @@ -68,23 +102,38 @@ int execute(const std::string& command, auto pid = vfork(); if(pid == 0) { - execv(command.data(), (char**)argv.data()); + EnvMap envmap((const char**)environ); + for(const auto& [key, value] : env) + { + envmap.insert(key + "=" + value); + } + + auto [_, envv] = envmap.get(); + execve(command.data(), const_cast<char* const *>(argv.data()), + const_cast<char* const *>(envv)); std::cout << "Could not execute " << command << ": " << strerror(errno) << "\n"; - _exit(1); // execv only returns if an error occurred + _exit(1); // execve only returns if an error occurred } - auto ret = parent_waitpid(pid); + return parent_waitpid(pid); #elif 0 pid_t pid; + std::vector<std::string> venv; + for(const auto& [key, value] : env) + { + venv.push_back(key + "=" + value); + } + Env penv(venv); if(posix_spawn(&pid, command.data(), nullptr, nullptr, - (char**)argv.data(), nullptr)) + (char**)argv.data(), penv.data())) { return 1; } - auto ret = parent_waitpid(pid); + return parent_waitpid(pid); #else - auto ret = system(cmd.data()); + (void)parent_waitpid; + return system(cmd.data()); #endif - return ret; + return 1; } diff --git a/src/execute.h b/src/execute.h index c750a83..4288bb7 100644 --- a/src/execute.h +++ b/src/execute.h @@ -5,7 +5,14 @@ #include <string> #include <vector> +#include <map> -int execute(const std::string& command, - const std::vector<std::string>& args, - bool verbose = true); +namespace ctor { +struct settings; +}//ctor:: + +int execute(const ctor::settings& settings, + const std::string& command, + const std::vector<std::string>& args = {}, + const std::map<std::string, std::string>& env = {}, + bool terminate = false); diff --git a/src/externals_manual.cc b/src/externals_manual.cc index 3b96263..0563a5e 100644 --- a/src/externals_manual.cc +++ b/src/externals_manual.cc @@ -13,7 +13,7 @@ extern std::map<std::string, std::string> external_includedir; extern std::map<std::string, std::string> external_libdir; -int resolv(const ctor::settings& settings, const ctor::external_configuration& config, +int resolv([[maybe_unused]]const ctor::settings& settings, const ctor::external_configuration& config, const ctor::external_manual& ext, ctor::flags& flags) { flags = ext.flags; @@ -21,14 +21,14 @@ int resolv(const ctor::settings& settings, const ctor::external_configuration& c auto inc = external_includedir.find(config.name); if(inc != external_includedir.end()) { - flags.cflags.push_back({ctor::c_opt::include_path, inc->second}); - flags.cxxflags.push_back({ctor::cxx_opt::include_path, inc->second}); + flags.cflags.emplace_back(ctor::c_opt::include_path, inc->second); + flags.cxxflags.emplace_back(ctor::cxx_opt::include_path, inc->second); } auto lib = external_libdir.find(config.name); if(lib != external_libdir.end()) { - flags.ldflags.push_back({ctor::ld_opt::library_path, lib->second}); + flags.ldflags.emplace_back(ctor::ld_opt::library_path, lib->second); } return 0; diff --git a/src/getoptpp b/src/getoptpp -Subproject 9ff20ef857429619267e3f156a4f81ad9e1eb8c +Subproject 5aba94355ec638c6f8612f86be309ed684979ae diff --git a/src/libctor.cc b/src/libctor.cc index 21792e1..aaf17c9 100644 --- a/src/libctor.cc +++ b/src/libctor.cc @@ -15,7 +15,7 @@ #include <deque> #include <fstream> #include <cstdlib> -#include <set> +#include <span> #include <getoptpp/getoptpp.hpp> @@ -28,6 +28,8 @@ int main(int argc, char* argv[]) { + auto args = std::span(argv, static_cast<std::size_t>(argc)); + ctor::settings settings{}; const auto& c = ctor::get_configuration(); @@ -35,12 +37,12 @@ int main(int argc, char* argv[]) settings.parallel_processes = std::max(1u, std::thread::hardware_concurrency()) * 2 - 1; - if(argc > 1 && std::string(argv[1]) == "configure") + if(args.size() > 1 && std::string(args[1]) == "configure") { return configure(settings, argc, argv); } - if(argc > 1 && std::string(argv[1]) == "reconfigure") + if(args.size() > 1 && std::string(args[1]) == "reconfigure") { return reconfigure(settings, argc, argv); } @@ -65,7 +67,8 @@ int main(int argc, char* argv[]) [&]() { try { - settings.parallel_processes = std::stoi(optarg); + settings.parallel_processes = + static_cast<std::size_t>(std::stoi(optarg)); } catch(...) { @@ -101,7 +104,7 @@ int main(int argc, char* argv[]) "Add specified file to the build configurations.", [&]() { no_relaunch = true; - add_files.push_back(optarg); + add_files.emplace_back(optarg); return 0; }); @@ -109,7 +112,7 @@ int main(int argc, char* argv[]) "Remove specified file from the build configurations.", [&]() { no_relaunch = true; - remove_files.push_back(optarg); + remove_files.emplace_back(optarg); return 0; }); @@ -156,8 +159,8 @@ int main(int argc, char* argv[]) opt.add("help", no_argument, 'h', "Print this help text.", - [&]() { - std::cout << "Usage: " << argv[0] << " [options] [target] ...\n"; + [&]() -> int { + std::cout << "Usage: " << args[0] << " [options] [target] ...\n"; std::cout << R"_( where target can be either: configure - run configuration step (cannot be used with other targets). @@ -171,7 +174,6 @@ Options: )_"; opt.help(); exit(0); - return 0; }); opt.process(argc, argv); @@ -185,17 +187,18 @@ Options: if(list_files) { no_default_build = true; - std::set<std::string> files; + std::vector<std::string> files; for(std::size_t i = 0; i < numConfigFiles; ++i) { - files.insert(configFiles[i].file); + files.emplace_back(configFiles[i].file); } for(std::size_t i = 0; i < numExternalConfigFiles; ++i) { - files.insert(externalConfigFiles[i].file); + files.emplace_back(externalConfigFiles[i].file); } + std::sort(files.begin(), files.end()); for(const auto& file : files) { std::cout << file << "\n"; @@ -270,7 +273,6 @@ Options: if(print_configure_db) { no_default_build = true; - const auto& c = ctor::get_configuration(); for(const auto& config : c.tools) { std::cout << config.first << ": " << config.second << "\n"; diff --git a/src/pointerlist.cc b/src/pointerlist.cc new file mode 100644 index 0000000..c0242f7 --- /dev/null +++ b/src/pointerlist.cc @@ -0,0 +1,123 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "pointerlist.h" + +#include <cstring> + +PointerList::PointerList(int argc, const char* const argv[]) +{ + for(int i = 0; i < argc; ++i) + { + push_back(argv[i]); + } +} + +std::pair<int, const char* const*> PointerList::get() +{ + argptrs.clear(); + for(const auto& arg : *this) + { + argptrs.push_back(arg.data()); + } + argptrs.push_back(nullptr); + + return {argptrs.size() - 1, // size not counting the nullptr at the end + argptrs.data()}; +} + +EnvMap::EnvMap(const char* const env[]) +{ + if(env == nullptr) + { + return; + } + + auto ptr = env; + while(*ptr) + { + insert(std::string_view(*ptr)); + ++ptr; + } +} + +EnvMap::EnvMap(const char* env) +{ + if(env == nullptr) + { + return; + } + + auto ptr = env; + while(*ptr) + { + insert(ptr); + ptr += strlen(ptr) + 1; + } +} + +std::string EnvMap::operator[](const std::string& key) const +{ + try + { + return data.at(key); + } + catch(...) + { + return {}; + } +} + +void EnvMap::clear() +{ + data.clear(); +} + +void EnvMap::insert(std::string_view key_value) +{ + auto equals_sign = key_value.find('='); + if(equals_sign == std::string::npos) + { + insert({key_value, ""}); + return; + } + std::string key{key_value.substr(0, equals_sign)}; + std::string value{key_value.substr(equals_sign + 1)}; // skip '=' + insert({key, value}); +} + +void EnvMap::insert(const std::pair<std::string_view, std::string_view>& item) +{ + data[std::string(item.first)] = item.second; +} + +bool EnvMap::contains(std::string_view key) const +{ + return data.contains(std::string{key}); +} + +std::size_t EnvMap::size() const +{ + return data.size(); +} + +std::string EnvMap::stringify() const +{ + std::string str; + for(const auto& [key, value] : data) + { + str += key + "=" + value + '\0'; + } + str += '\0'; + return str; +} + +std::pair<int, const char* const*> EnvMap::get() +{ + pointerlist.clear(); + for(const auto& [key, value] : data) + { + pointerlist.push_back(key + "=" + value + '\0'); + } + return pointerlist.get(); +} diff --git a/src/pointerlist.h b/src/pointerlist.h new file mode 100644 index 0000000..988bb26 --- /dev/null +++ b/src/pointerlist.h @@ -0,0 +1,74 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <string> +#include <vector> +#include <deque> +#include <utility> +#include <map> + +//! Maintains an (owning) list of string args and converts them to argc/argv +//! compatible arguments on request. +//! The returned pointers are guaranteed to be valid as long as the PointerList +//! object lifetime is not exceeded. +class PointerList + : public std::deque<std::string> +{ +public: + PointerList() = default; + PointerList(int argc, const char* const argv[]); + + //! Returns argc/argv pair from the current list of args + //! The argv entry after the last is a nullptr (not included in the argc) + std::pair<int, const char* const*> get(); + +private: + std::vector<const char*> argptrs; +}; + + +//! Maintains an owning map of strings representing the env. +class EnvMap +{ +public: + EnvMap() = default; + + //! Initialize from an array of pointers to key=value\0 strings terminated + //! by \0 + EnvMap(const char* const env[]); + + //! Initialize from a string of the format + //! key1=val\0key2=val\0...keyN=val\0\0 + EnvMap(const char* env); + + std::string operator[](const std::string& key) const; + + //! Clear all items in the map + void clear(); + + //! Insert string from format: key=value + void insert(std::string_view key_value); + + //! Regular map insert + void insert(const std::pair<std::string_view, std::string_view>& item); + + //! Checks if the container contains element with specific key + bool contains(std::string_view key) const; + + std::size_t size() const; + + //! Return string with the following format: + //! key1=val\0key2=val\0...keyN=val\0\0 + std::string stringify() const; + + //! Returns the map as argc/argv pair where each pointer points to a string + //! of the format key=value\0 and is terminated with a nullptr + std::pair<int, const char* const*> get(); + +private: + std::map<std::string, std::string> data{}; + + PointerList pointerlist; +}; diff --git a/src/rebuild.cc b/src/rebuild.cc index c97452d..52245fe 100644 --- a/src/rebuild.cc +++ b/src/rebuild.cc @@ -8,6 +8,7 @@ #include <algorithm> #include <source_location> #include <cstring> +#include <span> #include "configure.h" #include "ctor.h" @@ -70,7 +71,7 @@ int reg(const char* location) int unreg(const char* location) { - std::size_t found{0}; + int found{0}; for(std::size_t i = 0; i < numConfigFiles;) { if(std::string(location) == configFiles[i].file) @@ -150,6 +151,8 @@ bool contains(const std::vector<ctor::source>& sources, const std::string& file) bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[], bool relaunch_allowed) { + auto args_span = std::span(argv, static_cast<std::size_t>(argc)); + using namespace std::string_literals; if(global_settings.verbose > 1) @@ -159,38 +162,44 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ ctor::build_configuration config; + config.type = ctor::target_type::executable; config.name = "ctor"; config.system = ctor::output_system::build; - config.flags.cxxflags.push_back({ctor::cxx_opt::optimization, "3"}); - config.flags.cxxflags.push_back({ctor::cxx_opt::cpp_std, "c++20"}); + config.flags.cxxflags.emplace_back(ctor::cxx_opt::optimization, "3"); + config.flags.cxxflags.emplace_back(ctor::cxx_opt::cpp_std, "c++20"); const auto& c = ctor::get_configuration(); if(c.has(ctor::cfg::ctor_includedir)) { - config.flags.cxxflags.push_back({ctor::cxx_opt::include_path, - c.get(ctor::cfg::ctor_includedir)}); + config.flags.cxxflags.emplace_back(ctor::cxx_opt::include_path, + c.get(ctor::cfg::ctor_includedir)); } if(c.has(ctor::cfg::ctor_libdir)) { - config.flags.ldflags.push_back({ctor::ld_opt::library_path, c.get(ctor::cfg::ctor_libdir)}); + config.flags.ldflags.emplace_back(ctor::ld_opt::library_path, + c.get(ctor::cfg::ctor_libdir)); } - config.flags.ldflags.push_back({ctor::ld_opt::link, "ctor"}); - config.flags.ldflags.push_back({ctor::ld_opt::threads}); + config.flags.ldflags.emplace_back(ctor::ld_opt::link, "ctor"); + config.flags.ldflags.emplace_back(ctor::ld_opt::strip); + config.flags.ldflags.emplace_back(ctor::ld_opt::threads); ctor::settings settings{global_settings}; settings.verbose = -1; // Make check completely silent. - settings.builddir += "/ctor"; // override builddir to use ctor subdir + + // override builddir to use ctor subdir + auto ctor_builddir = std::filesystem::path(settings.builddir) / "ctor"; + settings.builddir = ctor_builddir.string(); { std::filesystem::path buildfile = settings.builddir; - std::filesystem::path currentfile = argv[0]; + std::filesystem::path currentfile = args_span[0]; config.target = std::filesystem::relative(currentfile, buildfile).string(); } if(std::filesystem::exists(configurationFile)) { - config.sources.push_back(configurationFile.string()); + config.sources.emplace_back(configurationFile.string()); } for(std::size_t i = 0; i < numConfigFiles; ++i) @@ -204,7 +213,7 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ // Ensure that files containing multiple configurations are only added once. if(!contains(config.sources, location)) { - config.sources.push_back(location); + config.sources.emplace_back(location); } } @@ -219,11 +228,11 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ // Ensure that files containing multiple configurations are only added once. if(!contains(config.sources, location)) { - config.sources.push_back(location); + config.sources.emplace_back(location); } } - auto tasks = taskFactory({config}, settings, {}); + auto tasks = taskFactory({config}, settings, {}, true); for(auto task : tasks) { @@ -259,16 +268,17 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[ if(reconfigure) { std::vector<std::string> args; - args.push_back("reconfigure"); + args.emplace_back("reconfigure"); if(!relaunch_allowed) { - args.push_back("--no-rerun"); + args.emplace_back("--no-rerun"); } - for(int i = 1; i < argc; ++i) + for(std::size_t i = 1; i < args_span.size(); ++i) { - args.push_back(argv[i]); + args.emplace_back(args_span[i]); } - auto ret = execute(argv[0], args); + + auto ret = execute(settings, args_span[0], args); //if(ret != 0) { exit(ret); diff --git a/src/task.cc b/src/task.cc index 817ee3a..ef7731b 100644 --- a/src/task.cc +++ b/src/task.cc @@ -3,19 +3,20 @@ // See accompanying file LICENSE for details. #include "task.h" -#include <unistd.h> #include <iostream> +#include <algorithm> +#include <utility> -Task::Task(const ctor::build_configuration& config, const ctor::settings& settings, - const std::string& sourceDir) - : config(config) +Task::Task(const ctor::build_configuration& config_, const ctor::settings& settings_, + std::string sourceDir_) + : config(config_) , output_system(config.system) - , settings(settings) - , sourceDir(sourceDir) + , settings(settings_) + , sourceDir(std::move(sourceDir_)) { } -int Task::registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks) +int Task::registerDepTasks(const std::vector<std::shared_ptr<Task>>& tasks) { for(const auto& depStr : depends()) { @@ -24,7 +25,10 @@ int Task::registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks) { if(*task == depStr) { - dependsTasks.insert(task); + if(std::find(dependsTasks.begin(), dependsTasks.end(), task) == dependsTasks.end()) + { + dependsTasks.push_back(task); + } found = true; } } @@ -41,11 +45,14 @@ int Task::registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks) bool Task::operator==(const std::string& depStr) { + std::filesystem::path generated_output = sourceDir; + generated_output /= target(); return - name() == depStr || - target() == depStr || - sourceDir + "/" + target() == depStr || - targetFile().string() == depStr + (!derived() && name() == depStr) || // compare to name + (!derived() && config.target == depStr) || // compare to stated target (ex. foo.a) + target() == depStr || // compare to derived (derived to foo.lib on msvc) + generated_output == depStr || // not sure what this is for?! + targetFile().string() == depStr // compare to target output file ; } @@ -140,6 +147,7 @@ std::string Task::compiler() const case ctor::output_system::build: return c.get(ctor::cfg::build_cc, "/usr/bin/gcc"); } + break; case ctor::language::cpp: switch(outputSystem()) { @@ -148,14 +156,18 @@ std::string Task::compiler() const case ctor::output_system::build: return c.get(ctor::cfg::build_cxx, "/usr/bin/g++"); } + break; default: std::cerr << "Unknown CC target type\n"; exit(1); break; } + + std::cerr << "Unhandled compiler!\n"; + exit(1); } -std::set<std::shared_ptr<Task>> Task::getDependsTasks() +std::vector<std::shared_ptr<Task>> Task::getDependsTasks() { return dependsTasks; } @@ -6,7 +6,6 @@ #include <vector> #include <string> #include <atomic> -#include <set> #include <memory> #include <filesystem> @@ -25,15 +24,15 @@ class Task { public: Task(const ctor::build_configuration& config, const ctor::settings& settings, - const std::string& sourceDir); + std::string sourceDir); virtual ~Task() = default; - int registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks); - virtual int registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) { return 0; } + int registerDepTasks(const std::vector<std::shared_ptr<Task>>& tasks); + virtual int registerDepTasksInner([[maybe_unused]]const std::vector<std::shared_ptr<Task>>& tasks) { return 0; } bool operator==(const std::string& dep); - virtual std::string name() const; + std::string name() const; bool dirty(); bool ready(); int run(); @@ -64,7 +63,7 @@ public: ctor::output_system outputSystem() const; std::string compiler() const; - std::set<std::shared_ptr<Task>> getDependsTasks(); + std::vector<std::shared_ptr<Task>> getDependsTasks(); virtual std::string source() const { return {}; } @@ -74,7 +73,7 @@ protected: virtual bool dirtyInner() { return false; } std::vector<std::string> dependsStr; - std::set<std::shared_ptr<Task>> dependsTasks; + std::vector<std::shared_ptr<Task>> dependsTasks; const ctor::build_configuration& config; ctor::target_type target_type{ctor::target_type::automatic}; ctor::language source_language{ctor::language::automatic}; diff --git a/src/task_ar.cc b/src/task_ar.cc index 81ced0f..3b45cc2 100644 --- a/src/task_ar.cc +++ b/src/task_ar.cc @@ -11,19 +11,22 @@ #include "util.h" #include "tools.h" -TaskAR::TaskAR(const ctor::build_configuration& config, - const ctor::settings& settings, +TaskAR::TaskAR(const ctor::build_configuration& config_, + const ctor::settings& settings_, const std::string& target, const std::vector<std::string>& objects, - const std::string& sourceDir) - : Task(config, settings, sourceDir) - , config(config) - , settings(settings) - , sourceDir(sourceDir) + const std::string& sourceDir_) + : Task(config_, settings_, sourceDir_) + , _targetFile(target) + , config(config_) + , settings(settings_) + , sourceDir(sourceDir_) { - std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceDir); + target_type = ctor::target_type::static_library; + output_system = config.system; - _targetFile = target; + auto toolchain = getToolChain(config.system); + _targetFile = extension(toolchain, target_type, config.system, _targetFile); for(const auto& object : objects) { std::filesystem::path objectFile = object; @@ -33,13 +36,12 @@ TaskAR::TaskAR(const ctor::build_configuration& config, for(const auto& dep : config.depends) { - depFiles.push_back(dep); + depFiles.emplace_back(dep); } flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); flagsFile += ".flags"; - target_type = ctor::target_type::static_library; source_language = ctor::language::c; for(const auto& source : config.sources) { @@ -50,6 +52,8 @@ TaskAR::TaskAR(const ctor::build_configuration& config, source_language = ctor::language::cpp; } } + + std::filesystem::create_directories(targetFile().parent_path()); } bool TaskAR::dirtyInner() @@ -78,9 +82,17 @@ bool TaskAR::dirtyInner() int TaskAR::runInner() { + auto toolchain = getToolChain(config.system); + std::vector<std::string> args; - args.push_back("rcs"); - args.push_back(targetFile().string()); + for(const auto& flag : config.flags.arflags) + { + append(args, to_strings(toolchain, flag)); + } + append(args, ar_option(toolchain, ctor::ar_opt::replace)); + append(args, ar_option(toolchain, ctor::ar_opt::add_index)); + append(args, ar_option(toolchain, ctor::ar_opt::create)); + append(args, ar_option(toolchain, ctor::ar_opt::output, targetFile().string())); for(const auto& task : getDependsTasks()) { args.push_back(task->targetFile().string()); @@ -93,7 +105,8 @@ int TaskAR::runInner() if(settings.verbose == 0) { - std::cout << "AR => " << targetFile().string() << std::endl; + std::string output = "AR => " + targetFile().string() + '\n'; + std::cout << output << std::flush; } const auto& c = ctor::get_configuration(); @@ -108,7 +121,13 @@ int TaskAR::runInner() break; } - return execute(tool, args, settings.verbose > 0); + auto res = execute(settings, tool, args, c.env); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskAR::clean() @@ -176,7 +195,6 @@ std::string TaskAR::flagsString() const flagsStr += str; } } - flagsStr += "\n"; for(const auto& dep : config.depends) { diff --git a/src/task_cc.cc b/src/task_cc.cc index 56915f5..b9edcf7 100644 --- a/src/task_cc.cc +++ b/src/task_cc.cc @@ -6,31 +6,33 @@ #include <iostream> #include <fstream> #include <cassert> +#include <algorithm> #include "ctor.h" #include "execute.h" #include "util.h" #include "tools.h" - -TaskCC::TaskCC(const ctor::build_configuration& config, const ctor::settings& settings, - const std::string& sourceDir, const ctor::source& source) - : Task(config, settings, sourceDir) - , config(config) - , settings(settings) - , sourceDir(sourceDir) +#include "deps.h" + +TaskCC::TaskCC(const ctor::build_configuration& config_, const ctor::settings& settings_, + const std::string& sourceDir_, const ctor::source& source) + : Task(config_, settings_, sourceDir_) + , sourceFile(sourceDir_) + , config(config_) + , settings(settings_) + , sourceDir(sourceDir_) , _source(source) { - sourceFile = sourceDir; sourceFile /= source.file; std::filesystem::path base = sourceFile.parent_path(); - std::filesystem::create_directories(std::filesystem::path(settings.builddir) / base); base /= cleanUp(config.target); base += "-"; base += sourceFile.stem(); target_type = ctor::target_type::object; + output_system = config.system; source_language = source.language; if(source_language == ctor::language::automatic) { @@ -56,36 +58,38 @@ TaskCC::TaskCC(const ctor::build_configuration& config, const ctor::settings& se if(source.output.empty()) { _targetFile = base; - _targetFile += ".o"; } else { _targetFile = source.output; } + auto toolchain = getToolChain(config.system); + _targetFile = extension(toolchain, target_type, config.system, _targetFile); + depsFile = targetFile().parent_path() / targetFile().stem(); depsFile += ".d"; flagsFile = targetFile().parent_path() / targetFile().stem(); flagsFile += ".flags"; + + std::filesystem::create_directories(targetFile().parent_path()); } -int TaskCC::registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) +int TaskCC::registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& tasks) { for(const auto& task : tasks) { if(*task == _source.file) { - dependsTasks.insert(task); + if(std::find(dependsTasks.begin(), dependsTasks.end(), task) == dependsTasks.end()) + { + dependsTasks.push_back(task); + } } } return 0; } -std::string TaskCC::name() const -{ - return {}; -} - bool TaskCC::dirtyInner() { if(!std::filesystem::exists(sourceFile)) @@ -128,7 +132,8 @@ bool TaskCC::dirtyInner() } } - auto depList = readDeps(depsFile.string()); + auto toolchain = getToolChain(config.system); + auto depList = readDeps(depsFile.string(), toolchain); for(const auto& dep : depList) { if(!std::filesystem::exists(dep) || @@ -167,25 +172,33 @@ int TaskCC::runInner() if(settings.verbose == 0) { + std::string output; switch(sourceLanguage()) { case ctor::language::c: - std::cout << "CC "; + output = "CC "; break; case ctor::language::cpp: - std::cout << "CXX "; + output = "CXX "; break; case ctor::language::automatic: case ctor::language::assembler: // Only c/c++ handled by this task type. break; } - std::cout << - sourceFile.lexically_normal().string() << " => " << - targetFile().lexically_normal().string() << std::endl; + output += sourceFile.lexically_normal().string() + " => " + + targetFile().lexically_normal().string() + '\n'; + std::cout << output << std::flush; + } + + const auto& cfg = ctor::get_configuration(); + auto res = execute(settings, compiler(), args, cfg.env); + if(res != 0) + { + std::filesystem::remove(targetFile()); } - return execute(compiler(), args, settings.verbose > 0); + return res; } int TaskCC::clean() @@ -278,6 +291,7 @@ std::vector<std::string> TaskCC::flags() const exit(1); break; } + } std::string TaskCC::flagsString() const @@ -301,7 +315,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const { case ctor::language::c: { - append(args, c_option(toolchain, ctor::c_opt::generate_dep_tree)); + append(args, c_option(toolchain, ctor::c_opt::generate_dep_tree, depsFile.string())); if(std::filesystem::path(config.target).extension() == ".so") { @@ -343,7 +357,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const case ctor::language::cpp: { - append(args, cxx_option(toolchain, ctor::cxx_opt::generate_dep_tree)); + append(args, cxx_option(toolchain, ctor::cxx_opt::generate_dep_tree, depsFile.string())); if(std::filesystem::path(config.target).extension() == ".so") { diff --git a/src/task_cc.h b/src/task_cc.h index 70bb13c..2299fcd 100644 --- a/src/task_cc.h +++ b/src/task_cc.h @@ -19,9 +19,8 @@ public: const std::string& sourceDir, const ctor::source& source); virtual ~TaskCC() = default; - int registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) override; + int registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& tasks) override; - std::string name() const override; bool dirtyInner() override; int runInner() override; diff --git a/src/task_fn.cc b/src/task_fn.cc index b11ff15..b6b50ea 100644 --- a/src/task_fn.cc +++ b/src/task_fn.cc @@ -11,13 +11,13 @@ #include "execute.h" #include "util.h" -TaskFn::TaskFn(const ctor::build_configuration& config, const ctor::settings& settings, - const std::string& sourceDir, const ctor::source& source) - : Task(config, settings, sourceDir) - , config(config) - , settings(settings) +TaskFn::TaskFn(const ctor::build_configuration& config_, const ctor::settings& settings_, + const std::string& sourceDir_, const ctor::source& source) + : Task(config_, settings_, sourceDir_) + , sourceFile(sourceDir_) + , config(config_) + , settings(settings_) { - sourceFile = sourceDir; sourceFile /= source.file; std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceFile.parent_path()); @@ -68,15 +68,22 @@ int TaskFn::runInner() if(settings.verbose >= 0) { - std::cout << "Fn" << " " << - sourceFile.lexically_normal().string() << " => " << - targetFile().lexically_normal().string() << std::endl; + std::string output = "Fn " + + sourceFile.lexically_normal().string() + " => " + + targetFile().lexically_normal().string() + '\n'; + std::cout << output << std::flush; } - return config.function(sourceFile.string(), - targetFile().string(), - config, - settings); + auto res = config.function(sourceFile.string(), + targetFile().string(), + config, + settings); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskFn::clean() @@ -97,7 +104,7 @@ std::vector<std::string> TaskFn::depends() const std::string TaskFn::target() const { - return _targetFile; + return _targetFile.string(); } std::filesystem::path TaskFn::targetFile() const diff --git a/src/task_ld.cc b/src/task_ld.cc index 3d917e4..69c3a8a 100644 --- a/src/task_ld.cc +++ b/src/task_ld.cc @@ -11,25 +11,29 @@ #include "util.h" #include "tools.h" -TaskLD::TaskLD(const ctor::build_configuration& config, - const ctor::settings& settings, +TaskLD::TaskLD(const ctor::build_configuration& config_, + const ctor::settings& settings_, const std::string& target, const std::vector<std::string>& objects, - const std::string& sourceDir) - : Task(config, settings, sourceDir) - , config(config) - , settings(settings) - , sourceDir(sourceDir) + const std::string& sourceDir_, + bool is_self_) + : Task(config_, settings_, sourceDir_) + , config(config_) + , settings(settings_) + , sourceDir(sourceDir_) + , is_self(is_self_) { target_type = config.type; + output_system = config.system; + if(target_type == ctor::target_type::automatic) { target_type = ctor::target_type::executable; } - std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceDir); - _targetFile = target; + auto toolchain = getToolChain(config.system); + _targetFile = extension(toolchain, target_type, config.system, _targetFile); for(const auto& object : objects) { std::filesystem::path objectFile = object; @@ -39,7 +43,7 @@ TaskLD::TaskLD(const ctor::build_configuration& config, for(const auto& dep : config.depends) { - depFiles.push_back(dep); + depFiles.emplace_back(dep); } flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); @@ -54,6 +58,8 @@ TaskLD::TaskLD(const ctor::build_configuration& config, source_language = ctor::language::cpp; } } + + std::filesystem::create_directories(targetFile().parent_path()); } bool TaskLD::dirtyInner() @@ -88,14 +94,16 @@ int TaskLD::runInner() for(const auto& dep : getDependsTasks()) { auto depFile = dep->targetFile(); - if(depFile.extension() == ".so") + auto dep_type = target_type_from_extension(toolchain, depFile); + if(dep_type == ctor::target_type::dynamic_library) { append(args, ld_option(toolchain, ctor::ld_opt::library_path, targetFile().parent_path().string())); auto lib = depFile.stem().string().substr(3); // strip 'lib' prefix append(args, ld_option(toolchain, ctor::ld_opt::link, lib)); } - else if(depFile.extension() == ".a" || depFile.extension() == ".o") + else if(dep_type == ctor::target_type::static_library || + dep_type == ctor::target_type::object) { args.push_back(depFile.string()); } @@ -115,11 +123,19 @@ int TaskLD::runInner() if(settings.verbose == 0) { - std::cout << "LD => " << targetFile().string() << std::endl; + std::string output = "LD => " + targetFile().string() + '\n'; + std::cout << output << std::flush; } auto tool = compiler(); - return execute(tool, args, settings.verbose > 0); + const auto& c = ctor::get_configuration(); + auto res = execute(settings, tool, args, c.env, is_self); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskLD::clean() @@ -187,7 +203,6 @@ std::string TaskLD::flagsString() const flagsStr += str; } } - flagsStr += "\n"; for(const auto& dep : config.depends) { diff --git a/src/task_ld.h b/src/task_ld.h index dbe7db1..c0e3ebb 100644 --- a/src/task_ld.h +++ b/src/task_ld.h @@ -18,7 +18,8 @@ public: const ctor::settings& settings, const std::string& target, const std::vector<std::string>& objects, - const std::string& _sourceDir); + const std::string& _sourceDir, + bool is_self); virtual ~TaskLD() = default; bool dirtyInner() override; @@ -44,4 +45,5 @@ private: const ctor::build_configuration& config; const ctor::settings& settings; std::string sourceDir; + bool is_self; }; diff --git a/src/task_so.cc b/src/task_so.cc index 9061591..c98e4a7 100644 --- a/src/task_so.cc +++ b/src/task_so.cc @@ -11,20 +11,24 @@ #include "util.h" #include "tools.h" -TaskSO::TaskSO(const ctor::build_configuration& config, - const ctor::settings& settings, +TaskSO::TaskSO(const ctor::build_configuration& config_, + const ctor::settings& settings_, const std::string& target, const std::vector<std::string>& objects, - const std::string& sourceDir) - : Task(config, settings, sourceDir) - , config(config) - , settings(settings) - , sourceDir(sourceDir) + const std::string& sourceDir_) + : Task(config_, settings_, sourceDir_) + , config(config_) + , settings(settings_) + , sourceDir(sourceDir_) { std::filesystem::path base = sourceDir; - std::filesystem::create_directories(std::filesystem::path(settings.builddir) / base); + + target_type = ctor::target_type::dynamic_library; + output_system = config.system; _targetFile = base / target; + auto toolchain = getToolChain(config.system); + _targetFile = extension(toolchain, target_type, config.system, _targetFile); for(const auto& object : objects) { std::filesystem::path objectFile = object; @@ -34,13 +38,12 @@ TaskSO::TaskSO(const ctor::build_configuration& config, for(const auto& dep : config.depends) { - depFiles.push_back(dep); + depFiles.emplace_back(dep); } flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); flagsFile += ".flags"; - target_type = ctor::target_type::dynamic_library; source_language = ctor::language::c; for(const auto& source : config.sources) { @@ -51,6 +54,8 @@ TaskSO::TaskSO(const ctor::build_configuration& config, source_language = ctor::language::cpp; } } + + std::filesystem::create_directories(targetFile().parent_path()); } bool TaskSO::dirtyInner() @@ -105,11 +110,19 @@ int TaskSO::runInner() if(settings.verbose == 0) { - std::cout << "LD => " << targetFile().string() << std::endl; + std::string output = "LD => " + targetFile().string() + '\n'; + std::cout << output << std::flush; } auto tool = compiler(); - return execute(tool, args, settings.verbose > 0); + const auto& cfg = ctor::get_configuration(); + auto res = execute(settings, tool, args, cfg.env); + if(res != 0) + { + std::filesystem::remove(targetFile()); + } + + return res; } int TaskSO::clean() @@ -177,7 +190,6 @@ std::string TaskSO::flagsString() const flagsStr += str; } } - flagsStr += "\n"; for(const auto& dep : config.depends) { diff --git a/src/tasks.cc b/src/tasks.cc index 67bed2b..2f9e47a 100644 --- a/src/tasks.cc +++ b/src/tasks.cc @@ -8,6 +8,7 @@ #include <list> #include <iostream> #include <algorithm> +#include <span> #include "ctor.h" #include "task.h" @@ -18,24 +19,28 @@ #include "task_fn.h" #include "rebuild.h" #include "configure.h" +#include "util.h" +#include "tools.h" const std::deque<Target>& getTargets(const ctor::settings& settings, bool resolve_externals) { + auto config_files = std::span(configFiles).subspan(0, numConfigFiles); + static bool initialised{false}; static std::deque<Target> targets; if(!initialised) { const auto& externals = ctor::get_configuration().externals; - for(std::size_t i = 0; i < numConfigFiles; ++i) + for(const auto& config_file : config_files) { std::string path = - std::filesystem::path(configFiles[i].file).parent_path().string(); + std::filesystem::path(config_file.file).parent_path().string(); if(settings.verbose > 1) { - std::cout << configFiles[i].file << " in path " << path << "\n"; + std::cout << config_file.file << " in path " << path << "\n"; } - auto configs = configFiles[i].cb(settings); + auto configs = config_file.cb(settings); for(auto& config : configs) { if(resolve_externals) @@ -50,21 +55,12 @@ const std::deque<Target>& getTargets(const ctor::settings& settings, 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()); + append(config.flags.cflags, flags.cflags); + append(config.flags.cxxflags, flags.cxxflags); + append(config.flags.ldflags, flags.ldflags); + append(config.flags.arflags, flags.arflags); + append(config.flags.asmflags, flags.asmflags); + //append(config.libs.insert(config.libs libs); } } @@ -77,11 +73,12 @@ const std::deque<Target>& getTargets(const ctor::settings& settings, return targets; } -std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& config, - const ctor::settings& settings, - const std::string& sourceDir) +std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& config, + const ctor::settings& settings, + const std::string& sourceDir, + bool is_self) { - std::set<std::shared_ptr<Task>> tasks; + std::vector<std::shared_ptr<Task>> tasks; std::filesystem::path targetFile(config.target); @@ -92,34 +89,26 @@ std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& con { target_type = ctor::target_type::function; } - else if(targetFile.extension() == ".a") - { - target_type = ctor::target_type::static_library; - } - else if(targetFile.extension() == ".so") - { - target_type = ctor::target_type::dynamic_library; - } - else if(targetFile.extension() == "") - { - target_type = ctor::target_type::executable; - } else { - std::cerr << "Could not deduce target type from target " << - targetFile.string() << " please specify.\n"; - exit(1); + target_type = target_type_from_extension(ctor::toolchain::any, targetFile); } } + const auto& c = ctor::get_configuration(); std::vector<std::string> objects; if(target_type != ctor::target_type::function) { - for(const auto& file : config.sources) + for(const auto& source : config.sources) { - auto task = std::make_shared<TaskCC>(config, settings, sourceDir, file); - tasks.insert(task); - objects.push_back(task->targetFile().string()); + if(source.toolchain == ctor::toolchain::any || + (config.system == ctor::output_system::build && source.toolchain == c.build_toolchain) || + (config.system == ctor::output_system::host && source.toolchain == c.host_toolchain)) + { + auto task = std::make_shared<TaskCC>(config, settings, sourceDir, source); + tasks.push_back(task); + objects.push_back(task->targetFile().string()); + } } } #ifndef BOOTSTRAP @@ -128,7 +117,7 @@ std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& con for(const auto& file : config.sources) { auto task = std::make_shared<TaskFn>(config, settings, sourceDir, file); - tasks.insert(task); + tasks.push_back(task); objects.push_back(task->target()); } } @@ -140,10 +129,16 @@ std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& con // The target_type cannot be Auto break; + case ctor::target_type::unknown: + std::cerr << "Could not deduce target type from target " << + targetFile.string() << " please specify.\n"; + exit(1); + break; + case ctor::target_type::static_library: case ctor::target_type::unit_test_library: - tasks.insert(std::make_shared<TaskAR>(config, settings, config.target, - objects, sourceDir)); + tasks.push_back(std::make_shared<TaskAR>(config, settings, config.target, + objects, sourceDir)); break; #ifndef BOOTSTRAP case ctor::target_type::dynamic_library: @@ -153,14 +148,14 @@ std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& con std::cerr << "Dynamic library target must have 'lib' prefix\n"; exit(1); } - tasks.insert(std::make_shared<TaskSO>(config, settings, config.target, - objects, sourceDir)); + tasks.push_back(std::make_shared<TaskSO>(config, settings, config.target, + objects, sourceDir)); break; case ctor::target_type::executable: case ctor::target_type::unit_test: - tasks.insert(std::make_shared<TaskLD>(config, settings, config.target, - objects, sourceDir)); + tasks.push_back(std::make_shared<TaskLD>(config, settings, config.target, + objects, sourceDir, is_self)); break; case ctor::target_type::object: @@ -175,18 +170,19 @@ std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& con return tasks; } -std::shared_ptr<Task> getNextTask(const std::set<std::shared_ptr<Task>>& allTasks, - std::set<std::shared_ptr<Task>>& dirtyTasks) +std::shared_ptr<Task> getNextTask([[maybe_unused]]const std::vector<std::shared_ptr<Task>>& allTasks, + std::vector<std::shared_ptr<Task>>& dirtyTasks) { for(auto dirtyTask = dirtyTasks.begin(); dirtyTask != dirtyTasks.end(); ++dirtyTask) { + auto task = *dirtyTask; //std::cout << "Examining target " << (*dirtyTask)->target() << "\n"; - if((*dirtyTask)->ready()) + if(task->ready()) { dirtyTasks.erase(dirtyTask); - return *dirtyTask; + return task; } } @@ -194,20 +190,20 @@ std::shared_ptr<Task> getNextTask(const std::set<std::shared_ptr<Task>>& allTask return nullptr; } -std::set<std::shared_ptr<Task>> getTasks(const ctor::settings& settings, - const std::vector<std::string> names, - bool resolve_externals) +std::vector<std::shared_ptr<Task>> getTasks(const ctor::settings& settings, + const std::vector<std::string> names, + bool resolve_externals) { auto& targets = getTargets(settings, resolve_externals); - std::set<std::shared_ptr<Task>> tasks; + std::vector<std::shared_ptr<Task>> tasks; for(const auto& target : targets) { if(names.empty() || std::find(std::begin(names), std::end(names), target.config.target) != std::end(names)) { std::vector<std::string> objects; - auto t = taskFactory(target.config, settings, target.path); - tasks.insert(t.begin(), t.end()); + auto t = taskFactory(target.config, settings, target.path, false); + tasks.insert(tasks.end(), t.begin(), t.end()); } } diff --git a/src/tasks.h b/src/tasks.h index ed1bf60..6573784 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -4,7 +4,6 @@ #pragma once #include <string> -#include <set> #include <memory> #include <deque> @@ -24,17 +23,18 @@ const std::deque<Target>& getTargets(const ctor::settings& settings, //! fulfilled. //! The returned task is removed from the dirty list. //! Return nullptr if no dirty task is ready. -std::shared_ptr<Task> getNextTask(const std::set<std::shared_ptr<Task>>& allTasks, - std::set<std::shared_ptr<Task>>& dirtyTasks); +std::shared_ptr<Task> getNextTask(const std::vector<std::shared_ptr<Task>>& allTasks, + std::vector<std::shared_ptr<Task>>& dirtyTasks); //! Get list of tasks filtered by name including each of their direct //! dependency tasks (ie. objects tasks from their sources). -std::set<std::shared_ptr<Task>> getTasks(const ctor::settings& settings, - const std::vector<std::string> names = {}, - bool resolve_externals = true); +std::vector<std::shared_ptr<Task>> getTasks(const ctor::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 ctor::build_configuration& config, - const ctor::settings& settings, - const std::string& sourceDir); +std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& config, + const ctor::settings& settings, + const std::string& sourceDir, + bool is_self); diff --git a/src/tools.cc b/src/tools.cc index 476386b..8c9b7e2 100644 --- a/src/tools.cc +++ b/src/tools.cc @@ -6,8 +6,12 @@ #include <filesystem> #include <iostream> #include <sstream> +#include <array> #include <cassert> +#include <cstdio> + +#include "util.h" std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt) { @@ -17,6 +21,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt) case ctor::c_opt::output: stream << "ctor::c_opt::output"; break; case ctor::c_opt::debug: stream << "ctor::c_opt::debug"; break; case ctor::c_opt::warn_all: stream << "ctor::c_opt::warn_all"; break; + case ctor::c_opt::warn_conversion: stream << "ctor::c_opt::warn_conversion"; break; + case ctor::c_opt::warn_shadow: stream << "ctor::c_opt::warn_shadow"; break; + case ctor::c_opt::warn_extra: stream << "ctor::c_opt::warn_extra"; break; case ctor::c_opt::warnings_as_errors: stream << "ctor::c_opt::warnings_as_errors"; break; case ctor::c_opt::generate_dep_tree: stream << "ctor::c_opt::generate_dep_tree"; break; case ctor::c_opt::no_link: stream << "ctor::c_opt::no_link"; break; @@ -25,6 +32,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt) case ctor::c_opt::optimization: stream << "ctor::c_opt::optimization"; break; case ctor::c_opt::position_independent_code: stream << "ctor::c_opt::position_independent_code"; break; case ctor::c_opt::position_independent_executable: stream << "ctor::c_opt::position_independent_executable"; break; + case ctor::c_opt::define: stream << "ctor::c_opt::define"; break; case ctor::c_opt::custom: stream << "ctor::c_opt::custom"; break; } @@ -39,6 +47,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt) case ctor::cxx_opt::output: stream << "ctor::cxx_opt::output"; break; case ctor::cxx_opt::debug: stream << "ctor::cxx_opt::debug"; break; case ctor::cxx_opt::warn_all: stream << "ctor::cxx_opt::warn_all"; break; + case ctor::cxx_opt::warn_conversion: stream << "ctor::cxx_opt::warn_conversion"; break; + case ctor::cxx_opt::warn_shadow: stream << "ctor::cxx_opt::warn_shadow"; break; + case ctor::cxx_opt::warn_extra: stream << "ctor::cxx_opt::warn_extra"; break; case ctor::cxx_opt::warnings_as_errors: stream << "ctor::cxx_opt::warnings_as_errors"; break; case ctor::cxx_opt::generate_dep_tree: stream << "ctor::cxx_opt::generate_dep_tree"; break; case ctor::cxx_opt::no_link: stream << "ctor::cxx_opt::no_link"; break; @@ -47,6 +58,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt) case ctor::cxx_opt::optimization: stream << "ctor::cxx_opt::optimization"; break; case ctor::cxx_opt::position_independent_code: stream << "ctor::cxx_opt::position_independent_code"; break; case ctor::cxx_opt::position_independent_executable: stream << "ctor::cxx_opt::position_independent_executable"; break; + case ctor::cxx_opt::define: stream << "ctor::cxx_opt::define"; break; case ctor::cxx_opt::custom: stream << "ctor::cxx_opt::custom"; break; } @@ -80,6 +92,10 @@ std::ostream& operator<<(std::ostream& stream, const ctor::ar_opt& opt) // Adding to this enum should also imply adding to the unit-tests below switch(opt) { + case ctor::ar_opt::replace: stream << "ctor::ar_opt::replace"; break; + case ctor::ar_opt::add_index: stream << "ctor::ar_opt::add_index"; break; + case ctor::ar_opt::create: stream << "ctor::ar_opt::create"; break; + case ctor::ar_opt::output: stream << "ctor::ar_opt::output"; break; case ctor::ar_opt::custom: stream << "ctor::ar_opt::custom"; break; } @@ -103,11 +119,14 @@ ctor::toolchain getToolChain(const std::string& 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) + if(cc_cmd.find("clang++") != std::string::npos || + cc_cmd.find("clang") != std::string::npos) { return ctor::toolchain::clang; } - else if(cc_cmd.find("g++") != std::string::npos) + else if(cc_cmd.find("g++") != std::string::npos || + cc_cmd.find("gcc") != std::string::npos || + cc_cmd.find("mingw") != std::string::npos) { return ctor::toolchain::gcc; } @@ -138,43 +157,253 @@ ctor::toolchain getToolChain(ctor::output_system system) } namespace gcc { +std::string get_arch(ctor::output_system system) +{ + std::string cmd; + + const auto& c = ctor::get_configuration(); + switch(system) + { + case ctor::output_system::host: + cmd = c.get(ctor::cfg::host_cxx, "/usr/bin/g++"); + break; + case ctor::output_system::build: + cmd = c.get(ctor::cfg::build_cxx, "/usr/bin/g++"); + break; + } + + cmd += " -v"; + cmd += " 2>&1"; + auto pipe = popen(cmd.data(), "r"); + if(!pipe) + { + std::cerr << "Could not run compiler " << cmd << "\n"; + return {};//ctor::arch::unknown; + } + + std::string arch; + while(!feof(pipe)) + { + constexpr auto buffer_size{1024}; + std::array<char, buffer_size> buf{}; + if(fgets(buf.data(), buf.size(), pipe) != nullptr) + { + arch = buf.data(); + if(arch.starts_with("Target:")) + { + break; + } + } + } + + pclose(pipe); + + // Remove trailing newline + if(arch.ends_with("\n")) + { + arch = arch.substr(0, arch.length() - 1); + } + + // Remove 'Target: ' prefix + constexpr auto prefix_length{8}; + arch = arch.substr(prefix_length); + return arch; +} + +ctor::arch get_arch(const std::string& str) +{ + // gcc -v 2>&1 | grep Target + // Target: x86_64-apple-darwin19.6.0 + // Target: x86_64-pc-linux-gnu + // Target: arm-linux-gnueabihf + // Target: x86_64-linux-gnu + // Target: x86_64-unknown-freebsd13.1 + // # x86_64-w64-mingw32-c++-win32 -v 2>&1 | grep Target + // Target: x86_64-w64-mingw32 + // # i686-w64-mingw32-c++-win32 -v 2>&1 | grep Target + // Target: i686-w64-mingw32 + + if(str.find("apple") != std::string::npos || + str.find("darwin") != std::string::npos) + { + return ctor::arch::apple; + } + + if(str.find("linux") != std::string::npos || + str.find("bsd") != std::string::npos) + { + return ctor::arch::unix; + } + + if(str.find("mingw") != std::string::npos) + { + return ctor::arch::windows; + } + + std::cerr << "Could not deduce gcc arch from '" << str << "'" << std::endl; + + return ctor::arch::unknown; +} + ctor::c_flag c_option(const std::string& flag) { - if(flag.substr(0, 2) == "-I") + if(flag.starts_with("-I")) { std::string path = flag.substr(2); path.erase(0, path.find_first_not_of(' ')); return { ctor::c_opt::include_path, path }; } + if(flag.starts_with("-std=")) + { + std::string std = flag.substr(5); + return { ctor::c_opt::c_std, std }; + } + + if(flag.starts_with("-O")) + { + std::string opt = flag.substr(2, 1); + return { ctor::c_opt::optimization, opt }; + } + + if(flag.starts_with("-Wall")) + { + return { ctor::c_opt::warn_all }; + } + + if(flag.starts_with("-Wconversion")) + { + return { ctor::c_opt::warn_conversion}; + } + + if(flag.starts_with("-Wshadow")) + { + return { ctor::c_opt::warn_shadow}; + } + + if(flag.starts_with("-Wextra")) + { + return { ctor::c_opt::warn_extra}; + } + + if(flag.starts_with("-Werror")) + { + return { ctor::c_opt::warnings_as_errors }; + } + + if(flag.starts_with("-g")) + { + return { ctor::c_opt::debug }; + } + + if(flag.starts_with("-D")) + { + std::string def = flag.substr(2); + auto pos = def.find('='); + if(pos != def.npos) + { + return { ctor::c_opt::define, def.substr(0, pos), def.substr(pos + 1) }; + } + else + { + return { ctor::c_opt::define, def }; + } + } return { ctor::c_opt::custom, flag }; } ctor::cxx_flag cxx_option(const std::string& flag) { - if(flag.substr(0, 2) == "-I") + if(flag.starts_with("-I")) { std::string path = flag.substr(2); path.erase(0, path.find_first_not_of(' ')); return { ctor::cxx_opt::include_path, path }; } + if(flag.starts_with("-std=")) + { + std::string std = flag.substr(5); + return { ctor::cxx_opt::cpp_std, std }; + } + + if(flag.starts_with("-O")) + { + std::string opt = flag.substr(2, 1); + return { ctor::cxx_opt::optimization, opt }; + } + + if(flag.starts_with("-Wall")) + { + return { ctor::cxx_opt::warn_all }; + } + + if(flag.starts_with("-Werror")) + { + return { ctor::cxx_opt::warnings_as_errors }; + } + + if(flag.starts_with("-Wconversion")) + { + return { ctor::cxx_opt::warn_conversion}; + } + + if(flag.starts_with("-Wshadow")) + { + return { ctor::cxx_opt::warn_shadow}; + } + + if(flag.starts_with("-Wextra")) + { + return { ctor::cxx_opt::warn_extra}; + } + + if(flag.starts_with("-g")) + { + return { ctor::cxx_opt::debug }; + } + + if(flag.starts_with("-D")) + { + std::string def = flag.substr(2); + auto pos = def.find('='); + if(pos != def.npos) + { + return { ctor::cxx_opt::define, def.substr(0, pos), def.substr(pos + 1) }; + } + else + { + return { ctor::cxx_opt::define, def }; + } + } + return { ctor::cxx_opt::custom, flag }; } ctor::ld_flag ld_option(const std::string& flag) { - if(flag.substr(0, 2) == "-L") + if(flag.starts_with("-L")) { std::string path = flag.substr(2); path.erase(0, path.find_first_not_of(' ')); return { ctor::ld_opt::library_path, path }; } + if(flag.starts_with("-pthread")) + { + return { ctor::ld_opt::threads }; + } + return { ctor::ld_opt::custom, flag }; } -std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg) +ctor::ar_flag ar_option(const std::string& flag) +{ + return { ctor::ar_opt::custom, flag }; +} + +std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, + const std::string& arg2) { switch(opt) { @@ -184,6 +413,12 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg) return {"-g"}; case ctor::cxx_opt::warn_all: return {"-Wall"}; + case ctor::cxx_opt::warn_conversion: + return {"-Wconversion"}; + case ctor::cxx_opt::warn_shadow: + return {"-Wshadow"}; + case ctor::cxx_opt::warn_extra: + return {"-Wextra"}; case ctor::cxx_opt::warnings_as_errors: return {"-Werror"}; case ctor::cxx_opt::generate_dep_tree: @@ -200,6 +435,12 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg) return {"-fPIC"}; case ctor::cxx_opt::position_independent_executable: return {"-fPIE"}; + case ctor::cxx_opt::define: + if(!arg2.empty()) + { + return {"-D" + arg + "=" + arg2}; + } + return {"-D" + arg}; case ctor::cxx_opt::custom: return {arg}; } @@ -208,7 +449,8 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg) return {}; } -std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg) +std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg, + const std::string& arg2) { switch(opt) { @@ -218,6 +460,12 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg) return {"-g"}; case ctor::c_opt::warn_all: return {"-Wall"}; + case ctor::c_opt::warn_conversion: + return {"-Wconversion"}; + case ctor::c_opt::warn_shadow: + return {"-Wshadow"}; + case ctor::c_opt::warn_extra: + return {"-Wextra"}; case ctor::c_opt::warnings_as_errors: return {"-Werror"}; case ctor::c_opt::generate_dep_tree: @@ -234,6 +482,12 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg) return {"-fPIC"}; case ctor::c_opt::position_independent_executable: return {"-fPIE"}; + case ctor::c_opt::define: + if(!arg2.empty()) + { + return {"-D" + arg + "=" + arg2}; + } + return {"-D" + arg}; case ctor::c_opt::custom: return {arg}; } @@ -242,7 +496,8 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg) return {}; } -std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg) +std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg, + [[maybe_unused]]const std::string& arg2) { switch(opt) { @@ -276,10 +531,19 @@ std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg) return {}; } -std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg) +std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg, + [[maybe_unused]]const std::string& arg2) { switch(opt) { + case ctor::ar_opt::replace: + return {"-r"}; + case ctor::ar_opt::add_index: + return {"-s"}; + case ctor::ar_opt::create: + return {"-c"}; + case ctor::ar_opt::output: + return {arg}; case ctor::ar_opt::custom: return {arg}; } @@ -288,7 +552,8 @@ std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg) return {}; } -std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg) +std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg, + [[maybe_unused]]const std::string& arg2) { switch(opt) { @@ -301,16 +566,46 @@ std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg) } } // gcc:: +std::string get_arch(ctor::output_system system) +{ + auto toolchain = getToolChain(system); + switch(toolchain) + { + case ctor::toolchain::clang: + case ctor::toolchain::gcc: + return gcc::get_arch(system); + case ctor::toolchain::any: + case ctor::toolchain::none: + break; + } + return {}; +} + +ctor::arch get_arch(ctor::output_system system, const std::string& str) +{ + auto toolchain = getToolChain(system); + switch(toolchain) + { + case ctor::toolchain::clang: + case ctor::toolchain::gcc: + return gcc::get_arch(str); + case ctor::toolchain::any: + case ctor::toolchain::none: + break; + } + return ctor::arch::unknown; +} std::vector<std::string> c_option(ctor::toolchain toolchain, ctor::c_opt opt, - const std::string& arg) + const std::string& arg, + const std::string& arg2) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: - return gcc::c_option(opt, arg); + return gcc::c_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -319,6 +614,10 @@ std::vector<std::string> c_option(ctor::toolchain toolchain, { ss << ", \"" << arg << "\""; } + if(!arg2.empty()) + { + ss << ", \"" << arg2 << "\""; + } ss << "}"; return { ss.str() }; } @@ -332,13 +631,14 @@ std::vector<std::string> c_option(ctor::toolchain toolchain, std::vector<std::string> cxx_option(ctor::toolchain toolchain, ctor::cxx_opt opt, - const std::string& arg) + const std::string& arg, + const std::string& arg2) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: - return gcc::cxx_option(opt, arg); + return gcc::cxx_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -347,6 +647,10 @@ std::vector<std::string> cxx_option(ctor::toolchain toolchain, { ss << ", \"" << arg << "\""; } + if(!arg2.empty()) + { + ss << ", \"" << arg2 << "\""; + } ss << "}"; return { ss.str() }; } @@ -360,13 +664,14 @@ std::vector<std::string> cxx_option(ctor::toolchain toolchain, std::vector<std::string> ld_option(ctor::toolchain toolchain, ctor::ld_opt opt, - const std::string& arg) + const std::string& arg, + const std::string& arg2) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: - return gcc::ld_option(opt, arg); + return gcc::ld_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -375,6 +680,10 @@ std::vector<std::string> ld_option(ctor::toolchain toolchain, { ss << ", \"" << arg << "\""; } + if(!arg2.empty()) + { + ss << ", \"" << arg2 << "\""; + } ss << "}"; return { ss.str() }; } @@ -388,13 +697,14 @@ std::vector<std::string> ld_option(ctor::toolchain toolchain, std::vector<std::string> ar_option(ctor::toolchain toolchain, ctor::ar_opt opt, - const std::string& arg) + const std::string& arg, + const std::string& arg2) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: - return gcc::ar_option(opt, arg); + return gcc::ar_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -403,6 +713,10 @@ std::vector<std::string> ar_option(ctor::toolchain toolchain, { ss << ", \"" << arg << "\""; } + if(!arg2.empty()) + { + ss << ", \"" << arg2 << "\""; + } ss << "}"; return { ss.str() }; } @@ -416,13 +730,14 @@ std::vector<std::string> ar_option(ctor::toolchain toolchain, std::vector<std::string> asm_option(ctor::toolchain toolchain, ctor::asm_opt opt, - const std::string& arg) + const std::string& arg, + const std::string& arg2) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: - return gcc::asm_option(opt, arg); + return gcc::asm_option(opt, arg, arg2); case ctor::toolchain::any: { std::ostringstream ss; @@ -431,6 +746,10 @@ std::vector<std::string> asm_option(ctor::toolchain toolchain, { ss << ", \"" << arg << "\""; } + if(!arg2.empty()) + { + ss << ", \"" << arg2 << "\""; + } ss << "}"; return { ss.str() }; } @@ -494,6 +813,7 @@ ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: + return gcc::ar_option(flag); case ctor::toolchain::any: case ctor::toolchain::none: break; @@ -524,7 +844,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { - return c_option(toolchain, flag.opt, flag.arg); + return c_option(toolchain, flag.opt, flag.arg, flag.arg2); } return {}; @@ -536,7 +856,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { - return cxx_option(toolchain, flag.opt, flag.arg); + return cxx_option(toolchain, flag.opt, flag.arg, flag.arg2); } return {}; @@ -548,7 +868,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { - return ld_option(toolchain, flag.opt, flag.arg); + return ld_option(toolchain, flag.opt, flag.arg, flag.arg2); } return {}; @@ -560,7 +880,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { - return ar_option(toolchain, flag.opt, flag.arg); + return ar_option(toolchain, flag.opt, flag.arg, flag.arg2); } return {}; @@ -572,7 +892,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { - return asm_option(toolchain, flag.opt, flag.arg); + return asm_option(toolchain, flag.opt, flag.arg, flag.arg2); } return {}; @@ -628,3 +948,176 @@ ctor::flag<ctor::asm_opt>::flag(const char* str) { *this = asm_option(str, guess_toolchain(str)); } + + +ctor::target_type target_type_from_extension(ctor::toolchain toolchain, + const std::filesystem::path& file) +{ + auto ext = to_lower(file.extension().string()); + // Loosely based on: + // https://en.wikipedia.org/wiki/List_of_file_formats#Object_code,_executable_files,_shared_and_dynamically_linked_libraries + if(toolchain == ctor::toolchain::any || + toolchain == ctor::toolchain::gcc || + toolchain == ctor::toolchain::clang) + { + if(ext == ".a") + { + return ctor::target_type::static_library; + } + + if(ext == ".so" || + ext == ".dylib") + { + return ctor::target_type::dynamic_library; + } + + if(ext == ".o") + { + return ctor::target_type::object; + } + + if(ext == "" || + ext == ".bin" || + ext == ".run" || + ext == ".out") + { + return ctor::target_type::executable; + } + } + + if(toolchain == ctor::toolchain::any// || + //toolchain == ctor::toolchain::msvc || + //toolchain == ctor::toolchain::mingw || + ) + { + if(ext == ".lib") + { + return ctor::target_type::static_library; + } + + if(ext == ".dll") + { + return ctor::target_type::dynamic_library; + } + + if(ext == ".obj") + { + return ctor::target_type::object; + } + + if(ext == ".exe" || + ext == ".com") + { + return ctor::target_type::executable; + } + } + + return ctor::target_type::unknown; +} + + +std::filesystem::path extension(ctor::toolchain toolchain, + ctor::target_type target_type, + ctor::output_system system, + const std::filesystem::path& file) +{ + auto type = target_type_from_extension(toolchain, file); + if(type == target_type) + { + // File already has the correct extension + return file; + } + + const auto& c = ctor::get_configuration(); + ctor::arch arch{}; + switch(system) + { + case ctor::output_system::host: + arch = c.host_arch; + break; + case ctor::output_system::build: + arch = c.build_arch; + break; + } + + // This might be before configure - so detection is needed for boostrap + if(arch == ctor::arch::unknown) + { + arch = get_arch(system, get_arch(system)); + } + + std::string ext{file.extension().string()}; + switch(target_type) + { + case ctor::target_type::automatic: + break; + case ctor::target_type::executable: + case ctor::target_type::unit_test: + switch(arch) + { + case ctor::arch::unix: + case ctor::arch::apple: + ext = ""; + break; + case ctor::arch::windows: + ext = ".exe"; + break; + case ctor::arch::unknown: + break; + } + break; + case ctor::target_type::static_library: + case ctor::target_type::unit_test_library: + switch(arch) + { + case ctor::arch::unix: + case ctor::arch::apple: + ext = ".a"; + break; + case ctor::arch::windows: + ext = ".lib"; + break; + case ctor::arch::unknown: + break; + } + break; + case ctor::target_type::dynamic_library: + switch(arch) + { + case ctor::arch::unix: + ext = ".so"; + break; + case ctor::arch::apple: + ext = ".dylib"; + break; + case ctor::arch::windows: + ext = ".dll"; + break; + case ctor::arch::unknown: + break; + } + break; + case ctor::target_type::object: + switch(arch) + { + case ctor::arch::unix: + case ctor::arch::apple: + ext = ".o"; + break; + case ctor::arch::windows: + ext = ".obj"; + break; + case ctor::arch::unknown: + break; + } + break; + case ctor::target_type::function: + break; + case ctor::target_type::unknown: + break; + } + + auto output{file}; + output.replace_extension(ext); + return output; +} diff --git a/src/tools.h b/src/tools.h index b1285ec..0e7fc15 100644 --- a/src/tools.h +++ b/src/tools.h @@ -6,6 +6,7 @@ #include <vector> #include <string> #include <ostream> +#include <filesystem> #include "ctor.h" @@ -16,6 +17,8 @@ std::ostream& operator<<(std::ostream& stream, const ctor::ld_opt& opt); std::ostream& operator<<(std::ostream& stream, const ctor::ar_opt& opt); std::ostream& operator<<(std::ostream& stream, const ctor::asm_opt& opt); +std::string get_arch(ctor::output_system system); +ctor::arch get_arch(ctor::output_system system, const std::string& str); //! Get tool-chain type from compiler path string ctor::toolchain getToolChain(const std::string& compiler); @@ -29,31 +32,36 @@ ctor::toolchain getToolChain(ctor::output_system system); //! tool-chain std::vector<std::string> c_option(ctor::toolchain toolchain, ctor::c_opt option, - const std::string& arg = {}); + const std::string& arg = {}, + const std::string& arg2 = {}); //! Get tool argument(s) for specific option type matching the supplied //! tool-chain std::vector<std::string> cxx_option(ctor::toolchain toolchain, ctor::cxx_opt option, - const std::string& arg = {}); + const std::string& arg = {}, + const std::string& arg2 = {}); //! Get tool argument(s) for specific option type matching the supplied //! tool-chain std::vector<std::string> ld_option(ctor::toolchain toolchain, ctor::ld_opt option, - const std::string& arg = {}); + const std::string& arg = {}, + const std::string& arg2 = {}); //! Get tool argument(s) for specific option type matching the supplied //! tool-chain std::vector<std::string> ar_option(ctor::toolchain toolchain, ctor::ar_opt option, - const std::string& arg = {}); + const std::string& arg = {}, + const std::string& arg2 = {}); //! Get tool argument(s) for specific option type matching the supplied //! tool-chain std::vector<std::string> asm_option(ctor::toolchain toolchain, ctor::asm_opt option, - const std::string& arg = {}); + const std::string& arg = {}, + const std::string& arg2 = {}); @@ -94,3 +102,28 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain, const ctor::ar_flag& flag); std::vector<std::string> to_strings(ctor::toolchain toolchain, const ctor::asm_flag& flag); + + +// Get target type from file extension +// If toolchain is not ::any only extensions for that toolchain will be accepted +// If no match is found ::unknown will be returned. +ctor::target_type target_type_from_extension(ctor::toolchain toolchain, + const std::filesystem::path& file); + + +// Get appropriate extension from original extension and target type using +// the toolchain. +// ie. { gcc, static_lib, ".lib" } will return ".a" +// { msvc, dynamic_lib, ".dylib" } will return ".dll" +// ... +// If the supplied extension is normal for the supplied type and toolchain, then this is used, otherwise a conversion to the default extension to the given toolchain and type is given. +// The defaults for the toolchains are as follows: +// toolchain executable static-lib dynamic-lib +// gcc (none) .a .so(.dylib on macos) +// clang (none) .a .so(.dylib on macos) +// msvc .exe .lib .dll +// mingw .exe .lib .dll +std::filesystem::path extension(ctor::toolchain toolchain, + ctor::target_type target_type, + ctor::output_system system, + const std::filesystem::path& file); diff --git a/src/unittest.cc b/src/unittest.cc index 237b2e3..b95a931 100644 --- a/src/unittest.cc +++ b/src/unittest.cc @@ -8,7 +8,7 @@ #include "execute.h" #include "task.h" -int runUnitTests(std::set<std::shared_ptr<Task>>& tasks, +int runUnitTests(std::vector<std::shared_ptr<Task>>& tasks, const ctor::settings& settings) { bool ok{true}; @@ -24,7 +24,7 @@ int runUnitTests(std::set<std::shared_ptr<Task>>& tasks, name = task->target(); } std::cout << name << ": " << std::flush; - auto ret = execute(task->targetFile(), {}, settings.verbose > 0); + auto ret = execute(settings, task->targetFile().string(), {}, {}); ok &= ret == 0; if(ret == 0) { diff --git a/src/unittest.h b/src/unittest.h index 2880319..6d1385e 100644 --- a/src/unittest.h +++ b/src/unittest.h @@ -3,7 +3,7 @@ // See accompanying file LICENSE for details. #pragma once -#include <set> +#include <vector> #include <memory> class Task; @@ -12,5 +12,5 @@ namespace ctor { struct settings; } // namespace ctor:: -int runUnitTests(std::set<std::shared_ptr<Task>>& tasks, +int runUnitTests(std::vector<std::shared_ptr<Task>>& tasks, const ctor::settings& settings); diff --git a/src/util.cc b/src/util.cc index ee56ede..dbd4c3c 100644 --- a/src/util.cc +++ b/src/util.cc @@ -5,6 +5,14 @@ #include <iostream> #include <fstream> +#include <algorithm> + +std::string to_lower(const std::string& str) +{ + std::string out{str}; + std::transform(out.begin(), out.end(), out.begin(), ::tolower); + return out; +} std::string readFile(const std::string& fileName) { @@ -14,84 +22,32 @@ std::string readFile(const std::string& fileName) std::ifstream::pos_type fileSize = ifs.tellg(); ifs.seekg(0, std::ios::beg); - std::vector<char> bytes(fileSize); + std::vector<char> bytes(static_cast<std::size_t>(fileSize)); ifs.read(bytes.data(), fileSize); - return std::string(bytes.data(), fileSize); + return {bytes.data(), static_cast<std::size_t>(fileSize)}; } -std::vector<std::string> readDeps(const std::string& depFile) +ctor::language languageFromExtension(const std::filesystem::path& file) { - if(!std::filesystem::exists(depFile)) - { - return {}; - } - - auto str = readFile(depFile); + auto ext = file.extension().string(); - std::vector<std::string> output; - std::string tmp; - bool start{false}; - bool in_whitespace{false}; - for(const auto& c : str) + // First a few case sensitive comparisons + if(ext == ".c") { - 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; - } + return ctor::language::c; } - if(!tmp.empty()) + if(ext == ".C") { - output.push_back(tmp); + return ctor::language::cpp; } - return output; -} + // The rest are compared in lowercase + ext = to_lower(ext); -ctor::language languageFromExtension(const std::filesystem::path& file) -{ - auto ext = file.extension().string(); - if(ext == ".c") - { - return ctor::language::c; - } - - if(ext == ".C" || - ext == ".cc" || + if(ext == ".cc" || ext == ".cpp" || - ext == ".CPP" || ext == ".c++" || ext == ".cp" || ext == ".cxx") @@ -100,7 +56,6 @@ ctor::language languageFromExtension(const std::filesystem::path& file) } if(ext == ".s" || - ext == ".S" || ext == ".asm") { return ctor::language::assembler; @@ -135,3 +90,97 @@ std::string cleanUp(const std::string& path) } return cleaned; } + +std::string esc(const std::string& in) +{ + std::string out; + for(auto c : in) + { + switch(c) + { + case '\\': out += "\\\\"; break; + case '"': out += "\\\""; break; + default: + out += c; + break; + } + } + return out; +} + +std::vector<std::string> get_paths(const std::string& path_env) +{ + std::vector<std::string> paths; + +#ifdef _WIN32 + const char sep{';'}; +#else + const char sep{':'}; +#endif + + std::stringstream ss(path_env); + std::string path; + while (std::getline(ss, path, sep)) + { + paths.push_back(path); + } + + return paths; +} + +bool check_executable(const std::filesystem::path& prog) +{ + auto perms = std::filesystem::status(prog).permissions(); + + if((perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none) + { + return true; + } + + if((perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none) + { + return true; + } + + if((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) + { + return true; + } + + return false; +} + +std::string locate(const std::string& prog, + const std::vector<std::string>& paths, + const std::string& arch) +{ + std::string program = prog; + if(!arch.empty()) + { + program = arch + "-" + prog; + } + + // first check if arch contains an absolute path to prog + if(std::filesystem::exists(program)) + { + if(check_executable(program)) + { + return program; + } + } + + for(const auto& path_str : paths) + { + std::filesystem::path path(path_str); + auto prog_path = path / program; + if(std::filesystem::exists(prog_path)) + { + if(check_executable(prog_path)) + { + return prog_path.string(); + } + } + } + + return {}; +} @@ -7,9 +7,11 @@ #include <string> #include <filesystem> +#include <cstdlib> + +std::string to_lower(const std::string& str); std::string readFile(const std::string& fileName); -std::vector<std::string> readDeps(const std::string& depFile); ctor::language languageFromExtension(const std::filesystem::path& file); std::string cleanUp(const std::string& path); @@ -19,7 +21,10 @@ void append(T& a, const T& b) a.insert(a.end(), b.begin(), b.end()); } -//using cxx_flags = std::vector<ctor::cxx_flag>; -//using c_flags = std::vector<std::string>; -//using ld_flags= std::vector<std::string>; -//using asm_flags = std::vector<std::string>; +std::string esc(const std::string& in); + +std::vector<std::string> get_paths(const std::string& path_env = std::getenv("PATH")); + +std::string locate(const std::string& app, + const std::vector<std::string>& paths, + const std::string& arch = {}); diff --git a/test/ctor.cc b/test/ctor.cc index a2ad64d..31c63ab 100644 --- a/test/ctor.cc +++ b/test/ctor.cc @@ -11,12 +11,65 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) { { .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, + .target = "pointerlist_test", + .sources = { + "pointerlist_test.cc", + "testmain.cc", + "../src/pointerlist.cc", + }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"pointerlist\"", + }, + }, + }, + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, + .target = "deps_test", + .sources = { + "deps_test.cc", + "testmain.cc", + "../src/deps.cc", + "../src/util.cc", + }, + .depends = { "testprog", }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"deps\"", + }, + }, + }, + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, + .target = "testprog", + .sources = { + "testprog.cc", + }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-Wall", "-Werror", + }, + }, + }, + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, .target = "execute_test", .sources = { "execute_test.cc", "testmain.cc", "../src/execute.cc", + "../src/pointerlist.cc", + "../src/util.cc", }, + .depends = { "testprog", }, .flags = { .cxxflags = { "-std=c++20", "-O3", "-Wall", "-Werror", @@ -28,6 +81,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) }, { .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, .target = "tasks_test", .sources = { "tasks_test.cc", @@ -45,6 +99,25 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) }, { .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, + .target = "cycle_test", + .sources = { + "cycle_test.cc", + "testmain.cc", + }, + .depends = { "libctor_nomain.a" }, + .flags = { + .cxxflags = { + "-std=c++20", "-O3", "-Wall", "-Werror", + "-I../src", "-Iuunit", + "-DOUTPUT=\"cycle\"", + }, + .ldflags = { "-pthread" }, + }, + }, + { + .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, .target = "source_type_test", .sources = { "source_type_test.cc", @@ -62,10 +135,12 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) }, { .type = ctor::target_type::unit_test, + .system = ctor::output_system::build, .target = "tools_test", .sources = { "tools_test.cc", "testmain.cc", + "../src/util.cc", "../src/tools.cc", }, //.depends = { "libctor_nomain.a" }, @@ -79,11 +154,14 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings) }, { .type = ctor::target_type::unit_test_library, + .system = ctor::output_system::build, .target = "libctor_nomain.a", .sources = { "../src/build.cc", "../src/configure.cc", + "../src/deps.cc", "../src/execute.cc", + "../src/pointerlist.cc", "../src/rebuild.cc", "../src/tasks.cc", "../src/task.cc", diff --git a/test/cycle_test.cc b/test/cycle_test.cc new file mode 100644 index 0000000..3b45632 --- /dev/null +++ b/test/cycle_test.cc @@ -0,0 +1,87 @@ +#include <uunit.h> + +#include <ctor.h> +#include <build.h> + +namespace +{ +ctor::build_configurations ctorTestConfigsCyclic(const ctor::settings&) +{ + return + { + // No dependency + { + .target = "target0", + }, + + // Direct (self-depends) + { + .target = "target1", + .depends = { "target1" }, + }, + + // Indirect cyclic depends + { + .target = "target2", + .depends = { "target3" }, + }, + { + .target = "target3", + .depends = { "target2" }, + }, + }; +} +} + +REG(ctorTestConfigsCyclic); + +class CycleTest + : public uUnit +{ +public: + CycleTest() + { + uTEST(CycleTest::getTargets_test); + } + + void getTargets_test() + { + using namespace std::string_literals; + ctor::settings settings{}; + const auto& tasks = getTasks(settings); + uASSERT_EQUAL(4u, tasks.size()); + + uASSERT_EQUAL("target0"s, tasks[0]->target()); + uASSERT_EQUAL("target1"s, tasks[1]->target()); + uASSERT_EQUAL("target2"s, tasks[2]->target()); + uASSERT_EQUAL("target3"s, tasks[3]->target()); + + for(auto task : tasks) + { + if(task->registerDepTasks(tasks)) + { + uASSERT(false); + } + } + + bool exc; + exc = false; + try { getDepTasks(tasks[0]); } catch(...) { exc = true; } + uASSERT(!exc); + + exc = false; + try { getDepTasks(tasks[1]); } catch(...) { exc = true; } + uASSERT(exc); + + exc = false; + try { getDepTasks(tasks[2]); } catch(...) { exc = true; } + uASSERT(exc); + + exc = false; + try { getDepTasks(tasks[3]); } catch(...) { exc = true; } + uASSERT(exc); + } +}; + +// Registers the fixture into the 'registry' +static CycleTest test; diff --git a/test/deps_test.cc b/test/deps_test.cc new file mode 100644 index 0000000..762b0e5 --- /dev/null +++ b/test/deps_test.cc @@ -0,0 +1,97 @@ +#include <uunit.h> + +#include <fstream> +#include <map> +#include <vector> + +#include <deps.h> +#include <algorithm> + +#include "paths.h" +#include "tmpfile.h" + +class DepsTest + : public uUnit +{ +public: + DepsTest() + { + uTEST(DepsTest::parser_test); + } + + void parser_test() + { + using namespace std::string_literals; + + auto test_data = paths::top_srcdir / "test" / "deps_test_data"; + + auto empty = test_data / "empty.d"; + auto trivial = test_data / "trivial.d"; + auto no_newline = test_data / "no_newline.d"; + auto no_deps = test_data / "no_deps.d"; + auto trailing_whitespace = test_data / "trailing_whitespace.d"; + auto spaces = test_data / "spaces.d"; + auto multiline = test_data / "multiline.d"; + auto no_such_file = test_data / "no_such_file.d"; // doesn't exist + auto missing_colon = test_data / "missing_colon.d"; + + { + auto res = readDeps(empty.string(), ctor::toolchain::gcc); + uASSERT(res.empty()); + } + + { + auto res = readDeps(trivial.string(), ctor::toolchain::gcc); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("x.cc"s, res[0]); + } + + { + auto res = readDeps(no_newline.string(), ctor::toolchain::gcc); + uASSERT_EQUAL(1u, res.size()); + uASSERT_EQUAL("x.cc"s, res[0]); + } + + { + auto res = readDeps(no_deps.string(), ctor::toolchain::gcc); + uASSERT_EQUAL(0u, res.size()); + } + + { + auto res = readDeps(spaces.string(), ctor::toolchain::gcc); + uASSERT_EQUAL(2u, res.size()); + uASSERT_EQUAL("x y.cc"s, res[0]); + uASSERT_EQUAL("x y.h"s, res[1]); + } + + { + auto res = readDeps(multiline.string(), ctor::toolchain::gcc); + uASSERT_EQUAL(12u, res.size()); + uASSERT_EQUAL("src/configure.cc"s, res[0]); + uASSERT_EQUAL("src/configure.h"s, res[1]); + uASSERT_EQUAL("src/getoptpp/getoptpp.hpp"s, res[2]); + uASSERT_EQUAL("src/execute.h"s, res[3]); + uASSERT_EQUAL("src/ctor.h"s, res[4]); + uASSERT_EQUAL("src/tasks.h"s, res[5]); + uASSERT_EQUAL("src/task.h"s, res[6]); + uASSERT_EQUAL("src/rebuild.h"s, res[7]); + uASSERT_EQUAL("src/externals.h"s, res[8]); + uASSERT_EQUAL("src/externals_manual.h"s, res[9]); + uASSERT_EQUAL("src/tools.h"s, res[10]); + uASSERT_EQUAL("src/util.h"s, res[11]); + } + + { + auto res = readDeps(no_such_file.string(), ctor::toolchain::gcc); + uASSERT(res.empty()); + } + + { + auto res = readDeps(missing_colon.string(), ctor::toolchain::gcc); + uASSERT(res.empty()); + } + } +}; + +// Registers the fixture into the 'registry' +static DepsTest test; diff --git a/test/deps_test_data/empty.d b/test/deps_test_data/empty.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/deps_test_data/empty.d diff --git a/test/deps_test_data/missing_colon.d b/test/deps_test_data/missing_colon.d new file mode 100644 index 0000000..e46996c --- /dev/null +++ b/test/deps_test_data/missing_colon.d @@ -0,0 +1 @@ +x.cc x.h diff --git a/test/deps_test_data/multiline.d b/test/deps_test_data/multiline.d new file mode 100644 index 0000000..8862ab0 --- /dev/null +++ b/test/deps_test_data/multiline.d @@ -0,0 +1,4 @@ +build/src/libctor_a-configure_cc.o: src/configure.cc src/configure.h \ + src/getoptpp/getoptpp.hpp src/execute.h src/ctor.h src/tasks.h \ + src/task.h src/rebuild.h src/externals.h src/externals_manual.h \ + src/tools.h src/util.h diff --git a/test/deps_test_data/no_deps.d b/test/deps_test_data/no_deps.d new file mode 100644 index 0000000..e7cccf6 --- /dev/null +++ b/test/deps_test_data/no_deps.d @@ -0,0 +1 @@ +x.o: diff --git a/test/deps_test_data/no_newline.d b/test/deps_test_data/no_newline.d new file mode 100644 index 0000000..88829ea --- /dev/null +++ b/test/deps_test_data/no_newline.d @@ -0,0 +1 @@ +x.o: x.cc
\ No newline at end of file diff --git a/test/deps_test_data/spaces.d b/test/deps_test_data/spaces.d new file mode 100644 index 0000000..c53fd64 --- /dev/null +++ b/test/deps_test_data/spaces.d @@ -0,0 +1 @@ +x\ y.o: x\ y.cc x\ y.h diff --git a/test/deps_test_data/trivial.d b/test/deps_test_data/trivial.d new file mode 100644 index 0000000..15a0c29 --- /dev/null +++ b/test/deps_test_data/trivial.d @@ -0,0 +1 @@ +x.o: x.cc diff --git a/test/execute_test.cc b/test/execute_test.cc index 9da18dc..722b6ea 100644 --- a/test/execute_test.cc +++ b/test/execute_test.cc @@ -1,6 +1,15 @@ #include <uunit.h> +#include <fstream> +#include <map> +#include <vector> + #include <execute.h> +#include <util.h> +#include <algorithm> + +#include "paths.h" +#include "tmpfile.h" class ExecuteTest : public uUnit @@ -8,14 +17,78 @@ class ExecuteTest public: ExecuteTest() { - uTEST(ExecuteTest::runit); + uTEST(ExecuteTest::return_value); + uTEST(ExecuteTest::env); + } + + void return_value() + { + ctor::settings s; + auto cur_path = std::filesystem::path(paths::argv_0).parent_path(); + std::vector<std::string> paths{{cur_path.string()}}; + auto cmd = locate("testprog", paths); + uASSERT(!cmd.empty()); + + auto value = execute(s, cmd, {"retval", "0"}, {}, false); + uASSERT_EQUAL(0, value); + value = execute(s, cmd, {"retval", "1"}, {}, false); + uASSERT_EQUAL(1, value); + value = execute(s, "no-such-binary", {}, {}, false); + uASSERT_EQUAL(1, value); + value = execute(s, cmd, {"segfault"}, {}, false); + uASSERT_EQUAL(11, value); + value = execute(s, cmd, {"throw"}, {}, false); + uASSERT_EQUAL(6, value); + value = execute(s, cmd, {"abort"}, {}, false); + uASSERT_EQUAL(6, value); } - void runit() + void env() { - uASSERT_EQUAL(0, execute("/bin/true", {}, false)); - uASSERT_EQUAL(1, execute("/bin/false", {}, false)); - uASSERT_EQUAL(1, execute("no-such-binary", {}, false)); + using namespace std::string_literals; + + ctor::settings s; + auto cur_path = std::filesystem::path(paths::argv_0).parent_path(); + std::vector<std::string> paths{{cur_path.string()}}; + auto cmd = locate("testprog", paths); + uASSERT(!cmd.empty()); + + tmp_file tmp; + + std::map<std::string, std::string> env; + + // New env vars + env["foo"] = "bar"; + env["bar"] = "42"; + + // Overwrite the exiting LANG var + env["LANG"] = "foo"; + + auto value = execute(s, cmd, {"envdump", tmp.get()}, env, false); + uASSERT_EQUAL(0, value); + + std::vector<std::string> vars; + { + std::ifstream infile(tmp.get()); + std::string line; + while (std::getline(infile, line)) + { + vars.push_back(line); + } + } + + // Check the two explicitly set vars + auto chk = std::find(vars.begin(), vars.end(), "foo=bar"s); + uASSERT(chk != vars.end()); + chk = std::find(vars.begin(), vars.end(), "bar=42"s); + uASSERT(chk != vars.end()); + + // Check the one that should have overwritten the existing one (probably LANG=en_US.UTF-8 or something) + chk = std::find(vars.begin(), vars.end(), "LANG=foo"s); + uASSERT(chk != vars.end()); + + // Check that other vars are also there (ie. the env wasn't cleared on entry) + uASSERT(vars.size() > 3); } }; diff --git a/test/pointerlist_test.cc b/test/pointerlist_test.cc new file mode 100644 index 0000000..4274473 --- /dev/null +++ b/test/pointerlist_test.cc @@ -0,0 +1,320 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <uunit.h> + +#include <set> +#include <algorithm> + +#include "pointerlist.h" + +class PointerListTest + : public uUnit +{ +public: + PointerListTest() + { + uTEST(PointerListTest::test_zom_pointerlist_push); + uTEST(PointerListTest::test_zom_pointerlist_from_args); + uTEST(PointerListTest::test_zom_envmap_insert); + uTEST(PointerListTest::test_zom_envmap_from_env); + uTEST(PointerListTest::test_exceptional_env); + } + + void test_zom_pointerlist_push() + { + using namespace std::string_literals; + + { // Zero + PointerList args; + uASSERT_EQUAL(0u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // One + PointerList args; + args.push_back("hello"); + uASSERT_EQUAL(1u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello"s, std::string(argv[0])); + } + + { // Many + PointerList args; + args.push_back("hello"); + args.push_back("dear"); + args.push_back("world"); + uASSERT_EQUAL(3u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(3, argc); + uASSERT_EQUAL("hello"s, std::string(argv[0])); + uASSERT_EQUAL("dear"s, std::string(argv[1])); + uASSERT_EQUAL("world"s, std::string(argv[2])); + } + } + + void test_zom_pointerlist_from_args() + { + using namespace std::string_literals; + + { // Zero + PointerList args(0, nullptr); + uASSERT_EQUAL(0u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // One + int _argc{1}; + const char* _argv[] = { "hello" }; + PointerList args(_argc, _argv); + uASSERT_EQUAL(1u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello"s, std::string(argv[0])); + } + + { // Many + int _argc{3}; + const char* _argv[] = { "hello", "dear", "world" }; + PointerList args(_argc, _argv); + uASSERT_EQUAL(3u, args.size()); + auto [argc, argv] = args.get(); + uASSERT_EQUAL(3, argc); + // order must be preserved + uASSERT_EQUAL("hello"s, std::string(argv[0])); + uASSERT_EQUAL("dear"s, std::string(argv[1])); + uASSERT_EQUAL("world"s, std::string(argv[2])); + } + } + + void test_zom_envmap_insert() + { + using namespace std::string_literals; + + { // Zero + EnvMap env; + uASSERT_EQUAL(0u, env.size()); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + + auto str = env.stringify(); + uASSERT_EQUAL(1u, str.size()); + uASSERT_EQUAL('\0', str[0]); + } + + { // One (key only) + EnvMap env; + env.insert("hello"); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello="s, std::string(argv[0])); + uASSERT_EQUAL(""s, env["hello"]); + } + { // One (with value) + EnvMap env; + env.insert("hello=world"); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello=world"s, std::string(argv[0])); + uASSERT_EQUAL("world"s, env["hello"]); + + uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); + } + + { // Overwrite one + EnvMap env; + env.insert("hello=world"); + uASSERT_EQUAL(1u, env.size()); + uASSERT_EQUAL("world"s, env["hello"s]); + + env.insert("hello=foo"); + uASSERT_EQUAL(1u, env.size()); + uASSERT_EQUAL("foo"s, env["hello"s]); + } + + { // Many + EnvMap env; + env.insert("hello=world"); + env.insert("world=leader"); + env.insert("dear=boar"); + uASSERT_EQUAL(3u, env.size()); + + uASSERT_EQUAL("boar"s, env["dear"s]); + uASSERT_EQUAL("world"s, env["hello"s]); + uASSERT_EQUAL("leader"s, env["world"s]); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(3, argc); + // store and sort to verify unordered + std::vector<std::string> vals{argv[0], argv[1], argv[2]}; + std::ranges::sort(vals); + uASSERT_EQUAL("dear=boar"s, vals[0]); + uASSERT_EQUAL("hello=world"s, vals[1]); + uASSERT_EQUAL("world=leader"s, vals[2]); + + // test all combinations since ordering is not a requirement + // exactly one of the must be true (boolean XOR) + auto str = env.stringify(); + uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != + ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != + ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != + ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != + ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != + ("world=leader\0hello=world\0dear=boar\0\0"s == str)); + } + } + + void test_zom_envmap_from_env() + { + using namespace std::string_literals; + + { // Zero + const char* penv = nullptr; + EnvMap env(penv); + uASSERT_EQUAL(0u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // Zero + const char** ppenv = nullptr; + EnvMap env(ppenv); + uASSERT_EQUAL(0u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(0, argc); + uASSERT(nullptr != argv); + uASSERT_EQUAL(nullptr, argv[0]); + } + + { // One (key only) + const char* ptr = "hello\0\0"; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello="s, std::string(argv[0])); + uASSERT_EQUAL(""s, env["hello"]); + } + + { // One (key only) + const char* ptr[] = {"hello", nullptr}; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello="s, std::string(argv[0])); + uASSERT_EQUAL(""s, env["hello"]); + } + + { // One (with value) + const char* ptr = "hello=world\0\0"; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello=world"s, std::string(argv[0])); + uASSERT_EQUAL("world"s, env["hello"]); + + uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); + } + + { // One (with value) + const char* ptr[] = {"hello=world\0", nullptr}; + EnvMap env(ptr); + uASSERT_EQUAL(1u, env.size()); + auto [argc, argv] = env.get(); + uASSERT_EQUAL(1, argc); + uASSERT_EQUAL("hello=world"s, std::string(argv[0])); + uASSERT_EQUAL("world"s, env["hello"]); + + uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); + } + + { // Many + const char* ptr = "hello=world\0world=leader\0dear=boar\0\0"; + EnvMap env(ptr); + uASSERT_EQUAL(3u, env.size()); + + uASSERT_EQUAL("boar"s, env["dear"s]); + uASSERT_EQUAL("world"s, env["hello"s]); + uASSERT_EQUAL("leader"s, env["world"s]); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(3, argc); + // store and sort to verify unordered + std::vector<std::string> vals{argv[0], argv[1], argv[2]}; + std::ranges::sort(vals); + uASSERT_EQUAL("dear=boar"s, vals[0]); + uASSERT_EQUAL("hello=world"s, vals[1]); + uASSERT_EQUAL("world=leader"s, vals[2]); + + // test all combinations since ordering is not a requirement + // exactly one of the must be true (boolean XOR) + auto str = env.stringify(); + uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != + ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != + ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != + ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != + ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != + ("world=leader\0hello=world\0dear=boar\0\0"s == str)); + } + + { // Many + const char* ptr[] = + {"hello=world\0", "world=leader\0", "dear=boar\0", nullptr}; + EnvMap env(ptr); + uASSERT_EQUAL(3u, env.size()); + + uASSERT_EQUAL("boar"s, env["dear"s]); + uASSERT_EQUAL("world"s, env["hello"s]); + uASSERT_EQUAL("leader"s, env["world"s]); + + auto [argc, argv] = env.get(); + uASSERT_EQUAL(3, argc); + // store and sort to verify unordered + std::vector<std::string> vals{argv[0], argv[1], argv[2]}; + std::ranges::sort(vals); + uASSERT_EQUAL("dear=boar"s, vals[0]); + uASSERT_EQUAL("hello=world"s, vals[1]); + uASSERT_EQUAL("world=leader"s, vals[2]); + + // test all combinations since ordering is not a requirement + // exactly one of the must be true (boolean XOR) + auto str = env.stringify(); + uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != + ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != + ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != + ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != + ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != + ("world=leader\0hello=world\0dear=boar\0\0"s == str)); + } + } + + void test_exceptional_env() + { + using namespace std::string_literals; + + { // Zero + EnvMap env; + uASSERT_EQUAL(0u, env.size()); + uASSERT_EQUAL(""s, env["foo"]); // lookup of non-existing key + } + } +}; + +// Registers the fixture into the 'registry' +static PointerListTest test; diff --git a/test/tasks_test.cc b/test/tasks_test.cc index 5f1db26..b444bd5 100644 --- a/test/tasks_test.cc +++ b/test/tasks_test.cc @@ -10,6 +10,7 @@ ctor::build_configurations ctorTestConfigs1(const ctor::settings&) return { { + .name = "Target1", .target = "target1", .sources = {"foo.cc", "bar.c"}, }, @@ -33,16 +34,29 @@ ctor::build_configurations ctorTestConfigs2(const ctor::settings&) } } +namespace test_global { +ctor::toolchain toolchain{}; +ctor::arch arch{}; +} +const ctor::configuration& ctor::get_configuration() +{ + static ctor::configuration cfg{}; + cfg.build_toolchain = test_global::toolchain; + cfg.build_arch = test_global::arch; + return cfg; +} + + REG(ctorTestConfigs1); REG(ctorTestConfigs2); -std::size_t count(const std::set<std::shared_ptr<Task>>& tasks, - const std::string& name) +std::size_t count(const std::vector<std::shared_ptr<Task>>& tasks, + const std::filesystem::path& name) { auto cnt{0u}; for(const auto& task : tasks) { - if(task->target() == name) + if(task->target() == name.string()) { cnt++; } @@ -54,16 +68,17 @@ class TestTask : public Task { public: - TestTask(const std::string& name, bool dirty, + TestTask(const ctor::build_configuration& config, + const ctor::settings& settings, + const std::string& name, bool dirty, const std::vector<std::string>& deps = {}) - : Task({}, {}, {}) + : Task(config, settings, {}) , task_name(name) , task_dirty(dirty) , task_deps(deps) { } - std::string name() const override { return task_name; } int clean() override { return 0; } std::vector<std::string> depends() const override { return task_deps; } std::string target() const override { return task_name; } @@ -81,11 +96,14 @@ class TasksTest : public uUnit { public: + using fs = std::filesystem::path; + TasksTest() { uTEST(TasksTest::getTargets_test); uTEST(TasksTest::getTasks_test); uTEST(TasksTest::getNextTask_test); + uTEST(TasksTest::comparison_test); } void getTargets_test() @@ -108,27 +126,29 @@ public: void getTasks_test() { + test_global::toolchain = ctor::toolchain::gcc; + test_global::arch = ctor::arch::unix; + using namespace std::string_literals; ctor::settings settings{ .builddir = "foo" }; { auto tasks = getTasks(settings); uASSERT_EQUAL(6u, tasks.size()); - // Note: count() is used here because the order of - // std::set<std::shared_ptr<T>> is not deterministic. - uASSERT_EQUAL(1u, count(tasks, "target1"s)); - uASSERT_EQUAL(1u, count(tasks, "target2"s)); - uASSERT_EQUAL(1u, count(tasks, "target3"s)); - uASSERT_EQUAL(1u, count(tasks, "target4"s)); - uASSERT_EQUAL(1u, count(tasks, "test/target1-foo_cc.o"s)); - uASSERT_EQUAL(1u, count(tasks, "test/target1-bar_c.o"s)); + // Note: count() is used here because the order doesn't matter + uASSERT_EQUAL(1u, count(tasks, fs("target1"))); + uASSERT_EQUAL(1u, count(tasks, fs("target2"))); + uASSERT_EQUAL(1u, count(tasks, fs("target3"))); + uASSERT_EQUAL(1u, count(tasks, fs("target4"))); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o")); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o")); } { auto tasks = getTasks(settings, {"target1", "target3"}); uASSERT_EQUAL(4u, tasks.size()); - uASSERT_EQUAL(1u, count(tasks, "target1"s)); - uASSERT_EQUAL(1u, count(tasks, "target3"s)); - uASSERT_EQUAL(1u, count(tasks, "test/target1-foo_cc.o"s)); - uASSERT_EQUAL(1u, count(tasks, "test/target1-bar_c.o"s)); + uASSERT_EQUAL(1u, count(tasks, fs("target1"))); + uASSERT_EQUAL(1u, count(tasks, fs("target3"))); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o")); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o")); } { auto tasks = getTasks(settings, {"no-such-target"}); @@ -142,8 +162,8 @@ public: ctor::settings settings{}; { // Zero (Empty) - std::set<std::shared_ptr<Task>> allTasks; - std::set<std::shared_ptr<Task>> dirtyTasks; + std::vector<std::shared_ptr<Task>> allTasks; + std::vector<std::shared_ptr<Task>> dirtyTasks; for(auto& task : dirtyTasks) { @@ -154,12 +174,13 @@ public: } { // Zero (One task, no dirty) - auto task1 = std::make_shared<TestTask>("task1", false); + ctor::build_configuration config; + auto task1 = std::make_shared<TestTask>(config, settings, "task1", false); - std::set<std::shared_ptr<Task>> allTasks; - allTasks.insert(task1); + std::vector<std::shared_ptr<Task>> allTasks; + allTasks.push_back(task1); - std::set<std::shared_ptr<Task>> dirtyTasks; + std::vector<std::shared_ptr<Task>> dirtyTasks; for(auto& task : dirtyTasks) { @@ -170,13 +191,14 @@ public: } { // One (One task, one dirty) - auto task1 = std::make_shared<TestTask>("task1", true); + ctor::build_configuration config; + auto task1 = std::make_shared<TestTask>(config, settings, "task1", true); - std::set<std::shared_ptr<Task>> allTasks; - allTasks.insert(task1); + std::vector<std::shared_ptr<Task>> allTasks; + allTasks.push_back(task1); - std::set<std::shared_ptr<Task>> dirtyTasks; - dirtyTasks.insert(task1); + std::vector<std::shared_ptr<Task>> dirtyTasks; + dirtyTasks.push_back(task1); for(auto& task : dirtyTasks) { @@ -188,15 +210,16 @@ public: } { // One (Two tasks, one dirty) - auto task1 = std::make_shared<TestTask>("task1", false); - auto task2 = std::make_shared<TestTask>("task2", true); + ctor::build_configuration config; + auto task1 = std::make_shared<TestTask>(config, settings, "task1", false); + auto task2 = std::make_shared<TestTask>(config, settings, "task2", true); - std::set<std::shared_ptr<Task>> allTasks; - allTasks.insert(task1); - allTasks.insert(task2); + std::vector<std::shared_ptr<Task>> allTasks; + allTasks.push_back(task1); + allTasks.push_back(task2); - std::set<std::shared_ptr<Task>> dirtyTasks; - dirtyTasks.insert(task2); + std::vector<std::shared_ptr<Task>> dirtyTasks; + dirtyTasks.push_back(task2); for(auto& task : dirtyTasks) { @@ -208,17 +231,19 @@ public: } { // One (Two tasks, one dirty which depends on the other) - auto task1 = std::make_shared<TestTask>("task1", false); + ctor::build_configuration config; + auto task1 = std::make_shared<TestTask>(config, settings, "task1", false); std::vector<std::string> deps = {"task1"}; - auto task2 = std::make_shared<TestTask>("task2", true, deps); + auto task2 = + std::make_shared<TestTask>(config, settings, "task2", true, deps); - std::set<std::shared_ptr<Task>> allTasks; - allTasks.insert(task1); - allTasks.insert(task2); + std::vector<std::shared_ptr<Task>> allTasks; + allTasks.push_back(task1); + allTasks.push_back(task2); - std::set<std::shared_ptr<Task>> dirtyTasks; - dirtyTasks.insert(task2); + std::vector<std::shared_ptr<Task>> dirtyTasks; + dirtyTasks.push_back(task2); for(auto& task : dirtyTasks) { @@ -230,18 +255,20 @@ public: } { // One (Two tasks, Both dirty, one depends on the other) - auto task1 = std::make_shared<TestTask>("task1", true); + ctor::build_configuration config{}; + auto task1 = std::make_shared<TestTask>(config, settings, "task1", true); std::vector<std::string> deps = {"task1"}; - auto task2 = std::make_shared<TestTask>("task2", true, deps); + auto task2 = + std::make_shared<TestTask>(config, settings, "task2", true, deps); - std::set<std::shared_ptr<Task>> allTasks; - allTasks.insert(task2); - allTasks.insert(task1); + std::vector<std::shared_ptr<Task>> allTasks; + allTasks.push_back(task2); + allTasks.push_back(task1); - std::set<std::shared_ptr<Task>> dirtyTasks; - dirtyTasks.insert(task2); - dirtyTasks.insert(task1); + std::vector<std::shared_ptr<Task>> dirtyTasks; + dirtyTasks.push_back(task2); + dirtyTasks.push_back(task1); for(auto& task : dirtyTasks) { @@ -251,7 +278,51 @@ public: uASSERT_EQUAL(task1, getNextTask(allTasks, dirtyTasks)); uASSERT_EQUAL(1u, dirtyTasks.size()); } + } + void comparison_test() + { + test_global::toolchain = ctor::toolchain::gcc; + test_global::arch = ctor::arch::unix; + + using namespace std::string_literals; + ctor::settings settings{ .builddir = "foo" }; + { // Test Task::operator== + auto tasks = getTasks(settings, {"target1"}); + uASSERT_EQUAL(3u, tasks.size()); + uASSERT_EQUAL(1u, count(tasks, fs("target1"))); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o")); + uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o")); + + int cnt1{}; + int cnt2{}; + int cnt3{}; + for(const auto& task : tasks) + { + if(task->target() == fs("target1")) + { + ++cnt1; + uASSERT(*task == "target1"); + uASSERT(*task == "Target1"); + } + if(task->target() == fs("test")/"target1-foo_cc.o") + { + ++cnt2; + uASSERT(*task != "target1"); + uASSERT(*task != "Target1"); + } + if(task->target() == fs("test")/"target1-bar_c.o") + { + ++cnt3; + uASSERT(*task != "target1"); + uASSERT(*task != "Target1"); + } + } + // Assert that we did actually perform all three tests exactly once + uASSERT_EQUAL(1, cnt1); + uASSERT_EQUAL(1, cnt2); + uASSERT_EQUAL(1, cnt3); + } } }; diff --git a/test/testprog.cc b/test/testprog.cc new file mode 100644 index 0000000..faf1498 --- /dev/null +++ b/test/testprog.cc @@ -0,0 +1,53 @@ +#include <iostream> +#include <fstream> +#include <string> +#include <signal.h> + +int main(int argc, const char* argv[]) +{ + if(argc < 2) + { + return 0; + } + + std::string cmd = argv[1]; + + if(cmd == "envdump") + { + if(argc < 3) + { + return 0; + } + std::ofstream ostrm(argv[2], std::ios::binary); + for(auto current = environ; *current; ++current) + { + ostrm << (*current) << "\n"; + } + } + + if(cmd == "retval") + { + if(argc < 3) + { + return 0; + } + return std::stoi(argv[2]); + } + + if(cmd == "abort") + { + abort(); + } + + if(cmd == "segfault") + { + raise(SIGSEGV); + } + + if(cmd == "throw") + { + throw "ouch"; + } + + return 0; +} diff --git a/test/tmpfile.h b/test/tmpfile.h new file mode 100644 index 0000000..5d114d0 --- /dev/null +++ b/test/tmpfile.h @@ -0,0 +1,48 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <cstdlib> +#include <unistd.h> + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#endif + +struct tmp_file +{ + tmp_file(const std::string& data = {}) + { + int fd; +#ifdef _WIN32 + char templ[] = "ctor_tmp_file-XXXXXX"; // buffer for filename + _mktemp_s(templ, sizeof(templ)); + fd = open(templ, O_CREAT | O_RDWR); +#else + char templ[] = "/tmp/ctor_tmp_file-XXXXXX"; // buffer for filename + fd = mkstemp(templ); +#endif + filename = templ; + auto sz = write(fd, data.data(), data.size()); + (void)sz; + close(fd); + } + + ~tmp_file() + { + unlink(filename.data()); + } + + const std::string& get() const + { + return filename; + } + +private: + std::string filename; +}; diff --git a/test/tools_test.cc b/test/tools_test.cc index 1d377b7..46ab084 100644 --- a/test/tools_test.cc +++ b/test/tools_test.cc @@ -114,33 +114,27 @@ bool operator!=(const ctor::asm_flag& a, const ctor::asm_flag& b) #include <uunit.h> -namespace { -std::string conf_host_cxx{}; -std::string conf_build_cxx{}; -} - const ctor::configuration& ctor::get_configuration() { static ctor::configuration cfg; return cfg; } -const std::string& ctor::configuration::get(const std::string& key, const std::string& defval) const +std::string ctor::configuration::get(const std::string& key, [[maybe_unused]]const std::string& default_value) const { if(key == ctor::cfg::host_cxx) { - return conf_host_cxx; + return {}; } if(key == ctor::cfg::build_cxx) { - return conf_build_cxx; + return {}; } assert(false); // bad key - static std::string res{}; - return res; + return {}; } class ToolsTest @@ -172,9 +166,24 @@ public: void getToolChain_test() { + // + // gcc + // + uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/gcc")); + uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/gcc-10")); + uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/x86_64-pc-linux-gnu-g++-9.3.0")); + uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/g++")); uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/g++-10")); uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/x86_64-pc-linux-gnu-g++-9.3.0")); + + // + // clang + // + uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/bin/clang")); + uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/bin/clang-16")); + uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/lib/llvm/16/bin/i686-pc-linux-gnu-clang-16")); + uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/bin/clang++")); uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/bin/clang++-16")); uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/lib/llvm/16/bin/i686-pc-linux-gnu-clang++-16")); @@ -666,22 +675,54 @@ public: // // gcc // + exp = { "-r" }; + act = ar_option(ctor::toolchain::gcc, ctor::ar_opt::replace); + uASSERT_EQUAL(exp, act); + + exp = { "-s" }; + act = ar_option(ctor::toolchain::gcc, ctor::ar_opt::add_index); + uASSERT_EQUAL(exp, act); + + exp = { "-c" }; + act = ar_option(ctor::toolchain::gcc, ctor::ar_opt::create); + uASSERT_EQUAL(exp, act); + + exp = { "foo" }; + act = ar_option(ctor::toolchain::gcc, ctor::ar_opt::output, "foo"); + uASSERT_EQUAL(exp, act); + exp = { "-foo" }; - act = asm_option(ctor::toolchain::gcc, ctor::asm_opt::custom, "-foo"); + act = ar_option(ctor::toolchain::gcc, ctor::ar_opt::custom, "-foo"); uASSERT_EQUAL(exp, act); // // clang // + exp = { "-r" }; + act = ar_option(ctor::toolchain::clang, ctor::ar_opt::replace); + uASSERT_EQUAL(exp, act); + + exp = { "-s" }; + act = ar_option(ctor::toolchain::clang, ctor::ar_opt::add_index); + uASSERT_EQUAL(exp, act); + + exp = { "-c" }; + act = ar_option(ctor::toolchain::clang, ctor::ar_opt::create); + uASSERT_EQUAL(exp, act); + + exp = { "foo" }; + act = ar_option(ctor::toolchain::clang, ctor::ar_opt::output, "foo"); + uASSERT_EQUAL(exp, act); + exp = { "-foo" }; - act = asm_option(ctor::toolchain::clang, ctor::asm_opt::custom, "-foo"); + act = ar_option(ctor::toolchain::clang, ctor::ar_opt::custom, "-foo"); uASSERT_EQUAL(exp, act); // // any // - exp = { "{ctor::asm_opt::custom, \"-foo\"}" }; - act = asm_option(ctor::toolchain::any, ctor::asm_opt::custom, "-foo"); + exp = { "{ctor::ar_opt::custom, \"-foo\"}" }; + act = ar_option(ctor::toolchain::any, ctor::ar_opt::custom, "-foo"); uASSERT_EQUAL(exp, act); } diff --git a/test/uunit b/test/uunit -Subproject bc078da645412c6b36ef59e635d6c35d11088c9 +Subproject 0f371777e02dd068f9675a05a29230221d5d6a7 |