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

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: Respond to 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 builders;
ojan 2014/09/12 00:36:28 types of builders.
qyearsley 2014/09/12 03:21:13 Done.
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.
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 # Delete downloaded zip file.
ojan 2014/09/12 00:36:27 That's pretty clearly what the code does. No need
qyearsley 2014/09/12 03:21:13 Done.
65 if os.path.exists(filename):
66 os.remove(filename)
67
68
69 class BuildArchive(object):
70 """Represents a place where builds of some type are stored.
71
72 There are two pieces of information required to locate a file in Google
73 Cloud Storage, bucket name and file path. Subclasses of this class contain
74 specific logic about which bucket names and paths should be used to fetch
75 a build.
76 """
77
78 @staticmethod
79 def Create(builder_type, target_arch='x64', target_platform='chromium'):
80 """Creates a BuildArchive instance for fetching builds of some type."""
ojan 2014/09/12 00:36:28 Ditto
qyearsley 2014/09/12 03:21:13 Done.
81 if builder_type == PERF_BUILDER:
82 return PerfBuildArchive(target_arch, target_platform)
83 if builder_type == FULL_BUILDER:
84 return FullBuildArchive(target_arch, target_platform)
85 raise NotImplementedError('Builder type "%s" not supported.' % builder_type)
86
87 def __init__(self, target_arch='x86', target_platform='chromium'):
88 self._target_arch = target_arch
89 self._target_platform = target_platform
90
91 def BucketName(self):
92 """Returns the Google Cloud Storage bucket name to download from."""
ojan 2014/09/12 00:36:27 Ditto
qyearsley 2014/09/12 03:21:13 Done.
93 raise NotImplementedError()
94
95 def FilePath(self, revision, deps_patch_sha=None):
96 """Returns the remote file path to download a build from.
97
98 Args:
99 revision: A Chromium revision; this could be a git commit hash or
100 commit position or SVN revision number.
101 deps_patch_sha: The SHA1 hash of a patch to the DEPS file, which
102 uniquely identifies a change to use a particular revision of
103 a dependency.
104
105 Returns:
106 A file path, which not does not include a bucket name.
107 """
108 raise NotImplementedError()
109
110 def _ZipFileName(self, revision, deps_patch_sha=None):
111 """Gets the file name of a zip archive for a particular revision.
112
113 This returns a file name of the form full-build-<platform>_<revision>.zip,
114 which is a format used by multiple types of builders that store archives.
115
116 Args:
117 revision: A git commit hash or other revision string.
118 deps_patch_sha: SHA1 hash of a DEPS file patch.
119
120 Returns:
121 The archive file name.
122 """
123 base_name = 'full-build-%s' % self._PlatformName()
124 if deps_patch_sha:
125 revision = '%s_%s' % (revision , deps_patch_sha)
126 return '%s_%s.zip' % (base_name, revision)
127
128 @staticmethod
129 def _PlatformName():
130 """Return a string to be used in paths for the platform."""
131 if bisect_utils.IsWindowsHost() or bisect_utils.Is64BitWindows():
132 # Build archive for x64 is still stored with "win32" in the name.
133 return 'win32'
134 if bisect_utils.IsLinuxHost():
135 # Android builds are also stored with "linux" in the name.
136 return 'linux'
137 if bisect_utils.IsMacHost():
138 return 'mac'
139 raise NotImplementedError('Unknown platform "%s".' % sys.platform)
140
141
142 class PerfBuildArchive(BuildArchive):
143 """Provides information about where perf builds are stored."""
ojan 2014/09/12 00:36:28 I'll stop commenting about removing comments...but
qyearsley 2014/09/12 03:21:13 Done.
144
145 def BucketName(self):
146 """Returns the Google Cloud Storage bucket name."""
147 return 'chrome-perf'
148
149 def FilePath(self, revision, deps_patch_sha=None):
150 """Returns the remote file path to download a build from."""
151 return '%s/%s' % (self._ArchiveDirectory(),
152 self._ZipFileName(revision, deps_patch_sha))
153
154 def _ArchiveDirectory(self):
155 """Returns the directory name to download builds from."""
156 if bisect_utils.Is64BitWindows() and self._target_arch == 'x64':
157 return 'Win x64 Builder'
158 if bisect_utils.IsWindowsHost():
159 return 'Win Builder'
160 if bisect_utils.IsLinuxHost() and self._target_platform == 'android':
161 return 'android_perf_rel'
162 if bisect_utils.IsLinuxHost():
163 return 'Linux Builder'
164 if bisect_utils.IsMacHost():
165 return 'Mac Builder'
166 raise NotImplementedError('Unsupported Platform: "%s".' % sys.platform)
167
168
169 class FullBuildArchive(BuildArchive):
170 """Provides information about where perf builds are stored."""
171
172 def BucketName(self):
173 """Returns the Google Cloud Storage bucket name."""
174 if bisect_utils.IsWindowsHost() or bisect_utils.Is64BitWindows():
175 return 'chromium-win-archive'
176 if bisect_utils.IsLinuxHost() and self._target_platform == 'android':
177 return 'chromium-android'
178 if bisect_utils.IsLinuxHost():
179 return 'chromium-linux-archive'
180 if bisect_utils.IsMacHost():
181 return 'chromium-mac-archive'
182 raise NotImplementedError('Unknown platform "%s".' % sys.platform)
183
184 def FilePath(self, revision, deps_patch_sha=None):
185 """Returns the remote file path to download a build from."""
186 return '%s/%s' % (self._ArchiveDirectory(),
187 self._ZipFileName(revision, deps_patch_sha))
188
189 def _ArchiveDirectory(self):
190 """Returns the remote directory to download builds from."""
191 if bisect_utils.Is64BitWindows() and self._target_arch == 'x64':
ojan 2014/09/12 00:36:28 This sort of manual mapping isn't going to work lo
qyearsley 2014/09/12 03:21:13 On first thought, the only way I could think of be
192 return 'chromium.win/Win x64 Builder'
193 if bisect_utils.IsWindowsHost():
194 return 'chromium.win/Win Builder'
195 if bisect_utils.IsLinuxHost() and self._target_platform == 'android':
196 return 'android_main_rel'
197 if bisect_utils.IsLinuxHost():
198 return 'chromium.linux/Linux Builder'
199 if bisect_utils.Mac():
200 return 'chromium.mac/Mac Builder'
201 raise NotImplementedError('Unknown platform "%s".' % sys.platform)
202
203
204 def FetchFromCloudStorage(bucket_name, source_path, destination_dir):
205 """Fetches file(s) from the Google Cloud Storage.
206
207 As a side-effect, this prints messages to stdout about what's happening.
208
209 Args:
210 bucket_name: Google Storage bucket name.
211 source_path: Source file path.
212 destination_dir: Destination file path.
213
214 Returns:
215 Local file path of downloaded file if it was downloaded. If the file does
216 not exist in the given bucket, or if there was an error while downloading,
217 None is returned.
218 """
219 target_file = os.path.join(destination_dir, os.path.basename(source_path))
220 gs_url = 'gs://%s/%s' % (bucket_name, source_path)
221 try:
222 if cloud_storage.Exists(bucket_name, source_path):
223 print 'Fetching file from %s...' % gs_url
224 cloud_storage.Get(bucket_name, source_path, target_file)
225 if os.path.exists(target_file):
226 return target_file
227 else:
228 print 'File %s not found in cloud storage.' % gs_url
229 except Exception as e:
230 print 'Exception while fetching from cloud storage: %s' % e
231 if os.path.exists(target_file):
232 os.remove(target_file)
233 return None
234
235
236 def Unzip(filename, output_dir, verbose=True):
237 """Extracts a zip archive's contents into the given output directory.
238
239 This was based on ExtractZip from build/scripts/common/chromium_utils.py.
240
241 Args:
242 filename: Name of the zip file to extract.
243 output_dir: Path to the destination directory.
244 verbose: Whether to print out what is being extracted.
245
246 Raises:
247 IOError: The unzip command had a non-zero exit code.
248 RuntimeError: Failed to create the output directory.
249 """
250 _MakeDirectory(output_dir)
251
252 # On Linux and Mac, we use the unzip command because it handles links and
253 # file permissions bits, so achieving this behavior is easier than with
254 # ZipInfo options.
255 #
256 # The Mac Version of unzip unfortunately does not support Zip64, whereas
257 # the python module does, so we have to fall back to the python zip module
258 # on Mac if the file size is greater than 4GB.
259 mac_zip_size_limit = 2 ** 32 # 4GB
260 if (bisect_utils.IsLinuxHost() or
261 (bisect_utils.IsMacHost()
262 and os.path.getsize(filename) < mac_zip_size_limit)):
263 unzip_command = ['unzip', '-o']
264 _UnzipUsingCommand(unzip_command, filename, output_dir)
265 return
266
267 # On Windows, try to use 7z if it is installed, otherwise fall back to the
268 # Python zipfile module. If 7z is not installed, then this may fail if the
269 # zip file is larger than 512MB.
270 sevenzip_path = r'C:\Program Files\7-Zip\7z.exe'
271 if (bisect_utils.IsWindowsHost() and os.path.exists(sevenzip_path)):
272 unzip_command = [sevenzip_path, 'x', '-y']
273 _UnzipUsingCommand(unzip_command, filename, output_dir)
274 return
275
276 _UnzipUsingZipFile(filename, output_dir, verbose)
277
278
279 def _UnzipUsingCommand(unzip_command, filename, output_dir):
280 """Extracts a zip file using an external command.
281
282 Args:
283 unzip_command: An unzipping command, as a string list, without the filename.
284 filename: Path to the zip file.
285 output_dir: The directory which the contents should be extracted to.
286
287 Raises:
288 IOError: The command had a non-zero exit code.
289 """
290 absolute_filepath = os.path.abspath(filename)
291 command = unzip_command + [absolute_filepath]
292 return_code = _RunCommandInDirectory(output_dir, command)
293 if return_code:
294 _RmTree(output_dir)
295 raise IOError('Unzip failed: %s => %s' % (str(command), return_code))
296
297
298 def _RunCommandInDirectory(directory, command):
299 """Changes to a directory, runs a command, then changes back."""
300 saved_dir = os.getcwd()
301 os.chdir(directory)
302 return_code = bisect_utils.RunProcess(command)
303 os.chdir(saved_dir)
304 return return_code
305
306
307 def _UnzipUsingZipFile(filename, output_dir, verbose=True):
308 """Extracts a zip file using the Python zipfile module."""
309 assert bisect_utils.IsWindowsHost() or bisect_utils.IsMacHost()
310 zf = zipfile.ZipFile(filename)
311 for name in zf.namelist():
312 if verbose:
313 print 'Extracting %s' % name
314 zf.extract(name, output_dir)
315 if bisect_utils.IsMacHost():
316 # Restore file permission bits.
317 mode = zf.getinfo(name).external_attr >> 16
318 os.chmod(os.path.join(output_dir, name), mode)
319
320
321 def _MakeDirectory(path):
322 """Creates a directory if it doesn't already exist."""
323 try:
324 os.makedirs(path)
325 except OSError as e:
326 if e.errno != errno.EEXIST:
327 raise
328
329
330 def _RmTree(path):
331 """Removes the directory tree specified if it exists."""
332 try:
333 if os.path.exists(path):
334 shutil.rmtree(path)
335 except OSError, e:
336 if e.errno != errno.ENOENT:
337 raise
338
339
340 def Main(argv):
341 """Downloads and extracts a build based on the command line arguments."""
342 parser = argparse.ArgumentParser()
343 parser.add_argument('builder_type')
344 parser.add_argument('revision')
345 parser.add_argument('output_dir')
346 parser.add_argument('--target-arch', default='ia32')
347 parser.add_argument('--target-platform', default='chromium')
348 parser.add_argument('--deps-patch-sha')
349 args = parser.parse_args(argv[1:])
350
351 FetchBuild(
352 args.builder_type, args.revision, args.output_dir,
353 target_arch=args.target_arch, target_platform=args.target_platform,
354 deps_patch_sha=args.deps_patch_sha)
355
356 print 'Build has been downloaded to and extracted in %s.' % args.output_dir
357
358 return 0
359
360
361 if __name__ == '__main__':
362 sys.exit(Main(sys.argv))
363
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698