1#!/usr/bin/env python 2# Copyright (c) 2012 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Archives or replays webpages and creates SKPs in a Google Storage location. 7 8To archive webpages and store SKP files (archives should be rarely updated): 9 10cd skia 11python tools/skp/webpages_playback.py --data_store=gs://rmistry --record \ 12--page_sets=all --skia_tools=/home/default/trunk/out/Debug/ \ 13--browser_executable=/tmp/chromium/out/Release/chrome 14 15The above command uses Google Storage bucket 'rmistry' to download needed files. 16 17To replay archived webpages and re-generate SKP files (should be run whenever 18SkPicture.PICTURE_VERSION changes): 19 20cd skia 21python tools/skp/webpages_playback.py --data_store=gs://rmistry \ 22--page_sets=all --skia_tools=/home/default/trunk/out/Debug/ \ 23--browser_executable=/tmp/chromium/out/Release/chrome 24 25 26Specify the --page_sets flag (default value is 'all') to pick a list of which 27webpages should be archived and/or replayed. Eg: 28 29--page_sets=tools/skp/page_sets/skia_yahooanswers_desktop.py,\ 30tools/skp/page_sets/skia_googlecalendar_nexus10.py 31 32The --browser_executable flag should point to the browser binary you want to use 33to capture archives and/or capture SKP files. Majority of the time it should be 34a newly built chrome binary. 35 36The --data_store flag controls where the needed artifacts, such as 37credential files, are downloaded from. It also controls where the 38generated artifacts, such as recorded webpages and resulting skp renderings, 39are uploaded to. URLs with scheme 'gs://' use Google Storage. Otherwise 40use local filesystem. 41 42The --upload=True flag means generated artifacts will be 43uploaded or copied to the location specified by --data_store. (default value is 44False if not specified). 45 46The --non-interactive flag controls whether the script will prompt the user 47(default value is False if not specified). 48 49The --skia_tools flag if specified will allow this script to run 50debugger, render_pictures, and render_pdfs on the captured 51SKP(s). The tools are run after all SKPs are succesfully captured to make sure 52they can be added to the buildbots with no breakages. 53""" 54 55import glob 56import optparse 57import os 58import posixpath 59import shutil 60import subprocess 61import sys 62import tempfile 63import time 64import traceback 65 66sys.path.insert(0, os.getcwd()) 67 68from common.py.utils import gs_utils 69from common.py.utils import shell_utils 70 71ROOT_PLAYBACK_DIR_NAME = 'playback' 72SKPICTURES_DIR_NAME = 'skps' 73 74PARTNERS_GS_BUCKET = 'gs://chrome-partner-telemetry' 75 76# Local archive and SKP directories. 77LOCAL_PLAYBACK_ROOT_DIR = os.path.join( 78 tempfile.gettempdir(), ROOT_PLAYBACK_DIR_NAME) 79LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR = os.path.join( 80 os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data') 81TMP_SKP_DIR = tempfile.mkdtemp() 82 83# Location of the credentials.json file and the string that represents missing 84# passwords. 85CREDENTIALS_FILE_PATH = os.path.join( 86 os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data', 87 'credentials.json' 88) 89 90# Name of the SKP benchmark 91SKP_BENCHMARK = 'skpicture_printer' 92 93# The max base name length of Skp files. 94MAX_SKP_BASE_NAME_LEN = 31 95 96# Dictionary of device to platform prefixes for SKP files. 97DEVICE_TO_PLATFORM_PREFIX = { 98 'desktop': 'desk', 99 'galaxynexus': 'mobi', 100 'nexus10': 'tabl' 101} 102 103# How many times the record_wpr binary should be retried. 104RETRY_RECORD_WPR_COUNT = 5 105# How many times the run_benchmark binary should be retried. 106RETRY_RUN_MEASUREMENT_COUNT = 5 107 108# Location of the credentials.json file in Google Storage. 109CREDENTIALS_GS_PATH = '/playback/credentials/credentials.json' 110 111X11_DISPLAY = os.getenv('DISPLAY', ':0') 112 113GS_PREDEFINED_ACL = gs_utils.GSUtils.PredefinedACL.PRIVATE 114GS_FINE_GRAINED_ACL_LIST = [ 115 (gs_utils.GSUtils.IdType.GROUP_BY_DOMAIN, 'google.com', 116 gs_utils.GSUtils.Permission.READ), 117] 118 119# Path to Chromium's page sets. 120CHROMIUM_PAGE_SETS_PATH = os.path.join('tools', 'perf', 'page_sets') 121 122# Dictionary of supported Chromium page sets to their file prefixes. 123CHROMIUM_PAGE_SETS_TO_PREFIX = { 124 'key_mobile_sites_smooth.py': 'keymobi', 125 'top_25_smooth.py': 'top25desk', 126} 127 128 129def remove_prefix(s, prefix): 130 if s.startswith(prefix): 131 return s[len(prefix):] 132 return s 133 134 135class SkPicturePlayback(object): 136 """Class that archives or replays webpages and creates SKPs.""" 137 138 def __init__(self, parse_options): 139 """Constructs a SkPicturePlayback BuildStep instance.""" 140 assert parse_options.browser_executable, 'Must specify --browser_executable' 141 self._browser_executable = parse_options.browser_executable 142 self._browser_args = '--disable-setuid-sandbox' 143 if parse_options.browser_extra_args: 144 self._browser_args = '%s %s' % ( 145 self._browser_args, parse_options.browser_extra_args) 146 147 self._chrome_page_sets_path = os.path.join(parse_options.chrome_src_path, 148 CHROMIUM_PAGE_SETS_PATH) 149 self._all_page_sets_specified = parse_options.page_sets == 'all' 150 self._page_sets = self._ParsePageSets(parse_options.page_sets) 151 152 self._record = parse_options.record 153 self._skia_tools = parse_options.skia_tools 154 self._non_interactive = parse_options.non_interactive 155 self._upload = parse_options.upload 156 self._skp_prefix = parse_options.skp_prefix 157 data_store_location = parse_options.data_store 158 if data_store_location.startswith(gs_utils.GS_PREFIX): 159 self.gs = GoogleStorageDataStore(data_store_location) 160 else: 161 self.gs = LocalFileSystemDataStore(data_store_location) 162 self._upload_to_partner_bucket = parse_options.upload_to_partner_bucket 163 self._alternate_upload_dir = parse_options.alternate_upload_dir 164 self._telemetry_binaries_dir = os.path.join(parse_options.chrome_src_path, 165 'tools', 'perf') 166 167 self._local_skp_dir = os.path.join( 168 parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, SKPICTURES_DIR_NAME) 169 self._local_record_webpages_archive_dir = os.path.join( 170 parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, 'webpages_archive') 171 172 # List of SKP files generated by this script. 173 self._skp_files = [] 174 175 def _ParsePageSets(self, page_sets): 176 if not page_sets: 177 raise ValueError('Must specify at least one page_set!') 178 elif self._all_page_sets_specified: 179 # Get everything from the page_sets directory. 180 page_sets_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 181 'page_sets') 182 ps = [os.path.join(page_sets_dir, page_set) 183 for page_set in os.listdir(page_sets_dir) 184 if not os.path.isdir(os.path.join(page_sets_dir, page_set)) and 185 page_set.endswith('.py')] 186 chromium_ps = [ 187 os.path.join(self._chrome_page_sets_path, cr_page_set) 188 for cr_page_set in CHROMIUM_PAGE_SETS_TO_PREFIX] 189 ps.extend(chromium_ps) 190 elif '*' in page_sets: 191 # Explode and return the glob. 192 ps = glob.glob(page_sets) 193 else: 194 ps = page_sets.split(',') 195 ps.sort() 196 return ps 197 198 def _IsChromiumPageSet(self, page_set): 199 """Returns true if the specified page set is a Chromium page set.""" 200 return page_set.startswith(self._chrome_page_sets_path) 201 202 def Run(self): 203 """Run the SkPicturePlayback BuildStep.""" 204 205 # Download the credentials file if it was not previously downloaded. 206 if not os.path.isfile(CREDENTIALS_FILE_PATH): 207 # Download the credentials.json file from Google Storage. 208 self.gs.download_file(CREDENTIALS_GS_PATH, CREDENTIALS_FILE_PATH) 209 210 if not os.path.isfile(CREDENTIALS_FILE_PATH): 211 print """\n\nCould not locate credentials file in the storage. 212 Please create a %s file that contains: 213 { 214 "google": { 215 "username": "google_testing_account_username", 216 "password": "google_testing_account_password" 217 }, 218 "facebook": { 219 "username": "facebook_testing_account_username", 220 "password": "facebook_testing_account_password" 221 } 222 }\n\n""" % CREDENTIALS_FILE_PATH 223 raw_input("Please press a key when you are ready to proceed...") 224 225 # Delete any left over data files in the data directory. 226 for archive_file in glob.glob( 227 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, 'skia_*')): 228 os.remove(archive_file) 229 230 # Delete the local root directory if it already exists. 231 if os.path.exists(LOCAL_PLAYBACK_ROOT_DIR): 232 shutil.rmtree(LOCAL_PLAYBACK_ROOT_DIR) 233 234 # Create the required local storage directories. 235 self._CreateLocalStorageDirs() 236 237 # Start the timer. 238 start_time = time.time() 239 240 # Loop through all page_sets. 241 for page_set in self._page_sets: 242 243 page_set_basename = os.path.basename(page_set).split('.')[0] 244 page_set_json_name = page_set_basename + '.json' 245 wpr_data_file = page_set.split(os.path.sep)[-1].split('.')[0] + '_000.wpr' 246 page_set_dir = os.path.dirname(page_set) 247 248 if self._IsChromiumPageSet(page_set): 249 print 'Using Chromium\'s captured archives for Chromium\'s page sets.' 250 elif self._record: 251 # Create an archive of the specified webpages if '--record=True' is 252 # specified. 253 record_wpr_cmd = ( 254 'PYTHONPATH=%s:$PYTHONPATH' % page_set_dir, 255 'DISPLAY=%s' % X11_DISPLAY, 256 os.path.join(self._telemetry_binaries_dir, 'record_wpr'), 257 '--extra-browser-args="%s"' % self._browser_args, 258 '--browser=exact', 259 '--browser-executable=%s' % self._browser_executable, 260 '%s_page_set' % page_set_basename, 261 '--page-set-base-dir=%s' % page_set_dir 262 ) 263 for _ in range(RETRY_RECORD_WPR_COUNT): 264 try: 265 shell_utils.run(' '.join(record_wpr_cmd), shell=True) 266 267 # Move over the created archive into the local webpages archive 268 # directory. 269 shutil.move( 270 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file), 271 self._local_record_webpages_archive_dir) 272 shutil.move( 273 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, 274 page_set_json_name), 275 self._local_record_webpages_archive_dir) 276 277 # Break out of the retry loop since there were no errors. 278 break 279 except Exception: 280 # There was a failure continue with the loop. 281 traceback.print_exc() 282 else: 283 # If we get here then record_wpr did not succeed and thus did not 284 # break out of the loop. 285 raise Exception('record_wpr failed for page_set: %s' % page_set) 286 287 else: 288 # Get the webpages archive so that it can be replayed. 289 self._DownloadWebpagesArchive(wpr_data_file, page_set_json_name) 290 291 run_benchmark_cmd = ( 292 'PYTHONPATH=%s:$PYTHONPATH' % page_set_dir, 293 'DISPLAY=%s' % X11_DISPLAY, 294 'timeout', '300', 295 os.path.join(self._telemetry_binaries_dir, 'run_benchmark'), 296 '--extra-browser-args="%s"' % self._browser_args, 297 '--browser=exact', 298 '--browser-executable=%s' % self._browser_executable, 299 SKP_BENCHMARK, 300 '--page-set-name=%s' % page_set_basename, 301 '--page-set-base-dir=%s' % page_set_dir, 302 '--skp-outdir=%s' % TMP_SKP_DIR, 303 '--also-run-disabled-tests' 304 ) 305 306 for _ in range(RETRY_RUN_MEASUREMENT_COUNT): 307 try: 308 print '\n\n=======Capturing SKP of %s=======\n\n' % page_set 309 shell_utils.run(' '.join(run_benchmark_cmd), shell=True) 310 except shell_utils.CommandFailedException: 311 # skpicture_printer sometimes fails with AssertionError but the 312 # captured SKP is still valid. This is a known issue. 313 pass 314 315 # Rename generated SKP files into more descriptive names. 316 try: 317 self._RenameSkpFiles(page_set) 318 # Break out of the retry loop since there were no errors. 319 break 320 except Exception: 321 # There was a failure continue with the loop. 322 traceback.print_exc() 323 print '\n\n=======Retrying %s=======\n\n' % page_set 324 time.sleep(10) 325 else: 326 # If we get here then run_benchmark did not succeed and thus did not 327 # break out of the loop. 328 raise Exception('run_benchmark failed for page_set: %s' % page_set) 329 330 print '\n\n=======Capturing SKP files took %s seconds=======\n\n' % ( 331 time.time() - start_time) 332 333 if self._skia_tools: 334 render_pictures_cmd = [ 335 os.path.join(self._skia_tools, 'render_pictures'), 336 '-r', self._local_skp_dir 337 ] 338 render_pdfs_cmd = [ 339 os.path.join(self._skia_tools, 'render_pdfs'), 340 '-r', self._local_skp_dir 341 ] 342 343 for tools_cmd in (render_pictures_cmd, render_pdfs_cmd): 344 print '\n\n=======Running %s=======' % ' '.join(tools_cmd) 345 proc = subprocess.Popen(tools_cmd) 346 (code, _) = shell_utils.log_process_after_completion(proc, echo=False) 347 if code != 0: 348 raise Exception('%s failed!' % ' '.join(tools_cmd)) 349 350 if not self._non_interactive: 351 print '\n\n=======Running debugger=======' 352 os.system('%s %s' % (os.path.join(self._skia_tools, 'debugger'), 353 self._local_skp_dir)) 354 355 print '\n\n' 356 357 if self._upload: 358 print '\n\n=======Uploading to %s=======\n\n' % self.gs.target_type() 359 # Copy the directory structure in the root directory into Google Storage. 360 dest_dir_name = ROOT_PLAYBACK_DIR_NAME 361 if self._alternate_upload_dir: 362 dest_dir_name = self._alternate_upload_dir 363 364 self.gs.upload_dir_contents( 365 LOCAL_PLAYBACK_ROOT_DIR, dest_dir=dest_dir_name, 366 upload_if=gs_utils.GSUtils.UploadIf.IF_MODIFIED, 367 predefined_acl=GS_PREDEFINED_ACL, 368 fine_grained_acl_list=GS_FINE_GRAINED_ACL_LIST) 369 370 print '\n\n=======New SKPs have been uploaded to %s =======\n\n' % ( 371 posixpath.join(self.gs.target_name(), dest_dir_name, 372 SKPICTURES_DIR_NAME)) 373 374 if self._upload_to_partner_bucket: 375 print '\n\n=======Uploading to Partner bucket %s =======\n\n' % ( 376 PARTNERS_GS_BUCKET) 377 partner_gs = GoogleStorageDataStore(PARTNERS_GS_BUCKET) 378 partner_gs.delete_path(SKPICTURES_DIR_NAME) 379 partner_gs.upload_dir_contents( 380 os.path.join(LOCAL_PLAYBACK_ROOT_DIR, SKPICTURES_DIR_NAME), 381 dest_dir=SKPICTURES_DIR_NAME, 382 upload_if=gs_utils.GSUtils.UploadIf.IF_MODIFIED) 383 print '\n\n=======New SKPs have been uploaded to %s =======\n\n' % ( 384 posixpath.join(partner_gs.target_name(), SKPICTURES_DIR_NAME)) 385 else: 386 print '\n\n=======Not Uploading to %s=======\n\n' % self.gs.target_type() 387 print 'Generated resources are available in %s\n\n' % ( 388 LOCAL_PLAYBACK_ROOT_DIR) 389 390 return 0 391 392 def _GetSkiaSkpFileName(self, page_set): 393 """Returns the SKP file name for Skia page sets.""" 394 # /path/to/skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop.py 395 ps_filename = os.path.basename(page_set) 396 # skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop 397 ps_basename, _ = os.path.splitext(ps_filename) 398 # skia_yahooanswers_desktop -> skia, yahooanswers, desktop 399 _, page_name, device = ps_basename.split('_') 400 basename = '%s_%s' % (DEVICE_TO_PLATFORM_PREFIX[device], page_name) 401 return basename[:MAX_SKP_BASE_NAME_LEN] + '.skp' 402 403 def _GetChromiumSkpFileName(self, page_set, site): 404 """Returns the SKP file name for Chromium page sets.""" 405 # /path/to/http___mobile_news_sandbox_pt0 -> http___mobile_news_sandbox_pt0 406 _, webpage = os.path.split(site) 407 # http___mobile_news_sandbox_pt0 -> mobile_news_sandbox_pt0 408 for prefix in ('http___', 'https___', 'www_'): 409 if webpage.startswith(prefix): 410 webpage = webpage[len(prefix):] 411 # /path/to/skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop.py 412 ps_filename = os.path.basename(page_set) 413 # http___mobile_news_sandbox -> pagesetprefix_http___mobile_news_sandbox 414 basename = '%s_%s' % (CHROMIUM_PAGE_SETS_TO_PREFIX[ps_filename], webpage) 415 return basename[:MAX_SKP_BASE_NAME_LEN] + '.skp' 416 417 def _RenameSkpFiles(self, page_set): 418 """Rename generated SKP files into more descriptive names. 419 420 Look into the subdirectory of TMP_SKP_DIR and find the most interesting 421 .skp in there to be this page_set's representative .skp. 422 """ 423 subdirs = glob.glob(os.path.join(TMP_SKP_DIR, '*')) 424 for site in subdirs: 425 if self._IsChromiumPageSet(page_set): 426 filename = self._GetChromiumSkpFileName(page_set, site) 427 else: 428 filename = self._GetSkiaSkpFileName(page_set) 429 filename = filename.lower() 430 431 if self._skp_prefix: 432 filename = '%s%s' % (self._skp_prefix, filename) 433 434 # We choose the largest .skp as the most likely to be interesting. 435 largest_skp = max(glob.glob(os.path.join(site, '*.skp')), 436 key=lambda path: os.stat(path).st_size) 437 dest = os.path.join(self._local_skp_dir, filename) 438 print 'Moving', largest_skp, 'to', dest 439 shutil.move(largest_skp, dest) 440 self._skp_files.append(filename) 441 shutil.rmtree(site) 442 443 def _CreateLocalStorageDirs(self): 444 """Creates required local storage directories for this script.""" 445 for d in (self._local_record_webpages_archive_dir, 446 self._local_skp_dir): 447 if os.path.exists(d): 448 shutil.rmtree(d) 449 os.makedirs(d) 450 451 def _DownloadWebpagesArchive(self, wpr_data_file, page_set_json_name): 452 """Downloads the webpages archive and its required page set from GS.""" 453 wpr_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME, 'webpages_archive', 454 wpr_data_file) 455 page_set_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME, 456 'webpages_archive', 457 page_set_json_name) 458 gs = self.gs 459 if (gs.does_storage_object_exist(wpr_source) and 460 gs.does_storage_object_exist(page_set_source)): 461 gs.download_file(wpr_source, 462 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, 463 wpr_data_file)) 464 gs.download_file(page_set_source, 465 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, 466 page_set_json_name)) 467 else: 468 raise Exception('%s and %s do not exist in %s!' % (gs.target_type(), 469 wpr_source, page_set_source)) 470 471class DataStore: 472 """An abstract base class for uploading recordings to a data storage. 473 The interface emulates the google storage api.""" 474 def target_name(self): 475 raise NotImplementedError() 476 def target_type(self): 477 raise NotImplementedError() 478 def does_storage_object_exist(self, *args): 479 raise NotImplementedError() 480 def download_file(self, *args): 481 raise NotImplementedError() 482 def upload_dir_contents(self, source_dir, **kwargs): 483 raise NotImplementedError() 484 485class GoogleStorageDataStore(DataStore): 486 def __init__(self, data_store_url): 487 self._data_store_url = data_store_url 488 self._bucket = remove_prefix(self._data_store_url.lstrip(), 489 gs_utils.GS_PREFIX) 490 self.gs = gs_utils.GSUtils() 491 def target_name(self): 492 return self._data_store_url 493 def target_type(self): 494 return 'Google Storage' 495 def does_storage_object_exist(self, *args): 496 return self.gs.does_storage_object_exist(self._bucket, *args) 497 def delete_path(self, path): 498 _, files = self.gs.list_bucket_contents(self._bucket, subdir=path) 499 for f in files: 500 self.gs.delete_file(self._bucket, posixpath.join(path, f)) 501 def download_file(self, *args): 502 self.gs.download_file(self._bucket, *args) 503 def upload_dir_contents(self, source_dir, **kwargs): 504 self.gs.upload_dir_contents(source_dir, self._bucket, **kwargs) 505 506class LocalFileSystemDataStore(DataStore): 507 def __init__(self, data_store_location): 508 self._base_dir = data_store_location 509 def target_name(self): 510 return self._base_dir 511 def target_type(self): 512 return self._base_dir 513 def does_storage_object_exist(self, name, *args): 514 return os.path.isfile(os.path.join(self._base_dir, name)) 515 def delete_path(self, path): 516 shutil.rmtree(path) 517 def download_file(self, name, local_path, *args): 518 shutil.copyfile(os.path.join(self._base_dir, name), local_path) 519 def upload_dir_contents(self, source_dir, dest_dir, **kwargs): 520 def copytree(source_dir, dest_dir): 521 if not os.path.exists(dest_dir): 522 os.makedirs(dest_dir) 523 for item in os.listdir(source_dir): 524 source = os.path.join(source_dir, item) 525 dest = os.path.join(dest_dir, item) 526 if os.path.isdir(source): 527 copytree(source, dest) 528 else: 529 shutil.copy2(source, dest) 530 copytree(source_dir, os.path.join(self._base_dir, dest_dir)) 531 532if '__main__' == __name__: 533 option_parser = optparse.OptionParser() 534 option_parser.add_option( 535 '', '--page_sets', 536 help='Specifies the page sets to use to archive. Supports globs.', 537 default='all') 538 option_parser.add_option( 539 '', '--record', action='store_true', 540 help='Specifies whether a new website archive should be created.', 541 default=False) 542 option_parser.add_option( 543 '', '--skia_tools', 544 help=('Path to compiled Skia executable tools. ' 545 'render_pictures/render_pdfs is run on the set ' 546 'after all SKPs are captured. If the script is run without ' 547 '--non-interactive then the debugger is also run at the end. Debug ' 548 'builds are recommended because they seem to catch more failures ' 549 'than Release builds.'), 550 default=None) 551 option_parser.add_option( 552 '', '--upload', action='store_true', 553 help=('Uploads to Google Storage or copies to local filesystem storage ' 554 ' if this is True.'), 555 default=False) 556 option_parser.add_option( 557 '', '--upload_to_partner_bucket', action='store_true', 558 help=('Uploads SKPs to the chrome-partner-telemetry Google Storage ' 559 'bucket if true.'), 560 default=False) 561 option_parser.add_option( 562 '', '--data_store', 563 help=('The location of the file storage to use to download and upload ' 564 'files. Can be \'gs://<bucket>\' for Google Storage, or ' 565 'a directory for local filesystem storage'), 566 default='gs://chromium-skia-gm') 567 option_parser.add_option( 568 '', '--alternate_upload_dir', 569 help= ('Uploads to a different directory in Google Storage or local ' 570 'storage if this flag is specified'), 571 default=None) 572 option_parser.add_option( 573 '', '--output_dir', 574 help=('Temporary directory where SKPs and webpage archives will be ' 575 'outputted to.'), 576 default=tempfile.gettempdir()) 577 option_parser.add_option( 578 '', '--browser_executable', 579 help='The exact browser executable to run.', 580 default=None) 581 option_parser.add_option( 582 '', '--browser_extra_args', 583 help='Additional arguments to pass to the browser.', 584 default=None) 585 option_parser.add_option( 586 '', '--chrome_src_path', 587 help='Path to the chromium src directory.', 588 default=None) 589 option_parser.add_option( 590 '', '--non-interactive', action='store_true', 591 help='Runs the script without any prompts. If this flag is specified and ' 592 '--skia_tools is specified then the debugger is not run.', 593 default=False) 594 option_parser.add_option( 595 '', '--skp_prefix', 596 help='Prefix to add to the names of generated SKPs.', 597 default=None) 598 options, unused_args = option_parser.parse_args() 599 600 playback = SkPicturePlayback(options) 601 sys.exit(playback.Run()) 602