aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.ckati1
-rw-r--r--main.cc1
-rw-r--r--ninja.cc317
-rw-r--r--ninja.h4
-rw-r--r--regen.cc297
-rw-r--r--regen.h24
6 files changed, 349 insertions, 295 deletions
diff --git a/Makefile.ckati b/Makefile.ckati
index 9c1f016..0ba8385 100644
--- a/Makefile.ckati
+++ b/Makefile.ckati
@@ -38,6 +38,7 @@ KATI_CXX_SRCS := \
main.cc \
ninja.cc \
parser.cc \
+ regen.cc \
rule.cc \
stats.cc \
stmt.cc \
diff --git a/main.cc b/main.cc
index ff2b751..a64a37e 100644
--- a/main.cc
+++ b/main.cc
@@ -33,6 +33,7 @@
#include "log.h"
#include "ninja.h"
#include "parser.h"
+#include "regen.h"
#include "stats.h"
#include "stmt.h"
#include "string_piece.h"
diff --git a/ninja.cc b/ninja.cc
index 5b4b7df..f14610f 100644
--- a/ninja.cc
+++ b/ninja.cc
@@ -21,7 +21,6 @@
#include <sys/stat.h>
#include <unistd.h>
-#include <algorithm>
#include <map>
#include <string>
#include <unordered_map>
@@ -189,28 +188,23 @@ class NinjaGenerator {
void Generate(const vector<DepNode*>& nodes,
const string& orig_args) {
- unlink(GetStampFilename().c_str());
+ unlink(GetNinjaStampFilename().c_str());
GenerateNinja(nodes, orig_args);
GenerateShell();
GenerateStamp(orig_args);
}
- static string GetNinjaFilename() {
- return GetFilename("build%s.ninja");
- }
-
- static string GetShellScriptFilename() {
- return GetFilename("ninja%s.sh");
- }
-
- static string GetStampFilename() {
- return GetFilename(".kati_stamp%s");
- }
-
static string GetStampTempFilename() {
return GetFilename(".kati_stamp%s.tmp");
}
+ static string GetFilename(const char* fmt) {
+ string r = g_flags.ninja_dir ? g_flags.ninja_dir : ".";
+ r += '/';
+ r += StringPrintf(fmt, g_flags.ninja_suffix ? g_flags.ninja_suffix : "");
+ return r;
+ }
+
private:
string GenRuleName() {
return StringPrintf("rule%d", rule_id_++);
@@ -579,13 +573,6 @@ class NinjaGenerator {
return GetFilename("env%s.sh");
}
- static string GetFilename(const char* fmt) {
- string r = g_flags.ninja_dir ? g_flags.ninja_dir : ".";
- r += '/';
- r += StringPrintf(fmt, g_flags.ninja_suffix ? g_flags.ninja_suffix : "");
- return r;
- }
-
void GenerateNinja(const vector<DepNode*>& nodes,
const string& orig_args) {
fp_ = fopen(GetNinjaFilename().c_str(), "wb");
@@ -663,7 +650,7 @@ class NinjaGenerator {
fclose(fp);
- fp = fopen(GetShellScriptFilename().c_str(), "wb");
+ fp = fopen(GetNinjaShellScriptFilename().c_str(), "wb");
if (fp == NULL)
PERROR("fopen(ninja.sh) failed");
@@ -683,7 +670,7 @@ class NinjaGenerator {
fclose(fp);
- if (chmod(GetShellScriptFilename().c_str(), 0755) != 0)
+ if (chmod(GetNinjaShellScriptFilename().c_str(), 0755) != 0)
PERROR("chmod ninja.sh failed");
}
@@ -766,7 +753,7 @@ class NinjaGenerator {
fclose(fp);
- rename(GetStampTempFilename().c_str(), GetStampFilename().c_str());
+ rename(GetStampTempFilename().c_str(), GetNinjaStampFilename().c_str());
}
CommandEvaluator ce_;
@@ -782,6 +769,18 @@ class NinjaGenerator {
DepNode* default_target_;
};
+string GetNinjaFilename() {
+ return NinjaGenerator::GetFilename("build%s.ninja");
+}
+
+string GetNinjaShellScriptFilename() {
+ return NinjaGenerator::GetFilename("ninja%s.sh");
+}
+
+string GetNinjaStampFilename() {
+ return NinjaGenerator::GetFilename(".kati_stamp%s");
+}
+
void GenerateNinja(const vector<DepNode*>& nodes,
Evaluator* ev,
const string& orig_args,
@@ -789,273 +788,3 @@ void GenerateNinja(const vector<DepNode*>& nodes,
NinjaGenerator ng(ev, start_time);
ng.Generate(nodes, orig_args);
}
-
-static bool ShouldIgnoreDirty(StringPiece s) {
- Pattern pat(g_flags.ignore_dirty_pattern);
- Pattern nopat(g_flags.no_ignore_dirty_pattern);
- return pat.Match(s) && !nopat.Match(s);
-}
-
-bool NeedsRegen(double start_time, const string& orig_args) {
- bool retval = false;
-#define RETURN_TRUE do { \
- if (g_flags.dump_kati_stamp) \
- retval = true; \
- else \
- return true; \
- } while (0)
-
-#define LOAD_INT(fp) ({ \
- int v = LoadInt(fp); \
- if (v < 0) { \
- fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); \
- RETURN_TRUE; \
- } \
- v; \
- })
-
-#define LOAD_STRING(fp, s) ({ \
- if (!LoadString(fp, s)) { \
- fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); \
- RETURN_TRUE; \
- } \
- })
-
- if (!Exists(NinjaGenerator::GetNinjaFilename())) {
- fprintf(stderr, "%s is missing, regenerating...\n",
- NinjaGenerator::GetNinjaFilename().c_str());
- return true;
- }
- if (!Exists(NinjaGenerator::GetShellScriptFilename())) {
- fprintf(stderr, "%s is missing, regenerating...\n",
- NinjaGenerator::GetShellScriptFilename().c_str());
- return true;
- }
-
- const string& stamp_filename = NinjaGenerator::GetStampFilename();
- FILE* fp = fopen(stamp_filename.c_str(), "rb+");
- if (!fp) {
- if (g_flags.dump_kati_stamp)
- printf("%s: %s\n", stamp_filename.c_str(), strerror(errno));
- return true;
- }
- ScopedFile sfp(fp);
-
- double gen_time;
- size_t r = fread(&gen_time, sizeof(gen_time), 1, fp);
- if (r != 1) {
- fprintf(stderr, "incomplete kati_stamp, regenerating...\n");
- RETURN_TRUE;
- }
- if (g_flags.dump_kati_stamp)
- printf("Generated time: %f\n", gen_time);
-
- string s, s2;
- int num_files = LOAD_INT(fp);
- for (int i = 0; i < num_files; i++) {
- LOAD_STRING(fp, &s);
- double ts = GetTimestamp(s);
- if (gen_time < ts) {
- if (g_flags.regen_ignoring_kati_binary) {
- string kati_binary;
- GetExecutablePath(&kati_binary);
- if (s == kati_binary) {
- fprintf(stderr, "%s was modified, ignored.\n", s.c_str());
- continue;
- }
- }
- if (ShouldIgnoreDirty(s)) {
- if (g_flags.dump_kati_stamp)
- printf("file %s: ignored (%f)\n", s.c_str(), ts);
- continue;
- }
- if (g_flags.dump_kati_stamp)
- printf("file %s: dirty (%f)\n", s.c_str(), ts);
- else
- fprintf(stderr, "%s was modified, regenerating...\n", s.c_str());
- RETURN_TRUE;
- } else if (g_flags.dump_kati_stamp) {
- printf("file %s: clean (%f)\n", s.c_str(), ts);
- }
- }
-
- int num_undefineds = LOAD_INT(fp);
- for (int i = 0; i < num_undefineds; i++) {
- LOAD_STRING(fp, &s);
- if (getenv(s.c_str())) {
- if (g_flags.dump_kati_stamp) {
- printf("env %s: dirty (unset => %s)\n", s.c_str(), getenv(s.c_str()));
- } else {
- fprintf(stderr, "Environment variable %s was set, regenerating...\n",
- s.c_str());
- }
- RETURN_TRUE;
- } else if (g_flags.dump_kati_stamp) {
- printf("env %s: clean (unset)\n", s.c_str());
- }
- }
-
- int num_envs = LOAD_INT(fp);
- for (int i = 0; i < num_envs; i++) {
- LOAD_STRING(fp, &s);
- StringPiece val(getenv(s.c_str()));
- LOAD_STRING(fp, &s2);
- if (val != s2) {
- if (g_flags.dump_kati_stamp) {
- printf("env %s: dirty (%s => %.*s)\n",
- s.c_str(), s2.c_str(), SPF(val));
- } else {
- fprintf(stderr, "Environment variable %s was modified (%s => %.*s), "
- "regenerating...\n",
- s.c_str(), s2.c_str(), SPF(val));
- }
- RETURN_TRUE;
- } else if (g_flags.dump_kati_stamp) {
- printf("env %s: clean (%.*s)\n", s.c_str(), SPF(val));
- }
- }
-
- {
- int num_globs = LOAD_INT(fp);
- string pat;
- for (int i = 0; i < num_globs; i++) {
- COLLECT_STATS("glob time (regen)");
- LOAD_STRING(fp, &pat);
-#if 0
- bool needs_reglob = false;
- int num_dirs = LOAD_INT(fp);
- for (int j = 0; j < num_dirs; j++) {
- LOAD_STRING(fp, &s);
- // TODO: Handle removed files properly.
- needs_reglob |= gen_time < GetTimestamp(s);
- }
-#endif
- int num_files = LOAD_INT(fp);
- vector<string>* files;
- Glob(pat.c_str(), &files);
- sort(files->begin(), files->end());
- bool needs_regen = files->size() != static_cast<size_t>(num_files);
- for (int j = 0; j < num_files; j++) {
- LOAD_STRING(fp, &s);
- if (!needs_regen) {
- if ((*files)[j] != s) {
- needs_regen = true;
- break;
- }
- }
- }
- if (needs_regen) {
- if (ShouldIgnoreDirty(pat)) {
- if (g_flags.dump_kati_stamp) {
- printf("wildcard %s: ignored\n", pat.c_str());
- }
- continue;
- }
- if (g_flags.dump_kati_stamp) {
- printf("wildcard %s: dirty\n", pat.c_str());
- } else {
- fprintf(stderr, "wildcard(%s) was changed, regenerating...\n",
- pat.c_str());
- }
- RETURN_TRUE;
- } else if (g_flags.dump_kati_stamp) {
- printf("wildcard %s: clean\n", pat.c_str());
- }
- }
- }
-
- int num_crs = LOAD_INT(fp);
- for (int i = 0; i < num_crs; i++) {
- string cmd, expected;
- LOAD_STRING(fp, &cmd);
- LOAD_STRING(fp, &expected);
-
- {
- COLLECT_STATS("stat time (regen)");
- bool has_condition = LOAD_INT(fp);
- if (has_condition) {
- bool should_run_command = false;
-
- int num_missing_dirs = LOAD_INT(fp);
- for (int j = 0; j < num_missing_dirs; j++) {
- LOAD_STRING(fp, &s);
- should_run_command |= Exists(s);
- }
-
- int num_read_dirs = LOAD_INT(fp);
- for (int j = 0; j < num_read_dirs; j++) {
- LOAD_STRING(fp, &s);
- // We assume we rarely do a significant change for the top
- // directory which affects the results of find command.
- if (s == "" || s == "." || ShouldIgnoreDirty(s))
- continue;
-
- struct stat st;
- if (lstat(s.c_str(), &st) != 0) {
- should_run_command = true;
- continue;
- }
- double ts = GetTimestampFromStat(st);
- if (gen_time < ts) {
- should_run_command = true;
- continue;
- }
- if (S_ISLNK(st.st_mode)) {
- ts = GetTimestamp(s);
- should_run_command |= (ts < 0 || gen_time < ts);
- }
- }
-
- if (!should_run_command) {
- if (g_flags.dump_kati_stamp)
- printf("shell %s: clean (no rerun)\n", cmd.c_str());
- continue;
- }
- }
- }
-
- FindCommand fc;
- if (fc.Parse(cmd) && !fc.chdir.empty() && ShouldIgnoreDirty(fc.chdir)) {
- if (g_flags.dump_kati_stamp)
- printf("shell %s: ignored\n", cmd.c_str());
- continue;
- }
-
- {
- COLLECT_STATS_WITH_SLOW_REPORT("shell time (regen)", cmd.c_str());
- string result;
- RunCommand("/bin/sh", cmd, RedirectStderr::DEV_NULL, &result);
- FormatForCommandSubstitution(&result);
- if (expected != result) {
- if (g_flags.dump_kati_stamp) {
- printf("shell %s: dirty\n", cmd.c_str());
- } else {
- fprintf(stderr, "$(shell %s) was changed, regenerating...\n",
- cmd.c_str());
-#if 0
- fprintf(stderr, "%s => %s\n",
- expected.c_str(), result.c_str());
-#endif
- }
- RETURN_TRUE;
- } else if (g_flags.dump_kati_stamp) {
- printf("shell %s: clean (rerun)\n", cmd.c_str());
- }
- }
- }
-
- LoadString(fp, &s);
- if (orig_args != s) {
- fprintf(stderr, "arguments changed, regenerating...\n");
- RETURN_TRUE;
- }
-
- if (!retval) {
- if (fseek(fp, 0, SEEK_SET) < 0)
- PERROR("fseek");
- size_t r = fwrite(&start_time, sizeof(start_time), 1, fp);
- CHECK(r == 1);
- }
-
- return retval;
-}
diff --git a/ninja.h b/ninja.h
index f117f2b..89683e8 100644
--- a/ninja.h
+++ b/ninja.h
@@ -32,7 +32,9 @@ void GenerateNinja(const vector<DepNode*>& nodes,
const string& orig_args,
double start_time);
-bool NeedsRegen(double start_time, const string& orig_args);
+string GetNinjaFilename();
+string GetNinjaShellScriptFilename();
+string GetNinjaStampFilename();
// Exposed only for test.
bool GetDepfileFromCommand(string* cmd, string* out);
diff --git a/regen.cc b/regen.cc
new file mode 100644
index 0000000..2c7cc73
--- /dev/null
+++ b/regen.cc
@@ -0,0 +1,297 @@
+// Copyright 2016 Google Inc. All rights reserved
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "regen.h"
+
+#include <sys/stat.h>
+
+#include <algorithm>
+
+#include "fileutil.h"
+#include "find.h"
+#include "io.h"
+#include "log.h"
+#include "ninja.h"
+#include "stats.h"
+#include "strutil.h"
+
+static bool ShouldIgnoreDirty(StringPiece s) {
+ Pattern pat(g_flags.ignore_dirty_pattern);
+ Pattern nopat(g_flags.no_ignore_dirty_pattern);
+ return pat.Match(s) && !nopat.Match(s);
+}
+
+bool NeedsRegen(double start_time, const string& orig_args) {
+ bool retval = false;
+#define RETURN_TRUE do { \
+ if (g_flags.dump_kati_stamp) \
+ retval = true; \
+ else \
+ return true; \
+ } while (0)
+
+#define LOAD_INT(fp) ({ \
+ int v = LoadInt(fp); \
+ if (v < 0) { \
+ fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); \
+ RETURN_TRUE; \
+ } \
+ v; \
+ })
+
+#define LOAD_STRING(fp, s) ({ \
+ if (!LoadString(fp, s)) { \
+ fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); \
+ RETURN_TRUE; \
+ } \
+ })
+
+ if (!Exists(GetNinjaFilename())) {
+ fprintf(stderr, "%s is missing, regenerating...\n",
+ GetNinjaFilename().c_str());
+ return true;
+ }
+ if (!Exists(GetNinjaShellScriptFilename())) {
+ fprintf(stderr, "%s is missing, regenerating...\n",
+ GetNinjaShellScriptFilename().c_str());
+ return true;
+ }
+
+ const string& stamp_filename = GetNinjaStampFilename();
+ FILE* fp = fopen(stamp_filename.c_str(), "rb+");
+ if (!fp) {
+ if (g_flags.dump_kati_stamp)
+ printf("%s: %s\n", stamp_filename.c_str(), strerror(errno));
+ return true;
+ }
+ ScopedFile sfp(fp);
+
+ double gen_time;
+ size_t r = fread(&gen_time, sizeof(gen_time), 1, fp);
+ if (r != 1) {
+ fprintf(stderr, "incomplete kati_stamp, regenerating...\n");
+ RETURN_TRUE;
+ }
+ if (g_flags.dump_kati_stamp)
+ printf("Generated time: %f\n", gen_time);
+
+ string s, s2;
+ int num_files = LOAD_INT(fp);
+ for (int i = 0; i < num_files; i++) {
+ LOAD_STRING(fp, &s);
+ double ts = GetTimestamp(s);
+ if (gen_time < ts) {
+ if (g_flags.regen_ignoring_kati_binary) {
+ string kati_binary;
+ GetExecutablePath(&kati_binary);
+ if (s == kati_binary) {
+ fprintf(stderr, "%s was modified, ignored.\n", s.c_str());
+ continue;
+ }
+ }
+ if (ShouldIgnoreDirty(s)) {
+ if (g_flags.dump_kati_stamp)
+ printf("file %s: ignored (%f)\n", s.c_str(), ts);
+ continue;
+ }
+ if (g_flags.dump_kati_stamp)
+ printf("file %s: dirty (%f)\n", s.c_str(), ts);
+ else
+ fprintf(stderr, "%s was modified, regenerating...\n", s.c_str());
+ RETURN_TRUE;
+ } else if (g_flags.dump_kati_stamp) {
+ printf("file %s: clean (%f)\n", s.c_str(), ts);
+ }
+ }
+
+ int num_undefineds = LOAD_INT(fp);
+ for (int i = 0; i < num_undefineds; i++) {
+ LOAD_STRING(fp, &s);
+ if (getenv(s.c_str())) {
+ if (g_flags.dump_kati_stamp) {
+ printf("env %s: dirty (unset => %s)\n", s.c_str(), getenv(s.c_str()));
+ } else {
+ fprintf(stderr, "Environment variable %s was set, regenerating...\n",
+ s.c_str());
+ }
+ RETURN_TRUE;
+ } else if (g_flags.dump_kati_stamp) {
+ printf("env %s: clean (unset)\n", s.c_str());
+ }
+ }
+
+ int num_envs = LOAD_INT(fp);
+ for (int i = 0; i < num_envs; i++) {
+ LOAD_STRING(fp, &s);
+ StringPiece val(getenv(s.c_str()));
+ LOAD_STRING(fp, &s2);
+ if (val != s2) {
+ if (g_flags.dump_kati_stamp) {
+ printf("env %s: dirty (%s => %.*s)\n",
+ s.c_str(), s2.c_str(), SPF(val));
+ } else {
+ fprintf(stderr, "Environment variable %s was modified (%s => %.*s), "
+ "regenerating...\n",
+ s.c_str(), s2.c_str(), SPF(val));
+ }
+ RETURN_TRUE;
+ } else if (g_flags.dump_kati_stamp) {
+ printf("env %s: clean (%.*s)\n", s.c_str(), SPF(val));
+ }
+ }
+
+ {
+ int num_globs = LOAD_INT(fp);
+ string pat;
+ for (int i = 0; i < num_globs; i++) {
+ COLLECT_STATS("glob time (regen)");
+ LOAD_STRING(fp, &pat);
+#if 0
+ bool needs_reglob = false;
+ int num_dirs = LOAD_INT(fp);
+ for (int j = 0; j < num_dirs; j++) {
+ LOAD_STRING(fp, &s);
+ // TODO: Handle removed files properly.
+ needs_reglob |= gen_time < GetTimestamp(s);
+ }
+#endif
+ int num_files = LOAD_INT(fp);
+ vector<string>* files;
+ Glob(pat.c_str(), &files);
+ sort(files->begin(), files->end());
+ bool needs_regen = files->size() != static_cast<size_t>(num_files);
+ for (int j = 0; j < num_files; j++) {
+ LOAD_STRING(fp, &s);
+ if (!needs_regen) {
+ if ((*files)[j] != s) {
+ needs_regen = true;
+ break;
+ }
+ }
+ }
+ if (needs_regen) {
+ if (ShouldIgnoreDirty(pat)) {
+ if (g_flags.dump_kati_stamp) {
+ printf("wildcard %s: ignored\n", pat.c_str());
+ }
+ continue;
+ }
+ if (g_flags.dump_kati_stamp) {
+ printf("wildcard %s: dirty\n", pat.c_str());
+ } else {
+ fprintf(stderr, "wildcard(%s) was changed, regenerating...\n",
+ pat.c_str());
+ }
+ RETURN_TRUE;
+ } else if (g_flags.dump_kati_stamp) {
+ printf("wildcard %s: clean\n", pat.c_str());
+ }
+ }
+ }
+
+ int num_crs = LOAD_INT(fp);
+ for (int i = 0; i < num_crs; i++) {
+ string cmd, expected;
+ LOAD_STRING(fp, &cmd);
+ LOAD_STRING(fp, &expected);
+
+ {
+ COLLECT_STATS("stat time (regen)");
+ bool has_condition = LOAD_INT(fp);
+ if (has_condition) {
+ bool should_run_command = false;
+
+ int num_missing_dirs = LOAD_INT(fp);
+ for (int j = 0; j < num_missing_dirs; j++) {
+ LOAD_STRING(fp, &s);
+ should_run_command |= Exists(s);
+ }
+
+ int num_read_dirs = LOAD_INT(fp);
+ for (int j = 0; j < num_read_dirs; j++) {
+ LOAD_STRING(fp, &s);
+ // We assume we rarely do a significant change for the top
+ // directory which affects the results of find command.
+ if (s == "" || s == "." || ShouldIgnoreDirty(s))
+ continue;
+
+ struct stat st;
+ if (lstat(s.c_str(), &st) != 0) {
+ should_run_command = true;
+ continue;
+ }
+ double ts = GetTimestampFromStat(st);
+ if (gen_time < ts) {
+ should_run_command = true;
+ continue;
+ }
+ if (S_ISLNK(st.st_mode)) {
+ ts = GetTimestamp(s);
+ should_run_command |= (ts < 0 || gen_time < ts);
+ }
+ }
+
+ if (!should_run_command) {
+ if (g_flags.dump_kati_stamp)
+ printf("shell %s: clean (no rerun)\n", cmd.c_str());
+ continue;
+ }
+ }
+ }
+
+ FindCommand fc;
+ if (fc.Parse(cmd) && !fc.chdir.empty() && ShouldIgnoreDirty(fc.chdir)) {
+ if (g_flags.dump_kati_stamp)
+ printf("shell %s: ignored\n", cmd.c_str());
+ continue;
+ }
+
+ {
+ COLLECT_STATS_WITH_SLOW_REPORT("shell time (regen)", cmd.c_str());
+ string result;
+ RunCommand("/bin/sh", cmd, RedirectStderr::DEV_NULL, &result);
+ FormatForCommandSubstitution(&result);
+ if (expected != result) {
+ if (g_flags.dump_kati_stamp) {
+ printf("shell %s: dirty\n", cmd.c_str());
+ } else {
+ fprintf(stderr, "$(shell %s) was changed, regenerating...\n",
+ cmd.c_str());
+#if 0
+ fprintf(stderr, "%s => %s\n",
+ expected.c_str(), result.c_str());
+#endif
+ }
+ RETURN_TRUE;
+ } else if (g_flags.dump_kati_stamp) {
+ printf("shell %s: clean (rerun)\n", cmd.c_str());
+ }
+ }
+ }
+
+ LoadString(fp, &s);
+ if (orig_args != s) {
+ fprintf(stderr, "arguments changed, regenerating...\n");
+ RETURN_TRUE;
+ }
+
+ if (!retval) {
+ if (fseek(fp, 0, SEEK_SET) < 0)
+ PERROR("fseek");
+ size_t r = fwrite(&start_time, sizeof(start_time), 1, fp);
+ CHECK(r == 1);
+ }
+
+ return retval;
+}
diff --git a/regen.h b/regen.h
new file mode 100644
index 0000000..3d43d70
--- /dev/null
+++ b/regen.h
@@ -0,0 +1,24 @@
+// Copyright 2016 Google Inc. All rights reserved
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef REGEN_H_
+#define REGEN_H_
+
+#include <string>
+
+using namespace std;
+
+bool NeedsRegen(double start_time, const string& orig_args);
+
+#endif // REGEN_H_