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->EvalVar(kShellSym)); 188 if (g_flags.goma_dir) 189 gomacc_ = StringPrintf("%s/gomacc ", g_flags.goma_dir); 190 191 GetExecutablePath(&kati_binary_); 192 } 193 194 ~NinjaGenerator() { 195 ev_->set_avoid_io(false); 196 for (NinjaNode* nn : nodes_) 197 delete nn; 198 } 199 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 209 static string GetStampTempFilename() { 210 return GetFilename(".kati_stamp%s.tmp"); 211 } 212 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: 221 void PopulateNinjaNodes(const vector<DepNode*>& nodes) { 222 ScopedTimeReporter tr("ninja gen (eval)"); 223 for (DepNode* node : nodes) { 224 PopulateNinjaNode(node); 225 } 226 } 227 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 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 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 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 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 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 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 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 521 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 539 string EscapeBuildTarget(Symbol s) const { 540 return EscapeNinja(s.str()); 541 } 542 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 570 static string GetEnvScriptFilename() { 571 return GetFilename("env%s.sh"); 572 } 573 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 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 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 787 string GetNinjaFilename() { 788 return NinjaGenerator::GetFilename("build%s.ninja"); 789 } 790 791 string GetNinjaShellScriptFilename() { 792 return NinjaGenerator::GetFilename("ninja%s.sh"); 793 } 794 795 string GetNinjaStampFilename() { 796 return NinjaGenerator::GetFilename(".kati_stamp%s"); 797 } 798 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