Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python2 | 1 #!/usr/bin/env python2 |
| 2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
| 3 | 3 |
| 4 # Copyright 2015 Google Inc. All Rights Reserved. | 4 # Copyright 2015 Google Inc. All Rights Reserved. |
| 5 # | 5 # |
| 6 # Licensed under the Apache License, Version 2.0 (the "License"); | 6 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 # you may not use this file except in compliance with the License. | 7 # you may not use this file except in compliance with the License. |
| 8 # You may obtain a copy of the License at | 8 # You may obtain a copy of the License at |
| 9 # | 9 # |
| 10 # http://www.apache.org/licenses/LICENSE-2.0 | 10 # http://www.apache.org/licenses/LICENSE-2.0 |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 61 | 61 |
| 62 # Name of the service worker static script. | 62 # Name of the service worker static script. |
| 63 SW_STATIC_SCRIPT_NAME = 'sw_static.js' | 63 SW_STATIC_SCRIPT_NAME = 'sw_static.js' |
| 64 | 64 |
| 65 # Largest number that the cache version can be. | 65 # Largest number that the cache version can be. |
| 66 MAX_CACHE_VERSION = 1000000 | 66 MAX_CACHE_VERSION = 1000000 |
| 67 | 67 |
| 68 # Where this file is located (so we can find resources). | 68 # Where this file is located (so we can find resources). |
| 69 SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) | 69 SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) |
| 70 | 70 |
| 71 # Maps dependency managers to the folder they install dependencies into. | |
| 72 DEPENDENCY_MANAGER_INSTALL_FOLDER = { | |
| 73 'bower': 'bower_components', | |
| 74 'npm': 'node_modules', | |
| 75 } | |
| 76 | |
| 71 SW_FORMAT_STRING = """/** | 77 SW_FORMAT_STRING = """/** |
| 72 * @file Service worker generated by Caterpillar. | 78 * @file Service worker generated by Caterpillar. |
| 73 */ | 79 */ |
| 74 | 80 |
| 75 /** | 81 /** |
| 76 * @const Current cache version. | 82 * @const Current cache version. |
| 77 * | 83 * |
| 78 * Increment this to force cache to clear. | 84 * Increment this to force cache to clear. |
| 79 */ | 85 */ |
| 80 var CACHE_VERSION = {cache_version}; | 86 var CACHE_VERSION = {cache_version}; |
| (...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 326 output_dir: Directory of the web app to insert TODOs into. | 332 output_dir: Directory of the web app to insert TODOs into. |
| 327 """ | 333 """ |
| 328 logging.debug('Inserting TODOs.') | 334 logging.debug('Inserting TODOs.') |
| 329 dirwalk = os.walk(output_dir) | 335 dirwalk = os.walk(output_dir) |
| 330 for (dirpath, _, filenames) in dirwalk: | 336 for (dirpath, _, filenames) in dirwalk: |
| 331 for filename in filenames: | 337 for filename in filenames: |
| 332 if filename.endswith('.js'): | 338 if filename.endswith('.js'): |
| 333 path = os.path.join(dirpath, filename) | 339 path = os.path.join(dirpath, filename) |
| 334 insert_todos_into_file(path) | 340 insert_todos_into_file(path) |
| 335 | 341 |
| 336 def generate_service_worker(output_dir, ca_manifest, polyfill_paths, | 342 def generate_service_worker(output_dir, ca_manifest, required_js_paths, |
| 337 boilerplate_dir): | 343 boilerplate_dir): |
| 338 """Generates code for a service worker. | 344 """Generates code for a service worker. |
| 339 | 345 |
| 340 Args: | 346 Args: |
| 341 output_dir: Directory of the web app that this service worker will run in. | 347 output_dir: Directory of the web app that this service worker will run in. |
| 342 ca_manifest: Chrome App manifest dictionary. | 348 ca_manifest: Chrome App manifest dictionary. |
| 343 polyfill_paths: List of paths to required polyfill scripts, relative to the | 349 required_js_paths: List of paths to required scripts, relative to the |
| 344 boilerplate directory. | 350 boilerplate directory. |
| 345 boilerplate_dir: Caterpillar script directory within output web app. | 351 boilerplate_dir: Caterpillar script directory within output web app. |
| 346 | 352 |
| 347 Returns: | 353 Returns: |
| 348 JavaScript string. | 354 JavaScript string. |
| 349 """ | 355 """ |
| 350 # Get the paths of files we will cache. | 356 # Get the paths of files we will cache. |
| 351 all_filepaths = [] | 357 all_filepaths = [] |
| 352 logging.debug('Looking for files to cache.') | 358 logging.debug('Looking for files to cache.') |
| 353 dirwalk = os.walk(output_dir) | 359 dirwalk = os.walk(output_dir) |
| 354 for (dirpath, _, filenames) in dirwalk: | 360 for (dirpath, _, filenames) in dirwalk: |
| 355 # Add the relative file paths of each file to the filepaths list. | 361 # Add the relative file paths of each file to the filepaths list. |
| 356 all_filepaths.extend( | 362 all_filepaths.extend( |
| 357 os.path.relpath(os.path.join(dirpath, filename), output_dir) | 363 os.path.relpath(os.path.join(dirpath, filename), output_dir) |
| 358 for filename in filenames) | 364 for filename in filenames) |
| 359 logging.debug('Cached files:\n\t%s', '\n\t'.join(all_filepaths)) | 365 logging.debug('Cached files:\n\t%s', '\n\t'.join(all_filepaths)) |
| 360 # Format the file paths as JavaScript strings. | 366 # Format the file paths as JavaScript strings. |
| 361 all_filepaths = ["'{}'".format(fp) for fp in all_filepaths] | 367 all_filepaths = ["'{}'".format(fp) for fp in all_filepaths] |
| 362 | 368 |
| 363 logging.debug('Generating service worker.') | 369 logging.debug('Generating service worker.') |
| 364 | 370 |
| 365 sw_js = SW_FORMAT_STRING.format( | 371 sw_js = SW_FORMAT_STRING.format( |
| 366 cache_version=random.randrange(MAX_CACHE_VERSION), | 372 cache_version=random.randrange(MAX_CACHE_VERSION), |
| 367 joined_filepaths=',\n '.join(all_filepaths), | 373 joined_filepaths=',\n '.join(all_filepaths), |
| 368 boilerplate_dir=boilerplate_dir | 374 boilerplate_dir=boilerplate_dir |
| 369 ) | 375 ) |
| 370 | 376 |
| 371 # The polyfills we get as input are relative to the boilerplate directory, but | 377 # The polyfills we get as input are relative to the boilerplate directory, but |
| 372 # the service worker is in the root directory, so we need to change the paths. | 378 # the service worker is in the root directory, so we need to change the paths. |
| 373 polyfills_paths = [os.path.join(boilerplate_dir, path) | 379 required_js_paths = [os.path.join(boilerplate_dir, path) |
| 374 for path in polyfill_paths] | 380 for path in required_js_paths] |
| 375 | 381 |
| 376 background_scripts = ca_manifest['app']['background'].get('scripts', []) | 382 background_scripts = ca_manifest['app']['background'].get('scripts', []) |
| 377 for script in polyfill_paths + background_scripts: | 383 for script in required_js_paths + background_scripts: |
| 378 logging.debug('Importing `%s` to the service worker.', script) | 384 logging.debug('Importing `%s` to the service worker.', script) |
| 379 sw_js += "importScripts('{}');\n".format(script) | 385 sw_js += "importScripts('{}');\n".format(script) |
| 380 | 386 |
| 381 return sw_js | 387 return sw_js |
| 382 | 388 |
| 383 def copy_script(script, directory): | 389 def copy_script(script, directory): |
| 384 """Copies a script from Caterpillar into the given directory. | 390 """Copies a script from Caterpillar into the given directory. |
| 385 | 391 |
| 386 Args: | 392 Args: |
| 387 script: Caterpillar JavaScript filename. | 393 script: Caterpillar JavaScript filename. |
| 388 directory: Path to directory. | 394 directory: Path to directory. |
| 389 """ | 395 """ |
| 390 path = os.path.join(SCRIPT_DIR, 'js', script) | 396 path = os.path.join(SCRIPT_DIR, 'js', script) |
| 391 new_path = os.path.join(directory, script) | 397 new_path = os.path.join(directory, script) |
| 392 logging.debug('Writing `%s` to `%s`.', path, new_path) | 398 logging.debug('Writing `%s` to `%s`.', path, new_path) |
| 393 shutil.copyfile(path, new_path) | 399 shutil.copyfile(path, new_path) |
| 394 | 400 |
| 395 def add_service_worker(output_dir, ca_manifest, polyfill_paths, | 401 def add_service_worker(output_dir, ca_manifest, required_js_paths, |
| 396 boilerplate_dir): | 402 boilerplate_dir): |
| 397 """Adds service worker scripts to a web app. | 403 """Adds service worker scripts to a web app. |
| 398 | 404 |
| 399 Args: | 405 Args: |
| 400 output_dir: Path to web app to add service worker scripts to. | 406 output_dir: Path to web app to add service worker scripts to. |
| 401 ca_manifest: Chrome App manifest dictionary. | 407 ca_manifest: Chrome App manifest dictionary. |
| 402 polyfill_paths: List of paths to required polyfill scripts, relative to the | 408 required_js_paths: List of paths to required scripts, relative to the |
| 403 boilerplate directory. | 409 boilerplate directory. |
| 404 boilerplate_dir: Caterpillar script directory within web app. | 410 boilerplate_dir: Caterpillar script directory within web app. |
| 405 """ | 411 """ |
| 406 # We have to copy the other scripts before we generate the service worker | 412 # We have to copy the other scripts before we generate the service worker |
| 407 # caching script, or else they won't be cached. | 413 # caching script, or else they won't be cached. |
| 408 boilerplate_path = os.path.join(output_dir, boilerplate_dir) | 414 boilerplate_path = os.path.join(output_dir, boilerplate_dir) |
| 409 copy_script(REGISTER_SCRIPT_NAME, boilerplate_path) | 415 copy_script(REGISTER_SCRIPT_NAME, boilerplate_path) |
| 410 copy_script(SW_STATIC_SCRIPT_NAME, boilerplate_path) | 416 copy_script(SW_STATIC_SCRIPT_NAME, boilerplate_path) |
| 411 | 417 |
| 412 sw_js = generate_service_worker(output_dir, ca_manifest, polyfill_paths, | 418 sw_js = generate_service_worker(output_dir, ca_manifest, required_js_paths, |
| 413 boilerplate_dir) | 419 boilerplate_dir) |
| 414 | 420 |
| 415 # We can now write the service worker. Note that it must be in the root. | 421 # We can now write the service worker. Note that it must be in the root. |
| 416 sw_path = os.path.join(output_dir, SW_SCRIPT_NAME) | 422 sw_path = os.path.join(output_dir, SW_SCRIPT_NAME) |
| 417 logging.debug('Writing service worker to `%s`.', sw_path) | 423 logging.debug('Writing service worker to `%s`.', sw_path) |
| 418 with open(sw_path, 'w') as sw_file: | 424 with open(sw_path, 'w') as sw_file: |
| 419 sw_file.write(sw_js) | 425 sw_file.write(sw_js) |
| 420 | 426 |
| 421 class InstallationError(Exception): | 427 class InstallationError(Exception): |
| 422 """Exception raised when a dependency fails to install.""" | 428 """Exception raised when a dependency fails to install.""" |
| (...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 556 logging.info('Found Chrome APIs: %s', ', '.join(apis)) | 562 logging.info('Found Chrome APIs: %s', ', '.join(apis)) |
| 557 | 563 |
| 558 # Determine which Chrome Apps APIs can be polyfilled, and which cannot. | 564 # Determine which Chrome Apps APIs can be polyfilled, and which cannot. |
| 559 polyfillable = [] | 565 polyfillable = [] |
| 560 not_polyfillable = [] | 566 not_polyfillable = [] |
| 561 for api in apis: | 567 for api in apis: |
| 562 if api in POLYFILLS: | 568 if api in POLYFILLS: |
| 563 polyfillable.append(api) | 569 polyfillable.append(api) |
| 564 else: | 570 else: |
| 565 not_polyfillable.append(api) | 571 not_polyfillable.append(api) |
| 572 if 'runtime' not in polyfillable: | |
|
Matt Giuca
2016/01/21 00:23:40
why?
Matthew Alger
2016/01/21 00:29:30
I don't want to add it twice, and polyfillable is
Matt Giuca
2016/01/27 00:35:25
I mean why do you special-case runtime here? (Why
Matthew Alger
2016/01/27 00:36:38
All Chrome Apps errors are stored in chrome.runtim
| |
| 573 polyfillable.insert(0, 'runtime') | |
| 566 | 574 |
| 567 logging.info('Polyfilled Chrome APIs: %s', ', '.join(polyfillable)) | 575 logging.info('Polyfilled Chrome APIs: %s', ', '.join(polyfillable)) |
| 568 logging.warning('Could not polyfill Chrome APIs: %s', | 576 logging.warning('Could not polyfill Chrome APIs: %s', |
| 569 ', '.join(not_polyfillable)) | 577 ', '.join(not_polyfillable)) |
| 570 | 578 |
| 579 # Read in the polyfill manifests and store their dependencies. We can't | |
| 580 # install them yet, though, since that has to be done after editing code or | |
| 581 # the dependencies will also be edited. | |
| 582 polyfill_manifests = polyfill_manifest.load_many(polyfillable) | |
| 583 dependencies = [dependency | |
| 584 for manifest in polyfill_manifests.values() | |
| 585 for dependency in manifest['dependencies']] | |
| 586 | |
| 571 # List of paths of static code to be copied from Caterpillar into the output | 587 # List of paths of static code to be copied from Caterpillar into the output |
| 572 # web app, relative to Caterpillar's JS source directory. | 588 # web app, relative to Caterpillar's JS source directory. |
| 573 required_js_paths = [ | 589 required_always_paths = [ |
|
Matt Giuca
2016/01/21 00:23:40
Why the name change?
Matthew Alger
2016/01/21 00:29:30
Added three different lists with a similar name -
Matt Giuca
2016/01/27 00:35:25
Acknowledged.
| |
| 574 'caterpillar.js', | 590 'caterpillar.js', |
| 575 REGISTER_SCRIPT_NAME, | 591 REGISTER_SCRIPT_NAME, |
| 576 ] + polyfill_paths(polyfillable) | 592 ] |
| 593 | |
| 594 # The dependencies and polyfills are also requirements, but we need to handle | |
| 595 # them differently, so they're split up into two lists. | |
| 596 required_dependency_paths = [] | |
| 597 for dependency in dependencies: | |
| 598 # Note that dependencies are installed into the root, but we need paths | |
| 599 # relative to Caterpillar's boilerplate directory. | |
| 600 dependency_path = os.path.join('..', | |
| 601 DEPENDENCY_MANAGER_INSTALL_FOLDER[dependency['manager']], | |
| 602 dependency['name'], dependency['path']) | |
| 603 required_dependency_paths.append(dependency_path) | |
| 604 | |
| 605 required_polyfill_paths = polyfill_paths(polyfillable) | |
| 577 | 606 |
| 578 # Read in and check the manifest file. | 607 # Read in and check the manifest file. |
| 579 try: | 608 try: |
| 580 ca_manifest = chrome_app.manifest.get(input_dir) | 609 ca_manifest = chrome_app.manifest.get(input_dir) |
| 581 chrome_app.manifest.verify(ca_manifest) | 610 chrome_app.manifest.verify(ca_manifest) |
| 582 except ValueError as e: | 611 except ValueError as e: |
| 583 logging.error(e.message) | 612 logging.error(e.message) |
| 584 return | 613 return |
| 585 | 614 |
| 586 # TODO(alger): Identify background scripts and determine start_url. | 615 # TODO(alger): Identify background scripts and determine start_url. |
| 587 start_url = config['start_url'] | 616 start_url = config['start_url'] |
| 588 logging.info('Got start URL from config file: `%s`', start_url) | 617 logging.info('Got start URL from config file: `%s`', start_url) |
| 589 | 618 |
| 590 # Generate a progressive web app manifest. | 619 # Generate a progressive web app manifest. |
| 591 pwa_manifest = generate_web_manifest(ca_manifest, start_url) | 620 pwa_manifest = generate_web_manifest(ca_manifest, start_url) |
| 592 pwa_manifest_path = os.path.join(output_dir, PWA_MANIFEST_FILENAME) | 621 pwa_manifest_path = os.path.join(output_dir, PWA_MANIFEST_FILENAME) |
| 593 with open(pwa_manifest_path, 'w') as pwa_manifest_file: | 622 with open(pwa_manifest_path, 'w') as pwa_manifest_file: |
| 594 json.dump(pwa_manifest, pwa_manifest_file, indent=4, sort_keys=True) | 623 json.dump(pwa_manifest, pwa_manifest_file, indent=4, sort_keys=True) |
| 595 logging.debug('Wrote `%s` to `%s`.', PWA_MANIFEST_FILENAME, pwa_manifest_path) | 624 logging.debug('Wrote `%s` to `%s`.', PWA_MANIFEST_FILENAME, pwa_manifest_path) |
| 596 | 625 |
| 597 # Remove unnecessary files from the output web app. This must be done before | 626 # Remove unnecessary files from the output web app. This must be done before |
| 598 # the service worker is generated, or these files will be cached. | 627 # the service worker is generated, or these files will be cached. |
| 599 cleanup_output_dir(output_dir) | 628 cleanup_output_dir(output_dir) |
| 600 | 629 |
| 601 # Edit the HTML and JS code of the output web app. | 630 # Edit the HTML and JS code of the output web app. |
| 602 # This is adding TODOs, injecting tags, etc. - anything that involves editing | 631 # This is adding TODOs, injecting tags, etc. - anything that involves editing |
| 603 # user code directly. This must be done before the static code is copied | 632 # user code directly. This must be done before the static code is copied |
| 604 # across, or the polyfills will have TODOs added to them. | 633 # across, or the polyfills will have TODOs added to them. |
| 605 edit_code(output_dir, required_js_paths, ca_manifest, config) | 634 required_script_paths = (required_always_paths + required_dependency_paths |
| 635 + required_polyfill_paths) # Order significant here. | |
|
Matt Giuca
2016/01/21 00:23:40
nit: Don't squeeze this comment here; put it above
Matthew Alger
2016/01/21 00:29:30
Done.
| |
| 636 edit_code(output_dir, required_script_paths, ca_manifest, config) | |
| 606 | 637 |
| 607 # We want the static SW file to be copied in too, so we add it here. | 638 # We want the static SW file to be copied in too, so we add it here. |
| 608 # We have to add it after edit_code or it would be included in the HTML, but | 639 # We have to add it after edit_code or it would be included in the HTML, but |
| 609 # this is service worker-only code, and shouldn't be included there. | 640 # this is service worker-only code, and shouldn't be included there. |
| 610 required_js_paths.append(SW_STATIC_SCRIPT_NAME) | 641 required_always_paths.append(SW_STATIC_SCRIPT_NAME) |
| 611 | 642 |
| 612 # Copy static code from Caterpillar into the output web app. | 643 # Copy static code from Caterpillar into the output web app. |
| 613 # This must be done before the service worker is generated, or these files | 644 # This must be done before the service worker is generated, or these files |
| 614 # will not be cached. | 645 # will not be cached. |
| 615 copy_static_code(required_js_paths, output_dir, boilerplate_dir) | 646 required_static_paths = required_always_paths + required_polyfill_paths |
| 647 copy_static_code(required_static_paths, output_dir, boilerplate_dir) | |
| 616 | 648 |
| 617 # Read in the polyfill manifests and install polyfill dependencies. | 649 # Install the polyfill dependencies. This must be done before the service |
| 618 # This must be done after editing code (or the dependencies will also be | 650 # worker is generated, or the dependencies won't be cached. |
| 619 # edited) and before the service worker is generated (or the dependencies | |
| 620 # won't be cached). | |
| 621 polyfill_manifests = polyfill_manifest.load_many(polyfillable) | |
| 622 dependencies = [dependency | |
| 623 for manifest in polyfill_manifests.values() | |
| 624 for dependency in manifest['dependencies']] | |
| 625 try: | 651 try: |
| 626 install_dependencies(dependencies, output_dir) | 652 install_dependencies(dependencies, output_dir) |
| 627 except ValueError as e: | 653 except ValueError as e: |
| 628 logging.error(e.message) | 654 logging.error(e.message) |
| 629 return | 655 return |
| 630 | 656 |
| 631 # Generate and write a service worker. | 657 # Generate and write a service worker. |
| 632 add_service_worker(output_dir, ca_manifest, polyfill_paths(polyfillable), | 658 required_sw_paths = required_dependency_paths + required_polyfill_paths |
| 659 add_service_worker(output_dir, ca_manifest, required_sw_paths, | |
| 633 boilerplate_dir) | 660 boilerplate_dir) |
| 634 | 661 |
| 635 logging.info('Conversion complete.') | 662 logging.info('Conversion complete.') |
| 636 | 663 |
| 637 class Formatter(logging.Formatter): | 664 class Formatter(logging.Formatter): |
| 638 """Caterpillar logging formatter. | 665 """Caterpillar logging formatter. |
| 639 | 666 |
| 640 Adds color to the logged information. | 667 Adds color to the logged information. |
| 641 """ | 668 """ |
| 642 def format(self, record): | 669 def format(self, record): |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 693 # Main program. | 720 # Main program. |
| 694 if args.mode == 'config': | 721 if args.mode == 'config': |
| 695 configuration.generate_and_save(args.output, args.interactive) | 722 configuration.generate_and_save(args.output, args.interactive) |
| 696 | 723 |
| 697 elif args.mode == 'convert': | 724 elif args.mode == 'convert': |
| 698 config = configuration.load(args.config) | 725 config = configuration.load(args.config) |
| 699 convert_app(args.input, args.output, config, args.force) | 726 convert_app(args.input, args.output, config, args.force) |
| 700 | 727 |
| 701 if __name__ == '__main__': | 728 if __name__ == '__main__': |
| 702 sys.exit(main()) | 729 sys.exit(main()) |
| OLD | NEW |