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