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 |