# found in the LICENSE file.
# pylint: disable=F0401
-import compress_js_and_css
-import operator
+import json
import optparse
import os
import re
import shutil
import subprocess
import sys
+import tempfile
-sys.path.append('scripts/gyp')
-from customize import ReplaceInvalidChars
-from dex import AddExeExtensions
+# get xwalk absolute path so we can run this script from any location
+xwalk_dir = os.path.dirname(os.path.abspath(__file__))
+sys.path.append(xwalk_dir)
+
+from app_info import AppInfo
+from customize import VerifyPackageName, CustomizeAll, \
+ ParseParameterForCompressor
+from extension_manager import GetExtensionList, GetExtensionStatus
from handle_permissions import permission_mapping_table
+from util import AllArchitectures, CleanDir, GetVersion, RunCommand, \
+ CreateAndCopyDir
from manifest_json_parser import HandlePermissionList
from manifest_json_parser import ManifestJsonParser
-def CleanDir(path):
- if os.path.exists(path):
- shutil.rmtree(path)
+NATIVE_LIBRARY = 'libxwalkcore.so'
-def RunCommand(command, verbose=False, shell=False):
- """Runs the command list, print the output, and propagate its result."""
- proc = subprocess.Popen(command, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT, shell=shell)
- if not shell:
- output = proc.communicate()[0]
- result = proc.returncode
- if verbose:
- print(output.decode("utf-8").strip())
- if result != 0:
- print ('Command "%s" exited with non-zero exit code %d'
- % (' '.join(command), result))
- sys.exit(result)
+def ConvertArchNameToArchFolder(arch):
+ arch_dict = {
+ 'x86': 'x86',
+ 'arm': 'armeabi-v7a'
+ }
+ return arch_dict.get(arch, None)
-def Which(name):
- """Search PATH for executable files with the given name."""
+
+def AddExeExtensions(name):
+ exts_str = os.environ.get('PATHEXT', '').lower()
+ exts = [_f for _f in exts_str.split(os.pathsep) if _f]
result = []
- exts = [_f for _f in os.environ.get('PATHEXT', '').split(os.pathsep) if _f]
- path = os.environ.get('PATH', None)
- if path is None:
- return []
- for p in os.environ.get('PATH', '').split(os.pathsep):
- p = os.path.join(p, name)
- if os.access(p, os.X_OK):
- result.append(p)
- for e in exts:
- pext = p + e
- if os.access(pext, os.X_OK):
- result.append(pext)
+ for e in exts:
+ result.append(name + e)
+ result.append(name)
return result
-def Find(name, path):
- """Find executable file with the given name
- and maximum API level under specific path."""
- result = {}
- for root, _, files in os.walk(path):
- if name in files:
- key = os.path.join(root, name)
- sdk_version = os.path.basename(os.path.dirname(key))
- str_num = re.search(r'\d+', sdk_version)
- if str_num:
- result[key] = int(str_num.group())
- else:
- result[key] = 0
- if not result:
- raise Exception()
- return max(iter(result.items()), key=operator.itemgetter(1))[0]
+def Which(name):
+ """Searches PATH for executable files with the given name, also taking
+ PATHEXT into account. Returns the first existing match, or None if no matches
+ are found."""
+ for path in os.environ.get('PATH', '').split(os.pathsep):
+ for filename in AddExeExtensions(name):
+ full_path = os.path.join(path, filename)
+ if os.path.isfile(full_path) and os.access(full_path, os.X_OK):
+ return full_path
+ return None
-def GetVersion(path):
- """Get the version of this python tool."""
- version_str = 'Crosswalk app packaging tool version is '
- file_handle = open(path, 'r')
- src_content = file_handle.read()
- version_nums = re.findall(r'\d+', src_content)
- version_str += ('.').join(version_nums)
- file_handle.close()
- return version_str
+def GetAndroidApiLevel(android_path):
+ """Get Highest Android target level installed.
+ return -1 if no targets have been found.
+ """
+ target_output = RunCommand([android_path, 'list', 'target', '-c'])
+ target_regex = re.compile(r'android-(\d+)')
+ targets = [int(i) for i in target_regex.findall(target_output)]
+ targets.extend([-1])
+ return max(targets)
+
+
+def ContainsNativeLibrary(path):
+ return os.path.isfile(os.path.join(path, NATIVE_LIBRARY))
def ParseManifest(options):
parser = ManifestJsonParser(os.path.expanduser(options.manifest))
- if not options.package:
- options.package = 'org.xwalk.' + parser.GetAppName().lower()
if not options.name:
options.name = parser.GetAppName()
if not options.app_version:
sys.exit(9)
if parser.GetAppRoot():
options.app_root = parser.GetAppRoot()
- temp_dict = parser.GetIcons()
- try:
- icon_dict = dict((int(k), v) for k, v in temp_dict.items())
- except ValueError:
- print('The key of icon in the manifest file should be a number.')
- # TODO(junmin): add multiple icons support.
- if icon_dict:
- icon_file = max(iter(icon_dict.items()), key=operator.itemgetter(0))[1]
- options.icon = os.path.join(options.app_root, icon_file)
+ options.icon_dict = parser.GetIcons()
+ if parser.GetOrientation():
+ options.orientation = parser.GetOrientation()
if parser.GetFullScreenFlag().lower() == 'true':
options.fullscreen = True
elif parser.GetFullScreenFlag().lower() == 'false':
options.fullscreen = False
- if parser.GetLaunchScreenImg():
- options.launch_screen_img = os.path.join(options.app_root,
- parser.GetLaunchScreenImg())
+ return parser
def ParseXPK(options, out_dir):
- cmd = ['python', 'parse_xpk.py',
+ cmd = ['python', os.path.join(xwalk_dir, 'parse_xpk.py'),
'--file=%s' % os.path.expanduser(options.xpk),
'--out=%s' % out_dir]
RunCommand(cmd)
extension_jars.append(extension_jar)
return extension_jars
+
# Follows the recommendation from
# http://software.intel.com/en-us/blogs/2012/11/12/how-to-publish-
# your-apps-on-google-play-for-x86-based-android-devices-using
def MakeVersionCode(options):
''' Construct a version code'''
if options.app_versionCode:
- return '--app-versionCode=%s' % options.app_versionCode
+ return options.app_versionCode
# First digit is ABI, ARM=2, x86=6
abi = '0'
sys.exit(12)
# zero pad to 7 digits, middle digits can be used for other
# features, according to recommendation in URL
- return '--app-versionCode=%s%s' % (abi, b.zfill(7))
+ return '%s%s' % (abi, b.zfill(7))
+
+
+def GetExtensionBinaryPathList():
+ local_extension_list = []
+ extensions_path = os.path.join(os.getcwd(), "extensions")
+ exist_extension_list = GetExtensionList(extensions_path)
+ for item in exist_extension_list:
+ build_json_path = os.path.join(extensions_path, item, "build.json")
+ with open(build_json_path) as fd:
+ data = json.load(fd)
+ if not GetExtensionStatus(item, extensions_path):
+ continue
+ else:
+ if data.get("binary_path", False):
+ extension_binary_path = os.path.join(extensions_path,
+ item,
+ data["binary_path"])
+ else:
+ print("The extension \"%s\" doesn't exists." % item)
+ sys.exit(1)
+ if os.path.isdir(extension_binary_path):
+ local_extension_list.append(extension_binary_path)
+ else:
+ print("The extension \"%s\" doesn't exists." % item)
+ sys.exit(1)
+ return local_extension_list
-def Customize(options):
- package = '--package=org.xwalk.app.template'
- if options.package:
- package = '--package=%s' % options.package
- name = '--name=AppTemplate'
- if options.name:
- name = '--name=%s' % options.name
- app_version = '--app-version=1.0.0'
+
+def Customize(options, app_info, manifest):
+ app_info.package = options.package
+ app_info.app_name = options.name
+ # 'org.xwalk.my_first_app' => 'MyFirstApp'
+ android_name = options.package.split('.')[-1].split('_')
+ app_info.android_name = ''.join([i.capitalize() for i in android_name if i])
if options.app_version:
- app_version = '--app-version=%s' % options.app_version
- app_versionCode = MakeVersionCode(options)
- description = ''
- if options.description:
- description = '--description=%s' % options.description
- permissions = ''
- if options.permissions:
- permissions = '--permissions=%s' % options.permissions
- icon = ''
- if options.icon:
- icon = '--icon=%s' % os.path.expanduser(options.icon)
- app_url = ''
- if options.app_url:
- app_url = '--app-url=%s' % options.app_url
- app_root = ''
+ app_info.app_version = options.app_version
+ app_info.app_versionCode = MakeVersionCode(options)
if options.app_root:
- app_root = '--app-root=%s' % os.path.expanduser(options.app_root)
- app_local_path = ''
- if options.app_local_path:
- app_local_path = '--app-local-path=%s' % options.app_local_path
- remote_debugging = ''
+ app_info.app_root = os.path.expanduser(options.app_root)
if options.enable_remote_debugging:
- remote_debugging = '--enable-remote-debugging'
- fullscreen_flag = ''
+ app_info.remote_debugging = '--enable-remote-debugging'
+ if options.use_animatable_view:
+ app_info.use_animatable_view = '--use-animatable-view'
if options.fullscreen:
- fullscreen_flag = '-f'
- extensions_list = ''
- if options.extensions:
- extensions_list = '--extensions=%s' % options.extensions
- orientation = '--orientation=unspecified'
+ app_info.fullscreen_flag = '-f'
if options.orientation:
- orientation = '--orientation=%s' % options.orientation
- default_image = ''
- if options.launch_screen_img:
- default_image = '--launch-screen-img=' + options.launch_screen_img
- cmd = ['python', 'customize.py', package,
- name, app_version, app_versionCode, description, icon, permissions,
- app_url, remote_debugging, app_root, app_local_path, fullscreen_flag,
- extensions_list, orientation, default_image]
- RunCommand(cmd, options.verbose)
-
-
-def Execution(options, sanitized_name):
- android_path_array = Which('android')
- if not android_path_array:
- print('Please install Android SDK first.')
- sys.exit(1)
+ app_info.orientation = options.orientation
+ if options.icon:
+ app_info.icon = '%s' % os.path.expanduser(options.icon)
- sdk_root_path = os.path.dirname(os.path.dirname(android_path_array[0]))
+ #Add local extensions to extension list.
+ extension_binary_path_list = GetExtensionBinaryPathList()
+ if len(extension_binary_path_list) > 0:
+ if options.extensions is None:
+ options.extensions = ""
+ else:
+ options.extensions += os.pathsep
+
+ for item in extension_binary_path_list:
+ options.extensions += item
+ options.extensions += os.pathsep
+ #trim final path separator
+ options.extensions = options.extensions[0:-1]
+
+ CustomizeAll(app_info, options.description, options.icon_dict,
+ options.permissions, options.app_url, options.app_local_path,
+ options.keep_screen_on, options.extensions, manifest,
+ options.xwalk_command_line, options.compressor)
- try:
- sdk_jar_path = Find('android.jar', os.path.join(sdk_root_path, 'platforms'))
- except Exception:
- print('Your Android SDK may be ruined, please reinstall it.')
- sys.exit(2)
-
- level_string = os.path.basename(os.path.dirname(sdk_jar_path))
- api_level = int(re.search(r'\d+', level_string).group())
- if api_level < 14:
- print('Please install Android API level (>=14) first.')
- sys.exit(3)
+def Execution(options, name):
+ arch_string = (' ('+options.arch+')' if options.arch else '')
+ print('\nStarting application build' + arch_string)
+ app_dir = os.path.join(tempfile.gettempdir(), name)
+ android_path = Which('android')
+ api_level = GetAndroidApiLevel(android_path)
+ target_string = 'android-%d' % api_level
+
+ print (' * Checking keystore for signing')
if options.keystore_path:
key_store = os.path.expanduser(options.keystore_path)
if options.keystore_alias:
if options.keystore_passcode:
key_code = options.keystore_passcode
else:
- print('Please provide the passcode of the developer key.')
- sys.exit(6)
+ key_code = None
+ if options.keystore_alias_passcode:
+ key_alias_code = options.keystore_alias_passcode
+ else:
+ key_alias_code = None
else:
- print ('Use xwalk\'s keystore by default for debugging. '
- 'Please switch to your keystore when distributing it to app market.')
- key_store = 'scripts/ant/xwalk-debug.keystore'
+ print(' No keystore provided for signing. Using xwalk\'s keystore '
+ 'for debugging.\n Please use a valid keystore when '
+ 'distributing to the app market.')
+ key_store = os.path.join(xwalk_dir, 'xwalk-debug.keystore')
key_alias = 'xwalkdebugkey'
key_code = 'xwalkdebug'
+ key_alias_code = 'xwalkdebug'
- if not os.path.exists('out'):
- os.mkdir('out')
-
- # Make sure to use ant-tasks.jar correctly.
- # Default Android SDK names it as ant-tasks.jar
- # Chrome third party Android SDk names it as anttasks.jar
- ant_tasks_jar_path = os.path.join(sdk_root_path,
- 'tools', 'lib', 'ant-tasks.jar')
- if not os.path.exists(ant_tasks_jar_path):
- ant_tasks_jar_path = os.path.join(sdk_root_path,
- 'tools', 'lib' ,'anttasks.jar')
-
- aapt_path = ''
- for aapt_str in AddExeExtensions('aapt'):
- try:
- aapt_path = Find(aapt_str, sdk_root_path)
- print('Use %s in %s.' % (aapt_str, sdk_root_path))
- break
- except Exception:
- print('There doesn\'t exist %s in %s.' % (aapt_str, sdk_root_path))
- if not aapt_path:
- print('Your Android SDK may be ruined, please reinstall it.')
- sys.exit(2)
-
- # Check whether ant is installed.
- try:
- cmd = ['ant', '-version']
- RunCommand(cmd, shell=True)
- except EnvironmentError:
- print('Please install ant first.')
- sys.exit(4)
-
- res_dirs = '-DADDITIONAL_RES_DIRS=\'\''
- res_packages = '-DADDITIONAL_RES_PACKAGES=\'\''
- res_r_text_files = '-DADDITIONAL_R_TEXT_FILES=\'\''
+ # Update android project for app and xwalk_core_library.
+ update_project_cmd = [android_path, 'update', 'project',
+ '--path', app_dir,
+ '--target', target_string,
+ '--name', name]
if options.mode == 'embedded':
- # Prepare the .pak file for embedded mode.
- pak_src_path = os.path.join('native_libs_res', 'xwalk.pak')
- pak_des_path = os.path.join(sanitized_name, 'assets', 'xwalk.pak')
- shutil.copy(pak_src_path, pak_des_path)
-
- js_src_dir = os.path.join('native_libs_res', 'jsapi')
- js_des_dir = os.path.join(sanitized_name, 'assets', 'jsapi')
- if os.path.exists(js_des_dir):
- shutil.rmtree(js_des_dir)
- shutil.copytree(js_src_dir, js_des_dir)
-
- res_ui_java = os.path.join('gen', 'ui_java')
- res_content_java = os.path.join('gen', 'content_java')
- res_xwalk_java = os.path.join('gen', 'xwalk_core_java')
- res_dirs = ('-DADDITIONAL_RES_DIRS='
- + os.path.join(res_ui_java, 'res_crunched') + ' '
- + os.path.join(res_ui_java, 'res_v14_compatibility') + ' '
- + os.path.join(res_ui_java, 'res_grit') + ' '
- + os.path.join('libs_res', 'ui') + ' '
- + os.path.join(res_content_java, 'res_crunched') + ' '
- + os.path.join(res_content_java, 'res_v14_compatibility') + ' '
- + os.path.join('libs_res', 'content') + ' '
- + os.path.join(res_content_java, 'res_grit') + ' '
- + os.path.join(res_xwalk_java, 'res_crunched') + ' '
- + os.path.join(res_xwalk_java, 'res_v14_compatibility') + ' '
- + os.path.join('libs_res', 'runtime') + ' '
- + os.path.join(res_xwalk_java, 'res_grit'))
- res_packages = ('-DADDITIONAL_RES_PACKAGES=org.chromium.ui '
- 'org.xwalk.core org.chromium.content')
- res_r_text_files = ('-DADDITIONAL_R_TEXT_FILES='
- + os.path.join(res_ui_java, 'java_R', 'R.txt') + ' '
- + os.path.join(res_xwalk_java, 'java_R', 'R.txt') + ' '
- + os.path.join(res_content_java, 'java_R', 'R.txt'))
-
- resource_dir = '-DRESOURCE_DIR=' + os.path.join(sanitized_name, 'res')
- manifest_path = os.path.join(sanitized_name, 'AndroidManifest.xml')
- cmd = ['python', os.path.join('scripts', 'gyp', 'ant.py'),
- '-DAAPT_PATH=%s' % aapt_path,
- res_dirs,
- res_packages,
- res_r_text_files,
- '-DANDROID_MANIFEST=%s' % manifest_path,
- '-DANDROID_SDK_JAR=%s' % sdk_jar_path,
- '-DANDROID_SDK_ROOT=%s' % sdk_root_path,
- '-DANDROID_SDK_VERSION=%d' % api_level,
- '-DANT_TASKS_JAR=%s' % ant_tasks_jar_path,
- '-DLIBRARY_MANIFEST_PATHS= ',
- '-DOUT_DIR=out',
- resource_dir,
- '-DSTAMP=codegen.stamp',
- '-Dbasedir=.',
- '-buildfile',
- os.path.join('scripts', 'ant', 'apk-codegen.xml')]
- RunCommand(cmd, options.verbose)
-
- # Check whether java is installed.
- try:
- cmd = ['java', '-version']
- RunCommand(cmd, shell=True)
- except EnvironmentError:
- print('Please install Oracle JDK first.')
- sys.exit(5)
-
- # Compile App source code with app runtime code.
- classpath = '--classpath='
- classpath += os.path.join(os.getcwd(), 'libs',
- 'xwalk_app_runtime_java.jar')
- classpath += ' ' + sdk_jar_path
- src_dirs = '--src-dirs=' + os.path.join(os.getcwd(), sanitized_name, 'src') +\
- ' ' + os.path.join(os.getcwd(), 'out', 'gen')
- cmd = ['python', os.path.join('scripts', 'gyp', 'javac.py'),
- '--output-dir=%s' % os.path.join('out', 'classes'),
- classpath,
- src_dirs,
- '--javac-includes=',
- '--chromium-code=0',
- '--stamp=compile.stam']
- RunCommand(cmd)
-
- # Package resources.
- asset_dir = '-DASSET_DIR=%s' % os.path.join(sanitized_name, 'assets')
- xml_path = os.path.join('scripts', 'ant', 'apk-package-resources.xml')
- cmd = ['python', os.path.join('scripts', 'gyp', 'ant.py'),
- '-DAAPT_PATH=%s' % aapt_path,
- res_dirs,
- res_packages,
- res_r_text_files,
- '-DANDROID_SDK_JAR=%s' % sdk_jar_path,
- '-DANDROID_SDK_ROOT=%s' % sdk_root_path,
- '-DANT_TASKS_JAR=%s' % ant_tasks_jar_path,
- '-DAPK_NAME=%s' % sanitized_name,
- '-DAPP_MANIFEST_VERSION_CODE=0',
- '-DAPP_MANIFEST_VERSION_NAME=Developer Build',
- asset_dir,
- '-DCONFIGURATION_NAME=Release',
- '-DOUT_DIR=out',
- resource_dir,
- '-DSTAMP=package_resources.stamp',
- '-Dbasedir=.',
- '-buildfile',
- xml_path]
- RunCommand(cmd, options.verbose)
-
- dex_path = '--dex-path=' + os.path.join(os.getcwd(), 'out', 'classes.dex')
- app_runtime_jar = os.path.join(os.getcwd(),
- 'libs', 'xwalk_app_runtime_java.jar')
+ print(' * Updating project with xwalk_core_library')
+ RunCommand([android_path, 'update', 'lib-project',
+ '--path', os.path.join(app_dir, 'xwalk_core_library'),
+ '--target', target_string])
+ update_project_cmd.extend(['-l', 'xwalk_core_library'])
+ else:
+ print(' * Updating project')
+ RunCommand(update_project_cmd)
# Check whether external extensions are included.
+ print(' * Checking for external extensions')
extensions_string = 'xwalk-extensions'
- extensions_dir = os.path.join(os.getcwd(), sanitized_name, extensions_string)
+ extensions_dir = os.path.join(app_dir, extensions_string)
external_extension_jars = FindExtensionJars(extensions_dir)
- input_jars = []
- if options.mode == 'embedded':
- input_jars.append(os.path.join(os.getcwd(), 'libs',
- 'xwalk_runtime_embedded.dex.jar'))
- dex_command_list = ['python', os.path.join('scripts', 'gyp', 'dex.py'),
- dex_path,
- '--android-sdk-root=%s' % sdk_root_path,
- app_runtime_jar,
- os.path.join(os.getcwd(), 'out', 'classes')]
- dex_command_list.extend(external_extension_jars)
- dex_command_list.extend(input_jars)
- RunCommand(dex_command_list)
-
- src_dir = '-DSOURCE_DIR=' + os.path.join(sanitized_name, 'src')
- apk_path = '-DUNSIGNED_APK_PATH=' + os.path.join('out', 'app-unsigned.apk')
- native_lib_path = '-DNATIVE_LIBS_DIR='
+ for external_extension_jar in external_extension_jars:
+ shutil.copyfile(external_extension_jar,
+ os.path.join(app_dir, 'libs',
+ os.path.basename(external_extension_jar)))
+
if options.mode == 'embedded':
- if options.arch == 'x86':
- x86_native_lib_path = os.path.join('native_libs', 'x86', 'libs',
- 'x86', 'libxwalkcore.so')
- if os.path.isfile(x86_native_lib_path):
- native_lib_path += os.path.join('native_libs', 'x86', 'libs')
- else:
- print('Missing x86 native library for Crosswalk embedded APK. Abort!')
- sys.exit(10)
- elif options.arch == 'arm':
- arm_native_lib_path = os.path.join('native_libs', 'armeabi-v7a', 'libs',
- 'armeabi-v7a', 'libxwalkcore.so')
- if os.path.isfile(arm_native_lib_path):
- native_lib_path += os.path.join('native_libs', 'armeabi-v7a', 'libs')
- else:
- print('Missing ARM native library for Crosswalk embedded APK. Abort!')
- sys.exit(10)
- # A space is needed for Windows.
- native_lib_path += ' '
- cmd = ['python', 'scripts/gyp/ant.py',
- '-DANDROID_SDK_ROOT=%s' % sdk_root_path,
- '-DANT_TASKS_JAR=%s' % ant_tasks_jar_path,
- '-DAPK_NAME=%s' % sanitized_name,
- '-DCONFIGURATION_NAME=Release',
- native_lib_path,
- '-DOUT_DIR=out',
- src_dir,
- apk_path,
- '-Dbasedir=.',
- '-buildfile',
- 'scripts/ant/apk-package.xml']
- RunCommand(cmd, options.verbose)
-
- apk_path = '--unsigned-apk-path=' + os.path.join('out', 'app-unsigned.apk')
- final_apk_path = '--final-apk-path=' + \
- os.path.join('out', sanitized_name + '.apk')
- cmd = ['python', 'scripts/gyp/finalize_apk.py',
- '--android-sdk-root=%s' % sdk_root_path,
- apk_path,
- final_apk_path,
- '--keystore-path=%s' % key_store,
- '--keystore-alias=%s' % key_alias,
- '--keystore-passcode=%s' % key_code]
- RunCommand(cmd)
+ print (' * Copying native libraries for %s' % options.arch)
+ # Remove existing native libraries in xwalk_core_library, they are probably
+ # for the last execution to make apk for another CPU arch.
+ # And then copy the native libraries for the specified arch into
+ # xwalk_core_library.
+ arch = ConvertArchNameToArchFolder(options.arch)
+ if not arch:
+ print ('Invalid CPU arch: %s.' % arch)
+ sys.exit(10)
+ library_lib_path = os.path.join(app_dir, 'xwalk_core_library', 'libs')
+ for dir_name in os.listdir(library_lib_path):
+ lib_dir = os.path.join(library_lib_path, dir_name)
+ if ContainsNativeLibrary(lib_dir):
+ shutil.rmtree(lib_dir)
+ native_lib_path = os.path.join(app_dir, 'native_libs', arch)
+ if ContainsNativeLibrary(native_lib_path):
+ shutil.copytree(native_lib_path, os.path.join(library_lib_path, arch))
+ else:
+ print('No %s native library has been found for creating a Crosswalk '
+ 'embedded APK.' % arch)
+ sys.exit(10)
- src_file = os.path.join('out', sanitized_name + '.apk')
- package_name = options.name
+ if options.project_only:
+ print (' (Skipping apk package creation)')
+ return
+
+ # Build the APK
+ if options.mode == 'embedded':
+ print(' * Building Android apk package with Crosswalk embedded' +
+ arch_string)
+ else:
+ print(' * Building Android apk package')
+ ant_path = Which('ant')
+ ant_cmd = [ant_path, 'release', '-f', os.path.join(app_dir, 'build.xml')]
+ ant_cmd.extend(['-Dkey.store=%s' % os.path.abspath(key_store)])
+ ant_cmd.extend(['-Dkey.alias=%s' % key_alias])
+ if key_code:
+ ant_cmd.extend(['-Dkey.store.password=%s' % key_code])
+ if key_alias_code:
+ ant_cmd.extend(['-Dkey.alias.password=%s' % key_alias_code])
+
+ cmd_display = ' '.join([str(item) for item in ant_cmd])
+ if options.verbose:
+ print('Executing:\n %s\n' % cmd_display)
+ else:
+ ant_cmd.extend(['-quiet'])
+ ant_result = subprocess.call(ant_cmd)
+ if ant_result != 0:
+ print('Command "%s" exited with non-zero exit code %d'
+ % (cmd_display, ant_result))
+ sys.exit(ant_result)
+
+ src_file = os.path.join(app_dir, 'bin', '%s-release.apk' % name)
+ package_name = name
if options.app_version:
package_name += ('_' + options.app_version)
if options.mode == 'shared':
- dst_file = '%s.apk' % package_name
+ dst_file = os.path.join(options.target_dir, '%s.apk' % package_name)
elif options.mode == 'embedded':
- dst_file = '%s_%s.apk' % (package_name, options.arch)
+ dst_file = os.path.join(options.target_dir,
+ '%s_%s.apk' % (package_name, options.arch))
shutil.copyfile(src_file, dst_file)
- CleanDir('out')
- if options.mode == 'embedded':
- os.remove(pak_des_path)
-
-
-def PrintPackageInfo(app_name, app_version, arch = ''):
- package_name_version = app_name
- if app_version != '':
- package_name_version += ('_' + app_version)
- if arch == '':
- print ('A non-platform specific APK for the web application "%s" was '
- 'generated successfully at %s.apk. It requires a shared Crosswalk '
- 'Runtime to be present.'
- % (app_name, package_name_version))
+ print(' (Location: %s)' % dst_file)
+
+def PrintPackageInfo(options, name, packaged_archs):
+ package_name_version = os.path.join(options.target_dir, name)
+ if options.app_version:
+ package_name_version += '_' + options.app_version
+
+ if len(packaged_archs) == 0:
+ print ('\nA non-platform specific APK for the web application "%s" was '
+ 'generated successfully at:\n %s.apk.\nIt requires a shared '
+ 'Crosswalk Runtime to be present.'
+ % (name, package_name_version))
+ return
+
+ all_archs = set(AllArchitectures())
+
+ if len(packaged_archs) != len(all_archs):
+ missed_archs = all_archs - set(packaged_archs)
+ print ('\nNote: This APK will only work on %s-based Android devices.'
+ ' Consider building\nfor %s as well.' %
+ (', '.join(packaged_archs), ', '.join(missed_archs)))
else:
- print ('An APK for the web application "%s" including the Crosswalk '
- 'Runtime built for %s was generated successfully, which can be '
- 'found at %s_%s.apk.'
- % (app_name, arch, package_name_version, arch))
+ print ("\nApplication apk's were created for %d architectures (%s)." %
+ (len(all_archs), (','.join(all_archs))))
+ print ('If you submit this application to an application '
+ 'store, please submit both\npackages. Instructions '
+ 'for submitting multiple APKs to Google Play Store are\navailable '
+ 'here:')
+ print (' https://software.intel.com/en-us/html5/articles/submitting'
+ '-multiple-crosswalk-apk-to-google-play-store')
+
+
+def CheckSystemRequirements():
+ ''' Check for android, ant, template dir '''
+ sys.stdout.write('Checking system requirements...')
+ sys.stdout.flush()
+ # check android install
+ android_path = Which('android')
+ if android_path is None:
+ print('failed\nThe "android" binary could not be found. Check your Android '
+ 'SDK installation and your PATH environment variable.')
+ sys.exit(1)
+ if GetAndroidApiLevel(android_path) < 14:
+ print('failed\nPlease install Android API level (>=14) first.')
+ sys.exit(3)
+
+ # Check ant install
+ ant_path = Which('ant')
+ if ant_path is None:
+ print('failed\nAnt could not be found. Please make sure it is installed.')
+ sys.exit(4)
+ print('ok')
-def MakeApk(options, sanitized_name):
- Customize(options)
- app_version = ''
- if options.app_version:
- app_version = options.app_version
+
+def MakeApk(options, app_info, manifest):
+ CheckSystemRequirements()
+ Customize(options, app_info, manifest)
+ name = app_info.android_name
+ app_dir = os.path.join(tempfile.gettempdir(), name)
+ packaged_archs = []
if options.mode == 'shared':
- Execution(options, sanitized_name)
- PrintPackageInfo(sanitized_name, app_version)
+ # For shared mode, it's not necessary to use the whole xwalk core library,
+ # use xwalk_core_library_java_app_part.jar from it is enough.
+ java_app_part_jar = os.path.join(xwalk_dir, 'xwalk_core_library', 'libs',
+ 'xwalk_core_library_java_app_part.jar')
+ shutil.copy(java_app_part_jar, os.path.join(app_dir, 'libs'))
+ Execution(options, name)
elif options.mode == 'embedded':
+ # Copy xwalk_core_library into app folder and move the native libraries
+ # out.
+ # When making apk for specified CPU arch, will only include the
+ # corresponding native library by copying it back into xwalk_core_library.
+ target_library_path = os.path.join(app_dir, 'xwalk_core_library')
+ shutil.copytree(os.path.join(xwalk_dir, 'xwalk_core_library'),
+ target_library_path)
+ library_lib_path = os.path.join(target_library_path, 'libs')
+ native_lib_path = os.path.join(app_dir, 'native_libs')
+ os.makedirs(native_lib_path)
+ available_archs = []
+ for dir_name in os.listdir(library_lib_path):
+ lib_dir = os.path.join(library_lib_path, dir_name)
+ if ContainsNativeLibrary(lib_dir):
+ shutil.move(lib_dir, os.path.join(native_lib_path, dir_name))
+ available_archs.append(dir_name)
if options.arch:
- Execution(options, sanitized_name)
- PrintPackageInfo(sanitized_name, app_version, options.arch)
+ Execution(options, name)
+ packaged_archs.append(options.arch)
else:
# If the arch option is unspecified, all of available platform APKs
# will be generated.
valid_archs = ['x86', 'armeabi-v7a']
- packaged_archs = []
for arch in valid_archs:
- if os.path.isfile(os.path.join('native_libs', arch, 'libs',
- arch, 'libxwalkcore.so')):
+ if arch in available_archs:
if arch.find('x86') != -1:
options.arch = 'x86'
elif arch.find('arm') != -1:
options.arch = 'arm'
- Execution(options, sanitized_name)
+ Execution(options, name)
packaged_archs.append(options.arch)
- for arch in packaged_archs:
- PrintPackageInfo(sanitized_name, app_version, arch)
- else:
- print('Unknown mode for packaging the application. Abort!')
- sys.exit(11)
-
-def parse_optional_arg(default_value):
- def func(option, value, values, parser):
- del value
- del values
- if parser.rargs and not parser.rargs[0].startswith('-'):
- val = parser.rargs[0]
- parser.rargs.pop(0)
+ else:
+ print('Warning: failed to create package for arch "%s" '
+ 'due to missing native library' % arch)
+
+ if len(packaged_archs) == 0:
+ print('No packages created, aborting')
+ sys.exit(13)
+
+ # if project_dir, save build directory
+ if options.project_dir:
+ print ('\nCreating project directory')
+ save_dir = os.path.join(options.project_dir, name)
+ if CreateAndCopyDir(app_dir, save_dir, True):
+ print (' A project directory was created successfully in:\n %s' %
+ os.path.abspath(save_dir))
+ print (' To manually generate an APK, run the following in that '
+ 'directory:')
+ print (' ant release -f build.xml')
+ print (' For more information, see:\n'
+ ' http://developer.android.com/tools/building/'
+ 'building-cmdline.html')
else:
- val = default_value
- setattr(parser.values, option.dest, val)
- return func
+ print ('Error: Unable to create a project directory during the build. '
+ 'Please check the directory passed in --project-dir, '
+ 'available disk space, and write permission.')
+
+ if not options.project_only:
+ PrintPackageInfo(options, name, packaged_archs)
def main(argv):
parser = optparse.OptionParser()
'The value \'embedded\' means that the runtime is embedded into the '
'application itself and distributed along with it.'
'Set the default mode as \'embedded\'. For example: --mode=embedded')
- parser.add_option('--mode', default='embedded', help=info)
+ parser.add_option('--mode', choices=('embedded', 'shared'),
+ default='embedded', help=info)
info = ('The target architecture of the embedded runtime. Supported values '
'are \'x86\' and \'arm\'. Note, if undefined, APKs for all possible '
'architestures will be generated.')
- parser.add_option('--arch', help=info)
+ parser.add_option('--arch', choices=AllArchitectures(), help=info)
group = optparse.OptionGroup(parser, 'Application Source Options',
'This packaging tool supports 3 kinds of web application source: '
'1) XPK package; 2) manifest.json; 3) various command line options, '
'For example, --app-local-path=/relative/path/of/entry/file')
group.add_option('--app-local-path', help=info)
parser.add_option_group(group)
+ # Mandatory options group
group = optparse.OptionGroup(parser, 'Mandatory arguments',
'They are used for describing the APK information through '
'command line options.')
'--package=com.example.YourPackage')
group.add_option('--package', help=info)
parser.add_option_group(group)
+ # Optional options group (alphabetical)
group = optparse.OptionGroup(parser, 'Optional arguments',
'They are used for various settings for applications through '
'command line options.')
'--description=YourApplicationDescription')
group.add_option('--description', help=info)
group.add_option('--enable-remote-debugging', action='store_true',
- dest='enable_remote_debugging', default=False,
- help = 'Enable remote debugging.')
+ dest='enable_remote_debugging', default=False,
+ help='Enable remote debugging.')
+ group.add_option('--use-animatable-view', action='store_true',
+ dest='use_animatable_view', default=False,
+ help='Enable using animatable view (TextureView).')
info = ('The list of external extension paths splitted by OS separators. '
'The separators are \':\' , \';\' and \':\' on Linux, Windows and '
'Mac OS respectively. For example, '
group.add_option('-f', '--fullscreen', action='store_true',
dest='fullscreen', default=False,
help='Make application fullscreen.')
+ group.add_option('--keep-screen-on', action='store_true', default=False,
+ help='Support keeping screen on')
info = ('The path of application icon. '
'Such as: --icon=/path/to/your/customized/icon')
group.add_option('--icon', help=info)
info = ('The list of permissions to be used by web application. For example, '
'--permissions=geolocation:webgl')
group.add_option('--permissions', help=info)
+ info = ('Create an Android project directory with Crosswalk at this location.'
+ ' (See project-only option below)')
+ group.add_option('--project-dir', help=info)
+ info = ('Must be used with project-dir option. Create an Android project '
+ 'directory with Crosswalk but do not build the APK package')
+ group.add_option('--project-only', action='store_true', default=False,
+ dest='project_only', help=info)
+ info = ('Packaging tool will move the output APKs to the target directory')
+ group.add_option('--target-dir', default=os.getcwd(), help=info)
+ info = ('Use command lines.'
+ 'Crosswalk is powered by Chromium and supports Chromium command line.'
+ 'For example, '
+ '--xwalk-command-line=\'--chromium-command-1 --xwalk-command-2\'')
+ group.add_option('--xwalk-command-line', default='', help=info)
parser.add_option_group(group)
+ # Keystore options group
group = optparse.OptionGroup(parser, 'Keystore Options',
'The keystore is a signature from web developer, it\'s used when '
'developer wants to distribute the applications.')
group.add_option('--keystore-alias', help=info)
info = ('The passcode of keystore. For example, --keystore-passcode=code')
group.add_option('--keystore-passcode', help=info)
+ info = ('Passcode for alias\'s private key in the keystore, '
+ 'For example, --keystore-alias-passcode=alias-code')
+ group.add_option('--keystore-alias-passcode', help=info)
info = ('Minify and obfuscate javascript and css.'
'--compressor: compress javascript and css.'
'--compressor=js: compress javascript.'
'--compressor=css: compress css.')
group.add_option('--compressor', dest='compressor', action='callback',
- callback=parse_optional_arg('all'), help=info)
+ callback=ParseParameterForCompressor, type='string',
+ nargs=0, help=info)
parser.add_option_group(group)
options, _ = parser.parse_args()
if len(argv) == 1:
parser.print_help()
return 0
- # This option will not export to users.
- # Initialize here and will be read from manifest.json.
- options.launch_screen_img = ''
-
if options.version:
if os.path.isfile('VERSION'):
print(GetVersion('VERSION'))
return 0
else:
- parser.error('Can\'t get version due to the VERSION file missing!')
+ parser.error('VERSION was not found, so Crosswalk\'s version could not '
+ 'be determined.')
xpk_temp_dir = ''
if options.xpk:
xpk_name = os.path.splitext(os.path.basename(options.xpk))[0]
- xpk_temp_dir = xpk_name + '_xpk'
+ xpk_temp_dir = os.path.join(tempfile.gettempdir(), xpk_name + '_xpk')
+ CleanDir(xpk_temp_dir)
ParseXPK(options, xpk_temp_dir)
if options.app_root and not options.manifest:
print('Using manifest.json distributed with the application.')
options.manifest = manifest_path
+ app_info = AppInfo()
+ manifest = None
if not options.manifest:
- if not options.package:
- parser.error('The package name is required! '
- 'Please use "--package" option.')
- if not options.name:
- parser.error('The APK name is required! Please use "--name" option.')
- if not ((options.app_url and not options.app_root
- and not options.app_local_path) or ((not options.app_url)
- and options.app_root and options.app_local_path)):
- parser.error('The entry is required. If the entry is a remote url, '
- 'please use "--app-url" option; If the entry is local, '
- 'please use "--app-root" and '
- '"--app-local-path" options together!')
+ # The checks here are really convoluted, but at the moment make_apk
+ # misbehaves any of the following conditions is true.
+ if options.app_url:
+ # 1) --app-url must be passed without either --app-local-path or
+ # --app-root.
+ if options.app_root or options.app_local_path:
+ parser.error('You must pass either "--app-url" or "--app-local-path" '
+ 'with "--app-root", but not all.')
+ else:
+ # 2) --app-url is not passed but only one of --app-local-path and
+ # --app-root is set.
+ if bool(options.app_root) != bool(options.app_local_path):
+ parser.error('You must specify both "--app-local-path" and '
+ '"--app-root".')
+ # 3) None of --app-url, --app-local-path and --app-root are passed.
+ elif not options.app_root and not options.app_local_path:
+ parser.error('You must pass either "--app-url" or "--app-local-path" '
+ 'with "--app-root".')
+
if options.permissions:
permission_list = options.permissions.split(':')
else:
- print ('Warning: all supported permissions on Android port are added. '
- 'Refer to https://github.com/crosswalk-project/'
- 'crosswalk-website/wiki/Crosswalk-manifest')
+ print('Warning: all supported permissions on Android port are added. '
+ 'Refer to https://github.com/crosswalk-project/'
+ 'crosswalk-website/wiki/Crosswalk-manifest')
permission_list = permission_mapping_table.keys()
options.permissions = HandlePermissionList(permission_list)
-
+ options.icon_dict = {}
else:
try:
- ParseManifest(options)
+ manifest = ParseManifest(options)
except SystemExit as ec:
return ec.code
- options.name = ReplaceInvalidChars(options.name, 'apkname')
- options.package = ReplaceInvalidChars(options.package)
- sanitized_name = ReplaceInvalidChars(options.name, 'apkname')
+ if not options.name:
+ parser.error('An APK name is required. Please use the "--name" option.')
+
+ if not options.package:
+ parser.error('A package name is required. Please use the "--package" '
+ 'option.')
+ VerifyPackageName(options.package)
+
+ if (options.app_root and options.app_local_path and
+ not os.path.isfile(os.path.join(options.app_root,
+ options.app_local_path))):
+ print('Please make sure that the local path file of launching app '
+ 'does exist.')
+ sys.exit(7)
+
+ if options.target_dir:
+ target_dir = os.path.abspath(os.path.expanduser(options.target_dir))
+ options.target_dir = target_dir
+ if not os.path.isdir(target_dir):
+ os.makedirs(target_dir)
+
+ if options.project_dir:
+ if options.project_dir == tempfile.gettempdir():
+ print('\nmake_apk.py error: Option --project-dir can not be '
+ 'the system temporary\ndirectory.')
+ sys.exit(8)
+ if options.project_only and not options.project_dir:
+ print('\nmake_apk.py error: Option --project-only must be used '
+ 'with --project-dir')
+ sys.exit(8)
try:
- compress = compress_js_and_css.CompressJsAndCss(options.app_root)
- if options.compressor == 'all':
- compress.CompressJavaScript()
- compress.CompressCss()
- elif options.compressor == 'js':
- compress.CompressJavaScript()
- elif options.compressor == 'css':
- compress.CompressCss()
- MakeApk(options, sanitized_name)
+ MakeApk(options, app_info, manifest)
except SystemExit as ec:
- CleanDir(sanitized_name)
- CleanDir('out')
- if os.path.exists(xpk_temp_dir):
- CleanDir(xpk_temp_dir)
return ec.code
+ finally:
+ CleanDir(os.path.join(tempfile.gettempdir(), app_info.android_name))
+ CleanDir(xpk_temp_dir)
return 0
if __name__ == '__main__':
- sys.exit(main(sys.argv))
+ try:
+ sys.exit(main(sys.argv))
+ except KeyboardInterrupt:
+ print('')
+