| 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 | 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 |
| int x = 10; |
| #pragma once | |||||
| extern int x; |
| #include <math.h> | #include <math.h> | ||||
| #include <stdio.h> | #include <stdio.h> | ||||
| #include "hello world.h" | |||||
| //#bb ldflags := -lm | //#bb ldflags := -lm | ||||
| //#bb pkgs := sdl2 cairo | |||||
| int main() { | int main() { | ||||
| printf("sin(PI) is %f\n", sin(M_PI)); | |||||
| printf("sin(%i) is %f\n", x, sin(x)); | |||||
| } | } |
| #include <sstream> | #include <sstream> | ||||
| #include <string.h> | #include <string.h> | ||||
| #include "toolchain.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) { | ||||
| if (parser.peek() != str[i]) | if (parser.peek() != str[i]) | ||||
| SourceFile::FileType SourceFile::fileTypeFrom(const std::string &name) { | SourceFile::FileType SourceFile::fileTypeFrom(const std::string &name) { | ||||
| size_t idx = name.find_last_of('.'); | size_t idx = name.find_last_of('.'); | ||||
| if (idx >= std::string::npos) { | if (idx >= std::string::npos) { | ||||
| printf("%s: no ftype\n", name.c_str()); | |||||
| return FileType::UNKNOWN; | return FileType::UNKNOWN; | ||||
| } | } | ||||
| return FileType::UNKNOWN; | 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_); | 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'); | ||||
| } | } | ||||
| return &it->second; | 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); | |||||
| } |
| #include <vector> | #include <vector> | ||||
| #include <string> | #include <string> | ||||
| #include <optional> | |||||
| #include "BBBParser.h" | #include "BBBParser.h" | ||||
| #include "sys.h" | |||||
| class SourceFile { | class SourceFile { | ||||
| public: | public: | ||||
| static FileType fileTypeFrom(const std::string &name); | 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 &dir() const { return dir_; } | ||||
| const std::string &name() const { return name_; } | const std::string &name() const { return name_; } | ||||
| 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; | 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: | private: | ||||
| std::string dir_; | std::string dir_; | ||||
| std::string name_; | std::string name_; | ||||
| FileType type_; | FileType type_; | ||||
| BBBParser::Variables vars_; | 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; | |||||
| }; | }; |
| #include <sys/types.h> | |||||
| #include <sys/stat.h> | |||||
| #include <unistd.h> | #include <unistd.h> | ||||
| #include <getopt.h> | #include <getopt.h> | ||||
| #include <dirent.h> | #include <dirent.h> | ||||
| #include <errno.h> | #include <errno.h> | ||||
| #include <fstream> | #include <fstream> | ||||
| #include <iostream> | #include <iostream> | ||||
| #include <mutex> | |||||
| #include "SourceFile.h" | #include "SourceFile.h" | ||||
| #include "BBBParser.h" | #include "BBBParser.h" | ||||
| static void readPath(std::string dir, std::string name, | static void readPath(std::string dir, std::string name, | ||||
| std::vector<SourceFile> &sources, BBBParser::Variables vars) { | std::vector<SourceFile> &sources, BBBParser::Variables vars) { | ||||
| std::string path = dir + "/" + name; | 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' | // We don't want to send 'files' | ||||
| BBBParser::Variables subvars = vars; | BBBParser::Variables subvars = vars; | ||||
| subvars.erase("files"); | subvars.erase("files"); | ||||
| } | } | ||||
| } | } | ||||
| 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) { | int main(int argc, char **argv) { | ||||
| switch (action) { | switch (action) { | ||||
| case Action::BUILD: | case Action::BUILD: | ||||
| compile(sources, outDir, jobs); | |||||
| if (compile(sources, outDir, jobs)) { | |||||
| link(sources, outDir); | |||||
| } else { | |||||
| std::cerr << "Nothing to do.\n"; | |||||
| } | |||||
| break; | break; | ||||
| case Action::PRINT_STATE: | case Action::PRINT_STATE: |
| #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; | |||||
| } | |||||
| } |
| #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); | |||||
| } |
| } | } | ||||
| } | } | ||||
| 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; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| std::string output; | std::string output; | ||||
| execute(argv, &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( | void compile( |
| void pkgFlags(const std::vector<std::string> &pkgs, std::vector<std::string> &flags); | 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 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( | void compile( | ||||
| const std::vector<std::string> &flags, | const std::vector<std::string> &flags, | ||||
| SourceFile::FileType type, | SourceFile::FileType type, |