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, 204 const string& orig_args) { 205 unlink(GetNinjaStampFilename().c_str()); 206 PopulateNinjaNodes(nodes); 207 GenerateNinja(); 208 GenerateShell(); 209 GenerateStamp(orig_args); 210 } 211 212 static string GetStampTempFilename() { 213 return GetFilename(".kati_stamp%s.tmp"); 214 } 215 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: 224 void PopulateNinjaNodes(const vector<DepNode*>& nodes) { 225 ScopedTimeReporter tr("ninja gen (eval)"); 226 for (DepNode* node : nodes) { 227 PopulateNinjaNode(node); 228 } 229 } 230 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 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 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 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 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 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 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 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 523 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 541 string EscapeBuildTarget(Symbol s) const { 542 return EscapeNinja(s.str()); 543 } 544 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 577 static string GetEnvScriptFilename() { 578 return GetFilename("env%s.sh"); 579 } 580 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 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 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 799 string GetNinjaFilename() { 800 return NinjaGenerator::GetFilename("build%s.ninja"); 801 } 802 803 string GetNinjaShellScriptFilename() { 804 return NinjaGenerator::GetFilename("ninja%s.sh"); 805 } 806 807 string GetNinjaStampFilename() { 808 return NinjaGenerator::GetFilename(".kati_stamp%s"); 809 } 810 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