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