@@ -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 |
@@ -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; | |||
@@ -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_; |
@@ -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_; | |||
} |
@@ -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"; } | |||
}; |
@@ -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; | |||
} |
@@ -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_; | |||
}; |
@@ -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_; | |||
} |
@@ -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"; } | |||
}; |
@@ -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; | |||
} |
@@ -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; | |||
}; |
@@ -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; | |||
} |
@@ -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); |
@@ -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."); | |||
} | |||
} |
@@ -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, ¤tJobs, &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() { |
@@ -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(); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} |