Skip to content

Commit c1c2d19

Browse files
committed
Add --depfile flag to inputs and multi-inputs tools.
This forces the load of depfile entries from either the Ninja log or explicit depfiles to be performed when visiting the graph, thus making them visible in the result. - graph.h, graph.cc: Add optional ImplicitDepLoader* argument to InputsCollector constructor, and if provided, use it to load deps during the visit if needed. - ninja.cc: Modify `inputs` and `multi-inputs` implementation to support a new `--depfile` or `-D` flag, and create an ImplicitDepLoader instance when it is used to initialize the InputsCollector instances. NOTE: using std::make_unique<> fails on our MacOS CI builder, which still uses -std=gnu+11 for some unknown reason, so this uses `std::unique_ptr<T>::reset(new T(...))` instead :-/ - output_test.py: Add regression tests + fix minor typing issues. Fixes #2618
1 parent 370edd4 commit c1c2d19

File tree

5 files changed

+131
-12
lines changed

5 files changed

+131
-12
lines changed

doc/manual.asciidoc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,12 @@ executed in order, may be used to rebuild those targets, assuming that all
299299
output files are out of date.
300300
301301
`inputs`:: given a list of targets, print a list of all inputs used to
302-
rebuild those targets.
302+
rebuild those targets. By default, this only includes static inputs declared
303+
in the Ninja build plan itself.
304+
305+
Since Ninja 1.14, the `--depfile` flag can be used to include Ninja log / depfile
306+
inputs in the result. Note that if the build plan was regenerated since the last
307+
build, the log will contain stale entries, which may result in an incorrect output.
303308
_Available since Ninja 1.11._
304309
305310
`multi-inputs`:: print one or more sets of inputs required to build targets.
@@ -341,6 +346,10 @@ target3 file3.c
341346
+
342347
_Note that a given input may appear for several targets if it is used by more
343348
than one targets._
349+
350+
Like the `inputs` tool, this only reports static inputs from the build plan itself,
351+
but since Ninja 1.14, the `--depfile` flag can be used to also includes entries
352+
from the Ninja log or explicit depfiles.
344353
_Available since Ninja 1.13._
345354
346355
`clean`:: remove built files. By default, it removes all built files

misc/output_test.py

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def remove_non_visible_lines(raw_output: bytes) -> str:
5858
class BuildDir:
5959
def __init__(self, build_ninja: str):
6060
self.build_ninja = dedent(build_ninja)
61-
self.d = None
61+
self.d : None | tempfile.TemporaryDirectory[str] = None
6262

6363
def __enter__(self):
6464
self.d = tempfile.TemporaryDirectory()
@@ -72,6 +72,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
7272

7373
@property
7474
def path(self) -> str:
75+
assert self.d
7576
return os.path.realpath(self.d.name)
7677

7778

@@ -111,13 +112,13 @@ def run(
111112
try:
112113
if pipe:
113114
output = subprocess.check_output(
114-
[ninja_cmd], shell=True, cwd=self.d.name, env=env)
115+
[ninja_cmd], shell=True, cwd=self.path, env=env)
115116
elif platform.system() == 'Darwin':
116117
output = subprocess.check_output(['script', '-q', '/dev/null', 'bash', '-c', ninja_cmd],
117-
cwd=self.d.name, env=env)
118+
cwd=self.path, env=env)
118119
else:
119120
output = subprocess.check_output(['script', '-qfec', ninja_cmd, '/dev/null'],
120-
cwd=self.d.name, env=env)
121+
cwd=self.path, env=env)
121122
except subprocess.CalledProcessError as err:
122123
if print_err_output:
123124
sys.stdout.buffer.write(err.output)
@@ -366,8 +367,8 @@ def test_depfile_directory_creation(self) -> None:
366367
self.assertEqual(b.run('', pipe=True), dedent('''\
367368
[1/1] touch somewhere/out && echo "somewhere/out: extra" > somewhere_else/out.d
368369
'''))
369-
self.assertTrue(os.path.isfile(os.path.join(b.d.name, "somewhere", "out")))
370-
self.assertTrue(os.path.isfile(os.path.join(b.d.name, "somewhere_else", "out.d")))
370+
self.assertTrue(os.path.isfile(os.path.join(b.path, "somewhere", "out")))
371+
self.assertTrue(os.path.isfile(os.path.join(b.path, "somewhere_else", "out.d")))
371372

372373
def test_status(self) -> None:
373374
self.assertEqual(run(''), 'ninja: no work to do.\n')
@@ -468,6 +469,19 @@ def test_tool_inputs(self) -> None:
468469
f'''in1\0out1\0out 2\0'''
469470
)
470471

472+
def test_tool_inputs_with_depfile_entries(self) -> None:
473+
build_ninja = '''
474+
rule touch
475+
command = touch $out && echo "$out: extra extra2" > $depfile
476+
build out: touch
477+
depfile = out.d
478+
'''
479+
with BuildDir(build_ninja) as b:
480+
# Run the plan and create the log entry for 'out'
481+
b.run('out')
482+
self.assertEqual(b.run(flags='-t inputs out'), '')
483+
self.assertEqual(b.run(flags='-t inputs --depfile out'), 'extra\nextra2\n')
484+
471485

472486
def test_tool_compdb_targets(self) -> None:
473487
plan = '''
@@ -556,6 +570,55 @@ def test_tool_multi_inputs(self) -> None:
556570
)
557571

558572

573+
def test_tool_multi_inputs_with_depfile_entries(self) -> None:
574+
build_ninja = '''
575+
rule touch1
576+
command = touch $out && echo "$out: extra extra2" > $depfile
577+
rule touch2
578+
command = touch $out && echo "$out: extra extra3" > $depfile
579+
build out1: touch1
580+
depfile = out1.d
581+
build out2: touch2
582+
depfile = out2.d
583+
'''
584+
with BuildDir(build_ninja) as b:
585+
# Run the plan and create the log entries for 'out1' and 'out2'
586+
b.run('out1 out2')
587+
588+
self.assertEqual(b.run(flags='-t multi-inputs'), '')
589+
self.assertEqual(b.run(flags='-t multi-inputs out1'), '')
590+
self.assertEqual(b.run(flags='-t multi-inputs out2'), '')
591+
self.assertEqual(b.run(flags='-t multi-inputs out1 out2'), '')
592+
self.assertEqual(b.run(flags='-t multi-inputs out2 out1'), '')
593+
594+
self.assertEqual(b.run(flags='-t multi-inputs --depfile'), '''out1<TAB>extra
595+
out1<TAB>extra2
596+
out2<TAB>extra
597+
out2<TAB>extra3
598+
'''.replace('<TAB>', '\t'))
599+
600+
self.assertEqual(b.run(flags='-t multi-inputs --depfile out1'), '''out1<TAB>extra
601+
out1<TAB>extra2
602+
'''.replace('<TAB>', '\t'))
603+
604+
self.assertEqual(b.run(flags='-t multi-inputs --depfile out2'), '''out2<TAB>extra
605+
out2<TAB>extra3
606+
'''.replace('<TAB>', '\t'))
607+
608+
self.assertEqual(b.run(flags='-t multi-inputs --depfile out1 out2'), '''out1<TAB>extra
609+
out1<TAB>extra2
610+
out2<TAB>extra
611+
out2<TAB>extra3
612+
'''.replace('<TAB>', '\t'))
613+
614+
self.assertEqual(b.run(flags='-t multi-inputs --depfile out2 out1'), '''out2<TAB>extra
615+
out2<TAB>extra3
616+
out1<TAB>extra
617+
out1<TAB>extra2
618+
'''.replace('<TAB>', '\t'))
619+
620+
621+
559622
def test_explain_output(self):
560623
b = BuildDir('''\
561624
build .FORCE: phony

src/graph.cc

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,11 +762,26 @@ vector<Node*>::iterator ImplicitDepLoader::PreallocateSpace(Edge* edge,
762762
}
763763

764764
void InputsCollector::VisitNode(const Node* node) {
765-
const Edge* edge = node->in_edge();
765+
Edge* edge = node->in_edge();
766766

767767
if (!edge) // A source file.
768768
return;
769769

770+
if (implicit_dep_loader_ && !edge->deps_loaded_) {
771+
// Record that the deps were loaded in |deps_loaded_| as
772+
// multiple visits to the same edge can be performed by
773+
// repeated InputsCollector uses, as for the multi-inputs tool.
774+
edge->deps_loaded_ = true;
775+
776+
// Ignore errors when loading depfile entries.
777+
std::string err;
778+
if (!implicit_dep_loader_->LoadDeps(edge, &err)) {
779+
// Print the error as a warning on stderr when an error occurred during
780+
// the load.
781+
Warning("%s", err.c_str());
782+
}
783+
}
784+
770785
// Add inputs of the producing edge to the result,
771786
// except if they are themselves produced by a phony
772787
// edge.

src/graph.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,12 @@ class EdgePriorityQueue:
440440
/// vector.
441441
///
442442
struct InputsCollector {
443+
/// Constructor allows passing a pointer to an optional ImplicitDepLoader
444+
/// which will be used to load implicit dependencies from the Ninja log
445+
/// or existing depfiles. Note that this will mutate the graph.
446+
explicit InputsCollector(ImplicitDepLoader* implicit_dep_loader = nullptr)
447+
: implicit_dep_loader_(implicit_dep_loader) {}
448+
443449
/// Visit a single @arg node during this collection.
444450
void VisitNode(const Node* node);
445451

@@ -459,6 +465,7 @@ struct InputsCollector {
459465
}
460466

461467
private:
468+
ImplicitDepLoader* implicit_dep_loader_ = nullptr;
462469
std::vector<const Node*> inputs_;
463470
std::set<const Node*> visited_nodes_;
464471
};

src/ninja.cc

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -784,14 +784,20 @@ int NinjaMain::ToolInputs(const Options* options, int argc, char* argv[]) {
784784

785785
optind = 1;
786786
int opt;
787+
bool include_depfile_inputs = false;
788+
enum { OPT_DEPFILE = 1 };
787789
const option kLongOptions[] = { { "help", no_argument, NULL, 'h' },
788790
{ "no-shell-escape", no_argument, NULL, 'E' },
789791
{ "print0", no_argument, NULL, '0' },
792+
{ "depfile", no_argument, NULL, OPT_DEPFILE },
790793
{ "dependency-order", no_argument, NULL,
791794
'd' },
792795
{ NULL, 0, NULL, 0 } };
793796
while ((opt = getopt_long(argc, argv, "h0Ed", kLongOptions, NULL)) != -1) {
794797
switch (opt) {
798+
case OPT_DEPFILE:
799+
include_depfile_inputs = true;
800+
break;
795801
case 'd':
796802
dependency_order = true;
797803
break;
@@ -815,6 +821,7 @@ int NinjaMain::ToolInputs(const Options* options, int argc, char* argv[]) {
815821
" -0, --print0 Use \\0, instead of \\n as a line terminator.\n"
816822
" -E, --no-shell-escape Do not shell escape the result.\n"
817823
" -d, --dependency-order Sort results by dependency order.\n"
824+
" --depfile Include depfile inputs in the result.\n"
818825
);
819826
// clang-format on
820827
return 1;
@@ -830,7 +837,13 @@ int NinjaMain::ToolInputs(const Options* options, int argc, char* argv[]) {
830837
return 1;
831838
}
832839

833-
InputsCollector collector;
840+
std::unique_ptr<ImplicitDepLoader> implicit_dep_loader;
841+
if (include_depfile_inputs) {
842+
implicit_dep_loader.reset(new ImplicitDepLoader(
843+
&state_, &deps_log_, &disk_interface_, nullptr, nullptr));
844+
}
845+
846+
InputsCollector collector(implicit_dep_loader.get());
834847
for (const Node* node : nodes)
835848
collector.VisitNode(node);
836849

@@ -861,13 +874,18 @@ int NinjaMain::ToolMultiInputs(const Options* options, int argc, char* argv[]) {
861874
int opt;
862875
char terminator = '\n';
863876
const char* delimiter = "\t";
877+
bool include_depfile_inputs = false;
878+
enum { OPT_DEPFILE = 1 };
864879
const option kLongOptions[] = { { "help", no_argument, NULL, 'h' },
865-
{ "delimiter", required_argument, NULL,
866-
'd' },
880+
{ "delimiter", required_argument, NULL, 'd' },
881+
{ "depfile", no_argument, NULL, OPT_DEPFILE },
867882
{ "print0", no_argument, NULL, '0' },
868883
{ NULL, 0, NULL, 0 } };
869884
while ((opt = getopt_long(argc, argv, "d:h0", kLongOptions, NULL)) != -1) {
870885
switch (opt) {
886+
case OPT_DEPFILE:
887+
include_depfile_inputs = true;
888+
break;
871889
case 'd':
872890
delimiter = optarg;
873891
break;
@@ -888,6 +906,7 @@ int NinjaMain::ToolMultiInputs(const Options* options, int argc, char* argv[]) {
888906
"Options:\n"
889907
" -h, --help Print this message.\n"
890908
" -d --delimiter=DELIM Use DELIM instead of TAB for field delimiter.\n"
909+
" --depfile Include depfile inputs in the result.\n"
891910
" -0, --print0 Use \\0, instead of \\n as a line terminator.\n"
892911
);
893912
// clang-format on
@@ -904,8 +923,14 @@ int NinjaMain::ToolMultiInputs(const Options* options, int argc, char* argv[]) {
904923
return 1;
905924
}
906925

926+
std::unique_ptr<ImplicitDepLoader> implicit_dep_loader;
927+
if (include_depfile_inputs) {
928+
implicit_dep_loader.reset(new ImplicitDepLoader(
929+
&state_, &deps_log_, &disk_interface_, nullptr, nullptr));
930+
}
931+
907932
for (const Node* node : nodes) {
908-
InputsCollector collector;
933+
InputsCollector collector(implicit_dep_loader.get());
909934

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

0 commit comments

Comments
 (0)