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.
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."""
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,}
52 "url" : "https://chromium.googlesource.com/chromium/src.git",
53 "deps_file" : ".DEPS.git",
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'
65 'https://chrome-internal.googlesource.com/chromeos/manifest-internal/',
67 'https://git.chromium.org/external/repo.git'
70 REPO_SYNC_COMMAND
= 'git checkout -f $(git rev-list --max-count=1 '\
71 '--before=%d remotes/m/master)'
75 def OutputAnnotationStepStart(name
):
76 """Outputs appropriate annotation to signal the start of a step to
80 name: The name of the step.
83 print '@@@SEED_STEP %s@@@' % name
84 print '@@@STEP_CURSOR %s@@@' % name
85 print '@@@STEP_STARTED@@@'
90 def OutputAnnotationStepClosed():
91 """Outputs appropriate annotation to signal the closing of a step to
94 print '@@@STEP_CLOSED@@@'
99 def OutputAnnotationStepLink(label
, url
):
100 """Outputs appropriate annotation to print a link.
103 label: The name to print.
104 url: The url to print.
107 print '@@@STEP_LINK@%s@%s@@@' % (label
, url
)
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.
117 The loaded src module, or None."""
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()
125 except ImportError, e
:
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
)
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.
140 True if the directory was successfully created (or already existed).
143 os
.chdir(working_directory
)
147 if e
.errno
!= errno
.EEXIST
:
153 def SubprocessCall(cmd
, cwd
=None):
154 """Runs a subprocess with specified parameters.
157 params: A list of parameters to pass to gclient.
158 cwd: Working directory to run from.
161 The return code of the call.
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
)
172 def RunGClient(params
, cwd
=None):
173 """Runs gclient with the specified parameters.
176 params: A list of parameters to pass to gclient.
177 cwd: Working directory to run from.
180 The return code of the call.
182 cmd
= ['gclient'] + params
184 return SubprocessCall(cmd
, cwd
=cwd
)
188 """Runs cros repo command with specified parameters.
191 params: A list of parameters to pass to gclient.
194 The return code of the call.
196 cmd
= ['repo'] + params
198 return SubprocessCall(cmd
)
201 def RunRepoSyncAtTimestamp(timestamp
):
202 """Syncs all git depots to the timestamp specified using repo forall.
205 params: Unix timestamp to sync to.
208 The return code of the call.
210 repo_sync
= REPO_SYNC_COMMAND
% timestamp
211 cmd
= ['forall', '-c', REPO_SYNC_COMMAND
% timestamp
]
215 def RunGClientAndCreateConfig(opts
, custom_deps
=None, cwd
=None):
216 """Runs gclient and creates a config containing both src and src-internal.
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.
224 The return code of the call.
226 spec
= GCLIENT_SPEC_DATA
229 for k
, v
in custom_deps
.iteritems():
230 spec
[0]['custom_deps'][k
] = v
232 # Cannot have newlines in string on windows
233 spec
= 'solutions =' + str(spec
)
234 spec
= ''.join([l
for l
in spec
.splitlines()])
236 if 'android' in opts
.target_platform
:
237 spec
+= GCLIENT_SPEC_ANDROID
239 return_code
= RunGClient(
240 ['config', '--spec=%s' % spec
, '--git-deps'], cwd
=cwd
)
244 def IsDepsFileBlink():
245 """Reads .DEPS.git and returns whether or not we're using blink.
248 True if blink, false if webkit.
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']
256 def OnAccessError(func
, path
, exc_info
):
258 Source: http://stackoverflow.com/questions/2656322/python-shutil-rmtree-fails-on-windows-with-access-is-denied
260 Error handler for ``shutil.rmtree``.
262 If the error is due to an access error (read only file)
263 it attempts to add write permission and then retries.
265 If the error is for another reason it re-raises the error.
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().
272 if not os
.access(path
, os
.W_OK
):
273 # Is the error an access error ?
274 os
.chmod(path
, stat
.S_IWUSR
)
280 def RemoveThirdPartyDirectory(dir_name
):
281 """Removes third_party directory from the source.
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
288 True on success, otherwise False.
290 path_to_dir
= os
.path
.join(os
.getcwd(), 'third_party', dir_name
)
292 if os
.path
.exists(path_to_dir
):
293 shutil
.rmtree(path_to_dir
, onerror
=OnAccessError
)
295 print 'Error #%d while running shutil.rmtree(%s): %s' % (
296 e
.errno
, path_to_dir
, str(e
))
297 if e
.errno
!= errno
.ENOENT
:
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
)
313 def RunGClientAndSync(cwd
=None):
314 """Runs gclient and does a normal sync.
317 cwd: Working directory to run from.
320 The return code of the call.
322 params
= ['sync', '--verbose', '--nohooks', '--reset', '--force']
323 return RunGClient(params
, cwd
=cwd
)
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'.
331 opts: The options parsed from the command line through parse_args().
332 custom_deps: A dictionary of additional dependencies to add to .gclient.
335 True if gclient successfully created the config file and did a sync, False
338 name
= 'Setting up Bisection Depot'
340 if opts
.output_buildbot_annotations
:
341 OutputAnnotationStepStart(name
)
345 if not RunGClientAndCreateConfig(opts
, custom_deps
):
346 passed_deps_check
= True
347 if os
.path
.isfile(os
.path
.join('src', FILE_DEPS_GIT
)):
350 if not IsDepsFileBlink():
351 passed_deps_check
= RemoveThirdPartyDirectory('Webkit')
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')
360 if passed_deps_check
:
361 _CleanupPreviousGitRuns()
363 RunGClient(['revert'])
364 if not RunGClientAndSync():
367 if opts
.output_buildbot_annotations
:
369 OutputAnnotationStepClosed()
375 """Sets up cros repo for bisecting chromeos.
378 Returns 0 on success.
384 if e
.errno
!= errno
.EEXIST
:
388 cmd
= ['init', '-u'] + REPO_PARAMS
393 if not RunRepo(['sync']):
400 def CopyAndSaveOriginalEnvironmentVars():
401 """Makes a copy of the current environment variables."""
402 # TODO: Waiting on crbug.com/255689, will remove this after.
404 for k
, v
in os
.environ
.iteritems():
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
):
419 ORIGINAL_ENV
= os
.environ
.copy()
422 def SetupAndroidBuildEnvironment(opts
, path_to_src
=None):
423 """Sets up the android build environment.
426 opts: The options parsed from the command line through parse_args().
427 path_to_src: Path to the src checkout.
433 # Revert the environment variables back to default before setting them up
435 env_vars
= os
.environ
.copy()
436 for k
, _
in env_vars
.iteritems():
438 for k
, v
in ORIGINAL_ENV
.iteritems():
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
,
446 (out
, _
) = proc
.communicate()
448 for line
in out
.splitlines():
449 (k
, _
, v
) = line
.partition('=')
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
455 if 'OS=android' not in os
.environ
['GYP_DEFINES']:
456 os
.environ
['GYP_DEFINES'] = '%s %s' % (os
.environ
['GYP_DEFINES'],
459 os
.environ
['GYP_DEFINES'] = 'OS=android'
462 os
.environ
['GYP_DEFINES'] = '%s %s' % (os
.environ
['GYP_DEFINES'],
464 return not proc
.returncode
467 def SetupPlatformBuildEnvironment(opts
):
468 """Performs any platform specific setup.
471 opts: The options parsed from the command line through parse_args().
476 if 'android' in opts
.target_platform
:
477 CopyAndSaveOriginalEnvironmentVars()
478 return SetupAndroidBuildEnvironment(opts
)
479 elif opts
.target_platform
== 'cros':
480 return SetupCrosRepo()
485 def CheckIfBisectDepotExists(opts
):
486 """Checks if the bisect directory already exists.
489 opts: The options parsed from the command line through parse_args().
492 Returns True if it exists.
494 path_to_dir
= os
.path
.join(opts
.working_directory
, 'bisect', 'src')
495 return os
.path
.exists(path_to_dir
)
498 def CreateBisectDirectoryAndSetupDepot(opts
, custom_deps
):
499 """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot
503 opts: The options parsed from the command line through parse_args().
504 custom_deps: A dictionary of additional dependencies to add to .gclient.
506 if not CreateAndChangeToSourceDirectory(opts
.working_directory
):
507 raise RuntimeError('Could not create bisect directory.')
509 if not SetupGitDepot(opts
, custom_deps
):
510 raise RuntimeError('Failed to grab source.')