From 988c847ee728de5520d07051843c02a17dd44777 Mon Sep 17 00:00:00 2001 From: David 'Digit' Turner Date: Mon, 21 Feb 2022 19:32:35 +0100 Subject: 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. --- doc/manual.asciidoc | 4 ++-- misc/output_test.py | 18 ++++++++++++++++++ src/graph.cc | 22 ++++++++++++++++++++++ src/graph.h | 3 +++ src/graph_test.cc | 33 +++++++++++++++++++++++++++++++++ src/ninja.cc | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 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* out) const { + for (std::vector::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* out) const; + const Rule* rule_; Pool* pool_; std::vector 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 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 #include #include + +#include #include #ifdef _WIN32 @@ -744,7 +746,8 @@ int NinjaMain::ToolCommands(const Options* options, int argc, char* argv[]) { return 0; } -void PrintInputs(Edge* edge, set* seen) { +void CollectInputs(Edge* edge, std::set* seen, + std::vector* result) { if (!edge) return; if (!seen->insert(edge).second) @@ -752,13 +755,41 @@ void PrintInputs(Edge* edge, set* seen) { for (vector::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 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 seen; + std::set seen; + std::vector result; for (vector::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; } -- cgit v0.12