diff --git a/Code/Tools/Android/ProjectBuilder/root.build.gradle.in b/Code/Tools/Android/ProjectBuilder/root.build.gradle.in index 14606bee7b..c0102c9d21 100644 --- a/Code/Tools/Android/ProjectBuilder/root.build.gradle.in +++ b/Code/Tools/Android/ProjectBuilder/root.build.gradle.in @@ -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() } } diff --git a/cmake/3rdParty/Platform/Android/VkValidation_android.cmake b/cmake/3rdParty/Platform/Android/VkValidation_android.cmake index dc1c918aad..dbc6db88a1 100644 --- a/cmake/3rdParty/Platform/Android/VkValidation_android.cmake +++ b/cmake/3rdParty/Platform/Android/VkValidation_android.cmake @@ -6,4 +6,12 @@ # # -set(VKVALIDATION_RUNTIME_DEPENDENCIES $<$>:${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 $<$>:${LY_ANDROID_VULKAN_VALIDATION_PATH}/arm64-v8a/libVkLayer_khronos_validation.so>) diff --git a/cmake/Platform/Android/Configurations_android.cmake b/cmake/Platform/Android/Configurations_android.cmake index 69c481b50a..239d13e982 100644 --- a/cmake/Platform/Android/Configurations_android.cmake +++ b/cmake/Platform/Android/Configurations_android.cmake @@ -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 diff --git a/cmake/Tools/Platform/Android/android_deployment.py b/cmake/Tools/Platform/Android/android_deployment.py index f1a9fe92fb..1d02a086dc 100755 --- a/cmake/Tools/Platform/Android/android_deployment.py +++ b/cmake/Tools/Platform/Android/android_deployment.py @@ -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) diff --git a/cmake/Tools/Platform/Android/android_support.py b/cmake/Tools/Platform/Android/android_support.py index 54f1a4c8f4..f52cc2736c 100755 --- a/cmake/Tools/Platform/Android/android_support.py +++ b/cmake/Tools/Platform/Android/android_support.py @@ -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}") diff --git a/cmake/Tools/Platform/Android/generate_android_project.py b/cmake/Tools/Platform/Android/generate_android_project.py index d8f1021590..fe0d787d38 100755 --- a/cmake/Tools/Platform/Android/generate_android_project.py +++ b/cmake/Tools/Platform/Android/generate_android_project.py @@ -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() diff --git a/cmake/Tools/common.py b/cmake/Tools/common.py index 02db0730d6..cd42d8c818 100755 --- a/cmake/Tools/common.py +++ b/cmake/Tools/common.py @@ -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',