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

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: Chromium commands 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 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 aka /b/build/slave/cache_dir
89 CACHE_DIR = os.path.join(
agable 2014/02/07 22:19:06 could do this relative to SCRIPTS_DIR. Probably sh
iannucci 2014/02/07 22:24:09 * THIS_DIR = path.abspath(path.dirname(__file__))
Ryan Tseng 2014/02/07 22:27:50 Done.
90 os.path.dirname(os.path.dirname(os.path.abspath(os.getcwd()))),
91 'cache_dir')
92
93
94 class SubprocessFailed(Exception):
95 pass
96
97
98 def call(*args):
99 """Interactive subprocess call."""
100 tries = 0
101 while tries < RETRIES:
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 break
120 print '===Failed in %.1f mins===' % elapsed_time
121 print
122 tries += 1
123
124 if code:
125 raise SubprocessFailed('%s failed with code %d in %s' %
126 (' '.join(args), code, os.getcwd()))
127 return code
128
129
130 def get_gclient_spec(solutions):
131 return GCLIENT_TEMPLATE % {
132 'solutions': pprint.pformat(solutions, indent=4),
133 'cache_dir': '"%s"' % CACHE_DIR
134 }
135
66 136
67 def check_enabled(master, builder, slave): 137 def check_enabled(master, builder, slave):
68 if master in ENABLED_MASTERS: 138 if master in ENABLED_MASTERS:
69 return True 139 return True
70 builder_list = ENABLED_BUILDERS.get(master) 140 builder_list = ENABLED_BUILDERS.get(master)
71 if builder_list and builder in builder_list: 141 if builder_list and builder in builder_list:
72 return True 142 return True
73 slave_list = ENABLED_SLAVES.get(master) 143 slave_list = ENABLED_SLAVES.get(master)
74 if slave_list and slave in slave_list: 144 if slave_list and slave in slave_list:
75 return True 145 return True
76 return False 146 return False
77 147
78 148
79 def check_disabled(master, builder, slave): 149 def check_disabled(master, builder, slave):
80 """Returns True if disabled, False if not disabled.""" 150 """Returns True if disabled, False if not disabled."""
81 builder_list = DISABLED_BUILDERS.get(master) 151 builder_list = DISABLED_BUILDERS.get(master)
82 if builder_list and builder in builder_list: 152 if builder_list and builder in builder_list:
83 return True 153 return True
84 slave_list = DISABLED_SLAVES.get(master) 154 slave_list = DISABLED_SLAVES.get(master)
85 if slave_list and slave in slave_list: 155 if slave_list and slave in slave_list:
86 return True 156 return True
87 return False 157 return False
88 158
89 159
90 def check_valid_host(master, builder, slave): 160 def check_valid_host(master, builder, slave):
91 return False 161 return (check_enabled(master, builder, slave)
162 and not check_disabled(master, builder, slave))
92 163
93 164
94 def solutions_printer(solutions): 165 def solutions_printer(solutions):
95 """Prints gclient solution to stdout.""" 166 """Prints gclient solution to stdout."""
96 print 'Gclient Solutions' 167 print 'Gclient Solutions'
97 print '=================' 168 print '================='
98 for solution in solutions: 169 for solution in solutions:
99 name = solution.get('name') 170 name = solution.get('name')
100 url = solution.get('url') 171 url = solution.get('url')
101 print '%s (%s)' % (name, url) 172 print '%s (%s)' % (name, url)
173 if solution.get('deps_file'):
174 print ' Dependencies file is %s' % solution['deps_file']
175 if 'managed' in solution:
176 print ' Managed mode is %s' % ('ON' if solution['managed'] else 'OFF')
102 custom_vars = solution.get('custom_vars') 177 custom_vars = solution.get('custom_vars')
103 if custom_vars: 178 if custom_vars:
104 print ' Custom Variables:' 179 print ' Custom Variables:'
105 for var_name, var_value in sorted(custom_vars.iteritems()): 180 for var_name, var_value in sorted(custom_vars.iteritems()):
106 print ' %s = %s' % (var_name, var_value) 181 print ' %s = %s' % (var_name, var_value)
107 custom_deps = solution.get('custom_deps') 182 custom_deps = solution.get('custom_deps')
108 if 'custom_deps' in solution: 183 if 'custom_deps' in solution:
109 print ' Custom Dependencies:' 184 print ' Custom Dependencies:'
110 for deps_name, deps_value in sorted(custom_deps.iteritems()): 185 for deps_name, deps_value in sorted(custom_deps.iteritems()):
111 if deps_value: 186 if deps_value:
112 print ' %s -> %s' % (deps_name, deps_value) 187 print ' %s -> %s' % (deps_name, deps_value)
113 else: 188 else:
114 print ' %s: Ignore' % deps_name 189 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 190 print
120 191
121 192
122 def solutions_to_git(input_solutions): 193 def solutions_to_git(input_solutions):
123 """Modifies urls in solutions to point at Git repos.""" 194 """Modifies urls in solutions to point at Git repos."""
124 solutions = copy.deepcopy(input_solutions) 195 solutions = copy.deepcopy(input_solutions)
125 for solution in solutions: 196 for solution in solutions:
126 original_url = solution['url'] 197 original_url = solution['url']
127 parsed_url = urlparse.urlparse(original_url) 198 parsed_url = urlparse.urlparse(original_url)
128 path = parsed_url.path 199 path = parsed_url.path
129 if path in RECOGNIZED_PATHS: 200 if path in RECOGNIZED_PATHS:
130 solution['url'] = RECOGNIZED_PATHS[path] 201 solution['url'] = RECOGNIZED_PATHS[path]
131 else: 202 else:
132 print 'Warning: path %s not recognized' % path 203 print 'Warning: path %s not recognized' % path
133 if solution.get('deps_file', 'DEPS') == 'DEPS': 204 if solution.get('deps_file', 'DEPS') == 'DEPS':
134 solution['deps_file'] = '.DEPS.git' 205 solution['deps_file'] = '.DEPS.git'
135 solution['managed'] = False 206 solution['managed'] = False
136 return solutions 207 return solutions
137 208
138 209
139 def ensure_no_git_checkout(): 210 def ensure_no_checkout(dir_names, scm_dirname):
140 """Ensure that there is no git checkout under build/. 211 """Ensure that there is no git checkout under build/.
141 212
142 If there is a git checkout under build/, then move build/ to build.dead/ 213 If there is an incorrect checkout under build/, then
214 move build/ to build.dead/
215 This function will check each directory in dir_names.
216
217 scm_dirname is expected to be either ['.svn', '.git']
143 """ 218 """
144 pass 219 assert scm_dirname in ['.svn', '.git']
220 has_checkout = any(map(lambda dir_name: path.exists(
221 path.join(os.getcwd(), dir_name, scm_dirname)), dir_names))
145 222
223 if has_checkout:
224 # cd .. && rm -rf ./build && mkdir ./build && cd build
225 build_dir = os.getcwd()
146 226
147 def ensure_no_svn_checkout(): 227 os.chdir(path.dirname(os.getcwd()))
148 """Ensure that there is no svn checkout under build/. 228 print '%s detected in checking, deleting %s...' % (scm_dirname, build_dir),
229 shutil.rmtree(build_dir)
230 print 'done'
231 os.mkdir(build_dir)
232 os.chdir(build_dir)
149 233
150 If there is a svn checkout under build/, then move build/ to build.dead/
151 """
152 pass
153 234
154 235
155 def gclient_configure(solutions): 236 def gclient_configure(solutions):
156 pass 237 """Should do the same thing as gclient --spec='...'."""
238 with codecs.open('.gclient', mode='w', encoding='utf-8') as f:
239 f.write(get_gclient_spec(solutions))
157 240
158 241
159 def gclient_shallow_sync(): 242 def gclient_sync():
160 pass 243 call('gclient', 'sync', '--verbose', '--reset', '--force',
244 '--nohooks', '--noprehooks')
161 245
162 246
163 def git_pull_and_clean(): 247 def get_git_hash(revision, dir_name):
164 pass 248 match = "^git-svn-id: [^ ]*@%d" % revision
249 cmd = ['git', 'log', '--grep', match, '--format=%H', dir_name]
250 return subprocess.check_output(cmd).strip() or None
251
252
253 def deps2git(sln_dirs):
254 for sln_dir in sln_dirs:
255 deps_file = path.join(os.getcwd(), sln_dir, 'DEPS')
256 deps_git_file = path.join(os.getcwd(), sln_dir, '.DEPS.git')
257 if not path.isfile(deps_file):
258 return
259 # Do we have a better way of doing this....?
260 repo_type = 'internal' if 'internal' in sln_dir else 'public'
261 # TODO(hinoka): This will be what populates the git caches on the first
262 # run for all of the bots. Since deps2git is single threaded,
263 # all of this will run in a single threaded context and be
264 # super slow. Should make deps2git multithreaded.
265 call(sys.executable, DEPS2GIT_PATH, '-t', repo_type,
266 '--cache_dir=%s' % CACHE_DIR,
267 '--deps=%s' % deps_file, '--out=%s' % deps_git_file)
268
269
270 def git_checkout(solutions, revision):
271 build_dir = os.getcwd()
272 # Revision only applies to the first solution.
273 first_solution = True
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 # TODO(hinoka): We probably have to make use of revision mapping.
286 if first_solution and revision and revision.lower() != 'head':
287 if revision and revision.isdigit() and len(revision) < 40:
288 # rev_num is really a svn revision number, convert it into a git hash.
289 git_hash = get_git_hash(revision, name)
290 else:
291 # rev_num is actually a git hash or ref, we can just use it.
292 git_hash = revision
agable 2014/02/07 22:19:06 Since we're willing to use refs (HEAD, master, my-
Ryan Tseng 2014/02/07 22:27:50 Done.
293 call('git', '-C', sln_dir, 'checkout', git_hash)
294 else:
295 call('git', '-C', sln_dir, 'checkout', 'origin/master')
296
297 # Force another clean, because why not.
298 call('git', '-C', sln_dir, 'clean', '-df')
agable 2014/02/07 22:19:06 Only do this if you really think it's necessary. S
iannucci 2014/02/07 22:24:09 Isn't this necessary because there could be a patc
Ryan Tseng 2014/02/07 22:27:50 The one on line 283 is probably enough. I'll remov
299
300 first_solution = False
165 301
166 302
167 def apply_issue(issue, patchset, root, server): 303 def apply_issue(issue, patchset, root, server):
168 pass 304 pass
169 305
170 306
171 def deps2git(): 307 def delete_flag(flag_file):
172 pass 308 """Remove bot update flag."""
309 if os.path.exists(flag_file):
310 os.remove(flag_file)
173 311
174 312
175 def gclient_sync(): 313 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.""" 314 """Deposit a bot update flag on the system to tell gclient not to run."""
181 pass 315 print 'Emitting flag file at %s' % flag_file
316 with open(flag_file, 'wb') as f:
317 f.write('Success!')
182 318
183 319
184 def parse_args(): 320 def parse_args():
185 parse = optparse.OptionParser() 321 parse = optparse.OptionParser()
186 322
187 parse.add_option('-i', '--issue', help='Issue number to patch from.') 323 parse.add_option('-i', '--issue', help='Issue number to patch from.')
188 parse.add_option('-p', '--patchset', 324 parse.add_option('-p', '--patchset',
189 help='Patchset from issue to patch from, if applicable.') 325 help='Patchset from issue to patch from, if applicable.')
190 parse.add_option('-r', '--root', help='Repository root.') 326 parse.add_option('-r', '--root', help='Repository root.')
191 parse.add_option('-c', '--server', help='Rietveld server.') 327 parse.add_option('-c', '--server', help='Rietveld server.')
192 parse.add_option('-s', '--specs', help='Gcilent spec.') 328 parse.add_option('-s', '--specs', help='Gcilent spec.')
193 parse.add_option('-m', '--master', help='Master name.') 329 parse.add_option('-m', '--master', help='Master name.')
194 parse.add_option('-f', '--force', action='store_true', 330 parse.add_option('-f', '--force', action='store_true',
195 help='Bypass check to see if we want to be run. ' 331 help='Bypass check to see if we want to be run. '
196 'Should ONLY be used locally.') 332 'Should ONLY be used locally.')
333 # TODO(hinoka): We don't actually use this
agable 2014/02/07 22:19:06 Then can we remove it? Or does that require a mast
iannucci 2014/02/07 22:24:09 We probably shouldn't rely on this being a paramet
Ryan Tseng 2014/02/07 22:27:50 Rephrased. I mean we need to use this at some poi
197 parse.add_option('-e', '--revision-mapping') 334 parse.add_option('-e', '--revision-mapping')
335 parse.add_option('-v', '--revision')
336 parse.add_option('-b', '--build_dir', default=os.getcwd())
337 parse.add_option('-g', '--flag_file', default=path.join(os.getcwd(),
338 'update.flag'))
198 339
199 return parse.parse_args() 340 return parse.parse_args()
200 341
201 342
202 def main(): 343 def main():
203 # Get inputs. 344 # Get inputs.
204 options, _ = parse_args() 345 options, _ = parse_args()
205 builder = os.environ.get('BUILDBOT_BUILDERNAME', None) 346 builder = os.environ.get('BUILDBOT_BUILDERNAME', None)
206 slave = os.environ.get('BUILDBOT_SLAVENAME', None) 347 slave = os.environ.get('BUILDBOT_SLAVENAME', socket.getfqdn().split('.')[0])
207 master = options.master 348 master = options.master
349 if not options.revision:
350 options.revision = os.environ.get('BUILDBOT_REVISION')
208 351
209 # Check if this script should activate or not. 352 # Check if this script should activate or not.
210 active = check_valid_host(master, builder, slave) or options.force 353 active = check_valid_host(master, builder, slave) or options.force
211 354
212 # Print helpful messages to tell devs whats going on. 355 # Print helpful messages to tell devs whats going on.
213 print BOT_UPDATE_MESSAGE % { 356 print BOT_UPDATE_MESSAGE % {
214 'master': master, 357 'master': master,
215 'builder': builder, 358 'builder': builder,
216 'slave': slave, 359 'slave': slave,
217 }, 360 },
218 # Print to stderr so that it shows up red on win/mac. 361 # Print to stderr so that it shows up red on win/mac.
219 print ACTIVATED_MESSAGE if active else NOT_ACTIVATED_MESSAGE 362 print ACTIVATED_MESSAGE if active else NOT_ACTIVATED_MESSAGE
220 363
221 # Parse, munipulate, and print the gclient solutions. 364 # Parse, munipulate, and print the gclient solutions.
222 specs = {} 365 specs = {}
223 exec(options.specs, specs) # TODO(hinoka): LOL this is terrible. 366 exec(options.specs, specs)
224 solutions = specs.get('solutions', []) 367 svn_solutions = specs.get('solutions', [])
225 git_solutions = solutions_to_git(solutions) 368 git_solutions = solutions_to_git(svn_solutions)
226 solutions_printer(git_solutions) 369 solutions_printer(git_solutions)
227 370
228 # Do the checkout. 371 # Cleanup svn checkout if active, otherwise remove git checkout and exit.
229 # TODO(hinoka): Uncomment these once they're implemented. 372 dir_names = [sln.get('name') for sln in svn_solutions if 'name' in sln]
230 # ensure_no_svn_checkout() 373 if active:
231 # gclient_configure(git_solutions) 374 ensure_no_checkout(dir_names, '.svn')
232 # gclient_shallow_sync() 375 emit_flag(options.flag_file)
233 # git_pull_and_clean() 376 else:
377 ensure_no_checkout(dir_names, '.git')
378 delete_flag(options.flag_file)
379 return
380
381 # Get a checkout of each solution, without DEPS or hooks.
382 # Calling git directory because there is no way to run Gclient without
383 # invoking DEPS.
384 print 'Fetching Git checkout'
385 git_checkout(git_solutions, options.revision)
386
387 # TODO(hinoka): This must be implemented before we can turn this on for TS.
234 # if options.issue: 388 # if options.issue:
235 # apply_issue(options.issue, options.patchset, options.root, options.server) 389 # apply_issue(options.issue, options.patchset, options.root, options.server)
236 # deps2git() 390
237 # gclient_sync() 391 # Magic to get deps2git to work with internal DEPS.
392 shutil.copyfile(S2G_INTERNAL_FROM_PATH, S2G_INTERNAL_DEST_PATH)
393 deps2git(dir_names)
394
395 gclient_configure(git_solutions)
396 gclient_sync()
238 397
239 398
240 if __name__ == '__main__': 399 if __name__ == '__main__':
241 sys.exit(main()) 400 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