975c5024bdc6232feba34e7b240dae22efb11423
[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.fullscreen:
209     app_info.fullscreen_flag = '-f'
210   if options.orientation:
211     app_info.orientation = options.orientation
212   if options.icon:
213     app_info.icon = '%s' % os.path.expanduser(options.icon)
214
215   #Add local extensions to extension list.
216   extension_binary_path_list = GetExtensionBinaryPathList()
217   if len(extension_binary_path_list) > 0:
218     if options.extensions is None:
219       options.extensions = "" 
220     else:
221       options.extensions += os.pathsep
222
223     for item in extension_binary_path_list:
224       options.extensions += item
225       options.extensions += os.pathsep
226     #trim final path separator
227     options.extensions = options.extensions[0:-1]
228
229   CustomizeAll(app_info, options.description, options.icon_dict,
230                options.permissions, options.app_url, options.app_local_path,
231                options.keep_screen_on, options.extensions, manifest,
232                options.xwalk_command_line, options.compressor)
233
234
235 def Execution(options, name):
236   android_path = Which('android')
237   if android_path is None:
238     print('The "android" binary could not be found. Check your Android SDK '
239           'installation and your PATH environment variable.')
240     sys.exit(1)
241
242   api_level = GetAndroidApiLevel()
243   if api_level < 14:
244     print('Please install Android API level (>=14) first.')
245     sys.exit(3)
246   target_string = 'android-%d' % api_level
247
248   if options.keystore_path:
249     key_store = os.path.expanduser(options.keystore_path)
250     if options.keystore_alias:
251       key_alias = options.keystore_alias
252     else:
253       print('Please provide an alias name of the developer key.')
254       sys.exit(6)
255     if options.keystore_passcode:
256       key_code = options.keystore_passcode
257     else:
258       key_code = None
259     if options.keystore_alias_passcode:
260       key_alias_code = options.keystore_alias_passcode
261     else:
262       key_alias_code = None
263   else:
264     print ('Use xwalk\'s keystore by default for debugging. '
265            'Please switch to your keystore when distributing it to app market.')
266     key_store = os.path.join(xwalk_dir, 'xwalk-debug.keystore')
267     key_alias = 'xwalkdebugkey'
268     key_code = 'xwalkdebug'
269     key_alias_code = 'xwalkdebug'
270
271   # Check whether ant is installed.
272   try:
273     cmd = ['ant', '-version']
274     RunCommand(cmd, shell=True)
275   except EnvironmentError:
276     print('Please install ant first.')
277     sys.exit(4)
278
279   # Update android project for app and xwalk_core_library.
280   update_project_cmd = ['android', 'update', 'project',
281                         '--path', os.path.join (xwalk_dir, name),
282                         '--target', target_string,
283                         '--name', name]
284   if options.mode == 'embedded':
285     RunCommand(['android', 'update', 'lib-project',
286                 '--path', os.path.join(xwalk_dir, name, 'xwalk_core_library'),
287                 '--target', target_string])
288     update_project_cmd.extend(['-l', 'xwalk_core_library'])
289   else:
290     # Shared mode doesn't need xwalk_runtime_java.jar.
291     os.remove(os.path.join(xwalk_dir, name, 'libs', 'xwalk_runtime_java.jar'))
292
293   RunCommand(update_project_cmd)
294
295   # Check whether external extensions are included.
296   extensions_string = 'xwalk-extensions'
297   extensions_dir = os.path.join(xwalk_dir, name, extensions_string)
298   external_extension_jars = FindExtensionJars(extensions_dir)
299   for external_extension_jar in external_extension_jars:
300     shutil.copyfile(external_extension_jar,
301                     os.path.join(xwalk_dir, name, 'libs',
302                                  os.path.basename(external_extension_jar)))
303
304   if options.mode == 'embedded':
305     # Remove existing native libraries in xwalk_core_library, they are probably
306     # for the last execution to make apk for another CPU arch.
307     # And then copy the native libraries for the specified arch into
308     # xwalk_core_library.
309     arch = ConvertArchNameToArchFolder(options.arch)
310     if not arch:
311       print ('Invalid CPU arch: %s.' % arch)
312       sys.exit(10)
313     library_lib_path = os.path.join(xwalk_dir, name, 'xwalk_core_library',
314                                     'libs')
315     for dir_name in os.listdir(library_lib_path):
316       lib_dir = os.path.join(library_lib_path, dir_name)
317       if ContainsNativeLibrary(lib_dir):
318         shutil.rmtree(lib_dir)
319     native_lib_path = os.path.join(xwalk_dir, name, 'native_libs', arch)
320     if ContainsNativeLibrary(native_lib_path):
321       shutil.copytree(native_lib_path, os.path.join(library_lib_path, arch))
322     else:
323       print('No %s native library has been found for creating a Crosswalk '
324             'embedded APK.' % arch)
325       sys.exit(10)
326
327   ant_cmd = ['ant', 'release', '-f', os.path.join(xwalk_dir, name, 'build.xml')]
328   if not options.verbose:
329     ant_cmd.extend(['-quiet'])
330   ant_cmd.extend(['-Dkey.store="%s"' % os.path.abspath(key_store)])
331   ant_cmd.extend(['-Dkey.alias="%s"' % key_alias])
332   if key_code:
333     ant_cmd.extend(['-Dkey.store.password="%s"' % key_code])
334   if key_alias_code:
335     ant_cmd.extend(['-Dkey.alias.password="%s"' % key_alias_code])
336   ant_result = subprocess.call(ant_cmd)
337   if ant_result != 0:
338     print('Command "%s" exited with non-zero exit code %d'
339           % (' '.join(ant_cmd), ant_result))
340     sys.exit(ant_result)
341
342   src_file = os.path.join(xwalk_dir, name, 'bin', '%s-release.apk' % name)
343   package_name = name
344   if options.app_version:
345     package_name += ('_' + options.app_version)
346   if options.mode == 'shared':
347     dst_file = os.path.join(options.target_dir, '%s.apk' % package_name)
348   elif options.mode == 'embedded':
349     dst_file = os.path.join(options.target_dir,
350                             '%s_%s.apk' % (package_name, options.arch))
351   shutil.copyfile(src_file, dst_file)
352
353
354 def PrintPackageInfo(options, name, packaged_archs):
355   package_name_version = os.path.join(options.target_dir, name)
356   if options.app_version:
357     package_name_version += '_' + options.app_version
358
359   if len(packaged_archs) == 0:
360     print ('A non-platform specific APK for the web application "%s" was '
361            'generated successfully at\n%s.apk. It requires a shared Crosswalk '
362            'Runtime to be present.'
363            % (name, package_name_version))
364     return
365
366   for arch in packaged_archs:
367     print ('An APK for the web application "%s" including the Crosswalk '
368            'Runtime built for %s was generated successfully, which can be '
369            'found at\n%s_%s.apk.'
370            % (name, arch, package_name_version, arch))
371
372   all_archs = set(AllArchitectures())
373
374   if len(packaged_archs) != len(all_archs):
375     missed_archs = all_archs - set(packaged_archs)
376     print ('\n\nWARNING: ')
377     print ('This APK will only work on %s based Android devices. Consider '
378            'building for %s as well.' %
379            (', '.join(packaged_archs), ', '.join(missed_archs)))
380   else:
381     print ('\n\n%d APKs were created for %s devices. '
382            % (len(all_archs), ', '.join(all_archs)))
383     print ('Please install the one that matches the processor architecture '
384            'of your device.\n\n')
385     print ('If you are going to submit this application to an application '
386            'store, please make sure you submit both packages.\nInstructions '
387            'for submitting multiple APKs to Google Play Store are available '
388            'here:\nhttps://software.intel.com/en-us/html5/articles/submitting'
389            '-multiple-crosswalk-apk-to-google-play-store')
390
391 def MakeApk(options, app_info, manifest):
392   Customize(options, app_info, manifest)
393   name = app_info.android_name
394   packaged_archs = []
395   if options.mode == 'shared':
396     Execution(options, name)
397   elif options.mode == 'embedded':
398     # Copy xwalk_core_library into app folder and move the native libraries
399     # out.
400     # When making apk for specified CPU arch, will only include the
401     # corresponding native library by copying it back into xwalk_core_library.
402     target_library_path = os.path.join(xwalk_dir, name, 'xwalk_core_library')
403     shutil.copytree(os.path.join(xwalk_dir, 'xwalk_core_library'),
404                     target_library_path)
405     library_lib_path = os.path.join(target_library_path, 'libs')
406     native_lib_path = os.path.join(xwalk_dir, name, 'native_libs')
407     os.makedirs(native_lib_path)
408     available_archs = []
409     for dir_name in os.listdir(library_lib_path):
410       lib_dir = os.path.join(library_lib_path, dir_name)
411       if ContainsNativeLibrary(lib_dir):
412         shutil.move(lib_dir, os.path.join(native_lib_path, dir_name))
413         available_archs.append(dir_name)
414     if options.arch:
415       Execution(options, name)
416       packaged_archs.append(options.arch)
417     else:
418       # If the arch option is unspecified, all of available platform APKs
419       # will be generated.
420       valid_archs = ['x86', 'armeabi-v7a']
421       for arch in valid_archs:
422         if arch in available_archs:
423           if arch.find('x86') != -1:
424             options.arch = 'x86'
425           elif arch.find('arm') != -1:
426             options.arch = 'arm'
427           Execution(options, name)
428           packaged_archs.append(options.arch)
429         else:
430           print('Warning: failed to create package for arch "%s" '
431                 'due to missing native library' % arch)
432
433       if len(packaged_archs) == 0:
434         print('No packages created, aborting')
435         sys.exit(13)
436
437   PrintPackageInfo(options, name, packaged_archs)
438
439 def main(argv):
440   parser = optparse.OptionParser()
441   parser.add_option('-v', '--version', action='store_true',
442                     dest='version', default=False,
443                     help='The version of this python tool.')
444   parser.add_option('--verbose', action="store_true",
445                     dest='verbose', default=False,
446                     help='Print debug messages.')
447   info = ('The packaging mode of the web application. The value \'shared\' '
448           'means that the runtime is shared across multiple application '
449           'instances and that the runtime needs to be distributed separately. '
450           'The value \'embedded\' means that the runtime is embedded into the '
451           'application itself and distributed along with it.'
452           'Set the default mode as \'embedded\'. For example: --mode=embedded')
453   parser.add_option('--mode', choices=('embedded', 'shared'),
454                     default='embedded', help=info)
455   info = ('The target architecture of the embedded runtime. Supported values '
456           'are \'x86\' and \'arm\'. Note, if undefined, APKs for all possible '
457           'architestures will be generated.')
458   parser.add_option('--arch', choices=AllArchitectures(), help=info)
459   group = optparse.OptionGroup(parser, 'Application Source Options',
460       'This packaging tool supports 3 kinds of web application source: '
461       '1) XPK package; 2) manifest.json; 3) various command line options, '
462       'for example, \'--app-url\' for website, \'--app-root\' and '
463       '\'--app-local-path\' for local web application.')
464   info = ('The path of the XPK package. For example, --xpk=/path/to/xpk/file')
465   group.add_option('--xpk', help=info)
466   info = ('The manifest file with the detail description of the application. '
467           'For example, --manifest=/path/to/your/manifest/file')
468   group.add_option('--manifest', help=info)
469   info = ('The url of application. '
470           'This flag allows to package website as apk. For example, '
471           '--app-url=http://www.intel.com')
472   group.add_option('--app-url', help=info)
473   info = ('The root path of the web app. '
474           'This flag allows to package local web app as apk. For example, '
475           '--app-root=/root/path/of/the/web/app')
476   group.add_option('--app-root', help=info)
477   info = ('The relative path of entry file based on the value from '
478           '\'app_root\'. This flag should work with \'--app-root\' together. '
479           'For example, --app-local-path=/relative/path/of/entry/file')
480   group.add_option('--app-local-path', help=info)
481   parser.add_option_group(group)
482   group = optparse.OptionGroup(parser, 'Mandatory arguments',
483       'They are used for describing the APK information through '
484       'command line options.')
485   info = ('The apk name. For example, --name="Your Application Name"')
486   group.add_option('--name', help=info)
487   info = ('The package name. For example, '
488           '--package=com.example.YourPackage')
489   group.add_option('--package', help=info)
490   parser.add_option_group(group)
491   group = optparse.OptionGroup(parser, 'Optional arguments',
492       'They are used for various settings for applications through '
493       'command line options.')
494   info = ('The version name of the application. '
495           'For example, --app-version=1.0.0')
496   group.add_option('--app-version', help=info)
497   info = ('The version code of the application. '
498           'For example, --app-versionCode=24')
499   group.add_option('--app-versionCode', type='int', help=info)
500   info = ('The version code base of the application. Version code will '
501           'be made by adding a prefix based on architecture to the version '
502           'code base. For example, --app-versionCodeBase=24')
503   group.add_option('--app-versionCodeBase', type='int', help=info)
504   info = ('Use command lines.'
505           'Crosswalk is powered by Chromium and supports Chromium command line.'
506           'For example, '
507           '--xwalk-command-line=\'--chromium-command-1 --xwalk-command-2\'')
508   group.add_option('--xwalk-command-line', default='', help=info)
509   info = ('The description of the application. For example, '
510           '--description=YourApplicationDescription')
511   group.add_option('--description', help=info)
512   group.add_option('--enable-remote-debugging', action='store_true',
513                    dest='enable_remote_debugging', default=False,
514                    help='Enable remote debugging.')
515   info = ('The list of external extension paths splitted by OS separators. '
516           'The separators are \':\' , \';\' and \':\' on Linux, Windows and '
517           'Mac OS respectively. For example, '
518           '--extensions=/path/to/extension1:/path/to/extension2.')
519   group.add_option('--extensions', help=info)
520   group.add_option('-f', '--fullscreen', action='store_true',
521                    dest='fullscreen', default=False,
522                    help='Make application fullscreen.')
523   group.add_option('--keep-screen-on', action='store_true', default=False,
524                    help='Support keeping screen on')
525   info = ('The path of application icon. '
526           'Such as: --icon=/path/to/your/customized/icon')
527   group.add_option('--icon', help=info)
528   info = ('The orientation of the web app\'s display on the device. '
529           'For example, --orientation=landscape. The default value is '
530           '\'unspecified\'. The permitted values are from Android: '
531           'http://developer.android.com/guide/topics/manifest/'
532           'activity-element.html#screen')
533   group.add_option('--orientation', help=info)
534   info = ('The list of permissions to be used by web application. For example, '
535           '--permissions=geolocation:webgl')
536   group.add_option('--permissions', help=info)
537   info = ('Packaging tool will move the output APKS to the target directory')
538   group.add_option('--target-dir', default=os.getcwd(), help=info)
539   parser.add_option_group(group)
540   group = optparse.OptionGroup(parser, 'Keystore Options',
541       'The keystore is a signature from web developer, it\'s used when '
542       'developer wants to distribute the applications.')
543   info = ('The path to the developer keystore. For example, '
544           '--keystore-path=/path/to/your/developer/keystore')
545   group.add_option('--keystore-path', help=info)
546   info = ('The alias name of keystore. For example, --keystore-alias=name')
547   group.add_option('--keystore-alias', help=info)
548   info = ('The passcode of keystore. For example, --keystore-passcode=code')
549   group.add_option('--keystore-passcode', help=info)
550   info = ('Passcode for alias\'s private key in the keystore, '
551           'For example, --keystore-alias-passcode=alias-code')
552   group.add_option('--keystore-alias-passcode', help=info)
553   info = ('Minify and obfuscate javascript and css.'
554           '--compressor: compress javascript and css.'
555           '--compressor=js: compress javascript.'
556           '--compressor=css: compress css.')
557   group.add_option('--compressor', dest='compressor', action='callback',
558                    callback=ParseParameterForCompressor, type='string',
559                    nargs=0, help=info)
560   parser.add_option_group(group)
561   options, _ = parser.parse_args()
562   if len(argv) == 1:
563     parser.print_help()
564     return 0
565
566   if options.version:
567     if os.path.isfile('VERSION'):
568       print(GetVersion('VERSION'))
569       return 0
570     else:
571       parser.error('VERSION was not found, so Crosswalk\'s version could not '
572                    'be determined.')
573
574   xpk_temp_dir = ''
575   if options.xpk:
576     xpk_name = os.path.splitext(os.path.basename(options.xpk))[0]
577     xpk_temp_dir = os.path.join(xwalk_dir, xpk_name + '_xpk')
578     ParseXPK(options, xpk_temp_dir)
579
580   if options.app_root and not options.manifest:
581     manifest_path = os.path.join(options.app_root, 'manifest.json')
582     if os.path.exists(manifest_path):
583       print('Using manifest.json distributed with the application.')
584       options.manifest = manifest_path
585
586   app_info = AppInfo()
587   manifest = None
588   if not options.manifest:
589     # The checks here are really convoluted, but at the moment make_apk
590     # misbehaves any of the following conditions is true.
591     if options.app_url:
592       # 1) --app-url must be passed without either --app-local-path or
593       #    --app-root.
594       if options.app_root or options.app_local_path:
595         parser.error('You must pass either "--app-url" or "--app-local-path" '
596                      'with "--app-root", but not all.')
597     else:
598       # 2) --app-url is not passed but only one of --app-local-path and
599       #    --app-root is set.
600       if bool(options.app_root) != bool(options.app_local_path):
601         parser.error('You must specify both "--app-local-path" and '
602                      '"--app-root".')
603       # 3) None of --app-url, --app-local-path and --app-root are passed.
604       elif not options.app_root and not options.app_local_path:
605         parser.error('You must pass either "--app-url" or "--app-local-path" '
606                      'with "--app-root".')
607
608     if options.permissions:
609       permission_list = options.permissions.split(':')
610     else:
611       print('Warning: all supported permissions on Android port are added. '
612             'Refer to https://github.com/crosswalk-project/'
613             'crosswalk-website/wiki/Crosswalk-manifest')
614       permission_list = permission_mapping_table.keys()
615     options.permissions = HandlePermissionList(permission_list)
616     options.icon_dict = {}
617   else:
618     try:
619       manifest = ParseManifest(options)
620     except SystemExit as ec:
621       return ec.code
622
623   if not options.name:
624     parser.error('An APK name is required. Please use the "--name" option.')
625
626   if not options.package:
627     parser.error('A package name is required. Please use the "--package" '
628                  'option.')
629   VerifyPackageName(options.package)
630
631   if (options.app_root and options.app_local_path and
632       not os.path.isfile(os.path.join(options.app_root,
633                                       options.app_local_path))):
634     print('Please make sure that the local path file of launching app '
635           'does exist.')
636     sys.exit(7)
637
638   if options.target_dir:
639     target_dir = os.path.abspath(os.path.expanduser(options.target_dir))
640     options.target_dir = target_dir
641     if not os.path.isdir(target_dir):
642       os.makedirs(target_dir)
643
644   try:
645     MakeApk(options, app_info, manifest)
646   except SystemExit as ec:
647     CleanDir(app_info.android_name)
648     CleanDir('out')
649     CleanDir(xpk_temp_dir)
650     return ec.code
651   return 0
652
653
654 if __name__ == '__main__':
655   sys.exit(main(sys.argv))