summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid 'Digit' Turner <digit+github@google.com>2022-02-21 19:32:35 +0100
committerDavid 'Digit' Turner <digit+github@google.com>2022-03-21 14:44:26 +0100
commit988c847ee728de5520d07051843c02a17dd44777 (patch)
tree2576c688b1db657ebaa21a92a02bada0dca81e6f
parent1463b1fc3135358ebb19d29448e99ef62e5f1ee6 (diff)
downloadninja-988c847ee728de5520d07051843c02a17dd44777.tar.gz
Make the output of `ninja -t inputs` deterministic
This sorts the output of `ninja -t inputs` to make it deterministic and remove duplicates, and adds a regression test in output_test.py + Ensure all inputs are listed, not only explicit ones. + Document the `inputs` tool in doc/manual.asciidoc.
-rw-r--r--doc/manual.asciidoc4
-rwxr-xr-xmisc/output_test.py18
-rw-r--r--src/graph.cc22
-rw-r--r--src/graph.h3
-rw-r--r--src/graph_test.cc33
-rw-r--r--src/ninja.cc51
6 files changed, 123 insertions, 8 deletions
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index 00b293c..2062a2a 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -257,8 +257,8 @@ than the _depth_ mode.
executed in order, may be used to rebuild those targets, assuming that all
output files are out of date.
-`inputs`:: given a list of targets, print a list of all inputs which are used
-to rebuild those targets.
+`inputs`:: given a list of targets, print a list of all inputs used to
+rebuild those targets.
_Available since Ninja 1.11._
`clean`:: remove built files. By default it removes all built files
diff --git a/misc/output_test.py b/misc/output_test.py
index 45698f1..141716c 100755
--- a/misc/output_test.py
+++ b/misc/output_test.py
@@ -134,5 +134,23 @@ red
output = run(Output.BUILD_SIMPLE_ECHO, flags='-C$PWD', pipe=True)
self.assertEqual(output.splitlines()[0][:25], "ninja: Entering directory")
+ def test_tool_inputs(self):
+ plan = '''
+rule cat
+ command = cat $in $out
+build out1 : cat in1
+build out2 : cat in2 out1
+build out3 : cat out2 out1 | implicit || order_only
+'''
+ self.assertEqual(run(plan, flags='-t inputs out3'),
+'''implicit
+in1
+in2
+order_only
+out1
+out2
+''')
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/graph.cc b/src/graph.cc
index 8d58587..43ba45a 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -456,6 +456,28 @@ std::string EdgeEnv::MakePathList(const Node* const* const span,
return result;
}
+void Edge::CollectInputs(bool shell_escape,
+ std::vector<std::string>* out) const {
+ for (std::vector<Node*>::const_iterator it = inputs_.begin();
+ it != inputs_.end(); ++it) {
+ std::string path = (*it)->PathDecanonicalized();
+ if (shell_escape) {
+ std::string unescaped;
+ unescaped.swap(path);
+#ifdef _WIN32
+ GetWin32EscapedString(unescaped, &path);
+#else
+ GetShellEscapedString(unescaped, &path);
+#endif
+ }
+#if __cplusplus >= 201103L
+ out->push_back(std::move(path));
+#else
+ out->push_back(path);
+#endif
+ }
+}
+
std::string Edge::EvaluateCommand(const bool incl_rsp_file) const {
string command = GetBinding("command");
if (incl_rsp_file) {
diff --git a/src/graph.h b/src/graph.h
index 141b439..9de67d2 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -195,6 +195,9 @@ struct Edge {
void Dump(const char* prefix="") const;
+ // Append all edge explicit inputs to |*out|. Possibly with shell escaping.
+ void CollectInputs(bool shell_escape, std::vector<std::string>* out) const;
+
const Rule* rule_;
Pool* pool_;
std::vector<Node*> inputs_;
diff --git a/src/graph_test.cc b/src/graph_test.cc
index 5314bc5..9dba8af 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -215,6 +215,39 @@ TEST_F(GraphTest, RootNodes) {
}
}
+TEST_F(GraphTest, CollectInputs) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(
+ &state_,
+ "build out$ 1: cat in1 in2 in$ with$ space | implicit || order_only\n"));
+
+ std::vector<std::string> inputs;
+ Edge* edge = GetNode("out 1")->in_edge();
+
+ // Test without shell escaping.
+ inputs.clear();
+ edge->CollectInputs(false, &inputs);
+ EXPECT_EQ(5u, inputs.size());
+ EXPECT_EQ("in1", inputs[0]);
+ EXPECT_EQ("in2", inputs[1]);
+ EXPECT_EQ("in with space", inputs[2]);
+ EXPECT_EQ("implicit", inputs[3]);
+ EXPECT_EQ("order_only", inputs[4]);
+
+ // Test with shell escaping.
+ inputs.clear();
+ edge->CollectInputs(true, &inputs);
+ EXPECT_EQ(5u, inputs.size());
+ EXPECT_EQ("in1", inputs[0]);
+ EXPECT_EQ("in2", inputs[1]);
+#ifdef _WIN32
+ EXPECT_EQ("\"in with space\"", inputs[2]);
+#else
+ EXPECT_EQ("'in with space'", inputs[2]);
+#endif
+ EXPECT_EQ("implicit", inputs[3]);
+ EXPECT_EQ("order_only", inputs[4]);
+}
+
TEST_F(GraphTest, VarInOutPathEscaping) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build a$ b: cat no'space with$ space$$ no\"space2\n"));
diff --git a/src/ninja.cc b/src/ninja.cc
index ad0912e..aea430d 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -17,6 +17,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+
+#include <algorithm>
#include <cstdlib>
#ifdef _WIN32
@@ -744,7 +746,8 @@ int NinjaMain::ToolCommands(const Options* options, int argc, char* argv[]) {
return 0;
}
-void PrintInputs(Edge* edge, set<Edge*>* seen) {
+void CollectInputs(Edge* edge, std::set<Edge*>* seen,
+ std::vector<std::string>* result) {
if (!edge)
return;
if (!seen->insert(edge).second)
@@ -752,13 +755,41 @@ void PrintInputs(Edge* edge, set<Edge*>* seen) {
for (vector<Node*>::iterator in = edge->inputs_.begin();
in != edge->inputs_.end(); ++in)
- PrintInputs((*in)->in_edge(), seen);
+ CollectInputs((*in)->in_edge(), seen, result);
- if (!edge->is_phony())
- puts(edge->GetBinding("in_newline").c_str());
+ if (!edge->is_phony()) {
+ edge->CollectInputs(true, result);
+ }
}
int NinjaMain::ToolInputs(const Options* options, int argc, char* argv[]) {
+ // The inputs tool uses getopt, and expects argv[0] to contain the name of
+ // the tool, i.e. "inputs".
+ argc++;
+ argv--;
+ optind = 1;
+ int opt;
+ const option kLongOptions[] = { { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 } };
+ while ((opt = getopt_long(argc, argv, "h", kLongOptions, NULL)) != -1) {
+ switch (opt) {
+ case 'h':
+ default:
+ // clang-format off
+ printf(
+"Usage '-t inputs [options] [targets]\n"
+"\n"
+"List all inputs used for a set of targets. Note that this includes\n"
+"explicit, implicit and order-only inputs, but not validation ones.\n\n"
+"Options:\n"
+" -h, --help Print this message.\n");
+ // clang-format on
+ return 1;
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
vector<Node*> nodes;
string err;
if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
@@ -766,9 +797,17 @@ int NinjaMain::ToolInputs(const Options* options, int argc, char* argv[]) {
return 1;
}
- set<Edge*> seen;
+ std::set<Edge*> seen;
+ std::vector<std::string> result;
for (vector<Node*>::iterator in = nodes.begin(); in != nodes.end(); ++in)
- PrintInputs((*in)->in_edge(), &seen);
+ CollectInputs((*in)->in_edge(), &seen, &result);
+
+ // Make output deterministic by sorting then removing duplicates.
+ std::sort(result.begin(), result.end());
+ result.erase(std::unique(result.begin(), result.end()), result.end());
+
+ for (size_t n = 0; n < result.size(); ++n)
+ puts(result[n].c_str());
return 0;
}