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

Side by Side Diff: tools/auto_bisect/fetch_build.py

Issue 548233002: Add a module to fetch builds from different types of builders. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Updated comments Created 6 years, 3 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
(Empty)
1 # Copyright 2014 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 """This module contains functions for fetching and extracting archived builds.
6
7 The builds may be stored in different places by different types of builders;
8 for example, builders on tryserver.chromium.perf stores builds in one place,
9 while builders on chromium.linux store builds in another.
10
11 This module can be either imported or run as a stand-alone script to download
12 and extract a build.
13
14 Usage: fetch_build.py <type> <revision> <output_dir> [options]
15 """
16
17 import argparse
18 import errno
19 import os
20 import shutil
21 import sys
22 import zipfile
23
24 import bisect_utils
25 import cloud_storage_wrapper as cloud_storage
26
27 # Possible builder types.
28 PERF_BUILDER = 'perf'
29 FULL_BUILDER = 'full'
30
31
32 def FetchBuild(builder_type, revision, output_dir, target_arch='x64',
33 target_platform='chromium', deps_patch_sha=None):
34 """Downloads and extracts a build for a particular revision.
35
36 If the build is successfully downloaded and extracted to |output_dir|, the
37 downloaded archive file is also deleted.
38
39 Args:
40 revision: Revision string, e.g. a git commit hash or SVN revision.
41 builder_type: Type of build archive.
42 target_arch: Architecture, e.g. "ia32".
43 target_platform: Platform name, e.g. "chromium" or "android".
44 deps_patch_sha: SHA1 hash of a DEPS file, if we want to fetch a build for
45 a Chromium revision with custom dependencies.
46
47 Raises:
48 IOError: Unzipping failed.
49 OSError: Directory creation or deletion failed.
50 NotImplementedError: The given builder type was not recognized.
ghost stip (do not use) 2014/09/17 00:12:08 probably don't need to have NotImplementedError or
qyearsley 2014/09/18 22:58:59 Alright - although those are also exceptions that
51 RuntimeError: Some other error fetching this particular build.
52 """
53 build_archive = BuildArchive.Create(
54 builder_type, target_arch=target_arch, target_platform=target_platform)
55 bucket = build_archive.BucketName()
56 remote_path = build_archive.FilePath(revision, deps_patch_sha=deps_patch_sha)
57
58 filename = FetchFromCloudStorage(bucket, remote_path, output_dir)
59 if not filename:
60 raise RuntimeError('Failed to fetch gs://%s/%s.' % (bucket, remote_path))
61
62 Unzip(filename, output_dir)
63
64 if os.path.exists(filename):
65 os.remove(filename)
66
67
68 class BuildArchive(object):
69 """Represents a place where builds of some type are stored.
70
71 There are two pieces of information required to locate a file in Google
72 Cloud Storage, bucket name and file path. Subclasses of this class contain
73 specific logic about which bucket names and paths should be used to fetch
74 a build.
75 """
76
77 @staticmethod
78 def Create(builder_type, target_arch='x64', target_platform='chromium'):
79 if builder_type == PERF_BUILDER:
80 return PerfBuildArchive(target_arch, target_platform)
81 if builder_type == FULL_BUILDER:
82 return FullBuildArchive(target_arch, target_platform)
83 raise NotImplementedError('Builder type "%s" not supported.' % builder_type)
84
85 def __init__(self, target_arch='x86', target_platform='chromium'):
86 self._target_arch = target_arch
87 self._target_platform = target_platform
88
89 def BucketName(self):
90 raise NotImplementedError()
91
92 def FilePath(self, revision, deps_patch_sha=None):
93 """Returns the remote file path to download a build from.
94
95 Args:
96 revision: A Chromium revision; this could be a git commit hash or
97 commit position or SVN revision number.
98 deps_patch_sha: The SHA1 hash of a patch to the DEPS file, which
99 uniquely identifies a change to use a particular revision of
100 a dependency.
101
102 Returns:
103 A file path, which not does not include a bucket name.
104 """
105 raise NotImplementedError()
106
107 def _ZipFileName(self, revision, deps_patch_sha=None):
108 """Gets the file name of a zip archive for a particular revision.
109
110 This returns a file name of the form full-build-<platform>_<revision>.zip,
111 which is a format used by multiple types of builders that store archives.
112
113 Args:
114 revision: A git commit hash or other revision string.
115 deps_patch_sha: SHA1 hash of a DEPS file patch.
116
117 Returns:
118 The archive file name.
119 """
120 base_name = 'full-build-%s' % self._PlatformName()
121 if deps_patch_sha:
122 revision = '%s_%s' % (revision , deps_patch_sha)
123 return '%s_%s.zip' % (base_name, revision)
124
125 @staticmethod
126 def _PlatformName():
127 """Return a string to be used in paths for the platform."""
128 if bisect_utils.IsWindowsHost() or bisect_utils.Is64BitWindows():
129 # Build archive for x64 is still stored with "win32" in the name.
130 return 'win32'
131 if bisect_utils.IsLinuxHost():
132 # Android builds are also stored with "linux" in the name.
133 return 'linux'
134 if bisect_utils.IsMacHost():
135 return 'mac'
136 raise NotImplementedError('Unknown platform "%s".' % sys.platform)
137
138
139 class PerfBuildArchive(BuildArchive):
140
141 def BucketName(self):
142 return 'chrome-perf'
143
144 def FilePath(self, revision, deps_patch_sha=None):
145 return '%s/%s' % (self._ArchiveDirectory(),
146 self._ZipFileName(revision, deps_patch_sha))
147
148 def _ArchiveDirectory(self):
149 """Returns the directory name to download builds from."""
150 if bisect_utils.Is64BitWindows() and self._target_arch == 'x64':
151 return 'Win x64 Builder'
152 if bisect_utils.IsWindowsHost():
153 return 'Win Builder'
154 if bisect_utils.IsLinuxHost() and self._target_platform == 'android':
155 return 'android_perf_rel'
156 if bisect_utils.IsLinuxHost():
157 return 'Linux Builder'
158 if bisect_utils.IsMacHost():
159 return 'Mac Builder'
160 raise NotImplementedError('Unsupported Platform: "%s".' % sys.platform)
161
162
163 class FullBuildArchive(BuildArchive):
164
165 def BucketName(self):
166 if bisect_utils.IsWindowsHost() or bisect_utils.Is64BitWindows():
167 return 'chromium-win-archive'
168 if bisect_utils.IsLinuxHost() and self._target_platform == 'android':
169 return 'chromium-android'
170 if bisect_utils.IsLinuxHost():
171 return 'chromium-linux-archive'
172 if bisect_utils.IsMacHost():
173 return 'chromium-mac-archive'
174 raise NotImplementedError('Unknown platform "%s".' % sys.platform)
175
176 def FilePath(self, revision, deps_patch_sha=None):
177 return '%s/%s' % (self._ArchiveDirectory(),
178 self._ZipFileName(revision, deps_patch_sha))
179
180 def _ArchiveDirectory(self):
181 """Returns the remote directory to download builds from."""
182 if bisect_utils.Is64BitWindows() and self._target_arch == 'x64':
ghost stip (do not use) 2014/09/17 00:12:08 I recommend making these a separate data structure
qyearsley 2014/09/18 22:58:59 Hey, what about this? In this version, a private
183 return 'chromium.win/Win x64 Builder'
184 if bisect_utils.IsWindowsHost():
185 return 'chromium.win/Win Builder'
186 if bisect_utils.IsLinuxHost() and self._target_platform == 'android':
187 return 'android_main_rel'
188 if bisect_utils.IsLinuxHost():
189 return 'chromium.linux/Linux Builder'
190 if bisect_utils.Mac():
191 return 'chromium.mac/Mac Builder'
192 raise NotImplementedError('Unknown platform "%s".' % sys.platform)
193
194
195 def FetchFromCloudStorage(bucket_name, source_path, destination_dir):
196 """Fetches file(s) from the Google Cloud Storage.
197
198 As a side-effect, this prints messages to stdout about what's happening.
199
200 Args:
201 bucket_name: Google Storage bucket name.
202 source_path: Source file path.
203 destination_dir: Destination file path.
204
205 Returns:
206 Local file path of downloaded file if it was downloaded. If the file does
207 not exist in the given bucket, or if there was an error while downloading,
208 None is returned.
209 """
210 target_file = os.path.join(destination_dir, os.path.basename(source_path))
211 gs_url = 'gs://%s/%s' % (bucket_name, source_path)
212 try:
213 if cloud_storage.Exists(bucket_name, source_path):
214 print 'Fetching file from %s...' % gs_url
215 cloud_storage.Get(bucket_name, source_path, target_file)
216 if os.path.exists(target_file):
ghost stip (do not use) 2014/09/17 00:12:08 when would this be false?
qyearsley 2014/09/18 22:58:59 Not 100% sure, but if something goes wrong with cl
217 return target_file
218 else:
219 print 'File %s not found in cloud storage.' % gs_url
220 except Exception as e:
221 print 'Exception while fetching from cloud storage: %s' % e
222 if os.path.exists(target_file):
223 os.remove(target_file)
224 return None
225
226
227 def Unzip(filename, output_dir, verbose=True):
228 """Extracts a zip archive's contents into the given output directory.
229
230 This was based on ExtractZip from build/scripts/common/chromium_utils.py.
231
232 Args:
233 filename: Name of the zip file to extract.
234 output_dir: Path to the destination directory.
235 verbose: Whether to print out what is being extracted.
236
237 Raises:
238 IOError: The unzip command had a non-zero exit code.
239 RuntimeError: Failed to create the output directory.
240 """
241 _MakeDirectory(output_dir)
242
243 # On Linux and Mac, we use the unzip command because it handles links and
244 # file permissions bits, so achieving this behavior is easier than with
245 # ZipInfo options.
246 #
247 # The Mac Version of unzip unfortunately does not support Zip64, whereas
248 # the python module does, so we have to fall back to the python zip module
249 # on Mac if the file size is greater than 4GB.
250 mac_zip_size_limit = 2 ** 32 # 4GB
251 if (bisect_utils.IsLinuxHost() or
252 (bisect_utils.IsMacHost()
253 and os.path.getsize(filename) < mac_zip_size_limit)):
254 unzip_command = ['unzip', '-o']
255 _UnzipUsingCommand(unzip_command, filename, output_dir)
256 return
257
258 # On Windows, try to use 7z if it is installed, otherwise fall back to the
259 # Python zipfile module. If 7z is not installed, then this may fail if the
260 # zip file is larger than 512MB.
261 sevenzip_path = r'C:\Program Files\7-Zip\7z.exe'
262 if (bisect_utils.IsWindowsHost() and os.path.exists(sevenzip_path)):
263 unzip_command = [sevenzip_path, 'x', '-y']
264 _UnzipUsingCommand(unzip_command, filename, output_dir)
265 return
266
267 _UnzipUsingZipFile(filename, output_dir, verbose)
268
269
270 def _UnzipUsingCommand(unzip_command, filename, output_dir):
271 """Extracts a zip file using an external command.
272
273 Args:
274 unzip_command: An unzipping command, as a string list, without the filename.
275 filename: Path to the zip file.
276 output_dir: The directory which the contents should be extracted to.
277
278 Raises:
279 IOError: The command had a non-zero exit code.
280 """
281 absolute_filepath = os.path.abspath(filename)
282 command = unzip_command + [absolute_filepath]
283 return_code = _RunCommandInDirectory(output_dir, command)
284 if return_code:
285 _RemoveDirectoryTree(output_dir)
286 raise IOError('Unzip failed: %s => %s' % (str(command), return_code))
287
288
289 def _RunCommandInDirectory(directory, command):
290 """Changes to a directory, runs a command, then changes back."""
291 saved_dir = os.getcwd()
292 os.chdir(directory)
293 return_code = bisect_utils.RunProcess(command)
294 os.chdir(saved_dir)
295 return return_code
296
297
298 def _UnzipUsingZipFile(filename, output_dir, verbose=True):
299 """Extracts a zip file using the Python zipfile module."""
300 assert bisect_utils.IsWindowsHost() or bisect_utils.IsMacHost()
301 zf = zipfile.ZipFile(filename)
302 for name in zf.namelist():
303 if verbose:
304 print 'Extracting %s' % name
305 zf.extract(name, output_dir)
306 if bisect_utils.IsMacHost():
307 # Restore file permission bits.
308 mode = zf.getinfo(name).external_attr >> 16
309 os.chmod(os.path.join(output_dir, name), mode)
310
311
312 def _MakeDirectory(path):
313 try:
314 os.makedirs(path)
315 except OSError as e:
316 if e.errno != errno.EEXIST:
317 raise
318
319
320 def _RemoveDirectoryTree(path):
321 try:
322 if os.path.exists(path):
323 shutil.rmtree(path)
324 except OSError, e:
325 if e.errno != errno.ENOENT:
326 raise
327
328
329 def Main(argv):
330 """Downloads and extracts a build based on the command line arguments."""
331 parser = argparse.ArgumentParser()
332 parser.add_argument('builder_type')
333 parser.add_argument('revision')
334 parser.add_argument('output_dir')
335 parser.add_argument('--target-arch', default='ia32')
336 parser.add_argument('--target-platform', default='chromium')
337 parser.add_argument('--deps-patch-sha')
338 args = parser.parse_args(argv[1:])
339
340 FetchBuild(
341 args.builder_type, args.revision, args.output_dir,
342 target_arch=args.target_arch, target_platform=args.target_platform,
343 deps_patch_sha=args.deps_patch_sha)
344
345 print 'Build has been downloaded to and extracted in %s.' % args.output_dir
346
347 return 0
348
349
350 if __name__ == '__main__':
351 sys.exit(Main(sys.argv))
352
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698