blob: 2a1bedd5e27b9d99c923e5be5835715f4a9dad96 [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
Yang Guo75beda92019-10-28 08:29:25 +010026import special_case_namespaces
Blink Reformat4c46d092018-04-07 15:32:37 +000027
28try:
29 import simplejson as json
30except ImportError:
31 import json
32
Blink Reformat4c46d092018-04-07 15:32:37 +000033def 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
51def resource_source_url(url):
52 return '\n/*# sourceURL=' + url + ' */'
53
54
55def minify_js(javascript):
56 return rjsmin.jsmin(javascript)
57
58
59def concatenated_module_filename(module_name, output_dir):
60 return join(output_dir, module_name + '/' + module_name + '_module.js')
61
62
63def 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
72def 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
89class 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 Guo75beda92019-10-28 08:29:25 +010096 self._special_case_namespaces = special_case_namespaces.special_case_namespaces
Blink Reformat4c46d092018-04-07 15:32:37 +000097
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 Reformat4c46d092018-04-07 15:32:37 +0000127 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 Lippe790b9292019-09-19 15:14:16 +0000132 if ('<script ' in line and 'type="module"' not in line) or '<link ' in line:
Blink Reformat4c46d092018-04-07 15:32:37 +0000133 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 Lippe790b9292019-09-19 15:14:16 +0000138 output.write(' <script type="module">%s</script>\n' % minify_js(read_file(js_file)))
Blink Reformat4c46d092018-04-07 15:32:37 +0000139 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 Lippe790b9292019-09-19 15:14:16 +0000153 return ' <script defer src="%s"></script>\n' % resource_path
Blink Reformat4c46d092018-04-07 15:32:37 +0000154 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 Lippe99e59b82019-09-30 20:00:59 +0000182 output.write('Root.Runtime.cachedResources["%s"] = "' % resource_name)
cjamcl@google.com7e9c8a52019-04-26 21:11:26 +0000183 resource_content = read_file(path.join(self.application_dir, resource_name))
184 resource_content += resource_source_url(resource_name).encode('utf-8')
Blink Reformat4c46d092018-04-07 15:32:37 +0000185 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 Guo4fd355c2019-09-19 10:59:03 +0200202 bail_error(
203 'Non-autostart dependencies specified for the autostarted module "%s": %s' % (name, non_autostart_deps))
Blink Reformat4c46d092018-04-07 15:32:37 +0000204 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 Lippe99e59b82019-09-30 20:00:59 +0000220 output.write('Root.allDescriptors.push(...%s);' % self._release_module_descriptors())
Blink Reformat4c46d092018-04-07 15:32:37 +0000221 output.write('/* Application descriptor %s */\n' % self.app_file('json'))
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000222 output.write('Root.applicationDescriptor = ')
Blink Reformat4c46d092018-04-07 15:32:37 +0000223 output.write(self.descriptors.application_json())
224 else:
225 output.write('/* Additional descriptors */\n')
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000226 output.write('Root.allDescriptors.push(...%s);' % self._release_module_descriptors())
Blink Reformat4c46d092018-04-07 15:32:37 +0000227 output.write('/* Additional descriptors %s */\n' % self.app_file('json'))
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000228 output.write('Root.applicationDescriptor.modules.push(...%s)' % json.dumps(self.descriptors.application.values()))
Blink Reformat4c46d092018-04-07 15:32:37 +0000229
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
255if __name__ == '__main__':
256 sys.exit(main(sys.argv))