ninja_build/build_log.rs
1// Copyright 2011 Google Inc. All Rights Reserved.
2// Copyright 2017 The Ninja-rs Project Developers. All Rights Reserved.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use std::collections::HashMap;
17use std::fs::File;
18use std::io::Write;
19use std::path::Path;
20
21use super::timestamp::TimeStamp;
22use super::graph::{Edge, EdgeIndex};
23use super::state::State;
24
25
26/// Can answer questions about the manifest for the BuildLog.
27pub trait BuildLogUser {
28 /// Return if a given output is no longer part of the build manifest.
29 /// This is only called during recompaction and doesn't have to be fast.
30 fn is_path_dead(&self, s: &str) -> bool;
31}
32
33
34/// Store a log of every command ran for every build.
35/// It has a few uses:
36///
37/// 1) (hashes of) command lines for existing output files, so we know
38/// when we need to rebuild due to the command changing
39/// 2) timing information, perhaps for generating reports
40/// 3) restat information
41pub struct BuildLog<'a> {
42 entries: BuildLogEntries<'a>,
43 log_file: Option<File>,
44 needs_recompaction: bool,
45}
46
47impl<'a> BuildLog<'a> {
48 pub fn new() -> Self {
49 BuildLog {
50 entries: BuildLogEntries::new(),
51 log_file: None,
52 needs_recompaction: false,
53 }
54 }
55
56 pub fn open_for_write<User: BuildLogUser>(
57 &self,
58 path: &Path,
59 user: &User,
60 ) -> Result<(), String> {
61 return Ok(());
62 unimplemented!()
63 }
64
65 pub fn record_command_without_timestamp(
66 &self,
67 state: &State,
68 edge_idx: EdgeIndex,
69 start_time: u64,
70 end_time: u64,
71 ) -> Result<(), ()> {
72 self.record_command(state, edge_idx, start_time, end_time, TimeStamp::default())
73 }
74
75 pub fn record_command(
76 &self,
77 state: &State,
78 edge_idx: EdgeIndex,
79 start_time: u64,
80 end_time: u64,
81 mtime: TimeStamp,
82 ) -> Result<(), ()> {
83 return Ok(());
84 unimplemented!()
85 }
86
87 pub fn close(&mut self) {
88 self.log_file = None;
89 }
90
91 /// Load the on-disk log.
92 pub fn load(&mut self, path: &Path) -> Result<Option<String>, String> {
93 return Ok(None);
94 unimplemented!{}
95 }
96
97 /// Lookup a previously-run command by its output path.
98 pub fn lookup_by_output(&self, path: &[u8]) -> Option<&BuildLogEntry> {
99 return None;
100 unimplemented!{}
101 }
102
103 /// Serialize an entry into a log file.
104 pub fn write_entry<W: Write>(&self, f: &W, entry: &BuildLogEntry) -> Result<(), ()> {
105 unimplemented!{}
106 }
107
108 /// Rewrite the known log entries, throwing away old data.
109 pub fn recompact<User: BuildLogUser>(&self, path: &Path, user: &User) -> Result<(), String> {
110 unimplemented!()
111 }
112
113 pub fn entries(&'a self) -> &'a BuildLogEntries<'a> {
114 &self.entries
115 }
116}
117
118#[derive(PartialEq)]
119pub struct BuildLogEntry {
120 pub output: String,
121 pub command_hash: u64,
122 pub start_time: isize,
123 pub end_time: isize,
124 pub mtime: TimeStamp,
125}
126
127impl BuildLogEntry {
128 pub fn new_with_output(output: &str) -> Self {
129 unimplemented!()
130 }
131
132 pub fn new(
133 output: &str,
134 command_hash: u64,
135 start_time: isize,
136 end_time: isize,
137 restat_mtime: TimeStamp,
138 ) -> Self {
139 unimplemented!()
140 }
141
142 pub fn hash_command(command: &str) -> u64 {
143 unimplemented!()
144 }
145}
146
147type BuildLogEntries<'a> = HashMap<&'a str, &'a BuildLogEntry>;
148
149
150/*
151
152// On AIX, inttypes.h gets indirectly included by build_log.h.
153// It's easiest just to ask for the printf format macros right away.
154#ifndef _WIN32
155#ifndef __STDC_FORMAT_MACROS
156#define __STDC_FORMAT_MACROS
157#endif
158#endif
159
160#include "build_log.h"
161
162#include <errno.h>
163#include <stdlib.h>
164#include <string.h>
165
166#ifndef _WIN32
167#include <inttypes.h>
168#include <unistd.h>
169#endif
170
171#include "build.h"
172#include "graph.h"
173#include "metrics.h"
174#include "util.h"
175
176// Implementation details:
177// Each run's log appends to the log file.
178// To load, we run through all log entries in series, throwing away
179// older runs.
180// Once the number of redundant entries exceeds a threshold, we write
181// out a new file and replace the existing one with it.
182
183namespace {
184
185const char kFileSignature[] = "# ninja log v%d\n";
186const int kOldestSupportedVersion = 4;
187const int kCurrentVersion = 5;
188
189// 64bit MurmurHash2, by Austin Appleby
190#if defined(_MSC_VER)
191#define BIG_CONSTANT(x) (x)
192#else // defined(_MSC_VER)
193#define BIG_CONSTANT(x) (x##LLU)
194#endif // !defined(_MSC_VER)
195inline
196uint64_t MurmurHash64A(const void* key, size_t len) {
197 static const uint64_t seed = 0xDECAFBADDECAFBADull;
198 const uint64_t m = BIG_CONSTANT(0xc6a4a7935bd1e995);
199 const int r = 47;
200 uint64_t h = seed ^ (len * m);
201 const unsigned char* data = (const unsigned char*)key;
202 while (len >= 8) {
203 uint64_t k;
204 memcpy(&k, data, sizeof k);
205 k *= m;
206 k ^= k >> r;
207 k *= m;
208 h ^= k;
209 h *= m;
210 data += 8;
211 len -= 8;
212 }
213 switch (len & 7)
214 {
215 case 7: h ^= uint64_t(data[6]) << 48;
216 case 6: h ^= uint64_t(data[5]) << 40;
217 case 5: h ^= uint64_t(data[4]) << 32;
218 case 4: h ^= uint64_t(data[3]) << 24;
219 case 3: h ^= uint64_t(data[2]) << 16;
220 case 2: h ^= uint64_t(data[1]) << 8;
221 case 1: h ^= uint64_t(data[0]);
222 h *= m;
223 };
224 h ^= h >> r;
225 h *= m;
226 h ^= h >> r;
227 return h;
228}
229#undef BIG_CONSTANT
230
231
232} // namespace
233
234// static
235uint64_t BuildLog::LogEntry::HashCommand(StringPiece command) {
236 return MurmurHash64A(command.str_, command.len_);
237}
238
239BuildLog::LogEntry::LogEntry(const string& output)
240 : output(output) {}
241
242BuildLog::LogEntry::LogEntry(const string& output, uint64_t command_hash,
243 int start_time, int end_time, TimeStamp restat_mtime)
244 : output(output), command_hash(command_hash),
245 start_time(start_time), end_time(end_time), mtime(restat_mtime)
246{}
247
248bool BuildLog::OpenForWrite(const string& path, const BuildLogUser& user,
249 string* err) {
250 if (needs_recompaction_) {
251 if (!Recompact(path, user, err))
252 return false;
253 }
254
255 log_file_ = fopen(path.c_str(), "ab");
256 if (!log_file_) {
257 *err = strerror(errno);
258 return false;
259 }
260 setvbuf(log_file_, NULL, _IOLBF, BUFSIZ);
261 SetCloseOnExec(fileno(log_file_));
262
263 // Opening a file in append mode doesn't set the file pointer to the file's
264 // end on Windows. Do that explicitly.
265 fseek(log_file_, 0, SEEK_END);
266
267 if (ftell(log_file_) == 0) {
268 if (fprintf(log_file_, kFileSignature, kCurrentVersion) < 0) {
269 *err = strerror(errno);
270 return false;
271 }
272 }
273
274 return true;
275}
276
277bool BuildLog::RecordCommand(Edge* edge, int start_time, int end_time,
278 TimeStamp mtime) {
279 string command = edge->EvaluateCommand(true);
280 uint64_t command_hash = LogEntry::HashCommand(command);
281 for (vector<Node*>::iterator out = edge->outputs_.begin();
282 out != edge->outputs_.end(); ++out) {
283 const string& path = (*out)->path();
284 Entries::iterator i = entries_.find(path);
285 LogEntry* log_entry;
286 if (i != entries_.end()) {
287 log_entry = i->second;
288 } else {
289 log_entry = new LogEntry(path);
290 entries_.insert(Entries::value_type(log_entry->output, log_entry));
291 }
292 log_entry->command_hash = command_hash;
293 log_entry->start_time = start_time;
294 log_entry->end_time = end_time;
295 log_entry->mtime = mtime;
296
297 if (log_file_) {
298 if (!WriteEntry(log_file_, *log_entry))
299 return false;
300 }
301 }
302 return true;
303}
304
305
306struct LineReader {
307 explicit LineReader(FILE* file)
308 : file_(file), buf_end_(buf_), line_start_(buf_), line_end_(NULL) {
309 memset(buf_, 0, sizeof(buf_));
310 }
311
312 // Reads a \n-terminated line from the file passed to the constructor.
313 // On return, *line_start points to the beginning of the next line, and
314 // *line_end points to the \n at the end of the line. If no newline is seen
315 // in a fixed buffer size, *line_end is set to NULL. Returns false on EOF.
316 bool ReadLine(char** line_start, char** line_end) {
317 if (line_start_ >= buf_end_ || !line_end_) {
318 // Buffer empty, refill.
319 size_t size_read = fread(buf_, 1, sizeof(buf_), file_);
320 if (!size_read)
321 return false;
322 line_start_ = buf_;
323 buf_end_ = buf_ + size_read;
324 } else {
325 // Advance to next line in buffer.
326 line_start_ = line_end_ + 1;
327 }
328
329 line_end_ = (char*)memchr(line_start_, '\n', buf_end_ - line_start_);
330 if (!line_end_) {
331 // No newline. Move rest of data to start of buffer, fill rest.
332 size_t already_consumed = line_start_ - buf_;
333 size_t size_rest = (buf_end_ - buf_) - already_consumed;
334 memmove(buf_, line_start_, size_rest);
335
336 size_t read = fread(buf_ + size_rest, 1, sizeof(buf_) - size_rest, file_);
337 buf_end_ = buf_ + size_rest + read;
338 line_start_ = buf_;
339 line_end_ = (char*)memchr(line_start_, '\n', buf_end_ - line_start_);
340 }
341
342 *line_start = line_start_;
343 *line_end = line_end_;
344 return true;
345 }
346
347 private:
348 FILE* file_;
349 char buf_[256 << 10];
350 char* buf_end_; // Points one past the last valid byte in |buf_|.
351
352 char* line_start_;
353 // Points at the next \n in buf_ after line_start, or NULL.
354 char* line_end_;
355};
356
357bool BuildLog::Load(const string& path, string* err) {
358 METRIC_RECORD(".ninja_log load");
359 FILE* file = fopen(path.c_str(), "r");
360 if (!file) {
361 if (errno == ENOENT)
362 return true;
363 *err = strerror(errno);
364 return false;
365 }
366
367 int log_version = 0;
368 int unique_entry_count = 0;
369 int total_entry_count = 0;
370
371 LineReader reader(file);
372 char* line_start = 0;
373 char* line_end = 0;
374 while (reader.ReadLine(&line_start, &line_end)) {
375 if (!log_version) {
376 sscanf(line_start, kFileSignature, &log_version);
377
378 if (log_version < kOldestSupportedVersion) {
379 *err = ("build log version invalid, perhaps due to being too old; "
380 "starting over");
381 fclose(file);
382 unlink(path.c_str());
383 // Don't report this as a failure. An empty build log will cause
384 // us to rebuild the outputs anyway.
385 return true;
386 }
387 }
388
389 // If no newline was found in this chunk, read the next.
390 if (!line_end)
391 continue;
392
393 const char kFieldSeparator = '\t';
394
395 char* start = line_start;
396 char* end = (char*)memchr(start, kFieldSeparator, line_end - start);
397 if (!end)
398 continue;
399 *end = 0;
400
401 int start_time = 0, end_time = 0;
402 TimeStamp restat_mtime = 0;
403
404 start_time = atoi(start);
405 start = end + 1;
406
407 end = (char*)memchr(start, kFieldSeparator, line_end - start);
408 if (!end)
409 continue;
410 *end = 0;
411 end_time = atoi(start);
412 start = end + 1;
413
414 end = (char*)memchr(start, kFieldSeparator, line_end - start);
415 if (!end)
416 continue;
417 *end = 0;
418 restat_mtime = atol(start);
419 start = end + 1;
420
421 end = (char*)memchr(start, kFieldSeparator, line_end - start);
422 if (!end)
423 continue;
424 string output = string(start, end - start);
425
426 start = end + 1;
427 end = line_end;
428
429 LogEntry* entry;
430 Entries::iterator i = entries_.find(output);
431 if (i != entries_.end()) {
432 entry = i->second;
433 } else {
434 entry = new LogEntry(output);
435 entries_.insert(Entries::value_type(entry->output, entry));
436 ++unique_entry_count;
437 }
438 ++total_entry_count;
439
440 entry->start_time = start_time;
441 entry->end_time = end_time;
442 entry->mtime = restat_mtime;
443 if (log_version >= 5) {
444 char c = *end; *end = '\0';
445 entry->command_hash = (uint64_t)strtoull(start, NULL, 16);
446 *end = c;
447 } else {
448 entry->command_hash = LogEntry::HashCommand(StringPiece(start,
449 end - start));
450 }
451 }
452 fclose(file);
453
454 if (!line_start) {
455 return true; // file was empty
456 }
457
458 // Decide whether it's time to rebuild the log:
459 // - if we're upgrading versions
460 // - if it's getting large
461 int kMinCompactionEntryCount = 100;
462 int kCompactionRatio = 3;
463 if (log_version < kCurrentVersion) {
464 needs_recompaction_ = true;
465 } else if (total_entry_count > kMinCompactionEntryCount &&
466 total_entry_count > unique_entry_count * kCompactionRatio) {
467 needs_recompaction_ = true;
468 }
469
470 return true;
471}
472
473BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) {
474 Entries::iterator i = entries_.find(path);
475 if (i != entries_.end())
476 return i->second;
477 return NULL;
478}
479
480bool BuildLog::WriteEntry(FILE* f, const LogEntry& entry) {
481 return fprintf(f, "%d\t%d\t%d\t%s\t%" PRIx64 "\n",
482 entry.start_time, entry.end_time, entry.mtime,
483 entry.output.c_str(), entry.command_hash) > 0;
484}
485
486bool BuildLog::Recompact(const string& path, const BuildLogUser& user,
487 string* err) {
488 METRIC_RECORD(".ninja_log recompact");
489
490 Close();
491 string temp_path = path + ".recompact";
492 FILE* f = fopen(temp_path.c_str(), "wb");
493 if (!f) {
494 *err = strerror(errno);
495 return false;
496 }
497
498 if (fprintf(f, kFileSignature, kCurrentVersion) < 0) {
499 *err = strerror(errno);
500 fclose(f);
501 return false;
502 }
503
504 vector<StringPiece> dead_outputs;
505 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
506 if (user.IsPathDead(i->first)) {
507 dead_outputs.push_back(i->first);
508 continue;
509 }
510
511 if (!WriteEntry(f, *i->second)) {
512 *err = strerror(errno);
513 fclose(f);
514 return false;
515 }
516 }
517
518 for (size_t i = 0; i < dead_outputs.size(); ++i)
519 entries_.erase(dead_outputs[i]);
520
521 fclose(f);
522 if (unlink(path.c_str()) < 0) {
523 *err = strerror(errno);
524 return false;
525 }
526
527 if (rename(temp_path.c_str(), path.c_str()) < 0) {
528 *err = strerror(errno);
529 return false;
530 }
531
532 return true;
533}
534*/