96e2544464c5f866c94ae3b95b41be7c6ab03a2a
[platform/framework/web/crosswalk.git] / src / xwalk / app / tools / android / make_apk.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 # pylint: disable=F0401
7
8 import compress_js_and_css
9 import operator
10 import optparse
11 import os
12 import re
13 import shutil
14 import subprocess
15 import sys
16
17 sys.path.append('scripts/gyp')
18 from customize import ReplaceInvalidChars
19 from dex import AddExeExtensions
20 from handle_permissions import permission_mapping_table
21 from manifest_json_parser import HandlePermissionList
22 from manifest_json_parser import ManifestJsonParser
23
24 def CleanDir(path):
25   if os.path.exists(path):
26     shutil.rmtree(path)
27
28
29 def RunCommand(command, shell=False):
30   """Runs the command list, print the output, and propagate its result."""
31   proc = subprocess.Popen(command, stdout=subprocess.PIPE,
32                           stderr=subprocess.STDOUT, shell=shell)
33   if not shell:
34     output = proc.communicate()[0]
35     result = proc.returncode
36     print(output.decode("utf-8"))
37     if result != 0:
38       print ('Command "%s" exited with non-zero exit code %d'
39              % (' '.join(command), result))
40       sys.exit(result)
41
42
43 def Which(name):
44   """Search PATH for executable files with the given name."""
45   result = []
46   exts = [_f for _f in os.environ.get('PATHEXT', '').split(os.pathsep) if _f]
47   path = os.environ.get('PATH', None)
48   if path is None:
49     return []
50   for p in os.environ.get('PATH', '').split(os.pathsep):
51     p = os.path.join(p, name)
52     if os.access(p, os.X_OK):
53       result.append(p)
54     for e in exts:
55       pext = p + e
56       if os.access(pext, os.X_OK):
57         result.append(pext)
58   return result
59
60
61 def Find(name, path):
62   """Find executable file with the given name
63   and maximum API level under specific path."""
64   result = {}
65   for root, _, files in os.walk(path):
66     if name in files:
67       key = os.path.join(root, name)
68       sdk_version = os.path.basename(os.path.dirname(key))
69       str_num = re.search(r'\d+', sdk_version)
70       if str_num:
71         result[key] = int(str_num.group())
72       else:
73         result[key] = 0
74   if not result:
75     raise Exception()
76   return max(iter(result.items()), key=operator.itemgetter(1))[0]
77
78
79 def GetVersion(path):
80   """Get the version of this python tool."""
81   version_str = 'Crosswalk app packaging tool version is '
82   file_handle = open(path, 'r')
83   src_content = file_handle.read()
84   version_nums = re.findall(r'\d+', src_content)
85   version_str += ('.').join(version_nums)
86   file_handle.close()
87   return version_str
88
89
90 def ParseManifest(options):
91   parser = ManifestJsonParser(os.path.expanduser(options.manifest))
92   if not options.package:
93     options.package = 'org.xwalk.' + parser.GetAppName().lower()
94   if not options.name:
95     options.name = parser.GetAppName()
96   if not options.app_version:
97     options.app_version = parser.GetVersion()
98   if not options.app_versionCode and not options.app_versionCodeBase:
99     options.app_versionCode = 1
100   if parser.GetDescription():
101     options.description = parser.GetDescription()
102   if parser.GetPermissions():
103     options.permissions = parser.GetPermissions()
104   if parser.GetAppUrl():
105     options.app_url = parser.GetAppUrl()
106   elif parser.GetAppLocalPath():
107     options.app_local_path = parser.GetAppLocalPath()
108   else:
109     print('Error: there is no app launch path defined in manifest.json.')
110     sys.exit(9)
111   if parser.GetAppRoot():
112     options.app_root = parser.GetAppRoot()
113     temp_dict = parser.GetIcons()
114     try:
115       icon_dict = dict((int(k), v) for k, v in temp_dict.items())
116     except ValueError:
117       print('The key of icon in the manifest file should be a number.')
118     # TODO(junmin): add multiple icons support.
119     if icon_dict:
120       icon_file = max(iter(icon_dict.items()), key=operator.itemgetter(0))[1]
121       options.icon = os.path.join(options.app_root, icon_file)
122   if parser.GetFullScreenFlag().lower() == 'true':
123     options.fullscreen = True
124   elif parser.GetFullScreenFlag().lower() == 'false':
125     options.fullscreen = False
126   if parser.GetLaunchScreenImg():
127     options.launch_screen_img  = os.path.join(options.app_root,
128                                               parser.GetLaunchScreenImg())
129
130
131 def ParseXPK(options, out_dir):
132   cmd = ['python', 'parse_xpk.py',
133          '--file=%s' % os.path.expanduser(options.xpk),
134          '--out=%s' % out_dir]
135   RunCommand(cmd)
136   if options.manifest:
137     print ('Use the manifest from XPK by default '
138            'when "--xpk" option is specified, and '
139            'the "--manifest" option would be ignored.')
140     sys.exit(7)
141
142   if os.path.isfile(os.path.join(out_dir, 'manifest.json')):
143     options.manifest = os.path.join(out_dir, 'manifest.json')
144   else:
145     print('XPK doesn\'t contain manifest file.')
146     sys.exit(8)
147
148
149 def FindExtensionJars(root_path):
150   ''' Find all .jar files for external extensions. '''
151   extension_jars = []
152   if not os.path.exists(root_path):
153     return extension_jars
154
155   for afile in os.listdir(root_path):
156     if os.path.isdir(os.path.join(root_path, afile)):
157       base_name = os.path.basename(afile)
158       extension_jar = os.path.join(root_path, afile, base_name + '.jar')
159       if os.path.isfile(extension_jar):
160         extension_jars.append(extension_jar)
161   return extension_jars
162
163 # Follows the recommendation from
164 # http://software.intel.com/en-us/blogs/2012/11/12/how-to-publish-
165 # your-apps-on-google-play-for-x86-based-android-devices-using
166 def MakeVersionCode(options):
167   ''' Construct a version code'''
168   if options.app_versionCode:
169     return '--app-versionCode=%s' % options.app_versionCode
170
171   # First digit is ABI, ARM=2, x86=6
172   abi = '0'
173   if options.arch == 'arm':
174     abi = '2'
175   if options.arch == 'x86':
176     abi = '6'
177   b = '0'
178   if options.app_versionCodeBase:
179     b = str(options.app_versionCodeBase)
180     if len(b) > 7:
181       print('Version code base must be 7 digits or less: '
182             'versionCodeBase=%s' % (b))
183       sys.exit(12)
184   # zero pad to 7 digits, middle digits can be used for other
185   # features, according to recommendation in URL
186   return '--app-versionCode=%s%s' % (abi, b.zfill(7))
187
188 def Customize(options):
189   package = '--package=org.xwalk.app.template'
190   if options.package:
191     package = '--package=%s' % options.package
192   name = '--name=AppTemplate'
193   if options.name:
194     name = '--name=%s' % options.name
195   app_version = '--app-version=1.0.0'
196   if options.app_version:
197     app_version = '--app-version=%s' % options.app_version
198   app_versionCode = MakeVersionCode(options)
199   description = ''
200   if options.description:
201     description = '--description=%s' % options.description
202   permissions = ''
203   if options.permissions:
204     permissions = '--permissions=%s' % options.permissions
205   icon = ''
206   if options.icon:
207     icon = '--icon=%s' % os.path.expanduser(options.icon)
208   app_url =  ''
209   if options.app_url:
210     app_url = '--app-url=%s' % options.app_url
211   app_root = ''
212   if options.app_root:
213     app_root = '--app-root=%s' % os.path.expanduser(options.app_root)
214   app_local_path = ''
215   if options.app_local_path:
216     app_local_path = '--app-local-path=%s' % options.app_local_path
217   remote_debugging = ''
218   if options.enable_remote_debugging:
219     remote_debugging = '--enable-remote-debugging'
220   fullscreen_flag = ''
221   if options.fullscreen:
222     fullscreen_flag = '-f'
223   extensions_list = ''
224   if options.extensions:
225     extensions_list = '--extensions=%s' % options.extensions
226   orientation = '--orientation=unspecified'
227   if options.orientation:
228     orientation = '--orientation=%s' % options.orientation
229   default_image = ''
230   if options.launch_screen_img:
231     default_image = '--launch-screen-img=' + options.launch_screen_img
232   cmd = ['python', 'customize.py', package,
233           name, app_version, app_versionCode, description, icon, permissions,
234           app_url, remote_debugging, app_root, app_local_path, fullscreen_flag,
235           extensions_list, orientation, default_image]
236   RunCommand(cmd)
237
238
239 def Execution(options, sanitized_name):
240   android_path_array = Which('android')
241   if not android_path_array:
242     print('Please install Android SDK first.')
243     sys.exit(1)
244
245   sdk_root_path = os.path.dirname(os.path.dirname(android_path_array[0]))
246
247   try:
248     sdk_jar_path = Find('android.jar', os.path.join(sdk_root_path, 'platforms'))
249   except Exception:
250     print('Your Android SDK may be ruined, please reinstall it.')
251     sys.exit(2)
252
253   level_string = os.path.basename(os.path.dirname(sdk_jar_path))
254   api_level = int(re.search(r'\d+', level_string).group())
255   if api_level < 14:
256     print('Please install Android API level (>=14) first.')
257     sys.exit(3)
258
259   if options.keystore_path:
260     key_store = os.path.expanduser(options.keystore_path)
261     if options.keystore_alias:
262       key_alias = options.keystore_alias
263     else:
264       print('Please provide an alias name of the developer key.')
265       sys.exit(6)
266     if options.keystore_passcode:
267       key_code = options.keystore_passcode
268     else:
269       print('Please provide the passcode of the developer key.')
270       sys.exit(6)
271   else:
272     print ('Use xwalk\'s keystore by default for debugging. '
273            'Please switch to your keystore when distributing it to app market.')
274     key_store = 'scripts/ant/xwalk-debug.keystore'
275     key_alias = 'xwalkdebugkey'
276     key_code = 'xwalkdebug'
277
278   if not os.path.exists('out'):
279     os.mkdir('out')
280
281   # Make sure to use ant-tasks.jar correctly.
282   # Default Android SDK names it as ant-tasks.jar
283   # Chrome third party Android SDk names it as anttasks.jar
284   ant_tasks_jar_path = os.path.join(sdk_root_path,
285                                     'tools', 'lib', 'ant-tasks.jar')
286   if not os.path.exists(ant_tasks_jar_path):
287     ant_tasks_jar_path = os.path.join(sdk_root_path,
288                                       'tools', 'lib' ,'anttasks.jar')
289
290   aapt_path = ''
291   for aapt_str in AddExeExtensions('aapt'):
292     try:
293       aapt_path = Find(aapt_str, sdk_root_path)
294       print('Use %s in %s.' % (aapt_str, sdk_root_path))
295       break
296     except Exception:
297       print('There doesn\'t exist %s in %s.' % (aapt_str, sdk_root_path))
298   if not aapt_path:
299     print('Your Android SDK may be ruined, please reinstall it.')
300     sys.exit(2)
301
302   # Check whether ant is installed.
303   try:
304     cmd = ['ant', '-version']
305     RunCommand(cmd, True)
306   except EnvironmentError:
307     print('Please install ant first.')
308     sys.exit(4)
309
310   res_dirs = '-DADDITIONAL_RES_DIRS=\'\''
311   res_packages = '-DADDITIONAL_RES_PACKAGES=\'\''
312   res_r_text_files = '-DADDITIONAL_R_TEXT_FILES=\'\''
313   if options.mode == 'embedded':
314     # Prepare the .pak file for embedded mode.
315     pak_src_path = os.path.join('native_libs_res', 'xwalk.pak')
316     pak_des_path = os.path.join(sanitized_name, 'assets', 'xwalk.pak')
317     shutil.copy(pak_src_path, pak_des_path)
318
319     js_src_dir = os.path.join('native_libs_res', 'jsapi')
320     js_des_dir = os.path.join(sanitized_name, 'assets', 'jsapi')
321     if os.path.exists(js_des_dir):
322       shutil.rmtree(js_des_dir)
323     shutil.copytree(js_src_dir, js_des_dir)
324
325     res_ui_java = os.path.join('gen', 'ui_java')
326     res_content_java = os.path.join('gen', 'content_java')
327     res_xwalk_java = os.path.join('gen', 'xwalk_core_java')
328     res_dirs = ('-DADDITIONAL_RES_DIRS='
329                 + os.path.join(res_ui_java, 'res_crunched') + ' '
330                 + os.path.join(res_ui_java, 'res_v14_compatibility') + ' '
331                 + os.path.join(res_ui_java, 'res_grit') + ' '
332                 + os.path.join('libs_res', 'ui') + ' '
333                 + os.path.join(res_content_java, 'res_crunched') + ' '
334                 + os.path.join(res_content_java, 'res_v14_compatibility') + ' '
335                 + os.path.join('libs_res', 'content') + ' '
336                 + os.path.join(res_content_java, 'res_grit') + ' '
337                 + os.path.join(res_xwalk_java, 'res_crunched') + ' '
338                 + os.path.join(res_xwalk_java, 'res_v14_compatibility') + ' '
339                 + os.path.join('libs_res', 'runtime') + ' '
340                 + os.path.join(res_xwalk_java, 'res_grit'))
341     res_packages = ('-DADDITIONAL_RES_PACKAGES=org.chromium.ui '
342                     'org.xwalk.core org.chromium.content')
343     res_r_text_files = ('-DADDITIONAL_R_TEXT_FILES='
344                         + os.path.join(res_ui_java, 'java_R', 'R.txt') + ' '
345                         + os.path.join(res_xwalk_java, 'java_R', 'R.txt') + ' '
346                         + os.path.join(res_content_java, 'java_R', 'R.txt'))
347
348   resource_dir = '-DRESOURCE_DIR=' + os.path.join(sanitized_name, 'res')
349   manifest_path = os.path.join(sanitized_name, 'AndroidManifest.xml')
350   cmd = ['python', os.path.join('scripts', 'gyp', 'ant.py'),
351          '-DAAPT_PATH=%s' % aapt_path,
352          res_dirs,
353          res_packages,
354          res_r_text_files,
355          '-DANDROID_MANIFEST=%s' % manifest_path,
356          '-DANDROID_SDK_JAR=%s' % sdk_jar_path,
357          '-DANDROID_SDK_ROOT=%s' % sdk_root_path,
358          '-DANDROID_SDK_VERSION=%d' % api_level,
359          '-DANT_TASKS_JAR=%s' % ant_tasks_jar_path,
360          '-DLIBRARY_MANIFEST_PATHS= ',
361          '-DOUT_DIR=out',
362          resource_dir,
363          '-DSTAMP=codegen.stamp',
364          '-Dbasedir=.',
365          '-buildfile',
366          os.path.join('scripts', 'ant', 'apk-codegen.xml')]
367   RunCommand(cmd)
368
369   # Check whether java is installed.
370   try:
371     cmd = ['java', '-version']
372     RunCommand(cmd, True)
373   except EnvironmentError:
374     print('Please install Oracle JDK first.')
375     sys.exit(5)
376
377   # Compile App source code with app runtime code.
378   classpath = '--classpath='
379   classpath += os.path.join(os.getcwd(), 'libs',
380                             'xwalk_app_runtime_java.jar')
381   classpath += ' ' + sdk_jar_path
382   src_dirs = '--src-dirs=' + os.path.join(os.getcwd(), sanitized_name, 'src') +\
383              ' ' + os.path.join(os.getcwd(), 'out', 'gen')
384   cmd = ['python', os.path.join('scripts', 'gyp', 'javac.py'),
385          '--output-dir=%s' % os.path.join('out', 'classes'),
386          classpath,
387          src_dirs,
388          '--javac-includes=',
389          '--chromium-code=0',
390          '--stamp=compile.stam']
391   RunCommand(cmd)
392
393   # Package resources.
394   asset_dir = '-DASSET_DIR=%s' % os.path.join(sanitized_name, 'assets')
395   xml_path = os.path.join('scripts', 'ant', 'apk-package-resources.xml')
396   cmd = ['python', os.path.join('scripts', 'gyp', 'ant.py'),
397          '-DAAPT_PATH=%s' % aapt_path,
398          res_dirs,
399          res_packages,
400          res_r_text_files,
401          '-DANDROID_SDK_JAR=%s' % sdk_jar_path,
402          '-DANDROID_SDK_ROOT=%s' % sdk_root_path,
403          '-DANT_TASKS_JAR=%s' % ant_tasks_jar_path,
404          '-DAPK_NAME=%s' % sanitized_name,
405          '-DAPP_MANIFEST_VERSION_CODE=0',
406          '-DAPP_MANIFEST_VERSION_NAME=Developer Build',
407          asset_dir,
408          '-DCONFIGURATION_NAME=Release',
409          '-DOUT_DIR=out',
410          resource_dir,
411          '-DSTAMP=package_resources.stamp',
412          '-Dbasedir=.',
413          '-buildfile',
414          xml_path]
415   RunCommand(cmd)
416
417   dex_path = '--dex-path=' + os.path.join(os.getcwd(), 'out', 'classes.dex')
418   app_runtime_jar = os.path.join(os.getcwd(),
419                                  'libs', 'xwalk_app_runtime_java.jar')
420
421   # Check whether external extensions are included.
422   extensions_string = 'xwalk-extensions'
423   extensions_dir = os.path.join(os.getcwd(), sanitized_name, extensions_string)
424   external_extension_jars = FindExtensionJars(extensions_dir)
425   input_jars = []
426   if options.mode == 'embedded':
427     input_jars.append(os.path.join(os.getcwd(), 'libs',
428                                    'xwalk_runtime_embedded.dex.jar'))
429   dex_command_list = ['python', os.path.join('scripts', 'gyp', 'dex.py'),
430                       dex_path,
431                       '--android-sdk-root=%s' % sdk_root_path,
432                       app_runtime_jar,
433                       os.path.join(os.getcwd(), 'out', 'classes')]
434   dex_command_list.extend(external_extension_jars)
435   dex_command_list.extend(input_jars)
436   RunCommand(dex_command_list)
437
438   src_dir = '-DSOURCE_DIR=' + os.path.join(sanitized_name, 'src')
439   apk_path = '-DUNSIGNED_APK_PATH=' + os.path.join('out', 'app-unsigned.apk')
440   native_lib_path = '-DNATIVE_LIBS_DIR='
441   if options.mode == 'embedded':
442     if options.arch == 'x86':
443       x86_native_lib_path = os.path.join('native_libs', 'x86', 'libs',
444                                          'x86', 'libxwalkcore.so')
445       if os.path.isfile(x86_native_lib_path):
446         native_lib_path += os.path.join('native_libs', 'x86', 'libs')
447       else:
448         print('Missing x86 native library for Crosswalk embedded APK. Abort!')
449         sys.exit(10)
450     elif options.arch == 'arm':
451       arm_native_lib_path = os.path.join('native_libs', 'armeabi-v7a', 'libs',
452                                          'armeabi-v7a', 'libxwalkcore.so')
453       if os.path.isfile(arm_native_lib_path):
454         native_lib_path += os.path.join('native_libs', 'armeabi-v7a', 'libs')
455       else:
456         print('Missing ARM native library for Crosswalk embedded APK. Abort!')
457         sys.exit(10)
458   # A space is needed for Windows.
459   native_lib_path += ' '
460   cmd = ['python', 'scripts/gyp/ant.py',
461          '-DANDROID_SDK_ROOT=%s' % sdk_root_path,
462          '-DANT_TASKS_JAR=%s' % ant_tasks_jar_path,
463          '-DAPK_NAME=%s' % sanitized_name,
464          '-DCONFIGURATION_NAME=Release',
465          native_lib_path,
466          '-DOUT_DIR=out',
467          src_dir,
468          apk_path,
469          '-Dbasedir=.',
470          '-buildfile',
471          'scripts/ant/apk-package.xml']
472   RunCommand(cmd)
473
474   apk_path = '--unsigned-apk-path=' + os.path.join('out', 'app-unsigned.apk')
475   final_apk_path = '--final-apk-path=' + \
476                    os.path.join('out', sanitized_name + '.apk')
477   cmd = ['python', 'scripts/gyp/finalize_apk.py',
478          '--android-sdk-root=%s' % sdk_root_path,
479          apk_path,
480          final_apk_path,
481          '--keystore-path=%s' % key_store,
482          '--keystore-alias=%s' % key_alias,
483          '--keystore-passcode=%s' % key_code]
484   RunCommand(cmd)
485
486   src_file = os.path.join('out', sanitized_name + '.apk')
487   package_name = options.name
488   if options.app_version:
489     package_name += ('_' + options.app_version)
490   if options.mode == 'shared':
491     dst_file = '%s.apk' % package_name
492   elif options.mode == 'embedded':
493     dst_file = '%s_%s.apk' % (package_name, options.arch)
494   shutil.copyfile(src_file, dst_file)
495   CleanDir('out')
496   if options.mode == 'embedded':
497     os.remove(pak_des_path)
498
499
500 def PrintPackageInfo(app_name, app_version, arch = ''):
501   package_name_version = app_name
502   if app_version != '':
503     package_name_version += ('_' + app_version)
504   if arch == '':
505     print ('A non-platform specific APK for the web application "%s" was '
506            'generated successfully at %s.apk. It requires a shared Crosswalk '
507            'Runtime to be present.'
508            % (app_name, package_name_version))
509   else:
510     print ('An APK for the web application "%s" including the Crosswalk '
511            'Runtime built for %s was generated successfully, which can be '
512            'found at %s_%s.apk.'
513            % (app_name, arch, package_name_version, arch))
514
515
516 def MakeApk(options, sanitized_name):
517   Customize(options)
518   app_version = ''
519   if options.app_version:
520     app_version = options.app_version
521   if options.mode == 'shared':
522     Execution(options, sanitized_name)
523     PrintPackageInfo(sanitized_name, app_version)
524   elif options.mode == 'embedded':
525     if options.arch:
526       Execution(options, sanitized_name)
527       PrintPackageInfo(sanitized_name, app_version, options.arch)
528     else:
529       # If the arch option is unspecified, all of available platform APKs
530       # will be generated.
531       valid_archs = ['x86', 'armeabi-v7a']
532       packaged_archs = []
533       for arch in valid_archs:
534         if os.path.isfile(os.path.join('native_libs', arch, 'libs',
535                                        arch, 'libxwalkcore.so')):
536           if arch.find('x86') != -1:
537             options.arch = 'x86'
538           elif arch.find('arm') != -1:
539             options.arch = 'arm'
540           Execution(options, sanitized_name)
541           packaged_archs.append(options.arch)
542       for arch in packaged_archs:
543         PrintPackageInfo(sanitized_name, app_version, arch)
544   else:
545     print('Unknown mode for packaging the application. Abort!')
546     sys.exit(11)
547
548 def parse_optional_arg(default_value):
549   def func(option, value, values, parser):
550     del value
551     del values
552     if parser.rargs and not parser.rargs[0].startswith('-'):
553       val = parser.rargs[0]
554       parser.rargs.pop(0)
555     else:
556       val = default_value
557     setattr(parser.values, option.dest, val)
558   return func
559
560 def main(argv):
561   parser = optparse.OptionParser()
562   parser.add_option('-v', '--version', action='store_true',
563                     dest='version', default=False,
564                     help='The version of this python tool.')
565   info = ('The packaging mode of the web application. The value \'shared\' '
566           'means that the runtime is shared across multiple application '
567           'instances and that the runtime needs to be distributed separately. '
568           'The value \'embedded\' means that the runtime is embedded into the '
569           'application itself and distributed along with it.'
570           'Set the default mode as \'embedded\'. For example: --mode=embedded')
571   parser.add_option('--mode', default='embedded', help=info)
572   info = ('The target architecture of the embedded runtime. Supported values '
573           'are \'x86\' and \'arm\'. Note, if undefined, APKs for all possible '
574           'architestures will be generated.')
575   parser.add_option('--arch', help=info)
576   group = optparse.OptionGroup(parser, 'Application Source Options',
577       'This packaging tool supports 3 kinds of web application source: '
578       '1) XPK package; 2) manifest.json; 3) various command line options, '
579       'for example, \'--app-url\' for website, \'--app-root\' and '
580       '\'--app-local-path\' for local web application.')
581   info = ('The path of the XPK package. For example, --xpk=/path/to/xpk/file')
582   group.add_option('--xpk', help=info)
583   info = ('The manifest file with the detail description of the application. '
584           'For example, --manifest=/path/to/your/manifest/file')
585   group.add_option('--manifest', help=info)
586   info = ('The url of application. '
587           'This flag allows to package website as apk. For example, '
588           '--app-url=http://www.intel.com')
589   group.add_option('--app-url', help=info)
590   info = ('The root path of the web app. '
591           'This flag allows to package local web app as apk. For example, '
592           '--app-root=/root/path/of/the/web/app')
593   group.add_option('--app-root', help=info)
594   info = ('The relative path of entry file based on the value from '
595           '\'app_root\'. This flag should work with \'--app-root\' together. '
596           'For example, --app-local-path=/relative/path/of/entry/file')
597   group.add_option('--app-local-path', help=info)
598   parser.add_option_group(group)
599   group = optparse.OptionGroup(parser, 'Mandatory arguments',
600       'They are used for describing the APK information through '
601       'command line options.')
602   info = ('The apk name. For example, --name="Your Application Name"')
603   group.add_option('--name', help=info)
604   info = ('The package name. For example, '
605           '--package=com.example.YourPackage')
606   group.add_option('--package', help=info)
607   parser.add_option_group(group)
608   group = optparse.OptionGroup(parser, 'Optional arguments',
609       'They are used for various settings for applications through '
610       'command line options.')
611   info = ('The version name of the application. '
612           'For example, --app-version=1.0.0')
613   group.add_option('--app-version', help=info)
614   info = ('The version code of the application. '
615           'For example, --app-versionCode=24')
616   group.add_option('--app-versionCode', type='int', help=info)
617   info = ('The version code base of the application. Version code will '
618           'be made by adding a prefix based on architecture to the version '
619           'code base. For example, --app-versionCodeBase=24')
620   group.add_option('--app-versionCodeBase', type='int', help=info)
621   info = ('The description of the application. For example, '
622           '--description=YourApplicationDescription')
623   group.add_option('--description', help=info)
624   group.add_option('--enable-remote-debugging', action='store_true',
625                     dest='enable_remote_debugging', default=False,
626                     help = 'Enable remote debugging.')
627   info = ('The list of external extension paths splitted by OS separators. '
628           'The separators are \':\' , \';\' and \':\' on Linux, Windows and '
629           'Mac OS respectively. For example, '
630           '--extensions=/path/to/extension1:/path/to/extension2.')
631   group.add_option('--extensions', help=info)
632   group.add_option('-f', '--fullscreen', action='store_true',
633                    dest='fullscreen', default=False,
634                    help='Make application fullscreen.')
635   info = ('The path of application icon. '
636           'Such as: --icon=/path/to/your/customized/icon')
637   group.add_option('--icon', help=info)
638   info = ('The orientation of the web app\'s display on the device. '
639           'For example, --orientation=landscape. The default value is '
640           '\'unspecified\'. The permitted values are from Android: '
641           'http://developer.android.com/guide/topics/manifest/'
642           'activity-element.html#screen')
643   group.add_option('--orientation', help=info)
644   info = ('The list of permissions to be used by web application. For example, '
645           '--permissions=geolocation:webgl')
646   group.add_option('--permissions', help=info)
647   parser.add_option_group(group)
648   group = optparse.OptionGroup(parser, 'Keystore Options',
649       'The keystore is a signature from web developer, it\'s used when '
650       'developer wants to distribute the applications.')
651   info = ('The path to the developer keystore. For example, '
652           '--keystore-path=/path/to/your/developer/keystore')
653   group.add_option('--keystore-path', help=info)
654   info = ('The alias name of keystore. For example, --keystore-alias=name')
655   group.add_option('--keystore-alias', help=info)
656   info = ('The passcode of keystore. For example, --keystore-passcode=code')
657   group.add_option('--keystore-passcode', help=info)
658   info = ('Minify and obfuscate javascript and css.'
659           '--compressor: compress javascript and css.'
660           '--compressor=js: compress javascript.'
661           '--compressor=css: compress css.')
662   group.add_option('--compressor', dest='compressor', action='callback',
663                    callback=parse_optional_arg('all'), help=info)
664   parser.add_option_group(group)
665   options, _ = parser.parse_args()
666   if len(argv) == 1:
667     parser.print_help()
668     return 0
669
670   # This option will not export to users.
671   # Initialize here and will be read from manifest.json.
672   options.launch_screen_img = ''
673
674   if options.version:
675     if os.path.isfile('VERSION'):
676       print(GetVersion('VERSION'))
677       return 0
678     else:
679       parser.error('Can\'t get version due to the VERSION file missing!')
680
681   xpk_temp_dir = ''
682   if options.xpk:
683     xpk_name = os.path.splitext(os.path.basename(options.xpk))[0]
684     xpk_temp_dir = xpk_name + '_xpk'
685     ParseXPK(options, xpk_temp_dir)
686
687   if options.app_root and not options.manifest:
688     manifest_path = os.path.join(options.app_root, 'manifest.json')
689     if os.path.exists(manifest_path):
690       print('Using manifest.json distributed with the application.')
691       options.manifest = manifest_path
692
693   if not options.manifest:
694     if not options.package:
695       parser.error('The package name is required! '
696                    'Please use "--package" option.')
697     if not options.name:
698       parser.error('The APK name is required! Please use "--name" option.')
699     if not ((options.app_url and not options.app_root
700         and not options.app_local_path) or ((not options.app_url)
701             and options.app_root and options.app_local_path)):
702       parser.error('The entry is required. If the entry is a remote url, '
703                    'please use "--app-url" option; If the entry is local, '
704                    'please use "--app-root" and '
705                    '"--app-local-path" options together!')
706     if options.permissions:
707       permission_list = options.permissions.split(':')
708     else:
709       print ('Warning: all supported permissions on Android port are added. '
710              'Refer to https://github.com/crosswalk-project/'
711              'crosswalk-website/wiki/Crosswalk-manifest')
712       permission_list = permission_mapping_table.keys()
713     options.permissions = HandlePermissionList(permission_list)
714
715   else:
716     try:
717       ParseManifest(options)
718     except SystemExit as ec:
719       return ec.code
720
721   options.name = ReplaceInvalidChars(options.name, 'apkname')
722   options.package = ReplaceInvalidChars(options.package)
723   sanitized_name = ReplaceInvalidChars(options.name, 'apkname')
724
725   try:
726     compress = compress_js_and_css.CompressJsAndCss(options.app_root)
727     if options.compressor == 'all':
728       compress.CompressJavaScript()
729       compress.CompressCss()
730     elif options.compressor == 'js':
731       compress.CompressJavaScript()
732     elif options.compressor == 'css':
733       compress.CompressCss()
734     MakeApk(options, sanitized_name)
735   except SystemExit as ec:
736     CleanDir(sanitized_name)
737     CleanDir('out')
738     if os.path.exists(xpk_temp_dir):
739       CleanDir(xpk_temp_dir)
740     return ec.code
741   return 0
742
743
744 if __name__ == '__main__':
745   sys.exit(main(sys.argv))