| // Copyright 2011 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "build_log.h" |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #ifndef _WIN32 |
| #include <unistd.h> |
| #endif |
| |
| #include "build.h" |
| #include "graph.h" |
| #include "metrics.h" |
| #include "util.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. |
| // Once the number of redundant entries exceeds a threshold, we write |
| // out a new file and replace the existing one with it. |
| |
| namespace { |
| |
| const char kFileSignature[] = "# ninja log v%d\n"; |
| const int kCurrentVersion = 4; |
| |
| } // namespace |
| |
| BuildLog::BuildLog() |
| : log_file_(NULL), config_(NULL), needs_recompaction_(false) {} |
| |
| BuildLog::~BuildLog() { |
| Close(); |
| } |
| |
| bool BuildLog::OpenForWrite(const string& path, string* err) { |
| if (config_ && config_->dry_run) |
| return true; // Do nothing, report success. |
| |
| if (needs_recompaction_) { |
| Close(); |
| if (!Recompact(path, err)) |
| return false; |
| } |
| |
| log_file_ = fopen(path.c_str(), "ab"); |
| if (!log_file_) { |
| *err = strerror(errno); |
| return false; |
| } |
| setvbuf(log_file_, NULL, _IOLBF, BUFSIZ); |
| SetCloseOnExec(fileno(log_file_)); |
| |
| // Opening a file in append mode doesn't set the file pointer to the file's |
| // end on Windows. Do that explicitly. |
| fseek(log_file_, 0, SEEK_END); |
| |
| if (ftell(log_file_) == 0) { |
| if (fprintf(log_file_, kFileSignature, kCurrentVersion) < 0) { |
| *err = strerror(errno); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void BuildLog::RecordCommand(Edge* edge, int start_time, int end_time, |
| TimeStamp restat_mtime) { |
| string command = edge->EvaluateCommand(true); |
| for (vector<Node*>::iterator out = edge->outputs_.begin(); |
| out != edge->outputs_.end(); ++out) { |
| const string& path = (*out)->path(); |
| Log::iterator i = log_.find(path); |
| LogEntry* log_entry; |
| if (i != log_.end()) { |
| log_entry = i->second; |
| } else { |
| log_entry = new LogEntry; |
| log_entry->output = path; |
| log_.insert(Log::value_type(log_entry->output, log_entry)); |
| } |
| log_entry->command = command; |
| log_entry->start_time = start_time; |
| log_entry->end_time = end_time; |
| log_entry->restat_mtime = restat_mtime; |
| |
| if (log_file_) |
| WriteEntry(log_file_, *log_entry); |
| } |
| } |
| |
| void BuildLog::Close() { |
| if (log_file_) |
| fclose(log_file_); |
| log_file_ = NULL; |
| } |
| |
| bool BuildLog::Load(const string& path, string* err) { |
| METRIC_RECORD(".ninja_log load"); |
| FILE* file = fopen(path.c_str(), "r"); |
| if (!file) { |
| if (errno == ENOENT) |
| return true; |
| *err = strerror(errno); |
| return false; |
| } |
| |
| int log_version = 0; |
| int unique_entry_count = 0; |
| int total_entry_count = 0; |
| |
| char buf[256 << 10]; |
| while (fgets(buf, sizeof(buf), file)) { |
| if (!log_version) { |
| log_version = 1; // Assume by default. |
| if (sscanf(buf, kFileSignature, &log_version) > 0) |
| continue; |
| } |
| |
| char field_separator = log_version >= 4 ? '\t' : ' '; |
| |
| char* start = buf; |
| char* end = strchr(start, field_separator); |
| if (!end) |
| continue; |
| *end = 0; |
| |
| int start_time = 0, end_time = 0; |
| TimeStamp restat_mtime = 0; |
| |
| start_time = atoi(start); |
| start = end + 1; |
| |
| end = strchr(start, field_separator); |
| if (!end) |
| continue; |
| *end = 0; |
| end_time = atoi(start); |
| start = end + 1; |
| |
| end = strchr(start, field_separator); |
| if (!end) |
| continue; |
| *end = 0; |
| restat_mtime = atol(start); |
| start = end + 1; |
| |
| end = strchr(start, field_separator); |
| if (!end) |
| continue; |
| string output = string(start, end - start); |
| |
| start = end + 1; |
| end = strchr(start, '\n'); |
| if (!end) |
| continue; |
| |
| LogEntry* entry; |
| Log::iterator i = log_.find(output); |
| if (i != log_.end()) { |
| entry = i->second; |
| } else { |
| entry = new LogEntry; |
| entry->output = output; |
| log_.insert(Log::value_type(entry->output, entry)); |
| ++unique_entry_count; |
| } |
| ++total_entry_count; |
| |
| entry->start_time = start_time; |
| entry->end_time = end_time; |
| entry->restat_mtime = restat_mtime; |
| entry->command = string(start, end - start); |
| } |
| |
| // Decide whether it's time to rebuild the log: |
| // - if we're upgrading versions |
| // - if it's getting large |
| int kMinCompactionEntryCount = 100; |
| int kCompactionRatio = 3; |
| if (log_version < kCurrentVersion) { |
| needs_recompaction_ = true; |
| } else if (total_entry_count > kMinCompactionEntryCount && |
| total_entry_count > unique_entry_count * kCompactionRatio) { |
| needs_recompaction_ = true; |
| } |
| |
| fclose(file); |
| |
| return true; |
| } |
| |
| BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) { |
| Log::iterator i = log_.find(path); |
| if (i != log_.end()) |
| return i->second; |
| return NULL; |
| } |
| |
| void BuildLog::WriteEntry(FILE* f, const LogEntry& entry) { |
| fprintf(f, "%d\t%d\t%ld\t%s\t%s\n", |
| entry.start_time, entry.end_time, (long) entry.restat_mtime, |
| entry.output.c_str(), entry.command.c_str()); |
| } |
| |
| bool BuildLog::Recompact(const string& path, string* err) { |
| printf("Recompacting log...\n"); |
| |
| string temp_path = path + ".recompact"; |
| FILE* f = fopen(temp_path.c_str(), "wb"); |
| if (!f) { |
| *err = strerror(errno); |
| return false; |
| } |
| |
| if (fprintf(f, kFileSignature, kCurrentVersion) < 0) { |
| *err = strerror(errno); |
| fclose(f); |
| return false; |
| } |
| |
| for (Log::iterator i = log_.begin(); i != log_.end(); ++i) { |
| WriteEntry(f, *i->second); |
| } |
| |
| fclose(f); |
| if (unlink(path.c_str()) < 0) { |
| *err = strerror(errno); |
| return false; |
| } |
| |
| if (rename(temp_path.c_str(), path.c_str()) < 0) { |
| *err = strerror(errno); |
| return false; |
| } |
| |
| return true; |
| } |