diff options
author | Evan Martin <martine@danga.com> | 2010-12-16 19:52:24 (GMT) |
---|---|---|
committer | Evan Martin <martine@danga.com> | 2010-12-23 19:20:33 (GMT) |
commit | 9a6fd1a97b64ea1245a7343aee1e584c401a391b (patch) | |
tree | 73e83a65f9c157ffdce98d45969cfa7c4f18757e /src | |
parent | 12e36285ae52feca764e8fb2a417a87b913fe3e6 (diff) | |
download | Ninja-9a6fd1a97b64ea1245a7343aee1e584c401a391b.zip Ninja-9a6fd1a97b64ea1245a7343aee1e584c401a391b.tar.gz Ninja-9a6fd1a97b64ea1245a7343aee1e584c401a391b.tar.bz2 |
add a class for logging builds (commands + timing)
Diffstat (limited to 'src')
-rw-r--r-- | src/build_log.cc | 89 | ||||
-rw-r--r-- | src/build_log.h | 37 | ||||
-rw-r--r-- | src/build_log_test.cc | 43 |
3 files changed, 169 insertions, 0 deletions
diff --git a/src/build_log.cc b/src/build_log.cc new file mode 100644 index 0000000..06db0e6 --- /dev/null +++ b/src/build_log.cc @@ -0,0 +1,89 @@ +#include "build_log.h" + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "ninja.h" + +// Implementation details: +// Each run's log appends to the log file. +// To load, we run through all log entries in series, throwing away +// older runs. +// XXX figure out recompaction strategy + +bool BuildLog::OpenForWrite(const string& path, string* err) { + log_file_ = fopen(path.c_str(), "ab"); + if (!log_file_) { + *err = strerror(errno); + return false; + } + setlinebuf(log_file_); + return true; +} + +void BuildLog::RecordCommand(Edge* edge, int time_ms) { + const string command = edge->EvaluateCommand(); + for (vector<Node*>::iterator out = edge->outputs_.begin(); + out != edge->outputs_.end(); ++out) { + const string& path = (*out)->file_->path_; + Log::iterator i = log_.find(path); + LogEntry* log_entry; + if (i != log_.end()) { + log_entry = i->second; + } else { + log_entry = new LogEntry; + log_.insert(make_pair(path, log_entry)); + } + log_entry->output = path; + log_entry->command = command; + log_entry->time_ms = time_ms; + + fprintf(log_file_, "%d %s %s\n", time_ms, path.c_str(), command.c_str()); + } +} + +void BuildLog::Close() { + fclose(log_file_); + log_file_ = NULL; +} + +// Load the on-disk log. +bool BuildLog::Load(const string& path, string* err) { + FILE* file = fopen(path.c_str(), "r"); + if (!file) { + *err = strerror(errno); + return false; + } + + char buf[4 << 10]; + while (fgets(buf, sizeof(buf), file)) { + char* start = buf; + char* end = strchr(start, ' '); + if (!end) + continue; + + LogEntry* entry = new LogEntry; + *end = 0; + entry->time_ms = atoi(start); + + start = end + 1; + end = strchr(start, ' '); + entry->output = string(start, end - start); + + start = end + 1; + end = strchr(start, '\n'); + entry->command = string(start, end - start); + log_.insert(make_pair(entry->output, entry)); + } + + return true; +} + +// Lookup a previously-run command by its output path. +BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) { + Log::iterator i = log_.find(path); + if (i != log_.end()) + return i->second; + return NULL; +} diff --git a/src/build_log.h b/src/build_log.h new file mode 100644 index 0000000..1d9463c --- /dev/null +++ b/src/build_log.h @@ -0,0 +1,37 @@ +#include <map> +#include <string> +using namespace std; + +struct Edge; + +// Store a log of every command ran for every build. +// It has a few uses: +// 1) historical command lines for output files, so we know +// when we need to rebuild due to the command changing +// 2) historical timing information +// 3) maybe we can generate some sort of build overview output +// from it +struct BuildLog { + bool OpenForWrite(const string& path, string* err); + void RecordCommand(Edge* edge, int time_ms); + void Close(); + + // Load the on-disk log. + bool Load(const string& path, string* err); + + struct LogEntry { + string output; + string command; + int time_ms; + bool operator==(const LogEntry& o) { + return output == o.output && command == o.command && time_ms == o.time_ms; + } + }; + + // Lookup a previously-run command by its output path. + LogEntry* LookupByOutput(const string& path); + + typedef map<string, LogEntry*> Log; + Log log_; + FILE* log_file_; +}; diff --git a/src/build_log_test.cc b/src/build_log_test.cc new file mode 100644 index 0000000..6807a04 --- /dev/null +++ b/src/build_log_test.cc @@ -0,0 +1,43 @@ +#include "build_log.h" + +#include "test.h" + +struct BuildLogTest : public StateTestWithBuiltinRules { + virtual void SetUp() { + char mktemp_template[] = "BuildLogTest-XXXXXX"; + log_filename_ = mktemp(mktemp_template); + } + virtual void TearDown() { + unlink(log_filename_.c_str()); + } + + string log_filename_; +}; + +TEST_F(BuildLogTest, WriteRead) { + AssertParse(&state_, +"build out: cat mid\n" +"build mid: cat in\n"); + + BuildLog log1; + string err; + EXPECT_TRUE(log1.OpenForWrite(log_filename_, &err)); + ASSERT_EQ("", err); + log1.RecordCommand(state_.edges_[0], 15); + log1.RecordCommand(state_.edges_[1], 20); + log1.Close(); + + BuildLog log2; + EXPECT_TRUE(log2.Load(log_filename_, &err)); + ASSERT_EQ("", err); + + ASSERT_EQ(2, log1.log_.size()); + ASSERT_EQ(2, log2.log_.size()); + BuildLog::LogEntry* e1 = log1.LookupByOutput("out"); + ASSERT_TRUE(e1); + BuildLog::LogEntry* e2 = log2.LookupByOutput("out"); + ASSERT_TRUE(e2); + ASSERT_TRUE(*e1 == *e2); + ASSERT_EQ(15, e1->time_ms); + ASSERT_EQ("out", e1->output); +} |