| @@ -1,5 +1,5 @@ | |||
| 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 | |||
| OBJS = $(patsubst %,$(BUILD)/%.o,$(SRCS)) | |||
| CFLAGS = -g -Wall -Wextra -Wno-unused-parameter | |||
| @@ -0,0 +1 @@ | |||
| /bbbuild | |||
| @@ -0,0 +1 @@ | |||
| cflags := -O3 | |||
| @@ -2,7 +2,7 @@ | |||
| #include <stdio.h> | |||
| //#bb ldflags := -lm | |||
| //#bb pkgs := libsdl cairo | |||
| //#bb pkgs := sdl2 cairo | |||
| int main() { | |||
| printf("sin(PI) is %f\n", sin(M_PI)); | |||
| @@ -3,6 +3,7 @@ | |||
| #include <iostream> | |||
| #include <fstream> | |||
| #include <sstream> | |||
| #include <string.h> | |||
| static bool startsWith(BBBParser &parser, const char *str) { | |||
| for (size_t i = 0; str[i] != '\0'; ++i) { | |||
| @@ -14,8 +15,31 @@ static bool startsWith(BBBParser &parser, const char *str) { | |||
| 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_); | |||
| BBBParser parser(file, BBBParser::FLAG_ONE_LINE); | |||
| @@ -23,8 +47,18 @@ SourceFile::SourceFile(std::string dir, std::string name, BBBParser::Variables v | |||
| while (file.good()) { | |||
| if (startsWith(parser, "//#bb")) { | |||
| parser.parse(vars_); | |||
| contains_bbb_ = true; | |||
| } else { | |||
| 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; | |||
| } | |||
| @@ -7,14 +7,24 @@ | |||
| class SourceFile { | |||
| 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 &name() const { return name_; } | |||
| FileType type() const { return type_; } | |||
| const BBBParser::Variables &vars() const { return vars_; } | |||
| const std::vector<std::string> *variable(const std::string &name) const; | |||
| private: | |||
| std::string dir_; | |||
| std::string name_; | |||
| FileType type_; | |||
| BBBParser::Variables vars_; | |||
| bool contains_bbb_ = false; | |||
| }; | |||
| @@ -0,0 +1,7 @@ | |||
| #include "globals.h" | |||
| namespace global { | |||
| bool verbose = false; | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| #pragma once | |||
| namespace global { | |||
| extern bool verbose; | |||
| } | |||
| @@ -11,6 +11,9 @@ | |||
| #include "SourceFile.h" | |||
| #include "BBBParser.h" | |||
| #include "parallel.h" | |||
| #include "toolchain.h" | |||
| #include "globals.h" | |||
| static void readDir(std::string dir, std::vector<SourceFile> &sources, | |||
| BBBParser::Variables vars); | |||
| @@ -29,7 +32,10 @@ static void readPath(std::string dir, std::string name, | |||
| subvars.erase("files"); | |||
| readDir(path, sources, std::move(subvars)); | |||
| } 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -77,7 +83,7 @@ static void readDir(std::string dir, std::vector<SourceFile> &sources, | |||
| } | |||
| } | |||
| void printState(const std::vector<SourceFile> &sources) { | |||
| static void printState(const std::vector<SourceFile> &sources) { | |||
| for (auto &source: sources) { | |||
| std::cout << source.dir() << '/' << source.name() << ":\n"; | |||
| for (auto &kv: source.vars()) { | |||
| @@ -89,31 +95,85 @@ void printState(const std::vector<SourceFile> &sources) { | |||
| } | |||
| } | |||
| 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) { | |||
| std::vector<std::string> srcDirs; | |||
| std::string outdir = "bbbuild"; | |||
| std::string outDir = "bbbuild"; | |||
| int jobs = parallel::coreCount(); | |||
| std::string workDir = ""; | |||
| enum class Action { | |||
| BUILD, PRINT_STATE, | |||
| }; | |||
| Action action = Action::BUILD; | |||
| const char *shortopts = "o:hp"; | |||
| const char *shortopts = "hvo:j:C:p"; | |||
| 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[] = | |||
| "Usage: bbbuild [options...] [sources]\n" | |||
| "\n" | |||
| " -h, --help " | |||
| " -h, --help " | |||
| "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"; | |||
| // Parse options from argv | |||
| @@ -130,8 +190,24 @@ int main(int argc, char **argv) { | |||
| puts(usage); | |||
| return 0; | |||
| case 'v': | |||
| global::verbose = true; | |||
| break; | |||
| 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; | |||
| case 'p': | |||
| @@ -140,11 +216,22 @@ int main(int argc, char **argv) { | |||
| default: | |||
| printf("Unknown option: '%c'.\n%s", (char)c, usage); | |||
| break; | |||
| 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 | |||
| if (optind < argc) { | |||
| while (optind < argc) { | |||
| @@ -162,8 +249,8 @@ int main(int argc, char **argv) { | |||
| switch (action) { | |||
| case Action::BUILD: | |||
| std::cerr << "Building is not implemented yet.\n"; | |||
| abort(); | |||
| compile(sources, outDir, jobs); | |||
| break; | |||
| case Action::PRINT_STATE: | |||
| printState(sources); | |||
| @@ -0,0 +1,7 @@ | |||
| #include "parallel.h" | |||
| #include <thread> | |||
| int coreCount() { | |||
| return std::thread::hardware_concurrency(); | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| #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(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,220 @@ | |||
| #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); | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| #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); | |||
| } | |||