11ed2ae45f59c2864ea05838b4da2750b85472824borenet#!/usr/bin/env python
2f171e1625bd3ea46c2980c97143168598d99987dEric Boren
31ed2ae45f59c2864ea05838b4da2750b85472824borenet# Copyright 2016 The LUCI Authors. All rights reserved.
41ed2ae45f59c2864ea05838b4da2750b85472824borenet# Use of this source code is governed under the Apache License, Version 2.0
51ed2ae45f59c2864ea05838b4da2750b85472824borenet# that can be found in the LICENSE file.
6f171e1625bd3ea46c2980c97143168598d99987dEric Boren
71ed2ae45f59c2864ea05838b4da2750b85472824borenet"""Bootstrap script to clone and forward to the recipe engine tool.
8f171e1625bd3ea46c2980c97143168598d99987dEric Boren
91ed2ae45f59c2864ea05838b4da2750b85472824borenet***********************************************************************
101ed2ae45f59c2864ea05838b4da2750b85472824borenet** DO NOT MODIFY EXCEPT IN THE PER-REPO CONFIGURATION SECTION BELOW. **
111ed2ae45f59c2864ea05838b4da2750b85472824borenet***********************************************************************
12f171e1625bd3ea46c2980c97143168598d99987dEric Boren
131ed2ae45f59c2864ea05838b4da2750b85472824borenetThis is a copy of https://github.com/luci/recipes-py/blob/master/doc/recipes.py.
141ed2ae45f59c2864ea05838b4da2750b85472824borenetTo fix bugs, fix in the github repo then copy it back to here and fix the
151ed2ae45f59c2864ea05838b4da2750b85472824borenetPER-REPO CONFIGURATION section to look like this one.
161ed2ae45f59c2864ea05838b4da2750b85472824borenet"""
17f171e1625bd3ea46c2980c97143168598d99987dEric Boren
181ed2ae45f59c2864ea05838b4da2750b85472824borenetimport os
191ed2ae45f59c2864ea05838b4da2750b85472824borenet
20342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci# IMPORTANT: Do not alter the header or footer line for the
21342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci# "PER-REPO CONFIGURATION" section below, or the autoroller will not be able
22342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci# to automatically update this file! All lines between the header and footer
23342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci# lines will be retained verbatim by the autoroller.
24342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci
25f171e1625bd3ea46c2980c97143168598d99987dEric Boren#### PER-REPO CONFIGURATION (editable) ####
261ed2ae45f59c2864ea05838b4da2750b85472824borenet# The root of the repository relative to the directory of this file.
271ed2ae45f59c2864ea05838b4da2750b85472824borenetREPO_ROOT = os.path.join(os.pardir, os.pardir)
281ed2ae45f59c2864ea05838b4da2750b85472824borenet# The path of the recipes.cfg file relative to the root of the repository.
291ed2ae45f59c2864ea05838b4da2750b85472824borenetRECIPES_CFG = os.path.join('infra', 'config', 'recipes.cfg')
301ed2ae45f59c2864ea05838b4da2750b85472824borenet#### END PER-REPO CONFIGURATION ####
31f171e1625bd3ea46c2980c97143168598d99987dEric Boren
321ed2ae45f59c2864ea05838b4da2750b85472824borenetBOOTSTRAP_VERSION = 1
33f171e1625bd3ea46c2980c97143168598d99987dEric Boren
34e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucciimport argparse
351ed2ae45f59c2864ea05838b4da2750b85472824borenetimport ast
362ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucciimport json
371ed2ae45f59c2864ea05838b4da2750b85472824borenetimport logging
381ed2ae45f59c2864ea05838b4da2750b85472824borenetimport random
391ed2ae45f59c2864ea05838b4da2750b85472824borenetimport re
401ed2ae45f59c2864ea05838b4da2750b85472824borenetimport subprocess
411ed2ae45f59c2864ea05838b4da2750b85472824borenetimport sys
421ed2ae45f59c2864ea05838b4da2750b85472824borenetimport time
431ed2ae45f59c2864ea05838b4da2750b85472824borenetimport traceback
44342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucciimport urlparse
45f171e1625bd3ea46c2980c97143168598d99987dEric Boren
462ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannuccifrom cStringIO import StringIO
47f171e1625bd3ea46c2980c97143168598d99987dEric Boren
482ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci
492ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannuccidef parse(repo_root, recipes_cfg_path):
502ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  """Parse is transitional code which parses a recipes.cfg file as either jsonpb
512ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  or as textpb.
522ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci
532ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  Args:
542ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    repo_root (str) - native path to the root of the repo we're trying to run
552ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      recipes for.
562ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    recipes_cfg_path (str) - native path to the recipes.cfg file to process.
572ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci
582ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  Returns (as tuple):
592ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine_url (str) - the url to the engine repo we want to use.
602ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine_revision (str) - the git revision for the engine to get.
612ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine_subpath (str) - the subdirectory in the engine repo we should use to
622ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      find it's recipes.py entrypoint. This is here for completeness, but will
632ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      essentially always be empty. It would be used if the recipes-py repo was
642ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      merged as a subdirectory of some other repo and you depended on that
652ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      subdirectory.
662ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    recipes_path (str) - native path to where the recipes live inside of the
672ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      current repo (i.e. the folder containing `recipes/` and/or
682ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      `recipe_modules`)
692ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  """
702ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  with open(recipes_cfg_path, 'rU') as fh:
712ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    data = fh.read()
722ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci
732ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  if data.lstrip().startswith('{'):
742ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    pb = json.loads(data)
752ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine = next(
762ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      (d for d in pb['deps'] if d['project_id'] == 'recipe_engine'), None)
772ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    if engine is None:
782ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      raise ValueError('could not find recipe_engine dep in %r'
792ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci                       % recipes_cfg_path)
802ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine_url = engine['url']
81342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci    engine_revision = engine.get('revision', '')
822ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine_subpath = engine.get('path_override', '')
832ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    recipes_path = pb.get('recipes_path', '')
842ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  else:
852ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    def get_unique(things):
862ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      if len(things) == 1:
872ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci        return things[0]
882ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      elif len(things) == 0:
892ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci        raise ValueError("Expected to get one thing, but dinna get none.")
902ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      else:
912ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci        logging.warn('Expected to get one thing, but got a bunch: %s\n%s' %
922ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci                     (things, traceback.format_stack()))
932ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci        return things[0]
942ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci
952ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    protobuf = parse_textpb(StringIO(data))
962ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci
972ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine_buf = get_unique([
982ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci        b for b in protobuf.get('deps', [])
992ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci        if b.get('project_id') == ['recipe_engine'] ])
1002ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine_url = get_unique(engine_buf['url'])
101342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci    engine_revision = get_unique(engine_buf.get('revision', ['']))
1022ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine_subpath = (get_unique(engine_buf.get('path_override', ['']))
1032ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci                      .replace('/', os.path.sep))
1042ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    recipes_path = get_unique(protobuf.get('recipes_path', ['']))
1052ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci
1062ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  recipes_path = os.path.join(repo_root, recipes_path.replace('/', os.path.sep))
1072ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  return engine_url, engine_revision, engine_subpath, recipes_path
1082ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci
1092ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci
1102ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannuccidef parse_textpb(fh):
1111ed2ae45f59c2864ea05838b4da2750b85472824borenet  """Parse the protobuf text format just well enough to understand recipes.cfg.
112f171e1625bd3ea46c2980c97143168598d99987dEric Boren
1131ed2ae45f59c2864ea05838b4da2750b85472824borenet  We don't use the protobuf library because we want to be as self-contained
1141ed2ae45f59c2864ea05838b4da2750b85472824borenet  as possible in this bootstrap, so it can be simply vendored into a client
1151ed2ae45f59c2864ea05838b4da2750b85472824borenet  repo.
116f171e1625bd3ea46c2980c97143168598d99987dEric Boren
1171ed2ae45f59c2864ea05838b4da2750b85472824borenet  We assume all fields are repeated since we don't have a proto spec to work
1181ed2ae45f59c2864ea05838b4da2750b85472824borenet  with.
119f171e1625bd3ea46c2980c97143168598d99987dEric Boren
1201ed2ae45f59c2864ea05838b4da2750b85472824borenet  Args:
1211ed2ae45f59c2864ea05838b4da2750b85472824borenet    fh: a filehandle containing the text format protobuf.
1221ed2ae45f59c2864ea05838b4da2750b85472824borenet  Returns:
1231ed2ae45f59c2864ea05838b4da2750b85472824borenet    A recursive dictionary of lists.
1241ed2ae45f59c2864ea05838b4da2750b85472824borenet  """
125f171e1625bd3ea46c2980c97143168598d99987dEric Boren  def parse_atom(field, text):
1261ed2ae45f59c2864ea05838b4da2750b85472824borenet    if text == 'true':
1271ed2ae45f59c2864ea05838b4da2750b85472824borenet      return True
1281ed2ae45f59c2864ea05838b4da2750b85472824borenet    if text == 'false':
1291ed2ae45f59c2864ea05838b4da2750b85472824borenet      return False
130f171e1625bd3ea46c2980c97143168598d99987dEric Boren
131f171e1625bd3ea46c2980c97143168598d99987dEric Boren    # repo_type is an enum. Since it does not have quotes,
132f171e1625bd3ea46c2980c97143168598d99987dEric Boren    # invoking literal_eval would fail.
133f171e1625bd3ea46c2980c97143168598d99987dEric Boren    if field == 'repo_type':
134f171e1625bd3ea46c2980c97143168598d99987dEric Boren      return text
135f171e1625bd3ea46c2980c97143168598d99987dEric Boren
1361ed2ae45f59c2864ea05838b4da2750b85472824borenet    return ast.literal_eval(text)
137f171e1625bd3ea46c2980c97143168598d99987dEric Boren
1381ed2ae45f59c2864ea05838b4da2750b85472824borenet  ret = {}
1391ed2ae45f59c2864ea05838b4da2750b85472824borenet  for line in fh:
1401ed2ae45f59c2864ea05838b4da2750b85472824borenet    line = line.strip()
1411ed2ae45f59c2864ea05838b4da2750b85472824borenet    m = re.match(r'(\w+)\s*:\s*(.*)', line)
1421ed2ae45f59c2864ea05838b4da2750b85472824borenet    if m:
143f171e1625bd3ea46c2980c97143168598d99987dEric Boren      ret.setdefault(m.group(1), []).append(parse_atom(m.group(1), m.group(2)))
1441ed2ae45f59c2864ea05838b4da2750b85472824borenet      continue
145f171e1625bd3ea46c2980c97143168598d99987dEric Boren
1461ed2ae45f59c2864ea05838b4da2750b85472824borenet    m = re.match(r'(\w+)\s*{', line)
1471ed2ae45f59c2864ea05838b4da2750b85472824borenet    if m:
1482ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      subparse = parse_textpb(fh)
1491ed2ae45f59c2864ea05838b4da2750b85472824borenet      ret.setdefault(m.group(1), []).append(subparse)
1501ed2ae45f59c2864ea05838b4da2750b85472824borenet      continue
151f171e1625bd3ea46c2980c97143168598d99987dEric Boren
1521ed2ae45f59c2864ea05838b4da2750b85472824borenet    if line == '}':
1531ed2ae45f59c2864ea05838b4da2750b85472824borenet      return ret
1541ed2ae45f59c2864ea05838b4da2750b85472824borenet    if line == '':
1551ed2ae45f59c2864ea05838b4da2750b85472824borenet      continue
156f171e1625bd3ea46c2980c97143168598d99987dEric Boren
1571ed2ae45f59c2864ea05838b4da2750b85472824borenet    raise ValueError('Could not understand line: <%s>' % line)
158f171e1625bd3ea46c2980c97143168598d99987dEric Boren
1591ed2ae45f59c2864ea05838b4da2750b85472824borenet  return ret
160f171e1625bd3ea46c2980c97143168598d99987dEric Boren
161f171e1625bd3ea46c2980c97143168598d99987dEric Boren
1621ed2ae45f59c2864ea05838b4da2750b85472824borenetdef _subprocess_call(argv, **kwargs):
1631ed2ae45f59c2864ea05838b4da2750b85472824borenet  logging.info('Running %r', argv)
1641ed2ae45f59c2864ea05838b4da2750b85472824borenet  return subprocess.call(argv, **kwargs)
165f171e1625bd3ea46c2980c97143168598d99987dEric Boren
166e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci
1671ed2ae45f59c2864ea05838b4da2750b85472824borenetdef _subprocess_check_call(argv, **kwargs):
1681ed2ae45f59c2864ea05838b4da2750b85472824borenet  logging.info('Running %r', argv)
1691ed2ae45f59c2864ea05838b4da2750b85472824borenet  subprocess.check_call(argv, **kwargs)
170f171e1625bd3ea46c2980c97143168598d99987dEric Boren
171f171e1625bd3ea46c2980c97143168598d99987dEric Boren
172e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannuccidef find_engine_override(argv):
173e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  """Since the bootstrap process attempts to defer all logic to the recipes-py
174e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  repo, we need to be aware if the user is overriding the recipe_engine
175e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  dependency. This looks for and returns the overridden recipe_engine path, if
176e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  any, or None if the user didn't override it."""
177e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  PREFIX = 'recipe_engine='
178e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci
179e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  p = argparse.ArgumentParser()
180e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  p.add_argument('-O', '--project-override', action='append')
181e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  args, _ = p.parse_known_args(argv)
182e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  for override in args.project_override or ():
183e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci    if override.startswith(PREFIX):
184e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      return override[len(PREFIX):]
185e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  return None
186e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci
187e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci
1881ed2ae45f59c2864ea05838b4da2750b85472824borenetdef main():
1891ed2ae45f59c2864ea05838b4da2750b85472824borenet  if '--verbose' in sys.argv:
1901ed2ae45f59c2864ea05838b4da2750b85472824borenet    logging.getLogger().setLevel(logging.INFO)
191f171e1625bd3ea46c2980c97143168598d99987dEric Boren
192e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  if REPO_ROOT is None or RECIPES_CFG is None:
193e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci    logging.error(
194e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      'In order to use this script, please copy it to your repo and '
195e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      'replace the REPO_ROOT and RECIPES_CFG values with approprite paths.')
196e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci    sys.exit(1)
197e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci
1981ed2ae45f59c2864ea05838b4da2750b85472824borenet  if sys.platform.startswith(('win', 'cygwin')):
1991ed2ae45f59c2864ea05838b4da2750b85472824borenet    git = 'git.bat'
2001ed2ae45f59c2864ea05838b4da2750b85472824borenet  else:
2011ed2ae45f59c2864ea05838b4da2750b85472824borenet    git = 'git'
202f171e1625bd3ea46c2980c97143168598d99987dEric Boren
2031ed2ae45f59c2864ea05838b4da2750b85472824borenet  # Find the repository and config file to operate on.
2041ed2ae45f59c2864ea05838b4da2750b85472824borenet  repo_root = os.path.abspath(
2051ed2ae45f59c2864ea05838b4da2750b85472824borenet      os.path.join(os.path.dirname(__file__), REPO_ROOT))
2061ed2ae45f59c2864ea05838b4da2750b85472824borenet  recipes_cfg_path = os.path.join(repo_root, RECIPES_CFG)
207f171e1625bd3ea46c2980c97143168598d99987dEric Boren
2082ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci  engine_url, engine_revision, engine_subpath, recipes_path = parse(
2092ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    repo_root, recipes_cfg_path)
2102ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci
211e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  engine_path = find_engine_override(sys.argv[1:])
212342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci  if not engine_path and engine_url.startswith('file://'):
213342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci    engine_path = urlparse.urlparse(engine_url).path
214342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci
215e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci  if not engine_path:
216342977ced701d06df2b3d2eedd8b64aeae1eb5c5Robert Iannucci    deps_path = os.path.join(recipes_path, '.recipe_deps')
217e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci    # Ensure that we have the recipe engine cloned.
2182ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine_root_path = os.path.join(deps_path, 'recipe_engine')
2192ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci    engine_path = os.path.join(engine_root_path, engine_subpath)
220e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci    def ensure_engine():
221e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      if not os.path.exists(deps_path):
222e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci        os.makedirs(deps_path)
2232ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      if not os.path.exists(engine_root_path):
2242ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci        _subprocess_check_call([git, 'clone', engine_url, engine_root_path])
225e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci
226e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      needs_fetch = _subprocess_call(
227e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci          [git, 'rev-parse', '--verify', '%s^{commit}' % engine_revision],
2282ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci          cwd=engine_root_path, stdout=open(os.devnull, 'w'))
229e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      if needs_fetch:
2302ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci        _subprocess_check_call([git, 'fetch'], cwd=engine_root_path)
231e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      _subprocess_check_call(
2322ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci          [git, 'checkout', '--quiet', engine_revision], cwd=engine_root_path)
233e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci
234e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci    try:
235e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      ensure_engine()
236e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci    except subprocess.CalledProcessError:
237e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      logging.exception('ensure_engine failed')
238e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci
239e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      # Retry errors.
240e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      time.sleep(random.uniform(2,5))
241e8b508556cdd1b18b7461301b35e8a20d3fe35e2Robert Iannucci      ensure_engine()
242f171e1625bd3ea46c2980c97143168598d99987dEric Boren
243f171e1625bd3ea46c2980c97143168598d99987dEric Boren  args = ['--package', recipes_cfg_path] + sys.argv[1:]
2441ed2ae45f59c2864ea05838b4da2750b85472824borenet  return _subprocess_call([
2451ed2ae45f59c2864ea05838b4da2750b85472824borenet      sys.executable, '-u',
2462ba659e85ffeeb392f0dfd94dd5ee3c7d89787d6Robert Iannucci      os.path.join(engine_path, 'recipes.py')] + args)
247f171e1625bd3ea46c2980c97143168598d99987dEric Boren
2481ed2ae45f59c2864ea05838b4da2750b85472824borenetif __name__ == '__main__':
2491ed2ae45f59c2864ea05838b4da2750b85472824borenet  sys.exit(main())
250