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