75147d1aee44a77f47dafea883c78bbb30a48400
[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 json
9 import optparse
10 import os
11 import re
12 import shutil
13 import subprocess
14 import sys
15
16 # get xwalk absolute path so we can run this script from any location
17 xwalk_dir = os.path.dirname(os.path.abspath(__file__))
18 sys.path.append(xwalk_dir)
19
20 from app_info import AppInfo
21 from customize import VerifyPackageName, CustomizeAll, \
22                       ParseParameterForCompressor
23 from extension_manager import GetExtensionList, GetExtensionStatus
24 from handle_permissions import permission_mapping_table
25 from util import AllArchitectures, CleanDir, GetVersion, RunCommand
26 from manifest_json_parser import HandlePermissionList
27 from manifest_json_parser import ManifestJsonParser
28
29
30 NATIVE_LIBRARY = 'libxwalkcore.so'
31
32
33 def ConvertArchNameToArchFolder(arch):
34   arch_dict = {
35       'x86': 'x86',
36       'arm': 'armeabi-v7a'
37   }
38   return arch_dict.get(arch, None)
39
40
41 def AddExeExtensions(name):
42   exts_str = os.environ.get('PATHEXT', '').lower()
43   exts = [_f for _f in exts_str.split(os.pathsep) if _f]
44   result = []
45   result.append(name)
46   for e in exts:
47     result.append(name + e)
48   return 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 GetAndroidApiLevel():
64   """Get Highest Android target level installed.
65      return -1 if no targets have been found.
66   """
67   target_output = RunCommand(['android', 'list', 'target', '-c'])
68   target_regex = re.compile(r'android-(\d+)')
69   targets = [int(i) for i in target_regex.findall(target_output)]
70   targets.extend([-1])
71   return max(targets)
72
73
74 def ContainsNativeLibrary(path):
75   return os.path.isfile(os.path.join(path, NATIVE_LIBRARY))
76
77
78 def ParseManifest(options):
79   parser = ManifestJsonParser(os.path.expanduser(options.manifest))
80   if not options.name:
81     options.name = parser.GetAppName()
82   if not options.app_version:
83     options.app_version = parser.GetVersion()
84   if not options.app_versionCode and not options.app_versionCodeBase:
85     options.app_versionCode = 1
86   if parser.GetDescription():
87     options.description = parser.GetDescription()
88   if parser.GetPermissions():
89     options.permissions = parser.GetPermissions()
90   if parser.GetAppUrl():
91     options.app_url = parser.GetAppUrl()
92   elif parser.GetAppLocalPath():
93     options.app_local_path = parser.GetAppLocalPath()
94   else:
95     print('Error: there is no app launch path defined in manifest.json.')
96     sys.exit(9)
97   if parser.GetAppRoot():
98     options.app_root = parser.GetAppRoot()
99     options.icon_dict = parser.GetIcons()
100   if parser.GetOrientation():
101     options.orientation = parser.GetOrientation()
102   if parser.GetFullScreenFlag().lower() == 'true':
103     options.fullscreen = True
104   elif parser.GetFullScreenFlag().lower() == 'false':
105     options.fullscreen = False
106   return parser
107
108
109 def ParseXPK(options, out_dir):
110   cmd = ['python', os.path.join (xwalk_dir, 'parse_xpk.py'),
111          '--file=%s' % os.path.expanduser(options.xpk),
112          '--out=%s' % out_dir]
113   RunCommand(cmd)
114   if options.manifest:
115     print ('Use the manifest from XPK by default '
116            'when "--xpk" option is specified, and '
117            'the "--manifest" option would be ignored.')
118     sys.exit(7)
119
120   if os.path.isfile(os.path.join(out_dir, 'manifest.json')):
121     options.manifest = os.path.join(out_dir, 'manifest.json')
122   else:
123     print('XPK doesn\'t contain manifest file.')
124     sys.exit(8)
125
126
127 def FindExtensionJars(root_path):
128   ''' Find all .jar files for external extensions. '''
129   extension_jars = []
130   if not os.path.exists(root_path):
131     return extension_jars
132
133   for afile in os.listdir(root_path):
134     if os.path.isdir(os.path.join(root_path, afile)):
135       base_name = os.path.basename(afile)
136       extension_jar = os.path.join(root_path, afile, base_name + '.jar')
137       if os.path.isfile(extension_jar):
138         extension_jars.append(extension_jar)
139   return extension_jars
140
141
142 # Follows the recommendation from
143 # http://software.intel.com/en-us/blogs/2012/11/12/how-to-publish-
144 # your-apps-on-google-play-for-x86-based-android-devices-using
145 def MakeVersionCode(options):
146   ''' Construct a version code'''
147   if options.app_versionCode:
148     return options.app_versionCode
149
150   # First digit is ABI, ARM=2, x86=6
151   abi = '0'
152   if options.arch == 'arm':
153     abi = '2'
154   if options.arch == 'x86':
155     abi = '6'
156   b = '0'
157   if options.app_versionCodeBase:
158     b = str(options.app_versionCodeBase)
159     if len(b) > 7:
160       print('Version code base must be 7 digits or less: '
161             'versionCodeBase=%s' % (b))
162       sys.exit(12)
163   # zero pad to 7 digits, middle digits can be used for other
164   # features, according to recommendation in URL
165   return '%s%s' % (abi, b.zfill(7))
166
167
168 def GetExtensionBinaryPathList():
169   local_extension_list = []
170   extensions_path = os.path.join(os.getcwd(), "extensions")
171   exist_extension_list = GetExtensionList(extensions_path)
172   for item in exist_extension_list:
173     build_json_path = os.path.join(extensions_path, item, "build.json")
174     with open(build_json_path) as fd:
175       data = json.load(fd)
176     if not GetExtensionStatus(item, extensions_path):
177       continue
178     else:
179       if data.get("binary_path", False):
180         extension_binary_path = os.path.join(extensions_path,
181                                              item,
182                                              data["binary_path"])
183       else:
184         print "The extension \"%s\" doesn't exists." % item
185         sys.exit(1)
186     if os.path.isdir(extension_binary_path):
187       local_extension_list.append(extension_binary_path)
188     else:
189       print "The extension \"%s\" doesn't exists." % item
190       sys.exit(1)
191
192   return local_extension_list
193
194
195 def Customize(options, app_info, manifest):
196   app_info.package = options.package
197   app_info.app_name = options.name
198   # 'org.xwalk.my_first_app' => 'MyFirstApp'
199   android_name = options.package.split('.')[-1].split('_')
200   app_info.android_name = ''.join([i.capitalize() for i in android_name if i])
201   if options.app_version:
202     app_info.app_version = options.app_version
203   app_info.app_versionCode = MakeVersionCode(options)
204   if options.app_root:
205     app_info.app_root = os.path.expanduser(options.app_root)
206   if options.enable_remote_debugging:
207     app_info.remote_debugging = '--enable-remote-debugging'
208   if options.use_animatable_view:
209     app_info.use_animatable_view = '--use-animatable-view'
210   if options.fullscreen:
211     app_info.fullscreen_flag = '-f'
212   if options.orientation:
213     app_info.orientation = options.orientation
214   if options.icon:
215     app_info.icon = '%s' % os.path.expanduser(options.icon)
216
217   #Add local extensions to extension list.
218   extension_binary_path_list = GetExtensionBinaryPathList()
219   if len(extension_binary_path_list) > 0:
220     if options.extensions is None:
221       options.extensions = "" 
222     else:
223       options.extensions += os.pathsep
224
225     for item in extension_binary_path_list:
226       options.extensions += item
227       options.extensions += os.pathsep
228     #trim final path separator
229     options.extensions = options.extensions[0:-1]
230
231   CustomizeAll(app_info, options.description, options.icon_dict,
232                options.permissions, options.app_url, options.app_local_path,
233                options.keep_screen_on, options.extensions, manifest,
234                options.xwalk_command_line, options.compressor)
235
236
237 def Execution(options, name):
238   android_path = Which('android')
239   if android_path is None:
240     print('The "android" binary could not be found. Check your Android SDK '
241           'installation and your PATH environment variable.')
242     sys.exit(1)
243
244   api_level = GetAndroidApiLevel()
245   if api_level < 14:
246     print('Please install Android API level (>=14) first.')
247     sys.exit(3)
248   target_string = 'android-%d' % api_level
249
250   if options.keystore_path:
251     key_store = os.path.expanduser(options.keystore_path)
252     if options.keystore_alias:
253       key_alias = options.keystore_alias
254     else:
255       print('Please provide an alias name of the developer key.')
256       sys.exit(6)
257     if options.keystore_passcode:
258       key_code = options.keystore_passcode
259     else:
260       key_code = None
261     if options.keystore_alias_passcode:
262       key_alias_code = options.keystore_alias_passcode
263     else:
264       key_alias_code = None
265   else:
266     print ('Use xwalk\'s keystore by default for debugging. '
267            'Please switch to your keystore when distributing it to app market.')
268     key_store = os.path.join(xwalk_dir, 'xwalk-debug.keystore')
269     key_alias = 'xwalkdebugkey'
270     key_code = 'xwalkdebug'
271     key_alias_code = 'xwalkdebug'
272
273   # Check whether ant is installed.
274   try:
275     cmd = ['ant', '-version']
276     RunCommand(cmd, shell=True)
277   except EnvironmentError:
278     print('Please install ant first.')
279     sys.exit(4)
280
281   # Update android project for app and xwalk_core_library.
282   update_project_cmd = ['android', 'update', 'project',
283                         '--path', os.path.join (xwalk_dir, name),
284                         '--target', target_string,
285                         '--name', name]
286   if options.mode == 'embedded':
287     RunCommand(['android', 'update', 'lib-project',
288                 '--path', os.path.join(xwalk_dir, name, 'xwalk_core_library'),
289                 '--target', target_string])
290     update_project_cmd.extend(['-l', 'xwalk_core_library'])
291   else:
292     # Shared mode doesn't need xwalk_runtime_java.jar.
293     os.remove(os.path.join(xwalk_dir, name, 'libs', 'xwalk_runtime_java.jar'))
294
295   RunCommand(update_project_cmd)
296
297   # Check whether external extensions are included.
298   extensions_string = 'xwalk-extensions'
299   extensions_dir = os.path.join(xwalk_dir, name, extensions_string)
300   external_extension_jars = FindExtensionJars(extensions_dir)
301   for external_extension_jar in external_extension_jars:
302     shutil.copyfile(external_extension_jar,
303                     os.path.join(xwalk_dir, name, 'libs',
304                                  os.path.basename(external_extension_jar)))
305
306   if options.mode == 'embedded':
307     # Remove existing native libraries in xwalk_core_library, they are probably
308     # for the last execution to make apk for another CPU arch.
309     # And then copy the native libraries for the specified arch into
310     # xwalk_core_library.
311     arch = ConvertArchNameToArchFolder(options.arch)
312     if not arch:
313       print ('Invalid CPU arch: %s.' % arch)
314       sys.exit(10)
315     library_lib_path = os.path.join(xwalk_dir, name, 'xwalk_core_library',
316                                     'libs')
317     for dir_name in os.listdir(library_lib_path):
318       lib_dir = os.path.join(library_lib_path, dir_name)
319       if ContainsNativeLibrary(lib_dir):
320         shutil.rmtree(lib_dir)
321     native_lib_path = os.path.join(xwalk_dir, name, 'native_libs', arch)
322     if ContainsNativeLibrary(native_lib_path):
323       shutil.copytree(native_lib_path, os.path.join(library_lib_path, arch))
324     else:
325       print('No %s native library has been found for creating a Crosswalk '
326             'embedded APK.' % arch)
327       sys.exit(10)
328
329   ant_cmd = ['ant', 'release', '-f', os.path.join(xwalk_dir, name, 'build.xml')]
330   if not options.verbose:
331     ant_cmd.extend(['-quiet'])
332   ant_cmd.extend(['-Dkey.store="%s"' % os.path.abspath(key_store)])
333   ant_cmd.extend(['-Dkey.alias="%s"' % key_alias])
334   if key_code:
335     ant_cmd.extend(['-Dkey.store.password="%s"' % key_code])
336   if key_alias_code:
337     ant_cmd.extend(['-Dkey.alias.password="%s"' % key_alias_code])
338   ant_result = subprocess.call(ant_cmd)
339   if ant_result != 0:
340     print('Command "%s" exited with non-zero exit code %d'
341           % (' '.join(ant_cmd), ant_result))
342     sys.exit(ant_result)
343
344   src_file = os.path.join(xwalk_dir, name, 'bin', '%s-release.apk' % name)
345   package_name = name
346   if options.app_version:
347     package_name += ('_' + options.app_version)
348   if options.mode == 'shared':
349     dst_file = os.path.join(options.target_dir, '%s.apk' % package_name)
350   elif options.mode == 'embedded':
351     dst_file = os.path.join(options.target_dir,
352                             '%s_%s.apk' % (package_name, options.arch))
353   shutil.copyfile(src_file, dst_file)
354
355
356 def PrintPackageInfo(options, name, packaged_archs):
357   package_name_version = os.path.join(options.target_dir, name)
358   if options.app_version:
359     package_name_version += '_' + options.app_version
360
361   if len(packaged_archs) == 0:
362     print ('A non-platform specific APK for the web application "%s" was '
363            'generated successfully at\n%s.apk. It requires a shared Crosswalk '
364            'Runtime to be present.'
365            % (name, package_name_version))
366     return
367
368   for arch in packaged_archs:
369     print ('An APK for the web application "%s" including the Crosswalk '
370            'Runtime built for %s was generated successfully, which can be '
371            'found at\n%s_%s.apk.'
372            % (name, arch, package_name_version, arch))
373
374   all_archs = set(AllArchitectures())
375
376   if len(packaged_archs) != len(all_archs):
377     missed_archs = all_archs - set(packaged_archs)
378     print ('\n\nWARNING: ')
379     print ('This APK will only work on %s based Android devices. Consider '
380            'building for %s as well.' %
381            (', '.join(packaged_archs), ', '.join(missed_archs)))
382   else:
383     print ('\n\n%d APKs were created for %s devices. '
384            % (len(all_archs), ', '.join(all_archs)))
385     print ('Please install the one that matches the processor architecture '
386            'of your device.\n\n')
387     print ('If you are going to submit this application to an application '
388            'store, please make sure you submit both packages.\nInstructions '
389            'for submitting multiple APKs to Google Play Store are available '
390            'here:\nhttps://software.intel.com/en-us/html5/articles/submitting'
391            '-multiple-crosswalk-apk-to-google-play-store')
392
393 def MakeApk(options, app_info, manifest):
394   Customize(options, app_info, manifest)
395   name = app_info.android_name
396   packaged_archs = []
397   if options.mode == 'shared':
398     Execution(options, name)
399   elif options.mode == 'embedded':
400     # Copy xwalk_core_library into app folder and move the native libraries
401     # out.
402     # When making apk for specified CPU arch, will only include the
403     # corresponding native library by copying it back into xwalk_core_library.
404     target_library_path = os.path.join(xwalk_dir, name, 'xwalk_core_library')
405     shutil.copytree(os.path.join(xwalk_dir, 'xwalk_core_library'),
406                     target_library_path)
407     library_lib_path = os.path.join(target_library_path, 'libs')
408     native_lib_path = os.path.join(xwalk_dir, name, 'native_libs')
409     os.makedirs(native_lib_path)
410     available_archs = []
411     for dir_name in os.listdir(library_lib_path):
412       lib_dir = os.path.join(library_lib_path, dir_name)
413       if ContainsNativeLibrary(lib_dir):
414         shutil.move(lib_dir, os.path.join(native_lib_path, dir_name))
415         available_archs.append(dir_name)
416     if options.arch:
417       Execution(options, name)
418       packaged_archs.append(options.arch)
419     else:
420       # If the arch option is unspecified, all of available platform APKs
421       # will be generated.
422       valid_archs = ['x86', 'armeabi-v7a']
423       for arch in valid_archs:
424         if arch in available_archs:
425           if arch.find('x86') != -1:
426             options.arch = 'x86'
427           elif arch.find('arm') != -1:
428             options.arch = 'arm'
429           Execution(options, name)
430           packaged_archs.append(options.arch)
431         else:
432           print('Warning: failed to create package for arch "%s" '
433                 'due to missing native library' % arch)
434
435       if len(packaged_archs) == 0:
436         print('No packages created, aborting')
437         sys.exit(13)
438
439   PrintPackageInfo(options, name, packaged_archs)
440
441 def main(argv):
442   parser = optparse.OptionParser()
443   parser.add_option('-v', '--version', action='store_true',
444                     dest='version', default=False,
445                     help='The version of this python tool.')
446   parser.add_option('--verbose', action="store_true",
447                     dest='verbose', default=False,
448                     help='Print debug messages.')
449   info = ('The packaging mode of the web application. The value \'shared\' '
450           'means that the runtime is shared across multiple application '
451           'instances and that the runtime needs to be distributed separately. '
452           'The value \'embedded\' means that the runtime is embedded into the '
453           'application itself and distributed along with it.'
454           'Set the default mode as \'embedded\'. For example: --mode=embedded')
455   parser.add_option('--mode', choices=('embedded', 'shared'),
456                     default='embedded', help=info)
457   info = ('The target architecture of the embedded runtime. Supported values '
458           'are \'x86\' and \'arm\'. Note, if undefined, APKs for all possible '
459           'architestures will be generated.')
460   parser.add_option('--arch', choices=AllArchitectures(), help=info)
461   group = optparse.OptionGroup(parser, 'Application Source Options',
462       'This packaging tool supports 3 kinds of web application source: '
463       '1) XPK package; 2) manifest.json; 3) various command line options, '
464       'for example, \'--app-url\' for website, \'--app-root\' and '
465       '\'--app-local-path\' for local web application.')
466   info = ('The path of the XPK package. For example, --xpk=/path/to/xpk/file')
467   group.add_option('--xpk', help=info)
468   info = ('The manifest file with the detail description of the application. '
469           'For example, --manifest=/path/to/your/manifest/file')
470   group.add_option('--manifest', help=info)
471   info = ('The url of application. '
472           'This flag allows to package website as apk. For example, '
473           '--app-url=http://www.intel.com')
474   group.add_option('--app-url', help=info)
475   info = ('The root path of the web app. '
476           'This flag allows to package local web app as apk. For example, '
477           '--app-root=/root/path/of/the/web/app')
478   group.add_option('--app-root', help=info)
479   info = ('The relative path of entry file based on the value from '
480           '\'app_root\'. This flag should work with \'--app-root\' together. '
481           'For example, --app-local-path=/relative/path/of/entry/file')
482   group.add_option('--app-local-path', help=info)
483   parser.add_option_group(group)
484   group = optparse.OptionGroup(parser, 'Mandatory arguments',
485       'They are used for describing the APK information through '
486       'command line options.')
487   info = ('The apk name. For example, --name="Your Application Name"')
488   group.add_option('--name', help=info)
489   info = ('The package name. For example, '
490           '--package=com.example.YourPackage')
491   group.add_option('--package', help=info)
492   parser.add_option_group(group)
493   group = optparse.OptionGroup(parser, 'Optional arguments',
494       'They are used for various settings for applications through '
495       'command line options.')
496   info = ('The version name of the application. '
497           'For example, --app-version=1.0.0')
498   group.add_option('--app-version', help=info)
499   info = ('The version code of the application. '
500           'For example, --app-versionCode=24')
501   group.add_option('--app-versionCode', type='int', help=info)
502   info = ('The version code base of the application. Version code will '
503           'be made by adding a prefix based on architecture to the version '
504           'code base. For example, --app-versionCodeBase=24')
505   group.add_option('--app-versionCodeBase', type='int', help=info)
506   info = ('Use command lines.'
507           'Crosswalk is powered by Chromium and supports Chromium command line.'
508           'For example, '
509           '--xwalk-command-line=\'--chromium-command-1 --xwalk-command-2\'')
510   group.add_option('--xwalk-command-line', default='', help=info)
511   info = ('The description of the application. For example, '
512           '--description=YourApplicationDescription')
513   group.add_option('--description', help=info)
514   group.add_option('--enable-remote-debugging', action='store_true',
515                    dest='enable_remote_debugging', default=False,
516                    help='Enable remote debugging.')
517   group.add_option('--use-animatable-view', action='store_true',
518                    dest='use_animatable_view', default=False,
519                    help='Enable using animatable view (TextureView).')
520   info = ('The list of external extension paths splitted by OS separators. '
521           'The separators are \':\' , \';\' and \':\' on Linux, Windows and '
522           'Mac OS respectively. For example, '
523           '--extensions=/path/to/extension1:/path/to/extension2.')
524   group.add_option('--extensions', help=info)
525   group.add_option('-f', '--fullscreen', action='store_true',
526                    dest='fullscreen', default=False,
527                    help='Make application fullscreen.')
528   group.add_option('--keep-screen-on', action='store_true', default=False,
529                    help='Support keeping screen on')
530   info = ('The path of application icon. '
531           'Such as: --icon=/path/to/your/customized/icon')
532   group.add_option('--icon', help=info)
533   info = ('The orientation of the web app\'s display on the device. '
534           'For example, --orientation=landscape. The default value is '
535           '\'unspecified\'. The permitted values are from Android: '
536           'http://developer.android.com/guide/topics/manifest/'
537           'activity-element.html#screen')
538   group.add_option('--orientation', help=info)
539   info = ('The list of permissions to be used by web application. For example, '
540           '--permissions=geolocation:webgl')
541   group.add_option('--permissions', help=info)
542   info = ('Packaging tool will move the output APKS to the target directory')
543   group.add_option('--target-dir', default=os.getcwd(), help=info)
544   parser.add_option_group(group)
545   group = optparse.OptionGroup(parser, 'Keystore Options',
546       'The keystore is a signature from web developer, it\'s used when '
547       'developer wants to distribute the applications.')
548   info = ('The path to the developer keystore. For example, '
549           '--keystore-path=/path/to/your/developer/keystore')
550   group.add_option('--keystore-path', help=info)
551   info = ('The alias name of keystore. For example, --keystore-alias=name')
552   group.add_option('--keystore-alias', help=info)
553   info = ('The passcode of keystore. For example, --keystore-passcode=code')
554   group.add_option('--keystore-passcode', help=info)
555   info = ('Passcode for alias\'s private key in the keystore, '
556           'For example, --keystore-alias-passcode=alias-code')
557   group.add_option('--keystore-alias-passcode', help=info)
558   info = ('Minify and obfuscate javascript and css.'
559           '--compressor: compress javascript and css.'
560           '--compressor=js: compress javascript.'
561           '--compressor=css: compress css.')
562   group.add_option('--compressor', dest='compressor', action='callback',
563                    callback=ParseParameterForCompressor, type='string',
564                    nargs=0, help=info)
565   parser.add_option_group(group)
566   options, _ = parser.parse_args()
567   if len(argv) == 1:
568     parser.print_help()
569     return 0
570
571   if options.version:
572     if os.path.isfile('VERSION'):
573       print(GetVersion('VERSION'))
574       return 0
575     else:
576       parser.error('VERSION was not found, so Crosswalk\'s version could not '
577                    'be determined.')
578
579   xpk_temp_dir = ''
580   if options.xpk:
581     xpk_name = os.path.splitext(os.path.basename(options.xpk))[0]
582     xpk_temp_dir = os.path.join(xwalk_dir, xpk_name + '_xpk')
583     ParseXPK(options, xpk_temp_dir)
584
585   if options.app_root and not options.manifest:
586     manifest_path = os.path.join(options.app_root, 'manifest.json')
587     if os.path.exists(manifest_path):
588       print('Using manifest.json distributed with the application.')
589       options.manifest = manifest_path
590
591   app_info = AppInfo()
592   manifest = None
593   if not options.manifest:
594     # The checks here are really convoluted, but at the moment make_apk
595     # misbehaves any of the following conditions is true.
596     if options.app_url:
597       # 1) --app-url must be passed without either --app-local-path or
598       #    --app-root.
599       if options.app_root or options.app_local_path:
600         parser.error('You must pass either "--app-url" or "--app-local-path" '
601                      'with "--app-root", but not all.')
602     else:
603       # 2) --app-url is not passed but only one of --app-local-path and
604       #    --app-root is set.
605       if bool(options.app_root) != bool(options.app_local_path):
606         parser.error('You must specify both "--app-local-path" and '
607                      '"--app-root".')
608       # 3) None of --app-url, --app-local-path and --app-root are passed.
609       elif not options.app_root and not options.app_local_path:
610         parser.error('You must pass either "--app-url" or "--app-local-path" '
611                      'with "--app-root".')
612
613     if options.permissions:
614       permission_list = options.permissions.split(':')
615     else:
616       print('Warning: all supported permissions on Android port are added. '
617             'Refer to https://github.com/crosswalk-project/'
618             'crosswalk-website/wiki/Crosswalk-manifest')
619       permission_list = permission_mapping_table.keys()
620     options.permissions = HandlePermissionList(permission_list)
621     options.icon_dict = {}
622   else:
623     try:
624       manifest = ParseManifest(options)
625     except SystemExit as ec:
626       return ec.code
627
628   if not options.name:
629     parser.error('An APK name is required. Please use the "--name" option.')
630
631   if not options.package:
632     parser.error('A package name is required. Please use the "--package" '
633                  'option.')
634   VerifyPackageName(options.package)
635
636   if (options.app_root and options.app_local_path and
637       not os.path.isfile(os.path.join(options.app_root,
638                                       options.app_local_path))):
639     print('Please make sure that the local path file of launching app '
640           'does exist.')
641     sys.exit(7)
642
643   if options.target_dir:
644     target_dir = os.path.abspath(os.path.expanduser(options.target_dir))
645     options.target_dir = target_dir
646     if not os.path.isdir(target_dir):
647       os.makedirs(target_dir)
648
649   try:
650     MakeApk(options, app_info, manifest)
651   except SystemExit as ec:
652     CleanDir(app_info.android_name)
653     CleanDir('out')
654     CleanDir(xpk_temp_dir)
655     return ec.code
656   return 0
657
658
659 if __name__ == '__main__':
660   sys.exit(main(sys.argv))