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

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

Powered by Google App Engine
This is Rietveld 408576698