Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1220)

Unified Diff: scripts/slave/bot_update.py

Issue 157073002: Bot update! (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@master
Patch Set: No need to print message in chromium_util Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: scripts/slave/bot_update.py
diff --git a/scripts/slave/bot_update.py b/scripts/slave/bot_update.py
index 2e16e4cd7c7366ff710d7bdea1866cd7e8eaee5e..1c05bb9274a62e6ab4980b036973c70d93675250 100644
--- a/scripts/slave/bot_update.py
+++ b/scripts/slave/bot_update.py
@@ -3,13 +3,20 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-
+import codecs
import copy
import optparse
import os
+import pprint
+import shutil
+import socket
+import subprocess
import sys
+import time
import urlparse
+import os.path as path
+
RECOGNIZED_PATHS = {
# If SVN path matches key, the entire URL is rewritten to the Git url.
@@ -51,10 +58,14 @@ This step does nothing. You actually want to look at the "update" step.
"""
+
+GCLIENT_TEMPLATE = """solutions = %(solutions)s
+
+cache_dir = %(cache_dir)s
+"""
+
ENABLED_MASTERS = ['chromium.git']
-# Master: Builders dict.
ENABLED_BUILDERS = {}
-# Master: Slaves dict.
ENABLED_SLAVES = {}
# Disabled filters get run AFTER enabled filters, so for example if a builder
@@ -63,6 +74,65 @@ ENABLED_SLAVES = {}
DISABLED_BUILDERS = {}
DISABLED_SLAVES = {}
+# How many times to retry failed subprocess calls.
+RETRIES = 3
+
+SCRIPTS_PATH = path.dirname(path.dirname(path.abspath(__file__)))
+DEPS2GIT_DIR_PATH = path.join(SCRIPTS_PATH, 'tools', 'deps2git')
+DEPS2GIT_PATH = path.join(DEPS2GIT_DIR_PATH, 'deps2git.py')
+S2G_INTERNAL_FROM_PATH = path.join(SCRIPTS_PATH, 'tools', 'deps2git_internal',
+ 'svn_to_git_internal.py')
+S2G_INTERNAL_DEST_PATH = path.join(DEPS2GIT_DIR_PATH, 'svn_to_git_internal.py')
+
+# ../../cache_dir aka /b/build/slave/cache_dir
+THIS_DIR = path.abspath(os.getcwd())
+BUILDER_DIR = path.dirname(THIS_DIR)
+SLAVE_DIR = path.dirname(BUILDER_DIR)
+CACHE_DIR = path.join(SLAVE_DIR, 'cache_dir')
+
+
+class SubprocessFailed(Exception):
+ pass
+
+
+def call(*args):
+ """Interactive subprocess call."""
+ tries = 0
+ while tries < RETRIES:
iannucci 2014/02/08 01:31:31 could also do for try in xrange(RETRIES): ...
Ryan Tseng 2014/02/08 01:52:21 Done.
+ tries_msg = '(retry #%d)' % tries if tries else ''
+ print '===Running %s%s===' % (' '.join(args), tries_msg)
+ start_time = time.time()
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ # This is here because passing 'sys.stdout' into stdout for proc will
+ # produce out of order output.
+ while True:
+ buf = proc.stdout.read(1)
iannucci 2014/02/08 01:31:31 can't we just do readline() calls?
Ryan Tseng 2014/02/08 01:52:21 And rely on the system native newline semantics pl
iannucci 2014/02/08 03:37:21 oh, I see you're just copying bytes... how does th
Ryan Tseng 2014/02/10 23:11:28 It doesn't but unless we do multiple calls at a ti
+ if not buf:
+ break
+ sys.stdout.write(buf)
+ code = proc.wait()
+ elapsed_time = ((time.time() - start_time) / 60.0)
+ if not code:
+ print '===Succeeded in %.1f mins===' % elapsed_time
+ print
+ break
iannucci 2014/02/08 01:31:31 just do a return here
Ryan Tseng 2014/02/08 01:52:21 Done.
+ print '===Failed in %.1f mins===' % elapsed_time
+ print
+ tries += 1
+
+ if code:
iannucci 2014/02/08 01:31:31 then you don't need this check here
Ryan Tseng 2014/02/08 01:52:21 Done.
+ raise SubprocessFailed('%s failed with code %d in %s' %
+ (' '.join(args), code, os.getcwd()))
iannucci 2014/02/08 01:31:31 ... after XX tries ?
Ryan Tseng 2014/02/08 01:52:21 Done.
+ return code
iannucci 2014/02/08 01:31:31 don't need this here either
Ryan Tseng 2014/02/08 01:52:21 Done.
+
+
+def get_gclient_spec(solutions):
+ return GCLIENT_TEMPLATE % {
+ 'solutions': pprint.pformat(solutions, indent=4),
+ 'cache_dir': '"%s"' % CACHE_DIR
+ }
+
def check_enabled(master, builder, slave):
if master in ENABLED_MASTERS:
@@ -88,7 +158,8 @@ def check_disabled(master, builder, slave):
def check_valid_host(master, builder, slave):
- return False
+ return (check_enabled(master, builder, slave)
+ and not check_disabled(master, builder, slave))
def solutions_printer(solutions):
@@ -99,6 +170,10 @@ def solutions_printer(solutions):
name = solution.get('name')
url = solution.get('url')
print '%s (%s)' % (name, url)
+ if solution.get('deps_file'):
+ print ' Dependencies file is %s' % solution['deps_file']
+ if 'managed' in solution:
+ print ' Managed mode is %s' % ('ON' if solution['managed'] else 'OFF')
custom_vars = solution.get('custom_vars')
if custom_vars:
print ' Custom Variables:'
@@ -112,10 +187,6 @@ def solutions_printer(solutions):
print ' %s -> %s' % (deps_name, deps_value)
else:
print ' %s: Ignore' % deps_name
- if solution.get('deps_file'):
- print ' Dependencies file is %s' % solution['deps_file']
- if 'managed' in solution:
- print ' Managed mode is %s' % ('ON' if solution['managed'] else 'OFF')
print
@@ -125,60 +196,122 @@ def solutions_to_git(input_solutions):
for solution in solutions:
original_url = solution['url']
parsed_url = urlparse.urlparse(original_url)
- path = parsed_url.path
- if path in RECOGNIZED_PATHS:
- solution['url'] = RECOGNIZED_PATHS[path]
+ parsed_path = parsed_url.path
+ if parsed_path in RECOGNIZED_PATHS:
+ solution['url'] = RECOGNIZED_PATHS[parsed_path]
else:
- print 'Warning: path %s not recognized' % path
+ print 'Warning: path %s not recognized' % parsed_path
if solution.get('deps_file', 'DEPS') == 'DEPS':
solution['deps_file'] = '.DEPS.git'
solution['managed'] = False
return solutions
-def ensure_no_git_checkout():
+def ensure_no_checkout(dir_names, scm_dirname):
"""Ensure that there is no git checkout under build/.
- If there is a git checkout under build/, then move build/ to build.dead/
+ If there is an incorrect checkout under build/, then
+ move build/ to build.dead/
+ This function will check each directory in dir_names.
+
+ scm_dirname is expected to be either ['.svn', '.git']
"""
- pass
+ assert scm_dirname in ['.svn', '.git']
+ has_checkout = any(map(lambda dir_name: path.exists(
+ path.join(os.getcwd(), dir_name, scm_dirname)), dir_names))
+ if has_checkout:
+ # cd .. && rm -rf ./build && mkdir ./build && cd build
+ build_dir = os.getcwd()
-def ensure_no_svn_checkout():
- """Ensure that there is no svn checkout under build/.
+ os.chdir(path.dirname(os.getcwd()))
+ print '%s detected in checking, deleting %s...' % (scm_dirname, build_dir),
iannucci 2014/02/08 01:31:31 checking? checkout?
Ryan Tseng 2014/02/08 01:52:21 Done.
+ shutil.rmtree(build_dir)
+ print 'done'
+ os.mkdir(build_dir)
+ os.chdir(build_dir)
- If there is a svn checkout under build/, then move build/ to build.dead/
- """
- pass
def gclient_configure(solutions):
- pass
+ """Should do the same thing as gclient --spec='...'."""
+ with codecs.open('.gclient', mode='w', encoding='utf-8') as f:
+ f.write(get_gclient_spec(solutions))
-def gclient_shallow_sync():
- pass
-
+def gclient_sync():
+ call('gclient', 'sync', '--verbose', '--reset', '--force',
+ '--nohooks', '--noprehooks')
+
+
+def get_git_hash(revision, dir_name):
+ match = "^git-svn-id: [^ ]*@%d" % revision
+ cmd = ['git', 'log', '--grep', match, '--format=%H', dir_name]
+ return subprocess.check_output(cmd).strip() or None
+
+
+def deps2git(sln_dirs):
+ for sln_dir in sln_dirs:
+ deps_file = path.join(os.getcwd(), sln_dir, 'DEPS')
+ deps_git_file = path.join(os.getcwd(), sln_dir, '.DEPS.git')
+ if not path.isfile(deps_file):
+ return
+ # Do we have a better way of doing this....?
+ repo_type = 'internal' if 'internal' in sln_dir else 'public'
+ # TODO(hinoka): This will be what populates the git caches on the first
+ # run for all of the bots. Since deps2git is single threaded,
+ # all of this will run in a single threaded context and be
+ # super slow. Should make deps2git multithreaded.
+ call(sys.executable, DEPS2GIT_PATH, '-t', repo_type,
+ '--cache_dir=%s' % CACHE_DIR,
+ '--deps=%s' % deps_file, '--out=%s' % deps_git_file)
+
+
+def git_checkout(solutions, revision):
+ build_dir = os.getcwd()
+ # Revision only applies to the first solution.
+ first_solution = True
+ for sln in solutions:
+ name = sln['name']
+ url = sln['url']
+ sln_dir = path.join(build_dir, name)
+ if not path.isdir(sln_dir):
+ call('git', 'clone', url, sln_dir)
+
+ # Clean out .DEPS.git changes first.
+ call('git', '-C', sln_dir, 'reset', '--hard')
+ call('git', '-C', sln_dir, 'clean', '-df')
+ call('git', '-C', sln_dir, 'pull', 'origin', 'master')
+ # 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(revision, name)
+ else:
+ # rev_num is actually a git hash or ref, we can just use it.
+ git_ref = revision
+ call('git', '-C', sln_dir, 'checkout', git_ref)
+ else:
+ call('git', '-C', sln_dir, 'checkout', 'origin/master')
-def git_pull_and_clean():
- pass
+ first_solution = False
def apply_issue(issue, patchset, root, server):
pass
-def deps2git():
- pass
+def delete_flag(flag_file):
+ """Remove bot update flag."""
+ if os.path.exists(flag_file):
+ os.remove(flag_file)
-def gclient_sync():
- pass
-
-
-def deposit_bot_update_flag():
+def emit_flag(flag_file):
"""Deposit a bot update flag on the system to tell gclient not to run."""
- pass
+ print 'Emitting flag file at %s' % flag_file
+ with open(flag_file, 'wb') as f:
+ f.write('Success!')
def parse_args():
@@ -194,7 +327,12 @@ def parse_args():
parse.add_option('-f', '--force', action='store_true',
help='Bypass check to see if we want to be run. '
'Should ONLY be used locally.')
+ # TODO(hinoka): We don't actually use this yet, we should factor this in.
parse.add_option('-e', '--revision-mapping')
+ parse.add_option('-v', '--revision')
+ parse.add_option('-b', '--build_dir', default=os.getcwd())
+ parse.add_option('-g', '--flag_file', default=path.join(os.getcwd(),
iannucci 2014/02/08 01:31:31 These short options aren't super-meaningful... wdy
Ryan Tseng 2014/02/08 01:52:21 But I like short option names :( I guess thats ok
+ 'update.flag'))
return parse.parse_args()
@@ -203,8 +341,10 @@ def main():
# Get inputs.
options, _ = parse_args()
builder = os.environ.get('BUILDBOT_BUILDERNAME', None)
- slave = os.environ.get('BUILDBOT_SLAVENAME', None)
+ slave = os.environ.get('BUILDBOT_SLAVENAME', socket.getfqdn().split('.')[0])
iannucci 2014/02/08 01:31:31 urk.... We need CLI options for these so we can us
Ryan Tseng 2014/02/08 01:52:21 We get them as free always in the environ as part
iannucci 2014/02/08 03:37:21 1) They assume buildbot 2) Passing data via env va
master = options.master
+ if not options.revision:
+ options.revision = os.environ.get('BUILDBOT_REVISION')
# Check if this script should activate or not.
active = check_valid_host(master, builder, slave) or options.force
@@ -220,21 +360,37 @@ def main():
# Parse, munipulate, and print the gclient solutions.
specs = {}
- exec(options.specs, specs) # TODO(hinoka): LOL this is terrible.
- solutions = specs.get('solutions', [])
- git_solutions = solutions_to_git(solutions)
+ exec(options.specs, specs)
+ svn_solutions = specs.get('solutions', [])
+ git_solutions = solutions_to_git(svn_solutions)
solutions_printer(git_solutions)
- # Do the checkout.
- # TODO(hinoka): Uncomment these once they're implemented.
- # ensure_no_svn_checkout()
- # gclient_configure(git_solutions)
- # gclient_shallow_sync()
- # git_pull_and_clean()
+ # Cleanup svn checkout if active, otherwise remove git checkout and exit.
+ dir_names = [sln.get('name') for sln in svn_solutions if 'name' in sln]
+ if active:
+ ensure_no_checkout(dir_names, '.svn')
+ emit_flag(options.flag_file)
+ else:
+ ensure_no_checkout(dir_names, '.git')
+ delete_flag(options.flag_file)
+ return
+
+ # Get a checkout of each solution, without DEPS or hooks.
+ # Calling git directory because there is no way to run Gclient without
+ # invoking DEPS.
+ print 'Fetching Git checkout'
+ git_checkout(git_solutions, options.revision)
+
+ # TODO(hinoka): This must be implemented before we can turn this on for TS.
# if options.issue:
# apply_issue(options.issue, options.patchset, options.root, options.server)
- # deps2git()
- # gclient_sync()
+
+ # Magic to get deps2git to work with internal DEPS.
+ shutil.copyfile(S2G_INTERNAL_FROM_PATH, S2G_INTERNAL_DEST_PATH)
+ deps2git(dir_names)
+
+ gclient_configure(git_solutions)
+ gclient_sync()
if __name__ == '__main__':

Powered by Google App Engine
This is Rietveld 408576698