#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; /* Prefix for all cpp files in `units` array */ std::string units_dir; /* .c and .cpp source files relative to the special src/${units_dir} 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; std::string version = "0.1"; 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 just a name in `units` array, return value is relative to $IR/$TARGET_NAME/obj */ path_t c_unit_name_to_obj_filename(const std::string& units_dir, 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; } path_t c_unit_name_to_source_filename(const std::string& units_dir, const std::string& PtoC){ return path_t(units_dir) / PtoC; } 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) / c_unit_name_to_source_filename(tg.units_dir, 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(tg.units_dir, 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" + "Version: " + tg.version + "\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