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*/