[development] Android compatibility fixes (#5424)

- Added support for NDK r23
 - Added support for the versioned Android 'cmdline-tools' package which replace the deprecated 'tools' package
 - Updated max Gradle version to latest point release of 7.0 to fix volume query bug during sync in some configurations
 - Updated min Android Gradle plugin version to latest point release of 4.2
 - Fixed issue with Gradle task chaining for some custom copy tasks
 - Replaced use of deprecated 'jcenter' repo with 'mavenCentral' in Android project generator template
 - Increased debug logging around adb calls in Android deployment script
 - Fixed a couple try/except cases that were looking for the wrong exception when invoking adb_* functions
 - Fixed issue where asset cache would get deleted when regenerating an existing Android Gradle project with --overwrite-existing
 - Fixed issues with running Android project generation scripts on Unix systems

Signed-off-by: AMZN-ScottR 24445312+AMZN-ScottR@users.noreply.github.com
This commit is contained in:
Scott Romero
2021-11-09 10:32:44 -08:00
committed by GitHub
7 changed files with 83 additions and 29 deletions
@@ -8,7 +8,7 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:${ANDROID_GRADLE_PLUGIN_VERSION}'
@@ -21,7 +21,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
+9 -1
View File
@@ -6,4 +6,12 @@
#
#
set(VKVALIDATION_RUNTIME_DEPENDENCIES $<$<NOT:$<CONFIG:Release>>:${LY_NDK_DIR}/sources/third_party/vulkan/src/build-android/jniLibs/arm64-v8a/libVkLayer_khronos_validation.so>)
set(LY_ANDROID_VULKAN_VALIDATION_PATH "${LY_NDK_DIR}/sources/third_party/vulkan/src/build-android/jniLibs" CACHE PATH "Path to the Vulkan Validation Layers libs for Android")
if(NOT EXISTS ${LY_ANDROID_VULKAN_VALIDATION_PATH})
message(FATAL_ERROR
"Unable to locate the Android Vulkan validation layer libs at ${LY_ANDROID_VULKAN_VALIDATION_PATH}. "
"If using NDK r23 or above, these libs are distributed separately via https://github.com/KhronosGroup/Vulkan-ValidationLayers")
endif()
set(VKVALIDATION_RUNTIME_DEPENDENCIES $<$<NOT:$<CONFIG:Release>>:${LY_ANDROID_VULKAN_VALIDATION_PATH}/arm64-v8a/libVkLayer_khronos_validation.so>)
@@ -12,6 +12,11 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
include(cmake/Platform/Common/Configurations_common.cmake)
include(cmake/Platform/Common/Clang/Configurations_clang.cmake)
set(_android_api_define)
if(${LY_TOOLCHAIN_NDK_PKG_MAJOR} VERSION_LESS "23")
set(_android_api_define __ANDROID_API__=${LY_TOOLCHAIN_NDK_API_LEVEL})
endif()
ly_append_configurations_options(
DEFINES
LINUX64
@@ -22,9 +27,9 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
MOBILE
_HAS_C9X
ENABLE_TYPE_INFO
__ANDROID_API__=${LY_TOOLCHAIN_NDK_API_LEVEL}
NDK_REV_MAJOR=${LY_TOOLCHAIN_NDK_PKG_MAJOR}
NDK_REV_MINOR=${LY_TOOLCHAIN_NDK_PKG_MINOR}
${_android_api_define}
COMPILATION
-femulated-tls # All accesses to TLS variables are converted to calls to __emutls_get_address in the runtime library
@@ -180,16 +180,20 @@ class AndroidDeployment(object):
call_arguments.extend(['-s', device_id])
call_arguments.extend(arg_list)
logging.debug(f"adb command: {subprocess.list2cmdline(call_arguments)}")
try:
output = subprocess.check_output(call_arguments,
shell=True,
stderr=subprocess.PIPE).decode(common.DEFAULT_TEXT_READ_ENCODING,
common.ENCODING_ERROR_HANDLINGS)
logging.debug(f"adb output:\n{output}")
return output
except subprocess.CalledProcessError as err:
raise common.LmbrCmdError(err.stderr.decode(common.DEFAULT_TEXT_READ_ENCODING,
common.ENCODING_ERROR_HANDLINGS))
std_out = err.stdout.decode(common.DEFAULT_TEXT_READ_ENCODING, common.ENCODING_ERROR_HANDLINGS)
std_err = err.stderr.decode(common.DEFAULT_TEXT_READ_ENCODING, common.ENCODING_ERROR_HANDLINGS)
logging.debug(f"adb returned non-zero.\noutput:\n{std_out}\nerror:\n{std_err}\n")
raise common.LmbrCmdError(std_err)
def adb_shell(self, command, device_id):
"""
@@ -224,19 +228,15 @@ class AndroidDeployment(object):
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
@@ -347,7 +347,7 @@ class AndroidDeployment(object):
try:
timestamp_string = self.adb_shell(command=f'cat {remote_file_path}',
device_id=device_id).strip()
except (subprocess.CalledProcessError, AttributeError):
except (common.LmbrCmdError, AttributeError):
return None
if not timestamp_string:
@@ -463,7 +463,7 @@ class AndroidDeployment(object):
try:
self.adb_call(arg_list=['push', str(path_to_deploy), target_path],
device_id=target_device)
except subprocess.CalledProcessError as err:
except common.LmbrCmdError as err:
# Something went wrong, clean up before leaving
self.adb_shell(command=f'rm -rf {output_target}',
device_id=target_device)
@@ -30,10 +30,11 @@ if ROOT_DEV_PATH not in sys.path:
sys.path.append(ROOT_DEV_PATH)
from cmake.Tools import common
from cmake.Tools.layout_tool import remove_link
ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP = {
'4.2.0': {'min_gradle_version': '6.7.1',
'4.2.2': {'min_gradle_version': '6.7.1',
'sdk_build': '30.0.2',
'default_ndk': '21.4.7075529',
'min_cmake_version': '3.20'}
@@ -358,7 +359,7 @@ CUSTOM_GRADLE_COPY_NATIVE_CONFIG_FORMAT_STR = """
into 'outputs/native-lib/{abi}'
}}
compile{config}Sources.dependsOn copyNativeLibs{config}
merge{config}JniLibFolders.dependsOn copyNativeLibs{config}
copyNativeLibs{config}.mustRunAfter {{
tasks.findAll {{ task->task.name.contains('externalNativeBuild{config}') }}
@@ -388,7 +389,7 @@ CUSTOM_GRADLE_COPY_REGISTRY_FOLDER_FORMAT_STR = """
include ('*.setreg')
}}
compile{config}Sources.dependsOn copyRegistryFolder{config}
merge{config}Assets.dependsOn copyRegistryFolder{config}
"""
CUSTOM_GRADLE_COPY_REGISTRY_FOLDER_DEPENDENCY_FORMAT_STR = """
@@ -470,7 +471,7 @@ class AndroidProjectGenerator(object):
def __init__(self, engine_root, build_dir, android_sdk_path, build_tool, android_sdk_platform, android_native_api_level, android_ndk,
project_path, third_party_path, cmake_version, override_cmake_path, override_gradle_path, gradle_version, gradle_plugin_version,
override_ninja_path, include_assets_in_apk, asset_mode, asset_type, signing_config, native_build_path, is_test_project=False,
override_ninja_path, include_assets_in_apk, asset_mode, asset_type, signing_config, native_build_path, vulkan_validation_path, is_test_project=False,
overwrite_existing=True, unity_build_enabled=False):
"""
Initialize the object with all the required parameters needed to create an Android Project. The parameters should be verified before initializing this object
@@ -494,6 +495,8 @@ class AndroidProjectGenerator(object):
:param asset_mode:
:param asset_type:
:param signing_config: Optional signing configuration arguments
:param native_build_path: Override the native build staging path in gradle
:param vulkan_validation_path: Override the path to where the Vulkan Validation Layers libraries are (required when using NDK r23+)
:param is_test_project: Flag to indicate if this is a unit test runner project. (If true, project_path, asset_mode, asset_type, and include_assets_in_apk are ignored)
:param overwrite_existing: Flag to overwrite existing project files when being generated, or skip if they already exist.
"""
@@ -534,6 +537,8 @@ class AndroidProjectGenerator(object):
self.native_build_path = native_build_path
self.vulkan_validation_path = vulkan_validation_path
self.asset_mode = asset_mode
self.asset_type = asset_type
@@ -616,7 +621,7 @@ class AndroidProjectGenerator(object):
gradle_wrapper_cmd.extend(['wrapper', '-p', str(self.build_dir.resolve())])
proc_result = subprocess.run(gradle_wrapper_cmd,
shell=True)
shell=(platform.system() == 'Windows'))
if proc_result.returncode != 0:
raise common.LmbrCmdError("Gradle was unable to generate a gradle wrapper for this project (code {}): {}"
.format(proc_result.returncode, proc_result.stderr or ""),
@@ -767,6 +772,8 @@ class AndroidProjectGenerator(object):
# We must always delete 'src' any existing copied AzAndroid projects since building may pick up stale java sources
lumberyard_app_src = az_android_dst_path / 'src'
if lumberyard_app_src.exists():
# special case the 'assets' directory before cleaning the whole directory tree
remove_link(lumberyard_app_src / 'main' / 'assets')
common.remove_dir_path(lumberyard_app_src)
logging.debug("Copying AzAndroid to '%s'", az_android_dst_path.resolve())
@@ -817,6 +824,9 @@ class AndroidProjectGenerator(object):
f'"-DLY_3RDPARTY_PATH={template_third_party_path}"',
f'"-DLY_UNITY_BUILD={template_unity_build}"']
if self.vulkan_validation_path:
cmake_argument_list.append(f'"-DLY_ANDROID_VULKAN_VALIDATION_PATH={pathlib.PurePath(self.vulkan_validation_path).as_posix()}"')
if not self.is_test_project:
cmake_argument_list.append(f'"-DLY_PROJECTS={pathlib.PurePath(self.project_path).as_posix()}"')
else:
@@ -1548,17 +1558,29 @@ class AndroidSDKResolver(object):
self.version = LooseVersion(available_update_components[1])
self.available = available_update_components[2]
def __init__(self, android_sdk_path):
def __init__(self, android_sdk_path, command_line_tools_version):
self.android_sdk_path = android_sdk_path or os.environ.get(ANDROID_SDK_ENV_NAME)
if not self.android_sdk_path:
raise common.LmbrCmdError(f"Android SDK path not set or it was not passed into the command to generate the android project")
if not os.path.isdir(self.android_sdk_path):
raise common.LmbrCmdError(f"Android SDK path {self.android_sdk_path} is not valid")
if platform.system() == 'Windows':
self.sdk_manager_path = pathlib.Path(self.android_sdk_path) / 'tools' / 'bin' / 'sdkmanager.bat'
sdk_root = pathlib.Path(self.android_sdk_path)
tools_path = sdk_root / 'cmdline-tools'
if tools_path.exists():
tools_path = tools_path / command_line_tools_version
if not tools_path.exists():
raise common.LmbrCmdError(f"The desired version of the Android 'cmdline-tools' ({command_line_tools_version}) is not detected")
else:
raise common.LmbrCmdError(f"This tool is not supported on the current platform {platform.system()}")
tools_path = sdk_root / 'tools'
ext = ''
if platform.system() == 'Windows':
ext = '.bat'
self.sdk_manager_path = tools_path / 'bin' / f'sdkmanager{ext}'
if not self.sdk_manager_path.is_file():
raise common.LmbrCmdError(f"Android SDK path {self.android_sdk_path} is not valid or complete. Missing {self.sdk_manager_path}")
@@ -25,7 +25,7 @@ from cmake.Tools.Platform.Android import android_support
GRADLE_ARGUMENT_NAME = '--gradle-install-path'
GRADLE_MIN_VERSION = LooseVersion('6.5')
GRADLE_MAX_VERSION = LooseVersion('7.0.0')
GRADLE_MAX_VERSION = LooseVersion('7.0.2')
GRADLE_VERSION_REGEX = re.compile(r"Gradle\s(\d+.\d+.?\d*)")
GRADLE_EXECUTABLE = 'gradle.bat' if platform.system() == 'Windows' else 'gradle'
@@ -102,6 +102,7 @@ def build_optional_signing_profile(store_file, store_password, key_alias, key_pa
ANDROID_SDK_ARGUMENT_NAME = '--android-sdk-path'
ANDROID_SDK_PLATFORM_ARGUMENT_NAME = '--android-sdk-platform'
ANDROID_SDK_PREFERRED_TOOL_VER = '--android-sdk-build-tool-version'
ANDROID_SDK_COMMAND_LINE_TOOLS_VER = '--android-sdk-command-line-tools-version'
ANDROID_NATIVE_API_LEVEL = '--android-native-api-level'
@@ -113,7 +114,7 @@ MIN_NATIVE_API_LEVEL = 24 # The minimum Native API level that is supported
ANDROID_NDK_PLATFORM_ARGUMENT_NAME = '--android-ndk-version'
ANDROID_GRADLE_PLUGIN_ARGUMENT_NAME = '--gradle-plugin-version'
ANDROID_GRADLE_MIN_PLUGIN_VERSION = LooseVersion("4.2.0")
ANDROID_GRADLE_MIN_PLUGIN_VERSION = LooseVersion("4.2.2")
# Constants for asset-related options for APK generation
INCLUDE_APK_ASSETS_ARGUMENT_NAME = "--include-apk-assets"
@@ -185,6 +186,11 @@ def main(args):
default=-1)
# Override arguments
parser.add_argument(ANDROID_SDK_COMMAND_LINE_TOOLS_VER,
default='latest',
help='The android SDK command line tools version.',
required=False)
parser.add_argument(ANDROID_SDK_PREFERRED_TOOL_VER,
help='The android SDK build tools version.',
required=False)
@@ -217,6 +223,11 @@ def main(args):
default=None,
required=False)
parser.add_argument('--vulkan-validation-path',
help='Override path to where the Vulkan Validation Layers libraries are. Required for use with NDK r23+',
default=None,
required=False)
# Asset Options
parser.add_argument(INCLUDE_APK_ASSETS_ARGUMENT_NAME,
action='store_true',
@@ -304,7 +315,8 @@ def main(args):
f"({android_gradle_plugin_version}).")
# Use the SDK Resolver to make sure the build tools and ndk
android_sdk = android_support.AndroidSDKResolver(android_sdk_path=parsed_args.get_argument(ANDROID_SDK_ARGUMENT_NAME))
android_sdk = android_support.AndroidSDKResolver(android_sdk_path=parsed_args.get_argument(ANDROID_SDK_ARGUMENT_NAME),
command_line_tools_version=parsed_args.get_argument(ANDROID_SDK_COMMAND_LINE_TOOLS_VER))
# If no SDK platform is provided, check for any installed one
if android_sdk_platform_version < 0:
@@ -402,7 +414,8 @@ def main(args):
is_test_project=is_test_project,
overwrite_existing=parsed_args.overwrite_existing,
unity_build_enabled=parsed_args.enable_unity_build,
native_build_path=parsed_args.native_build_path)
native_build_path=parsed_args.native_build_path,
vulkan_validation_path=parsed_args.vulkan_validation_path)
generator.execute()
+10 -4
View File
@@ -30,6 +30,9 @@ ENCODING_ERROR_HANDLINGS = 'ignore' # What to do if we encounter any encodin
DEFAULT_PAK_ROOT = 'Pak' # The default Pak root folder under engine root where the game paks are built
if platform.system() == 'Windows':
class PlatformError(WindowsError):
pass
# Re-use microsoft error codes since this script is meant to only run on windows host platforms
ERROR_CODE_FILE_NOT_FOUND = 2
ERROR_CODE_ERROR_NOT_SUPPORTED = 50
@@ -37,6 +40,9 @@ if platform.system() == 'Windows':
ERROR_CODE_CANNOT_COPY = 266
ERROR_CODE_ERROR_DIRECTORY = 267
else:
class PlatformError(Exception):
pass
# Posix does not match any of the following errors to specific codes, so just the standard '1'
ERROR_CODE_FILE_NOT_FOUND = 1
ERROR_CODE_ERROR_NOT_SUPPORTED = 1
@@ -309,7 +315,7 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too
# Extract the version and verify
version_output = subprocess.check_output([tool_source, tool_version_argument],
shell=True,
shell=(platform.system() == 'Windows'),
stderr=subprocess.PIPE).decode(DEFAULT_TEXT_READ_ENCODING,
ENCODING_ERROR_HANDLINGS)
version_match = tool_version_regex.search(version_output)
@@ -330,13 +336,13 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too
return result_version, resolved_override_tool_path
except CalledProcessError as e:
error_msg = e.output.decode(DEFAULT_TEXT_READ_ENCODING,
error_msg = e.stderr.decode(DEFAULT_TEXT_READ_ENCODING,
ENCODING_ERROR_HANDLINGS)
raise LmbrCmdError(f"{tool_name} cannot be resolved or there was a problem determining its version number. "
f"Either make sure its in the system path environment or a valid path is passed in "
f"through the {argument_name} argument.\n{error_msg}",
ERROR_CODE_ERROR_NOT_SUPPORTED)
except (WindowsError, RuntimeError) as e:
except (PlatformError, RuntimeError) as e:
logging.error(f"Call to '{tool_source}' resulted in error: {e}")
raise LmbrCmdError(f"{tool_name} cannot be resolved or there was a problem determining its version number. "
f"Either make sure its in the system path environment or a valid path is passed in "
@@ -552,7 +558,7 @@ class CommandLineExec(object):
call_args.append(str(arguments))
logging.debug("exec(%s)", subprocess.list2cmdline(call_args))
result = subprocess.run(call_args,
shell=True,
shell=(platform.system() == 'Windows'),
capture_output=capture_stdout,
stderr=subprocess.DEVNULL if not capture_stdout and suppress_stderr else None,
encoding='utf-8',