blob: 9c85815bb09f32d1d27c784fe0e263578d8c1594 [file] [log] [blame]
// Copyright 2015 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.
// +build ignore
#include "ninja.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <algorithm>
#include <map>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "command.h"
#include "dep.h"
#include "eval.h"
#include "file_cache.h"
#include "fileutil.h"
#include "find.h"
#include "flags.h"
#include "func.h"
#include "io.h"
#include "log.h"
#include "stats.h"
#include "string_piece.h"
#include "stringprintf.h"
#include "strutil.h"
#include "var.h"
#include "version.h"
static size_t FindCommandLineFlag(StringPiece cmd, StringPiece name) {
const size_t found = cmd.find(name);
if (found == string::npos || found == 0)
return string::npos;
return found;
}
static StringPiece FindCommandLineFlagWithArg(StringPiece cmd,
StringPiece name) {
size_t index = FindCommandLineFlag(cmd, name);
if (index == string::npos)
return StringPiece();
StringPiece val = TrimLeftSpace(cmd.substr(index + name.size()));
index = val.find(name);
while (index != string::npos) {
val = TrimLeftSpace(val.substr(index + name.size()));
index = val.find(name);
}
index = val.find_first_of(" \t");
return val.substr(0, index);
}
static bool StripPrefix(StringPiece p, StringPiece* s) {
if (!HasPrefix(*s, p))
return false;
*s = s->substr(p.size());
return true;
}
size_t GetGomaccPosForAndroidCompileCommand(StringPiece cmdline) {
size_t index = cmdline.find(' ');
if (index == string::npos)
return string::npos;
StringPiece cmd = cmdline.substr(0, index);
if (HasSuffix(cmd, "ccache")) {
index++;
size_t pos = GetGomaccPosForAndroidCompileCommand(cmdline.substr(index));
return pos == string::npos ? string::npos : pos + index;
}
if (!StripPrefix("prebuilts/", &cmd))
return string::npos;
if (!StripPrefix("gcc/", &cmd) && !StripPrefix("clang/", &cmd))
return string::npos;
if (!HasSuffix(cmd, "gcc") && !HasSuffix(cmd, "g++") &&
!HasSuffix(cmd, "clang") && !HasSuffix(cmd, "clang++")) {
return string::npos;
}
StringPiece rest = cmdline.substr(index);
return rest.find(" -c ") != string::npos ? 0 : string::npos;
}
static bool GetDepfileFromCommandImpl(StringPiece cmd, string* out) {
if ((FindCommandLineFlag(cmd, " -MD") == string::npos &&
FindCommandLineFlag(cmd, " -MMD") == string::npos) ||
FindCommandLineFlag(cmd, " -c") == string::npos) {
return false;
}
StringPiece mf = FindCommandLineFlagWithArg(cmd, " -MF");
if (!mf.empty()) {
mf.AppendToString(out);
return true;
}
StringPiece o = FindCommandLineFlagWithArg(cmd, " -o");
if (o.empty()) {
ERROR("Cannot find the depfile in %s", cmd.as_string().c_str());
return false;
}
StripExt(o).AppendToString(out);
*out += ".d";
return true;
}
bool GetDepfileFromCommand(string* cmd, string* out) {
CHECK(!cmd->empty());
if (!GetDepfileFromCommandImpl(*cmd, out))
return false;
// A hack for Android - llvm-rs-cc seems not to emit a dep file.
if (cmd->find("bin/llvm-rs-cc ") != string::npos) {
return false;
}
// TODO: A hack for Makefiles generated by automake.
// A hack for Android to get .P files instead of .d.
string p;
StripExt(*out).AppendToString(&p);
p += ".P";
if (cmd->find(p) != string::npos) {
const string rm_f = "; rm -f " + *out;
const size_t found = cmd->find(rm_f);
if (found == string::npos) {
ERROR("Cannot find removal of .d file: %s", cmd->c_str());
}
cmd->erase(found, rm_f.size());
return true;
}
// A hack for Android. For .s files, GCC does not use C
// preprocessor, so it ignores -MF flag.
string as = "/";
StripExt(Basename(*out)).AppendToString(&as);
as += ".s";
if (cmd->find(as) != string::npos) {
return false;
}
*cmd += "&& cp ";
*cmd += *out;
*cmd += ' ';
*cmd += *out;
*cmd += ".tmp ";
*out += ".tmp";
return true;
}
class NinjaGenerator {
public:
NinjaGenerator(Evaluator* ev, double start_time)
: ce_(ev), ev_(ev), fp_(NULL), rule_id_(0), start_time_(start_time),
default_target_(NULL) {
ev_->set_avoid_io(true);
shell_ = ev->EvalVar(kShellSym);
if (g_flags.goma_dir)
gomacc_ = StringPrintf("%s/gomacc ", g_flags.goma_dir);
GetExecutablePath(&kati_binary_);
}
~NinjaGenerator() {
ev_->set_avoid_io(false);
}
void Generate(const vector<DepNode*>& nodes,
const string& orig_args) {
unlink(GetStampFilename().c_str());
GenerateNinja(nodes, orig_args);
GenerateShell();
GenerateStamp(orig_args);
}
static string GetStampFilename() {
return GetFilename(".kati_stamp%s");
}
static string GetStampTempFilename() {
return GetFilename(".kati_stamp%s.tmp");
}
private:
string GenRuleName() {
return StringPrintf("rule%d", rule_id_++);
}
StringPiece TranslateCommand(const char* in, string* cmd_buf) {
const size_t orig_size = cmd_buf->size();
bool prev_backslash = false;
// Set space as an initial value so the leading comment will be
// stripped out.
char prev_char = ' ';
char quote = 0;
for (; *in; in++) {
switch (*in) {
case '#':
if (quote == 0 && isspace(prev_char)) {
while (in[1] && *in != '\n')
in++;
} else {
*cmd_buf += *in;
}
break;
case '\'':
case '"':
case '`':
if (quote) {
if (quote == *in)
quote = 0;
} else if (!prev_backslash) {
quote = *in;
}
*cmd_buf += *in;
break;
case '$':
*cmd_buf += "$$";
break;
case '\n':
if (prev_backslash) {
cmd_buf->resize(cmd_buf->size()-1);
} else {
*cmd_buf += ' ';
}
break;
case '\\':
*cmd_buf += '\\';
break;
default:
*cmd_buf += *in;
}
if (*in == '\\') {
prev_backslash = !prev_backslash;
} else {
prev_backslash = false;
}
prev_char = *in;
}
if (prev_backslash) {
cmd_buf->resize(cmd_buf->size()-1);
}
while (true) {
char c = (*cmd_buf)[cmd_buf->size()-1];
if (!isspace(c) && c != ';')
break;
cmd_buf->resize(cmd_buf->size() - 1);
}
return StringPiece(cmd_buf->data() + orig_size,
cmd_buf->size() - orig_size);
}
bool GetDescriptionFromCommand(StringPiece cmd, string *out) {
if (!HasPrefix(cmd, "echo ")) {
return false;
}
cmd = cmd.substr(5, cmd.size());
bool prev_backslash = false;
char quote = 0;
string out_buf;
// Strip outer quotes, and fail if it is not a single echo command
for (StringPiece::iterator in = cmd.begin(); in != cmd.end(); in++) {
if (prev_backslash) {
prev_backslash = false;
out_buf += *in;
} else if (*in == '\\') {
prev_backslash = true;
out_buf += *in;
} else if (quote) {
if (*in == quote) {
quote = 0;
} else {
out_buf += *in;
}
} else {
switch (*in) {
case '\'':
case '"':
case '`':
quote = *in;
break;
case '<':
case '>':
case '&':
case '|':
case ';':
return false;
default:
out_buf += *in;
}
}
}
*out = out_buf;
return true;
}
bool GenShellScript(const vector<Command*>& commands,
string* cmd_buf,
string* description) {
bool got_descritpion = false;
bool use_gomacc = false;
bool should_ignore_error = false;
for (const Command* c : commands) {
if (!cmd_buf->empty()) {
if (should_ignore_error) {
*cmd_buf += " ; ";
} else {
*cmd_buf += " && ";
}
}
should_ignore_error = c->ignore_error;
const char* in = c->cmd.c_str();
while (isspace(*in))
in++;
bool needs_subshell = commands.size() > 1;
if (*in == '(') {
needs_subshell = false;
}
if (needs_subshell)
*cmd_buf += '(';
size_t cmd_start = cmd_buf->size();
StringPiece translated = TranslateCommand(in, cmd_buf);
if (g_flags.detect_android_echo && !got_descritpion && !c->echo &&
GetDescriptionFromCommand(translated, description)) {
got_descritpion = true;
cmd_buf->resize(cmd_start);
translated.clear();
}
if (translated.empty()) {
*cmd_buf += "true";
} else if (g_flags.goma_dir) {
size_t pos = GetGomaccPosForAndroidCompileCommand(translated);
if (pos != string::npos) {
cmd_buf->insert(cmd_start + pos, gomacc_);
use_gomacc = true;
}
} else if (translated.find("/gomacc") != string::npos) {
use_gomacc = true;
}
if (c == commands.back() && c->ignore_error) {
*cmd_buf += " ; true";
}
if (needs_subshell)
*cmd_buf += ')';
}
return (g_flags.remote_num_jobs || g_flags.goma_dir) && !use_gomacc;
}
void EmitDepfile(string* cmd_buf) {
*cmd_buf += ' ';
string depfile;
bool result = GetDepfileFromCommand(cmd_buf, &depfile);
cmd_buf->resize(cmd_buf->size()-1);
if (!result)
return;
fprintf(fp_, " depfile = %s\n", depfile.c_str());
fprintf(fp_, " deps = gcc\n");
}
void EmitNode(DepNode* node) {
auto p = done_.insert(node->output);
if (!p.second)
return;
// A hack to exclude out phony target in Android. If this exists,
// "ninja -t clean" tries to remove this directory and fails.
if (g_flags.detect_android_echo && node->output.str() == "out")
return;
// This node is a leaf node
if (!node->has_rule && !node->is_phony) {
return;
}
vector<Command*> commands;
ce_.Eval(node, &commands);
string rule_name = "phony";
bool use_local_pool = false;
if (!commands.empty()) {
rule_name = GenRuleName();
fprintf(fp_, "rule %s\n", rule_name.c_str());
string description = "build $out";
string cmd_buf;
use_local_pool |= GenShellScript(commands, &cmd_buf, &description);
fprintf(fp_, " description = %s\n", description.c_str());
EmitDepfile(&cmd_buf);
// It seems Linux is OK with ~130kB and Mac's limit is ~250kB.
// TODO: Find this number automatically.
if (cmd_buf.size() > 100 * 1000) {
fprintf(fp_, " rspfile = $out.rsp\n");
fprintf(fp_, " rspfile_content = %s\n", cmd_buf.c_str());
fprintf(fp_, " command = %s $out.rsp\n", shell_.c_str());
} else {
EscapeShell(&cmd_buf);
fprintf(fp_, " command = %s -c \"%s\"\n",
shell_.c_str(), cmd_buf.c_str());
}
if (node->is_restat) {
fprintf(fp_, " restat = 1\n");
}
}
EmitBuild(node, rule_name, use_local_pool);
for (DepNode* d : node->deps) {
EmitNode(d);
}
for (DepNode* d : node->order_onlys) {
EmitNode(d);
}
}
string EscapeBuildTarget(Symbol s) const {
if (s.str().find_first_of("$: ") == string::npos)
return s.str();
string r;
for (char c : s.str()) {
switch (c) {
case '$':
case ':':
case ' ':
r += '$';
// fall through.
default:
r += c;
}
}
return r;
}
void EscapeShell(string* s) const {
if (s->find_first_of("$`\\\"") == string::npos)
return;
string r;
bool last_dollar = false;
for (char c : *s) {
switch (c) {
case '$':
if (last_dollar) {
r += c;
last_dollar = false;
} else {
r += '\\';
r += c;
last_dollar = true;
}
break;
case '`':
case '"':
case '\\':
r += '\\';
// fall through.
default:
r += c;
last_dollar = false;
}
}
s->swap(r);
}
void EmitBuild(DepNode* node, const string& rule_name, bool use_local_pool) {
string target = EscapeBuildTarget(node->output);
fprintf(fp_, "build %s: %s",
target.c_str(),
rule_name.c_str());
vector<Symbol> order_onlys;
if (node->is_phony) {
fprintf(fp_, " _kati_always_build_");
}
for (DepNode* d : node->deps) {
fprintf(fp_, " %s", EscapeBuildTarget(d->output).c_str());
}
if (!node->order_onlys.empty()) {
fprintf(fp_, " ||");
for (DepNode* d : node->order_onlys) {
fprintf(fp_, " %s", EscapeBuildTarget(d->output).c_str());
}
}
fprintf(fp_, "\n");
if (use_local_pool)
fprintf(fp_, " pool = local_pool\n");
if (node->is_default_target) {
default_target_ = node;
}
}
void EmitRegenRules(const string& orig_args) {
if (!g_flags.gen_regen_rule)
return;
fprintf(fp_, "rule regen_ninja\n");
fprintf(fp_, " command = %s\n", orig_args.c_str());
fprintf(fp_, " generator = 1\n");
fprintf(fp_, " description = Regenerate ninja files due to dependency\n");
fprintf(fp_, "build %s: regen_ninja", GetNinjaFilename().c_str());
unordered_set<string> makefiles;
MakefileCacheManager::Get()->GetAllFilenames(&makefiles);
for (const string& makefile : makefiles) {
fprintf(fp_, " %.*s", SPF(makefile));
}
fprintf(fp_, " %s", kati_binary_.c_str());
fprintf(fp_, "\n\n");
}
string GetNinjaFilename() const {
return GetFilename("build%s.ninja");
}
string GetShellScriptFilename() const {
return GetFilename("ninja%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");
if (fp_ == NULL)
PERROR("fopen(build.ninja) failed");
fprintf(fp_, "# Generated by kati %s\n", kGitVersion);
fprintf(fp_, "\n");
if (!used_envs_.empty()) {
fprintf(fp_, "# Environment variables used:\n");
for (const auto& p : used_envs_) {
fprintf(fp_, "# %s=%s\n", p.first.c_str(), p.second.c_str());
}
fprintf(fp_, "\n");
}
if (g_flags.ninja_dir) {
fprintf(fp_, "builddir = %s\n\n", g_flags.ninja_dir);
}
fprintf(fp_, "pool local_pool\n");
fprintf(fp_, " depth = %d\n\n", g_flags.num_jobs);
fprintf(fp_, "build _kati_always_build_: phony\n\n");
EmitRegenRules(orig_args);
for (DepNode* node : nodes) {
EmitNode(node);
}
for (Symbol e : Vars::used_env_vars()) {
StringPiece val(getenv(e.c_str()));
used_envs_.emplace(e.str(), val.as_string());
}
string default_targets;
if (g_flags.targets.empty() ||
g_flags.gen_all_targets || g_flags.gen_all_phony_targets) {
CHECK(default_target_);
default_targets = EscapeBuildTarget(default_target_->output);
} else {
for (Symbol s : g_flags.targets) {
if (!default_targets.empty())
default_targets += ' ';
default_targets += EscapeBuildTarget(s);
}
}
fprintf(fp_, "\n");
fprintf(fp_, "default %s\n", default_targets.c_str());
fclose(fp_);
}
void GenerateShell() {
FILE* fp = fopen(GetShellScriptFilename().c_str(), "wb");
if (fp == NULL)
PERROR("fopen(ninja.sh) failed");
fprintf(fp, "#!/bin/sh\n");
fprintf(fp, "# Generated by kati %s\n", kGitVersion);
fprintf(fp, "\n");
for (const auto& p : ev_->exports()) {
if (p.second) {
const string val = ev_->EvalVar(p.first);
fprintf(fp, "export '%s'='%s'\n", p.first.c_str(), val.c_str());
} else {
fprintf(fp, "unset '%s'\n", p.first.c_str());
}
}
fprintf(fp, "exec ninja -f %s ", GetNinjaFilename().c_str());
if (g_flags.remote_num_jobs > 0) {
fprintf(fp, "-j%d ", g_flags.remote_num_jobs);
} else if (g_flags.goma_dir) {
fprintf(fp, "-j500 ");
}
fprintf(fp, "\"$@\"\n");
if (chmod(GetShellScriptFilename().c_str(), 0755) != 0)
PERROR("chmod ninja.sh failed");
}
void GenerateStamp(const string& orig_args) {
FILE* fp = fopen(GetStampTempFilename().c_str(), "wb");
CHECK(fp);
size_t r = fwrite(&start_time_, sizeof(start_time_), 1, fp);
CHECK(r == 1);
unordered_set<string> makefiles;
MakefileCacheManager::Get()->GetAllFilenames(&makefiles);
DumpInt(fp, makefiles.size() + 1);
DumpString(fp, kati_binary_);
for (const string& makefile : makefiles) {
DumpString(fp, makefile);
}
DumpInt(fp, Evaluator::used_undefined_vars().size());
for (Symbol v : Evaluator::used_undefined_vars()) {
DumpString(fp, v.str());
}
DumpInt(fp, used_envs_.size());
for (const auto& p : used_envs_) {
DumpString(fp, p.first);
DumpString(fp, p.second);
}
const unordered_map<string, vector<string>*>& globs = GetAllGlobCache();
DumpInt(fp, globs.size());
for (const auto& p : globs) {
DumpString(fp, p.first);
const vector<string>& files = *p.second;
#if 0
unordered_set<string> dirs;
GetReadDirs(p.first, files, &dirs);
DumpInt(fp, dirs.size());
for (const string& dir : dirs) {
DumpString(fp, dir);
}
#endif
DumpInt(fp, files.size());
for (const string& file : files) {
DumpString(fp, file);
}
}
const vector<CommandResult*>& crs = GetShellCommandResults();
DumpInt(fp, crs.size());
for (CommandResult* cr : crs) {
DumpString(fp, cr->cmd);
DumpString(fp, cr->result);
if (!cr->find.get()) {
// Always re-run this command.
DumpInt(fp, 0);
continue;
}
DumpInt(fp, 1);
vector<string> missing_dirs;
for (StringPiece fd : cr->find->finddirs) {
const string& d = ConcatDir(cr->find->chdir, fd);
if (!Exists(d))
missing_dirs.push_back(d);
}
DumpInt(fp, missing_dirs.size());
for (const string& d : missing_dirs) {
DumpString(fp, d);
}
DumpInt(fp, cr->find->read_dirs->size());
for (StringPiece s : *cr->find->read_dirs) {
DumpString(fp, ConcatDir(cr->find->chdir, s));
}
}
DumpString(fp, orig_args);
fclose(fp);
rename(GetStampTempFilename().c_str(), GetStampFilename().c_str());
}
CommandEvaluator ce_;
Evaluator* ev_;
FILE* fp_;
unordered_set<Symbol> done_;
int rule_id_;
string gomacc_;
string shell_;
map<string, string> used_envs_;
string kati_binary_;
double start_time_;
DepNode* default_target_;
};
void GenerateNinja(const vector<DepNode*>& nodes,
Evaluator* ev,
const string& orig_args,
double start_time) {
NinjaGenerator ng(ev, start_time);
ng.Generate(nodes, orig_args);
}
static bool ShouldIgnoreDirty(StringPiece s) {
return (g_flags.ignore_dirty_pattern &&
Pattern(g_flags.ignore_dirty_pattern).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; \
} \
})
const string& stamp_filename = NinjaGenerator::GetStampFilename();
FILE* fp = fopen(stamp_filename.c_str(), "rb+");
if (!fp)
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;
double 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;
}