Skip to content

Commit

Permalink
Add multi-inputs tool
Browse files Browse the repository at this point in the history
The 'multi-inputs' option will list all <target> + <inputs> for
the given targets.

Run:
ninja -t multi-inputs <target1> <target2> <target3>

Ninja will then output:
<target1>	<input_x>
<target1>	<input_y>
<target2>	<input_x>
<target2>	<input_z>
<target3>	<input_y>
  • Loading branch information
freand76 committed Nov 12, 2024
1 parent a3fda2b commit 39274e5
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 0 deletions.
5 changes: 5 additions & 0 deletions doc/manual.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,11 @@ output files are out of date.
rebuild those targets.
_Available since Ninja 1.11._
`multi-inputs`:: given a list of targets, print a list of all inputs used
to rebuild those targets. Each line will constst of a target, a field separator
and a file path. The list produced by the tool can be helpful if one
would like to know which targets that are affected by a certain file.
`clean`:: remove built files. By default, it removes all built files
except for those created by the generator. Adding the `-g` flag also
removes built files created by the generator (see <<ref_rule,the rule
Expand Down
34 changes: 34 additions & 0 deletions misc/output_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,40 @@ def test_tool_compdb_targets(self) -> None:
self.assertEqual(expected, actual)


def test_tool_multi_inputs(self) -> None:
plan = '''
rule cat
command = cat $in $out
build out1 : cat in1
build out2 : cat in1 in2
build out3 : cat in1 in2 in3
'''
self.assertEqual(run(plan, flags='-t multi-inputs out1'),
'''out1 in1
''')

self.assertEqual(run(plan, flags='-t multi-inputs out1 out2 out3'),
'''out1 in1
out2 in1
out2 in2
out3 in1
out3 in2
out3 in3
''')

self.assertEqual(run(plan, flags='-t multi-inputs -F: out1'),
'''out1:in1
''')

self.assertEqual(
run(
plan,
flags='-t multi-inputs -F, --print0 out1 out2'
),
f'''out1,in1\0out2,in1\0out2,in2\0'''
)


def test_explain_output(self):
b = BuildDir('''\
build .FORCE: phony
Expand Down
73 changes: 73 additions & 0 deletions src/ninja.cc
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ struct NinjaMain : public BuildLogUser {
int ToolTargets(const Options* options, int argc, char* argv[]);
int ToolCommands(const Options* options, int argc, char* argv[]);
int ToolInputs(const Options* options, int argc, char* argv[]);
int ToolMultiInputs(const Options* options, int argc, char* argv[]);
int ToolClean(const Options* options, int argc, char* argv[]);
int ToolCleanDead(const Options* options, int argc, char* argv[]);
int ToolCompilationDatabase(const Options* options, int argc, char* argv[]);
Expand Down Expand Up @@ -845,6 +846,76 @@ int NinjaMain::ToolInputs(const Options* options, int argc, char* argv[]) {
return 0;
}

int NinjaMain::ToolMultiInputs(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--;

bool print0 = false;

optind = 1;
int opt;
const char* defaultfieldseparator = "\t";
const char* fieldseparator = defaultfieldseparator;
const option kLongOptions[] = { { "help", no_argument, NULL, 'h' },
{ "fieldseparator", required_argument, NULL,
'F' },
{ "print0", no_argument, NULL, '0' },
{ NULL, 0, NULL, 0 } };
while ((opt = getopt_long(argc, argv, "F:h0", kLongOptions, NULL)) != -1) {
switch (opt) {
case 'F':
fieldseparator = optarg;
break;
case '0':
print0 = true;
break;
case 'h':
default:
// clang-format off
printf(
"Usage '-t multiinputs [options] [targets]\n"
"\n"
"List all target+inputs used for a set of targets, sorted in dependency order.\n"
"Options:\n"
" -h, --help Print this message.\n"
" -F fs, --fieldseparator fs Use fs as field separator instead of default TAB.\n"
" -0, --print0 Use \\0, instead of \\n as a line terminator.\n"
);
// clang-format on
return 1;
}
}
argv += optind;
argc -= optind;

std::vector<Node*> nodes;
std::string err;
if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
Error("%s", err.c_str());
return 1;
}

for (const Node* node : nodes) {
InputsCollector collector;

collector.VisitNode(node);
std::vector<std::string> inputs = collector.GetInputsAsStrings();

for (const std::string& input : inputs) {
printf("%s%s%s", node->path().c_str(), fieldseparator, input.c_str());
if (print0) {
fputc('\0', stdout);
} else {
fputc('\n', stdout);
}
}
}

return 0;
}

int NinjaMain::ToolClean(const Options* options, int argc, char* argv[]) {
// The clean tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "clean".
Expand Down Expand Up @@ -1234,6 +1305,8 @@ const Tool* ChooseTool(const string& tool_name) {
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCommands },
{ "inputs", "list all inputs required to rebuild given targets",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolInputs},
{ "multi-inputs", "list all target+inputs required to rebuild given targets",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolMultiInputs},
{ "deps", "show dependencies stored in the deps log",
Tool::RUN_AFTER_LOGS, &NinjaMain::ToolDeps },
{ "missingdeps", "check deps log dependencies on generated files",
Expand Down

0 comments on commit 39274e5

Please sign in to comment.