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