Update Android Project Generation to support AGP 4.2.0, Cmake 3.20, and newer versions of NDK&SDK
- build.gradle format updates for newer Android Gradle Plugin - Remove hard coded Android Gradle Plugin version 3.6.4 to be passed in from command args - Set Android Gradle Plugin min version 4.2.0 in order to support Min CMake version 3.20 - Add ability to use the android sdk to install missing components if needed rather than doing it externally - Removed argument to pass in the NDK folder to use the android, use the android-sdk instead. Can request specific NDK versions if possible - Android Gradle Plugin has dependencies by version and is being managed - More defaults based on tools on path, agp version made possible so they are no longer needed in the command args
This commit is contained in:
@@ -15,14 +15,14 @@ android {
|
||||
${SIGNING_CONFIGS}
|
||||
compileSdkVersion sdkVer
|
||||
buildToolsVersion buildToolsVer
|
||||
|
||||
ndkVersion ndkPlatformVer
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion ndkPlatformVer
|
||||
minSdkVersion minSdkVer
|
||||
targetSdkVersion sdkVer
|
||||
${NATIVE_CMAKE_SECTION_DEFAULT_CONFIG}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,5 @@
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
# ${GENERATION_TIMESTAMP}
|
||||
ndk.dir=${ANDROID_NDK_PATH}
|
||||
sdk.dir=${ANDROID_SDK_PATH}
|
||||
${CMAKE_DIR_LINE}
|
||||
|
||||
@@ -12,10 +12,9 @@ buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.4'
|
||||
classpath 'com.android.tools.build:gradle:${ANDROID_GRADLE_PLUGIN_VERSION}'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
@@ -26,14 +25,14 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
ext {
|
||||
minSdkVer = ${MIN_SDK_VER}
|
||||
sdkVer = ${SDK_VER}
|
||||
ndkPlatformVer = ${NDK_PLATFORM_VER}
|
||||
ndkPlatformVer = '${NDK_VERSION}'
|
||||
buildToolsVer = '${SDK_BUILD_TOOL_VER}'
|
||||
lyEngineRoot = '${LY_ENGINE_ROOT}'
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
#
|
||||
|
||||
import imghdr
|
||||
import configparser
|
||||
import datetime
|
||||
import fnmatch
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
@@ -33,6 +35,13 @@ if ROOT_DEV_PATH not in sys.path:
|
||||
from cmake.Tools import common
|
||||
|
||||
|
||||
ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP = {
|
||||
'4.2.0': {'min_gradle_version': '6.7.1',
|
||||
'sdk_build': '30.0.2',
|
||||
'default_ndk': '21.4.7075529',
|
||||
'min_cmake_version': '3.20'}
|
||||
}
|
||||
|
||||
APP_NAME = 'app'
|
||||
ANDROID_MANIFEST_FILE = 'AndroidManifest.xml'
|
||||
ANDROID_LIBRARIES_JSON_FILE = 'android_libraries.json'
|
||||
@@ -86,83 +95,93 @@ PYTHON_SCRIPT = 'python.cmd' if platform.system() == 'Windows' else 'python.sh'
|
||||
|
||||
ANDROID_LAUNCHER_NAME_PATTERN = "{project_name}.GameLauncher"
|
||||
|
||||
|
||||
class AndroidProjectManifestEnvironment(object):
|
||||
"""
|
||||
This class manages the environment for the AndroidManifiest.xml template file, based on project settings and environments
|
||||
This class manages the environment for the AndroidManifest.xml template file, based on project settings and environments
|
||||
that were passed in or calculated from the command line arguments.
|
||||
"""
|
||||
|
||||
def __init__(self, engine_root, project_path, android_sdk_version_number, android_ndk_platform_number, is_test:bool):
|
||||
def __init__(self, engine_root, project_path, android_sdk_version_number, is_test:bool):
|
||||
"""
|
||||
Initialize the object with the project specific parameters and values for the game project
|
||||
|
||||
:param engine_root: The path where the engine is located
|
||||
:param project_path: The path were the project is located
|
||||
:param android_sdk_version_number: The android SDK platform version
|
||||
:param android_ndk_platform_number: The android NDK platform version
|
||||
:param is_test: Indicates if theAzTestRunner application should be run
|
||||
"""
|
||||
|
||||
if is_test:
|
||||
# The AzTestRunner project.json is located under {engine_root}/Code/Tools/AzTestRunner/Platform/Android/android_project.json
|
||||
project_properties_path = engine_root / 'Code' / 'Tools' / 'AzTestRunner' / 'Platform' / 'Android' / 'android_project.json'
|
||||
else:
|
||||
# The project.json file is located under the game name folder
|
||||
project_properties_path = project_path / 'project.json'
|
||||
# Read and parse the project.json file into a dictionary to process the specific attributes needed for the manifest template
|
||||
project_properties_content = project_properties_path.resolve(strict=True)\
|
||||
.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING,
|
||||
errors=common.ENCODING_ERROR_HANDLINGS)
|
||||
self.project_path = project_path
|
||||
try:
|
||||
if is_test:
|
||||
# The AzTestRunner project.json is located under {engine_root}/Code/Tools/AzTestRunner/Platform/Android/android_project.json
|
||||
project_properties_path = engine_root / 'Code' / 'Tools' / 'AzTestRunner' / 'Platform' / 'Android' / 'android_project.json'
|
||||
assert project_properties_path.is_file(), f'Missing required android settings file {project_properties_path.resolve()}'
|
||||
project_properties_content = project_properties_path.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING,
|
||||
errors=common.ENCODING_ERROR_HANDLINGS)
|
||||
project_json = json.loads(project_properties_content)
|
||||
|
||||
# Extract the key attributes we need to process and build up our environment table
|
||||
project_json = json.loads(project_properties_content)
|
||||
android_settings = project_json['android_settings']
|
||||
|
||||
project_name = project_json.get('project_name')
|
||||
if not project_name:
|
||||
raise common.LmbrCmdError(f"Missing required 'project_name' from project.json for project at '{str(project_path)}'")
|
||||
product_name = project_json.get('product_name', project_name)
|
||||
else:
|
||||
# O3DE projects have both a project.json and an android_project.json files (unless its internal)
|
||||
project_properties_path = project_path / 'project.json'
|
||||
assert project_properties_path.is_file(), f'Missing required project settings file {project_properties_path.resolve()}'
|
||||
project_properties_content = project_properties_path.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING,
|
||||
errors=common.ENCODING_ERROR_HANDLINGS)
|
||||
project_json = json.loads(project_properties_content)
|
||||
|
||||
game_project_android_settings = project_json['android_settings']
|
||||
android_project_properties_path = project_path / 'Platform' / 'Android' / 'android_project.json'
|
||||
if android_project_properties_path.is_file():
|
||||
android_project_properties_content = android_project_properties_path.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING,
|
||||
errors=common.ENCODING_ERROR_HANDLINGS)
|
||||
android_project_json = json.loads(android_project_properties_content)
|
||||
android_settings = android_project_json['android_settings']
|
||||
else:
|
||||
android_settings = project_json['android_settings']
|
||||
|
||||
package_name = game_project_android_settings["package_name"]
|
||||
self.project_path = project_path
|
||||
|
||||
package_path = package_name.replace('.', '/')
|
||||
project_name = project_json['project_name']
|
||||
product_name = project_json.get('product_name', project_name)
|
||||
package_name = android_settings["package_name"]
|
||||
package_path = package_name.replace('.', '/')
|
||||
|
||||
project_activity = f'{TEST_RUNNER_PROJECT}Activity' if is_test else f'{project_name}Activity'
|
||||
project_activity = f'{TEST_RUNNER_PROJECT}Activity' if is_test else f'{project_name}Activity'
|
||||
|
||||
# Multiview options require special processing
|
||||
multi_window_options = AndroidProjectManifestEnvironment.process_android_multi_window_options(game_project_android_settings)
|
||||
# Multiview options require special processing
|
||||
multi_window_options = AndroidProjectManifestEnvironment.process_android_multi_window_options(android_settings)
|
||||
|
||||
self.internal_dict = {
|
||||
'ANDROID_PACKAGE': package_name,
|
||||
'ANDROID_PACKAGE_PATH': package_path,
|
||||
'ANDROID_VERSION_NUMBER': game_project_android_settings["version_number"],
|
||||
"ANDROID_VERSION_NAME": game_project_android_settings["version_name"],
|
||||
"ANDROID_SCREEN_ORIENTATION": game_project_android_settings["orientation"],
|
||||
'ANDROID_APP_NAME': TEST_RUNNER_PROJECT if is_test else product_name, # external facing name
|
||||
'ANDROID_PROJECT_NAME': TEST_RUNNER_PROJECT if is_test else project_name, # internal facing name
|
||||
'ANDROID_PROJECT_ACTIVITY': project_activity,
|
||||
'ANDROID_LAUNCHER_NAME': TEST_RUNNER_PROJECT if is_test else ANDROID_LAUNCHER_NAME_PATTERN.format(project_name=project_name),
|
||||
'ANDROID_CONFIG_CHANGES': multi_window_options['ANDROID_CONFIG_CHANGES'],
|
||||
'ANDROID_APP_PUBLIC_KEY': game_project_android_settings.get('app_public_key', 'NoKey'),
|
||||
'ANDROID_APP_OBFUSCATOR_SALT': game_project_android_settings.get('app_obfuscator_salt', ''),
|
||||
'ANDROID_USE_MAIN_OBB': game_project_android_settings.get('use_main_obb', 'false'),
|
||||
'ANDROID_USE_PATCH_OBB': game_project_android_settings.get('use_patch_obb', 'false'),
|
||||
'ANDROID_ENABLE_KEEP_SCREEN_ON': game_project_android_settings.get('enable_keep_screen_on', 'false'),
|
||||
'ANDROID_DISABLE_IMMERSIVE_MODE': game_project_android_settings.get('disable_immersive_mode', 'false'),
|
||||
'ANDROID_MIN_SDK_VERSION': android_ndk_platform_number,
|
||||
'ANDROID_TARGET_SDK_VERSION': android_sdk_version_number,
|
||||
'ICONS': game_project_android_settings.get('icons', None),
|
||||
'SPLASH_SCREEN': game_project_android_settings.get('splash_screen', None),
|
||||
self.internal_dict = {
|
||||
'ANDROID_PACKAGE': package_name,
|
||||
'ANDROID_PACKAGE_PATH': package_path,
|
||||
'ANDROID_VERSION_NUMBER': android_settings["version_number"],
|
||||
"ANDROID_VERSION_NAME": android_settings["version_name"],
|
||||
"ANDROID_SCREEN_ORIENTATION": android_settings["orientation"],
|
||||
'ANDROID_APP_NAME': TEST_RUNNER_PROJECT if is_test else product_name, # external facing name
|
||||
'ANDROID_PROJECT_NAME': TEST_RUNNER_PROJECT if is_test else project_name, # internal facing name
|
||||
'ANDROID_PROJECT_ACTIVITY': project_activity,
|
||||
'ANDROID_LAUNCHER_NAME': TEST_RUNNER_PROJECT if is_test else ANDROID_LAUNCHER_NAME_PATTERN.format(project_name=project_name),
|
||||
'ANDROID_CONFIG_CHANGES': multi_window_options['ANDROID_CONFIG_CHANGES'],
|
||||
'ANDROID_APP_PUBLIC_KEY': android_settings.get('app_public_key', 'NoKey'),
|
||||
'ANDROID_APP_OBFUSCATOR_SALT': android_settings.get('app_obfuscator_salt', ''),
|
||||
'ANDROID_USE_MAIN_OBB': android_settings.get('use_main_obb', 'false'),
|
||||
'ANDROID_USE_PATCH_OBB': android_settings.get('use_patch_obb', 'false'),
|
||||
'ANDROID_ENABLE_KEEP_SCREEN_ON': android_settings.get('enable_keep_screen_on', 'false'),
|
||||
'ANDROID_DISABLE_IMMERSIVE_MODE': android_settings.get('disable_immersive_mode', 'false'),
|
||||
'ANDROID_TARGET_SDK_VERSION': android_sdk_version_number,
|
||||
'ICONS': android_settings.get('icons', None),
|
||||
'SPLASH_SCREEN': android_settings.get('splash_screen', None),
|
||||
|
||||
'ANDROID_MULTI_WINDOW': multi_window_options['ANDROID_MULTI_WINDOW'],
|
||||
'ANDROID_MULTI_WINDOW_PROPERTIES': multi_window_options['ANDROID_MULTI_WINDOW_PROPERTIES'],
|
||||
'ANDROID_MULTI_WINDOW': multi_window_options['ANDROID_MULTI_WINDOW'],
|
||||
'ANDROID_MULTI_WINDOW_PROPERTIES': multi_window_options['ANDROID_MULTI_WINDOW_PROPERTIES'],
|
||||
|
||||
'SAMSUNG_DEX_KEEP_ALIVE': multi_window_options['SAMSUNG_DEX_KEEP_ALIVE'],
|
||||
'SAMSUNG_DEX_LAUNCH_WIDTH': multi_window_options['SAMSUNG_DEX_LAUNCH_WIDTH'],
|
||||
'SAMSUNG_DEX_LAUNCH_HEIGHT': multi_window_options['SAMSUNG_DEX_LAUNCH_HEIGHT']
|
||||
}
|
||||
'SAMSUNG_DEX_KEEP_ALIVE': multi_window_options['SAMSUNG_DEX_KEEP_ALIVE'],
|
||||
'SAMSUNG_DEX_LAUNCH_WIDTH': multi_window_options['SAMSUNG_DEX_LAUNCH_WIDTH'],
|
||||
'SAMSUNG_DEX_LAUNCH_HEIGHT': multi_window_options['SAMSUNG_DEX_LAUNCH_HEIGHT']
|
||||
}
|
||||
except KeyError as e:
|
||||
raise common.LmbrCmdError(f"Missing key from android project settings for project at {project_path}:'{e}' ")
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.internal_dict.get(item)
|
||||
@@ -306,6 +325,7 @@ asset_deploy_type={asset_type}
|
||||
android_sdk_path={android_sdk_path}
|
||||
embed_assets_in_apk={embed_assets_in_apk}
|
||||
is_unit_test={is_unit_test}
|
||||
android_gradle_plugin={android_gradle_plugin_version}
|
||||
"""
|
||||
|
||||
NATIVE_CMAKE_SECTION_ANDROID_FORMAT = """
|
||||
@@ -425,26 +445,28 @@ class AndroidProjectGenerator(object):
|
||||
Class the manages the process to generate an android project folder in order to build with gradle/android studio
|
||||
"""
|
||||
|
||||
def __init__(self, engine_root, build_dir, android_ndk_path, android_sdk_path, android_sdk_version, android_ndk_platform,
|
||||
project_path, third_party_path, cmake_version, override_cmake_path, override_gradle_path, override_ninja_path,
|
||||
android_sdk_build_tool_version, include_assets_in_apk, asset_mode, asset_type, signing_config, is_test_project=False,
|
||||
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, is_test_project=False,
|
||||
overwrite_existing=True):
|
||||
"""
|
||||
Initialize the object with all the required parameters needed to create an Android Project. The parameters should be verified before initializing this object
|
||||
|
||||
|
||||
:param engine_root: The engine root that contains the engine
|
||||
:param build_dir: The target folder under the where the android project folder will be created
|
||||
:param android_ndk_path: The path to the ANDROID_NDK used for building the native android code
|
||||
:param android_sdk_path: The path to the ANDROID_SDK used for building the android java code
|
||||
:param android_sdk_version: The android platform version number to use for the Android SDK related builds
|
||||
:param android_ndk_platform: The android platform version number to use for the Android NDK related builds
|
||||
:param build_tool: The android SDK build-tool version.
|
||||
:param android_sdk_platform: The android sdk platform version number to use for the Android SDK related builds
|
||||
:param android_native_api_level:The android native API level (ANDROID_NATIVE_API_LEVEL) to set
|
||||
:param android_ndk: The android ndk version number to use for the native builds
|
||||
:param project_path: The path to the project
|
||||
:param third_party_path: The required path to the lumberyard 3rd party path
|
||||
:param cmake_version: The version number of cmake that will be used by gradle
|
||||
:param override_cmake_path: The override path to cmake if it does not exists in the system path
|
||||
:param override_gradle_path: The override path to gradle if it does not exists in the system path
|
||||
:param gradle_version: The detected version of gradle being used
|
||||
:param gradle_plugin_version: The android gradle plugin version
|
||||
:param override_ninja_path: The override path to ninja if it does not exists in the system path
|
||||
:param android_sdk_build_tool_version: The preferred android SDK build-tool version. Will default to the first one detected in the android sdk path
|
||||
:param include_assets_in_apk:
|
||||
:param asset_mode:
|
||||
:param asset_type:
|
||||
@@ -458,17 +480,16 @@ class AndroidProjectGenerator(object):
|
||||
|
||||
self.build_dir = build_dir
|
||||
|
||||
self.android_ndk_path = android_ndk_path
|
||||
|
||||
self.android_sdk_path = android_sdk_path
|
||||
|
||||
self.android_project_builder_path = self.engine_root / 'Code/Tools/Android/ProjectBuilder'
|
||||
|
||||
self.android_sdk_version = android_sdk_version
|
||||
self.android_sdk_platform = android_sdk_platform
|
||||
self.android_sdk_build_tool_version = build_tool.version
|
||||
|
||||
self.android_sdk_build_tool_version = android_sdk_build_tool_version
|
||||
|
||||
self.android_ndk_platform = android_ndk_platform
|
||||
self.android_ndk = android_ndk
|
||||
self.android_ndk_version = android_ndk.version
|
||||
self.android_native_api_level = android_native_api_level
|
||||
|
||||
self.project_path = project_path
|
||||
|
||||
@@ -480,6 +501,10 @@ class AndroidProjectGenerator(object):
|
||||
|
||||
self.override_gradle_path = override_gradle_path
|
||||
|
||||
self.gradle_version = gradle_version
|
||||
|
||||
self.gradle_plugin_version = gradle_plugin_version
|
||||
|
||||
self.override_ninja_path = override_ninja_path
|
||||
|
||||
self.include_assets_in_apk = include_assets_in_apk
|
||||
@@ -511,8 +536,10 @@ class AndroidProjectGenerator(object):
|
||||
project_names.extend(self.create_lumberyard_app(project_names))
|
||||
|
||||
root_gradle_env = {
|
||||
'SDK_VER': self.android_sdk_version,
|
||||
'NDK_PLATFORM_VER': self.android_ndk_platform,
|
||||
'ANDROID_GRADLE_PLUGIN_VERSION': str(self.gradle_plugin_version),
|
||||
'SDK_VER': self.android_sdk_platform,
|
||||
'MIN_SDK_VER': self.android_sdk_platform,
|
||||
'NDK_VERSION': self.android_ndk_version,
|
||||
'SDK_BUILD_TOOL_VER': self.android_sdk_build_tool_version,
|
||||
'LY_ENGINE_ROOT': common.normalize_path_for_settings(self.engine_root)
|
||||
}
|
||||
@@ -557,7 +584,7 @@ class AndroidProjectGenerator(object):
|
||||
if self.override_gradle_path:
|
||||
gradle_wrapper_cmd = [self.override_gradle_path]
|
||||
else:
|
||||
gradle_wrapper_cmd = ['gradle.bat' if platform.system() == 'Windows' else 'gradle']
|
||||
gradle_wrapper_cmd = ['gradle']
|
||||
|
||||
gradle_wrapper_cmd.extend(['wrapper', '-p', str(self.build_dir.resolve())])
|
||||
|
||||
@@ -580,7 +607,8 @@ class AndroidProjectGenerator(object):
|
||||
asset_type='',
|
||||
android_sdk_path=str(self.android_sdk_path),
|
||||
embed_assets_in_apk=True,
|
||||
is_unit_test=True)
|
||||
is_unit_test=True,
|
||||
android_gradle_plugin_version=self.gradle_plugin_version)
|
||||
else:
|
||||
platform_settings_content = PLATFORM_SETTINGS_FORMAT.format(generation_timestamp=str(datetime.datetime.now().strftime("%c")),
|
||||
platform='android',
|
||||
@@ -589,16 +617,28 @@ class AndroidProjectGenerator(object):
|
||||
asset_type=self.asset_type,
|
||||
android_sdk_path=str(self.android_sdk_path),
|
||||
embed_assets_in_apk=str(self.include_assets_in_apk),
|
||||
is_unit_test=False)
|
||||
is_unit_test=False,
|
||||
android_gradle_plugin_version=self.gradle_plugin_version)
|
||||
|
||||
platform_settings_file = self.build_dir / 'platform.settings'
|
||||
|
||||
# Check if there already exists the build folder and a 'platform.settings' file. If there is an android gradle
|
||||
# plugin version set and it is different than the one configured here, we will always overwrite it since
|
||||
# there could be significant differences from one plug-in to the next
|
||||
if platform_settings_file.is_file():
|
||||
config = configparser.ConfigParser()
|
||||
config.read([str(platform_settings_file.resolve(strict=True))])
|
||||
if config.has_option('android', 'android_gradle_plugin'):
|
||||
exist_agp_version = config.get('android', 'android_gradle_plugin')
|
||||
if exist_agp_version != self.gradle_plugin_version:
|
||||
self.overwrite_existing = True
|
||||
|
||||
platform_settings_file.open('w').write(platform_settings_content)
|
||||
|
||||
def create_default_local_properties(self):
|
||||
"""
|
||||
Create the default 'local.properties' file in the build folder
|
||||
"""
|
||||
template_android_ndk_path = common.normalize_path_for_settings(self.android_ndk_path, True)
|
||||
template_android_sdk_path = common.normalize_path_for_settings(self.android_sdk_path, True)
|
||||
if self.override_cmake_path:
|
||||
# The cmake dir references the base cmake folder, not the executable path itself, so resolve to the base folder
|
||||
@@ -608,7 +648,6 @@ class AndroidProjectGenerator(object):
|
||||
|
||||
local_properties_env = {
|
||||
"GENERATION_TIMESTAMP": str(datetime.datetime.now().strftime("%c")),
|
||||
"ANDROID_NDK_PATH": template_android_ndk_path,
|
||||
"ANDROID_SDK_PATH": template_android_sdk_path,
|
||||
"CMAKE_DIR_LINE": f'cmake.dir={template_cmake_path}' if template_cmake_path else ''
|
||||
}
|
||||
@@ -626,8 +665,7 @@ class AndroidProjectGenerator(object):
|
||||
# before we can process it.
|
||||
android_libraries_substitution_table = {
|
||||
"ANDROID_SDK_HOME": common.normalize_path_for_settings(self.android_sdk_path, False),
|
||||
"ANDROID_NDK_HOME": common.normalize_path_for_settings(self.android_ndk_path, False),
|
||||
"ANDROID_SDK_VERSION": "android-".format(self.android_sdk_version)
|
||||
"ANDROID_SDK_VERSION": f"android-{self.android_sdk_platform}"
|
||||
}
|
||||
android_libraries_template_json_path = self.android_project_builder_path / ANDROID_LIBRARIES_JSON_FILE
|
||||
|
||||
@@ -717,7 +755,7 @@ class AndroidProjectGenerator(object):
|
||||
|
||||
template_engine_root = common.normalize_path_for_settings(self.engine_root)
|
||||
template_third_party_path = common.normalize_path_for_settings(self.third_party_path)
|
||||
template_ndk_path = common.normalize_path_for_settings(self.android_ndk_path)
|
||||
template_ndk_path = common.normalize_path_for_settings(os.path.join(self.android_sdk_path, self.android_ndk.location))
|
||||
|
||||
gradle_build_env = dict()
|
||||
|
||||
@@ -733,7 +771,6 @@ class AndroidProjectGenerator(object):
|
||||
|
||||
gradle_build_env['OVERRIDE_JAVA_SOURCESET'] = OVERRIDE_JAVA_SOURCESET_STR.format(absolute_azandroid_path=absolute_azandroid_path)
|
||||
|
||||
|
||||
gradle_build_env['OPTIONAL_JNI_SRC_LIB_SET'] = ', "outputs/native-lib"'
|
||||
|
||||
for native_config in BUILD_CONFIGURATIONS:
|
||||
@@ -755,7 +792,7 @@ class AndroidProjectGenerator(object):
|
||||
cmake_argument_list.append('"-DLY_TEST_PROJECT=1"')
|
||||
|
||||
cmake_argument_list.extend([
|
||||
f'"-DANDROID_NATIVE_API_LEVEL={self.android_ndk_platform}"',
|
||||
f'"-DANDROID_NATIVE_API_LEVEL={self.android_native_api_level}"',
|
||||
f'"-DLY_NDK_DIR={template_ndk_path}"',
|
||||
'"-DANDROID_STL=c++_shared"',
|
||||
'"-Wno-deprecated"',
|
||||
@@ -835,8 +872,7 @@ class AndroidProjectGenerator(object):
|
||||
dest_src_main_path.mkdir(parents=True)
|
||||
az_android_package_env = AndroidProjectManifestEnvironment(engine_root=self.engine_root,
|
||||
project_path=self.project_path,
|
||||
android_sdk_version_number=self.android_sdk_version,
|
||||
android_ndk_platform_number=self.android_ndk_platform,
|
||||
android_sdk_version_number=self.android_sdk_platform,
|
||||
is_test=self.is_test_project)
|
||||
self.create_file_from_project_template(src_template_file=ANDROID_MANIFEST_FILE,
|
||||
template_env=az_android_package_env,
|
||||
@@ -1304,218 +1340,7 @@ class AndroidProjectGenerator(object):
|
||||
self.new = new
|
||||
|
||||
|
||||
ANDROID_PLATFORM_PATTERN = re.compile(r'([\w\d]*-)?(\d+\d*)') # Regex to handle android platform naming for both SDKs and NDKs
|
||||
|
||||
|
||||
def validate_android_platform_input(input_android_platform, platform_variable_type, min_version, max_version):
|
||||
"""
|
||||
Helper tool to support android platform number inputs and perform min/max version validation
|
||||
|
||||
:param input_android_platform: The inpuit argument to evaluate
|
||||
:param platform_variable_type: The type of platform version to validate (android sdk / android ndk)
|
||||
:param min_version: The minimum version to validate against
|
||||
:param max_version: The maximum version to validate against
|
||||
:return: The int version of the extracted platform number from the input
|
||||
"""
|
||||
# Validate the platform number's format and against the supported versions
|
||||
platform_number_match = ANDROID_PLATFORM_PATTERN.search(input_android_platform)
|
||||
if not platform_number_match or not platform_number_match.group(2) or (platform_number_match.group(1) and platform_number_match.group(1) != 'android-'):
|
||||
raise common.LmbrCmdError(f"Invalid {platform_variable_type} version value ({input_android_platform}). It must be "
|
||||
f"either 'XX' or android-'XX' where 'XX' is a platform number.",
|
||||
common.ERROR_CODE_INVALID_PARAMETER)
|
||||
|
||||
android_platform_number = int(platform_number_match.group(2))
|
||||
if android_platform_number < min_version:
|
||||
raise common.LmbrCmdError(f"Invalid {platform_variable_type} version value ({input_android_platform}) is less than the minimum "
|
||||
f"supported version ({min_version}).",
|
||||
common.ERROR_CODE_INVALID_PARAMETER)
|
||||
if android_platform_number > max_version:
|
||||
raise common.LmbrCmdError(f"Invalid {platform_variable_type} version value ({input_android_platform}) is greater than the maximum "
|
||||
f"supported version ({max_version}).",
|
||||
common.ERROR_CODE_INVALID_PARAMETER)
|
||||
return android_platform_number
|
||||
|
||||
|
||||
ANDROID_SDK_ENV_NAME = 'ANDROID_SDK'
|
||||
ANDROID_SDK_MIN_PLATFORM = 28
|
||||
ANDROID_SDK_MAX_PLATFORM = 29
|
||||
|
||||
|
||||
def verify_android_sdk(android_sdk_platform, argument_name, override_android_sdk_path=None, preferred_sdk_build_tools_ver=None):
|
||||
"""
|
||||
Verify the android sdk and the requested platform platform against the android sdk path
|
||||
|
||||
:param android_sdk_platform: The android sdk platform to use (e.g. '28' or 'android-28')
|
||||
:param argument_name: The name of the argument for descriptive errors to present
|
||||
:param override_android_sdk_path: The location of the android SDK path if not set through the environment variable
|
||||
:param preferred_sdk_build_tools_ver: Option prefered built tool version under the android SDK if available. Will fallback to the first one discovered
|
||||
:returns tuple of the verified android sdk platform number, path to the Android SDK path and the build tool version
|
||||
"""
|
||||
android_sdk_platform_number = validate_android_platform_input(input_android_platform=android_sdk_platform,
|
||||
platform_variable_type='android sdk',
|
||||
min_version=ANDROID_SDK_MIN_PLATFORM,
|
||||
max_version=ANDROID_SDK_MAX_PLATFORM)
|
||||
|
||||
# Get the candidate android sdk path from either the override argument or the system environment variable
|
||||
if override_android_sdk_path:
|
||||
check_android_sdk_path = override_android_sdk_path
|
||||
else:
|
||||
check_android_sdk_path = os.environ.get(ANDROID_SDK_ENV_NAME)
|
||||
if not check_android_sdk_path:
|
||||
raise common.LmbrCmdError(f"Android SDK path not set. Make sure that either the '{ANDROID_SDK_ENV_NAME}' environment is "
|
||||
f"set or it is passed in through the {argument_name} argument")
|
||||
|
||||
# The android sdk folder structure is expected to have a 'platforms' sub folder based on the android sdk-platform number
|
||||
check_android_sdk_path = pathlib.Path(check_android_sdk_path)
|
||||
android_sdk_platforms_path = check_android_sdk_path / 'platforms'
|
||||
if not android_sdk_platforms_path.is_dir():
|
||||
raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Missing 'platforms' directory.")
|
||||
|
||||
# Collect the available platform numbers from the platforms subdirectory
|
||||
validated_android_platforms = []
|
||||
for dir_item in android_sdk_platforms_path.iterdir():
|
||||
if not dir_item.is_dir():
|
||||
continue
|
||||
check_file = dir_item / 'package.xml'
|
||||
if check_file.is_file():
|
||||
validated_android_platforms.append(dir_item.name)
|
||||
|
||||
if not validated_android_platforms:
|
||||
raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Unable to find any android platforms.")
|
||||
|
||||
# Normalize the android_sdk argument to fit the same folder name pattern
|
||||
android_sdk_platform_name = f'android-{android_sdk_platform_number}'
|
||||
if android_sdk_platform_name not in validated_android_platforms:
|
||||
raise common.LmbrCmdError(f"Android SDK platform {android_sdk_platform_name} is not a valid for the android SDK located under '{str(check_android_sdk_path)}'")
|
||||
|
||||
# Enumerate through the build tools under android sdk
|
||||
android_sdk_build_tools_dir = check_android_sdk_path / 'build-tools'
|
||||
if not android_sdk_build_tools_dir.is_dir():
|
||||
raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Unable to find any built-tools folder.")
|
||||
supported_build_tools = [str(build_tool.name) for build_tool in android_sdk_build_tools_dir.iterdir() if build_tool.is_dir()]
|
||||
if not supported_build_tools:
|
||||
raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Unable to find any built-tools.")
|
||||
if preferred_sdk_build_tools_ver:
|
||||
if preferred_sdk_build_tools_ver in supported_build_tools:
|
||||
validated_build_tool = preferred_sdk_build_tools_ver
|
||||
else:
|
||||
validated_build_tool = supported_build_tools[0]
|
||||
logging.warning("Unable to locate android sdk build tool version {preferred_sdk_build_tools_ver}. Defaulting to version {validated_build_tool}")
|
||||
|
||||
else:
|
||||
validated_build_tool = supported_build_tools[0]
|
||||
|
||||
return android_sdk_platform_number, check_android_sdk_path, validated_build_tool
|
||||
|
||||
|
||||
ANDROID_NDK_ENV_NAME = 'ANDROID_NDK'
|
||||
ANDROID_NDK_MIN_PLATFORM = 21
|
||||
ANDROID_NDK_MAX_PLATFORM = 29
|
||||
ANDROID_NDK_SOURCE_PROPERTIES_REVISION_PATTERN = re.compile(r'Pkg.Revision\s*=\s*(\d+.\d+.\d+)')
|
||||
|
||||
|
||||
def verify_android_ndk(android_ndk_platform, argument_name, override_android_ndk_path=None):
|
||||
"""
|
||||
Verify the android ndk and requested platform against the android ndk path
|
||||
|
||||
:param android_ndk_platform: The android ndk platform to use (e.g. '21' or 'android-21')
|
||||
:param argument_name: The name of the argument for descriptive errors to present
|
||||
:param override_android_ndk_path: The location of the android NDK path if not set through the environment variable
|
||||
:returns tuple of the verified android ndk platform number and the Path to the Android SDK path and the
|
||||
"""
|
||||
|
||||
android_ndk_platform_number = validate_android_platform_input(input_android_platform=android_ndk_platform,
|
||||
platform_variable_type='android ndk',
|
||||
min_version=ANDROID_NDK_MIN_PLATFORM,
|
||||
max_version=ANDROID_NDK_MAX_PLATFORM)
|
||||
|
||||
# Get the candidate android ndk path from either the override argument or the system environment variable
|
||||
if override_android_ndk_path:
|
||||
check_android_ndk_path = str(override_android_ndk_path)
|
||||
else:
|
||||
check_android_ndk_path = os.environ.get(ANDROID_NDK_ENV_NAME)
|
||||
if not check_android_ndk_path:
|
||||
raise common.LmbrCmdError(f"Android NDK path not set. Make sure that either the {ANDROID_NDK_ENV_NAME} environment "
|
||||
f"is set or it is passed in through the {argument_name} argument")
|
||||
check_android_ndk_path = pathlib.Path(check_android_ndk_path)
|
||||
|
||||
# Validate the android ndk path
|
||||
|
||||
# Determine the NDK revision by reading the source.properties file
|
||||
ndk_source_properties_file = check_android_ndk_path / 'source.properties'
|
||||
if not ndk_source_properties_file.is_file():
|
||||
raise common.LmbrCmdError(f"Invalid Android NDK path '{str(check_android_ndk_path)}'. Missing 'source.properties' file.",
|
||||
common.ERROR_CODE_INVALID_PARAMETER)
|
||||
ndk_source_properties_file_content = ndk_source_properties_file.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING,
|
||||
errors=common.ENCODING_ERROR_HANDLINGS)
|
||||
|
||||
ndk_revision_match = ANDROID_NDK_SOURCE_PROPERTIES_REVISION_PATTERN.search(ndk_source_properties_file_content)
|
||||
if not ndk_revision_match:
|
||||
raise common.LmbrCmdError(f"Invalid Android NDK path '{str(check_android_ndk_path)}'. Unable to extract version from 'source.properties' file.",
|
||||
common.ERROR_CODE_INVALID_PARAMETER)
|
||||
ndk_revision_number = LooseVersion(ndk_revision_match.group(1))
|
||||
logging.info(f"Detected Android NDK Revision {str(ndk_revision_number)}")
|
||||
|
||||
# Collect the supported android platforms from the required 'platforms' folder under the ndk path
|
||||
android_ndk_platforms_path = check_android_ndk_path / 'platforms'
|
||||
if not android_ndk_platforms_path.is_dir():
|
||||
raise common.LmbrCmdError(f"Invalid Android NDK path '{str(check_android_ndk_path)}'. Missing 'platforms' folder.",
|
||||
common.ERROR_CODE_INVALID_PARAMETER)
|
||||
|
||||
validated_android_platforms = []
|
||||
for dir_item in android_ndk_platforms_path.iterdir():
|
||||
if not dir_item.is_dir():
|
||||
continue
|
||||
api_version_match = ANDROID_PLATFORM_PATTERN.search(dir_item.name)
|
||||
if not api_version_match or api_version_match.group(1) != 'android-':
|
||||
continue
|
||||
|
||||
check_lib_path = dir_item / 'arch-arm64/usr/lib'
|
||||
if check_lib_path.is_dir():
|
||||
validated_android_platforms.append(dir_item.name)
|
||||
|
||||
# For NDK revisions 19 and up, there is a mapping file for version numbers that map to other version.
|
||||
platforms_map_aliases = {}
|
||||
if ndk_revision_number >= LooseVersion('19.0.0'):
|
||||
platforms_map_file = check_android_ndk_path / 'meta/platforms.json'
|
||||
if platforms_map_file.exists():
|
||||
with open(platforms_map_file, 'r') as platforms_map_file_handle:
|
||||
platforms_map_file_json = json.load(platforms_map_file_handle)
|
||||
platforms_map_aliases = platforms_map_file_json['aliases']
|
||||
elif validated_android_platforms:
|
||||
# Revisions before 19 does not have a mapping file for API versions, they fall back to the previous one
|
||||
# So we need to make a mapping file that does the same
|
||||
platforms_map_aliases = {}
|
||||
validated_android_platforms.sort()
|
||||
max_supported_api_number = int(ANDROID_PLATFORM_PATTERN.search(validated_android_platforms[-1]).group(2))
|
||||
for validated_android_platform in validated_android_platforms:
|
||||
current_api_version = int(ANDROID_PLATFORM_PATTERN.search(validated_android_platform).group(2))
|
||||
next_api_version = current_api_version + 1
|
||||
while f'android-{next_api_version}' not in validated_android_platforms and next_api_version <= max_supported_api_number:
|
||||
platforms_map_aliases[str(next_api_version)] = current_api_version
|
||||
next_api_version += 1
|
||||
|
||||
# Go through the aliases and add to the validated platforms if it is mapped to an existing platform
|
||||
for alias_key, alias_value in platforms_map_aliases.items():
|
||||
if not ANDROID_PLATFORM_PATTERN.search(f'android-{alias_key}'):
|
||||
# Skip any non android-XX (XX = number) aliases
|
||||
continue
|
||||
aliased_platform_key = f'android-{alias_value}'
|
||||
if aliased_platform_key in validated_android_platforms:
|
||||
validated_android_platforms.append(f'android-{alias_key}')
|
||||
|
||||
if not validated_android_platforms:
|
||||
raise common.LmbrCmdError(f"Invalid Android NDK path {str(check_android_ndk_path)}")
|
||||
|
||||
# Verify the ndk platform against the ndk path
|
||||
android_ndk_platform_name = f'android-{android_ndk_platform_number}'
|
||||
if android_ndk_platform_name not in validated_android_platforms:
|
||||
raise common.LmbrCmdError(f"Android NDK platform {android_ndk_platform_name} is not a valid for the Android NDK located under '{str(check_android_ndk_path)}'")
|
||||
|
||||
return android_ndk_platform_number, check_android_ndk_path
|
||||
|
||||
|
||||
ADB_TARGET = 'adb.exe' if platform.system() == 'Windows' else 'adb'
|
||||
|
||||
|
||||
def resolve_adb_tool(android_sdk_path):
|
||||
@@ -1528,9 +1353,16 @@ def resolve_adb_tool(android_sdk_path):
|
||||
if isinstance(android_sdk_path, str):
|
||||
android_sdk_path = pathlib.Path(android_sdk_path)
|
||||
|
||||
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}'.")
|
||||
file_found = False
|
||||
for executable_path_ext in common.PLATFORM_EXECUTABLE_EXTENSIONS:
|
||||
check_adb_target = android_sdk_path / 'platform-tools' / f'adb{executable_path_ext}'
|
||||
if check_adb_target.is_file():
|
||||
file_found = True
|
||||
break
|
||||
|
||||
if not file_found:
|
||||
raise common.LmbrCmdError(f"Invalid Android SDK path '{str(android_sdk_path)}': Unable to locate 'adb'.")
|
||||
|
||||
return check_adb_target
|
||||
|
||||
|
||||
@@ -1633,3 +1465,196 @@ class AdbTool(common.CommandLineExec):
|
||||
else:
|
||||
adb_params = arguments
|
||||
return super().popen(adb_params, cwd)
|
||||
|
||||
|
||||
class AndroidGradlePluginInfo(object):
|
||||
|
||||
def __init__(self, android_gradle_plugin_version):
|
||||
|
||||
if android_gradle_plugin_version not in ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP.keys():
|
||||
raise common.LmbrCmdError(f"Android Gradle Plugin version {android_gradle_plugin_version} is not supported. "
|
||||
f"Only the following version(s) are supported: {','.join(ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP.keys())}")
|
||||
|
||||
details = ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP[android_gradle_plugin_version]
|
||||
self.default_sdk_build_tools_version = LooseVersion(details.get('sdk_build'))
|
||||
|
||||
self.default_ndk_version = LooseVersion(details.get('default_ndk'))
|
||||
|
||||
self.min_gradle_version = LooseVersion(details.get('min_gradle_version'))
|
||||
|
||||
self.min_cmake_version = LooseVersion(details.get('min_cmake_version'))
|
||||
|
||||
max_cmake_version_number = details.get('max_cmake_version')
|
||||
self.max_cmake_version = None if max_cmake_version_number is None else LooseVersion(max_cmake_version_number)
|
||||
|
||||
|
||||
class AndroidSDKResolver(object):
|
||||
"""
|
||||
Class that manages the Android SDK tool to validate, install packages (e.g. built tools, sdk platforms, ndk, etc)
|
||||
"""
|
||||
|
||||
class InstalledPackage(object):
|
||||
def __init__(self, installed_package_components):
|
||||
assert len(installed_package_components) == 4, '4 sections expected for installed package components (path, version, description, location)'
|
||||
self.path = installed_package_components[0]
|
||||
self.version = LooseVersion(installed_package_components[1])
|
||||
self.description = installed_package_components[2]
|
||||
self.location = installed_package_components[3]
|
||||
|
||||
class AvailablePackage(object):
|
||||
def __init__(self, available_package_components):
|
||||
assert len(available_package_components) == 3, '3 sections expected for installed package components (path, version, description)'
|
||||
self.path = available_package_components[0]
|
||||
self.version = LooseVersion(available_package_components[1])
|
||||
self.description = available_package_components[2]
|
||||
|
||||
class AvailableUpdate(object):
|
||||
def __init__(self, available_update_components):
|
||||
assert len(available_update_components) == 3, '3 sections expected for installed package components (path, version, available)'
|
||||
self.path = available_update_components[0]
|
||||
self.version = LooseVersion(available_update_components[1])
|
||||
self.available = available_update_components[2]
|
||||
|
||||
def __init__(self, android_sdk_path):
|
||||
|
||||
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'
|
||||
else:
|
||||
raise common.LmbrCmdError(f"This tool is not supported on the current platform {platform.system()}")
|
||||
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}")
|
||||
|
||||
self.sdk_manager = common.CommandLineExec(str(self.sdk_manager_path.resolve()))
|
||||
|
||||
self.installed_packages = {}
|
||||
self.available_packages = {}
|
||||
self.available_updates = {}
|
||||
self.refresh_sdk_installation()
|
||||
|
||||
def refresh_sdk_installation(self):
|
||||
"""
|
||||
Utilize the sdk_manager command line tool from the Android SDK to collect / refresh the list of
|
||||
installed, available, and updateable packages that are managed by the android SDK.
|
||||
"""
|
||||
self.installed_packages = {}
|
||||
self.available_packages = {}
|
||||
self.available_updates = {}
|
||||
|
||||
def _factory_installed_package(package_map, item_components):
|
||||
package_map[item_components[0]] = AndroidSDKResolver.InstalledPackage(item_components)
|
||||
|
||||
def _factory_available_package(package_map, item_components):
|
||||
package_map[item_components[0]] = AndroidSDKResolver.AvailablePackage(item_components)
|
||||
|
||||
def _factory_available_update(package_map, item_components):
|
||||
package_map[item_components[0]] = AndroidSDKResolver.AvailableUpdate(item_components)
|
||||
|
||||
# Use the SDK manager to collect the available and installed packages
|
||||
result_code, result_stdout, result_stderr = self.sdk_manager.exec(['--list'], capture_stdout=True, suppress_stderr=True)
|
||||
|
||||
current_append_map = None
|
||||
current_item_factory = None
|
||||
for package_item in result_stdout.split('\n'):
|
||||
package_item_stripped = package_item.strip()
|
||||
if not package_item_stripped:
|
||||
continue
|
||||
if '|' not in package_item_stripped:
|
||||
if package_item_stripped.upper() == 'INSTALLED PACKAGES:':
|
||||
current_append_map = self.installed_packages
|
||||
current_item_factory = _factory_installed_package
|
||||
elif package_item_stripped.upper() == 'AVAILABLE PACKAGES:':
|
||||
current_append_map = self.available_packages
|
||||
current_item_factory = _factory_available_package
|
||||
elif package_item_stripped.upper() == 'AVAILABLE UPDATES:':
|
||||
current_append_map = self.available_updates
|
||||
current_item_factory = _factory_available_update
|
||||
else:
|
||||
current_append_map = None
|
||||
current_item_factory = None
|
||||
continue
|
||||
item_parts = [split.strip() for split in package_item_stripped.split('|')]
|
||||
if len(item_parts) < 3:
|
||||
continue
|
||||
elif item_parts[1].upper() in ('VERSION', 'INSTALLED', '-------'):
|
||||
continue
|
||||
elif current_append_map is None:
|
||||
continue
|
||||
if current_append_map is not None and current_item_factory is not None:
|
||||
current_item_factory(current_append_map, item_parts)
|
||||
|
||||
def is_package_installed(self, search_package_path):
|
||||
"""
|
||||
Check if a package path to see if its a package that is installed. The path can use wildcard '*'s
|
||||
The function will return a list of the results that match the package paths, ordered by the newest version first
|
||||
"""
|
||||
def _package_sort(package):
|
||||
return package.version
|
||||
package_detail_result_list = []
|
||||
for installed_package_path, installed_package_details in self.installed_packages.items():
|
||||
if fnmatch.fnmatch(installed_package_path, search_package_path):
|
||||
package_detail_result_list.append(installed_package_details)
|
||||
package_detail_result_list.sort(reverse=True, key=_package_sort)
|
||||
return package_detail_result_list
|
||||
|
||||
def is_package_available(self, search_package_path):
|
||||
"""
|
||||
Check if a package path to see if its an available package to install. The path can use wildcard '*'s
|
||||
The function will return a list of the results that match the package paths, ordered by the newest version first
|
||||
"""
|
||||
def _package_sort(package):
|
||||
return package.version
|
||||
package_detail_result_list = []
|
||||
for available_package_path, available_package_details in self.available_packages.items():
|
||||
if fnmatch.fnmatch(available_package_path, search_package_path):
|
||||
package_detail_result_list.append(available_package_details)
|
||||
package_detail_result_list.sort(reverse=True, key=_package_sort)
|
||||
return package_detail_result_list
|
||||
|
||||
def install_package(self, package_install_path, package_description):
|
||||
"""
|
||||
Install a package based on the path of an available android sdk package
|
||||
"""
|
||||
|
||||
# Skip installation if the package is already installed
|
||||
package_result_list = self.is_package_installed(package_install_path)
|
||||
if package_result_list:
|
||||
installed_package_detail = package_result_list[0]
|
||||
logging.info(f"{installed_package_detail.description} (version {installed_package_detail.version}) Detected")
|
||||
return installed_package_detail
|
||||
|
||||
# Make sure the package name is available
|
||||
package_result_list = self.is_package_available(package_install_path)
|
||||
if not package_result_list:
|
||||
raise common.LmbrCmdError(f"Invalid Android SDK Package {package_description}: Bad package path {package_install_path}")
|
||||
|
||||
# Reverse sort and pick the first item, which should be the latest (if the install path contains wildcards)
|
||||
def _available_sort(item):
|
||||
return item.path
|
||||
|
||||
package_result_list.sort(reverse=True, key=_available_sort)
|
||||
|
||||
available_package_to_install = package_result_list[0] # For multiple hits, resolve to the first item which will be the latest version
|
||||
|
||||
# Perform the package installation
|
||||
logging.info(f"Installing {available_package_to_install.description} ...")
|
||||
result_code, result_stdout, result_stderr = self.sdk_manager.exec(['--install', available_package_to_install.path], capture_stdout=True, suppress_stderr=True)
|
||||
if result_code != 0:
|
||||
raise common.LmbrCmdError(f"Error installing package {available_package_to_install.path}: \n{result_stderr}")
|
||||
|
||||
# Refresh the tracked SDK Contents
|
||||
self.refresh_sdk_installation()
|
||||
|
||||
# Get the package details to verify
|
||||
package_result_list = self.is_package_installed(package_install_path)
|
||||
if package_result_list:
|
||||
installed_package_detail = package_result_list[0]
|
||||
logging.info(f"{installed_package_detail.description} (version {installed_package_detail.version}) Installed")
|
||||
return installed_package_detail
|
||||
else:
|
||||
raise common.LmbrCmdError(f"Error installing package {available_package_to_install.path}: \n{result_stderr}")
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ 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_MIN_VERSION = LooseVersion('6.5')
|
||||
GRADLE_MAX_VERSION = LooseVersion('7.0.0')
|
||||
GRADLE_VERSION_REGEX = re.compile(r"Gradle\s(\d+.\d+.?\d*)")
|
||||
GRADLE_EXECUTABLE = 'gradle.bat' if platform.system() == 'Windows' else 'gradle'
|
||||
@@ -48,9 +48,9 @@ def verify_gradle(override_gradle_path=None):
|
||||
|
||||
|
||||
CMAKE_ARGUMENT_NAME = '--cmake-install-path'
|
||||
CMAKE_MIN_VERSION = LooseVersion('3.17.0')
|
||||
CMAKE_MIN_VERSION = LooseVersion('3.19.0')
|
||||
CMAKE_VERSION_REGEX = re.compile(r'cmake version (\d+.\d+.?\d*)')
|
||||
CMAKE_EXECUTABLE = 'cmake.exe' if platform.system() == 'Windows' else 'cmake'
|
||||
CMAKE_EXECUTABLE = 'cmake'
|
||||
|
||||
|
||||
def verify_cmake(override_cmake_path=None):
|
||||
@@ -69,7 +69,7 @@ def verify_cmake(override_cmake_path=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'
|
||||
NINJA_EXECUTABLE = 'ninja'
|
||||
|
||||
|
||||
def verify_ninja(override_ninja_path=None):
|
||||
@@ -78,7 +78,7 @@ def verify_ninja(override_ninja_path=None):
|
||||
"""
|
||||
return common.verify_tool(override_tool_path=override_ninja_path,
|
||||
tool_name='ninja',
|
||||
tool_filename='ninja.exe' if platform.system() == 'Windows' else 'ninja',
|
||||
tool_filename='ninja',
|
||||
argument_name=NINJA_ARGUMENT_NAME,
|
||||
tool_version_argument='--version',
|
||||
tool_version_regex=NINJA_VERSION_REGEX,
|
||||
@@ -103,13 +103,21 @@ 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-version'
|
||||
ANDROID_SDK_PLATFORM_ARGUMENT_NAME = '--android-sdk-platform'
|
||||
ANDROID_SDK_PREFERRED_TOOL_VER = '--android-sdk-build-tool-version'
|
||||
|
||||
ANDROID_NATIVE_API_LEVEL = '--android-native-api-level'
|
||||
|
||||
|
||||
MIN_ANDROID_SDK_PLATFORM = 28 # The minimum platform/api level that is supported for the SDK Platform
|
||||
MIN_NATIVE_API_LEVEL = 24 # The minimum Native API level that is supported for the NDK
|
||||
|
||||
|
||||
ANDROID_NDK_ARGUMENT_NAME = '--android-ndk-path'
|
||||
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")
|
||||
|
||||
# Constants for asset-related options for APK generation
|
||||
INCLUDE_APK_ASSETS_ARGUMENT_NAME = "--include-apk-assets"
|
||||
ASSET_MODE_ARGUMENT_NAME = "--asset-mode"
|
||||
@@ -147,6 +155,7 @@ def main(args):
|
||||
|
||||
parser = argparse.ArgumentParser(description="Prepare the android studio subfolder")
|
||||
|
||||
# Required Arguments
|
||||
parser.add_argument('--engine-root',
|
||||
help='The path to the engine root. Defaults to the current working directory.',
|
||||
default=os.getcwd())
|
||||
@@ -160,32 +169,42 @@ def main(args):
|
||||
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',
|
||||
parser.add_argument('-g', '--project-path',
|
||||
help='The project path to generate an android project',
|
||||
required=True)
|
||||
|
||||
parser.add_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME,
|
||||
help=f'The android SDK platform number version to use for the APK. (Minimum {MIN_ANDROID_SDK_PLATFORM})',
|
||||
type=int,
|
||||
default=-1)
|
||||
|
||||
parser.add_argument(ANDROID_NATIVE_API_LEVEL,
|
||||
help=f'The android native API level to use for the APK. If not set, this will default to the android SDK platform. (Minimum {MIN_ANDROID_SDK_PLATFORM})',
|
||||
type=int,
|
||||
default=-1)
|
||||
|
||||
# Override arguments
|
||||
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,
|
||||
help='The android SDK build tools version.',
|
||||
required=False)
|
||||
|
||||
parser.add_argument(ANDROID_NDK_PLATFORM_ARGUMENT_NAME,
|
||||
help='The android NDK version',
|
||||
required=True)
|
||||
required=False)
|
||||
|
||||
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(ANDROID_GRADLE_PLUGIN_ARGUMENT_NAME,
|
||||
help=f'The version of the android gradle plugin to use. Defaults to the minimum version ({ANDROID_GRADLE_MIN_PLUGIN_VERSION})',
|
||||
default=str(ANDROID_GRADLE_MIN_PLUGIN_VERSION))
|
||||
|
||||
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,
|
||||
@@ -196,9 +215,6 @@ def main(args):
|
||||
default=None,
|
||||
required=False)
|
||||
|
||||
parser.add_argument('-g', '--project-path',
|
||||
help='The project path to generate an android project')
|
||||
|
||||
# Asset Options
|
||||
parser.add_argument(INCLUDE_APK_ASSETS_ARGUMENT_NAME,
|
||||
action='store_true',
|
||||
@@ -207,11 +223,11 @@ def main(args):
|
||||
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')
|
||||
help=f'Asset Mode (vfs|pak|loose) to use when including assets into the APK. (Defaults to {ASSET_MODE_LOOSE})')
|
||||
|
||||
parser.add_argument(ASSET_TYPE_ARGUMENT_NAME,
|
||||
default=DEFAULT_ASSET_TYPE,
|
||||
help='Asset Type to use when including assets into the APK')
|
||||
help=f'Asset Type to use when including assets into the APK. (Defaults to {DEFAULT_ASSET_TYPE})')
|
||||
|
||||
parser.add_argument('--debug',
|
||||
action='store_true',
|
||||
@@ -260,16 +276,81 @@ def main(args):
|
||||
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))
|
||||
# Get the android sdk platform version to use from the arguments, but also handle the deprecated argument name
|
||||
android_sdk_platform_version = parsed_args.get_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME)
|
||||
|
||||
# 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))
|
||||
# Get the gradle plugin details and validate against the current environment
|
||||
android_gradle_plugin_version = parsed_args.get_argument(ANDROID_GRADLE_PLUGIN_ARGUMENT_NAME)
|
||||
android_gradle_plugin = android_support.AndroidGradlePluginInfo(android_gradle_plugin_version)
|
||||
logging.info(f"Generating Android Gradle Plugin version {android_gradle_plugin_version} based project")
|
||||
|
||||
if gradle_version < android_gradle_plugin.min_gradle_version:
|
||||
raise common.LmbrCmdError(f"The current version of gradle ({gradle_version}) does not satisfy the minimum version "
|
||||
f"({android_gradle_plugin.min_gradle_version}) needed for the android gradle plugin "
|
||||
f"({android_gradle_plugin_version}). Please upgrade your gradle.")
|
||||
if cmake_version < android_gradle_plugin.min_cmake_version:
|
||||
raise common.LmbrCmdError(f"The current version of cmake ({cmake_version}) does not satisfy the minimum version "
|
||||
f"({android_gradle_plugin.min_cmake_version}) needed for the android gradle plugin "
|
||||
f"({android_gradle_plugin_version}). Please upgrade your cmake.")
|
||||
if android_gradle_plugin.max_cmake_version and cmake_version > android_gradle_plugin.max_cmake_version:
|
||||
raise common.LmbrCmdError(f"The current version of cmake ({cmake_version}) exceeds the maximum version "
|
||||
f"({android_gradle_plugin.max_cmake_version}) of the android gradle plugin "
|
||||
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))
|
||||
|
||||
# If no SDK platform is provided, check for any installed one
|
||||
if android_sdk_platform_version < 0:
|
||||
android_sdk_platform_version = MIN_ANDROID_SDK_PLATFORM
|
||||
installed_android_sdk_platforms = android_sdk.is_package_installed('platforms;*')
|
||||
if installed_android_sdk_platforms:
|
||||
# If there are installed platforms, check the most recent one
|
||||
latest_platform_version = -1
|
||||
for installed_android_sdk_platform in installed_android_sdk_platforms:
|
||||
platform_number_match = re.match(r'platforms;android-([0-9]*)', installed_android_sdk_platform.path)
|
||||
if not platform_number_match:
|
||||
continue
|
||||
check_platform_version = int(platform_number_match.group(1))
|
||||
if check_platform_version > latest_platform_version:
|
||||
latest_platform_version = check_platform_version
|
||||
if latest_platform_version >= MIN_ANDROID_SDK_PLATFORM:
|
||||
android_sdk_platform_version = latest_platform_version
|
||||
else:
|
||||
if android_sdk_platform_version < MIN_ANDROID_SDK_PLATFORM:
|
||||
raise common.LmbrCmdError(f"Invalid argument for {ANDROID_SDK_PLATFORM_ARGUMENT_NAME} ({android_sdk_platform_version}). Must be greater than the minimum value supported {MIN_ANDROID_SDK_PLATFORM}.")
|
||||
|
||||
# Get the android native api level from the arguments. Default to the sdk platform version if not provided
|
||||
android_native_api_level = parsed_args.get_argument(ANDROID_NATIVE_API_LEVEL)
|
||||
if android_native_api_level < 0:
|
||||
android_native_api_level = android_sdk_platform_version
|
||||
else:
|
||||
if android_native_api_level < MIN_NATIVE_API_LEVEL:
|
||||
raise common.LmbrCmdError(f"Invalid argument for {ANDROID_NATIVE_API_LEVEL} ({android_native_api_level}). Must be greater than the minimum value supported {MIN_NATIVE_API_LEVEL}.")
|
||||
|
||||
# Check and make sure that the requested sdk platform exists, download if necessary
|
||||
platform_package_name = f"platforms;android-{android_sdk_platform_version}"
|
||||
android_sdk.install_package(package_install_path=platform_package_name,
|
||||
package_description=f'Android SDK Platform {android_sdk_platform_version}')
|
||||
|
||||
# Make sure we have the extra android packages "market_apk_expansion" and "market_licensing" which is needed by the APK
|
||||
android_sdk.install_package(package_install_path='extras;google;market_apk_expansion',
|
||||
package_description='Google APK Expansion Library')
|
||||
|
||||
android_sdk.install_package(package_install_path='extras;google;market_licensing',
|
||||
package_description='Google Play Licensing Library')
|
||||
|
||||
# Install either the requested SDK build tools or the default one for the android gradle plugin version
|
||||
build_tools_version = parsed_args.get_argument(ANDROID_SDK_PREFERRED_TOOL_VER) or android_gradle_plugin.default_sdk_build_tools_version
|
||||
build_tools_package_name = f'build-tools;{build_tools_version}'
|
||||
build_tools_package = android_sdk.install_package(package_install_path=build_tools_package_name,
|
||||
package_description='Android SDK Build Tools')
|
||||
|
||||
# Install either the requested NDK version or the default one for the android gradle plugin version
|
||||
android_ndk_version = parsed_args.get_argument(ANDROID_NDK_PLATFORM_ARGUMENT_NAME) or android_gradle_plugin.default_ndk_version
|
||||
android_ndk_package_name = f'ndk;{android_ndk_version}'
|
||||
android_ndk_package = android_sdk.install_package(package_install_path=android_ndk_package_name,
|
||||
package_description='Android NDK')
|
||||
|
||||
# Verify the engine root path and project path
|
||||
verified_project_path, verified_engine_root = common.verify_project_and_engine_root(project_root=parsed_args.project_path,
|
||||
@@ -277,10 +358,9 @@ def main(args):
|
||||
is_test_project = parsed_args.unit_test
|
||||
|
||||
# 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),
|
||||
third_party_path = pathlib.Path(parsed_args.third_party_path)
|
||||
if not third_party_path.is_dir():
|
||||
raise common.LmbrCmdError(f"Invalid --third-party-path '{parsed_args.third_party_path}'.",
|
||||
common.ERROR_CODE_INVALID_PARAMETER)
|
||||
third_party_path = third_party_path.parent
|
||||
|
||||
@@ -293,23 +373,23 @@ def main(args):
|
||||
|
||||
logging.debug("Engine Root : %s", str(verified_engine_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(engine_root=verified_engine_root,
|
||||
project_path=verified_project_path,
|
||||
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,
|
||||
android_sdk_path=android_sdk.android_sdk_path,
|
||||
build_tool=build_tools_package,
|
||||
android_sdk_platform=android_sdk_platform_version,
|
||||
android_native_api_level=android_native_api_level,
|
||||
android_ndk=android_ndk_package,
|
||||
project_path=verified_project_path,
|
||||
third_party_path=third_party_path,
|
||||
cmake_version=cmake_version,
|
||||
override_cmake_path=override_cmake_path,
|
||||
override_gradle_path=override_gradle_path,
|
||||
gradle_version=gradle_version,
|
||||
gradle_plugin_version=android_gradle_plugin_version,
|
||||
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),
|
||||
|
||||
@@ -170,117 +170,3 @@ def test_verify_ninja(tmpdir, from_override, version_str, expected_result):
|
||||
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
|
||||
|
||||
|
||||
+30
-7
@@ -55,6 +55,7 @@ ENGINE_ROOT_CHECK_FILE = 'engine.json'
|
||||
HASH_CHUNK_SIZE = 200000
|
||||
|
||||
|
||||
|
||||
class LmbrCmdError(Exception):
|
||||
"""
|
||||
Wrapper class to the general exception class where will absorb and prevent the printing of stack.
|
||||
@@ -244,6 +245,19 @@ def load_template_file(template_file_path, template_env):
|
||||
raise FileNotFoundError(f"Invalid file path. Cannot find template file located at {str(template_file_path)}")
|
||||
|
||||
|
||||
# Determine the possible file extensions for executable files based on the host platform
|
||||
PLATFORM_EXECUTABLE_EXTENSIONS = [''] # Files without extensions are always considered
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
# Windows manages its executable extensions through the %PATHEXT% environment variable
|
||||
path_extensions_str = os.environ.get('PATHEXT', default='.EXE;.COM;.BAT;.CMD')
|
||||
PLATFORM_EXECUTABLE_EXTENSIONS.extend([pathext.lower() for pathext in path_extensions_str.split(';')])
|
||||
elif platform.system() == 'Linux':
|
||||
PLATFORM_EXECUTABLE_EXTENSIONS = ['', '.out']
|
||||
else:
|
||||
PLATFORM_EXECUTABLE_EXTENSIONS = ['']
|
||||
|
||||
|
||||
def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, tool_version_argument, tool_version_regex, min_version, max_version):
|
||||
"""
|
||||
Support method to validate a required system tool needed for the build either through an installed tool in the
|
||||
@@ -270,12 +284,21 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too
|
||||
elif not isinstance(override_tool_path, pathlib.Path):
|
||||
raise LmbrCmdError(f"Invalid {tool_name} path argument. '{override_tool_path}' must be a string or Path",
|
||||
ERROR_CODE_INVALID_PARAMETER)
|
||||
check_tool_path = override_tool_path / tool_filename
|
||||
|
||||
if not check_tool_path.is_file():
|
||||
check_tool_path = pathlib.Path(override_tool_path) / 'bin' / tool_filename
|
||||
file_found = False
|
||||
for executable_path_ext in PLATFORM_EXECUTABLE_EXTENSIONS:
|
||||
check_tool_filename = f'{tool_filename}{executable_path_ext}'
|
||||
|
||||
if not check_tool_path.is_file():
|
||||
check_tool_path = override_tool_path / check_tool_filename
|
||||
if check_tool_path.is_file():
|
||||
file_found = True
|
||||
break
|
||||
check_tool_path = override_tool_path / 'bin' / check_tool_filename
|
||||
if check_tool_path.is_file():
|
||||
file_found = True
|
||||
break
|
||||
|
||||
if not file_found:
|
||||
raise LmbrCmdError(f"Invalid {tool_name} path argument. '{override_tool_path}' is not a valid {tool_name} path",
|
||||
ERROR_CODE_INVALID_PARAMETER)
|
||||
resolved_override_tool_path = str(check_tool_path.resolve())
|
||||
@@ -284,7 +307,7 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too
|
||||
else:
|
||||
resolved_override_tool_path = None
|
||||
tool_source = tool_name
|
||||
tool_desc = "installed gradle in the system path"
|
||||
tool_desc = f"installed {tool_name} in the system path"
|
||||
|
||||
# Extract the version and verify
|
||||
version_output = subprocess.check_output([tool_source, tool_version_argument],
|
||||
@@ -296,10 +319,10 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too
|
||||
result_version = LooseVersion(str(version_match.group(1)).strip())
|
||||
|
||||
if min_version and result_version < min_version:
|
||||
raise LmbrCmdError(f"The {tool_desc} does not meet the minimum version of gradle required ({str(min_version)}).",
|
||||
raise LmbrCmdError(f"The {tool_desc} does not meet the minimum version of {tool_name} required ({str(min_version)}).",
|
||||
ERROR_CODE_ENVIRONMENT_ERROR)
|
||||
elif max_version and result_version > max_version:
|
||||
raise LmbrCmdError(f"The {tool_desc} exceeds maximum version of gradle supported ({str(max_version)}).",
|
||||
raise LmbrCmdError(f"The {tool_desc} exceeds maximum version of {tool_name} supported ({str(max_version)}).",
|
||||
ERROR_CODE_ENVIRONMENT_ERROR)
|
||||
|
||||
return result_version, resolved_override_tool_path
|
||||
|
||||
@@ -141,10 +141,8 @@
|
||||
"COMMAND":"gradle_windows.cmd",
|
||||
"PARAMETERS": {
|
||||
"CONFIGURATION":"profile",
|
||||
"OUTPUT_DIRECTORY":"build\\android_gradle",
|
||||
"OUTPUT_DIRECTORY":"build\\ad_grd",
|
||||
"GAME_PROJECT": "AutomatedTesting",
|
||||
"ANDROID_NDK_PLATFORM": "21",
|
||||
"ANDROID_SDK_PLATFORM": "29",
|
||||
"SIGN_APK": "false",
|
||||
"GRADLE_BUILD_CMD": "build",
|
||||
"ADDITIONAL_GENERATE_ARGS": ""
|
||||
@@ -158,8 +156,6 @@
|
||||
"CONFIGURATION":"profile",
|
||||
"OUTPUT_DIRECTORY":"build\\android_unittest",
|
||||
"GAME_PROJECT": "AutomatedTesting",
|
||||
"ANDROID_NDK_PLATFORM": "21",
|
||||
"ANDROID_SDK_PLATFORM": "29",
|
||||
"SIGN_APK": "true",
|
||||
"GRADLE_BUILD_CMD": "assemble",
|
||||
"ADDITIONAL_GENERATE_ARGS": "--unit-test"
|
||||
|
||||
@@ -17,20 +17,12 @@ IF NOT EXIST "%LY_3RDPARTY_PATH%" (
|
||||
GOTO :error
|
||||
)
|
||||
|
||||
IF NOT EXIST "%GRADLE_HOME%" (
|
||||
IF NOT EXIST "%GRADLE_BUILD_HOME%" (
|
||||
REM This is the default for developers
|
||||
SET GRADLE_HOME=C:\Gradle\gradle-5.6.4
|
||||
SET GRADLE_BUILD_HOME=C:\Gradle\gradle-7.0
|
||||
)
|
||||
IF NOT EXIST "%GRADLE_HOME%" (
|
||||
ECHO [ci_build] FAIL: GRADLE_HOME=%GRADLE_HOME%
|
||||
GOTO :error
|
||||
)
|
||||
|
||||
IF NOT EXIST "%CMAKE_HOME%" (
|
||||
SET CMAKE_HOME=%LY_3RDPARTY_PATH%/CMake/3.19.1/Windows/
|
||||
)
|
||||
IF NOT EXIST "%CMAKE_HOME%" (
|
||||
ECHO [ci_build] FAIL: CMAKE_HOME=%CMAKE_HOME%
|
||||
IF NOT EXIST "%GRADLE_BUILD_HOME%" (
|
||||
ECHO [ci_build] FAIL: GRADLE_BUILD_HOME=%GRADLE_BUILD_HOME%
|
||||
GOTO :error
|
||||
)
|
||||
|
||||
@@ -50,20 +42,9 @@ ECHO Ninja wasnt in the call path, add the value set by LY_NINJA_PATH
|
||||
SET PATH=%PATH%;%LY_NINJA_PATH%
|
||||
|
||||
:ninja_on_path
|
||||
IF NOT EXIST "%LY_ANDROID_SDK%" (
|
||||
SET LY_ANDROID_SDK=!LY_3RDPARTY_PATH!/android-sdk/platform-29
|
||||
)
|
||||
IF NOT EXIST "%LY_ANDROID_SDK%" (
|
||||
ECHO [ci_build] FAIL: LY_ANDROID_SDK=!LY_ANDROID_SDK!
|
||||
GOTO :error
|
||||
)
|
||||
|
||||
IF NOT EXIST "%LY_ANDROID_NDK%" (
|
||||
set LY_ANDROID_NDK=!LY_3RDPARTY_PATH!/android-ndk/r21d
|
||||
)
|
||||
IF NOT EXIST "%LY_ANDROID_NDK%" (
|
||||
ECHO [ci_build] LY_ANDROID_NDK=!LY_ANDROID_NDK!
|
||||
GOTO :error
|
||||
IF NOT "%ANDROID_GRADLE_PLUGIN%" == "" (
|
||||
set ANDROID_GRADLE_PLUGIN_OPTION=--gradle-plugin-version=%ANDROID_GRADLE_PLUGIN%
|
||||
)
|
||||
|
||||
IF NOT EXIST %OUTPUT_DIRECTORY% (
|
||||
@@ -154,11 +135,11 @@ IF "%GENERATE_SIGNED_APK%"=="true" (
|
||||
ECHO Using keystore file at %CI_ANDROID_KEYSTORE_FILE_ABS%
|
||||
)
|
||||
|
||||
ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %OPTIONAL_TEST_FLAG% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing
|
||||
CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing
|
||||
ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-sdk-path=%ANDROID_HOME% %ANDROID_GRADLE_PLUGIN_OPTION% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing
|
||||
CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-sdk-path=%ANDROID_HOME% %ANDROID_GRADLE_PLUGIN_OPTION% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing
|
||||
) ELSE (
|
||||
ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing
|
||||
CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing
|
||||
ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% %GRADLE_OVERRIDE_OPTION% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% %ANDROID_GRADLE_PLUGIN_OPTION% --android-sdk-path=%ANDROID_HOME% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing
|
||||
CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% %ANDROID_GRADLE_PLUGIN_OPTION% --android-sdk-path=%ANDROID_HOME% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing
|
||||
)
|
||||
|
||||
REM Validate the android project generation
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ENV": {
|
||||
"GRADLE_HOME": "C:/Gradle/gradle-5.6.4",
|
||||
"NODE_LABEL": "windows-047e5cdf",
|
||||
"GRADLE_HOME": "C:/Gradle/gradle-7.0",
|
||||
"NODE_LABEL": "windows-b3c8994f1",
|
||||
"LY_3RDPARTY_PATH": "C:/ly/3rdParty",
|
||||
"TIMEOUT": 30,
|
||||
"WORKSPACE": "D:/workspace",
|
||||
|
||||
Reference in New Issue
Block a user