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

Side by Side Diff: tools/bisect_repackage/bisect_repackage.py

Issue 2236703003: tool for repackaging chrome.perf builds for manual bisect (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fixed error logging/exception Created 4 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
« no previous file with comments | « no previous file | tools/bisect_repackage/bisect_repackage_utils.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2016 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 """Bisect repackage tool for Linux.
5
6 This script repacakges chrome builds for manual bisect script.
7 """
8
9 from functools import partial
10 import json
11 import logging
12 from multiprocessing import Pool
13 import optparse
14 import os
15 import re
16 import sys
17 import tempfile
18 import threading
19 import urllib
20 import bisect_repackage_utils
21
22 # Declares required files to run manual bisect script on chrome Linux
23 # builds in perf. Binary files that should be stripped to reduce zip file
24 # size are declared. The file list was gotten from the local chrome
25 # executable path. (This can be retrieved by typing 'chrome://version'
26 # in chrome and following the executable path. The list needs to be updated if
27 # future chrome versions require additional files.
28 CHROME_REQUIRED_FILES = {
29 'linux': [
30 'chrome',
31 'chrome_100_percent.pak',
32 'chrome_200_percent.pak',
33 'default_apps',
34 'icudtl.dat',
35 'libwidevinecdm.so',
36 'locales',
37 'nacl_helper',
38 'nacl_helper_bootstrap',
39 'nacl_irt_x86_64.nexe',
40 'natives_blob.bin',
41 'PepperFlash',
42 'product_logo_48.png',
43 'resources.pak',
44 'snapshot_blob.bin',
45 'xdg-mime',
46 'xdg-settings'
47 ],
48 'win64': [
49 'chrome.dll',
50 'chrome.exe',
51 'chrome_100_percent.pak',
52 'chrome_200_percent.pak',
53 'chrome_child.dll',
54 'chrome_elf.dll',
55 'chrome_watcher.dll',
56 'default_apps',
57 'd3dcompiler_47.dll',
58 'icudtl.dat',
59 'libEGL.dll',
60 'libGLESv2.dll',
61 'locales',
62 'nacl_irt_x86_64.nexe',
63 'natives_blob.bin',
64 'PepperFlash',
65 'resources.pak',
66 'SecondaryTile.png',
67 'snapshot_blob.bin'
68 ],
69 'mac': [
70 'Google Chrome.app'
71 ]
72 }
73
74 CHROME_WHITELIST_FILES = {
75 # ^$ means not to include any files from whitelist
76 'linux': '^$',
77 'win64': '^\d+\.\d+\.\d+\.\d+\.manifest$',
78 'mac': '^$'
79 }
80
81 CHROME_STRIP_LIST = {
82 'linux': [
83 'chrome',
84 'nacl_helper'
85 ],
86 'win64': [
87 # No stripping symbols from win64 archives
88 ],
89 'mac': [
90 # No stripping symbols from mac archives
91 ]
92 }
93
94 # API to convert Githash to Commit position number.
95 CHROMIUM_GITHASH_TO_SVN_URL = (
96 'https://cr-rev.appspot.com/_ah/api/crrev/v1/commit/%s')
97
98 REVISION_MAP_FILE = 'revision_map.json'
99
100 BUILDER_NAME = {
101 'linux': 'Linux Builder',
102 'mac': 'Mac Builder',
103 'win32': 'Win Builder',
104 'win64': 'Win x64 Builder'
105 }
106
107 ARCHIVE_PREFIX = {
108 'linux': 'full-build-linux',
109 'mac': 'full-build-mac',
110 'win32': 'full-build-win32',
111 'win64': 'full-build-win32'
112 }
113
114 class ChromeExecutionError(Exception):
115 """Raised when Chrome execution fails."""
116 pass
117
118 class GitConversionError(Exception):
119 """Raised when Chrome execution fails."""
120 pass
121
122 class PathContext(object):
123 """Stores information to repackage from a bucket to another.
124
125 A PathContext is used to carry the information used to construct URLs and
126 paths when dealing with the storage server and archives.
127 """
128
129 def __init__(self, original_gs_url, repackage_gs_url,
130 archive, revision_file=REVISION_MAP_FILE):
131 super(PathContext, self).__init__()
132 self.original_gs_url = original_gs_url
133 self.repackage_gs_url = repackage_gs_url
134 self.archive = archive
135 self.builder_name = BUILDER_NAME[archive]
136 self.file_prefix = ARCHIVE_PREFIX[archive]
137 self.revision_file = revision_file
138
139
140 def get_cp_from_hash(git_hash):
141 """Converts a git hash to commit position number."""
142 json_url = CHROMIUM_GITHASH_TO_SVN_URL % git_hash
143 response = urllib.urlopen(json_url)
144 if response.getcode() == 200:
145 try:
146 data = json.loads(response.read())
147 except Exception,e:
148 logging.warning('JSON URL: %s, Error Message: %s' % json_url, e)
149 raise GitConversionError
150 else:
151 logging.warning('JSON URL: %s, Error Message: %s' % json_url, e)
152 raise GitConversionError
153 if 'number' in data:
154 return data['number']
155 logging.warning('JSON URL: %s, Error Message: %s' % json_url, e)
156 raise GitConversionError
157
158
159 def create_cp_from_hash_map(hash_list):
160 """Returns dict used for conversion of hash list.
161
162 Creates a dictionary that maps from Commit position number
163 to corresponding GitHash.
164 """
165 hash_map = {}
166 for git_hash in hash_list:
167 try:
168 cp_num = get_cp_from_hash(git_hash)
169 hash_map[cp_num] = git_hash
170 except GitConversionError:
171 pass
172 return hash_map
173
174
175 def get_list_of_suffix(bucket_address, prefix, filter_function):
176 """Gets the list of suffixes in files in a google storage bucket.
177
178 Example: a google storage bucket containing one file
179 'full-build-linux_20983' will return ['20983'] if prefix is
180 provided as 'full-build-linux'. Google Storage bucket
181 containing multiple files will return multiple suffixes.
182
183 Args:
184 bucket_address(String): Bucket URL to examine files from.
185 prefix(String): The prefix used in creating build file names
186 filter_function: A function that returns true if the extracted
187 suffix is in correct format and false otherwise. It allows
188 only proper suffix to be extracted and returned.
189
190 Returns:
191 (List) list of proper suffixes in the bucket.
192 """
193 file_list = bisect_repackage_utils.GSutilList(bucket_address)
194 suffix_list = []
195 extract_suffix = '.*?%s_(.*?)\.zip' %(prefix)
196 for file in file_list:
197 match = re.match(extract_suffix, file)
198 if match and filter_function(match.groups()[0]):
199 suffix_list.append(match.groups()[0])
200 return suffix_list
201
202
203 def download_build(cp_num, revision_map, zip_file_name, context):
204 """Download a single build corresponding to the cp_num and context."""
205 file_url = '%s/%s/%s_%s.zip' %(context.original_gs_url, context.builder_name,
206 context.file_prefix, revision_map[cp_num])
207 bisect_repackage_utils.GSUtilDownloadFile(file_url, zip_file_name)
208
209
210 def upload_build(zip_file, context):
211 """Uploads a single build in zip_file to the repackage_gs_url in context."""
212 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
213 upload_url = gs_base_url + '/'
214 bisect_repackage_utils.GSUtilCopy(zip_file, upload_url)
215
216
217 def download_revision_map(context):
218 """Downloads the revision map in original_gs_url in context."""
219 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
220 download_url = gs_base_url + '/' + context.revision_file
221 bisect_repackage_utils.GSUtilDownloadFile(download_url,
222 context.revision_file)
223
224
225 def get_revision_map(context):
226 """Downloads and returns the revision map in repackage_gs_url in context."""
227 bisect_repackage_utils.RemoveFile(context.revision_file)
228 download_revision_map(context)
229 with open(context.revision_file, 'r') as revision_file:
230 revision_map = json.load(revision_file)
231 bisect_repackage_utils.RemoveFile(context.revision_file)
232 return revision_map
233
234
235 def upload_revision_map(revision_map, context):
236 """Upload the given revision_map to the repackage_gs_url in context."""
237 with open(context.revision_file, 'w') as revision_file:
238 json.dump(revision_map, revision_file)
239 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
240 upload_url = gs_base_url + '/'
241 bisect_repackage_utils.GSUtilCopy(context.revision_file, upload_url)
242 bisect_repackage_utils.RemoveFile(context.revision_file)
243
244
245 def create_upload_revision_map(context):
246 """Creates and uploads a dictionary that maps from GitHash to CP number."""
247 gs_base_url = '%s/%s' %(context.original_gs_url, context.builder_name)
248 hash_list = get_list_of_suffix(gs_base_url, context.file_prefix,
249 bisect_repackage_utils.IsGitCommitHash)
250 cp_num_to_hash_map = create_cp_from_hash_map(hash_list)
251 upload_revision_map(cp_num_to_hash_map, context)
252
253
254 def update_upload_revision_map(context):
255 """Updates and uploads a dictionary that maps from GitHash to CP number."""
256 gs_base_url = '%s/%s' %(context.original_gs_url, context.builder_name)
257 revision_map = get_revision_map(context)
258 hash_list = get_list_of_suffix(gs_base_url, context.file_prefix,
259 bisect_repackage_utils.IsGitCommitHash)
260 hash_list = list(set(hash_list)-set(revision_map.values()))
261 cp_num_to_hash_map = create_cp_from_hash_map(hash_list)
262 merged_dict = dict(cp_num_to_hash_map.items() + revision_map.items())
263 upload_revision_map(merged_dict, context)
264
265
266 def make_lightweight_archive(file_archive, archive_name, files_to_archive,
267 context, staging_dir):
268 """Repackages and strips the archive.
269
270 Repacakges and strips according to CHROME_REQUIRED_FILES and
271 CHROME_STRIP_LIST.
272 """
273 strip_list = CHROME_STRIP_LIST[context.archive]
274 tmp_archive = os.path.join(staging_dir, 'tmp_%s' % archive_name)
275 (zip_file, zip_dir) = bisect_repackage_utils.MakeZip(tmp_archive,
276 archive_name,
277 files_to_archive,
278 file_archive,
279 raise_error=False,
280 strip_files=strip_list)
281 return (zip_file, zip_dir, tmp_archive)
282
283
284 def remove_created_files_and_path(files, paths):
285 """Removes all the files and paths passed in."""
286 for file in files:
287 bisect_repackage_utils.RemoveFile(file)
288 for path in paths:
289 bisect_repackage_utils.RemovePath(path)
290
291
292 def verify_chrome_run(zip_dir):
293 """This function executes chrome executable in zip_dir.
294
295 Currently, it is only supported for Linux Chrome builds.
296 Raises error if the execution fails for any reason.
297 """
298 try:
299 command = [os.path.join(zip_dir, 'chrome')]
300 code = bisect_repackage_utils.RunCommand(command)
301 if code != 0:
302 raise ChromeExecutionError('An error occurred when executing Chrome')
303 except ChromeExecutionError,e:
304 print str(e)
305
306
307 def get_whitelist_files(extracted_folder, archive):
308 """Gets all the files & directories matching whitelisted regex."""
309 whitelist_files = []
310 all_files = os.listdir(extracted_folder)
311 for file in all_files:
312 if re.match(CHROME_WHITELIST_FILES[archive], file):
313 whitelist_files.append(file)
314 return whitelist_files
315
316
317 def repackage_single_revision(revision_map, verify_run, staging_dir,
318 context, cp_num):
319 """Repackages a single Chrome build for manual bisect."""
320 archive_name = '%s_%s' %(context.file_prefix, cp_num)
321 file_archive = os.path.join(staging_dir, archive_name)
322 zip_file_name = '%s.zip' % (file_archive)
323 download_build(cp_num, revision_map, zip_file_name, context)
324 extract_dir = os.path.join(staging_dir, archive_name)
325 bisect_repackage_utils.ExtractZip(zip_file_name, extract_dir)
326 extracted_folder = os.path.join(extract_dir, context.file_prefix)
327 if CHROME_WHITELIST_FILES[context.archive]:
328 whitelist_files = get_whitelist_files(extracted_folder, context.archive)
329 files_to_include = whitelist_files + CHROME_REQUIRED_FILES[context.archive]
330 else:
331 files_to_include = CHROME_REQUIRED_FILES[context.archive]
332 (zip_dir, zip_file, tmp_archive) = make_lightweight_archive(extracted_folder,
333 archive_name,
334 files_to_include,
335 context,
336 staging_dir)
337
338 if verify_run:
339 verify_chrome_run(zip_dir)
340
341 upload_build(zip_file, context)
342 # Removed temporary files created during repackaging process.
343 remove_created_files_and_path([zip_file_name],
344 [zip_dir, extract_dir, tmp_archive])
345
346
347 def repackage_revisions(revisions, revision_map, verify_run, staging_dir,
348 context, quit_event=None, progress_event=None):
349 """Repackages all Chrome builds listed in revisions.
350
351 This function calls 'repackage_single_revision' with multithreading pool.
352 """
353 p = Pool(3)
354 func = partial(repackage_single_revision, revision_map, verify_run,
355 staging_dir, context)
356 p.imap(func, revisions)
357 p.close()
358 p.join()
359
360
361 def get_uploaded_builds(context):
362 """Returns already uploaded revisions in original bucket."""
363 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
364 return get_list_of_suffix(gs_base_url, context.file_prefix,
365 bisect_repackage_utils.IsCommitPosition)
366
367
368 def get_revisions_to_package(revision_map, context):
369 """Returns revisions that need to be repackaged.
370
371 It subtracts revisions that are already packaged from all revisions that
372 need to be packaged. The revisions will be sorted in descending order.
373 """
374 already_packaged = get_uploaded_builds(context)
375 not_already_packaged = list(set(revision_map.keys())-set(already_packaged))
376 revisions_to_package = sorted(not_already_packaged, reverse=True)
377 return revisions_to_package
378
379
380 class RepackageJob(object):
381
382 def __init__(self, name, revisions_to_package, revision_map, verify_run,
383 staging_dir, context):
384 super(RepackageJob, self).__init__()
385 self.name = name
386 self.revisions_to_package = revisions_to_package
387 self.revision_map = revision_map
388 self.verify_run = verify_run
389 self.staging_dir = staging_dir
390 self.context = context
391 self.quit_event = threading.Event()
392 self.progress_event = threading.Event()
393 self.thread = None
394
395 def Start(self):
396 """Starts the download."""
397 fetchargs = (self.revisions_to_package,
398 self.revision_map,
399 self.verify_run,
400 self.staging_dir,
401 self.context,
402 self.quit_event,
403 self.progress_event)
404 self.thread = threading.Thread(target=repackage_revisions,
405 name=self.name,
406 args=fetchargs)
407 self.thread.start()
408
409 def Stop(self):
410 """Stops the download which must have been started previously."""
411 assert self.thread, 'DownloadJob must be started before Stop is called.'
412 self.quit_event.set()
413 self.thread.join()
414
415 def WaitFor(self):
416 """Prints a message and waits for the download to complete."""
417 assert self.thread, 'DownloadJob must be started before WaitFor is called.'
418 self.progress_event.set() # Display progress of download. def Stop(self):
419 assert self.thread, 'DownloadJob must be started before Stop is called.'
420 self.quit_event.set()
421 self.thread.join()
422
423
424 def main(argv):
425 option_parser = optparse.OptionParser()
426
427 choices = ['mac', 'win32', 'win64', 'linux']
428
429 option_parser.add_option('-a', '--archive',
430 choices=choices,
431 help='Builders to repacakge from [%s].' %
432 '|'.join(choices))
433
434 # Verifies that the chrome executable runs
435 option_parser.add_option('-v', '--verify',
436 action='store_true',
437 help='Verifies that the Chrome executes normally'
438 'without errors')
439
440 # This option will update the revision map.
441 option_parser.add_option('-u', '--update',
442 action='store_true',
443 help='Updates the list of revisions to repackage')
444
445 # This option will creates the revision map.
446 option_parser.add_option('-c', '--create',
447 action='store_true',
448 help='Creates the list of revisions to repackage')
449
450 # Original bucket that contains perf builds
451 option_parser.add_option('-o', '--original',
452 type='str',
453 help='Google storage url containing original'
454 'Chrome builds')
455
456 # Bucket that should archive lightweight perf builds
457 option_parser.add_option('-r', '--repackage',
458 type='str',
459 help='Google storage url to re-archive Chrome'
460 'builds')
461
462 verify_run = False
463 (opts, args) = option_parser.parse_args()
464 if opts.archive is None:
465 print 'Error: missing required parameter: --archive'
466 option_parser.print_help()
467 return 1
468 if not opts.original or not opts.repackage:
469 raise ValueError('Need to specify original gs bucket url and'
470 'repackage gs bucket url')
471 context = PathContext(opts.original, opts.repackage, opts.archive)
472
473 if opts.create:
474 create_upload_revision_map(context)
475
476 if opts.update:
477 update_upload_revision_map(context)
478
479 if opts.verify:
480 verify_run = True
481
482 revision_map = get_revision_map(context)
483 backward_rev = get_revisions_to_package(revision_map, context)
484 base_dir = os.path.join('.', context.archive)
485 # Clears any uncleared staging directories and create one
486 bisect_repackage_utils.RemovePath(base_dir)
487 bisect_repackage_utils.MaybeMakeDirectory(base_dir)
488 staging_dir = os.path.abspath(tempfile.mkdtemp(prefix='staging',
489 dir=base_dir))
490 repackage = RepackageJob('backward_fetch', backward_rev, revision_map,
491 verify_run, staging_dir, context)
492 # Multi-threading is not currently being used. But it can be used in
493 # cases when the repackaging needs to be quicker.
494 try:
495 repackage.Start()
496 repackage.WaitFor()
497 except (KeyboardInterrupt, SystemExit):
498 print 'Cleaning up...'
499 bisect_repackage_utils.RemovePath(staging_dir)
500 print 'Cleaning up...'
501 bisect_repackage_utils.RemovePath(staging_dir)
502
503
504 if '__main__' == __name__:
505 sys.exit(main(sys.argv))
OLDNEW
« no previous file with comments | « no previous file | tools/bisect_repackage/bisect_repackage_utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698