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