1111 lines
42 KiB
C++
1111 lines
42 KiB
C++
#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;
|
||
};
|
||
|
||
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;
|
||
|
||
/* .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");
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
|
||
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 {
|
||
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) {
|
||
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;
|
||
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
|
||
|