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