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