1 // Copyright 2016 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 "regen.h" 18 19 #include <sys/stat.h> 20 21 #include <algorithm> 22 #include <memory> 23 #include <mutex> 24 #include <vector> 25 26 #include "fileutil.h" 27 #include "find.h" 28 #include "func.h" 29 #include "io.h" 30 #include "log.h" 31 #include "ninja.h" 32 #include "stats.h" 33 #include "strutil.h" 34 #include "thread_pool.h" 35 36 namespace { 37 38 #define RETURN_TRUE \ 39 do { \ 40 if (g_flags.dump_kati_stamp) \ 41 needs_regen_ = true; \ 42 else \ 43 return true; \ 44 } while (0) 45 46 bool ShouldIgnoreDirty(StringPiece s) { 47 Pattern pat(g_flags.ignore_dirty_pattern); 48 Pattern nopat(g_flags.no_ignore_dirty_pattern); 49 return pat.Match(s) && !nopat.Match(s); 50 } 51 52 class StampChecker { 53 struct GlobResult { 54 string pat; 55 vector<string> result; 56 }; 57 58 struct ShellResult { 59 CommandOp op; 60 string shell; 61 string shellflag; 62 string cmd; 63 string result; 64 vector<string> missing_dirs; 65 vector<string> files; 66 vector<string> read_dirs; 67 }; 68 69 public: 70 StampChecker() : needs_regen_(false) {} 71 72 ~StampChecker() { 73 for (GlobResult* gr : globs_) { 74 delete gr; 75 } 76 for (ShellResult* sr : commands_) { 77 delete sr; 78 } 79 } 80 81 bool NeedsRegen(double start_time, const string& orig_args) { 82 if (IsMissingOutputs()) 83 RETURN_TRUE; 84 85 if (CheckStep1(orig_args)) 86 RETURN_TRUE; 87 88 if (CheckStep2()) 89 RETURN_TRUE; 90 91 if (!needs_regen_) { 92 FILE* fp = fopen(GetNinjaStampFilename().c_str(), "rb+"); 93 if (!fp) 94 return true; 95 ScopedFile sfp(fp); 96 if (fseek(fp, 0, SEEK_SET) < 0) 97 PERROR("fseek"); 98 size_t r = fwrite(&start_time, sizeof(start_time), 1, fp); 99 CHECK(r == 1); 100 } 101 return needs_regen_; 102 } 103 104 private: 105 bool IsMissingOutputs() { 106 if (!Exists(GetNinjaFilename())) { 107 fprintf(stderr, "%s is missing, regenerating...\n", 108 GetNinjaFilename().c_str()); 109 return true; 110 } 111 if (!Exists(GetNinjaShellScriptFilename())) { 112 fprintf(stderr, "%s is missing, regenerating...\n", 113 GetNinjaShellScriptFilename().c_str()); 114 return true; 115 } 116 return false; 117 } 118 119 bool CheckStep1(const string& orig_args) { 120 #define LOAD_INT(fp) \ 121 ({ \ 122 int v = LoadInt(fp); \ 123 if (v < 0) { \ 124 fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); \ 125 RETURN_TRUE; \ 126 } \ 127 v; \ 128 }) 129 130 #define LOAD_STRING(fp, s) \ 131 ({ \ 132 if (!LoadString(fp, s)) { \ 133 fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); \ 134 RETURN_TRUE; \ 135 } \ 136 }) 137 138 const string& stamp_filename = GetNinjaStampFilename(); 139 FILE* fp = fopen(stamp_filename.c_str(), "rb"); 140 if (!fp) { 141 if (g_flags.regen_debug) 142 printf("%s: %s\n", stamp_filename.c_str(), strerror(errno)); 143 return true; 144 } 145 ScopedFile sfp(fp); 146 147 double gen_time; 148 size_t r = fread(&gen_time, sizeof(gen_time), 1, fp); 149 gen_time_ = gen_time; 150 if (r != 1) { 151 fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); 152 RETURN_TRUE; 153 } 154 if (g_flags.regen_debug) 155 printf("Generated time: %f\n", gen_time); 156 157 string s, s2; 158 int num_files = LOAD_INT(fp); 159 for (int i = 0; i < num_files; i++) { 160 LOAD_STRING(fp, &s); 161 double ts = GetTimestamp(s); 162 if (gen_time < ts) { 163 if (g_flags.regen_ignoring_kati_binary) { 164 string kati_binary; 165 GetExecutablePath(&kati_binary); 166 if (s == kati_binary) { 167 fprintf(stderr, "%s was modified, ignored.\n", s.c_str()); 168 continue; 169 } 170 } 171 if (ShouldIgnoreDirty(s)) { 172 if (g_flags.regen_debug) 173 printf("file %s: ignored (%f)\n", s.c_str(), ts); 174 continue; 175 } 176 if (g_flags.dump_kati_stamp) 177 printf("file %s: dirty (%f)\n", s.c_str(), ts); 178 else 179 fprintf(stderr, "%s was modified, regenerating...\n", s.c_str()); 180 RETURN_TRUE; 181 } else if (g_flags.dump_kati_stamp) { 182 printf("file %s: clean (%f)\n", s.c_str(), ts); 183 } 184 } 185 186 int num_undefineds = LOAD_INT(fp); 187 for (int i = 0; i < num_undefineds; i++) { 188 LOAD_STRING(fp, &s); 189 if (getenv(s.c_str())) { 190 if (g_flags.dump_kati_stamp) { 191 printf("env %s: dirty (unset => %s)\n", s.c_str(), getenv(s.c_str())); 192 } else { 193 fprintf(stderr, "Environment variable %s was set, regenerating...\n", 194 s.c_str()); 195 } 196 RETURN_TRUE; 197 } else if (g_flags.dump_kati_stamp) { 198 printf("env %s: clean (unset)\n", s.c_str()); 199 } 200 } 201 202 int num_envs = LOAD_INT(fp); 203 for (int i = 0; i < num_envs; i++) { 204 LOAD_STRING(fp, &s); 205 StringPiece val(getenv(s.c_str())); 206 LOAD_STRING(fp, &s2); 207 if (val != s2) { 208 if (g_flags.dump_kati_stamp) { 209 printf("env %s: dirty (%s => %.*s)\n", s.c_str(), s2.c_str(), 210 SPF(val)); 211 } else { 212 fprintf(stderr, 213 "Environment variable %s was modified (%s => %.*s), " 214 "regenerating...\n", 215 s.c_str(), s2.c_str(), SPF(val)); 216 } 217 RETURN_TRUE; 218 } else if (g_flags.dump_kati_stamp) { 219 printf("env %s: clean (%.*s)\n", s.c_str(), SPF(val)); 220 } 221 } 222 223 int num_globs = LOAD_INT(fp); 224 string pat; 225 for (int i = 0; i < num_globs; i++) { 226 GlobResult* gr = new GlobResult; 227 globs_.push_back(gr); 228 229 LOAD_STRING(fp, &gr->pat); 230 int num_files = LOAD_INT(fp); 231 gr->result.resize(num_files); 232 for (int j = 0; j < num_files; j++) { 233 LOAD_STRING(fp, &gr->result[j]); 234 } 235 } 236 237 int num_crs = LOAD_INT(fp); 238 for (int i = 0; i < num_crs; i++) { 239 ShellResult* sr = new ShellResult; 240 commands_.push_back(sr); 241 sr->op = static_cast<CommandOp>(LOAD_INT(fp)); 242 LOAD_STRING(fp, &sr->shell); 243 LOAD_STRING(fp, &sr->shellflag); 244 LOAD_STRING(fp, &sr->cmd); 245 LOAD_STRING(fp, &sr->result); 246 247 if (sr->op == CommandOp::FIND) { 248 int num_missing_dirs = LOAD_INT(fp); 249 for (int j = 0; j < num_missing_dirs; j++) { 250 LOAD_STRING(fp, &s); 251 sr->missing_dirs.push_back(s); 252 } 253 int num_files = LOAD_INT(fp); 254 for (int j = 0; j < num_files; j++) { 255 LOAD_STRING(fp, &s); 256 sr->files.push_back(s); 257 } 258 int num_read_dirs = LOAD_INT(fp); 259 for (int j = 0; j < num_read_dirs; j++) { 260 LOAD_STRING(fp, &s); 261 sr->read_dirs.push_back(s); 262 } 263 } 264 } 265 266 LoadString(fp, &s); 267 if (orig_args != s) { 268 fprintf(stderr, "arguments changed, regenerating...\n"); 269 RETURN_TRUE; 270 } 271 272 return needs_regen_; 273 } 274 275 bool CheckGlobResult(const GlobResult* gr, string* err) { 276 COLLECT_STATS("glob time (regen)"); 277 vector<string>* files; 278 Glob(gr->pat.c_str(), &files); 279 bool needs_regen = files->size() != gr->result.size(); 280 for (size_t i = 0; i < gr->result.size(); i++) { 281 if (!needs_regen) { 282 if ((*files)[i] != gr->result[i]) { 283 needs_regen = true; 284 break; 285 } 286 } 287 } 288 if (needs_regen) { 289 if (ShouldIgnoreDirty(gr->pat)) { 290 if (g_flags.dump_kati_stamp) { 291 printf("wildcard %s: ignored\n", gr->pat.c_str()); 292 } 293 return false; 294 } 295 if (g_flags.dump_kati_stamp) { 296 printf("wildcard %s: dirty\n", gr->pat.c_str()); 297 } else { 298 *err = StringPrintf("wildcard(%s) was changed, regenerating...\n", 299 gr->pat.c_str()); 300 } 301 } else if (g_flags.dump_kati_stamp) { 302 printf("wildcard %s: clean\n", gr->pat.c_str()); 303 } 304 return needs_regen; 305 } 306 307 bool ShouldRunCommand(const ShellResult* sr) { 308 if (sr->op != CommandOp::FIND) 309 return true; 310 311 COLLECT_STATS("stat time (regen)"); 312 for (const string& dir : sr->missing_dirs) { 313 if (Exists(dir)) 314 return true; 315 } 316 for (const string& file : sr->files) { 317 if (!Exists(file)) 318 return true; 319 } 320 for (const string& dir : sr->read_dirs) { 321 // We assume we rarely do a significant change for the top 322 // directory which affects the results of find command. 323 if (dir == "" || dir == "." || ShouldIgnoreDirty(dir)) 324 continue; 325 326 struct stat st; 327 if (lstat(dir.c_str(), &st) != 0) { 328 return true; 329 } 330 double ts = GetTimestampFromStat(st); 331 if (gen_time_ < ts) { 332 return true; 333 } 334 if (S_ISLNK(st.st_mode)) { 335 ts = GetTimestamp(dir); 336 if (ts < 0 || gen_time_ < ts) 337 return true; 338 } 339 } 340 return false; 341 } 342 343 bool CheckShellResult(const ShellResult* sr, string* err) { 344 if (sr->op == CommandOp::READ_MISSING) { 345 if (Exists(sr->cmd)) { 346 if (g_flags.dump_kati_stamp) 347 printf("file %s: dirty\n", sr->cmd.c_str()); 348 else 349 *err = StringPrintf("$(file <%s) was changed, regenerating...\n", 350 sr->cmd.c_str()); 351 return true; 352 } 353 if (g_flags.dump_kati_stamp) 354 printf("file %s: clean\n", sr->cmd.c_str()); 355 return false; 356 } 357 358 if (sr->op == CommandOp::READ) { 359 double ts = GetTimestamp(sr->cmd); 360 if (gen_time_ < ts) { 361 if (g_flags.dump_kati_stamp) 362 printf("file %s: dirty\n", sr->cmd.c_str()); 363 else 364 *err = StringPrintf("$(file <%s) was changed, regenerating...\n", 365 sr->cmd.c_str()); 366 return true; 367 } 368 if (g_flags.dump_kati_stamp) 369 printf("file %s: clean\n", sr->cmd.c_str()); 370 return false; 371 } 372 373 if (sr->op == CommandOp::WRITE || sr->op == CommandOp::APPEND) { 374 FILE* f = 375 fopen(sr->cmd.c_str(), (sr->op == CommandOp::WRITE) ? "wb" : "ab"); 376 if (f == NULL) { 377 PERROR("fopen"); 378 } 379 380 if (fwrite(&sr->result[0], sr->result.size(), 1, f) != 1) { 381 PERROR("fwrite"); 382 } 383 384 if (fclose(f) != 0) { 385 PERROR("fclose"); 386 } 387 388 if (g_flags.dump_kati_stamp) 389 printf("file %s: clean (write)\n", sr->cmd.c_str()); 390 return false; 391 } 392 393 if (!ShouldRunCommand(sr)) { 394 if (g_flags.regen_debug) 395 printf("shell %s: clean (no rerun)\n", sr->cmd.c_str()); 396 return false; 397 } 398 399 FindCommand fc; 400 if (fc.Parse(sr->cmd) && !fc.chdir.empty() && ShouldIgnoreDirty(fc.chdir)) { 401 if (g_flags.dump_kati_stamp) 402 printf("shell %s: ignored\n", sr->cmd.c_str()); 403 return false; 404 } 405 406 COLLECT_STATS_WITH_SLOW_REPORT("shell time (regen)", sr->cmd.c_str()); 407 string result; 408 RunCommand(sr->shell, sr->shellflag, sr->cmd, RedirectStderr::DEV_NULL, 409 &result); 410 FormatForCommandSubstitution(&result); 411 if (sr->result != result) { 412 if (g_flags.dump_kati_stamp) { 413 printf("shell %s: dirty\n", sr->cmd.c_str()); 414 } else { 415 *err = StringPrintf("$(shell %s) was changed, regenerating...\n", 416 sr->cmd.c_str()); 417 //*err += StringPrintf("%s => %s\n", expected.c_str(), result.c_str()); 418 } 419 return true; 420 } else if (g_flags.regen_debug) { 421 printf("shell %s: clean (rerun)\n", sr->cmd.c_str()); 422 } 423 return false; 424 } 425 426 bool CheckStep2() { 427 unique_ptr<ThreadPool> tp(NewThreadPool(g_flags.num_jobs)); 428 429 tp->Submit([this]() { 430 string err; 431 // TODO: Make glob cache thread safe and create a task for each glob. 432 for (GlobResult* gr : globs_) { 433 if (CheckGlobResult(gr, &err)) { 434 unique_lock<mutex> lock(mu_); 435 if (!needs_regen_) { 436 needs_regen_ = true; 437 msg_ = err; 438 } 439 break; 440 } 441 } 442 }); 443 444 tp->Submit([this]() { 445 for (ShellResult* sr : commands_) { 446 string err; 447 if (CheckShellResult(sr, &err)) { 448 unique_lock<mutex> lock(mu_); 449 if (!needs_regen_) { 450 needs_regen_ = true; 451 msg_ = err; 452 } 453 } 454 } 455 }); 456 457 tp->Wait(); 458 if (needs_regen_) { 459 fprintf(stderr, "%s", msg_.c_str()); 460 } 461 return needs_regen_; 462 } 463 464 private: 465 double gen_time_; 466 vector<GlobResult*> globs_; 467 vector<ShellResult*> commands_; 468 mutex mu_; 469 bool needs_regen_; 470 string msg_; 471 }; 472 473 } // namespace 474 475 bool NeedsRegen(double start_time, const string& orig_args) { 476 return StampChecker().NeedsRegen(start_time, orig_args); 477 } 478