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

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 style issues 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 # ^$ meanse not to include any files from whitelist
prasadv 2016/09/09 21:34:01 nit: s/meanse/means
miimnk 2016/09/09 23:13:10 Done.
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
115 class PathContext(object):
116 """Stores information to repackage from a bucket to another.
117
118 A PathContext is used to carry the information used to construct URLs and
119 paths when dealing with the storage server and archives.
120 """
121
122 def __init__(self, original_gs_url, repackage_gs_url,
123 archive, revision_file=REVISION_MAP_FILE):
124 super(PathContext, self).__init__()
125 self.original_gs_url = original_gs_url
126 self.repackage_gs_url = repackage_gs_url
127 self.archive = archive
128 self.builder_name = BUILDER_NAME[archive]
129 self.file_prefix = ARCHIVE_PREFIX[archive]
130 self.revision_file = revision_file
131
132
133 def get_cp_from_hash(git_hash):
134 """Converts a GitHash to Commit position number."""
prasadv 2016/09/09 21:34:02 s/GitHash/git hash s/Commit/commit
miimnk 2016/09/09 23:13:11 Done.
135 json_url = CHROMIUM_GITHASH_TO_SVN_URL % git_hash
136 response = urllib.urlopen(json_url)
137 if response.getcode() == 200:
138 try:
139 data = json.loads(response.read())
140 except ValueError:
prasadv 2016/09/09 21:34:01 May be you should display the actual error in the
miimnk 2016/09/09 23:13:10 Changed to: except Exception,e: logging
141 logging.warning('ValueError for JSON URL: %s' % json_url)
142 raise ValueError('Failed to extract commit position')
143 else:
144 logging.warning('ValueError for JSON URL: %s' % json_url)
145 raise ValueError('Failed to extract commit position')
prasadv 2016/09/09 21:34:02 Can we display the the actual response code and th
miimnk 2016/09/09 23:13:10 Changed to: except Exception,e: logging
146 if 'number' in data:
147 return data['number']
148 logging.warning('Failed to get svn revision number for %s' % git_hash)
149 raise ValueError('Failed to extract commit position')
150
151
152 def create_cp_from_hash_map(hash_list):
153 """Returns dict used for conversion of hash list.
154
155 Creates a dictionary that maps from Commit position number
156 to corresponding GitHash.
157 """
158 hash_map = {}
159 count = 0
160 for git_hash in hash_list:
161 try:
162 cp_num = get_cp_from_hash(git_hash)
163 hash_map[cp_num] = git_hash
164 print 'Downloaded %s / %s git hash' %(count, len(hash_list))
prasadv 2016/09/09 21:34:01 Is there any specific reason for printing this?
miimnk 2016/09/09 23:13:10 Removed
165 count += 1
166 except ValueError:
167 pass
168 return hash_map
169
170
171 def get_list_of_suffix(bucket_address, prefix, filter_function):
172 """Gets the list of suffixes in files in a google storage bucket.
173
174 Example: a google storage bucket containing one file
175 'full-build-linux_20983' will return ['20983'] if prefix is
176 provided as 'full-build-linux'. Google Storage bucket
177 containing multiple files will return multiple suffixes.
178
179 Args:
180 bucket_address(String): Bucket URL to examine files from.
181 prefix(String): The prefix used in creating build file names
182 filter_function: A function that returns true if the extracted
183 suffix is in correct format and false otherwise. It allows
184 only proper suffix to be extracted and returned.
185
186 Returns:
187 (List) list of proper suffixes in the bucket.
188 """
189 file_list = bisect_repackage_utils.GSutilList(bucket_address)
190 suffix_list = []
191 extract_suffix = '.*?%s_(.*?)\.zip' %(prefix)
192 for file in file_list:
193 match = re.match(extract_suffix, file)
194 if match and filter_function(match.groups()[0]):
195 suffix_list.append(match.groups()[0])
196 return suffix_list
197
198
199 def download_build(cp_num, revision_map, zip_file_name, context):
200 """Download a single build corresponding to the cp_num and context."""
201 file_url = '%s/%s/%s_%s.zip' %(context.original_gs_url, context.builder_name,
202 context.file_prefix, revision_map[cp_num])
203 bisect_repackage_utils.GSUtilDownloadFile(file_url, zip_file_name)
204
205
206 def upload_build(zip_file, context):
207 """Uploads a single build in zip_file to the repackage_gs_url in context."""
208 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
209 upload_url = gs_base_url + '/'
210 bisect_repackage_utils.GSUtilCopy(zip_file, upload_url)
211
212
213 def download_revision_map(context):
214 """Downloads the revision map in original_gs_url in context."""
215 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
216 download_url = gs_base_url + '/' + context.revision_file
217 bisect_repackage_utils.GSUtilDownloadFile(download_url,
218 context.revision_file)
219
220
221 def get_revision_map(context):
222 """Downloads and returns the revision map in repackage_gs_url in context."""
223 bisect_repackage_utils.RemoveFile(context.revision_file)
224 download_revision_map(context)
225 with open(context.revision_file, 'r') as revision_file:
226 revision_map = json.load(revision_file)
227 bisect_repackage_utils.RemoveFile(context.revision_file)
228 return revision_map
229
230
231 def upload_revision_map(revision_map, context):
232 """Upload the given revision_map to the repackage_gs_url in context."""
233 with open(context.revision_file, 'w') as revision_file:
234 json.dump(revision_map, revision_file)
235 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
236 upload_url = gs_base_url + '/'
237 bisect_repackage_utils.GSUtilCopy(context.revision_file, upload_url)
238 bisect_repackage_utils.RemoveFile(context.revision_file)
239
240
241 def create_upload_revision_map(context):
242 """Creates and uploads a dictionary that maps from GitHash to CP number."""
243 gs_base_url = '%s/%s' %(context.original_gs_url, context.builder_name)
244 hash_list = get_list_of_suffix(gs_base_url, context.file_prefix,
245 bisect_repackage_utils.IsGitCommitHash)
246 cp_num_to_hash_map = create_cp_from_hash_map(hash_list)
247 upload_revision_map(cp_num_to_hash_map, context)
248
249
250 def update_upload_revision_map(context):
251 """Updates and uploads a dictionary that maps from GitHash to CP number."""
252 gs_base_url = '%s/%s' %(context.original_gs_url, context.builder_name)
253 revision_map = get_revision_map(context)
254 hash_list = get_list_of_suffix(gs_base_url, context.file_prefix,
255 bisect_repackage_utils.IsGitCommitHash)
256 hash_list = list(set(hash_list)-set(revision_map.values()))
257 cp_num_to_hash_map = create_cp_from_hash_map(hash_list)
258 merged_dict = dict(cp_num_to_hash_map.items() + revision_map.items())
259 upload_revision_map(merged_dict, context)
260
261
262 def make_lightweight_archive(file_archive, archive_name, files_to_archive,
263 context, staging_dir):
264 """Repackages and strips the archive.
265
266 Repacakges and strips according to CHROME_REQUIRED_FILES and
267 CHROME_STRIP_LIST.
268 """
269 strip_list = CHROME_STRIP_LIST[context.archive]
270 tmp_archive = os.path.join(staging_dir, 'tmp_%s' % archive_name)
271 (zip_file, zip_dir) = bisect_repackage_utils.MakeZip(tmp_archive,
272 archive_name,
273 files_to_archive,
274 file_archive,
275 raise_error=False,
276 strip_files=strip_list)
277 return (zip_file, zip_dir, tmp_archive)
278
279
280 def remove_created_files_and_path(files, paths):
281 """Removes all the files and paths passed in."""
282 for file in files:
283 bisect_repackage_utils.RemoveFile(file)
284 for path in paths:
285 bisect_repackage_utils.RemovePath(path)
286
287
288 def verify_chrome_run(zip_dir):
289 """This function executes chrome executable in zip_dir.
290
291 Currently, it is only supported for Linux Chrome builds.
292 Raises error if the execution fails for any reason.
293 """
294 try:
295 command = [os.path.join(zip_dir, 'chrome')]
296 code = bisect_repackage_utils.RunCommand(command)
297 if code != 0:
298 raise ValueError('Executing Chrome Failed: Need to check ')
prasadv 2016/09/09 21:34:02 I think we should define a userdefine exception an
miimnk 2016/09/09 23:13:10 Changed to try: command = [os.path.join(zi
299 except Exception, e:
300 raise ValueError('Executing Chrome Failed: Need to check ')
301
302
303 def get_whitelist_files(extracted_folder, archive):
304 """Gets all the files & directories matching whitelisted regex."""
305 whitelist_files = []
306 all_files = os.listdir(extracted_folder)
307 for file in all_files:
308 if re.match(CHROME_WHITELIST_FILES[archive], file):
309 whitelist_files.append(file)
310 return whitelist_files
311
312
313 def repackage_single_revision(revision_map, verify_run, staging_dir,
314 context, cp_num):
315 """Repackages a single Chrome build for manual bisect."""
316 archive_name = '%s_%s' %(context.file_prefix, cp_num)
317 file_archive = os.path.join(staging_dir, archive_name)
318 zip_file_name = '%s.zip' % (file_archive)
319 download_build(cp_num, revision_map, zip_file_name, context)
320 extract_dir = os.path.join(staging_dir, archive_name)
321 bisect_repackage_utils.ExtractZip(zip_file_name, extract_dir)
322 extracted_folder = os.path.join(extract_dir, context.file_prefix)
323 if CHROME_WHITELIST_FILES[context.archive]:
324 whitelist_files = get_whitelist_files(extracted_folder, context.archive)
325 files_to_include = whitelist_files + CHROME_REQUIRED_FILES[context.archive]
326 else:
327 files_to_include = CHROME_REQUIRED_FILES[context.archive]
328 (zip_dir, zip_file, tmp_archive) = make_lightweight_archive(extracted_folder,
329 archive_name,
330 files_to_include,
331 context,
332 staging_dir)
333
334 if verify_run:
335 verify_chrome_run(zip_dir)
336
337 upload_build(zip_file, context)
338 # Removed temporary files created during repackaging process.
339 remove_created_files_and_path([zip_file_name],
340 [zip_dir, extract_dir, tmp_archive])
341
342
343 def repackage_revisions(revisions, revision_map, verify_run, staging_dir,
344 context, quit_event=None, progress_event=None):
345 """Repackages all Chrome builds listed in revisions.
346
347 This function calls 'repackage_single_revision' with multithreading pool.
348 """
349 p = Pool(3)
350 func = partial(repackage_single_revision, revision_map, verify_run,
351 staging_dir, context)
352 p.imap(func, revisions)
353 p.close()
354 p.join()
355
356
357 def get_uploaded_builds(context):
358 """Returns already uploaded revisions in original bucket."""
359 gs_base_url = '%s/%s' %(context.repackage_gs_url, context.builder_name)
360 return get_list_of_suffix(gs_base_url, context.file_prefix,
361 bisect_repackage_utils.IsCommitPosition)
362
363
364 def get_revisions_to_package(revision_map, context):
365 """Returns revisions that need to be repackaged.
366
367 It subtracts revisions that are already packaged from all revisions that
368 need to be packaged. The revisions will be sorted in descending order.
369 """
370 already_packaged = get_uploaded_builds(context)
371 not_already_packaged = list(set(revision_map.keys())-set(already_packaged))
372 revisions_to_package = sorted(not_already_packaged, reverse=True)
373 return revisions_to_package
374
375
376 class RepackageJob(object):
377
378 def __init__(self, name, revisions_to_package, revision_map, verify_run,
379 staging_dir, context):
380 super(RepackageJob, self).__init__()
381 self.name = name
382 self.revisions_to_package = revisions_to_package
383 self.revision_map = revision_map
384 self.verify_run = verify_run
385 self.staging_dir = staging_dir
386 self.context = context
387 self.quit_event = threading.Event()
388 self.progress_event = threading.Event()
389 self.thread = None
390
391 def Start(self):
392 """Starts the download."""
393 fetchargs = (self.revisions_to_package,
394 self.revision_map,
395 self.verify_run,
396 self.staging_dir,
397 self.context,
398 self.quit_event,
399 self.progress_event)
400 self.thread = threading.Thread(target=repackage_revisions,
401 name=self.name,
402 args=fetchargs)
403 self.thread.start()
404
405 def Stop(self):
406 """Stops the download which must have been started previously."""
407 assert self.thread, 'DownloadJob must be started before Stop is called.'
408 self.quit_event.set()
409 self.thread.join()
410
411 def WaitFor(self):
412 """Prints a message and waits for the download to complete."""
413 assert self.thread, 'DownloadJob must be started before WaitFor is called.'
414 self.progress_event.set() # Display progress of download. def Stop(self):
415 assert self.thread, 'DownloadJob must be started before Stop is called.'
416 self.quit_event.set()
417 self.thread.join()
418
419
420 def main(argv):
421 option_parser = optparse.OptionParser()
422
423 choices = ['mac', 'win32', 'win64', 'linux']
424
425 option_parser.add_option('-a', '--archive',
426 choices=choices,
427 help='Builders to repacakge from [%s].' %
428 '|'.join(choices))
429
430 # Verifies that the chrome executable runs
431 option_parser.add_option('-v', '--verify',
432 action='store_true',
433 help='Verifies that the Chrome executes normally'
434 'without errors')
435
436 # This option will update the revision map.
437 option_parser.add_option('-u', '--update',
438 action='store_true',
439 help='Updates the list of revisions to repackage')
440
441 # This option will creates the revision map.
442 option_parser.add_option('-c', '--create',
443 action='store_true',
444 help='Creates the list of revisions to repackage')
445
446 # Original bucket that contains perf builds
447 option_parser.add_option('-o', '--original',
448 type='str',
449 help='Google storage url containing original'
450 'Chrome builds')
451
452 # Bucket that should archive lightweight perf builds
453 option_parser.add_option('-r', '--repackage',
454 type='str',
455 help='Google storage url to re-archive Chrome'
456 'builds')
457
458 verify_run = False
459 (opts, args) = option_parser.parse_args()
460 if opts.archive is None:
461 print 'Error: missing required parameter: --archive'
462 option_parser.print_help()
463 return 1
464 if not opts.original or not opts.repackage:
465 raise ValueError('Need to specify original gs bucket url and'
466 'repackage gs bucket url')
467 context = PathContext(opts.original, opts.repackage, opts.archive)
468
469 if opts.create:
470 create_upload_revision_map(context)
471
472 if opts.update:
473 update_upload_revision_map(context)
474
475 if opts.verify:
476 verify_run = True
477
478 revision_map = get_revision_map(context)
479 backward_rev = get_revisions_to_package(revision_map, context)
480 base_dir = os.path.join('.', context.archive)
481 # Clears any uncleared staging directories and create one
482 bisect_repackage_utils.RemovePath(base_dir)
483 bisect_repackage_utils.MaybeMakeDirectory(base_dir)
484 staging_dir = os.path.abspath(tempfile.mkdtemp(prefix='staging',
485 dir=base_dir))
486 repackage = RepackageJob('backward_fetch', backward_rev, revision_map,
487 verify_run, staging_dir, context)
488 # Multi-threading is not currently being used. But it can be used in
489 # cases when the repackaging needs to be quicker.
490 try:
491 # Only run backward fetch
492 repackage.Start()
493 repackage.WaitFor()
494 except (KeyboardInterrupt, SystemExit):
495 print 'Cleaning up...'
496 bisect_repackage_utils.RemovePath(staging_dir)
497 print 'Cleaning up...'
498 bisect_repackage_utils.RemovePath(staging_dir)
499
500
501 if '__main__' == __name__:
502 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