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