blob: 7dff77efd0d15aa9d42ebd1bf5f9184aa6bd7c1c [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
Blink Reformat4c46d092018-04-07 15:32:37 +0000130 def _build_html(self):
131 html_name = self.app_file('html')
132 output = StringIO()
133 with open(join(self.application_dir, html_name), 'r') as app_input_html:
134 for line in app_input_html:
Tim van der Lippe790b9292019-09-19 15:14:16 +0000135 if ('<script ' in line and 'type="module"' not in line) or '<link ' in line:
Blink Reformat4c46d092018-04-07 15:32:37 +0000136 continue
137 if '</head>' in line:
138 self._write_include_tags(self.descriptors, output)
139 js_file = join(self.application_dir, self.app_file('js'))
140 if path.exists(js_file):
Tim van der Lippe790b9292019-09-19 15:14:16 +0000141 output.write(' <script type="module">%s</script>\n' % minify_js(read_file(js_file)))
Blink Reformat4c46d092018-04-07 15:32:37 +0000142 output.write(line)
143
144 write_file(join(self.output_dir, html_name), output.getvalue())
145 output.close()
146
147 def _build_app_script(self):
148 script_name = self.app_file('js')
149 output = StringIO()
150 self._concatenate_application_script(output)
151 write_file(join(self.output_dir, script_name), minify_js(output.getvalue()))
152 output.close()
153
154 def _generate_include_tag(self, resource_path):
155 if resource_path.endswith('.js'):
Tim van der Lippe790b9292019-09-19 15:14:16 +0000156 return ' <script defer src="%s"></script>\n' % resource_path
Blink Reformat4c46d092018-04-07 15:32:37 +0000157 else:
158 assert resource_path
159
160 def _release_module_descriptors(self):
161 module_descriptors = self.descriptors.modules
162 result = []
163 for name in module_descriptors:
164 module = copy.copy(module_descriptors[name])
165 module_type = self.descriptors.application[name].get('type')
166 # Clear scripts, as they are not used at runtime
167 # (only the fact of their presence is important).
168 resources = module.get('resources', None)
169 if module.get('scripts') or resources:
170 if module_type == 'autostart':
171 # Autostart modules are already baked in.
172 del module['scripts']
173 else:
174 # Non-autostart modules are vulcanized.
175 module['scripts'] = [name + '_module.js']
176 # Resources are already baked into scripts.
177 if resources is not None:
178 del module['resources']
179 result.append(module)
180 return json.dumps(result)
181
182 def _write_module_resources(self, resource_names, output):
183 for resource_name in resource_names:
184 resource_name = path.normpath(resource_name).replace('\\', '/')
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000185 output.write('Root.Runtime.cachedResources["%s"] = "' % resource_name)
cjamcl@google.com7e9c8a52019-04-26 21:11:26 +0000186 resource_content = read_file(path.join(self.application_dir, resource_name))
187 resource_content += resource_source_url(resource_name).encode('utf-8')
Blink Reformat4c46d092018-04-07 15:32:37 +0000188 resource_content = resource_content.replace('\\', '\\\\')
189 resource_content = resource_content.replace('\n', '\\n')
190 resource_content = resource_content.replace('"', '\\"')
191 output.write(resource_content)
192 output.write('";\n')
193
194 def _concatenate_autostart_modules(self, output):
195 non_autostart = set()
196 sorted_module_names = self.descriptors.sorted_modules()
197 for name in sorted_module_names:
198 desc = self.descriptors.modules[name]
199 name = desc['name']
200 type = self.descriptors.application[name].get('type')
201 if type == 'autostart':
202 deps = set(desc.get('dependencies', []))
203 non_autostart_deps = deps & non_autostart
204 if len(non_autostart_deps):
Yang Guo4fd355c2019-09-19 10:59:03 +0200205 bail_error(
206 'Non-autostart dependencies specified for the autostarted module "%s": %s' % (name, non_autostart_deps))
Blink Reformat4c46d092018-04-07 15:32:37 +0000207 namespace = self._map_module_to_namespace(name)
208 output.write('\n/* Module %s */\n' % name)
209 output.write('\nself[\'%s\'] = self[\'%s\'] || {};\n' % (namespace, namespace))
210 modular_build.concatenate_scripts(desc.get('scripts'), join(self.application_dir, name), self.output_dir, output)
211 else:
212 non_autostart.add(name)
213
214 def _map_module_to_namespace(self, module):
215 camel_case_namespace = "".join(map(lambda x: x[0].upper() + x[1:], module.split('_')))
216 return self._special_case_namespaces.get(module, camel_case_namespace)
217
218 def _concatenate_application_script(self, output):
219 if not self.descriptors.extends:
220 runtime_contents = read_file(join(self.application_dir, 'Runtime.js'))
221 output.write('/* Runtime.js */\n')
222 output.write(runtime_contents)
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000223 output.write('Root.allDescriptors.push(...%s);' % self._release_module_descriptors())
Blink Reformat4c46d092018-04-07 15:32:37 +0000224 output.write('/* Application descriptor %s */\n' % self.app_file('json'))
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000225 output.write('Root.applicationDescriptor = ')
Blink Reformat4c46d092018-04-07 15:32:37 +0000226 output.write(self.descriptors.application_json())
227 else:
228 output.write('/* Additional descriptors */\n')
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000229 output.write('Root.allDescriptors.push(...%s);' % self._release_module_descriptors())
Blink Reformat4c46d092018-04-07 15:32:37 +0000230 output.write('/* Additional descriptors %s */\n' % self.app_file('json'))
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000231 output.write('Root.applicationDescriptor.modules.push(...%s)' % json.dumps(self.descriptors.application.values()))
Blink Reformat4c46d092018-04-07 15:32:37 +0000232
233 output.write('\n/* Autostart modules */\n')
234 self._concatenate_autostart_modules(output)
235 output.write(';\n/* Autostart resources */\n')
236 self._write_module_resources(self.autorun_resource_names(), output)
237 if not self.descriptors.has_html:
238 js_file = join(self.application_dir, self.app_file('js'))
239 if path.exists(js_file):
240 output.write(';\n/* Autostart script for worker */\n')
241 output.write(read_file(js_file))
242
243 def _concatenate_dynamic_module(self, module_name):
244 module = self.descriptors.modules[module_name]
245 scripts = module.get('scripts')
246 resources = self.descriptors.module_resources(module_name)
247 module_dir = join(self.application_dir, module_name)
248 output = StringIO()
249 if scripts:
250 modular_build.concatenate_scripts(scripts, module_dir, self.output_dir, output)
251 if resources:
252 self._write_module_resources(resources, output)
253 output_file_path = concatenated_module_filename(module_name, self.output_dir)
254 write_file(output_file_path, minify_js(output.getvalue()))
255 output.close()
256
257
258if __name__ == '__main__':
259 sys.exit(main(sys.argv))