From 3c62bc66f7cfae8a6c869a30bc18e0fa998f9d6b Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Mon, 21 Jul 2025 16:19:53 +0200 Subject: [PATCH 01/12] metafile with projects configurations --- .yamato/project-builders/builder.metafile | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .yamato/project-builders/builder.metafile diff --git a/.yamato/project-builders/builder.metafile b/.yamato/project-builders/builder.metafile new file mode 100644 index 0000000000..8f1eb332a8 --- /dev/null +++ b/.yamato/project-builders/builder.metafile @@ -0,0 +1,19 @@ +# 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) + BossRoom: + GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git" + branch: main + manifestPath: Packages/manifest.json + projectPath: '.' + Asteroids: + GithubRepo: "https://github.cds.internal.unity3d.com/unity/Asteroids-CMB-NGO-Sample.git" + branch: main + manifestPath: Packages/manifest.json + projectPath: '.' + SocialHub: + GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize.git" + branch: main + manifestPath: Basic/DistributedAuthoritySocialHub/Packages/manifest.json + projectPath: 'Basic/DistributedAuthoritySocialHub' From e3362603ad12d1944418169c971745babd1c204a Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Mon, 21 Jul 2025 16:20:54 +0200 Subject: [PATCH 02/12] in project builder scripts --- .../scripts/BuildAutomation/BuilderScripts.cs | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 Tools/CI/scripts/BuildAutomation/BuilderScripts.cs 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); + } +} From 0e839090889c9ebcf4e2e0ea38233f48184572ce Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Mon, 21 Jul 2025 16:21:04 +0200 Subject: [PATCH 03/12] param validation script --- .../BuildAutomation/validate_params.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Tools/CI/scripts/BuildAutomation/validate_params.py 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() From 8890744dfcaf07c690953b9bfa5e53e61efcd08a Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Mon, 21 Jul 2025 16:21:19 +0200 Subject: [PATCH 04/12] assmdef for builder script --- .../Unity.ProjectBuild.Editor.asmdef | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Tools/CI/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef 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 +} From 01b1bc9afe525f6875aee614f5ec8b56c6686b54 Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Mon, 21 Jul 2025 16:21:30 +0200 Subject: [PATCH 05/12] script to update manifest --- .../BuildAutomation/manifest_update.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 Tools/CI/scripts/BuildAutomation/manifest_update.py 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() From 40eb3b3343fe2ad31aea98bbcc5d1f7d4286c692 Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Mon, 21 Jul 2025 16:21:39 +0200 Subject: [PATCH 06/12] script to control burst --- .../BuildAutomation/disable-enable-burst.py | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Tools/CI/scripts/BuildAutomation/disable-enable-burst.py 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() From 18ab5549b8f6f27bb9c7cd63f0208f06bfa2caac Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Mon, 21 Jul 2025 16:21:49 +0200 Subject: [PATCH 07/12] script to connect to services --- .../BuildAutomation/connect_services.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Tools/CI/scripts/BuildAutomation/connect_services.py 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() + + From fb820e7261ab1c1737924bd02e45cc14acbadb0b Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Mon, 21 Jul 2025 16:21:55 +0200 Subject: [PATCH 08/12] builder job --- .yamato/project-builders/project-builders.yml | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .yamato/project-builders/project-builders.yml diff --git a/.yamato/project-builders/project-builders.yml b/.yamato/project-builders/project-builders.yml new file mode 100644 index 0000000000..ac47c646f5 --- /dev/null +++ b/.yamato/project-builders/project-builders.yml @@ -0,0 +1,98 @@ +{% 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%/Packages/com.unity.netcode + + # 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 -c "import os; os.makedirs('C:/ClonedProject/{{ netcodeProject[1].projectPath }}/Assets/Scripts/Editor', exist_ok=True)" # --> Create the directory if it doesn't exist + - python -c "import shutil; shutil.copy('Tools/CI/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef', 'C:/ClonedProject/{{ netcodeProject[1].projectPath }}/Assets/Scripts/Editor/')" # --> Copy the asmdef file to the directory + - python -c "import shutil; shutil.copy('Tools/CI/scripts/BuildAutomation/BuilderScripts.cs', 'C:/ClonedProject/{{ netcodeProject[1].projectPath }}/Assets/Scripts/Editor/')" # --> Copy the BuilderScripts.cs file to the directory (for build configuration setup) + + # 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 From 31f55d7601d34d9a7717ec8855fdf74c10ef903c Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Mon, 21 Jul 2025 17:03:27 +0200 Subject: [PATCH 09/12] Updated builder.metafile to include only relevant projects --- .yamato/project-builders/builder.metafile | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.yamato/project-builders/builder.metafile b/.yamato/project-builders/builder.metafile index 8f1eb332a8..c95443cb9f 100644 --- a/.yamato/project-builders/builder.metafile +++ b/.yamato/project-builders/builder.metafile @@ -7,13 +7,3 @@ NetcodeProjects: branch: main manifestPath: Packages/manifest.json projectPath: '.' - Asteroids: - GithubRepo: "https://github.cds.internal.unity3d.com/unity/Asteroids-CMB-NGO-Sample.git" - branch: main - manifestPath: Packages/manifest.json - projectPath: '.' - SocialHub: - GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize.git" - branch: main - manifestPath: Basic/DistributedAuthoritySocialHub/Packages/manifest.json - projectPath: 'Basic/DistributedAuthoritySocialHub' From faac42bc94fb7f7d9f6dbfa2c6b6314f709943f5 Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Mon, 21 Jul 2025 22:25:23 +0200 Subject: [PATCH 10/12] ngo package.json path correction --- .yamato/project-builders/project-builders.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.yamato/project-builders/project-builders.yml b/.yamato/project-builders/project-builders.yml index ac47c646f5..135f5a2b08 100644 --- a/.yamato/project-builders/project-builders.yml +++ b/.yamato/project-builders/project-builders.yml @@ -39,7 +39,7 @@ build_{{ netcodeProject[0] }}_project: - 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%/Packages/com.unity.netcode + - 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. From 907f2e6170e11727275043f28dceb8e45547c1ba Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Tue, 22 Jul 2025 00:41:29 +0200 Subject: [PATCH 11/12] Added FileCopy script and corrected BossRoom branch --- .yamato/project-builders/builder.metafile | 3 +- .yamato/project-builders/project-builders.yml | 4 +- Tools/CI/scripts/BuildAutomation/FileCopy.py | 61 +++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 Tools/CI/scripts/BuildAutomation/FileCopy.py diff --git a/.yamato/project-builders/builder.metafile b/.yamato/project-builders/builder.metafile index c95443cb9f..4a91426c0f 100644 --- a/.yamato/project-builders/builder.metafile +++ b/.yamato/project-builders/builder.metafile @@ -2,8 +2,9 @@ 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) + # TODO: Why develop and not main? BossRoom: GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git" - branch: main + branch: develop manifestPath: Packages/manifest.json projectPath: '.' diff --git a/.yamato/project-builders/project-builders.yml b/.yamato/project-builders/project-builders.yml index 135f5a2b08..54fff4e0ec 100644 --- a/.yamato/project-builders/project-builders.yml +++ b/.yamato/project-builders/project-builders.yml @@ -56,9 +56,7 @@ build_{{ netcodeProject[0] }}_project: # 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 -c "import os; os.makedirs('C:/ClonedProject/{{ netcodeProject[1].projectPath }}/Assets/Scripts/Editor', exist_ok=True)" # --> Create the directory if it doesn't exist - - python -c "import shutil; shutil.copy('Tools/CI/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef', 'C:/ClonedProject/{{ netcodeProject[1].projectPath }}/Assets/Scripts/Editor/')" # --> Copy the asmdef file to the directory - - python -c "import shutil; shutil.copy('Tools/CI/scripts/BuildAutomation/BuilderScripts.cs', 'C:/ClonedProject/{{ netcodeProject[1].projectPath }}/Assets/Scripts/Editor/')" # --> Copy the BuilderScripts.cs file to the directory (for build configuration setup) + - 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 diff --git a/Tools/CI/scripts/BuildAutomation/FileCopy.py b/Tools/CI/scripts/BuildAutomation/FileCopy.py new file mode 100644 index 0000000000..392e37783b --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/FileCopy.py @@ -0,0 +1,61 @@ +# Filename: Tools/CI/scripts/prepare_build_scripts.py + +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() + From 4cb1dfb8a993116def38935c3009ca00e1b518bc Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Tue, 22 Jul 2025 09:26:45 +0200 Subject: [PATCH 12/12] BossRoom branch corrected --- .yamato/project-builders/builder.metafile | 4 ++-- Tools/CI/scripts/BuildAutomation/FileCopy.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.yamato/project-builders/builder.metafile b/.yamato/project-builders/builder.metafile index 4a91426c0f..b31b8c5167 100644 --- a/.yamato/project-builders/builder.metafile +++ b/.yamato/project-builders/builder.metafile @@ -2,9 +2,9 @@ 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) - # TODO: Why develop and not main? + # 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: develop + branch: main manifestPath: Packages/manifest.json projectPath: '.' diff --git a/Tools/CI/scripts/BuildAutomation/FileCopy.py b/Tools/CI/scripts/BuildAutomation/FileCopy.py index 392e37783b..249c12f16c 100644 --- a/Tools/CI/scripts/BuildAutomation/FileCopy.py +++ b/Tools/CI/scripts/BuildAutomation/FileCopy.py @@ -1,6 +1,4 @@ -# Filename: Tools/CI/scripts/prepare_build_scripts.py - -import os +import os import shutil import sys