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

Side by Side Diff: scripts/slave/bot_update.py

Issue 157073002: Bot update! (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@master
Patch Set: We dont' actually want to run on chromium.linux yet 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 unified diff | Download patch
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 6 import codecs
7 import copy 7 import copy
8 import json
8 import optparse 9 import optparse
9 import os 10 import os
11 import pprint
12 import shutil
13 import socket
14 import subprocess
10 import sys 15 import sys
16 import time
11 import urlparse 17 import urlparse
12 18
19 import os.path as path
20
13 21
14 RECOGNIZED_PATHS = { 22 RECOGNIZED_PATHS = {
15 # If SVN path matches key, the entire URL is rewritten to the Git url. 23 # If SVN path matches key, the entire URL is rewritten to the Git url.
16 '/chrome/trunk/src': 24 '/chrome/trunk/src':
17 'https://chromium.googlesource.com/chromium/src.git', 25 'https://chromium.googlesource.com/chromium/src.git',
18 '/chrome-internal/trunk/src-internal': 26 '/chrome-internal/trunk/src-internal':
19 'https://chrome-internal.googlesource.com/chrome/src-internal.git' 27 'https://chrome-internal.googlesource.com/chrome/src-internal.git'
20 } 28 }
21 29
22 30
(...skipping 21 matching lines...) Expand all
44 The bot will perform a Git checkout in this step. 52 The bot will perform a Git checkout in this step.
45 The "gclient revert" and "update" steps are no-ops. 53 The "gclient revert" and "update" steps are no-ops.
46 54
47 """ 55 """
48 56
49 NOT_ACTIVATED_MESSAGE = """INACTIVE. 57 NOT_ACTIVATED_MESSAGE = """INACTIVE.
50 This step does nothing. You actually want to look at the "update" step. 58 This step does nothing. You actually want to look at the "update" step.
51 59
52 """ 60 """
53 61
62
63 GCLIENT_TEMPLATE = """solutions = %(solutions)s
64
65 cache_dir = %(cache_dir)s
66 """
67
54 ENABLED_MASTERS = ['chromium.git'] 68 ENABLED_MASTERS = ['chromium.git']
55 # Master: Builders dict.
56 ENABLED_BUILDERS = {} 69 ENABLED_BUILDERS = {}
57 # Master: Slaves dict.
58 ENABLED_SLAVES = {} 70 ENABLED_SLAVES = {}
59 71
60 # Disabled filters get run AFTER enabled filters, so for example if a builder 72 # Disabled filters get run AFTER enabled filters, so for example if a builder
61 # config is enabled, but a bot on that builder is disabled, that bot will 73 # config is enabled, but a bot on that builder is disabled, that bot will
62 # be disabled. 74 # be disabled.
63 DISABLED_BUILDERS = {} 75 DISABLED_BUILDERS = {}
64 DISABLED_SLAVES = {} 76 DISABLED_SLAVES = {}
65 77
78 # How many times to retry failed subprocess calls.
79 RETRIES = 3
80
81 SCRIPTS_PATH = path.dirname(path.dirname(path.abspath(__file__)))
82 DEPS2GIT_DIR_PATH = path.join(SCRIPTS_PATH, 'tools', 'deps2git')
83 DEPS2GIT_PATH = path.join(DEPS2GIT_DIR_PATH, 'deps2git.py')
84 S2G_INTERNAL_FROM_PATH = path.join(SCRIPTS_PATH, 'tools', 'deps2git_internal',
85 'svn_to_git_internal.py')
86 S2G_INTERNAL_DEST_PATH = path.join(DEPS2GIT_DIR_PATH, 'svn_to_git_internal.py')
87
88 CACHE_DIR = os.path.join(os.path.dirname(os.path.abspath(os.getcwd())),
89 'cache_dir')
90
91
92 class SubprocessFailed(Exception):
93 pass
94
95
96 def call(*args):
97 """Interactive subprocess call."""
98 tries = 0
99 while tries < RETRIES:
100 tries_msg = '(retry #%d)' % tries if tries else ''
101 print '===Running %s%s===' % (' '.join(args), tries_msg)
102 start_time = time.time()
103 proc = subprocess.Popen(args, stdout=subprocess.PIPE,
104 stderr=subprocess.STDOUT)
105 # This is here because passing 'sys.stdout' into stdout for proc will
106 # produce out of order output.
107 while True:
108 buf = proc.stdout.read(1)
109 if not buf:
110 break
111 sys.stdout.write(buf)
112 code = proc.wait()
agable 2014/02/07 04:47:40 retcode
Ryan Tseng 2014/02/07 21:23:07 I feel more comfortable waiting for the process, b
113 if not code:
agable 2014/02/07 04:47:40 elapsed = ((time.time() - start_time) / 60.0) reu
Ryan Tseng 2014/02/07 21:23:07 Done.
114 print '===Succeeded in %.1f mins===' % ((time.time() - start_time) / 60.0)
115 print
116 break
117 print '===Failed in %.1f mins===' % ((time.time() - start_time) / 60.0)
118 print
119 tries += 1
120
121 if code:
122 raise SubprocessFailed('%s failed with code %d in %s' %
123 (' '.join(args), code, os.getcwd()))
124 return code
125
126
127 def get_gclient_spec(solutions):
128 for sln in solutions:
129 sln['deps_file'] = '.DEPS.git'
agable 2014/02/07 04:47:40 solutions_to_git already does this, I wouldn't do
Ryan Tseng 2014/02/07 21:23:07 oops you're right. Done.
130 return GCLIENT_TEMPLATE % {
131 'solutions': pprint.pformat(solutions, indent=4),
132 'cache_dir': '"%s"' % CACHE_DIR
133 }
134
66 135
67 def check_enabled(master, builder, slave): 136 def check_enabled(master, builder, slave):
68 if master in ENABLED_MASTERS: 137 if master in ENABLED_MASTERS:
69 return True 138 return True
70 builder_list = ENABLED_BUILDERS.get(master) 139 builder_list = ENABLED_BUILDERS.get(master)
71 if builder_list and builder in builder_list: 140 if builder_list and builder in builder_list:
72 return True 141 return True
73 slave_list = ENABLED_SLAVES.get(master) 142 slave_list = ENABLED_SLAVES.get(master)
74 if slave_list and slave in slave_list: 143 if slave_list and slave in slave_list:
75 return True 144 return True
145 if os.environ.get('TESTING_MASTER') == 'localhost':
agable 2014/02/07 04:47:40 nope nope nope
Ryan Tseng 2014/02/07 21:23:07 herp de derp. Done.
146 # Magic conditional to get bot_update to always run locally.
147 return True
76 return False 148 return False
77 149
78 150
79 def check_disabled(master, builder, slave): 151 def check_disabled(master, builder, slave):
80 """Returns True if disabled, False if not disabled.""" 152 """Returns True if disabled, False if not disabled."""
81 builder_list = DISABLED_BUILDERS.get(master) 153 builder_list = DISABLED_BUILDERS.get(master)
82 if builder_list and builder in builder_list: 154 if builder_list and builder in builder_list:
83 return True 155 return True
84 slave_list = DISABLED_SLAVES.get(master) 156 slave_list = DISABLED_SLAVES.get(master)
85 if slave_list and slave in slave_list: 157 if slave_list and slave in slave_list:
86 return True 158 return True
87 return False 159 return False
88 160
89 161
90 def check_valid_host(master, builder, slave): 162 def check_valid_host(master, builder, slave):
91 return False 163 return (check_enabled(master, builder, slave)
164 and not check_disabled(master, builder, slave))
92 165
93 166
94 def solutions_printer(solutions): 167 def solutions_printer(solutions):
95 """Prints gclient solution to stdout.""" 168 """Prints gclient solution to stdout."""
96 print 'Gclient Solutions' 169 print 'Gclient Solutions'
97 print '=================' 170 print '================='
98 for solution in solutions: 171 for solution in solutions:
99 name = solution.get('name') 172 name = solution.get('name')
100 url = solution.get('url') 173 url = solution.get('url')
101 print '%s (%s)' % (name, url) 174 print '%s (%s)' % (name, url)
175 if solution.get('deps_file'):
176 print ' Dependencies file is %s' % solution['deps_file']
177 if 'managed' in solution:
178 print ' Managed mode is %s' % ('ON' if solution['managed'] else 'OFF')
102 custom_vars = solution.get('custom_vars') 179 custom_vars = solution.get('custom_vars')
103 if custom_vars: 180 if custom_vars:
104 print ' Custom Variables:' 181 print ' Custom Variables:'
105 for var_name, var_value in sorted(custom_vars.iteritems()): 182 for var_name, var_value in sorted(custom_vars.iteritems()):
106 print ' %s = %s' % (var_name, var_value) 183 print ' %s = %s' % (var_name, var_value)
107 custom_deps = solution.get('custom_deps') 184 custom_deps = solution.get('custom_deps')
108 if 'custom_deps' in solution: 185 if 'custom_deps' in solution:
109 print ' Custom Dependencies:' 186 print ' Custom Dependencies:'
110 for deps_name, deps_value in sorted(custom_deps.iteritems()): 187 for deps_name, deps_value in sorted(custom_deps.iteritems()):
111 if deps_value: 188 if deps_value:
112 print ' %s -> %s' % (deps_name, deps_value) 189 print ' %s -> %s' % (deps_name, deps_value)
113 else: 190 else:
114 print ' %s: Ignore' % deps_name 191 print ' %s: Ignore' % deps_name
115 if solution.get('deps_file'):
116 print ' Dependencies file is %s' % solution['deps_file']
117 if 'managed' in solution:
118 print ' Managed mode is %s' % ('ON' if solution['managed'] else 'OFF')
119 print 192 print
120 193
121 194
122 def solutions_to_git(input_solutions): 195 def solutions_to_git(input_solutions):
123 """Modifies urls in solutions to point at Git repos.""" 196 """Modifies urls in solutions to point at Git repos."""
124 solutions = copy.deepcopy(input_solutions) 197 solutions = copy.deepcopy(input_solutions)
125 for solution in solutions: 198 for solution in solutions:
126 original_url = solution['url'] 199 original_url = solution['url']
127 parsed_url = urlparse.urlparse(original_url) 200 parsed_url = urlparse.urlparse(original_url)
128 path = parsed_url.path 201 path = parsed_url.path
129 if path in RECOGNIZED_PATHS: 202 if path in RECOGNIZED_PATHS:
130 solution['url'] = RECOGNIZED_PATHS[path] 203 solution['url'] = RECOGNIZED_PATHS[path]
131 else: 204 else:
132 print 'Warning: path %s not recognized' % path 205 print 'Warning: path %s not recognized' % path
133 if solution.get('deps_file', 'DEPS') == 'DEPS': 206 if solution.get('deps_file', 'DEPS') == 'DEPS':
134 solution['deps_file'] = '.DEPS.git' 207 solution['deps_file'] = '.DEPS.git'
135 solution['managed'] = False 208 solution['managed'] = False
136 return solutions 209 return solutions
137 210
138 211
139 def ensure_no_git_checkout(): 212 def ensure_no_checkout(dir_names, scm_filename):
140 """Ensure that there is no git checkout under build/. 213 """Ensure that there is no git checkout under build/.
141 214
142 If there is a git checkout under build/, then move build/ to build.dead/ 215 If there is an incorrect checkout under build/, then
216 move build/ to build.dead/
217 This function will check each directory in dir_names.
218
219 scm_filename is expected to be either ['.svn', '.git']
agable 2014/02/07 04:47:40 scm_dirname?
Ryan Tseng 2014/02/07 21:23:07 Done.
143 """ 220 """
144 pass 221 assert scm_filename in ['.svn', '.git']
222 has_checkout = any(map(lambda dir_name: path.exists(
223 path.join(os.getcwd(), dir_name, scm_filename)), dir_names))
145 224
225 if has_checkout:
226 # cd .. && rm -rf ./build && mkdir ./build && cd build
227 build_dir = os.getcwd()
146 228
147 def ensure_no_svn_checkout(): 229 os.chdir(path.dirname(os.getcwd()))
agable 2014/02/07 04:47:40 Can't this be done without chdir? rmtree should on
Ryan Tseng 2014/02/07 21:23:07 Discussed offline, but this needs to be here.
148 """Ensure that there is no svn checkout under build/. 230 print '%s detected in checking, deleting %s...' % (scm_filename, build_dir),
231 shutil.rmtree(build_dir)
232 print 'done'
233 os.mkdir(build_dir)
234 os.chdir(build_dir)
149 235
150 If there is a svn checkout under build/, then move build/ to build.dead/
151 """
152 pass
153 236
154 237
155 def gclient_configure(solutions): 238 def gclient_configure(solutions):
156 pass 239 """Should do the same thing as gclient --spec='...'."""
240 with codecs.open('.gclient', mode='w', encoding='utf-8') as f:
241 f.write(get_gclient_spec(solutions))
157 242
158 243
159 def gclient_shallow_sync(): 244 def gclient_sync():
160 pass 245 call('gclient', 'sync', '--verbose', '--reset', '--force',
246 '--nohooks', '--noprehooks')
161 247
162 248
163 def git_pull_and_clean(): 249 def get_git_hash(revision, dir_name):
164 pass 250 match = "^git-svn-id: [^ ]*@%d" % revision
251 cmd = ['git', 'log', '--grep', match, '--format=%H', dir_name]
252 return subprocess.check_output(cmd).strip() or None
253
254
255 def deps2git(sln_dirs):
256 for sln_dir in sln_dirs:
257 deps_file = path.join(os.getcwd(), sln_dir, 'DEPS')
258 deps_git_file = path.join(os.getcwd(), sln_dir, '.DEPS.git')
259 if not path.isfile(deps_file):
260 return
261 # Do we have a better way of doing this....?
262 repo_type = 'internal' if 'internal' in sln_dir else 'public'
263 # TODO(hinoka): This will be what populates the git caches on the first
264 # run for all of the bots. Since deps2git is single threaded,
265 # all of this will run in a single threaded context and be
266 # super slow. Should make deps2git multithreaded.
267 call(sys.executable, DEPS2GIT_PATH, '-t', repo_type,
268 '--cache_dir=%s' % CACHE_DIR,
269 '--deps=%s' % deps_file, '--out=%s' % deps_git_file)
270
271
272 def git_checkout(solutions, revision):
273 build_dir = os.getcwd()
274 for sln in solutions:
275 name = sln['name']
276 url = sln['url']
277 sln_dir = path.join(build_dir, name)
278 if not path.isdir(sln_dir):
279 call('git', 'clone', url, sln_dir)
280
281 # Clean out .DEPS.git changes first.
282 call('git', '-C', sln_dir, 'reset', '--hard')
283 call('git', '-C', sln_dir, 'clean', '-df')
284 call('git', '-C', sln_dir, 'pull', 'origin', 'master')
285 if revision and revision.lower() != 'head':
286 root, rev_num = revision.split('@')
287 git_hash = get_git_hash(rev_num, name) if root == name else None
288 call('git', '-C', sln_dir, 'checkout', git_hash)
agable 2014/02/07 04:47:40 does call('git', '-C', sln_dir, 'checkout', None)
Ryan Tseng 2014/02/07 21:23:07 oops fixed (by not even checking for that conditio
289 else:
290 call('git', '-C', sln_dir, 'checkout', 'origin/master')
291
292 # Force another clean, because why not.
293 call('git', '-C', sln_dir, 'clean', '-df')
165 294
166 295
167 def apply_issue(issue, patchset, root, server): 296 def apply_issue(issue, patchset, root, server):
168 pass 297 pass
169 298
170 299
171 def deps2git(): 300 def delete_flag(flag_file):
172 pass 301 """Remove bot update flag."""
302 if os.path.exists(flag_file):
303 os.remove(flag_file)
173 304
174 305
175 def gclient_sync(): 306 def emit_flag(flag_file):
176 pass
177
178
179 def deposit_bot_update_flag():
180 """Deposit a bot update flag on the system to tell gclient not to run.""" 307 """Deposit a bot update flag on the system to tell gclient not to run."""
181 pass 308 print 'Emitting flag file at %s' % flag_file
309 with open(flag_file, 'wb') as f:
310 f.write('Success!')
182 311
183 312
184 def parse_args(): 313 def parse_args():
185 parse = optparse.OptionParser() 314 parse = optparse.OptionParser()
186 315
187 parse.add_option('-i', '--issue', help='Issue number to patch from.') 316 parse.add_option('-i', '--issue', help='Issue number to patch from.')
188 parse.add_option('-p', '--patchset', 317 parse.add_option('-p', '--patchset',
189 help='Patchset from issue to patch from, if applicable.') 318 help='Patchset from issue to patch from, if applicable.')
190 parse.add_option('-r', '--root', help='Repository root.') 319 parse.add_option('-r', '--root', help='Repository root.')
191 parse.add_option('-c', '--server', help='Rietveld server.') 320 parse.add_option('-c', '--server', help='Rietveld server.')
192 parse.add_option('-s', '--specs', help='Gcilent spec.') 321 parse.add_option('-s', '--specs', help='Gcilent spec.')
193 parse.add_option('-m', '--master', help='Master name.') 322 parse.add_option('-m', '--master', help='Master name.')
194 parse.add_option('-f', '--force', action='store_true', 323 parse.add_option('-f', '--force', action='store_true',
195 help='Bypass check to see if we want to be run. ' 324 help='Bypass check to see if we want to be run. '
196 'Should ONLY be used locally.') 325 'Should ONLY be used locally.')
197 parse.add_option('-e', '--revision-mapping') 326 parse.add_option('-e', '--revision-mapping')
327 parse.add_option('-v', '--revision')
328 parse.add_option('-b', '--build_dir', default=os.getcwd())
329 parse.add_option('-g', '--flag_file', default=path.join(os.getcwd(),
330 'update.flag'))
198 331
199 return parse.parse_args() 332 return parse.parse_args()
200 333
201 334
202 def main(): 335 def main():
203 # Get inputs. 336 # Get inputs.
204 options, _ = parse_args() 337 options, _ = parse_args()
338 delete_flag(options.flag_file)
agable 2014/02/07 04:47:40 I think we should only delete the flag if we're no
Ryan Tseng 2014/02/07 21:23:07 sgtm. Moved delete/emit flag down next to ensure_
205 builder = os.environ.get('BUILDBOT_BUILDERNAME', None) 339 builder = os.environ.get('BUILDBOT_BUILDERNAME', None)
206 slave = os.environ.get('BUILDBOT_SLAVENAME', None) 340 slave = os.environ.get('BUILDBOT_SLAVENAME', socket.getfqdn().split('.')[0])
207 master = options.master 341 master = options.master
208 342
209 # Check if this script should activate or not. 343 # Check if this script should activate or not.
210 active = check_valid_host(master, builder, slave) or options.force 344 active = check_valid_host(master, builder, slave) or options.force
211 345
212 # Print helpful messages to tell devs whats going on. 346 # Print helpful messages to tell devs whats going on.
213 print BOT_UPDATE_MESSAGE % { 347 print BOT_UPDATE_MESSAGE % {
214 'master': master, 348 'master': master,
215 'builder': builder, 349 'builder': builder,
216 'slave': slave, 350 'slave': slave,
217 }, 351 },
218 # Print to stderr so that it shows up red on win/mac. 352 # Print to stderr so that it shows up red on win/mac.
219 print ACTIVATED_MESSAGE if active else NOT_ACTIVATED_MESSAGE 353 print ACTIVATED_MESSAGE if active else NOT_ACTIVATED_MESSAGE
220 354
221 # Parse, munipulate, and print the gclient solutions. 355 # Parse, munipulate, and print the gclient solutions.
222 specs = {} 356 specs = {}
223 exec(options.specs, specs) # TODO(hinoka): LOL this is terrible. 357 exec(options.specs, specs)
224 solutions = specs.get('solutions', []) 358 svn_solutions = specs.get('solutions', [])
225 git_solutions = solutions_to_git(solutions) 359 git_solutions = solutions_to_git(svn_solutions)
226 solutions_printer(git_solutions) 360 solutions_printer(git_solutions)
227 361
228 # Do the checkout. 362 # Cleanup svn checkout if active, otherwise remove git checkout and exit.
229 # TODO(hinoka): Uncomment these once they're implemented. 363 dir_names = [sln.get('name') for sln in svn_solutions if 'name' in sln]
230 # ensure_no_svn_checkout() 364 if active:
231 # gclient_configure(git_solutions) 365 ensure_no_checkout(dir_names, '.svn')
232 # gclient_shallow_sync() 366 else:
233 # git_pull_and_clean() 367 ensure_no_checkout(dir_names, '.git')
368 return
369
370 # Get a checkout of each solution, without DEPS or hooks.
371 # Calling git directory because there is no way to run Gclient without
372 # invoking DEPS.
373 print 'Fetching Git checkout'
374 git_checkout(git_solutions, options.revision)
375
376 # TODO(hinoka): This must be implemented before we can turn this on for TS.
234 # if options.issue: 377 # if options.issue:
235 # apply_issue(options.issue, options.patchset, options.root, options.server) 378 # apply_issue(options.issue, options.patchset, options.root, options.server)
236 # deps2git() 379
237 # gclient_sync() 380 # Magic to get deps2git to work with internal DEPS.
381 shutil.copyfile(S2G_INTERNAL_FROM_PATH, S2G_INTERNAL_DEST_PATH)
382 deps2git(dir_names)
383
384 gclient_configure(git_solutions)
385 gclient_sync()
386 emit_flag(options.flag_file)
238 387
239 388
240 if __name__ == '__main__': 389 if __name__ == '__main__':
241 sys.exit(main()) 390 sys.exit(main())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698