b1a01634349a6ec6c6d7c87e105124e6ecdc18d5
[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 operator
9 import optparse
10 import os
11 import re
12 import shutil
13 import subprocess
14 import sys
15
16 sys.path.append('scripts/gyp')
17
18 from app_info import AppInfo
19 from customize import VerifyPackageName, CustomizeAll, \
20                       ParseParameterForCompressor
21 from dex import AddExeExtensions
22 from handle_permissions import permission_mapping_table
23 from manifest_json_parser import HandlePermissionList
24 from manifest_json_parser import ManifestJsonParser
25
26
27 def CleanDir(path):
28   if os.path.exists(path):
29     shutil.rmtree(path)
30
31
32 def AllArchitectures():
33   return ("x86", "arm")
34
35
36 def RunCommand(command, verbose=False, shell=False):
37   """Runs the command list, print the output, and propagate its result."""
38   proc = subprocess.Popen(command, stdout=subprocess.PIPE,
39                           stderr=subprocess.STDOUT, shell=shell)
40   if not shell:
41     output = proc.communicate()[0]
42     result = proc.returncode
43     if verbose:
44       print(output.decode("utf-8").strip())
45     if result != 0:
46       print ('Command "%s" exited with non-zero exit code %d'
47              % (' '.join(command), result))
48       sys.exit(result)
49
50
51 def Which(name):
52   """Searches PATH for executable files with the given name, also taking
53   PATHEXT into account. Returns the first existing match, or None if no matches
54   are found."""
55   for path in os.environ.get('PATH', '').split(os.pathsep):
56     for filename in AddExeExtensions(name):
57       full_path = os.path.join(path, filename)
58       if os.path.isfile(full_path) and os.access(full_path, os.X_OK):
59         return full_path
60   return None
61
62
63 def Find(name, path):
64   """Find executable file with the given name
65   and maximum API level under specific path."""
66   result = {}
67   for root, _, files in os.walk(path):
68     if name in files:
69       key = os.path.join(root, name)
70       sdk_version = os.path.basename(os.path.dirname(key))
71       str_num = re.search(r'\d+', sdk_version)
72       if str_num:
73         result[key] = int(str_num.group())
74       else:
75         result[key] = 0
76   if not result:
77     raise Exception()
78   return max(iter(result.items()), key=operator.itemgetter(1))[0]
79
80
81 def GetVersion(path):
82   """Get the version of this python tool."""
83   version_str = 'Crosswalk app packaging tool version is '
84   file_handle = open(path, 'r')
85   src_content = file_handle.read()
86   version_nums = re.findall(r'\d+', src_content)
87   version_str += ('.').join(version_nums)
88   file_handle.close()
89   return version_str
90
91
92 def ParseManifest(options):
93   parser = ManifestJsonParser(os.path.expanduser(options.manifest))
94   if not options.name:
95     options.name = parser.GetAppName()
96   if not options.app_version:
97     options.app_version = parser.GetVersion()
98   if not options.app_versionCode and not options.app_versionCodeBase:
99     options.app_versionCode = 1
100   if parser.GetDescription():
101     options.description = parser.GetDescription()
102   if parser.GetPermissions():
103     options.permissions = parser.GetPermissions()
104   if parser.GetAppUrl():
105     options.app_url = parser.GetAppUrl()
106   elif parser.GetAppLocalPath():
107     options.app_local_path = parser.GetAppLocalPath()
108   else:
109     print('Error: there is no app launch path defined in manifest.json.')
110     sys.exit(9)
111   if parser.GetAppRoot():
112     options.app_root = parser.GetAppRoot()
113     options.icon_dict = parser.GetIcons()
114   if parser.GetOrientation():
115     options.orientation = parser.GetOrientation()
116   if parser.GetFullScreenFlag().lower() == 'true':
117     options.fullscreen = True
118   elif parser.GetFullScreenFlag().lower() == 'false':
119     options.fullscreen = False
120   return parser
121
122
123 def ParseXPK(options, out_dir):
124   cmd = ['python', 'parse_xpk.py',
125          '--file=%s' % os.path.expanduser(options.xpk),
126          '--out=%s' % out_dir]
127   RunCommand(cmd)
128   if options.manifest:
129     print ('Use the manifest from XPK by default '
130            'when "--xpk" option is specified, and '
131            'the "--manifest" option would be ignored.')
132     sys.exit(7)
133
134   if os.path.isfile(os.path.join(out_dir, 'manifest.json')):
135     options.manifest = os.path.join(out_dir, 'manifest.json')
136   else:
137     print('XPK doesn\'t contain manifest file.')
138     sys.exit(8)
139
140
141 def FindExtensionJars(root_path):
142   ''' Find all .jar files for external extensions. '''
143   extension_jars = []
144   if not os.path.exists(root_path):
145     return extension_jars
146
147   for afile in os.listdir(root_path):
148     if os.path.isdir(os.path.join(root_path, afile)):
149       base_name = os.path.basename(afile)
150       extension_jar = os.path.join(root_path, afile, base_name + '.jar')
151       if os.path.isfile(extension_jar):
152         extension_jars.append(extension_jar)
153   return extension_jars
154
155
156 # Follows the recommendation from
157 # http://software.intel.com/en-us/blogs/2012/11/12/how-to-publish-
158 # your-apps-on-google-play-for-x86-based-android-devices-using
159 def MakeVersionCode(options):
160   ''' Construct a version code'''
161   if options.app_versionCode:
162     return options.app_versionCode
163
164   # First digit is ABI, ARM=2, x86=6
165   abi = '0'
166   if options.arch == 'arm':
167     abi = '2'
168   if options.arch == 'x86':
169     abi = '6'
170   b = '0'
171   if options.app_versionCodeBase:
172     b = str(options.app_versionCodeBase)
173     if len(b) > 7:
174       print('Version code base must be 7 digits or less: '
175             'versionCodeBase=%s' % (b))
176       sys.exit(12)
177   # zero pad to 7 digits, middle digits can be used for other
178   # features, according to recommendation in URL
179   return '%s%s' % (abi, b.zfill(7))
180
181
182 def Customize(options, app_info, manifest):
183   app_info.package = options.package
184   app_info.app_name = options.name
185   # 'org.xwalk.my_first_app' => 'MyFirstApp'
186   android_name = options.package.split('.')[-1].split('_')
187   app_info.android_name = ''.join([i.capitalize() for i in android_name if i])
188   if options.app_version:
189     app_info.app_version = options.app_version
190   app_info.app_versionCode = MakeVersionCode(options)
191   if options.app_root:
192     app_info.app_root = os.path.expanduser(options.app_root)
193   if options.enable_remote_debugging:
194     app_info.remote_debugging = '--enable-remote-debugging'
195   if options.fullscreen:
196     app_info.fullscreen_flag = '-f'
197   if options.orientation:
198     app_info.orientation = options.orientation
199   if options.icon:
200     app_info.icon = '%s' % os.path.expanduser(options.icon)
201   CustomizeAll(app_info, options.description, options.icon_dict,
202                options.permissions, options.app_url, options.app_local_path,
203                options.keep_screen_on, options.extensions, manifest,
204                options.xwalk_command_line, options.compressor)
205
206
207 def Execution(options, name):
208   android_path = Which('android')
209   if android_path is None:
210     print('The "android" binary could not be found. Check your Android SDK '
211           'installation and your PATH environment variable.')
212     sys.exit(1)
213
214   sdk_root_path = os.path.dirname(os.path.dirname(android_path))
215
216   try:
217     sdk_jar_path = Find('android.jar',
218                         os.path.join(sdk_root_path, 'platforms'))
219   except Exception:
220     print('Your Android SDK may be ruined, please reinstall it.')
221     sys.exit(2)
222
223   level_string = os.path.basename(os.path.dirname(sdk_jar_path))
224   api_level = int(re.search(r'\d+', level_string).group())
225   if api_level < 14:
226     print('Please install Android API level (>=14) first.')
227     sys.exit(3)
228
229   if options.keystore_path:
230     key_store = os.path.expanduser(options.keystore_path)
231     if options.keystore_alias:
232       key_alias = options.keystore_alias
233     else:
234       print('Please provide an alias name of the developer key.')
235       sys.exit(6)
236     if options.keystore_passcode:
237       key_code = options.keystore_passcode
238     else:
239       print('Please provide the passcode of the developer key.')
240       sys.exit(6)
241     if options.keystore_alias_passcode:
242       key_alias_code = options.keystore_alias_passcode
243     else:
244       print('--keystore-alias-passcode was not specified, '
245             'using the keystore\'s passcode as the alias keystore passcode.')
246       key_alias_code = key_code
247   else:
248     print ('Use xwalk\'s keystore by default for debugging. '
249            'Please switch to your keystore when distributing it to app market.')
250     key_store = 'scripts/ant/xwalk-debug.keystore'
251     key_alias = 'xwalkdebugkey'
252     key_code = 'xwalkdebug'
253     key_alias_code = 'xwalkdebug'
254
255   if not os.path.exists('out'):
256     os.mkdir('out')
257
258   # Make sure to use ant-tasks.jar correctly.
259   # Default Android SDK names it as ant-tasks.jar
260   # Chrome third party Android SDk names it as anttasks.jar
261   ant_tasks_jar_path = os.path.join(sdk_root_path,
262                                     'tools', 'lib', 'ant-tasks.jar')
263   if not os.path.exists(ant_tasks_jar_path):
264     ant_tasks_jar_path = os.path.join(sdk_root_path,
265                                       'tools', 'lib', 'anttasks.jar')
266
267   aapt_path = ''
268   for aapt_str in AddExeExtensions('aapt'):
269     try:
270       aapt_path = Find(aapt_str, sdk_root_path)
271       print('Use %s in %s.' % (aapt_str, sdk_root_path))
272       break
273     except Exception:
274       pass
275   if not aapt_path:
276     print('Your Android SDK may be ruined, please reinstall it.')
277     sys.exit(2)
278
279   # Check whether ant is installed.
280   try:
281     cmd = ['ant', '-version']
282     RunCommand(cmd, shell=True)
283   except EnvironmentError:
284     print('Please install ant first.')
285     sys.exit(4)
286
287   res_dirs = '-DADDITIONAL_RES_DIRS=\'\''
288   res_packages = '-DADDITIONAL_RES_PACKAGES=\'\''
289   res_r_text_files = '-DADDITIONAL_R_TEXT_FILES=\'\''
290   if options.mode == 'embedded':
291     # Prepare the .pak file for embedded mode.
292     pak_src_path = os.path.join('native_libs_res', 'xwalk.pak')
293     pak_des_path = os.path.join(name, 'assets', 'xwalk.pak')
294     shutil.copy(pak_src_path, pak_des_path)
295
296     # Prepare the icudtl.dat for embedded mode.
297     icudtl_src_path = os.path.join('native_libs_res', 'icudtl.dat')
298     icudtl_des_path = os.path.join(name, 'assets', 'icudtl.dat')
299     shutil.copy(icudtl_src_path, icudtl_des_path)
300
301     js_src_dir = os.path.join('native_libs_res', 'jsapi')
302     js_des_dir = os.path.join(name, 'assets', 'jsapi')
303     if os.path.exists(js_des_dir):
304       shutil.rmtree(js_des_dir)
305     shutil.copytree(js_src_dir, js_des_dir)
306
307     res_ui_java = os.path.join('gen', 'ui_java')
308     res_content_java = os.path.join('gen', 'content_java')
309     res_xwalk_java = os.path.join('gen', 'xwalk_core_internal_java')
310     res_dir = os.path.join('libs_res', 'xwalk_core_library')
311     res_dirs = ('-DADDITIONAL_RES_DIRS=' + res_dir)
312     res_packages = ('-DADDITIONAL_RES_PACKAGES=org.chromium.ui '
313                     'org.xwalk.core.internal org.chromium.content')
314     res_r_text_files = ('-DADDITIONAL_R_TEXT_FILES='
315                         + os.path.join(res_ui_java, 'java_R', 'R.txt') + ' '
316                         + os.path.join(res_xwalk_java, 'java_R', 'R.txt') + ' '
317                         + os.path.join(res_content_java, 'java_R', 'R.txt'))
318
319   resource_dir = '-DRESOURCE_DIR=' + os.path.join(name, 'res')
320   manifest_path = os.path.join(name, 'AndroidManifest.xml')
321   cmd = ['python', os.path.join('scripts', 'gyp', 'ant.py'),
322          '-DAAPT_PATH=%s' % aapt_path,
323          res_dirs,
324          res_packages,
325          res_r_text_files,
326          '-DANDROID_MANIFEST=%s' % manifest_path,
327          '-DANDROID_SDK_JAR=%s' % sdk_jar_path,
328          '-DANDROID_SDK_ROOT=%s' % sdk_root_path,
329          '-DANDROID_SDK_VERSION=%d' % api_level,
330          '-DANT_TASKS_JAR=%s' % ant_tasks_jar_path,
331          '-DLIBRARY_MANIFEST_PATHS= ',
332          '-DOUT_DIR=out',
333          resource_dir,
334          '-DSTAMP=codegen.stamp',
335          '-Dbasedir=.',
336          '-buildfile',
337          os.path.join('scripts', 'ant', 'apk-codegen.xml')]
338   RunCommand(cmd, options.verbose)
339
340   # Check whether java is installed.
341   try:
342     cmd = ['java', '-version']
343     RunCommand(cmd, shell=True)
344   except EnvironmentError:
345     print('Please install Oracle JDK first.')
346     sys.exit(5)
347
348   # Compile App source code with app runtime code.
349   cmd = ['python', os.path.join('scripts', 'gyp', 'javac.py'),
350          '--output-dir=%s' % os.path.join('out', 'classes'),
351          '--classpath',
352          os.path.join(os.getcwd(), 'libs', 'xwalk_app_runtime_java.jar'),
353          '--classpath',
354          sdk_jar_path,
355          '--src-dirs',
356          os.path.join(os.getcwd(), name, 'src'),
357          '--src-dirs',
358          os.path.join(os.getcwd(), 'out', 'gen'),
359          '--chromium-code=0',
360          '--stamp=compile.stam']
361   RunCommand(cmd, options.verbose)
362
363   # Package resources.
364   asset_dir = '-DASSET_DIR=%s' % os.path.join(name, 'assets')
365   xml_path = os.path.join('scripts', 'ant', 'apk-package-resources.xml')
366   cmd = ['python', os.path.join('scripts', 'gyp', 'ant.py'),
367          '-DAAPT_PATH=%s' % aapt_path,
368          res_dirs,
369          res_packages,
370          res_r_text_files,
371          '-DANDROID_SDK_JAR=%s' % sdk_jar_path,
372          '-DANDROID_SDK_ROOT=%s' % sdk_root_path,
373          '-DANT_TASKS_JAR=%s' % ant_tasks_jar_path,
374          '-DAPK_NAME=%s' % name,
375          '-DAPP_MANIFEST_VERSION_CODE=0',
376          '-DAPP_MANIFEST_VERSION_NAME=Developer Build',
377          asset_dir,
378          '-DCONFIGURATION_NAME=Release',
379          '-DOUT_DIR=out',
380          resource_dir,
381          '-DSTAMP=package_resources.stamp',
382          '-Dbasedir=.',
383          '-buildfile',
384          xml_path]
385   RunCommand(cmd, options.verbose)
386
387   dex_path = '--dex-path=' + os.path.join(os.getcwd(), 'out', 'classes.dex')
388   app_runtime_jar = os.path.join(os.getcwd(),
389                                  'libs', 'xwalk_app_runtime_java.jar')
390
391   # Check whether external extensions are included.
392   extensions_string = 'xwalk-extensions'
393   extensions_dir = os.path.join(os.getcwd(), name, extensions_string)
394   external_extension_jars = FindExtensionJars(extensions_dir)
395   input_jars = []
396   if options.mode == 'embedded':
397     input_jars.append(os.path.join(os.getcwd(), 'libs',
398                                    'xwalk_runtime_embedded.dex.jar'))
399   dex_command_list = ['python', os.path.join('scripts', 'gyp', 'dex.py'),
400                       dex_path,
401                       '--android-sdk-root=%s' % sdk_root_path,
402                       app_runtime_jar,
403                       os.path.join(os.getcwd(), 'out', 'classes')]
404   dex_command_list.extend(external_extension_jars)
405   dex_command_list.extend(input_jars)
406   RunCommand(dex_command_list)
407
408   src_dir = '-DSOURCE_DIR=' + os.path.join(name, 'src')
409   apk_path = '-DUNSIGNED_APK_PATH=' + os.path.join('out', 'app-unsigned.apk')
410   native_lib_path = '-DNATIVE_LIBS_DIR='
411   if options.mode == 'embedded':
412     if options.arch == 'x86':
413       x86_native_lib_path = os.path.join('native_libs', 'x86', 'libs',
414                                          'x86', 'libxwalkcore.so')
415       if os.path.isfile(x86_native_lib_path):
416         native_lib_path += os.path.join('native_libs', 'x86', 'libs')
417       else:
418         print('No x86 native library has been found for creating a Crosswalk '
419               'embedded APK.')
420         sys.exit(10)
421     elif options.arch == 'arm':
422       arm_native_lib_path = os.path.join('native_libs', 'armeabi-v7a', 'libs',
423                                          'armeabi-v7a', 'libxwalkcore.so')
424       if os.path.isfile(arm_native_lib_path):
425         native_lib_path += os.path.join('native_libs', 'armeabi-v7a', 'libs')
426       else:
427         print('No ARM native library has been found for creating a Crosswalk '
428               'embedded APK.')
429         sys.exit(10)
430   # A space is needed for Windows.
431   native_lib_path += ' '
432   cmd = ['python', 'scripts/gyp/ant.py',
433          '-DANDROID_SDK_ROOT=%s' % sdk_root_path,
434          '-DANT_TASKS_JAR=%s' % ant_tasks_jar_path,
435          '-DAPK_NAME=%s' % name,
436          '-DCONFIGURATION_NAME=Release',
437          native_lib_path,
438          '-DOUT_DIR=out',
439          src_dir,
440          apk_path,
441          '-Dbasedir=.',
442          '-buildfile',
443          'scripts/ant/apk-package.xml']
444   RunCommand(cmd, options.verbose)
445
446   # Find the path of zipalign.
447   # XWALK-2033: zipalign can be in different locations depending on Android
448   # SDK version that used ((eg. /tools, /build-tools/android-4.4W etc),).
449   # So looking up the location of zipalign here instead of hard coding.
450   # Refer to: https://codereview.chromium.org/238253015
451   zipalign_path = ''
452   for zipalign_str in AddExeExtensions('zipalign'):
453     try:
454       zipalign_path = Find(zipalign_str, sdk_root_path)
455       if options.verbose:
456         print('Use %s in %s.' % (zipalign_str, sdk_root_path))
457       break
458     except Exception:
459       pass
460   if not zipalign_path:
461     print('zipalign could not be found in your Android SDK.'
462           ' Make sure it is installed.')
463     sys.exit(10)
464   apk_path = '--unsigned-apk-path=' + os.path.join('out', 'app-unsigned.apk')
465   final_apk_path = '--final-apk-path=' + \
466                    os.path.join('out', name + '.apk')
467   cmd = ['python', 'scripts/gyp/finalize_apk.py',
468          '--zipalign-path=%s' % zipalign_path,
469          apk_path,
470          final_apk_path,
471          '--keystore-path=%s' % key_store,
472          '--keystore-alias=%s' % key_alias,
473          '--keystore-passcode=%s' % key_code,
474          '--keystore-alias-passcode=%s' % key_alias_code]
475   RunCommand(cmd)
476
477   src_file = os.path.join('out', name + '.apk')
478   package_name = name
479   if options.app_version:
480     package_name += ('_' + options.app_version)
481   if options.mode == 'shared':
482     dst_file = os.path.join(options.target_dir, '%s.apk' % package_name)
483   elif options.mode == 'embedded':
484     dst_file = os.path.join(options.target_dir,
485                             '%s_%s.apk' % (package_name, options.arch))
486   shutil.copyfile(src_file, dst_file)
487   CleanDir('out')
488   if options.mode == 'embedded':
489     os.remove(pak_des_path)
490
491
492 def PrintPackageInfo(options, name, packaged_archs):
493   package_name_version = os.path.join(options.target_dir, name)
494   if options.app_version:
495     package_name_version += '_' + options.app_version
496
497   if len(packaged_archs) == 0:
498     print ('A non-platform specific APK for the web application "%s" was '
499            'generated successfully at\n%s.apk. It requires a shared Crosswalk '
500            'Runtime to be present.'
501            % (name, package_name_version))
502     return
503
504   for arch in packaged_archs:
505     print ('An APK for the web application "%s" including the Crosswalk '
506            'Runtime built for %s was generated successfully, which can be '
507            'found at\n%s_%s.apk.'
508            % (name, arch, package_name_version, arch))
509
510   all_archs = set(AllArchitectures())
511
512   if len(packaged_archs) != len(all_archs):
513     missed_archs = all_archs - set(packaged_archs)
514     print ('\n\nWARNING: ')
515     print ('This APK will only work on %s based Android devices. Consider '
516            'building for %s as well.' %
517            (', '.join(packaged_archs), ', '.join(missed_archs)))
518   else:
519     print ('\n\n%d APKs were created for %s devices. '
520            % (len(all_archs), ', '.join(all_archs)))
521     print ('Please install the one that matches the processor architecture '
522            'of your device.\n\n')
523     print ('If you are going to submit this application to an application '
524            'store, please make sure you submit both packages.\nInstructions '
525            'for submitting multiple APKs to Google Play Store are available '
526            'here:\nhttps://software.intel.com/en-us/html5/articles/submitting'
527            '-multiple-crosswalk-apk-to-google-play-store')
528
529 def MakeApk(options, app_info, manifest):
530   Customize(options, app_info, manifest)
531   name = app_info.android_name
532   packaged_archs = []
533   if options.mode == 'shared':
534     Execution(options, name)
535   elif options.mode == 'embedded':
536     if options.arch:
537       Execution(options, name)
538       packaged_archs.append(options.arch)
539     else:
540       # If the arch option is unspecified, all of available platform APKs
541       # will be generated.
542       valid_archs = ['x86', 'armeabi-v7a']
543       for arch in valid_archs:
544         lib_path = os.path.join('native_libs', arch, 'libs',
545                                 arch, 'libxwalkcore.so')
546         if os.path.isfile(lib_path):
547           if arch.find('x86') != -1:
548             options.arch = 'x86'
549           elif arch.find('arm') != -1:
550             options.arch = 'arm'
551           Execution(options, name)
552           packaged_archs.append(options.arch)
553         else:
554           print('Warning: failed to create package for arch "%s" '
555                 'due to missing library %s' %
556                 (arch, lib_path))
557
558       if len(packaged_archs) == 0:
559         print('No packages created, aborting')
560         sys.exit(13)
561
562   PrintPackageInfo(options, name, packaged_archs)
563
564 def main(argv):
565   parser = optparse.OptionParser()
566   parser.add_option('-v', '--version', action='store_true',
567                     dest='version', default=False,
568                     help='The version of this python tool.')
569   parser.add_option('--verbose', action="store_true",
570                     dest='verbose', default=False,
571                     help='Print debug messages.')
572   info = ('The packaging mode of the web application. The value \'shared\' '
573           'means that the runtime is shared across multiple application '
574           'instances and that the runtime needs to be distributed separately. '
575           'The value \'embedded\' means that the runtime is embedded into the '
576           'application itself and distributed along with it.'
577           'Set the default mode as \'embedded\'. For example: --mode=embedded')
578   parser.add_option('--mode', choices=('embedded', 'shared'),
579                     default='embedded', help=info)
580   info = ('The target architecture of the embedded runtime. Supported values '
581           'are \'x86\' and \'arm\'. Note, if undefined, APKs for all possible '
582           'architestures will be generated.')
583   parser.add_option('--arch', choices=AllArchitectures(), help=info)
584   group = optparse.OptionGroup(parser, 'Application Source Options',
585       'This packaging tool supports 3 kinds of web application source: '
586       '1) XPK package; 2) manifest.json; 3) various command line options, '
587       'for example, \'--app-url\' for website, \'--app-root\' and '
588       '\'--app-local-path\' for local web application.')
589   info = ('The path of the XPK package. For example, --xpk=/path/to/xpk/file')
590   group.add_option('--xpk', help=info)
591   info = ('The manifest file with the detail description of the application. '
592           'For example, --manifest=/path/to/your/manifest/file')
593   group.add_option('--manifest', help=info)
594   info = ('The url of application. '
595           'This flag allows to package website as apk. For example, '
596           '--app-url=http://www.intel.com')
597   group.add_option('--app-url', help=info)
598   info = ('The root path of the web app. '
599           'This flag allows to package local web app as apk. For example, '
600           '--app-root=/root/path/of/the/web/app')
601   group.add_option('--app-root', help=info)
602   info = ('The relative path of entry file based on the value from '
603           '\'app_root\'. This flag should work with \'--app-root\' together. '
604           'For example, --app-local-path=/relative/path/of/entry/file')
605   group.add_option('--app-local-path', help=info)
606   parser.add_option_group(group)
607   group = optparse.OptionGroup(parser, 'Mandatory arguments',
608       'They are used for describing the APK information through '
609       'command line options.')
610   info = ('The apk name. For example, --name="Your Application Name"')
611   group.add_option('--name', help=info)
612   info = ('The package name. For example, '
613           '--package=com.example.YourPackage')
614   group.add_option('--package', help=info)
615   parser.add_option_group(group)
616   group = optparse.OptionGroup(parser, 'Optional arguments',
617       'They are used for various settings for applications through '
618       'command line options.')
619   info = ('The version name of the application. '
620           'For example, --app-version=1.0.0')
621   group.add_option('--app-version', help=info)
622   info = ('The version code of the application. '
623           'For example, --app-versionCode=24')
624   group.add_option('--app-versionCode', type='int', help=info)
625   info = ('The version code base of the application. Version code will '
626           'be made by adding a prefix based on architecture to the version '
627           'code base. For example, --app-versionCodeBase=24')
628   group.add_option('--app-versionCodeBase', type='int', help=info)
629   info = ('Use command lines.'
630           'Crosswalk is powered by Chromium and supports Chromium command line.'
631           'For example, '
632           '--xwalk-command-line=\'--chromium-command-1 --xwalk-command-2\'')
633   group.add_option('--xwalk-command-line', default='', help=info)
634   info = ('The description of the application. For example, '
635           '--description=YourApplicationDescription')
636   group.add_option('--description', help=info)
637   group.add_option('--enable-remote-debugging', action='store_true',
638                    dest='enable_remote_debugging', default=False,
639                    help='Enable remote debugging.')
640   info = ('The list of external extension paths splitted by OS separators. '
641           'The separators are \':\' , \';\' and \':\' on Linux, Windows and '
642           'Mac OS respectively. For example, '
643           '--extensions=/path/to/extension1:/path/to/extension2.')
644   group.add_option('--extensions', help=info)
645   group.add_option('-f', '--fullscreen', action='store_true',
646                    dest='fullscreen', default=False,
647                    help='Make application fullscreen.')
648   group.add_option('--keep-screen-on', action='store_true', default=False,
649                    help='Support keeping screen on')
650   info = ('The path of application icon. '
651           'Such as: --icon=/path/to/your/customized/icon')
652   group.add_option('--icon', help=info)
653   info = ('The orientation of the web app\'s display on the device. '
654           'For example, --orientation=landscape. The default value is '
655           '\'unspecified\'. The permitted values are from Android: '
656           'http://developer.android.com/guide/topics/manifest/'
657           'activity-element.html#screen')
658   group.add_option('--orientation', help=info)
659   info = ('The list of permissions to be used by web application. For example, '
660           '--permissions=geolocation:webgl')
661   group.add_option('--permissions', help=info)
662   info = ('Packaging tool will move the output APKS to the target directory')
663   group.add_option('--target-dir', default=os.getcwd(), help=info)
664   parser.add_option_group(group)
665   group = optparse.OptionGroup(parser, 'Keystore Options',
666       'The keystore is a signature from web developer, it\'s used when '
667       'developer wants to distribute the applications.')
668   info = ('The path to the developer keystore. For example, '
669           '--keystore-path=/path/to/your/developer/keystore')
670   group.add_option('--keystore-path', help=info)
671   info = ('The alias name of keystore. For example, --keystore-alias=name')
672   group.add_option('--keystore-alias', help=info)
673   info = ('The passcode of keystore. For example, --keystore-passcode=code')
674   group.add_option('--keystore-passcode', help=info)
675   info = ('Passcode for alias\'s private key in the keystore, '
676           'For example, --keystore-alias-passcode=alias-code')
677   group.add_option('--keystore-alias-passcode', help=info)
678   info = ('Minify and obfuscate javascript and css.'
679           '--compressor: compress javascript and css.'
680           '--compressor=js: compress javascript.'
681           '--compressor=css: compress css.')
682   group.add_option('--compressor', dest='compressor', action='callback',
683                    callback=ParseParameterForCompressor, type='string',
684                    nargs=0, help=info)
685   parser.add_option_group(group)
686   options, _ = parser.parse_args()
687   if len(argv) == 1:
688     parser.print_help()
689     return 0
690
691   if options.version:
692     if os.path.isfile('VERSION'):
693       print(GetVersion('VERSION'))
694       return 0
695     else:
696       parser.error('VERSION was not found, so Crosswalk\'s version could not '
697                    'be determined.')
698
699   xpk_temp_dir = ''
700   if options.xpk:
701     xpk_name = os.path.splitext(os.path.basename(options.xpk))[0]
702     xpk_temp_dir = xpk_name + '_xpk'
703     ParseXPK(options, xpk_temp_dir)
704
705   if options.app_root and not options.manifest:
706     manifest_path = os.path.join(options.app_root, 'manifest.json')
707     if os.path.exists(manifest_path):
708       print('Using manifest.json distributed with the application.')
709       options.manifest = manifest_path
710
711   app_info = AppInfo()
712   manifest = None
713   if not options.manifest:
714     # The checks here are really convoluted, but at the moment make_apk
715     # misbehaves any of the following conditions is true.
716     if options.app_url:
717       # 1) --app-url must be passed without either --app-local-path or
718       #    --app-root.
719       if options.app_root or options.app_local_path:
720         parser.error('You must pass either "--app-url" or "--app-local-path" '
721                      'with "--app-root", but not all.')
722     else:
723       # 2) --app-url is not passed but only one of --app-local-path and
724       #    --app-root is set.
725       if bool(options.app_root) != bool(options.app_local_path):
726         parser.error('You must specify both "--app-local-path" and '
727                      '"--app-root".')
728       # 3) None of --app-url, --app-local-path and --app-root are passed.
729       elif not options.app_root and not options.app_local_path:
730         parser.error('You must pass either "--app-url" or "--app-local-path" '
731                      'with "--app-root".')
732
733     if options.permissions:
734       permission_list = options.permissions.split(':')
735     else:
736       print('Warning: all supported permissions on Android port are added. '
737             'Refer to https://github.com/crosswalk-project/'
738             'crosswalk-website/wiki/Crosswalk-manifest')
739       permission_list = permission_mapping_table.keys()
740     options.permissions = HandlePermissionList(permission_list)
741     options.icon_dict = {}
742   else:
743     try:
744       manifest = ParseManifest(options)
745     except SystemExit as ec:
746       return ec.code
747
748   if not options.name:
749     parser.error('An APK name is required. Please use the "--name" option.')
750
751   if not options.package:
752     parser.error('A package name is required. Please use the "--package" '
753                  'option.')
754   VerifyPackageName(options.package)
755
756   if (options.app_root and options.app_local_path and
757       not os.path.isfile(os.path.join(options.app_root,
758                                       options.app_local_path))):
759     print('Please make sure that the local path file of launching app '
760           'does exist.')
761     sys.exit(7)
762
763   if options.target_dir:
764     target_dir = os.path.abspath(os.path.expanduser(options.target_dir))
765     options.target_dir = target_dir
766     if not os.path.isdir(target_dir):
767       os.makedirs(target_dir)
768
769   try:
770     MakeApk(options, app_info, manifest)
771   except SystemExit as ec:
772     CleanDir(app_info.android_name)
773     CleanDir('out')
774     CleanDir(xpk_temp_dir)
775     return ec.code
776   return 0
777
778
779 if __name__ == '__main__':
780   sys.exit(main(sys.argv))