Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
webpages_playback.py
Go to the documentation of this file.
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://your-bucket-name \
12--record --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 'your-bucket-name' to download
16needed files.
17
18To replay archived webpages and re-generate SKP files (should be run whenever
19SkPicture.PICTURE_VERSION changes):
20
21cd skia
22python tools/skp/webpages_playback.py --data_store=gs://your-bucket-name \
23--page_sets=all --skia_tools=/home/default/trunk/out/Debug/ \
24--browser_executable=/tmp/chromium/out/Release/chrome
25
26
27Specify the --page_sets flag (default value is 'all') to pick a list of which
28webpages should be archived and/or replayed. Eg:
29
30--page_sets=tools/skp/page_sets/skia_yahooanswers_desktop.py,\
31tools/skp/page_sets/skia_googlecalendar_nexus10.py
32
33The --browser_executable flag should point to the browser binary you want to use
34to capture archives and/or capture SKP files. Majority of the time it should be
35a newly built chrome binary.
36
37The --data_store flag controls where the needed artifacts are downloaded from.
38It also controls where the generated artifacts, such as recorded webpages and
39resulting skp renderings, are uploaded to. URLs with scheme 'gs://' use Google
40Storage. Otherwise use 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
55
56from __future__ import print_function
57import datetime
58import glob
59import optparse
60import os
61import posixpath
62import shutil
63import subprocess
64import sys
65import tempfile
66import time
67import traceback
68
69
70ROOT_PLAYBACK_DIR_NAME = 'playback'
71SKPICTURES_DIR_NAME = 'skps'
72
73GS_PREFIX = 'gs://'
74
75PARTNERS_GS_BUCKET = 'gs://chrome-partner-telemetry'
76
77# Local archive and SKP directories.
78LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR = os.path.join(
79 os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data')
80TMP_SKP_DIR = tempfile.mkdtemp()
81
82# Location of the credentials.json file and the string that represents missing
83# passwords.
84CREDENTIALS_FILE_PATH = os.path.join(
85 os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data',
86 'credentials.json'
87)
88
89# Name of the SKP benchmark
90SKP_BENCHMARK = 'skpicture_printer'
91
92# The max base name length of Skp files.
93MAX_SKP_BASE_NAME_LEN = 31
94
95# Dictionary of device to platform prefixes for SKP files.
96DEVICE_TO_PLATFORM_PREFIX = {
97 'desktop': 'desk',
98 'mobile': 'mobi',
99 'tablet': 'tabl'
100}
101
102# How many times the record_wpr binary should be retried.
103RETRY_RECORD_WPR_COUNT = 5
104# How many times the run_benchmark binary should be retried.
105RETRY_RUN_MEASUREMENT_COUNT = 3
106
107# Location of the credentials.json file in Google Storage.
108CREDENTIALS_GS_PATH = 'playback/credentials/credentials.json'
109
110X11_DISPLAY = os.getenv('DISPLAY', ':0')
111
112# Path to Chromium's page sets.
113CHROMIUM_PAGE_SETS_PATH = os.path.join('tools', 'perf', 'page_sets')
114
115# Dictionary of supported Chromium page sets to their file prefixes.
116CHROMIUM_PAGE_SETS_TO_PREFIX = {
117}
118
119PAGE_SETS_TO_EXCLUSIONS = {
120 # See skbug.com/7348
121 'key_mobile_sites_smooth.py': '"(digg|worldjournal|twitter|espn)"',
122 # See skbug.com/7421
123 'top_25_smooth.py': '"(mail\.google\.com)"',
124}
125
126
127class InvalidSKPException(Exception):
128 """Raised when the created SKP is invalid."""
129 pass
130
131
132def remove_prefix(s, prefix):
133 if s.startswith(prefix):
134 return s[len(prefix):]
135 return s
136
137
138class SkPicturePlayback(object):
139 """Class that archives or replays webpages and creates SKPs."""
140
141 def __init__(self, parse_options):
142 """Constructs a SkPicturePlayback BuildStep instance."""
143 assert parse_options.browser_executable, 'Must specify --browser_executable'
144 self._browser_executable = parse_options.browser_executable
145 self._browser_args = '--disable-setuid-sandbox'
146 if parse_options.browser_extra_args:
147 self._browser_args = '%s %s' % (
148 self._browser_args, parse_options.browser_extra_args)
149
150 self._chrome_page_sets_path = os.path.join(parse_options.chrome_src_path,
151 CHROMIUM_PAGE_SETS_PATH)
152 self._all_page_sets_specified = parse_options.page_sets == 'all'
153 self._page_sets = self._ParsePageSets(parse_options.page_sets)
154
155 self._record = parse_options.record
156 self._skia_tools = parse_options.skia_tools
157 self._non_interactive = parse_options.non_interactive
158 self._upload = parse_options.upload
159 self._skp_prefix = parse_options.skp_prefix
160 data_store_location = parse_options.data_store
161 if data_store_location.startswith(GS_PREFIX):
162 self.gs = GoogleStorageDataStore(data_store_location)
163 else:
164 self.gs = LocalFileSystemDataStore(data_store_location)
165 self._upload_to_partner_bucket = parse_options.upload_to_partner_bucket
166 self._alternate_upload_dir = parse_options.alternate_upload_dir
167 self._telemetry_binaries_dir = os.path.join(parse_options.chrome_src_path,
168 'tools', 'perf')
169 self._catapult_dir = os.path.join(parse_options.chrome_src_path,
170 'third_party', 'catapult')
171
172 self._local_skp_dir = os.path.join(
173 parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, SKPICTURES_DIR_NAME)
175 parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, 'webpages_archive')
176
177 # List of SKP files generated by this script.
178 self._skp_files = []
179
180 def _ParsePageSets(self, page_sets):
181 if not page_sets:
182 raise ValueError('Must specify at least one page_set!')
183 elif self._all_page_sets_specified:
184 # Get everything from the page_sets directory.
185 page_sets_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
186 'page_sets')
187 ps = [os.path.join(page_sets_dir, page_set)
188 for page_set in os.listdir(page_sets_dir)
189 if not os.path.isdir(os.path.join(page_sets_dir, page_set)) and
190 page_set.endswith('.py')]
191 chromium_ps = [
192 os.path.join(self._chrome_page_sets_path, cr_page_set)
193 for cr_page_set in CHROMIUM_PAGE_SETS_TO_PREFIX]
194 ps.extend(chromium_ps)
195 elif '*' in page_sets:
196 # Explode and return the glob.
197 ps = glob.glob(page_sets)
198 else:
199 ps = page_sets.split(',')
200 ps.sort()
201 return ps
202
203 def _IsChromiumPageSet(self, page_set):
204 """Returns true if the specified page set is a Chromium page set."""
205 return page_set.startswith(self._chrome_page_sets_path)
206
207 def Run(self):
208 """Run the SkPicturePlayback BuildStep."""
209
210 # Download the credentials file if it was not previously downloaded.
211 if not os.path.isfile(CREDENTIALS_FILE_PATH):
212 # Download the credentials.json file from Google Storage.
213 self.gs.download_file(CREDENTIALS_GS_PATH, CREDENTIALS_FILE_PATH)
214
215 if not os.path.isfile(CREDENTIALS_FILE_PATH):
216 raise Exception("""Could not locate credentials file in the storage.
217 Please create a credentials file in gs://%s that contains:
218 {
219 "google": {
220 "username": "google_testing_account_username",
221 "password": "google_testing_account_password"
222 }
223 }\n\n""" % CREDENTIALS_GS_PATH)
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 # Create the required local storage directories.
232
233 # Start the timer.
234 start_time = time.time()
235
236 # Loop through all page_sets.
237 for page_set in self._page_sets:
238 if os.path.basename(page_set) == '__init__.py':
239 continue
240 page_set_basename = os.path.basename(page_set).split('.')[0]
241 page_set_json_name = page_set_basename + '.json'
242 wpr_data_file_glob = (
243 page_set.split(os.path.sep)[-1].split('.')[0] + '_*.wprgo')
244 page_set_dir = os.path.dirname(page_set)
245
246 if self._IsChromiumPageSet(page_set):
247 print('Using Chromium\'s captured archives for Chromium\'s page sets.')
248 elif self._record:
249 # Create an archive of the specified webpages if '--record=True' is
250 # specified.
251 record_wpr_cmd = (
252 'PYTHONPATH=%s:%s:$PYTHONPATH' % (page_set_dir, self._catapult_dir),
253 'DISPLAY=%s' % X11_DISPLAY,
254 os.path.join(self._telemetry_binaries_dir, 'record_wpr'),
255 '--extra-browser-args="%s"' % self._browser_args,
256 '--browser=exact',
257 '--browser-executable=%s' % self._browser_executable,
258 '%s_page_set' % page_set_basename,
259 '--page-set-base-dir=%s' % page_set_dir
260 )
261 for _ in range(RETRY_RECORD_WPR_COUNT):
262 try:
263 subprocess.check_call(' '.join(record_wpr_cmd), shell=True)
264
265 # Copy over the created archive into the local webpages archive
266 # directory.
267 for wpr_data_file in glob.glob(os.path.join(
268 LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file_glob)):
269 shutil.copy(
270 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file),
272 shutil.copy(
273 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
274 page_set_json_name),
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_glob, page_set_json_name)
290
291 run_benchmark_cmd = [
292 'PYTHONPATH=%s:%s:$PYTHONPATH' % (page_set_dir, self._catapult_dir),
293 'DISPLAY=%s' % X11_DISPLAY,
294 'timeout', '1800',
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 exclusions = PAGE_SETS_TO_EXCLUSIONS.get(os.path.basename(page_set))
307 if exclusions:
308 run_benchmark_cmd.append('--story-filter-exclude=' + exclusions)
309
310 for _ in range(RETRY_RUN_MEASUREMENT_COUNT):
311 try:
312 print('\n\n=======Capturing SKP of %s=======\n\n' % page_set)
313 subprocess.check_call(' '.join(run_benchmark_cmd), shell=True)
314 except subprocess.CalledProcessError:
315 # There was a failure continue with the loop.
316 traceback.print_exc()
317 print('\n\n=======Retrying %s=======\n\n' % page_set)
318 time.sleep(10)
319 continue
320
321 try:
322 # Rename generated SKP files into more descriptive names.
323 self._RenameSkpFiles(page_set)
324 except InvalidSKPException:
325 # There was a failure continue with the loop.
326 traceback.print_exc()
327 print('\n\n=======Retrying %s=======\n\n' % page_set)
328 time.sleep(10)
329 continue
330
331 # Break out of the retry loop since there were no errors.
332 break
333 else:
334 # If we get here then run_benchmark did not succeed and thus did not
335 # break out of the loop.
336 raise Exception('run_benchmark failed for page_set: %s' % page_set)
337
338 print('\n\n=======Capturing SKP files took %s seconds=======\n\n' % (
339 time.time() - start_time))
340
341 if self._skia_tools:
342 render_pictures_cmd = [
343 os.path.join(self._skia_tools, 'render_pictures'),
344 '-r', self._local_skp_dir
345 ]
346 render_pdfs_cmd = [
347 os.path.join(self._skia_tools, 'render_pdfs'),
348 '-r', self._local_skp_dir
349 ]
350
351 for tools_cmd in (render_pictures_cmd, render_pdfs_cmd):
352 print('\n\n=======Running %s=======' % ' '.join(tools_cmd))
353 subprocess.check_call(tools_cmd)
354
355 if not self._non_interactive:
356 print('\n\n=======Running debugger=======')
357 os.system('%s %s' % (os.path.join(self._skia_tools, 'debugger'),
358 self._local_skp_dir))
359
360 print('\n\n')
361
362 if self._upload:
363 print('\n\n=======Uploading to %s=======\n\n' % self.gs.target_type())
364 # Copy the directory structure in the root directory into Google Storage.
365 dest_dir_name = ROOT_PLAYBACK_DIR_NAME
366 if self._alternate_upload_dir:
367 dest_dir_name = self._alternate_upload_dir
368
369 self.gs.upload_dir_contents(
370 self._local_skp_dir, dest_dir=dest_dir_name)
371
372 print('\n\n=======New SKPs have been uploaded to %s =======\n\n' %
373 posixpath.join(self.gs.target_name(), dest_dir_name,
374 SKPICTURES_DIR_NAME))
375
376 else:
377 print('\n\n=======Not Uploading to %s=======\n\n' % self.gs.target_type())
378 print('Generated resources are available in %s\n\n' % self._local_skp_dir)
379
381 print('\n\n=======Uploading to Partner bucket %s =======\n\n' %
382 PARTNERS_GS_BUCKET)
383 partner_gs = GoogleStorageDataStore(PARTNERS_GS_BUCKET)
384 timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%d')
385 upload_dir = posixpath.join(SKPICTURES_DIR_NAME, timestamp)
386 try:
387 partner_gs.delete_path(upload_dir)
388 except subprocess.CalledProcessError:
389 print('Cannot delete %s because it does not exist yet.' % upload_dir)
390 print('Uploading %s to %s' % (self._local_skp_dir, upload_dir))
391 partner_gs.upload_dir_contents(self._local_skp_dir, upload_dir)
392 print('\n\n=======New SKPs have been uploaded to %s =======\n\n' %
393 posixpath.join(partner_gs.target_name(), upload_dir))
394
395 return 0
396
397 def _GetSkiaSkpFileName(self, page_set):
398 """Returns the SKP file name for Skia page sets."""
399 # /path/to/skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop.py
400 ps_filename = os.path.basename(page_set)
401 # skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop
402 ps_basename, _ = os.path.splitext(ps_filename)
403 # skia_yahooanswers_desktop -> skia, yahooanswers, desktop
404 _, page_name, device = ps_basename.split('_')
405 basename = '%s_%s' % (DEVICE_TO_PLATFORM_PREFIX[device], page_name)
406 return basename[:MAX_SKP_BASE_NAME_LEN] + '.skp'
407
408 def _GetChromiumSkpFileName(self, page_set, site):
409 """Returns the SKP file name for Chromium page sets."""
410 # /path/to/http___mobile_news_sandbox_pt0 -> http___mobile_news_sandbox_pt0
411 _, webpage = os.path.split(site)
412 # http___mobile_news_sandbox_pt0 -> mobile_news_sandbox_pt0
413 for prefix in ('http___', 'https___', 'www_'):
414 if webpage.startswith(prefix):
415 webpage = webpage[len(prefix):]
416 # /path/to/skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop.py
417 ps_filename = os.path.basename(page_set)
418 # http___mobile_news_sandbox -> pagesetprefix_http___mobile_news_sandbox
419 basename = '%s_%s' % (CHROMIUM_PAGE_SETS_TO_PREFIX[ps_filename], webpage)
420 return basename[:MAX_SKP_BASE_NAME_LEN] + '.skp'
421
422 def _RenameSkpFiles(self, page_set):
423 """Rename generated SKP files into more descriptive names.
424
425 Look into the subdirectory of TMP_SKP_DIR and find the most interesting
426 .skp in there to be this page_set's representative .skp.
427
428 Throws InvalidSKPException if the chosen .skp is less than 1KB. This
429 typically happens when there is a 404 or a redirect loop. Anything greater
430 than 1KB seems to have captured at least some useful information.
431 """
432 subdirs = glob.glob(os.path.join(TMP_SKP_DIR, '*'))
433 for site in subdirs:
434 if self._IsChromiumPageSet(page_set):
435 filename = self._GetChromiumSkpFileName(page_set, site)
436 else:
437 filename = self._GetSkiaSkpFileName(page_set)
438 filename = filename.lower()
439
440 if self._skp_prefix:
441 filename = '%s%s' % (self._skp_prefix, filename)
442
443 # We choose the largest .skp as the most likely to be interesting.
444 largest_skp = max(glob.glob(os.path.join(site, '*.skp')),
445 key=lambda path: os.stat(path).st_size)
446 dest = os.path.join(self._local_skp_dir, filename)
447 print('Moving', largest_skp, 'to', dest)
448 shutil.move(largest_skp, dest)
449 self._skp_files.append(filename)
450 shutil.rmtree(site)
451 skp_size = os.path.getsize(dest)
452 if skp_size < 1024:
454 'Size of %s is only %d. Something is wrong.' % (dest, skp_size))
455
456
458 """Creates required local storage directories for this script."""
460 self._local_skp_dir):
461 if os.path.exists(d):
462 shutil.rmtree(d)
463 os.makedirs(d)
464
465 def _DownloadWebpagesArchive(self, wpr_data_file, page_set_json_name):
466 """Downloads the webpages archive and its required page set from GS."""
467 wpr_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME, 'webpages_archive',
468 wpr_data_file)
469 page_set_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME,
470 'webpages_archive',
471 page_set_json_name)
472 gs = self.gs
473 if (gs.does_storage_object_exist(wpr_source) and
474 gs.does_storage_object_exist(page_set_source)):
475 gs.download_file(wpr_source, LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR)
476 gs.download_file(page_set_source,
477 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
478 page_set_json_name))
479 else:
480 raise Exception('%s and %s do not exist in %s!' % (gs.target_type(),
481 wpr_source, page_set_source))
482
484 """An abstract base class for uploading recordings to a data storage.
485 The interface emulates the google storage api."""
486 def target_name(self):
487 raise NotImplementedError()
488 def target_type(self):
489 raise NotImplementedError()
491 raise NotImplementedError()
492 def download_file(self, name, local_path):
493 raise NotImplementedError()
494 def upload_dir_contents(self, source_dir, dest_dir):
495 raise NotImplementedError()
496
497
499 def __init__(self, data_store_url):
500 self._url = data_store_url.rstrip('/')
501
502 def target_name(self):
503 return self._url
504
505 def target_type(self):
506 return 'Google Storage'
507
509 try:
510 output = subprocess.check_output([
511 'gsutil', 'ls', '/'.join((self._url, name))])
512 except subprocess.CalledProcessError:
513 return False
514 if len(output.splitlines()) != 1:
515 return False
516 return True
517
518 def delete_path(self, path):
519 subprocess.check_call(['gsutil', 'rm', '-r', '/'.join((self._url, path))])
520
521 def download_file(self, name, local_path):
522 subprocess.check_call([
523 'gsutil', 'cp', '/'.join((self._url, name)), local_path])
524
525 def upload_dir_contents(self, source_dir, dest_dir):
526 subprocess.check_call([
527 'gsutil', 'cp', '-r', source_dir, '/'.join((self._url, dest_dir))])
528
529
531 def __init__(self, data_store_location):
532 self._base_dir = data_store_location
533 def target_name(self):
534 return self._base_dir
535 def target_type(self):
536 return self._base_dir
538 return os.path.isfile(os.path.join(self._base_dir, name))
539 def delete_path(self, path):
540 shutil.rmtree(path)
541 def download_file(self, name, local_path):
542 shutil.copyfile(os.path.join(self._base_dir, name), local_path)
543 def upload_dir_contents(self, source_dir, dest_dir):
544 def copytree(source_dir, dest_dir):
545 if not os.path.exists(dest_dir):
546 os.makedirs(dest_dir)
547 for item in os.listdir(source_dir):
548 source = os.path.join(source_dir, item)
549 dest = os.path.join(dest_dir, item)
550 if os.path.isdir(source):
551 copytree(source, dest)
552 else:
553 shutil.copy2(source, dest)
554 copytree(source_dir, os.path.join(self._base_dir, dest_dir))
555
556if '__main__' == __name__:
557 option_parser = optparse.OptionParser()
558 option_parser.add_option(
559 '', '--page_sets',
560 help='Specifies the page sets to use to archive. Supports globs.',
561 default='all')
562 option_parser.add_option(
563 '', '--record', action='store_true',
564 help='Specifies whether a new website archive should be created.',
565 default=False)
566 option_parser.add_option(
567 '', '--skia_tools',
568 help=('Path to compiled Skia executable tools. '
569 'render_pictures/render_pdfs is run on the set '
570 'after all SKPs are captured. If the script is run without '
571 '--non-interactive then the debugger is also run at the end. Debug '
572 'builds are recommended because they seem to catch more failures '
573 'than Release builds.'),
574 default=None)
575 option_parser.add_option(
576 '', '--upload', action='store_true',
577 help=('Uploads to Google Storage or copies to local filesystem storage '
578 ' if this is True.'),
579 default=False)
580 option_parser.add_option(
581 '', '--upload_to_partner_bucket', action='store_true',
582 help=('Uploads SKPs to the chrome-partner-telemetry Google Storage '
583 'bucket if true.'),
584 default=False)
585 option_parser.add_option(
586 '', '--data_store',
587 help=('The location of the file storage to use to download and upload '
588 'files. Can be \'gs://<bucket>\' for Google Storage, or '
589 'a directory for local filesystem storage'),
590 default='gs://skia-skps')
591 option_parser.add_option(
592 '', '--alternate_upload_dir',
593 help= ('Uploads to a different directory in Google Storage or local '
594 'storage if this flag is specified'),
595 default=None)
596 option_parser.add_option(
597 '', '--output_dir',
598 help=('Temporary directory where SKPs and webpage archives will be '
599 'outputted to.'),
600 default=tempfile.gettempdir())
601 option_parser.add_option(
602 '', '--browser_executable',
603 help='The exact browser executable to run.',
604 default=None)
605 option_parser.add_option(
606 '', '--browser_extra_args',
607 help='Additional arguments to pass to the browser.',
608 default=None)
609 option_parser.add_option(
610 '', '--chrome_src_path',
611 help='Path to the chromium src directory.',
612 default=None)
613 option_parser.add_option(
614 '', '--non-interactive', action='store_true',
615 help='Runs the script without any prompts. If this flag is specified and '
616 '--skia_tools is specified then the debugger is not run.',
617 default=False)
618 option_parser.add_option(
619 '', '--skp_prefix',
620 help='Prefix to add to the names of generated SKPs.',
621 default=None)
622 options, unused_args = option_parser.parse_args()
623
624 playback = SkPicturePlayback(options)
625 sys.exit(playback.Run())
void print(void *str)
Definition bridge.cpp:126
download_file(self, name, local_path)
upload_dir_contents(self, source_dir, dest_dir)
upload_dir_contents(self, source_dir, dest_dir)
upload_dir_contents(self, source_dir, dest_dir)
_GetChromiumSkpFileName(self, page_set, site)
_DownloadWebpagesArchive(self, wpr_data_file, page_set_json_name)
static void append(char **dst, size_t *count, const char *src, size_t n)
Definition editor.cpp:211
static float max(float r, float g, float b)
Definition hsl.cpp:49