• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
FindCommandLineFlag(StringPiece cmd,StringPiece name)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 
FindCommandLineFlagWithArg(StringPiece cmd,StringPiece name)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 
StripPrefix(StringPiece p,StringPiece * s)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 
GetGomaccPosForAndroidCompileCommand(StringPiece cmdline)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 
GetDepfileFromCommandImpl(StringPiece cmd,string * out)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 
GetDepfileFromCommand(string * cmd,string * out)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:
NinjaGenerator(Evaluator * ev,double start_time)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 
~NinjaGenerator()197   ~NinjaGenerator() {
198     ev_->set_avoid_io(false);
199     for (NinjaNode* nn : nodes_)
200       delete nn;
201   }
202 
Generate(const vector<NamedDepNode> & nodes,const string & orig_args)203   void Generate(const vector<NamedDepNode>& nodes, const string& orig_args) {
204     unlink(GetNinjaStampFilename().c_str());
205     PopulateNinjaNodes(nodes);
206     GenerateNinja();
207     GenerateShell();
208     GenerateStamp(orig_args);
209   }
210 
GetStampTempFilename()211   static string GetStampTempFilename() {
212     return GetFilename(".kati_stamp%s.tmp");
213   }
214 
GetFilename(const char * fmt)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:
PopulateNinjaNodes(const vector<NamedDepNode> & nodes)223   void PopulateNinjaNodes(const vector<NamedDepNode>& nodes) {
224     ScopedTimeReporter tr("ninja gen (eval)");
225     for (auto const& node : nodes) {
226       PopulateNinjaNode(node.second);
227     }
228   }
229 
PopulateNinjaNode(DepNode * node)230   void PopulateNinjaNode(DepNode* node) {
231     if (done_.exists(node->output)) {
232       return;
233     }
234     done_.insert(node->output);
235 
236     // A hack to exclude out phony target in Android. If this exists,
237     // "ninja -t clean" tries to remove this directory and fails.
238     if (g_flags.detect_android_echo && node->output.str() == "out")
239       return;
240 
241     // This node is a leaf node
242     if (!node->has_rule && !node->is_phony) {
243       return;
244     }
245 
246     NinjaNode* nn = new NinjaNode;
247     nn->node = node;
248     ce_.Eval(node, &nn->commands);
249     nn->rule_id = nn->commands.empty() ? -1 : rule_id_++;
250     nodes_.push_back(nn);
251 
252     for (auto const& d : node->deps) {
253       PopulateNinjaNode(d.second);
254     }
255     for (auto const& d : node->order_onlys) {
256       PopulateNinjaNode(d.second);
257     }
258   }
259 
TranslateCommand(const char * in,string * cmd_buf)260   StringPiece TranslateCommand(const char* in, string* cmd_buf) {
261     const size_t orig_size = cmd_buf->size();
262     bool prev_backslash = false;
263     // Set space as an initial value so the leading comment will be
264     // stripped out.
265     char prev_char = ' ';
266     char quote = 0;
267     for (; *in; in++) {
268       switch (*in) {
269         case '#':
270           if (quote == 0 && isspace(prev_char)) {
271             while (in[1] && *in != '\n')
272               in++;
273           } else {
274             *cmd_buf += *in;
275           }
276           break;
277 
278         case '\'':
279         case '"':
280         case '`':
281           if (quote) {
282             if (quote == *in)
283               quote = 0;
284           } else if (!prev_backslash) {
285             quote = *in;
286           }
287           *cmd_buf += *in;
288           break;
289 
290         case '$':
291           *cmd_buf += "$$";
292           break;
293 
294         case '\n':
295           if (prev_backslash) {
296             cmd_buf->resize(cmd_buf->size() - 1);
297           } else {
298             *cmd_buf += ' ';
299           }
300           break;
301 
302         case '\\':
303           *cmd_buf += '\\';
304           break;
305 
306         default:
307           *cmd_buf += *in;
308       }
309 
310       if (*in == '\\') {
311         prev_backslash = !prev_backslash;
312       } else {
313         prev_backslash = false;
314       }
315 
316       prev_char = *in;
317     }
318 
319     if (prev_backslash) {
320       cmd_buf->resize(cmd_buf->size() - 1);
321     }
322 
323     while (true) {
324       char c = (*cmd_buf)[cmd_buf->size() - 1];
325       if (!isspace(c) && c != ';')
326         break;
327       cmd_buf->resize(cmd_buf->size() - 1);
328     }
329 
330     return StringPiece(cmd_buf->data() + orig_size,
331                        cmd_buf->size() - orig_size);
332   }
333 
IsOutputMkdir(const char * name,StringPiece cmd)334   bool IsOutputMkdir(const char* name, StringPiece cmd) {
335     if (!HasPrefix(cmd, "mkdir -p ")) {
336       return false;
337     }
338     cmd = cmd.substr(9, cmd.size());
339     if (cmd.get(cmd.size() - 1) == '/') {
340       cmd = cmd.substr(0, cmd.size() - 1);
341     }
342 
343     StringPiece dir = Dirname(name);
344     if (cmd == dir) {
345       return true;
346     }
347     return false;
348   }
349 
GetDescriptionFromCommand(StringPiece cmd,string * out)350   bool GetDescriptionFromCommand(StringPiece cmd, string* out) {
351     if (!HasPrefix(cmd, "echo ")) {
352       return false;
353     }
354     cmd = cmd.substr(5, cmd.size());
355 
356     bool prev_backslash = false;
357     char quote = 0;
358     string out_buf;
359 
360     // Strip outer quotes, and fail if it is not a single echo command
361     for (StringPiece::iterator in = cmd.begin(); in != cmd.end(); in++) {
362       if (prev_backslash) {
363         prev_backslash = false;
364         out_buf += *in;
365       } else if (*in == '\\') {
366         prev_backslash = true;
367         out_buf += *in;
368       } else if (quote) {
369         if (*in == quote) {
370           quote = 0;
371         } else {
372           out_buf += *in;
373         }
374       } else {
375         switch (*in) {
376           case '\'':
377           case '"':
378           case '`':
379             quote = *in;
380             break;
381 
382           case '<':
383           case '>':
384           case '&':
385           case '|':
386           case ';':
387             return false;
388 
389           default:
390             out_buf += *in;
391         }
392       }
393     }
394 
395     *out = out_buf;
396     return true;
397   }
398 
GenShellScript(const char * name,const vector<Command * > & commands,string * cmd_buf,string * description)399   bool GenShellScript(const char* name,
400                       const vector<Command*>& commands,
401                       string* cmd_buf,
402                       string* description) {
403     bool got_descritpion = false;
404     bool use_gomacc = false;
405     auto command_count = commands.size();
406     for (const Command* c : commands) {
407       size_t cmd_begin = cmd_buf->size();
408 
409       if (!cmd_buf->empty()) {
410         *cmd_buf += " && ";
411       }
412 
413       const char* in = c->cmd.c_str();
414       while (isspace(*in))
415         in++;
416 
417       bool needs_subshell = (command_count > 1 || c->ignore_error);
418 
419       if (needs_subshell)
420         *cmd_buf += '(';
421 
422       size_t cmd_start = cmd_buf->size();
423       StringPiece translated = TranslateCommand(in, cmd_buf);
424       if (g_flags.detect_android_echo && !got_descritpion && !c->echo &&
425           GetDescriptionFromCommand(translated, description)) {
426         got_descritpion = true;
427         translated.clear();
428       } else if (IsOutputMkdir(name, translated) && !c->echo &&
429                  cmd_begin == 0) {
430         translated.clear();
431       }
432       if (translated.empty()) {
433         cmd_buf->resize(cmd_begin);
434         command_count -= 1;
435         continue;
436       } else if (g_flags.goma_dir) {
437         size_t pos = GetGomaccPosForAndroidCompileCommand(translated);
438         if (pos != string::npos) {
439           cmd_buf->insert(cmd_start + pos, gomacc_);
440           use_gomacc = true;
441         }
442       } else if (translated.find("/gomacc") != string::npos) {
443         use_gomacc = true;
444       }
445 
446       if (c->ignore_error) {
447         *cmd_buf += " ; true";
448       }
449 
450       if (needs_subshell)
451         *cmd_buf += " )";
452     }
453     return (use_goma_ || g_flags.remote_num_jobs || g_flags.goma_dir) &&
454            !use_gomacc;
455   }
456 
GetDepfile(const DepNode * node,string * cmd_buf,string * depfile)457   bool GetDepfile(const DepNode* node, string* cmd_buf, string* depfile) {
458     if (node->depfile_var) {
459       node->depfile_var->Eval(ev_, depfile);
460       return true;
461     }
462     if (!g_flags.detect_depfiles)
463       return false;
464 
465     *cmd_buf += ' ';
466     bool result = GetDepfileFromCommand(cmd_buf, depfile);
467     cmd_buf->resize(cmd_buf->size() - 1);
468     return result;
469   }
470 
EmitDepfile(NinjaNode * nn,string * cmd_buf,ostringstream * o)471   void EmitDepfile(NinjaNode* nn, string* cmd_buf, ostringstream* o) {
472     const DepNode* node = nn->node;
473     string depfile;
474     if (!GetDepfile(node, cmd_buf, &depfile))
475       return;
476     *o << " depfile = " << depfile << "\n";
477     *o << " deps = gcc\n";
478   }
479 
EmitNode(NinjaNode * nn,ostringstream * o)480   void EmitNode(NinjaNode* nn, ostringstream* o) {
481     const DepNode* node = nn->node;
482     const vector<Command*>& commands = nn->commands;
483 
484     string rule_name = "phony";
485     bool use_local_pool = false;
486     if (node->output.get(0) == '.') {
487       return;
488     }
489     if (g_flags.enable_debug) {
490       *o << "# " << (node->loc.filename ? node->loc.filename : "(null)") << ':'
491          << node->loc.lineno << "\n";
492     }
493     if (!commands.empty()) {
494       rule_name = StringPrintf("rule%d", nn->rule_id);
495       *o << "rule " << rule_name << "\n";
496 
497       string description = "build $out";
498       string cmd_buf;
499       use_local_pool |= GenShellScript(node->output.c_str(), commands, &cmd_buf,
500                                        &description);
501       *o << " description = " << description << "\n";
502       EmitDepfile(nn, &cmd_buf, o);
503 
504       // It seems Linux is OK with ~130kB and Mac's limit is ~250kB.
505       // TODO: Find this number automatically.
506       if (cmd_buf.size() > 100 * 1000) {
507         *o << " rspfile = $out.rsp\n";
508         *o << " rspfile_content = " << cmd_buf << "\n";
509         *o << " command = " << shell_ << " $out.rsp\n";
510       } else {
511         EscapeShell(&cmd_buf);
512         *o << " command = " << shell_ << ' ' << shell_flags_ << " \"" << cmd_buf
513            << "\"\n";
514       }
515       if (node->is_restat) {
516         *o << " restat = 1\n";
517       }
518     }
519 
520     EmitBuild(nn, rule_name, use_local_pool, o);
521   }
522 
EscapeNinja(const string & s) const523   string EscapeNinja(const string& s) const {
524     if (s.find_first_of("$: ") == string::npos)
525       return s;
526     string r;
527     for (char c : s) {
528       switch (c) {
529         case '$':
530         case ':':
531         case ' ':
532           r += '$';
533 #if defined(__has_cpp_attribute) && __has_cpp_attribute(clang::fallthrough)
534           [[clang::fallthrough]];
535 #endif
536         default:
537           r += c;
538       }
539     }
540     return r;
541   }
542 
EscapeBuildTarget(Symbol s) const543   string EscapeBuildTarget(Symbol s) const { return EscapeNinja(s.str()); }
544 
EmitBuild(NinjaNode * nn,const string & rule_name,bool use_local_pool,ostringstream * o)545   void EmitBuild(NinjaNode* nn,
546                  const string& rule_name,
547                  bool use_local_pool,
548                  ostringstream* o) {
549     const DepNode* node = nn->node;
550     string target = EscapeBuildTarget(node->output);
551     *o << "build " << target;
552     if (!node->implicit_outputs.empty()) {
553       *o << " |";
554       for (Symbol output : node->implicit_outputs) {
555         *o << " " << EscapeBuildTarget(output);
556       }
557     }
558     *o << ": " << rule_name;
559     vector<Symbol> order_onlys;
560     if (node->is_phony) {
561       *o << " _kati_always_build_";
562     }
563     for (auto const& d : node->deps) {
564       *o << " " << EscapeBuildTarget(d.first).c_str();
565     }
566     if (!node->order_onlys.empty()) {
567       *o << " ||";
568       for (auto const& d : node->order_onlys) {
569         *o << " " << EscapeBuildTarget(d.first).c_str();
570       }
571     }
572     *o << "\n";
573     if (node->ninja_pool_var) {
574       string pool;
575       node->ninja_pool_var->Eval(ev_, &pool);
576       *o << " pool = " << pool << "\n";
577     } else if (use_local_pool) {
578       *o << " pool = local_pool\n";
579     }
580     if (node->is_default_target) {
581       unique_lock<mutex> lock(mu_);
582       default_target_ = node;
583     }
584   }
585 
GetEnvScriptFilename()586   static string GetEnvScriptFilename() { return GetFilename("env%s.sh"); }
587 
GenerateNinja()588   void GenerateNinja() {
589     ScopedTimeReporter tr("ninja gen (emit)");
590     fp_ = fopen(GetNinjaFilename().c_str(), "wb");
591     if (fp_ == NULL)
592       PERROR("fopen(build.ninja) failed");
593 
594     fprintf(fp_, "# Generated by kati %s\n", kGitVersion);
595     fprintf(fp_, "\n");
596 
597     if (!used_envs_.empty()) {
598       fprintf(fp_, "# Environment variables used:\n");
599       for (const auto& p : used_envs_) {
600         fprintf(fp_, "# %s=%s\n", p.first.c_str(), p.second.c_str());
601       }
602       fprintf(fp_, "\n");
603     }
604 
605     if (!g_flags.no_ninja_prelude) {
606       if (g_flags.ninja_dir) {
607         fprintf(fp_, "builddir = %s\n\n", g_flags.ninja_dir);
608       }
609 
610       fprintf(fp_, "pool local_pool\n");
611       fprintf(fp_, " depth = %d\n\n", g_flags.num_jobs);
612 
613       fprintf(fp_, "build _kati_always_build_: phony\n\n");
614     }
615 
616     unique_ptr<ThreadPool> tp(NewThreadPool(g_flags.num_jobs));
617     CHECK(g_flags.num_jobs);
618     int num_nodes_per_task = nodes_.size() / (g_flags.num_jobs * 10) + 1;
619     int num_tasks = nodes_.size() / num_nodes_per_task + 1;
620     vector<ostringstream> bufs(num_tasks);
621     for (int i = 0; i < num_tasks; i++) {
622       tp->Submit([this, i, num_nodes_per_task, &bufs]() {
623         int l =
624             min(num_nodes_per_task * (i + 1), static_cast<int>(nodes_.size()));
625         for (int j = num_nodes_per_task * i; j < l; j++) {
626           EmitNode(nodes_[j], &bufs[i]);
627         }
628       });
629     }
630     tp->Wait();
631 
632     if (!g_flags.generate_empty_ninja) {
633       for (const ostringstream& buf : bufs) {
634         fprintf(fp_, "%s", buf.str().c_str());
635       }
636     }
637 
638     SymbolSet used_env_vars(Vars::used_env_vars());
639     // PATH changes $(shell).
640     used_env_vars.insert(Intern("PATH"));
641     for (Symbol e : used_env_vars) {
642       StringPiece val(getenv(e.c_str()));
643       used_envs_.emplace(e.str(), val.as_string());
644     }
645 
646     string default_targets;
647     if (g_flags.targets.empty() || g_flags.gen_all_targets) {
648       CHECK(default_target_);
649       default_targets = EscapeBuildTarget(default_target_->output);
650     } else {
651       for (Symbol s : g_flags.targets) {
652         if (!default_targets.empty())
653           default_targets += ' ';
654         default_targets += EscapeBuildTarget(s);
655       }
656     }
657     if (!g_flags.generate_empty_ninja) {
658       fprintf(fp_, "\n");
659       fprintf(fp_, "default %s\n", default_targets.c_str());
660     }
661 
662     fclose(fp_);
663   }
664 
GenerateShell()665   void GenerateShell() {
666     FILE* fp = fopen(GetEnvScriptFilename().c_str(), "wb");
667     if (fp == NULL)
668       PERROR("fopen(env.sh) failed");
669 
670     fprintf(fp, "#!/bin/sh\n");
671     fprintf(fp, "# Generated by kati %s\n", kGitVersion);
672     fprintf(fp, "\n");
673 
674     for (const auto& p : ev_->exports()) {
675       if (p.second) {
676         const string val = ev_->EvalVar(p.first);
677         fprintf(fp, "export '%s'='%s'\n", p.first.c_str(), val.c_str());
678       } else {
679         fprintf(fp, "unset '%s'\n", p.first.c_str());
680       }
681     }
682 
683     fclose(fp);
684 
685     fp = fopen(GetNinjaShellScriptFilename().c_str(), "wb");
686     if (fp == NULL)
687       PERROR("fopen(ninja.sh) failed");
688 
689     fprintf(fp, "#!/bin/sh\n");
690     fprintf(fp, "# Generated by kati %s\n", kGitVersion);
691     fprintf(fp, "\n");
692 
693     fprintf(fp, ". %s\n", GetEnvScriptFilename().c_str());
694 
695     fprintf(fp, "exec ninja -f %s ", GetNinjaFilename().c_str());
696     if (g_flags.remote_num_jobs > 0) {
697       fprintf(fp, "-j%d ", g_flags.remote_num_jobs);
698     } else if (g_flags.goma_dir) {
699       fprintf(fp, "-j500 ");
700     }
701     fprintf(fp, "\"$@\"\n");
702 
703     fclose(fp);
704 
705     if (chmod(GetNinjaShellScriptFilename().c_str(), 0755) != 0)
706       PERROR("chmod ninja.sh failed");
707   }
708 
GenerateStamp(const string & orig_args)709   void GenerateStamp(const string& orig_args) {
710     FILE* fp = fopen(GetStampTempFilename().c_str(), "wb");
711     CHECK(fp);
712 
713     size_t r = fwrite(&start_time_, sizeof(start_time_), 1, fp);
714     CHECK(r == 1);
715 
716     unordered_set<string> makefiles;
717     MakefileCacheManager::Get()->GetAllFilenames(&makefiles);
718     DumpInt(fp, makefiles.size() + 1);
719     DumpString(fp, kati_binary_);
720     for (const string& makefile : makefiles) {
721       DumpString(fp, makefile);
722     }
723 
724     DumpInt(fp, Evaluator::used_undefined_vars().size());
725     for (Symbol v : Evaluator::used_undefined_vars()) {
726       DumpString(fp, v.str());
727     }
728     DumpInt(fp, used_envs_.size());
729     for (const auto& p : used_envs_) {
730       DumpString(fp, p.first);
731       DumpString(fp, p.second);
732     }
733 
734     const unordered_map<string, vector<string>*>& globs = GetAllGlobCache();
735     DumpInt(fp, globs.size());
736     for (const auto& p : globs) {
737       DumpString(fp, p.first);
738       const vector<string>& files = *p.second;
739 #if 0
740       unordered_set<string> dirs;
741       GetReadDirs(p.first, files, &dirs);
742       DumpInt(fp, dirs.size());
743       for (const string& dir : dirs) {
744         DumpString(fp, dir);
745       }
746 #endif
747       DumpInt(fp, files.size());
748       for (const string& file : files) {
749         DumpString(fp, file);
750       }
751     }
752 
753     const vector<CommandResult*>& crs = GetShellCommandResults();
754     DumpInt(fp, crs.size());
755     for (CommandResult* cr : crs) {
756       DumpInt(fp, static_cast<int>(cr->op));
757       DumpString(fp, cr->shell);
758       DumpString(fp, cr->shellflag);
759       DumpString(fp, cr->cmd);
760       DumpString(fp, cr->result);
761 
762       if (cr->op == CommandOp::FIND) {
763         vector<string> missing_dirs;
764         for (StringPiece fd : cr->find->finddirs) {
765           const string& d = ConcatDir(cr->find->chdir, fd);
766           if (!Exists(d))
767             missing_dirs.push_back(d);
768         }
769         DumpInt(fp, missing_dirs.size());
770         for (const string& d : missing_dirs) {
771           DumpString(fp, d);
772         }
773 
774         DumpInt(fp, cr->find->found_files->size());
775         for (StringPiece s : *cr->find->found_files) {
776           DumpString(fp, ConcatDir(cr->find->chdir, s));
777         }
778 
779         DumpInt(fp, cr->find->read_dirs->size());
780         for (StringPiece s : *cr->find->read_dirs) {
781           DumpString(fp, ConcatDir(cr->find->chdir, s));
782         }
783       }
784     }
785 
786     DumpString(fp, orig_args);
787 
788     fclose(fp);
789 
790     rename(GetStampTempFilename().c_str(), GetNinjaStampFilename().c_str());
791   }
792 
793   CommandEvaluator ce_;
794   Evaluator* ev_;
795   FILE* fp_;
796   SymbolSet done_;
797   int rule_id_;
798   bool use_goma_;
799   string gomacc_;
800   string shell_;
801   string shell_flags_;
802   map<string, string> used_envs_;
803   string kati_binary_;
804   const double start_time_;
805   vector<NinjaNode*> nodes_;
806 
807   mutex mu_;
808   const DepNode* default_target_;
809 };
810 
GetNinjaFilename()811 string GetNinjaFilename() {
812   return NinjaGenerator::GetFilename("build%s.ninja");
813 }
814 
GetNinjaShellScriptFilename()815 string GetNinjaShellScriptFilename() {
816   return NinjaGenerator::GetFilename("ninja%s.sh");
817 }
818 
GetNinjaStampFilename()819 string GetNinjaStampFilename() {
820   return NinjaGenerator::GetFilename(".kati_stamp%s");
821 }
822 
GenerateNinja(const vector<NamedDepNode> & nodes,Evaluator * ev,const string & orig_args,double start_time)823 void GenerateNinja(const vector<NamedDepNode>& nodes,
824                    Evaluator* ev,
825                    const string& orig_args,
826                    double start_time) {
827   NinjaGenerator ng(ev, start_time);
828   ng.Generate(nodes, orig_args);
829 }
830