| @@ -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, | |||