Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
@@ -0,0 +1,552 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import datetime
import logging
import os
import json
import platform
import subprocess
import sys
import time
import pathlib
from distutils.version import LooseVersion
# Resolve the common python module
ROOT_DEV_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
if ROOT_DEV_PATH not in sys.path:
sys.path.append(ROOT_DEV_PATH)
from cmake.Tools import common
from cmake.Tools.Platform.Android import android_support
# The following is the list of known android external storage paths that we will attempt to verify on a device and
# return the first one that is detected
KNOWN_ANDROID_EXTERNAL_STORAGE_PATHS = [
'/sdcard/',
'/storage/emulated/0/',
'/storage/emulated/legacy/',
'/storage/sdcard0/',
'/storage/self/primary/',
]
ANDROID_TARGET_TIMESTAMP_FILENAME = 'deploy.timestamp'
class AndroidDeployment(object):
"""
Class to manage the deployment of game assets to an android device (Separately from the APK)
"""
DEPLOY_APK_ONLY = 'APK'
DEPLOY_ASSETS_ONLY = 'ASSETS'
DEPLOY_BOTH = 'BOTH'
def __init__(self, dev_root, build_dir, configuration, android_device_filter, clean_deploy, android_sdk_path, deployment_type, game_name=None, asset_mode=None, asset_type=None, embedded_assets=True):
"""
Initialize the Android Deployment Worker
:param dev_root: The dev-root of the engine
:param android_device_filter: An optional list of devices to filter on the connected devices to deploy to. If not supplied, deploy to all devices
:param clean_deploy: Option to clean the target device's assets before deploying the game's assets from the host
:param android_sdk_path: Path to the android SDK (to use the adb tool)
:param deployment_type: The type of deployment (DEPLOY_APK_ONLY, DEPLOY_ASSETS_ONLY, or DEPLOY_BOTH)
:param game_name: The name of the game whoses assets are being deployed. None if is_test_project is True
:param asset_mode: The asset mode of deployment (LOOSE, PAK, VFS). None if is_test_project is True
:param asset_type: The asset type (for android, 'es3'). None if is_test_project is True
:param embedded_assets: Boolean to indicate if the assets are embedded in the APK or not
"""
self.dev_root = pathlib.Path(dev_root)
self.build_dir = self.dev_root / build_dir
self.configuration = configuration
self.game_name = game_name
self.asset_mode = asset_mode
self.asset_type = asset_type
self.clean_deploy = clean_deploy
self.embedded_assets = embedded_assets
self.deployment_type = deployment_type
self.is_test_project = game_name == android_support.TEST_RUNNER_PROJECT
if not self.is_test_project:
if embedded_assets:
# If the assets are embedded, then warn that both APK and ASSETS will be deployed even if 'BOTH' is not specified
if deployment_type in (AndroidDeployment.DEPLOY_APK_ONLY, AndroidDeployment.DEPLOY_ASSETS_ONLY):
logging.warning(f"Deployment type of {deployment_type} set but the assets are embedded in the APK. Both the APK and the Assets will be deployed.")
if asset_mode == 'PAK':
self.local_asset_path = self.dev_root / 'Pak' / f'{game_name.lower()}_{asset_type}_paks'
else:
self.local_asset_path = self.dev_root / 'Cache' / game_name / asset_type
assert game_name is not None, f"'game_name' is required"
self.game_name = game_name
assert asset_mode is not None, f"'asset_mode' is required"
self.asset_mode = asset_mode
assert asset_type is not None, f"'asset_type' is required"
self.asset_type = asset_type
self.files_in_asset_path = list(self.local_asset_path.glob('**/*'))
self.android_settings = AndroidDeployment.read_android_settings(self.dev_root, game_name)
else:
self.local_asset_path = None
if asset_mode:
logging.warning(f"'asset_mode' argument '{asset_mode}' ignored for unit test deployment.")
if asset_type:
logging.warning(f"'asset_type' argument '{asset_type}' ignored for unit test deployment.")
self.files_in_asset_path = []
self.apk_path = self.build_dir / 'app' / 'build' / 'outputs' / 'apk' / configuration / f'app-{configuration}.apk'
self.android_device_filter = [android_device.strip() for android_device in android_device_filter.split(',')] if android_device_filter else []
self.adb_path = AndroidDeployment.resolve_adb_tool(pathlib.Path(android_sdk_path))
self.adb_started = False
@staticmethod
def read_android_settings(dev_root, game_name):
"""
Read and parse the project.json file into a dictionary to process the specific attributes needed for the manifest template
:param dev_root: The dev root we are working from
:param game_name: Name of the game under the dev root
:return: The android settings for the game project if any
"""
game_folder = dev_root / game_name
game_folder_project_properties_path = game_folder / 'project.json'
game_project_properties_content = game_folder_project_properties_path.resolve(strict=True)\
.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING,
errors=common.ENCODING_ERROR_HANDLINGS)
# Extract the key attributes we need to process and build up our environment table
game_project_json = json.loads(game_project_properties_content)
android_settings = game_project_json.get('android_settings', {})
return android_settings
@staticmethod
def resolve_adb_tool(android_sdk_path):
"""
Resolve the location of the adb tool based on the input Android SDK Path
:param android_sdk_path: The android SDK path to search for the adb tool
:return: The absolute path to the adb tool
"""
adb_target = 'adb.exe' if platform.system() == 'Windows' else 'adb'
check_adb_target = android_sdk_path / 'platform-tools' / adb_target
if not check_adb_target.exists():
raise common.LmbrCmdError(f"Invalid Android SDK path '{str(android_sdk_path)}': Unable to locate '{adb_target}'.")
return check_adb_target
def get_android_project_settings(self, key_name, default_value):
return self.android_settings.get(key_name, default_value)
def adb_call(self, arg_list, device_id=None):
"""
Wrapper to execute the adb command-line tool
:param arg_list: Argument list to send to the tool
:param device_id: Optional device id (from the 'get_target_android_devices' call) to invoke the call to.
:return: The stdout result of the call
"""
if isinstance(arg_list, str):
arg_list = [arg_list]
call_arguments = [str(self.adb_path.resolve())]
if device_id:
call_arguments.extend(['-s', device_id])
call_arguments.extend(arg_list)
output = subprocess.check_output(call_arguments,
shell=True,
stderr=subprocess.DEVNULL).decode(common.DEFAULT_TEXT_READ_ENCODING,
common.ENCODING_ERROR_HANDLINGS)
return output
def adb_shell(self, command, device_id):
"""
Special wrapper around calling "adb shell" which will invoke a shell command on the android device
:param command: The shell command to invoke on the android device
:param device_id: The device id (from the 'get_target_android_devices' call) to invoke the shell call on
:return: The stdout result of the call
"""
shell_command = ['shell', command]
return self.adb_call(shell_command, device_id=device_id)
def adb_ls(self, path, device_id, args=None):
"""
Request an 'ls' call on the android device
:param path: The path to perform the 'ls' call on
:param device_id: device id (from the 'get_target_android_devices' call) to invoke the shell call on
:param args: Additional args to pass into the l'ls' call
:return: Tuple of Boolean result of the call and the output of the call
"""
error_messages = [
'No such file or directory',
'Permission denied'
]
shell_command = ['ls']
if args:
shell_command.extend(args)
shell_command.append(path)
logging.debug(f"Testing {device_id}: ls {' '.join(shell_command)}")
raw_output = self.adb_shell(command=' '.join(shell_command),
device_id=device_id)
if not raw_output:
logging.debug('adb_ls: No output given')
return False, None
if raw_output is None or any([error for error in error_messages if error in raw_output]):
logging.debug('adb_ls: Error message found')
status = False
else:
logging.debug('adb_ls: Command was successful')
status = True
return status, raw_output
def get_target_android_devices(self):
"""
Gets all of the connected android devices with adb, filtered by the set optional device filter
:return: list of serial numbers of optionally filtered connected devices.
"""
connected_devices = []
# Call adb to get the device list and process the raw response
raw_devices_output = self.adb_call("devices")
if not raw_devices_output:
raise common.LmbrCmdError("Error getting connected devices through adb")
device_output_list = raw_devices_output.split(os.linesep)
for device_output in device_output_list:
if any(x in device_output for x in ['List', '*']):
logging.debug(f"Skipping the following line as it has 'List', '*' or 'emulator' in it: {device_output}")
continue
device_serial = device_output.split()
if device_serial:
if 'unauthorized' in device_output.lower():
logging.warning(f"Device {device_serial[0]} is not authorized for development access. Please reconnect the device and check for a confirmation dialog.")
elif device_serial[0] in self.android_device_filter or not self.android_device_filter:
connected_devices.append(device_serial[0])
else:
logging.debug(f"Skipping filtered out Device {device_serial[0]} .")
if not connected_devices:
raise common.LmbrCmdError("No connected android devices found")
return connected_devices
def check_known_android_paths(self, device_id):
"""
Look for a known android path that is writeable and return the first one that is found
:param device_id: The device id (from the 'get_target_android_devices' call) to invoke the shell call on
:return: The first available android path if found, None if not
"""
for path in KNOWN_ANDROID_EXTERNAL_STORAGE_PATHS:
logging.debug(f"Checking known path '{path}' on device '{device_id}'")
# Test the path by performing an 'ls' call on it and checking if an error is returned from the result
result, output = self.adb_ls(path=path,
args=None,
device_id=device_id)
if result:
return path[:-1]
return None
def detect_device_storage_path(self, device_id):
"""
Uses the device's environment variable "EXTERNAL_STORAGE" to determine the correct
path to public storage that has write permissions. If at any point does the env var
validation fail, fallback to checking known possible paths to external storage.
:param device_id:
:return: The first available storage device
"""
external_storage = self.adb_shell(command="set | grep EXTERNAL_STORAGE",
device_id=device_id)
if not external_storage:
logging.debug(f"Unable to get 'EXTERNAL_STORAGE' environment from device '{device_id}'. Falling back to known android paths.")
return self.check_known_android_paths(device_id)
# Given the 'EXTERNAL_STORAGE' environment, parse out the value and validate it
storage_path_key_value = external_storage.split('=')
if len(storage_path_key_value) != 2:
logging.debug(f"The value for 'EXTERNAL_STORAGE' environment from device '{device_id}' does not represent a valid key-value pair: {storage_path_key_value}. Falling back to known android paths")
return self.check_known_android_paths(device_id)
# Check the existence and permissions issue of the storage path
storage_path = storage_path_key_value[1].strip()
is_external_valid, _ = self.adb_ls(path=storage_path,
device_id=device_id)
if is_external_valid:
return storage_path
# The set external path has an issue, attempt to determine its real path through an adb shell call
logging.debug(f"The path specified in EXTERNAL_STORAGE seems to have permission issues, attempting to resolve with realpath for device {device_id}.")
real_path = self.adb_shell(command=f'realpath {storage_path}',
device_id=device_id)
if not real_path:
logging.debug(f"Unable to determine the real path '{storage_path}' (from EXTERNAL_STORAGE) for {self.game_name} on device {device_id}. Falling back to known android paths")
return self.check_known_android_paths(device_id)
real_path = real_path.strip()
is_external_valid, _ = self.adb_ls(path=real_path,
device_id=device_id)
if is_external_valid:
return real_path
logging.debug(f'Unable to validate the resolved EXTERNAL_STORAGE environment variable path for device {device_id}.')
return self.check_known_android_paths(device_id)
def get_device_file_timestamp(self, remote_file_path, device_id):
"""
Get the integer timestamp value of a file from a given device.
:param remote_file_path: The path to the timestamp file on the android device
:param device_id: The device id (from the 'get_target_android_devices' call) to invoke the shell call on
:return: The time value if found, None if not
"""
try:
timestamp_string = self.adb_shell(command=f'cat {remote_file_path}',
device_id=device_id).strip()
except (subprocess.CalledProcessError, AttributeError):
return None
if not timestamp_string:
return None
for fmt in ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M:%S.%f'):
try:
target_time = time.mktime(time.strptime(timestamp_string, fmt))
break
except ValueError:
target_time = None
return target_time
def update_device_file_timestamp(self, relative_assets_path, device_id):
"""
Update the device timestamp file on an android device to track files that need updating on pushes
:param relative_assets_path: The relative path to the assets on the android device
:param device_id: The device id (from the 'get_target_android_devices' call) to invoke the shell call on
"""
timestamp_str = str(datetime.datetime.now())
logging.debug(f"Updating timestamp on device {device_id} to {timestamp_str}")
local_timestamp_file_path = self.local_asset_path / ANDROID_TARGET_TIMESTAMP_FILENAME
local_timestamp_file_path.write_text(timestamp_str)
target_timestamp_file_path = f'{relative_assets_path}/{ANDROID_TARGET_TIMESTAMP_FILENAME}'
self.adb_call(arg_list=['push', str(local_timestamp_file_path), target_timestamp_file_path],
device_id=device_id)
@staticmethod
def should_copy_file(check_path, check_time):
"""
Check if a source file should be copied, by checking if its timestamp is newer than the 'check_time'
:param check_path: The path to the source file whose timestamp will be evaluated
:param check_time: The baseline 'check_time' value to compare the source file timestamp against
:return: True if the source file is newer than the baseline 'check_time', False if not
"""
if not check_path.is_file():
return False
stat_src = check_path.stat()
should_copy = stat_src.st_mtime >= check_time
return should_copy
def check_package_installed(self, package_name, target_device):
"""
Checks if the package for the game is currently installed or not
@param package_name: The name of the package to search for
@param target_device: The target device to search for the package on
@return: True if there an existing package on the device with the same package name, false if not
"""
output_result = self.adb_call(['shell', 'cmd', 'package', 'list', 'packages', package_name],
target_device)
return output_result != ''
def install_apk_to_device(self, target_device):
"""
Install the APK to a target device
@param target_device: The device id of the connected device to install to
"""
if self.is_test_project:
android_package_name = android_support.TEST_RUNNER_PACKAGE_NAME
else:
android_package_name = self.get_android_project_settings(key_name='package_name',
default_value='com.lumberyard.sdk')
if self.clean_deploy and self.check_package_installed(android_package_name, target_device):
logging.info(f"Device '{target_device}': Uninstalling pre-existing APK for {self.game_name} ...")
self.adb_call(arg_list=['uninstall', android_package_name],
device_id=target_device)
logging.info(f"Device '{target_device}': Installing APK for {self.game_name} ...")
self.adb_call(arg_list=['install', '-t', '-r', str(self.apk_path.resolve())],
device_id=target_device)
def install_assets_to_device(self, detected_storage, target_device):
"""
Install the assets for the game to a target device
@param detected_storage: The detected storage path on the target device
@param target_device: The ID of the target device
"""
assert not self.is_test_project
android_package_name = self.get_android_project_settings(key_name='package_name',
default_value='com.lumberyard.sdk')
relative_assets_path = f'Android/data/{android_package_name}/files'
output_target = f'{detected_storage}/{relative_assets_path}'
device_timestamp_file = f'{output_target}/{ANDROID_TARGET_TIMESTAMP_FILENAME}'
# Track the current timestamp if possible to see if we can incrementally push files rather
# than always pushing all files
target_timestamp = self.get_device_file_timestamp(remote_file_path=device_timestamp_file,
device_id=target_device)
if self.clean_deploy:
logging.info(f"Device '{target_device}': Cleaning target assets before deployment...")
self.adb_shell(command=f'rm -rf {output_target}',
device_id=target_device)
logging.info(f"Device '{target_device}': Target cleaned.")
settings_registry_src = self.build_dir / 'app/src/main/assets/Registry'
settings_registry_dst = f'{output_target}/Registry'
if self.clean_deploy or not target_timestamp:
logging.info(f"Device '{target_device}': Pushing {len(self.files_in_asset_path)} files from {str(self.local_asset_path.resolve())} to device ...")
paths_to_deploy = [(str(self.local_asset_path.resolve()), output_target),
(str(settings_registry_src), settings_registry_dst)]
for path_to_deploy, target_path in paths_to_deploy:
try:
self.adb_call(arg_list=['push', str(path_to_deploy), target_path],
device_id=target_device)
except subprocess.CalledProcessError as err:
# Something went wrong, clean up before leaving
self.adb_shell(command=f'rm -rf {output_target}',
device_id=target_device)
raise err
else:
# If no clean was specified, individually inspect all files to see if it needs to be updated
files_to_copy = []
for asset_file in self.files_in_asset_path:
# TODO: Check if the target exists in the destination as well?
if AndroidDeployment.should_copy_file(asset_file, target_timestamp):
files_to_copy.append(asset_file)
if len(files_to_copy) > 0:
logging.info(f"Copying {len(files_to_copy)} assets to device {target_device}")
for src_path in files_to_copy:
relative_path = os.path.relpath(str(src_path), str(self.local_asset_path)).replace('\\', '/')
target_path = f"{output_target}/{relative_path}"
self.adb_call(arg_list=['push', str(src_path), target_path],
device_id=target_device)
# Always update the settings registry
self.adb_call(arg_list=['push', str(settings_registry_src), settings_registry_dst],
device_id=target_device)
self.update_device_file_timestamp(relative_assets_path=output_target,
device_id=target_device)
def execute(self):
"""
Execute the asset deployment
"""
if self.is_test_project:
if not self.apk_path.is_file():
raise common.LmbrCmdError(f"Missing apk for {android_support.TEST_RUNNER_PROJECT} ({str(self.apk_path)}). Make sure it is built and is set as a signed APK.")
else:
if self.embedded_assets or self.deployment_type in (AndroidDeployment.DEPLOY_APK_ONLY, AndroidDeployment.DEPLOY_BOTH):
if not self.apk_path.is_file():
raise common.LmbrCmdError(f"Missing apk for game {self.game_name} ({str(self.apk_path)}). Make sure it is built and is set as a signed APK.")
if not self.embedded_assets or self.deployment_type in (AndroidDeployment.DEPLOY_ASSETS_ONLY, AndroidDeployment.DEPLOY_BOTH):
if not self.local_asset_path.is_dir():
raise common.LmbrCmdError(f"Missing {self.asset_type} assets for game {self.game_name} .")
try:
logging.debug("Starting ADB Server")
self.adb_call('start-server')
self.adb_started = True
# Get the list of target devices to deploy to
target_devices = self.get_target_android_devices()
if not target_devices:
raise common.LmbrCmdError("No connected and eligible android devices found")
for target_device in target_devices:
detected_storage = self.detect_device_storage_path(target_device)
if not detected_storage:
logging.warning(f"Unable to resolve storage path for device '{target_device}'. Skipping.")
continue
if self.is_test_project:
# If this is the unit test runner, then only install the APK, assets are not applicable
self.install_apk_to_device(target_device=target_device)
else:
# Otherwise install the apk and assets based on the deployment type
if self.embedded_assets or self.deployment_type in (AndroidDeployment.DEPLOY_APK_ONLY, AndroidDeployment.DEPLOY_BOTH):
self.install_apk_to_device(target_device=target_device)
if not self.embedded_assets and self.deployment_type in (AndroidDeployment.DEPLOY_ASSETS_ONLY, AndroidDeployment.DEPLOY_BOTH):
if self.deployment_type == AndroidDeployment.DEPLOY_ASSETS_ONLY:
# If we are deploying assets only without an APK, make sure the APK is even installed first
android_package_name = self.get_android_project_settings(key_name='package_name',
default_value='com.lumberyard.sdk')
if not self.check_package_installed(package_name=android_package_name,
target_device=target_device):
raise common.LmbrCmdError(f"Unable to locate APK for {self.game_name} on device '{target_device}'. Make sure it is installed "
f"first before installing the assets.")
self.install_assets_to_device(detected_storage=detected_storage,
target_device=target_device)
logging.info(f"{self.game_name} deployed to device {target_device}")
finally:
if self.adb_started:
self.adb_call('kill-server')
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,125 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import argparse
import logging
import os
import pathlib
import sys
# Resolve the common python module
ROOT_DEV_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
if ROOT_DEV_PATH not in sys.path:
sys.path.append(ROOT_DEV_PATH)
from cmake.Tools import common
from cmake.Tools.Platform.Android import android_deployment
DEPLOY_TYPES = (android_deployment.AndroidDeployment.DEPLOY_APK_ONLY,
android_deployment.AndroidDeployment.DEPLOY_ASSETS_ONLY,
android_deployment.AndroidDeployment.DEPLOY_BOTH)
def validate_android_deployment_arguments(build_dir_name):
"""
Validate the minimal platform deployment arguments
@param build_dir_name: The name of the build directory relative to the current working directory
@param game_name: The name of the game project to deploy
@return: Tuple of (resolved pathlib, game name, asset mode, asset_type, platform_settings object, Android SDK path, embedded_assets (bool) )
"""
build_dir = pathlib.Path(os.getcwd()) / build_dir_name
if not build_dir.is_dir():
raise common.LmbrCmdError(f"Invalid build directory {build_dir_name}")
platform_settings = common.PlatformSettings(build_dir)
if not platform_settings.projects:
raise common.LmbrCmdError("Missing required platform settings object from build directory.")
game_name = platform_settings.projects[0]
android_sdk_path = getattr(platform_settings, 'android_sdk_path', None)
if not android_sdk_path:
raise common.LmbrCmdError(f"Android SDK Path {android_sdk_path} is missing in the platform settings for {build_dir_name}.")
if not os.path.isdir(android_sdk_path):
raise common.LmbrCmdError(f"Android SDK Path {android_sdk_path} in the platform settings for {build_dir_name} is not valid.")
embedded_assets_str = getattr(platform_settings, 'embed_assets_in_apk', None)
if not embedded_assets_str:
raise common.LmbrCmdError(f"The emdedded assets flag 'embed_assets_in_apk' is missing in the platform settings for {build_dir_name}.")
embedded_assets = embedded_assets_str.lower() in ('t', 'true', '1')
return build_dir, game_name, platform_settings.asset_deploy_mode, platform_settings.asset_deploy_type, android_sdk_path, embedded_assets
def main(args):
parser = argparse.ArgumentParser()
parser.add_argument('-b', '--build-dir',
help='The relative build directory to deploy from.',
required=True)
parser.add_argument('-c', '--configuration',
help='The build configuration from the build directory for the source deployment files',
default='profile')
parser.add_argument('--device-id-filter',
help='Comma separated list of connected android device IDs to filter the deployment to. If not supplied, no filter will be applied and deployment will occur on all devices.',
default='')
parser.add_argument('-t', '--deployment-type',
help=f'The deployment type ({"|".join(DEPLOY_TYPES)}) to execute.',
choices=DEPLOY_TYPES,
default=android_deployment.AndroidDeployment.DEPLOY_BOTH)
parser.add_argument('--clean',
help='Option to clean the target dev kit before deploying, ensuring a clean installation.',
action='store_true')
parser.add_argument('--debug',
help='Option to enable debug messages',
action='store_true')
parsed_args = parser.parse_args(args)
# Prepare the logging
logging.basicConfig(format='%(levelname)s: %(message)s',
level=logging.DEBUG if parsed_args.debug else logging.INFO)
build_dir, game_project, asset_mode, asset_type, android_sdk_path, embedded_assets = validate_android_deployment_arguments(build_dir_name=parsed_args.build_dir)
deployment = android_deployment.AndroidDeployment(dev_root=ROOT_DEV_PATH,
build_dir=build_dir,
configuration=parsed_args.configuration,
game_name=game_project,
asset_mode=asset_mode,
asset_type=asset_type,
embedded_assets=embedded_assets,
android_device_filter=parsed_args.device_id_filter,
clean_deploy=parsed_args.clean,
android_sdk_path=android_sdk_path,
deployment_type=parsed_args.deployment_type)
deployment.execute()
if __name__ == '__main__':
try:
main(sys.argv[1:])
exit(0)
except common.LmbrCmdError as err:
print(str(err), file=sys.stderr)
exit(err.code)
@@ -0,0 +1,330 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import argparse
import logging
import os
import pathlib
import platform
import re
import sys
from distutils.version import LooseVersion
ROOT_DEV_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
if ROOT_DEV_PATH not in sys.path:
sys.path.append(ROOT_DEV_PATH)
from cmake.Tools import common
from cmake.Tools.Platform.Android import android_support
GRADLE_ARGUMENT_NAME = '--gradle-install-path'
GRADLE_MIN_VERSION = LooseVersion('4.10.1')
GRADLE_MAX_VERSION = LooseVersion('5.6.4')
GRADLE_VERSION_REGEX = re.compile(r"Gradle\s(\d+.\d+.?\d*)")
GRADLE_EXECUTABLE = 'gradle.bat' if platform.system() == 'Windows' else 'gradle'
def verify_gradle(override_gradle_path=None):
"""
Verify the installed gradle requirement.
"""
return common.verify_tool(override_tool_path=override_gradle_path,
tool_name='gradle',
tool_filename=GRADLE_EXECUTABLE,
argument_name=GRADLE_ARGUMENT_NAME,
tool_version_argument='-v',
tool_version_regex=GRADLE_VERSION_REGEX,
min_version=GRADLE_MIN_VERSION,
max_version=GRADLE_MAX_VERSION)
CMAKE_ARGUMENT_NAME = '--cmake-install-path'
CMAKE_MIN_VERSION = LooseVersion('3.17.0')
CMAKE_VERSION_REGEX = re.compile(r'cmake version (\d+.\d+.?\d*)')
CMAKE_EXECUTABLE = 'cmake.exe' if platform.system() == 'Windows' else 'cmake'
def verify_cmake(override_cmake_path=None):
"""
Verify the installed cmake requirement.
"""
return common.verify_tool(override_tool_path=override_cmake_path,
tool_name='cmake',
tool_filename=CMAKE_EXECUTABLE,
argument_name=CMAKE_ARGUMENT_NAME,
tool_version_argument='--version',
tool_version_regex=CMAKE_VERSION_REGEX,
min_version=CMAKE_MIN_VERSION,
max_version=None)
NINJA_ARGUMENT_NAME = '--ninja-install-path'
NINJA_VERSION_REGEX = re.compile(r'(\d+.\d+.?\d*)')
NINJA_EXECUTABLE = 'ninja.exe' if platform.system() == 'Windows' else 'ninja'
def verify_ninja(override_ninja_path=None):
"""
Verify the installed ninja requirement.
"""
return common.verify_tool(override_tool_path=override_ninja_path,
tool_name='ninja',
tool_filename='ninja.exe' if platform.system() == 'Windows' else 'ninja',
argument_name=NINJA_ARGUMENT_NAME,
tool_version_argument='--version',
tool_version_regex=NINJA_VERSION_REGEX,
min_version=None,
max_version=None)
SIGNING_PROFILE_STORE_FILE_ARGUMENT_NAME = "--signconfig-store-file"
SIGNING_PROFILE_STORE_PASSWORD_ARGUMENT_NAME = "--signconfig-store-password"
SIGNING_PROFILE_KEY_ALIAS_ARGUMENT_NAME = "--signconfig-key-alias"
SIGNING_PROFILE_KEY_PASSWORD_ARGUMENT_NAME = "--signconfig-key-password"
def build_optional_signing_profile(store_file, store_password, key_alias, key_password):
# If none of the arguments are set, then return None and skip the signing config generation
if not any([store_file, store_password, key_alias, key_password]):
return
return android_support.AndroidSigningConfig(store_file=store_file,
store_password=store_password,
key_alias=key_alias,
key_password=key_password)
ANDROID_SDK_ARGUMENT_NAME = '--android-sdk-path'
ANDROID_SDK_PLATFORM_ARGUMENT_NAME = '--android-sdk-version'
ANDROID_SDK_PREFERRED_TOOL_VER = '--android-sdk-build-tool-version'
ANDROID_NDK_ARGUMENT_NAME = '--android-ndk-path'
ANDROID_NDK_PLATFORM_ARGUMENT_NAME = '--android-ndk-version'
# Constants for asset-related options for APK generation
INCLUDE_APK_ASSETS_ARGUMENT_NAME = "--include-apk-assets"
ASSET_MODE_ARGUMENT_NAME = "--asset-mode"
ASSET_MODE_PAK = 'PAK'
ASSET_MODE_LOOSE = 'LOOSE'
ASSET_MODE_VFS = 'VFS'
ALL_ASSET_MODES = [ASSET_MODE_PAK, ASSET_MODE_LOOSE, ASSET_MODE_VFS]
ASSET_TYPE_ARGUMENT_NAME = '--asset-type'
DEFAULT_ASSET_TYPE = 'es3'
def wrap_parsed_args(parsed_args):
"""
Function to add a method to the parsed argument object to transform a long-form argument name to and get the
parsed values based on the input long form.
This will allow us to read an argument like '--foo-bar=Orange' by using the built in method rather than looking for
the argparsed transformed attrobite 'foo_bar'
:param parsed_args: The parsed args object to wrap
"""
def parse_argument_attr(argument):
argument_attr = argument[2:].replace('-', '_')
return getattr(parsed_args, argument_attr)
parsed_args.get_argument = parse_argument_attr
def main(args):
"""
Perform the main argument processing and execution of the project generator
:param args: The arguments to process
"""
parser = argparse.ArgumentParser(description="Prepare the android studio subfolder")
parser.add_argument('--dev-root',
help='The path to the dev root. Defaults to the current working directory.',
default=os.getcwd())
parser.add_argument('--build-dir',
help='The build dir subpath from the dev root',
required=True)
parser.add_argument('--third-party-path',
help='The path to the 3rd Party root directory',
required=True)
parser.add_argument(ANDROID_NDK_ARGUMENT_NAME,
help='The path to the android NDK',
required=True)
parser.add_argument(ANDROID_SDK_ARGUMENT_NAME,
help='The path to the android SDK',
required=True)
parser.add_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME,
help='The android SDK version',
required=True)
parser.add_argument(ANDROID_SDK_PREFERRED_TOOL_VER,
help='The preferred android sdk build version (i.e. 28.0.3). Will default to the first one detected under the android sdk',
default=None,
required=False)
parser.add_argument(ANDROID_NDK_PLATFORM_ARGUMENT_NAME,
help='The android NDK version',
required=True)
parser.add_argument(GRADLE_ARGUMENT_NAME,
help=f'The path to installed gradle. The version of gradle must fall in between {str(GRADLE_MIN_VERSION)} and {str(GRADLE_MAX_VERSION)}.',
default=None,
required=False)
parser.add_argument(CMAKE_ARGUMENT_NAME,
help=f'The path to cmake build tool if not installed on the system path. The version of cmake must be at least version {str(CMAKE_MIN_VERSION)}.',
default=None,
required=False)
parser.add_argument(NINJA_ARGUMENT_NAME,
help='The path to the ninja build tool if not installed on the system path.',
default=None,
required=False)
parser.add_argument('-g', '--game-name',
help='The game project to base off of')
# Asset Options
parser.add_argument(INCLUDE_APK_ASSETS_ARGUMENT_NAME,
action='store_true',
help='Option to include the game assets when building the APK. If this option is set, you must have the android assets built.')
parser.add_argument(ASSET_MODE_ARGUMENT_NAME,
choices=ALL_ASSET_MODES,
default=ASSET_MODE_LOOSE,
help='Asset Mode (vfs|pak|loose) to use when including assets into the APK')
parser.add_argument(ASSET_TYPE_ARGUMENT_NAME,
default=DEFAULT_ASSET_TYPE,
help='Asset Type to use when including assets into the APK')
parser.add_argument('--debug',
action='store_true',
help='Enable debug logs.')
# Signing Config options
parser.add_argument(SIGNING_PROFILE_STORE_FILE_ARGUMENT_NAME,
default=None,
help='(Optional) If specified, create a signing profile based on this supplied android jks keystore file.')
parser.add_argument(SIGNING_PROFILE_STORE_PASSWORD_ARGUMENT_NAME,
default=None,
help='If an android jks keystore file is specified, this is the store password for the keystore.')
parser.add_argument(SIGNING_PROFILE_KEY_ALIAS_ARGUMENT_NAME,
default=None,
help='If an android jks keystore file is specified, this is the alias of the signing key in the keystore.')
parser.add_argument(SIGNING_PROFILE_KEY_PASSWORD_ARGUMENT_NAME,
default=None,
help='If an android jks keystore file is specified, this is the password of the signing key in the keystore.')
parser.add_argument('--unit-test',
action='store_true',
help='Generate a unit test APK instead of a game APK.')
parsed_args = parser.parse_args(args)
wrap_parsed_args(parsed_args)
# Prepare the logging
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG if parsed_args.debug else logging.INFO)
# Verify the gradle requirements
gradle_version, override_gradle_path = verify_gradle(override_gradle_path=parsed_args.get_argument(GRADLE_ARGUMENT_NAME))
logging.info("Detected Gradle version %s", str(gradle_version))
# Verify the cmake requirements
cmake_version, override_cmake_path = verify_cmake(override_cmake_path=parsed_args.get_argument(CMAKE_ARGUMENT_NAME))
logging.info("Detected CMake version %s", str(cmake_version))
# Verify the ninja requirements
ninja_version, override_ninja_path = verify_ninja(override_ninja_path=parsed_args.get_argument(NINJA_ARGUMENT_NAME))
logging.info("Detected Ninja version %s", str(ninja_version))
# Verify the android sdk path and sdk version
verified_android_sdk_platform, verified_android_sdk_path, android_sdk_build_tool_ver = android_support.verify_android_sdk(android_sdk_platform=parsed_args.get_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME),
argument_name=ANDROID_SDK_ARGUMENT_NAME,
override_android_sdk_path=parsed_args.get_argument(ANDROID_SDK_ARGUMENT_NAME),
preferred_sdk_build_tools_ver=parsed_args.get_argument(ANDROID_SDK_PREFERRED_TOOL_VER))
# Verify the android ndk path and ndk version
verified_android_ndk_platform, verified_android_ndk_path = android_support.verify_android_ndk(android_ndk_platform=parsed_args.get_argument(ANDROID_NDK_PLATFORM_ARGUMENT_NAME),
argument_name=ANDROID_NDK_ARGUMENT_NAME,
override_android_ndk_path=parsed_args.get_argument(ANDROID_NDK_ARGUMENT_NAME))
if parsed_args.unit_test or parsed_args.game_name == android_support.TEST_RUNNER_PROJECT:
verified_game_name = android_support.TEST_RUNNER_PROJECT
_, verified_dev_root = common.verify_game_project_and_dev_root(game_name=None,
dev_root=parsed_args.dev_root)
is_test_project = True
else:
# Verify the dev-root and game name
verified_game_name, verified_dev_root = common.verify_game_project_and_dev_root(game_name=parsed_args.game_name,
dev_root=parsed_args.dev_root)
is_test_project = False
# Verify the 3rd Party Root Path
third_party_path = pathlib.Path(parsed_args.third_party_path) / '3rdParty.txt'
if not third_party_path.is_file():
raise common.LmbrCmdError("Invalid --third-party-path '{}'. Make sure it exists and contains "
"3rdParty.txt".format(parsed_args.third_party_path),
common.ERROR_CODE_INVALID_PARAMETER)
third_party_path = third_party_path.parent
build_dir = verified_dev_root / parsed_args.build_dir
signing_config = build_optional_signing_profile(store_file=parsed_args.get_argument(SIGNING_PROFILE_STORE_FILE_ARGUMENT_NAME),
store_password=parsed_args.get_argument(SIGNING_PROFILE_STORE_PASSWORD_ARGUMENT_NAME),
key_alias=parsed_args.get_argument(SIGNING_PROFILE_KEY_ALIAS_ARGUMENT_NAME),
key_password=parsed_args.get_argument(SIGNING_PROFILE_KEY_PASSWORD_ARGUMENT_NAME))
logging.debug("Dev Root : %s", str(verified_dev_root.resolve()))
logging.debug("Build Path : %s", str(build_dir.resolve()))
logging.debug("Android NDK Path : %s", str(verified_android_ndk_path.resolve()))
logging.debug("Android SDK Path : %s", str(verified_android_sdk_path.resolve()))
# Prepare the generator and execute
generator = android_support.AndroidProjectGenerator(dev_root=verified_dev_root,
build_dir=build_dir,
android_sdk_path=verified_android_sdk_path,
android_ndk_path=verified_android_ndk_path,
android_sdk_version=verified_android_sdk_platform,
android_ndk_platform=verified_android_ndk_platform,
game_name=verified_game_name,
third_party_path=third_party_path,
cmake_version=cmake_version,
override_cmake_path=override_cmake_path,
override_gradle_path=override_gradle_path,
override_ninja_path=override_ninja_path,
android_sdk_build_tool_version=android_sdk_build_tool_ver,
include_assets_in_apk=parsed_args.get_argument(INCLUDE_APK_ASSETS_ARGUMENT_NAME),
asset_mode=parsed_args.get_argument(ASSET_MODE_ARGUMENT_NAME),
asset_type=parsed_args.get_argument(ASSET_TYPE_ARGUMENT_NAME),
signing_config=signing_config,
is_test_project=is_test_project)
generator.execute()
if __name__ == '__main__':
try:
main(sys.argv[1:])
exit(0)
except common.LmbrCmdError as err:
print(str(err), file=sys.stderr)
exit(err.code)
@@ -0,0 +1,295 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import argparse
import logging
import os
import pathlib
import queue
import re
import sys
import threading
import time
# Resolve the common python module
ROOT_DEV_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
if ROOT_DEV_PATH not in sys.path:
sys.path.append(ROOT_DEV_PATH)
from cmake.Tools import common
from cmake.Tools.Platform.Android import android_support
# The name of the unit test target
TEST_PROJECT = 'AzTestRunner'
TEST_ACTIVITY = 'AzTestRunnerActivity'
# Prepare a regex that will strip out the timestamp and PID information from adb's logcat to just show 'LMBR' tagged logs
REGEX_LOGCAT_LINE = re.compile(r'([\d-]+)\s+([\d:\.]+)\s+(\d+)\s+(\d+)\s+(I)\s+(LMBR)([\s]+)(:\s)(.*)')
# The startup delay will allow the test runner to pause so that the we have a chance to query for the PID of the test launcher
TEST_RUNNER_STARTUP_DELAY = 1
LOGCAT_BUFFER_SIZE_MB = 32
LOGCAT_READ_QUEUE = queue.Queue()
def validate_android_test_build_dir(build_dir, configuration):
"""
Validate an android test build folder
:param build_dir: The build directory where the android test project was generated.
:param configuration: The configuration of the test build
:return: tuple of (Path of build_dir, path of the build dir native path (where the native binaries are built for the configuration), and the android AdbTool wrapper
"""
build_path = pathlib.Path(build_dir) if os.path.isabs(build_dir) else pathlib.Path(ROOT_DEV_PATH) / build_dir
if not build_path.is_dir():
raise common.LmbrCmdError(f"Invalid android build directory '{str(build_path)}'")
# Get the platform settings to validate the test game name (must match TEST_PROJECT)
platform_settings = common.PlatformSettings(build_path)
if not platform_settings.projects:
raise common.LmbrCmdError("Missing required platform settings object from build directory.")
game_name = platform_settings.projects[0]
if game_name != TEST_PROJECT:
raise common.LmbrCmdError("Invalid android build folder for tests.")
# Construct and validate the path to the native binaries that are built for the APK based on the input confiugration
build_configuration_path = build_path / 'app' / 'cmake' / configuration / 'arm64-v8a'
if not build_configuration_path.is_dir():
raise common.LmbrCmdError(f"Invalid android build configuration '{configuration}': Make sure that the APK has been built with this configuration successfully")
# Validate the android SDK path that was registered in the platform settings
android_sdk_path = getattr(platform_settings, 'android_sdk_path', None)
if not android_sdk_path:
raise common.LmbrCmdError(f"Android SDK Path {android_sdk_path} is missing in the platform settings for {build_dir}.")
if not os.path.isdir(android_sdk_path):
raise common.LmbrCmdError(f"Android SDK Path {android_sdk_path} in the platform settings for {build_dir} is not valid.")
return build_path, build_configuration_path, android_sdk_path
def launch_test_on_device(adb_tool, test_module, timeout_secs, test_filter):
"""
Launch an test module on the connect android device
:param adb_tool: The ADB Tool to exec the adb commands necessary to run a test
:param test_module: The name of the test module to run
:param timeout_secs: Timeout for a test run
:return: True if the tests passed, False if not
"""
# Clear user data before each test
adb_tool.exec(['shell', 'pm', 'clear', 'com.lumberyard.tests'])
# Increase the log buffer to prevent 'end of file' error from logcat
adb_tool.exec(['shell', 'logcat', '-G', f'{LOGCAT_BUFFER_SIZE_MB}M'])
# Start the test activity
exec_args = ['shell', 'am', 'start',
'-n', f'com.lumberyard.tests/.{TEST_ACTIVITY}',
'--es', test_module, 'AzRunUnitTests',
'--es', 'startdelay', str(TEST_RUNNER_STARTUP_DELAY)]
if test_filter:
exec_args.extend([
'--es', 'gtest_filter', test_filter
])
ret, result_output, result_error = adb_tool.exec(exec_args, capture_stdout=True)
if ret != 0:
raise common.LmbrCmdError(f"Unable to launch test runner activity: {result_error or result_output}")
tests_passed = False
result_pid = None
try:
# Make multiple attempts to get the PID of the test process
max_pid_retries = 5
while max_pid_retries > 0:
ret, result_pid, _ = adb_tool.exec(['shell', 'pidof', '-s', 'com.lumberyard.tests'], capture_stdout=True)
if ret == 0:
break
time.sleep(1)
max_pid_retries -= 1
if not result_pid:
raise common.LmbrCmdError("Unable to get process id for the Test Runner launcher")
result_pid = result_pid.strip()
# Start the adb logcat process for the result PID and filter the stdout
logcat_proc = adb_tool.popen(['shell', 'logcat', f'--pid={result_pid}', '-s', 'LMBR'])
start_time = time.time()
while logcat_proc.poll() is None:
# Break out of the loop if we triggered the test timeout condition (timeout_secs)
elapsed_time = time.time() - start_time
if elapsed_time > timeout_secs > 0:
logging.error("Test Runner Timeout")
break
# Break out of the loop in case the process dies unexpectly
ret, _, _ = adb_tool.exec(['shell', 'pidof', '-s', 'com.lumberyard.tests'], capture_stdout=True)
if ret != 0:
break
# Use a regex to strip out timestamp/PID logcat line and filter only the 'LMBR' tagged log events
line = logcat_proc.stdout.readline()
result = REGEX_LOGCAT_LINE.match(line) if line else None
if result:
lmbr_log_line = result.group(9)
print(lmbr_log_line)
if '[FAILURE]' in lmbr_log_line:
break
if '[SUCCESS]' in lmbr_log_line:
tests_passed = True
break
finally:
if result_pid:
adb_tool.exec(['shell', 'logcat', f'--pid={result_pid}', '-c'])
# Kill the test launcher process
adb_tool.exec(['shell', 'am', 'force-stop', 'com.lumberyard.tests'])
time.sleep(2)
return tests_passed
def launch_android_test(build_dir, configuration, target_dev_serial, test_target, timeout_secs, test_filter):
"""
Launch the unit test android apk with specific test target(s)
:param build_dir: The cmake build directory to base the launch values on
:param configuration: The configuration to base the launch values on
:param target_dev_serial: The target device serial number to launch the test on. If none, launch on all connected devices
:param test_target: The name of the target module to invoke the test on. If 'all' is specified, then iterate through all of the test modules and launch them individually
:param timeout_secs: Timeout value for individual test runs
:return: True if the test run(s) were successful, false if not
"""
# Validate the build dir and configuration
build_path, build_configuration_path, android_sdk_path = validate_android_test_build_dir(build_dir=build_dir,
configuration=configuration)
test_targets = common.get_validated_test_modules(test_modules=test_target, build_dir_path=build_dir)
# Track the long text length for formatting/alignment for the final report
max_module_text_len = max([len(module) for module in test_targets])
module_column_width = max_module_text_len + 8
adb_tool = android_support.AdbTool(android_sdk_path)
adb_tool.connect(target_dev_serial)
start_time = time.time()
final_report_map = {}
test_run_complete_event = threading.Event()
successful_run = True
for test_target in test_targets:
logging.info(f"Launching test for module {test_target}")
result = launch_test_on_device(adb_tool, test_target, timeout_secs, test_filter)
if result:
logging.info(f"Tests for module {test_target} Passed")
final_report_map[test_target] = 'PASSED'
else:
logging.info(f"Tests for module {test_target} Failed")
final_report_map[test_target] = 'FAILED'
successful_run = False
time.sleep(1)
end_time = time.time()
elapsed_time = end_time - start_time
hours = elapsed_time // 3600
elapsed_time = elapsed_time - 3600 * hours
minutes = elapsed_time // 60
seconds = elapsed_time - 60 * minutes
logging.info(f"Total Time : {int(hours)}h {int(minutes)}m {int(seconds)}s")
logging.info(f"Test Modules: {len(test_targets)}")
logging.info('----------------------------------------------------')
for test_module, test_result in final_report_map.items():
module_text_len = len(test_module)
logging.info(f"{test_module}{' '* (module_column_width-module_text_len)} : {test_result}")
test_run_complete_event.set()
adb_tool.disconnect()
return successful_run
def main(args):
parser = argparse.ArgumentParser(description="Launch a test module on a target dev kit.")
parser.add_argument('-b', '--build-dir',
help=f'The relative build directory to deploy from.',
required=True)
parser.add_argument('-c', '--configuration',
help='The build configuration from the build directory for the source deployment files',
default='profile')
parser.add_argument('test_module',
nargs='*',
help="The test module(s) to launch on the target device. Defaults to all registered test modules",
default=[])
parser.add_argument('--device-serial',
help='The optional device serial to target the launch on. Defaults to the all devices connected.',
default=None)
parser.add_argument('--timeout',
help='The timeout in secs for each test module to prevent deadlocked tests',
type=int,
default=-1)
parser.add_argument('--test-filter',
help="Option gtest filter to pass along to the unit test launcher",
default=None)
parser.add_argument('--debug',
help='Enable debug logging',
action='store_true')
parsed_args = parser.parse_args(args)
logging.basicConfig(format='%(levelname)s: %(message)s',
level=logging.DEBUG if parsed_args.debug else logging.INFO)
result = launch_android_test(build_dir=parsed_args.build_dir,
configuration=parsed_args.configuration,
target_dev_serial=parsed_args.device_serial,
test_target=parsed_args.test_module,
timeout_secs=int(parsed_args.timeout),
test_filter=parsed_args.test_filter)
return 0 if result else 1
if __name__ == '__main__':
try:
result_code = main(sys.argv[1:])
exit(result_code)
except common.LmbrCmdError as err:
logging.error(str(err))
exit(err.code)
@@ -0,0 +1,853 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import datetime
import os
import pathlib
import platform
import pytest
import time
from unittest.mock import patch, Mock
from cmake.Tools.Platform.Android import android_deployment
TEST_GAME_NAME = "Foo"
TEST_DEV_ROOT = pathlib.Path("Foo")
TEST_ASSET_MODE = 'LOOSE'
TEST_ASSET_TYPE = 'es3'
TEST_ANDROID_SDK_PATH = pathlib.Path('c:\\AndroidSDK')
TEST_BUILD_DIR = 'android_gradle_test'
TEST_DEVICE_ID = '9A201FFAZ000ER'
def match_arg_list(input_args, expected_args):
if len(input_args) != len(expected_args):
return False
for index in range(len(input_args)):
if input_args[index] != expected_args[index]:
return False
return True
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.read_android_settings', return_value={})
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.resolve_adb_tool', return_value=pathlib.Path("Foo"))
def test_Initialize(mock_resolve_adb_tool, mock_read_android_settings):
attrs = {'glob.return_value': ["foo.bar"]}
mock_local_asset_path = Mock(**attrs)
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
android_sdk_path=TEST_ANDROID_SDK_PATH,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH)
mock_resolve_adb_tool.assert_called_once_with(TEST_ANDROID_SDK_PATH)
mock_read_android_settings.assert_called_once_with(TEST_DEV_ROOT, TEST_GAME_NAME)
assert inst
def test_read_android_settings(tmpdir):
game_name = "Foo"
tmpdir.ensure(f'dev_root/{game_name}/project.json')
game_project_json_file = tmpdir.join(f'dev_root/{game_name}/project.json')
game_project_json_file.write(f'{{"android_settings": {{"game_name": "{game_name.lower()}" }} }}')
result = android_deployment.AndroidDeployment.read_android_settings(pathlib.Path(tmpdir.join('dev_root').realpath()), game_name)
assert result['game_name'] == game_name.lower()
def test_resolve_adb_tool(tmpdir):
sdk_path = 'android_sdk'
adb_target = 'adb.exe' if platform.system() == 'Windows' else 'adb'
tmpdir.ensure(f'{sdk_path}/platform-tools/{adb_target}')
dummy_adb_file = tmpdir.join(f'{sdk_path}/platform-tools/{adb_target}')
dummy_adb_file.write('adb')
result = android_deployment.AndroidDeployment.resolve_adb_tool(pathlib.Path(tmpdir.join(sdk_path).realpath()))
assert pathlib.Path(dummy_adb_file.realpath()) == result
@patch('subprocess.check_output', return_value=b'PASS')
def test_adb_call(mock_check_output):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
android_sdk_path=TEST_ANDROID_SDK_PATH,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH)
result = inst.adb_call(arg_list=['foo'], device_id='123')
mock_check_output.assert_called_once()
assert result == 'PASS'
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_call', return_value='PASS')
def test_adb_shell(mock_adb_call):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
android_sdk_path=TEST_ANDROID_SDK_PATH,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH)
result = inst.adb_shell(command='foo me', device_id='123')
expected_args = ['shell', 'foo me']
mock_adb_call.assert_called_once_with(expected_args, device_id='123')
assert result == 'PASS'
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_shell', return_value='output')
def test_adb_ls_success(mock_adb_shell):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
android_sdk_path=TEST_ANDROID_SDK_PATH,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH)
result, output = inst.adb_ls(path='/foo/bar', device_id='123')
mock_adb_shell.assert_called_once_with(command='ls /foo/bar', device_id='123')
assert result
assert output == 'output'
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_shell', return_value='')
def test_adb_ls_error_no_output(mock_adb_shell):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
android_sdk_path=TEST_ANDROID_SDK_PATH,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH)
result, output = inst.adb_ls(path='/foo/bar', device_id='123')
mock_adb_shell.assert_called_once_with(command='ls /foo/bar', device_id='123')
assert not result
assert output is None
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_shell', return_value='No such file or directory')
def test_adb_ls_error_no_such_file(mock_adb_shell):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
android_sdk_path=TEST_ANDROID_SDK_PATH,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH)
result, output = inst.adb_ls(path='/foo/bar', device_id='123')
mock_adb_shell.assert_called_once_with(command='ls /foo/bar', device_id='123')
assert not result
assert output == 'No such file or directory'
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_shell', return_value='Permission denied')
def test_adb_ls_error_permission_denied(mock_adb_shell):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
result, output = inst.adb_ls(path='/foo/bar', device_id='123')
mock_adb_shell.assert_called_once_with(command='ls /foo/bar', device_id='123')
assert not result
assert output == 'Permission denied'
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_call',
return_value=f'List of devices attached{os.linesep}9A201FFAZ000ER device{os.linesep}1A201FFAZ000ER device{os.linesep}9A201FFAZ456ER unauthorized')
def test_get_target_android_devices(mock_adb_call):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=f'{TEST_DEVICE_ID},AAAAASDSFGG',
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
result = inst.get_target_android_devices()
mock_adb_call.assert_called_once_with("devices")
assert len(result) == 1
assert result[0] == TEST_DEVICE_ID
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_ls', return_value=(True,"file"))
def test_check_known_android_paths_success(mock_adb_ls):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
result = inst.check_known_android_paths(device_id='123')
mock_adb_ls.assert_called_once()
assert result == android_deployment.KNOWN_ANDROID_EXTERNAL_STORAGE_PATHS[0][:-1]
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_ls', return_value=(False,None))
def test_check_known_android_paths_fail(mock_adb_ls):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
result = inst.check_known_android_paths(device_id='123')
assert not result
assert mock_adb_ls.call_count == len(android_deployment.KNOWN_ANDROID_EXTERNAL_STORAGE_PATHS)
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_shell', return_value=None)
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.check_known_android_paths', return_value="PATH")
def test_detect_device_storage_path_no_external_storage_env(mock_check_known_android_paths, mock_adb_shell):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")),\
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
result = inst.detect_device_storage_path(device_id=TEST_DEVICE_ID)
assert result == "PATH"
mock_adb_shell.assert_called_once()
mock_check_known_android_paths.assert_called_once_with(TEST_DEVICE_ID)
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_shell', return_value="NotSet")
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.check_known_android_paths', return_value="PATH")
def test_detect_device_storage_path_invalid_external_storage_env(mock_check_known_android_paths, mock_adb_shell):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")),\
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
result = inst.detect_device_storage_path(device_id=TEST_DEVICE_ID)
assert result == "PATH"
mock_adb_shell.assert_called_once()
mock_check_known_android_paths.assert_called_once_with(TEST_DEVICE_ID)
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_shell', return_value="EXTERNAL_STORAGE=/foo/bar")
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_ls', return_value=(True, "foo.bar"))
def test_detect_device_storage_path_valid_external_storage_env(mock_adb_ls, mock_adb_shell):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")),\
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
result = inst.detect_device_storage_path(device_id=TEST_DEVICE_ID)
assert result == "/foo/bar"
mock_adb_shell.assert_called_once()
mock_adb_ls.assert_called_once_with(path='/foo/bar', device_id=TEST_DEVICE_ID)
def test_detect_device_storage_path_real_path():
def _mock_adb_shell(command, device_id):
if command == "set | grep EXTERNAL_STORAGE":
return "EXTERNAL_STORAGE=/foo/bar"
elif command == f'realpath /foo/bar':
return "/foo_reals"
else:
raise AssertionError
def _mock_adb_ls(path, device_id, args=None):
if path == "/foo/bar":
return False, None
elif path == '/foo_reals':
return True, "foo.bar"
else:
raise AssertionError
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(android_deployment.AndroidDeployment, 'adb_shell', wraps=_mock_adb_shell), \
patch.object(android_deployment.AndroidDeployment, 'adb_ls', wraps=_mock_adb_ls), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
result = inst.detect_device_storage_path(device_id=TEST_DEVICE_ID)
assert result == "/foo_reals"
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.check_known_android_paths', return_value="PATH")
def test_detect_device_storage_path_real_path_fail(mock_check_known_android_paths):
def _mock_adb_shell(command, device_id):
if command == "set | grep EXTERNAL_STORAGE":
return "EXTERNAL_STORAGE=/foo/bar"
elif command == f'realpath /foo/bar':
return "/foo_reals"
else:
raise AssertionError
def _mock_adb_ls(path, device_id, args=None):
if path == "/foo/bar":
return False, None
elif path == '/foo_reals':
return False, None
else:
raise AssertionError
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(android_deployment.AndroidDeployment, 'adb_shell', wraps=_mock_adb_shell), \
patch.object(android_deployment.AndroidDeployment, 'adb_ls', wraps=_mock_adb_ls), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
result = inst.detect_device_storage_path(device_id=TEST_DEVICE_ID)
assert result == "PATH"
mock_check_known_android_paths.assert_called_once_with(TEST_DEVICE_ID)
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_shell', return_value="2020-04-30 09:20:00.0000")
def test_get_device_file_timestamp_success(mock_adb_shell):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
remote_path = "/foo/bar/timestamp.txt"
result = inst.get_device_file_timestamp(remote_file_path=remote_path,
device_id=TEST_DEVICE_ID)
assert result == time.mktime(time.strptime("2020-04-30 09:20:00.0000", '%Y-%m-%d %H:%M:%S.%f'))
mock_adb_shell.assert_called_once_with(command=f'cat {remote_path}',
device_id=TEST_DEVICE_ID)
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_shell', return_value=None)
def test_get_device_file_timestamp_no_file(mock_adb_shell):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
remote_path = "/foo/bar/timestamp.txt"
result = inst.get_device_file_timestamp(remote_file_path=remote_path,
device_id=TEST_DEVICE_ID)
assert not result
mock_adb_shell.assert_called_once_with(command=f'cat {remote_path}',
device_id=TEST_DEVICE_ID)
@patch('cmake.Tools.Platform.Android.android_deployment.AndroidDeployment.adb_shell', return_value="ZZZZXXXX")
def test_get_device_file_timestamp_bad_timestamp_file(mock_adb_shell):
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
local_asset_path = pathlib.Path("Foo")
inst = android_deployment.AndroidDeployment(dev_root=TEST_DEV_ROOT,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
remote_path = "/foo/bar/timestamp.txt"
result = inst.get_device_file_timestamp(remote_file_path=remote_path,
device_id=TEST_DEVICE_ID)
assert not result
mock_adb_shell.assert_called_once_with(command=f'cat {remote_path}',
device_id=TEST_DEVICE_ID)
def test_update_device_file_timestamp(tmpdir):
cache_dir = f'{TEST_DEV_ROOT}/Cache/{TEST_GAME_NAME}/{TEST_ASSET_TYPE}'
tmpdir.ensure(f'{cache_dir}/foo.txt')
mock_dev_root = tmpdir.join(TEST_DEV_ROOT).realpath()
with patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(android_deployment.AndroidDeployment, 'adb_call', return_value="") as mock_adb_call:
local_asset_path = pathlib.Path(tmpdir.join(cache_dir).realpath())
inst = android_deployment.AndroidDeployment(dev_root=mock_dev_root,
build_dir=TEST_BUILD_DIR,
configuration='profile',
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=True,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_ASSETS_ONLY,
android_sdk_path=TEST_ANDROID_SDK_PATH)
remote_path = "/foo/bar/timestamp.txt"
inst.update_device_file_timestamp(relative_assets_path=remote_path,
device_id=TEST_DEVICE_ID)
mock_adb_call.assert_called()
@pytest.mark.parametrize(
"test_config, test_package_name, test_device_storage_path", [
pytest.param('profile', 'com.amazon.lumberyard.foo', '/data/fool_storage'),
pytest.param('debug', 'com.amazon.lumberyard.foo', '/data/fool_storage'),
pytest.param('profile', 'com.amazon.lumberyard.bar', '/data/fool_storage'),
pytest.param('debug', 'com.amazon.lumberyard.bar', '/data/fool_storage'),
pytest.param('profile', 'com.amazon.lumberyard.foo', '/data/fool_storage2'),
pytest.param('debug', 'com.amazon.lumberyard.foo', '/data/fool_storage2'),
pytest.param('profile', 'com.amazon.lumberyard.bar', '/data/fool_storage2'),
pytest.param('debug', 'com.amazon.lumberyard.bar', '/data/fool_storage2')
]
)
def test_execute_success(tmpdir, test_config, test_package_name, test_device_storage_path):
mock_dev_root = tmpdir.join(TEST_DEV_ROOT).realpath()
tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/build/outputs/apk/{test_config}/app-{test_config}.apk").ensure()
expected_apk_path = str(tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/build/outputs/apk/{test_config}/app-{test_config}.apk").realpath())
tmpdir.join(f"{TEST_DEV_ROOT}/Cache/{TEST_GAME_NAME}/{TEST_ASSET_TYPE}/dummy.txt").ensure()
expected_asset_path = str(tmpdir.join(f"{TEST_DEV_ROOT}/Cache/{TEST_GAME_NAME}/{TEST_ASSET_TYPE}").realpath())
tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/src/main/assets/Registry/dummy.txt").ensure()
expected_registry_path = str(tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/src/main/assets/Registry").realpath())
expected_storage_registry_path = f'{test_device_storage_path}/Android/data/{test_package_name}/files/Registry'
def _mock_adb_call(arg_list, device_id=None):
if arg_list == "start-server":
return "SUCCESS"
if arg_list == "kill-server":
return "SUCCESS"
elif isinstance(arg_list, list):
if match_arg_list(arg_list, ['install', '-r', expected_apk_path]):
return "SUCCESS"
elif match_arg_list(arg_list, ['install', '-t', '-r', expected_apk_path]):
return "SUCCESS"
elif match_arg_list(arg_list, ['push', expected_asset_path, f'{test_device_storage_path}/Android/data/{test_package_name}/files']):
return "SUCCESS"
elif match_arg_list(arg_list, ['push', expected_registry_path, expected_storage_registry_path]):
return "SUCCESS"
raise AssertionError
with patch.object(android_deployment.AndroidDeployment, 'get_target_android_devices', return_value=[TEST_DEVICE_ID]), \
patch.object(android_deployment.AndroidDeployment, 'detect_device_storage_path', return_value=test_device_storage_path), \
patch.object(android_deployment.AndroidDeployment, 'get_device_file_timestamp', return_value=None), \
patch.object(android_deployment.AndroidDeployment, 'update_device_file_timestamp') as mock_update_device_file_timestamp, \
patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={'package_name': test_package_name}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(android_deployment.AndroidDeployment, 'adb_call', wraps=_mock_adb_call) as mock_adb_call, \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
inst = android_deployment.AndroidDeployment(dev_root=mock_dev_root,
build_dir=TEST_BUILD_DIR,
configuration=test_config,
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=False,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
inst.execute()
mock_update_device_file_timestamp.assert_called_once()
assert mock_adb_call.call_count == 5
@pytest.mark.parametrize(
"test_game_name, test_config, test_package_name, test_device_storage_path, test_asset_type", [
pytest.param('game1','profile', 'com.amazon.lumberyard.foo', '/data/fool_storage', 'es3'),
pytest.param('game1','debug', 'com.amazon.lumberyard.foo', '/data/fool_storage', 'es3'),
pytest.param('game2','profile', 'com.amazon.lumberyard.bar', '/data/fool_storage', 'es3'),
pytest.param('game2','debug', 'com.amazon.lumberyard.bar', '/data/fool_storage', 'es3'),
pytest.param('game3','profile', 'com.amazon.lumberyard.foo', '/data/fool_storage2', 'pc'),
pytest.param('game3','debug', 'com.amazon.lumberyard.foo', '/data/fool_storage2', 'pc'),
pytest.param('game4','profile', 'com.amazon.lumberyard.bar', '/data/fool_storage2', 'pc'),
pytest.param('game4','debug', 'com.amazon.lumberyard.bar', '/data/fool_storage2', 'pc')
]
)
def test_execute_clean_deploy_success(tmpdir, test_game_name, test_config, test_package_name, test_device_storage_path, test_asset_type):
mock_dev_root = tmpdir.join(TEST_DEV_ROOT).realpath()
tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/build/outputs/apk/{test_config}/app-{test_config}.apk").ensure()
expected_apk_path = str(tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/build/outputs/apk/{test_config}/app-{test_config}.apk").realpath())
tmpdir.join(f"{TEST_DEV_ROOT}/Cache/{test_game_name}/{test_asset_type}/dummy.txt").ensure()
expected_asset_path = str(tmpdir.join(f"{TEST_DEV_ROOT}/Cache/{test_game_name}/{test_asset_type}").realpath())
tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/src/main/assets/Registry/dummy.txt").ensure()
expected_registry_path = str(tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/src/main/assets/Registry").realpath())
expected_storage_path = f'{test_device_storage_path}/Android/data/{test_package_name}/files'
expected_storage_registry_path = f'{test_device_storage_path}/Android/data/{test_package_name}/files/Registry'
def _mock_adb_call(arg_list, device_id=None):
if arg_list == "start-server":
return "SUCCESS"
if arg_list == "kill-server":
return "SUCCESS"
elif isinstance(arg_list,list):
if match_arg_list(arg_list, ['install', '-r', expected_apk_path]):
return "SUCCESS"
elif match_arg_list(arg_list, ['install', '-t', '-r', expected_apk_path]):
return "SUCCESS"
elif match_arg_list(arg_list, ['push', expected_asset_path, '-r', expected_apk_path, expected_storage_path]):
return "SUCCESS"
elif match_arg_list(arg_list, ['shell', 'cmd', 'package', 'list', 'packages', test_package_name]):
return test_package_name
elif match_arg_list(arg_list, ['uninstall', test_package_name]):
return "SUCCESS"
elif match_arg_list(arg_list, ['push', expected_asset_path, expected_storage_path]):
return "SUCCESS"
elif match_arg_list(arg_list, ['push', expected_registry_path, expected_storage_registry_path]):
return "SUCCESS"
raise AssertionError
def _mock_adb_shell(command, device_id):
assert command.startswith('rm -rf')
def _mock_adb_ls(path, device_id, args=None):
if path == "/foo/bar":
return False, None
elif path == '/foo_reals':
return False, None
else:
raise AssertionError
with patch.object(android_deployment.AndroidDeployment, 'get_target_android_devices', return_value=[TEST_DEVICE_ID]) as mock_get_target_android_devices, \
patch.object(android_deployment.AndroidDeployment, 'detect_device_storage_path', return_value=test_device_storage_path) as mock_detect_device_storage_path, \
patch.object(android_deployment.AndroidDeployment, 'get_device_file_timestamp', return_value=None) as mock_get_device_file_timestamp, \
patch.object(android_deployment.AndroidDeployment, 'update_device_file_timestamp') as mock_update_device_file_timestamp, \
patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={'package_name': test_package_name}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(android_deployment.AndroidDeployment, 'adb_call', wraps=_mock_adb_call) as mock_adb_call, \
patch.object(android_deployment.AndroidDeployment, 'adb_shell', wraps=_mock_adb_shell) as mock_adb_shell, \
patch.object(android_deployment.AndroidDeployment, 'adb_ls', wraps=_mock_adb_ls), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar"]):
inst = android_deployment.AndroidDeployment(dev_root=mock_dev_root,
build_dir=TEST_BUILD_DIR,
configuration=test_config,
game_name=test_game_name,
asset_mode=TEST_ASSET_MODE,
asset_type=test_asset_type,
embedded_assets=False,
android_device_filter=None,
clean_deploy=True,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
inst.execute()
assert mock_adb_call.call_count == 7
mock_adb_shell.assert_called_once()
mock_update_device_file_timestamp.assert_called_once()
mock_get_device_file_timestamp.assert_called_once()
mock_detect_device_storage_path.assert_called_once()
mock_get_target_android_devices.assert_called_once()
@pytest.mark.parametrize(
"test_config, test_package_name, test_device_storage_path", [
pytest.param('profile', 'com.amazon.lumberyard.foo', '/data/fool_storage'),
pytest.param('debug', 'com.amazon.lumberyard.foo', '/data/fool_storage'),
pytest.param('profile', 'com.amazon.lumberyard.bar', '/data/fool_storage'),
pytest.param('debug', 'com.amazon.lumberyard.bar', '/data/fool_storage'),
pytest.param('profile', 'com.amazon.lumberyard.foo', '/data/fool_storage2'),
pytest.param('debug', 'com.amazon.lumberyard.foo', '/data/fool_storage2'),
pytest.param('profile', 'com.amazon.lumberyard.bar', '/data/fool_storage2'),
pytest.param('debug', 'com.amazon.lumberyard.bar', '/data/fool_storage2')
]
)
def test_execute_incremental_deploy_success(tmpdir, test_config, test_package_name, test_device_storage_path):
mock_dev_root = tmpdir.join(TEST_DEV_ROOT).realpath()
tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/build/outputs/apk/{test_config}/app-{test_config}.apk").ensure()
expected_apk_path = str(tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/build/outputs/apk/{test_config}/app-{test_config}.apk").realpath())
tmpdir.join(f"{TEST_DEV_ROOT}/Cache/{TEST_GAME_NAME}/{TEST_ASSET_TYPE}/dummy.txt").ensure()
expected_asset_path = str(tmpdir.join(f"{TEST_DEV_ROOT}/Cache/{TEST_GAME_NAME}/{TEST_ASSET_TYPE}").realpath())
tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/src/main/assets/Registry/dummy.txt").ensure()
expected_registry_path = str(tmpdir.join(f"{TEST_DEV_ROOT}/{TEST_BUILD_DIR}/app/src/main/assets/Registry").realpath())
expected_storage_registry_path = f'{test_device_storage_path}/Android/data/{test_package_name}/files/Registry'
def _mock_adb_call(arg_list, device_id=None):
if arg_list == "start-server":
return "SUCCESS"
if arg_list == "kill-server":
return "SUCCESS"
elif isinstance(arg_list,list):
if match_arg_list(arg_list, ['install', '-r', expected_apk_path]):
return "SUCCESS"
elif match_arg_list(arg_list, ['install', '-t', '-r', expected_apk_path]):
return "SUCCESS"
elif match_arg_list(arg_list, ['push', expected_registry_path, expected_storage_registry_path]):
return "SUCCESS"
elif len(arg_list) == 3 and arg_list[0] == 'push' and arg_list[1] == 'foo.bar':
return "SUCCESS"
raise AssertionError
def _mock_should_copy_file(check_path, check_time):
return check_path == 'foo.bar'
with patch.object(android_deployment.AndroidDeployment, 'get_target_android_devices', return_value=[TEST_DEVICE_ID]) as mock_get_target_android_devices, \
patch.object(android_deployment.AndroidDeployment, 'detect_device_storage_path', return_value=test_device_storage_path) as mock_detect_device_storage_path, \
patch.object(android_deployment.AndroidDeployment, 'get_device_file_timestamp', return_value=datetime.datetime.now()) as mock_get_device_file_timestamp, \
patch.object(android_deployment.AndroidDeployment, 'update_device_file_timestamp') as mock_update_device_file_timestamp, \
patch.object(android_deployment.AndroidDeployment, 'read_android_settings', return_value={'package_name': test_package_name}), \
patch.object(android_deployment.AndroidDeployment, 'resolve_adb_tool', return_value=pathlib.Path("Foo")), \
patch.object(android_deployment.AndroidDeployment, 'adb_call', wraps=_mock_adb_call) as mock_adb_call, \
patch.object(android_deployment.AndroidDeployment, 'should_copy_file', wraps=_mock_should_copy_file), \
patch.object(pathlib.Path, 'glob', return_value=["foo.bar", "no.bar"]):
inst = android_deployment.AndroidDeployment(dev_root=mock_dev_root,
build_dir=TEST_BUILD_DIR,
configuration=test_config,
game_name=TEST_GAME_NAME,
asset_mode=TEST_ASSET_MODE,
asset_type=TEST_ASSET_TYPE,
embedded_assets=False,
android_device_filter=None,
clean_deploy=False,
deployment_type=android_deployment.AndroidDeployment.DEPLOY_BOTH,
android_sdk_path=TEST_ANDROID_SDK_PATH)
inst.execute()
assert mock_adb_call.call_count == 5
mock_update_device_file_timestamp.assert_called_once()
mock_get_device_file_timestamp.assert_called_once()
mock_detect_device_storage_path.assert_called_once()
mock_get_target_android_devices.assert_called_once()
@@ -0,0 +1,286 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import json
import os
import pytest
import platform
import subprocess
import sys
from distutils.version import LooseVersion
ROOT_DEV_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..'))
if ROOT_DEV_PATH not in sys.path:
sys.path.append(ROOT_DEV_PATH)
from cmake.Tools import common
from cmake.Tools.Platform.Android import android_support, generate_android_project
@pytest.mark.parametrize(
"from_override, version_str, expected_result", [
pytest.param(False, b"Gradle 4.10.1", LooseVersion('4.10.1'), id='equalMinVersion'),
pytest.param(False, b"Gradle 5.6.4", LooseVersion('5.6.4'), id='eualMaxVersion'),
pytest.param(False, b"Gradle 1.0", common.LmbrCmdError('error', common.ERROR_CODE_ENVIRONMENT_ERROR), id='lessThanMinVersion'),
pytest.param(False, b"Gradle 26.3", common.LmbrCmdError('error', common.ERROR_CODE_ENVIRONMENT_ERROR), id='greaterThanMaxVersion'),
pytest.param(True, b"Gradle 4.10.1", LooseVersion('4.10.1')),
pytest.param(True, b"Gradle 5.6.4", LooseVersion('5.6.4')),
pytest.param(True, b"Gradle 1.0", common.LmbrCmdError('error', common.ERROR_CODE_ENVIRONMENT_ERROR)),
pytest.param(True, b"Gradle 26.3", common.LmbrCmdError('error', common.ERROR_CODE_ENVIRONMENT_ERROR))
]
)
def test_verify_gradle(tmpdir, from_override, version_str, expected_result):
orig_check_output = subprocess.check_output
if from_override:
gradle_script = 'gradle.bat' if platform.system() == 'Windows' else 'gradle'
tmpdir.ensure(f'gradle/bin/{gradle_script}')
override_gradle_install_path = str(tmpdir.join('gradle').realpath())
else:
override_gradle_install_path = None
def _mock_check_output(args, shell):
assert args
assert shell is True
if from_override:
assert args[0] == os.path.normpath(f'{override_gradle_install_path}/bin/{gradle_script}')
assert args[1] == '-v'
return version_str
subprocess.check_output = _mock_check_output
try:
result_version, result_override_path = generate_android_project.verify_gradle(override_gradle_install_path)
assert isinstance(expected_result, LooseVersion)
assert result_version == expected_result
if from_override:
assert os.path.normpath(result_override_path) == os.path.normpath(os.path.join(override_gradle_install_path, 'bin', gradle_script))
else:
assert result_override_path is None
except common.LmbrCmdError:
assert isinstance(expected_result, common.LmbrCmdError)
except Exception as e:
pass
finally:
subprocess.check_output = orig_check_output
@pytest.mark.parametrize(
"from_override, version_str, expected_result", [
pytest.param(False, b"cmake version 3.17.0\nKit Ware", LooseVersion('3.17.0'), id='equalMinVersion'),
pytest.param(False, b"cmake version 4.0.0\nKit Ware", LooseVersion('4.0.0'), id='greaterThanMinVersion'),
pytest.param(False, b"cmake version 1.0.0\nKit Ware", common.LmbrCmdError('error', common.ERROR_CODE_ENVIRONMENT_ERROR), id='lessThanMinVersion'),
pytest.param(True, b"cmake version 3.17.0\nKit Ware", LooseVersion('3.17.0'), id='override_equalMinVersion'),
pytest.param(True, b"cmake version 4.0.0\nKit Ware", LooseVersion('4.0.0'), id='override_greaterThanMinVersion'),
pytest.param(True, b"cmake version 1.0.0\nKit Ware", common.LmbrCmdError('error', common.ERROR_CODE_ENVIRONMENT_ERROR), id='override_lessThanMinVersion'),
]
)
def test_verify_cmake(tmpdir, from_override, version_str, expected_result):
orig_check_output = subprocess.check_output
if from_override:
cmake_exe = 'cmake.exe' if platform.system() == 'Windows' else 'cmake'
tmpdir.ensure(f'cmake/bin/{cmake_exe}')
override_cmake_install_path = str(tmpdir.join('cmake').realpath())
else:
override_cmake_install_path = None
def _mock_check_output(args, shell):
assert args
assert shell is True
if from_override:
assert args[0] == os.path.normpath(f'{override_cmake_install_path}/bin/{cmake_exe}')
assert args[1] == '--version'
return version_str
subprocess.check_output = _mock_check_output
try:
result_version, result_override_path = generate_android_project.verify_cmake(override_cmake_install_path)
assert isinstance(expected_result, LooseVersion)
assert result_version == expected_result
if from_override:
assert os.path.normpath(result_override_path) == os.path.normpath(os.path.join(override_cmake_install_path, 'bin', cmake_exe))
else:
assert result_override_path is None
except common.LmbrCmdError:
assert isinstance(expected_result, common.LmbrCmdError)
finally:
subprocess.check_output = orig_check_output
@pytest.mark.parametrize(
"from_override, version_str, expected_result", [
pytest.param(False, b"1.0.0", LooseVersion('1.0.0')),
pytest.param(False, b"1.10.0", LooseVersion('1.10.0')),
pytest.param(True, b"1.0.0", LooseVersion('1.0.0')),
pytest.param(True, b"1.10.0", LooseVersion('1.10.0'))
]
)
def test_verify_ninja(tmpdir, from_override, version_str, expected_result):
orig_check_output = subprocess.check_output
if from_override:
ninja_exe = 'ninja.exe' if platform.system() == 'Windows' else 'ninja'
tmpdir.ensure(f'ninja/{ninja_exe}')
override_cmake_install_path = str(tmpdir.join('ninja').realpath())
else:
override_cmake_install_path = None
def _mock_check_output(args, shell):
assert args
assert shell is True
if from_override:
assert args[0] == os.path.normpath(f'{override_cmake_install_path}/{ninja_exe}')
assert args[1] == '--version'
return version_str
subprocess.check_output = _mock_check_output
try:
result_version, result_override_path = generate_android_project.verify_ninja(override_cmake_install_path)
assert isinstance(expected_result, LooseVersion)
assert result_version == expected_result
if from_override:
assert os.path.normpath(result_override_path) == os.path.normpath(os.path.join(override_cmake_install_path, ninja_exe))
else:
assert result_override_path is None
except common.LmbrCmdError:
assert isinstance(expected_result, common.LmbrCmdError)
finally:
subprocess.check_output = orig_check_output
TEST_VALIDATE_VERSION_MIN = 19
TEST_VALIDATE_VERSION_MAX = 21
@pytest.mark.parametrize(
"test_input, expected", [
pytest.param('20', 20),
pytest.param('android-20', 20),
pytest.param('bad-21', "android-'XX'"),
pytest.param('10', "minimum"),
pytest.param('30', "maximum")
]
)
def test_validate_android_platform_input(test_input, expected):
try:
result = android_support.validate_android_platform_input(input_android_platform=test_input,
platform_variable_type='test',
min_version=TEST_VALIDATE_VERSION_MIN,
max_version=TEST_VALIDATE_VERSION_MAX)
assert isinstance(expected, int)
assert result == expected
except Exception as e:
assert expected in str(e)
def test_verify_android_sdk_success(tmpdir):
test_android_path = 'android_sdk'
sdk_version_number = 28
sdk_version = f'android-{sdk_version_number}'
tmpdir.ensure(f'{test_android_path}/platforms/{sdk_version}/package.xml')
tmpdir.ensure(f'{test_android_path}/build-tools/28.0.3/package.xml')
tmpdir.ensure(f'{test_android_path}/build-tools/29.0.3/package.xml')
input_sdk_path = tmpdir.join(test_android_path).realpath()
argument_name = '--android-sdk'
requested_build_tool_version = '29.0.3'
result_sdk_version, result_sdk_path, result_build_tool_version = android_support.verify_android_sdk(android_sdk_platform=sdk_version,
argument_name=argument_name,
override_android_sdk_path=input_sdk_path,
preferred_sdk_build_tools_ver=requested_build_tool_version)
assert result_sdk_version == sdk_version_number
assert result_sdk_path == input_sdk_path
assert result_build_tool_version == requested_build_tool_version
sdk_version_number_only = str(sdk_version_number)
result_sdk_version, result_sdk_path, result_build_tool_version = android_support.verify_android_sdk(android_sdk_platform=sdk_version_number_only,
argument_name=argument_name,
override_android_sdk_path=input_sdk_path)
assert result_sdk_version == sdk_version_number
assert result_sdk_path == input_sdk_path
assert result_build_tool_version == '28.0.3'
requested_build_tool_version = '30.0.3'
result_sdk_version, result_sdk_path, result_build_tool_version = android_support.verify_android_sdk(android_sdk_platform=sdk_version,
argument_name=argument_name,
override_android_sdk_path=input_sdk_path,
preferred_sdk_build_tools_ver=requested_build_tool_version)
assert result_sdk_version == sdk_version_number
assert result_sdk_path == input_sdk_path
assert result_build_tool_version == '28.0.3'
@pytest.mark.parametrize(
"desired_ndk_version_number, available_ndk_revisions, pkg_revision, mappings, expect_error", [
pytest.param(21, [21, 22, 24], '15.2.4203891', None, False, id='preNdk19ExactMatch'),
pytest.param(23, [21, 22, 24], '15.2.4203891', None, False, id='preNdk19FallbackMatch'),
pytest.param(22, [21, 22, 24], '19.2.4203891', {'23': 21}, False, id='postNdk19ExactMatch'),
pytest.param(23, [21, 22, 24], '21.2.4203891', {'23': 21}, False, id='postNdk19MappingMatch'),
pytest.param(android_support.ANDROID_NDK_MIN_PLATFORM-1, [21, 22, 24], '15.2.4203891', None, True, id='preNdk19BelowMinVer'),
pytest.param(android_support.ANDROID_NDK_MAX_PLATFORM+1, [21, 22, 24], '15.2.4203891', None, True, id='preNdk19AboveMaxVer'),
pytest.param(25, [21, 22, 24], '19.2.4203891', {'23': 21}, True, id='postNdk19NoMatch')
]
)
def test_verify_android_ndk_success(tmpdir, desired_ndk_version_number, available_ndk_revisions, pkg_revision, mappings, expect_error):
test_android_path = 'android_ndk'
for ndk_number in available_ndk_revisions:
tmpdir.ensure(f'{test_android_path}/platforms/android-{ndk_number}/arch-arm64/usr/lib/libc.so')
tmpdir.ensure(f'{test_android_path}/source.properties')
test_ndk_source_properties_file = tmpdir / test_android_path / 'source.properties'
test_ndk_source_properties_file.write_text(f'Pkg.Desc = Android NDK\nPkg.Revision = {pkg_revision}\n', encoding='ASCII')
if mappings:
platform_mapping = {
# min and max are arbitrary for now since we dont use it during evaluation, but if we do, parameterize it here as well
"min": 16, #
"max": 29,
"aliases": {}
}
for key, value in mappings.items():
platform_mapping['aliases'][key] = value
tmpdir.ensure(f'{test_android_path}/meta/platforms.json')
platform_mapping_file = tmpdir / test_android_path / 'meta/platforms.json'
platform_mapping_file.write_text(json.dumps(platform_mapping), encoding='ASCII')
input_ndk_path = tmpdir.join(test_android_path).realpath()
try:
android_ndk_platform_number, android_ndk_path = android_support.verify_android_ndk(android_ndk_platform=str(desired_ndk_version_number),
argument_name="--android-ndk",
override_android_ndk_path=input_ndk_path)
assert not expect_error
assert android_ndk_platform_number == desired_ndk_version_number
assert android_ndk_path == input_ndk_path
except Exception:
assert expect_error