@@ -1,5 +1,5 @@ | |||
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 | |||
SRCS = src/BBBParser.cc src/SourceFile.cc src/toolchain.cc src/globals.cc src/sys.cc src/main.cc | |||
HDRS = src/BBBParser.h src/SourceFile.h src/toolchain.h src/globals.h src/sys.h src/parallel.h | |||
BUILD = build | |||
OBJS = $(patsubst %,$(BUILD)/%.o,$(SRCS)) | |||
CFLAGS = -g -Wall -Wextra -Wno-unused-parameter |
@@ -0,0 +1 @@ | |||
int x = 10; |
@@ -0,0 +1,3 @@ | |||
#pragma once | |||
extern int x; |
@@ -1,9 +1,10 @@ | |||
#include <math.h> | |||
#include <stdio.h> | |||
#include "hello world.h" | |||
//#bb ldflags := -lm | |||
//#bb pkgs := sdl2 cairo | |||
int main() { | |||
printf("sin(PI) is %f\n", sin(M_PI)); | |||
printf("sin(%i) is %f\n", x, sin(x)); | |||
} |
@@ -5,6 +5,8 @@ | |||
#include <sstream> | |||
#include <string.h> | |||
#include "toolchain.h" | |||
static bool startsWith(BBBParser &parser, const char *str) { | |||
for (size_t i = 0; str[i] != '\0'; ++i) { | |||
if (parser.peek() != str[i]) | |||
@@ -18,7 +20,6 @@ static bool startsWith(BBBParser &parser, const char *str) { | |||
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; | |||
} | |||
@@ -38,8 +39,11 @@ SourceFile::FileType SourceFile::fileTypeFrom(const std::string &name) { | |||
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)) { | |||
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); | |||
@@ -47,7 +51,6 @@ SourceFile::SourceFile(std::string dir, std::string name, FileType type, BBBPars | |||
while (file.good()) { | |||
if (startsWith(parser, "//#bb")) { | |||
parser.parse(vars_); | |||
contains_bbb_ = true; | |||
} else { | |||
while (file.good() && parser.get() != '\n'); | |||
} | |||
@@ -62,3 +65,86 @@ const std::vector<std::string> *SourceFile::variable(const std::string &name) co | |||
return &it->second; | |||
} | |||
const std::vector<std::string> &SourceFile::compileFlags() const { | |||
if (hasCompileFlags_) { | |||
return compileFlags_; | |||
} | |||
const std::vector<std::string> *pkgs = variable("pkgs"); | |||
if (pkgs != nullptr) { | |||
toolchain::pkgFlags(*pkgs, compileFlags_); | |||
} | |||
const std::vector<std::string> *cflags; | |||
switch (type_) { | |||
case SourceFile::FileType::C: | |||
cflags = variable("cflags"); | |||
break; | |||
case SourceFile::FileType::CXX: | |||
cflags = variable("cxxflags"); | |||
break; | |||
case SourceFile::FileType::UNKNOWN: | |||
break; | |||
} | |||
if (cflags != nullptr) { | |||
for (const std::string &flag: *cflags) { | |||
compileFlags_.push_back(flag); | |||
} | |||
} | |||
return compileFlags_; | |||
} | |||
const std::vector<std::string> &SourceFile::ldLibs() const { | |||
const std::vector<std::string> *pkgs = variable("pkgs"); | |||
if (pkgs != nullptr) { | |||
toolchain::pkgLDLibs(*pkgs, ldLibs_); | |||
} | |||
const std::vector<std::string> *libs = variable("ldlibs"); | |||
if (libs != nullptr) { | |||
for (const std::string &flag: *libs) { | |||
ldLibs_.push_back(flag); | |||
} | |||
} | |||
hasLDLibs_ = true; | |||
return ldLibs_; | |||
} | |||
std::vector<std::string> SourceFile::dependencies() const { | |||
std::vector<std::string> deps; | |||
toolchain::getDependencies(compileFlags(), type_, dir_, name_, deps); | |||
return deps; | |||
} | |||
bool SourceFile::needsRecompile(const std::string &outDir) const { | |||
std::string outPath = outDir + "/" + name_ + ".o"; | |||
if (!sys::fileExists(outPath)) { | |||
return true; | |||
} | |||
sys::FileInfo outInfo = sys::fileInfo(outPath); | |||
std::vector<std::string> deps = dependencies(); | |||
for (std::string &dep: deps) { | |||
sys::FileInfo depInfo = sys::fileInfo(dep); | |||
if (depInfo.mTimeSec > outInfo.mTimeSec || ( | |||
depInfo.mTimeSec == outInfo.mTimeSec && | |||
depInfo.mTimeNsec > outInfo.mTimeNsec)) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
void SourceFile::compile(const std::string &outDir) const { | |||
std::cerr << "Compile " << dir_ << '/' << name_ << '\n'; | |||
toolchain::compile(compileFlags(), type_, dir_, name_, outDir); | |||
} |
@@ -2,8 +2,10 @@ | |||
#include <vector> | |||
#include <string> | |||
#include <optional> | |||
#include "BBBParser.h" | |||
#include "sys.h" | |||
class SourceFile { | |||
public: | |||
@@ -13,7 +15,9 @@ public: | |||
static FileType fileTypeFrom(const std::string &name); | |||
SourceFile(std::string dir, std::string name, FileType type, BBBParser::Variables vars); | |||
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_; } | |||
@@ -21,10 +25,22 @@ public: | |||
const BBBParser::Variables &vars() const { return vars_; } | |||
const std::vector<std::string> *variable(const std::string &name) const; | |||
const std::vector<std::string> &compileFlags() const; | |||
const std::vector<std::string> &ldLibs() const; | |||
std::vector<std::string> dependencies() const; | |||
bool needsRecompile(const std::string &outDir) const; | |||
void compile(const std::string &outDir) const; | |||
private: | |||
std::string dir_; | |||
std::string name_; | |||
FileType type_; | |||
BBBParser::Variables vars_; | |||
bool contains_bbb_ = false; | |||
mutable std::vector<std::string> compileFlags_; | |||
mutable bool hasCompileFlags_ = false; | |||
mutable std::vector<std::string> ldLibs_; | |||
mutable bool hasLDLibs_ = false; | |||
}; |
@@ -1,5 +1,3 @@ | |||
#include <sys/types.h> | |||
#include <sys/stat.h> | |||
#include <unistd.h> | |||
#include <getopt.h> | |||
#include <dirent.h> | |||
@@ -8,6 +6,7 @@ | |||
#include <errno.h> | |||
#include <fstream> | |||
#include <iostream> | |||
#include <mutex> | |||
#include "SourceFile.h" | |||
#include "BBBParser.h" | |||
@@ -21,12 +20,9 @@ static void readDir(std::string dir, std::vector<SourceFile> &sources, | |||
static void readPath(std::string dir, std::string name, | |||
std::vector<SourceFile> &sources, BBBParser::Variables vars) { | |||
std::string path = dir + "/" + name; | |||
struct stat st; | |||
if (stat(path.c_str(), &st) < 0) { | |||
throw std::runtime_error("stat '" + path + "': " + strerror(errno)); | |||
} | |||
sys::FileInfo finfo = sys::fileInfo(path); | |||
if (S_ISDIR(st.st_mode)) { | |||
if (finfo.isDir) { | |||
// We don't want to send 'files' | |||
BBBParser::Variables subvars = vars; | |||
subvars.erase("files"); | |||
@@ -95,46 +91,24 @@ static 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 bool compile(const std::vector<SourceFile> &sources, const std::string &outDir, int jobs) { | |||
bool compiled = false; | |||
std::mutex mut; | |||
parallel::parallel(jobs, sources, [&](const SourceFile &source) { | |||
if (source.needsRecompile(outDir)) { | |||
source.compile(outDir); | |||
mut.lock(); | |||
compiled = true; | |||
mut.unlock(); | |||
} | |||
} | |||
} | |||
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); | |||
return compiled; | |||
} | |||
static void compile(const std::vector<SourceFile> &sources, const std::string &outDir, int jobs) { | |||
parallel::parallel(jobs, sources, [&](const SourceFile &source) { | |||
compileFile(source, outDir); | |||
}); | |||
static void link(const std::vector<SourceFile> &sources, const std::string &outDir) { | |||
// TODO: Actually link | |||
printf("Linking\n"); | |||
} | |||
int main(int argc, char **argv) { | |||
@@ -249,7 +223,12 @@ int main(int argc, char **argv) { | |||
switch (action) { | |||
case Action::BUILD: | |||
compile(sources, outDir, jobs); | |||
if (compile(sources, outDir, jobs)) { | |||
link(sources, outDir); | |||
} else { | |||
std::cerr << "Nothing to do.\n"; | |||
} | |||
break; | |||
case Action::PRINT_STATE: |
@@ -0,0 +1,39 @@ | |||
#include "sys.h" | |||
#include <sys/types.h> | |||
#include <sys/stat.h> | |||
#include <unistd.h> | |||
#include <string.h> | |||
#include <errno.h> | |||
#include <stdexcept> | |||
namespace sys { | |||
FileInfo fileInfo(const std::string &path) { | |||
FileInfo finfo; | |||
struct stat st; | |||
if (stat(path.c_str(), &st) < 0) { | |||
throw std::runtime_error("stat '" + path + "': " + strerror(errno)); | |||
} | |||
finfo.mTimeSec = st.st_mtim.tv_sec; | |||
finfo.mTimeNsec = st.st_mtim.tv_nsec; | |||
finfo.isDir = S_ISDIR(st.st_mode); | |||
return finfo; | |||
} | |||
bool fileExists(const std::string &path) { | |||
struct stat st; | |||
if (stat(path.c_str(), &st) < 0) { | |||
if (errno == ENOENT) { | |||
return false; | |||
} else { | |||
throw std::runtime_error("stat '" + path + "': " + strerror(errno)); | |||
} | |||
} | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
#pragma once | |||
#include <string> | |||
namespace sys { | |||
struct FileInfo { | |||
long mTimeSec; | |||
long mTimeNsec; | |||
bool isDir; | |||
}; | |||
FileInfo fileInfo(const std::string &path); | |||
bool fileExists(const std::string &path); | |||
} |
@@ -161,16 +161,39 @@ static void execute(std::vector<const char *> &args, std::string *output) { | |||
} | |||
} | |||
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; | |||
} | |||
static bool isWhitespace(char ch) { | |||
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; | |||
} | |||
static void parseWhitespaceSeparated( | |||
const std::string &input, | |||
std::vector<std::string> &output, | |||
size_t pos = 0) { | |||
size_t i = pos; | |||
while (true) { | |||
// Skip leading whitespace | |||
char ch = input[i]; | |||
while (isWhitespace(ch) || (ch == '\\' && isWhitespace(input[i + 1]))) { | |||
ch = input[i += 1]; | |||
} | |||
// Read non-whitespace | |||
std::string str; | |||
while (!isWhitespace(ch) && ch != '\0') { | |||
if (ch == '\\') { | |||
str += input[i + 1]; | |||
ch = input[i += 2]; | |||
} else { | |||
str += input[i]; | |||
ch = input[i += 1]; | |||
} | |||
} | |||
if (prevPos >= 0) { | |||
output.push_back(input.substr(prevPos + 1)); | |||
output.push_back(std::move(str)); | |||
if (ch == '\0') { | |||
break; | |||
} | |||
} | |||
} | |||
@@ -184,7 +207,46 @@ void pkgFlags(const std::vector<std::string> &pkgs, std::vector<std::string> &fl | |||
std::string output; | |||
execute(argv, &output); | |||
parseSpaceSeparated(output, flags); | |||
parseWhitespaceSeparated(output, flags); | |||
} | |||
void pkgLDLibs(const std::vector<std::string> &pkgs, std::vector<std::string> &flags) { | |||
std::vector<const char *> argv; | |||
argv.push_back(getPkgConfig()); | |||
argv.push_back("--libs"); | |||
for (auto &pkg: pkgs) { | |||
argv.push_back(pkg.c_str()); | |||
} | |||
std::string output; | |||
execute(argv, &output); | |||
parseWhitespaceSeparated(output, flags); | |||
} | |||
void getDependencies( | |||
const std::vector<std::string> &flags, | |||
SourceFile::FileType type, | |||
const std::string &srcDir, | |||
const std::string &name, | |||
std::vector<std::string> &deps) { | |||
std::string sourcePath = srcDir + "/" + name; | |||
std::vector<const char *> argv; | |||
argv.push_back(getCompilerFor(type)); | |||
for (auto &flag: flags) { | |||
argv.push_back(flag.c_str()); | |||
} | |||
argv.push_back("-MM"); | |||
argv.push_back("-c"); | |||
argv.push_back(sourcePath.c_str()); | |||
std::string output; | |||
execute(argv, &output); | |||
size_t idx = output.find(':'); | |||
if (idx != std::string::npos) { | |||
parseWhitespaceSeparated(output, deps, idx + 1); | |||
} | |||
} | |||
void compile( |
@@ -10,6 +10,13 @@ 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 getDependencies( | |||
const std::vector<std::string> &flags, | |||
SourceFile::FileType type, | |||
const std::string &srcDir, | |||
const std::string &name, | |||
std::vector<std::string> &deps); | |||
void compile( | |||
const std::vector<std::string> &flags, | |||
SourceFile::FileType type, |