@@ -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); | |||
} |