Index: scripts/slave/bot_update.py |
diff --git a/scripts/slave/bot_update.py b/scripts/slave/bot_update.py |
index 4543e6f0b8f3c03ac415a5cdd4ac54359d4a5252..63de04e334e1155436e81d828a866ef11fd86fb0 100755 |
--- a/scripts/slave/bot_update.py |
+++ b/scripts/slave/bot_update.py |
@@ -3,6 +3,8 @@ |
# Use of this source code is governed by a BSD-style license that can be |
# found in the LICENSE file. |
+# TODO(hinoka): Use logging. |
+ |
import codecs |
import copy |
import cStringIO |
@@ -507,101 +509,28 @@ def gclient_runhooks(gyp_envs): |
call(gclient_bin, 'runhooks', env=env) |
-def create_less_than_or_equal_regex(number): |
- """ Return a regular expression to test whether an integer less than or equal |
- to 'number' is present in a given string. |
- """ |
- |
- # In three parts, build a regular expression that match any numbers smaller |
- # than 'number'. |
- # For example, 78656 would give a regular expression that looks like: |
- # Part 1 |
- # (78356| # 78356 |
- # Part 2 |
- # 7835[0-5]| # 78350-78355 |
- # 783[0-4][0-9]| # 78300-78349 |
- # 78[0-2][0-9]{2}| # 78000-78299 |
- # 7[0-7][0-9]{3}| # 70000-77999 |
- # [0-6][0-9]{4}| # 10000-69999 |
- # Part 3 |
- # [0-9]{1,4} # 0-9999 |
- |
- # Part 1: Create an array with all the regexes, as described above. |
- # Prepopulate it with the number itself. |
- number = str(number) |
- expressions = [number] |
- |
- # Convert the number to a list, so we can translate digits in it to |
- # expressions. |
- num_list = list(number) |
- num_len = len(num_list) |
- |
- # Part 2: Go through all the digits in the number, starting from the end. |
- # Each iteration appends a line to 'expressions'. |
- for index in range (num_len - 1, -1, -1): |
- # Convert this digit back to an integer. |
- digit = int(num_list[index]) |
- |
- # Part 2.1: No processing if this digit is a zero. |
- if digit == 0: |
- continue |
- |
- # Part 2.2: We switch the current digit X by a range "[0-(X-1)]". |
- if digit == 1: |
- num_list[index] = '0' |
- else: |
- num_list[index] = '[0-%d]' % (digit - 1) |
- |
- # Part 2.3: We set all following digits to be "[0-9]". |
- # Since we just decrementented a digit in a most important position, all |
- # following digits don't matter. The possible numbers will always be smaller |
- # than before we decremented. |
- if (index + 1) < num_len: |
- if (num_len - (index + 1)) == 1: |
- num_list[index + 1] = '[0-9]' |
- else: |
- num_list[index + 1] = '[0-9]{%s}' % (num_len - (index + 1)) |
- |
- # Part 2.4: Add this new sub-expression to the list. |
- expressions.append(''.join(num_list[:min(index+2, num_len)])) |
- |
- # Part 3: We add all the full ranges to match all numbers that are at least |
- # one order of magnitude smaller than the original numbers. |
- if num_len == 2: |
- expressions.append('[0-9]') |
- elif num_len > 2: |
- expressions.append('[0-9]{1,%s}' % (num_len - 1)) |
- |
- # All done. We now have our final regular expression. |
- regex = '(%s)' % ('|'.join(expressions)) |
- return regex |
- |
- |
def get_svn_rev(git_hash, dir_name): |
pattern = r'^\s*git-svn-id: [^ ]*@(\d+) ' |
- lkcr_pattern = r'^\s*LK[CG]R w/ DEPS up to revision (\d+)' |
log = git('log', '-1', git_hash, cwd=dir_name) |
match = re.search(pattern, log, re.M) |
if not match: |
- # This might be a patched checkout, try the parent commit. |
- log = git('log', '-1', '%s^' % git_hash, cwd=dir_name) |
- match = re.search(pattern, log, re.M) |
- if not match: |
- # Might be patched on top of LKCR, which has a special message. |
- match = re.search(lkcr_pattern, log, re.M) |
- if not match: |
- return None |
+ return None |
return int(match.group(1)) |
-def get_git_hash(revision, dir_name): |
- match = "^git-svn-id: [^ ]*@%s " % create_less_than_or_equal_regex(revision) |
- cmd = ['log', '-E', '--grep', match, '--format=%H', '--max-count=1'] |
- results = git(*cmd, cwd=dir_name).strip().splitlines() |
- if results: |
- return results[0] |
- raise Exception('We can\'t resolve svn revision %s into a git hash' % |
- revision) |
+def get_git_hash(revision, sln_dir): |
+ """We want to search for the SVN revision on the git-svn branch. |
+ |
+ Note that git will search backwards from origin/master. |
+ """ |
+ match = "^git-svn-id: [^ ]*@%s " % revision |
+ cmd = ['log', '-E', '--grep', match, '--format=%H', '--max-count=1', |
+ 'origin/master'] |
+ result = git(*cmd, cwd=sln_dir).strip() |
+ if result: |
+ return result |
+ raise SVNRevisionNotFound('We can\'t resolve svn r%s into a git hash in %s' % |
+ (revision, sln_dir)) |
def get_revision_mapping(root, addl_rev_map): |
@@ -751,7 +680,30 @@ def get_total_disk_space(): |
return (total, free) |
-def git_checkout(solutions, revision, shallow): |
+def get_revision(folder_name, git_url, revisions): |
+ normalized_name = folder_name.strip('/') |
+ if normalized_name in revisions: |
+ return revisions[normalized_name] |
+ if git_url in revisions: |
+ return revisions[git_url] |
+ return None |
+ |
+ |
+def force_revision(folder_name, revision): |
+ if revision and revision.upper() != 'HEAD': |
+ if revision and revision.isdigit() and len(revision) < 40: |
+ # rev_num is really a svn revision number, convert it into a git hash. |
+ git_ref = get_git_hash(int(revision), folder_name) |
+ else: |
+ # rev_num is actually a git hash or ref, we can just use it. |
+ git_ref = revision |
+ git('checkout', git_ref, cwd=folder_name) |
+ else: |
+ # Revision is None or 'HEAD', we want ToT. |
+ git('checkout', 'origin/master', cwd=folder_name) |
+ |
+ |
+def git_checkout(solutions, revisions, shallow): |
build_dir = os.getcwd() |
# Before we do anything, break all git_cache locks. |
if path.isdir(CACHE_DIR): |
@@ -760,7 +712,6 @@ def git_checkout(solutions, revision, shallow): |
filename = os.path.join(CACHE_DIR, item) |
if item.endswith('.lock'): |
raise Exception('%s exists after cache unlock' % filename) |
- # Revision only applies to the first solution. |
first_solution = True |
for sln in solutions: |
# This is so we can loop back and try again if we need to wait for the |
@@ -802,35 +753,25 @@ def git_checkout(solutions, revision, shallow): |
git('clean', '-df', cwd=sln_dir) |
git('fetch', 'origin', cwd=sln_dir) |
- # TODO(hinoka): We probably have to make use of revision mapping. |
- if first_solution and revision and revision.lower() != 'head': |
- if revision and revision.isdigit() and len(revision) < 40: |
- # rev_num is really a svn revision number, convert it into a git hash. |
- git_ref = get_git_hash(int(revision), name) |
- # Lets make sure our checkout has the correct revision number. |
- got_revision = get_svn_rev(git_ref, sln_dir) |
- if not got_revision or int(got_revision) != int(revision): |
- tries_left -= 1 |
- if tries_left > 0: |
- # If we don't have the correct revision, wait and try again. |
- print 'We want revision %s, but got revision %s.' % ( |
- revision, got_revision) |
- print 'The svn to git replicator is probably falling behind.' |
- print 'waiting 5 seconds and trying again...' |
- time.sleep(5) |
- done = False |
- continue |
- raise SVNRevisionNotFound |
- else: |
- # rev_num is actually a git hash or ref, we can just use it. |
- git_ref = revision |
- git('checkout', git_ref, cwd=sln_dir) |
- else: |
- git('checkout', 'origin/master', cwd=sln_dir) |
- if first_solution: |
- git_ref = git('log', '--format=%H', '--max-count=1', |
- cwd=sln_dir).strip() |
+ revision = get_revision(name, url, revisions) or 'HEAD' |
+ try: |
+ force_revision(sln_dir, revision) |
+ except SVNRevisionNotFound: |
+ tries_left -= 1 |
+ if tries_left > 0: |
+ # If we don't have the correct revision, wait and try again. |
+ print 'We can\'t find revision %s.' % revision |
+ print 'The svn to git replicator is probably falling behind.' |
+ print 'waiting 5 seconds and trying again...' |
+ time.sleep(5) |
+ done = False |
+ continue |
+ raise |
+ |
+ if first_solution: |
+ git_ref = git('log', '--format=%H', '--max-count=1', |
+ cwd=sln_dir).strip() |
first_solution = False |
return git_ref |
@@ -909,7 +850,7 @@ def parse_got_revision(gclient_output, got_revision_mapping, use_svn_revs): |
else: |
# Since we are using .DEPS.git, everything had better be git. |
assert solution_output.get('scm') == 'git' |
- git_revision = get_real_git_hash(solution_output['revision'], dir_name) |
+ git_revision = git('rev-parse', 'HEAD', cwd=dir_name).strip() |
if use_svn_revs: |
revision = get_svn_rev(git_revision, dir_name) |
if not revision: |
@@ -934,14 +875,27 @@ def emit_json(out_file, did_run, gclient_output=None, **kwargs): |
f.write(json.dumps(output)) |
-def ensure_checkout(solutions, revision, first_sln, target_os, target_os_only, |
+def ensure_deps_revisions(deps_url_mapping, solutions, revisions): |
+ """Ensure correct DEPS revisions, ignores solutions.""" |
+ for deps_name, deps_data in sorted(deps_url_mapping.items()): |
+ if deps_name.strip('/') in solutions: |
+ # This has already been forced to the correct solution by git_checkout(). |
+ continue |
+ revision = get_revision(deps_name, deps_data.get('url', None), revisions) |
+ if not revision: |
+ continue |
+ # TODO(hinoka): Catch SVNRevisionNotFound error maybe? |
+ force_revision(deps_name, revision) |
+ |
+ |
+def ensure_checkout(solutions, revisions, first_sln, target_os, target_os_only, |
root, issue, patchset, patch_url, rietveld_server, |
revision_mapping, buildspec_name, gyp_env, shallow): |
# Get a checkout of each solution, without DEPS or hooks. |
# Calling git directly because there is no way to run Gclient without |
# invoking DEPS. |
print 'Fetching Git checkout' |
- git_ref = git_checkout(solutions, revision, shallow) |
+ git_ref = git_checkout(solutions, revisions, shallow) |
# If either patch_url or issue is passed in, then we need to apply a patch. |
if patch_url: |
@@ -970,6 +924,11 @@ def ensure_checkout(solutions, revision, first_sln, target_os, target_os_only, |
# TODO(hinoka): Remove this when the official builders run their own |
# runhooks step. |
gclient_runhooks(gyp_env) |
+ |
+ # Finally, ensure that all DEPS are pinned to the correct revision. |
+ dir_names = [sln['name'] for sln in solutions] |
+ ensure_deps_revisions(gclient_output.get('solutions', {}), |
+ dir_names, revisions) |
return gclient_output |
@@ -1023,6 +982,49 @@ def upload_telemetry(prefix, master, builder, slave, **kwargs): |
return thr |
+def parse_revisions(revisions, root): |
+ """Turn a list of revision specs into a nice dictionary. |
+ |
+ We will always return a dict with {root: something}. By default if root |
+ is unspecified, or if revisions is [], then revision will be assigned 'HEAD' |
+ """ |
+ results = {root.strip('/'): 'HEAD'} |
+ for revision in revisions: |
+ split_revision = revision.split('@') |
+ if len(split_revision) == 1: |
+ # This is just a plain revision, set it as the revision for root. |
+ results[root] = split_revision[0] |
+ elif len(split_revision) == 2: |
+ # This is an alt_root@revision argument. |
+ current_root, current_rev = split_revision |
+ |
+ # We want to normalize svn/git urls into .git urls. |
+ parsed_root = urlparse.urlparse(current_root) |
+ if parsed_root.scheme == 'svn': |
+ if parsed_root.path in RECOGNIZED_PATHS: |
+ normalized_root = RECOGNIZED_PATHS[parsed_root.path] |
+ else: |
+ print 'WARNING: SVN path %s not recognized, ignoring' % current_root |
+ continue |
+ elif parsed_root.scheme in ['http', 'https']: |
+ normalized_root = 'https://%s/%s' % (parsed_root.netloc, |
+ parsed_root.path) |
+ if not normalized_root.endswith('.git'): |
+ normalized_root = '%s.git' % normalized_root |
+ elif parsed_root.scheme: |
+ print 'WARNING: Unrecognized scheme %s, ignoring' % parsed_root.scheme |
+ continue |
+ else: |
+ # This is probably a local path. |
+ normalized_root = current_root.strip('/') |
+ |
+ results[normalized_root] = current_rev |
+ else: |
+ print ('WARNING: %r is not recognized as a valid revision specification,' |
+ 'skipping' % revision) |
+ return results |
+ |
+ |
def parse_args(): |
parse = optparse.OptionParser() |
@@ -1041,10 +1043,12 @@ def parse_args(): |
'Should ONLY be used locally.') |
parse.add_option('--revision_mapping') |
parse.add_option('--revision-mapping') # Backwards compatability. |
- # TODO(hinoka): Support root@revision format. |
- parse.add_option('--revision', |
+ parse.add_option('--revision', action='append', default=[], |
help='Revision to check out. Can be an SVN revision number, ' |
- 'git hash, or any form of git ref.') |
+ 'git hash, or any form of git ref. Can prepend ' |
+ 'root@<rev> to specify which repository, where root ' |
+ 'is either a filesystem path, git https url, or ' |
+ 'svn url. To specify Tip of Tree, set rev to HEAD.') |
parse.add_option('--slave_name', default=socket.getfqdn().split('.')[0], |
help='Hostname of the current machine, ' |
'used for determining whether or not to activate.') |
@@ -1150,11 +1154,18 @@ def main(): |
# The first solution is where the primary DEPS file resides. |
first_sln = dir_names[0] |
+ # Split all the revision specifications into a nice dict. |
+ print 'Revisions: %s' % options.revision |
+ revisions = parse_revisions(options.revision, options.root) |
+ # This is meant to be just an alias to the revision of the main solution. |
+ root_revision = revisions[options.root] |
+ print 'Fetching Git checkout at %s@%s' % (options.root, root_revision) |
+ |
try: |
checkout_parameters = dict( |
# First, pass in the base of what we want to check out. |
solutions=git_solutions, |
- revision=options.revision, |
+ revisions=revisions, |
first_sln=first_sln, |
# Also, target os variables for gclient. |