From d3fb753fb2346390011e625c9b148fc0ba2ab953 Mon Sep 17 00:00:00 2001 From: Gregory Date: Thu, 18 Jul 2024 15:22:03 +0300 Subject: [PATCH] Added minimum functions to be able to build libregexis024 and it's tests --- .gitignore | 5 + README.txt | 19 + install.sh | 31 + regexis024_build_system.h | 1342 +++++++++++++++++++++++++++++++++++++ 4 files changed, 1397 insertions(+) create mode 100644 .gitignore create mode 100644 README.txt create mode 100755 install.sh create mode 100644 regexis024_build_system.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1737b2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea/ +# Never use CMAKE in production +CMakeLists.txt +cmake-build-debug/ + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..6351a3a --- /dev/null +++ b/README.txt @@ -0,0 +1,19 @@ +Build system for libregexis024 + +ABOUT + +This is a build system in a form of header-only C++ library. It supports generation and installation of pkg-config files for your project. + +INSTALLATION + +You don't need to compile anything, this is a header-only library (more precisely, it is an only-one-file-kind library) + +./install.sh [header installation path] [pkg-config file path] + +[header installation path] defaults to /usr/include +[pkg-config file path] defaults to /usr/lib/pkgconfig/regexis024-build-system.h + +HISTORY + +One day I realized that make is a complete garbage, cmake is even worse, and shell scripting language is too hard to use to write anything complex. +I had to invent my own build system for my project libregexis024. But regexis024_build_system can be used for any other C++ project just fine. diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..5249cf6 --- /dev/null +++ b/install.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +H_FILE="regexis024_build_system.h" + +if [ \! \( -f "$H_FILE" \) ]; then echo "Change cwd to root of build system source code root directory"; exit 1; fi + +usage(){ + echo "Usage: ./install.sh [header installation dir path] [pkg-config file installation path]" +} + +if [ $# -gt 2 ]; then usage; exit 1; fi + +H_INST_DIR="/usr/include" +PC_INST_FILE="/usr/lib/pkgconfig/regexis024-build-system.pc" + +if [ $# -ge 1 ]; then H_INST_DIR="$1"; fi +if [ $# -ge 2 ]; then PC_INST_FILE="$2"; fi + +cp "$H_FILE" "$H_INST_DIR/$H_FILE" + +if [ $? != 0 ]; then echo "Can't install"; exit 1; fi + +echo "Name: regexis024-build-system" > "$PC_INST_FILE" +echo "Description: Cool C++ build system" >> "$PC_INST_FILE" + +COOL_FLAGS="-Wall -pedantic -Wno-unused-variable -Wno-unused-but-set-variable -Werror=return-type -Wno-reorder" +COOL_FLAGS="$COOL_FLAGS -D _GLIBCXX_DEBUG -D _POSIX_C_SOURCE=200809L" +COOL_FLAGS="$COOL_FLAGS -g --std c++14" + +echo "Cflags: $COOL_FLAGS -I $H_INST_DIR" >> "$PC_INST_FILE" + diff --git a/regexis024_build_system.h b/regexis024_build_system.h new file mode 100644 index 0000000..bead176 --- /dev/null +++ b/regexis024_build_system.h @@ -0,0 +1,1342 @@ +#ifndef REGEXIS024_BUILD_SYSTEM_H +#define REGEXIS024_BUILD_SYSTEM_H + +/* This file is standalone from libregexis024 project (but it is related to it as it's dependency) */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class buildSystemFailure{ + std::string err; + std::string FILE; + std::string func; + int LINE; + +public: + buildSystemFailure(const std::string &err, const std::string &file, const std::string &func, int line) + : err(err), + FILE(file), + func(func), + LINE(line) { + } + + std::string toString() const { + char buf[4096]; + snprintf(buf, 4096, "Build error occured in function %s (line %d of %s)\n" + "Build error: %s", + func.c_str(), LINE, FILE.c_str(), err.c_str()); + return buf; + } +}; + +std::string prettyprint_errno(const std::string& pref) { + const char* d = strerrorname_np(errno); + return pref.empty() ? std::string(d) : std::string(pref) + ": " + d; +} + +#define THROW(err) throw buildSystemFailure((err), __FILE__, __func__, __LINE__) +#define THROW_on_errno(err) THROW(prettyprint_errno(err)) +#define THROW_on_errno_pl() THROW(prettyprint_errno("")) +#define ASSERT(cond, err) do { if (!(cond)) { THROW(err); } } while (0); +#define ASSERT_pl(cond) ASSERT(cond, "Failed assertion `" #cond "`") +#define ASSERT_on_iret(iret, err) ASSERT((iret) >= 0, prettyprint_errno(err)); +#define ASSERT_on_iret_pl(iret) ASSERT(iret >= 0, prettyprint_errno("")); + +bool does_str_end_in(const std::string& A, const std::string& B) { + if (A.size() < B.size()) + return false; + return A.substr(A.size() - B.size()) == B; +} + +std::string escape_string(const std::string& inp) { + std::string out; + for (char ch: inp) { + if (ch == '\\' || ch == '"' || ch == ' ') { + out += '\\'; + } + out += ch; + } + return out; +} + +std::string escape_with_doublequoting(const std::string& inp) { + std::string out = "\""; + for (char ch: inp) { + if (ch == '\\' || ch == '"') + out += '\\'; + out += ch; + } + out += '\"'; + return out; +} + +std::string join_string_arr(const std::vector& arr, const std::string& sep) { + std::string res; + bool e = false; + for (auto& el: arr) { + if (e) + res += sep; + else + e = true; + res += el; + } + return res; +} + +struct path_t { + bool is_relative = true; + std::vector parts; + + path_t(const char* path) { + size_t i = 0; + if (path[i] == 0) + return; + if (path[i] == '/') { + is_relative = false; + i++; + } + parts.emplace_back(); + while (path[i] != 0) { + if (path[i] == '/') { + if (parts.back().empty()) { + } else if (parts.back() == ".") { + parts.back().clear(); + } else { + parts.emplace_back(); + } + } else { + parts.back() += path[i]; + } + i++; + } + if (parts.back().empty() || parts.back() == ".") { + parts.pop_back(); + } + } + + path_t(const std::string& path) : path_t(path.c_str()){} + + operator std::string() const { + std::string res; + for (auto& el: parts) { + if (!res.empty() || !is_relative) + res += "/"; + res += el; + } + return res; + } + + path_t operator/(const path_t& other) const { + if (!other.is_relative) + return other; + path_t me(*this); + for (auto& el: other.parts) + me.parts.push_back(el); + return me; + } + + void operator/=(const path_t& other) { + if (other.is_relative) { + for (auto& el: other.parts) + parts.push_back(el); + } else { + *this = other; + } + } +}; + +void createDir(const path_t& path) { + std::string cur_pref = std::string(path.is_relative ? "" : "/"); + for (size_t i = 0; i < path.parts.size(); i++) { + cur_pref += path.parts[i] + "/"; + struct stat info; + errno = 0; + stat(cur_pref.c_str(), &info); + if (errno == ENOENT) { + /* User : reads, writes, executes; Other: reads, executes; */ + int ret = mkdir(cur_pref.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + ASSERT_on_iret(ret, "mkdir(\"" + cur_pref + "\")"); + } else if (errno == 0) { + if (S_ISDIR(info.st_mode)) { + continue; + } + THROW("prefix \"" + cur_pref + "\" of destination path is preoccupied by entry that is not directory"); + } else { + THROW_on_errno("stat(\"" + cur_pref + "\")"); + } + + } +} + +struct fsDetourFrame { + std::string path; + bool has_expanded = false; + + explicit fsDetourFrame(const std::string &path) : path(path) {} +}; + +template +using uptr = std::unique_ptr; + +void deleteEntity(const std::string& path) { + int ret; + static constexpr size_t mem_limit = 10000; + std::vector> cs; + cs.emplace_back(new fsDetourFrame(path)); + while (!cs.empty()) { + fsDetourFrame& cur = *cs.back(); + if (cur.has_expanded) { + ret = rmdir(cur.path.c_str()); + ASSERT_on_iret(ret, "rmdir(\"" + cur.path + "\")"); + /* cur invalidated */ + cs.pop_back(); + } else { + struct stat info; + ret = stat(cur.path.c_str(), &info); + ASSERT_on_iret(ret, "stat(\"" + cur.path + "\")"); + if (S_ISDIR(info.st_mode)) { + DIR* D = opendir(cur.path.c_str()); + ASSERT(D != NULL, prettyprint_errno("opendir(\"" + cur.path +"\")")); + while (true) { + errno = 0; + struct dirent* Dent = readdir(D); + if (Dent == NULL) { + if (closedir(D) != 0) + throw std::runtime_error("closedir"); + if (errno == 0) + break; + THROW_on_errno("dirent in \"" + cur.path + "\""); + } + std::string child_entry = Dent->d_name; + if (child_entry != "." && child_entry != "..") { + if (cs.size() >= mem_limit) { + THROW("deleteEntity is deleting a very big entity"); + } + cs.emplace_back(new fsDetourFrame(cur.path + "/" + child_entry)); + } + } + cur.has_expanded = true; + } else if (S_ISREG(info.st_mode)) { + ret = unlink(cur.path.c_str()); + ASSERT_on_iret(ret, "unlink(\"" + cur.path + "\")"); + /* cur invalidated */ + cs.pop_back(); + } else { + THROW("unknown filetype of file \"" + cur.path + "\""); + } + } + } +} + +/* Recursively delete a directory `path` and nothing else. If `path` leads nowhere, task + * is considered successfully done. If `path` is not a directory, error arises */ +void ensureDirDeletion(const std::string& path) { + errno = 0; + struct stat info; + int ret = stat(path.c_str(), &info); + if (errno == ENOENT) + return; + ASSERT_on_iret(ret, "stat(\"" + path + "\")"); + ASSERT(S_ISDIR(info.st_mode), "Not a directory"); + deleteEntity(path); +} + +/* Delete empty directory `path` and nothing else. If `path` leads nowhere, task + * is considered successfully done. If `path` is not an empty directory, error arises */ +void enusreEmptyDirDeletion(const std::string& path) { + errno = 0; + int ret = rmdir(path.c_str()); + if (ret < 0) { + if (errno == ENOENT) + return; + THROW_on_errno("rmdir(\"" + path + "\")"); + } +} + +/* Delete a file `path` and nothing else. If `path` leads nowhere, task + * is considered successfully done. If `path` is not a file, error arises */ +void ensureFileDeletion(const std::string& path) { + errno = 0; + struct stat info; + int ret = stat(path.c_str(), &info); + if (errno == ENOENT) + return; + ASSERT_on_iret(ret, "stat(\"" + path + "\")"); + ASSERT(S_ISREG(info.st_mode), "Not a regular file"); + ret = unlink(path.c_str()); + ASSERT_on_iret(ret, "unlink(\"" + path + "\")"); +} + +void checkFoldernessOfDir(const std::string& path) { + struct stat info; + int ret = stat(path.c_str(), &info); + ASSERT_on_iret(ret, "stat(\"" + path + "\")"); + ASSERT(S_ISDIR(info.st_mode), "Not a directory: \"" + path + "\" is not a directory."); +} + +void checkRegularityOfFile(const std::string& path) { + struct stat info; + int ret = stat(path.c_str(), &info); + ASSERT_on_iret(ret, "stat(\"" + path + "\")"); + ASSERT(S_ISREG(info.st_mode), "Not a file: \"" + path + "\" is not a file."); +} + +/* result += read(fd); Argument description is for error handling */ +void readFromFileDescriptor(int fd, std::string& result, const std::string& description = "") { + int ret; + char buf[2048]; + while ((ret = read(fd, buf, 2048)) > 0) { + size_t oldN = result.size(); + result.resize(oldN + ret); + memcpy(&result[oldN], buf, ret); + } + ASSERT_on_iret(ret, "Reading from " + description); +} + +void readFile(const std::string& path, std::string& result) { + int fd = open(path.c_str(), O_RDONLY); + ASSERT_on_iret(fd, "Opening \"" + path + "\""); + readFromFileDescriptor(fd, result, "file \"" + path + "\""); + close(fd); +} + +/* write(fd, text); close(fd); Argument description is for error handling */ + +void writeToFileDescriptor(int fd, const std::string& text, const std::string& description = "") { + size_t n = text.size(); + size_t i = 0; + while (i < n) { + size_t block = std::min(2048lu, n - i); + int ret = write(fd, &text[i], block); + ASSERT_on_iret(ret, "Writing to" + description); + i += ret; + } + close(fd); +} + +/* Truncational */ +void writeFile(const std::string& path, const std::string& text) { + int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0770); + ASSERT_on_iret(fd, "Opening \"" + path + "\""); + writeToFileDescriptor(fd, text, "file \"" + path + "\n"); +} + +struct CommandReturnCode { + bool terminated_by_signal = false; + /* means exit code if !terminated_by_signal, means signal code if terminated_by_signal */ + int code; + + bool isOk() const { + return !terminated_by_signal && code == 0; + } + + CommandReturnCode(bool terminated_by_signal, int code): terminated_by_signal(terminated_by_signal), code(code) {} +}; + +typedef std::vector subprocess_command_t; + +std::string prettyprint_command(const subprocess_command_t& cmd) { + std::string res; + for (auto& arg: cmd) { + if (!res.empty()) + res += ' '; + bool found_dangerous = false; + for (char el: arg) + found_dangerous |= (el == '\\') | (el == '"') | (el == ' '); + if (found_dangerous) { + res += '"'; + for (char el: arg) { + if (el == '\\' || el == '"') + res += '\\'; + res += el; + } + res += '"'; + } else { + res += arg; + } + } + return res; +} + +/* Used in functions that spawn one subprocess and wait for it */ +CommandReturnCode wait_for_subprocess(pid_t pid) { + int wstatus = 0; + int ret = waitpid(pid, &wstatus, 0); + if (ret < 0) + throw std::runtime_error("waitpid(" + std::to_string(pid) + ")"); + if (WIFEXITED(wstatus)) { + return {false, WEXITSTATUS(wstatus)}; + } + if (WIFSIGNALED(wstatus)) { + return {true, WTERMSIG(wstatus)}; + } + THROW("Unknown reason of termination of subprocess"); +} + +void checkArgumentsForSubprocess(const subprocess_command_t& args) { + ASSERT(!args.empty(), "program to execute is unspecified"); + for (auto& string: args) + for (char el: string) + ASSERT(el != 0, "Bad argument to sub-process"); +} + +CommandReturnCode executeCommand(const subprocess_command_t& args) { + checkArgumentsForSubprocess(args); + size_t n = args.size(); + std::vector cnv(n + 1, NULL); + for (size_t i = 0; i < n; i++) { + cnv[i] = const_cast(args[i].data()); + } + /* Doing some forkussy todo: learn how to use clone */ + pid_t pid = fork(); + ASSERT_on_iret(pid, "Forking ") + if (pid == 0) { + execvp(cnv[0], cnv.data()); + _exit(2); + } + return wait_for_subprocess(pid); +} + +void prolozhit_trubu(int pfds[2]) { + int ret = pipe(pfds); + ASSERT_on_iret(ret, "pipe creation"); +} + +/* This duo is used inside subprocess before calling execvp, so exceptions have no right to be thrown */ +void substitute_input_in_subprocess_with_pipe(int pipi[2], int fd = STDIN_FILENO) { + close(pipi[1]); + dup2(pipi[0], fd); +} +void substitute_output_in_subprocess_with_pipe(int pipi[2], int fd = STDOUT_FILENO) { + close(pipi[0]); + dup2(pipi[1], fd); +} + + +/* std_output, std_error += out, errors */ +CommandReturnCode executeCommand_and_save_output(const std::vector& args, + std::string& std_output, std::string& std_error) +{ + checkArgumentsForSubprocess(args); + size_t n = args.size(); + std::vector cnv(n + 1, NULL); + for (size_t i = 0; i < n; i++) { + cnv[i] = const_cast(args[i].data()); + } + int pipe_for_stdout[2]; + int pipe_for_stderr[2]; + prolozhit_trubu(pipe_for_stdout); + prolozhit_trubu(pipe_for_stderr); + pid_t pid = fork(); + ASSERT_on_iret(pid, "Forking ") + if (pid == 0) { + substitute_output_in_subprocess_with_pipe(pipe_for_stdout); + substitute_output_in_subprocess_with_pipe(pipe_for_stderr, STDERR_FILENO); + execvp(cnv[0], cnv.data()); + _exit(2); + } + close(pipe_for_stdout[1]); + close(pipe_for_stderr[1]); + readFromFileDescriptor(pipe_for_stdout[0], std_output, "stdout of subprocess"); + readFromFileDescriptor(pipe_for_stderr[0], std_error, "stderr of subprocess"); + return wait_for_subprocess(pid); +} + +/* std_output, std_error += out, errors */ +CommandReturnCode executeCommand_imulating_whole_input_and_save_output(const std::vector& args, + const std::string& std_input, std::string& std_output, std::string& std_error) +{ + checkArgumentsForSubprocess(args); + size_t n = args.size(); + std::vector cnv(n + 1, NULL); + for (size_t i = 0; i < n; i++) { + cnv[i] = const_cast(args[i].data()); + } + int pipe_for_stdin[2]; + int pipe_for_stdout[2]; + int pipe_for_stderr[2]; + prolozhit_trubu(pipe_for_stdin); + prolozhit_trubu(pipe_for_stdout); + prolozhit_trubu(pipe_for_stderr); + pid_t pid = fork(); + ASSERT_on_iret(pid, "Forking ") + if (pid == 0) { + substitute_input_in_subprocess_with_pipe(pipe_for_stdin); + substitute_output_in_subprocess_with_pipe(pipe_for_stdout); + substitute_output_in_subprocess_with_pipe(pipe_for_stderr, STDERR_FILENO); + execvp(cnv[0], cnv.data()); + _exit(2); + } + close(pipe_for_stdin[0]); + close(pipe_for_stdout[1]); + close(pipe_for_stderr[1]); + /* Yeah, I COULD use some kind of polling to read and write simultaneously, but who cares */ + writeToFileDescriptor(pipe_for_stdin[1], std_input, "stdin of subprocess"); + readFromFileDescriptor(pipe_for_stdout[0], std_output, "stdout of subprocess"); + readFromFileDescriptor(pipe_for_stderr[0], std_error, "stderr of subprocess"); + return wait_for_subprocess(pid); +} + +/* A += B */ +void array_concat(std::vector& A, const std::vector& B) { + for (auto& str: B) + A.push_back(str); +} + +/* First is a list of g++ cli options that is getting populated by opts array * + * A += B */ +void gxx_add_cli_options(std::vector& dest, const std::vector& opts) { + for (auto& str: opts) + dest.push_back(str); +} + +/* First is a list of g++ cli options that is getting populated by `opts` array of defines * + * A += flatMap(B, x -> ("-D" x)) */ +void gxx_add_cli_defines(std::vector& dest, const std::vector& opts) { + for (auto& str: opts) { + dest.push_back("-D"); + dest.push_back(str); + } +} + +/* First is a list of g++ cli options that is getting populated by `opts` array of defines * + * A += flatMap(B, x -> ("-I" x)) */ +void gxx_add_cli_includes(std::vector& dest, const std::vector& opts) { + for (auto& str: opts) { + dest.push_back("-I"); + dest.push_back(str); + } +} + +struct ExpectedFSEntityState { + std::string path; + mode_t mode; + + ExpectedFSEntityState(const std::string &path, mode_t mode) : path(path), mode(mode) {} +}; + +/* General case */ +void checkFsEntity(const ExpectedFSEntityState& request) { + struct stat info; + int ret = stat(request.path.c_str(), &info); + ASSERT_on_iret(ret, "stat of \"" + request.path + "\""); + ASSERT_pl((info.st_mode & S_IFMT) == request.mode); +} + + +struct BuildUnit { + std::string type; + + std::vector all_fs_dependencies; + std::vector all_fs_results; + + /* Build unit dependencies are identidied by their index in array */ + std::vector bu_dependencies; + + BuildUnit(const std::string &type, const std::vector &all_fs_dependencies, + const std::vector &all_fs_results, const std::vector &bu_dependencies) + : type(type), + all_fs_dependencies(all_fs_dependencies), + all_fs_results(all_fs_results), + bu_dependencies(bu_dependencies) { + } + + BuildUnit(): type("blank"){} + + virtual void execute() const { } + + virtual std::string functionToString() const { return ""; } + + virtual ~BuildUnit(){}; +}; + +struct MkdirBuildUnit: public BuildUnit { + path_t dir_path; + + MkdirBuildUnit(const path_t &dir_path) + : BuildUnit("mkdir", {}, {ExpectedFSEntityState((std::string)dir_path, S_IFDIR)}, {}), dir_path(dir_path) {} + + void execute() const override { + createDir(dir_path); + } + + std::string functionToString() const override { + return "mkdir " + (std::string)dir_path; + } +}; + +struct SubprocessedBuildUnit: public BuildUnit { + std::vector build_command; + + SubprocessedBuildUnit(const std::string &type, const std::vector &all_fs_dependencies, + const std::vector &all_fs_results, const std::vector &bu_dependencies, + const std::vector &build_command) + : BuildUnit(type, all_fs_dependencies, all_fs_results, bu_dependencies), + build_command(build_command) { + } + + void execute() const override { + CommandReturnCode ret = executeCommand(build_command); + if (!ret.isOk()) { + if (ret.terminated_by_signal) + THROW("Command " + prettyprint_command(build_command) + " received signal " + std::to_string(ret.code)); + THROW("Command " + prettyprint_command(build_command) + " exited with error code " + std::to_string(ret.code)); + } + } + + std::string functionToString() const override { + return prettyprint_command(build_command); + } +}; + +struct FileWriteBuildUnit: public BuildUnit { + path_t filepath; + std::string text; + + FileWriteBuildUnit(const path_t &filepath, const std::string &text) : + BuildUnit("touch", {}, + {ExpectedFSEntityState(filepath, S_IFREG)}, + {}), + filepath(filepath),text(text) { + } + + void execute() const override { + ASSERT(!filepath.parts.empty(), "Bad installation destination"); + path_t where = filepath; + where.parts.pop_back(); + createDir(where); + writeFile(filepath, text); + } + + std::string functionToString() const override { + return "filling " + (std::string)filepath + (text.size() > 3000 ? "" : " with text \n" + text); + } +}; + +struct FileInstallBuildUnit: public BuildUnit { + path_t source; + path_t destination; + + FileInstallBuildUnit(const path_t& source, const path_t& destination): source(source), destination(destination), + BuildUnit("file-install", {ExpectedFSEntityState(source, S_IFREG)}, + {ExpectedFSEntityState(destination, S_IFREG)}, {}) { + } + + void execute() const override { + ASSERT(!destination.parts.empty(), "Bad installation destination"); + path_t where = destination; + where.parts.pop_back(); + createDir(where); + // todo, fix that func and writeFile() so that output file is set to source size at the beginning + int rfd = open(((std::string)source).c_str(), O_RDONLY); + ASSERT_on_iret(rfd, "opening source file to read"); + int wfd = open(((std::string)destination).c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0770); + ASSERT_on_iret(wfd, "opening destination file to write"); + char buf[2048]; + int sz; + while ((sz = read(rfd, buf, 2048)) > 0) { + int wrtn = 0; + while (wrtn < sz) { + int ret = write(wfd, buf, sz - wrtn); + ASSERT_on_iret(ret, "writing"); + ASSERT_pl(ret > 0); + wrtn += ret; + } + + } + ASSERT_on_iret(sz, "reading"); + close(rfd); + close(wfd); + } + + std::string functionToString() const override { + return "cp " + std::string(source) + " " + std::string(destination); + } +}; + +std::string prettyprint_build_unit(const BuildUnit& build_unit) { + std::string fnc = build_unit.functionToString(); + return "[" + build_unit.type + "]" + (fnc.empty() ? "" : " " + fnc); +} + +std::string prettyprint_outofboundness(size_t ind, size_t sz) { + return "(" + std::to_string(ind) + " >= " + std::to_string(sz) + ")"; +} + +/* circular_dependency_result is filled in case of circular dependency */ +void topsort(std::vector& result, std::vector& circular_dependency_result, + const std::vector& entry_points, const std::vector>& depgraph) +{ + size_t N = depgraph.size(); + for (size_t e: entry_points) + ASSERT(e < N, "bad entry point to tosorted task list " + prettyprint_outofboundness(e, N)); + for (size_t i = 0; i < N; i++) + for (size_t ch: depgraph[i]) + ASSERT(ch < N, "bad dependency point to tosorted task list " + prettyprint_outofboundness(ch, N)); + for (size_t i = 0; i < N; i++) + for (size_t ch: depgraph[i]) + if (ch == i) { + circular_dependency_result = {i}; + return; + } + std::vector status(N, 0); + struct topsortDfsFrame { + size_t v; + size_t i = 0; + explicit topsortDfsFrame(size_t v): v(v){} + }; + for (size_t st: entry_points) { + if (status[st] == 2) + continue; + std::vector callStack = {topsortDfsFrame(st)}; + bool cycle_exception = false; + /* Used only if cycle_exception flag is set */ + size_t problem_end = 0; + while (!callStack.empty()) { + size_t v = callStack.back().v; + size_t i = callStack.back().i; + if (cycle_exception) { + circular_dependency_result.push_back(v); + if (problem_end == v) { + break; + } + callStack.pop_back(); + continue; + } + if (i == 0) { + assert(status[v] == 0); + status[v] = 1; + } + if (i == depgraph[v].size()) { + assert(status[v] == 1); + status[v] = 2; + result.push_back(v); + callStack.pop_back(); + continue; + } + size_t u = depgraph[v][i]; + if (status[u] == 1) { + cycle_exception = true; + problem_end = u; + circular_dependency_result.push_back(v); + callStack.pop_back(); + // continued + } else { + callStack.back().i++; + if (status[u] == 0) + callStack.push_back(topsortDfsFrame(u)); + } + } + if (cycle_exception) + break; + } + + /* Right now circular dependency list is structured this way: [0] is required by [1], which is required by [2]... */ + std::reverse(circular_dependency_result.begin(), circular_dependency_result.end()); + /* Now this array is structured like this: [0] requires [1], which requires [2] ... and [-1] requires [0] */ +} + +int _utf8_retrieve_size(uint8_t firstByte) { + if (!(firstByte & 0b10000000)) + return 1; + uint8_t a = 0b11000000; + uint8_t b = 0b00100000; + for (int i = 2; i <= 4; i++){ + if ((firstByte & (a | b)) == a) + return i; + a |= b; + b >>= 1; + } + return -1; +} + +int32_t _utf8_retrieve_character(int sz, size_t pos, const uint8_t *string) { + if (sz == 1) + return string[pos]; + uint32_t v = string[pos] & (0b01111111 >> sz); + pos++; + for (int i = 1; i < sz; i++){ + uint32_t th = string[pos]; + if ((th & 0b11000000) != 0b10000000) + return -1; + v <<= 6; + v |= (th & 0b00111111); + pos++; + } + assert(v <= INT32_MAX); + return static_cast(v); +} + +void utf8_string_iterat(int32_t &cp, size_t &adj, size_t pos, const uint8_t *string, size_t string_size) { + if (pos >= string_size) {cp = -1; return;} + adj = _utf8_retrieve_size(string[pos]); + if (adj < 0 || pos + adj > string_size) {cp = -1; return;} + if ((cp = _utf8_retrieve_character(adj, pos, string)) < 0) {cp = -1; return;} +} + +size_t text_line_width(const std::string& str) { + size_t pos = 0; + size_t sz = 0; + while (pos < str.size()) { + int32_t code; size_t adj; + utf8_string_iterat(code, adj, pos, reinterpret_cast(str.data()), str.size()); + if (code < 0) + return 0; + sz++; + pos += adj; + } + return sz; +} + +std::string strMul(size_t n, const char* str) { + std::string res; + for (size_t i = 0; i < n; i++) + res += str; + return res; +} + +std::string prettyprint_cyclic_dependency(const std::vector& lines) { + static const char* arrow_right_to_down = "\u2B10"; + static const char* line_corner_top_right = "\u2510"; + static const char* line_corner_bottom_right = "\u2518"; + static const char* line_vertical = "\u2502"; + static const char* line_horizontal = "\u2500"; + + if (lines.empty()) + return ""; + if (lines.size() == 1) { + return lines[0] + " requires itself\n"; + } + size_t max_line = 0; + size_t n = lines.size(); + std::vector new_lines(n + 1); + for (size_t i = 0; i < n; i++) { + new_lines[i + 1] += lines[i]; + new_lines[i + 1] += i == 0 ? " requires" : ", which requires"; + max_line = std::max(max_line, text_line_width(new_lines[i + 1])); + } + new_lines[0] = arrow_right_to_down + strMul(max_line, line_horizontal) + line_corner_top_right; + for (size_t i = 1; i + 1 < n + 1; i++) { + new_lines[i] += strMul(max_line - text_line_width(new_lines[i]) + 1, " ") + line_vertical; + } + new_lines[n] += " " + strMul(max_line - text_line_width(new_lines[n]), line_horizontal) + line_corner_bottom_right; + std::string res; + for (auto& line: new_lines) + res += line + "\n"; + return res; +} + +struct BuildUnitsArray { + std::vector> tasks; + std::vector entry_points; +}; + +void complete_tasks_of_build_units (const BuildUnitsArray& arr) +{ + size_t N = arr.tasks.size(); + std::vector execution_order; + std::vector cyc_dep_problem; + std::vector> depgraph(N); + for (size_t i = 0; i < N; i++) + depgraph[i] = arr.tasks[i]->bu_dependencies; + topsort(execution_order, cyc_dep_problem, arr.entry_points, depgraph); + if (!cyc_dep_problem.empty()) { + std::vector bad_units(cyc_dep_problem.size()); + for (size_t i = 0; i < cyc_dep_problem.size(); i++) { + bad_units[i] = prettyprint_build_unit(*arr.tasks[cyc_dep_problem[i]]) + " "; + } + THROW("Cyclic dependency of build units was found:\n" + prettyprint_cyclic_dependency(bad_units)); + } + ASSERT(execution_order.size() == N, std::to_string(N - execution_order.size()) + " build units are unreachable in dependency tree"); + + for (size_t I: execution_order) { + printf("Executing %s\n", prettyprint_build_unit(*arr.tasks[I]).c_str()); + for (auto& rqd: arr.tasks[I]->all_fs_dependencies) + checkFsEntity(rqd); + arr.tasks[I]->execute(); + for (auto& rqr: arr.tasks[I]->all_fs_results) + checkFsEntity(rqr); + } +} + +/* Suppose A executable links to library B, which links to library C + * library C tells B which flags to add (like -I and -L) to be able to use C. + * Library B in it's dependency list can specify that whichever executable uses B, it should + * not only use flags that include B, but also flags that include C. Thus, these options are only useful when + * used to describe dependencies of project's library. + */ +struct CTargetDependenceOnLibraryFPass { + bool pass_compilational_flags = false; + bool pass_linkage_flags = false; +}; + +struct CTargetDependenceOnProjectsLibrary { + CTargetDependenceOnLibraryFPass passing_flags; + std::string project_library_target; + + explicit CTargetDependenceOnProjectsLibrary(const std::string& target_lib): project_library_target(target_lib){} + explicit CTargetDependenceOnProjectsLibrary (const std::string& target_lib, bool pass_comp_flags, bool pass_lib_flags): + project_library_target(target_lib), passing_flags{pass_comp_flags, pass_lib_flags} {} +}; + +struct CTargetDependenceOnExternalLibrary { + CTargetDependenceOnLibraryFPass passing_flags; + std::string external_library_name; + + explicit CTargetDependenceOnExternalLibrary(const std::string &ext_lib): external_library_name(ext_lib) {} + explicit CTargetDependenceOnExternalLibrary(const std::string &ext_lib, bool pass_comp_flags, bool pass_lib_flags): + external_library_name(ext_lib), passing_flags{pass_comp_flags, pass_lib_flags} {} +}; + +struct ExternalLibraryData { + std::vector compilation_flags; + std::vector linkage_flags; +}; + +struct ExternalLibraryTarget { + std::string name; + ExternalLibraryData data; +}; + +struct CTarget { + /* Must not contain / . , */ + std::string name; + std::string type; + std::vector external_deps; + std::vector proj_deps; + + std::vector additional_compilation_flags; + std::vector additional_linkage_flags; + + /* .c and .cpp source files relative to the special src directory*/ + std::vector units; + + std::string include_pr; + std::string include_ir; + /* At compile time these .h files are relative to include_pr, at installation they are copied relative to include_ir */ + std::vector exported_headers; + + std::string installation_dir; + /* If empty, no .pc file will be created. Otherwise, must include filename */ + std::string pc_output_path; + std::string description; + + bool entry_point = true; + + CTarget(const std::string& name, const std::string& type): name(name), type(type){} +}; + +void check_is_good_name_1(const std::string& name) { + for (char ch: name) { + ASSERT(ch != ':' && ch != '/' && ch != ',', "bad name \"" + name + "\""); + } + ASSERT(name != "" && name != "." && name != "..", "bad name \"" + name + "\""); +} + +void check_target_name(const std::string& name) { + check_is_good_name_1(name); + ASSERT(name != "obj", "DON'T YOU NEVER EVER CALL YOUR TARGET obj") +} + +void check_is_clean_path_1(const path_t& path) { + ASSERT_pl(path.is_relative); + ASSERT_pl(!path.parts.empty()); + for (const std::string& str: path.parts) + check_is_good_name_1(str); +} + +void check_c_unit_name(const path_t& P) { + check_is_clean_path_1(P); + const std::string& filename = P.parts.back(); + ssize_t ld = (ssize_t)filename.size() - 1; + for (; ld >= 0; ld--) { + if (filename[ld] == '.') { + break; + } + } + ASSERT(ld > 0, "Bad c compilation unit name \"" + (std::string)P + "\""); + ASSERT(P.parts.back().substr(ld) == ".cpp", "Right now only c++ is supported"); +} + +void check_pkg_conf_rel_install_path(const path_t& P) { + ASSERT_pl(P.is_relative && !P.parts.empty()); + ASSERT_pl(does_str_end_in(P.parts.back(), ".pc")); +} + +/* Argument `name` is relative to project_src_dir, return value is relative_to $IR/$TARGET_NAME/obj */ +path_t c_unit_name_to_obj_filename(const std::string& PtoC) { + path_t P(PtoC); + assert(!P.parts.empty()); + std::string& filename = P.parts.back(); + ssize_t ld = (ssize_t)filename.size() - 1; + for (; ld >= 0; ld--) { + if (filename[ld] == '.') { + break; + } + } + assert(ld > 0); + P.parts.back() = P.parts.back().substr(0, ld); + P.parts.back() += ".o"; + return P; +} + +void load_ctargets_on_building_and_installing( + const std::vector& ext_lib_targs, + const std::vector& proj_targs, + BuildUnitsArray& ret_at_build, + BuildUnitsArray& ret_at_install, + const std::string& proj_src_dir_path, + const std::string& proj_compiled_dir_path, + const std::string& install_include_dir_path, + const std::string& install_lib_dir_path, + const std::string& install_bin_dir_path, + const std::string& install_pkgconfig_dir_path) +{ + ret_at_build.tasks.clear(); + ret_at_build.entry_points.clear(); + std::map ext_libs_map; + for (auto& e: ext_lib_targs) { + check_target_name(e.name); + ASSERT(ext_libs_map.count(e.name) == 0, "external target " + e.name + " was repeated"); + ext_libs_map[e.name] = e.data; + } + struct S { + /* Main build unit of target in "build" runrevel */ + size_t end_BBU_id; + /* Main build unit of target in "install" runlevel */ + size_t end_IBU_id; + /* When this ctarget is used as dependency, these flags should be used aquire my ctarget as dependency */ + std::vector emitted_compilation_flags_USED_HERE; + std::vector emitted_compilation_flags_PASSED_FORWARD; + std::vector emitted_linkage_flags_USED_HERE; + std::vector emitted_linkage_flags_PASSED_FORWARD; + S() = default; + explicit S(size_t end_bu_id): end_BBU_id(end_bu_id) {} + }; + std::map before; + auto add_bbu = [&](BuildUnit* obj) -> size_t { + ret_at_build.tasks.emplace_back(obj); + return ret_at_build.tasks.size() - 1; + }; + auto add_ibu = [&](BuildUnit* obj) -> size_t { + ret_at_install.tasks.emplace_back(obj); + return ret_at_install.tasks.size() - 1; + }; + for (auto& tg: proj_targs) { + check_target_name(tg.name); + ASSERT(before.count(tg.name) == 0, "projects' target " + tg.name + " was repeated"); + for (auto& ed: tg.external_deps) { + ASSERT(ext_libs_map.count(ed.external_library_name) == 1, + "Unknown external dependency " + ed.external_library_name + " of target " + tg.name); + } + for (auto& pd: tg.proj_deps) { + ASSERT(before.count(pd.project_library_target) == 1, "No such library " + pd.project_library_target); + } + size_t mk_personal_targ_dir_bu_id = add_bbu(new MkdirBuildUnit(path_t(proj_compiled_dir_path) / tg.name)); + std::vector all_comp_units_bu_ids; + auto BU_to_SOURCE_FILEPATH = [&](const std::string& bu) -> path_t { + return path_t(proj_src_dir_path) / bu; + }; + auto BU_to_OBJ_FILEPATH = [&](const std::string& bu) -> path_t { + return path_t(proj_compiled_dir_path) / tg.name / "obj" / c_unit_name_to_obj_filename(bu); + }; + auto generate_cu_BUs = [&](const std::vector& ctg_type_intrinsic_comp_args) { + const std::string comp_cmd = "g++"; // todo: think of some other way around + for (const std::string& bu: tg.units) { + check_c_unit_name(bu); + path_t buDir = bu; + buDir.parts.pop_back(); + /* For each compilation unit there are two build units: first is to make an output directory */ + size_t mkdir_bu_id = add_bbu(new MkdirBuildUnit(path_t(proj_compiled_dir_path) / tg.name / "obj" / buDir)); + path_t source_filepath = BU_to_SOURCE_FILEPATH(bu); + path_t obj_filepath = BU_to_OBJ_FILEPATH(bu); + path_t proj_include_dirpath = path_t(proj_src_dir_path) / tg.include_pr; + + /* Second buid unit (that depends on the firsts) invokes the compiler */ + std::vector comp_cmd_full = {comp_cmd, "-c", "-o", obj_filepath}; + gxx_add_cli_options(comp_cmd_full, ctg_type_intrinsic_comp_args); + gxx_add_cli_options(comp_cmd_full, tg.additional_compilation_flags); + gxx_add_cli_includes(comp_cmd_full, {proj_include_dirpath}); + for (const auto& external_dep: tg.external_deps) { + gxx_add_cli_options(comp_cmd_full, ext_libs_map[external_dep.external_library_name].compilation_flags); + } + for (const auto& internal_dep: tg.proj_deps) { + gxx_add_cli_options(comp_cmd_full, before[internal_dep.project_library_target].emitted_compilation_flags_USED_HERE); + } + comp_cmd_full.push_back(source_filepath); + size_t compilation_bu_id = add_bbu(new SubprocessedBuildUnit("compilaion", + {ExpectedFSEntityState(source_filepath, S_IFREG), ExpectedFSEntityState(proj_include_dirpath, S_IFDIR)}, + {ExpectedFSEntityState(obj_filepath, S_IFREG)}, + {mkdir_bu_id}, + comp_cmd_full)); + all_comp_units_bu_ids.push_back(compilation_bu_id); + } + }; + + /* Initialized by generate_targ_link_BU */ + size_t targ_FINAL_bbu_id; + auto generate_targ_link_BU = [&](const std::vector& ctg_type_intrinsic_link_args, + const std::string& resuting_bin_extension) + { + std::string link_cmd = "g++"; + path_t resulting_binary_filepath = path_t(proj_compiled_dir_path) / tg.name / (tg.name + resuting_bin_extension); + std::vector full_linking_cmd = {"g++", "-o", resulting_binary_filepath}; + gxx_add_cli_options(full_linking_cmd, ctg_type_intrinsic_link_args); + gxx_add_cli_options(full_linking_cmd, tg.additional_linkage_flags); + for (const std::string& bu: tg.units) { + full_linking_cmd.push_back(BU_to_OBJ_FILEPATH(bu)); + } + for (auto& external_dep: tg.external_deps) { + gxx_add_cli_options(full_linking_cmd, ext_libs_map[external_dep.external_library_name].linkage_flags); + } + for (auto& internal_dep: tg.proj_deps) { + gxx_add_cli_options(full_linking_cmd, before[internal_dep.project_library_target].emitted_linkage_flags_USED_HERE); + } + SubprocessedBuildUnit* cibu = new SubprocessedBuildUnit("linkage", + {}, // Yet to be filled + {ExpectedFSEntityState(resulting_binary_filepath, S_IFREG)}, + {mk_personal_targ_dir_bu_id}, // Yet to be replenished + {full_linking_cmd}); + for (const std::string& bu: tg.units) + cibu->all_fs_dependencies.emplace_back(BU_to_OBJ_FILEPATH(bu), S_IFREG); + for (size_t buid: all_comp_units_bu_ids) + cibu->bu_dependencies.push_back(buid); + for (auto& pd: tg.proj_deps) { + ASSERT_pl(before.count(pd.project_library_target) == 1); + cibu->bu_dependencies.push_back(before[pd.project_library_target].end_BBU_id); + } + targ_FINAL_bbu_id = add_bbu(cibu); + }; + + /* Initialized in gen_ibus_for_this_th */ + size_t blank_ibu_for_tg_FINAL; + /* bon_install_root is either install_lib_dir_pth or intall_bin_dir_path */ + auto gen_ibus_for_this_th = [&](const std::string& bin_install_root, const std::string& resuting_bin_extension) { + BuildUnit* podveska = new BuildUnit(); + /* Time to initialize corresponding build units in "install" runlevel */ + size_t my_ibu_for_final_binary_installation = add_ibu(new FileInstallBuildUnit( + path_t(proj_compiled_dir_path) / tg.name / (tg.name + resuting_bin_extension), + path_t(bin_install_root) / tg.installation_dir / (tg.name + resuting_bin_extension))); + podveska->bu_dependencies.push_back(my_ibu_for_final_binary_installation); + for (const std::string& imp_header: tg.exported_headers) { + size_t h_file_install_ibu = add_ibu(new FileInstallBuildUnit( + path_t(proj_src_dir_path) / tg.include_pr / imp_header, + path_t(install_include_dir_path) / tg.include_ir / imp_header)); + podveska->bu_dependencies.push_back(h_file_install_ibu); + } + /* This target depends on some project's libraries. Have to connect it all */ + for (auto& pd: tg.proj_deps) { + ASSERT_pl(before.count(pd.project_library_target) == 1); + podveska->bu_dependencies.push_back(before[pd.project_library_target].end_IBU_id); + } + blank_ibu_for_tg_FINAL = add_ibu(podveska); + }; + + + if (tg.type == "executable") { + ASSERT(tg.exported_headers.empty(), "C-target's field `exported_headers` is unsupported for type `executable`"); + ASSERT(tg.include_ir.empty(), "C-target's field `include_ir` is unsupported for type `executable`"); + ASSERT(tg.pc_output_path.empty(), "C-target's field `pc_output_path` is unsupported for type `executable`"); + generate_cu_BUs({}); + generate_targ_link_BU({}, ""); + gen_ibus_for_this_th(install_bin_dir_path, ""); + } else if (tg.type == "shared_library") { + generate_cu_BUs({"-fPIC"}); + generate_targ_link_BU({"-shared"}, ".so"); + gen_ibus_for_this_th(install_lib_dir_path, ".so"); + before[tg.name] = S(targ_FINAL_bbu_id); + S& s = before[tg.name]; + for (auto& external_dep: tg.external_deps) { + if (external_dep.passing_flags.pass_compilational_flags) { + array_concat(s.emitted_compilation_flags_USED_HERE, ext_libs_map[external_dep.external_library_name].compilation_flags); + array_concat(s.emitted_compilation_flags_PASSED_FORWARD, ext_libs_map[external_dep.external_library_name].compilation_flags); + } + if (external_dep.passing_flags.pass_linkage_flags) { + array_concat(s.emitted_linkage_flags_USED_HERE, ext_libs_map[external_dep.external_library_name].linkage_flags); + array_concat(s.emitted_linkage_flags_PASSED_FORWARD, ext_libs_map[external_dep.external_library_name].linkage_flags); + } + } + for (auto& internal_dep: tg.proj_deps) { + if (internal_dep.passing_flags.pass_compilational_flags) { + array_concat(s.emitted_compilation_flags_USED_HERE, before[internal_dep.project_library_target].emitted_compilation_flags_USED_HERE); + array_concat(s.emitted_compilation_flags_PASSED_FORWARD, before[internal_dep.project_library_target].emitted_compilation_flags_PASSED_FORWARD); + } + if (internal_dep.passing_flags.pass_linkage_flags) { + array_concat(s.emitted_linkage_flags_USED_HERE, before[internal_dep.project_library_target].emitted_linkage_flags_USED_HERE); + array_concat(s.emitted_linkage_flags_PASSED_FORWARD, before[internal_dep.project_library_target].emitted_linkage_flags_PASSED_FORWARD); + } + } + gxx_add_cli_includes(s.emitted_compilation_flags_USED_HERE, {path_t(proj_src_dir_path) / tg.include_pr}); + gxx_add_cli_includes(s.emitted_compilation_flags_PASSED_FORWARD, {path_t(install_include_dir_path) / tg.include_ir}); + gxx_add_cli_options(s.emitted_linkage_flags_USED_HERE, { + "-L", path_t(proj_compiled_dir_path) / tg.name, + "-Wl,-rpath," + install_lib_dir_path + "/" + tg.installation_dir, + "-l:" + tg.name + ".so" + }); + ASSERT(!path_t(install_lib_dir_path).is_relative, "Dude, give normal library installation path"); + gxx_add_cli_options(s.emitted_linkage_flags_PASSED_FORWARD, { + "-L", install_lib_dir_path + "/" + tg.installation_dir, + "-Wl,-rpath," + install_lib_dir_path + "/" + tg.installation_dir, + "-l:" + tg.name + ".so" + }); + /* Determining how to create pkg-config file at installation stage */ + if (!tg.pc_output_path.empty() && tg.entry_point) { + check_pkg_conf_rel_install_path(tg.pc_output_path); + // todo: ESCAPE THESE VALUES + size_t pkg_conf_install_ibu = add_ibu(new FileWriteBuildUnit( + path_t(install_pkgconfig_dir_path) / tg.pc_output_path, + "Name: " + tg.name + "\n" + + "Description: " + tg.description + "\n" + + "Cflags: " + join_string_arr(s.emitted_compilation_flags_PASSED_FORWARD, " ") + "\n" + + "Libs: " + join_string_arr(s.emitted_linkage_flags_PASSED_FORWARD, " ") + "\n")); + ret_at_install.tasks[blank_ibu_for_tg_FINAL]->bu_dependencies.push_back(pkg_conf_install_ibu); + } + /* s.end_BU... fields allow us to establish dependency relations between BUs of ctargets with such relation */ + s.end_BBU_id = targ_FINAL_bbu_id; + s.end_IBU_id = blank_ibu_for_tg_FINAL; + } else { + THROW("Unknown C-target type " + tg.type); + } + if (tg.entry_point) { + ret_at_build.entry_points.push_back(targ_FINAL_bbu_id); + ret_at_install.entry_points.push_back(blank_ibu_for_tg_FINAL); + } + } +} + +struct NormalCBuildSystemCommandMeaning { + std::string project_root; + std::string installation_root; + bool allowed_symlink_install = false; + bool need_to_build = false; + bool need_to_install = false; +}; + +void normal_c_build_system_command_interpretation_only_ (const std::vector& args, size_t& i, + NormalCBuildSystemCommandMeaning& reta, + const std::string& postf_built_local_install) +{ + size_t an = args.size(); + ASSERT(i + 1 <= an, "No `command` provided on the command line (no first argument)") + const std::string& command = args[i]; + i++; + if (command == "local,build+install" || command == "lbi") { + ASSERT(i + 1 <= an, "Command `" + command + "` requires 1 argument"); + reta = {args[i], args[i] + postf_built_local_install, true, true, true}; + i += 1; + } else if (command == "build" || command == "b") { + ASSERT(i + 2 <= an, "Command `" + command + "` requires 2 arguments"); + reta = {args[i], args[i + 1], false, true, false}; + i += 2; + } else if (command == "install" || command == "i") { + ASSERT(i + 2 <= an, "Command `" + command + "` requires 2 arguments"); + reta = {args[i], args[i + 1], false, false, true}; + i += 2; + } else if (command == "build+install" || command == "bi") { + ASSERT(i + 2 <= an, "Command `" + command + "` requires 2 arguments"); + reta = {args[i], args[i + 1], false, true, true}; + i += 2; + } else { + THROW("Unknown command `" + command + "`"); + } +} + +void normal_c_build_system_command_interpret(const std::vector& args, + NormalCBuildSystemCommandMeaning& reta, + const std::string& postf_built_local_install) +{ + size_t i = 0; + normal_c_build_system_command_interpretation_only_(args, i, reta, postf_built_local_install); + ASSERT(i == args.size(), "Too many arguments"); +} + +const char* default_PR_postf_built_local_install = "/built/local-install"; +const char* default_PR_postf_src = "/src"; +const char* default_PR_postf_built_compiled = "/built/compiled"; +const char* default_IR_postf_include = "/include"; +const char* default_IR_postf_lib = "/lib"; +const char* default_IR_postf_bin = "/bin"; +const char* default_IR_postf_pkgconfig = "/lib/pkgconfig"; + +void regular_bs_cli_cmd_interpret(const std::vector& args, NormalCBuildSystemCommandMeaning& reta) { + normal_c_build_system_command_interpret(args, reta, default_PR_postf_built_local_install); +} + +void regular_ctargets_to_2bus_conversion( + const std::vector& ext_lib_targs, + const std::vector& proj_targs, + BuildUnitsArray& ret_at_build, + BuildUnitsArray& ret_at_install, + const std::string& project_root, const std::string& installation_root) { + load_ctargets_on_building_and_installing(ext_lib_targs, proj_targs, ret_at_build, ret_at_install, + project_root + default_PR_postf_src, + project_root + default_PR_postf_built_compiled, + installation_root + default_IR_postf_include, + installation_root + default_IR_postf_lib, + installation_root + default_IR_postf_bin, + installation_root + default_IR_postf_pkgconfig + ); +} + +std::string text_formatting_break_spaces(const std::string& text) { + std::string out; + return out; +} + +void draw_bu_arr_in_dot(const BuildUnitsArray& taskSet, std::string& output) { + size_t N = taskSet.tasks.size(); + std::vector turningRed(N, false); + for (size_t eid: taskSet.entry_points) { + ASSERT_pl(eid < N && !turningRed[eid]); + turningRed[eid] = true; + } + output += "digraph BUs { \n" + "graph [rankdir=LR]" + "node [fontcolor = black color = black fillcolor = white margin = \"0.2,0.2\"" + " shape=rect ]\n"; + for (size_t i = 0; i < N; i++) { + const BuildUnit& bu = *(taskSet.tasks[i]); + output += std::string("N_") + std::to_string(i) + " [ color = " + (turningRed[i] ? "red" : "black") + + " label = " + escape_with_doublequoting("[" + std::to_string(i) + "] " + prettyprint_build_unit(bu)) + + "]\n"; + for (size_t j: bu.bu_dependencies) { + output += "N_" + std::to_string(i) + " -> N_" + std::to_string(j) + "\n"; + } + } + output += "}\n"; +} + +void show_build_units_array_with_image_viewer(const BuildUnitsArray& taskSet, const std::string& image_viewer = "sxiv") +{ + std::string dot_input; + draw_bu_arr_in_dot(taskSet, dot_input); + std::string dot_output; + std::string dot_error; + printf("Look at that\n%s\n", dot_input.c_str()); + CommandReturnCode rc = executeCommand_imulating_whole_input_and_save_output({"dot", "-T", "svg"}, dot_input, dot_output, dot_error); + if (!rc.isOk()) { + printf("Dot error output:\n%s", dot_error.c_str()); + } + ASSERT_pl(rc.isOk()); + writeFile("taskSet.svg", dot_output); + ASSERT(executeCommand({image_viewer, "taskSet.svg"}).isOk(), "imageviewer error"); +} + +#endif //REGEXIS024_BUILD_SYSTEM_H