From db8fa7f287cb56f499183ead0145f29d843eabef Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Fri, 30 Jan 2026 12:45:54 +0100 Subject: [PATCH 1/4] File update based on changes in DOTS repo --- .yamato/project-builders/builder.metafile | 24 +- .yamato/project-builders/project-builders.yml | 132 +++++----- .../scripts/BuildAutomation/BuilderScripts.cs | 228 +++++++----------- Tools/scripts/BuildAutomation/FileCopy.py | 59 ----- .../BuildAutomation/connect_services.py | 148 +++++++++--- .../BuildAutomation/disable-enable-burst.py | 167 ++++++------- .../BuildAutomation/get_build_method.py | 64 +++++ .../BuildAutomation/manifest_update.py | 159 +++++++++--- .../BuildAutomation/print_manifest_info.py | 76 ++++++ .../resolve_file_references.py | 227 +++++++++++++++++ .../BuildAutomation/setup_build_scripts.py | 37 +++ .../BuildAutomation/validate_params.py | 74 +++--- 12 files changed, 917 insertions(+), 478 deletions(-) delete mode 100644 Tools/scripts/BuildAutomation/FileCopy.py create mode 100644 Tools/scripts/BuildAutomation/get_build_method.py create mode 100644 Tools/scripts/BuildAutomation/print_manifest_info.py create mode 100644 Tools/scripts/BuildAutomation/resolve_file_references.py create mode 100644 Tools/scripts/BuildAutomation/setup_build_scripts.py diff --git a/.yamato/project-builders/builder.metafile b/.yamato/project-builders/builder.metafile index b261d28dea..83ada60807 100644 --- a/.yamato/project-builders/builder.metafile +++ b/.yamato/project-builders/builder.metafile @@ -1,6 +1,19 @@ # https://internaldocs.unity.com/yamato_continuous_integration/usage/templating/ +# The list below contains an easily extendable list of projects to build and test against our latest package changes +# The parameters specify +# - GithubRepo --> project repo location to clone from. +# - defaultBranch --> default sample branch to use (you can modify it via job variable but that should suggest what's the default repo branch). Remember that package is being used from branch that you trigger the job on. +# - manifestPath --> path to manifest.json file in the sample repo so we can replace package entry. +# - projectPath --> path to the project root folder. Depends on projects sometimes it's the root of the repo, sometimes it's a subfolder. +# - localPackageName --> name of the package we want to replace in the manifest.json (TODO: it should be a list to support multiple packages in future) +# - localPackagePath --> path to the local package inside our repo which we want to use to replace the package entry in manifest.json +# - remove --> (optional) relative folder path to remove from the cloned repo root (e.g., "Packages/"). This prevents Unity from using local packages that should come from registry instead. This is the case only for DOTS related projects +# - minUnityVersion --> minimal Unity version that the samples supports. Used as a suggestion when triggering the build as to which Unity version to use. -NetcodeProjects: +# Constants +ClonedProjectRoot: C:/ClonedProject + +BuildProjects: # 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) # The parameters specify repo location, default branch to use (since you can modify it via job variable), path to manifest.json file so we can replace package entry and path to the project root folder since it differs between projects # Note that for BossRoom 'main' branch supports NGOv1.X and 'develop' branch supports NGOv2.X @@ -10,13 +23,22 @@ NetcodeProjects: defaultBranch: ngo-playtest-update manifestPath: Packages/manifest.json projectPath: '.' + localPackageName: com.unity.netcode.gameobjects + localPackagePath: Packages/com.unity.netcode.gameobjects + minUnityVersion: 6000.0.52f1 Asteroids: GithubRepo: "https://github.cds.internal.unity3d.com/unity/Asteroids-CMB-NGO-Sample.git" defaultBranch: main manifestPath: Packages/manifest.json projectPath: '.' + localPackageName: com.unity.netcode.gameobjects + localPackagePath: Packages/com.unity.netcode.gameobjects + minUnityVersion: 6000.2.9f1 SocialHub: GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize.git" defaultBranch: main manifestPath: Basic/DistributedAuthoritySocialHub/Packages/manifest.json projectPath: 'Basic/DistributedAuthoritySocialHub' + localPackageName: com.unity.netcode.gameobjects + localPackagePath: Packages/com.unity.netcode.gameobjects + minUnityVersion: 6000.0.24f1 diff --git a/.yamato/project-builders/project-builders.yml b/.yamato/project-builders/project-builders.yml index 327598d9ea..ff75e6a629 100644 --- a/.yamato/project-builders/project-builders.yml +++ b/.yamato/project-builders/project-builders.yml @@ -18,80 +18,68 @@ # 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] }} + {% for buildProject in BuildProjects -%} +build_{{ buildProject[0] }}_project: + name: {{ buildProject[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 - SAMPLE_BRANCH: {{ netcodeProject[1].defaultBranch }} - commands: - # Validate inputs passed via Yamato variables - - python Tools/scripts/BuildAutomation/validate_params.py - - echo Building {{ netcodeProject[0] }} project from branch %SAMPLE_BRANCH% with Unity version of %UNITY_VERSION%, Scripting backend %SCRIPTING_BACKEND_IL2CPP_MONO%, Burst %BURST_ON_OFF% for platform %PLATFORM_WIN64_MAC_ANDROID% +variables: + UNITY_VERSION: {{ buildProject[1].minUnityVersion }} + SCRIPTING_BACKEND_IL2CPP_MONO: il2cpp + BURST_ON_OFF: on + PLATFORM_WIN64_MAC_ANDROID: win64 + SAMPLE_BRANCH: {{ buildProject[1].defaultBranch }} +commands: + - echo Building {{ buildProject[0] }} project from branch %SAMPLE_BRANCH% with Unity version of %UNITY_VERSION%, Scripting backend %SCRIPTING_BACKEND_IL2CPP_MONO%, Burst %BURST_ON_OFF% for platform %PLATFORM_WIN64_MAC_ANDROID% + + # Validate inputs passed via Yamato variables + - python Tools/CI/scripts/BuildAutomation/validate_params.py + + # Clone the external project repository into a specific directory. Notice that branch is also specified. + - git clone --single-branch --branch %SAMPLE_BRANCH% {{ buildProject[1].GithubRepo }} {{ ClonedProjectRoot }} + + # Replace file: references in the manifest with latest released versions from Unity Package Vision API. + # This replaces the need for maintaining separate release-manifest.json files in each project. + - python Tools/CI/scripts/BuildAutomation/resolve_file_references.py --manifest-path C:/ClonedProject/{{ buildProject[1].manifestPath }} + + # 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 {{ ClonedProjectRoot }}/{{ buildProject[1].manifestPath }} --package-name {{ buildProject[1].localPackageName }} --local-package-path %YAMATO_SOURCE_DIR%/{{ buildProject[1].localPackagePath }}{% if buildProject[1].remove %} --remove-folder {{ buildProject[1].remove }}{% endif %} --cloned-project-root {{ ClonedProjectRoot }} + + # 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 {{ ClonedProjectRoot }}/{{ buildProject[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 {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}) + ELSE (python Tools/CI/scripts/BuildAutomation/disable-enable-burst.py --disable-burst --project-path {{ ClonedProjectRoot }}/{{ buildProject[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 requires proper assembly definition in order for those scripts to compile properly. + - python Tools/CI/scripts/BuildAutomation/setup_build_scripts.py --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }} --source-dir %YAMATO_SOURCE_DIR% + + # Build the project using Unity Editor. This calls the appropriate static BuilderScripts method. + - python Tools/CI/scripts/BuildAutomation/get_build_method.py --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }} + + # Print manifest dependencies after Unity Editor has processed the manifest to verify package overrides. + - python Tools/CI/scripts/BuildAutomation/print_manifest_info.py --manifest-path {{ ClonedProjectRoot }}/{{ buildProject[1].manifestPath }} + + # Copy build artifacts to source directory for Yamato artifact collection. + # TODO: This can be omitted if building directly in YAMATO_SOURCE_DIR instead of ClonedProjectRoot + - python -c "import os, shutil; os.makedirs('./build', exist_ok=True); shutil.copytree('{{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}/build', './build', dirs_exist_ok=True)" - # Clone the external project repository into a specific directory. Notice that branch is also specified. - - git clone --single-branch --branch %SAMPLE_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/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/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/scripts/BuildAutomation/disable-enable-burst.py --enable-burst --project-path C:/ClonedProject/{{ netcodeProject[1].projectPath }}) - ELSE (python Tools/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/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 /I "%PLATFORM_WIN64_MAC_ANDROID%"=="win64" ( - IF /I "%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 /I "%PLATFORM_WIN64_MAC_ANDROID%"=="mac" ( - IF /I "%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 /I "%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 +artifacts: + logs: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + players: + paths: + - build/**/* + {% endfor -%} diff --git a/Tools/scripts/BuildAutomation/BuilderScripts.cs b/Tools/scripts/BuildAutomation/BuilderScripts.cs index 159dd619e9..b1b15eec31 100644 --- a/Tools/scripts/BuildAutomation/BuilderScripts.cs +++ b/Tools/scripts/BuildAutomation/BuilderScripts.cs @@ -1,181 +1,119 @@ +using System; using System.Collections.Generic; +using System.Linq; 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. +// This script automates the build process for different platforms. +// Can be triggered from scripts for the given configuration. +// Note: It's possible to have these as buttons in the editor (see example in comments). +// Ideally we would pass scripting backend and platform as parameters, but calling via -executeMethod +// requires static methods and parameters aren't passed in a usable way. // TODO: add iOS support public class BuilderScripts : MonoBehaviour { - [MenuItem("Tools/Builder/Build Development Windows Il2cpp")] - static void BuildWinIl2cpp() + static string[] GetEnabledScenes() { - // 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); + return EditorBuildSettings.scenes + .Where(s => s.enabled) + .Select(s => { Debug.Log($"Adding scene to build: {s.path}"); return s.path; }) + .ToArray(); } - [MenuItem("Tools/Builder/Build Development Windows Mono")] - static void BuildWinMono() + static void BuildPlayer(BuildConfig config) { - 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(); + PlayerSettings.SetScriptingBackend(config.namedBuildTarget, config.scriptingBackend); - List scenesToAdd = new List(); - foreach (var scene in EditorBuildSettings.scenes) + if (config.graphicsAPIs != null && config.graphicsAPIs.Length > 0) { - if (scene.enabled) - { - Debug.Log("Adding scene to build: " + scene.path); - scenesToAdd.Add(scene.path); - } + // We need to specify the graphics API for Android builds to ensure proper shader compilation. Vulkan is recommended for modern devices. + PlayerSettings.SetUseDefaultGraphicsAPIs(config.buildTarget, false); + PlayerSettings.SetGraphicsAPIs(config.buildTarget, config.graphicsAPIs); } - var buildPlayerOptions = new BuildPlayerOptions - { - locationPathName = "./build/Windows_Mono/PlaytestBuild.exe", - target = BuildTarget.StandaloneWindows64, - options = BuildOptions.Development, - scenes = scenesToAdd.ToArray() - }; - - BuildPipeline.BuildPlayer(buildPlayerOptions); - } + if (config.applicationId != null) + // 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.SetApplicationIdentifier(config.namedBuildTarget, config.applicationId); - [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. + if (config.architecture.HasValue) + // 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. + PlayerSettings.SetArchitecture(config.namedBuildTarget, config.architecture.Value); AssetDatabase.SaveAssets(); - List scenesToAdd = new List(); - foreach (var scene in EditorBuildSettings.scenes) + var buildOptions = new BuildPlayerOptions { - 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, + locationPathName = config.outputPath, + target = config.buildTarget, options = BuildOptions.Development, - scenes = scenesToAdd.ToArray() + scenes = GetEnabledScenes() }; - BuildPipeline.BuildPlayer(buildPlayerOptions); + BuildPipeline.BuildPlayer(buildOptions); } - [MenuItem("Tools/Builder/Build Development Mac Il2cpp")] - static void BuildMacIl2cpp() + struct BuildConfig { - 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); + public NamedBuildTarget namedBuildTarget; + public ScriptingImplementation scriptingBackend; + public BuildTarget buildTarget; + public GraphicsDeviceType[] graphicsAPIs; + public string outputPath; + public string applicationId; + public int? architecture; } - [MenuItem("Tools/Builder/Build Development Android Il2cpp")] - static void BuildAndroidIl2cpp() + [MenuItem("Tools/Builder/Build Development Windows Il2cpp")] + static void BuildWinIl2cpp() => BuildPlayer(new BuildConfig { - 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 + namedBuildTarget = NamedBuildTarget.Standalone, + scriptingBackend = ScriptingImplementation.IL2CPP, + buildTarget = BuildTarget.StandaloneWindows64, + graphicsAPIs = new[] { GraphicsDeviceType.Vulkan, GraphicsDeviceType.Direct3D11, GraphicsDeviceType.Direct3D12 }, + outputPath = "./build/Windows_Il2cpp/PlaytestBuild.exe" + }); - AssetDatabase.SaveAssets(); + [MenuItem("Tools/Builder/Build Development Windows Mono")] + static void BuildWinMono() => BuildPlayer(new BuildConfig + { + namedBuildTarget = NamedBuildTarget.Standalone, + scriptingBackend = ScriptingImplementation.Mono2x, + buildTarget = BuildTarget.StandaloneWindows64, + graphicsAPIs = new[] { GraphicsDeviceType.Vulkan, GraphicsDeviceType.Direct3D11, GraphicsDeviceType.Direct3D12 }, + outputPath = "./build/Windows_Mono/PlaytestBuild.exe" + }); - 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); - } - } + [MenuItem("Tools/Builder/Build Development Mac Mono")] + static void BuildMacMono() => BuildPlayer(new BuildConfig + { + namedBuildTarget = NamedBuildTarget.Standalone, + scriptingBackend = ScriptingImplementation.Mono2x, + buildTarget = BuildTarget.StandaloneOSX, + graphicsAPIs = new[] { GraphicsDeviceType.Metal }, + outputPath = "./build/macOS_Mono/PlaytestBuild.app" + }); - var buildPlayerOptions = new BuildPlayerOptions - { - locationPathName = "./build/Android_Il2cpp_Vulkan/PlaytestBuild.apk", - target = BuildTarget.Android, - options = BuildOptions.Development, - scenes = scenesToAdd.ToArray() - }; + [MenuItem("Tools/Builder/Build Development Mac Il2cpp")] + static void BuildMacIl2cpp() => BuildPlayer(new BuildConfig + { + namedBuildTarget = NamedBuildTarget.Standalone, + scriptingBackend = ScriptingImplementation.IL2CPP, + buildTarget = BuildTarget.StandaloneOSX, + graphicsAPIs = new[] { GraphicsDeviceType.Metal }, + outputPath = "./build/macOS_Il2cpp/PlaytestBuild.app" + }); - BuildPipeline.BuildPlayer(buildPlayerOptions); - } + [MenuItem("Tools/Builder/Build Development Android Il2cpp")] + static void BuildAndroidIl2cpp() => BuildPlayer(new BuildConfig + { + namedBuildTarget = NamedBuildTarget.Android, + scriptingBackend = ScriptingImplementation.IL2CPP, + buildTarget = BuildTarget.Android, + graphicsAPIs = new[] { GraphicsDeviceType.Vulkan }, + outputPath = "./build/Android_Il2cpp_Vulkan/PlaytestBuild.apk", + applicationId = "com.UnityTestRunner.UnityTestRunner", + architecture = 1 // ARM64 + }); } diff --git a/Tools/scripts/BuildAutomation/FileCopy.py b/Tools/scripts/BuildAutomation/FileCopy.py deleted file mode 100644 index 5204fbb6b8..0000000000 --- a/Tools/scripts/BuildAutomation/FileCopy.py +++ /dev/null @@ -1,59 +0,0 @@ -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/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef' - source_script = 'Tools/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/scripts/BuildAutomation/connect_services.py b/Tools/scripts/BuildAutomation/connect_services.py index db11eeff13..401200442c 100644 --- a/Tools/scripts/BuildAutomation/connect_services.py +++ b/Tools/scripts/BuildAutomation/connect_services.py @@ -1,46 +1,138 @@ -# 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 +""" +Modifies ProjectSettings.asset to connect the Unity project to services like Relay. +Updates cloudProjectId, organizationId, and projectName fields. +Default values are provided but can be overridden via command-line. +If no override is specified, checks if services are already connected before applying defaults. +""" -# 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.') + parser = argparse.ArgumentParser(description='Update ProjectSettings.asset to connect to services.') + parser.add_argument('--project-settings-path', required=True, + help='Absolute path to ProjectSettings.asset file.') + parser.add_argument('--cloud-project-ID', default=None, + help='Cloud project ID to connect to. If provided, --organization-ID and --project-name must also be provided.') + parser.add_argument('--organization-ID', default=None, + help='Organization ID for the cloud project. If provided, --cloud-project-ID and --project-name must also be provided.') + parser.add_argument('--project-name', default=None, + help='Project name to connect to. If provided, --cloud-project-ID and --organization-ID must also be provided.') args = parser.parse_args() + # Validate that if any of the three service arguments is provided, all three must be provided + service_args = [args.cloud_project_ID, args.organization_ID, args.project_name] + provided_count = sum(1 for arg in service_args if arg is not None) + + if 0 < provided_count < 3: + parser.error('If any of --cloud-project-ID, --organization-ID, or --project-name is provided, all three must be provided.') + + return args + +def get_current_value(content, field_name): + """Extract the current value of a field from the ProjectSettings.asset content.""" + # Match field_name: followed by optional spaces/tabs (NOT newlines) and value on the same line + # Use ^ and $ anchors with MULTILINE flag to match line by line + # Use [ \t]* instead of \s* to avoid matching newlines (which would capture next line's content) + pattern = rf"^\s*{field_name}:[ \t]*(.*)$" + match = re.search(pattern, content, re.MULTILINE) + if match: + value = match.group(1).strip() + # Return value if it's non-empty and not a placeholder/empty indicator + if value and value not in ['0', '""', "''", '{}']: + return value + return None + def main(): - """ - Modifies ProjectSettings.asset in order to connect the project to Services - """ - parse_args() + args = parse_args() - with open(args.project_settings_path, 'r') as f: + # Default values + DEFAULT_CLOUD_PROJECT_ID = "ec34c3ba-b009-4677-8e50-d2772e1b87dc" + DEFAULT_ORGANIZATION_ID = "ericktest" #3573857280227 + DEFAULT_PROJECT_NAME = "cmb" + + # Read the current file content + with open(args.project_settings_path, 'r', encoding='utf-8') 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) + # Check current values in the file + current_cloud_id = get_current_value(content, "cloudProjectId") + current_org_id = get_current_value(content, "organizationId") + current_project_name = get_current_value(content, "projectName") + current_cloud_enabled = get_current_value(content, "cloudEnabled") + + # Print original values + print("\n" + "="*80) + print("UNITY SERVICES CONFIGURATION") + print("="*80) + print(f"File: {args.project_settings_path}") + print("\nOriginal values in ProjectSettings.asset:") + print(f" cloudProjectId: {current_cloud_id if current_cloud_id else ''}") + print(f" organizationId: {current_org_id if current_org_id else ''}") + print(f" projectName: {current_project_name if current_project_name else ''}") + print(f" cloudEnabled: {current_cloud_enabled if current_cloud_enabled else ''}") + # Check if all three fields are already set (have non-empty values) + all_fields_set = (current_cloud_id is not None and + current_org_id is not None and + current_project_name is not None) + + # Priority order: + # 1) If script params provided (all 3 required) -> use them + # 2) If ALL fields already have values in file -> keep them unchanged + # 3) Otherwise (no params AND at least 1 field empty) -> apply defaults + + if args.cloud_project_ID is not None: + # Priority 1: Command-line arguments provided + cloud_project_id = args.cloud_project_ID + organization_id = args.organization_ID + project_name = args.project_name + source = "command-line arguments" + print("\n[Priority 1] Using command-line arguments (all three provided)") + elif all_fields_set: + # Priority 2: All fields already configured - keep existing values + cloud_project_id = current_cloud_id + organization_id = current_org_id + project_name = current_project_name + source = "existing values (already configured)" + print("\n[Priority 2] Keeping existing values (all fields already have values)") + else: + # Priority 3: No params and at least one field is empty - apply defaults + cloud_project_id = DEFAULT_CLOUD_PROJECT_ID + organization_id = DEFAULT_ORGANIZATION_ID + project_name = DEFAULT_PROJECT_NAME + source = "default values" + print("\n[Priority 3] Applying defaults (no params provided and at least one field was empty)") + if current_cloud_id is None: + print(" - cloudProjectId was empty") + if current_org_id is None: + print(" - organizationId was empty") + if current_project_name is None: + print(" - projectName was empty") + + # Build replacements dictionary + replacements = { + r"cloudProjectId:.*": f"cloudProjectId: {cloud_project_id}", + r"organizationId:.*": f"organizationId: {organization_id}", + r"projectName:.*": f"projectName: {project_name}", + r"cloudEnabled:.*": "cloudEnabled: 1" + } + + # Apply replacements + for pattern, replacement in replacements.items(): + content = re.sub(pattern, replacement, content) + + # Write back to file with open(args.project_settings_path, 'w', encoding='UTF-8', newline='\n') 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}") + print("\nFinal values written:") + print(f" cloudProjectId: {cloud_project_id}") + print(f" organizationId: {organization_id}") + print(f" projectName: {project_name}") + print(" cloudEnabled: 1") + print(f"\nSource: {source}") + print("="*80 + "\n") if __name__ == "__main__": main() - - diff --git a/Tools/scripts/BuildAutomation/disable-enable-burst.py b/Tools/scripts/BuildAutomation/disable-enable-burst.py index d9cd0b29e6..56d133c39a 100644 --- a/Tools/scripts/BuildAutomation/disable-enable-burst.py +++ b/Tools/scripts/BuildAutomation/disable-enable-burst.py @@ -1,120 +1,95 @@ -# 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 +""" +Script to enable or disable Burst compilation for Unity projects. +Modifies BurstAotSettings files located under the ProjectSettings folder. + +Note: +- Burst package must be installed. +- Platform is specified via PLATFORM_WIN64_MAC_ANDROID env var. +- The script replaces the settings file entirely. +""" 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.") +PLATFORM_MAP = { + 'win64': 'StandaloneWindows', + 'mac': 'StandaloneOSX', + 'android': 'Android' +} + +DEFAULT_BURST_CONFIG = { + 'Version': 4, + 'EnableBurstCompilation': True, + 'EnableOptimisations': True, + 'EnableSafetyChecks': False, + 'EnableDebugInAllBuilds': False, + 'CpuMinTargetX32': 0, + 'CpuMaxTargetX32': 0, + 'CpuMinTargetX64': 0, + 'CpuMaxTargetX64': 0, + 'CpuTargetsX32': 6, + 'CpuTargetsX64': 72, + 'OptimizeFor': 0 +} - # Add the mutually exclusive group for --disable-burst and --enable-burst +def parse_args(): + parser = argparse.ArgumentParser(description="Enable or disable Burst compilation") 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.') + parser.add_argument('--project-path', required=True, help='Project location') + return parser.parse_args() - # 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', encoding='UTF-8', newline='\n') 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').lower() - - 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: + platform_key = os.environ.get('PLATFORM_WIN64_MAC_ANDROID', '').lower() + target = PLATFORM_MAP.get(platform_key) + if not target: + raise ValueError(f"Unsupported platform: {platform_key}. Supported: {list(PLATFORM_MAP.keys())}") + return target + +def create_config(settings_path, target): + config_path = os.path.join(settings_path, f"BurstAotSettings_{target}.json") + with open(config_path, 'w', encoding='UTF-8', newline='\n') as f: + json.dump({'MonoBehaviour': DEFAULT_BURST_CONFIG}, f) + return config_path + +def get_or_create_burst_config(project_path): + settings_path = os.path.join(project_path, 'ProjectSettings') + os.makedirs(settings_path, exist_ok=True) + + target = resolve_target() + prefix = f"BurstAotSettings_{target}" + configs = [os.path.join(settings_path, f) for f in os.listdir(settings_path) + if f.startswith(prefix) and f.endswith('.json')] + + return configs if configs else [create_config(settings_path, target)] + +def set_burst_status(config_file, enabled): + with open(config_file, 'r', encoding='utf-8') as f: config = json.load(f) - assert config is not None, 'AOT settings not found; did the burst-enabled build finish successfully?' + if not config or 'MonoBehaviour' not in config: + raise AssertionError('AOT settings not found') - config['MonoBehaviour']['EnableBurstCompilation'] = status + config['MonoBehaviour']['EnableBurstCompilation'] = enabled with open(config_file, 'w', encoding='UTF-8', newline='\n') as f: - json.dump(config, f) - + json.dump(config, f, indent=2) def main(): - parse_args() - config_names = get_or_create_burst_AOT_config() + args = parse_args() + configs = get_or_create_burst_config(args.project_path) + platform = os.environ.get('PLATFORM_WIN64_MAC_ANDROID', 'unknown').lower() - platform_key = os.environ.get('PLATFORM_WIN64_MAC_ANDROID').lower() 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)) + print(f"Burst compilation script: Target platform is {platform}") + status = args.enable_burst + status_text = "ENABLED" if status else "DISABLED" + print(f'BURST COMPILATION: {status_text}') + for config_file in configs: + set_burst_status(config_file, status) if __name__ == '__main__': main() diff --git a/Tools/scripts/BuildAutomation/get_build_method.py b/Tools/scripts/BuildAutomation/get_build_method.py new file mode 100644 index 0000000000..18fe0e2d9b --- /dev/null +++ b/Tools/scripts/BuildAutomation/get_build_method.py @@ -0,0 +1,64 @@ +""" +Executes Unity build command based on platform and scripting backend. +Determines the appropriate BuilderScripts method and runs Unity with the correct parameters. +""" + +import os +import sys +import subprocess +import argparse + +BUILD_CONFIGS = { + ('win64', 'il2cpp'): { + 'method': 'BuilderScripts.BuildWinIl2cpp', + 'buildTarget': 'win64' + }, + ('win64', 'mono'): { + 'method': 'BuilderScripts.BuildWinMono', + 'buildTarget': 'win64' + }, + ('mac', 'il2cpp'): { + 'method': 'BuilderScripts.BuildMacIl2cpp', + 'buildTarget': 'osx' + }, + ('mac', 'mono'): { + 'method': 'BuilderScripts.BuildMacMono', + 'buildTarget': 'osx' + }, + ('android', 'il2cpp'): { + 'method': 'BuilderScripts.BuildAndroidIl2cpp', + 'buildTarget': 'android' + } +} + +def execute_unity_build(project_path, unity_path='C:/TestingEditor/Unity.exe'): + platform = os.environ.get('PLATFORM_WIN64_MAC_ANDROID', '').lower() + backend = os.environ.get('SCRIPTING_BACKEND_IL2CPP_MONO', '').lower() + + config = BUILD_CONFIGS.get((platform, backend)) + if not config: + print(f"ERROR: Invalid combination: platform={platform}, backend={backend}", file=sys.stderr) + sys.exit(1) + + cmd = [ + unity_path, + '-projectPath', project_path, + '-buildTarget', config['buildTarget'], + '-executeMethod', config['method'], + '-batchmode', + '-logFile', './artifacts/UnityLog.txt', + '-automated', + '-crash-report-folder', './artifacts/CrashArtifacts', + '-quit' + ] + + print(f"Executing: {' '.join(cmd)}") + result = subprocess.run(cmd, check=False) + sys.exit(result.returncode) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Execute Unity build command.') + parser.add_argument('--project-path', required=True, help='Path to Unity project.') + parser.add_argument('--unity-path', default='C:/TestingEditor/Unity.exe', help='Path to Unity executable.') + args = parser.parse_args() + execute_unity_build(args.project_path, args.unity_path) diff --git a/Tools/scripts/BuildAutomation/manifest_update.py b/Tools/scripts/BuildAutomation/manifest_update.py index 0ad8913b8e..31e67e4d24 100644 --- a/Tools/scripts/BuildAutomation/manifest_update.py +++ b/Tools/scripts/BuildAutomation/manifest_update.py @@ -1,62 +1,147 @@ -# 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?) +""" +Replaces a package in project manifest.json with a local version. +Used when triggering builds from a branch (e.g., release/1.2.3) to use the package from that branch. + +Note: This script assumes the package already has an entry in the manifest. +""" import json import argparse import os +import sys +import shutil 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() + parser = argparse.ArgumentParser(description='Update Unity project manifest to use local package version.') + parser.add_argument('--manifest-path', required=True, + help='Absolute path to project manifest.json file.') + parser.add_argument('--package-name', required=True, + help="Name of the package to modify in the manifest.") + parser.add_argument('--local-package-path', required=True, + help='Absolute path to local package source directory.') + parser.add_argument('--remove-folder', default='', + help='Relative folder path to remove from cloned repo root (e.g., "Packages"). ' + 'Path is resolved relative to the cloned project root.') + parser.add_argument('--cloned-project-root', required=True, + help='Absolute path to the cloned project root (e.g., C:/ClonedProject). ' + 'If not provided, will be derived from manifest-path by finding the repo root.') + return parser.parse_args() -def main(): + +def update_manifest(manifest_path, package_name, local_package_path): + with open(manifest_path, 'r', encoding='utf-8') as f: + manifest_data = json.load(f) + + # Get old value for comparison + old_value = manifest_data.get("dependencies", {}).get(package_name, "") + + local_path_normalized = local_package_path.replace(os.sep, '/') + manifest_data.setdefault("dependencies", {})[package_name] = f"file:{local_path_normalized}" + + with open(manifest_path, 'w', encoding='utf-8', newline='\n') as f: + json.dump(manifest_data, f, indent=4) + + print("\n" + "="*80) + print("PACKAGE SUBSTITUTION") + print("="*80) + print(f"Package: {package_name}") + print(f" Old value: {old_value}") + print(f" New value: file:{local_path_normalized}") + print(f"[OK] Successfully updated manifest at '{manifest_path}'") + print("="*80 + "\n") + +def print_manifest_dependencies(manifest_path): """ - Updates a project's manifest.json to use package under passed local-package-path, - and then prints the version of that local package. + Prints all dependencies from the manifest.json file. """ - parse_args() - - # Update the target project's manifest try: - with open(args.manifest_path, 'r') as f: + with open(manifest_path, 'r', encoding='utf-8') 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}" + dependencies = manifest_data.get("dependencies", {}) + if dependencies: + print("\n" + "="*80) + print("FINAL MANIFEST CONTENT") + print("="*80) + for package_name, package_version in sorted(dependencies.items()): + print(f" {package_name}: {package_version}") + print("="*80 + "\n") + else: + print("\nNo dependencies found in manifest.json\n") + except Exception as e: + print(f"Warning: Could not read manifest dependencies: {e}") - with open(args.manifest_path, 'w', encoding='UTF-8', newline='\n') 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) +def remove_folder(folder_path, cloned_project_root): + """ + Removes a folder from the cloned project root. + """ + if not folder_path: + return - # --- 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') + if not cloned_project_root: + print("Warning: Cannot determine cloned project root, skipping folder removal.") + return + + if not os.path.isdir(cloned_project_root): + print(f"Warning: Cloned project root not found: {cloned_project_root}, skipping folder removal.") + return + + # Resolve the folder path relative to cloned project root + folder_path = folder_path.strip().strip('/').strip('\\') + absolute_folder_path = os.path.join(cloned_project_root, folder_path) - with open(local_package_json_path, 'r') as f: - local_package_data = json.load(f) + print("\n" + "="*80) + print("REMOVING FOLDER FROM CLONED REPOSITORY") + print("="*80) + print(f"Cloned project root: {cloned_project_root}") + print(f"Folder to remove: {folder_path}") + print(f"Absolute path: {absolute_folder_path}") - # Extract the version, providing a default if not found - local_package_version = local_package_data.get('version', 'N/A') + if os.path.exists(absolute_folder_path) and os.path.isdir(absolute_folder_path): + try: + shutil.rmtree(absolute_folder_path) + print(f"Successfully removed folder: {absolute_folder_path}") + except Exception as e: + print(f"ERROR: Failed to remove folder {absolute_folder_path}: {e}", file=sys.stderr) + raise + else: + print(f" - Folder not found (skipping): {absolute_folder_path}") - print(f"--> Verified local '{args.package-name}' version is: {local_package_version}") + print("="*80 + "\n") +def get_package_version(package_path): + package_json_path = os.path.join(package_path, 'package.json') + try: + with open(package_json_path, 'r', encoding='utf-8') as f: + return json.load(f).get('version', 'N/A') except FileNotFoundError: - print(f"Warning: Could not find package.json at '{local_package_json_path}'") + print(f"Warning: Could not find package.json at '{package_json_path}'") + return None except Exception as e: print(f"Error reading local package version: {e}") + return None + +def main(): + args = parse_args() + + try: + # First, remove folder that should not override registry packages + if args.remove_folder: + remove_folder(args.remove_folder, args.cloned_project_root) + + # Then proceed with package substitution + update_manifest(args.manifest_path, args.package_name, args.local_package_path) + + # Print final manifest content + print_manifest_dependencies(args.manifest_path) + + version = get_package_version(args.local_package_path) + if version: + print(f"[OK] Verified local '{args.package_name}' version is: {version}\n") + except Exception as e: + print(f"Error updating manifest: {e}", file=sys.stderr) + sys.exit(1) if __name__ == "__main__": main() diff --git a/Tools/scripts/BuildAutomation/print_manifest_info.py b/Tools/scripts/BuildAutomation/print_manifest_info.py new file mode 100644 index 0000000000..b79bb24d12 --- /dev/null +++ b/Tools/scripts/BuildAutomation/print_manifest_info.py @@ -0,0 +1,76 @@ +""" +Prints manifest.json dependencies information. +Used to verify package overrides after Unity Editor has processed the manifest. +""" + +import json +import argparse +import os +import sys + +def parse_args(): + parser = argparse.ArgumentParser(description='Print Unity project manifest dependencies.') + parser.add_argument('--manifest-path', required=True, + help='Absolute path to project manifest.json file.') + return parser.parse_args() + +def print_manifest_dependencies(manifest_path): + """ + Prints all dependencies from the manifest.json file in a readable format. + """ + if not os.path.exists(manifest_path): + print(f"ERROR: Manifest file not found at '{manifest_path}'", file=sys.stderr) + sys.exit(1) + + try: + with open(manifest_path, 'r', encoding='utf-8') as f: + manifest_data = json.load(f) + + dependencies = manifest_data.get("dependencies", {}) + + print("\n" + "="*80) + print("Project dependencies in manifest.json (after Unity Editor processing):") + print("="*80) + + if dependencies: + file_packages = [] + version_packages = [] + + for package_name, package_version in sorted(dependencies.items()): + if package_version.startswith("file:"): + file_packages.append((package_name, package_version)) + else: + version_packages.append((package_name, package_version)) + + if file_packages: + print("\nLocal file packages (override built-in packages):") + for package_name, package_version in file_packages: + print(f" {package_name}: {package_version}") + + if version_packages: + print("\nVersion-based packages:") + for package_name, package_version in version_packages: + print(f" {package_name}: {package_version}") + + print(f"\nTotal dependencies: {len(dependencies)}") + if file_packages: + print(f" - Local file packages: {len(file_packages)}") + print(f" - Version-based packages: {len(version_packages)}") + else: + print("\nNo dependencies found in manifest.json") + + print("="*80 + "\n") + + except json.JSONDecodeError as e: + print(f"ERROR: Invalid JSON in manifest file: {e}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"ERROR: Could not read manifest dependencies: {e}", file=sys.stderr) + sys.exit(1) + +def main(): + args = parse_args() + print_manifest_dependencies(args.manifest_path) + +if __name__ == "__main__": + main() diff --git a/Tools/scripts/BuildAutomation/resolve_file_references.py b/Tools/scripts/BuildAutomation/resolve_file_references.py new file mode 100644 index 0000000000..27d3923d79 --- /dev/null +++ b/Tools/scripts/BuildAutomation/resolve_file_references.py @@ -0,0 +1,227 @@ +""" +This script replaces 'file:' references in a Unity project's manifest.json with the latest +released versions from Unity's package registry while using Unity Package Vision API: +https://package-vision.prd.cds.internal.unity3d.com/PackageData/{package_name} + +Usage: + python resolve_file_references.py --manifest-path [--exclude ...] [--dry-run] +""" + +import json +import argparse +import sys +import urllib.request +import urllib.error +from typing import Optional + + +# Unity Package Vision API endpoint for dots-monorepo packages +PACKAGE_VISION_API = "https://package-vision.prd.cds.internal.unity3d.com/PackageData" + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Replace 'file:' references in a Unity manifest.json with the latest released versions." + ) + parser.add_argument( + "--manifest-path", + required=True, + help="The path to the project's manifest.json file." + ) + parser.add_argument( + "--exclude", + nargs="*", + default=[], + help="Package names to exclude from resolution (keep as file: references)." + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Print the changes without modifying the manifest file." + ) + return parser.parse_args() + + +def get_latest_version_from_api(package_name: str) -> Optional[str]: + """ + Fetches the latest released version of a package from the Unity Package Vision API. + + The API returns package data with a 'productionRegistryData' field containing version info. + We need to find the latest version by sorting semantically. + + Returns None if the package is not found or an error occurs. + """ + url = f"{PACKAGE_VISION_API}/{package_name}" + + try: + with urllib.request.urlopen(url, timeout=30) as response: + data = json.loads(response.read().decode("utf-8")) + + # The API returns data with productionRegistryData containing versions + production_data = data.get("productionRegistryData", {}) + versions = production_data.get("versions", {}) + + if not versions: + print(f"Warning: No versions found for package '{package_name}'") + return None + + # Get all version strings and find the latest one + version_list = list(versions.keys()) + latest_version = find_latest_version(version_list) + + return latest_version + + except urllib.error.HTTPError as e: + if e.code == 404: + print(f"Warning: Package '{package_name}' not found in Package Vision API (404)") + else: + print(f"Warning: HTTP error fetching '{package_name}': {e.code} {e.reason}") + return None + except urllib.error.URLError as e: + print(f"Warning: Network error fetching '{package_name}': {e.reason}") + return None + except json.JSONDecodeError as e: + print(f"Warning: Failed to parse JSON response for '{package_name}': {e}") + return None + except Exception as e: + print(f"Warning: Unexpected error fetching '{package_name}': {e}") + return None + + +def parse_version(version_str: str) -> tuple: + """ + Parse a semver version string into a tuple for comparison. + + Handles formats like: + - "1.4.2" -> (1, 4, 2, "", 0) + - "1.4.2-pre.1" -> (1, 4, 2, "pre", 1) + - "0.1.0-preview.25" -> (0, 1, 0, "preview", 25) + + Returns a tuple that can be compared for sorting. + Pre-release versions sort before release versions. + """ + # Split on hyphen to separate version from pre-release tag + parts = version_str.split("-", 1) + main_version = parts[0] + prerelease = parts[1] if len(parts) > 1 else "" + + try: + version_nums = tuple(int(x) for x in main_version.split(".")) + except ValueError: + # If parsing fails, treat as very old version + version_nums = (0, 0, 0) + + # Pad to 3 elements + version_nums = version_nums + (0,) * (3 - len(version_nums)) + + # Pre-release sorting: + # - Empty string (release) should sort after pre-release + # - We use a tuple: (is_release, prerelease_tag, prerelease_num) + if not prerelease: + prerelease_tuple = (1, "", 0) + else: + prerelease_parts = prerelease.rsplit(".", 1) + prerelease_tag = prerelease_parts[0] + try: + prerelease_num = int(prerelease_parts[1]) if len(prerelease_parts) > 1 else 0 + except ValueError: + prerelease_num = 0 + prerelease_tuple = (0, prerelease_tag, prerelease_num) + + return version_nums + prerelease_tuple + + +def find_latest_version(versions: list) -> str: + """ + Find the latest version from a list of version strings. + Prefers release versions over pre-release versions. + """ + if not versions: + return None + + # Sort versions by parsed version tuple (highest first) + sorted_versions = sorted(versions, key=parse_version, reverse=True) + return sorted_versions[0] + + +def load_manifest(manifest_path: str) -> dict: + """Load and parse the manifest.json file.""" + try: + with open(manifest_path, "r", encoding="utf-8") as f: + return json.load(f) + except FileNotFoundError: + print(f"Error: Manifest file not found at '{manifest_path}'") + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Failed to parse manifest JSON: {e}") + sys.exit(1) + + +def save_manifest(manifest_path: str, manifest_data: dict): + """Save manifest data to file.""" + try: + with open(manifest_path, "w", encoding="utf-8", newline="\n") as f: + json.dump(manifest_data, f, indent=4) + print(f"\nSuccessfully updated manifest at '{manifest_path}'") + except Exception as e: + print(f"\nError writing manifest: {e}") + sys.exit(1) + + +def process_dependencies(dependencies: dict, exclude: list) -> tuple: + """Process dependencies and resolve file: references. Returns (changes_made, errors).""" + changes_made = [] + errors = [] + + for package_name, version_ref in list(dependencies.items()): + if not version_ref.startswith("file:"): + continue + + if package_name in exclude: + print(f"Skipping excluded package: {package_name} (keeping as '{version_ref}')") + continue + + print(f"Fetching latest version for: {package_name}...") + latest_version = get_latest_version_from_api(package_name) + + if latest_version: + changes_made.append(f" {package_name}: '{version_ref}' -> '{latest_version}'") + dependencies[package_name] = latest_version + else: + errors.append(f" {package_name}: Could not resolve version (keeping as '{version_ref}')") + + return changes_made, errors + + +def print_results(changes_made: list, errors: list): + """Print the results of dependency resolution.""" + if changes_made: + print("\nChanges to be made:") + for change in changes_made: + print(change) + else: + print("\nNo changes needed.") + + if errors: + print("\nWarning: Failed to resolve the following packages:") + for error in errors: + print(error) + print("\nThese packages will keep their file: references.") + + +def main(): + args = parse_args() + manifest_data = load_manifest(args.manifest_path) + dependencies = manifest_data.get("dependencies", {}) + + changes_made, errors = process_dependencies(dependencies, args.exclude) + print_results(changes_made, errors) + + if args.dry_run: + print("\n[Dry run] No changes written to file.") + elif changes_made: + save_manifest(args.manifest_path, manifest_data) + + +if __name__ == "__main__": + main() diff --git a/Tools/scripts/BuildAutomation/setup_build_scripts.py b/Tools/scripts/BuildAutomation/setup_build_scripts.py new file mode 100644 index 0000000000..624f023207 --- /dev/null +++ b/Tools/scripts/BuildAutomation/setup_build_scripts.py @@ -0,0 +1,37 @@ +""" +Helper script to set up build scripts in the cloned project. +Handles directory creation and file copying for build automation. +""" + +import argparse +import os +import shutil + +def parse_args(): + parser = argparse.ArgumentParser(description='Set up build scripts in cloned project.') + parser.add_argument('--project-path', required=True, + help='Path to the cloned project root.') + parser.add_argument('--source-dir', required=True, + help='Source directory containing build automation scripts.') + return parser.parse_args() + +def main(): + args = parse_args() + target_dir = os.path.join(args.project_path, 'Assets', 'CIScripts', 'Editor') + source_dir = args.source_dir + + os.makedirs(target_dir, exist_ok=True) + + files_to_copy = [ + 'Unity.ProjectBuild.Editor.asmdef', + 'BuilderScripts.cs' + ] + + for filename in files_to_copy: + source_path = os.path.join(source_dir, 'Tools', 'CI', 'scripts', 'BuildAutomation', filename) + target_path = os.path.join(target_dir, filename) + shutil.copy(source_path, target_path) + print(f"Copied {filename} to {target_dir}") + +if __name__ == "__main__": + main() diff --git a/Tools/scripts/BuildAutomation/validate_params.py b/Tools/scripts/BuildAutomation/validate_params.py index 4c105836cc..160b56da2a 100644 --- a/Tools/scripts/BuildAutomation/validate_params.py +++ b/Tools/scripts/BuildAutomation/validate_params.py @@ -1,58 +1,52 @@ +""" +Validates Yamato environment variables based on predefined rules. +Checks individual variable values and invalid combinations (e.g., incompatible platform/backend). +Exits with non-zero status if validation fails, halting the build process. +""" + 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 +INVALID_COMBINATIONS = [ + ('mac', 'il2cpp', "Mac platform with il2cpp is not supported yet. " + "Windows build machines can only build Mac with mono."), + ('android', 'mono', "Android platform with mono is not supported. " + "Mobile builds require il2cpp scripting backend.") +] +def validate_variables(): + errors = [] for var_name, allowed_values in VALIDATION_RULES.items(): - actual_value = os.environ.get(var_name, '').lower() - allowed_values_lower = {v.lower() for v in allowed_values} - - if actual_value not in allowed_values_lower: - print( - f"ERROR: Invalid {var_name}: '{actual_value}'. " - f"Allowed values are: {list(allowed_values)}", - file=sys.stderr - ) - all_params_valid = False - + value = os.environ.get(var_name, '').lower() + if value not in {v.lower() for v in allowed_values}: + errors.append(f"ERROR: Invalid {var_name}: '{value}'. " + f"Allowed values: {list(allowed_values)}") + return errors + +def validate_combinations(): + errors = [] platform = os.environ.get('PLATFORM_WIN64_MAC_ANDROID', '').lower() scripting_backend = os.environ.get('SCRIPTING_BACKEND_IL2CPP_MONO', '').lower() - 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 - - if not all_params_valid: + for invalid_platform, invalid_backend, message in INVALID_COMBINATIONS: + if platform == invalid_platform and scripting_backend == invalid_backend: + errors.append(f"ERROR: Invalid Configuration: {message}") + return errors + +def main(): + errors = validate_variables() + validate_combinations() + + if errors: + for error in errors: + print(error, file=sys.stderr) print("\nOne or more parameters failed validation. Halting build.", file=sys.stderr) sys.exit(1) - print("All parameters are valid. Proceeding with the build.") - if __name__ == "__main__": - main() \ No newline at end of file + main() From d2ce2a62aed8b9246f26abd34f897f1af9e81979 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Fri, 30 Jan 2026 12:49:20 +0100 Subject: [PATCH 2/4] corrected indentation --- .yamato/project-builders/project-builders.yml | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/.yamato/project-builders/project-builders.yml b/.yamato/project-builders/project-builders.yml index ff75e6a629..d933b1b208 100644 --- a/.yamato/project-builders/project-builders.yml +++ b/.yamato/project-builders/project-builders.yml @@ -18,68 +18,68 @@ # 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 buildProject in BuildProjects -%} +{% for buildProject in BuildProjects -%} build_{{ buildProject[0] }}_project: name: {{ buildProject[0] }} agent: type: Unity::VM image: package-ci/win10:v4 flavor: b1.xlarge -variables: - UNITY_VERSION: {{ buildProject[1].minUnityVersion }} - SCRIPTING_BACKEND_IL2CPP_MONO: il2cpp - BURST_ON_OFF: on - PLATFORM_WIN64_MAC_ANDROID: win64 - SAMPLE_BRANCH: {{ buildProject[1].defaultBranch }} -commands: - - echo Building {{ buildProject[0] }} project from branch %SAMPLE_BRANCH% with Unity version of %UNITY_VERSION%, Scripting backend %SCRIPTING_BACKEND_IL2CPP_MONO%, Burst %BURST_ON_OFF% for platform %PLATFORM_WIN64_MAC_ANDROID% + variables: + UNITY_VERSION: {{ buildProject[1].minUnityVersion }} + SCRIPTING_BACKEND_IL2CPP_MONO: il2cpp + BURST_ON_OFF: on + PLATFORM_WIN64_MAC_ANDROID: win64 + SAMPLE_BRANCH: {{ buildProject[1].defaultBranch }} + commands: + - echo Building {{ buildProject[0] }} project from branch %SAMPLE_BRANCH% with Unity version of %UNITY_VERSION%, Scripting backend %SCRIPTING_BACKEND_IL2CPP_MONO%, Burst %BURST_ON_OFF% for platform %PLATFORM_WIN64_MAC_ANDROID% - # Validate inputs passed via Yamato variables - - python Tools/CI/scripts/BuildAutomation/validate_params.py + # Validate inputs passed via Yamato variables + - python Tools/CI/scripts/BuildAutomation/validate_params.py - # Clone the external project repository into a specific directory. Notice that branch is also specified. - - git clone --single-branch --branch %SAMPLE_BRANCH% {{ buildProject[1].GithubRepo }} {{ ClonedProjectRoot }} + # Clone the external project repository into a specific directory. Notice that branch is also specified. + - git clone --single-branch --branch %SAMPLE_BRANCH% {{ buildProject[1].GithubRepo }} {{ ClonedProjectRoot }} - # Replace file: references in the manifest with latest released versions from Unity Package Vision API. - # This replaces the need for maintaining separate release-manifest.json files in each project. - - python Tools/CI/scripts/BuildAutomation/resolve_file_references.py --manifest-path C:/ClonedProject/{{ buildProject[1].manifestPath }} + # Replace file: references in the manifest with latest released versions from Unity Package Vision API. + # This replaces the need for maintaining separate release-manifest.json files in each project. + - python Tools/CI/scripts/BuildAutomation/resolve_file_references.py --manifest-path C:/ClonedProject/{{ buildProject[1].manifestPath }} - # 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 {{ ClonedProjectRoot }}/{{ buildProject[1].manifestPath }} --package-name {{ buildProject[1].localPackageName }} --local-package-path %YAMATO_SOURCE_DIR%/{{ buildProject[1].localPackagePath }}{% if buildProject[1].remove %} --remove-folder {{ buildProject[1].remove }}{% endif %} --cloned-project-root {{ ClonedProjectRoot }} + # 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 {{ ClonedProjectRoot }}/{{ buildProject[1].manifestPath }} --package-name {{ buildProject[1].localPackageName }} --local-package-path %YAMATO_SOURCE_DIR%/{{ buildProject[1].localPackagePath }}{% if buildProject[1].remove %} --remove-folder {{ buildProject[1].remove }}{% endif %} --cloned-project-root {{ ClonedProjectRoot }} - # 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 {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}/ProjectSettings/ProjectSettings.asset + # 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 {{ ClonedProjectRoot }}/{{ buildProject[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 {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}) - ELSE (python Tools/CI/scripts/BuildAutomation/disable-enable-burst.py --disable-burst --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}) + # 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 {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}) + ELSE (python Tools/CI/scripts/BuildAutomation/disable-enable-burst.py --disable-burst --project-path {{ ClonedProjectRoot }}/{{ buildProject[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 + # 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 requires proper assembly definition in order for those scripts to compile properly. - - python Tools/CI/scripts/BuildAutomation/setup_build_scripts.py --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }} --source-dir %YAMATO_SOURCE_DIR% + # Add BuilderScript.cs to the project so we can modify and build the project using Unity Editor. + # This requires proper assembly definition in order for those scripts to compile properly. + - python Tools/CI/scripts/BuildAutomation/setup_build_scripts.py --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }} --source-dir %YAMATO_SOURCE_DIR% - # Build the project using Unity Editor. This calls the appropriate static BuilderScripts method. - - python Tools/CI/scripts/BuildAutomation/get_build_method.py --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }} + # Build the project using Unity Editor. This calls the appropriate static BuilderScripts method. + - python Tools/CI/scripts/BuildAutomation/get_build_method.py --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }} - # Print manifest dependencies after Unity Editor has processed the manifest to verify package overrides. - - python Tools/CI/scripts/BuildAutomation/print_manifest_info.py --manifest-path {{ ClonedProjectRoot }}/{{ buildProject[1].manifestPath }} + # Print manifest dependencies after Unity Editor has processed the manifest to verify package overrides. + - python Tools/CI/scripts/BuildAutomation/print_manifest_info.py --manifest-path {{ ClonedProjectRoot }}/{{ buildProject[1].manifestPath }} - # Copy build artifacts to source directory for Yamato artifact collection. - # TODO: This can be omitted if building directly in YAMATO_SOURCE_DIR instead of ClonedProjectRoot - - python -c "import os, shutil; os.makedirs('./build', exist_ok=True); shutil.copytree('{{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}/build', './build', dirs_exist_ok=True)" + # Copy build artifacts to source directory for Yamato artifact collection. + # TODO: This can be omitted if building directly in YAMATO_SOURCE_DIR instead of ClonedProjectRoot + - python -c "import os, shutil; os.makedirs('./build', exist_ok=True); shutil.copytree('{{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}/build', './build', dirs_exist_ok=True)" -artifacts: - logs: - paths: - - '*.log' - - '*.xml' - - artifacts/**/* - players: - paths: - - build/**/* - {% endfor -%} + artifacts: + logs: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + players: + paths: + - build/**/* +{% endfor -%} From 2a62d2f146270ffc994c6c1cdd59161d4120d1ef Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Fri, 30 Jan 2026 14:19:53 +0100 Subject: [PATCH 3/4] corrected path --- .yamato/project-builders/project-builders.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.yamato/project-builders/project-builders.yml b/.yamato/project-builders/project-builders.yml index d933b1b208..6cd59a998d 100644 --- a/.yamato/project-builders/project-builders.yml +++ b/.yamato/project-builders/project-builders.yml @@ -35,25 +35,25 @@ build_{{ buildProject[0] }}_project: - echo Building {{ buildProject[0] }} project from branch %SAMPLE_BRANCH% with Unity version of %UNITY_VERSION%, Scripting backend %SCRIPTING_BACKEND_IL2CPP_MONO%, Burst %BURST_ON_OFF% for platform %PLATFORM_WIN64_MAC_ANDROID% # Validate inputs passed via Yamato variables - - python Tools/CI/scripts/BuildAutomation/validate_params.py + - python Tools/scripts/BuildAutomation/validate_params.py # Clone the external project repository into a specific directory. Notice that branch is also specified. - git clone --single-branch --branch %SAMPLE_BRANCH% {{ buildProject[1].GithubRepo }} {{ ClonedProjectRoot }} # Replace file: references in the manifest with latest released versions from Unity Package Vision API. # This replaces the need for maintaining separate release-manifest.json files in each project. - - python Tools/CI/scripts/BuildAutomation/resolve_file_references.py --manifest-path C:/ClonedProject/{{ buildProject[1].manifestPath }} + - python Tools/scripts/BuildAutomation/resolve_file_references.py --manifest-path C:/ClonedProject/{{ buildProject[1].manifestPath }} # 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 {{ ClonedProjectRoot }}/{{ buildProject[1].manifestPath }} --package-name {{ buildProject[1].localPackageName }} --local-package-path %YAMATO_SOURCE_DIR%/{{ buildProject[1].localPackagePath }}{% if buildProject[1].remove %} --remove-folder {{ buildProject[1].remove }}{% endif %} --cloned-project-root {{ ClonedProjectRoot }} + - python Tools/scripts/BuildAutomation/manifest_update.py --manifest-path {{ ClonedProjectRoot }}/{{ buildProject[1].manifestPath }} --package-name {{ buildProject[1].localPackageName }} --local-package-path %YAMATO_SOURCE_DIR%/{{ buildProject[1].localPackagePath }}{% if buildProject[1].remove %} --remove-folder {{ buildProject[1].remove }}{% endif %} --cloned-project-root {{ ClonedProjectRoot }} # 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 {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}/ProjectSettings/ProjectSettings.asset + - python Tools/scripts/BuildAutomation/connect_services.py --project-settings-path {{ ClonedProjectRoot }}/{{ buildProject[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 {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}) - ELSE (python Tools/CI/scripts/BuildAutomation/disable-enable-burst.py --disable-burst --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}) + - IF "%BURST_ON_OFF%"=="on" (python Tools/scripts/BuildAutomation/disable-enable-burst.py --enable-burst --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }}) + ELSE (python Tools/scripts/BuildAutomation/disable-enable-burst.py --disable-burst --project-path {{ ClonedProjectRoot }}/{{ buildProject[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 @@ -61,13 +61,13 @@ build_{{ buildProject[0] }}_project: # Add BuilderScript.cs to the project so we can modify and build the project using Unity Editor. # This requires proper assembly definition in order for those scripts to compile properly. - - python Tools/CI/scripts/BuildAutomation/setup_build_scripts.py --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }} --source-dir %YAMATO_SOURCE_DIR% + - python Tools/scripts/BuildAutomation/setup_build_scripts.py --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }} --source-dir %YAMATO_SOURCE_DIR% # Build the project using Unity Editor. This calls the appropriate static BuilderScripts method. - - python Tools/CI/scripts/BuildAutomation/get_build_method.py --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }} + - python Tools/scripts/BuildAutomation/get_build_method.py --project-path {{ ClonedProjectRoot }}/{{ buildProject[1].projectPath }} # Print manifest dependencies after Unity Editor has processed the manifest to verify package overrides. - - python Tools/CI/scripts/BuildAutomation/print_manifest_info.py --manifest-path {{ ClonedProjectRoot }}/{{ buildProject[1].manifestPath }} + - python Tools/scripts/BuildAutomation/print_manifest_info.py --manifest-path {{ ClonedProjectRoot }}/{{ buildProject[1].manifestPath }} # Copy build artifacts to source directory for Yamato artifact collection. # TODO: This can be omitted if building directly in YAMATO_SOURCE_DIR instead of ClonedProjectRoot From 1cdfddc21606d1733576af3e2d1d9bc27638fb9b Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Fri, 30 Jan 2026 14:26:36 +0100 Subject: [PATCH 4/4] Added NGOtemplate and StarterKits projects --- .yamato/project-builders/builder.metafile | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.yamato/project-builders/builder.metafile b/.yamato/project-builders/builder.metafile index 83ada60807..7d7f9a721e 100644 --- a/.yamato/project-builders/builder.metafile +++ b/.yamato/project-builders/builder.metafile @@ -24,7 +24,7 @@ BuildProjects: manifestPath: Packages/manifest.json projectPath: '.' localPackageName: com.unity.netcode.gameobjects - localPackagePath: Packages/com.unity.netcode.gameobjects + localPackagePath: com.unity.netcode.gameobjects/ minUnityVersion: 6000.0.52f1 Asteroids: GithubRepo: "https://github.cds.internal.unity3d.com/unity/Asteroids-CMB-NGO-Sample.git" @@ -32,7 +32,7 @@ BuildProjects: manifestPath: Packages/manifest.json projectPath: '.' localPackageName: com.unity.netcode.gameobjects - localPackagePath: Packages/com.unity.netcode.gameobjects + localPackagePath: com.unity.netcode.gameobjects/ minUnityVersion: 6000.2.9f1 SocialHub: GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize.git" @@ -40,5 +40,21 @@ BuildProjects: manifestPath: Basic/DistributedAuthoritySocialHub/Packages/manifest.json projectPath: 'Basic/DistributedAuthoritySocialHub' localPackageName: com.unity.netcode.gameobjects - localPackagePath: Packages/com.unity.netcode.gameobjects + localPackagePath: com.unity.netcode.gameobjects/ minUnityVersion: 6000.0.24f1 + NGOtemplate: + GithubRepo: "https://github.cds.internal.unity3d.com/unity/com.unity.template.multiplayer.ngo.git" + defaultBranch: main-unity-6 + manifestPath: Packages/manifest.json + projectPath: '.' + localPackageName: com.unity.netcode.gameobjects + localPackagePath: com.unity.netcode.gameobjects/ + minUnityVersion: 6000.0.33f1 + StarterKits: + GithubRepo: "https://github.cds.internal.unity3d.com/unity/StarterKits-GameplayNGO.git" + defaultBranch: main + manifestPath: StarterKits/Packages/manifest.json + projectPath: StarterKits/ + localPackageName: com.unity.netcode.gameobjects + localPackagePath: com.unity.netcode.gameobjects/ + minUnityVersion: 6000.3.2f1 \ No newline at end of file