#include "build.h" #include #include #include #include #include "sys.h" #include "parallel.h" #include "toolchain.h" #include "logger.h" namespace build { static void findSourcesInDir(std::string dir, std::vector &sources, BBParser::Variables vars); static void findSources(std::string dir, std::string name, std::vector &sources, BBParser::Variables vars) { std::string path = dir + "/" + name; sys::FileInfo finfo = sys::fileInfo(path); if (finfo.isDir) { // We don't want to send 'files' BBParser::Variables subvars = vars; subvars.erase("files"); findSourcesInDir(path, sources, std::move(subvars)); } else { SourceFile::FileType type = SourceFile::fileTypeFrom(name); if (type != SourceFile::FileType::UNKNOWN) { sources.emplace_back(dir, name, type, vars); } } } static void findSourcesInDir(std::string dir, std::vector &sources, BBParser::Variables vars) { std::ifstream buildFile(dir + "/build.bb"); std::vector 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; } } // 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); } } static std::string findTargetName(const BuildConf &conf) { auto it = conf.variables.find("target"); if (it != conf.variables.end() && it->second.size() != 0) { return it->second[0]; } for (auto &source: conf.sources) { const std::vector *ptr = source.variable("target"); if (ptr != nullptr && ptr->size() != 0) { return (*ptr)[0]; } } return "target"; } BuildConf readBuildConf(std::string outDir, int numJobs, const std::vector &args) { BuildConf conf; conf.outDir = outDir; conf.numJobs = numJobs; // Read config from file if (sys::fileExists("build.bb")) { std::ifstream stream("build.bb"); BBParser parser(stream, BBParser::FLAG_NONE); parser.parse(conf.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 sourceDirs; { auto it = conf.variables.find("files"); if (it == conf.variables.end()) { sys::readDir(".", sourceDirs); } else { sourceDirs = it->second; } } // Read configs from source dirs for (std::string &dir: sourceDirs) { findSources(".", dir, conf.sources, conf.variables); } 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 ldFlags; std::unordered_set ldFlagsSet; std::vector ldLibs; std::unordered_set ldLibsSet; std::vector objects; SourceFile::FileType type = SourceFile::FileType::C; // Gather ldflags, ldlibs and objects from sources std::vector 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))); } 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"); } }