blob: a40ebf37a7323fe7c5ed475c42c140a113b1a378 [file] [log] [blame]
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +09001// 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>
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +090020#include <memory>
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +090021#include <vector>
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +090022
23#include "fileutil.h"
24#include "find.h"
25#include "io.h"
26#include "log.h"
Shinichiro Hamaji702befc2016-01-27 17:21:39 +090027#include "mutex.h"
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +090028#include "ninja.h"
29#include "stats.h"
30#include "strutil.h"
Shinichiro Hamaji702befc2016-01-27 17:21:39 +090031#include "thread_pool.h"
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +090032
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +090033namespace {
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
42bool ShouldIgnoreDirty(StringPiece s) {
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +090043 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
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +090048class StampChecker {
49 struct GlobResult {
50 string pat;
51 vector<string> result;
52 };
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +090053
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +090054 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) {
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900115#define LOAD_INT(fp) ({ \
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900116 int v = LoadInt(fp); \
117 if (v < 0) { \
118 fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); \
119 RETURN_TRUE; \
120 } \
121 v; \
122 })
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900123
124#define LOAD_STRING(fp, s) ({ \
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900125 if (!LoadString(fp, s)) { \
126 fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); \
127 RETURN_TRUE; \
128 } \
129 })
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900130
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900131 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);
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900139
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900140 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 }
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900147 if (g_flags.dump_kati_stamp)
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900148 printf("Generated time: %f\n", gen_time);
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900149
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900150 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);
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900167 continue;
168 }
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900169 if (g_flags.dump_kati_stamp)
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900170 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);
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900176 }
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900177 }
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900178
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900179 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());
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900192 }
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900193 }
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900194
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900195 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));
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900212 }
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900213 }
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900214
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900215 int num_globs = LOAD_INT(fp);
216 string pat;
217 for (int i = 0; i < num_globs; i++) {
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900218 GlobResult* gr = new GlobResult;
219 globs_.push_back(gr);
220
221 LOAD_STRING(fp, &gr->pat);
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900222 int num_files = LOAD_INT(fp);
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900223 gr->result.resize(num_files);
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900224 for (int j = 0; j < num_files; j++) {
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900225 LOAD_STRING(fp, &gr->result[j]);
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900226 }
227 }
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900228
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_;
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900258 }
259
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900260 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;
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900271 }
272 }
273 }
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900274 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 }
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900331
332 FindCommand fc;
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900333 if (fc.Parse(sr->cmd) &&
334 !fc.chdir.empty() && ShouldIgnoreDirty(fc.chdir)) {
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900335 if (g_flags.dump_kati_stamp)
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900336 printf("shell %s: ignored\n", sr->cmd.c_str());
337 return false;
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900338 }
339
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900340 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());
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900351 }
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900352 return true;
353 } else if (g_flags.dump_kati_stamp) {
354 printf("shell %s: clean (rerun)\n", sr->cmd.c_str());
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900355 }
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900356 return false;
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900357 }
358
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900359 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_;
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900395 }
396
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900397 private:
398 double gen_time_;
399 vector<GlobResult*> globs_;
400 vector<ShellResult*> commands_;
401 mutex mu_;
402 bool needs_regen_;
403 string msg_;
404};
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900405
Shinichiro Hamaji6bbf9e22016-01-19 18:59:13 +0900406} // namespace
407
408bool NeedsRegen(double start_time, const string& orig_args) {
409 return StampChecker().NeedsRegen(start_time, orig_args);
Shinichiro Hamaji8ef0ce52016-01-19 18:07:43 +0900410}