You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
558 lines
27 KiB
Python
558 lines
27 KiB
Python
#
|
|
# 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, is_unit_test=False):
|
|
"""
|
|
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 whose 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. None if is_test_project is True
|
|
:param embedded_assets: Boolean to indicate if the assets are embedded in the APK or not
|
|
:param is_unit_test: Boolean to indicate if this is a unit test deployment
|
|
"""
|
|
|
|
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 = is_unit_test
|
|
|
|
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 / game_name / 'Cache' / 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)
|
|
|
|
try:
|
|
output = subprocess.check_output(call_arguments,
|
|
shell=True,
|
|
stderr=subprocess.PIPE).decode(common.DEFAULT_TEXT_READ_ENCODING,
|
|
common.ENCODING_ERROR_HANDLINGS)
|
|
return output
|
|
except subprocess.CalledProcessError as err:
|
|
raise common.LmbrCmdError(err.stderr.decode(common.DEFAULT_TEXT_READ_ENCODING,
|
|
common.ENCODING_ERROR_HANDLINGS))
|
|
|
|
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')
|