Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # -*- coding: UTF-8 -*- |
| 3 | # |
| 4 | # Copyright 2016 The Chromium Authors. All rights reserved. |
| 5 | # Use of this source code is governed by a BSD-style license that can be |
| 6 | # found in the LICENSE file. |
| 7 | """ |
| 8 | Builds applications in release mode: |
| 9 | - Concatenates autostart modules, application modules' module.json descriptors, |
| 10 | and the application loader into a single script. |
| 11 | - Builds app.html referencing the application script. |
| 12 | """ |
| 13 | |
| 14 | from cStringIO import StringIO |
| 15 | from os import path |
| 16 | from os.path import join |
| 17 | import copy |
| 18 | import os |
| 19 | import re |
| 20 | import shutil |
| 21 | import sys |
| 22 | |
| 23 | from modular_build import read_file, write_file, bail_error |
| 24 | import modular_build |
| 25 | import rjsmin |
Yang Guo | 75beda9 | 2019-10-28 08:29:25 +0100 | [diff] [blame^] | 26 | import special_case_namespaces |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 27 | |
| 28 | try: |
| 29 | import simplejson as json |
| 30 | except ImportError: |
| 31 | import json |
| 32 | |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 33 | def main(argv): |
| 34 | try: |
| 35 | input_path_flag_index = argv.index('--input_path') |
| 36 | input_path = argv[input_path_flag_index + 1] |
| 37 | output_path_flag_index = argv.index('--output_path') |
| 38 | output_path = argv[output_path_flag_index + 1] |
| 39 | application_names = argv[1:input_path_flag_index] |
| 40 | except: |
| 41 | print('Usage: %s app_1 app_2 ... app_N --input_path <input_path> --output_path <output_path>' % argv[0]) |
| 42 | raise |
| 43 | |
| 44 | loader = modular_build.DescriptorLoader(input_path) |
| 45 | for app in application_names: |
| 46 | descriptors = loader.load_application(app) |
| 47 | builder = ReleaseBuilder(app, descriptors, input_path, output_path) |
| 48 | builder.build_app() |
| 49 | |
| 50 | |
| 51 | def resource_source_url(url): |
| 52 | return '\n/*# sourceURL=' + url + ' */' |
| 53 | |
| 54 | |
| 55 | def minify_js(javascript): |
| 56 | return rjsmin.jsmin(javascript) |
| 57 | |
| 58 | |
| 59 | def concatenated_module_filename(module_name, output_dir): |
| 60 | return join(output_dir, module_name + '/' + module_name + '_module.js') |
| 61 | |
| 62 | |
| 63 | def symlink_or_copy_file(src, dest, safe=False): |
| 64 | if safe and path.exists(dest): |
| 65 | os.remove(dest) |
| 66 | if hasattr(os, 'symlink'): |
| 67 | os.symlink(src, dest) |
| 68 | else: |
| 69 | shutil.copy(src, dest) |
| 70 | |
| 71 | |
| 72 | def symlink_or_copy_dir(src, dest): |
| 73 | if path.exists(dest): |
| 74 | shutil.rmtree(dest) |
| 75 | for src_dir, dirs, files in os.walk(src): |
| 76 | subpath = path.relpath(src_dir, src) |
| 77 | dest_dir = path.normpath(join(dest, subpath)) |
| 78 | os.mkdir(dest_dir) |
| 79 | for name in files: |
| 80 | src_name = join(os.getcwd(), src_dir, name) |
| 81 | dest_name = join(dest_dir, name) |
| 82 | symlink_or_copy_file(src_name, dest_name) |
| 83 | |
| 84 | |
| 85 | # Outputs: |
| 86 | # <app_name>.html |
| 87 | # <app_name>.js |
| 88 | # <module_name>_module.js |
| 89 | class ReleaseBuilder(object): |
| 90 | |
| 91 | def __init__(self, application_name, descriptors, application_dir, output_dir): |
| 92 | self.application_name = application_name |
| 93 | self.descriptors = descriptors |
| 94 | self.application_dir = application_dir |
| 95 | self.output_dir = output_dir |
Yang Guo | 75beda9 | 2019-10-28 08:29:25 +0100 | [diff] [blame^] | 96 | self._special_case_namespaces = special_case_namespaces.special_case_namespaces |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 97 | |
| 98 | def app_file(self, extension): |
| 99 | return self.application_name + '.' + extension |
| 100 | |
| 101 | def autorun_resource_names(self): |
| 102 | result = [] |
| 103 | for module in self.descriptors.sorted_modules(): |
| 104 | if self.descriptors.application[module].get('type') != 'autostart': |
| 105 | continue |
| 106 | |
| 107 | resources = self.descriptors.modules[module].get('resources') |
| 108 | if not resources: |
| 109 | continue |
| 110 | for resource_name in resources: |
| 111 | result.append(path.join(module, resource_name)) |
| 112 | return result |
| 113 | |
| 114 | def build_app(self): |
| 115 | if self.descriptors.has_html: |
| 116 | self._build_html() |
| 117 | self._build_app_script() |
| 118 | for module in filter(lambda desc: (not desc.get('type') or desc.get('type') == 'remote'), |
| 119 | self.descriptors.application.values()): |
| 120 | self._concatenate_dynamic_module(module['name']) |
| 121 | |
| 122 | def _write_include_tags(self, descriptors, output): |
| 123 | if descriptors.extends: |
| 124 | self._write_include_tags(descriptors.extends, output) |
| 125 | output.write(self._generate_include_tag(descriptors.application_name + '.js')) |
| 126 | |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 127 | def _build_html(self): |
| 128 | html_name = self.app_file('html') |
| 129 | output = StringIO() |
| 130 | with open(join(self.application_dir, html_name), 'r') as app_input_html: |
| 131 | for line in app_input_html: |
Tim van der Lippe | 790b929 | 2019-09-19 15:14:16 +0000 | [diff] [blame] | 132 | if ('<script ' in line and 'type="module"' not in line) or '<link ' in line: |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 133 | continue |
| 134 | if '</head>' in line: |
| 135 | self._write_include_tags(self.descriptors, output) |
| 136 | js_file = join(self.application_dir, self.app_file('js')) |
| 137 | if path.exists(js_file): |
Tim van der Lippe | 790b929 | 2019-09-19 15:14:16 +0000 | [diff] [blame] | 138 | output.write(' <script type="module">%s</script>\n' % minify_js(read_file(js_file))) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 139 | output.write(line) |
| 140 | |
| 141 | write_file(join(self.output_dir, html_name), output.getvalue()) |
| 142 | output.close() |
| 143 | |
| 144 | def _build_app_script(self): |
| 145 | script_name = self.app_file('js') |
| 146 | output = StringIO() |
| 147 | self._concatenate_application_script(output) |
| 148 | write_file(join(self.output_dir, script_name), minify_js(output.getvalue())) |
| 149 | output.close() |
| 150 | |
| 151 | def _generate_include_tag(self, resource_path): |
| 152 | if resource_path.endswith('.js'): |
Tim van der Lippe | 790b929 | 2019-09-19 15:14:16 +0000 | [diff] [blame] | 153 | return ' <script defer src="%s"></script>\n' % resource_path |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 154 | else: |
| 155 | assert resource_path |
| 156 | |
| 157 | def _release_module_descriptors(self): |
| 158 | module_descriptors = self.descriptors.modules |
| 159 | result = [] |
| 160 | for name in module_descriptors: |
| 161 | module = copy.copy(module_descriptors[name]) |
| 162 | module_type = self.descriptors.application[name].get('type') |
| 163 | # Clear scripts, as they are not used at runtime |
| 164 | # (only the fact of their presence is important). |
| 165 | resources = module.get('resources', None) |
| 166 | if module.get('scripts') or resources: |
| 167 | if module_type == 'autostart': |
| 168 | # Autostart modules are already baked in. |
| 169 | del module['scripts'] |
| 170 | else: |
| 171 | # Non-autostart modules are vulcanized. |
| 172 | module['scripts'] = [name + '_module.js'] |
| 173 | # Resources are already baked into scripts. |
| 174 | if resources is not None: |
| 175 | del module['resources'] |
| 176 | result.append(module) |
| 177 | return json.dumps(result) |
| 178 | |
| 179 | def _write_module_resources(self, resource_names, output): |
| 180 | for resource_name in resource_names: |
| 181 | resource_name = path.normpath(resource_name).replace('\\', '/') |
Tim van der Lippe | 99e59b8 | 2019-09-30 20:00:59 +0000 | [diff] [blame] | 182 | output.write('Root.Runtime.cachedResources["%s"] = "' % resource_name) |
cjamcl@google.com | 7e9c8a5 | 2019-04-26 21:11:26 +0000 | [diff] [blame] | 183 | resource_content = read_file(path.join(self.application_dir, resource_name)) |
| 184 | resource_content += resource_source_url(resource_name).encode('utf-8') |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 185 | resource_content = resource_content.replace('\\', '\\\\') |
| 186 | resource_content = resource_content.replace('\n', '\\n') |
| 187 | resource_content = resource_content.replace('"', '\\"') |
| 188 | output.write(resource_content) |
| 189 | output.write('";\n') |
| 190 | |
| 191 | def _concatenate_autostart_modules(self, output): |
| 192 | non_autostart = set() |
| 193 | sorted_module_names = self.descriptors.sorted_modules() |
| 194 | for name in sorted_module_names: |
| 195 | desc = self.descriptors.modules[name] |
| 196 | name = desc['name'] |
| 197 | type = self.descriptors.application[name].get('type') |
| 198 | if type == 'autostart': |
| 199 | deps = set(desc.get('dependencies', [])) |
| 200 | non_autostart_deps = deps & non_autostart |
| 201 | if len(non_autostart_deps): |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 202 | bail_error( |
| 203 | 'Non-autostart dependencies specified for the autostarted module "%s": %s' % (name, non_autostart_deps)) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 204 | namespace = self._map_module_to_namespace(name) |
| 205 | output.write('\n/* Module %s */\n' % name) |
| 206 | output.write('\nself[\'%s\'] = self[\'%s\'] || {};\n' % (namespace, namespace)) |
| 207 | modular_build.concatenate_scripts(desc.get('scripts'), join(self.application_dir, name), self.output_dir, output) |
| 208 | else: |
| 209 | non_autostart.add(name) |
| 210 | |
| 211 | def _map_module_to_namespace(self, module): |
| 212 | camel_case_namespace = "".join(map(lambda x: x[0].upper() + x[1:], module.split('_'))) |
| 213 | return self._special_case_namespaces.get(module, camel_case_namespace) |
| 214 | |
| 215 | def _concatenate_application_script(self, output): |
| 216 | if not self.descriptors.extends: |
| 217 | runtime_contents = read_file(join(self.application_dir, 'Runtime.js')) |
| 218 | output.write('/* Runtime.js */\n') |
| 219 | output.write(runtime_contents) |
Tim van der Lippe | 99e59b8 | 2019-09-30 20:00:59 +0000 | [diff] [blame] | 220 | output.write('Root.allDescriptors.push(...%s);' % self._release_module_descriptors()) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 221 | output.write('/* Application descriptor %s */\n' % self.app_file('json')) |
Tim van der Lippe | 99e59b8 | 2019-09-30 20:00:59 +0000 | [diff] [blame] | 222 | output.write('Root.applicationDescriptor = ') |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 223 | output.write(self.descriptors.application_json()) |
| 224 | else: |
| 225 | output.write('/* Additional descriptors */\n') |
Tim van der Lippe | 99e59b8 | 2019-09-30 20:00:59 +0000 | [diff] [blame] | 226 | output.write('Root.allDescriptors.push(...%s);' % self._release_module_descriptors()) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 227 | output.write('/* Additional descriptors %s */\n' % self.app_file('json')) |
Tim van der Lippe | 99e59b8 | 2019-09-30 20:00:59 +0000 | [diff] [blame] | 228 | output.write('Root.applicationDescriptor.modules.push(...%s)' % json.dumps(self.descriptors.application.values())) |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 229 | |
| 230 | output.write('\n/* Autostart modules */\n') |
| 231 | self._concatenate_autostart_modules(output) |
| 232 | output.write(';\n/* Autostart resources */\n') |
| 233 | self._write_module_resources(self.autorun_resource_names(), output) |
| 234 | if not self.descriptors.has_html: |
| 235 | js_file = join(self.application_dir, self.app_file('js')) |
| 236 | if path.exists(js_file): |
| 237 | output.write(';\n/* Autostart script for worker */\n') |
| 238 | output.write(read_file(js_file)) |
| 239 | |
| 240 | def _concatenate_dynamic_module(self, module_name): |
| 241 | module = self.descriptors.modules[module_name] |
| 242 | scripts = module.get('scripts') |
| 243 | resources = self.descriptors.module_resources(module_name) |
| 244 | module_dir = join(self.application_dir, module_name) |
| 245 | output = StringIO() |
| 246 | if scripts: |
| 247 | modular_build.concatenate_scripts(scripts, module_dir, self.output_dir, output) |
| 248 | if resources: |
| 249 | self._write_module_resources(resources, output) |
| 250 | output_file_path = concatenated_module_filename(module_name, self.output_dir) |
| 251 | write_file(output_file_path, minify_js(output.getvalue())) |
| 252 | output.close() |
| 253 | |
| 254 | |
| 255 | if __name__ == '__main__': |
| 256 | sys.exit(main(sys.argv)) |