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<DepNode * > & nodes,const string & orig_args)203 void Generate(const vector<DepNode*>& nodes,
204 const string& orig_args) {
205 unlink(GetNinjaStampFilename().c_str());
206 PopulateNinjaNodes(nodes);
207 GenerateNinja();
208 GenerateShell();
209 GenerateStamp(orig_args);
210 }
211
GetStampTempFilename()212 static string GetStampTempFilename() {
213 return GetFilename(".kati_stamp%s.tmp");
214 }
215
GetFilename(const char * fmt)216 static string GetFilename(const char* fmt) {
217 string r = g_flags.ninja_dir ? g_flags.ninja_dir : ".";
218 r += '/';
219 r += StringPrintf(fmt, g_flags.ninja_suffix ? g_flags.ninja_suffix : "");
220 return r;
221 }
222
223 private:
PopulateNinjaNodes(const vector<DepNode * > & nodes)224 void PopulateNinjaNodes(const vector<DepNode*>& nodes) {
225 ScopedTimeReporter tr("ninja gen (eval)");
226 for (DepNode* node : nodes) {
227 PopulateNinjaNode(node);
228 }
229 }
230
PopulateNinjaNode(DepNode * node)231 void PopulateNinjaNode(DepNode* node) {
232 auto p = done_.insert(node->output);
233 if (!p.second)
234 return;
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 (DepNode* d : node->deps) {
253 PopulateNinjaNode(d);
254 }
255 for (DepNode* d : node->order_onlys) {
256 PopulateNinjaNode(d);
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 ||
454 g_flags.goma_dir) && !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,
500 &cmd_buf, &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_
513 << " \"" << cmd_buf << "\"\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 // fall through.
534 default:
535 r += c;
536 }
537 }
538 return r;
539 }
540
EscapeBuildTarget(Symbol s) const541 string EscapeBuildTarget(Symbol s) const {
542 return EscapeNinja(s.str());
543 }
544
EmitBuild(NinjaNode * nn,const string & rule_name,bool use_local_pool,ostringstream * o)545 void EmitBuild(NinjaNode* nn, const string& rule_name,
546 bool use_local_pool, ostringstream* o) {
547 const DepNode* node = nn->node;
548 string target = EscapeBuildTarget(node->output);
549 *o << "build " << target << ": " << rule_name;
550 vector<Symbol> order_onlys;
551 if (node->is_phony) {
552 *o << " _kati_always_build_";
553 }
554 for (DepNode* d : node->deps) {
555 *o << " " << EscapeBuildTarget(d->output).c_str();
556 }
557 if (!node->order_onlys.empty()) {
558 *o << " ||";
559 for (DepNode* d : node->order_onlys) {
560 *o << " " << EscapeBuildTarget(d->output).c_str();
561 }
562 }
563 *o << "\n";
564 if (node->ninja_pool_var) {
565 string pool;
566 node->ninja_pool_var->Eval(ev_, &pool);
567 *o << " pool = " << pool << "\n";
568 } else if (use_local_pool) {
569 *o << " pool = local_pool\n";
570 }
571 if (node->is_default_target) {
572 unique_lock<mutex> lock(mu_);
573 default_target_ = node;
574 }
575 }
576
GetEnvScriptFilename()577 static string GetEnvScriptFilename() {
578 return GetFilename("env%s.sh");
579 }
580
GenerateNinja()581 void GenerateNinja() {
582 ScopedTimeReporter tr("ninja gen (emit)");
583 fp_ = fopen(GetNinjaFilename().c_str(), "wb");
584 if (fp_ == NULL)
585 PERROR("fopen(build.ninja) failed");
586
587 fprintf(fp_, "# Generated by kati %s\n", kGitVersion);
588 fprintf(fp_, "\n");
589
590 if (!used_envs_.empty()) {
591 fprintf(fp_, "# Environment variables used:\n");
592 for (const auto& p : used_envs_) {
593 fprintf(fp_, "# %s=%s\n", p.first.c_str(), p.second.c_str());
594 }
595 fprintf(fp_, "\n");
596 }
597
598 if (g_flags.ninja_dir) {
599 fprintf(fp_, "builddir = %s\n\n", g_flags.ninja_dir);
600 }
601
602 fprintf(fp_, "pool local_pool\n");
603 fprintf(fp_, " depth = %d\n\n", g_flags.num_jobs);
604
605 fprintf(fp_, "build _kati_always_build_: phony\n\n");
606
607 unique_ptr<ThreadPool> tp(NewThreadPool(g_flags.num_jobs));
608 CHECK(g_flags.num_jobs);
609 int num_nodes_per_task = nodes_.size() / (g_flags.num_jobs * 10) + 1;
610 int num_tasks = nodes_.size() / num_nodes_per_task + 1;
611 vector<ostringstream> bufs(num_tasks);
612 for (int i = 0; i < num_tasks; i++) {
613 tp->Submit([this, i, num_nodes_per_task, &bufs]() {
614 int l = min(num_nodes_per_task * (i + 1),
615 static_cast<int>(nodes_.size()));
616 for (int j = num_nodes_per_task * i; j < l; j++) {
617 EmitNode(nodes_[j], &bufs[i]);
618 }
619 });
620 }
621 tp->Wait();
622
623 for (const ostringstream& buf : bufs) {
624 fprintf(fp_, "%s", buf.str().c_str());
625 }
626
627 unordered_set<Symbol> used_env_vars(Vars::used_env_vars());
628 // PATH changes $(shell).
629 used_env_vars.insert(Intern("PATH"));
630 for (Symbol e : used_env_vars) {
631 StringPiece val(getenv(e.c_str()));
632 used_envs_.emplace(e.str(), val.as_string());
633 }
634
635 string default_targets;
636 if (g_flags.targets.empty() || g_flags.gen_all_targets) {
637 CHECK(default_target_);
638 default_targets = EscapeBuildTarget(default_target_->output);
639 } else {
640 for (Symbol s : g_flags.targets) {
641 if (!default_targets.empty())
642 default_targets += ' ';
643 default_targets += EscapeBuildTarget(s);
644 }
645 }
646 fprintf(fp_, "\n");
647 fprintf(fp_, "default %s\n", default_targets.c_str());
648
649 fclose(fp_);
650 }
651
GenerateShell()652 void GenerateShell() {
653 FILE* fp = fopen(GetEnvScriptFilename().c_str(), "wb");
654 if (fp == NULL)
655 PERROR("fopen(env.sh) failed");
656
657 fprintf(fp, "#!/bin/sh\n");
658 fprintf(fp, "# Generated by kati %s\n", kGitVersion);
659 fprintf(fp, "\n");
660
661 for (const auto& p : ev_->exports()) {
662 if (p.second) {
663 const string val = ev_->EvalVar(p.first);
664 fprintf(fp, "export '%s'='%s'\n", p.first.c_str(), val.c_str());
665 } else {
666 fprintf(fp, "unset '%s'\n", p.first.c_str());
667 }
668 }
669
670 fclose(fp);
671
672 fp = fopen(GetNinjaShellScriptFilename().c_str(), "wb");
673 if (fp == NULL)
674 PERROR("fopen(ninja.sh) failed");
675
676 fprintf(fp, "#!/bin/sh\n");
677 fprintf(fp, "# Generated by kati %s\n", kGitVersion);
678 fprintf(fp, "\n");
679
680 fprintf(fp, ". %s\n", GetEnvScriptFilename().c_str());
681
682 fprintf(fp, "exec ninja -f %s ", GetNinjaFilename().c_str());
683 if (g_flags.remote_num_jobs > 0) {
684 fprintf(fp, "-j%d ", g_flags.remote_num_jobs);
685 } else if (g_flags.goma_dir) {
686 fprintf(fp, "-j500 ");
687 }
688 fprintf(fp, "\"$@\"\n");
689
690 fclose(fp);
691
692 if (chmod(GetNinjaShellScriptFilename().c_str(), 0755) != 0)
693 PERROR("chmod ninja.sh failed");
694 }
695
GenerateStamp(const string & orig_args)696 void GenerateStamp(const string& orig_args) {
697 FILE* fp = fopen(GetStampTempFilename().c_str(), "wb");
698 CHECK(fp);
699
700 size_t r = fwrite(&start_time_, sizeof(start_time_), 1, fp);
701 CHECK(r == 1);
702
703 unordered_set<string> makefiles;
704 MakefileCacheManager::Get()->GetAllFilenames(&makefiles);
705 DumpInt(fp, makefiles.size() + 1);
706 DumpString(fp, kati_binary_);
707 for (const string& makefile : makefiles) {
708 DumpString(fp, makefile);
709 }
710
711 DumpInt(fp, Evaluator::used_undefined_vars().size());
712 for (Symbol v : Evaluator::used_undefined_vars()) {
713 DumpString(fp, v.str());
714 }
715
716 DumpInt(fp, used_envs_.size());
717 for (const auto& p : used_envs_) {
718 DumpString(fp, p.first);
719 DumpString(fp, p.second);
720 }
721
722 const unordered_map<string, vector<string>*>& globs = GetAllGlobCache();
723 DumpInt(fp, globs.size());
724 for (const auto& p : globs) {
725 DumpString(fp, p.first);
726 const vector<string>& files = *p.second;
727 #if 0
728 unordered_set<string> dirs;
729 GetReadDirs(p.first, files, &dirs);
730 DumpInt(fp, dirs.size());
731 for (const string& dir : dirs) {
732 DumpString(fp, dir);
733 }
734 #endif
735 DumpInt(fp, files.size());
736 for (const string& file : files) {
737 DumpString(fp, file);
738 }
739 }
740
741 const vector<CommandResult*>& crs = GetShellCommandResults();
742 DumpInt(fp, crs.size());
743 for (CommandResult* cr : crs) {
744 DumpInt(fp, static_cast<int>(cr->op));
745 DumpString(fp, cr->shell);
746 DumpString(fp, cr->shellflag);
747 DumpString(fp, cr->cmd);
748 DumpString(fp, cr->result);
749
750 if (cr->op == CommandOp::FIND) {
751 vector<string> missing_dirs;
752 for (StringPiece fd : cr->find->finddirs) {
753 const string& d = ConcatDir(cr->find->chdir, fd);
754 if (!Exists(d))
755 missing_dirs.push_back(d);
756 }
757 DumpInt(fp, missing_dirs.size());
758 for (const string& d : missing_dirs) {
759 DumpString(fp, d);
760 }
761
762 DumpInt(fp, cr->find->found_files->size());
763 for (StringPiece s : *cr->find->found_files) {
764 DumpString(fp, ConcatDir(cr->find->chdir, s));
765 }
766
767 DumpInt(fp, cr->find->read_dirs->size());
768 for (StringPiece s : *cr->find->read_dirs) {
769 DumpString(fp, ConcatDir(cr->find->chdir, s));
770 }
771 }
772 }
773
774 DumpString(fp, orig_args);
775
776 fclose(fp);
777
778 rename(GetStampTempFilename().c_str(), GetNinjaStampFilename().c_str());
779 }
780
781 CommandEvaluator ce_;
782 Evaluator* ev_;
783 FILE* fp_;
784 unordered_set<Symbol> done_;
785 int rule_id_;
786 bool use_goma_;
787 string gomacc_;
788 string shell_;
789 string shell_flags_;
790 map<string, string> used_envs_;
791 string kati_binary_;
792 const double start_time_;
793 vector<NinjaNode*> nodes_;
794
795 mutex mu_;
796 const DepNode* default_target_;
797 };
798
GetNinjaFilename()799 string GetNinjaFilename() {
800 return NinjaGenerator::GetFilename("build%s.ninja");
801 }
802
GetNinjaShellScriptFilename()803 string GetNinjaShellScriptFilename() {
804 return NinjaGenerator::GetFilename("ninja%s.sh");
805 }
806
GetNinjaStampFilename()807 string GetNinjaStampFilename() {
808 return NinjaGenerator::GetFilename(".kati_stamp%s");
809 }
810
GenerateNinja(const vector<DepNode * > & nodes,Evaluator * ev,const string & orig_args,double start_time)811 void GenerateNinja(const vector<DepNode*>& nodes,
812 Evaluator* ev,
813 const string& orig_args,
814 double start_time) {
815 NinjaGenerator ng(ev, start_time);
816 ng.Generate(nodes, orig_args);
817 }
818