3 # Copyright 2014 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
9 - Concatenates autostart modules, application modules' module.json descriptors,
10 and the application loader into a single script.
11 - Concatenates all workers' dependencies into individual worker loader scripts.
12 - Builds app.html referencing the application script.
14 - Copies the module directories into their destinations.
15 - Copies app.html as-is.
18 from cStringIO
import StringIO
20 from os
.path
import join
21 from modular_build
import read_file
, write_file
, bail_error
30 import simplejson
as json
34 rjsmin_path
= path
.abspath(join(
35 path
.dirname(__file__
),
40 sys
.path
.append(rjsmin_path
)
44 def resource_source_url(url
):
45 return '\n/*# sourceURL=' + url
+ ' */'
48 def minify_js(javascript
):
49 return rjsmin
.jsmin(javascript
)
52 def concatenated_module_filename(module_name
, output_dir
):
53 return join(output_dir
, module_name
+ '_module.js')
56 def symlink_or_copy_file(src
, dest
, safe
=False):
57 if safe
and path
.exists(dest
):
59 if hasattr(os
, 'symlink'):
62 shutil
.copy(src
, dest
)
65 def symlink_or_copy_dir(src
, dest
):
68 for src_dir
, dirs
, files
in os
.walk(src
):
69 subpath
= path
.relpath(src_dir
, src
)
70 dest_dir
= path
.normpath(join(dest
, subpath
))
73 src_name
= join(os
.getcwd(), src_dir
, name
)
74 dest_name
= join(dest_dir
, name
)
75 symlink_or_copy_file(src_name
, dest_name
)
79 def __init__(self
, application_name
, descriptors
, application_dir
, output_dir
):
80 self
.application_name
= application_name
81 self
.descriptors
= descriptors
82 self
.application_dir
= application_dir
83 self
.output_dir
= output_dir
85 def app_file(self
, extension
):
86 return self
.application_name
+ '.' + extension
88 def core_resource_names(self
):
90 for module
in self
.descriptors
.sorted_modules():
91 if self
.descriptors
.application
[module
].get('type') != 'autostart':
94 resources
= self
.descriptors
.modules
[module
].get('resources')
97 for resource_name
in resources
:
98 result
.append(path
.join(module
, resource_name
))
105 # <module_name>_module.js
106 class ReleaseBuilder(AppBuilder
):
107 def __init__(self
, application_name
, descriptors
, application_dir
, output_dir
):
108 AppBuilder
.__init
__(self
, application_name
, descriptors
, application_dir
, output_dir
)
112 self
._build
_app
_script
()
113 for module
in filter(lambda desc
: (not desc
.get('type') or desc
.get('type') == 'remote'), self
.descriptors
.application
.values()):
114 self
._concatenate
_dynamic
_module
(module
['name'])
115 for module
in filter(lambda desc
: desc
.get('type') == 'worker', self
.descriptors
.application
.values()):
116 self
._concatenate
_worker
(module
['name'])
118 def _build_html(self
):
119 html_name
= self
.app_file('html')
121 with
open(join(self
.application_dir
, html_name
), 'r') as app_input_html
:
122 for line
in app_input_html
:
123 if '<script ' in line
or '<link ' in line
:
125 if '</head>' in line
:
126 output
.write(self
._generate
_include
_tag
(self
.app_file('css')))
127 output
.write(self
._generate
_include
_tag
(self
.app_file('js')))
130 write_file(join(self
.output_dir
, html_name
), output
.getvalue())
133 def _build_app_script(self
):
134 script_name
= self
.app_file('js')
136 self
._concatenate
_application
_script
(output
)
137 write_file(join(self
.output_dir
, script_name
), minify_js(output
.getvalue()))
140 def _generate_include_tag(self
, resource_path
):
141 if (resource_path
.endswith('.js')):
142 return ' <script type="text/javascript" src="%s"></script>\n' % resource_path
143 elif (resource_path
.endswith('.css')):
144 return ' <link rel="stylesheet" type="text/css" href="%s">\n' % resource_path
148 def _release_module_descriptors(self
):
149 module_descriptors
= self
.descriptors
.modules
151 for name
in module_descriptors
:
152 module
= copy
.copy(module_descriptors
[name
])
153 # Clear scripts, as they are not used at runtime
154 # (only the fact of their presence is important).
155 resources
= module
.get('resources', None)
156 if module
.get('scripts') or resources
:
157 module
['scripts'] = []
158 # Resources list is not used at runtime.
159 if resources
is not None:
160 del module
['resources']
161 condition
= self
.descriptors
.application
[name
].get('condition')
163 module
['condition'] = condition
164 type = self
.descriptors
.application
[name
].get('type')
166 module
['remote'] = True
167 result
.append(module
)
168 return json
.dumps(result
)
170 def _write_module_resources(self
, resource_names
, output
):
171 for resource_name
in resource_names
:
172 resource_name
= path
.normpath(resource_name
).replace('\\', '/')
173 output
.write('Runtime.cachedResources["%s"] = "' % resource_name
)
174 resource_content
= read_file(path
.join(self
.application_dir
, resource_name
)) + resource_source_url(resource_name
)
175 resource_content
= resource_content
.replace('\\', '\\\\')
176 resource_content
= resource_content
.replace('\n', '\\n')
177 resource_content
= resource_content
.replace('"', '\\"')
178 output
.write(resource_content
)
181 def _concatenate_autostart_modules(self
, output
):
182 non_autostart
= set()
183 sorted_module_names
= self
.descriptors
.sorted_modules()
184 for name
in sorted_module_names
:
185 desc
= self
.descriptors
.modules
[name
]
187 type = self
.descriptors
.application
[name
].get('type')
188 if type == 'autostart':
189 deps
= set(desc
.get('dependencies', []))
190 non_autostart_deps
= deps
& non_autostart
191 if len(non_autostart_deps
):
192 bail_error('Non-autostart dependencies specified for the autostarted module "%s": %s' % (name
, non_autostart_deps
))
193 output
.write('\n/* Module %s */\n' % name
)
194 modular_build
.concatenate_scripts(desc
.get('scripts'), join(self
.application_dir
, name
), self
.output_dir
, output
)
195 elif type != 'worker':
196 non_autostart
.add(name
)
198 def _concatenate_application_script(self
, output
):
199 runtime_contents
= read_file(join(self
.application_dir
, 'Runtime.js'))
200 runtime_contents
= re
.sub('var allDescriptors = \[\];', 'var allDescriptors = %s;' % self
._release
_module
_descriptors
().replace('\\', '\\\\'), runtime_contents
, 1)
201 output
.write('/* Runtime.js */\n')
202 output
.write(runtime_contents
)
203 output
.write('\n/* Autostart modules */\n')
204 self
._concatenate
_autostart
_modules
(output
)
205 output
.write('/* Application descriptor %s */\n' % self
.app_file('json'))
206 output
.write('applicationDescriptor = ')
207 output
.write(self
.descriptors
.application_json())
208 output
.write(';\n/* Core resources */\n')
209 self
._write
_module
_resources
(self
.core_resource_names(), output
)
210 output
.write('\n/* Application loader */\n')
211 output
.write(read_file(join(self
.application_dir
, self
.app_file('js'))))
213 def _concatenate_dynamic_module(self
, module_name
):
214 module
= self
.descriptors
.modules
[module_name
]
215 scripts
= module
.get('scripts')
216 resources
= self
.descriptors
.module_resources(module_name
)
217 module_dir
= join(self
.application_dir
, module_name
)
220 modular_build
.concatenate_scripts(scripts
, module_dir
, self
.output_dir
, output
)
222 self
._write
_module
_resources
(resources
, output
)
223 output_file_path
= concatenated_module_filename(module_name
, self
.output_dir
)
224 write_file(output_file_path
, minify_js(output
.getvalue()))
227 def _concatenate_worker(self
, module_name
):
228 descriptor
= self
.descriptors
.modules
[module_name
]
229 scripts
= descriptor
.get('scripts')
234 output
.write('/* Worker %s */\n' % module_name
)
236 for dep_name
in self
.descriptors
.sorted_dependencies_closure(module_name
):
237 dep_descriptor
= self
.descriptors
.modules
[dep_name
]
238 dep_descriptors
.append(dep_descriptor
)
239 scripts
= dep_descriptor
.get('scripts')
241 output
.write('\n/* Module %s */\n' % dep_name
)
242 modular_build
.concatenate_scripts(scripts
, join(self
.application_dir
, dep_name
), self
.output_dir
, output
)
244 output_file_path
= concatenated_module_filename(module_name
, self
.output_dir
)
245 write_file(output_file_path
, minify_js(output
.getvalue()))
250 # <app_name>.html as-is
251 # <app_name>.js as-is
252 # <module_name>/<all_files>
253 class DebugBuilder(AppBuilder
):
254 def __init__(self
, application_name
, descriptors
, application_dir
, output_dir
):
255 AppBuilder
.__init
__(self
, application_name
, descriptors
, application_dir
, output_dir
)
259 js_name
= self
.app_file('js')
260 src_name
= join(os
.getcwd(), self
.application_dir
, js_name
)
261 symlink_or_copy_file(src_name
, join(self
.output_dir
, js_name
), True)
262 for module_name
in self
.descriptors
.modules
:
263 module
= self
.descriptors
.modules
[module_name
]
264 input_module_dir
= join(self
.application_dir
, module_name
)
265 output_module_dir
= join(self
.output_dir
, module_name
)
266 symlink_or_copy_dir(input_module_dir
, output_module_dir
)
268 def _build_html(self
):
269 html_name
= self
.app_file('html')
270 symlink_or_copy_file(join(os
.getcwd(), self
.application_dir
, html_name
), join(self
.output_dir
, html_name
), True)
273 def build_application(application_name
, loader
, application_dir
, output_dir
, release_mode
):
274 descriptors
= loader
.load_application(application_name
+ '.json')
276 builder
= ReleaseBuilder(application_name
, descriptors
, application_dir
, output_dir
)
278 builder
= DebugBuilder(application_name
, descriptors
, application_dir
, output_dir
)