Skip to content

Commit

Permalink
Make the output of ninja -t inputs deterministic
Browse files Browse the repository at this point in the history
This sorts the output of `ninja -t inputs` to make it
deterministic and remove duplicates, and adds a regression
test in output_test.py

Note that the default is to print explicit and implicit
inputs, but this can be changed using the --type=TYPE
option or the tool as in:

  ninja -t inputs --type=explicit <targets>

To print only explicit inputs. See --type=list for a list
of all possible values.
  • Loading branch information
digit-android authored and digit-google committed Feb 11, 2022
1 parent f404f00 commit 56ba9a2
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 31 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ add_library(libninja OBJECT
src/eval_env.cc
src/graph.cc
src/graphviz.cc
src/inputs_type.cc
src/json.cc
src/line_printer.cc
src/manifest_parser.cc
Expand Down Expand Up @@ -195,6 +196,7 @@ if(BUILD_TESTING)
src/dyndep_parser_test.cc
src/edit_distance_test.cc
src/graph_test.cc
src/inputs_type_test.cc
src/json_test.cc
src/lexer_test.cc
src/manifest_parser_test.cc
Expand Down
2 changes: 2 additions & 0 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ def has_re2c():
'eval_env',
'graph',
'graphviz',
'inputs_type',
'json',
'lexer',
'line_printer',
Expand Down Expand Up @@ -578,6 +579,7 @@ def has_re2c():
'disk_interface_test',
'edit_distance_test',
'graph_test',
'inputs_type_test',
'json_test',
'lexer_test',
'manifest_parser_test',
Expand Down
5 changes: 4 additions & 1 deletion doc/manual.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,10 @@ 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.
to rebuild those targets. By default this prints explicit and implicit inputs,
but this can be changed with the `--type` option as in
+ninja -t inputs --type=explicit _targets_+ to only print explicit inputs.
Use `--type=list` to list all possible values and their purpose.
_Available since Ninja 1.11._
`clean`:: remove built files. By default it removes all built files
Expand Down
38 changes: 38 additions & 0 deletions misc/output_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,43 @@ def test_entering_directory_on_stdout(self):
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
out1
out2
''')
self.assertEqual(run(plan, flags='-t inputs --type=default out3'),
'''implicit
in1
in2
out1
out2
''')
self.assertEqual(run(plan, flags='-t inputs --type=explicit out3'),
'''in1
in2
out1
out2
''')
self.assertEqual(run(plan, flags='-t inputs --type=all out3'),
'''implicit
in1
in2
order_only
out1
out2
''')


if __name__ == '__main__':
unittest.main()
32 changes: 32 additions & 0 deletions src/graph.cc
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,38 @@ std::string EdgeEnv::MakePathList(const Node* const* const span,
return result;
}

void Edge::CollectInputs(bool shell_escape, InputsType::Type inputs_type,
std::vector<std::string>* out) const {
int count;
switch (inputs_type) {
case InputsType::Default:
count = inputs_.size() - order_only_deps_;
break;
case InputsType::Explicit:
count = inputs_.size() - implicit_deps_ - order_only_deps_;
break;
default: // InputsType::All
count = inputs_.size();
}
for (int n = 0; n < count; ++n) {
std::string path = inputs_[n]->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) {
Expand Down
5 changes: 5 additions & 0 deletions src/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include "dyndep.h"
#include "eval_env.h"
#include "inputs_type.h"
#include "timestamp.h"
#include "util.h"

Expand Down Expand Up @@ -195,6 +196,10 @@ struct Edge {

void Dump(const char* prefix="") const;

// Append all edge explicit inputs to |*out|. Possibly with shell escaping.
void CollectInputs(bool shell_escape, InputsType::Type inputs_type,
std::vector<std::string>* out) const;

const Rule* rule_;
Pool* pool_;
std::vector<Node*> inputs_;
Expand Down
47 changes: 47 additions & 0 deletions src/graph_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,53 @@ 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();

inputs.clear();
edge->CollectInputs(false, InputsType::Default, &inputs);
for (size_t n = 0; n < inputs.size(); ++n)
printf(" %s", inputs[n].c_str());
printf("]\n");
EXPECT_EQ(4u, inputs.size());
EXPECT_EQ("in1", inputs[0]);
EXPECT_EQ("in2", inputs[1]);
EXPECT_EQ("in with space", inputs[2]);
EXPECT_EQ("implicit", inputs[3]);

inputs.clear();
edge->CollectInputs(false, InputsType::Explicit, &inputs);
EXPECT_EQ(3u, inputs.size());
EXPECT_EQ("in1", inputs[0]);
EXPECT_EQ("in2", inputs[1]);
EXPECT_EQ("in with space", inputs[2]);

inputs.clear();
edge->CollectInputs(false, InputsType::All, &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]);

inputs.clear();
edge->CollectInputs(true, InputsType::Explicit, &inputs);
EXPECT_EQ(3u, 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
}

TEST_F(GraphTest, VarInOutPathEscaping) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build a$ b: cat no'space with$ space$$ no\"space2\n"));
Expand Down
40 changes: 40 additions & 0 deletions src/inputs_type.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2022 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 "inputs_type.h"

#include <cstring>

// static
const InputsType::TypeInfo* InputsType::GetTypeInfos(int* count) {
static const TypeInfo kInfo[] = {
{ "default", InputsType::Default, "explicit and implicit inputs" },
{ "explicit", InputsType::Explicit, "explicit inputs only" },
{ "all", InputsType::All, "explicit, implicit and order-only inputs" },
};
*count = 3;
return kInfo;
}

bool InputsType::ParseArg(const char* arg) {
int count;
const TypeInfo* infos = GetTypeInfos(&count);
for (int n = 0; n < count; ++n) {
if (!strcmp(infos[n].name, arg)) {
value = infos[n].value;
return true;
}
}
return false;
}
53 changes: 53 additions & 0 deletions src/inputs_type.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2022 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 NINJA_INPUTS_TYPE_H_
#define NINJA_INPUTS_TYPE_H_

// A small struct representing a value for the --inputs-type option,
// used to select which inputs are printed by `-t inputs`.
//
// The Default value prints explicit and implicit inputs.
// The Explicit value prints explicit inputs only.
// The All value prints explicit, implicit as well as order-only inputs.
struct InputsType {
enum Type {
Default,
Explicit,
All,
};

InputsType() : value(Default) {}

Type value;

// Parse |arg| for a vlid --inputs-type argument. On
// success return true and set |value|. On failure return false.
bool ParseArg(const char* arg);

// Small struct describing the name and purpose of each supported
// argument. Does not include 'list'.
struct TypeInfo {
const char* name;
Type value;
const char* help;
};

// Return the address of an array of TypeInfo items whichi describe
// all supported argument strings, and their corresponding value,
// plus some help text. Set |*count| to the size of the array.
static const TypeInfo* GetTypeInfos(int* count);
};

#endif // NINJA_INPUTS_TYPE_H_
40 changes: 40 additions & 0 deletions src/inputs_type_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2022 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 "inputs_type.h"

#include "test.h"

TEST(InputsType, Test) {
InputsType type;
EXPECT_EQ(InputsType::Default, type.value);

EXPECT_TRUE(type.ParseArg("explicit"));
EXPECT_EQ(InputsType::Explicit, type.value);

EXPECT_TRUE(type.ParseArg("all"));
EXPECT_EQ(InputsType::All, type.value);

EXPECT_TRUE(type.ParseArg("default"));
EXPECT_EQ(InputsType::Default, type.value);

EXPECT_FALSE(type.ParseArg("unknown-type"));
EXPECT_EQ(InputsType::Default, type.value);
}

TEST(InputsType, GetTypeInfos) {
int count = 0;
const InputsType::TypeInfo* infos = InputsType::GetTypeInfos(&count);
EXPECT_GT(count, 0);
}
Loading

0 comments on commit 56ba9a2

Please sign in to comment.