Index: recipes.py |
diff --git a/recipes.py b/recipes.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..e03508472d375d2f74ad5e390e42d9d55e38fb2a |
--- /dev/null |
+++ b/recipes.py |
@@ -0,0 +1,136 @@ |
+#!/usr/bin/env python |
+ |
+# Copyright 2015 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. |
+ |
+"""Bootstrap script to clone and forward to the recipe engine tool.""" |
+ |
+import ast |
+import logging |
+import os |
+import random |
+import re |
+import subprocess |
+import sys |
+import time |
+import traceback |
+ |
+BOOTSTRAP_VERSION = 1 |
+# The root of the repository relative to the directory of this file. |
+REPO_ROOT = '' |
+# The path of the recipes.cfg file relative to the root of the repository. |
+RECIPES_CFG = os.path.join('infra', 'config', 'recipes.cfg') |
+ |
+ |
+def parse_protobuf(fh): |
+ """Parse the protobuf text format just well enough to understand recipes.cfg. |
+ |
+ We don't use the protobuf library because we want to be as self-contained |
+ as possible in this bootstrap, so it can be simply vendored into a client |
+ repo. |
+ |
+ We assume all fields are repeated since we don't have a proto spec to work |
+ with. |
+ |
+ Args: |
+ fh: a filehandle containing the text format protobuf. |
+ Returns: |
+ A recursive dictionary of lists. |
+ """ |
+ def parse_atom(text): |
+ if text == 'true': return True |
+ if text == 'false': return False |
+ return ast.literal_eval(text) |
+ |
+ ret = {} |
+ for line in fh: |
+ line = line.strip() |
+ m = re.match(r'(\w+)\s*:\s*(.*)', line) |
+ if m: |
+ ret.setdefault(m.group(1), []).append(parse_atom(m.group(2))) |
+ continue |
+ |
+ m = re.match(r'(\w+)\s*{', line) |
+ if m: |
+ subparse = parse_protobuf(fh) |
+ ret.setdefault(m.group(1), []).append(subparse) |
+ continue |
+ |
+ if line == '}': return ret |
+ if line == '': continue |
+ |
+ raise Exception('Could not understand line: <%s>' % line) |
+ |
+ return ret |
+ |
+ |
+def get_unique(things): |
+ if len(things) == 1: |
+ return things[0] |
+ elif len(things) == 0: |
+ raise ValueError("Expected to get one thing, but dinna get none.") |
+ else: |
+ logging.warn('Expected to get one thing, but got a bunch: %s\n%s' % |
+ (things, traceback.format_stack())) |
+ return things[0] |
+ |
+ |
+def main(): |
+ if sys.platform.startswith(('win', 'cygwin')): |
+ git = 'git.bat' |
+ else: |
+ git = 'git' |
+ |
+ # Find the repository and config file to operate on. |
+ repo_root = os.path.abspath( |
+ os.path.join(os.path.dirname(__file__), REPO_ROOT)) |
+ recipes_cfg_path = os.path.join(repo_root, RECIPES_CFG) |
+ |
+ with open(recipes_cfg_path, 'rU') as fh: |
+ protobuf = parse_protobuf(fh) |
+ |
+ engine_buf = get_unique([ |
+ b for b in protobuf['deps'] if b.get('project_id') == ['recipe_engine'] ]) |
+ engine_url = get_unique(engine_buf['url']) |
+ engine_revision = get_unique(engine_buf['revision']) |
+ engine_subpath = (get_unique(engine_buf.get('path_override', [''])) |
+ .replace('/', os.path.sep)) |
+ |
+ recipes_path = os.path.join(repo_root, |
+ get_unique(protobuf['recipes_path']).replace('/', os.path.sep)) |
+ deps_path = os.path.join(recipes_path, '.recipe_deps') |
+ engine_path = os.path.join(deps_path, 'recipe_engine') |
+ |
+ # Ensure that we have the recipe engine cloned. |
+ def ensure_engine(): |
+ if not os.path.exists(deps_path): |
+ os.makedirs(deps_path) |
+ if not os.path.exists(engine_path): |
+ subprocess.check_call([git, 'clone', engine_url, engine_path]) |
+ |
+ needs_fetch = subprocess.call( |
+ [git, 'rev-parse', '--verify', '%s^{commit}' % engine_revision], |
+ cwd=engine_path, stdout=open(os.devnull, 'w')) |
+ if needs_fetch: |
+ subprocess.check_call([git, 'fetch'], cwd=engine_path) |
+ subprocess.check_call( |
+ [git, 'checkout', '--quiet', engine_revision], cwd=engine_path) |
+ |
+ try: |
+ ensure_engine() |
+ except subprocess.CalledProcessError as e: |
+ if e.returncode == 128: # Thrown when git gets a lock error. |
+ time.sleep(random.uniform(2,5)) |
+ ensure_engine() |
+ else: |
+ raise |
+ |
+ args = ['--package', recipes_cfg_path, |
+ '--bootstrap-script', __file__] + sys.argv[1:] |
+ return subprocess.call([ |
+ sys.executable, '-u', |
+ os.path.join(engine_path, engine_subpath, 'recipes.py')] + args) |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |
iannucci
2015/12/04 20:17:17
This is getting a bit long for copy-paste. Is ther
|