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