in lib/Commands/NinjaBuildCommand.cpp [1758:2365]
int commands::executeNinjaBuildCommand(std::vector<std::string> args) {
std::string chdirPath = "";
std::string customTool = "";
std::string dbFilename = "build.db";
std::string dumpGraphPath, profileFilename, traceFilename;
std::string manifestFilename = "build.ninja";
// Create a context for the build.
bool autoRegenerateManifest = true;
bool quiet = false;
bool simulate = false;
bool strict = false;
bool verbose = false;
unsigned numJobsInParallel = 0;
SchedulerAlgorithm schedulerAlgorithm = SchedulerAlgorithm::NamePriority;
unsigned numFailedCommandsToTolerate = 1;
double maximumLoadAverage = 0.0;
std::vector<std::string> debugTools;
if (basic::sys::raiseOpenFileLimit() != 0) {
fprintf(stderr, "%s: error: unable to raise open file limit\n\n",
getProgramName());
return -1;
}
while (!args.empty() && args[0][0] == '-') {
const std::string option = args[0];
args.erase(args.begin());
if (option == "--")
break;
if (option == "--version") {
// Report a fake version for tools (like CMake) that detect compatibility
// based on the 'Ninja' version.
printf("1.7.0 Ninja Compatible (%s)\n", getLLBuildFullVersion().c_str());
return 0;
} else if (option == "--help") {
usage(/*exitCode=*/0);
} else if (option == "--simulate") {
simulate = true;
} else if (option == "--quiet") {
quiet = true;
} else if (option == "-C" || option == "--chdir") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
chdirPath = args[0];
args.erase(args.begin());
} else if (option == "--no-db") {
dbFilename = "";
} else if (option == "--db") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
dbFilename = args[0];
args.erase(args.begin());
} else if (option == "--dump-graph") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
dumpGraphPath = args[0];
args.erase(args.begin());
} else if (option == "-f") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
manifestFilename = args[0];
args.erase(args.begin());
} else if (option == "-k") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
char *end;
numFailedCommandsToTolerate = ::strtol(args[0].c_str(), &end, 10);
if (*end != '\0') {
fprintf(stderr, "%s: error: invalid argument '%s' to '%s'\n\n",
getProgramName(), args[0].c_str(), option.c_str());
usage();
}
args.erase(args.begin());
} else if (option == "-l") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
char *end;
maximumLoadAverage = ::strtod(args[0].c_str(), &end);
if (*end != '\0') {
fprintf(stderr, "%s: error: invalid argument '%s' to '%s'\n\n",
getProgramName(), args[0].c_str(), option.c_str());
usage();
}
args.erase(args.begin());
} else if (option == "-j" || option == "--jobs") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
char *end;
numJobsInParallel = ::strtol(args[0].c_str(), &end, 10);
if (*end != '\0') {
fprintf(stderr, "%s: error: invalid argument '%s' to '%s'\n\n",
getProgramName(), args[0].c_str(), option.c_str());
usage();
}
args.erase(args.begin());
} else if (option == "--scheduler") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
break;
}
auto algorithm = args[0];
if (algorithm == "commandNamePriority" || algorithm == "default") {
schedulerAlgorithm = SchedulerAlgorithm::NamePriority;
} else if (algorithm == "fifo") {
schedulerAlgorithm = SchedulerAlgorithm::FIFO;
} else {
fprintf(stderr, "%s: error: unknown scheduler algorithm '%s'\n\n",
getProgramName(), args[0].c_str());
break;
}
args.erase(args.begin());
} else if (StringRef(option).startswith("-j")) {
char *end;
numJobsInParallel = ::strtol(&option[2], &end, 10);
if (*end != '\0') {
fprintf(stderr, "%s: error: invalid argument '%s' to '-j'\n\n",
getProgramName(), &option[2]);
usage();
}
} else if (option == "--no-regenerate") {
autoRegenerateManifest = false;
} else if (option == "--profile") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
profileFilename = args[0];
args.erase(args.begin());
} else if (option == "--strict") {
strict = true;
} else if (option == "-t" || option == "--tool") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
customTool = args[0];
args.erase(args.begin());
} else if (option == "-d") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
debugTools.push_back(args[0]);
args.erase(args.begin());
} else if (option == "--trace") {
if (args.empty()) {
fprintf(stderr, "%s: error: missing argument to '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
traceFilename = args[0];
args.erase(args.begin());
} else if (option == "-v" || option == "--verbose") {
verbose = true;
} else {
fprintf(stderr, "%s: error: invalid option: '%s'\n\n",
getProgramName(), option.c_str());
usage();
}
}
if (maximumLoadAverage > 0.0) {
fprintf(stderr, "%s: warning: maximum load average %.8g not implemented\n",
getProgramName(), maximumLoadAverage);
}
if (!debugTools.empty()) {
if (std::find(debugTools.begin(), debugTools.end(), "list")
!= debugTools.end()) {
printf("debugging modes:\n");
printf("no debugging modes supported\n");
} else {
fprintf(stderr, "%s: error: unknown debug mode '%s'\n",
getProgramName(), debugTools.front().c_str());
}
return 1;
}
// Honor the --chdir option, if used.
if (!chdirPath.empty()) {
if (!sys::chdir(chdirPath.c_str())) {
fprintf(stderr, "%s: error: unable to honor --chdir: %s\n",
getProgramName(), strerror(errno));
return 1;
}
// Print a message about the changed directory. The exact format here is
// important, it is recognized by other tools (like Emacs).
fprintf(stdout, "%s: Entering directory `%s'\n", getProgramName(),
chdirPath.c_str());
fflush(stdout);
}
if (!customTool.empty()) {
std::vector<std::string> availableTools = {
"clean",
"targets",
"list",
};
if (std::find(availableTools.begin(), availableTools.end(), customTool) ==
availableTools.end()) {
fprintf(stderr, "error: unknown tool '%s'\n", customTool.c_str());
return 1;
} else if (customTool == "list") {
if (!args.empty()) {
fprintf(stderr, "error: unsupported arguments to tool '%s'\n",
customTool.c_str());
return 1;
}
fprintf(stdout, "available ninja tools:\n");
for (const auto& tool: availableTools) {
fprintf(stdout, " %s\n", tool.c_str());
}
return 0;
}
}
SmallString<128> current_dir;
if (std::error_code ec = llvm::sys::fs::current_path(current_dir)) {
fprintf(stderr, "%s: error: cannot determine current directory\n",
getProgramName());
return 1;
}
if (numJobsInParallel == 0) {
unsigned numCPUs = std::thread::hardware_concurrency();
if (numCPUs == 0) {
fprintf(stderr, "%s: error: unable to detect number of CPUs (%s)",
getProgramName(), strerror(errno));
return 1;
}
numJobsInParallel = numCPUs + 2;
}
const std::string workingDirectory = current_dir.str();
// Run up to two iterations, the first one loads the manifest and rebuilds it
// if necessary, the second only runs if the manifest needs to be reloaded.
//
// This is somewhat inefficient in the case where the manifest needs to be
// reloaded (we reopen the database, for example), but we don't expect that to
// be a common case spot in practice.
for (int iteration = 0; iteration != 2; ++iteration) {
BuildContext context{workingDirectory};
context.numFailedCommandsToTolerate = numFailedCommandsToTolerate;
context.quiet = quiet;
context.simulate = simulate;
context.strict = strict;
context.verbose = verbose;
context.numJobsInParallel = numJobsInParallel;
context.schedulerAlgorithm = schedulerAlgorithm;
// Load the manifest.
BuildManifestActions actions(context);
ninja::ManifestLoader loader(workingDirectory, manifestFilename, actions);
context.manifest = loader.load();
// If there were errors loading, we are done.
if (unsigned numErrors = actions.getNumErrors()) {
context.emitNote("%d errors generated.", numErrors);
return 1;
}
// Run the targets tool, if specified.
if (!customTool.empty() && customTool == "targets") {
if (args.size() != 1 || args[0] != "all") {
if (args.empty()) {
context.emitError("unsupported arguments to tool '%s'",
customTool.c_str());
} else {
context.emitError("unsupported argument to tool '%s': '%s'",
customTool.c_str(), args[0].c_str());
}
return 1;
}
for (const auto command: context.manifest->getCommands()) {
for (const auto& output: command->getOutputs()) {
fprintf(stdout, "%s: %s\n", output->getScreenPath().c_str(),
command->getRule()->getName().c_str());
}
}
return 0;
}
// Emulate `-t clean` by removing the database.
if (!customTool.empty() && customTool == "clean") {
if(dbFilename.empty()) {
context.emitError("unable to clean without a database. Command ignored.");
return 1;
} else {
(void)basic::sys::unlink(dbFilename.c_str());
context.emitNote("cleaned the build database, artifacts preserved.");
return 0;
}
}
// Otherwise, run the build.
// Attach the database, if requested.
if (!dbFilename.empty()) {
std::string error;
std::unique_ptr<core::BuildDB> db(
core::createSQLiteBuildDB(dbFilename,
BuildValue::currentSchemaVersion,
/* recreateUnmatchedVersion = */ true,
&error));
if (!db || !context.engine.attachDB(std::move(db), &error)) {
context.emitError("unable to open build database: %s", error.c_str());
return 1;
}
}
// Enable tracing, if requested.
if (!traceFilename.empty()) {
std::string error;
if (!context.engine.enableTracing(traceFilename, &error)) {
context.emitError("unable to enable tracing: %s", error.c_str());
return 1;
}
}
class NinjaBuildCommandRule: public core::Rule {
BuildContext& context;
ninja::Command* command;
public:
NinjaBuildCommandRule(const core::KeyType& key, BuildContext& context,
ninja::Command* command)
: core::Rule(key), context(context), command(command) {}
core::Task* createTask(core::BuildEngine&) override {
return buildCommand(context, command);
}
bool isResultValid(core::BuildEngine&, const core::ValueType& value) override {
// If simulating, assume cached results are valid.
if (context.simulate)
return true;
return buildCommandIsResultValid(command, value);
}
void updateStatus(core::BuildEngine&, core::Rule::StatusKind status) override {
updateCommandStatus(context, command, status);
}
};
class NinjaCompositeResultRule: public core::Rule {
BuildContext& context;
ninja::Command* command;
int index;
const core::KeyType compositeRuleName;
public:
NinjaCompositeResultRule(const core::KeyType& key, BuildContext& context,
ninja::Command* command, int index,
const core::KeyType& compositeRuleName)
: core::Rule(key), context(context), command(command), index(index)
, compositeRuleName(compositeRuleName) {}
core::Task* createTask(core::BuildEngine&) override {
return selectCompositeBuildResult(context, command, index, compositeRuleName);
}
bool isResultValid(core::BuildEngine&, const core::ValueType& value) override {
// If simulating, assume cached results are valid.
if (context.simulate)
return true;
return selectCompositeIsResultValid(command, value);
}
};
class NinjaBuildTargetsRule: public core::Rule {
BuildContext& context;
std::vector<std::string> targets;
public:
NinjaBuildTargetsRule(const core::KeyType& key, BuildContext& context,
const std::vector<std::string>& targets)
: core::Rule(key), context(context), targets(targets) {}
core::Task* createTask(core::BuildEngine&) override {
return buildTargets(context, targets);
}
bool isResultValid(core::BuildEngine&, const core::ValueType& value) override {
return false;
}
};
// Create rules for all of the build commands up front.
//
// FIXME: We should probably also move this to be dynamic.
for (const auto command: context.manifest->getCommands()) {
// If this command has a single output, create the trivial rule.
if (command->getOutputs().size() == 1) {
context.engine.addRule(std::unique_ptr<core::Rule>(new NinjaBuildCommandRule(command->getOutputs()[0]->getCanonicalPath(), context, command)));
continue;
}
// Otherwise, create a composite rule group for the multiple outputs.
// Create a signature for the composite rule.
//
// FIXME: Make efficient.
std::string compositeRuleName = "";
for (auto& output: command->getOutputs()) {
if (!compositeRuleName.empty())
compositeRuleName += "&&";
compositeRuleName += output->getCanonicalPath();
}
// Add the composite rule, which will run the command and build all
// outputs.
context.engine.addRule(std::unique_ptr<core::Rule>(new NinjaBuildCommandRule(compositeRuleName, context, command)));
// Create the per-output selection rules that select the individual output
// result from the composite result.
for (unsigned i = 0, e = command->getOutputs().size(); i != e; ++i) {
context.engine.addRule(std::unique_ptr<core::Rule>(new NinjaCompositeResultRule(command->getOutputs()[i]->getCanonicalPath(), context, command, i, compositeRuleName)));
}
}
// If this is the first iteration, build the manifest, unless disabled.
if (autoRegenerateManifest && iteration == 0) {
SmallString<256> absManifestPath = StringRef(manifestFilename);
llbuild::ninja::Manifest::normalize_path(workingDirectory, absManifestPath);
context.engine.build(StringRef(absManifestPath));
// If the manifest was rebuilt, then reload it and build again.
if (context.numBuiltCommands) {
continue;
}
// Otherwise, perform the main build.
//
// FIXME: This is somewhat inefficient, as we will end up repeating any
// dependency scanning that was required for checking the manifest. We can
// fix this by building the manifest inline with the targets...
}
// If using a build profile, open it.
if (!profileFilename.empty()) {
context.profileFP = ::fopen(profileFilename.c_str(), "w");
if (!context.profileFP) {
context.emitError("unable to open build profile '%s' (%s)\n",
profileFilename.c_str(), strerror(errno));
return 1;
}
fprintf(context.profileFP, "[\n");
}
// Parse the positional arguments.
std::vector<std::string> targetsToBuild;
for (const std::string& arg: args) {
if (auto *node = context.manifest->findNode(context.workingDirectory, arg)) {
targetsToBuild.push_back(node->getCanonicalPath());
} else {
fprintf(stderr, "%s: error: unknown target: '%s'\n",
getProgramName(), arg.c_str());
exit(1);
}
}
// If no explicit targets were named, build the default targets.
if (targetsToBuild.empty()) {
for (auto& target: context.manifest->getDefaultTargets())
targetsToBuild.push_back(target->getCanonicalPath());
// If there are no default targets, then build all of the root targets.
if (targetsToBuild.empty()) {
std::unordered_set<const ninja::Node*> inputNodes;
// Collect all of the input nodes.
for (const auto& command: context.manifest->getCommands()) {
for (const auto* input: command->getInputs()) {
inputNodes.emplace(input);
}
}
// Build all of the targets that are not an input.
for (const auto& command: context.manifest->getCommands()) {
for (const auto& output: command->getOutputs()) {
if (!inputNodes.count(output)) {
targetsToBuild.push_back(output->getCanonicalPath());
}
}
}
}
}
// Generate an error if there is nothing to build.
if (targetsToBuild.empty()) {
context.emitError("no targets to build");
return 1;
}
// If building multiple targets, do so via a dummy rule to allow them to
// build concurrently (and without duplicates).
//
// FIXME: We should sort out eventually whether the engine itself should
// support this. It seems like an obvious feature, but it is also trivial
// for the client to implement on top of the existing API.
if (targetsToBuild.size() > 1) {
// Create a dummy rule to build all targets.
context.engine.addRule(std::unique_ptr<core::Rule>(new NinjaBuildTargetsRule("<<build>>", context,
targetsToBuild)));
context.engine.build("<<build>>");
} else {
context.engine.build(targetsToBuild[0]);
}
if (!dumpGraphPath.empty()) {
context.engine.dumpGraphToFile(dumpGraphPath);
}
// Close the build profile, if used.
if (context.profileFP) {
::fclose(context.profileFP);
context.emitNote(
"wrote build profile to '%s', use Chrome's about:tracing to view.",
profileFilename.c_str());
}
// If the build was cancelled by SIGINT, cause ourself to also die by SIGINT
// to support proper shell behavior.
if (context.wasCancelledBySigint) {
// Ensure SIGINT action is default.
#if defined(_WIN32)
signal(SIGINT, SIG_DFL);
#else
struct sigaction action {};
action.sa_handler = SIG_DFL;
sigaction(SIGINT, &action, 0);
#endif
#if defined(_WIN32)
raise(SIGINT);
#else
kill(getpid(), SIGINT);
#endif
std::this_thread::sleep_for(std::chrono::microseconds(1000));
return 2;
}
// If there were command failures, report the count.
if (context.numFailedCommands) {
context.emitError("build had %d command failures",
context.numFailedCommands.load());
}
// If the build was stopped because of an error, return an error status.
if (context.numErrors) {
return 1;
}
// Otherwise, if nothing was done, print a single message to let the user
// know we completed successfully.
if (!context.quiet && !context.numBuiltCommands) {
context.emitNote("no work to do.");
}
// If we reached here on the first iteration, then we don't need a second
// and are done.
if (iteration == 0)
break;
}
// Return an appropriate exit status.
return 0;
}