Home | History | Annotate | Download | only in kati
      1 // Copyright 2015 Google Inc. All rights reserved
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //      http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 // +build ignore
     16 
     17 #include "ninja.h"
     18 
     19 #include <stdio.h>
     20 #include <stdlib.h>
     21 #include <sys/stat.h>
     22 #include <unistd.h>
     23 
     24 #include <map>
     25 #include <sstream>
     26 #include <string>
     27 #include <unordered_map>
     28 #include <unordered_set>
     29 
     30 #include "command.h"
     31 #include "dep.h"
     32 #include "eval.h"
     33 #include "file_cache.h"
     34 #include "fileutil.h"
     35 #include "find.h"
     36 #include "flags.h"
     37 #include "func.h"
     38 #include "io.h"
     39 #include "log.h"
     40 #include "stats.h"
     41 #include "string_piece.h"
     42 #include "stringprintf.h"
     43 #include "strutil.h"
     44 #include "thread_pool.h"
     45 #include "timeutil.h"
     46 #include "var.h"
     47 #include "version.h"
     48 
     49 static size_t FindCommandLineFlag(StringPiece cmd, StringPiece name) {
     50   const size_t found = cmd.find(name);
     51   if (found == string::npos || found == 0)
     52     return string::npos;
     53   return found;
     54 }
     55 
     56 static StringPiece FindCommandLineFlagWithArg(StringPiece cmd,
     57                                               StringPiece name) {
     58   size_t index = FindCommandLineFlag(cmd, name);
     59   if (index == string::npos)
     60     return StringPiece();
     61 
     62   StringPiece val = TrimLeftSpace(cmd.substr(index + name.size()));
     63   index = val.find(name);
     64   while (index != string::npos) {
     65     val = TrimLeftSpace(val.substr(index + name.size()));
     66     index = val.find(name);
     67   }
     68 
     69   index = val.find_first_of(" \t");
     70   return val.substr(0, index);
     71 }
     72 
     73 static bool StripPrefix(StringPiece p, StringPiece* s) {
     74   if (!HasPrefix(*s, p))
     75     return false;
     76   *s = s->substr(p.size());
     77   return true;
     78 }
     79 
     80 size_t GetGomaccPosForAndroidCompileCommand(StringPiece cmdline) {
     81   size_t index = cmdline.find(' ');
     82   if (index == string::npos)
     83     return string::npos;
     84   StringPiece cmd = cmdline.substr(0, index);
     85   if (HasSuffix(cmd, "ccache")) {
     86     index++;
     87     size_t pos = GetGomaccPosForAndroidCompileCommand(cmdline.substr(index));
     88     return pos == string::npos ? string::npos : pos + index;
     89   }
     90   if (!StripPrefix("prebuilts/", &cmd))
     91     return string::npos;
     92   if (!StripPrefix("gcc/", &cmd) && !StripPrefix("clang/", &cmd))
     93     return string::npos;
     94   if (!HasSuffix(cmd, "gcc") && !HasSuffix(cmd, "g++") &&
     95       !HasSuffix(cmd, "clang") && !HasSuffix(cmd, "clang++")) {
     96     return string::npos;
     97   }
     98 
     99   StringPiece rest = cmdline.substr(index);
    100   return rest.find(" -c ") != string::npos ? 0 : string::npos;
    101 }
    102 
    103 static bool GetDepfileFromCommandImpl(StringPiece cmd, string* out) {
    104   if ((FindCommandLineFlag(cmd, " -MD") == string::npos &&
    105        FindCommandLineFlag(cmd, " -MMD") == string::npos) ||
    106       FindCommandLineFlag(cmd, " -c") == string::npos) {
    107     return false;
    108   }
    109 
    110   StringPiece mf = FindCommandLineFlagWithArg(cmd, " -MF");
    111   if (!mf.empty()) {
    112     mf.AppendToString(out);
    113     return true;
    114   }
    115 
    116   StringPiece o = FindCommandLineFlagWithArg(cmd, " -o");
    117   if (o.empty()) {
    118     ERROR("Cannot find the depfile in %s", cmd.as_string().c_str());
    119     return false;
    120   }
    121 
    122   StripExt(o).AppendToString(out);
    123   *out += ".d";
    124   return true;
    125 }
    126 
    127 bool GetDepfileFromCommand(string* cmd, string* out) {
    128   CHECK(!cmd->empty());
    129   if (!GetDepfileFromCommandImpl(*cmd, out))
    130     return false;
    131 
    132   // A hack for Android - llvm-rs-cc seems not to emit a dep file.
    133   if (cmd->find("bin/llvm-rs-cc ") != string::npos) {
    134     return false;
    135   }
    136 
    137   // TODO: A hack for Makefiles generated by automake.
    138 
    139   // A hack for Android to get .P files instead of .d.
    140   string p;
    141   StripExt(*out).AppendToString(&p);
    142   p += ".P";
    143   if (cmd->find(p) != string::npos) {
    144     const string rm_f = "; rm -f " + *out;
    145     const size_t found = cmd->find(rm_f);
    146     if (found == string::npos) {
    147       ERROR("Cannot find removal of .d file: %s", cmd->c_str());
    148     }
    149     cmd->erase(found, rm_f.size());
    150     return true;
    151   }
    152 
    153   // A hack for Android. For .s files, GCC does not use C
    154   // preprocessor, so it ignores -MF flag.
    155   string as = "/";
    156   StripExt(Basename(*out)).AppendToString(&as);
    157   as += ".s";
    158   if (cmd->find(as) != string::npos) {
    159     return false;
    160   }
    161 
    162   *cmd += "&& cp ";
    163   *cmd += *out;
    164   *cmd += ' ';
    165   *cmd += *out;
    166   *cmd += ".tmp ";
    167   *out += ".tmp";
    168   return true;
    169 }
    170 
    171 struct NinjaNode {
    172   const DepNode* node;
    173   vector<Command*> commands;
    174   int rule_id;
    175 };
    176 
    177 class NinjaGenerator {
    178  public:
    179   NinjaGenerator(Evaluator* ev, double start_time)
    180       : ce_(ev),
    181         ev_(ev),
    182         fp_(NULL),
    183         rule_id_(0),
    184         start_time_(start_time),
    185         default_target_(NULL) {
    186     ev_->set_avoid_io(true);
    187     shell_ = EscapeNinja(ev->GetShell());
    188     shell_flags_ = EscapeNinja(ev->GetShellFlag());
    189     const string use_goma_str = ev->EvalVar(Intern("USE_GOMA"));
    190     use_goma_ = !(use_goma_str.empty() || use_goma_str == "false");
    191     if (g_flags.goma_dir)
    192       gomacc_ = StringPrintf("%s/gomacc ", g_flags.goma_dir);
    193 
    194     GetExecutablePath(&kati_binary_);
    195   }
    196 
    197   ~NinjaGenerator() {
    198     ev_->set_avoid_io(false);
    199     for (NinjaNode* nn : nodes_)
    200       delete nn;
    201   }
    202 
    203   void Generate(const vector<DepNode*>& nodes, const string& orig_args) {
    204     unlink(GetNinjaStampFilename().c_str());
    205     PopulateNinjaNodes(nodes);
    206     GenerateNinja();
    207     GenerateShell();
    208     GenerateStamp(orig_args);
    209   }
    210 
    211   static string GetStampTempFilename() {
    212     return GetFilename(".kati_stamp%s.tmp");
    213   }
    214 
    215   static string GetFilename(const char* fmt) {
    216     string r = g_flags.ninja_dir ? g_flags.ninja_dir : ".";
    217     r += '/';
    218     r += StringPrintf(fmt, g_flags.ninja_suffix ? g_flags.ninja_suffix : "");
    219     return r;
    220   }
    221 
    222  private:
    223   void PopulateNinjaNodes(const vector<DepNode*>& nodes) {
    224     ScopedTimeReporter tr("ninja gen (eval)");
    225     for (DepNode* node : nodes) {
    226       PopulateNinjaNode(node);
    227     }
    228   }
    229 
    230   void PopulateNinjaNode(DepNode* node) {
    231     auto p = done_.insert(node->output);
    232     if (!p.second)
    233       return;
    234 
    235     // A hack to exclude out phony target in Android. If this exists,
    236     // "ninja -t clean" tries to remove this directory and fails.
    237     if (g_flags.detect_android_echo && node->output.str() == "out")
    238       return;
    239 
    240     // This node is a leaf node
    241     if (!node->has_rule && !node->is_phony) {
    242       return;
    243     }
    244 
    245     NinjaNode* nn = new NinjaNode;
    246     nn->node = node;
    247     ce_.Eval(node, &nn->commands);
    248     nn->rule_id = nn->commands.empty() ? -1 : rule_id_++;
    249     nodes_.push_back(nn);
    250 
    251     for (DepNode* d : node->deps) {
    252       PopulateNinjaNode(d);
    253     }
    254     for (DepNode* d : node->order_onlys) {
    255       PopulateNinjaNode(d);
    256     }
    257   }
    258 
    259   StringPiece TranslateCommand(const char* in, string* cmd_buf) {
    260     const size_t orig_size = cmd_buf->size();
    261     bool prev_backslash = false;
    262     // Set space as an initial value so the leading comment will be
    263     // stripped out.
    264     char prev_char = ' ';
    265     char quote = 0;
    266     for (; *in; in++) {
    267       switch (*in) {
    268         case '#':
    269           if (quote == 0 && isspace(prev_char)) {
    270             while (in[1] && *in != '\n')
    271               in++;
    272           } else {
    273             *cmd_buf += *in;
    274           }
    275           break;
    276 
    277         case '\'':
    278         case '"':
    279         case '`':
    280           if (quote) {
    281             if (quote == *in)
    282               quote = 0;
    283           } else if (!prev_backslash) {
    284             quote = *in;
    285           }
    286           *cmd_buf += *in;
    287           break;
    288 
    289         case '$':
    290           *cmd_buf += "$$";
    291           break;
    292 
    293         case '\n':
    294           if (prev_backslash) {
    295             cmd_buf->resize(cmd_buf->size() - 1);
    296           } else {
    297             *cmd_buf += ' ';
    298           }
    299           break;
    300 
    301         case '\\':
    302           *cmd_buf += '\\';
    303           break;
    304 
    305         default:
    306           *cmd_buf += *in;
    307       }
    308 
    309       if (*in == '\\') {
    310         prev_backslash = !prev_backslash;
    311       } else {
    312         prev_backslash = false;
    313       }
    314 
    315       prev_char = *in;
    316     }
    317 
    318     if (prev_backslash) {
    319       cmd_buf->resize(cmd_buf->size() - 1);
    320     }
    321 
    322     while (true) {
    323       char c = (*cmd_buf)[cmd_buf->size() - 1];
    324       if (!isspace(c) && c != ';')
    325         break;
    326       cmd_buf->resize(cmd_buf->size() - 1);
    327     }
    328 
    329     return StringPiece(cmd_buf->data() + orig_size,
    330                        cmd_buf->size() - orig_size);
    331   }
    332 
    333   bool IsOutputMkdir(const char* name, StringPiece cmd) {
    334     if (!HasPrefix(cmd, "mkdir -p ")) {
    335       return false;
    336     }
    337     cmd = cmd.substr(9, cmd.size());
    338     if (cmd.get(cmd.size() - 1) == '/') {
    339       cmd = cmd.substr(0, cmd.size() - 1);
    340     }
    341 
    342     StringPiece dir = Dirname(name);
    343     if (cmd == dir) {
    344       return true;
    345     }
    346     return false;
    347   }
    348 
    349   bool GetDescriptionFromCommand(StringPiece cmd, string* out) {
    350     if (!HasPrefix(cmd, "echo ")) {
    351       return false;
    352     }
    353     cmd = cmd.substr(5, cmd.size());
    354 
    355     bool prev_backslash = false;
    356     char quote = 0;
    357     string out_buf;
    358 
    359     // Strip outer quotes, and fail if it is not a single echo command
    360     for (StringPiece::iterator in = cmd.begin(); in != cmd.end(); in++) {
    361       if (prev_backslash) {
    362         prev_backslash = false;
    363         out_buf += *in;
    364       } else if (*in == '\\') {
    365         prev_backslash = true;
    366         out_buf += *in;
    367       } else if (quote) {
    368         if (*in == quote) {
    369           quote = 0;
    370         } else {
    371           out_buf += *in;
    372         }
    373       } else {
    374         switch (*in) {
    375           case '\'':
    376           case '"':
    377           case '`':
    378             quote = *in;
    379             break;
    380 
    381           case '<':
    382           case '>':
    383           case '&':
    384           case '|':
    385           case ';':
    386             return false;
    387 
    388           default:
    389             out_buf += *in;
    390         }
    391       }
    392     }
    393 
    394     *out = out_buf;
    395     return true;
    396   }
    397 
    398   bool GenShellScript(const char* name,
    399                       const vector<Command*>& commands,
    400                       string* cmd_buf,
    401                       string* description) {
    402     bool got_descritpion = false;
    403     bool use_gomacc = false;
    404     auto command_count = commands.size();
    405     for (const Command* c : commands) {
    406       size_t cmd_begin = cmd_buf->size();
    407 
    408       if (!cmd_buf->empty()) {
    409         *cmd_buf += " && ";
    410       }
    411 
    412       const char* in = c->cmd.c_str();
    413       while (isspace(*in))
    414         in++;
    415 
    416       bool needs_subshell = (command_count > 1 || c->ignore_error);
    417 
    418       if (needs_subshell)
    419         *cmd_buf += '(';
    420 
    421       size_t cmd_start = cmd_buf->size();
    422       StringPiece translated = TranslateCommand(in, cmd_buf);
    423       if (g_flags.detect_android_echo && !got_descritpion && !c->echo &&
    424           GetDescriptionFromCommand(translated, description)) {
    425         got_descritpion = true;
    426         translated.clear();
    427       } else if (IsOutputMkdir(name, translated) && !c->echo &&
    428                  cmd_begin == 0) {
    429         translated.clear();
    430       }
    431       if (translated.empty()) {
    432         cmd_buf->resize(cmd_begin);
    433         command_count -= 1;
    434         continue;
    435       } else if (g_flags.goma_dir) {
    436         size_t pos = GetGomaccPosForAndroidCompileCommand(translated);
    437         if (pos != string::npos) {
    438           cmd_buf->insert(cmd_start + pos, gomacc_);
    439           use_gomacc = true;
    440         }
    441       } else if (translated.find("/gomacc") != string::npos) {
    442         use_gomacc = true;
    443       }
    444 
    445       if (c->ignore_error) {
    446         *cmd_buf += " ; true";
    447       }
    448 
    449       if (needs_subshell)
    450         *cmd_buf += " )";
    451     }
    452     return (use_goma_ || g_flags.remote_num_jobs || g_flags.goma_dir) &&
    453            !use_gomacc;
    454   }
    455 
    456   bool GetDepfile(const DepNode* node, string* cmd_buf, string* depfile) {
    457     if (node->depfile_var) {
    458       node->depfile_var->Eval(ev_, depfile);
    459       return true;
    460     }
    461     if (!g_flags.detect_depfiles)
    462       return false;
    463 
    464     *cmd_buf += ' ';
    465     bool result = GetDepfileFromCommand(cmd_buf, depfile);
    466     cmd_buf->resize(cmd_buf->size() - 1);
    467     return result;
    468   }
    469 
    470   void EmitDepfile(NinjaNode* nn, string* cmd_buf, ostringstream* o) {
    471     const DepNode* node = nn->node;
    472     string depfile;
    473     if (!GetDepfile(node, cmd_buf, &depfile))
    474       return;
    475     *o << " depfile = " << depfile << "\n";
    476     *o << " deps = gcc\n";
    477   }
    478 
    479   void EmitNode(NinjaNode* nn, ostringstream* o) {
    480     const DepNode* node = nn->node;
    481     const vector<Command*>& commands = nn->commands;
    482 
    483     string rule_name = "phony";
    484     bool use_local_pool = false;
    485     if (node->output.get(0) == '.') {
    486       return;
    487     }
    488     if (g_flags.enable_debug) {
    489       *o << "# " << (node->loc.filename ? node->loc.filename : "(null)") << ':'
    490          << node->loc.lineno << "\n";
    491     }
    492     if (!commands.empty()) {
    493       rule_name = StringPrintf("rule%d", nn->rule_id);
    494       *o << "rule " << rule_name << "\n";
    495 
    496       string description = "build $out";
    497       string cmd_buf;
    498       use_local_pool |= GenShellScript(node->output.c_str(), commands, &cmd_buf,
    499                                        &description);
    500       *o << " description = " << description << "\n";
    501       EmitDepfile(nn, &cmd_buf, o);
    502 
    503       // It seems Linux is OK with ~130kB and Mac's limit is ~250kB.
    504       // TODO: Find this number automatically.
    505       if (cmd_buf.size() > 100 * 1000) {
    506         *o << " rspfile = $out.rsp\n";
    507         *o << " rspfile_content = " << cmd_buf << "\n";
    508         *o << " command = " << shell_ << " $out.rsp\n";
    509       } else {
    510         EscapeShell(&cmd_buf);
    511         *o << " command = " << shell_ << ' ' << shell_flags_ << " \"" << cmd_buf
    512            << "\"\n";
    513       }
    514       if (node->is_restat) {
    515         *o << " restat = 1\n";
    516       }
    517     }
    518 
    519     EmitBuild(nn, rule_name, use_local_pool, o);
    520   }
    521 
    522   string EscapeNinja(const string& s) const {
    523     if (s.find_first_of("$: ") == string::npos)
    524       return s;
    525     string r;
    526     for (char c : s) {
    527       switch (c) {
    528         case '$':
    529         case ':':
    530         case ' ':
    531           r += '$';
    532         // fall through.
    533         default:
    534           r += c;
    535       }
    536     }
    537     return r;
    538   }
    539 
    540   string EscapeBuildTarget(Symbol s) const { return EscapeNinja(s.str()); }
    541 
    542   void EmitBuild(NinjaNode* nn,
    543                  const string& rule_name,
    544                  bool use_local_pool,
    545                  ostringstream* o) {
    546     const DepNode* node = nn->node;
    547     string target = EscapeBuildTarget(node->output);
    548     *o << "build " << target;
    549     if (!node->implicit_outputs.empty()) {
    550       *o << " |";
    551       for (Symbol output : node->implicit_outputs) {
    552         *o << " " << EscapeBuildTarget(output);
    553       }
    554     }
    555     *o << ": " << rule_name;
    556     vector<Symbol> order_onlys;
    557     if (node->is_phony) {
    558       *o << " _kati_always_build_";
    559     }
    560     for (DepNode* d : node->deps) {
    561       *o << " " << EscapeBuildTarget(d->output).c_str();
    562     }
    563     if (!node->order_onlys.empty()) {
    564       *o << " ||";
    565       for (DepNode* d : node->order_onlys) {
    566         *o << " " << EscapeBuildTarget(d->output).c_str();
    567       }
    568     }
    569     *o << "\n";
    570     if (node->ninja_pool_var) {
    571       string pool;
    572       node->ninja_pool_var->Eval(ev_, &pool);
    573       *o << " pool = " << pool << "\n";
    574     } else if (use_local_pool) {
    575       *o << " pool = local_pool\n";
    576     }
    577     if (node->is_default_target) {
    578       unique_lock<mutex> lock(mu_);
    579       default_target_ = node;
    580     }
    581   }
    582 
    583   static string GetEnvScriptFilename() { return GetFilename("env%s.sh"); }
    584 
    585   void GenerateNinja() {
    586     ScopedTimeReporter tr("ninja gen (emit)");
    587     fp_ = fopen(GetNinjaFilename().c_str(), "wb");
    588     if (fp_ == NULL)
    589       PERROR("fopen(build.ninja) failed");
    590 
    591     fprintf(fp_, "# Generated by kati %s\n", kGitVersion);
    592     fprintf(fp_, "\n");
    593 
    594     if (!used_envs_.empty()) {
    595       fprintf(fp_, "# Environment variables used:\n");
    596       for (const auto& p : used_envs_) {
    597         fprintf(fp_, "# %s=%s\n", p.first.c_str(), p.second.c_str());
    598       }
    599       fprintf(fp_, "\n");
    600     }
    601 
    602     if (g_flags.ninja_dir) {
    603       fprintf(fp_, "builddir = %s\n\n", g_flags.ninja_dir);
    604     }
    605 
    606     fprintf(fp_, "pool local_pool\n");
    607     fprintf(fp_, " depth = %d\n\n", g_flags.num_jobs);
    608 
    609     fprintf(fp_, "build _kati_always_build_: phony\n\n");
    610 
    611     unique_ptr<ThreadPool> tp(NewThreadPool(g_flags.num_jobs));
    612     CHECK(g_flags.num_jobs);
    613     int num_nodes_per_task = nodes_.size() / (g_flags.num_jobs * 10) + 1;
    614     int num_tasks = nodes_.size() / num_nodes_per_task + 1;
    615     vector<ostringstream> bufs(num_tasks);
    616     for (int i = 0; i < num_tasks; i++) {
    617       tp->Submit([this, i, num_nodes_per_task, &bufs]() {
    618         int l =
    619             min(num_nodes_per_task * (i + 1), static_cast<int>(nodes_.size()));
    620         for (int j = num_nodes_per_task * i; j < l; j++) {
    621           EmitNode(nodes_[j], &bufs[i]);
    622         }
    623       });
    624     }
    625     tp->Wait();
    626 
    627     for (const ostringstream& buf : bufs) {
    628       fprintf(fp_, "%s", buf.str().c_str());
    629     }
    630 
    631     unordered_set<Symbol> used_env_vars(Vars::used_env_vars());
    632     // PATH changes $(shell).
    633     used_env_vars.insert(Intern("PATH"));
    634     for (Symbol e : used_env_vars) {
    635       StringPiece val(getenv(e.c_str()));
    636       used_envs_.emplace(e.str(), val.as_string());
    637     }
    638 
    639     string default_targets;
    640     if (g_flags.targets.empty() || g_flags.gen_all_targets) {
    641       CHECK(default_target_);
    642       default_targets = EscapeBuildTarget(default_target_->output);
    643     } else {
    644       for (Symbol s : g_flags.targets) {
    645         if (!default_targets.empty())
    646           default_targets += ' ';
    647         default_targets += EscapeBuildTarget(s);
    648       }
    649     }
    650     fprintf(fp_, "\n");
    651     fprintf(fp_, "default %s\n", default_targets.c_str());
    652 
    653     fclose(fp_);
    654   }
    655 
    656   void GenerateShell() {
    657     FILE* fp = fopen(GetEnvScriptFilename().c_str(), "wb");
    658     if (fp == NULL)
    659       PERROR("fopen(env.sh) failed");
    660 
    661     fprintf(fp, "#!/bin/sh\n");
    662     fprintf(fp, "# Generated by kati %s\n", kGitVersion);
    663     fprintf(fp, "\n");
    664 
    665     for (const auto& p : ev_->exports()) {
    666       if (p.second) {
    667         const string val = ev_->EvalVar(p.first);
    668         fprintf(fp, "export '%s'='%s'\n", p.first.c_str(), val.c_str());
    669       } else {
    670         fprintf(fp, "unset '%s'\n", p.first.c_str());
    671       }
    672     }
    673 
    674     fclose(fp);
    675 
    676     fp = fopen(GetNinjaShellScriptFilename().c_str(), "wb");
    677     if (fp == NULL)
    678       PERROR("fopen(ninja.sh) failed");
    679 
    680     fprintf(fp, "#!/bin/sh\n");
    681     fprintf(fp, "# Generated by kati %s\n", kGitVersion);
    682     fprintf(fp, "\n");
    683 
    684     fprintf(fp, ". %s\n", GetEnvScriptFilename().c_str());
    685 
    686     fprintf(fp, "exec ninja -f %s ", GetNinjaFilename().c_str());
    687     if (g_flags.remote_num_jobs > 0) {
    688       fprintf(fp, "-j%d ", g_flags.remote_num_jobs);
    689     } else if (g_flags.goma_dir) {
    690       fprintf(fp, "-j500 ");
    691     }
    692     fprintf(fp, "\"$@\"\n");
    693 
    694     fclose(fp);
    695 
    696     if (chmod(GetNinjaShellScriptFilename().c_str(), 0755) != 0)
    697       PERROR("chmod ninja.sh failed");
    698   }
    699 
    700   void GenerateStamp(const string& orig_args) {
    701     FILE* fp = fopen(GetStampTempFilename().c_str(), "wb");
    702     CHECK(fp);
    703 
    704     size_t r = fwrite(&start_time_, sizeof(start_time_), 1, fp);
    705     CHECK(r == 1);
    706 
    707     unordered_set<string> makefiles;
    708     MakefileCacheManager::Get()->GetAllFilenames(&makefiles);
    709     DumpInt(fp, makefiles.size() + 1);
    710     DumpString(fp, kati_binary_);
    711     for (const string& makefile : makefiles) {
    712       DumpString(fp, makefile);
    713     }
    714 
    715     DumpInt(fp, Evaluator::used_undefined_vars().size());
    716     for (Symbol v : Evaluator::used_undefined_vars()) {
    717       DumpString(fp, v.str());
    718     }
    719 
    720     DumpInt(fp, used_envs_.size());
    721     for (const auto& p : used_envs_) {
    722       DumpString(fp, p.first);
    723       DumpString(fp, p.second);
    724     }
    725 
    726     const unordered_map<string, vector<string>*>& globs = GetAllGlobCache();
    727     DumpInt(fp, globs.size());
    728     for (const auto& p : globs) {
    729       DumpString(fp, p.first);
    730       const vector<string>& files = *p.second;
    731 #if 0
    732       unordered_set<string> dirs;
    733       GetReadDirs(p.first, files, &dirs);
    734       DumpInt(fp, dirs.size());
    735       for (const string& dir : dirs) {
    736         DumpString(fp, dir);
    737       }
    738 #endif
    739       DumpInt(fp, files.size());
    740       for (const string& file : files) {
    741         DumpString(fp, file);
    742       }
    743     }
    744 
    745     const vector<CommandResult*>& crs = GetShellCommandResults();
    746     DumpInt(fp, crs.size());
    747     for (CommandResult* cr : crs) {
    748       DumpInt(fp, static_cast<int>(cr->op));
    749       DumpString(fp, cr->shell);
    750       DumpString(fp, cr->shellflag);
    751       DumpString(fp, cr->cmd);
    752       DumpString(fp, cr->result);
    753 
    754       if (cr->op == CommandOp::FIND) {
    755         vector<string> missing_dirs;
    756         for (StringPiece fd : cr->find->finddirs) {
    757           const string& d = ConcatDir(cr->find->chdir, fd);
    758           if (!Exists(d))
    759             missing_dirs.push_back(d);
    760         }
    761         DumpInt(fp, missing_dirs.size());
    762         for (const string& d : missing_dirs) {
    763           DumpString(fp, d);
    764         }
    765 
    766         DumpInt(fp, cr->find->found_files->size());
    767         for (StringPiece s : *cr->find->found_files) {
    768           DumpString(fp, ConcatDir(cr->find->chdir, s));
    769         }
    770 
    771         DumpInt(fp, cr->find->read_dirs->size());
    772         for (StringPiece s : *cr->find->read_dirs) {
    773           DumpString(fp, ConcatDir(cr->find->chdir, s));
    774         }
    775       }
    776     }
    777 
    778     DumpString(fp, orig_args);
    779 
    780     fclose(fp);
    781 
    782     rename(GetStampTempFilename().c_str(), GetNinjaStampFilename().c_str());
    783   }
    784 
    785   CommandEvaluator ce_;
    786   Evaluator* ev_;
    787   FILE* fp_;
    788   unordered_set<Symbol> done_;
    789   int rule_id_;
    790   bool use_goma_;
    791   string gomacc_;
    792   string shell_;
    793   string shell_flags_;
    794   map<string, string> used_envs_;
    795   string kati_binary_;
    796   const double start_time_;
    797   vector<NinjaNode*> nodes_;
    798 
    799   mutex mu_;
    800   const DepNode* default_target_;
    801 };
    802 
    803 string GetNinjaFilename() {
    804   return NinjaGenerator::GetFilename("build%s.ninja");
    805 }
    806 
    807 string GetNinjaShellScriptFilename() {
    808   return NinjaGenerator::GetFilename("ninja%s.sh");
    809 }
    810 
    811 string GetNinjaStampFilename() {
    812   return NinjaGenerator::GetFilename(".kati_stamp%s");
    813 }
    814 
    815 void GenerateNinja(const vector<DepNode*>& nodes,
    816                    Evaluator* ev,
    817                    const string& orig_args,
    818                    double start_time) {
    819   NinjaGenerator ng(ev, start_time);
    820   ng.Generate(nodes, orig_args);
    821 }
    822