summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/build.cc173
-rw-r--r--src/build.h18
-rw-r--r--src/configure.cc301
-rw-r--r--src/configure.h19
-rw-r--r--src/execute.cc87
-rw-r--r--src/execute.h9
-rw-r--r--src/libcppbuild.cc316
-rw-r--r--src/libcppbuild.h76
-rw-r--r--src/rebuild.cc141
-rw-r--r--src/rebuild.h24
-rw-r--r--src/settings.h11
-rw-r--r--src/task.cc146
-rw-r--r--src/task.h60
-rw-r--r--src/task_ar.cc221
-rw-r--r--src/task_ar.h42
-rw-r--r--src/task_cc.cc339
-rw-r--r--src/task_cc.h47
-rw-r--r--src/task_ld.cc227
-rw-r--r--src/task_ld.h42
-rw-r--r--src/task_so.cc217
-rw-r--r--src/task_so.h42
-rw-r--r--src/tasks.cc130
-rw-r--r--src/tasks.h18
23 files changed, 2706 insertions, 0 deletions
diff --git a/src/build.cc b/src/build.cc
new file mode 100644
index 0000000..445979e
--- /dev/null
+++ b/src/build.cc
@@ -0,0 +1,173 @@
+#include "build.h"
+
+#include <future>
+#include <vector>
+#include <iostream>
+#include <chrono>
+#include <set>
+#include <thread>
+
+#include "tasks.h"
+
+using namespace std::chrono_literals;
+
+int build(const Settings& settings,
+ const std::string& name,
+ const std::list<std::shared_ptr<Task>>& tasks,
+ const std::list<std::shared_ptr<Task>>& all_tasks)
+{
+ if(settings.verbose > 1)
+ {
+ std::cout << "Building '" << name << "'\n";
+ }
+
+ std::list<std::shared_ptr<Task>> dirtyTasks;
+ for(auto task : tasks)
+ {
+ if(task->dirty())
+ {
+ dirtyTasks.push_back(task);
+ }
+ }
+
+ if(dirtyTasks.empty())
+ {
+ std::cout << "Nothing to be done for '"<< name << "'\n";
+ return 0;
+ }
+
+ std::list<std::future<int>> processes;
+
+ // Start all tasks
+ bool done{false};
+ while(!done)
+ {
+ bool started_one{false};
+ while(processes.size() < settings.parallel_processes)
+ {
+ if(dirtyTasks.empty())
+ {
+ done = true;
+ break;
+ }
+
+ auto task = getNextTask(all_tasks, dirtyTasks);
+ if(task == nullptr)
+ {
+ if(processes.empty() && !dirtyTasks.empty())
+ {
+ // No running processes, yet no process to run. This is a dead-lock...
+ std::cout << "Dead-lock detected.\n";
+ return 1;
+ }
+ break;
+ }
+
+ processes.emplace_back(
+ std::async(std::launch::async,
+ [task]()
+ {
+ return task->run();
+ }));
+ started_one = true;
+ std::this_thread::sleep_for(2ms);
+ }
+
+ for(auto process = processes.begin();
+ process != processes.end();
+ ++process)
+ {
+ if(process->valid())
+ {
+ if(process->get() != 0)
+ {
+ // TODO: Wait for other processes to finish before returning
+ return 1;
+ }
+ processes.erase(process);
+ break;
+ }
+ }
+
+ if(started_one)
+ {
+ std::this_thread::sleep_for(2ms);
+ }
+ else
+ {
+ std::this_thread::sleep_for(200ms);
+ }
+ }
+
+ for(auto process = processes.begin();
+ process != processes.end();
+ ++process)
+ {
+ process->wait();
+ auto ret = process->get();
+ if(ret != 0)
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+namespace
+{
+std::set<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task)
+{
+ std::set<std::shared_ptr<Task>> tasks;
+ tasks.insert(task);
+
+ auto deps = task->getDependsTasks();
+ for(const auto& dep : deps)
+ {
+ auto depSet = getDepTasks(dep);
+ for(const auto& dep : depSet)
+ {
+ tasks.insert(dep);
+ }
+ }
+
+ return tasks;
+}
+}
+
+int build(const Settings& settings,
+ const std::string& name,
+ const std::list<std::shared_ptr<Task>>& all_tasks)
+{
+ bool task_found{false};
+ for(auto task : all_tasks)
+ {
+ if(task->name() == name || task->target() == name)
+ {
+ std::cout << name << "\n";
+ task_found = true;
+
+ auto depSet = getDepTasks(task);
+ std::list<std::shared_ptr<Task>> ts;
+ for(const auto& task : depSet)
+ {
+ ts.push_back(task);
+ }
+ auto ret = build(settings, name, ts, all_tasks);
+ if(ret != 0)
+ {
+ return ret;
+ }
+
+ break;
+ }
+ }
+
+ if(!task_found)
+ {
+ std::cerr << "*** No rule to make target '" << name << "'. Stop.\n";
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/src/build.h b/src/build.h
new file mode 100644
index 0000000..36e48ad
--- /dev/null
+++ b/src/build.h
@@ -0,0 +1,18 @@
+// -*- c++ -*-
+#pragma once
+
+#include <string>
+#include <list>
+#include <memory>
+
+#include "task.h"
+#include "settings.h"
+
+int build(const Settings& settings,
+ const std::string& name,
+ const std::list<std::shared_ptr<Task>>& tasks,
+ const std::list<std::shared_ptr<Task>>& all_tasks);
+
+int build(const Settings& settings,
+ const std::string& name,
+ const std::list<std::shared_ptr<Task>>& all_tasks);
diff --git a/src/configure.cc b/src/configure.cc
new file mode 100644
index 0000000..ab2f837
--- /dev/null
+++ b/src/configure.cc
@@ -0,0 +1,301 @@
+#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;
+}
+
+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 {};
+}
+
+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{128};
+
+ std::string build_arch;
+ std::string build_path;
+ std::string host_arch;
+ std::string host_path;
+ std::string cc_prog = "gcc";
+ std::string cxx_prog = "g++";
+ std::string ar_prog = "ar";
+ std::string ld_prog = "ld";
+
+ 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("cc", required_argument, key++,
+ "Use specified c-compiler instead of gcc.",
+ [&]() {
+ cc_prog = optarg;
+ return 0;
+ });
+
+ opt.add("cxx", required_argument, key++,
+ "Use specified c++-compiler instead of g++.",
+ [&]() {
+ cxx_prog = optarg;
+ return 0;
+ });
+
+ opt.add("ar", required_argument, key++,
+ "Use specified archiver instead of ar.",
+ [&]() {
+ ar_prog = optarg;
+ return 0;
+ });
+
+ opt.add("ld", required_argument, key++,
+ "Use specified linker instead of ld.",
+ [&]() {
+ ld_prog = optarg;
+ 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;
+ }
+ }
+*/
+ auto cc_env = getenv("CC");
+ if(cc_env)
+ {
+ cmd_str = std::string("CC=") + cc_env + " " + cmd_str;
+ cc_prog = cc_env;
+ }
+
+ auto cxx_env = getenv("CXX");
+ if(cxx_env)
+ {
+ cmd_str = std::string("CXX=") + cxx_env + " " + cmd_str;
+ cxx_prog = cxx_env;
+ }
+
+ auto ar_env = getenv("AR");
+ if(ar_env)
+ {
+ cmd_str = std::string("AR=") + ar_env + " " + cmd_str;
+ ar_prog = ar_env;
+ }
+
+ auto ld_env = getenv("LD");
+ if(ld_env)
+ {
+ cmd_str = std::string("LD=") + ld_env + " " + cmd_str;
+ ld_prog = ld_env;
+ }
+
+ 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);
+
+ std::cout << "Writing results to: " << configurationFile.string() << "\n";
+ {
+ 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::host_cc << "\", \"" << host_cc << "\" },\n";
+ istr << " { \"" << cfg::host_cxx << "\", \"" << host_cxx << "\" },\n";
+ istr << " { \"" << cfg::host_ar << "\", \"" << host_ar << "\" },\n";
+ istr << " { \"" << cfg::host_ld << "\", \"" << host_ld << "\" },\n";
+ istr << " { \"" << cfg::build_cc << "\", \"" << build_cc << "\" },\n";
+ istr << " { \"" << cfg::build_cxx << "\", \"" << build_cxx << "\" },\n";
+ istr << " { \"" << cfg::build_ar << "\", \"" << build_ar << "\" },\n";
+ istr << " { \"" << cfg::build_ld << "\", \"" << build_ld << "\" },\n";
+ istr << " };\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
new file mode 100644
index 0000000..95b6765
--- /dev/null
+++ b/src/configure.h
@@ -0,0 +1,19 @@
+// -*- c++ -*-
+#pragma once
+
+#include <filesystem>
+#include <string>
+#include <map>
+
+extern std::filesystem::path configurationFile;;
+extern std::filesystem::path configHeaderFile;
+
+int configure(int argc, char* argv[]);
+
+bool hasConfiguration(const std::string& key);
+const std::string& getConfiguration(const std::string& key,
+ const std::string& defaultValue);
+
+const std::map<std::string, std::string>& configuration();
+
+extern const std::map<std::string, std::string> default_configuration;
diff --git a/src/execute.cc b/src/execute.cc
new file mode 100644
index 0000000..bc4cd5f
--- /dev/null
+++ b/src/execute.cc
@@ -0,0 +1,87 @@
+#include "execute.h"
+
+#include <unistd.h>
+#include <cstring>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <spawn.h>
+#include <iostream>
+
+/*
+https://blog.famzah.net/2009/11/20/a-much-faster-popen-and-system-implementation-for-linux/
+https://github.com/famzah/popen-noshell/commit/1f9eaf4eeef348d1efe0f3c7fe8ab670653cfbb1
+https://blog.famzah.net/2018/12/19/posix_spawn-performance-benchmarks-and-usage-examples/
+https://stackoverflow.com/questions/4259629/what-is-the-difference-between-fork-and-vfork/5207945#5207945
+ */
+
+
+namespace
+{
+int parent_waitpid(pid_t pid)
+{
+ int status;
+
+ if(waitpid(pid, &status, 0) != pid)
+ {
+ return 1;
+ }
+
+ return status;
+}
+} // namespace ::
+
+int execute(const std::string& command,
+ const std::vector<std::string>& args,
+ bool verbose)
+{
+ std::vector<const char*> argv;
+ argv.push_back(command.data());
+ for(const auto& arg : args)
+ {
+ argv.push_back(arg.data());
+ }
+ argv.push_back(nullptr);
+
+ if(verbose)
+ {
+ std::string cmd;
+ for(const auto& arg : argv)
+ {
+ if(arg == nullptr)
+ {
+ break;
+ }
+ if(!cmd.empty())
+ {
+ cmd += " ";
+ }
+ cmd += arg;
+ }
+
+ std::cout << cmd << "\n";
+ }
+
+#if 1
+ auto pid = vfork();
+ if(pid == 0)
+ {
+ execv(command.data(), (char**)argv.data());
+ std::cout << "Could not execute " << command << ": " <<
+ strerror(errno) << "\n";
+ _exit(1); // execv only returns if an error occurred
+ }
+ auto ret = parent_waitpid(pid);
+#elif 0
+ pid_t pid;
+ if(posix_spawn(&pid, command.data(), nullptr, nullptr,
+ (char**)argv.data(), nullptr))
+ {
+ return 1;
+ }
+ auto ret = parent_waitpid(pid);
+#else
+ auto ret = system(cmd.data());
+#endif
+
+ return ret;
+}
diff --git a/src/execute.h b/src/execute.h
new file mode 100644
index 0000000..f284230
--- /dev/null
+++ b/src/execute.h
@@ -0,0 +1,9 @@
+// -*- c++ -*-
+#pragma once
+
+#include <string>
+#include <vector>
+
+int execute(const std::string& command,
+ const std::vector<std::string>& args,
+ bool verbose = true);
diff --git a/src/libcppbuild.cc b/src/libcppbuild.cc
new file mode 100644
index 0000000..d3d8a51
--- /dev/null
+++ b/src/libcppbuild.cc
@@ -0,0 +1,316 @@
+#include <vector>
+#include <string>
+#include <filesystem>
+#include <iostream>
+#include <utility>
+#include <list>
+#include <thread>
+#include <memory>
+#include <algorithm>
+#include <list>
+#include <array>
+#include <deque>
+#include <fstream>
+#include <cstdlib>
+#include <set>
+
+#include <getoptpp/getoptpp.hpp>
+
+#include "libcppbuild.h"
+#include "settings.h"
+#include "configure.h"
+#include "rebuild.h"
+#include "tasks.h"
+#include "build.h"
+int main(int argc, char* argv[])
+{
+ if(argc > 1 && std::string(argv[1]) == "configure")
+ {
+ return configure(argc, argv);
+ }
+
+ Settings settings{};
+
+ settings.builddir = getConfiguration(cfg::builddir, "build");
+ settings.parallel_processes =
+ std::max(1u, std::thread::hardware_concurrency() * 2 - 1);
+ settings.verbose = 0;
+
+ bool write_compilation_database{false};
+ std::string compilation_database;
+ bool print_configure_cmd{false};
+ bool print_configure_db{false};
+ std::vector<std::string> add_files;
+ std::vector<std::string> remove_files;
+ bool list_files{false};
+ bool list_targets{false};
+ bool no_relaunch{false}; // true means no re-launch after rebuild.
+
+ dg::Options opt;
+ int key{128};
+
+ opt.add("jobs", required_argument, 'j',
+ "Number of parallel jobs. (default: cpucount * 2 - 1)",
+ [&]() {
+ try
+ {
+ settings.parallel_processes = std::stoi(optarg);
+ }
+ catch(...)
+ {
+ std::cerr << "Not a number\n";
+ return 1;
+ }
+ return 0;
+ });
+
+ opt.add("build-dir", required_argument, 'b',
+ "Overload 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("add", required_argument, 'a',
+ "Add specified file to the build configurations.",
+ [&]() {
+ no_relaunch = true;
+ add_files.push_back(optarg);
+ return 0;
+ });
+
+ opt.add("remove", required_argument, 'r',
+ "Remove specified file from the build configurations.",
+ [&]() {
+ no_relaunch = true;
+ remove_files.push_back(optarg);
+ return 0;
+ });
+
+ opt.add("list-files", no_argument, 'L',
+ "List files in the build configurations.",
+ [&]() {
+ no_relaunch = true;
+ list_files = true;
+ return 0;
+ });
+
+ opt.add("list-targets", no_argument, 'l',
+ "List targets.",
+ [&]() {
+ no_relaunch = true;
+ list_targets = true;
+ return 0;
+ });
+
+ opt.add("configure-cmd", no_argument, key++,
+ "Print commandline for last configure.",
+ [&]() {
+ no_relaunch = true;
+ print_configure_cmd = true;
+ return 0;
+ });
+
+ opt.add("configure-db", no_argument, key++,
+ "Print entire configure parameter database.",
+ [&]() {
+ no_relaunch = true;
+ print_configure_db = true;
+ return 0;
+ });
+
+ opt.add("database", required_argument, 'd',
+ "Write compilation database json file.",
+ [&]() {
+ no_relaunch = true;
+ write_compilation_database = true;
+ compilation_database = optarg;
+ return 0;
+ });
+
+ opt.add("help", no_argument, 'h',
+ "Print this help text.",
+ [&]() {
+ std::cout << "Usage: " << argv[0] << " [options] [target] ...\n";
+ std::cout <<
+R"_( where target can be either:
+ configure - run configuration step (cannot be used with other targets).
+ clean - clean all generated files.
+ all - build all targets (default)
+ or the name of a target which will be built along with its dependencies.
+ Use '-l' to see a list of possible target names.
+
+Options:
+)_";
+ opt.help();
+ exit(0);
+ return 0;
+ });
+
+ opt.process(argc, argv);
+
+ auto verbose_env = std::getenv("V");
+ if(verbose_env)
+ {
+ settings.verbose = std::atoi(verbose_env);
+ }
+
+ if(list_files)
+ {
+ std::set<std::string> files;
+ for(std::size_t i = 0; i < numConfigFiles; ++i)
+ {
+ files.insert(configFiles[i].file);
+ }
+
+ for(const auto& file : files)
+ {
+ std::cout << file << "\n";
+ }
+ }
+
+ if(!add_files.empty() || !remove_files.empty())
+ {
+ for(const auto& add_file : add_files)
+ {
+ reg(add_file.data(), [](){ return std::vector<BuildConfiguration>{};});
+ }
+
+ for(const auto& remove_file : remove_files)
+ {
+ unreg(remove_file.data());
+ }
+
+ // Force rebuild if files were added
+ recompileCheck(settings, 1, argv, true, no_relaunch == false);
+ }
+
+ recompileCheck(settings, argc, argv);
+
+ std::filesystem::path builddir(settings.builddir);
+ std::filesystem::create_directories(builddir);
+
+ auto all_tasks = getTasks(settings);
+
+ if(list_targets)
+ {
+ for(const auto& task : all_tasks)
+ {
+ if(task->targetType() != TargetType::Object)
+ {
+ std::cout << task->name() << "\n";
+ }
+ }
+ }
+
+ if(write_compilation_database)
+ {
+ std::ofstream istr(compilation_database);
+ istr << "[";
+ bool first{true};
+ for(auto task : all_tasks)
+ {
+ auto s = task->toJSON();
+ if(!s.empty())
+ {
+ if(!first)
+ {
+ istr << ",\n";
+ }
+ else
+ {
+ istr << "\n";
+ }
+ first = false;
+ istr << s;
+ }
+ }
+ istr << "\n]\n";
+ }
+
+ if(print_configure_cmd)
+ {
+ std::cout << getConfiguration("cmd") << "\n";
+ }
+
+ if(print_configure_db)
+ {
+ const auto& c = configuration();
+ for(const auto& config : c)
+ {
+ std::cout << config.first << ": " << config.second << "\n";
+ }
+ }
+
+ for(auto task : all_tasks)
+ {
+ if(task->registerDepTasks(all_tasks))
+ {
+ return 1;
+ }
+ }
+
+ bool build_all{true};
+ for(const auto& arg : opt.arguments())
+ {
+ if(arg == "configure")
+ {
+ std::cerr << "The 'configure' target must be the first argument.\n";
+ return 1;
+ }
+
+ if(arg == "clean")
+ {
+ build_all = false;
+
+ std::cout << "Cleaning\n";
+ for(auto& task : all_tasks)
+ {
+ if(task->clean() != 0)
+ {
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ build_all = false;
+
+ if(arg == "all")
+ {
+ auto ret = build(settings, "all", all_tasks, all_tasks);
+ if(ret != 0)
+ {
+ return ret;
+ }
+ }
+ else
+ {
+ auto ret = build(settings, arg, all_tasks);
+ if(ret != 0)
+ {
+ return ret;
+ }
+ }
+ }
+ }
+
+ if(build_all)
+ {
+ auto ret = build(settings, "all", all_tasks, all_tasks);
+ if(ret != 0)
+ {
+ return ret;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/libcppbuild.h b/src/libcppbuild.h
new file mode 100644
index 0000000..d0a0080
--- /dev/null
+++ b/src/libcppbuild.h
@@ -0,0 +1,76 @@
+// -*- c++ -*-
+#pragma once
+
+#include <string>
+#include <vector>
+#include <map>
+
+enum class TargetType
+{
+ Auto, // Default - deduce from target name and sources extensions
+
+ Executable,
+ StaticLibrary,
+ DynamicLibrary,
+ Object,
+};
+
+enum class Language
+{
+ Auto, // Default - deduce language from source extensions
+
+ C,
+ Cpp,
+ Asm,
+};
+
+enum class OutputSystem
+{
+ Host, // Output for the target system
+ Build, // Internal tool during cross-compilation
+};
+
+struct BuildConfiguration
+{
+ TargetType type{TargetType::Auto};
+ Language language{Language::Auto};
+ OutputSystem system{OutputSystem::Host};
+ std::string target;
+ std::vector<std::string> sources; // source list
+ std::vector<std::string> depends; // internal dependencies
+ std::vector<std::string> cxxflags; // flags for c++ compiler
+ std::vector<std::string> cflags; // flags for c compiler
+ std::vector<std::string> ldflags; // flags for linker
+ std::vector<std::string> asmflags; // flags for asm translator
+};
+
+using BuildConfigurations = std::vector<BuildConfiguration>;
+
+int reg(const char* location, BuildConfigurations (*cb)());
+
+// Convenience macro - ugly but keeps things simple(r)
+#define CONCAT(a, b) CONCAT_INNER(a, b)
+#define CONCAT_INNER(a, b) a ## b
+#define UNIQUE_NAME(base) CONCAT(base, __LINE__)
+#define REG(cb) namespace { int UNIQUE_NAME(unique) = reg(__FILE__, cb); }
+
+// Predefined configuration keys
+namespace cfg
+{
+constexpr auto builddir = "builddir";
+
+constexpr auto host_cc = "host-cc";
+constexpr auto host_cxx = "host-cpp";
+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_ar = "build-ar";
+constexpr auto build_ld = "build-ld";
+}
+
+const std::map<std::string, std::string>& configuration();
+bool hasConfiguration(const std::string& key);
+const std::string& getConfiguration(const std::string& key,
+ const std::string& defaultValue = {});
diff --git a/src/rebuild.cc b/src/rebuild.cc
new file mode 100644
index 0000000..43c4c98
--- /dev/null
+++ b/src/rebuild.cc
@@ -0,0 +1,141 @@
+#include "rebuild.h"
+
+#include <iostream>
+#include <filesystem>
+#include <algorithm>
+
+#include "execute.h"
+#include "configure.h"
+#include "settings.h"
+#include "libcppbuild.h"
+
+std::array<BuildConfigurationEntry, 1024> configFiles;
+std::size_t numConfigFiles{0};
+
+// TODO: Use c++20 when ready, somehing like this:
+//int reg(const std::source_location location = std::source_location::current())
+int reg(const char* location, std::vector<BuildConfiguration> (*cb)())
+{
+ // NOTE: std::cout cannot be used here
+ if(numConfigFiles >= configFiles.size())
+ {
+ fprintf(stderr, "Max %d build configurations currently supported.\n",
+ (int)configFiles.size());
+ exit(1);
+ }
+
+ configFiles[numConfigFiles].file = location;
+ configFiles[numConfigFiles].cb = cb;
+ ++numConfigFiles;
+
+ return 0;
+}
+
+int unreg(const char* location)
+{
+ std::size_t found{0};
+ for(std::size_t i = 0; i < numConfigFiles;)
+ {
+ if(std::string(location) == configFiles[i].file)
+ {
+ ++found;
+ for(std::size_t j = i; j < numConfigFiles; ++j)
+ {
+ configFiles[j] = configFiles[j + 1];
+ }
+ --numConfigFiles;
+ }
+ else
+ {
+ ++i;
+ }
+ }
+
+ return found;
+}
+
+void recompileCheck(const Settings& settings, int argc, char* argv[],
+ bool force, bool relaunch_allowed)
+{
+ bool dirty{force};
+
+ std::vector<std::string> args;
+ args.push_back("-s");
+ args.push_back("-O3");
+ args.push_back("-std=c++17");
+ args.push_back("-pthread");
+
+ std::filesystem::path binFile(argv[0]);
+
+ if(std::filesystem::exists(configurationFile))
+ {
+ args.push_back(configurationFile.string());
+
+ if(std::filesystem::last_write_time(binFile) <=
+ std::filesystem::last_write_time(configurationFile))
+ {
+ dirty = true;
+ }
+
+ const auto& c = configuration();
+ if(&c == &default_configuration)
+ {
+ // configuration.cc exists, but currently compiled with the default one.
+ dirty = true;
+ }
+ }
+
+ if(settings.verbose > 1)
+ {
+ std::cout << "Recompile check (" << numConfigFiles << "):\n";
+ }
+
+ for(std::size_t i = 0; i < numConfigFiles; ++i)
+ {
+ std::string location = configFiles[i].file;
+ if(settings.verbose > 1)
+ {
+ std::cout << " - " << location << "\n";
+ }
+ std::filesystem::path configFile(location);
+ if(std::filesystem::last_write_time(binFile) <=
+ std::filesystem::last_write_time(configFile))
+ {
+ dirty = true;
+ }
+
+ // Support adding multiple config functions from the same file
+ if(std::find(args.begin(), args.end(), location) == std::end(args))
+ {
+ args.push_back(location);
+ }
+ }
+ args.push_back("libcppbuild.a");
+ args.push_back("-o");
+ args.push_back(binFile.string());
+
+ if(dirty)
+ {
+ std::cout << "Rebuilding config\n";
+ auto tool = getConfiguration(cfg::build_cxx, "/usr/bin/g++");
+ auto ret = execute(tool, args, settings.verbose > 0);
+ if(ret != 0)
+ {
+ std::cerr << "Failed: ." << ret << "\n";
+ exit(1);
+ }
+ else
+ {
+ if(relaunch_allowed)
+ {
+ std::cout << "Re-launch\n";
+ std::vector<std::string> args;
+ for(int i = 1; i < argc; ++i)
+ {
+ args.push_back(argv[i]);
+ }
+ exit(execute(argv[0], args, settings.verbose > 0));
+ }
+ }
+ }
+}
diff --git a/src/rebuild.h b/src/rebuild.h
new file mode 100644
index 0000000..bc5d889
--- /dev/null
+++ b/src/rebuild.h
@@ -0,0 +1,24 @@
+// -*- c++ -*-
+#pragma once
+
+#include <vector>
+#include <array>
+
+#include "libcppbuild.h"
+
+class Settings;
+
+struct BuildConfigurationEntry
+{
+ const char* file;
+ std::vector<BuildConfiguration> (*cb)();
+};
+
+extern std::array<BuildConfigurationEntry, 1024> configFiles;
+extern std::size_t numConfigFiles;
+
+//int reg(const char* location, std::vector<BuildConfiguration> (*cb)());
+int unreg(const char* location);
+
+void recompileCheck(const Settings& settings, int argc, char* argv[],
+ bool force = false, bool relaunch_allowed = true);
diff --git a/src/settings.h b/src/settings.h
new file mode 100644
index 0000000..6b8729f
--- /dev/null
+++ b/src/settings.h
@@ -0,0 +1,11 @@
+// -*- c++ -*-
+#pragma once
+
+#include <cstddef>
+
+struct Settings
+{
+ std::string builddir;
+ std::size_t parallel_processes;
+ int verbose{1};
+};
diff --git a/src/task.cc b/src/task.cc
new file mode 100644
index 0000000..962a02b
--- /dev/null
+++ b/src/task.cc
@@ -0,0 +1,146 @@
+#include "task.h"
+
+#include <unistd.h>
+#include <iostream>
+
+Task::Task(const BuildConfiguration& config,
+ const std::vector<std::string>& depends)
+ : dependsStr(depends)
+ , config(config)
+ , output_system(config.system)
+{
+}
+
+int Task::registerDepTasks(const std::list<std::shared_ptr<Task>>& tasks)
+{
+ for(auto const& depStr : dependsStr)
+ {
+ bool found{false};
+ for(const auto& task : tasks)
+ {
+ if(task->target() == depStr)
+ {
+ dependsTasks.push_back(task);
+ found = true;
+ }
+ }
+ if(!found)
+ {
+ std::cerr << "Could not find dependency " << depStr << " needed by " <<
+ target() << " target\n";
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+std::string Task::name() const
+{
+ return config.target;
+}
+
+bool Task::dirty()
+{
+ for(const auto& task : dependsTasks)
+ {
+ if(task->dirty())
+ {
+ return true;
+ }
+ }
+
+ return dirtyInner();
+}
+
+bool Task::ready()
+{
+ for(const auto& task : dependsTasks)
+ {
+ if(task->dirty() || task->state() == State::Running)
+ {
+ return false;
+ }
+ }
+
+ task_state.store(State::Ready);
+ return true;
+}
+
+int Task::run()
+{
+ if(task_state.load() == State::Done)
+ {
+ return 0;
+ }
+
+ task_state.store(State::Running);
+ auto ret = runInner();
+ if(ret == 0)
+ {
+ task_state.store(State::Done);
+ }
+ else
+ {
+ task_state.store(State::Error);
+ }
+
+ return ret;
+}
+
+State Task::state() const
+{
+ return task_state.load();
+}
+
+const BuildConfiguration& Task::buildConfig() const
+{
+ return config;
+}
+
+TargetType Task::targetType() const
+{
+ return target_type;
+}
+
+Language Task::sourceLanguage() const
+{
+ return source_language;
+}
+
+OutputSystem Task::outputSystem() const
+{
+ return output_system;
+}
+
+std::string Task::compiler() const
+{
+ switch(sourceLanguage())
+ {
+ case Language::C:
+ switch(outputSystem())
+ {
+ case OutputSystem::Host:
+ return getConfiguration(cfg::host_cc, "/usr/bin/gcc");
+ case OutputSystem::Build:
+ return getConfiguration(cfg::build_cc, "/usr/bin/gcc");
+ }
+ case Language::Cpp:
+ switch(outputSystem())
+ {
+ case OutputSystem::Host:
+ return getConfiguration(cfg::host_cxx, "/usr/bin/g++");
+ case OutputSystem::Build:
+ return getConfiguration(cfg::build_cxx, "/usr/bin/g++");
+ }
+ default:
+ std::cerr << "Unknown CC target type\n";
+ exit(1);
+ break;
+ }
+}
+
+std::list<std::shared_ptr<Task>> Task::getDependsTasks()
+{
+ return dependsTasks;
+}
diff --git a/src/task.h b/src/task.h
new file mode 100644
index 0000000..98363a1
--- /dev/null
+++ b/src/task.h
@@ -0,0 +1,60 @@
+// -*- c++ -*-
+#pragma once
+
+#include <vector>
+#include <string>
+#include <atomic>
+#include <list>
+#include <memory>
+
+#include "libcppbuild.h"
+
+enum class State
+{
+ Unknown,
+ Ready,
+ Running,
+ Done,
+ Error,
+};
+
+class Task
+{
+public:
+ Task(const BuildConfiguration& config,
+ const std::vector<std::string>& depends = {});
+
+ int registerDepTasks(const std::list<std::shared_ptr<Task>>& tasks);
+
+ virtual std::string name() const;
+ bool dirty();
+ bool ready();
+ int run();
+ State state() const;
+ virtual int clean() = 0 ;
+ virtual std::vector<std::string> depends() const = 0;
+ virtual std::string target() const = 0;
+
+ virtual std::string toJSON() const { return {}; };
+
+ const BuildConfiguration& buildConfig() const;
+
+ TargetType targetType() const;
+ Language sourceLanguage() const;
+ OutputSystem outputSystem() const;
+ std::string compiler() const;
+
+ std::list<std::shared_ptr<Task>> getDependsTasks();
+
+protected:
+ std::atomic<State> task_state{State::Unknown};
+ virtual int runInner() { return 0; };
+ virtual bool dirtyInner() { return false; }
+
+ std::vector<std::string> dependsStr;
+ std::list<std::shared_ptr<Task>> dependsTasks;
+ const BuildConfiguration& config;
+ TargetType target_type{TargetType::Auto};
+ Language source_language{Language::Auto};
+ OutputSystem output_system{OutputSystem::Host};
+};
diff --git a/src/task_ar.cc b/src/task_ar.cc
new file mode 100644
index 0000000..5568629
--- /dev/null
+++ b/src/task_ar.cc
@@ -0,0 +1,221 @@
+#include "task_ar.h"
+
+#include <iostream>
+#include <fstream>
+
+#include "libcppbuild.h"
+#include "settings.h"
+#include "execute.h"
+
+namespace
+{
+std::string readFile(const std::string &fileName)
+{
+ std::ifstream ifs(fileName.c_str(),
+ std::ios::in | std::ios::binary | std::ios::ate);
+
+ std::ifstream::pos_type fileSize = ifs.tellg();
+ ifs.seekg(0, std::ios::beg);
+
+ std::vector<char> bytes(fileSize);
+ ifs.read(bytes.data(), fileSize);
+
+ return std::string(bytes.data(), fileSize);
+}
+
+std::vector<std::string> addPrefix(const std::vector<std::string>& lst,
+ const Settings& settings)
+{
+ std::vector<std::string> out;
+ for(const auto& item : lst)
+ {
+ std::filesystem::path file = settings.builddir;
+ file /= item;
+ out.push_back(file.string());
+ }
+ return out;
+}
+} // namespace ::
+
+TaskAR::TaskAR(const BuildConfiguration& config,
+ const Settings& settings,
+ const std::string& target,
+ const std::vector<std::string>& objects)
+ : Task(config, addPrefix(config.depends, settings))
+ , config(config)
+ , settings(settings)
+{
+ targetFile = settings.builddir;
+ targetFile /= target;
+ for(const auto& object : objects)
+ {
+ std::filesystem::path objectFile = object;
+ objectFiles.push_back(objectFile);
+ dependsStr.push_back(objectFile.string());
+ }
+
+ for(const auto& dep : config.depends)
+ {
+ std::filesystem::path depFile = settings.builddir;
+ depFile /= dep;
+ depFiles.push_back(depFile);
+ }
+
+ flagsFile = settings.builddir / targetFile.stem();
+ flagsFile += ".flags";
+
+ target_type = TargetType::StaticLibrary;
+ source_language = Language::C;
+ for(const auto& source : config.sources)
+ {
+ std::filesystem::path sourceFile(source);
+ if(sourceFile.extension().string() != ".c")
+ {
+ source_language = Language::Cpp;
+ }
+ }
+}
+
+bool TaskAR::dirtyInner()
+{
+ if(!std::filesystem::exists(targetFile))
+ {
+ return true;
+ }
+
+ if(!std::filesystem::exists(flagsFile))
+ {
+ return true;
+ }
+
+ for(const auto& objectFile : objectFiles)
+ {
+ if(std::filesystem::last_write_time(targetFile) <=
+ std::filesystem::last_write_time(objectFile))
+ {
+ return true;
+ }
+ }
+
+ {
+ auto lastFlags = readFile(flagsFile.string());
+ if(flagsString() != lastFlags)
+ {
+ //std::cout << "The compiler flags changed\n";
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int TaskAR::runInner()
+{
+ std::string objectlist;
+ for(const auto& objectFile : objectFiles)
+ {
+ if(!objectlist.empty())
+ {
+ objectlist += " ";
+ }
+ objectlist += objectFile.string();
+ }
+
+ std::vector<std::string> args;
+ args.push_back("rcs");
+ args.push_back(targetFile.string());
+ for(const auto& objectFile : objectFiles)
+ {
+ args.push_back(objectFile.string());
+ }
+ for(const auto& flag : config.ldflags)
+ {
+ args.push_back(flag);
+ }
+
+ { // Write flags to file.
+ std::ofstream flagsStream(flagsFile);
+ flagsStream << flagsString();
+ }
+
+ if(settings.verbose == 0)
+ {
+ std::cout << "AR => " << targetFile.string() << "\n";
+ }
+
+ std::string tool;
+ switch(outputSystem())
+ {
+ case OutputSystem::Host:
+ tool = getConfiguration(cfg::host_ar, "/usr/bin/ar");
+ break;
+ case OutputSystem::Build:
+ tool = getConfiguration(cfg::build_ar, "/usr/bin/ar");
+ break;
+ }
+
+ return execute(tool, args, settings.verbose > 0);
+}
+
+int TaskAR::clean()
+{
+ if(std::filesystem::exists(targetFile))
+ {
+ std::cout << "Removing " << targetFile.string() << "\n";
+ std::filesystem::remove(targetFile);
+ }
+
+ if(std::filesystem::exists(flagsFile))
+ {
+ std::cout << "Removing " << flagsFile.string() << "\n";
+ std::filesystem::remove(flagsFile);
+ }
+
+ return 0;
+}
+
+std::vector<std::string> TaskAR::depends() const
+{
+ std::vector<std::string> deps;
+ for(const auto& objectFile : objectFiles)
+ {
+ deps.push_back(objectFile.string());
+ }
+
+ for(const auto& depFile : depFiles)
+ {
+ deps.push_back(depFile.string());
+ }
+
+ return deps;
+}
+
+std::string TaskAR::target() const
+{
+ return targetFile.string();
+}
+
+std::string TaskAR::flagsString() const
+{
+ std::string flagsStr;
+ for(const auto& flag : config.ldflags)
+ {
+ if(flag != config.ldflags[0])
+ {
+ flagsStr += " ";
+ }
+ flagsStr += flag;
+ }
+ flagsStr += "\n";
+
+ for(const auto& dep : config.depends)
+ {
+ if(dep != config.depends[0])
+ {
+ flagsStr += " ";
+ }
+ flagsStr += dep;
+ }
+
+ return flagsStr;
+}
diff --git a/src/task_ar.h b/src/task_ar.h
new file mode 100644
index 0000000..bfa21a2
--- /dev/null
+++ b/src/task_ar.h
@@ -0,0 +1,42 @@
+// -*- c++ -*-
+#pragma once
+
+#include "task.h"
+
+#include <vector>
+#include <string>
+#include <future>
+#include <filesystem>
+
+struct BuildConfiguration;
+struct Settings;
+
+class TaskAR
+ : public Task
+{
+public:
+ TaskAR(const BuildConfiguration& config,
+ const Settings& settings,
+ const std::string& target,
+ const std::vector<std::string>& objects);
+
+ bool dirtyInner() override;
+
+ int runInner() override;
+ int clean() override;
+
+ std::vector<std::string> depends() const override;
+
+ std::string target() const override;
+
+private:
+ std::string flagsString() const;
+
+ std::vector<std::filesystem::path> objectFiles;
+ std::vector<std::filesystem::path> depFiles;
+ std::filesystem::path targetFile;
+ std::filesystem::path flagsFile;
+
+ const BuildConfiguration& config;
+ const Settings& settings;
+};
diff --git a/src/task_cc.cc b/src/task_cc.cc
new file mode 100644
index 0000000..af9cf7a
--- /dev/null
+++ b/src/task_cc.cc
@@ -0,0 +1,339 @@
+#include "task_cc.h"
+
+#include <iostream>
+#include <fstream>
+
+#include "libcppbuild.h"
+#include "settings.h"
+#include "execute.h"
+
+namespace
+{
+std::string readFile(const std::string &fileName)
+{
+ std::ifstream ifs(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate);
+
+ std::ifstream::pos_type fileSize = ifs.tellg();
+ ifs.seekg(0, std::ios::beg);
+
+ std::vector<char> bytes(fileSize);
+ ifs.read(bytes.data(), fileSize);
+
+ return std::string(bytes.data(), fileSize);
+}
+
+std::vector<std::string> readDeps(const std::string& depFile)
+{
+ if(!std::filesystem::exists(depFile))
+ {
+ return {};
+ }
+
+ auto str = readFile(depFile);
+
+ std::vector<std::string> output;
+ std::string tmp;
+ bool start{false};
+ bool in_whitespace{false};
+ for(const auto& c : str)
+ {
+ if(c == '\\' || c == '\n')
+ {
+ continue;
+ }
+
+ if(c == ':')
+ {
+ start = true;
+ continue;
+ }
+
+ if(!start)
+ {
+ continue;
+ }
+
+ if(c == ' ' || c == '\t')
+ {
+ if(in_whitespace)
+ {
+ continue;
+ }
+
+ if(!tmp.empty())
+ {
+ output.push_back(tmp);
+ }
+ tmp.clear();
+ in_whitespace = true;
+ }
+ else
+ {
+ in_whitespace = false;
+ tmp += c;
+ }
+ }
+
+ if(!tmp.empty())
+ {
+ output.push_back(tmp);
+ }
+
+ return output;
+}
+} // namespace ::
+
+TaskCC::TaskCC(const BuildConfiguration& config, const Settings& settings,
+ const std::string& sourceDir, const std::string& source)
+ : Task(config)
+ , config(config)
+ , settings(settings)
+ , sourceDir(sourceDir)
+{
+ sourceFile = sourceDir;
+ sourceFile /= source;
+
+ std::filesystem::path base = settings.builddir;
+ base /= config.target;
+ base += "-";
+ base += sourceFile.stem();
+
+ if(sourceFile.extension().string() == ".c")
+ {
+ base += "_c";
+ }
+ else
+ {
+ base += "_cc";
+ }
+
+ targetFile = base;
+ targetFile += ".o";
+ depsFile = base;
+ depsFile += ".d";
+ flagsFile = base;
+ flagsFile += ".flags";
+
+ target_type = TargetType::Object;
+ if(sourceFile.extension().string() == ".c")
+ {
+ source_language = Language::C;
+ }
+ else
+ {
+ source_language = Language::Cpp;
+ }
+}
+
+std::string TaskCC::name() const
+{
+ return target();
+}
+
+bool TaskCC::dirtyInner()
+{
+ if(!std::filesystem::exists(sourceFile))
+ {
+ //std::cout << "Missing source file: " << std::string(sourceFile) << "\n";
+ return true;
+ }
+
+ if(!std::filesystem::exists(targetFile))
+ {
+ //std::cout << "Missing targetFile\n";
+ return true;
+ }
+
+ if(!std::filesystem::exists(depsFile))
+ {
+ //std::cout << "Missing depsFile\n";
+ return true;
+ }
+
+ if(!std::filesystem::exists(flagsFile))
+ {
+ //std::cout << "Missing flagsFile\n";
+ return true;
+ }
+
+ if(std::filesystem::last_write_time(sourceFile) >
+ std::filesystem::last_write_time(depsFile))
+ {
+ //std::cout << "The sourceFile newer than depsFile\n";
+ return true;
+ }
+
+ {
+ auto lastFlags = readFile(flagsFile.string());
+ if(flagsString() != lastFlags)
+ {
+ //std::cout << "The compiler flags changed\n";
+ return true;
+ }
+ }
+
+ auto depList = readDeps(depsFile.string());
+ for(const auto& dep : depList)
+ {
+ if(!std::filesystem::exists(dep) ||
+ std::filesystem::last_write_time(targetFile) <
+ std::filesystem::last_write_time(dep))
+ {
+ //std::cout << "The targetFile older than " << std::string(dep) << "\n";
+ return true;
+ }
+ }
+
+ if(std::filesystem::last_write_time(sourceFile) >
+ std::filesystem::last_write_time(targetFile))
+ {
+ //std::cout << "The targetFile older than sourceFile\n";
+ return true;
+ }
+
+ return false;
+}
+
+int TaskCC::runInner()
+{
+ if(!std::filesystem::exists(sourceFile))
+ {
+ std::cout << "Missing source file: " << sourceFile.string() << "\n";
+ return 1;
+ }
+
+ auto args = getCompilerArgs();
+
+ { // Write flags to file.
+ std::ofstream flagsStream(flagsFile.string());
+ flagsStream << flagsString();
+ }
+
+ if(settings.verbose == 0)
+ {
+ std::cout << compiler() << " " <<
+ sourceFile.lexically_normal().string() << " => " <<
+ targetFile.lexically_normal().string() << "\n";
+ }
+
+ return execute(compiler(), args, settings.verbose > 0);
+}
+
+int TaskCC::clean()
+{
+ if(std::filesystem::exists(targetFile))
+ {
+ std::cout << "Removing " << targetFile.string() << "\n";
+ std::filesystem::remove(targetFile);
+ }
+
+ if(std::filesystem::exists(depsFile))
+ {
+ std::cout << "Removing " << depsFile.string() << "\n";
+ std::filesystem::remove(depsFile);
+ }
+
+ if(std::filesystem::exists(flagsFile))
+ {
+ std::cout << "Removing " << flagsFile.string() << "\n";
+ std::filesystem::remove(flagsFile);
+ }
+
+ return 0;
+}
+
+std::vector<std::string> TaskCC::depends() const
+{
+ return {};
+}
+
+std::string TaskCC::target() const
+{
+ return targetFile.string();
+}
+
+std::string TaskCC::toJSON() const
+{
+ std::string json;
+ json += "\t{\n";
+ json += "\t\t\"directory\": \"" + sourceDir.string() + "\",\n";
+ json += "\t\t\"file\": \"" + sourceFile.lexically_normal().string() + "\",\n";
+ json += "\t\t\"output\": \"" + targetFile.string() + "\",\n";
+ json += "\t\t\"arguments\": [ \"" + compiler() + "\"";
+ auto args = getCompilerArgs();
+ for(const auto& arg : args)
+ {
+ json += ", \"" + arg + "\"";
+ }
+ json += " ]\n";
+ json += "\t}";
+ return json;
+}
+
+std::vector<std::string> TaskCC::flags() const
+{
+ switch(sourceLanguage())
+ {
+ case Language::C:
+ return config.cflags;
+ case Language::Cpp:
+ return config.cxxflags;
+ default:
+ std::cerr << "Unknown CC target type\n";
+ exit(1);
+ break;
+ }
+}
+
+std::string TaskCC::flagsString() const
+{
+ std::string flagsStr = compiler();
+ for(const auto& flag : flags())
+ {
+ flagsStr += " " + flag;
+ }
+ return flagsStr;
+}
+
+std::vector<std::string> TaskCC::getCompilerArgs() const
+{
+ auto compiler_flags = flags();
+
+ std::vector<std::string> args;
+ args.push_back("-MMD");
+
+ if(std::filesystem::path(config.target).extension() == ".so")
+ {
+ // Add -fPIC arg to all contained object files
+ args.push_back("-fPIC");
+ }
+
+ args.push_back("-c");
+ args.push_back(sourceFile.string());
+ args.push_back("-o");
+ args.push_back(targetFile.string());
+
+ for(const auto& flag : compiler_flags)
+ {
+ // Is arg an added include path?
+ if(flag.substr(0, 2) == "-I")
+ {
+ std::string include_path = flag.substr(2);
+ include_path.erase(0, include_path.find_first_not_of(' '));
+ std::filesystem::path path(include_path);
+
+ // Is it relative?
+ if(path.is_relative())
+ {
+ path = (sourceDir / path).lexically_normal();
+ std::string new_include_path = "-I" + path.string();
+ args.push_back(new_include_path);
+ continue;
+ }
+ }
+
+ args.push_back(flag);
+ }
+
+ return args;
+}
diff --git a/src/task_cc.h b/src/task_cc.h
new file mode 100644
index 0000000..0ce4947
--- /dev/null
+++ b/src/task_cc.h
@@ -0,0 +1,47 @@
+// -*- c++ -*-
+#pragma once
+
+#include "task.h"
+
+#include <vector>
+#include <string>
+#include <future>
+#include <filesystem>
+
+struct BuildConfiguration;
+struct Settings;
+
+class TaskCC
+ : public Task
+{
+public:
+ TaskCC(const BuildConfiguration& config,
+ const Settings& settings,
+ const std::string& sourceDir, const std::string& source);
+
+ std::string name() const override;
+ bool dirtyInner() override;
+
+ int runInner() override;
+ int clean() override;
+
+ std::vector<std::string> depends() const override;
+
+ std::string target() const override;
+
+ std::string toJSON() const override;
+
+private:
+ std::vector<std::string> flags() const;
+ std::string flagsString() const;
+ std::vector<std::string> getCompilerArgs() const;
+
+ std::filesystem::path sourceFile;
+ std::filesystem::path targetFile;
+ std::filesystem::path depsFile;
+ std::filesystem::path flagsFile;
+
+ const BuildConfiguration& config;
+ const Settings& settings;
+ std::filesystem::path sourceDir;
+};
diff --git a/src/task_ld.cc b/src/task_ld.cc
new file mode 100644
index 0000000..91f3316
--- /dev/null
+++ b/src/task_ld.cc
@@ -0,0 +1,227 @@
+#include "task_ld.h"
+
+#include <iostream>
+#include <fstream>
+
+#include "libcppbuild.h"
+#include "settings.h"
+#include "execute.h"
+
+namespace
+{
+std::string readFile(const std::string &fileName)
+{
+ std::ifstream ifs(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate);
+
+ std::ifstream::pos_type fileSize = ifs.tellg();
+ ifs.seekg(0, std::ios::beg);
+
+ std::vector<char> bytes(fileSize);
+ ifs.read(bytes.data(), fileSize);
+
+ return std::string(bytes.data(), fileSize);
+}
+
+std::vector<std::string> addPrefix(const std::vector<std::string>& lst,
+ const Settings& settings)
+{
+ std::vector<std::string> out;
+ for(const auto& item : lst)
+ {
+ std::filesystem::path file = settings.builddir;
+ file /= item;
+ out.push_back(file.string());
+ }
+ return out;
+}
+} // namespace ::
+
+TaskLD::TaskLD(const BuildConfiguration& config,
+ const Settings& settings,
+ const std::string& target,
+ const std::vector<std::string>& objects)
+ : Task(config, addPrefix(config.depends, settings))
+ , config(config)
+ , settings(settings)
+{
+ target_type = TargetType::Executable;
+
+ targetFile = settings.builddir;
+ targetFile /= target;
+ for(const auto& object : objects)
+ {
+ std::filesystem::path objectFile = object;
+ objectFiles.push_back(objectFile);
+ dependsStr.push_back(objectFile.string());
+ }
+
+ for(const auto& dep : config.depends)
+ {
+ std::filesystem::path depFile = settings.builddir;
+ depFile /= dep;
+ depFiles.push_back(depFile);
+ }
+
+ flagsFile = settings.builddir / targetFile.stem();
+ flagsFile += ".flags";
+
+ target_type = TargetType::Executable;
+ source_language = Language::C;
+ for(const auto& source : config.sources)
+ {
+ std::filesystem::path sourceFile(source);
+ if(sourceFile.extension().string() != ".c")
+ {
+ source_language = Language::Cpp;
+ }
+ }
+}
+
+bool TaskLD::dirtyInner()
+{
+ if(!std::filesystem::exists(targetFile))
+ {
+ return true;
+ }
+
+ if(!std::filesystem::exists(flagsFile))
+ {
+ return true;
+ }
+
+ for(const auto& objectFile : objectFiles)
+ {
+ if(std::filesystem::last_write_time(targetFile) <=
+ std::filesystem::last_write_time(objectFile))
+ {
+ return true;
+ }
+ }
+
+ {
+ auto lastFlags = readFile(flagsFile.string());
+ if(flagsString() != lastFlags)
+ {
+ //std::cout << "The compiler flags changed\n";
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int TaskLD::runInner()
+{
+ std::string objectlist;
+ for(const auto& objectFile : objectFiles)
+ {
+ if(!objectlist.empty())
+ {
+ objectlist += " ";
+ }
+ objectlist += objectFile.string();
+ }
+
+ std::vector<std::string> args;
+ for(const auto& objectFile : objectFiles)
+ {
+ args.push_back(objectFile.string());
+ }
+
+ for(const auto& depFile : depFiles)
+ {
+ if(depFile.extension() == ".so")
+ {
+ args.push_back(std::string("-L") + settings.builddir);
+ auto lib = depFile.stem().string().substr(3); // strip 'lib' prefix
+ args.push_back(std::string("-l") + lib);
+ }
+ else if(depFile.extension() == ".a")
+ {
+ args.push_back(depFile.string());
+ }
+ }
+
+ for(const auto& flag : config.ldflags)
+ {
+ args.push_back(flag);
+ }
+ args.push_back("-o");
+ args.push_back(targetFile.string());
+
+ { // Write flags to file.
+ std::ofstream flagsStream(flagsFile);
+ flagsStream << flagsString();
+ }
+
+ if(settings.verbose == 0)
+ {
+ std::cout << "LD => " << targetFile.string() << "\n";
+ }
+
+ auto tool = compiler();
+ return execute(tool, args, settings.verbose > 0);
+}
+
+int TaskLD::clean()
+{
+ if(std::filesystem::exists(targetFile))
+ {
+ std::cout << "Removing " << targetFile.string() << "\n";
+ std::filesystem::remove(targetFile);
+ }
+
+ if(std::filesystem::exists(flagsFile))
+ {
+ std::cout << "Removing " << flagsFile.string() << "\n";
+ std::filesystem::remove(flagsFile);
+ }
+
+ return 0;
+}
+
+std::vector<std::string> TaskLD::depends() const
+{
+ std::vector<std::string> deps;
+ for(const auto& objectFile : objectFiles)
+ {
+ deps.push_back(objectFile.string());
+ }
+
+ for(const auto& depFile : depFiles)
+ {
+ deps.push_back(depFile.string());
+ }
+
+ return deps;
+}
+
+std::string TaskLD::target() const
+{
+ return targetFile.string();
+}
+
+std::string TaskLD::flagsString() const
+{
+ std::string flagsStr;
+ for(const auto& flag : config.ldflags)
+ {
+ if(flag != config.ldflags[0])
+ {
+ flagsStr += " ";
+ }
+ flagsStr += flag;
+ }
+ flagsStr += "\n";
+
+ for(const auto& dep : config.depends)
+ {
+ if(dep != config.depends[0])
+ {
+ flagsStr += " ";
+ }
+ flagsStr += dep;
+ }
+
+ return flagsStr;
+}
diff --git a/src/task_ld.h b/src/task_ld.h
new file mode 100644
index 0000000..f56f00d
--- /dev/null
+++ b/src/task_ld.h
@@ -0,0 +1,42 @@
+// -*- c++ -*-
+#pragma once
+
+#include "task.h"
+
+#include <vector>
+#include <string>
+#include <future>
+#include <filesystem>
+
+struct BuildConfiguration;
+struct Settings;
+
+class TaskLD
+ : public Task
+{
+public:
+ TaskLD(const BuildConfiguration& config,
+ const Settings& settings,
+ const std::string& target,
+ const std::vector<std::string>& objects);
+
+ bool dirtyInner() override;
+
+ int runInner() override;
+ int clean() override;
+
+ std::vector<std::string> depends() const override;
+
+ std::string target() const override;
+
+private:
+ std::string flagsString() const;
+
+ std::vector<std::filesystem::path> objectFiles;
+ std::vector<std::filesystem::path> depFiles;
+ std::filesystem::path targetFile;
+ std::filesystem::path flagsFile;
+
+ const BuildConfiguration& config;
+ const Settings& settings;
+};
diff --git a/src/task_so.cc b/src/task_so.cc
new file mode 100644
index 0000000..eaf6a85
--- /dev/null
+++ b/src/task_so.cc
@@ -0,0 +1,217 @@
+#include "task_so.h"
+
+#include <iostream>
+#include <fstream>
+
+#include "libcppbuild.h"
+#include "settings.h"
+#include "execute.h"
+
+namespace
+{
+std::string readFile(const std::string &fileName)
+{
+ std::ifstream ifs(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate);
+
+ std::ifstream::pos_type fileSize = ifs.tellg();
+ ifs.seekg(0, std::ios::beg);
+
+ std::vector<char> bytes(fileSize);
+ ifs.read(bytes.data(), fileSize);
+
+ return std::string(bytes.data(), fileSize);
+}
+
+std::vector<std::string> addPrefix(const std::vector<std::string>& lst,
+ const Settings& settings)
+{
+ std::vector<std::string> out;
+ for(const auto& item : lst)
+ {
+ std::filesystem::path file = settings.builddir;
+ file /= item;
+ out.push_back(file.string());
+ }
+ return out;
+}
+} // namespace ::
+
+TaskSO::TaskSO(const BuildConfiguration& config,
+ const Settings& settings,
+ const std::string& target,
+ const std::vector<std::string>& objects)
+ : Task(config, addPrefix(config.depends, settings))
+ , config(config)
+ , settings(settings)
+{
+ targetFile = settings.builddir;
+ targetFile /= target;
+ for(const auto& object : objects)
+ {
+ std::filesystem::path objectFile = object;
+ objectFiles.push_back(objectFile);
+ dependsStr.push_back(objectFile.string());
+ }
+
+ for(const auto& dep : config.depends)
+ {
+ std::filesystem::path depFile = settings.builddir;
+ depFile /= dep;
+ depFiles.push_back(depFile);
+ }
+
+ flagsFile = settings.builddir / targetFile.stem();
+ flagsFile += ".flags";
+
+ target_type = TargetType::DynamicLibrary;
+ source_language = Language::C;
+ for(const auto& source : config.sources)
+ {
+ std::filesystem::path sourceFile(source);
+ if(sourceFile.extension().string() != ".c")
+ {
+ source_language = Language::Cpp;
+ }
+ }
+}
+
+bool TaskSO::dirtyInner()
+{
+ if(!std::filesystem::exists(targetFile))
+ {
+ return true;
+ }
+
+ if(!std::filesystem::exists(flagsFile))
+ {
+ return true;
+ }
+
+ for(const auto& objectFile : objectFiles)
+ {
+ if(std::filesystem::last_write_time(targetFile) <=
+ std::filesystem::last_write_time(objectFile))
+ {
+ return true;
+ }
+ }
+
+ {
+ auto lastFlags = readFile(flagsFile.string());
+ if(flagsString() != lastFlags)
+ {
+ //std::cout << "The compiler flags changed\n";
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int TaskSO::runInner()
+{
+ std::string objectlist;
+ for(const auto& objectFile : objectFiles)
+ {
+ if(!objectlist.empty())
+ {
+ objectlist += " ";
+ }
+ objectlist += objectFile.string();
+ }
+
+ std::vector<std::string> args;
+
+ args.push_back("-fPIC");
+ args.push_back("-shared");
+
+ args.push_back("-o");
+ args.push_back(targetFile.string());
+
+ for(const auto& objectFile : objectFiles)
+ {
+ args.push_back(objectFile.string());
+ }
+
+ for(const auto& depFile : depFiles)
+ {
+ args.push_back(depFile.string());
+ }
+
+ for(const auto& flag : config.ldflags)
+ {
+ args.push_back(flag);
+ }
+
+ { // Write flags to file.
+ std::ofstream flagsStream(flagsFile);
+ flagsStream << flagsString();
+ }
+
+ if(settings.verbose == 0)
+ {
+ std::cout << "LD => " << targetFile.string() << "\n";
+ }
+
+ auto tool = compiler();
+ return execute(tool, args, settings.verbose > 0);
+}
+
+int TaskSO::clean()
+{
+ if(std::filesystem::exists(targetFile))
+ {
+ std::cout << "Removing " << targetFile.string() << "\n";
+ std::filesystem::remove(targetFile);
+ }
+
+ if(std::filesystem::exists(flagsFile))
+ {
+ std::cout << "Removing " << flagsFile.string() << "\n";
+ std::filesystem::remove(flagsFile);
+ }
+
+ return 0;
+}
+
+std::vector<std::string> TaskSO::depends() const
+{
+ std::vector<std::string> deps;
+ for(const auto& objectFile : objectFiles)
+ {
+ deps.push_back(objectFile.string());
+ }
+
+ for(const auto& depFile : depFiles)
+ {
+ deps.push_back(depFile.string());
+ }
+
+ return deps;
+}
+
+std::string TaskSO::target() const
+{
+ return targetFile.string();
+}
+
+std::string TaskSO::flagsString() const
+{
+ std::string flagsStr = compiler();
+ for(const auto& flag : config.ldflags)
+ {
+ flagsStr += " " + flag;
+ }
+ flagsStr += "\n";
+
+ for(const auto& dep : config.depends)
+ {
+ if(dep != config.depends[0])
+ {
+ flagsStr += " ";
+ }
+ flagsStr += dep;
+ }
+
+ return flagsStr;
+}
diff --git a/src/task_so.h b/src/task_so.h
new file mode 100644
index 0000000..864d108
--- /dev/null
+++ b/src/task_so.h
@@ -0,0 +1,42 @@
+// -*- c++ -*-
+#pragma once
+
+#include "task.h"
+
+#include <vector>
+#include <string>
+#include <future>
+#include <filesystem>
+
+struct BuildConfiguration;
+struct Settings;
+
+class TaskSO
+ : public Task
+{
+public:
+ TaskSO(const BuildConfiguration& config,
+ const Settings& settings,
+ const std::string& target,
+ const std::vector<std::string>& objects);
+
+ bool dirtyInner() override;
+
+ int runInner() override;
+ int clean() override;
+
+ std::vector<std::string> depends() const override;
+
+ std::string target() const override;
+
+private:
+ std::string flagsString() const;
+
+ std::vector<std::filesystem::path> objectFiles;
+ std::vector<std::filesystem::path> depFiles;
+ std::filesystem::path targetFile;
+ std::filesystem::path flagsFile;
+
+ const BuildConfiguration& config;
+ const Settings& settings;
+};
diff --git a/src/tasks.cc b/src/tasks.cc
new file mode 100644
index 0000000..93e5a8b
--- /dev/null
+++ b/src/tasks.cc
@@ -0,0 +1,130 @@
+#include "tasks.h"
+
+#include <filesystem>
+#include <deque>
+#include <iostream>
+
+#include "settings.h"
+#include "libcppbuild.h"
+#include "task.h"
+#include "task_cc.h"
+#include "task_ld.h"
+#include "task_ar.h"
+#include "task_so.h"
+#include "rebuild.h"
+
+std::list<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config,
+ const Settings& settings,
+ const std::string& sourceDir)
+{
+ std::filesystem::path targetFile(config.target);
+
+ TargetType target_type{config.type};
+ if(target_type == TargetType::Auto)
+ {
+ if(targetFile.extension() == ".a")
+ {
+ target_type = TargetType::StaticLibrary;
+ }
+ else if(targetFile.extension() == ".so")
+ {
+ target_type = TargetType::DynamicLibrary;
+ }
+ else if(targetFile.extension() == "")
+ {
+ target_type = TargetType::Executable;
+ }
+ else
+ {
+ std::cerr << "Could not deduce target type from target " <<
+ targetFile.string() << " please specify.\n";
+ exit(1);
+ }
+ }
+
+ std::vector<std::string> objects;
+ std::list<std::shared_ptr<Task>> tasks;
+ for(const auto& file : config.sources)
+ {
+ tasks.emplace_back(std::make_shared<TaskCC>(config, settings,
+ sourceDir, file));
+ objects.push_back(tasks.back()->target());
+ }
+
+ switch(target_type)
+ {
+ case TargetType::Auto:
+ // The target_type cannot be Auto
+ break;
+
+ case TargetType::StaticLibrary:
+ tasks.emplace_back(std::make_shared<TaskAR>(config, settings, config.target,
+ objects));
+ break;
+
+ case TargetType::DynamicLibrary:
+ if(targetFile.stem().string().substr(0, 3) != "lib")
+ {
+ std::cerr << "Dynamic library target must have 'lib' prefix\n";
+ exit(1);
+ }
+ tasks.emplace_back(std::make_shared<TaskSO>(config, settings, config.target,
+ objects));
+ break;
+
+ case TargetType::Executable:
+ tasks.emplace_back(std::make_shared<TaskLD>(config, settings, config.target,
+ objects));
+ break;
+
+ case TargetType::Object:
+ break;
+ }
+
+ return tasks;
+}
+
+std::shared_ptr<Task> getNextTask(const std::list<std::shared_ptr<Task>>& allTasks,
+ std::list<std::shared_ptr<Task>>& dirtyTasks)
+{
+ for(auto dirtyTask = dirtyTasks.begin();
+ dirtyTask != dirtyTasks.end();
+ ++dirtyTask)
+ {
+ //std::cout << "Examining target " << (*dirtyTask)->target() << "\n";
+ if((*dirtyTask)->ready())
+ {
+ dirtyTasks.erase(dirtyTask);
+ return *dirtyTask;
+ }
+ }
+
+ //std::cout << "No task ready ... \n";
+ return nullptr;
+}
+
+std::list<std::shared_ptr<Task>> getTasks(const Settings& settings)
+{
+ static std::deque<BuildConfiguration> build_configs;
+ std::list<std::shared_ptr<Task>> tasks;
+ for(std::size_t i = 0; i < numConfigFiles; ++i)
+ {
+ std::string path =
+ std::filesystem::path(configFiles[i].file).parent_path().string();
+ if(settings.verbose > 1)
+ {
+ std::cout << configFiles[i].file << " in path " << path << "\n";
+ }
+ auto configs = configFiles[i].cb();
+ for(const auto& config : configs)
+ {
+ build_configs.push_back(config);
+ const auto& build_config = build_configs.back();
+ std::vector<std::string> objects;
+ auto t = taskFactory(build_config, settings, path);
+ tasks.insert(tasks.end(), t.begin(), t.end());
+ }
+ }
+
+ return tasks;
+}
diff --git a/src/tasks.h b/src/tasks.h
new file mode 100644
index 0000000..119c7d6
--- /dev/null
+++ b/src/tasks.h
@@ -0,0 +1,18 @@
+// -*- c++ -*-
+#pragma once
+
+#include <string>
+#include <list>
+#include <memory>
+
+#include "task.h"
+
+class BuildConfiguration;
+class Settings;
+
+std::list<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config,
+ const Settings& settings,
+ const std::string& sourceDir);
+std::shared_ptr<Task> getNextTask(const std::list<std::shared_ptr<Task>>& allTasks,
+ std::list<std::shared_ptr<Task>>& dirtyTasks);
+std::list<std::shared_ptr<Task>> getTasks(const Settings& settings);