lib/toolchain.h lib/bufio.h | lib/toolchain.h lib/bufio.h | ||||
BUILD = build | BUILD = build | ||||
OBJS = $(patsubst %,$(BUILD)/%.o,$(SRCS)) | OBJS = $(patsubst %,$(BUILD)/%.o,$(SRCS)) | ||||
CFLAGS = -std=c++14 -Wall -Wextra -Wno-unused-parameter -O3 -g | |||||
CFLAGS = -std=c++14 -Wall -Wextra -Wpedantic -Wno-unused-parameter -Ilib -O3 -g | |||||
LDLIBS = -lpthread | LDLIBS = -lpthread | ||||
$(BUILD)/%.cc.o: %.cc $(HDRS) | $(BUILD)/%.cc.o: %.cc $(HDRS) |
target := box | target := box | ||||
files := lib cmd | files := lib cmd | ||||
includes := lib | includes := lib | ||||
warnings := all extra no-unused-parameter | |||||
warnings := all extra pedantic no-unused-parameter | |||||
std := c++14 | std := c++14 | ||||
optimize := 3 | optimize := 3 | ||||
cxxflags := -g | cxxflags := -g |
#include "build.h" | #include "build.h" | ||||
#include "compdb.h" | #include "compdb.h" | ||||
static void run(std::vector<std::string> args, std::vector<std::pair<std::string, std::string>> kwargs) { | |||||
struct Conf { | |||||
std::vector<std::string> args; | |||||
std::vector<std::string> args2; | |||||
std::vector<std::pair<std::string, std::string>> kvargs; | |||||
bool exec = false; | |||||
}; | |||||
static void run(Conf conf) { | |||||
std::string op; | std::string op; | ||||
std::string path; | std::string path; | ||||
if (args.size() == 0) { | |||||
if (conf.args.size() == 0) { | |||||
op = "build"; | op = "build"; | ||||
path = "./bx-out"; | path = "./bx-out"; | ||||
} else if (args[0][0] == '.' || args[0][0] == '/') { | |||||
} else if (conf.args[0][0] == '.' || conf.args[0][0] == '/') { | |||||
op = "build"; | op = "build"; | ||||
path = args[0]; | |||||
} else if (args.size() == 1) { | |||||
op = args[0]; | |||||
path = conf.args[0]; | |||||
} else if (conf.args.size() == 1) { | |||||
op = conf.args[0]; | |||||
path = "./bx-out"; | path = "./bx-out"; | ||||
} else if (args.size() == 2) { | |||||
op = args[0]; | |||||
path = args[1]; | |||||
} else if (conf.args.size() == 2) { | |||||
op = conf.args[0]; | |||||
path = conf.args[1]; | |||||
} else { | } else { | ||||
// TODO: Print usage instead? | // TODO: Print usage instead? | ||||
throw std::runtime_error("Incorrect number of arguments"); | throw std::runtime_error("Incorrect number of arguments"); | ||||
BXVariables variables; | BXVariables variables; | ||||
if (sys::fileExists(path + "/.config.bx")) { | if (sys::fileExists(path + "/.config.bx")) { | ||||
bufio::IFStream f(path + "/.config.bx"); | bufio::IFStream f(path + "/.config.bx"); | ||||
BXParser parser(f, BXParser::FLAG_NONE); | |||||
BXParser parser(f); | |||||
parser.parse(variables); | parser.parse(variables); | ||||
} | } | ||||
for (auto &pair: kwargs) { | |||||
for (auto &pair: conf.kvargs) { | |||||
bufio::ISStream ss(pair.second); | bufio::ISStream ss(pair.second); | ||||
BXParser parser(ss, BXParser::FLAG_NONE); | |||||
BXParser parser(ss); | |||||
auto &list = variables[pair.first]; | auto &list = variables[pair.first]; | ||||
list.clear(); | list.clear(); | ||||
parser.parseList(variables, list); | parser.parseList(variables, list); | ||||
} | } | ||||
if (kwargs.size() > 0) { | |||||
if (conf.kvargs.size() > 0) { | |||||
bufio::OFStream f(path + "/.config.bx"); | bufio::OFStream f(path + "/.config.bx"); | ||||
BXWriter w(f); | BXWriter w(f); | ||||
w.write(variables); | w.write(variables); | ||||
}; | }; | ||||
auto buildTree = [&](BXVariables vars) -> std::unique_ptr<DepNode> { | auto buildTree = [&](BXVariables vars) -> std::unique_ptr<DepNode> { | ||||
return buildDepTree(path, std::move(vars)); | |||||
return build::buildDepTree(path, std::move(vars)); | |||||
}; | }; | ||||
auto buildCompileCommands = [&](DepNode &root) { | auto buildCompileCommands = [&](DepNode &root) { | ||||
}; | }; | ||||
if (op == "build") { | if (op == "build") { | ||||
auto root = buildTree(buildVariables()); | |||||
auto vars = buildVariables(); | |||||
std::string targetName = build::findTargetName(vars); | |||||
auto root = buildTree(vars); | |||||
buildCompileCommands(*root); | buildCompileCommands(*root); | ||||
if (root->hasChanged(path)) { | if (root->hasChanged(path)) { | ||||
logger::log("Nothing to do."); | logger::log("Nothing to do."); | ||||
} | } | ||||
if (conf.exec) { | |||||
std::vector<std::string> argv; | |||||
argv.reserve(conf.args2.size() + 1); | |||||
argv.push_back(path + '/' + targetName); | |||||
for (const auto &arg: conf.args2) { | |||||
argv.push_back(std::move(arg)); | |||||
} | |||||
try { | |||||
sys::ProcConf conf; | |||||
conf.forwardSignals = true; | |||||
sys::execute(argv, conf); | |||||
} catch (std::exception &ex) { | |||||
logger::log(ex.what()); | |||||
} | |||||
} | |||||
} else if (op == "config") { | } else if (op == "config") { | ||||
BXVariables variables = buildVariables(); | BXVariables variables = buildVariables(); | ||||
int main(int argc, char **argv) { | int main(int argc, char **argv) { | ||||
int jobs = parallel::coreCount() * 1.2 + 2; | int jobs = parallel::coreCount() * 1.2 + 2; | ||||
Conf conf; | |||||
std::string workDir = ""; | std::string workDir = ""; | ||||
std::string target = ""; | std::string target = ""; | ||||
{ "verbose", no_argument, NULL, 'v' }, | { "verbose", no_argument, NULL, 'v' }, | ||||
{ "jobs", required_argument, NULL, 'j' }, | { "jobs", required_argument, NULL, 'j' }, | ||||
{ "directory", required_argument, NULL, 'C' }, | { "directory", required_argument, NULL, 'C' }, | ||||
{ "run", no_argument, NULL, 'R' }, | |||||
{}, | {}, | ||||
}; | }; | ||||
"Set the number of jobs run simultaneously. " | "Set the number of jobs run simultaneously. " | ||||
"Default: the number of cores in the machine.\n" | "Default: the number of cores in the machine.\n" | ||||
" -C, --directory <dir> " | " -C, --directory <dir> " | ||||
"Change directory before doing anything else.\n"; | |||||
"Change directory before doing anything else.\n" | |||||
" -R, --run <args...> " | |||||
"Run executable after building.\n"; | |||||
// Parse options from argv | // Parse options from argv | ||||
while (1) { | |||||
bool parsingOpts = true; | |||||
while (parsingOpts) { | |||||
int optidx; | int optidx; | ||||
int c = getopt_long(argc, argv, shortopts, opts, &optidx); | int c = getopt_long(argc, argv, shortopts, opts, &optidx); | ||||
workDir = optarg; | workDir = optarg; | ||||
break; | break; | ||||
case 'R': | |||||
conf.exec = true; | |||||
parsingOpts = false; | |||||
for (; optind < argc; ++optind) { | |||||
conf.args2.push_back(argv[optind]); | |||||
} | |||||
break; | |||||
default: | default: | ||||
printf("Unknown option: '%c'.\n", (char)c); | printf("Unknown option: '%c'.\n", (char)c); | ||||
printf(usage, argv[0]); | printf(usage, argv[0]); | ||||
} | } | ||||
// Find args and keyword args | // Find args and keyword args | ||||
std::vector<std::string> args; | |||||
std::vector<std::pair<std::string, std::string>> kwargs; | |||||
while (optind < argc) { | |||||
for (; optind < argc; ++optind) { | |||||
char *arg = argv[optind++]; | char *arg = argv[optind++]; | ||||
char *eq = strchr(arg, '='); | char *eq = strchr(arg, '='); | ||||
if (eq == nullptr) { | if (eq == nullptr) { | ||||
args.push_back(arg); | |||||
conf.args.push_back(arg); | |||||
} else { | } else { | ||||
kwargs.push_back(std::make_pair( | |||||
conf.kvargs.push_back(std::make_pair( | |||||
std::string(arg, eq - arg), std::string(eq + 1))); | std::string(arg, eq - arg), std::string(eq + 1))); | ||||
} | } | ||||
} | } | ||||
run(std::move(args), std::move(kwargs)); | |||||
run(std::move(conf)); | |||||
} | } |
return c; | return c; | ||||
} | } | ||||
BXParser::Operator BXParser::readOperator() { | |||||
int ch2 = peek(2); | |||||
if (peek() == ':' && ch2 == '=') { | |||||
skip(); // ':' | |||||
skip(); // '=' | |||||
return Operator::COLON_EQUALS; | |||||
} else if (peek() == '+' && ch2 == '=') { | |||||
skip(); // '+' | |||||
skip(); // '=' | |||||
return Operator::PLUS_EQUALS; | |||||
} else if (peek() == '=' && ch2 == '+') { | |||||
skip(); // '=' | |||||
skip(); // '+' | |||||
return Operator::EQUALS_PLUS; | |||||
} else if (peek() == '|' && ch2 == '=') { | |||||
skip(); // '|' | |||||
skip(); // '=' | |||||
return Operator::BAR_EQUALS; | |||||
} | |||||
return Operator::NONE; | |||||
} | |||||
void BXParser::skip(char expected) { | void BXParser::skip(char expected) { | ||||
int ch = get(); | int ch = get(); | ||||
if (ch == EOF) { | if (ch == EOF) { | ||||
throw BXParseError(std::to_string(line_) + ":" + std::to_string(ch_) + ": " + msg); | throw BXParseError(std::to_string(line_) + ":" + std::to_string(ch_) + ": " + msg); | ||||
} | } | ||||
static bool isWhitespace(int ch) { | |||||
if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') | |||||
return true; | |||||
return false; | |||||
} | |||||
void BXParser::skipWhitespace() { | |||||
if (flags_ & FLAG_ONE_LINE) { | |||||
int ch; | |||||
while (isWhitespace(ch = peek()) && ch != '\r' && ch != '\n') | |||||
get(); | |||||
} else { | |||||
while (isWhitespace(peek())) | |||||
get(); | |||||
} | |||||
} | |||||
[[noreturn]] void BXParser::error(std::string msg, TokenKind kind) { | |||||
switch (kind) { | |||||
case TokenKind::E_O_F: | |||||
msg += " EOF"; | |||||
break; | |||||
char BXParser::parseEscape() { | |||||
skip(); // '\' | |||||
int ch; | |||||
switch (ch = get()) { | |||||
case EOF: | |||||
error("Unexpected EOF"); | |||||
case TokenKind::INDENTATION: | |||||
msg += " indentation"; | |||||
break; | |||||
case 'n': | |||||
return '\n'; | |||||
case TokenKind::NEWLINE: | |||||
msg += " newline"; | |||||
break; | |||||
case 'r': | |||||
return '\r'; | |||||
case TokenKind::COMMA: | |||||
msg += " comma ','"; | |||||
break; | |||||
case 't': | |||||
return '\t'; | |||||
case TokenKind::COLON_EQUALS: | |||||
msg += " colon equals ':='"; | |||||
break; | |||||
default: | |||||
return (char)ch; | |||||
} | |||||
} | |||||
case TokenKind::PLUS_EQUALS: | |||||
msg += " plus equals '+='"; | |||||
break; | |||||
static void appendVariableToString( | |||||
const BXVariables &vars, std::string &name, | |||||
std::string &value) { | |||||
if (name.size() == 0) | |||||
return; | |||||
auto it = vars.find(name); | |||||
if (it == vars.end()) | |||||
return; | |||||
auto &vec = it->second; | |||||
bool first = true; | |||||
for (auto &part: vec) { | |||||
if (!first) { | |||||
value += ' '; | |||||
} | |||||
case TokenKind::EQUALS_PLUS: | |||||
msg += " equals plus '=+'"; | |||||
break; | |||||
first = false; | |||||
value += part; | |||||
} | |||||
} | |||||
case TokenKind::BAR_EQUALS: | |||||
msg += " bar equals '|='"; | |||||
break; | |||||
static void appendVariableToArray( | |||||
const BXVariables &vars, const std::string &name, | |||||
std::vector<std::string> &values) { | |||||
if (name.size() == 0) | |||||
return; | |||||
case TokenKind::EXPANSION: | |||||
msg += " expansion"; | |||||
break; | |||||
auto it = vars.find(name); | |||||
if (it == vars.end()) | |||||
return; | |||||
case TokenKind::STRING: | |||||
msg += " string"; | |||||
break; | |||||
auto &vec = it->second; | |||||
for (auto &part: vec) { | |||||
values.push_back(part); | |||||
case TokenKind::NONE: | |||||
msg += " none"; | |||||
break; | |||||
} | } | ||||
} | |||||
void BXParser::parseExpansion(const BXVariables &vars, std::vector<std::string> &values) { | |||||
skip(); // '$' | |||||
std::string str; | |||||
switch (peek()) { | |||||
case '{': | |||||
skip(); | |||||
parseString(vars, str, '}'); | |||||
skip('}'); | |||||
appendVariableToArray(vars, str, values); | |||||
break; | |||||
default: | |||||
if (!parseIdentifier(str)) { | |||||
error("No identifier after $."); | |||||
} | |||||
appendVariableToArray(vars, str, values); | |||||
break; | |||||
} | |||||
error(msg); | |||||
} | } | ||||
void BXParser::parseQuotedExpansion(const BXVariables &vars, std::string &content) { | |||||
skip(); // '$' | |||||
std::string BXParser::readIdent(const BXVariables &vars) { | |||||
std::string str; | std::string str; | ||||
switch (peek()) { | |||||
case '{': | |||||
skip(); | |||||
parseString(vars, str, '}'); | |||||
skip('}'); | |||||
appendVariableToString(vars, str, content); | |||||
break; | |||||
default: | |||||
if (!parseIdentifier(str)) { | |||||
error("No identifier after $."); | |||||
} | |||||
appendVariableToString(vars, str, content); | |||||
break; | |||||
} | |||||
} | |||||
void BXParser::parseQuotedString(const BXVariables &vars, std::string &content) { | |||||
skip(); // '"' | |||||
int ch; | int ch; | ||||
while ((ch = peek()) != EOF) { | while ((ch = peek()) != EOF) { | ||||
switch (ch) { | |||||
case EOF: | |||||
error("Unexpected EOF"); | |||||
case '\\': | |||||
content.push_back(parseEscape()); | |||||
break; | |||||
case '$': | |||||
parseQuotedExpansion(vars, content); | |||||
break; | |||||
case '"': | |||||
skip(); | |||||
return; | |||||
default: | |||||
content.push_back(get()); | |||||
break; | |||||
if ( | |||||
(ch >= 'a' && ch <= 'z') || | |||||
(ch >= 'A' && ch <= 'Z') || | |||||
(ch == '_')) { | |||||
str.push_back(ch); | |||||
get(); | |||||
} else { | |||||
break; | |||||
} | } | ||||
} | } | ||||
return str; | |||||
} | } | ||||
bool BXParser::parseString(const BXVariables &vars, std::string &content, int sep) { | |||||
bool success = false; | |||||
void BXParser::skipWhitespace() { | |||||
int ch; | int ch; | ||||
while (1) { | |||||
ch = peek(); | |||||
if ((sep > 0 && ch == sep) || isWhitespace(ch)) { | |||||
return success; | |||||
} | |||||
switch (ch) { | |||||
case EOF: | |||||
return success; | |||||
case '\\': | |||||
content.push_back(parseEscape()); | |||||
success = true; | |||||
break; | |||||
case '$': | |||||
parseQuotedExpansion(vars, content); | |||||
success = true; | |||||
break; | |||||
case '"': | |||||
parseQuotedString(vars, content); | |||||
success = true; | |||||
break; | |||||
default: | |||||
int ch2 = peek(2); | |||||
if ( | |||||
(ch == ':' && ch2 == '=') || | |||||
(ch == '+' && ch2 == '=') || | |||||
(ch == '=' && ch2 == '+') || | |||||
(ch == '|' && ch2 == '=')) { | |||||
return success; | |||||
} | |||||
content.push_back(get()); | |||||
success = true; | |||||
break; | |||||
} | |||||
while ((ch = peek()) == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { | |||||
get(); | |||||
} | } | ||||
} | } | ||||
bool BXParser::parseIdentifier(std::string &content) { | |||||
int ch = peek(); | |||||
if (!( | |||||
(ch >= 'a' && ch <= 'z') || | |||||
(ch >= 'A' && ch <= 'Z') || | |||||
(ch == '_'))) { | |||||
return false; | |||||
} | |||||
content += get(); | |||||
while (1) { | |||||
ch = peek(); | |||||
if (!( | |||||
(ch >= '0' && ch <= '9') || | |||||
(ch >= 'a' && ch <= 'z') || | |||||
(ch >= 'A' && ch <= 'Z') || | |||||
(ch == '_'))) { | |||||
return true; | |||||
} | |||||
content += get(); | |||||
char BXParser::readEscape() { | |||||
int ch = get(); | |||||
if (ch == EOF) { | |||||
error("Unexpected EOF"); | |||||
} else if (ch == 'n') { | |||||
return '\n'; | |||||
} else if (ch == 'r') { | |||||
return '\r'; | |||||
} else if (ch == 't') { | |||||
return '\t'; | |||||
} else { | |||||
return (char)ch; | |||||
} | } | ||||
} | } | ||||
void BXParser::parse(BXVariables &vars) { | |||||
std::string key, value; | |||||
std::vector<std::string> values; | |||||
skipWhitespace(); | |||||
if (!parseString(vars, key)) { | |||||
return; | |||||
std::string BXParser::readStringExpansion(const BXVariables &vars) { | |||||
bool braced = peek() == '{'; | |||||
std::string key; | |||||
if (braced) { | |||||
get(); | |||||
skipWhitespace(); | |||||
key = readString(vars); | |||||
} else { | |||||
key = readIdent(vars); | |||||
} | } | ||||
skipWhitespace(); | |||||
Operator prevOper = readOperator(); | |||||
if (prevOper == Operator::NONE) { | |||||
error("Expected operator."); | |||||
auto it = vars.find(key); | |||||
if (it == vars.end()) { | |||||
error("Key '" + key + "' doesn't exist"); | |||||
} | } | ||||
auto doAssignment = [&] { | |||||
switch (prevOper) { | |||||
case Operator::COLON_EQUALS: | |||||
vars[key] = std::move(values); | |||||
values.clear(); | |||||
break; | |||||
if (braced) { | |||||
skipWhitespace(); | |||||
case Operator::PLUS_EQUALS: | |||||
{ | |||||
auto &vec = vars[key]; | |||||
vec.reserve(vec.size() + values.size()); | |||||
for (size_t i = 0; i < values.size(); ++i) { | |||||
vec.push_back(std::move(values[i])); | |||||
} | |||||
} | |||||
values.clear(); | |||||
break; | |||||
if (peek() != '}') { | |||||
error("Expected a '}' after a '${' expansion"); | |||||
} | |||||
case Operator::EQUALS_PLUS: | |||||
{ | |||||
auto &vec = vars[key]; | |||||
vec.reserve(vec.size() + values.size()); | |||||
for (size_t i = 0; i < vec.size(); ++i) { | |||||
values.push_back(std::move(vec[i])); | |||||
} | |||||
vec = std::move(values); | |||||
} | |||||
values.clear(); | |||||
break; | |||||
get(); | |||||
} | |||||
case Operator::BAR_EQUALS: | |||||
{ | |||||
auto &vec = vars[key]; | |||||
for (size_t i = 0; i < vec.size(); ++i) { | |||||
bool exists = false; | |||||
for (auto &val: values) { | |||||
if (val == vec[i]) { | |||||
exists = true; | |||||
break; | |||||
} | |||||
} | |||||
// TODO: Use BXValue.asString() | |||||
return it->second[0]; | |||||
} | |||||
if (!exists) { | |||||
values.push_back(std::move(vec[i])); | |||||
} | |||||
} | |||||
vec = std::move(values); | |||||
} | |||||
values.clear(); | |||||
break; | |||||
std::string BXParser::readQuotedString(const BXVariables &vars) { | |||||
std::string str; | |||||
case Operator::NONE: | |||||
int ch; | |||||
while ((ch = peek()) != EOF) { | |||||
if (ch == '\\') { | |||||
get(); | |||||
str.push_back(readEscape()); | |||||
} else if (ch == '$') { | |||||
get(); | |||||
str += readStringExpansion(vars); | |||||
} else if (ch == '"') { | |||||
get(); | |||||
break; | break; | ||||
} else { | |||||
str.push_back(ch); | |||||
get(); | |||||
} | } | ||||
}; | |||||
} | |||||
while (true) { | |||||
skipWhitespace(); | |||||
return str; | |||||
} | |||||
std::string BXParser::readString(const BXVariables &vars) { | |||||
std::string str; | |||||
// Parse next value | |||||
if (peek() == '$') { | |||||
parseExpansion(vars, values); | |||||
value.clear(); | |||||
continue; // We can't have an assignment after an expansion | |||||
} else if (!parseString(vars, value)) { | |||||
int ch; | |||||
while ((ch = peek()) != EOF) { | |||||
if (ch == '\\') { | |||||
get(); | |||||
str.push_back(readEscape()); | |||||
} else if (ch == '$') { | |||||
get(); | |||||
str += readStringExpansion(vars); | |||||
} else if (ch == '"') { | |||||
get(); | |||||
str += readQuotedString(vars); | |||||
} else if ( | |||||
ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || | |||||
ch == '}' || ch == ',') { | |||||
break; | break; | ||||
} else { | |||||
str.push_back(ch); | |||||
get(); | |||||
} | } | ||||
} | |||||
skipWhitespace(); | |||||
return str; | |||||
} | |||||
// If there's an operator next, the value we just read was a actually a key. | |||||
// Otherwise, it was just another value. | |||||
Operator op = readOperator(); | |||||
if (op == Operator::NONE) { | |||||
values.push_back(std::move(value)); | |||||
value.clear(); | |||||
} else { | |||||
if (value.size() == 0) { | |||||
error("Expected string before assignment operator"); | |||||
} | |||||
BXParser::Token BXParser::getToken(const BXVariables &vars) { | |||||
Token tok; | |||||
tok.line = line(); | |||||
tok.ch = ch(); | |||||
doAssignment(); | |||||
prevOper = op; | |||||
key = std::move(value); | |||||
value.clear(); | |||||
} | |||||
int ch = peek(); | |||||
if (ch == EOF) { | |||||
tok.kind = TokenKind::E_O_F; | |||||
return tok; | |||||
} | } | ||||
doAssignment(); | |||||
} | |||||
if (ch == '\t' || ch == ' ') { | |||||
tok.kind = TokenKind::INDENTATION; | |||||
do { | |||||
get(); | |||||
ch = peek(); | |||||
} while (ch == '\t' || ch == ' '); | |||||
return tok; | |||||
} else if (ch == '\n' || ch == '\r') { | |||||
tok.kind = TokenKind::NEWLINE; | |||||
do { | |||||
get(); | |||||
ch = peek(); | |||||
} while (ch == '\n' || ch == '\r'); | |||||
return tok; | |||||
} | |||||
void BXParser::parseList(const BXVariables &vars, std::vector<std::string> &values) { | |||||
while (true) { | |||||
int ch2 = peek(2); | |||||
if (ch == ',') { | |||||
get(); | |||||
tok.kind = TokenKind::COMMA; | |||||
} else if (ch == ':' && ch2 == '=') { | |||||
get(); get(); | |||||
tok.kind = TokenKind::COLON_EQUALS; | |||||
} else if (ch == '+' && ch2 == '=') { | |||||
get(); get(); | |||||
tok.kind = TokenKind::PLUS_EQUALS; | |||||
} else if (ch == '=' && ch2 == '+') { | |||||
get(); get(); | |||||
tok.kind = TokenKind::EQUALS_PLUS; | |||||
} else if (ch == '|' && ch2 == '=') { | |||||
get(); get(); | |||||
tok.kind = TokenKind::BAR_EQUALS; | |||||
} else if (ch == '$' && ch2 == '{') { | |||||
get(); get(); | |||||
skipWhitespace(); | skipWhitespace(); | ||||
std::string value; | |||||
if (!parseString(vars, value)) { | |||||
break; | |||||
tok.kind = TokenKind::EXPANSION; | |||||
tok.str = readString(vars); | |||||
skipWhitespace(); | |||||
if (peek() != '}') { | |||||
error("Expected a '}' after a '${' expansion."); | |||||
} | } | ||||
get(); | |||||
} else if (ch == '$') { | |||||
get(); | |||||
tok.kind = TokenKind::EXPANSION; | |||||
tok.str = readString(vars); | |||||
} else { | |||||
tok.kind = TokenKind::STRING; | |||||
tok.str = readString(vars); | |||||
} | |||||
values.push_back(std::move(value)); | |||||
while ((ch = peek()) == '\t' || ch == ' ') { | |||||
get(); | |||||
} | } | ||||
return tok; | |||||
} | |||||
BXParser::Token BXParser::readToken(const BXVariables &vars) { | |||||
Token t = tok_; | |||||
tok_ = getToken(vars); | |||||
return t; | |||||
} | } | ||||
void BXWriter::escape(const std::string &str) { | void BXWriter::escape(const std::string &str) { | ||||
buf_.put('\n'); | buf_.put('\n'); | ||||
} | } | ||||
} | } | ||||
void BXParser::parse(BXVariables &vars, bool oneLine) { | |||||
readToken(vars); | |||||
while (true) { | |||||
if (peekToken().kind == TokenKind::E_O_F) { | |||||
break; | |||||
} else if (peekToken().kind != TokenKind::STRING) { | |||||
error("Expected string, got", peekToken().kind); | |||||
} | |||||
Token t = readToken(vars); | |||||
std::string key = t.str; | |||||
std::vector<std::string> &var = vars[key]; | |||||
void (*addVal)(std::vector<std::string> &var, std::string val); | |||||
switch (peekToken().kind) { | |||||
case TokenKind::COLON_EQUALS: | |||||
var.clear(); | |||||
// Fallthrough | |||||
case TokenKind::PLUS_EQUALS: | |||||
addVal = [](auto &var, auto val) { | |||||
var.push_back(std::move(val)); | |||||
}; | |||||
break; | |||||
case TokenKind::EQUALS_PLUS: | |||||
addVal = [](auto &var, auto val) { | |||||
var.insert(var.begin(), std::move(val)); | |||||
}; | |||||
break; | |||||
case TokenKind::BAR_EQUALS: | |||||
addVal = [](auto &var, auto val) { | |||||
for (auto &v: var) { | |||||
if (v == val) { | |||||
return; | |||||
} | |||||
} | |||||
var.push_back(val); | |||||
}; | |||||
break; | |||||
default: | |||||
error("Expected operator, got", peekToken().kind); | |||||
} | |||||
readToken(vars); | |||||
parseList(vars, var, addVal, oneLine); | |||||
} | |||||
} | |||||
void BXParser::parseList( | |||||
BXVariables &vars, std::vector<std::string> &var, | |||||
void (*addVal)(std::vector<std::string> &var, std::string val), | |||||
bool oneLine) { | |||||
while (true) { | |||||
Token tok = peekToken(); | |||||
switch (tok.kind) { | |||||
case TokenKind::NEWLINE: | |||||
if (oneLine) { | |||||
return; | |||||
} | |||||
readToken(vars); | |||||
if (peekToken().kind != TokenKind::INDENTATION) { | |||||
return; | |||||
} | |||||
readToken(vars); // Read indentation | |||||
break; | |||||
case TokenKind::STRING: | |||||
addVal(var, std::move(tok.str)); | |||||
readToken(vars); | |||||
break; | |||||
case TokenKind::COMMA: | |||||
readToken(vars); | |||||
return; | |||||
case TokenKind::E_O_F: | |||||
return; | |||||
case TokenKind::EXPANSION: | |||||
for (auto &v: vars[tok.str]) { | |||||
addVal(var, v); | |||||
} | |||||
readToken(vars); | |||||
break; | |||||
default: | |||||
error("Unexpected token", tok.kind); | |||||
} | |||||
} | |||||
} | |||||
void BXParser::parseList(BXVariables &vars, std::vector<std::string> &var) { | |||||
auto addVal = [](auto &var, auto val) { | |||||
var.push_back(std::move(val)); | |||||
}; | |||||
readToken(vars); | |||||
parseList(vars, var, addVal, false); | |||||
} |
class BXParser { | class BXParser { | ||||
public: | public: | ||||
static const int FLAG_NONE = 0; | |||||
static const int FLAG_ONE_LINE = 1 << 0; | |||||
enum class TokenKind { | |||||
E_O_F, | |||||
INDENTATION, | |||||
NEWLINE, | |||||
COMMA, | |||||
COLON_EQUALS, | |||||
PLUS_EQUALS, | |||||
EQUALS_PLUS, | |||||
BAR_EQUALS, | |||||
EXPANSION, | |||||
STRING, | |||||
NONE, | |||||
}; | |||||
BXParser(bufio::IStream &stream, int flags = FLAG_NONE, int line = 1, int ch = 1): | |||||
flags_(flags), line_(line), ch_(ch), buf_(stream) {} | |||||
struct Token { | |||||
TokenKind kind; | |||||
std::string str; | |||||
int line; | |||||
int ch; | |||||
}; | |||||
void parse(BXVariables &vars); | |||||
void parseList(const BXVariables &vars, std::vector<std::string> &values); | |||||
BXParser(bufio::IStream &stream, int line = 1, int ch = 1): | |||||
line_(line), ch_(ch), buf_(stream) {} | |||||
int get(); | int get(); | ||||
int peek(size_t count = 1) { return buf_.peek(count); } | int peek(size_t count = 1) { return buf_.peek(count); } | ||||
int line() const { return line_; } | int line() const { return line_; } | ||||
int ch() const { return ch_; } | int ch() const { return ch_; } | ||||
private: | |||||
enum class Operator { | |||||
COLON_EQUALS, | |||||
PLUS_EQUALS, | |||||
EQUALS_PLUS, | |||||
BAR_EQUALS, | |||||
NONE, | |||||
}; | |||||
Token readToken(const BXVariables &vars); | |||||
Token &peekToken() { return tok_; } | |||||
void parse(BXVariables &vars, bool oneLine = false); | |||||
void parseLine(BXVariables &vars) { parse(vars, true); } | |||||
void parseList(BXVariables &vars, std::vector<std::string> &list); | |||||
private: | |||||
[[noreturn]] void error(std::string); | [[noreturn]] void error(std::string); | ||||
[[noreturn]] void error(std::string, TokenKind); | |||||
Operator readOperator(); | |||||
void skipWhitespaceLine(); | |||||
std::string readIdent(const BXVariables &vars); | |||||
void skipWhitespace(); | void skipWhitespace(); | ||||
Token getToken(const BXVariables &vars); | |||||
std::string readString(const BXVariables &vars); | |||||
std::string readQuotedString(const BXVariables &vars); | |||||
std::string readStringExpansion(const BXVariables &vars); | |||||
char readEscape(); | |||||
void parseList( | |||||
BXVariables &vars, std::vector<std::string> &var, | |||||
void (*addVal)(std::vector<std::string> &var, std::string val), | |||||
bool oneLine); | |||||
char parseEscape(); | |||||
void parseExpansion(const BXVariables &vars, std::vector<std::string> &values); | |||||
void parseQuotedExpansion(const BXVariables &vars, std::string &content); | |||||
void parseQuotedString(const BXVariables &vars, std::string &content); | |||||
bool parseString(const BXVariables &vars, std::string &content, int sep = -1); | |||||
bool parseIdentifier(std::string &content); | |||||
int flags_; | |||||
int line_; | int line_; | ||||
int ch_; | int ch_; | ||||
Token tok_; | |||||
bufio::IBuf<> buf_; | bufio::IBuf<> buf_; | ||||
}; | }; |
} | } | ||||
void CompileStep::doBuild(const std::string &outDir) { | void CompileStep::doBuild(const std::string &outDir) { | ||||
bool verboseCommand = global::verbose >= 1; | |||||
if (!verboseCommand) { | |||||
logger::log("Compile " + path_); | |||||
} | |||||
std::string objPath = toolchain::objectFilePath(path_, outDir); | std::string objPath = toolchain::objectFilePath(path_, outDir); | ||||
std::string dirPath = sys::dirname(objPath); | std::string dirPath = sys::dirname(objPath); | ||||
BXWriter writer(f); | BXWriter writer(f); | ||||
writer.write(newCachedVars); | writer.write(newCachedVars); | ||||
sys::execute(command, nullptr, verboseCommand); | |||||
sys::ProcConf conf; | |||||
conf.print = true; | |||||
switch (type_) { | |||||
case toolchain::FileType::C: | |||||
conf.prefix = "(CC)"; | |||||
break; | |||||
case toolchain::FileType::CXX: | |||||
conf.prefix = "(CXX)"; | |||||
break; | |||||
} | |||||
sys::execute(command, conf); | |||||
} | } | ||||
void CompileStep::doWriteCompDB(const std::string &outDir, compdb::Writer &w) { | void CompileStep::doWriteCompDB(const std::string &outDir, compdb::Writer &w) { | ||||
} | } | ||||
bufio::IFStream f(path_); | bufio::IFStream f(path_); | ||||
BXParser parser(f, BXParser::FLAG_ONE_LINE); | |||||
BXParser parser(f); | |||||
while (parser.peek() != EOF) { | while (parser.peek() != EOF) { | ||||
if (startsWith(parser, "//#bx")) { | if (startsWith(parser, "//#bx")) { | ||||
parser.parse(variables_); | |||||
parser.parseLine(variables_); | |||||
} else { | } else { | ||||
while (parser.peek() != EOF && parser.get() != '\n'); | while (parser.peek() != EOF && parser.get() != '\n'); | ||||
} | } |
toolchain::FileType type_; | toolchain::FileType type_; | ||||
bool checkHasChanged(const std::string &outDir) override; | bool checkHasChanged(const std::string &outDir) override; | ||||
toolchain::FileType doGetLinkType() override { return type_; } | |||||
void doBuild(const std::string &outDir) override; | void doBuild(const std::string &outDir) override; | ||||
void doWriteCompDB(const std::string &outDir, compdb::Writer &w) override; | void doWriteCompDB(const std::string &outDir, compdb::Writer &w) override; | ||||
std::vector<std::string> getPublicLDFlags(const std::string &outDir) override; | std::vector<std::string> getPublicLDFlags(const std::string &outDir) override; |
#include <condition_variable> | #include <condition_variable> | ||||
#include "compdb.h" | #include "compdb.h" | ||||
#include "toolchain.h" | |||||
class DepNode { | class DepNode { | ||||
public: | public: | ||||
virtual ~DepNode() = default; | virtual ~DepNode() = default; | ||||
const std::vector<std::shared_ptr<DepNode>> &children() { return deps_; } | const std::vector<std::shared_ptr<DepNode>> &children() { return deps_; } | ||||
toolchain::FileType linkType() { return doGetLinkType(); } | |||||
void addChild(std::shared_ptr<DepNode> node); | void addChild(std::shared_ptr<DepNode> node); | ||||
bool hasChanged(const std::string &outDir); | bool hasChanged(const std::string &outDir); | ||||
void startBuild(const std::string &outDir); | void startBuild(const std::string &outDir); | ||||
void joinBuild(); | void joinBuild(); | ||||
void writeCompDB(const std::string &outDir, compdb::Writer &w); | void writeCompDB(const std::string &outDir, compdb::Writer &w); | ||||
virtual std::vector<std::string> publicLDFlags(const std::string &outDir); | virtual std::vector<std::string> publicLDFlags(const std::string &outDir); | ||||
virtual std::vector<std::string> publicLDLibs(const std::string &outDir); | virtual std::vector<std::string> publicLDLibs(const std::string &outDir); | ||||
virtual std::vector<std::string> publicObjects(const std::string &outDir); | virtual std::vector<std::string> publicObjects(const std::string &outDir); | ||||
}; | }; | ||||
virtual bool checkHasChanged(const std::string &outDir) = 0; | virtual bool checkHasChanged(const std::string &outDir) = 0; | ||||
virtual toolchain::FileType doGetLinkType() = 0; | |||||
virtual void doBuild(const std::string &outDir) = 0; | virtual void doBuild(const std::string &outDir) = 0; | ||||
virtual void doWriteCompDB(const std::string &outDir, compdb::Writer &w) {} | virtual void doWriteCompDB(const std::string &outDir, compdb::Writer &w) {} | ||||
virtual std::vector<std::string> getPublicLDFlags(const std::string &outDir) { return {}; } | virtual std::vector<std::string> getPublicLDFlags(const std::string &outDir) { return {}; } |
} | } | ||||
void LinkStep::doBuild(const std::string &outDir) { | void LinkStep::doBuild(const std::string &outDir) { | ||||
logger::log("Link " + path_); | |||||
std::vector<std::string> command = linkCommand(outDir); | std::vector<std::string> command = linkCommand(outDir); | ||||
std::string dirPath = sys::dirname(outDir + '/' + path_); | std::string dirPath = sys::dirname(outDir + '/' + path_); | ||||
writer.write(newCachedVars); | writer.write(newCachedVars); | ||||
sys::mkdirp(dirPath); | sys::mkdirp(dirPath); | ||||
sys::execute(command, nullptr, global::verbose >= 1); | |||||
sys::ProcConf conf; | |||||
conf.print = true; | |||||
conf.prefix = "(LD)"; | |||||
sys::execute(command, conf); | |||||
} | |||||
toolchain::FileType LinkStep::doGetLinkType() { | |||||
for (auto &child: children()) { | |||||
if (child->linkType() == toolchain::FileType::CXX) { | |||||
return toolchain::FileType::CXX; | |||||
} | |||||
} | |||||
return toolchain::FileType::C; | |||||
} | } | ||||
std::vector<std::string> &LinkStep::linkCommand(const std::string &outDir) { | std::vector<std::string> &LinkStep::linkCommand(const std::string &outDir) { | ||||
// TODO: Don't use FileType::CXX hard-coded here | // TODO: Don't use FileType::CXX hard-coded here | ||||
linkCommand_ = toolchain::getLinkCommand( | linkCommand_ = toolchain::getLinkCommand( | ||||
publicLDFlags(outDir), publicLDLibs(outDir), | publicLDFlags(outDir), publicLDLibs(outDir), | ||||
toolchain::FileType::CXX, type_, objs, path_, outDir); | |||||
linkType(), type_, objs, path_, outDir); | |||||
hasLinkCommand_ = true; | hasLinkCommand_ = true; | ||||
return linkCommand_; | return linkCommand_; | ||||
} | } |
toolchain::TargetType type_; | toolchain::TargetType type_; | ||||
bool checkHasChanged(const std::string &outDir) override; | bool checkHasChanged(const std::string &outDir) override; | ||||
toolchain::FileType doGetLinkType() override; | |||||
void doBuild(const std::string &outDir) override; | void doBuild(const std::string &outDir) override; | ||||
bool hasLinkCommand_ = false; | bool hasLinkCommand_ = false; |
#include "LinkStep.h" | #include "LinkStep.h" | ||||
#include "logger.h" | #include "logger.h" | ||||
namespace build { | |||||
static std::string extension(const std::string &path) { | static std::string extension(const std::string &path) { | ||||
size_t idx = path.find_last_of('.'); | size_t idx = path.find_last_of('.'); | ||||
if (idx >= std::string::npos) { | if (idx >= std::string::npos) { | ||||
} | } | ||||
} | } | ||||
static std::string findTargetName(const BXVariables &variables) { | |||||
std::string findTargetName(const BXVariables &variables) { | |||||
auto it = variables.find("target"); | auto it = variables.find("target"); | ||||
if (it != variables.end() && it->second.size() != 0) { | if (it != variables.end() && it->second.size() != 0) { | ||||
return it->second[0]; | return it->second[0]; | ||||
return link; | return link; | ||||
} | } | ||||
} |
#include "DepNode.h" | #include "DepNode.h" | ||||
#include "BXParser.h" | #include "BXParser.h" | ||||
namespace build { | |||||
std::string findTargetName(const BXVariables &variables); | |||||
std::unique_ptr<DepNode> buildDepTree(const std::string &outDir, BXVariables variables); | std::unique_ptr<DepNode> buildDepTree(const std::string &outDir, BXVariables variables); | ||||
} |
#include <stdexcept> | #include <stdexcept> | ||||
#include <system_error> | #include <system_error> | ||||
#include <unordered_set> | #include <unordered_set> | ||||
#include <atomic> | |||||
#include "logger.h" | #include "logger.h" | ||||
#include "globals.h" | #include "globals.h" | ||||
static thread_local std::unordered_set<std::string> mkdirp_set; | static thread_local std::unordered_set<std::string> mkdirp_set; | ||||
static std::atomic<int> signalCounter(0); | |||||
struct SigCtx { | |||||
SigCtx(bool enabled): enabled(enabled) { | |||||
if (enabled && signalCounter++ == 0) { | |||||
signal(SIGINT, SIG_IGN); | |||||
} | |||||
} | |||||
~SigCtx() { | |||||
if (enabled && --signalCounter == 0) { | |||||
signal(SIGINT, SIG_DFL); | |||||
} | |||||
} | |||||
bool enabled; | |||||
}; | |||||
std::string sanitizePath(const std::string &path) { | std::string sanitizePath(const std::string &path) { | ||||
std::string npath; | std::string npath; | ||||
size_t idx = 0; | size_t idx = 0; | ||||
argv.push_back("-p"); | argv.push_back("-p"); | ||||
argv.push_back("--"); | argv.push_back("--"); | ||||
argv.push_back(path); | argv.push_back(path); | ||||
execute(argv, nullptr, global::verbose >= 2); | |||||
ProcConf conf; | |||||
conf.print = global::verbose >= 1; | |||||
conf.prefix = "(MKDIRP)"; | |||||
execute(argv, conf); | |||||
} | } | ||||
void rmrf(const std::string &path) { | void rmrf(const std::string &path) { | ||||
argv.push_back("-rf"); | argv.push_back("-rf"); | ||||
argv.push_back("--"); | argv.push_back("--"); | ||||
argv.push_back(path); | argv.push_back(path); | ||||
execute(argv, nullptr, global::verbose >= 2); | |||||
ProcConf conf; | |||||
conf.print = global::verbose >= 1; | |||||
conf.prefix = "(RMRF)"; | |||||
execute(argv, conf); | |||||
} | } | ||||
void execute(const std::vector<std::string> &args, std::string *output, bool print) { | |||||
if (print) { | |||||
void execute(const std::vector<std::string> &args, const ProcConf &conf) { | |||||
if (conf.print || global::verbose >= 2) { | |||||
std::string str; | std::string str; | ||||
for (size_t i = 0; i < args.size(); ++i) { | for (size_t i = 0; i < args.size(); ++i) { | ||||
if (i != 0) { | if (i != 0) { | ||||
} | } | ||||
str += args[i]; | str += args[i]; | ||||
} | } | ||||
if (conf.prefix.size() > 0) { | |||||
str = conf.prefix + " " + str; | |||||
} | |||||
logger::log(str); | logger::log(str); | ||||
} | } | ||||
for (size_t i = 1; i < args.size(); ++i) { | for (size_t i = 1; i < args.size(); ++i) { | ||||
argv.push_back(args[i].c_str()); | argv.push_back(args[i].c_str()); | ||||
} | } | ||||
argv.push_back(nullptr); | argv.push_back(nullptr); | ||||
int fds[2]; | |||||
if (pipe(fds) < 0) { | |||||
throw std::runtime_error(std::string("fork: ") + strerror(errno)); | |||||
int stdoutPipe[2]; | |||||
if (conf.output == nullptr) { | |||||
stdoutPipe[1] = STDOUT_FILENO; | |||||
} else { | |||||
if (pipe(stdoutPipe) < 0) { | |||||
throw std::runtime_error(std::string("pipe: ") + strerror(errno)); | |||||
} | |||||
} | } | ||||
SigCtx sigCtx(conf.forwardSignals); | |||||
pid_t child = fork(); | pid_t child = fork(); | ||||
if (child == 0) { | if (child == 0) { | ||||
close(fds[0]); | |||||
dup2(fds[1], 1); | |||||
signal(SIGINT, SIG_DFL); | |||||
if (conf.output != nullptr) { | |||||
close(stdoutPipe[0]); | |||||
dup2(stdoutPipe[1], 1); | |||||
} | |||||
// So, from what I've read, execvp should never modify its argv; so this should be fine? | // So, from what I've read, execvp should never modify its argv; so this should be fine? | ||||
if (execvp(argv[0], (char *const *)argv.data()) < 0) { | if (execvp(argv[0], (char *const *)argv.data()) < 0) { | ||||
} | } | ||||
// This shouldn't happen | // This shouldn't happen | ||||
exit(0); | |||||
abort(); | |||||
} else if (child < 0) { | } else if (child < 0) { | ||||
throw std::runtime_error(std::string("fork: ") + strerror(errno)); | throw std::runtime_error(std::string("fork: ") + strerror(errno)); | ||||
} | } | ||||
close(fds[1]); | |||||
if (output == nullptr) { | |||||
close(fds[0]); | |||||
} else { | |||||
if (conf.output != nullptr) { | |||||
close(stdoutPipe[1]); | |||||
char buf[1025]; | char buf[1025]; | ||||
while (true) { | while (true) { | ||||
ssize_t num = read(fds[0], buf, sizeof(buf) - 1); | |||||
ssize_t num = read(stdoutPipe[0], buf, sizeof(buf) - 1); | |||||
if (num < 0 && errno != EAGAIN) { | if (num < 0 && errno != EAGAIN) { | ||||
close(fds[0]); | |||||
close(stdoutPipe[0]); | |||||
throw std::runtime_error( | throw std::runtime_error( | ||||
std::string("read: ") + strerror(errno) + | std::string("read: ") + strerror(errno) + | ||||
" (fd: " + std::to_string(fds[0]) + ")"); | |||||
" (fd: " + std::to_string(stdoutPipe[0]) + ")"); | |||||
} else if (num < 0) { | } else if (num < 0) { | ||||
continue; | continue; | ||||
} | } | ||||
if (num == 0) { | if (num == 0) { | ||||
close(fds[0]); | |||||
close(stdoutPipe[0]); | |||||
break; | break; | ||||
} | } | ||||
buf[num] = '\0'; | buf[num] = '\0'; | ||||
*output += buf; | |||||
*conf.output += buf; | |||||
} | } | ||||
} | } | ||||
" terminated due to " + strsignal(WTERMSIG(wstatus))); | " terminated due to " + strsignal(WTERMSIG(wstatus))); | ||||
} | } | ||||
if (output != nullptr && output->back() == '\n') { | |||||
output->pop_back(); | |||||
if (conf.output != nullptr && conf.output->back() == '\n') { | |||||
conf.output->pop_back(); | |||||
} | } | ||||
} | } | ||||
argv.push_back("--"); | argv.push_back("--"); | ||||
argv.push_back(from); | argv.push_back(from); | ||||
argv.push_back(to); | argv.push_back(to); | ||||
sys::execute(argv, nullptr, global::verbose > 2); | |||||
sys::execute(argv, ProcConf{}); | |||||
} | } | ||||
std::string dirname(const std::string &path) { | std::string dirname(const std::string &path) { |
} | } | ||||
}; | }; | ||||
struct ProcConf { | |||||
bool print = false; | |||||
bool forwardSignals = false; | |||||
std::string prefix; | |||||
std::string *output = nullptr; | |||||
}; | |||||
std::string sanitizePath(const std::string &path); | std::string sanitizePath(const std::string &path); | ||||
FileInfo fileInfo(const std::string &path); | FileInfo fileInfo(const std::string &path); | ||||
bool fileExists(const std::string &path); | bool fileExists(const std::string &path); | ||||
void mkdirp(const std::string &path); | void mkdirp(const std::string &path); | ||||
void rmrf(const std::string &path); | void rmrf(const std::string &path); | ||||
void execute(const std::vector<std::string> &args, std::string *output, bool print); | |||||
void execute(const std::vector<std::string> &args, const ProcConf &conf); | |||||
void readDir(const std::string &path, std::vector<std::string> &files); | void readDir(const std::string &path, std::vector<std::string> &files); | ||||
void chdir(const std::string &path); | void chdir(const std::string &path); | ||||
std::string cwd(); | std::string cwd(); |
// Execute $(PKG_CONFIG) --cflags $(PKGS) | // Execute $(PKG_CONFIG) --cflags $(PKGS) | ||||
std::string output; | std::string output; | ||||
sys::execute(argv, &output, global::verbose >= 2); | |||||
sys::ProcConf conf; | |||||
conf.print = global::verbose >= 1; | |||||
conf.prefix = "(PKGCONFIG)"; | |||||
conf.output = &output; | |||||
sys::execute(argv, conf); | |||||
parseWhitespaceSeparated(output, flags); | parseWhitespaceSeparated(output, flags); | ||||
} | } | ||||
// Execute $(PKG_CONFIG) --cflags $(PKGS) | // Execute $(PKG_CONFIG) --cflags $(PKGS) | ||||
std::string output; | std::string output; | ||||
sys::execute(argv, &output, global::verbose >= 2); | |||||
sys::ProcConf conf; | |||||
conf.print = global::verbose >= 1; | |||||
conf.prefix = "(PKGCONFIG)"; | |||||
conf.output = &output; | |||||
sys::execute(argv, conf); | |||||
parseWhitespaceSeparated(output, flags); | parseWhitespaceSeparated(output, flags); | ||||
} | } | ||||
// Execute $(compiler) $(flags) -MM $< | // Execute $(compiler) $(flags) -MM $< | ||||
std::string output; | std::string output; | ||||
sys::execute(argv, &output, global::verbose >= 2); | |||||
sys::ProcConf conf; | |||||
conf.print = global::verbose >= 1; | |||||
conf.output = &output; | |||||
conf.prefix = "(DEP)"; | |||||
sys::execute(argv, conf); | |||||
std::vector<std::string> deps; | std::vector<std::string> deps; | ||||
size_t idx = output.find(':'); | size_t idx = output.find(':'); |
} | } | ||||
} | } | ||||
static void lexeq(BXVariables &vars, const char *str, std::initializer_list<BXParser::Token> list) { | |||||
INFO("Source: " << str); | |||||
bufio::ISStream stream(str); | |||||
BXParser parser(stream); | |||||
parser.readToken(vars); | |||||
size_t i = 1; | |||||
for (auto &expected: list) { | |||||
BXParser::Token tok = parser.readToken(vars); | |||||
if (tok.kind != expected.kind) { | |||||
INFO("Expected: " << (int)expected.kind); | |||||
INFO("Actual: " << (int)tok.kind); | |||||
FAIL("Token " << i << " doesn't match"); | |||||
} | |||||
if (tok.str != expected.str) { | |||||
INFO("Expected: '" << expected.str << '\''); | |||||
INFO("Actual: '" << tok.str << '\''); | |||||
FAIL("Token " << i << "'s string doesn't match"); | |||||
} | |||||
if (tok.kind == BXParser::TokenKind::E_O_F) { | |||||
return; | |||||
} | |||||
i += 1; | |||||
} | |||||
} | |||||
static void lexeq(const char *str, std::initializer_list<BXParser::Token> list) { | |||||
BXVariables vars; | |||||
lexeq(vars, str, list); | |||||
} | |||||
TEST_CASE("BXParser lex", "[BXParser][lex]") { | |||||
lexeq("hello world", { | |||||
{ BXParser::TokenKind::STRING, "hello" }, | |||||
{ BXParser::TokenKind::STRING, "world" }, | |||||
{ BXParser::TokenKind::E_O_F }, | |||||
}); | |||||
lexeq("hello := what's up", { | |||||
{ BXParser::TokenKind::STRING, "hello" }, | |||||
{ BXParser::TokenKind::COLON_EQUALS }, | |||||
{ BXParser::TokenKind::STRING, "what's" }, | |||||
{ BXParser::TokenKind::STRING, "up" }, | |||||
{ BXParser::TokenKind::E_O_F }, | |||||
}); | |||||
lexeq("\t\t \t, := += =+\n\n\r\nhello", { | |||||
{ BXParser::TokenKind::INDENTATION }, | |||||
{ BXParser::TokenKind::COMMA }, | |||||
{ BXParser::TokenKind::COLON_EQUALS }, | |||||
{ BXParser::TokenKind::PLUS_EQUALS }, | |||||
{ BXParser::TokenKind::EQUALS_PLUS }, | |||||
{ BXParser::TokenKind::NEWLINE }, | |||||
{ BXParser::TokenKind::STRING, "hello" }, | |||||
{ BXParser::TokenKind::E_O_F }, | |||||
}); | |||||
} | |||||
TEST_CASE("BXParser lex string interpolation", "[BXParser][lex]") { | |||||
BXVariables vars = { | |||||
{ "foo", { "hello"} }, | |||||
}; | |||||
lexeq(vars, "hey \"$foo\" lol", { | |||||
{ BXParser::TokenKind::STRING, "hey" }, | |||||
{ BXParser::TokenKind::STRING, "hello" }, | |||||
{ BXParser::TokenKind::STRING, "lol" }, | |||||
}); | |||||
lexeq(vars, "hey \"no$foo\" lol", { | |||||
{ BXParser::TokenKind::STRING, "hey" }, | |||||
{ BXParser::TokenKind::STRING, "nohello" }, | |||||
{ BXParser::TokenKind::STRING, "lol" }, | |||||
}); | |||||
lexeq(vars, "hey \"Xx${foo}xX\" lol", { | |||||
{ BXParser::TokenKind::STRING, "hey" }, | |||||
{ BXParser::TokenKind::STRING, "XxhelloxX" }, | |||||
{ BXParser::TokenKind::STRING, "lol" }, | |||||
}); | |||||
} | |||||
TEST_CASE("BXParser lex string interpolation without quotes", "[BXParser][lex]") { | |||||
BXVariables vars = { | |||||
{ "foo", { "hello"} }, | |||||
}; | |||||
lexeq(vars, "hey Xx$foo lol", { | |||||
{ BXParser::TokenKind::STRING, "hey" }, | |||||
{ BXParser::TokenKind::STRING, "Xxhello" }, | |||||
{ BXParser::TokenKind::STRING, "lol" }, | |||||
}); | |||||
lexeq(vars, "hey Xx${ foo }xX lol", { | |||||
{ BXParser::TokenKind::STRING, "hey" }, | |||||
{ BXParser::TokenKind::STRING, "XxhelloxX" }, | |||||
{ BXParser::TokenKind::STRING, "lol" }, | |||||
}); | |||||
} | |||||
TEST_CASE("BXParser lex array expansion", "[BXParser][lex]") { | |||||
lexeq("hey $foo lol", { | |||||
{ BXParser::TokenKind::STRING, "hey" }, | |||||
{ BXParser::TokenKind::EXPANSION, "foo" }, | |||||
{ BXParser::TokenKind::STRING, "lol" }, | |||||
}); | |||||
lexeq("hey ${foo} lol", { | |||||
{ BXParser::TokenKind::STRING, "hey" }, | |||||
{ BXParser::TokenKind::EXPANSION, "foo" }, | |||||
{ BXParser::TokenKind::STRING, "lol" }, | |||||
}); | |||||
lexeq("hey ${ \tfoo\n \n} lol", { | |||||
{ BXParser::TokenKind::STRING, "hey" }, | |||||
{ BXParser::TokenKind::EXPANSION, "foo" }, | |||||
{ BXParser::TokenKind::STRING, "lol" }, | |||||
}); | |||||
} | |||||
TEST_CASE("BXParser parsing", "[BXParser][parse]") { | TEST_CASE("BXParser parsing", "[BXParser][parse]") { | ||||
expecteq("hello := 10 world := 20", { | |||||
expecteq("hello := 10, world := 20", { | |||||
{ "hello", { "10" } }, | { "hello", { "10" } }, | ||||
{ "world", { "20" } }, | { "world", { "20" } }, | ||||
}); | }); | ||||
expecteq("foo := hello world bar := 10 20 30 baz := 0", { | |||||
expecteq("foo := hello world, bar := 10 20 30, baz := 0", { | |||||
{ "foo", { "hello", "world" } }, | { "foo", { "hello", "world" } }, | ||||
{ "bar", { "10", "20", "30" } }, | { "bar", { "10", "20", "30" } }, | ||||
{ "baz", { "0" } }, | { "baz", { "0" } }, | ||||
} | } | ||||
TEST_CASE("BXParser parsing variables", "[BXParser][parse]") { | TEST_CASE("BXParser parsing variables", "[BXParser][parse]") { | ||||
expecteq("foo := \"hello world\" bar := $foo", { | |||||
expecteq("foo := \"hello world\", bar := $foo", { | |||||
{ "foo", { "hello world" } }, | { "foo", { "hello world" } }, | ||||
{ "bar", { "hello world" } }, | { "bar", { "hello world" } }, | ||||
}); | }); | ||||
expecteq("foo := hello world bar := hey $foo what's up", { | |||||
expecteq("foo := hello world, bar := hey $foo what's up", { | |||||
{ "foo", { "hello", "world" } }, | { "foo", { "hello", "world" } }, | ||||
{ "bar", { "hey", "hello", "world", "what's", "up" } }, | { "bar", { "hey", "hello", "world", "what's", "up" } }, | ||||
}); | }); | ||||
} | } | ||||
TEST_CASE("BXParser append/prepend", "[BXParser][parse]") { | TEST_CASE("BXParser append/prepend", "[BXParser][parse]") { | ||||
expecteq("foo := 10 foo += 20 foo += 30", { | |||||
expecteq("foo := 10, foo += 20, foo += 30", { | |||||
{ "foo", { "10", "20", "30" } }, | { "foo", { "10", "20", "30" } }, | ||||
}); | }); | ||||
expecteq("foo := 10 foo =+ 20 foo =+ 30", { | |||||
expecteq("foo := 10, foo =+ 20, foo =+ 30", { | |||||
{ "foo", { "30", "20", "10" } }, | { "foo", { "30", "20", "10" } }, | ||||
}); | }); | ||||
} | } | ||||
TEST_CASE("BXParser |=", "[BXParser][parse]") { | |||||
expecteq("foo := 10, foo |= 10, foo |= 20", { | |||||
{ "foo", { "10", "20" } }, | |||||
}); | |||||
} | |||||
TEST_CASE("BXParser newline separation", "[BXParser][parse]") { | TEST_CASE("BXParser newline separation", "[BXParser][parse]") { | ||||
expecteq( | expecteq( | ||||
"foo := 10 20\n" | "foo := 10 20\n" | ||||
{ "bar", { "30", "40" } }, | { "bar", { "30", "40" } }, | ||||
}); | }); | ||||
} | } | ||||
TEST_CASE("BXParser newline continuation", "[BXParser][parse]") { | |||||
expecteq( | |||||
"foo := 10 20\n" | |||||
"\t30 40\n" | |||||
"bar := hello world\n" | |||||
"\tbaz \":=\" 30\n", { | |||||
{ "foo", { "10", "20", "30", "40" } }, | |||||
{ "bar", { "hello", "world", "baz", ":=", "30" } }, | |||||
}); | |||||
} |