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