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