ソースを参照

adding ability to detect if using installer SDK

Signed-off-by: TJ Kotha <[email protected]>
TJ Kotha 1 年間 前
コミット
12dcaca9ca

+ 2 - 1
cmake/install/engine.json.in

@@ -11,5 +11,6 @@
     "gem_names": [@O3DE_INSTALL_ENGINE_GEMS@],
     "projects": [@O3DE_INSTALL_PROJECTS@],
     "repos": [@O3DE_INSTALL_REPOS@],
-    "templates": [@O3DE_INSTALL_TEMPLATES@]
+    "templates": [@O3DE_INSTALL_TEMPLATES@],
+    "sdk_engine": true
 }

+ 33 - 6
scripts/o3de/ExportScripts/export_source_built_project.py

@@ -11,6 +11,7 @@ import logging
 import sys
 
 import o3de.export_project as exp
+import o3de.manifest as manifest
 import pathlib
 
 from typing import List
@@ -22,6 +23,8 @@ def export_standalone_project(ctx: exp.O3DEScriptExportContext,
                               should_build_tools: bool,
                               build_config: str,
                               seedlist_paths: List[pathlib.Path],
+                              seedfile_paths: List[pathlib.Path],
+                              level_names: List[str],
                               game_project_file_patterns_to_copy: List[str] = [],
                               server_project_file_patterns_to_copy: List[str] = [],
                               project_file_patterns_to_copy: List[str] = [],
@@ -50,6 +53,8 @@ def export_standalone_project(ctx: exp.O3DEScriptExportContext,
     :param should_build_tools:                      Option to build the export process dependent tools (AssetProcessor, AssetBundlerBatch, and dependencies)
     :param build_config:                            The build configuration to use for the export package launchers
     :param seedlist_paths:                          List of seed list files to optionally include in the asset bundling process
+    :param seedfile_paths:                          List of seed files to optionally include in the asset bundling process. These can be individual O3DE assets, such as levels or prefabs.
+    :param level_names:                             List of individual level names. These are assumed to follow standard O3DE level convention, and will be used in the asset bundler.
     :param game_project_file_patterns_to_copy:      List of game (or unified) launcher specific files to include in the game/unified package
     :param server_project_file_patterns_to_copy:    List of server (or unified) launcher specific files to include in the server/unified package
     :param project_file_patterns_to_copy:           List of general file patterns to include in all packages
@@ -69,6 +74,9 @@ def export_standalone_project(ctx: exp.O3DEScriptExportContext,
     :param logger:                                  Optional logger to use to log the process and errors
     """
 
+    is_installer_sdk = manifest.is_sdk_engine(engine_path=ctx.engine_path)
+    project_name = manifest.get_project_json_data(project_path=ctx.project_path)['project_name']
+
     # Use a provided logger or get the current system one
     if not logger:
         logger = logging.getLogger()
@@ -77,7 +85,10 @@ def export_standalone_project(ctx: exp.O3DEScriptExportContext,
     # Calculate the tools and game build paths
     default_base_build_path = ctx.engine_path / 'build' if engine_centric else ctx.project_path / 'build'
     if not tools_build_path:
-        tools_build_path = default_base_build_path / 'tools'
+        if is_installer_sdk:
+            tools_build_path = ctx.engine_path / 'bin' / exp.get_platform_installer_folder_name(selected_platform) / 'profile/Default'
+        else:
+            tools_build_path = default_base_build_path / 'tools'
     if not launcher_build_path:
         launcher_build_path = default_base_build_path / 'launcher'
     if not asset_bundling_path:
@@ -86,12 +97,17 @@ def export_standalone_project(ctx: exp.O3DEScriptExportContext,
     # Resolve (if possible) and validate any provided seedlist files
     validated_seedslist_paths = exp.validate_project_artifact_paths(project_path=ctx.project_path,
                                                                     artifact_paths=seedlist_paths)
+    
+    # Convert level names into seed files that the asset bundler can utilize for packaging
+    for level in level_names:
+        seedfile_paths.append(str(pathlib.PurePath('levels') / level.lower() / (level.lower() + ".spawnable")))
 
     # Make sure there are no running processes for the current project before continuing
     exp.kill_existing_processes(ctx.project_name)
 
     # Optionally build the toolchain needed to bundle the assets
-    if should_build_tools:
+    # Do not run this logic if we're using an installer SDK engine. Tool binaries should already exist
+    if should_build_tools and not is_installer_sdk:
         exp.build_export_toolchain(ctx=ctx,
                                    tools_build_path=tools_build_path,
                                    engine_centric=engine_centric,
@@ -120,24 +136,29 @@ def export_standalone_project(ctx: exp.O3DEScriptExportContext,
     # Optionally build the assets
     if should_build_all_assets:
         asset_processor_path = exp.get_asset_processor_batch_path(tools_build_path=tools_build_path,
+                                                                  using_installer_sdk=is_installer_sdk, 
                                                                   required=True)
         logger.info(f"Using '{asset_processor_path}' to process the assets.")
         exp.build_assets(ctx=ctx,
                          tools_build_path=tools_build_path,
                          engine_centric=engine_centric,
                          fail_on_ap_errors=fail_on_asset_errors,
+                         using_installer_sdk=is_installer_sdk,
                          logger=logger)
 
     # Generate the bundle
     asset_bundler_path = exp.get_asset_bundler_batch_path(tools_build_path=tools_build_path,
+                                                          using_installer_sdk=is_installer_sdk,
                                                           required=True)
     logger.info(f"Using '{asset_bundler_path}' to bundle the assets.")
     expected_bundles_path = exp.bundle_assets(ctx=ctx,
                                               selected_platform=selected_platform,
                                               seedlist_paths=validated_seedslist_paths,
+                                              seedfile_paths=seedfile_paths,
                                               tools_build_path=tools_build_path,
                                               engine_centric=engine_centric,
                                               asset_bundling_path=asset_bundling_path,
+                                              using_installer_sdk=is_installer_sdk,
                                               max_bundle_size=max_bundle_size)
 
     # Prepare the different layouts based on the desired launcher types
@@ -213,6 +234,10 @@ if "o3de_context" in globals():
                             help='Option to fail the project export process on any failed asset during asset building (applicable if --should-build-assets is true)')
         parser.add_argument('-sl', '--seedlist', type=pathlib.Path, dest='seedlist_paths', action='append',
                                 help='Path to a seed list file for asset bundling. Specify multiple times for each seed list.')
+        parser.add_argument('-sf', '--seedfile', type=pathlib.Path, dest='seedfile_paths', action='append',
+                            help='Path to a seed file for asset bundling. Example seed files are levels or prefabs.')
+        parser.add_argument('-lvl', '--level-name', type=str, dest='level_names', action='append',
+                            help='The name of the level you want to export. This will look in <o3de_project_path>/Levels to fetch the right level prefab.')
         parser.add_argument('-gpfp', '--game-project-file-pattern-to-copy', type=str, dest='game_project_file_patterns_to_copy', action='append',
                                 help="Any additional file patterns located in the project directory. File patterns will be relative to the project path. Will be included in GameLauncher.")
         parser.add_argument('-spfp', '--server-project-file-pattern-to-copy', type=str, dest='server_project_file_patterns_to_copy', action='append',
@@ -230,10 +255,10 @@ if "o3de_context" in globals():
         parser.add_argument('-abp', '--asset-bundling-path', type=pathlib.Path, default=None,
                             help="Designates where the artifacts from the asset bundling process will be written to before creation of the package. If not specified, default is <o3de_project_path>/build/asset_bundling.")
         parser.add_argument('-maxsize', '--max-bundle-size', type=int, default=2048, help='Specify the maximum size of a given asset bundle.')
-        parser.add_argument('-nogame', '--no-game-launcher', action='store_true', help='This flag skips building the Game Launcher on a platform if not needed.')
-        parser.add_argument('-noserver', '--no-server-launcher', action='store_true', help='This flag skips building the Server Launcher on a platform if not needed.')
-        parser.add_argument('-noheadless', '--no-headless-server-launcher', action='store_true', help='This flag skips building the Headless Server Launcher on a platform if not needed.')
-        parser.add_argument('-nounified', '--no-unified-launcher', action='store_true', help='This flag skips building the Unified Launcher on a platform if not needed.')
+        parser.add_argument('-nogame', '--no-game-launcher', default=False, action='store_true', help='This flag skips building the Game Launcher on a platform if not needed.')
+        parser.add_argument('-noserver', '--no-server-launcher', default=False, action='store_true', help='This flag skips building the Server Launcher on a platform if not needed.')
+        parser.add_argument('-noheadless', '--no-headless-server-launcher', default=False, action='store_true', help='This flag skips building the Headless Server Launcher on a platform if not needed.')
+        parser.add_argument('-nounified', '--no-unified-launcher', default=False, action='store_true', help='This flag skips building the Unified Launcher on a platform if not needed.')
         parser.add_argument('-pl', '--platform', type=str, default=exp.get_default_asset_platform(), choices=['pc', 'linux', 'mac'])
         parser.add_argument('-ec', '--engine-centric', action='store_true', default=False, help='Option use the engine-centric work flow to export the project.')
         parser.add_argument('-q', '--quiet', action='store_true', help='Suppresses logging information unless an error occurs.')
@@ -258,6 +283,8 @@ if "o3de_context" in globals():
                                   should_build_tools=args.build_tools,
                                   build_config=args.config,
                                   seedlist_paths=[] if not args.seedlist_paths else args.seedlist_paths,
+                                  seedfile_paths=[] if not args.seedfile_paths else args.seedfile_paths,
+                                  level_names=[] if not args.level_names else args.level_names,
                                   game_project_file_patterns_to_copy=[] if not args.game_project_file_patterns_to_copy else args.game_project_file_patterns_to_copy,
                                   server_project_file_patterns_to_copy=[] if not args.server_project_file_patterns_to_copy else args.server_project_file_patterns_to_copy,
                                   project_file_patterns_to_copy=[] if not args.project_file_patterns_to_copy else args.project_file_patterns_to_copy,

+ 32 - 6
scripts/o3de/o3de/export_project.py

@@ -195,6 +195,12 @@ def get_default_asset_platform():
                                             'darwin':  'mac' }
     return host_platform_to_asset_platform_map.get(platform.system().lower(), "")
 
+def get_platform_installer_folder_name():
+    host_platform_to_installer_name_map = {'pc': 'Windows',
+                                           'linux': 'Linux',
+                                           'mac': 'Mac'}
+    return host_platform_to_installer_name_map.get(platform.lower(), "")
+
 def process_command(args: list,
                     cwd: pathlib.Path = None,
                     env: os._Environ = None) -> int:
@@ -266,7 +272,7 @@ def extract_cmake_custom_args(arg_list: List[str])->tuple:
                 in_cca = False
             elif arg in ('-cba', '--cmake-build-arg'):
                 in_cca = False
-                in_cca = True
+                in_cba = True
             elif arg not in ('-cca', '--cmake-configure-arg'):
                 cmake_configure_args.append(arg)
         elif in_cba:
@@ -369,31 +375,40 @@ def add_args(subparsers) -> None:
 
 
 def get_asset_processor_batch_path(tools_build_path: pathlib.Path,
+                                   using_installer_sdk: bool = False,
                                    required: bool = False) -> pathlib.Path:
     """
     Get the expected path to the asset processor tool
 
     @param tools_build_path:    The tools (cmake) build path to locate AssetProcessorBatch
+    @param using_installer_sdk: Indicate if the tools path belongs to an installer SDK. If True, expect the path to point at the folder containing the executable.
     @param required:            If true, check if the asset processor actually exists on file at the expected location, and raise an error if not
     @return: Path to the asset processor tool
     """
-    asset_processor_batch_path = tools_build_path / f'bin/profile/AssetProcessorBatch{EXECUTABLE_EXTENSION}'
+    if using_installer_sdk:
+        asset_processor_batch_path = tools_build_path / f'AssetProcessorBatch{EXECUTABLE_EXTENSION}'
+    else:
+        asset_processor_batch_path = tools_build_path / f'bin/profile/AssetProcessorBatch{EXECUTABLE_EXTENSION}'
     if required and not asset_processor_batch_path.is_file():
         raise ExportProjectError(f"Missing the 'AssetProcessorBatch' tool, expected at '{asset_processor_batch_path}'")
     return asset_processor_batch_path
 
 
 def get_asset_bundler_batch_path(tools_build_path: pathlib.Path,
+                                 using_installer_sdk: bool = False,
                                  required: bool = False) -> pathlib.Path:
     """
     Get the expected path to the asset bundler tool
 
     @param tools_build_path:    The tools (cmake) build path to locate AssetBundlerBatch
+    @param using_installer_sdk: Indicate if the tools path belongs to an installer SDK. If True, expect the path to point at the folder containing the executable.
     @param required:            If true, check if the asset bundler actually exists on file at the expected location, and raise an error if not
     @return: Path to the asset bundler tool
     """
-
-    asset_bundler_batch_path = tools_build_path / f'bin/profile/AssetBundlerBatch{EXECUTABLE_EXTENSION}'
+    if using_installer_sdk:
+        asset_bundler_batch_path = tools_build_path / f'AssetBundlerBatch{EXECUTABLE_EXTENSION}'
+    else:
+        asset_bundler_batch_path = tools_build_path / f'bin/profile/AssetBundlerBatch{EXECUTABLE_EXTENSION}'
     if required and not asset_bundler_batch_path.is_file():
         raise ExportProjectError(f"Missing the 'AssetBundlerBatch' tool, expected at '{asset_bundler_batch_path}'")
     return asset_bundler_batch_path
@@ -403,18 +418,20 @@ def build_assets(ctx: O3DEScriptExportContext,
                  tools_build_path: pathlib.Path,
                  engine_centric: bool,
                  fail_on_ap_errors: bool,
+                 using_installer_sdk: bool = False,
                  logger: logging.Logger = None) -> int:
     """
     Build the assets for the project
     @param ctx:                 Export Context
     @param tools_build_path:    The tools (cmake) build path to locate AssetProcessorBatch
     @param fail_on_ap_errors:   Option to fail the whole process if an error occurs during asset processing
+    @param using_installer_sdk: Indicate if the tools path belongs to an installer SDK. If True, expect the path to point at the folder containing the executable.
     @param logger:              Optional Logger
     @return: None
     """
 
     # Make sure `AssetProcessorBatch` is available
-    asset_processor_batch_path = get_asset_processor_batch_path(tools_build_path, required=True)
+    asset_processor_batch_path = get_asset_processor_batch_path(tools_build_path, using_installer_sdk, required=True)
     if not asset_processor_batch_path.exists():
         raise ExportProjectError("Missing AssetProcessorBatch. The pre-requisite tools must be built first.")
 
@@ -598,8 +615,10 @@ def build_game_targets(ctx: O3DEScriptExportContext,
 def bundle_assets(ctx: O3DEScriptExportContext,
                   selected_platform: str,
                   seedlist_paths: List[pathlib.Path],
+                  seedfile_paths: List[pathlib.Path],
                   tools_build_path: pathlib.Path,
                   engine_centric: bool,
+                  using_installer_sdk: bool = False,
                   asset_bundling_path: pathlib.Path | None = None,
                   max_bundle_size: int = 2048) -> pathlib.Path:
     """
@@ -608,13 +627,15 @@ def bundle_assets(ctx: O3DEScriptExportContext,
     @param ctx:                      Export Context
     @param selected_platform:        The desired asset platform
     @param seedlist_paths:           The list of seedlist files
+    @param seedfile_paths:           The list of individual seed files
     @param tools_build_path:         The path to the tools cmake build project
+    @param using_installer_sdk:      Indicate if the tools path belongs to an installer SDK. If True, expect the path to point at the folder containing the executable.
     @param asset_bundling_path:      The path to use to write all the intermediate and final artifacts from the bundling process
     @param max_bundle_size:          The size limit to put on the bundle
     @return: The path to the bundle
     """
 
-    asset_bundler_batch_path = get_asset_bundler_batch_path(tools_build_path, required=True)
+    asset_bundler_batch_path = get_asset_bundler_batch_path(tools_build_path, using_installer_sdk, required=True)
     asset_list_path = asset_bundling_path / 'AssetLists'
 
     game_asset_list_path = asset_list_path / f'game_{selected_platform}.assetlist'
@@ -630,6 +651,11 @@ def bundle_assets(ctx: O3DEScriptExportContext,
     for seed in seedlist_paths:
         gen_game_asset_list_command.append("--seedListFile")
         gen_game_asset_list_command.append(str(seed))
+    
+    for seed in seedfile_paths:
+        gen_game_asset_list_command.append("--addSeed")
+        gen_game_asset_list_command.append(str(seed))
+
     ret = process_command(gen_game_asset_list_command,
                           cwd=ctx.engine_path if engine_centric else ctx.project_path)
     if ret != 0:

+ 22 - 1
scripts/o3de/o3de/manifest.py

@@ -32,7 +32,6 @@ def get_this_engine_path() -> pathlib.Path:
     else:
         return pathlib.Path(os.path.realpath(__file__)).parents[3].resolve()
 
-
 def get_home_folder() -> pathlib.Path:
     return pathlib.Path(os.path.expanduser("~")).resolve()
 
@@ -335,6 +334,28 @@ def get_engine_templates() -> list:
                         engine_object['templates'])) if 'templates' in engine_object else []
     return []
 
+def is_sdk_engine(engine_name:str = None, engine_path:str or pathlib.Path = None) -> bool:
+    """
+    Queries if the engine is an SDK engine that was created via the CMake install command
+    :param engine_name: Name of a registered engine to lookup in the ~/.o3de/o3de_manifest.json file
+    :param engine_path: The path to the engine
+    :return True if the engine.json contains an "sdk_engine" field which is True
+    """
+    engine_json_data = get_engine_json_data(engine_name, engine_path)
+    if engine_json_data:
+        return engine_json_data.get('sdk_engine', False)
+
+    if engine_path:
+        logger.error('Failed to retrieve engine json data for the engine at '
+                     f'"{engine_path}". Treating engine as source engine.')
+    elif engine_name:
+        logger.error('Failed to retrieve  engine json data for registered engine '
+                     f'"{engine_name}". Treating engine as source engine.')
+    else:
+        logger.error('Failed to retrieve engine json data as neither an engine name nor path were given.'
+                     'Treating engine as source engine')
+    return False
+
 
 # project.json queries
 def get_project_gems(project_path:pathlib.Path, recurse:bool = False, gems_json_data_by_path:dict = None) -> list: