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