| 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" } }, | |||||
| }); | |||||
| } |