15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Prints paths between gyp targets.
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import json
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from collections import deque
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def usage():
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  print """\
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Usage:
19b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  tools/gyp-explain.py [--dot] chrome_dll# gtest#
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def GetPath(graph, fro, to):
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Given a graph in (node -> list of successor nodes) dictionary format,
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  yields all paths from |fro| to |to|, starting with the shortest."""
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Storing full paths in the queue is a bit wasteful, but good enough for this.
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  q = deque([(fro, [])])
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while q:
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    t, path = q.popleft()
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if t == to:
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      yield path + [t]
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for d in graph[t]:
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      q.append((d, path + [t]))
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def MatchNode(graph, substring):
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Given a dictionary, returns the key that matches |substring| best. Exits
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if there's not one single best match."""
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  candidates = []
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for target in graph:
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if substring in target:
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      candidates.append(target)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not candidates:
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'No targets match "%s"' % substring
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sys.exit(1)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if len(candidates) > 1:
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'More than one target matches "%s": %s' % (
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        substring, ' '.join(candidates))
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sys.exit(1)
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return candidates[0]
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
54b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)def EscapeForDot(string):
55b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  suffix = '#target'
56b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  if string.endswith(suffix):
57b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    string = string[:-len(suffix)]
58b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  string = string.replace('\\', '\\\\')
59b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  return '"' + string + '"'
60b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
61b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
62b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)def GenerateDot(fro, to, paths):
63b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  """Generates an input file for graphviz's dot program."""
64b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  prefixes = [os.path.commonprefix(path) for path in paths]
65b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  prefix = os.path.commonprefix(prefixes)
66b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  print '// Build with "dot -Tpng -ooutput.png this_file.dot"'
67b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  # "strict" collapses common paths.
68b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  print 'strict digraph {'
69b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  for path in paths:
70b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    print (' -> '.join(EscapeForDot(item[len(prefix):]) for item in path)), ';'
71b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  print '}'
72b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
73b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def Main(argv):
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Check that dump.json exists and that it's not too old.
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  dump_json_dirty = False
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    st = os.stat('dump.json')
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    file_age_s = time.time() - st.st_mtime
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if file_age_s > 2 * 60 * 60:
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print 'dump.json is more than 2 hours old.'
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dump_json_dirty = True
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except OSError:
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'dump.json not found.'
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dump_json_dirty = True
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if dump_json_dirty:
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'Run'
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print '    GYP_GENERATORS=dump_dependency_json build/gyp_chromium'
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'first, then try again.'
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sys.exit(1)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  g = json.load(open('dump.json'))
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  if len(argv) not in (3, 4):
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    usage()
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sys.exit(1)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
99b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  generate_dot = argv[1] == '--dot'
100b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  if generate_dot:
101b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    argv.pop(1)
102b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fro = MatchNode(g, argv[1])
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  to = MatchNode(g, argv[2])
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  paths = list(GetPath(g, fro, to))
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if len(paths) > 0:
108b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    if generate_dot:
109b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)      GenerateDot(fro, to, paths)
110b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    else:
111b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)      print 'These paths lead from %s to %s:' % (fro, to)
112b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)      for path in paths:
113b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)        print ' -> '.join(path)
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else:
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'No paths found from %s to %s.' % (fro, to)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__':
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Main(sys.argv)
120