diff --git a/.yamato/project-builders/builder.metafile b/.yamato/project-builders/builder.metafile new file mode 100644 index 0000000000..b31b8c5167 --- /dev/null +++ b/.yamato/project-builders/builder.metafile @@ -0,0 +1,10 @@ +# https://internaldocs.unity.com/yamato_continuous_integration/usage/templating/ + +NetcodeProjects: + # Note that we are using internal Unity repo. This means that we may test with newest changes that are not yet released to our users (there are also public versions) + # Note that for BossRoom 'main' branch supports NGOv1.X and 'develop' branch supports NGOv2.X + BossRoom: + GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git" + branch: main + manifestPath: Packages/manifest.json + projectPath: '.' diff --git a/.yamato/project-builders/project-builders.yml b/.yamato/project-builders/project-builders.yml new file mode 100644 index 0000000000..54fff4e0ec --- /dev/null +++ b/.yamato/project-builders/project-builders.yml @@ -0,0 +1,96 @@ +{% metadata_file .yamato/project-builders/builder.metafile %} +--- +# The above line corresponds to https://internaldocs.unity.com/yamato_continuous_integration/usage/templating/ + +# Yamato paths disclaimer: +# Note that artifacts can get only local paths and since cloned project is in different location, you need to be careful with paths +# All artifact paths are defined relative to the Yamato source directory (YAMATO_SOURCE_DIR) and are case-sensitive. +# !!! important “Artifact directory locations” All artifact globs are defined relative to the source directory (YAMATO_SOURCE_DIR). Yamato can’t capture artifacts from outside this directory, so if you need files from elsewhere, you should copy them into your source directory as part of job commands. + +# Those jobs were created in order to (in most cases) speed up playtesting and development time. +# The aim is to collect all possible projects that use Netcode for Entities and create a job that will build the project with the given version of N4E package. +# The package is taken directly from the branch from which the job was triggered. So image triggering the job from release/2.0.0 and release/2.1.0 branch to compare differences +# Example use case would be to trigger the build job on Sunday so by Monday morning all builds are ready for playtesting. This limits the time a dev/QA has to spend building projects for different configurations (win, mac, Android, scripting backends, burst etc) or simply time necessary for building huge projects (Megacity). + +# This job takes parameters as scriptable backend configuration, burst compilation, unity editor version and platform. +# Since Yamato variables can't be restricted to only specific values, the job will validate the parameters passed to it and fail quickly if those are incorrect. In order to see what options are available, please look at the variable name. + +# Note that for now all of those builds are being made on Windows machine (so for example combination of macOS + il2cpp is expected to fail) +# TODO: for now all builds are being made on Windows machine, but it would be nice to have a Mac build as well. +# TODO: add iOS support +{% for netcodeProject in NetcodeProjects -%} +build_{{ netcodeProject[0] }}_project: + name: {{ netcodeProject[0] }} + agent: + type: Unity::VM + image: package-ci/win10:v4 + flavor: b1.xlarge + variables: + UNITY_VERSION: trunk + SCRIPTING_BACKEND_IL2CPP_MONO: il2cpp + BURST_ON_OFF: on + PLATFORM_WIN64_MAC_ANDROID: win64 + commands: + # Validate inputs passed via Yamato variables + - python Tools/CI/scripts/BuildAutomation/validate_params.py + - echo Building {{ netcodeProject[0] }} project with Unity version of %UNITY_VERSION%, Scripting backend %SCRIPTING_BACKEND_IL2CPP_MONO%, Burst %BURST_ON_OFF% for platform %PLATFORM_WIN64_MAC_ANDROID% + + # Clone the external project repository into a specific directory. Notice that branch is also specified. + - git clone --single-branch --branch {{ netcodeProject[1].branch }} {{ netcodeProject[1].GithubRepo }} C:/ClonedProject + + # Modify the external project's manifest to use the local N4E package from current branch on which this Yamato job is running. (requires python that should be preinstalled in the image) + - python Tools/CI/scripts/BuildAutomation/manifest_update.py --manifest-path C:/ClonedProject/{{ netcodeProject[1].manifestPath }} --local-package-path %YAMATO_SOURCE_DIR%/com.unity.netcode.gameobjects + + # Run python script to update ProjectSettings.asset in order to connect the project to Unity Services/set proper values. + # Notice that if a project has this already set up then in theory we don't need to run this script. + - python Tools/CI/scripts/BuildAutomation/connect_services.py --project-settings-path C:/ClonedProject/{{ netcodeProject[1].projectPath }}/ProjectSettings/ProjectSettings.asset + + # Enable or disable Burst compilation. This step is specific to Netcode package (or any package that uses Burst) + - IF "%BURST_ON_OFF%"=="on" (python Tools/CI/scripts/BuildAutomation/disable-enable-burst.py --enable-burst --project-path C:/ClonedProject/{{ netcodeProject[1].projectPath }}) + ELSE (python Tools/CI/scripts/BuildAutomation/disable-enable-burst.py --disable-burst --project-path C:/ClonedProject/{{ netcodeProject[1].projectPath }}) + + # Download the Unity Editor version specified in the UNITY_VERSION variable. Il2cpp component is downloaded only if the SCRIPTING_BACKEND_IL2CPP_MONO is set to "il2cpp". + # TODO: we could download components only if needed + - unity-downloader-cli --fast --wait -u %UNITY_VERSION% -p C:/TestingEditor -c Editor -c il2cpp -c Android -c macOS + + # Add BuilderScript.cs to the project so we can modify and build the project using Unity Editor. + # This is a bit tricky step, notice that we also need to include proper assembly definition in order for those scripts to compile properly. + # TODO: the asmdef file can be simplified + - python Tools/CI/scripts/BuildAutomation/FileCopy.py "C:/ClonedProject/{{ netcodeProject[1].projectPath }}" + + # Build the project using Unity Editor. This will call the given static BuilderScripts method. + # Ideally, it would be nice to parametrize the BuilderScripts (for example to pass scripting backend as parameter) but -executeMethod only calls static methods without parameters so for now we will have multiple configurations + # Notice that for Android platform even if mono is selected, il2cpp will be used since mono is not supported for Android builds. + - IF "%PLATFORM_WIN64_MAC_ANDROID%"=="win64" ( + IF "%SCRIPTING_BACKEND_IL2CPP_MONO%"=="il2cpp" ( + C:/TestingEditor/Unity.exe -projectPath C:/ClonedProject/{{ netcodeProject[1].projectPath }} -buildTarget win64 -executeMethod BuilderScripts.BuildWinIl2cpp -batchmode -logFile ./artifacts/UnityLog.txt -automated -crash-report-folder ./artifacts/CrashArtifacts -quit + ) ELSE ( + C:/TestingEditor/Unity.exe -projectPath C:/ClonedProject/{{ netcodeProject[1].projectPath }} -buildTarget win64 -executeMethod BuilderScripts.BuildWinMono -batchmode -logFile ./artifacts/UnityLog.txt -automated -crash-report-folder ./artifacts/CrashArtifacts -quit + ) + ) + ELSE IF "%PLATFORM_WIN64_MAC_ANDROID%"=="mac" ( + IF "%SCRIPTING_BACKEND_IL2CPP_MONO%"=="il2cpp" ( + C:/TestingEditor/Unity.exe -projectPath C:/ClonedProject/{{ netcodeProject[1].projectPath }} -buildTarget osx -executeMethod BuilderScripts.BuildMacIl2cpp -batchmode -logFile ./artifacts/UnityLog.txt -automated -crash-report-folder ./artifacts/CrashArtifacts -quit + ) ELSE ( + C:/TestingEditor/Unity.exe -projectPath C:/ClonedProject/{{ netcodeProject[1].projectPath }} -buildTarget osx -executeMethod BuilderScripts.BuildMacMono -batchmode -logFile ./artifacts/UnityLog.txt -automated -crash-report-folder ./artifacts/CrashArtifacts -quit + ) + ) + ELSE IF "%PLATFORM_WIN64_MAC_ANDROID%"=="android" ( + C:/TestingEditor/Unity.exe -projectPath C:/ClonedProject/{{ netcodeProject[1].projectPath }} -buildTarget android -executeMethod BuilderScripts.BuildAndroidIl2cpp -batchmode -logFile ./artifacts/UnityLog.txt -automated -crash-report-folder ./artifacts/CrashArtifacts -quit + ) + + # Because of this we need to ensure that all files are copied to the source directory. + # TODO: this can be omitted if I can somehow build the project in the source directory (YAMATO_SOURCE_DIR) instead of C:/CompetitiveAction + - python -c "import os; os.makedirs('./build', exist_ok=True)" # --> Create the build directory if it doesn't exist + - python -c "import shutil; shutil.copytree('C:/ClonedProject/{{ netcodeProject[1].projectPath }}/build', './build', dirs_exist_ok=True)" # --> Copy the build directory to the source directory (YAMATO_SOURCE_DIR). Remember to copy entire directory and not only exe file + + artifacts: + logs: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + players: + paths: + - build/**/* +{% endfor -%} \ No newline at end of file diff --git a/Tools/CI/scripts/BuildAutomation/BuilderScripts.cs b/Tools/CI/scripts/BuildAutomation/BuilderScripts.cs new file mode 100644 index 0000000000..159dd619e9 --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/BuilderScripts.cs @@ -0,0 +1,181 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.Build; +using UnityEngine; +using System; +using System.Collections; +using UnityEngine.Rendering; +using UnityEngine.SceneManagement; + +// This script is used to automate the build process for different platforms. +// When included in the project, it can be triggered from the script the game for teh given configuration. +// Note that it's possible to have those as a button in the editor (see https://github.cds.internal.unity3d.com/unity/Megacity-Metro/blob/c3b1b16ff1f04f96fbfbcc3267696679ad4e8396/Megacity-Metro/Assets/Scripts/Utils/Editor/BuilderScript.cs) +// Ideally we would like to pass scripting backend and platform as parameters instead of having different script per each combintation but the nature of calling this nmethod via script (via -executeMethod) is that it needs to be static and the parameters are not passed in a way that we can use them. +// TODO: add iOS support +public class BuilderScripts : MonoBehaviour +{ + [MenuItem("Tools/Builder/Build Development Windows Il2cpp")] + static void BuildWinIl2cpp() + { + // This part modifies Player Settings. We only use it (for our case) to modify scripting backend + PlayerSettings.SetScriptingBackend(NamedBuildTarget.Standalone, ScriptingImplementation.IL2CPP); + PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.StandaloneWindows64, false); // disable auto graphic to use our own custom list + PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneWindows64, new []{GraphicsDeviceType.Vulkan, GraphicsDeviceType.Direct3D11, GraphicsDeviceType.Direct3D12}); // We need to specify the graphics API for Android builds to ensure proper shader compilation. Vulkan is recommended for modern devices. + + // Below you can see additional settings that you can apply to the build: + //PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneWindows64, new []{GraphicsDeviceType.Direct3D12}); + //PlayerSettings.SetArchitecture(NamedBuildTarget.Standalone,0); + + // The settings that we applied above need to be saved. + AssetDatabase.SaveAssets(); + + // We want to build all scenes in build settings, so we collect them here. + // If you want to build only specific scenes, then you could just use something like buildPlayerOptions.scenes = new[] { "Assets/Scenes/Menu.unity","Assets/Scenes/Main.unity" }; below. + List scenesToAdd = new List(); + foreach (var scene in EditorBuildSettings.scenes) + { + if (scene.enabled) + { + Debug.Log("Adding scene to build: " + scene.path); + scenesToAdd.Add(scene.path); + } + } + + // This is an equivalent of BuildPlayerOptions in the Unity Editor. + // We want to choose development build, what platform are we targetting, where to save the build and which scenes to include. + // Some of those options can be omitted when triggering this script from withing GUI since more implicit context is provided (targetGroup, subtarget) + // subtarget = (int)StandaloneBuildSubtarget.Player + // targetGroup = BuildTargetGroup.Standalone + // extraScriptingDefines = new[] { "NETCODE_DEBUG", "UNITY_CLIENT" }, + var buildPlayerOptions = new BuildPlayerOptions + { + locationPathName = "./build/Windows_Il2cpp/PlaytestBuild.exe", + target = BuildTarget.StandaloneWindows64, + options = BuildOptions.Development, + scenes = scenesToAdd.ToArray() + }; + + BuildPipeline.BuildPlayer(buildPlayerOptions); + } + + [MenuItem("Tools/Builder/Build Development Windows Mono")] + static void BuildWinMono() + { + PlayerSettings.SetScriptingBackend(NamedBuildTarget.Standalone, ScriptingImplementation.Mono2x); + PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.StandaloneWindows64, false); // disable auto graphic to use our own custom list + PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneWindows64, new []{GraphicsDeviceType.Vulkan, GraphicsDeviceType.Direct3D11, GraphicsDeviceType.Direct3D12}); // We need to specify the graphics API for Android builds to ensure proper shader compilation. Vulkan is recommended for modern devices. + + AssetDatabase.SaveAssets(); + + List scenesToAdd = new List(); + foreach (var scene in EditorBuildSettings.scenes) + { + if (scene.enabled) + { + Debug.Log("Adding scene to build: " + scene.path); + scenesToAdd.Add(scene.path); + } + } + + var buildPlayerOptions = new BuildPlayerOptions + { + locationPathName = "./build/Windows_Mono/PlaytestBuild.exe", + target = BuildTarget.StandaloneWindows64, + options = BuildOptions.Development, + scenes = scenesToAdd.ToArray() + }; + + BuildPipeline.BuildPlayer(buildPlayerOptions); + } + + [MenuItem("Tools/Builder/Build Development Mac Mono")] + static void BuildMacMono() + { + PlayerSettings.SetScriptingBackend(NamedBuildTarget.Standalone, ScriptingImplementation.Mono2x); + PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.StandaloneOSX, false); // disable auto graphic to use our own custom list + PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneOSX, new []{GraphicsDeviceType.Metal}); // enforcing Metal Graphics API. Without this there will be shader errors in the final build. + + AssetDatabase.SaveAssets(); + + List scenesToAdd = new List(); + foreach (var scene in EditorBuildSettings.scenes) + { + if (scene.enabled) + { + Debug.Log("Adding scene to build: " + scene.path); + scenesToAdd.Add(scene.path); + } + } + + var buildPlayerOptions = new BuildPlayerOptions + { + locationPathName = "./build/macOS_Mono/PlaytestBuild.app", + target = BuildTarget.StandaloneOSX, + options = BuildOptions.Development, + scenes = scenesToAdd.ToArray() + }; + + BuildPipeline.BuildPlayer(buildPlayerOptions); + } + + [MenuItem("Tools/Builder/Build Development Mac Il2cpp")] + static void BuildMacIl2cpp() + { + PlayerSettings.SetScriptingBackend(NamedBuildTarget.Standalone, ScriptingImplementation.IL2CPP); + PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneOSX, new []{GraphicsDeviceType.Metal}); // enforcing Metal Graphics API. Without this there will be shader errors in the final build. + + AssetDatabase.SaveAssets(); + + List scenesToAdd = new List(); + foreach (var scene in EditorBuildSettings.scenes) + { + if (scene.enabled) + { + Debug.Log("Adding scene to build: " + scene.path); + scenesToAdd.Add(scene.path); + } + } + + var buildPlayerOptions = new BuildPlayerOptions + { + locationPathName = "./build/macOS_Il2cpp/PlaytestBuild.app", + target = BuildTarget.StandaloneOSX, + options = BuildOptions.Development, + scenes = scenesToAdd.ToArray() + }; + + BuildPipeline.BuildPlayer(buildPlayerOptions); + } + + [MenuItem("Tools/Builder/Build Development Android Il2cpp")] + static void BuildAndroidIl2cpp() + { + PlayerSettings.SetScriptingBackend(NamedBuildTarget.Android, ScriptingImplementation.IL2CPP); + PlayerSettings.SetApplicationIdentifier(NamedBuildTarget.Android, "com.UnityTestRunner.UnityTestRunner"); // This is needed only for mobiles since by default the application identifier quite often contains invalid characters like spaces so we wan't to make sure that this has a valid value. It's needed only for mobile since that's an app store requirement + PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.Android, false); // disable auto graphic to use our own custom list + PlayerSettings.SetGraphicsAPIs(BuildTarget.Android, new []{GraphicsDeviceType.Vulkan}); // We need to specify the graphics API for Android builds to ensure proper shader compilation. Vulkan is recommended for modern devices. + PlayerSettings.SetArchitecture(BuildTargetGroup.Android,1); // An integer value associated with the architecture of the build target. 0 - None, 1 - ARM64, 2 - Universal. most modern Android devices use the ARM64 architecture + + AssetDatabase.SaveAssets(); + + List scenesToAdd = new List(); + foreach (var scene in EditorBuildSettings.scenes) + { + if (scene.enabled) + { + Debug.Log("Adding scene to build: " + scene.path); + scenesToAdd.Add(scene.path); + } + } + + var buildPlayerOptions = new BuildPlayerOptions + { + locationPathName = "./build/Android_Il2cpp_Vulkan/PlaytestBuild.apk", + target = BuildTarget.Android, + options = BuildOptions.Development, + scenes = scenesToAdd.ToArray() + }; + + BuildPipeline.BuildPlayer(buildPlayerOptions); + } +} diff --git a/Tools/CI/scripts/BuildAutomation/FileCopy.py b/Tools/CI/scripts/BuildAutomation/FileCopy.py new file mode 100644 index 0000000000..249c12f16c --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/FileCopy.py @@ -0,0 +1,59 @@ +import os +import shutil +import sys + +def main(): + """ + Cleans and prepares the 'Assets/Scripts/Editor' directory for build automation scripts. + It deletes the directory if it exists, recreates it, and copies in the necessary + assembly definition and C# script files. + """ + # --- 1. Argument Validation --- + if len(sys.argv) < 2: + print("Error: Missing required argument.") + print("Usage: python prepare_build_scripts.py ") + sys.exit(1) + + project_root = sys.argv[1] + + # --- 2. Define File Paths --- + # The target directory inside the Unity project + target_dir = os.path.join(project_root, 'Assets', 'Scripts', 'Editor') + + # The source files for build automation + source_asmdef = 'Tools/CI/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef' + source_script = 'Tools/CI/scripts/BuildAutomation/BuilderScripts.cs' + + print(f"Preparing build scripts for project at: {project_root}") + print(f"Target editor script directory: {target_dir}") + + # --- 3. Clean and Recreate Directory --- + try: + if os.path.exists(target_dir): + print(f"Directory '{target_dir}' exists. Removing it.") + shutil.rmtree(target_dir) + + print(f"Creating directory: {target_dir}") + os.makedirs(target_dir) + + except OSError as e: + print(f"Error managing directory: {e}") + sys.exit(1) + + # --- 4. Copy Build Automation Files --- + try: + print(f"Copying '{source_asmdef}' to '{target_dir}'") + shutil.copy(source_asmdef, target_dir) + + print(f"Copying '{source_script}' to '{target_dir}'") + shutil.copy(source_script, target_dir) + + except IOError as e: + print(f"Error copying files: {e}") + sys.exit(1) + + print("\nSuccessfully prepared build automation scripts.") + +if __name__ == "__main__": + main() + diff --git a/Tools/CI/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef b/Tools/CI/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef new file mode 100644 index 0000000000..08a6717d18 --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef @@ -0,0 +1,16 @@ +{ + "name": "Unity.ProjectBuild.Editor", + "rootNamespace": "", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Tools/CI/scripts/BuildAutomation/connect_services.py b/Tools/CI/scripts/BuildAutomation/connect_services.py new file mode 100644 index 0000000000..ac2cfa01a0 --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/connect_services.py @@ -0,0 +1,46 @@ +# This script modifies ProjectSettings.asset in order to connect the project to Services (like Relay). This is needed for Netcode related builds and can be probably skipped in other cases +# As a Netcode team we usually use (during Playtesting) the following settings + +# Note that cloudProjectId, projectName and organizationId are defined as secrets + +# Notice that those parameters are included as defaults but in your yml file you can override them with your own values. + +import sys +import re +import argparse +import os + +def parse_args(): + global args + parser = argparse.ArgumentParser(description='Update ProjectSettings.asset in order to properly connect to services.') + parser.add_argument('--project-settings-path', required=True, help='The absolute path to the project ProjectSettings.asset file that contains all of the services settings.') + parser.add_argument('--cloud-project-ID', default=os.getenv("CLOUDPROJECTID"), help='ID of a cloud project to which we want to connect to.') + parser.add_argument('--organization-ID', default=os.getenv("ORGANIZATIONID"), help="ID of the organization to which the cloud project belongs.") + parser.add_argument('--project-name', default=os.getenv("PROJECTNAME"), help='Name of the project to which we want to connect to.') + args = parser.parse_args() + +def main(): + """ + Modifies ProjectSettings.asset in order to connect the project to Services + """ + parse_args() + + with open(args.project_settings_path, 'r') as f: + content = f.read() + + # Use regex to replace the values. This is safer than simple string replacement. + content = re.sub(r"cloudProjectId:.*", f"cloudProjectId: {args.cloud_project_ID}", content) + content = re.sub(r"organizationId:.*", f"organizationId: {args.organization_ID}", content) + content = re.sub(r"projectName:.*", f"projectName: {args.project_name}", content) + # Ensure the project is marked as connected + content = re.sub(r"cloudEnabled:.*", "cloudEnabled: 1", content) + + with open(args.project_settings_path, 'w') as f: + f.write(content) + + print(f"[Linker] Successfully updated {args.project_settings_path} with Project ID: {args.cloud_project_ID}, Org ID: {args.organization_ID}, Project Name: {args.project_name}") + +if __name__ == "__main__": + main() + + diff --git a/Tools/CI/scripts/BuildAutomation/disable-enable-burst.py b/Tools/CI/scripts/BuildAutomation/disable-enable-burst.py new file mode 100644 index 0000000000..38845c08a3 --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/disable-enable-burst.py @@ -0,0 +1,120 @@ +# An example usage would be "- python Tools/CI/Netcode/BuildAutomations/disable-enable-burst.py --project-path {{ project.path }} --platform WebGL" +# This file aims to modify BurstAotSettings file which should be present under ProjectSettings folder. +# Note that this requires Burst package to be installed as well as you need to specify the platform for which you are building since there are different settings for each. (this is taken from environment variable) +# This script is not overriding existing settings file but completely replacing it + +import argparse +import json +import os + +# Function that parses arguments of the script +def parse_args(): + global args + parser = argparse.ArgumentParser(description="Enable or disable Burst compilation and specify Unity project details.") + + # Add the mutually exclusive group for --disable-burst and --enable-burst + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('--disable-burst', action='store_true', help='Disable Burst compilation.') + group.add_argument('--enable-burst', action='store_true', help='Enable Burst compilation.') + + # Add additional arguments + parser.add_argument('--project-path', required=True, help='Specify the location of the Unity project.') + + args = parser.parse_args() + + +# This function creates a new burst settings file with default values. Notice that this should almost always not be used since assumption is that in our case we have projects with Burst preinstalled +# For the "default" values I used values from NetcodeSamples project in DOTS-monorepo +def create_config(settings_path): + config_name = os.path.join(settings_path, 'BurstAotSettings_{}.json'.format(resolve_target())) + monobehaviour = { + 'Version': 4, + 'EnableBurstCompilation': True, + 'EnableOptimisations': True, + 'EnableSafetyChecks': False, + 'EnableDebugInAllBuilds': False, + 'CpuMinTargetX32': 0, + 'CpuMaxTargetX32': 0, + 'CpuMinTargetX64': 0, + 'CpuMaxTargetX64': 0, + 'CpuTargetsX32': 6, + 'CpuTargetsX64': 72, + 'OptimizeFor': 0 + } + + data = {'MonoBehaviour': monobehaviour} + with open(config_name, 'w') as f: + json.dump(data, f) + return config_name + + +# Burst has specific files for each platform, so we need to resolve the target platform to get the correct settings file. +# Note that this jobs uses environment variables to pass parameters to the script. +def resolve_target(): + # Get the platform value from the environment variable + platform_key = os.environ.get('PLATFORM_WIN64_MAC_ANDROID') + + resolved_target = platform_key + if 'win64' == platform_key: + resolved_target = 'StandaloneWindows' + elif 'mac' == platform_key: + resolved_target = 'StandaloneOSX' + elif 'android' == platform_key: + resolved_target = 'Android' + else: + raise ValueError("Unsupported platform: {}".format(platform) + "Check if you are passing correct argument for one of the supported platforms: StandaloneWindows or StandaloneLinux") + + return resolved_target + + +# This function either returns existing burst settings or creates new if a file was not found +def get_or_create_burst_AOT_config(): + settings_path = os.path.join(args.project_path, 'ProjectSettings') + if not os.path.isdir(settings_path): + os.mkdir(settings_path) + config_names = [os.path.join(settings_path, filename) for filename in os.listdir(settings_path) if filename.startswith("BurstAotSettings_{}".format(resolve_target()))] + if not config_names: + return [create_config(settings_path)] + return config_names + + +# Function that sets the AOT status in the burst settings file (essentially enables or disables burst compilation) +def set_burst_AOT(config_file, status): + config = None + with open(config_file, 'r') as f: + config = json.load(f) + + assert config is not None, 'AOT settings not found; did the burst-enabled build finish successfully?' + + config['MonoBehaviour']['EnableBurstCompilation'] = status + with open(config_file, 'w') as f: + json.dump(config, f) + + +def main(): + parse_args() + config_names = get_or_create_burst_AOT_config() + + platform_key = os.environ.get('PLATFORM_WIN64_MAC_ANDROID') + print(f"Burst compilation script: Unity project path is {args.project_path}") + print(f"Burst compilation script: Target platform is {platform_key}") + + if args.disable_burst: + print('BURST COMPILATION: DISABLED') + + for config_name in config_names: + set_burst_AOT(config_name, False) + + elif args.enable_burst: + print('BURST COMPILATION: ENABLED') + + for config_name in config_names: + set_burst_AOT(config_name, True) + + else: + sys.exit('BURST COMPILATION: unexpected value: {}'.format(args.enable_burst)) + + + +if __name__ == '__main__': + main() diff --git a/Tools/CI/scripts/BuildAutomation/manifest_update.py b/Tools/CI/scripts/BuildAutomation/manifest_update.py new file mode 100644 index 0000000000..045ddb1776 --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/manifest_update.py @@ -0,0 +1,62 @@ +# This script is used to replace a package from the project manifest.json with a local version. +# The goal is that you will trigger the build process from your branch (for example release/1.2.3) and package from this branch will be use in the project manifest.1 +# Note that for now this script is assuming that such package already has an entry in the manifest +# TODO: consider if it makes sense to just add new manifest entry (to test what?) + +import json +import argparse +import os + +def parse_args(): + global args + parser = argparse.ArgumentParser(description='Update a Unity project manifest to point to the local package version.') + parser.add_argument('--manifest-path', required=True, help='The absolute path to the project manifest.json file.') + parser.add_argument('--package-name', default="com.unity.netcode.gameobjects", help="The name of the package to modify in the manifest.") + parser.add_argument('--local-package-path', required=True, help='The absolute file path to the local package source directory.') + args = parser.parse_args() + +def main(): + """ + Updates a project's manifest.json to use package under passed local-package-path, + and then prints the version of that local package. + """ + parse_args() + + # Update the target project's manifest + try: + with open(args.manifest_path, 'r') as f: + manifest_data = json.load(f) + + local_path_normalized = args.local_package_path.replace(os.sep, '/') + manifest_data["dependencies"][args.package_name] = f"file:{local_path_normalized}" + + with open(args.manifest_path, 'w') as f: + json.dump(manifest_data, f, indent=4) + + print(f"Successfully updated manifest at '{args.manifest_path}'") + print(f"Set '{args.package_name}' to use local package at '{args.local_package_path}'") + except Exception as e: + print(f"Error updating manifest: {e}") + exit(1) + + # --- Read and report the local package's version for log confirmation--- + # This is only for debug purposes + try: + # Construct the path to the local package's package.json file + local_package_json_path = os.path.join(args.local_package_path, 'package.json') + + with open(local_package_json_path, 'r') as f: + local_package_data = json.load(f) + + # Extract the version, providing a default if not found + local_package_version = local_package_data.get('version', 'N/A') + + print(f"--> Verified local '{args.package-name}' version is: {local_package_version}") + + except FileNotFoundError: + print(f"Warning: Could not find package.json at '{local_package_json_path}'") + except Exception as e: + print(f"Error reading local package version: {e}") + +if __name__ == "__main__": + main() diff --git a/Tools/CI/scripts/BuildAutomation/validate_params.py b/Tools/CI/scripts/BuildAutomation/validate_params.py new file mode 100644 index 0000000000..d95c065626 --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/validate_params.py @@ -0,0 +1,63 @@ +import os +import sys + +# --- Configuration --- +# A dictionary that maps each environment variable to a set of its allowed values. +# This is the single source of truth for all validation. +VALIDATION_RULES = { + 'SCRIPTING_BACKEND_IL2CPP_MONO': {'il2cpp', 'mono'}, + 'BURST_ON_OFF': {'on', 'off'}, + 'PLATFORM_WIN64_MAC_ANDROID': {'win64', 'mac', 'android'} +} + +def main(): + """ + Validates Yamato environment variables using a rule-based dictionary. + Exits with 1 if any variable is invalid, otherwise exits with 0. + """ + all_params_valid = True + + # Iterate through the dictionary of rules. + for var_name, allowed_values in VALIDATION_RULES.items(): + # Get the variable's value from the environment. + actual_value = os.environ.get(var_name, '') + + # Check if the actual value is in the set of allowed values. + if actual_value not in allowed_values: + print( + f"ERROR: Invalid {var_name}: '{actual_value}'. " + f"Allowed values are: {list(allowed_values)}", + file=sys.stderr + ) + all_params_valid = False + + # --- Validation for Invalid Combinations --- + platform = os.environ.get('PLATFORM_WIN64_MAC_ANDROID') + scripting_backend = os.environ.get('SCRIPTING_BACKEND_IL2CPP_MONO') + + if platform == 'mac' and scripting_backend == 'il2cpp': + print( + "ERROR: Invalid Configuration: The 'mac' platform with the 'il2cpp' " + "Note that for now windows machine is used for building project and it's a known limitation that mac builds (via windows machine) can be done only with mono", + file=sys.stderr + ) + all_params_valid = False + + if platform == 'android' and scripting_backend == 'mono': + print( + "ERROR: Invalid Configuration: The 'android' platform with the 'mono' " + "Note that mobile builds are not supporting mono and need il2cpp scripting backend", + file=sys.stderr + ) + all_params_valid = False + + # --- Final Result --- + if not all_params_valid: + print("\nOne or more parameters failed validation. Halting build.", file=sys.stderr) + # Exit with a non-zero code to fail the Yamato job. + sys.exit(1) + + print("All parameters are valid. Proceeding with the build.") + +if __name__ == "__main__": + main()