regexis024-build-system/regexis024_build_system.h

1087 lines
42 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <stdexcept>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <memory>
#include <functional>
#include <map>
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<std::string>& 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<std::string> 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 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(), 0755);
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 + "\")");
}
}
}
template<typename T>
using uptr = std::unique_ptr<T>;
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); */
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, 0755);
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<std::string> 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<char*> cnv(n + 1, NULL);
for (size_t i = 0; i < n; i++) {
cnv[i] = const_cast<char*>(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<std::string>& args,
std::string& std_output, std::string& std_error)
{
checkArgumentsForSubprocess(args);
size_t n = args.size();
std::vector<char*> cnv(n + 1, NULL);
for (size_t i = 0; i < n; i++) {
cnv[i] = const_cast<char*>(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<std::string>& args,
const std::string& std_input, std::string& std_output, std::string& std_error)
{
checkArgumentsForSubprocess(args);
size_t n = args.size();
std::vector<char*> cnv(n + 1, NULL);
for (size_t i = 0; i < n; i++) {
cnv[i] = const_cast<char*>(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<std::string>& A, const std::vector<std::string>& 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<std::string>& dest, const std::vector<std::string>& 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<std::string>& dest, const std::vector<std::string>& 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<std::string>& dest, const std::vector<std::string>& 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<ExpectedFSEntityState> all_fs_dependencies;
std::vector<ExpectedFSEntityState> all_fs_results;
/* Build unit dependencies are identidied by their index in array */
std::vector<size_t> bu_dependencies;
BuildUnit(std::string type_, std::vector<ExpectedFSEntityState> all_fs_deps_, std::vector<ExpectedFSEntityState> all_fs_results_,
std::vector<size_t> bu_deps_): type(std::move(type_)), all_fs_dependencies(std::move(all_fs_deps_)),
all_fs_results(std::move(all_fs_results_)), bu_dependencies(std::move(bu_deps_)){}
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<std::string> build_command;
SubprocessedBuildUnit(const std::string &type, const std::vector<ExpectedFSEntityState> &all_fs_dependencies,
const std::vector<ExpectedFSEntityState> &all_fs_results, const std::vector<size_t> &bu_dependencies,
const std::vector<std::string> &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, 0755);
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<size_t>& result, std::vector<size_t>& circular_dependency_result,
const std::vector<std::vector<size_t>>& depgraph)
{
size_t N = depgraph.size();
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<short> status(N, 0);
struct topsortDfsFrame {
size_t v;
size_t i = 0;
explicit topsortDfsFrame(size_t v): v(v){}
};
for (size_t st = 0; st < N; st++) {
if (status[st] == 2)
continue;
std::vector<topsortDfsFrame> 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] */
}
std::string prettyprint_cyclic_dependency(const std::vector<std::string>& lines) {
std::string res = "Брэ! ТУТ ЧЁ-ТО НЕ ТАК!\n";
for (auto& el: lines)
res += el; res += '\n';
res += "Lmao, you have a cyclic build unit dependency in your scipt.\n";
return res;
}
typedef std::vector<uptr<BuildUnit>> BuildUnitsArray;
void complete_tasks_of_build_units (const BuildUnitsArray& arr)
{
size_t N = arr.size();
std::vector<size_t> execution_order;
std::vector<size_t> cyc_dep_problem;
std::vector<std::vector<size_t>> depgraph(N);
for (size_t i = 0; i < N; i++)
depgraph[i] = arr[i]->bu_dependencies;
topsort(execution_order, cyc_dep_problem, depgraph);
if (!cyc_dep_problem.empty()) {
std::vector<std::string> bad_units(cyc_dep_problem.size());
for (size_t i = 0; i < cyc_dep_problem.size(); i++) {
bad_units[i] = prettyprint_build_unit(*arr[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[I]).c_str());
for (auto& rqd: arr[I]->all_fs_dependencies)
checkFsEntity(rqd);
arr[I]->execute();
for (auto& rqr: arr[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 {
std::string project_library_target;
CTargetDependenceOnLibraryFPass passing_flags;
};
struct CTargetDependenceOnExternalLibrary {
std::string external_library_name;
CTargetDependenceOnLibraryFPass passing_flags;
};
struct ExternalLibraryData {
std::vector<std::string> compilation_flags;
std::vector<std::string> linkage_flags;
};
struct ExternalLibraryTarget {
std::string name;
ExternalLibraryData data;
};
struct CTarget {
/* Must not contain / . , */
std::string name;
std::string type;
std::vector<CTargetDependenceOnExternalLibrary> external_deps;
std::vector<CTargetDependenceOnProjectsLibrary> proj_deps;
std::vector<std::string> additional_compilation_flags;
std::vector<std::string> additional_linkage_flags;
/* .c and .cpp source files relative to the special src directory*/
std::vector<std::string> 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<std::string> 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 version = "0.1";
};
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& 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& PtoC){
return path_t(PtoC);
}
void load_ctargets_on_building_and_installing(
const std::vector<ExternalLibraryTarget>& ext_lib_targs,
const std::vector<CTarget>& 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)
{
std::map<std::string, ExternalLibraryData> 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<std::string> emitted_compilation_flags_USED_HERE;
std::vector<std::string> emitted_compilation_flags_PASSED_FORWARD;
std::vector<std::string> emitted_linkage_flags_USED_HERE;
std::vector<std::string> emitted_linkage_flags_PASSED_FORWARD;
S() = default;
explicit S(size_t end_bu_id): end_BBU_id(end_bu_id) {}
};
std::map<std::string, S> before;
auto add_bbu = [&](BuildUnit* obj) -> size_t {
ret_at_build.emplace_back(obj);
return ret_at_build.size() - 1;
};
auto add_ibu = [&](BuildUnit* obj) -> size_t {
ret_at_install.emplace_back(obj);
return ret_at_install.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<size_t> 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(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<std::string>& 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<std::string> 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<std::string>& 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<std::string> 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()) {
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: \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[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);
}
}
}
struct NormalCBuildSystemCommandMeaning {
std::string project_root;
std::string installation_root;
bool local = false;
bool need_to_build = false;
bool need_to_install = false;
};
void normal_c_build_system_command_interpretation_only_ (const std::vector<std::string>& 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<std::string>& 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<std::string>& 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<ExternalLibraryTarget>& ext_lib_targs,
const std::vector<CTarget>& 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
);
}
#endif //REGEXIS024_BUILD_SYSTEM_H