7b661e9296bb4767ee36f0b1c99f0609ce1f536e
[platform/framework/web/crosswalk.git] / src / xwalk / app / tools / android / customize.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2013,2014 Intel Corporation. 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.
6
7 import compress_js_and_css
8 import fnmatch
9 import json
10 import optparse
11 import os
12 import re
13 import shutil
14 import stat
15 import sys
16 import tempfile
17
18 # get xwalk absolute path so we can run this script from any location
19 xwalk_dir = os.path.dirname(os.path.abspath(__file__))
20 sys.path.append(xwalk_dir)
21
22 from app_info import AppInfo
23 from customize_launch_screen import CustomizeLaunchScreen
24 from handle_xml import AddElementAttribute
25 from handle_xml import AddElementAttributeAndText
26 from handle_xml import EditElementAttribute
27 from handle_xml import EditElementValueByNodeName
28 from handle_permissions import HandlePermissions
29 from util import CleanDir, CreateAndCopyDir
30 from xml.dom import minidom
31
32 TEMPLATE_DIR_NAME = 'template'
33
34 def VerifyPackageName(value):
35   regex = r'^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$'
36   descrpt = 'Each part of package'
37   sample = 'org.xwalk.example, org.xwalk.example_'
38
39   if len(value) >= 128:
40     print('To be safe, the length of package name or app name '
41           'should be less than 128.')
42     sys.exit(6)
43
44   if not re.match(regex, value):
45     print('Error: %s name should be started with letters and should not '
46           'contain invalid characters.\n'
47           'It may contain lowercase letters, numbers, blank spaces and '
48           'underscores\n'
49           'Sample: %s' % (descrpt, sample))
50     sys.exit(6)
51
52
53 def ReplaceSpaceWithUnderscore(value):
54   return value.replace(' ', '_')
55
56
57 def ReplaceInvalidChars(value, mode='default'):
58   """ Replace the invalid chars with '_' for input string.
59   Args:
60     value: the original string.
61     mode: the target usage mode of original string.
62   """
63   if mode == 'default':
64     invalid_chars = '\/:*?"<>|- '
65   elif mode == 'apkname':
66     invalid_chars = '\/:.*?"<>|- '
67   for c in invalid_chars:
68     if mode == 'apkname' and c in value:
69       print("Illegal character: '%s' is replaced with '_'" % c)
70     value = value.replace(c, '_')
71   return value
72
73
74 def GetFilesByExt(path, ext, sub_dir=True):
75   if os.path.exists(path):
76     file_list = []
77     for name in os.listdir(path):
78       full_name = os.path.join(path, name)
79       st = os.lstat(full_name)
80       if stat.S_ISDIR(st.st_mode) and sub_dir:
81         file_list += GetFilesByExt(full_name, ext)
82       elif os.path.isfile(full_name):
83         if fnmatch.fnmatch(full_name, ext):
84           file_list.append(full_name)
85     return file_list
86   else:
87     return []
88
89
90 def ParseParameterForCompressor(option, value, values, parser):
91   if ((not values or values.startswith('-'))
92       and value.find('--compressor') != -1):
93     values = 'all'
94   val = values
95   if parser.rargs and not parser.rargs[0].startswith('-'):
96     val = parser.rargs[0]
97     parser.rargs.pop(0)
98   setattr(parser.values, option.dest, val)
99
100
101 def CompressSourceFiles(app_root, compressor):
102   js_list = []
103   css_list = []
104   js_ext = '*.js'
105   css_ext = '*.css'
106
107   if compressor == 'all' or compressor == 'js':
108     js_list = GetFilesByExt(app_root, js_ext)
109     compress_js_and_css.CompressJavaScript(js_list)
110
111   if compressor == 'all' or compressor == 'css':
112     css_list = GetFilesByExt(app_root, css_ext)
113     compress_js_and_css.CompressCss(css_list)
114
115
116 def Prepare(app_info, compressor):
117   """Copy the Android template project to a new app project
118      named app_info.app_name
119   """
120   # create new app_dir in temp dir
121   app_name = app_info.android_name
122   app_dir = os.path.join(tempfile.gettempdir(), app_name)
123   app_package = app_info.package
124   app_root = app_info.app_root
125   template_app_dir = os.path.join(xwalk_dir, TEMPLATE_DIR_NAME)
126
127   # 1) copy template project to app_dir
128   CleanDir(app_dir)
129   shutil.copytree(template_app_dir, app_dir)
130
131   # 2) replace app_dir 'src' dir with template 'src' dir
132   CleanDir(os.path.join(app_dir, 'src'))
133   template_src_root = os.path.join(template_app_dir, 'src', 'org', 'xwalk',
134                                    'app', 'template')
135
136   # 3) Create directory tree from app package (org.xyz.foo -> src/org/xyz/foo)
137   #    and copy AppTemplateActivity.java to <app_name>Activity.java
138   template_activity_file = os.path.join(template_src_root,
139                                         'AppTemplateActivity.java')
140   if not os.path.isfile(template_activity_file):
141     print ('Error: The template file %s was not found. '
142            'Please make sure this file exists.' % template_activity_file)
143     sys.exit(7)
144   app_pkg_dir = os.path.join(app_dir, 'src',
145                              app_package.replace('.', os.path.sep))
146   if not os.path.exists(app_pkg_dir):
147     os.makedirs(app_pkg_dir)
148   app_activity_file = app_name + 'Activity.java'
149   shutil.copyfile(template_activity_file,
150                   os.path.join(app_pkg_dir, app_activity_file))
151
152   # 4) Copy all HTML source from app_root to app_dir
153   if app_root:
154     app_assets_dir = os.path.join(app_dir, 'assets', 'www')
155     CleanDir(app_assets_dir)
156     shutil.copytree(app_root, app_assets_dir)
157     if compressor:
158       CompressSourceFiles(app_assets_dir, compressor)
159
160
161 def EncodingUnicodeValue(value):
162   try:
163     if isinstance(value, unicode):
164       value = value.encode("utf-8")
165   except NameError:
166     pass
167   return value
168
169
170 def CustomizeStringXML(name, description):
171   strings_path = os.path.join(tempfile.gettempdir(), name, 'res', 'values',
172                               'strings.xml')
173   if not os.path.isfile(strings_path):
174     print ('Please make sure strings_xml'
175            ' exists under template folder.')
176     sys.exit(6)
177
178   if description:
179     description = EncodingUnicodeValue(description)
180     xmldoc = minidom.parse(strings_path)
181     AddElementAttributeAndText(xmldoc, 'string', 'name', 'description',
182                                description)
183     strings_file = open(strings_path, 'w')
184     xmldoc.writexml(strings_file, encoding='utf-8')
185     strings_file.close()
186
187
188 def CustomizeThemeXML(name, fullscreen, manifest):
189   theme_path = os.path.join(tempfile.gettempdir(), name, 'res', 'values-v14',
190                             'theme.xml')
191   if not os.path.isfile(theme_path):
192     print('Error: theme.xml is missing in the build tool.')
193     sys.exit(6)
194
195   theme_xmldoc = minidom.parse(theme_path)
196   if fullscreen:
197     EditElementValueByNodeName(theme_xmldoc, 'item',
198                                'android:windowFullscreen', 'true')
199   has_background = CustomizeLaunchScreen(manifest, name)
200   if has_background:
201     EditElementValueByNodeName(theme_xmldoc, 'item',
202                                'android:windowBackground',
203                                '@drawable/launchscreen_bg')
204   theme_file = open(theme_path, 'w')
205   theme_xmldoc.writexml(theme_file, encoding='utf-8')
206   theme_file.close()
207
208
209 def CustomizeXML(app_info, description, icon_dict, manifest, permissions):
210   app_version = app_info.app_version
211   app_versionCode = app_info.app_versionCode
212   name = app_info.android_name
213   orientation = app_info.orientation
214   package = app_info.package
215   app_name = app_info.app_name
216   app_dir = os.path.join(tempfile.gettempdir(), name)
217   # Chinese character with unicode get from 'manifest.json' will cause
218   # 'UnicodeEncodeError' when finally wrote to 'AndroidManifest.xml'.
219   app_name = EncodingUnicodeValue(app_name)
220   # If string start with '@' or '?', it will be treated as Android resource,
221   # which will cause 'No resource found' error,
222   # append a space before '@' or '?' to fix that.
223   if app_name.startswith('@') or app_name.startswith('?'):
224     app_name = ' ' + app_name
225   manifest_path = os.path.join(app_dir, 'AndroidManifest.xml')
226   if not os.path.isfile(manifest_path):
227     print ('Please make sure AndroidManifest.xml'
228            ' exists under template folder.')
229     sys.exit(6)
230
231   CustomizeStringXML(name, description)
232   CustomizeThemeXML(name, app_info.fullscreen_flag, manifest)
233   xmldoc = minidom.parse(manifest_path)
234   EditElementAttribute(xmldoc, 'manifest', 'package', package)
235   if app_versionCode:
236     EditElementAttribute(xmldoc, 'manifest', 'android:versionCode',
237                          str(app_versionCode))
238   if app_version:
239     EditElementAttribute(xmldoc, 'manifest', 'android:versionName',
240                          app_version)
241   if description:
242     EditElementAttribute(xmldoc, 'manifest', 'android:description',
243                          "@string/description")
244   HandlePermissions(permissions, xmldoc)
245   EditElementAttribute(xmldoc, 'application', 'android:label', app_name)
246   activity_name = package + '.' + name + 'Activity'
247   EditElementAttribute(xmldoc, 'activity', 'android:name', activity_name)
248   EditElementAttribute(xmldoc, 'activity', 'android:label', app_name)
249   if orientation:
250     EditElementAttribute(xmldoc, 'activity', 'android:screenOrientation',
251                          orientation)
252   icon_name = CustomizeIcon(name, app_info.app_root, app_info.icon, icon_dict)
253   if icon_name:
254     EditElementAttribute(xmldoc, 'application', 'android:icon',
255                          '@drawable/%s' % icon_name)
256
257   file_handle = open(os.path.join(app_dir, 'AndroidManifest.xml'), 'w')
258   xmldoc.writexml(file_handle, encoding='utf-8')
259   file_handle.close()
260
261
262 def ReplaceString(file_path, src, dest):
263   file_handle = open(file_path, 'r')
264   src_content = file_handle.read()
265   file_handle.close()
266   file_handle = open(file_path, 'w')
267   dest_content = src_content.replace(src, dest)
268   file_handle.write(dest_content)
269   file_handle.close()
270
271
272 def SetVariable(file_path, string_line, variable, value):
273   function_string = ('%sset%s(%s);\n' %
274                     ('        ', variable, value))
275   temp_file_path = file_path + '.backup'
276   file_handle = open(temp_file_path, 'w+')
277   for line in open(file_path):
278     file_handle.write(line)
279     if (line.find(string_line) >= 0):
280       file_handle.write(function_string)
281   file_handle.close()
282   shutil.move(temp_file_path, file_path)
283
284
285 def CustomizeJava(app_info, app_url, app_local_path, keep_screen_on):
286   name = app_info.android_name
287   package = app_info.package
288   app_dir = os.path.join(tempfile.gettempdir(), name)
289   app_pkg_dir = os.path.join(app_dir, 'src', package.replace('.', os.path.sep))
290   dest_activity = os.path.join(app_pkg_dir, name + 'Activity.java')
291   ReplaceString(dest_activity, 'org.xwalk.app.template', package)
292   ReplaceString(dest_activity, 'AppTemplate', name)
293   manifest_file = os.path.join(app_dir, 'assets', 'www', 'manifest.json')
294   if os.path.isfile(manifest_file):
295     ReplaceString(
296         dest_activity,
297         'loadAppFromUrl("file:///android_asset/www/index.html")',
298         'loadAppFromManifest("file:///android_asset/www/manifest.json")')
299   else:
300     if app_url:
301       if re.search(r'^http(|s)', app_url):
302         ReplaceString(dest_activity, 'file:///android_asset/www/index.html',
303                       app_url)
304     elif app_local_path:
305       if os.path.isfile(os.path.join(app_dir, 'assets', 'www', app_local_path)):
306         ReplaceString(dest_activity, 'file:///android_asset/www/index.html',
307                       'app://' + package + '/' + app_local_path)
308       else:
309         print ('Please make sure that the relative path of entry file'
310                ' is correct.')
311         sys.exit(8)
312
313   if app_info.remote_debugging:
314     SetVariable(dest_activity,
315                 'public void onCreate(Bundle savedInstanceState)',
316                 'RemoteDebugging', 'true')
317   if app_info.use_animatable_view:
318     SetVariable(dest_activity,
319                 'public void onCreate(Bundle savedInstanceState)',
320                 'UseAnimatableView', 'true')
321   if app_info.fullscreen_flag:
322     SetVariable(dest_activity,
323                 'super.onCreate(savedInstanceState)',
324                 'IsFullscreen', 'true')
325   if keep_screen_on:
326     ReplaceString(
327         dest_activity,
328         'super.onCreate(savedInstanceState);',
329         'super.onCreate(savedInstanceState);\n        ' +
330         'getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);')
331
332
333 def CopyExtensionFile(extension_name, suffix, src_path, dest_path):
334   # Copy the file from src_path into dest_path.
335   dest_extension_path = os.path.join(dest_path, extension_name)
336   if os.path.exists(dest_extension_path):
337     # TODO: Refine it by renaming it internally.
338     print('Error: duplicated extension names "%s" are found. Please rename it.'
339           % extension_name)
340     sys.exit(9)
341   else:
342     os.mkdir(dest_extension_path)
343
344   file_name = extension_name + suffix
345   src_file = os.path.join(src_path, file_name)
346   dest_file = os.path.join(dest_extension_path, file_name)
347   if not os.path.isfile(src_file):
348     print('Error: %s was not found in %s.' % (file_name, src_path))
349     sys.exit(9)
350   else:
351     shutil.copyfile(src_file, dest_file)
352
353
354 def CustomizeExtensions(app_info, extensions):
355   """Copy the files from external extensions and merge them into APK.
356
357   The directory of one external extension should be like:
358     myextension/
359       myextension.jar
360       myextension.js
361       myextension.json
362   That means the name of the internal files should be the same as the
363   directory name.
364   For .jar files, they'll be copied to xwalk-extensions/ and then
365   built into classes.dex in make_apk.py.
366   For .js files, they'll be copied into assets/xwalk-extensions/.
367   For .json files, the'll be merged into one file called
368   extensions-config.json and copied into assets/.
369   """
370   if not extensions:
371     return
372   name = app_info.android_name
373   app_dir = os.path.join(tempfile.gettempdir(), name)
374   apk_assets_path = os.path.join(app_dir, 'assets')
375   extensions_string = 'xwalk-extensions'
376
377   # Set up the target directories and files.
378   dest_jar_path = os.path.join(app_dir, extensions_string)
379   os.mkdir(dest_jar_path)
380   dest_js_path = os.path.join(apk_assets_path, extensions_string)
381   os.mkdir(dest_js_path)
382   apk_extensions_json_path = os.path.join(apk_assets_path,
383                                           'extensions-config.json')
384
385   # Split the paths into a list.
386   extension_paths = extensions.split(os.pathsep)
387   extension_json_list = []
388   for source_path in extension_paths:
389     if not os.path.exists(source_path):
390       print('Error: can\'t find the extension directory \'%s\'.' % source_path)
391       sys.exit(9)
392     # Remove redundant separators to avoid empty basename.
393     source_path = os.path.normpath(source_path)
394     extension_name = os.path.basename(source_path)
395
396     # Copy .jar file into xwalk-extensions.
397     CopyExtensionFile(extension_name, '.jar', source_path, dest_jar_path)
398
399     # Copy .js file into assets/xwalk-extensions.
400     CopyExtensionFile(extension_name, '.js', source_path, dest_js_path)
401
402     # Merge .json file into assets/xwalk-extensions.
403     file_name = extension_name + '.json'
404     src_file = os.path.join(source_path, file_name)
405     if not os.path.isfile(src_file):
406       print('Error: %s is not found in %s.' % (file_name, source_path))
407       sys.exit(9)
408     else:
409       src_file_handle = open(src_file)
410       src_file_content = src_file_handle.read()
411       json_output = json.JSONDecoder().decode(src_file_content)
412       # Below 3 properties are used by runtime. See extension manager.
413       # And 'permissions' will be merged.
414       if not ('name' in json_output and
415               'class' in json_output and
416               'jsapi' in json_output):
417         print ('Error: properties \'name\', \'class\' and \'jsapi\' in a json '
418                'file are mandatory.')
419         sys.exit(9)
420       # Reset the path for JavaScript.
421       js_path_prefix = extensions_string + '/' + extension_name + '/'
422       json_output['jsapi'] = js_path_prefix + json_output['jsapi']
423       extension_json_list.append(json_output)
424       # Merge the permissions of extensions into AndroidManifest.xml.
425       manifest_path = os.path.join(app_dir, 'AndroidManifest.xml')
426       xmldoc = minidom.parse(manifest_path)
427       if ('permissions' in json_output):
428         # Get used permission list to avoid repetition as "--permissions"
429         # option can also be used to declare used permissions.
430         existingList = []
431         usedPermissions = xmldoc.getElementsByTagName("uses-permission")
432         for used in usedPermissions:
433           existingList.append(used.getAttribute("android:name"))
434
435         # Add the permissions to manifest file if not used yet.
436         for p in json_output['permissions']:
437           if p in existingList:
438             continue
439           AddElementAttribute(xmldoc, 'uses-permission', 'android:name', p)
440           existingList.append(p)
441
442         # Write to the manifest file to save the update.
443         file_handle = open(manifest_path, 'w')
444         xmldoc.writexml(file_handle, encoding='utf-8')
445         file_handle.close()
446
447   # Write configuration of extensions into the target extensions-config.json.
448   if extension_json_list:
449     extensions_string = json.JSONEncoder().encode(extension_json_list)
450     extension_json_file = open(apk_extensions_json_path, 'w')
451     extension_json_file.write(extensions_string)
452     extension_json_file.close()
453
454
455 def GenerateCommandLineFile(app_info, xwalk_command_line):
456   if xwalk_command_line == '':
457     return
458   assets_path = os.path.join(tempfile.gettempdir(), app_info.android_name,
459                              'assets')
460   file_path = os.path.join(assets_path, 'xwalk-command-line')
461   command_line_file = open(file_path, 'w')
462   command_line_file.write('xwalk ' + xwalk_command_line)
463
464
465 def CustomizeIconByDict(name, app_root, icon_dict):
466   app_dir = os.path.join(tempfile.gettempdir(), name)
467   icon_name = None
468   drawable_dict = {'ldpi': [1, 37], 'mdpi': [37, 72], 'hdpi': [72, 96],
469                    'xhdpi': [96, 120], 'xxhdpi': [120, 144],
470                    'xxxhdpi': [144, 168]}
471   if not icon_dict:
472     return icon_name
473
474   try:
475     icon_dict = dict((int(k), v) for k, v in icon_dict.items())
476   except ValueError:
477     print('The key of icon in the manifest file should be a number.')
478
479   if len(icon_dict) > 0:
480     icon_list = sorted(icon_dict.items(), key=lambda d: d[0])
481     for kd, vd in drawable_dict.items():
482       for item in icon_list:
483         if item[0] >= vd[0] and item[0] < vd[1]:
484           drawable_path = os.path.join(app_dir, 'res', 'drawable-' + kd)
485           if not os.path.exists(drawable_path):
486             os.makedirs(drawable_path)
487           icon = os.path.join(app_root, item[1])
488           if icon and os.path.isfile(icon):
489             icon_name = os.path.basename(icon)
490             icon_suffix = icon_name.split('.')[-1]
491             shutil.copyfile(icon, os.path.join(drawable_path,
492                                                'icon.' + icon_suffix))
493             icon_name = 'icon'
494           elif icon and (not os.path.isfile(icon)):
495             print('Error: "%s" does not exist.' % icon)
496             sys.exit(6)
497           break
498   return icon_name
499
500
501 def CustomizeIconByOption(name, icon):
502   if os.path.isfile(icon):
503     drawable_path = os.path.join(tempfile.gettempdir(), name, 'res', 'drawable')
504     if not os.path.exists(drawable_path):
505       os.makedirs(drawable_path)
506     icon_file = os.path.basename(icon)
507     icon_file = ReplaceInvalidChars(icon_file)
508     shutil.copyfile(icon, os.path.join(drawable_path, icon_file))
509     icon_name = os.path.splitext(icon_file)[0]
510     return icon_name
511   else:
512     print('Error: "%s" does not exist.' % icon)
513     sys.exit(6)
514
515
516 def CustomizeIcon(name, app_root, icon, icon_dict):
517   icon_name = None
518   if icon:
519     icon_name = CustomizeIconByOption(name, icon)
520   else:
521     icon_name = CustomizeIconByDict(name, app_root, icon_dict)
522   return icon_name
523
524
525 def CustomizeAll(app_info, description, icon_dict, permissions, app_url,
526                  app_local_path, keep_screen_on, extensions, manifest,
527                  xwalk_command_line='', compressor=None):
528   try:
529     Prepare(app_info, compressor)
530     CustomizeXML(app_info, description, icon_dict, manifest, permissions)
531     CustomizeJava(app_info, app_url, app_local_path, keep_screen_on)
532     CustomizeExtensions(app_info, extensions)
533     GenerateCommandLineFile(app_info, xwalk_command_line)
534   except SystemExit as ec:
535     print('Exiting with error code: %d' % ec.code)
536     sys.exit(ec.code)
537
538
539 def main():
540   parser = optparse.OptionParser()
541   info = ('The package name. Such as: '
542           '--package=com.example.YourPackage')
543   parser.add_option('--package', help=info)
544   info = ('The apk name. Such as: --name="Your Application Name"')
545   parser.add_option('--name', help=info)
546   info = ('The version of the app. Such as: --app-version=TheVersionNumber')
547   parser.add_option('--app-version', help=info)
548   info = ('The versionCode of the app. Such as: --app-versionCode=24')
549   parser.add_option('--app-versionCode', type='int', help=info)
550   info = ('The application description. Such as:'
551           '--description=YourApplicationdDescription')
552   parser.add_option('--description', help=info)
553   info = ('The permission list. Such as: --permissions="geolocation"'
554           'For more permissions, such as:'
555           '--permissions="geolocation:permission2"')
556   parser.add_option('--permissions', help=info)
557   info = ('The url of application. '
558           'This flag allows to package website as apk. Such as: '
559           '--app-url=http://www.intel.com')
560   parser.add_option('--app-url', help=info)
561   info = ('The root path of the web app. '
562           'This flag allows to package local web app as apk. Such as: '
563           '--app-root=/root/path/of/the/web/app')
564   parser.add_option('--app-root', help=info)
565   info = ('The reletive path of entry file based on |app_root|. '
566           'This flag should work with "--app-root" together. '
567           'Such as: --app-local-path=/reletive/path/of/entry/file')
568   parser.add_option('--app-local-path', help=info)
569   parser.add_option('--enable-remote-debugging', action='store_true',
570                     dest='enable_remote_debugging', default=False,
571                     help='Enable remote debugging.')
572   parser.add_option('--use-animatable-view', action='store_true',
573                     dest='use_animatable_view', default=False,
574                     help='Enable using animatable view (TextureView).')
575   parser.add_option('-f', '--fullscreen', action='store_true',
576                     dest='fullscreen', default=False,
577                     help='Make application fullscreen.')
578   parser.add_option('--keep-screen-on', action='store_true', default=False,
579                     help='Support keeping screen on')
580   info = ('The path list for external extensions separated by os separator.'
581           'On Linux and Mac, the separator is ":". On Windows, it is ";".'
582           'Such as: --extensions="/path/to/extension1:/path/to/extension2"')
583   parser.add_option('--extensions', help=info)
584   info = ('The orientation of the web app\'s display on the device. '
585           'Such as: --orientation=landscape. The default value is "unspecified"'
586           'The value options are the same as those on the Android: '
587           'http://developer.android.com/guide/topics/manifest/'
588           'activity-element.html#screen')
589   parser.add_option('--orientation', help=info)
590   parser.add_option('--manifest', help='The manifest path')
591   info = ('Use command lines.'
592           'Crosswalk is powered by Chromium and supports Chromium command line.'
593           'For example, '
594           '--xwalk-command-line=\'--chromium-command-1 --xwalk-command-2\'')
595   info = ('Create an Android project directory at this location. ')
596   parser.add_option('--project-dir', help=info)
597   parser.add_option('--xwalk-command-line', default='', help=info)
598   info = ('Minify and obfuscate javascript and css.'
599           '--compressor: compress javascript and css.'
600           '--compressor=js: compress javascript.'
601           '--compressor=css: compress css.')
602   parser.add_option('--compressor', dest='compressor', action='callback',
603                     callback=ParseParameterForCompressor,
604                     type='string', nargs=0, help=info)
605   options, _ = parser.parse_args()
606   try:
607     icon_dict = {144: 'icons/icon_144.png',
608                  72: 'icons/icon_72.png',
609                  96: 'icons/icon_96.png',
610                  48: 'icons/icon_48.png'}
611     app_info = AppInfo()
612     if options.name is not None:
613       app_info.android_name = options.name
614     if options.app_root is None:
615       app_info.app_root = os.path.join(xwalk_dir, 'test_data', 'manifest')
616     else:
617       app_info.app_root = options.app_root
618     if options.package is not None:
619       app_info.package = options.package
620     if options.orientation is not None:
621       app_info.orientation = options.orientation
622     if options.app_version is not None:
623       app_info.app_version = options.app_version
624     if options.enable_remote_debugging is not None:
625       app_info.remote_debugging = options.enable_remote_debugging
626     if options.fullscreen is not None:
627       app_info.fullscreen_flag = options.fullscreen
628     app_info.icon = os.path.join('test_data', 'manifest', 'icons',
629                                  'icon_96.png')
630     CustomizeAll(app_info, options.description, icon_dict,
631                  options.permissions, options.app_url, options.app_local_path,
632                  options.keep_screen_on, options.extensions, None,
633                  options.xwalk_command_line, options.compressor)
634
635     # build project is now in /tmp/<name>. Copy to project_dir
636     if options.project_dir:
637       src_dir = os.path.join(tempfile.gettempdir(), app_info.android_name)
638       dest_dir = os.path.join(options.project_dir, app_info.android_name)
639       CreateAndCopyDir(src_dir, dest_dir, True)
640
641   except SystemExit as ec:
642     print('Exiting with error code: %d' % ec.code)
643     return ec.code
644   finally:
645     CleanDir(os.path.join(tempfile.gettempdir(), app_info.android_name))
646   return 0
647
648
649 if __name__ == '__main__':
650   sys.exit(main())