cit.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2015 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Wrapper for updating and calling infra.git tools.
  6. This tool does a two things:
  7. * Maintains a infra.git checkout pinned at "deployed" in the home dir
  8. * Acts as an alias to infra.tools.*
  9. * Acts as an alias to infra.git/cipd/<executable>
  10. """
  11. # TODO(hinoka,iannucci): Pre-pack infra tools in cipd package with vpython spec.
  12. from __future__ import print_function
  13. import argparse
  14. import sys
  15. import os
  16. import re
  17. import subprocess2 as subprocess
  18. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  19. GCLIENT = os.path.join(SCRIPT_DIR, 'gclient.py')
  20. TARGET_DIR = os.path.expanduser(os.path.join('~', '.chrome-infra'))
  21. INFRA_DIR = os.path.join(TARGET_DIR, 'infra')
  22. def get_git_rev(target, branch):
  23. return subprocess.check_output(
  24. ['git', 'log', '--format=%B', '-n1', branch], cwd=target)
  25. def need_to_update(branch):
  26. """Checks to see if we need to update the ~/.chrome-infra/infra checkout."""
  27. try:
  28. cmd = [sys.executable, GCLIENT, 'revinfo']
  29. subprocess.check_call(
  30. cmd, cwd=os.path.join(TARGET_DIR), stdout=subprocess.DEVNULL)
  31. except subprocess.CalledProcessError:
  32. return True # Gclient failed, definitely need to update.
  33. except OSError:
  34. return True # Gclient failed, definitely need to update.
  35. if not os.path.isdir(INFRA_DIR):
  36. return True
  37. local_rev = get_git_rev(INFRA_DIR, 'HEAD')
  38. subprocess.check_call(
  39. ['git', 'fetch', 'origin'], cwd=INFRA_DIR,
  40. stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
  41. origin_rev = get_git_rev(INFRA_DIR, 'origin/%s' % (branch,))
  42. return origin_rev != local_rev
  43. def ensure_infra(branch):
  44. """Ensures that infra.git is present in ~/.chrome-infra."""
  45. sys.stderr.write(
  46. 'Fetching infra@%s into %s, may take a couple of minutes...' % (
  47. branch, TARGET_DIR))
  48. sys.stderr.flush()
  49. if not os.path.isdir(TARGET_DIR):
  50. os.mkdir(TARGET_DIR)
  51. if not os.path.exists(os.path.join(TARGET_DIR, '.gclient')):
  52. subprocess.check_call(
  53. [sys.executable, os.path.join(SCRIPT_DIR, 'fetch.py'), 'infra'],
  54. cwd=TARGET_DIR,
  55. stdout=subprocess.DEVNULL)
  56. subprocess.check_call(
  57. [sys.executable, GCLIENT, 'sync', '--revision', 'origin/%s' % (branch,)],
  58. cwd=TARGET_DIR,
  59. stdout=subprocess.DEVNULL)
  60. sys.stderr.write(' done.\n')
  61. sys.stderr.flush()
  62. def is_exe(filename):
  63. """Given a full filepath, return true if the file is an executable."""
  64. if sys.platform.startswith('win'):
  65. return filename.endswith('.exe')
  66. return os.path.isfile(filename) and os.access(filename, os.X_OK)
  67. def get_available_tools():
  68. """Returns a tuple of (list of infra tools, list of cipd tools)"""
  69. infra_tools = []
  70. cipd_tools = []
  71. starting = os.path.join(INFRA_DIR, 'infra', 'tools')
  72. for root, _, files in os.walk(starting):
  73. if '__main__.py' in files:
  74. infra_tools.append(root[len(starting)+1:].replace(os.path.sep, '.'))
  75. cipd = os.path.join(INFRA_DIR, 'cipd')
  76. for fn in os.listdir(cipd):
  77. if is_exe(os.path.join(cipd, fn)):
  78. cipd_tools.append(fn)
  79. return (sorted(infra_tools), sorted(cipd_tools))
  80. def usage():
  81. infra_tools, cipd_tools = get_available_tools()
  82. print("""usage: cit.py <name of tool> [args for tool]
  83. Wrapper for maintaining and calling tools in:
  84. "infra.git, infra.tools.*"
  85. "infra.git/cipd/*"
  86. Available infra tools are:""")
  87. for tool in infra_tools:
  88. print(' * %s' % tool)
  89. print("""
  90. Available cipd tools are:""")
  91. for tool in cipd_tools:
  92. print(' * %s' % tool)
  93. def run(args):
  94. if not args:
  95. return usage()
  96. env = os.environ
  97. tool_name = args[0]
  98. # Check to see if it is a infra tool first.
  99. tool_dir = os.path.join(
  100. INFRA_DIR, 'infra', 'tools', *tool_name.split('.'))
  101. cipd_file = os.path.join(INFRA_DIR, 'cipd', tool_name)
  102. if sys.platform.startswith('win'):
  103. cipd_file += '.exe'
  104. if (os.path.isdir(tool_dir)
  105. and os.path.isfile(os.path.join(tool_dir, '__main__.py'))):
  106. cmd = [
  107. 'vpython', '-vpython-spec', os.path.join(INFRA_DIR, '.vpython'),
  108. '-m', 'infra.tools.%s' % tool_name]
  109. # Augment PYTHONPATH so that infra.tools.<tool_name> can be found without
  110. # running from that directory, which would mess up any relative paths passed
  111. # to the tool.
  112. env['PYTHONPATH'] = INFRA_DIR + os.pathsep + env['PYTHONPATH']
  113. elif os.path.isfile(cipd_file) and is_exe(cipd_file):
  114. cmd = [cipd_file]
  115. else:
  116. print('Unknown tool "%s"' % tool_name, file=sys.stderr)
  117. return usage()
  118. # Add the remaining arguments.
  119. cmd.extend(args[1:])
  120. return subprocess.call(cmd, env=env)
  121. def main():
  122. parser = argparse.ArgumentParser("Chrome Infrastructure CLI.")
  123. parser.add_argument('-b', '--infra-branch', default='cit',
  124. help="The name of the 'infra' branch to use (default is %(default)s).")
  125. parser.add_argument('args', nargs=argparse.REMAINDER)
  126. args, extras = parser.parse_known_args()
  127. if args.args and args.args[0] == '--':
  128. args.args.pop(0)
  129. if extras:
  130. args.args = extras + args.args
  131. if need_to_update(args.infra_branch):
  132. ensure_infra(args.infra_branch)
  133. return run(args.args)
  134. if __name__ == '__main__':
  135. sys.exit(main())