OLD | NEW |
| (Empty) |
1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """Set of operations/utilities related to checking out the depot, and | |
6 outputting annotations on the buildbot waterfall. These are intended to be | |
7 used by the bisection scripts.""" | |
8 | |
9 import errno | |
10 import imp | |
11 import os | |
12 import shutil | |
13 import stat | |
14 import subprocess | |
15 import sys | |
16 | |
17 DEFAULT_GCLIENT_CUSTOM_DEPS = { | |
18 "src/data/page_cycler": "https://chrome-internal.googlesource.com/" | |
19 "chrome/data/page_cycler/.git", | |
20 "src/data/dom_perf": "https://chrome-internal.googlesource.com/" | |
21 "chrome/data/dom_perf/.git", | |
22 "src/data/mach_ports": "https://chrome-internal.googlesource.com/" | |
23 "chrome/data/mach_ports/.git", | |
24 "src/tools/perf/data": "https://chrome-internal.googlesource.com/" | |
25 "chrome/tools/perf/data/.git", | |
26 "src/third_party/adobe/flash/binaries/ppapi/linux": | |
27 "https://chrome-internal.googlesource.com/" | |
28 "chrome/deps/adobe/flash/binaries/ppapi/linux/.git", | |
29 "src/third_party/adobe/flash/binaries/ppapi/linux_x64": | |
30 "https://chrome-internal.googlesource.com/" | |
31 "chrome/deps/adobe/flash/binaries/ppapi/linux_x64/.git", | |
32 "src/third_party/adobe/flash/binaries/ppapi/mac": | |
33 "https://chrome-internal.googlesource.com/" | |
34 "chrome/deps/adobe/flash/binaries/ppapi/mac/.git", | |
35 "src/third_party/adobe/flash/binaries/ppapi/mac_64": | |
36 "https://chrome-internal.googlesource.com/" | |
37 "chrome/deps/adobe/flash/binaries/ppapi/mac_64/.git", | |
38 "src/third_party/adobe/flash/binaries/ppapi/win": | |
39 "https://chrome-internal.googlesource.com/" | |
40 "chrome/deps/adobe/flash/binaries/ppapi/win/.git", | |
41 "src/third_party/adobe/flash/binaries/ppapi/win_x64": | |
42 "https://chrome-internal.googlesource.com/" | |
43 "chrome/deps/adobe/flash/binaries/ppapi/win_x64/.git", | |
44 "src/chrome/tools/test/reference_build/chrome_win": None, | |
45 "src/chrome/tools/test/reference_build/chrome_mac": None, | |
46 "src/chrome/tools/test/reference_build/chrome_linux": None, | |
47 "src/third_party/WebKit/LayoutTests": None, | |
48 "src/tools/valgrind": None,} | |
49 | |
50 GCLIENT_SPEC_DATA = [ | |
51 { "name" : "src", | |
52 "url" : "https://chromium.googlesource.com/chromium/src.git", | |
53 "deps_file" : ".DEPS.git", | |
54 "managed" : True, | |
55 "custom_deps" : {}, | |
56 "safesync_url": "", | |
57 }, | |
58 ] | |
59 GCLIENT_SPEC_ANDROID = "\ntarget_os = ['android']" | |
60 GCLIENT_CUSTOM_DEPS_V8 = {"src/v8_bleeding_edge": "git://github.com/v8/v8.git"} | |
61 FILE_DEPS_GIT = '.DEPS.git' | |
62 FILE_DEPS = 'DEPS' | |
63 | |
64 REPO_PARAMS = [ | |
65 'https://chrome-internal.googlesource.com/chromeos/manifest-internal/', | |
66 '--repo-url', | |
67 'https://git.chromium.org/external/repo.git' | |
68 ] | |
69 | |
70 REPO_SYNC_COMMAND = 'git checkout -f $(git rev-list --max-count=1 '\ | |
71 '--before=%d remotes/m/master)' | |
72 | |
73 ORIGINAL_ENV = {} | |
74 | |
75 def OutputAnnotationStepStart(name): | |
76 """Outputs appropriate annotation to signal the start of a step to | |
77 a trybot. | |
78 | |
79 Args: | |
80 name: The name of the step. | |
81 """ | |
82 print | |
83 print '@@@SEED_STEP %s@@@' % name | |
84 print '@@@STEP_CURSOR %s@@@' % name | |
85 print '@@@STEP_STARTED@@@' | |
86 print | |
87 sys.stdout.flush() | |
88 | |
89 | |
90 def OutputAnnotationStepClosed(): | |
91 """Outputs appropriate annotation to signal the closing of a step to | |
92 a trybot.""" | |
93 print | |
94 print '@@@STEP_CLOSED@@@' | |
95 print | |
96 sys.stdout.flush() | |
97 | |
98 | |
99 def OutputAnnotationStepLink(label, url): | |
100 """Outputs appropriate annotation to print a link. | |
101 | |
102 Args: | |
103 label: The name to print. | |
104 url: The url to print. | |
105 """ | |
106 print | |
107 print '@@@STEP_LINK@%s@%s@@@' % (label, url) | |
108 print | |
109 sys.stdout.flush() | |
110 | |
111 | |
112 def LoadExtraSrc(path_to_file): | |
113 """Attempts to load an extra source file. If this is successful, uses the | |
114 new module to override some global values, such as gclient spec data. | |
115 | |
116 Returns: | |
117 The loaded src module, or None.""" | |
118 try: | |
119 global GCLIENT_SPEC_DATA | |
120 global GCLIENT_SPEC_ANDROID | |
121 extra_src = imp.load_source('data', path_to_file) | |
122 GCLIENT_SPEC_DATA = extra_src.GetGClientSpec() | |
123 GCLIENT_SPEC_ANDROID = extra_src.GetGClientSpecExtraParams() | |
124 return extra_src | |
125 except ImportError, e: | |
126 return None | |
127 | |
128 | |
129 def IsTelemetryCommand(command): | |
130 """Attempts to discern whether or not a given command is running telemetry.""" | |
131 return ('tools/perf/run_' in command or 'tools\\perf\\run_' in command) | |
132 | |
133 | |
134 def CreateAndChangeToSourceDirectory(working_directory): | |
135 """Creates a directory 'bisect' as a subdirectory of 'working_directory'. If | |
136 the function is successful, the current working directory will change to that | |
137 of the new 'bisect' directory. | |
138 | |
139 Returns: | |
140 True if the directory was successfully created (or already existed). | |
141 """ | |
142 cwd = os.getcwd() | |
143 os.chdir(working_directory) | |
144 try: | |
145 os.mkdir('bisect') | |
146 except OSError, e: | |
147 if e.errno != errno.EEXIST: | |
148 return False | |
149 os.chdir('bisect') | |
150 return True | |
151 | |
152 | |
153 def SubprocessCall(cmd, cwd=None): | |
154 """Runs a subprocess with specified parameters. | |
155 | |
156 Args: | |
157 params: A list of parameters to pass to gclient. | |
158 cwd: Working directory to run from. | |
159 | |
160 Returns: | |
161 The return code of the call. | |
162 """ | |
163 if os.name == 'nt': | |
164 # "HOME" isn't normally defined on windows, but is needed | |
165 # for git to find the user's .netrc file. | |
166 if not os.getenv('HOME'): | |
167 os.environ['HOME'] = os.environ['USERPROFILE'] | |
168 shell = os.name == 'nt' | |
169 return subprocess.call(cmd, shell=shell, cwd=cwd) | |
170 | |
171 | |
172 def RunGClient(params, cwd=None): | |
173 """Runs gclient with the specified parameters. | |
174 | |
175 Args: | |
176 params: A list of parameters to pass to gclient. | |
177 cwd: Working directory to run from. | |
178 | |
179 Returns: | |
180 The return code of the call. | |
181 """ | |
182 cmd = ['gclient'] + params | |
183 | |
184 return SubprocessCall(cmd, cwd=cwd) | |
185 | |
186 | |
187 def RunRepo(params): | |
188 """Runs cros repo command with specified parameters. | |
189 | |
190 Args: | |
191 params: A list of parameters to pass to gclient. | |
192 | |
193 Returns: | |
194 The return code of the call. | |
195 """ | |
196 cmd = ['repo'] + params | |
197 | |
198 return SubprocessCall(cmd) | |
199 | |
200 | |
201 def RunRepoSyncAtTimestamp(timestamp): | |
202 """Syncs all git depots to the timestamp specified using repo forall. | |
203 | |
204 Args: | |
205 params: Unix timestamp to sync to. | |
206 | |
207 Returns: | |
208 The return code of the call. | |
209 """ | |
210 repo_sync = REPO_SYNC_COMMAND % timestamp | |
211 cmd = ['forall', '-c', REPO_SYNC_COMMAND % timestamp] | |
212 return RunRepo(cmd) | |
213 | |
214 | |
215 def RunGClientAndCreateConfig(opts, custom_deps=None, cwd=None): | |
216 """Runs gclient and creates a config containing both src and src-internal. | |
217 | |
218 Args: | |
219 opts: The options parsed from the command line through parse_args(). | |
220 custom_deps: A dictionary of additional dependencies to add to .gclient. | |
221 cwd: Working directory to run from. | |
222 | |
223 Returns: | |
224 The return code of the call. | |
225 """ | |
226 spec = GCLIENT_SPEC_DATA | |
227 | |
228 if custom_deps: | |
229 for k, v in custom_deps.iteritems(): | |
230 spec[0]['custom_deps'][k] = v | |
231 | |
232 # Cannot have newlines in string on windows | |
233 spec = 'solutions =' + str(spec) | |
234 spec = ''.join([l for l in spec.splitlines()]) | |
235 | |
236 if 'android' in opts.target_platform: | |
237 spec += GCLIENT_SPEC_ANDROID | |
238 | |
239 return_code = RunGClient( | |
240 ['config', '--spec=%s' % spec, '--git-deps'], cwd=cwd) | |
241 return return_code | |
242 | |
243 | |
244 def IsDepsFileBlink(): | |
245 """Reads .DEPS.git and returns whether or not we're using blink. | |
246 | |
247 Returns: | |
248 True if blink, false if webkit. | |
249 """ | |
250 locals = {'Var': lambda _: locals["vars"][_], | |
251 'From': lambda *args: None} | |
252 execfile(FILE_DEPS_GIT, {}, locals) | |
253 return 'blink.git' in locals['vars']['webkit_url'] | |
254 | |
255 | |
256 def OnAccessError(func, path, exc_info): | |
257 """ | |
258 Source: http://stackoverflow.com/questions/2656322/python-shutil-rmtree-fails-
on-windows-with-access-is-denied | |
259 | |
260 Error handler for ``shutil.rmtree``. | |
261 | |
262 If the error is due to an access error (read only file) | |
263 it attempts to add write permission and then retries. | |
264 | |
265 If the error is for another reason it re-raises the error. | |
266 | |
267 Args: | |
268 func: The function that raised the error. | |
269 path: The path name passed to func. | |
270 exc_info: Exception information returned by sys.exc_info(). | |
271 """ | |
272 if not os.access(path, os.W_OK): | |
273 # Is the error an access error ? | |
274 os.chmod(path, stat.S_IWUSR) | |
275 func(path) | |
276 else: | |
277 raise | |
278 | |
279 | |
280 def RemoveThirdPartyDirectory(dir_name): | |
281 """Removes third_party directory from the source. | |
282 | |
283 At some point, some of the third_parties were causing issues to changes in | |
284 the way they are synced. We remove such folder in order to avoid sync errors | |
285 while bisecting. | |
286 | |
287 Returns: | |
288 True on success, otherwise False. | |
289 """ | |
290 path_to_dir = os.path.join(os.getcwd(), 'third_party', dir_name) | |
291 try: | |
292 if os.path.exists(path_to_dir): | |
293 shutil.rmtree(path_to_dir, onerror=OnAccessError) | |
294 except OSError, e: | |
295 print 'Error #%d while running shutil.rmtree(%s): %s' % ( | |
296 e.errno, path_to_dir, str(e)) | |
297 if e.errno != errno.ENOENT: | |
298 return False | |
299 return True | |
300 | |
301 | |
302 def _CleanupPreviousGitRuns(): | |
303 """Performs necessary cleanup between runs.""" | |
304 # If a previous run of git crashed, bot was reset, etc... we | |
305 # might end up with leftover index.lock files. | |
306 for (path, dir, files) in os.walk(os.getcwd()): | |
307 for cur_file in files: | |
308 if cur_file.endswith('index.lock'): | |
309 path_to_file = os.path.join(path, cur_file) | |
310 os.remove(path_to_file) | |
311 | |
312 | |
313 def RunGClientAndSync(cwd=None): | |
314 """Runs gclient and does a normal sync. | |
315 | |
316 Args: | |
317 cwd: Working directory to run from. | |
318 | |
319 Returns: | |
320 The return code of the call. | |
321 """ | |
322 params = ['sync', '--verbose', '--nohooks', '--reset', '--force'] | |
323 return RunGClient(params, cwd=cwd) | |
324 | |
325 | |
326 def SetupGitDepot(opts, custom_deps): | |
327 """Sets up the depot for the bisection. The depot will be located in a | |
328 subdirectory called 'bisect'. | |
329 | |
330 Args: | |
331 opts: The options parsed from the command line through parse_args(). | |
332 custom_deps: A dictionary of additional dependencies to add to .gclient. | |
333 | |
334 Returns: | |
335 True if gclient successfully created the config file and did a sync, False | |
336 otherwise. | |
337 """ | |
338 name = 'Setting up Bisection Depot' | |
339 | |
340 if opts.output_buildbot_annotations: | |
341 OutputAnnotationStepStart(name) | |
342 | |
343 passed = False | |
344 | |
345 if not RunGClientAndCreateConfig(opts, custom_deps): | |
346 passed_deps_check = True | |
347 if os.path.isfile(os.path.join('src', FILE_DEPS_GIT)): | |
348 cwd = os.getcwd() | |
349 os.chdir('src') | |
350 if not IsDepsFileBlink(): | |
351 passed_deps_check = RemoveThirdPartyDirectory('Webkit') | |
352 else: | |
353 passed_deps_check = True | |
354 if passed_deps_check: | |
355 passed_deps_check = RemoveThirdPartyDirectory('libjingle') | |
356 if passed_deps_check: | |
357 passed_deps_check = RemoveThirdPartyDirectory('skia') | |
358 os.chdir(cwd) | |
359 | |
360 if passed_deps_check: | |
361 _CleanupPreviousGitRuns() | |
362 | |
363 RunGClient(['revert']) | |
364 if not RunGClientAndSync(): | |
365 passed = True | |
366 | |
367 if opts.output_buildbot_annotations: | |
368 print | |
369 OutputAnnotationStepClosed() | |
370 | |
371 return passed | |
372 | |
373 | |
374 def SetupCrosRepo(): | |
375 """Sets up cros repo for bisecting chromeos. | |
376 | |
377 Returns: | |
378 Returns 0 on success. | |
379 """ | |
380 cwd = os.getcwd() | |
381 try: | |
382 os.mkdir('cros') | |
383 except OSError, e: | |
384 if e.errno != errno.EEXIST: | |
385 return False | |
386 os.chdir('cros') | |
387 | |
388 cmd = ['init', '-u'] + REPO_PARAMS | |
389 | |
390 passed = False | |
391 | |
392 if not RunRepo(cmd): | |
393 if not RunRepo(['sync']): | |
394 passed = True | |
395 os.chdir(cwd) | |
396 | |
397 return passed | |
398 | |
399 | |
400 def CopyAndSaveOriginalEnvironmentVars(): | |
401 """Makes a copy of the current environment variables.""" | |
402 # TODO: Waiting on crbug.com/255689, will remove this after. | |
403 vars_to_remove = [] | |
404 for k, v in os.environ.iteritems(): | |
405 if 'ANDROID' in k: | |
406 vars_to_remove.append(k) | |
407 vars_to_remove.append('CHROME_SRC') | |
408 vars_to_remove.append('CHROMIUM_GYP_FILE') | |
409 vars_to_remove.append('GYP_CROSSCOMPILE') | |
410 vars_to_remove.append('GYP_DEFINES') | |
411 vars_to_remove.append('GYP_GENERATORS') | |
412 vars_to_remove.append('GYP_GENERATOR_FLAGS') | |
413 vars_to_remove.append('OBJCOPY') | |
414 for k in vars_to_remove: | |
415 if os.environ.has_key(k): | |
416 del os.environ[k] | |
417 | |
418 global ORIGINAL_ENV | |
419 ORIGINAL_ENV = os.environ.copy() | |
420 | |
421 | |
422 def SetupAndroidBuildEnvironment(opts, path_to_src=None): | |
423 """Sets up the android build environment. | |
424 | |
425 Args: | |
426 opts: The options parsed from the command line through parse_args(). | |
427 path_to_src: Path to the src checkout. | |
428 | |
429 Returns: | |
430 True if successful. | |
431 """ | |
432 | |
433 # Revert the environment variables back to default before setting them up | |
434 # with envsetup.sh. | |
435 env_vars = os.environ.copy() | |
436 for k, _ in env_vars.iteritems(): | |
437 del os.environ[k] | |
438 for k, v in ORIGINAL_ENV.iteritems(): | |
439 os.environ[k] = v | |
440 | |
441 path_to_file = os.path.join('build', 'android', 'envsetup.sh') | |
442 proc = subprocess.Popen(['bash', '-c', 'source %s && env' % path_to_file], | |
443 stdout=subprocess.PIPE, | |
444 stderr=subprocess.PIPE, | |
445 cwd=path_to_src) | |
446 (out, _) = proc.communicate() | |
447 | |
448 for line in out.splitlines(): | |
449 (k, _, v) = line.partition('=') | |
450 os.environ[k] = v | |
451 # envsetup.sh no longer sets OS=android to GYP_DEFINES env variable | |
452 # (CL/170273005). Set this variable explicitly inorder to build chrome on | |
453 # android. | |
454 try: | |
455 if 'OS=android' not in os.environ['GYP_DEFINES']: | |
456 os.environ['GYP_DEFINES'] = '%s %s' % (os.environ['GYP_DEFINES'], | |
457 'OS=android') | |
458 except KeyError: | |
459 os.environ['GYP_DEFINES'] = 'OS=android' | |
460 | |
461 if opts.use_goma: | |
462 os.environ['GYP_DEFINES'] = '%s %s' % (os.environ['GYP_DEFINES'], | |
463 'use_goma=1') | |
464 return not proc.returncode | |
465 | |
466 | |
467 def SetupPlatformBuildEnvironment(opts): | |
468 """Performs any platform specific setup. | |
469 | |
470 Args: | |
471 opts: The options parsed from the command line through parse_args(). | |
472 | |
473 Returns: | |
474 True if successful. | |
475 """ | |
476 if 'android' in opts.target_platform: | |
477 CopyAndSaveOriginalEnvironmentVars() | |
478 return SetupAndroidBuildEnvironment(opts) | |
479 elif opts.target_platform == 'cros': | |
480 return SetupCrosRepo() | |
481 | |
482 return True | |
483 | |
484 | |
485 def CheckIfBisectDepotExists(opts): | |
486 """Checks if the bisect directory already exists. | |
487 | |
488 Args: | |
489 opts: The options parsed from the command line through parse_args(). | |
490 | |
491 Returns: | |
492 Returns True if it exists. | |
493 """ | |
494 path_to_dir = os.path.join(opts.working_directory, 'bisect', 'src') | |
495 return os.path.exists(path_to_dir) | |
496 | |
497 | |
498 def CreateBisectDirectoryAndSetupDepot(opts, custom_deps): | |
499 """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot | |
500 there using gclient. | |
501 | |
502 Args: | |
503 opts: The options parsed from the command line through parse_args(). | |
504 custom_deps: A dictionary of additional dependencies to add to .gclient. | |
505 """ | |
506 if not CreateAndChangeToSourceDirectory(opts.working_directory): | |
507 raise RuntimeError('Could not create bisect directory.') | |
508 | |
509 if not SetupGitDepot(opts, custom_deps): | |
510 raise RuntimeError('Failed to grab source.') | |
OLD | NEW |