blob: 5b8ac09af2897e9aa9fc6e30dbdd75efc71eff29 [file] [log] [blame]
//===-- NinjaBuildCommand.cpp ---------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "NinjaBuildCommand.h"
#include "llbuild/Basic/Compiler.h"
#include "llbuild/Basic/FileInfo.h"
#include "llbuild/Basic/Hashing.h"
#include "llbuild/Basic/SerialQueue.h"
#include "llbuild/Basic/Version.h"
#include "llbuild/Commands/Commands.h"
#include "llbuild/Core/BuildDB.h"
#include "llbuild/Core/BuildEngine.h"
#include "llbuild/Core/MakefileDepsParser.h"
#include "llbuild/Ninja/ManifestLoader.h"
#include "CommandLineStatusOutput.h"
#include "CommandUtil.h"
#include <atomic>
#include <cerrno>
#include <condition_variable>
#include <cstdarg>
#include <cstdlib>
#include <deque>
#include <mutex>
#include <sstream>
#include <thread>
#include <unordered_set>
#include <fcntl.h>
#include <signal.h>
#include <spawn.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
using namespace llbuild;
using namespace llbuild::basic;
using namespace llbuild::commands;
extern "C" {
extern char **environ;
}
static uint64_t getTimeInMicroseconds() {
struct timeval tv;
::gettimeofday(&tv, nullptr);
return tv.tv_sec * 1000000 + tv.tv_usec;
}
static std::string getFormattedString(const char* fmt, va_list ap) {
char* buf;
if (::vasprintf(&buf, fmt, ap) < 0) {
return "unable to format message";
}
std::string result(buf);
::free(buf);
return result;
}
static std::string getFormattedString(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
std::string result = getFormattedString(fmt, ap);
va_end(ap);
return result;
}
static void usage(int exitCode=1) {
int optionWidth = 20;
fprintf(stderr, "Usage: %s ninja build [options] [<targets>...]\n",
getProgramName());
fprintf(stderr, "\nOptions:\n");
fprintf(stderr, " %-*s %s\n", optionWidth, "--help",
"show this help message and exit");
fprintf(stderr, " %-*s %s\n", optionWidth, "--version",
"print the Ninja compatible version number");
fprintf(stderr, " %-*s %s\n", optionWidth, "--simulate",
"simulate the build, assuming commands succeed");
fprintf(stderr, " %-*s %s\n", optionWidth, "-C, --chdir <PATH>",
"change directory to PATH before anything else");
fprintf(stderr, " %-*s %s\n", optionWidth, "--no-db",
"do not persist build results");
fprintf(stderr, " %-*s %s\n", optionWidth, "--db <PATH>",
"persist build results at PATH [default='build.db']");
fprintf(stderr, " %-*s %s\n", optionWidth, "-f <PATH>",
"load the manifest at PATH [default='build.ninja']");
fprintf(stderr, " %-*s %s\n", optionWidth, "-k <N>",
"keep building until N commands fail [default=1]");
fprintf(stderr, " %-*s %s\n", optionWidth, "-t, --tool <TOOL>",
"run a ninja tool. use 'list' to list available tools.");
fprintf(stderr, " %-*s %s\n", optionWidth, "-j, --jobs <JOBS>",
"number of jobs to build in parallel [default=cpu dependent]");
fprintf(stderr, " %-*s %s\n", optionWidth, "--no-regenerate",
"disable manifest auto-regeneration");
fprintf(stderr, " %-*s %s\n", optionWidth, "--dump-graph <PATH>",
"dump build graph to PATH in Graphviz DOT format");
fprintf(stderr, " %-*s %s\n", optionWidth, "--profile <PATH>",
"write a build profile trace event file to PATH");
fprintf(stderr, " %-*s %s\n", optionWidth, "--strict",
"use strict mode (no bug compatibility)");
fprintf(stderr, " %-*s %s\n", optionWidth, "--trace <PATH>",
"trace build engine operation to PATH");
fprintf(stderr, " %-*s %s\n", optionWidth, "--quiet",
"don't show information on executed commands");
fprintf(stderr, " %-*s %s\n", optionWidth, "-v, --verbose",
"show full invocation for executed commands");
fprintf(stderr, " %-*s %s\n", optionWidth, "-l <N>",
"start jobs only when load average is below N [not implemented]");
fprintf(stderr, " %-*s %s\n", optionWidth, "-d <TOOL>",
"enable debugging tool TOOL. 'list' for available [not implemented]");
::exit(exitCode);
}
namespace {
/// The build execution queue manages the actual parallel execution of jobs
/// which have been enqueued as a result of command orocessing.
///
/// This queue guarantees serial execution when only configured with a single
/// lane.
struct BuildExecutionQueue {
/// The number of lanes the queue was configured with.
unsigned numLanes;
/// A thread for each lane.
std::vector<std::unique_ptr<std::thread>> lanes;
/// The ready queue of jobs to execute.
std::deque<std::function<void(unsigned)>> readyJobs;
std::mutex readyJobsMutex;
std::condition_variable readyJobsCondition;
/// Use LIFO execution.
bool useLIFO;
public:
BuildExecutionQueue(unsigned numLanes, bool useLIFO)
: numLanes(numLanes), useLIFO(useLIFO)
{
for (unsigned i = 0; i != numLanes; ++i) {
lanes.push_back(std::unique_ptr<std::thread>(
new std::thread(
&BuildExecutionQueue::executeLane, this, i)));
}
}
~BuildExecutionQueue() {
// Shut down the lanes.
for (unsigned i = 0; i != numLanes; ++i) {
addJob({});
}
for (unsigned i = 0; i != numLanes; ++i) {
lanes[i]->join();
}
}
void executeLane(unsigned laneNumber) {
// oust execute items from the queue until shutdown.
while (true) {
// Take a job from the ready queue.
std::function<void(unsigned)> job;
{
std::unique_lock<std::mutex> lock(readyJobsMutex);
// While the queue is empty, wait for an item.
while (readyJobs.empty()) {
readyJobsCondition.wait(lock);
}
// Take an item according to the chosen policy.
if (useLIFO) {
job = readyJobs.back();
readyJobs.pop_back();
} else {
job = readyJobs.front();
readyJobs.pop_front();
}
}
// If we got an empty job, the queue is shutting down.
if (!job)
break;
// Process the job.
job(laneNumber);
}
}
void addJob(std::function<void(unsigned)> job) {
std::lock_guard<std::mutex> guard(readyJobsMutex);
readyJobs.push_back(job);
readyJobsCondition.notify_one();
}
};
/// Result value that is computed by the rules for input and command files.
class BuildValue {
private:
// Copying and move assignment are disabled.
BuildValue(const BuildValue&) LLBUILD_DELETED_FUNCTION;
void operator=(const BuildValue&) LLBUILD_DELETED_FUNCTION;
BuildValue &operator=(BuildValue&& rhs) LLBUILD_DELETED_FUNCTION;
public:
static const int currentSchemaVersion = 3;
private:
enum class BuildValueKind : uint32_t {
/// A value produced by a existing input file.
ExistingInput = 0,
/// A value produced by a missing input file.
MissingInput,
/// A value produced by a successful command.
SuccessfulCommand,
/// A value produced by a failing command.
FailedCommand,
/// A value produced by a command that was not run.
SkippedCommand,
};
/// The kind of value.
BuildValueKind kind;
/// The number of attached output infos.
const uint32_t numOutputInfos = 0;
union {
/// The file info for the rule output, for existing inputs and successful
/// commands with a single output.
FileInfo asOutputInfo;
/// The file info for successful commands with multiple outputs.
FileInfo* asOutputInfos;
} valueData;
/// The command hash, for successful commands.
uint64_t commandHash;
private:
BuildValue() {}
BuildValue(BuildValueKind kind)
: kind(kind), numOutputInfos(0), commandHash(0)
{
}
BuildValue(BuildValueKind kind, const FileInfo& outputInfo,
uint64_t commandHash = 0)
: kind(kind), numOutputInfos(1), commandHash(commandHash)
{
valueData.asOutputInfo = outputInfo;
}
BuildValue(BuildValueKind kind, const FileInfo* outputInfos,
uint32_t numOutputInfos, uint64_t commandHash = 0)
: kind(kind), numOutputInfos(numOutputInfos), commandHash(commandHash)
{
valueData.asOutputInfos = new FileInfo[numOutputInfos];
for (uint32_t i = 0; i != numOutputInfos; ++i) {
valueData.asOutputInfos[i] = outputInfos[i];
}
}
public:
// Build values can only be moved via construction, not copied.
BuildValue(BuildValue&& rhs) {
memcpy(this, &rhs, sizeof(rhs));
memset(&rhs, 0, sizeof(rhs));
}
~BuildValue() {
if (hasMultipleOutputs()) {
delete[] valueData.asOutputInfos;
}
}
static BuildValue makeExistingInput(const FileInfo& outputInfo) {
return BuildValue(BuildValueKind::ExistingInput, outputInfo);
}
static BuildValue makeMissingInput() {
return BuildValue(BuildValueKind::MissingInput);
}
static BuildValue makeSuccessfulCommand(const FileInfo& outputInfo,
uint64_t commandHash) {
return BuildValue(BuildValueKind::SuccessfulCommand, outputInfo,
commandHash);
}
static BuildValue makeSuccessfulCommand(const FileInfo* outputInfos,
uint32_t numOutputInfos,
uint64_t commandHash) {
// This ctor function should only be used for multiple outputs.
assert(numOutputInfos > 1);
return BuildValue(BuildValueKind::SuccessfulCommand, outputInfos,
numOutputInfos, commandHash);
}
static BuildValue makeFailedCommand() {
return BuildValue(BuildValueKind::FailedCommand);
}
static BuildValue makeSkippedCommand() {
return BuildValue(BuildValueKind::SkippedCommand);
}
bool isExistingInput() const { return kind == BuildValueKind::ExistingInput; }
bool isMissingInput() const { return kind == BuildValueKind::MissingInput; }
bool isSuccessfulCommand() const {
return kind == BuildValueKind::SuccessfulCommand;
}
bool isFailedCommand() const { return kind == BuildValueKind::FailedCommand; }
bool isSkippedCommand() const {
return kind == BuildValueKind::SkippedCommand;
}
bool hasMultipleOutputs() const {
return numOutputInfos > 1;
}
unsigned getNumOutputs() const {
assert((isExistingInput() || isSuccessfulCommand()) &&
"invalid call for value kind");
return numOutputInfos;
}
const FileInfo& getOutputInfo() const {
assert((isExistingInput() || isSuccessfulCommand()) &&
"invalid call for value kind");
assert(!hasMultipleOutputs() &&
"invalid call on result with multiple outputs");
return valueData.asOutputInfo;
}
const FileInfo& getNthOutputInfo(unsigned n) const {
assert((isExistingInput() || isSuccessfulCommand()) &&
"invalid call for value kind");
assert(n < getNumOutputs());
if (hasMultipleOutputs()) {
return valueData.asOutputInfos[n];
} else {
assert(n == 0);
return valueData.asOutputInfo;
}
}
uint64_t getCommandHash() const {
assert(isSuccessfulCommand() && "invalid call for value kind");
return commandHash;
}
static BuildValue fromValue(const core::ValueType& value) {
BuildValue result;
assert(value.size() >= sizeof(result));
memcpy(&result, value.data(), sizeof(result));
// If this result has multiple output values, deserialize them properly.
if (result.numOutputInfos > 1) {
assert(value.size() == (sizeof(result) +
result.numOutputInfos * sizeof(FileInfo)));
result.valueData.asOutputInfos = new FileInfo[result.numOutputInfos];
memcpy(result.valueData.asOutputInfos,
value.data() + sizeof(result),
result.numOutputInfos * sizeof(FileInfo));
} else {
assert(value.size() == sizeof(result));
}
return result;
}
core::ValueType toValue() {
if (numOutputInfos > 1) {
// FIXME: This could be packed one entry tighter.
std::vector<uint8_t> result(sizeof(*this) +
numOutputInfos * sizeof(FileInfo));
memcpy(result.data(), this, sizeof(*this));
memcpy(result.data() + sizeof(*this), valueData.asOutputInfos,
numOutputInfos * sizeof(FileInfo));
return result;
} else {
std::vector<uint8_t> result(sizeof(*this));
memcpy(result.data(), this, sizeof(*this));
return result;
}
}
};
struct NinjaBuildEngineDelegate : public core::BuildEngineDelegate {
class BuildContext* context = nullptr;
virtual core::Rule lookupRule(const core::KeyType& key) override;
virtual void cycleDetected(const std::vector<core::Rule*>& items) override;
};
/// Wrapper for information used during a single build.
class BuildContext {
public:
/// The build engine delegate.
NinjaBuildEngineDelegate delegate;
/// The engine in use.
core::BuildEngine engine;
/// The Ninja manifest we are operating on.
std::unique_ptr<ninja::Manifest> manifest;
/// Whether commands should print status information.
bool quiet = false;
/// Whether the build is being "simulated", in which case commands won't be
/// run and inputs will be assumed to exist.
bool simulate = false;
/// Whether to use strict mode.
bool strict = false;
/// Whether output should use verbose mode.
bool verbose = false;
/// The number of failed commands to tolerate, or 0 if unlimited
unsigned numFailedCommandsToTolerate = 1;
/// The build profile output file.
FILE *profileFP = nullptr;
/// Whether the build has been cancelled or not.
std::atomic<bool> isCancelled{false};
/// Whether the build was cancelled by SIGINT.
std::atomic<bool> wasCancelledBySigint{false};
/// The number of generated errors.
std::atomic<unsigned> numErrors{0};
/// The number of commands executed during the build
unsigned numBuiltCommands{0};
/// The number of output commands written, for numbering purposes.
unsigned numOutputDescriptions{0};
/// The number of failed commands.
std::atomic<unsigned> numFailedCommands{0};
/// @name Status Reporting Command Counts
/// @{
/// The number of commands being scanned.
std::atomic<unsigned> numCommandsScanning{0};
/// The number of commands that were up-to-date.
std::atomic<unsigned> numCommandsUpToDate{0};
/// The number of commands that have been completed.
std::atomic<unsigned> numCommandsCompleted{0};
/// The number of commands that were updated (started, but didn't actually run
/// the command).
std::atomic<unsigned> numCommandsUpdated{0};
/// @}
/// The status output object.
CommandLineStatusOutput statusOutput;
/// The serial queue we used to order output consistently.
SerialQueue outputQueue;
/// The limited queue we use to execute parallel jobs.
std::unique_ptr<BuildExecutionQueue> jobQueue;
/// The previous SIGINT handler.
struct sigaction previousSigintHandler;
/// The set of spawned processes to cancel when interrupted.
std::unordered_set<pid_t> spawnedProcesses;
std::mutex spawnedProcessesMutex;
/// Low-level flag for when a SIGINT has been received.
static std::atomic<bool> wasInterrupted;
/// Pipe used to allow detection of signals.
static int signalWatchingPipe[2];
static void sigintHandler(int) {
// Set the atomic interrupt flag.
BuildContext::wasInterrupted = true;
// Write to wake up the signal monitoring thread.
char byte{};
write(signalWatchingPipe[1], &byte, 1);
}
/// Cancel the build in response to an interrupt event.
void cancelBuildOnInterrupt() {
std::lock_guard<std::mutex> guard(spawnedProcessesMutex);
emitNote("cancelling build.");
isCancelled = true;
wasCancelledBySigint = true;
// Cancel the spawned processes.
for (pid_t pid: spawnedProcesses) {
::kill(-pid, SIGINT);
}
// FIXME: In our model, we still wait for everything to terminate, which
// means a process that refuses to respond to SIGINT will cause us to just
// hang here. We should probably detect and report that and be willing to do
// a hard kill at some point (for example, on the second interrupt).
}
/// Check if an interrupt has occurred.
void checkForInterrupt() {
// Save and clear the interrupt flag, atomically.
bool wasInterrupted = BuildContext::wasInterrupted.exchange(false);
// Process the interrupt flag, if present.
if (wasInterrupted) {
// Otherwise, process the interrupt.
cancelBuildOnInterrupt();
}
}
/// Thread function to wait for indications that signals have arrived and to
/// process them.
void signalWaitThread() {
// Wait for signal arrival indications.
while (true) {
char byte;
int res = read(signalWatchingPipe[0], &byte, 1);
// If nothing was read, the pipe has been closed and we should shut down.
if (res == 0)
break;
// Otherwise, check if we were awoke because of an interrupt.
checkForInterrupt();
}
// Shut down the pipe.
close(signalWatchingPipe[0]);
signalWatchingPipe[0] = -1;
}
public:
BuildContext()
: engine(delegate),
isCancelled(false)
{
// Open the status output.
std::string error;
if (!statusOutput.open(&error)) {
fprintf(stderr, "error: %s: unable to open output: %s\n",
getProgramName(), error.c_str());
exit(1);
}
// Register the context with the delegate.
delegate.context = this;
// Register an interrupt handler.
struct sigaction action{};
action.sa_handler = &BuildContext::sigintHandler;
sigaction(SIGINT, &action, &previousSigintHandler);
// Create a pipe and thread to watch for signals.
assert(BuildContext::signalWatchingPipe[0] == -1 &&
BuildContext::signalWatchingPipe[1] == -1);
if (::pipe(BuildContext::signalWatchingPipe) < 0) {
perror("pipe");
}
new std::thread(&BuildContext::signalWaitThread, this);
}
~BuildContext() {
// Ensure the output queue is done.
outputQueue.sync([] {});
// Restore any previous SIGINT handler.
sigaction(SIGINT, &previousSigintHandler, NULL);
// Close the status output.
std::string error;
statusOutput.close(&error);
// Close the signal watching pipe.
::close(BuildContext::signalWatchingPipe[1]);
signalWatchingPipe[1] = -1;
}
/// @name Diagnostics Output
/// @{
/// Emit a status line, which can be updated.
///
/// This method should only be called from the outputQueue.
void emitStatus(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
std::string message = getFormattedString(fmt, ap);
va_end(ap);
if (verbose) {
statusOutput.writeText(message + "\n");
} else {
statusOutput.setOrWriteLine(message);
}
}
/// Emit a diagnostic to the error stream.
void emitDiagnostic(std::string kind, std::string message) {
outputQueue.async([this, kind=std::move(kind), message=std::move(message)] {
statusOutput.finishLine();
fprintf(stderr, "%s: %s: %s\n", getProgramName(), kind.c_str(),
message.c_str());
});
}
/// Emit a diagnostic followed by a block of text, ensuring the text
/// immediately follows the diagnostic.
void emitDiagnosticAndText(std::string kind, std::string message,
std::string text) {
outputQueue.async([this, kind=std::move(kind), message=std::move(message),
text=std::move(text)] {
statusOutput.finishLine();
fprintf(stderr, "%s: %s: %s\n", getProgramName(), kind.c_str(),
message.c_str());
fflush(stderr);
fwrite(text.data(), text.size(), 1, stdout);
fflush(stdout);
});
}
/// Emit a block of text to the output.
void emitText(std::string text) {
outputQueue.async([this, text=std::move(text)] {
statusOutput.finishLine();
fwrite(text.data(), text.size(), 1, stdout);
fflush(stdout);
});
}
void emitText(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
emitText(getFormattedString(fmt, ap));
va_end(ap);
}
void emitError(std::string&& message) {
emitDiagnostic("error", std::move(message));
++numErrors;
}
void emitErrorAndText(std::string&& message, std::string&& text) {
emitDiagnosticAndText("error", std::move(message), text);
++numErrors;
}
void emitError(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
emitError(getFormattedString(fmt, ap));
va_end(ap);
}
void emitNote(std::string&& message) {
emitDiagnostic("note", std::move(message));
}
void emitNote(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
emitNote(getFormattedString(fmt, ap));
va_end(ap);
}
/// @}
void reportMissingInput(const ninja::Node* node) {
// We simply report the missing input here, the build will be cancelled when
// a rule sees it missing.
emitError("missing input '%s' and no rule to build it",
node->getPath().c_str());
}
void incrementFailedCommands() {
// Update our count of the number of failed commands.
unsigned numFailedCommands = ++this->numFailedCommands;
// Cancel the build, if the number of command failures exceeds the
// number to continue past.
if (numFailedCommandsToTolerate != 0 &&
numFailedCommands == numFailedCommandsToTolerate) {
emitError("stopping build due to command failures");
isCancelled = true;
}
}
};
std::atomic<bool> BuildContext::wasInterrupted{false};
int BuildContext::signalWatchingPipe[2]{-1, -1};
class BuildManifestActions : public ninja::ManifestLoaderActions {
BuildContext& context;
ninja::ManifestLoader* loader = 0;
unsigned numErrors = 0;
unsigned maxErrors = 20;
private:
virtual void initialize(ninja::ManifestLoader* loader) override {
this->loader = loader;
}
virtual void error(std::string filename, std::string message,
const ninja::Token& at) override {
if (numErrors++ >= maxErrors)
return;
util::emitError(filename, message, at, loader->getCurrentParser());
}
virtual bool readFileContents(const std::string& fromFilename,
const std::string& filename,
const ninja::Token* forToken,
std::unique_ptr<char[]>* data_out,
uint64_t* length_out) override {
// Load the file contents and return if successful.
std::string error;
if (util::readFileContents(filename, data_out, length_out, &error))
return true;
// Otherwise, emit the error.
++numErrors;
if (forToken) {
util::emitError(fromFilename, error, *forToken,
loader->getCurrentParser());
} else {
context.emitError(std::move(error));
}
return false;
};
public:
BuildManifestActions(BuildContext& context) : context(context) {}
unsigned getNumErrors() const { return numErrors; }
};
static core::Task*
buildCommand(BuildContext& context, ninja::Command* command) {
struct NinjaCommandTask : core::Task {
BuildContext& context;
ninja::Command* command;
/// If true, the command should be skipped (because of an error in an
/// input).
bool shouldSkip = false;
/// If true, the command had a missing input (this implies ShouldSkip is
/// true).
bool hasMissingInput = false;
/// If true, the command can be updated if the output is newer than all of
/// the inputs.
bool canUpdateIfNewer = true;
/// Information on the prior command result, if present.
bool hasPriorResult = false;
uint64_t priorCommandHash;
/// The timestamp of the most recently rebuilt input.
FileTimestamp newestModTime{ 0, 0 };
NinjaCommandTask(BuildContext& context, ninja::Command* command)
: context(context), command(command) {
// If this command uses discovered dependencies, we can never skip it (we
// don't yet have a way to account for the discovered dependencies, or
// preserve them if skipped).
//
// FIXME: We should support update-if-newer for commands with deps.
if (command->getDepsStyle() != ninja::Command::DepsStyleKind::None)
canUpdateIfNewer = false;
}
virtual void provideValue(core::BuildEngine& engine, uintptr_t inputID,
const core::ValueType& valueData) override {
// Process the input value to see if we should skip this command.
BuildValue value = BuildValue::fromValue(valueData);
// All direct inputs to NinjaCommandTask objects should be singleton
// values.
assert(!value.hasMultipleOutputs());
// If the value is not an existing input or a successful command, then we
// shouldn't run this command.
if (!value.isExistingInput() && !value.isSuccessfulCommand()) {
shouldSkip = true;
if (value.isMissingInput()) {
hasMissingInput = true;
context.reportMissingInput(command->getInputs()[inputID]);
}
} else {
// Otherwise, track the information used to determine if we can just
// update the command instead of running it.
const FileInfo& outputInfo = value.getOutputInfo();
// If there is a missing input file (from a successful command), we
// always need to run the command.
if (outputInfo.isMissing()) {
canUpdateIfNewer = false;
} else {
// Otherwise, keep track of the newest input.
if (outputInfo.modTime > newestModTime) {
newestModTime = outputInfo.modTime;
}
}
}
}
bool isImmediatelyCyclicInput(const ninja::Node* node) const {
for (const auto* output: command->getOutputs())
if (node == output)
return true;
return false;
}
void completeTask(BuildValue&& result, bool forceChange=false) {
context.engine.taskIsComplete(this, result.toValue(), forceChange);
}
virtual void start(core::BuildEngine& engine) override {
// If this is a phony rule, ignore any immediately cyclic dependencies in
// non-strict mode, which are generated frequently by CMake, but can be
// ignored by Ninja. See https://github.com/martine/ninja/issues/935.
//
// FIXME: Find a way to harden this more, or see if we can just get CMake
// to fix it.
bool isPhony = command->getRule() == context.manifest->getPhonyRule();
// Request all of the explicit and implicit inputs (the only difference
// between them is that implicit inputs do not appear in ${in} during
// variable expansion, but that has already been performed).
unsigned id = 0;
for (auto it = command->explicitInputs_begin(),
ie = command->explicitInputs_end(); it != ie; ++it, ++id) {
if (!context.strict && isPhony && isImmediatelyCyclicInput(*it))
continue;
engine.taskNeedsInput(this, (*it)->getPath(), id);
}
for (auto it = command->implicitInputs_begin(),
ie = command->implicitInputs_end(); it != ie; ++it, ++id) {
if (!context.strict && isPhony && isImmediatelyCyclicInput(*it))
continue;
engine.taskNeedsInput(this, (*it)->getPath(), id);
}
// Request all of the order-only inputs.
for (auto it = command->orderOnlyInputs_begin(),
ie = command->orderOnlyInputs_end(); it != ie; ++it) {
if (!context.strict && isPhony && isImmediatelyCyclicInput(*it))
continue;
engine.taskMustFollow(this, (*it)->getPath());
}
}
virtual void providePriorValue(core::BuildEngine& engine,
const core::ValueType& valueData) override {
BuildValue value = BuildValue::fromValue(valueData);
if (value.isSuccessfulCommand()) {
hasPriorResult = true;
priorCommandHash = value.getCommandHash();
}
}
/// Compute the output result for the command.
BuildValue computeCommandResult(uint64_t commandHash) const {
unsigned numOutputs = command->getOutputs().size();
if (numOutputs == 1) {
return BuildValue::makeSuccessfulCommand(
FileInfo::getInfoForPath(
command->getOutputs()[0]->getPath()),
commandHash);
} else {
std::vector<FileInfo> outputInfos(numOutputs);
for (unsigned i = 0; i != numOutputs; ++i) {
outputInfos[i] = FileInfo::getInfoForPath(
command->getOutputs()[i]->getPath());
}
return BuildValue::makeSuccessfulCommand(outputInfos.data(), numOutputs,
commandHash);
}
}
/// Check if it is legal to only update the result (versus rerunning)
/// because the outputs are newer than all of the inputs.
bool canUpdateIfNewerWithResult(const BuildValue& result) {
assert(result.isSuccessfulCommand());
// Check each output.
for (unsigned i = 0, e = result.getNumOutputs(); i != e; ++i) {
const FileInfo& outputInfo = result.getNthOutputInfo(i);
// If the output is missing, we need to rebuild.
if (outputInfo.isMissing())
return false;
// Check if the output is actually newer than the most recent input.
//
// In strict mode, we use a strict "newer-than" check here, to guarantee
// correctness in the face of equivalent timestamps. This is
// particularly important on OS X, which has a low resolution mtime.
//
// However, in non-strict mode, we need to be compatible with Ninja
// here, because there are some very important uses cases where this
// behavior is relied on. One major example is CMake's initial
// configuration checks using Ninja -- if this is not in place, those
// rules will try and rerun the generator of the "TRY_COMPILE" steps,
// and will enter an infinite reconfiguration loop. See also:
//
// See: http://www.cmake.org/Bug/view.php?id=15456
if (context.strict) {
if (outputInfo.modTime <= newestModTime)
return false;
} else {
if (outputInfo.modTime < newestModTime)
return false;
}
}
return true;
}
virtual void inputsAvailable(core::BuildEngine& engine) override {
// If the build is cancelled, skip everything.
if (context.isCancelled) {
return completeTask(BuildValue::makeSkippedCommand());
}
// Ignore phony commands.
//
// FIXME: Is it right to bring this up-to-date when one of the inputs
// indicated a failure? It probably doesn't matter.
uint64_t commandHash = basic::hashString(command->getCommandString());
if (command->getRule() == context.manifest->getPhonyRule()) {
// Get the result.
BuildValue result = computeCommandResult(commandHash);
// If any output is missing, then we always want to force the change to
// propagate.
bool forceChange = false;
for (unsigned i = 0, e = result.getNumOutputs(); i != e; ++i) {
if (result.getNthOutputInfo(i).isMissing()) {
forceChange = true;
break;
}
}
return completeTask(std::move(result), forceChange);
}
// If it is legal to simply update the command, then if the command output
// exists and is newer than all of the inputs, don't actually run the
// command (just bring it up-to-date).
if (canUpdateIfNewer) {
// If this isn't a generator command and its command hash differs, we
// can't update it.
if (!command->hasGeneratorFlag() &&
(!hasPriorResult || priorCommandHash != commandHash))
canUpdateIfNewer = false;
if (canUpdateIfNewer) {
BuildValue result = computeCommandResult(commandHash);
if (canUpdateIfNewerWithResult(result)) {
// Update the count of the number of commands which have been
// updated without being rerun.
++context.numCommandsUpdated;
return completeTask(std::move(result));
}
}
}
// Otherwise, actually run the command.
++context.numBuiltCommands;
// If we are simulating the build, just print the description and
// complete.
if (context.simulate) {
if (!context.quiet)
writeDescription(context, command);
return completeTask(BuildValue::makeSkippedCommand());
}
// If not simulating, but this command should be skipped, then do nothing.
if (shouldSkip) {
// If this command had a failed input, treat it as having failed.
if (hasMissingInput) {
context.emitError("cannot build '%s' due to missing input",
command->getOutputs()[0]->getPath().c_str());
// Update the count of failed commands.
context.incrementFailedCommands();
}
return completeTask(BuildValue::makeSkippedCommand());
}
assert(!hasMissingInput);
// Otherwise, enqueue the job to run later.
context.jobQueue->addJob([&] (unsigned bucket) {
// Suppress static analyzer false positive on generalized lambda capture
// (rdar://problem/22165130).
#ifndef __clang_analyzer__
// Take care to not rely on the ``this`` object, which may disappear
// before the queue executes this block.
BuildContext& localContext(context);
ninja::Command* localCommand(command);
if (localContext.profileFP) {
localContext.outputQueue.sync(
[&localContext=localContext, localCommand=localCommand, bucket] {
uint64_t startTime = getTimeInMicroseconds();
fprintf(localContext.profileFP,
("{ \"name\": \"%s\", \"ph\": \"B\", \"pid\": 0, "
"\"tid\": %d, \"ts\": %llu},\n"),
localCommand->getEffectiveDescription().c_str(), bucket,
startTime);
});
}
executeCommand();
if (localContext.profileFP) {
localContext.outputQueue.sync(
[&localContext=localContext, localCommand=localCommand, bucket] {
uint64_t endTime = getTimeInMicroseconds();
fprintf(localContext.profileFP,
("{ \"name\": \"%s\", \"ph\": \"E\", \"pid\": 0, "
"\"tid\": %d, \"ts\": %llu},\n"),
localCommand->getEffectiveDescription().c_str(), bucket,
endTime);
});
}
#endif
});
}
static unsigned getNumPossibleMaxCommands(BuildContext& context) {
// Compute the "possible" number of maximum commands that will be
// run. This is only the "possible" max because we can start running
// commands before dependency scanning is complete -- we include the
// number of commands that are being scanned so that this number will
// always be greater than the number of commands that have been executed
// until the very last command is run.
int totalPossibleMaxCommands =
context.numCommandsCompleted + context.numCommandsScanning;
// Compute the number of max commands to show, subtracting out all the
// commands that we avoided running.
int possibleMaxCommands = totalPossibleMaxCommands -
(context.numCommandsUpToDate + context.numCommandsUpdated);
return possibleMaxCommands;
}
static void writeDescription(BuildContext& context,
ninja::Command* command) {
const std::string& description =
context.verbose ? command->getCommandString() :
command->getEffectiveDescription();
context.emitStatus(
"[%d/%d] %s", ++context.numOutputDescriptions,
getNumPossibleMaxCommands(context), description.c_str());
// Whenever we write a description for a console job, make sure to finish
// the output under the expectation that the console job might write to
// the output. We don't make any attempt to lock this in case the console
// job can run concurrently with anything else.
if (command->getExecutionPool() == context.manifest->getConsolePool())
context.statusOutput.finishLine();
}
void executeCommand() {
// If the build is cancelled, skip the job.
if (context.isCancelled) {
return completeTask(BuildValue::makeSkippedCommand());
}
// Write the description on the output queue, taking care to not rely on
// the ``this`` object, which may disappear before the queue executes this
// block.
if (!context.quiet) {
// Suppress static analyzer false positive on generalized lambda capture
// (rdar://problem/22165130).
#ifndef __clang_analyzer__
// If this is a console job, do the write synchronously to ensure it
// appears before the task might start.
if (command->getExecutionPool() == context.manifest->getConsolePool()) {
context.outputQueue.sync([&context=context, command=command] {
writeDescription(context, command);
});
} else {
context.outputQueue.async([&context=context, command=command] {
writeDescription(context, command);
});
}
#endif
}
// Actually run the command.
if (!spawnAndWaitForCommand()) {
// If the command failed, complete the task with the failed result and
// always propagate.
return completeTask(BuildValue::makeFailedCommand(),
/*ForceChange=*/true);
}
// Otherwise, the command succeeded so process the dependencies.
if (!processDiscoveredDependencies()) {
context.incrementFailedCommands();
return completeTask(BuildValue::makeFailedCommand(),
/*ForceChange=*/true);
}
// Complete the task with a successful value.
//
// We always restat the output, but we honor Ninja's restat flag by
// forcing downstream propagation if it isn't set.
uint64_t commandHash = basic::hashString(command->getCommandString());
BuildValue result = computeCommandResult(commandHash);
return completeTask(std::move(result),
/*ForceChange=*/!command->hasRestatFlag());
}
/// Execute the command process and wait for it to complete.
///
/// \returns True if the command succeeded.
bool spawnAndWaitForCommand() const {
bool isConsole = command->getExecutionPool() ==
context.manifest->getConsolePool();
// Initialize the spawn attributes.
posix_spawnattr_t attributes;
posix_spawnattr_init(&attributes);
// Unmask all signals
sigset_t noSignals;
sigemptyset(&noSignals);
posix_spawnattr_setsigmask(&attributes, &noSignals);
// Reset all signals to default behavior.
//
// On Linux, this can only be used to reset signals that are legal to
// modify, so we have to take care about the set we use.
#if defined(__linux__)
sigset_t mostSignals;
sigemptyset(&mostSignals);
for (int i = 1; i < SIGUNUSED; ++i) {
if (i == SIGKILL || i == SIGSTOP) continue;
sigaddset(&mostSignals, i);
}
posix_spawnattr_setsigdefault(&attributes, &mostSignals);
#else
sigset_t allSignals;
sigfillset(&allSignals);
posix_spawnattr_setsigdefault(&attributes, &allSignals);
#endif
// Establish a separate process group.
posix_spawnattr_setpgroup(&attributes, 0);
// Set the attribute flags.
unsigned flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
if (!isConsole)
flags |= POSIX_SPAWN_SETPGROUP;
// Close all other files by default.
//
// FIXME: This is an Apple-specific extension, and we will have to do
// something else on other platforms (and unfortunately, there isn't
// really an easy answer other than using a stub executable).
#ifdef __APPLE__
flags |= POSIX_SPAWN_CLOEXEC_DEFAULT;
#endif
posix_spawnattr_setflags(&attributes, flags);
// Setup the file actions.
posix_spawn_file_actions_t fileActions;
posix_spawn_file_actions_init(&fileActions);
// Create a pipe to use to read the command output, if necessary.
int pipe[2]{ -1, -1 };
if (!isConsole) {
if (::pipe(pipe) < 0) {
context.emitError("unable to create command pipe (%s)",
strerror(errno));
return false;
}
}
// Open /dev/null as stdin.
posix_spawn_file_actions_addopen(
&fileActions, 0, "/dev/null", O_RDONLY, 0);
if (isConsole) {
posix_spawn_file_actions_adddup2(&fileActions, 1, 1);
posix_spawn_file_actions_adddup2(&fileActions, 2, 2);
} else {
// Open the write end of the pipe as stdout and stderr.
posix_spawn_file_actions_adddup2(&fileActions, pipe[1], 1);
posix_spawn_file_actions_adddup2(&fileActions, pipe[1], 2);
// Close the read and write ends of the pipe.
posix_spawn_file_actions_addclose(&fileActions, pipe[0]);
posix_spawn_file_actions_addclose(&fileActions, pipe[1]);
}
// Spawn the command.
const char* args[4];
args[0] = "/bin/sh";
args[1] = "-c";
args[2] = command->getCommandString().c_str();
args[3] = nullptr;
// We need to hold the spawn processes lock when we spawn, to ensure that
// we don't create a process in between when we are cancelled.
pid_t pid;
{
std::lock_guard<std::mutex> guard(context.spawnedProcessesMutex);
if (posix_spawn(&pid, args[0], /*file_actions=*/&fileActions,
/*attrp=*/&attributes, const_cast<char**>(args),
::environ) != 0) {
context.emitError("unable to spawn process (%s)", strerror(errno));
return false;
}
// The console process will get interrupted automatically.
if (!isConsole)
context.spawnedProcesses.insert(pid);
}
posix_spawn_file_actions_destroy(&fileActions);
posix_spawnattr_destroy(&attributes);
// Read the command output, if buffering.
std::vector<char> outputData;
if (!isConsole) {
// Close the write end of the output pipe.
::close(pipe[1]);
// Read all the data from the output pipe.
while (true) {
char buf[4096];
ssize_t numBytes = read(pipe[0], buf, sizeof(buf));
if (numBytes < 0) {
context.emitError("unable to read from output pipe (%s)",
strerror(errno));
break;
}
if (numBytes == 0)
break;
outputData.insert(outputData.end(), &buf[0], &buf[numBytes]);
}
// Close the read end of the pipe.
::close(pipe[0]);
}
// Wait for the command to complete.
int status, result = waitpid(pid, &status, 0);
while (result == -1 && errno == EINTR)
result = waitpid(pid, &status, 0);
if (result == -1) {
context.emitError("unable to wait for process (%s)", strerror(errno));
}
// Update the set of spawned processes.
{
std::lock_guard<std::mutex> guard(context.spawnedProcessesMutex);
context.spawnedProcesses.erase(pid);
}
// If the build has been interrupted, return without writing any output or
// command status (since they will also have been interrupted).
if (context.isCancelled && context.wasCancelledBySigint) {
// We still return an accurate status just in case the command actually
// completed successfully.
return status == 0;
}
// If the child failed, show the full command and the output.
if (status != 0) {
// If the process was killed by SIGINT, assume it is because we were
// interrupted.
if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
return false;
// Otherwise, report the failure.
context.emitErrorAndText(
getFormattedString(
"process failed: %s", command->getCommandString().c_str()),
std::string(outputData.data(), outputData.size()));
// Update the count of failed commands.
context.incrementFailedCommands();
return false;
} else {
// Write the output data, if buffered.
if (!outputData.empty()) {
context.emitText(std::string(outputData.data(), outputData.size()));
}
}
return true;
}
bool processDiscoveredDependencies() {
// Process the discovered dependencies, if used.
switch (command->getDepsStyle()) {
case ninja::Command::DepsStyleKind::None:
return true;
case ninja::Command::DepsStyleKind::MSVC: {
context.emitError("MSVC style dependencies are unsupported");
return false;
}
case ninja::Command::DepsStyleKind::GCC: {
// Read the dependencies file.
std::string error;
std::unique_ptr<char[]> data;
uint64_t length;
if (!util::readFileContents(command->getDepsFile(), &data, &length,
&error)) {
// If the file is missing, just ignore it for consistency with Ninja
// (when using stored deps) in non-strict mode.
if (!context.strict)
return true;
// FIXME: Error handling.
context.emitError("unable to read dependency file: %s (%s)",
command->getDepsFile().c_str(), error.c_str());
return false;
}
// Parse the output.
//
// We just ignore the rule, and add any dependency that we encounter in
// the file.
struct DepsActions : public core::MakefileDepsParser::ParseActions {
BuildContext& context;
NinjaCommandTask* task;
const std::string& path;
unsigned numErrors{0};
DepsActions(BuildContext& context, NinjaCommandTask* task,
const std::string& path)
: context(context), task(task), path(path) {}
virtual void error(const char* message, uint64_t position) override {
context.emitError(
"error reading dependency file: %s (%s) at offset %u",
path.c_str(), message, unsigned(position));
++numErrors;
}
virtual void actOnRuleDependency(const char* dependency,
uint64_t length,
const StringRef
unescapedWord) override {
context.engine.taskDiscoveredDependency(task, unescapedWord);
}
virtual void actOnRuleStart(const char* name, uint64_t length,
const StringRef unescapedWord) override {}
virtual void actOnRuleEnd() override {}
};
DepsActions actions(context, this, command->getDepsFile());
core::MakefileDepsParser(data.get(), length, actions).parse();
return actions.numErrors == 0;
}
}
assert(0 && "unexpected case");
return false;
}
};
return context.engine.registerTask(new NinjaCommandTask(context, command));
}
static core::Task* buildInput(BuildContext& context, ninja::Node* input) {
struct NinjaInputTask : core::Task {
BuildContext& context;
ninja::Node* node;
NinjaInputTask(BuildContext& context, ninja::Node* node)
: context(context), node(node) { }
virtual void provideValue(core::BuildEngine& engine, uintptr_t inputID,
const core::ValueType& value) override { }
virtual void start(core::BuildEngine& engine) override { }
virtual void inputsAvailable(core::BuildEngine& engine) override {
if (context.simulate) {
engine.taskIsComplete(
this, BuildValue::makeExistingInput({}).toValue());
return;
}
auto outputInfo = FileInfo::getInfoForPath(node->getPath());
if (outputInfo.isMissing()) {
engine.taskIsComplete(this, BuildValue::makeMissingInput().toValue());
return;
}
engine.taskIsComplete(
this, BuildValue::makeExistingInput(outputInfo).toValue());
}
};
return context.engine.registerTask(new NinjaInputTask(context, input));
}
static core::Task*
buildTargets(BuildContext& context,
const std::vector<std::string>& targetsToBuild) {
struct TargetsTask : core::Task {
BuildContext& context;
std::vector<std::string> targetsToBuild;
TargetsTask(BuildContext& context,
const std::vector<std::string>& targetsToBuild)
: context(context), targetsToBuild(targetsToBuild) { }
virtual void provideValue(core::BuildEngine& engine, uintptr_t inputID,
const core::ValueType& valueData) override {
BuildValue value = BuildValue::fromValue(valueData);
if (value.isMissingInput()) {
context.emitError("unknown target '%s'",
targetsToBuild[inputID].c_str());
}
}
virtual void start(core::BuildEngine& engine) override {
// Request all of the targets.
unsigned id = 0;
for (const auto& target: targetsToBuild) {
engine.taskNeedsInput(this, target, id++);
}
}
virtual void inputsAvailable(core::BuildEngine& engine) override {
// Complete the job.
engine.taskIsComplete(
this, BuildValue::makeSuccessfulCommand({}, 0).toValue());
return;
}
};
return context.engine.registerTask(new TargetsTask(context, targetsToBuild));
}
static core::Task*
selectCompositeBuildResult(BuildContext& context, ninja::Command* command,
unsigned inputIndex,
const core::KeyType& compositeRuleName) {
struct SelectResultTask : core::Task {
const BuildContext& context;
const ninja::Command* command;
const unsigned inputIndex;
const core::KeyType compositeRuleName;
const core::ValueType *compositeValueData = nullptr;
SelectResultTask(BuildContext& context, ninja::Command* command,
unsigned inputIndex,
const core::KeyType& compositeRuleName)
: context(context), command(command),
inputIndex(inputIndex), compositeRuleName(compositeRuleName) { }
virtual void start(core::BuildEngine& engine) override {
// Request the composite input.
engine.taskNeedsInput(this, compositeRuleName, 0);
}
virtual void provideValue(core::BuildEngine& engine, uintptr_t inputID,
const core::ValueType& valueData) override {
compositeValueData = &valueData;
}
virtual void inputsAvailable(core::BuildEngine& engine) override {
// Construct the appropriate build value from the result.
assert(compositeValueData);
BuildValue value(BuildValue::fromValue(*compositeValueData));
// If the input was a failed or skipped command, propagate that result.
if (value.isFailedCommand() || value.isSkippedCommand()) {
engine.taskIsComplete(this, value.toValue(), /*ForceChange=*/true);
} else {
// FIXME: We don't try and set this in response to the restat flag on
// the incoming command, because it doesn't generally work -- the output
// will just honor update-if-newer and still not run. We need to move to
// a different model for handling restat = 0 to get this to work
// properly.
bool forceChange = false;
// Otherwise, the value should be a successful command with file info
// for each output.
assert(value.isSuccessfulCommand() && value.hasMultipleOutputs() &&
inputIndex < value.getNumOutputs());
// The result is the InputIndex-th element, and the command hash is
// propagated.
engine.taskIsComplete(
this, BuildValue::makeSuccessfulCommand(
value.getNthOutputInfo(inputIndex),
value.getCommandHash()).toValue(),
forceChange);
}
}
};
return context.engine.registerTask(
new SelectResultTask(context, command, inputIndex, compositeRuleName));
}
static bool buildInputIsResultValid(ninja::Node* node,
const core::ValueType& valueData) {
BuildValue value = BuildValue::fromValue(valueData);
// If the prior value wasn't for an existing input, recompute.
if (!value.isExistingInput())
return false;
// Otherwise, the result is valid if the path exists and the hash has not
// changed.
//
// FIXME: This is inefficient, we will end up doing the stat twice, once when
// we check the value for up to dateness, and once when we "build" the output.
//
// We can solve this by caching ourselves but I wonder if it is something the
// engine should support more naturally.
auto info = FileInfo::getInfoForPath(node->getPath());
if (info.isMissing())
return false;
return value.getOutputInfo() == info;
}
static bool buildCommandIsResultValid(ninja::Command* command,
const core::ValueType& valueData) {
BuildValue value = BuildValue::fromValue(valueData);
// If the prior value wasn't for a successful command, recompute.
if (!value.isSuccessfulCommand())
return false;
// For non-generator commands, if the command hash has changed, recompute.
if (!command->hasGeneratorFlag()) {
if (value.getCommandHash() != basic::hashString(
command->getCommandString()))
return false;
}
// Check the timestamps on each of the outputs.
for (unsigned i = 0, e = command->getOutputs().size(); i != e; ++i) {
// Always rebuild if the output is missing.
auto info = FileInfo::getInfoForPath(command->getOutputs()[i]->getPath());
if (info.isMissing())
return false;
// Otherwise, the result is valid if file information has not changed.
//
// Note that we may still decide not to actually run the command based on
// the update-if-newer handling, but it does require running the task.
if (value.getNthOutputInfo(i) != info)
return false;
}
return true;
}
static bool selectCompositeIsResultValid(ninja::Command* command,
const core::ValueType& valueData) {
BuildValue value = BuildValue::fromValue(valueData);
// If the prior value wasn't for a successful command, recompute.
if (!value.isSuccessfulCommand())
return false;
// If the command's signature has changed since it was built, rebuild. This is
// important for ensuring that we properly reevaluate the select rule when
// it's incoming composite rule no longer exists.
if (value.getCommandHash() != basic::hashString(command->getCommandString()))
return false;
// Otherwise, this result is always valid.
return true;
}
static void updateCommandStatus(BuildContext& context,
ninja::Command* command,
core::Rule::StatusKind status) {
// Ignore phony rules.
if (command->getRule() == context.manifest->getPhonyRule())
return;
// Track the number of commands which are currently being scanned along with
// the total number of completed commands.
if (status == core::Rule::StatusKind::IsScanning) {
++context.numCommandsScanning;
} else if (status == core::Rule::StatusKind::IsUpToDate) {
--context.numCommandsScanning;
++context.numCommandsUpToDate;
++context.numCommandsCompleted;
} else {
assert(status == core::Rule::StatusKind::IsComplete);
--context.numCommandsScanning;
++context.numCommandsCompleted;
}
}
core::Rule NinjaBuildEngineDelegate::lookupRule(const core::KeyType& key) {
// We created rules for all of the commands up front, so if we are asked for a
// rule here it is because we are looking for an input.
// Get the node for this input.
//
// FIXME: This is frequently a redundant lookup, given that the caller might
// well have had the Node* available. This is something that would be nice
// to avoid when we support generic key types.
ninja::Node* node = context->manifest->getOrCreateNode(key);
return core::Rule{
node->getPath(),
[&, node] (core::BuildEngine&) {
return buildInput(*context, node);
},
[&, node] (core::BuildEngine&, const core::Rule&,
const core::ValueType& value) {
// If simulating, assume cached results are valid.
if (context->simulate)
return true;
return buildInputIsResultValid(node, value);
} };
}
void NinjaBuildEngineDelegate::cycleDetected(
const std::vector<core::Rule*>& cycle) {
// Report the cycle.
std::stringstream message;
message << "cycle detected among targets:";
bool first = true;
for (const auto* rule: cycle) {
if (!first)
message << " ->";
message << " \"" << rule->key << '"';
first = false;
}
context->emitError(message.str());
// Cancel the build.
context->isCancelled = true;
}
}
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;
unsigned numFailedCommandsToTolerate = 1;
float maximumLoadAverage = 0.0;
std::vector<std::string> debugTools;
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.5 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 (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()) {
fprintf(stderr, "%s: warning: debug tools not implemented\n",
getProgramName());
}
// Honor the --chdir option, if used.
if (!chdirPath.empty()) {
if (::chdir(chdirPath.c_str()) < 0) {
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 = {
"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;
}
}
// 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;
context.numFailedCommandsToTolerate = numFailedCommandsToTolerate;
context.quiet = quiet;
context.simulate = simulate;
context.strict = strict;
context.verbose = verbose;
// Create the job queue to use.
//
// When running in parallel, we use a LIFO queue to work around the default
// traversal of the BuildEngine tending to build in BFS order. This is
// generally at avoiding clustering of links.
//
// FIXME: Do a serious analysis of scheduling, including ideally an active
// scheduler in the execution queue.
if (numJobsInParallel == 0) {
long numCPUs = sysconf(_SC_NPROCESSORS_ONLN);
if (numCPUs < 0) {
context.emitError("unable to detect number of CPUs (%s)",
strerror(errno));
return 1;
}
numJobsInParallel = numCPUs + 2;
}
bool useLIFO = (numJobsInParallel > 1);
context.jobQueue.reset(new BuildExecutionQueue(numJobsInParallel, useLIFO));
// Load the manifest.
BuildManifestActions actions(context);
ninja::ManifestLoader loader(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->getPath().c_str(),
command->getRule()->getName().c_str());
}
}
return 0;
}
// Otherwise, run the build.
// Parse the positional arguments.
std::vector<std::string> targetsToBuild(args);
// Attach the database, if requested.
if (!dbFilename.empty()) {
std::string error;
std::unique_ptr<core::BuildDB> db(
core::createSQLiteBuildDB(dbFilename,
BuildValue::currentSchemaVersion,
&error));
if (!db) {
context.emitError("unable to open build database: %s", error.c_str());
return 1;
}
context.engine.attachDB(std::move(db));
}
// 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;
}
}
// 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({
command->getOutputs()[0]->getPath(),
[=, &context](core::BuildEngine& engine) {
return buildCommand(context, command);
},
[=, &context](core::BuildEngine&, const core::Rule& rule,
const core::ValueType value) {
// If simulating, assume cached results are valid.
if (context.simulate)
return true;
return buildCommandIsResultValid(command, value);
},
[=, &context](core::BuildEngine&, core::Rule::StatusKind status) {
updateCommandStatus(context, command, status);
} });
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->getPath();
}
// Add the composite rule, which will run the command and build all
// outputs.
context.engine.addRule({
compositeRuleName,
[=, &context](core::BuildEngine& engine) {
return buildCommand(context, command);
},
[=, &context](core::BuildEngine&, const core::Rule& rule,
const core::ValueType value) {
// If simulating, assume cached results are valid.
if (context.simulate)
return true;
return buildCommandIsResultValid(command, value);
},
[=, &context](core::BuildEngine&, core::Rule::StatusKind status) {
updateCommandStatus(context, command, status);
} });
// 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({
command->getOutputs()[i]->getPath(),
[=, &context] (core::BuildEngine&) {
return selectCompositeBuildResult(context, command, i,
compositeRuleName);
},
[=, &context] (core::BuildEngine&, const core::Rule& rule,
const core::ValueType value) {
// If simulating, assume cached results are valid.
if (context.simulate)
return true;
return selectCompositeIsResultValid(command, value);
} });
}
}
// If this is the first iteration, build the manifest, unless disabled.
if (autoRegenerateManifest && iteration == 0) {
context.engine.build(manifestFilename);
// 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");
}
// If no explicit targets were named, build the default targets.
if (targetsToBuild.empty()) {
for (auto& target: context.manifest->getDefaultTargets())
targetsToBuild.push_back(target->getPath());
// 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->getPath());
}
}
}
}
}
// 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({
"<<build>>",
[&](core::BuildEngine&) {
return buildTargets(context, targetsToBuild);
},
[&](core::BuildEngine&, const core::Rule&, const core::ValueType&) {
// Always rebuild the dummy rule.
return false;
} });
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.
struct sigaction action{};
action.sa_handler = SIG_DFL;
sigaction(SIGINT, &action, 0);
kill(getpid(), SIGINT);
usleep(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;
}