Browse Source

dependency graph

feature/dependency-graph
Martin Dørum 3 years ago
parent
commit
96777c4c6b
20 changed files with 788 additions and 626 deletions
  1. 7
    4
      Makefile
  2. 7
    7
      src/BBParser.cc
  3. 7
    7
      src/BBParser.h
  4. 196
    0
      src/CompileStep.cc
  5. 35
    0
      src/CompileStep.h
  6. 152
    0
      src/DepNode.cc
  7. 35
    0
      src/DepNode.h
  8. 68
    0
      src/LinkStep.cc
  9. 25
    0
      src/LinkStep.h
  10. 0
    190
      src/SourceFile.cc
  11. 0
    49
      src/SourceFile.h
  12. 86
    180
      src/build.cc
  13. 3
    18
      src/build.h
  14. 15
    55
      src/main.cc
  15. 93
    20
      src/parallel.cc
  16. 6
    22
      src/parallel.h
  17. 8
    0
      src/sys.cc
  18. 6
    0
      src/sys.h
  19. 23
    49
      src/toolchain.cc
  20. 16
    25
      src/toolchain.h

+ 7
- 4
Makefile View File

@@ -1,8 +1,11 @@
SRCS = \
src/BBParser.cc src/SourceFile.cc src/toolchain.cc src/globals.cc \
src/logger.cc src/sys.cc src/parallel.cc src/build.cc src/main.cc
HDRS = src/BBParser.h src/SourceFile.h src/toolchain.h src/globals.h \
src/logger.h src/sys.h src/parallel.h src/build.h
src/BBParser.cc src/build.cc src/CompileStep.cc src/DepNode.cc \
src/globals.cc src/LinkStep.cc src/logger.cc src/parallel.cc src/sys.cc \
src/toolchain.cc src/main.cc
HDRS = \
src/BBParser.h src/build.h src/CompileStep.h src/DepNode.h \
src/globals.h src/LinkStep.h src/logger.h src/parallel.h src/sys.h \
src/toolchain.h
BUILD = build
OBJS = $(patsubst %,$(BUILD)/%.o,$(SRCS))
CFLAGS = -g -Wall -Wextra -Wno-unused-parameter

+ 7
- 7
src/BBParser.cc View File

@@ -88,7 +88,7 @@ char BBParser::parseEscape() {
}

static void appendVariableToString(
const BBParser::Variables &vars, std::string &name,
const BBVariables &vars, std::string &name,
std::string &value) {
if (name.size() == 0)
return;
@@ -110,7 +110,7 @@ static void appendVariableToString(
}

static void appendVariableToArray(
const BBParser::Variables &vars, const std::string &name,
const BBVariables &vars, const std::string &name,
std::vector<std::string> &values) {
if (name.size() == 0)
return;
@@ -125,7 +125,7 @@ static void appendVariableToArray(
}
}

void BBParser::parseExpansion(const Variables &vars, std::vector<std::string> &values) {
void BBParser::parseExpansion(const BBVariables &vars, std::vector<std::string> &values) {
skip(); // '$'

std::string str;
@@ -147,7 +147,7 @@ void BBParser::parseExpansion(const Variables &vars, std::vector<std::string> &v
}
}

void BBParser::parseQuotedExpansion(const Variables &vars, std::string &content) {
void BBParser::parseQuotedExpansion(const BBVariables &vars, std::string &content) {
skip(); // '$'

std::string str;
@@ -169,7 +169,7 @@ void BBParser::parseQuotedExpansion(const Variables &vars, std::string &content)
}
}

void BBParser::parseQuotedString(const Variables &vars, std::string &content) {
void BBParser::parseQuotedString(const BBVariables &vars, std::string &content) {
skip(); // '"'

int ch;
@@ -197,7 +197,7 @@ void BBParser::parseQuotedString(const Variables &vars, std::string &content) {
}
}

bool BBParser::parseString(const Variables &vars, std::string &content, int sep) {
bool BBParser::parseString(const BBVariables &vars, std::string &content, int sep) {
bool success = false;
int ch;
while (1) {
@@ -261,7 +261,7 @@ bool BBParser::parseIdentifier(std::string &content) {
}
}

void BBParser::parse(Variables &vars) {
void BBParser::parse(BBVariables &vars) {
std::string key, value;
std::vector<std::string> values;


+ 7
- 7
src/BBParser.h View File

@@ -15,17 +15,17 @@ struct BBParseError: std::exception {
}
};

using BBVariables = std::unordered_map<std::string, std::vector<std::string>>;

class BBParser {
public:
using Variables = std::unordered_map<std::string, std::vector<std::string>>;

static const int FLAG_NONE = 0;
static const int FLAG_ONE_LINE = 1 << 0;

BBParser(std::istream &stream, int flags, int line = 1, int ch = 1):
flags_(flags), line_(line), ch_(ch), stream_(stream) {}

void parse(Variables &vars);
void parse(BBVariables &vars);

int peek() { return stream_.peek(); }
int peek2() { stream_.get(); int ch = peek(); stream_.unget(); return ch; }
@@ -50,10 +50,10 @@ private:
void skipWhitespace();

char parseEscape();
void parseExpansion(const Variables &vars, std::vector<std::string> &values);
void parseQuotedExpansion(const Variables &vars, std::string &content);
void parseQuotedString(const Variables &vars, std::string &content);
bool parseString(const Variables &vars, std::string &content, int sep = -1);
void parseExpansion(const BBVariables &vars, std::vector<std::string> &values);
void parseQuotedExpansion(const BBVariables &vars, std::string &content);
void parseQuotedString(const BBVariables &vars, std::string &content);
bool parseString(const BBVariables &vars, std::string &content, int sep = -1);
bool parseIdentifier(std::string &content);

int flags_;

+ 196
- 0
src/CompileStep.cc View File

@@ -0,0 +1,196 @@
#include "CompileStep.h"

#include <fstream>

#include "sys.h"
#include "toolchain.h"
#include "logger.h"
#include "globals.h"

static bool startsWith(BBParser &parser, const char *str) {
for (size_t i = 0; str[i] != '\0'; ++i) {
if (parser.peek() != str[i])
return false;
parser.skip();
}

return true;
}

bool CompileStep::checkHasChanged(const std::string &outDir) {
std::string objPath = toolchain::objectFilePath(path_, outDir);
if (!sys::fileExists(objPath)) {
return true;
}

sys::FileInfo objInfo = sys::fileInfo(objPath);
sys::FileInfo sourceInfo = sys::fileInfo(path_);
if (objInfo.isOlderThan(sourceInfo)) {
return true;
}

std::string bbPath = this->bbPath();
if (!sys::fileExists(bbPath)) {
return true;
}

BBVariables cachedVariables;
try {
std::ifstream f(bbPath);
BBParser parser(f, BBParser::FLAG_NONE);
parser.parse(cachedVariables);
} catch (BBParseError &err) {
logger::log(bbPath + ": " + err.what());
return true;
}

// We can trust the cached "deps" value here, because we know
// the object file hasn't actually changed.
auto depsIt = cachedVariables.find("deps");
if (depsIt == cachedVariables.end()) {
logger::log(bbPath + ": Missing 'deps' field");
return true;
}

const std::vector<std::string> &deps = depsIt->second;
for (const auto &dep: deps) {
if (!sys::fileExists(dep)) {
return true;
}

sys::FileInfo depInfo = sys::fileInfo(dep);
if (objInfo.isOlderThan(depInfo)) {
return true;
}
}

// Maybe the build command has changed?
auto commandIt = cachedVariables.find("command");
if (commandIt == cachedVariables.end()) {
logger::log(bbPath + ": Missing 'command' field");
return true;
}

if (compileCommand(outDir) != commandIt->second) {
return true;
}

return false;
}

void CompileStep::doBuild(const std::string &outDir) {
logger::log("Compile " + path_);

std::string objPath = toolchain::objectFilePath(path_, outDir);
std::string dirPath = sys::dirname(objPath);
std::vector<std::string> command = compileCommand(outDir);

BBVariables newCachedVars = {
{ "deps", toolchain::getDependencies(flags(), type_, path_) },
{ "command", command},
};

// TODO: Write newCachedVars to bbPath()

sys::mkdirp(dirPath);
sys::execute(command, nullptr, global::verbose >= 1);
}

std::vector<std::string> CompileStep::getPublicLDFlags(const std::string &outDir) {
BBVariables &vars = variables();
auto it = vars.find("ldflags");
if (it == vars.end()) {
return {};
} else {
return it->second;
}
}

std::vector<std::string> CompileStep::getPublicLDLibs(const std::string &outDir) {
std::vector<std::string> libs;
BBVariables &vars = variables();

auto pkgsIt = vars.find("pkgs");
if (pkgsIt != vars.end()) {
toolchain::getPkgConfigLDLibs(pkgsIt->second, libs);
}

auto libsIt = vars.find("ldlibs");
if (libsIt != vars.end()) {
for (auto &lib: libsIt->second) {
libs.push_back(lib);
}
}

return libs;
}

std::vector<std::string> CompileStep::getPublicObjects(const std::string &outDir) {
return std::vector<std::string>{ toolchain::objectFilePath(path_, outDir) };
}

BBVariables &CompileStep::variables() {
if (hasVariables_) {
return variables_;
}

std::ifstream f(path_);
BBParser parser(f, BBParser::FLAG_ONE_LINE);

while (f.good()) {
if (startsWith(parser, "//#bb")) {
parser.parse(variables_);
} else {
while (f.good() && parser.get() != '\n');
}
}

hasVariables_ = true;
return variables_;
}

std::vector<std::string> &CompileStep::flags() {
if (hasFlags_) {
return flags_;
}

BBVariables &vars = variables();
std::vector<std::string> flags;

auto pkgsIt = vars.find("pkgs");
if (pkgsIt != vars.end()) {
for (auto &flag: pkgsIt->second) {
flags.push_back(flag);
}
}

std::string cflagsName;
switch (type_) {
case toolchain::FileType::C:
cflagsName = "cflags";
break;
case toolchain::FileType::CXX:
cflagsName = "cxxflags";
break;
}

auto cflagsIt = vars.find(cflagsName);
if (cflagsIt != vars.end()) {
for (auto &flag: cflagsIt->second) {
flags.push_back(flag);
}
}

hasFlags_ = true;
return flags_;
}

std::vector<std::string> &CompileStep::compileCommand(const std::string &outDir) {
if (hasCompileCommand_) {
return compileCommand_;
}

compileCommand_ = toolchain::getCompileCommand(flags(), type_, path_, outDir);
hasCompileCommand_ = true;
return compileCommand_;
}

+ 35
- 0
src/CompileStep.h View File

@@ -0,0 +1,35 @@
#pragma once

#include "DepNode.h"
#include "BBParser.h"
#include "toolchain.h"

class CompileStep: public DepNode {
public:
CompileStep(std::string path, toolchain::FileType type, BBVariables vars):
path_(std::move(path)), type_(type), variables_(std::move(vars)) {}

private:
std::string path_;
toolchain::FileType type_;

bool checkHasChanged(const std::string &outDir) override;
void doBuild(const std::string &outDir) override;
std::vector<std::string> getPublicLDFlags(const std::string &outDir) override;
std::vector<std::string> getPublicLDLibs(const std::string &outDir) override;
std::vector<std::string> getPublicObjects(const std::string &outDir) override;

bool hasVariables_ = false;
BBVariables variables_;
BBVariables &variables();

bool hasFlags_ = false;
std::vector<std::string> flags_;
std::vector<std::string> &flags();

bool hasCompileCommand_ = false;
std::vector<std::string> compileCommand_;
std::vector<std::string> &compileCommand(const std::string &outDir);

std::string bbPath() { return path_ + ".bb"; }
};

+ 152
- 0
src/DepNode.cc View File

@@ -0,0 +1,152 @@
#include "DepNode.h"

#include <mutex>
#include <condition_variable>
#include <unordered_set>

#include "parallel.h"
#include "logger.h"

static void maybeAddStrings(
std::unordered_set<std::string> &set,
const std::vector<std::string> &vec,
std::vector<std::string> &out) {
for (auto &str: vec) {
auto pair = set.emplace(str);
bool inserted = pair.second;
if (inserted) {
out.push_back(str);
}
}
}

void DepNode::addChild(std::shared_ptr<DepNode> node) {
deps_.push_back(std::move(node));
}

bool DepNode::haveDepsChanged(const std::string &outDir) {
auto it = deps_.begin();
bool changed = false;
int workers = 0;
std::mutex mut;
std::condition_variable cond;

std::unique_lock<std::mutex> lock(mut);
while (it != deps_.end()) {
lock.lock();
if (changed) {
cond.wait(lock, [&] { return workers == 0; });
return true;
}

workers += 1;
lock.unlock();
parallel::run([&, it] {
bool ch = (*it)->hasChanged(outDir);
std::unique_lock<std::mutex> lock(mut);
workers -= 1;
if (ch) {
changed = true;
}

lock.unlock();
cond.notify_one();
});

it = ++it;
}

lock.lock();
cond.wait(lock, [&] { return workers == 0; });
return changed;
}

bool DepNode::hasChanged(const std::string &outDir) {
std::lock_guard<std::mutex> lock(mut_);

if (was_rebuilt_) {
return true;
}

if (has_changed_ == TriState::TRUE) {
return true;
} else if (has_changed_ == TriState::FALSE) {
return false;
} else {
bool changed = checkHasChanged(outDir);
if (!changed) {
changed = haveDepsChanged(outDir);
}

if (changed) {
has_changed_ = TriState::TRUE;
return true;
} else {
has_changed_ = TriState::FALSE;
return false;
}
}
}

void DepNode::build(const std::string &outDir) {
bool changed = hasChanged(outDir);
std::lock_guard<std::mutex> lock(mut_);\

if (!was_rebuilt_ && changed) {
int workers = 0;
std::mutex mut;
std::condition_variable cond;

std::unique_lock<std::mutex> lock(mut, std::defer_lock);
for (auto &dep: deps_) {
if (dep->hasChanged(outDir)) {
lock.lock();
workers += 1;
lock.unlock();
parallel::run([&] {
dep->build(outDir);
std::unique_lock<std::mutex> lock(mut);
workers -= 1;

lock.unlock();
cond.notify_one();
});
}
}

lock.lock();
cond.wait(lock, [&] { return workers == 0; });
doBuild(outDir);
was_rebuilt_ = true;
}
}

std::vector<std::string> DepNode::publicLDFlags(const std::string &outDir) {
std::unordered_set<std::string> set;
std::vector<std::string> flags;
maybeAddStrings(set, getPublicLDFlags(outDir), flags);
for (auto &dep: deps_) {
maybeAddStrings(set, dep->publicLDFlags(outDir), flags);
}
return flags;
}

std::vector<std::string> DepNode::publicLDLibs(const std::string &outDir) {
std::unordered_set<std::string> set;
std::vector<std::string> libs;
maybeAddStrings(set, getPublicLDLibs(outDir), libs);
for (auto &dep: deps_) {
maybeAddStrings(set, dep->publicLDLibs(outDir), libs);
}
return libs;
}

std::vector<std::string> DepNode::publicObjects(const std::string &outDir) {
std::vector<std::string> objs = getPublicObjects(outDir);;
for (auto &dep: deps_) {
for (auto &obj: dep->publicObjects(outDir)) {
objs.push_back(obj);
}
}
return objs;
}

+ 35
- 0
src/DepNode.h View File

@@ -0,0 +1,35 @@
#pragma once

#include <vector>
#include <memory>
#include <mutex>
#include <string>

class DepNode {
public:
void addChild(std::shared_ptr<DepNode> node);
bool hasChanged(const std::string &outDir);
void build(const std::string &outDir);
virtual std::vector<std::string> publicLDFlags(const std::string &outDir);
virtual std::vector<std::string> publicLDLibs(const std::string &outDir);
virtual std::vector<std::string> publicObjects(const std::string &outDir);

protected:
enum class TriState {
UNKNOWN, FALSE, TRUE,
};

virtual bool checkHasChanged(const std::string &outDir) = 0;
virtual void doBuild(const std::string &outDir) = 0;
virtual std::vector<std::string> getPublicLDFlags(const std::string &outDir) { return {}; }
virtual std::vector<std::string> getPublicLDLibs(const std::string &outDir) { return {}; }
virtual std::vector<std::string> getPublicObjects(const std::string &outDir) { return {}; }

bool haveDepsChanged(const std::string &outDir);

std::mutex mut_;
TriState has_changed_ = TriState::UNKNOWN;
bool was_rebuilt_ = false;
bool was_prepared_ = false;
std::vector<std::shared_ptr<DepNode>> deps_;
};

+ 68
- 0
src/LinkStep.cc View File

@@ -0,0 +1,68 @@
#include "LinkStep.h"

#include <string>
#include <fstream>

#include "sys.h"
#include "logger.h"
#include "globals.h"
#include "BBParser.h"

bool LinkStep::checkHasChanged(const std::string &outDir) {
std::string targetPath = toolchain::targetFilePath(type_, path_, outDir);
if (!sys::fileExists(targetPath)) {
return true;
}

std::string bbPath = this->bbPath();
if (!sys::fileExists(bbPath)) {
return true;
}

BBVariables cachedVariables;
try {
std::ifstream f(bbPath);
BBParser parser(f, BBParser::FLAG_NONE);
parser.parse(cachedVariables);
} catch (BBParseError &err) {
logger::log(bbPath + ": " + err.what());
return true;
}

auto commandIt = cachedVariables.find("command");
if (commandIt == cachedVariables.end()) {
logger::log(bbPath + ": Missing 'command' field");
return true;
}

if (linkCommand(outDir) != commandIt->second) {
return true;
}

return false;
}

void LinkStep::doBuild(const std::string &outDir) {
logger::log("Link " + path_);
sys::execute(linkCommand(outDir), nullptr, global::verbose >= 1);
}

std::vector<std::string> &LinkStep::linkCommand(const std::string &outDir) {
if (hasLinkCommand_) {
return linkCommand_;
}

std::vector<std::string> objs;
for (auto &dep: deps_) {
for (auto &obj: dep->publicObjects(outDir)) {
objs.push_back(obj);
}
}

// TODO: Don't use FileType::CXX hard-coded here
linkCommand_ = toolchain::getLinkCommand(
publicLDFlags(outDir), publicLDLibs(outDir),
toolchain::FileType::CXX, type_, objs, path_, outDir);
hasLinkCommand_ = true;
return linkCommand_;
}

+ 25
- 0
src/LinkStep.h View File

@@ -0,0 +1,25 @@
#pragma once

#include <string>

#include "DepNode.h"
#include "toolchain.h"

class LinkStep: public DepNode {
public:
LinkStep(std::string path, toolchain::TargetType type):
path_(path), type_(type) {}

private:
std::string path_;
toolchain::TargetType type_;

bool checkHasChanged(const std::string &outDir) override;
void doBuild(const std::string &outDir) override;

bool hasLinkCommand_ = false;
std::vector<std::string> linkCommand_;
std::vector<std::string> &linkCommand(const std::string &outDir);

std::string bbPath() { return path_ + ".bb"; }
};

+ 0
- 190
src/SourceFile.cc View File

@@ -1,190 +0,0 @@
#include "SourceFile.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <string.h>

#include "toolchain.h"
#include "logger.h"
#include "globals.h"

static bool startsWith(BBParser &parser, const char *str) {
for (size_t i = 0; str[i] != '\0'; ++i) {
if (parser.peek() != str[i])
return false;
parser.skip();
}

return true;
}

SourceFile::FileType SourceFile::fileTypeFrom(const std::string &name) {
size_t idx = name.find_last_of('.');
if (idx >= std::string::npos) {
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, BBParser::Variables vars):
dir_(std::move(dir)), name_(std::move(name)),
type_(type), vars_(std::move(vars)) {

std::ifstream file(dir_ + "/" + name_);
BBParser parser(file, BBParser::FLAG_ONE_LINE);

while (file.good()) {
if (startsWith(parser, "//#bb")) {
parser.parse(vars_);
} else {
while (file.good() && parser.get() != '\n');
}
}
}

std::string SourceFile::objectPath(const std::string &outDir) const {
return toolchain::objectFilePath(dir(), name(), outDir);
}

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

const std::vector<std::string> &SourceFile::compileFlags() const {
if (hasCompileFlags_) {
return compileFlags_;
}

const std::vector<std::string> *pkgs = variable("pkgs");
if (pkgs != nullptr) {
toolchain::getPkgConfigFlags(*pkgs, compileFlags_);
}

const std::vector<std::string> *cflags = nullptr;
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);
}
}

hasCompileFlags_ = true;
return compileFlags_;
}

std::vector<std::string> SourceFile::ldFlags() const {
std::vector<std::string> ldFlags;

const std::vector<std::string> *flags = variable("ldflags");
if (flags != nullptr) {
for (const std::string &flag: *flags) {
ldFlags.push_back(std::move(flag));
}
}

return ldFlags;
}

std::vector<std::string> SourceFile::ldLibs() const {
std::vector<std::string> ldLibs;

const std::vector<std::string> *pkgs = variable("pkgs");
if (pkgs != nullptr) {
toolchain::getPkgConfigLDLibs(*pkgs, ldLibs);
}

const std::vector<std::string> *libs = variable("ldlibs");
if (libs != nullptr) {
for (const std::string &flag: *libs) {
ldLibs.push_back(std::move(flag));
}
}

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 = objectPath(outDir);
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 {
if (global::verbose == 0) {
logger::log("Compile " + objectPath(outDir));
}

toolchain::compile(compileFlags(), type_, dir_, name_, outDir);

// Nothing will need compile flags after it's compiled, so no reason to
// keep it around in memory
compileFlags_.clear();
hasCompileFlags_ = false;
}

std::vector<std::string> SourceFile::compileCommand(const std::string &outDir) const {
auto res = toolchain::getCompileCommand(compileFlags(), type_, dir_, name_, outDir);

// Nothing will need compile flags after this, so no reason to
// keep it around in memory
compileFlags_.clear();
hasCompileFlags_ = false;

return res;
}

+ 0
- 49
src/SourceFile.h View File

@@ -1,49 +0,0 @@
#pragma once

#include <vector>
#include <string>
#include <optional>

#include "BBParser.h"
#include "sys.h"

class SourceFile {
public:
enum FileType {
UNKNOWN, C, CXX,
};

static FileType fileTypeFrom(const std::string &name);

SourceFile(
std::string dir, std::string name,
FileType type, BBParser::Variables vars);

const std::string &dir() const { return dir_; }
const std::string &name() const { return name_; }
std::string path() const { return dir_ + '/' + name_; }
FileType type() const { return type_; }
const BBParser::Variables &vars() const { return vars_; }
std::string objectPath(const std::string &outDir) const;

const std::vector<std::string> *variable(const std::string &name) const;

const std::vector<std::string> &compileFlags() const;
std::vector<std::string> ldFlags() 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;
std::vector<std::string> compileCommand(const std::string &outDir) const;

private:
std::string dir_;
std::string name_;
FileType type_;
BBParser::Variables vars_;

// Compile flags are cached, because they're used multiple times
mutable std::vector<std::string> compileFlags_;
mutable bool hasCompileFlags_ = false;
};

+ 86
- 180
src/build.cc View File

@@ -1,219 +1,125 @@
#include "build.h"

#include <fstream>
#include <sstream>
#include <mutex>
#include <unordered_set>
#include <stdexcept>
#include <string.h>

#include "sys.h"
#include "parallel.h"
#include "toolchain.h"
#include "logger.h"

namespace build {

static void findSourcesInDir(std::string dir, std::vector<SourceFile> &sources,
BBParser::Variables vars);
#include "BBParser.h"
#include "CompileStep.h"
#include "LinkStep.h"

static std::string extension(const std::string &path) {
size_t idx = path.find_last_of('.');
if (idx >= std::string::npos) {
return "";
}

static void findSources(std::string dir, std::string name,
std::vector<SourceFile> &sources, BBParser::Variables vars) {
std::string path = dir + "/" + name;
sys::FileInfo finfo = sys::fileInfo(path);
char ext[16] = { 0 };
strncpy(ext, path.c_str() + idx, sizeof(ext) - 1);
return ext;
}

if (finfo.isDir) {
// We don't want to send 'files'
BBParser::Variables subvars = vars;
subvars.erase("files");
findSourcesInDir(path, sources, std::move(subvars));
static void addFile(
std::string path, BBVariables variables,
std::vector<std::unique_ptr<DepNode>> &deps) {
toolchain::FileType type;
std::string ext = extension(path);
if (ext == ".c") {
type = toolchain::FileType::C;
} else if (ext == ".C" || ext == ".cc" || ext == ".cxx" || ext == ".cpp") {
type = toolchain::FileType::CXX;
} else {
SourceFile::FileType type = SourceFile::fileTypeFrom(name);
if (type != SourceFile::FileType::UNKNOWN) {
sources.emplace_back(dir, name, type, vars);
}
return;
}

deps.push_back(std::make_unique<CompileStep>(std::move(path), type, std::move(variables)));
}

static void findSourcesInDir(std::string dir, std::vector<SourceFile> &sources,
BBParser::Variables vars) {
std::ifstream buildFile(dir + "/build.bb");
std::vector<std::string> files;
bool hasFiles = false;

// Parse $dir/build.bb, see if it specifies a 'files' value.
if (buildFile.good()) {
BBParser parser(buildFile, BBParser::FLAG_NONE);
parser.parse(vars);

auto it = vars.find("files");
if (it != vars.end()) {
files = it->second;
hasFiles = true;
static void findDeps(
const std::string &dir, const std::string &name,
const BBVariables &variables, std::vector<std::unique_ptr<DepNode>> &deps) {
std::string path = dir + "/" + name;
sys::FileInfo info = sys::fileInfo(path);

if (info.isDir) {
// May or may not need to copy variables
BBVariables subvars;
const BBVariables *varsptr = &variables;
std::vector<std::string> subpaths;

// There might be a $path/build.bb. If there is, files in that dir
// needs a new copy of variables, and that $path/build.bb might contain
// a files property.
if (sys::fileExists(path + "/build.bb")) {
subvars = variables;
varsptr = &subvars;

std::ifstream stream("build.bb");
BBParser parser(stream, BBParser::FLAG_NONE);
parser.parse(subvars);

auto it = subvars.find("files");
if (it == subvars.end()) {
sys::readDir(path, subpaths);
} else {
subpaths = it->second;
subvars.erase(it);
}
} else {
sys::readDir(path, subpaths);
}
}

// If build.bb didn't specify 'files', we have to readdir
if (!hasFiles) {
sys::readDir(dir, files);
}

// Go through files
for (auto &ent: files) {
std::string path = dir + "/" + ent;
BBParser::Variables subvars = vars;
findSources(dir, ent, sources, vars);
for (auto &subpath: subpaths) {
findDeps(path, subpath, *varsptr, deps);
}
} else {
addFile(std::move(path), variables, deps);
}
}

static std::string findTargetName(const BuildConf &conf) {
auto it = conf.variables.find("target");
if (it != conf.variables.end() && it->second.size() != 0) {
static std::string findTargetName(const BBVariables &variables) {
auto it = variables.find("target");
if (it != variables.end() && it->second.size() != 0) {
return it->second[0];
}

for (auto &source: conf.sources) {
const std::vector<std::string> *ptr = source.variable("target");
if (ptr != nullptr && ptr->size() != 0) {
return (*ptr)[0];
}
}
// TODO: Find from deps

return "target";
}

BuildConf readBuildConf(std::string outDir, int numJobs, const std::vector<std::string> &args) {
BuildConf conf;
conf.outDir = outDir;
conf.numJobs = numJobs;

std::unique_ptr<DepNode> buildDepTree(const std::string &outDir, BBVariables variables) {
// Read config from file
if (sys::fileExists("build.bb")) {
std::ifstream stream("build.bb");
BBParser parser(stream, BBParser::FLAG_NONE);
parser.parse(conf.variables);
parser.parse(variables);
}

// Read variables from args
for (auto &arg: args) {
std::istringstream stream(arg);
BBParser parser(stream, BBParser::FLAG_ONE_LINE);
parser.parse(conf.variables);
}

// Find source dirs
std::vector<std::string> sourceDirs;
// Find source files/dirs
std::vector<std::string> sourcePaths;
{
auto it = conf.variables.find("files");
if (it == conf.variables.end()) {
sys::readDir(".", sourceDirs);
auto it = variables.find("files");
if (it == variables.end()) {
sys::readDir(".", sourcePaths);
} else {
sourceDirs = it->second;
sourcePaths = it->second;
variables.erase(it);
}
}

// Read configs from source dirs
for (std::string &dir: sourceDirs) {
findSources(".", dir, conf.sources, conf.variables);
// Find dependencies
std::vector<std::unique_ptr<DepNode>> deps;
for (std::string &path: sourcePaths) {
findDeps(".", path, variables, deps);
}

conf.targetName = findTargetName(conf);

return conf;
}

bool compile(const BuildConf &conf) {
bool compiled = false;
std::mutex mut;
parallel::parallel(conf.numJobs, conf.sources, [&](const SourceFile &source) {
if (source.needsRecompile(conf.outDir)) {
source.compile(conf.outDir);
mut.lock();
compiled = true;
mut.unlock();
}
});

return compiled;
}

void link(const BuildConf &conf, toolchain::TargetType targetType) {
logger::log("Link " + conf.outDir + '/' + conf.targetName);

std::vector<std::string> ldFlags;
std::unordered_set<std::string> ldFlagsSet;
std::vector<std::string> ldLibs;
std::unordered_set<std::string> ldLibsSet;
std::vector<std::string> objects;

SourceFile::FileType type = SourceFile::FileType::C;

// Gather ldflags, ldlibs and objects from sources
std::vector<std::string> flags;
for (auto &source: conf.sources) {
if (source.type() != SourceFile::FileType::C) {
type = source.type();
}

flags = source.ldFlags();
for (auto &flag: flags) {
if (flag[0] == '-' && flag[1] == 'l') {
std::cerr << "Warning: -l flags should go in ldlibs "
<< "(at " << source.path() << ": " << flag << ")\n";
}

auto it = ldFlagsSet.find(flag);
if (it == ldFlagsSet.end()) {
ldFlagsSet.emplace(flag);
ldFlags.push_back(std::move(flag));
}
}

flags = source.ldLibs();
for (auto &flag: flags) {
auto it = ldLibsSet.find(flag);
if (it == ldLibsSet.end()) {
ldLibsSet.emplace(flag);
ldLibs.push_back(std::move(flag));
}
}

objects.push_back(std::move(source.objectPath(conf.outDir)));
std::unique_ptr<LinkStep> link = std::make_unique<LinkStep>(
findTargetName(variables), toolchain::TargetType::BINARY);
for (auto &dep: deps) {
link->addChild(std::move(dep));
}

flags.clear();

toolchain::link(conf.targetName, ldFlags, ldLibs, type, targetType, objects, conf.outDir);
}

void writeCompileCommands(const BuildConf &conf) {
sys::mkdirp(conf.outDir);
std::string cwd = sys::cwd();
std::ofstream os(conf.outDir + "/compile_commands.json");

os << "[\n\t";
bool first = true;
for (auto &source: conf.sources) {
std::string command;
for (auto arg: source.compileCommand(conf.outDir)) {
command += arg + ' ';
}
command.pop_back();

if (!first) {
os << ", ";
}

os << "{\n"
<< "\t\t\"directory\": \"" << cwd << "\"\n"
<< "\t\t\"command\": \"" << command << "\"\n"
<< "\t\t\"file\": \"" << source.path() << "\"\n"
<< "\t}";

first = false;
}

os << "\n]\n";

sys::symlink(conf.outDir + "/compile_commands.json", "compile_commands.json");
}

return link;
}

+ 3
- 18
src/build.h View File

@@ -1,24 +1,9 @@
#pragma once

#include <string>
#include <memory>

#include "SourceFile.h"
#include "DepNode.h"
#include "BBParser.h"
#include "toolchain.h"

namespace build {

struct BuildConf {
std::string outDir;
int numJobs;
BBParser::Variables variables;
std::string targetName;
std::vector<SourceFile> sources;
};

BuildConf readBuildConf(std::string outDir, int numJobs, const std::vector<std::string> &args);
bool compile(const BuildConf &conf);
void link(const BuildConf &conf, toolchain::TargetType targetType);
void writeCompileCommands(const BuildConf &conf);

}
std::unique_ptr<DepNode> buildDepTree(const std::string &outDir, BBVariables variables);

+ 15
- 55
src/main.cc View File

@@ -3,51 +3,20 @@
#include <getopt.h>
#include <iostream>

#include "SourceFile.h"
#include "BBParser.h"
#include "parallel.h"
#include "toolchain.h"
#include "globals.h"
#include "logger.h"
#include "sys.h"
#include "build.h"

static void printState(const std::vector<SourceFile> &sources) {
for (auto &source: sources) {
std::cout << source.dir() << '/' << source.name() << ":\n";
for (auto &kv: source.vars()) {
std::cout << " " << kv.first << ":\n";
for (auto &val: kv.second) {
std::cout << " " << val << '\n';
}
}
}
}

static bool compileAndLink(const build::BuildConf &conf, toolchain::TargetType targetType) {
std::string targetPath = toolchain::targetFilePath(
targetType, conf.targetName, conf.outDir);

if (build::compile(conf) || !sys::fileExists(targetPath)) {
build::link(conf, targetType);
return true;
}

return false;
}

int main(int argc, char **argv) {
logger::LogContext logCtx = logger::init();

std::string outDir = "bbbuild";
int jobs = parallel::coreCount();
std::string workDir = "";
std::string target = "";

enum class Action {
BUILD, PRINT_STATE,
};
Action action = Action::BUILD;

const char *shortopts = "hvo:j:C:cp";
const struct option opts[] = {
{ "help", no_argument, NULL, 'h' },
@@ -56,7 +25,6 @@ int main(int argc, char **argv) {
{ "jobs", required_argument, NULL, 'j' },
{ "directory", required_argument, NULL, 'C' },
{ "target", required_argument, NULL, 't' },
{ "print-state", no_argument, NULL, 'p' },
{},
};

@@ -73,9 +41,7 @@ int main(int argc, char **argv) {
"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";
"Change directory before doing anything else.\n";

// Parse options from argv
while (1) {
@@ -111,16 +77,15 @@ int main(int argc, char **argv) {
workDir = optarg;
break;

case 'p':
action = Action::PRINT_STATE;
break;

default:
printf("Unknown option: '%c'.\n%s", (char)c, usage);
return 1;
}
}

logger::LogContext logCtx = logger::init();
parallel::ParallelContext par = parallel::init(jobs);

// Change directory?
if (workDir.size() > 0) {
fprintf(stderr, "Entering directory '%s'\n", workDir.c_str());
@@ -133,21 +98,16 @@ int main(int argc, char **argv) {
while (optind < argc) {
args.push_back(argv[optind++]);
}
build::BuildConf conf = build::readBuildConf(std::move(outDir), jobs, args);
conf.numJobs = jobs;

switch (action) {
case Action::BUILD:
// TODO: Support more types than BINARY
if (compileAndLink(conf, toolchain::TargetType::BINARY)) {
build::writeCompileCommands(conf);
} else {
logger::log("Nothing to do.");
}
break;

case Action::PRINT_STATE:
printState(conf.sources);
break;
// TODO: Parse variables from CLI
BBVariables vars;

std::unique_ptr<DepNode> root = buildDepTree(outDir, vars);
if (root == nullptr) {
logger::log("No source files.");
} else if (root->hasChanged(outDir)) {
root->build(outDir);
} else {
logger::log("Nothing to do.");
}
}

+ 93
- 20
src/parallel.cc View File

@@ -1,37 +1,110 @@
#include "parallel.h"

#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <vector>

namespace parallel {

void runJobs(
int jobs, const std::vector<void *> &elems,
std::function<void(void *)> func) {
int currentJobs = 0;
std::mutex mut;
std::unique_lock<std::mutex> lock(mut);
struct Worker {
bool running;
std::function<void(void)> func;
std::condition_variable cond;
std::mutex mut;

std::thread thread;
Worker *next_worker;
};

static std::condition_variable workers_cond;
static std::mutex workers_mut;
static std::unique_ptr<Worker[]> workers;
static Worker *head= nullptr;
static int threads_running = 0;
static int waiting = 0;

for (void *elem: elems) {
cond.wait(lock, [&] { return currentJobs < jobs; });
static void workerFunc(Worker *w) {
std::unique_lock<std::mutex> lock(w->mut);
while (true) {
w->cond.wait(lock, [w] { return (bool)w->func || !w->running; });
if (!w->running)
break;

currentJobs += 1;
std::thread thread([elem, jobs, &currentJobs, &mut, &cond, &func] {
func(elem);
try {
w->func();
} catch (...) {
std::terminate();
}

mut.lock();
currentJobs -= 1;
mut.unlock();
cond.notify_one();
});
w->func = nullptr;
std::unique_lock<std::mutex> workers_lock(workers_mut);
w->next_worker = head;
head = w;
workers_lock.unlock();
workers_cond.notify_one();
threads_running -= 1;
}
}

ParallelContext init(int num) {
std::unique_lock<std::mutex> lock(workers_mut);
if (num <= 1) {
return ParallelContext{};
}

workers.reset(new Worker[num]);
for (int i = 0; i < num; ++i) {
Worker *w = &workers[i];
w->next_worker = head;
head = w;

thread.detach();
w->running = true;
w->thread = std::thread(workerFunc, w);
}

cond.wait(lock, [&] { return currentJobs == 0; });
return ParallelContext{};
}

ParallelContext::~ParallelContext() {
std::unique_lock<std::mutex> lock(workers_mut);
workers_cond.wait(lock, [] { return threads_running == 0 && waiting == 0; });

Worker *w = head;
while (w) {
w->running = false;
w->cond.notify_one();
w = w->next_worker;
}

w = head;
while (w) {
w->thread.join();
w = w->next_worker;
}

workers.reset();
head = nullptr;
}

void run(std::function<void(void)> func) {
std::unique_lock<std::mutex> lock(workers_mut);
if (!workers) {
func();
return;
}

waiting += 1;
workers_cond.wait(lock, [] { return head != nullptr; });
Worker *w = head;
head = head->next_worker;
threads_running += 1;
waiting -= 1;
lock.unlock();

// Launch!
w->func = std::move(func);
w->cond.notify_one();
}

int coreCount() {

+ 6
- 22
src/parallel.h View File

@@ -1,32 +1,16 @@
#pragma once

#include <functional>
#include <vector>

namespace parallel {

void runJobs(
int jobs, const std::vector<void *> &elems,
std::function<void(void *)> func);

template<typename Container, typename Func>
void parallel(int jobs, Container &cont, Func func) {
if (jobs == 1) {
for (auto &elem: cont) {
func(elem);
}
} else {
std::vector<void *> elems;
for (auto &elem: cont) {
elems.push_back((void *)&elem);
}

runJobs(jobs, elems, [&func](void *el) {
func(*(typename Container::value_type *)el);
});
}
}
struct ParallelContext {
~ParallelContext();
};

ParallelContext init(int num);
void quit();
void run(std::function<void(void)> func);
int coreCount();

}

+ 8
- 0
src/sys.cc View File

@@ -1,5 +1,6 @@
#include "sys.h"

#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -197,4 +198,11 @@ void symlink(const std::string &from, const std::string &to) {
sys::execute(argv, nullptr, global::verbose > 2);
}

std::string dirname(const std::string &path) {
std::vector<char> buf(path.begin(), path.end());
buf.push_back('\0');
char *dir = ::dirname(buf.data());
return std::string(dir);
}

}

+ 6
- 0
src/sys.h View File

@@ -9,6 +9,11 @@ struct FileInfo {
long mTimeSec;
long mTimeNsec;
bool isDir;

bool isOlderThan(FileInfo &f) {
return mTimeSec < f.mTimeSec ||
(mTimeSec == f.mTimeSec && mTimeNsec < f.mTimeNsec);
}
};

FileInfo fileInfo(const std::string &path);
@@ -19,5 +24,6 @@ void readDir(const std::string &path, std::vector<std::string> &files);
void chdir(const std::string &path);
std::string cwd();
void symlink(const std::string &from, const std::string &to);
std::string dirname(const std::string &path);

}

+ 23
- 49
src/toolchain.cc View File

@@ -52,16 +52,13 @@ static const char *getCXXCompiler() {
return (cxxCompiler = "g++");
}

static const char *getCompilerFor(SourceFile::FileType type) {
static const char *getCompilerFor(FileType type) {
switch (type) {
case SourceFile::FileType::C:
case FileType::C:
return getCCompiler();

case SourceFile::FileType::CXX:
case FileType::CXX:
return getCXXCompiler();

case SourceFile::FileType::UNKNOWN:
abort();
}

abort();
@@ -103,18 +100,15 @@ static void parseWhitespaceSeparated(
}
}

std::string objectFilePath(
const std::string &srcDir,
const std::string &name,
const std::string &outDir) {
return outDir + '/' + srcDir + '/' + name + ".o";
std::string objectFilePath(const std::string &path, const std::string &outDir) {
return outDir + '/' + path + ".o";
}

std::string targetFilePath(
TargetType type,
const std::string &name,
const std::string &path,
const std::string &outDir) {
std::string base = outDir + '/' + name;
std::string base = outDir + '/' + path;

switch (type) {
case TargetType::BINARY:
@@ -156,14 +150,10 @@ void getPkgConfigLDLibs(const std::vector<std::string> &pkgs, std::vector<std::s
parseWhitespaceSeparated(output, flags);
}

void getDependencies(
std::vector<std::string> 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;

FileType type,
const std::string &path) {
std::vector<std::string> argv;

// $(compiler)
@@ -176,26 +166,27 @@ void getDependencies(

// -MM $<
argv.push_back("-MM");
argv.push_back(sourcePath);
argv.push_back(path);

// Execute $(compiler) $(flags) -MM $<
std::string output;
sys::execute(argv, &output, global::verbose >= 2);

std::vector<std::string> deps;
size_t idx = output.find(':');
if (idx != std::string::npos) {
parseWhitespaceSeparated(output, deps, idx + 1);
}

return deps;
}

std::vector<std::string> getCompileCommand(
const std::vector<std::string> &flags,
SourceFile::FileType type,
const std::string &srcDir,
const std::string &name,
FileType type,
const std::string &path,
const std::string &outDir) {
std::string sourcePath = srcDir + "/" + name;
std::string objectPath = objectFilePath(srcDir, name, outDir);
std::string objectPath = objectFilePath(path, outDir);

std::vector<std::string> argv;

@@ -211,35 +202,19 @@ std::vector<std::string> getCompileCommand(
argv.push_back("-o");
argv.push_back(std::move(objectPath));
argv.push_back("-c");
argv.push_back(std::move(sourcePath));
argv.push_back(std::move(path));

return argv;
}

void compile(
const std::vector<std::string> &flags,
SourceFile::FileType type,
const std::string &srcDir,
const std::string &name,
const std::string &outDir) {
// Ensure the output directory actually exists
std::string destDir = outDir + "/" + srcDir;
sys::mkdirp(destDir);

std::vector<std::string> argv = getCompileCommand(flags, type, srcDir, name, outDir);
sys::execute(argv, nullptr, global::verbose >= 1);
}

void link(
const std::string &name,
std::vector<std::string> getLinkCommand(
const std::vector<std::string> &ldFlags,
const std::vector<std::string> &ldLibs,
SourceFile::FileType type,
FileType type,
TargetType targetType,
const std::vector<std::string> &objs,
const std::string &path,
const std::string &outDir) {
const std::string outPath = targetFilePath(targetType, name, outDir);

// TODO: Use ar to create STATIC_LIBRARY,
// use GCC with -shared to make SHARED_LIBRARY

@@ -255,7 +230,7 @@ void link(

// -o $@
argv.push_back("-o");
argv.push_back(outPath);
argv.push_back(targetFilePath(targetType, path, outDir));

// $(objs)
for (auto &obj: objs) {
@@ -267,8 +242,7 @@ void link(
argv.push_back(flag);
}

// Execute $(compiler) $(ldflags) -o $@ $(objs) $(ldlibs)
sys::execute(argv, nullptr, global::verbose >= 1);
return argv;
}

}

+ 16
- 25
src/toolchain.h View File

@@ -3,56 +3,47 @@
#include <vector>
#include <string>

#include "SourceFile.h"

namespace toolchain {

enum FileType {
C, CXX,
};

enum TargetType {
BINARY,
SHARED_LIBRARY,
STATIC_LIBRARY,
};

std::string objectFilePath(
const std::string &srcDir,
const std::string &name,
const std::string &outDir);
std::string objectFilePath(const std::string &path, const std::string &outDir);
std::string targetFilePath(
TargetType type,
const std::string &name,
const std::string &path,
const std::string &outDir);

void getPkgConfigFlags(const std::vector<std::string> &pkgs, std::vector<std::string> &flags);
void getPkgConfigLDLibs(const std::vector<std::string> &pkgs, std::vector<std::string> &flags);

void getDependencies(
std::vector<std::string> getDependencies(
const std::vector<std::string> &flags,
SourceFile::FileType type,
const std::string &srcDir,
const std::string &name,
std::vector<std::string> &deps);
FileType type,
const std::string &path);

std::vector<std::string> getCompileCommand(
const std::vector<std::string> &flags,
SourceFile::FileType type,
const std::string &srcDir,
const std::string &name,
const std::string &outDir);

void compile(
const std::vector<std::string> &flags,
SourceFile::FileType type,
const std::string &srcDir,
const std::string &name,
FileType type,
const std::string &path,
const std::string &outDir);

void link(
const std::string &name,
// TODO: Don't rely on FileType, just use LD and let the dependency graph nodes
// provide the necessary flags to link with libstdc++
std::vector<std::string> getLinkCommand(
const std::vector<std::string> &ldFlags,
const std::vector<std::string> &ldLibs,
SourceFile::FileType type,
FileType type,
TargetType targetType,
const std::vector<std::string> &objs,
const std::string &path,
const std::string &outDir);

}

Loading…
Cancel
Save