Skip to content

Commit

Permalink
Add frontend option for build status output
Browse files Browse the repository at this point in the history
Add a --frontend option that takes a command that will handle printing
build status.  The command will be executed with the read of a pipe on
fd 3, and each build event will cause a serialized message to be sent
on the pipe.
  • Loading branch information
colincross authored and nilac8991 committed Jan 10, 2017
1 parent 83eb3ff commit 6445c13
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 22 deletions.
3 changes: 1 addition & 2 deletions src/build.cc
Original file line number Diff line number Diff line change
Expand Up @@ -593,8 +593,7 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
end_time_millis = GetTimeMillis() - start_time_millis_;
running_edges_.erase(i);

status_->BuildEdgeFinished(edge, end_time_millis, result->success(),
result->output);
status_->BuildEdgeFinished(edge, end_time_millis, result);

// The rest of this function only applies to successful commands.
if (!result->success()) {
Expand Down
7 changes: 4 additions & 3 deletions src/build.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "graph.h" // XXX needed for DependencyScan; should rearrange.
#include "exit_status.h"
#include "serialize.h"
#include "util.h" // int64_t

struct BuildLog;
Expand Down Expand Up @@ -122,7 +123,7 @@ struct CommandRunner {
struct BuildConfig {
BuildConfig() : verbosity(NORMAL), dry_run(false), parallelism(1),
failures_allowed(1), max_load_average(-0.0f),
start_time_millis_(GetTimeMillis()) {}
frontend(NULL) {}

enum Verbosity {
NORMAL,
Expand All @@ -137,8 +138,8 @@ struct BuildConfig {
/// means that we do not have any limit.
double max_load_average;

// Time the build started.
int64_t start_time_millis_;
/// Command to execute to handle build output
const char* frontend;
};

/// Builder wraps the build process: starting commands, updating status.
Expand Down
35 changes: 30 additions & 5 deletions src/ninja.cc
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,12 @@ void Usage(const BuildConfig& config) {
" -d MODE enable debugging (use -d list to list modes)\n"
" -t TOOL run a subtool (use -t list to list subtools)\n"
" terminates toplevel options; further flags are passed to the tool\n"
" -w FLAG adjust warnings (use -w list to list warnings)\n",
kNinjaVersion, config.parallelism);
" -w FLAG adjust warnings (use -w list to list warnings)\n"
#ifndef _WIN32
"\n"
" --frontend COMMAND execute COMMAND and pass serialized build output to it\n"
#endif
, kNinjaVersion, config.parallelism);
}

/// Choose a default value for the -j (parallelism) flag.
Expand Down Expand Up @@ -1025,16 +1029,22 @@ int ReadFlags(int* argc, char*** argv,
Options* options, BuildConfig* config) {
config->parallelism = GuessParallelism();

enum { OPT_VERSION = 1 };
enum {
OPT_VERSION = 1,
OPT_FRONTEND = 2,
};
const option kLongOptions[] = {
#ifndef _WIN32
{ "frontend", required_argument, NULL, OPT_FRONTEND },
#endif
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, OPT_VERSION },
{ NULL, 0, NULL, 0 }
};

int opt;
while (!options->tool &&
(opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vw:C:h", kLongOptions,
(opt = getopt_long(*argc, *argv, "d:f:j:k:l:mnt:vw:C:h", kLongOptions,
NULL)) != -1) {
switch (opt) {
case 'd':
Expand Down Expand Up @@ -1093,6 +1103,9 @@ int ReadFlags(int* argc, char*** argv,
case OPT_VERSION:
printf("%s\n", kNinjaVersion);
return 0;
case OPT_FRONTEND:
config->frontend = optarg;
break;
case 'h':
default:
Usage(*config);
Expand Down Expand Up @@ -1138,7 +1151,7 @@ int real_main(int argc, char** argv) {
return (ninja.*options.tool->func)(&options, argc, argv);
}

Status* status = new StatusPrinter(config);
Status* status = NULL;

// Limit number of rebuilds, to prevent infinite loops.
const int kCycleLimit = 100;
Expand Down Expand Up @@ -1167,6 +1180,15 @@ int real_main(int argc, char** argv) {
if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS)
return (ninja.*options.tool->func)(&options, argc, argv);

if (status == NULL) {
#ifndef _WIN32
if (config.frontend != NULL)
status = new StatusSerializer(config);
else
#endif
status = new StatusPrinter(config);
}

// Attempt to rebuild the manifest before building anything else
if (ninja.RebuildManifest(options.input_file, &err, status)) {
// In dry_run mode the regeneration will succeed without changing the
Expand All @@ -1183,11 +1205,14 @@ int real_main(int argc, char** argv) {
int result = ninja.RunBuild(argc, argv, status);
if (g_metrics)
ninja.DumpMetrics();

delete status;
return result;
}

status->Error("manifest '%s' still dirty after %d tries",
options.input_file, kCycleLimit);
delete status;
return 1;
}

Expand Down
133 changes: 128 additions & 5 deletions src/status.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@

#include "status.h"

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

StatusPrinter::StatusPrinter(const BuildConfig& config)
: config_(config),
Expand Down Expand Up @@ -49,7 +53,7 @@ void StatusPrinter::BuildEdgeStarted(Edge* edge, int64_t start_time_millis) {
}

void StatusPrinter::BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
bool success, const string& output) {
const CommandRunner::Result* result) {
time_millis_ = end_time_millis;
++finished_edges_;

Expand All @@ -65,7 +69,7 @@ void StatusPrinter::BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
--running_edges_;

// Print the command that is spewing before printing its output.
if (!success) {
if (!result->success()) {
string outputs;
for (vector<Node*>::const_iterator o = edge->outputs_.begin();
o != edge->outputs_.end(); ++o)
Expand All @@ -75,7 +79,7 @@ void StatusPrinter::BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
printer_.PrintOnNewLine(edge->EvaluateCommand() + "\n");
}

if (!output.empty()) {
if (!result->output.empty()) {
// ninja sets stdout and stderr of subprocesses to a pipe, to be able to
// check if the output is empty. Some compilers, e.g. clang, check
// isatty(stderr) to decide if they should print colored output.
Expand All @@ -90,9 +94,9 @@ void StatusPrinter::BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
// TODO: There should be a flag to disable escape code stripping.
string final_output;
if (!printer_.is_smart_terminal())
final_output = StripAnsiEscapeCodes(output);
final_output = StripAnsiEscapeCodes(result->output);
else
final_output = output;
final_output = result->output;
printer_.PrintOnNewLine(final_output);
}
}
Expand Down Expand Up @@ -227,3 +231,122 @@ void StatusPrinter::Info(const char* msg, ...) {
::Info(msg, ap);
va_end(ap);
}

#ifndef _WIN32

StatusSerializer::StatusSerializer(const BuildConfig& config) :
config_(config), serializer_(NULL), subprocess_(NULL) {
int output_pipe[2];
if (pipe(output_pipe) < 0)
Fatal("pipe: %s", strerror(errno));
SetCloseOnExec(output_pipe[1]);

serializer_ = new Serializer(output_pipe[1]);

subprocess_ = subprocess_set_.Add(config.frontend, /*use_console=*/true,
output_pipe[0]);
close(output_pipe[0]);

serializer_->Uint(kHeader);
}

StatusSerializer::~StatusSerializer() {
serializer_->Flush();
delete serializer_;
subprocess_->Finish();
subprocess_set_.Clear();
}

void StatusSerializer::PlanHasTotalEdges(int total) {
serializer_->Array(2);
serializer_->Uint(kTotalEdges);
serializer_->Uint(total);
serializer_->Flush();
}

void StatusSerializer::BuildEdgeStarted(Edge* edge, int64_t start_time_millis) {
serializer_->Array(8);
serializer_->Uint(kEdgeStarted);
serializer_->Uint(edge->id_);
serializer_->Uint(start_time_millis);
serializer_->Array(edge->inputs_.size());
for (vector<Node*>::iterator it = edge->inputs_.begin(); it != edge->inputs_.end(); ++it) {
serializer_->String((*it)->path());
}
serializer_->Array(edge->outputs_.size());
for (vector<Node*>::iterator it = edge->outputs_.begin(); it != edge->outputs_.end(); ++it) {
serializer_->String((*it)->path());
}
serializer_->String(edge->GetBinding("description"));
serializer_->String(edge->GetBinding("command"));
serializer_->Bool(edge->use_console());
serializer_->Flush();
}

void StatusSerializer::BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
const CommandRunner::Result* result) {
serializer_->Array(5);
serializer_->Uint(kEdgeFinished);
serializer_->Uint(edge->id_);
serializer_->Uint(end_time_millis);
serializer_->Int(result->status);
serializer_->String(result->output);
serializer_->Flush();
}

void StatusSerializer::BuildStarted() {
serializer_->Array(3);
serializer_->Uint(kBuildStarted);
serializer_->Uint(config_.parallelism);
serializer_->Bool(config_.verbosity == BuildConfig::VERBOSE);
}

void StatusSerializer::BuildFinished() {
serializer_->Array(1);
serializer_->Uint(kBuildFinished);
}

void StatusSerializer::Message(messageType type, const char* msg,
va_list ap) {
va_list ap2;
va_copy(ap2, ap);

int len = vsnprintf(NULL, 0, msg, ap2);
if (len < 0) {
Fatal("vsnprintf failed");
}

va_end(ap2);

char* buf = new char[len + 1];
buf[0] = 0;
vsnprintf(buf, len + 1, msg, ap);
buf[len] = 0;

serializer_->Array(2);
serializer_->Uint(type);
serializer_->String(buf);
serializer_->Flush();
}

void StatusSerializer::Info(const char* msg, ...) {
va_list ap;
va_start(ap, msg);
Message(kNinjaInfo, msg, ap);
va_end(ap);
}

void StatusSerializer::Warning(const char* msg, ...) {
va_list ap;
va_start(ap, msg);
Message(kNinjaWarning, msg, ap);
va_end(ap);
}

void StatusSerializer::Error(const char* msg, ...) {
va_list ap;
va_start(ap, msg);
Message(kNinjaError, msg, ap);
va_end(ap);
}
#endif // !_WIN32
49 changes: 47 additions & 2 deletions src/status.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,23 @@
#ifndef NINJA_STATUS_H_
#define NINJA_STATUS_H_

#include <stdarg.h>

#include <map>
#include <string>
using namespace std;

#include "build.h"
#include "line_printer.h"
#include "subprocess.h"

/// Abstract interface to object that tracks the status of a build:
/// completion fraction, printing updates.
struct Status {
virtual void PlanHasTotalEdges(int total) = 0;
virtual void BuildEdgeStarted(Edge* edge, int64_t start_time_millis) = 0;
virtual void BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
bool success, const string& output) = 0;
const CommandRunner::Result* result) = 0;
virtual void BuildStarted() = 0;
virtual void BuildFinished() = 0;

Expand All @@ -46,7 +49,7 @@ struct StatusPrinter : Status {
virtual void PlanHasTotalEdges(int total);
virtual void BuildEdgeStarted(Edge* edge, int64_t start_time_millis);
virtual void BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
bool success, const string& output);
const CommandRunner::Result* result);
virtual void BuildStarted();
virtual void BuildFinished();

Expand Down Expand Up @@ -113,4 +116,46 @@ struct StatusPrinter : Status {
mutable SlidingRateInfo current_rate_;
};

#ifndef _WIN32

/// Implementation of the Status interface that serializes the status as
/// MessagePack objects to stdout
struct StatusSerializer : Status {
explicit StatusSerializer(const BuildConfig& config);
virtual ~StatusSerializer();

virtual void PlanHasTotalEdges(int total);
virtual void BuildEdgeStarted(Edge* edge, int64_t start_time);
virtual void BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
const CommandRunner::Result* result);
virtual void BuildStarted();
virtual void BuildFinished();

virtual void Info(const char* msg, ...);
virtual void Warning(const char* msg, ...);
virtual void Error(const char* msg, ...);

enum messageType {
kHeader = 0x4e4a5331, // NJS1
kTotalEdges = 0,
kBuildStarted = 1,
kBuildFinished = 2,
kEdgeStarted = 3,
kEdgeFinished = 4,
kNinjaInfo = 5,
kNinjaWarning = 6,
kNinjaError = 7,
};

const BuildConfig& config_;

Serializer* serializer_;
SubprocessSet subprocess_set_;
Subprocess* subprocess_;
private:
void Message(messageType type, const char* msg, va_list ap);
};

#endif // !_WIN32

#endif // NINJA_STATUS_H_
Loading

0 comments on commit 6445c13

Please sign in to comment.