aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Willemsen <dwillemsen@google.com>2020-04-28 20:50:36 -0700
committerDan Willemsen <dwillemsen@google.com>2020-04-28 20:50:36 -0700
commit1decd163d97a3f8b7ab88cbcff501ed1de9615af (patch)
tree699f6fa2bcc091cd4f17f4e3795d17c69f9d6f54
parentd4641a8f4179234bf95f334fac6621edcaf40370 (diff)
parent79efeda18cc5aa79f2a4e4566f5a955327701296 (diff)
downloadplatform_build_kati-1decd163d97a3f8b7ab88cbcff501ed1de9615af.tar.gz
platform_build_kati-1decd163d97a3f8b7ab88cbcff501ed1de9615af.tar.bz2
platform_build_kati-1decd163d97a3f8b7ab88cbcff501ed1de9615af.zip
Merge remote-tracking branch 'aosp/upstream'
* aosp/upstream: On-demand find emulator initialization Support '*' in finddirs Support '..' in find emulation Add new check for non-phony targets that have no commands Remove unused `parents` from DepNode Test: treehugger Change-Id: Idd4196c17eaa6e265e2f5dd0f7f552468e7a43e0
-rw-r--r--dep.cc47
-rw-r--r--dep.h1
-rw-r--r--find.cc451
-rw-r--r--find.h1
-rw-r--r--find_test.cc26
-rw-r--r--flags.cc10
-rw-r--r--flags.h4
-rw-r--r--stats.cc3
-rw-r--r--strutil.cc2
-rw-r--r--strutil_test.cc13
-rw-r--r--testcase/real_no_cmds.sh53
-rw-r--r--testcase/real_no_cmds_or_deps.sh47
12 files changed, 476 insertions, 182 deletions
diff --git a/dep.cc b/dep.cc
index 65687cf..a4eb2d2 100644
--- a/dep.cc
+++ b/dep.cc
@@ -790,6 +790,53 @@ class DepBuilder {
n->order_onlys.push_back({input, c});
}
+ // Block on werror_writable/werror_phony_looks_real, because otherwise we
+ // can't rely on is_phony being valid for this check.
+ if (!n->is_phony && n->cmds.empty() && g_flags.werror_writable &&
+ g_flags.werror_phony_looks_real) {
+ if (n->deps.empty() && n->order_onlys.empty()) {
+ if (g_flags.werror_real_no_cmds_or_deps) {
+ ERROR_LOC(
+ n->loc,
+ "*** target \"%s\" has no commands or deps that could create it",
+ output.c_str());
+ } else if (g_flags.warn_real_no_cmds_or_deps) {
+ WARN_LOC(n->loc,
+ "warning: target \"%s\" has no commands or deps that could "
+ "create it",
+ output.c_str());
+ }
+ } else {
+ if (n->actual_inputs.size() == 1) {
+ if (g_flags.werror_real_no_cmds) {
+ ERROR_LOC(n->loc,
+ "*** target \"%s\" has no commands. Should \"%s\" be "
+ "using .KATI_IMPLICIT_OUTPUTS?",
+ output.c_str(), n->actual_inputs[0].c_str());
+ } else if (g_flags.warn_real_no_cmds) {
+ WARN_LOC(n->loc,
+ "warning: target \"%s\" has no commands. Should \"%s\" be "
+ "using .KATI_IMPLICIT_OUTPUTS?",
+ output.c_str(), n->actual_inputs[0].c_str());
+ }
+ } else {
+ if (g_flags.werror_real_no_cmds) {
+ ERROR_LOC(
+ n->loc,
+ "*** target \"%s\" has no commands that could create output "
+ "file. Is a dependency missing .KATI_IMPLICIT_OUTPUTS?",
+ output.c_str());
+ } else if (g_flags.warn_real_no_cmds) {
+ WARN_LOC(
+ n->loc,
+ "warning: target \"%s\" has no commands that could create "
+ "output file. Is a dependency missing .KATI_IMPLICIT_OUTPUTS?",
+ output.c_str());
+ }
+ }
+ }
+ }
+
n->has_rule = true;
n->is_default_target = first_rule_ == output;
if (cur_rule_vars_->empty()) {
diff --git a/dep.h b/dep.h
index 259c017..963447b 100644
--- a/dep.h
+++ b/dep.h
@@ -39,7 +39,6 @@ struct DepNode {
vector<Value*> cmds;
vector<NamedDepNode> deps;
vector<NamedDepNode> order_onlys;
- vector<NamedDepNode> parents;
bool has_rule;
bool is_default_target;
bool is_phony;
diff --git a/find.cc b/find.cc
index a31adce..b693449 100644
--- a/find.cc
+++ b/find.cc
@@ -31,6 +31,7 @@
#include "fileutil.h"
#include "log.h"
+#include "stats.h"
#include "string_piece.h"
#include "strutil.h"
#include "timeutil.h"
@@ -44,6 +45,8 @@
} \
} while (0)
+static unsigned int find_emulator_node_cnt = 0;
+
class FindCond {
public:
virtual ~FindCond() = default;
@@ -139,6 +142,12 @@ class DirentNode {
virtual ~DirentNode() = default;
virtual const DirentNode* FindDir(StringPiece) const { return NULL; }
+ virtual bool FindNodes(const FindCommand&,
+ vector<pair<string, const DirentNode*>>&,
+ string*,
+ StringPiece) const {
+ return true;
+ }
virtual bool RunFind(const FindCommand& fc,
const Loc& loc,
int d,
@@ -221,7 +230,8 @@ struct ScopedReadDirTracker {
class DirentDirNode : public DirentNode {
public:
- explicit DirentDirNode(const string& name) : DirentNode(name) {}
+ explicit DirentDirNode(const DirentDirNode* parent, const string& name)
+ : DirentNode(name), parent_(parent), name_(name) {}
~DirentDirNode() {
for (auto& p : children_) {
@@ -230,13 +240,25 @@ class DirentDirNode : public DirentNode {
}
virtual const DirentNode* FindDir(StringPiece d) const override {
+ if (!is_initialized_) {
+ initialize();
+ }
+
if (d.empty() || d == ".")
return this;
+ if (d == "..")
+ return parent_;
+
size_t index = d.find('/');
const string& p = d.substr(0, index).as_string();
if (p.empty() || p == ".")
return FindDir(d.substr(index + 1));
- ;
+ if (p == "..") {
+ if (parent_ == NULL)
+ return NULL;
+ return parent_->FindDir(d.substr(index + 1));
+ }
+
for (auto& child : children_) {
if (p == child.first) {
if (index == string::npos)
@@ -248,12 +270,83 @@ class DirentDirNode : public DirentNode {
return NULL;
}
+ virtual bool FindNodes(const FindCommand& fc,
+ vector<pair<string, const DirentNode*>>& results,
+ string* path,
+ StringPiece d) const override {
+ if (!is_initialized_) {
+ initialize();
+ }
+
+ if (!path->empty())
+ path->append("/");
+
+ size_t orig_path_size = path->size();
+
+ size_t index = d.find('/');
+ const string& p = d.substr(0, index).as_string();
+
+ if (p.empty() || p == ".") {
+ path->append(p);
+ if (index == string::npos) {
+ results.emplace_back(*path, this);
+ return true;
+ }
+ return FindNodes(fc, results, path, d.substr(index + 1));
+ }
+ if (p == "..") {
+ if (parent_ == NULL) {
+ LOG("FindEmulator does not support leaving the source directory: %s",
+ path->c_str());
+ return false;
+ }
+ path->append(p);
+ if (index == string::npos) {
+ results.emplace_back(*path, parent_);
+ return true;
+ }
+ return parent_->FindNodes(fc, results, path, d.substr(index + 1));
+ }
+
+ bool is_wild = p.find_first_of("?*[") != string::npos;
+ if (is_wild) {
+ fc.read_dirs->insert(*path);
+ }
+
+ for (auto& child : children_) {
+ bool matches = false;
+ if (is_wild) {
+ matches = (fnmatch(p.c_str(), child.first.c_str(), FNM_PERIOD) == 0);
+ } else {
+ matches = (p == child.first);
+ }
+ if (matches) {
+ path->append(child.first);
+ if (index == string::npos) {
+ results.emplace_back(*path, child.second);
+ } else {
+ if (!child.second->FindNodes(fc, results, path,
+ d.substr(index + 1))) {
+ return false;
+ }
+ }
+ path->resize(orig_path_size);
+ }
+ }
+
+ return true;
+ }
+
virtual bool RunFind(const FindCommand& fc,
const Loc& loc,
int d,
string* path,
unordered_map<const DirentNode*, string>* cur_read_dirs,
vector<string>& out) const override {
+ if (!is_initialized_) {
+ initialize();
+ }
+
ScopedReadDirTracker srdt(this, *path, cur_read_dirs);
if (!srdt.ok()) {
FIND_WARN_LOC(loc,
@@ -336,25 +429,77 @@ class DirentDirNode : public DirentNode {
virtual bool IsDirectory() const override { return true; }
- void Add(const string& name, DirentNode* c) {
- children_.emplace(children_.end(), name, c);
+ private:
+ static unsigned char GetDtTypeFromStat(const struct stat& st) {
+ if (S_ISREG(st.st_mode)) {
+ return DT_REG;
+ } else if (S_ISDIR(st.st_mode)) {
+ return DT_DIR;
+ } else if (S_ISCHR(st.st_mode)) {
+ return DT_CHR;
+ } else if (S_ISBLK(st.st_mode)) {
+ return DT_BLK;
+ } else if (S_ISFIFO(st.st_mode)) {
+ return DT_FIFO;
+ } else if (S_ISLNK(st.st_mode)) {
+ return DT_LNK;
+ } else if (S_ISSOCK(st.st_mode)) {
+ return DT_SOCK;
+ } else {
+ return DT_UNKNOWN;
+ }
+ }
+
+ static unsigned char GetDtType(const string& path) {
+ struct stat st;
+ if (lstat(path.c_str(), &st)) {
+ PERROR("stat for %s", path.c_str());
+ }
+ return GetDtTypeFromStat(st);
}
- private:
- vector<pair<string, DirentNode*>> children_;
+ void initialize() const;
+
+ const DirentDirNode* parent_;
+
+ mutable vector<pair<string, DirentNode*>> children_;
+ mutable string name_;
+ mutable bool is_initialized_ = false;
};
class DirentSymlinkNode : public DirentNode {
public:
- explicit DirentSymlinkNode(const string& name)
- : DirentNode(name), to_(NULL), errno_(0) {}
+ explicit DirentSymlinkNode(const DirentDirNode* parent, const string& name)
+ : DirentNode(name), name_(name), parent_(parent) {}
virtual const DirentNode* FindDir(StringPiece d) const override {
+ if (!is_initialized_) {
+ initialize();
+ }
if (errno_ == 0 && to_)
return to_->FindDir(d);
return NULL;
}
+ virtual bool FindNodes(const FindCommand& fc,
+ vector<pair<string, const DirentNode*>>& results,
+ string* path,
+ StringPiece d) const override {
+ if (!is_initialized_) {
+ initialize();
+ }
+ if (errno_ != 0) {
+ return true;
+ }
+ if (!to_) {
+ LOG("FindEmulator does not support symlink %s", path->c_str());
+ return false;
+ }
+ if (to_->IsDirectory())
+ fc.read_dirs->insert(*path);
+ return to_->FindNodes(fc, results, path, d);
+ }
+
virtual bool RunFind(const FindCommand& fc,
const Loc& loc,
int d,
@@ -362,6 +507,9 @@ class DirentSymlinkNode : public DirentNode {
unordered_map<const DirentNode*, string>* cur_read_dirs,
vector<string>& out) const override {
unsigned char type = DT_LNK;
+ if (fc.follows_symlinks && !is_initialized_) {
+ initialize();
+ }
if (fc.follows_symlinks && errno_ != ENOENT) {
if (errno_) {
if (fc.type != FindCommandType::FINDLEAVES) {
@@ -383,18 +531,100 @@ class DirentSymlinkNode : public DirentNode {
}
virtual bool IsDirectory() const override {
+ if (!is_initialized_) {
+ initialize();
+ }
return errno_ == 0 && to_ && to_->IsDirectory();
}
- void set_to(const DirentNode* to) { to_ = to; }
+ private:
+ void initialize() const {
+ COLLECT_STATS("init find emulator DirentSymlinkNode::initialize");
+ char buf[PATH_MAX + 1];
+ buf[PATH_MAX] = 0;
+ ssize_t len = readlink(name_.c_str(), buf, PATH_MAX);
+ if (len <= 0) {
+ errno_ = errno;
+ WARN("readlink failed: %s", name_.c_str());
+ name_ = "";
+ is_initialized_ = true;
+ return;
+ }
+ buf[len] = 0;
+
+ struct stat st;
+ if (stat(name_.c_str(), &st) != 0) {
+ errno_ = errno;
+ LOG("stat failed: %s: %s", name_.c_str(), strerror(errno));
+ name_ = "";
+ is_initialized_ = true;
+ return;
+ }
+
+ // absolute symlinks aren't supported by the find emulator
+ if (*buf != '/') {
+ to_ = parent_->FindDir(buf);
+ }
- void set_errno(int e) { errno_ = e; }
+ name_ = "";
+ is_initialized_ = true;
+ }
- private:
- const DirentNode* to_;
- int errno_;
+ mutable string name_;
+ const DirentDirNode* parent_;
+
+ mutable const DirentNode* to_ = nullptr;
+ mutable int errno_ = 0;
+ mutable bool is_initialized_ = false;
};
+void DirentDirNode::initialize() const {
+ COLLECT_STATS("init find emulator DirentDirNode::initialize");
+ DIR* dir = opendir(name_.empty() ? "." : name_.c_str());
+ if (!dir) {
+ if (errno == ENOENT || errno == EACCES) {
+ LOG("opendir failed: %s", name_.c_str());
+ name_ = "";
+ is_initialized_ = true;
+ return;
+ } else {
+ PERROR("opendir failed: %s", name_.c_str());
+ }
+ }
+
+ struct dirent* ent;
+ while ((ent = readdir(dir)) != NULL) {
+ if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..") ||
+ !strcmp(ent->d_name, ".repo") || !strcmp(ent->d_name, ".git"))
+ continue;
+
+ string npath = name_;
+ if (!name_.empty())
+ npath += '/';
+ npath += ent->d_name;
+
+ DirentNode* c = NULL;
+ auto d_type = ent->d_type;
+ if (d_type == DT_UNKNOWN) {
+ d_type = GetDtType(npath);
+ CHECK(d_type != DT_UNKNOWN);
+ }
+ if (d_type == DT_DIR) {
+ c = new DirentDirNode(this, npath);
+ } else if (d_type == DT_LNK) {
+ c = new DirentSymlinkNode(this, npath);
+ } else {
+ c = new DirentFileNode(npath, d_type);
+ }
+ find_emulator_node_cnt++;
+ children_.emplace(children_.end(), ent->d_name, c);
+ }
+ closedir(dir);
+
+ name_ = "";
+ is_initialized_ = true;
+}
+
class FindCommandParser {
public:
FindCommandParser(StringPiece cmd, FindCommand* fc)
@@ -631,7 +861,7 @@ class FindCommandParser {
return false;
}
fc_->redirect_to_devnull = true;
- } else if (tok.find_first_of("|;&><*'\"") != string::npos) {
+ } else if (tok.find_first_of("|;&><'\"") != string::npos) {
return false;
} else {
fc_->finddirs.push_back(tok.as_string());
@@ -715,6 +945,8 @@ class FindCommandParser {
if (tok == "cd") {
if (!GetNextToken(&tok) || tok.empty() || !fc_->chdir.empty())
return false;
+ if (tok.find_first_of("?*[") != string::npos)
+ return false;
fc_->chdir = tok.as_string();
if (!GetNextToken(&tok) || (tok != ";" && tok != "&&"))
return false;
@@ -769,15 +1001,13 @@ static FindEmulator* g_instance;
class FindEmulatorImpl : public FindEmulator {
public:
- FindEmulatorImpl() : node_cnt_(0), is_initialized_(false) {
- g_instance = this;
- }
+ FindEmulatorImpl() { g_instance = this; }
virtual ~FindEmulatorImpl() = default;
bool CanHandle(StringPiece s) const {
- return (!HasPrefix(s, "../") && !HasPrefix(s, "/") &&
- !HasPrefix(s, ".repo") && !HasPrefix(s, ".git"));
+ return (!HasPrefix(s, "/") && !HasPrefix(s, ".repo") &&
+ !HasPrefix(s, ".git"));
}
const DirentNode* FindDir(StringPiece d, bool* should_fallback) {
@@ -798,17 +1028,6 @@ class FindEmulatorImpl : public FindEmulator {
return false;
}
- if (!is_initialized_) {
- ScopedTimeReporter tr("init find emulator time");
- root_.reset(ConstructDirectoryTree(""));
- if (!root_) {
- ERROR("FindEmulator: Cannot open root directory");
- }
- ResolveSymlinks();
- LOG_STAT("%d find nodes", node_cnt_);
- is_initialized_ = true;
- }
-
if (!fc.testdir.empty()) {
if (!CanHandle(fc.testdir)) {
LOG("FindEmulator: Cannot handle test dir (%.*s): %s", SPF(fc.testdir),
@@ -823,7 +1042,7 @@ class FindEmulatorImpl : public FindEmulator {
}
}
- const DirentNode* root = root_.get();
+ const DirentNode* root = root_;
if (!fc.chdir.empty()) {
if (!CanHandle(fc.chdir)) {
@@ -846,16 +1065,20 @@ class FindEmulatorImpl : public FindEmulator {
vector<string> results;
for (const string& finddir : fc.finddirs) {
- if (!CanHandle(finddir)) {
- LOG("FindEmulator: Cannot handle find dir (%s): %s", finddir.c_str(),
+ string fullpath = ConcatDir(fc.chdir, finddir);
+ if (!CanHandle(fullpath)) {
+ LOG("FindEmulator: Cannot handle find dir (%s): %s", fullpath.c_str(),
cmd.c_str());
return false;
}
- const DirentNode* base;
- base = root->FindDir(finddir);
- if (!base) {
- if (Exists(finddir)) {
+ string findnodestr;
+ vector<pair<string, const DirentNode*>> bases;
+ if (!root->FindNodes(fc, bases, &findnodestr, finddir)) {
+ return false;
+ }
+ if (bases.empty()) {
+ if (Exists(fullpath)) {
return false;
}
if (!fc.redirect_to_devnull) {
@@ -866,11 +1089,15 @@ class FindEmulatorImpl : public FindEmulator {
continue;
}
- string path = finddir;
- unordered_map<const DirentNode*, string> cur_read_dirs;
- if (!base->RunFind(fc, loc, 0, &path, &cur_read_dirs, results)) {
- LOG("FindEmulator: RunFind failed: %s", cmd.c_str());
- return false;
+ // bash guarantees that globs are sorted
+ sort(bases.begin(), bases.end());
+
+ for (auto [path, base] : bases) {
+ unordered_map<const DirentNode*, string> cur_read_dirs;
+ if (!base->RunFind(fc, loc, 0, &path, &cur_read_dirs, results)) {
+ LOG("FindEmulator: RunFind failed: %s", cmd.c_str());
+ return false;
+ }
}
}
@@ -897,141 +1124,7 @@ class FindEmulatorImpl : public FindEmulator {
}
private:
- static unsigned char GetDtTypeFromStat(const struct stat& st) {
- if (S_ISREG(st.st_mode)) {
- return DT_REG;
- } else if (S_ISDIR(st.st_mode)) {
- return DT_DIR;
- } else if (S_ISCHR(st.st_mode)) {
- return DT_CHR;
- } else if (S_ISBLK(st.st_mode)) {
- return DT_BLK;
- } else if (S_ISFIFO(st.st_mode)) {
- return DT_FIFO;
- } else if (S_ISLNK(st.st_mode)) {
- return DT_LNK;
- } else if (S_ISSOCK(st.st_mode)) {
- return DT_SOCK;
- } else {
- return DT_UNKNOWN;
- }
- }
-
- static unsigned char GetDtType(const string& path) {
- struct stat st;
- if (lstat(path.c_str(), &st)) {
- PERROR("stat for %s", path.c_str());
- }
- return GetDtTypeFromStat(st);
- }
-
- DirentNode* ConstructDirectoryTree(const string& path) {
- DIR* dir = opendir(path.empty() ? "." : path.c_str());
- if (!dir) {
- if (errno == ENOENT || errno == EACCES) {
- LOG("opendir failed: %s", path.c_str());
- return NULL;
- } else {
- PERROR("opendir failed: %s", path.c_str());
- }
- }
-
- DirentDirNode* n = new DirentDirNode(path);
-
- struct dirent* ent;
- while ((ent = readdir(dir)) != NULL) {
- if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..") ||
- !strcmp(ent->d_name, ".repo") || !strcmp(ent->d_name, ".git"))
- continue;
-
- string npath = path;
- if (!path.empty())
- npath += '/';
- npath += ent->d_name;
-
- DirentNode* c = NULL;
- auto d_type = ent->d_type;
- if (d_type == DT_UNKNOWN) {
- d_type = GetDtType(npath);
- CHECK(d_type != DT_UNKNOWN);
- }
- if (d_type == DT_DIR) {
- c = ConstructDirectoryTree(npath);
- if (c == NULL) {
- continue;
- }
- } else if (d_type == DT_LNK) {
- auto s = new DirentSymlinkNode(npath);
- symlinks_.push_back(make_pair(npath, s));
- c = s;
- } else {
- c = new DirentFileNode(npath, d_type);
- }
- node_cnt_++;
- n->Add(ent->d_name, c);
- }
- closedir(dir);
-
- return n;
- }
-
- void ResolveSymlinks() {
- vector<pair<string, DirentSymlinkNode*>> symlinks;
- symlinks.swap(symlinks_);
- for (const auto& p : symlinks) {
- const string& path = p.first;
- DirentSymlinkNode* s = p.second;
-
- char buf[PATH_MAX + 1];
- buf[PATH_MAX] = 0;
- ssize_t len = readlink(path.c_str(), buf, PATH_MAX);
- if (len < 0) {
- WARN("readlink failed: %s", path.c_str());
- continue;
- }
- buf[len] = 0;
-
- struct stat st;
- unsigned char type = DT_UNKNOWN;
- if (stat(path.c_str(), &st) == 0) {
- type = GetDtTypeFromStat(st);
- } else {
- s->set_errno(errno);
- LOG("stat failed: %s: %s", path.c_str(), strerror(errno));
- }
-
- if (*buf != '/') {
- const string npath = ConcatDir(Dirname(path), buf);
- bool should_fallback = false;
- const DirentNode* to = FindDir(npath, &should_fallback);
- if (to) {
- s->set_to(to);
- continue;
- }
- }
-
- if (type == DT_DIR) {
- if (path.find('/') == string::npos) {
- DirentNode* dir = ConstructDirectoryTree(path);
- if (dir != NULL) {
- s->set_to(dir);
- } else {
- s->set_errno(errno);
- }
- }
- } else if (type != DT_LNK && type != DT_UNKNOWN) {
- s->set_to(new DirentFileNode(path, type));
- }
- }
-
- if (!symlinks_.empty())
- ResolveSymlinks();
- }
-
- unique_ptr<DirentNode> root_;
- vector<pair<string, DirentSymlinkNode*>> symlinks_;
- int node_cnt_;
- bool is_initialized_;
+ DirentNode* root_ = new DirentDirNode(nullptr, "");
};
} // namespace
@@ -1066,6 +1159,10 @@ FindEmulator* FindEmulator::Get() {
return g_instance;
}
+unsigned int FindEmulator::GetNodeCount() {
+ return find_emulator_node_cnt;
+}
+
void InitFindEmulator() {
new FindEmulatorImpl();
}
diff --git a/find.h b/find.h
index 8cf3674..2a1abe4 100644
--- a/find.h
+++ b/find.h
@@ -68,6 +68,7 @@ class FindEmulator {
string* out) = 0;
static FindEmulator* Get();
+ static unsigned int GetNodeCount();
protected:
FindEmulator() = default;
diff --git a/find_test.cc b/find_test.cc
index aefe38b..c9c86b2 100644
--- a/find_test.cc
+++ b/find_test.cc
@@ -77,7 +77,7 @@ void CompareFind(const string& cmd) {
}
string emulated;
if (!FindEmulator::Get()->HandleFind(cmd, fc, Loc(), &emulated)) {
- fprintf(stderr, "Find emulator cannot parse `%s`\n", cmd.c_str());
+ fprintf(stderr, "Find emulator cannot handle `%s`\n", cmd.c_str());
exit(1);
}
@@ -109,8 +109,6 @@ void CompareFind(const string& cmd) {
}
void ExpectParseFailure(const string& cmd) {
- Run(cmd);
-
FindCommand fc;
if (fc.Parse(cmd)) {
fprintf(stderr, "Expected parse failure for `%s`\n", cmd.c_str());
@@ -131,6 +129,7 @@ int FindUnitTests() {
// drwxr-x--- top
// lrwxrwxrwx top/E -> missing
// lrwxrwxrwx top/C -> A
+ // lrwxrwxrwx top/F -> A/B
// -rw-r----- top/a
// drwxr-x--- top/A
// lrwxrwxrwx top/A/D -> B
@@ -141,6 +140,7 @@ int FindUnitTests() {
Run("cd top && ln -s A C");
Run("cd top/A && ln -s B D");
Run("cd top && ln -s missing E");
+ Run("cd top && ln -s A/B F");
Run("touch top/a top/A/b top/A/B/z");
InitFindEmulator();
@@ -153,6 +153,9 @@ int FindUnitTests() {
CompareFind("find -L top/C");
CompareFind("find -L top/C/.");
+ // A file in finddir
+ CompareFind("find top/A/b");
+
CompareFind("cd top && find C");
CompareFind("cd top && find -L C");
CompareFind("cd top/C && find .");
@@ -165,7 +168,24 @@ int FindUnitTests() {
CompareFind("find top \\! -name 'a*'");
CompareFind("find top \\( -name 'a*' \\)");
+ // Basic use of ..
+ CompareFind("cd top/C; find ../A");
+
+ // Use of .. in chdir
+ CompareFind("cd top/A/..; find .");
+
+ // .. through a symlink in chdir, should list under top/A/...
+ CompareFind("cd top/F; find ../");
+ // .. through a symlink in finddir, should do the same
+ CompareFind("cd top; find F/..");
+
+ // * in a finddir
+ CompareFind("find top/*/B");
+
ExpectParseFailure("find top -name a\\*");
+ // * in a chdir is not supported
+ ExpectParseFailure("cd top/*/B && find .");
+
return unit_test_failed ? 1 : 0;
}
diff --git a/flags.cc b/flags.cc
index 148d59a..bcc1b4f 100644
--- a/flags.cc
+++ b/flags.cc
@@ -134,6 +134,16 @@ void Flags::Parse(int argc, char** argv) {
werror_phony_looks_real = true;
} else if (!strcmp(arg, "--werror_writable")) {
werror_writable = true;
+ } else if (!strcmp(arg, "--warn_real_no_cmds_or_deps")) {
+ warn_real_no_cmds_or_deps = true;
+ } else if (!strcmp(arg, "--werror_real_no_cmds_or_deps")) {
+ warn_real_no_cmds_or_deps = true;
+ werror_real_no_cmds_or_deps = true;
+ } else if (!strcmp(arg, "--warn_real_no_cmds")) {
+ warn_real_no_cmds = true;
+ } else if (!strcmp(arg, "--werror_real_no_cmds")) {
+ warn_real_no_cmds = true;
+ werror_real_no_cmds = true;
} else if (ParseCommandLineOptionWithArg("-j", argv, &i, &num_jobs_str)) {
num_jobs = strtol(num_jobs_str, NULL, 10);
if (num_jobs <= 0) {
diff --git a/flags.h b/flags.h
index ee16c4f..279e7fe 100644
--- a/flags.h
+++ b/flags.h
@@ -56,6 +56,10 @@ struct Flags {
bool warn_phony_looks_real;
bool werror_phony_looks_real;
bool werror_writable;
+ bool warn_real_no_cmds_or_deps;
+ bool werror_real_no_cmds_or_deps;
+ bool warn_real_no_cmds;
+ bool werror_real_no_cmds;
const char* default_pool;
const char* goma_dir;
const char* ignore_dirty_pattern;
diff --git a/stats.cc b/stats.cc
index 5e7b27d..be03774 100644
--- a/stats.cc
+++ b/stats.cc
@@ -20,6 +20,7 @@
#include <mutex>
#include <vector>
+#include "find.h"
#include "flags.h"
#include "log.h"
#include "stringprintf.h"
@@ -139,4 +140,6 @@ void ReportAllStats() {
st->DumpTop();
}
delete g_stats;
+
+ LOG_STAT("%u find nodes", FindEmulator::GetNodeCount());
}
diff --git a/strutil.cc b/strutil.cc
index a585963..797775f 100644
--- a/strutil.cc
+++ b/strutil.cc
@@ -494,7 +494,7 @@ string SortWordsInString(StringPiece s) {
string ConcatDir(StringPiece b, StringPiece n) {
string r;
- if (!b.empty()) {
+ if (!b.empty() && (n.empty() || n[0] != '/')) {
b.AppendToString(&r);
r += '/';
}
diff --git a/strutil_test.cc b/strutil_test.cc
index e83b4c3..d0db1f3 100644
--- a/strutil_test.cc
+++ b/strutil_test.cc
@@ -198,6 +198,18 @@ void TestFindEndOfLineInvalidAccess() {
ASSERT_EQ(FindEndOfLine(CreateProtectedString("a\\"), 0, &lf_cnt), 2);
}
+void TestConcatDir() {
+ ASSERT_EQ(ConcatDir("", ""), "");
+ ASSERT_EQ(ConcatDir(".", ""), "");
+ ASSERT_EQ(ConcatDir("", "."), "");
+ ASSERT_EQ(ConcatDir("a", "b"), "a/b");
+ ASSERT_EQ(ConcatDir("a/", "b"), "a/b");
+ ASSERT_EQ(ConcatDir("a", "/b"), "/b");
+ ASSERT_EQ(ConcatDir("a", ".."), "");
+ ASSERT_EQ(ConcatDir("a", "../b"), "b");
+ ASSERT_EQ(ConcatDir("a", "../../b"), "../b");
+}
+
} // namespace
int main() {
@@ -214,5 +226,6 @@ int main() {
TestFindEndOfLine();
TestWordScannerInvalidAccess();
TestFindEndOfLineInvalidAccess();
+ TestConcatDir();
assert(!g_failed);
}
diff --git a/testcase/real_no_cmds.sh b/testcase/real_no_cmds.sh
new file mode 100644
index 0000000..88066d7
--- /dev/null
+++ b/testcase/real_no_cmds.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+#
+# Copyright 2020 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.
+
+set -u
+
+mk="$@"
+
+cat <<EOF > Makefile
+test: out/bad.baz out/good.baz
+ @echo "END"
+.PHONY: test
+
+out/bad.bar:
+ @echo bar >out/bad.bar
+ @echo baz >out/bad.baz
+
+out/bad.baz: out/bad.bar
+
+out/good.bar: .KATI_IMPLICIT_OUTPUTS := out/good.baz
+out/good.bar:
+ @echo bar >out/good.bar
+ @echo baz >out/good.baz
+EOF
+
+mkdir -p out
+
+if echo "${mk}" | grep -qv "kati"; then
+ # Make doesn't support these warnings, so write the expected output.
+ echo 'Makefile:9: warning: target "out/bad.baz" has no commands. Should "out/bad.bar" be using .KATI_IMPLICIT_OUTPUTS?'
+ echo 'END'
+else
+ ${mk} --werror_phony_looks_real --writable=out/ --werror_writable --warn_real_no_cmds 2>&1
+fi
+
+if echo "${mk}" | grep -qv "kati"; then
+ # Make doesn't support these warnings, so write the expected output.
+ echo 'Makefile:9: *** target "out/bad.baz" has no commands. Should "out/bad.bar" be using .KATI_IMPLICIT_OUTPUTS?'
+else
+ ${mk} --werror_phony_looks_real --writable=out/ --werror_writable --werror_real_no_cmds 2>&1
+fi
diff --git a/testcase/real_no_cmds_or_deps.sh b/testcase/real_no_cmds_or_deps.sh
new file mode 100644
index 0000000..b591803
--- /dev/null
+++ b/testcase/real_no_cmds_or_deps.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+#
+# Copyright 2020 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.
+
+set -u
+
+mk="$@"
+
+cat <<EOF > Makefile
+test: out/bad out/good
+ @echo "END"
+.PHONY: test
+
+out/bad:
+
+out/good:
+ @echo bar >out/good
+EOF
+
+mkdir -p out
+
+if echo "${mk}" | grep -qv "kati"; then
+ # Make doesn't support these warnings, so write the expected output.
+ echo 'Makefile:5: warning: target "out/bad" has no commands or deps that could create it'
+ echo 'END'
+else
+ ${mk} --werror_phony_looks_real --writable=out/ --werror_writable --warn_real_no_cmds_or_deps 2>&1
+fi
+
+if echo "${mk}" | grep -qv "kati"; then
+ # Make doesn't support these warnings, so write the expected output.
+ echo 'Makefile:5: *** target "out/bad" has no commands or deps that could create it'
+else
+ ${mk} --werror_phony_looks_real --writable=out/ --werror_writable --werror_real_no_cmds_or_deps 2>&1
+fi