| SRCS = src/BBBParser.cc src/SourceFile.cc src/main.cc | |||||
| HDRS = src/BBBParser.h src/SourceFile.h | |||||
| SRCS = src/BBBParser.cc src/SourceFile.cc src/toolchain.cc src/globals.cc src/main.cc | |||||
| HDRS = src/BBBParser.h src/SourceFile.h src/toolchain.h src/globals.h src/parallel.h | |||||
| BUILD = build | BUILD = build | ||||
| OBJS = $(patsubst %,$(BUILD)/%.o,$(SRCS)) | OBJS = $(patsubst %,$(BUILD)/%.o,$(SRCS)) | ||||
| CFLAGS = -g -Wall -Wextra -Wno-unused-parameter | CFLAGS = -g -Wall -Wextra -Wno-unused-parameter |
| /bbbuild |
| cflags := -O3 |
| #include <stdio.h> | #include <stdio.h> | ||||
| //#bb ldflags := -lm | //#bb ldflags := -lm | ||||
| //#bb pkgs := libsdl cairo | |||||
| //#bb pkgs := sdl2 cairo | |||||
| int main() { | int main() { | ||||
| printf("sin(PI) is %f\n", sin(M_PI)); | printf("sin(PI) is %f\n", sin(M_PI)); |
| #include <iostream> | #include <iostream> | ||||
| #include <fstream> | #include <fstream> | ||||
| #include <sstream> | #include <sstream> | ||||
| #include <string.h> | |||||
| static bool startsWith(BBBParser &parser, const char *str) { | static bool startsWith(BBBParser &parser, const char *str) { | ||||
| for (size_t i = 0; str[i] != '\0'; ++i) { | for (size_t i = 0; str[i] != '\0'; ++i) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| SourceFile::SourceFile(std::string dir, std::string name, BBBParser::Variables vars): | |||||
| dir_(std::move(dir)), name_(std::move(name)), vars_(std::move(vars)) { | |||||
| SourceFile::FileType SourceFile::fileTypeFrom(const std::string &name) { | |||||
| size_t idx = name.find_last_of('.'); | |||||
| if (idx >= std::string::npos) { | |||||
| printf("%s: no ftype\n", name.c_str()); | |||||
| return FileType::UNKNOWN; | |||||
| } | |||||
| char ext[16] = { 0 }; | |||||
| strncpy(ext, name.c_str() + idx, sizeof(ext) - 1); | |||||
| if (strcmp(ext, ".c") == 0) { | |||||
| return FileType::C; | |||||
| } else if ( | |||||
| (strcmp(ext, ".C") == 0) || | |||||
| (strcmp(ext, ".cc") == 0) || | |||||
| (strcmp(ext, ".cpp") == 0) || | |||||
| (strcmp(ext, ".cxx") == 0)) { | |||||
| return FileType::CXX; | |||||
| } | |||||
| return FileType::UNKNOWN; | |||||
| } | |||||
| SourceFile::SourceFile(std::string dir, std::string name, FileType type, BBBParser::Variables vars): | |||||
| dir_(std::move(dir)), name_(std::move(name)), type_(type), vars_(std::move(vars)) { | |||||
| std::ifstream file(dir_ + "/" + name_); | std::ifstream file(dir_ + "/" + name_); | ||||
| BBBParser parser(file, BBBParser::FLAG_ONE_LINE); | BBBParser parser(file, BBBParser::FLAG_ONE_LINE); | ||||
| while (file.good()) { | while (file.good()) { | ||||
| if (startsWith(parser, "//#bb")) { | if (startsWith(parser, "//#bb")) { | ||||
| parser.parse(vars_); | parser.parse(vars_); | ||||
| contains_bbb_ = true; | |||||
| } else { | } else { | ||||
| while (file.good() && parser.get() != '\n'); | while (file.good() && parser.get() != '\n'); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| const std::vector<std::string> *SourceFile::variable(const std::string &name) const { | |||||
| auto it = vars_.find(name); | |||||
| if (it == vars_.end()) { | |||||
| return nullptr; | |||||
| } | |||||
| return &it->second; | |||||
| } |
| class SourceFile { | class SourceFile { | ||||
| public: | public: | ||||
| SourceFile(std::string dir, std::string name, BBBParser::Variables vars); | |||||
| enum FileType { | |||||
| UNKNOWN, C, CXX, | |||||
| }; | |||||
| static FileType fileTypeFrom(const std::string &name); | |||||
| SourceFile(std::string dir, std::string name, FileType type, BBBParser::Variables vars); | |||||
| const std::string &dir() const { return dir_; } | const std::string &dir() const { return dir_; } | ||||
| const std::string &name() const { return name_; } | const std::string &name() const { return name_; } | ||||
| FileType type() const { return type_; } | |||||
| const BBBParser::Variables &vars() const { return vars_; } | const BBBParser::Variables &vars() const { return vars_; } | ||||
| const std::vector<std::string> *variable(const std::string &name) const; | |||||
| private: | private: | ||||
| std::string dir_; | std::string dir_; | ||||
| std::string name_; | std::string name_; | ||||
| FileType type_; | |||||
| BBBParser::Variables vars_; | BBBParser::Variables vars_; | ||||
| bool contains_bbb_ = false; | |||||
| }; | }; |
| #include "globals.h" | |||||
| namespace global { | |||||
| bool verbose = false; | |||||
| } |
| #pragma once | |||||
| namespace global { | |||||
| extern bool verbose; | |||||
| } |
| #include "SourceFile.h" | #include "SourceFile.h" | ||||
| #include "BBBParser.h" | #include "BBBParser.h" | ||||
| #include "parallel.h" | |||||
| #include "toolchain.h" | |||||
| #include "globals.h" | |||||
| static void readDir(std::string dir, std::vector<SourceFile> &sources, | static void readDir(std::string dir, std::vector<SourceFile> &sources, | ||||
| BBBParser::Variables vars); | BBBParser::Variables vars); | ||||
| subvars.erase("files"); | subvars.erase("files"); | ||||
| readDir(path, sources, std::move(subvars)); | readDir(path, sources, std::move(subvars)); | ||||
| } else { | } else { | ||||
| sources.emplace_back(dir, name, vars); | |||||
| SourceFile::FileType type = SourceFile::fileTypeFrom(name); | |||||
| if (type != SourceFile::FileType::UNKNOWN) { | |||||
| sources.emplace_back(dir, name, type, vars); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| void printState(const std::vector<SourceFile> &sources) { | |||||
| static void printState(const std::vector<SourceFile> &sources) { | |||||
| for (auto &source: sources) { | for (auto &source: sources) { | ||||
| std::cout << source.dir() << '/' << source.name() << ":\n"; | std::cout << source.dir() << '/' << source.name() << ":\n"; | ||||
| for (auto &kv: source.vars()) { | for (auto &kv: source.vars()) { | ||||
| } | } | ||||
| } | } | ||||
| static void getCompileFlags(const SourceFile &source, std::vector<std::string> &flags) { | |||||
| const std::vector<std::string> *pkgs = source.variable("pkgs"); | |||||
| // TODO: Decide whether to put this before or after cflags/cxxflags | |||||
| if (pkgs != nullptr) { | |||||
| toolchain::pkgFlags(*pkgs, flags); | |||||
| } | |||||
| const std::vector<std::string> *cflags; | |||||
| switch (source.type()) { | |||||
| case SourceFile::FileType::C: | |||||
| cflags = source.variable("cflags"); | |||||
| break; | |||||
| case SourceFile::FileType::CXX: | |||||
| cflags = source.variable("cxxflags"); | |||||
| break; | |||||
| case SourceFile::FileType::UNKNOWN: | |||||
| break; | |||||
| } | |||||
| if (cflags != nullptr) { | |||||
| for (const std::string &flag: *cflags) { | |||||
| flags.push_back(flag.c_str()); | |||||
| } | |||||
| } | |||||
| } | |||||
| static void compileFile(const SourceFile &source, const std::string &outDir) { | |||||
| std::vector<std::string> flags; | |||||
| getCompileFlags(source, flags); | |||||
| toolchain::compile(flags, source.type(), source.dir(), source.name(), outDir); | |||||
| } | |||||
| static void compile(const std::vector<SourceFile> &sources, const std::string &outDir, int jobs) { | |||||
| parallel::parallel(jobs, sources, [&](const SourceFile &source) { | |||||
| compileFile(source, outDir); | |||||
| }); | |||||
| } | |||||
| int main(int argc, char **argv) { | int main(int argc, char **argv) { | ||||
| std::vector<std::string> srcDirs; | std::vector<std::string> srcDirs; | ||||
| std::string outdir = "bbbuild"; | |||||
| std::string outDir = "bbbuild"; | |||||
| int jobs = parallel::coreCount(); | |||||
| std::string workDir = ""; | |||||
| enum class Action { | enum class Action { | ||||
| BUILD, PRINT_STATE, | BUILD, PRINT_STATE, | ||||
| }; | }; | ||||
| Action action = Action::BUILD; | Action action = Action::BUILD; | ||||
| const char *shortopts = "o:hp"; | |||||
| const char *shortopts = "hvo:j:C:p"; | |||||
| const struct option opts[] = { | const struct option opts[] = { | ||||
| { "help", no_argument, NULL, 'h' }, | |||||
| { "output", required_argument, NULL, 'o' }, | |||||
| { "print-state", no_argument, NULL, 'p' }, | |||||
| { "help", no_argument, NULL, 'h' }, | |||||
| { "verbose", no_argument, NULL, 'v' }, | |||||
| { "output", required_argument, NULL, 'o' }, | |||||
| { "jobs", required_argument, NULL, 'j' }, | |||||
| { "directory", required_argument, NULL, 'C' }, | |||||
| { "print-state", no_argument, NULL, 'p' }, | |||||
| {}, | {}, | ||||
| }; | }; | ||||
| const char usage[] = | const char usage[] = | ||||
| "Usage: bbbuild [options...] [sources]\n" | "Usage: bbbuild [options...] [sources]\n" | ||||
| "\n" | "\n" | ||||
| " -h, --help " | |||||
| " -h, --help " | |||||
| "Show this help text.\n" | "Show this help text.\n" | ||||
| " -o, --output <dir> " | |||||
| "Set output directory.\n" | |||||
| " -p, --print-state " | |||||
| " -v, --verbose " | |||||
| "Show every command as it's executing." | |||||
| " -o, --output <dir> " | |||||
| "Set output directory. Default: bbbuild\n" | |||||
| " -j, --jobs <count> " | |||||
| "Set the number of jobs run simultaneously. " | |||||
| "Default: the number of cores in the machine.\n" | |||||
| " -C, --directory <dir> " | |||||
| "Change directory before doing anything else.\n" | |||||
| " -p, --print-state " | |||||
| "Print the state instead of building.\n"; | "Print the state instead of building.\n"; | ||||
| // Parse options from argv | // Parse options from argv | ||||
| puts(usage); | puts(usage); | ||||
| return 0; | return 0; | ||||
| case 'v': | |||||
| global::verbose = true; | |||||
| break; | |||||
| case 'o': | case 'o': | ||||
| outdir = optarg; | |||||
| outDir = optarg; | |||||
| break; | |||||
| case 'j': | |||||
| jobs = atoi(optarg); | |||||
| if (jobs <= 0) { | |||||
| fprintf(stderr, "Can't run %i jobs.\n", jobs); | |||||
| return 1; | |||||
| } | |||||
| break; | |||||
| case 'C': | |||||
| workDir = optarg; | |||||
| break; | break; | ||||
| case 'p': | case 'p': | ||||
| default: | default: | ||||
| printf("Unknown option: '%c'.\n%s", (char)c, usage); | printf("Unknown option: '%c'.\n%s", (char)c, usage); | ||||
| break; | |||||
| return 1; | return 1; | ||||
| } | } | ||||
| } | } | ||||
| // Change directory? | |||||
| if (workDir.size() > 0) { | |||||
| if (global::verbose) { | |||||
| fprintf(stderr, "Entering directory '%s'\n", workDir.c_str()); | |||||
| } | |||||
| if (chdir(workDir.c_str()) < 0) { | |||||
| perror(workDir.c_str()); | |||||
| return 1; | |||||
| } | |||||
| } | |||||
| // Parse non-opt argv as source dirs | // Parse non-opt argv as source dirs | ||||
| if (optind < argc) { | if (optind < argc) { | ||||
| while (optind < argc) { | while (optind < argc) { | ||||
| switch (action) { | switch (action) { | ||||
| case Action::BUILD: | case Action::BUILD: | ||||
| std::cerr << "Building is not implemented yet.\n"; | |||||
| abort(); | |||||
| compile(sources, outDir, jobs); | |||||
| break; | |||||
| case Action::PRINT_STATE: | case Action::PRINT_STATE: | ||||
| printState(sources); | printState(sources); |
| #include "parallel.h" | |||||
| #include <thread> | |||||
| int coreCount() { | |||||
| return std::thread::hardware_concurrency(); | |||||
| } |
| #pragma once | |||||
| #include <thread> | |||||
| namespace parallel { | |||||
| template<typename Container, typename Func> | |||||
| void parallel(int jobs, Container &cont, Func func) { | |||||
| for (auto &elem: cont) { | |||||
| func(elem); | |||||
| } | |||||
| } | |||||
| int coreCount() { | |||||
| return std::thread::hardware_concurrency(); | |||||
| } | |||||
| } |
| #include "toolchain.h" | |||||
| #include <stdexcept> | |||||
| #include <stdio.h> | |||||
| #include <sys/types.h> | |||||
| #include <sys/wait.h> | |||||
| #include <unistd.h> | |||||
| #include <string.h> | |||||
| #include <errno.h> | |||||
| #include "globals.h" | |||||
| namespace toolchain { | |||||
| // TODO: Decide whether it would be better to use $SHELL | |||||
| static const char *getShell() { | |||||
| return "/bin/sh"; | |||||
| } | |||||
| static const char *getPkgConfig() { | |||||
| static const char *pkgConfig = nullptr; | |||||
| if (pkgConfig != nullptr) { | |||||
| return pkgConfig; | |||||
| } | |||||
| pkgConfig = getenv("PKG_CONFIG"); | |||||
| if (pkgConfig != nullptr) { | |||||
| return pkgConfig; | |||||
| } | |||||
| return (pkgConfig = "pkg-config"); | |||||
| } | |||||
| static const char *getCCompiler() { | |||||
| static const char *cCompiler = nullptr; | |||||
| if (cCompiler != nullptr) { | |||||
| return cCompiler; | |||||
| } | |||||
| cCompiler = getenv("CC"); | |||||
| if (cCompiler != nullptr) { | |||||
| return cCompiler; | |||||
| } | |||||
| return (cCompiler = "cc"); | |||||
| } | |||||
| static const char *getCXXCompiler() { | |||||
| static const char *cxxCompiler = nullptr; | |||||
| if (cxxCompiler != nullptr) { | |||||
| return cxxCompiler; | |||||
| } | |||||
| cxxCompiler = getenv("CC"); | |||||
| if (cxxCompiler != nullptr) { | |||||
| return cxxCompiler; | |||||
| } | |||||
| return (cxxCompiler = "g++"); | |||||
| } | |||||
| static const char *getCompilerFor(SourceFile::FileType type) { | |||||
| switch (type) { | |||||
| case SourceFile::FileType::C: | |||||
| return getCCompiler(); | |||||
| case SourceFile::FileType::CXX: | |||||
| return getCXXCompiler(); | |||||
| case SourceFile::FileType::UNKNOWN: | |||||
| abort(); | |||||
| } | |||||
| abort(); | |||||
| } | |||||
| static void execute(std::vector<const char *> &args, std::string *output) { | |||||
| if (global::verbose) { | |||||
| for (size_t i = 0; i < args.size(); ++i) { | |||||
| if (i == 0) { | |||||
| fprintf(stderr, "%s", args[i]); | |||||
| } else { | |||||
| fprintf(stderr, " %s", args[i]); | |||||
| } | |||||
| } | |||||
| fprintf(stderr, "\n"); | |||||
| } | |||||
| // argv[0] should be interpreted as a shell command, because being able to run | |||||
| // CC='gcc --sysroot=/blah' is used by some systems. | |||||
| std::string command = std::string(args[0]) + " \"$@\""; | |||||
| std::vector<const char *> argv; | |||||
| argv.push_back(getShell()); | |||||
| argv.push_back("-c"); | |||||
| argv.push_back(command.c_str()); | |||||
| argv.push_back("--"); | |||||
| for (size_t i = 1; i < args.size(); ++i) { | |||||
| argv.push_back(args[i]); | |||||
| } | |||||
| argv.push_back(nullptr); | |||||
| int fds[2]; | |||||
| if (pipe(fds) < 0) { | |||||
| throw std::runtime_error(std::string("fork: ") + strerror(errno)); | |||||
| } | |||||
| pid_t child = fork(); | |||||
| if (child == 0) { | |||||
| close(fds[0]); | |||||
| dup2(fds[1], 1); | |||||
| // 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) { | |||||
| perror(argv[0]); | |||||
| abort(); | |||||
| } | |||||
| } else if (child < 0) { | |||||
| throw std::runtime_error(std::string("fork: ") + strerror(errno)); | |||||
| } | |||||
| close(fds[1]); | |||||
| if (output == nullptr) { | |||||
| close(fds[0]); | |||||
| } else { | |||||
| char buf[1025]; | |||||
| while (true) { | |||||
| ssize_t num = read(fds[0], buf, sizeof(buf) - 1); | |||||
| if (num < 0 && errno != EAGAIN) { | |||||
| close(fds[0]); | |||||
| throw std::runtime_error(std::string("read: ") + strerror(errno)); | |||||
| } else if (num < 0) { | |||||
| continue; | |||||
| } | |||||
| if (num == 0) { | |||||
| close(fds[1]); | |||||
| break; | |||||
| } | |||||
| buf[num] = '\0'; | |||||
| *output += buf; | |||||
| } | |||||
| } | |||||
| int wstatus; | |||||
| waitpid(child, &wstatus, 0); | |||||
| if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != 0) { | |||||
| throw std::runtime_error(std::string(args[0]) + | |||||
| " exited with code " + std::to_string(WEXITSTATUS(wstatus))); | |||||
| } | |||||
| if (WTERMSIG(wstatus)) { | |||||
| throw std::runtime_error(std::string(args[0]) + | |||||
| " terminated due to " + strsignal(WTERMSIG(wstatus))); | |||||
| } | |||||
| if (output != nullptr && output->back() == '\n') { | |||||
| output->pop_back(); | |||||
| } | |||||
| } | |||||
| static void parseSpaceSeparated(const std::string &input, std::vector<std::string> &output) { | |||||
| ssize_t prevPos = -1; | |||||
| ssize_t pos = -1; | |||||
| while ((pos = input.find(' ', pos + 1)) != (ssize_t)std::string::npos) { | |||||
| output.push_back(input.substr(prevPos + 1, pos - prevPos)); | |||||
| prevPos = pos; | |||||
| } | |||||
| if (prevPos >= 0) { | |||||
| output.push_back(input.substr(prevPos + 1)); | |||||
| } | |||||
| } | |||||
| void pkgFlags(const std::vector<std::string> &pkgs, std::vector<std::string> &flags) { | |||||
| std::vector<const char *> argv; | |||||
| argv.push_back(getPkgConfig()); | |||||
| argv.push_back("--cflags"); | |||||
| for (auto &pkg: pkgs) { | |||||
| argv.push_back(pkg.c_str()); | |||||
| } | |||||
| std::string output; | |||||
| execute(argv, &output); | |||||
| parseSpaceSeparated(output, flags); | |||||
| } | |||||
| void compile( | |||||
| const std::vector<std::string> &flags, | |||||
| SourceFile::FileType type, | |||||
| const std::string &srcDir, | |||||
| const std::string &name, | |||||
| const std::string &outDir) { | |||||
| std::string sourcePath = srcDir + "/" + name; | |||||
| std::string destDir = outDir + "/" + srcDir; | |||||
| std::string destPath = destDir + "/" + name + ".o"; | |||||
| // TODO: Change this to a C++ mkdirp function | |||||
| std::vector<const char *> argv; | |||||
| argv.push_back("mkdir"); | |||||
| argv.push_back("-p"); | |||||
| argv.push_back(destDir.c_str()); | |||||
| execute(argv, nullptr); | |||||
| argv.clear(); | |||||
| argv.push_back(getCompilerFor(type)); | |||||
| for (auto &flag: flags) { | |||||
| argv.push_back(flag.c_str()); | |||||
| } | |||||
| argv.push_back("-o"); | |||||
| argv.push_back(destPath.c_str()); | |||||
| argv.push_back("-c"); | |||||
| argv.push_back(sourcePath.c_str()); | |||||
| execute(argv, nullptr); | |||||
| } | |||||
| } |
| #pragma once | |||||
| #include <vector> | |||||
| #include <string> | |||||
| #include "SourceFile.h" | |||||
| namespace toolchain { | |||||
| void pkgFlags(const std::vector<std::string> &pkgs, std::vector<std::string> &flags); | |||||
| void pkgLDLibs(const std::vector<std::string> &pkgs, std::vector<std::string> &flags); | |||||
| void compile( | |||||
| const std::vector<std::string> &flags, | |||||
| SourceFile::FileType type, | |||||
| const std::string &srcDir, | |||||
| const std::string &name, | |||||
| const std::string &outDir); | |||||
| } |