Browse Source

Updates to the export project script and instructions for MPS (#463)

 - Renamed, refactored export_standalone_monolithic.py->export_mps.py to use recently refactored `export-project` scripts in O3DE
- Updated testing instructions per changes to export_mps.py (formerly export_standalone_monolithic.py)
- Updates based on update to how the cmake build args are passed in

Co-authored-by: Gene Walters <[email protected]>
Signed-off-by: Steve Pham <[email protected]>
Steve Pham 1 năm trước cách đây
mục cha
commit
d7a23f7dbc

+ 120 - 53
Documentation/O3DEMPSProjectExportTesting.md

@@ -2,72 +2,139 @@
 
 ## Notes
 
-* This instruction set assumes the host platform is Windows. Instructions for Linux coming shortly.
+* These testing instructions are for Windows and Linux
+* Each step in the testing process will include commands for both Windows and Linux
+* The following environment variables will be used for these instructions for consistency. Update the path values if needed to match the testing environment
+  * Windows
+    ```commandline
+    set PROJECT_BASE=%HOMEPATH%\MPS_Test
+    set PROJECT_EXPORT_BASE=%HOMEPATH%\MPS_Export
+    ```
+  * Linux
+    ```bash
+    export PROJECT_BASE=$HOME/MPS_Test
+    export PROJECT_EXPORT_BASE=$HOME/MPS_Export
+    ```
 
 ## Fresh Install and Setup
-1. Clone the development branch of O3DE:
-```bash
-cd \path\to\workspace
-git clone -b development https://github.com/o3de/o3de.git 
-```
-2. Clone the development branch of O3DE MPS:
-```bash
-git clone -b development https://github.com/o3de/o3de-multiplayersample.git
-```
-3. Clone O3DE MPS Assets, then switch to development branch. Afterwards initialize any submodules (such as O3DEPopcornFX), and make sure they are on development:
-```bash
-git clone https://github.com/o3de/o3de-multiplayersample-assets.git
-cd o3de-multiplayersample-assets
-git switch development
-git submodule update --init --recursive
-```
-4. Now register the engine, project, and gems (this is similar to [registration found in O3DE MPS Github readme](https://github.com/o3de/o3de-multiplayersample/blob/MPSProjectExportTestingInstructions/README.md#step-2-register-the-engine-the-project-and-the-gems)):
-```bash
-cd \path\to\workspace\o3de
-python\get_python
-scripts\o3de register --this-engine
-scripts\o3de register -p \path\to\workspace\o3de-multiplayersample
-scripts\o3de register --all-gems-path \path\to\workspace\o3de-multiplayersample-assets\Gems
-```
 
-## Run Project Export
-Use the O3DE project-centric export script to produce a game and server package. 
 
-1. Create the directory you want to output the game and server.
+1. Clone the development branch of O3DE and configure it: <br/>
+   <br/>
+    **Windows**
+    ```commandline
+    git clone -b development https://github.com/o3de/o3de.git %PROJECT_BASE%\o3de
+    %PROJECT_BASE%\o3de\python\get_python.bat
+    %PROJECT_BASE%\o3de\scripts\o3de.bat register --this-engine
+    ```
+    **Linux**
+    ```bash
+    git clone -b development https://github.com/o3de/o3de.git %PROJECT_BASE%/o3de
+    $PROJECT_BASE/o3de/python/get_python.sh
+    $PROJECT_BASE/scripts/o3de.sh register --this-engine
+    ```
+2. Clone the development branch of the O3DE Multiplayer Sample Assets and its PopcornFX submodule <br/>
+   <br/>
+    **Windows**
+    ```commandline
+    git clone -b development https://github.com/o3de/o3de-multiplayersample-assets.git %PROJECT_BASE%\o3de-multiplayersample-assets
+    git -C %PROJECT_BASE%\o3de-multiplayersample-assets submodule update --init --recursive
+    ```
+    
+    **Linux**
+    ```bash
+    git clone -b development https://github.com/o3de/o3de-multiplayersample-assets.git %PROJECT_BASE%/o3de-multiplayersample-assets
+    git -C $PROJECT_BASE/o3de-multiplayersample-assets submodule update --init --recursive
+    ```
+   > Note: You may need to check the git revision of the Popcorn FX Plugin under:
+   > 
+   >> ...\o3de-multiplayersample-assets\Gems\O3DEPopcornFXPlugin
+   > 
+   > to make sure it is the latest version.
 
-It is recommended to set the following environment variables with custom values according to your setup before running the export script:
-```bash
-# On Windows
-set O3DE_PATH="C:\path\to\o3de"
-set O3DE_PROJECT_PATH="C:\path\to\o3de-multiplayersample"
-set OUTPUT_PATH="C:\path\to\output"
-
-# On Linux
-export O3DE_PATH='/path/to/o3de'
-export O3DE_PROJECT_PATH='/path/to/o3de-multiplayersample'
-export OUTPUT_PATH='/path/to/output'
-```
+   <br/>
+3. Register the Gems path for the O3DE Multiplayer Sample Assets that is needed for the Multiplayer Sample<br/>
+   <br/>
+    **Windows**
+    ```commandline
+    %PROJECT_BASE%\o3de\scripts\o3de.bat register --all-gems-path %PROJECT_BASE%\o3de-multiplayersample-assets\Gems 
+    ```
+    **Linux**
+    ```bash
+    $PROJECT_BASE/o3de/scripts/o3de.sh register --all-gems-path $PROJECT_BASE/o3de-multiplayersample-assets/Gems 
+    ```
+   <br/>
 
-After setting those variables, navigate to that directory, and run the export command verbatim:
-```bash
+4. Clone the development branch of the O3DE Multiplayer Sample and register the project:<br/><br/>
 
-# On Windows
-%O3DE_PATH%\scripts\o3de export-project -es %O3DE_PATH%\scripts\o3de\ExportScripts\export_standalone_monolithic_project_centric.py -pp %O3DE_PROJECT_PATH% -out %OUTPUT_PATH% -cfg release -a zip -nounified -gpfp launch_client.cfg -spfp launch_client.cfg -code -assets -ll INFO -sl %O3DE_PROJECT_PATH%\AssetBundling\SeedLists\BasePopcornFxSeedList.seed -sl %O3DE_PROJECT_PATH%\AssetBundling\SeedLists\GameSeedList.seed -sl %O3DE_PROJECT_PATH%\AssetBundling\SeedLists\VFXSeedList.seed
+    **Windows**
+    ```commandline
+    git clone -b development https://github.com/o3de/o3de-multiplayersample.git %PROJECT_BASE%\o3de-multiplayersample
+    %PROJECT_BASE%\o3de\scripts\o3de.bat register -pp %PROJECT_BASE%\o3de-multiplayersample
+    ```
+    **Linux**
+    ```bash
+    git clone -b development https://github.com/o3de/o3de-multiplayersample.git $PROJECT_BASE/o3de-multiplayersample
+    $PROJECT_BASE/o3de/scripts/o3de.sh register -pp $PROJECT_BASE/o3de-multiplayersample
+    ```
 
-# On Linux
-$O3DE_PATH/scripts/o3de export-project -es $O3DE_PATH/scripts/o3de/ExportScripts/export_standalone_monolithic_project_centric.py -pp $O3DE_PROJECT_PATH -out $OUTPUT_PATH -cfg release -a zip -nounified -gpfp launch_client.cfg -spfp launch_client.cfg -code -assets -ll INFO -sl $O3DE_PROJECT_PATH/AssetBundling/SeedLists/BasePopcornFxSeedList.seed -sl $O3DE_PROJECT_PATH/AssetBundling/SeedLists/GameSeedList.seed -sl $O3DE_PROJECT_PATH/AssetBundling/SeedLists/VFXSeedList.seed
+## Run Project Export
+Generate the project packages using the project's `export_mps.py` script in conjunction with the O3DE export command.
 
-```
 
-2. You should see two directories in your output folder: `MultiplayerSampleGamePackage` and `GameLiftPackageWindows`.
+1. Create the directory you want to output the game and server. <br/>
+   <br/>
+    **Windows**
+    ```commandline
+    mkdir %PROJECT_EXPORT_BASE%\packages
+    ```
+    **Linux**
+    ```bash
+    mkdir $PROJECT_EXPORT_BASE/packages
+    ```
+2. Run the o3de export script to generate the packages.<br/>
+    <br/>
+    **Windows**
+    ```commandline
+    %PROJECT_BASE%\o3de\scripts\o3de.bat export-project -pp %PROJECT_BASE%\o3de-multiplayersample -es ExportScripts\export_mps.py -out %PROJECT_EXPORT_BASE%\mps_server -cfg release -a zip -nounified -code -gl -assets -ll INFO -cba -- /m
+    ```
+    **Linux**
+    ```commandline
+    $PROJECT_BASE/o3de/scripts/o3de.sh export-project -pp $PROJECT_BASE/o3de-multiplayersample -es ExportScripts/export_mps.py -out $PROJECT_EXPORT_BASE/mps_server -cfg release -a zip -nounified -code -gl -assets -ll INFO
+    ```
+   
+    > Note: The export commands described above will automatically enable the `MPSGameLift` gem when building the package. To opt-out of enabling GameLift, remove the `-gl` argument from the above command.
+    
+    > Note: The export commands above will also generate an archive (zip) of the output directories  
+   
+3. 
 
 ## Test Exported Project
-1. To test MPS, first run the server, then run the game. You may need to provide admin privilege to enable a connection to AssetProcessor:
+
+1. Start testing MultiplayerSample by launching the server (You may need to provide admin privilege to enable a connection to AssetProcessor)
+
+**Windows**
+```commandline
+%PROJECT_EXPORT_BASE%\packages\MultiplayerSampleServerPackage\MultiplayerSample.ServerLauncher.exe --rhi=null -NullRenderer --console-command-file=launch_server.cfg --net_udpDefaultTimeoutMs=20000
+```
+
+**Linux**
 ```bash
-.\GameLiftPackageWindows\MultiplayerSample.ServerLauncher.exe --rhi=null -NullRenderer --console-command-file=launch_server.cfg --net_udpDefaultTimeoutMs=20000
+$PROJECT_EXPORT_BASE/packages/MultiplayerSampleServerPackage/MultiplayerSample.ServerLauncher --rhi=null -NullRenderer --console-command-file=launch_server.cfg --net_udpDefaultTimeoutMs=20000 &
+```
+
+2. Once the server is up and running, then launch the game 
+`
  
-# Wait for server to get setup, then run the game launcher
-.\MultiplayerSampleGamePackage\MultiplayerSample.GameLauncher.exe --connect=127.0.0.1 --net_udpDefaultTimeoutMs=20000
+**Windows**
+```commandline
+%PROJECT_EXPORT_BASE%\packages\MultiplayerSampleGamePackage\MultiplayerSample.GameLauncher.exe --connect=127.0.0.1 --net_udpDefaultTimeoutMs=20000
+```
+
+**Linux**
+```bash
+$PROJECT_EXPORT_BASE/packages/MultiplayerSampleGamePackage/MultiplayerSample.GameLauncher --connect=127.0.0.1 --net_udpDefaultTimeoutMs=20000
 ```
 
-2. At this point, check to see if the game runs, and if you're able to run around, see particles, shoot, and hear sounds. Any errors or crashes should result in timestamped logs, which can be found at `GameLiftPackageWindows\user\log\server.log`, or `MultiplayerSampleGamePackage\user\log\game.log`.
+
+2. At this point, check to see if the game runs, and if you're able to run around, see particles, shoot, and hear sounds. Any errors or crashes should result in timestamped logs, which can be found at `%PROJECT_EXPORT_BASE%\packages\MultiplayerSampleServerPackage\user\log\server.log`, or `%PROJECT_EXPORT_BASE%\packages\MultiplayerSampleGamePackage\user\log\game.log`.

+ 258 - 0
ExportScripts/export_mps.py

@@ -0,0 +1,258 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of https://www.github.com/o3de/o3de.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+import argparse
+import logging
+import sys
+
+import o3de.export_project as exp
+import o3de.enable_gem as enable_gem
+import o3de.disable_gem as disable_gem
+import o3de.manifest as manifest
+import pathlib
+
+
+def export_multiplayer_sample(ctx: exp.O3DEScriptExportContext,
+                              selected_platform: str,
+                              output_path: pathlib.Path,
+                              should_build_tools: bool,
+                              build_config: str,
+                              asset_bundling_path: pathlib.Path,
+                              max_bundle_size: int,
+                              enable_gamelift: bool,
+                              should_build_all_assets: bool,
+                              should_build_game_launcher: bool,
+                              should_build_server_launcher: bool,
+                              should_build_unified_launcher: bool,
+                              allow_registry_overrides: bool,
+                              tools_build_path: pathlib.Path,
+                              game_build_path: pathlib.Path,
+                              archive_output_format: str,
+                              fail_on_asset_errors: bool,
+                              engine_centric: bool,
+                              logger: logging.Logger):
+    if not logger:
+        logger = logging.getLogger()
+        logger.setLevel(logging.ERROR)
+
+    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 not game_build_path:
+        game_build_path = default_base_build_path / 'game'
+    if not asset_bundling_path:
+        asset_bundling_path = default_base_build_path / 'asset_bundling'
+
+    seed_folder_path = ctx.project_path / 'AssetBundling' / 'SeedLists'
+    seedlist_paths = [
+        seed_folder_path / 'BasePopcornFxSeedList.seed',
+        seed_folder_path / 'GameSeedList.seed',
+        seed_folder_path / 'VFXSeedList.seed'
+    ]
+    if build_config == 'profile':
+        # Dev branch has removed the profile seed list, but it still remains in main for now.
+        # This will be removed after the next release, when both branches are synchronized
+        profile_seed_list_path = seed_folder_path / 'ProfileOnlySeedList.seed'
+        if profile_seed_list_path.is_file():
+            seedlist_paths.extend([profile_seed_list_path])
+
+    validated_seedslist_paths = exp.validate_project_artifact_paths(project_path=ctx.project_path,
+                                                                    artifact_paths=seedlist_paths)
+
+    exp.kill_existing_processes(ctx.project_name)
+
+    gamelift_gem_added = False
+    gamelift_gem_removed = False
+
+    try:
+        # Check if we need to enable or disable the MPSGameLift gem for the requested packages
+        project_json_data = manifest.get_project_json_data(project_path=ctx.project_path)
+        assert project_json_data, f"Invalid project configuration file '{ctx.project_path}/project.json'. Invalid settings."
+        assert project_json_data['gem_names'], f"Invalid project configuration file '{ctx.project_path}/project.json'. Invalid settings."
+        has_mps_gem = 'MPSGameLift' in project_json_data['gem_names']
+        if enable_gamelift and not has_mps_gem:
+            if enable_gem.enable_gem_in_project(gem_name="MPSGameLift", project_path=ctx.project_path) != 0:
+                raise exp.ExportProjectError("Unable to enable the MPSGameLift gem for the project")
+            gamelift_gem_added = True
+        elif not enable_gamelift and has_mps_gem:
+            if disable_gem.disable_gem_in_project(gem_name="MPSGameLift", project_path=ctx.project_path) != 0:
+                raise exp.ExportProjectError("Unable to disable the MPSGameLift gem for the project")
+            gamelift_gem_removed = True
+
+        # Optionally build the toolchain needed to bundle the assets
+        if should_build_tools:
+            exp.build_export_toolchain(ctx=ctx,
+                                       tools_build_path=tools_build_path,
+                                       engine_centric=engine_centric,
+                                       logger=logger)
+
+        launcher_type = 0
+        if should_build_game_launcher:
+            launcher_type |= exp.LauncherType.GAME
+        if should_build_server_launcher:
+            launcher_type |= exp.LauncherType.SERVER
+        if should_build_unified_launcher:
+            launcher_type |= exp.LauncherType.UNIFIED
+
+        if launcher_type != 0:
+            exp.build_game_targets(ctx=ctx,
+                                   build_config=build_config,
+                                   game_build_path=game_build_path,
+                                   engine_centric=engine_centric,
+                                   launcher_types=launcher_type,
+                                   allow_registry_overrides=allow_registry_overrides,
+                                   logger=logger)
+
+        if should_build_all_assets:
+            asset_processor_path = exp.get_asset_processor_batch_path(tools_build_path=tools_build_path,
+                                                                      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,
+                             logger=logger)
+
+        # Generate the bundle
+        asset_bundler_path = exp.get_asset_bundler_batch_path(tools_build_path=tools_build_path,
+                                                              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,
+                                                  tools_build_path=tools_build_path,
+                                                  engine_centric=engine_centric,
+                                                  asset_bundling_path=asset_bundling_path,
+                                                  max_bundle_size=max_bundle_size)
+
+        project_file_patterns_to_copy = []
+        game_project_file_patterns_to_copy = ['launch_client.cfg']
+        server_project_file_patterns_to_copy = ['launch_server.cfg']
+
+        export_layouts = []
+        if should_build_game_launcher:
+            export_layouts.append(exp.ExportLayoutConfig(output_path=output_path / f'{ctx.project_name}GamePackage',
+                                                         project_file_patterns=project_file_patterns_to_copy + game_project_file_patterns_to_copy,
+                                                         ignore_file_patterns=[f'*.ServerLauncher{exp.EXECUTABLE_EXTENSION}', f'*.UnifiedLauncher{exp.EXECUTABLE_EXTENSION}']))
+
+        if should_build_server_launcher:
+            export_layouts.append(exp.ExportLayoutConfig(output_path=output_path / f'{ctx.project_name}ServerPackage',
+                                                         project_file_patterns=project_file_patterns_to_copy + server_project_file_patterns_to_copy,
+                                                         ignore_file_patterns=[f'*.GameLauncher{exp.EXECUTABLE_EXTENSION}', f'*.UnifiedLauncher{exp.EXECUTABLE_EXTENSION}']))
+
+        if should_build_unified_launcher:
+            export_layouts.append(exp.ExportLayoutConfig(output_path=output_path / f'{ctx.project_name}UnifiedPackage',
+                                                         project_file_patterns=project_file_patterns_to_copy + game_project_file_patterns_to_copy + server_project_file_patterns_to_copy,
+                                                         ignore_file_patterns=[f'*.ServerLauncher{exp.EXECUTABLE_EXTENSION}', f'*.GameLauncher{exp.EXECUTABLE_EXTENSION}']))
+
+        for export_layout in export_layouts:
+            exp.setup_launcher_layout_directory(project_path=ctx.project_path,
+                                                asset_platform=selected_platform,
+                                                launcher_build_path=game_build_path,
+                                                build_config=build_config,
+                                                bundles_to_copy=[expected_bundles_path / f'game_{selected_platform}.pak',
+                                                                 expected_bundles_path / f'engine_{selected_platform}.pak'],
+                                                export_layout=export_layout,
+                                                archive_output_format=archive_output_format,
+                                                logger=logger)
+    finally:
+        # Make sure to clean up and restore the state of the MPSGame
+        if gamelift_gem_added:
+            if disable_gem.disable_gem_in_project(gem_name="MPSGameLift", project_path=ctx.project_path) != 0:
+                logger.warning("Unable to remove the project's 'MPSGameList' gem")
+        elif gamelift_gem_removed:
+            if enable_gem.enable_gem_in_project(gem_name="MPSGameLift", project_path=ctx.project_path) != 0:
+                logger.warning("Unable to restore project's 'MPSGameList' gem")
+
+
+# This code is only run by the 'export-project' O3DE CLI command
+if "o3de_context" in globals():
+
+    global o3de_context
+    global o3de_logger
+
+    def parse_args(o3de_context: exp.O3DEScriptExportContext):
+
+        parser = argparse.ArgumentParser(
+                    prog=f'o3de.py export-project -es {__file__}',
+                    description="Exports the Multiplayer Samples project as standalone to the desired output directory with release layout. "
+                                "In order to use this script, the engine and project must be setup and registered beforehand. ",
+                    epilog=exp.CUSTOM_CMAKE_ARG_HELP_EPILOGUE,
+                    formatter_class=argparse.RawTextHelpFormatter,
+                    add_help=False
+        )
+        parser.add_argument(exp.CUSTOM_SCRIPT_HELP_ARGUMENT,default=False,action='store_true',help='Show this help message and exit.')
+        parser.add_argument('-out', '--output-path', type=pathlib.Path, required=True, help='Path that describes the final resulting Release Directory path location.')
+        parser.add_argument('-cfg', '--config', type=str, default='profile', choices=['release', 'profile'],
+                            help='The CMake build configuration to use when building project binaries.  Tool binaries built with this script will always be built with the profile configuration.')
+        parser.add_argument('-a', '--archive-output',  type=str,
+                            help="Option to create a compressed archive the output. "
+                                 "Specify the format of archive to create from the output directory. If 'none' specified, no archiving will occur.",
+                            choices=["none", "zip", "gzip", "bz2", "xz"], default="none")
+        parser.add_argument('-gl', '--game-lift', default=False, action='store_true',
+                            help='Enable Gamelift for the Multiplayer Sample package')
+        parser.add_argument('-assets', '--should-build-assets', default=False, action='store_true',
+                            help='Toggles building all assets for the project by launcher type (game, server, unified).')
+        parser.add_argument('-foa', '--fail-on-asset-errors', default=False, action='store_true',
+                            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('-bt', '--build-tools', default=True, type=bool,
+                            help="Specifies whether to build O3DE toolchain executables. This will build AssetBundlerBatch, AssetProcessorBatch.")
+        parser.add_argument('-tbp', '--tools-build-path', type=pathlib.Path, default=None,
+                            help='Designates where O3DE toolchain executables go. If not specified, default is <o3de_project_path>/build/tools.')
+        parser.add_argument('-gbp', '--game-build-path', type=pathlib.Path, default=None,
+                            help="Designates where project executables (like Game/Server Launcher) go."
+                            " If not specified, default is <o3de_project_path>/build/game.")
+        parser.add_argument('-regovr', '--allow-registry-overrides', default=False, type = bool,
+                            help="When configuring cmake builds, this determines if the script allows for overriding registry settings from external sources.")
+        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('-nounified', '--no-unified-launcher', 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.')
+
+        if o3de_context is None:
+            parser.print_help()
+            exit(0)
+        
+        parsed_args = parser.parse_args(o3de_context.args)
+        if parsed_args.script_help:
+            parser.print_help()
+            exit(0)
+
+        return parsed_args
+    
+    args = parse_args(o3de_context)
+    if args.quiet:
+        o3de_logger.setLevel(logging.ERROR)
+    try:
+        export_multiplayer_sample(ctx=o3de_context,
+                                  selected_platform=args.platform,
+                                  output_path=args.output_path,
+                                  should_build_tools=args.build_tools,
+                                  build_config=args.config,
+                                  asset_bundling_path=args.asset_bundling_path,
+                                  max_bundle_size=args.max_bundle_size,
+                                  enable_gamelift=args.game_lift,
+                                  should_build_all_assets=args.should_build_assets,
+                                  fail_on_asset_errors=args.fail_on_asset_errors,
+                                  should_build_game_launcher=not args.no_game_launcher,
+                                  should_build_server_launcher=not args.no_server_launcher,
+                                  should_build_unified_launcher=not args.no_unified_launcher,
+                                  allow_registry_overrides=args.allow_registry_overrides,
+                                  tools_build_path=args.tools_build_path,
+                                  game_build_path=args.game_build_path,
+                                  archive_output_format=args.archive_output,
+                                  engine_centric=args.engine_centric,
+                                  logger=o3de_logger)
+    except exp.ExportProjectError as err:
+        print(err)
+        sys.exit(1)

+ 0 - 340
ExportScripts/export_standalone_monolithic.py

@@ -1,340 +0,0 @@
-#
-# Copyright (c) Contributors to the Open 3D Engine Project.
-# For complete copyright and license terms please see the LICENSE at the root of https://www.github.com/o3de/o3de.
-#
-# SPDX-License-Identifier: Apache-2.0 OR MIT
-#
-#
-import argparse
-import pathlib
-import logging
-import os
-import time
-import glob
-import sys
-import platform
-from o3de.validation import valid_o3de_project_json, valid_o3de_engine_json
-from queue import Queue, Empty
-from threading  import Thread
-from typing import List
-from subprocess import Popen, PIPE
-
-logger = logging.getLogger('o3de.mps_export')
-LOG_FORMAT = '[%(levelname)s] %(name)s: %(message)s'
-logging.basicConfig(format=LOG_FORMAT)
-# This is an export script for MPS
-# this has to be a complete standalone script, b/c project export doesnt exist in main branch yet
-
-# View the argparse parameters for options available. An example invocation:
-
-# @<O3DE_ENGINE_ROOT_PATH>
-# > python\python.cmd <O3DE_MPS_PROJECT_ROOT_PATH>\ExportScripts\export_standalone_monolithic.py -pp <O3DE_MPS_PROJECT_ROOT_PATH> -ep <O3DE_ENGINE_ROOT_PATH> -bnmt -out <MPS_OUTPUT_RELEASE_DIR_PATH> -a -aof zip
-
-def enqueue_output(out, queue):
-    for line in iter(out.readline, b''):
-        queue.put(line)
-    out.close()
-
-def safe_kill_processes(*processes: List[Popen], process_logger: logging.Logger = None) -> None:
-    """
-    Kills a given process without raising an error
-    :param processes: An iterable of processes to kill
-    :param process_logger: (Optional) logger to use
-    """
-    def on_terminate(proc) -> None:
-        try:
-            process_logger.info(f"process '{proc.args[0]}' with PID({proc.pid}) terminated with exit code {proc.returncode}")
-        except Exception:  # purposefully broad
-            process_logger.error("Exception encountered with termination request, with stacktrace:", exc_info=True)
-
-    if not process_logger:
-        process_logger = logger
-    
-    for proc in processes:
-        try:
-            process_logger.info(f"Terminating process '{proc.args[0]}' with PID({proc.pid})")
-            proc.kill()
-        except Exception:  # purposefully broad
-            process_logger.error("Unexpected exception ignored while terminating process, with stacktrace:", exc_info=True)
-    try:
-        for proc in processes:
-            proc.wait(timeout=30)
-            on_terminate(proc)
-    except Exception:  # purposefully broad
-        process_logger.error("Unexpected exception while waiting for processes to terminate, with stacktrace:", exc_info=True)
-
-class CLICommand(object):
-    """
-    CLICommand is an interface for storing CLI commands as list of string arguments to run later in a script.
-    A current working directory, pre-existing OS environment, and desired logger can also be specified.
-    To execute a command, use the run() function.
-    This class is responsible for starting a new process, polling it for updates and logging, and safely terminating it.
-    """
-    def __init__(self, 
-                args: list,
-                cwd: pathlib.Path,
-                logger: logging.Logger,
-                env: os._Environ=None) -> None:
-        self.args = args
-        self.cwd = cwd
-        self.env = env
-        self.logger = logger
-        self._stdout_lines = []
-        self._stderr_lines = []
-    
-    @property
-    def stdout_lines(self) -> List[str]:
-        """The result of stdout, separated by newlines."""
-        return self._stdout_lines
-
-    @property
-    def stdout(self) -> str:
-        """The result of stdout, as a single string."""
-        return "\n".join(self._stdout_lines)
-
-    @property
-    def stderr_lines(self) -> List[str]:
-        """The result of stderr, separated by newlines."""
-        return self._stderr_lines
-
-    @property
-    def stderr(self) -> str:
-        """The result of stderr, as a single string."""
-        return "\n".join(self._stderr_lines)
-
-    def _poll_process(self, process, queue) -> None:
-        # while process is not done, read any log lines coming from subprocess
-        while process.poll() is None:
-            #handle readline in a non-blocking manner
-            try:  line = queue.get_nowait() 
-            except Empty:
-                pass
-            else: # got line
-                if not line: break
-                log_line = line.decode('utf-8', 'ignore')
-                self._stdout_lines.append(log_line)
-                self.logger.info(log_line)
-    
-    def _cleanup_process(self, process, queue) -> str:
-        # flush remaining log lines
-        while not queue.empty():
-            try: line = queue.get_nowait()
-            except Empty:
-                pass
-            else:
-                if not line: break
-                log_line = line.decode('utf-8', 'ignore')
-                self._stdout_lines.append(log_line)
-                self.logger.info(log_line)
-        stderr = process.stderr.read()
-
-        safe_kill_processes(process, process_logger = self.logger)
-
-        return stderr
-    
-    def run(self) -> int:
-        """
-        Takes the arguments specified during CLICommand initialization, and opens a new subprocess to handle it.
-        This function automatically manages polling the process for logs, error reporting, and safely cleaning up the process afterwards.
-        :return return code on success or failure 
-        """
-        ret = 1
-        try:
-            with Popen(self.args, cwd=self.cwd, env=self.env, stdout=PIPE, stderr=PIPE) as process:
-                self.logger.info(f"Running process '{self.args[0]}' with PID({process.pid}): {self.args}")
-
-                q = Queue()
-                t = Thread(target=enqueue_output, args=(process.stdout, q))
-                t.daemon = True
-                t.start()
-
-                process.stdout.flush()
-                self._poll_process(process, q)
-                stderr = self._cleanup_process(process, q)
-
-                ret = process.returncode
-
-                # print out errors if there are any      
-                if stderr:
-                    # bool(ret) --> if the process returns a FAILURE code (>0)
-                    logger_func = self.logger.error if bool(ret) else self.logger.warning
-                    err_txt = stderr.decode('utf-8', 'ignore')
-                    logger_func(err_txt)
-                    self._stderr_lines = err_txt.split("\n")
-        except Exception as err:
-            self.logger.error(err)
-            raise err
-        return ret
-
-# Helper API
-def process_command(args: list,
-                    cwd: pathlib.Path = None,
-                    env: os._Environ = None) -> int:
-    """
-    Wrapper for subprocess.Popen, which handles polling the process for logs, reacting to failure, and cleaning up the process.
-    :param args: A list of space separated strings which build up the entire command to run. Similar to the command list of subprocess.Popen
-    :param cwd: (Optional) The desired current working directory of the command. Useful for commands which require a differing starting environment.
-    :param env: (Optional) Environment to use when processing this command.
-    :return the exit code of the program that is run or 1 if no arguments were supplied
-    """
-    if len(args) == 0:
-        logging.error("function `process_command` must be supplied a non-empty list of arguments")
-        return 1
-    return CLICommand(args, cwd, logging.getLogger(), env=env).run()
-
-
-# EXPORT SCRIPT STARTS HERE!
-if __name__ == "__main__":
-    parser = argparse.ArgumentParser(prog='Exporter for MultiplayerSample as a standalone build',
-                                 description = "Exports O3DE's MultiplayerSample to the desired output directory with release layout. "
-                                                "In order to use this script, the engine and project must be setup and registered beforehand. "
-                                                "See this example on the MPS Github page: "
-                                                "https://github.com/o3de/o3de-multiplayersample/blob/development/README.md#required-step-to-compile")
-    parser.add_argument('-pp', '--project-path', type=pathlib.Path, required=True, help='Path to the intended O3DE project.')
-    parser.add_argument('-ep', '--engine-path', type=pathlib.Path, required=True, help='Path to the intended O3DE engine.')
-    parser.add_argument('-out', '--output-path', type=pathlib.Path, required=True, help='Path that describes the final resulting Release Directory path location.')
-    parser.add_argument('-cfg', '--config', type=str, default='profile', choices=['release', 'profile'],
-                        help='The CMake build configuration to use when building project binaries. If tool binaries are built with this script, they will use profile mode.')
-    parser.add_argument('-ll', '--log-level', default='ERROR',
-                        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
-                        help="Set the log level")
-    parser.add_argument('-aof', '--archive-output-format',
-                        type=str,
-                        help="Format of archive to create from the output directory",
-                        choices=["zip", "gzip", "bz2", "xz"], default="zip")
-    parser.add_argument('-bnmt', '--build-non-mono-tools', action='store_true')
-    parser.add_argument('-nmbp', '--non-mono-build-path', type=pathlib.Path, default=None)
-    parser.add_argument('-mbp', '--mono-build-path', type=pathlib.Path, default=None)
-    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('-nounified', '--no-unified-launcher', 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=None, choices=['pc', 'linux', 'mac'])
-    parser.add_argument('-a', '--archive-output', action='store_true', help='This option places the final output of the build into a compressed archive')
-    parser.add_argument('-q', '--quiet', action='store_true', help='Suppresses logging information unless an error occurs.')
-    args = parser.parse_args()
-
-    if args.quiet:
-        logging.getLogger().setLevel(logging.ERROR)
-    else:    
-        logging.getLogger().setLevel(args.log_level)
-
-    non_mono_build_path = (args.engine_path) / 'build' / 'non_mono' if args.non_mono_build_path is None else args.non_mono_build_path
-    mono_build_path = (args.engine_path) / 'build' / 'mono' if args.mono_build_path is None else args.mono_build_path
-
-    #validation
-    assert valid_o3de_project_json(args.project_path / 'project.json') and valid_o3de_engine_json(args.engine_path / 'engine.json')
-
-
-    #commands are based on 
-    #https://github.com/o3de/o3de-multiplayersample/blob/development/Documentation/PackedAssetBuilds.md
-    selected_platform = args.platform
-
-    system_platform = platform.system().lower()
-
-    if not selected_platform:
-        logger.info("Platform not specified! Defaulting to Host platform...")
-        if not system_platform:
-            logger.error("Unable to identify host platform! Please supply the platform using '--platform'. Options are [pc, linux, mac].")
-            sys.exit(1)
-        if system_platform == "windows":
-            selected_platform = "pc"
-        elif system_platform == "linux":
-            selected_platform = "linux"
-        elif system_platform == "darwin":
-            selected_platform = "mac"
-        else:
-            logger.error(f"MPS exporting for {system_platform} is currently unsupported! Please use either a Windows, Mac or Linux machine to build project.")
-            sys.exit(1)
-
-    logger.info(f"Project path for MPS: {args.project_path}")
-    logger.info(f"Engine path to build MPS: {args.engine_path}")
-    logger.info(f"Build path for non-monolithic executables: {args.non_mono_build_path}")
-    logger.info(f"Build path for monolithic executables: {args.mono_build_path}")
-    
-    output_cache_path = args.output_path / 'Cache' / selected_platform
-    output_aws_gem_path = args.output_path / 'Gems' / 'AWSCore'
-    
-    os.makedirs(output_cache_path, exist_ok=True)
-    os.makedirs(output_aws_gem_path, exist_ok=True)
-    
-    #Build o3de-multiplayersample and the engine (non-monolithic)
-    if args.build_non_mono_tools:
-        process_command(['cmake', '-S', '.', '-B', str(non_mono_build_path), '-DLY_MONOLITHIC_GAME=0', f'-DLY_PROJECTS={args.project_path}'], cwd=args.engine_path)
-
-        process_command(['cmake', '--build', str(non_mono_build_path), '--target', 'AssetBundler', 'AssetBundlerBatch', 'AssetProcessor', 'AssetProcessorBatch', '--config','profile'], cwd=args.engine_path)
-
-        process_command(['cmake', '--build', str(non_mono_build_path), '--target', 'MultiplayerSample.Assets', '--config', 'profile'], cwd=args.engine_path)
-    
-    #Build monolithic game
-    process_command(['cmake', '-S', '.', '-B', str(mono_build_path), '-DLY_MONOLITHIC_GAME=1', '-DALLOW_SETTINGS_REGISTRY_DEVELOPMENT_OVERRIDES=0', f'-DLY_PROJECTS={args.project_path}'], cwd=args.engine_path)
-    
-    if not args.no_game_launcher:
-        process_command(['cmake', '--build', str(mono_build_path), '--target', 'MultiplayerSample.GameLauncher', '--config', args.config], cwd=args.engine_path) 
-    
-    if not args.no_server_launcher:
-        process_command(['cmake', '--build', str(mono_build_path), '--target', 'MultiplayerSample.ServerLauncher', '--config', args.config], cwd=args.engine_path)
-
-    if not args.no_unified_launcher:
-        process_command(['cmake', '--build', str(mono_build_path), '--target', 'MultiplayerSample.UnifiedLauncher', '--config', args.config], cwd=args.engine_path)
-
-    #Before bundling content, make sure that the necessary executables exist
-    asset_bundler_batch_path = non_mono_build_path / 'bin' / 'profile' / ('AssetBundlerBatch' + ('.exe' if system_platform=='windows' else ''))
-    if not asset_bundler_batch_path.is_file():
-        logger.error(f"AssetBundlerBatch not found at path '{asset_bundler_batch_path}'. In order to bundle the data for MPS, this executable must be present!")
-        logger.error("To correct this issue, do 1 of the following: "
-                     "1) Use the --build-non-mono-tools flag in the CLI parameters"
-                     "2) If you are trying to build in a project-centric fashion, please switch to engine-centric for this export script"
-                     f"3) Build AssetBundlerBatch by hand and make sure it is available at {asset_bundler_batch_path}"
-                     "4) Set the --non-mono-build-path to point at a directory which contains this executable")
-        sys.exit(1)
-
-    #Bundle content
-    engine_asset_list_path = args.project_path / 'AssetBundling' /  'AssetLists' / f'engine_{selected_platform}.assetlist'
-    
-    process_command([asset_bundler_batch_path, 'assetLists','--addDefaultSeedListFiles', '--assetListFile', engine_asset_list_path, '--project-path', args.project_path, '--allowOverwrites' ], cwd=args.engine_path)
-
-
-    game_asset_list_path = args.project_path /'AssetBundling'/'AssetLists'/ f'game_{selected_platform}.assetlist'
-    seed_folder_path = args.project_path/'AssetBundling'/'SeedLists'
-
-    game_asset_list_command = [asset_bundler_batch_path, 'assetLists', '--assetListFile', game_asset_list_path, 
-                    '--seedListFile', seed_folder_path  / 'BasePopcornFxSeedList.seed',
-                    '--seedListFile', seed_folder_path  / 'GameSeedList.seed']
-
-    if args.config == 'profile':
-        # Dev branch has removed the profile seed list, but it still remains in main for now.
-        # This will be removed after the next release, when both branches are synchronized
-        profile_seed_list_path = seed_folder_path / 'ProfileOnlySeedList.seed'
-        if profile_seed_list_path.is_file():
-            game_asset_list_command += ['--seedListFile', profile_seed_list_path]
-
-    game_asset_list_command += ['--seedListFile', seed_folder_path / 'VFXSeedList.seed', '--project-path', args.project_path, '--allowOverwrites']
-
-    process_command(game_asset_list_command, cwd=args.engine_path)
-
-    engine_bundle_path = output_cache_path / f'engine_{selected_platform}.pak'
-    process_command([asset_bundler_batch_path, 'bundles', '--assetListFile', engine_asset_list_path, '--outputBundlePath', engine_bundle_path, '--project-path', args.project_path, '--allowOverwrites'], cwd=args.engine_path)
-
-    # This is to prevent any accidental file locking mechanism from failing subsequent bundling operations
-    time.sleep(1)
-
-    game_bundle_path = output_cache_path / f'game_{selected_platform}.pak'
-    process_command([asset_bundler_batch_path, 'bundles', '--assetListFile', game_asset_list_path, '--outputBundlePath', game_bundle_path, '--project-path', args.project_path, '--allowOverwrites'], cwd=args.engine_path)
-
-    # Create Launcher Layout Directory
-    import shutil
-
-    for file in glob.glob(str(pathlib.PurePath(mono_build_path / 'bin' / args.config / '*.*'))):
-        shutil.copy(file, args.output_path)
-    for file in glob.glob(str(pathlib.PurePath(mono_build_path / 'bin' / args.config / 'Gems' / 'AWSCore' / '*.*'))):
-        shutil.copy(file, output_aws_gem_path)
-    for file in glob.glob(str(pathlib.PurePath(args.project_path / 'launch_*.*'))):
-        shutil.copy(file, args.output_path)
-
-    # Optionally zip the layout directory if the user requests
-    if args.archive_output:
-        archive_name = args.output_path
-        logger.info("Archiving output directory (this may take a while)...")
-        shutil.make_archive(args.output_path, args.archive_output_format, root_dir = args.output_path)
-
-    logger.info(f"Exporting project is complete! Release Directory can be found at {args.output_path}")