Add script for finding unreachable targets
Change-Id: I65cec42517b3de3e5fd88f15ff67a0609d917068
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/8400
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/tools/find_unreachable.py b/tools/find_unreachable.py
new file mode 100755
index 0000000..406a312
--- /dev/null
+++ b/tools/find_unreachable.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+"""
+Finds unreachable gn targets by analysing --ide=json output
+from gn gen.
+
+Usage:
+# Generate json file with targets info, will be located at out/project.json:
+gn gen out --ide=json
+# Lists all targets that are not reachable from //:all or //ci:test_all:
+find_unreachable.py --from //:all --from //ci:test_all --json-file out/project.json
+# Lists targets unreachable from //:all that aren't referenced by any other target:
+find_unreachable.py --from //:all --json-file out/project.json --no-refs
+"""
+
+import argparse
+import json
+import sys
+
+
+def find_reachable_targets(known, graph):
+ reachable = set()
+ to_visit = known
+ while to_visit:
+ next = to_visit.pop()
+ if next in reachable:
+ continue
+ reachable.add(next)
+ to_visit += graph[next]['deps']
+ return reachable
+
+
+def find_source_targets_from(targets, graph):
+ source_targets = set(targets)
+ for target in targets:
+ source_targets -= set(graph[target]['deps'])
+ return source_targets
+
+
+def main():
+ parser = argparse.ArgumentParser(description='''
+ Tool to find unreachable targets.
+ This can be useful to inspect forgotten targets,
+ for example tests or intermediate targets in templates
+ that are no longer needed.
+ ''')
+ parser.add_argument(
+ '--json-file', required=True,
+ help='JSON file from gn gen with --ide=json option')
+ parser.add_argument(
+ '--from', action='append', dest='roots',
+ help='Known "root" targets. Can be multiple. Those targets \
+ and all their recursive dependencies are considered reachable.\
+ Examples: //:all, //ci:test_all')
+ parser.add_argument(
+ '--no-refs', action='store_true',
+ help='Show only targets that aren\'t referenced by any other target')
+ cmd_args = parser.parse_args()
+
+ with open(cmd_args.json_file) as json_file:
+ targets_graph = json.load(json_file)['targets']
+
+ reachable = find_reachable_targets(cmd_args.roots, targets_graph)
+ all = set(targets_graph.keys())
+ unreachable = all - reachable
+
+ result = find_source_targets_from(unreachable, targets_graph) \
+ if cmd_args.no_refs else unreachable
+
+ print '\n'.join(sorted(result))
+
+
+if __name__ == '__main__':
+ sys.exit(main())