regexis024-build-system/regexis024_build_system.h

1111 lines
42 KiB
C
Raw Normal View History

#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; */
2024-07-26 18:06:13 +00:00
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);
}
2024-07-31 17:02:24 +00:00
/* 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) {
2024-07-26 18:06:13 +00:00
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;
2024-07-31 17:02:24 +00:00
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)
2024-07-31 17:02:24 +00:00
: 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)
2024-07-31 17:02:24 +00:00
: 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) :
2024-07-31 17:02:24 +00:00
BuildUnit{"touch", {},
{ExpectedFSEntityState(filepath, S_IFREG)},
2024-07-31 17:02:24 +00:00
{}},
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),
2024-07-31 17:02:24 +00:00
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");
2024-07-26 18:06:13 +00:00
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) {
2024-07-31 17:02:24 +00:00
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;
2024-07-31 17:02:24 +00:00
CTargetDependenceOnLibraryFPass passing_flags;
};
struct CTargetDependenceOnExternalLibrary {
std::string external_library_name;
2024-07-31 17:02:24 +00:00
CTargetDependenceOnLibraryFPass passing_flags;
};
struct ExternalLibraryData {
std::vector<std::string> compilation_flags;
std::vector<std::string> linkage_flags;
};
std::string lib_connection_flags_to_passed_forward_str(const ExternalLibraryData& lib) {
std::string result;
for (const std::string& f: lib.compilation_flags)
result += escape_with_doublequoting(f);
result += ";";
for (const std::string& f: lib.linkage_flags)
result += escape_with_doublequoting(f);
return result;
}
ExternalLibraryData parse_passed_forward_str(const std::string& str) {
ExternalLibraryData result;
int f = 0;
auto getOut = [&]() -> std::vector<std::string>& {
return f > 0 ? result.linkage_flags : result.compilation_flags;
};
bool in_str = false;
bool bsl = false;
for (char ch: str) {
if (in_str) {
if (bsl) {
bsl = false;
getOut().back() += ch;
} else if (ch == '\\') {
bsl = true;
} else if (ch == '"') {
in_str = false;
} else {
getOut().back() += ch;
}
} else if (ch == '"') {
in_str = true;
getOut().emplace_back();
} else if (ch == ';') {
f++;
if (f > 1)
THROW("PASSED_FORWARD.txt has only two fields: first - cflags; second - linking flags");
} else {
ASSERT(ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n',
"Only whitespaces can be between arguments in PASSED_FORWARD.txt")
}
}
return result;
}
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;
2024-07-26 18:06:13 +00:00
/* .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;
};
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" && name != "PASSED_FORWARD.txt", "DON'T YOU NEVER EVER CALL YOUR TARGET like this")
}
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");
}
2024-07-19 13:02:16 +00:00
/* Argument `name` is just a name in `units` array, return value is relative to $IR/$TARGET_NAME/obj */
2024-07-26 18:06:13 +00:00
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<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)
{
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 to 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) / bu;
};
auto BU_to_OBJ_FILEPATH = [&](const std::string& bu) -> path_t {
2024-07-26 18:06:13 +00:00
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: *speaks in soydev voice* AAAA OOOO AAA I LOVE GCC
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) {
2024-07-31 17:02:24 +00:00
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`");
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"
});
size_t PASSED_FORWARD_file_ibu = add_ibu(new FileWriteBuildUnit(
path_t(proj_compiled_dir_path) / tg.name / "PASSED_FORWARD.txt",
lib_connection_flags_to_passed_forward_str(ExternalLibraryData{
s.emitted_compilation_flags_PASSED_FORWARD,
s.emitted_linkage_flags_PASSED_FORWARD
})));
ret_at_install[blank_ibu_for_tg_FINAL]->bu_dependencies.push_back(PASSED_FORWARD_file_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;
2024-07-31 17:02:24 +00:00
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";
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
);
}
#endif