// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) 2023 Johannes Deml. All rights reserved. // // // Johannes Deml // public@deml.io // // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.PackageManager; using UnityEditor.PackageManager.Requests; using UnityEngine; namespace UnityBuilderAction { /// /// Editor script to automatically update all packages in the project to the newest version /// Can be used in continuous integration (CI) or through menu items in the editor /// Uses non-blocking tasks, so you can continue working until the packages recompile /// public static class UnityPackageScripts { private static int ProgressId; private static ListRequest ListRequest; #if UNITY_2021_2_OR_NEWER private static AddAndRemoveRequest AddAndRemoveRequest; #else private static AddRequest AddRequest; private static Queue PackagesToAdd; #endif private static bool IncludePrereleases; [MenuItem("Tools/Packages/Update to verified version")] public static void UpgradeAllPackagesToVerifiedVersion() { IncludePrereleases = false; StartPackageListUpdate(); } [MenuItem("Tools/Packages/Update to latest pre-release")] public static void UpgradeAllPackagesToLatestCompatibleVersion() { IncludePrereleases = true; StartPackageListUpdate(); } private static void StartPackageListUpdate() { #if UNITY_2020_1_OR_NEWER ProgressId = Progress.Start("Update Packages", $"Update all packages to latest version (Include Pre-Releases: {IncludePrereleases})"); #endif ListRequest = Client.List(); EditorApplication.update += OnWaitForPackageList; } private static void OnWaitForPackageList() { if (!ListRequest.IsCompleted) { #if UNITY_2020_1_OR_NEWER Progress.Report(ProgressId, 0.1f, "Update package list"); #endif return; } EditorApplication.update -= OnWaitForPackageList; switch (ListRequest.Status) { case StatusCode.Success: Console.WriteLine("Package list updated, checking for newer package versions"); UpdatePackages(); break; case StatusCode.Failure: Console.WriteLine($"Retrieving package list failed! {ListRequest.Error}"); EndUpdate(101); break; case StatusCode.InProgress: Console.WriteLine("Retrieving package list is still in progress!"); EndUpdate(102); break; default: Console.WriteLine($"Unsupported status {ListRequest.Status}!"); EndUpdate(103); break; } } private static void UpdatePackages() { List toAdd = new List(); List toRemove = new List(); foreach (var package in ListRequest.Result) { if (package.source == PackageSource.Embedded || package.source == PackageSource.BuiltIn || package.source == PackageSource.Local) { continue; } string latestVersion = IncludePrereleases ? package.versions.latestCompatible : #if UNITY_2022_3_OR_NEWER package.versions.recommended; #elif UNITY_2019_3_OR_NEWER package.versions.verified; #else package.versions.recommended; #endif if (package.version == latestVersion || string.IsNullOrEmpty(latestVersion)) { continue; } Debug.Log($"Update {package.name} from {package.version} to {latestVersion}"); toAdd.Add($"{package.name}@{latestVersion}"); } if (toRemove.Count == 0 && toAdd.Count == 0) { Console.WriteLine("All packages up to date"); EndUpdate(0); return; } #if UNITY_2020_1_OR_NEWER Progress.Report(ProgressId, 0.5f, $"Updating {toAdd.Count} packages: {string.Join(", ", toAdd)}"); #endif #if UNITY_2021_2_OR_NEWER AddAndRemoveRequest = Client.AddAndRemove(toAdd.ToArray(), toRemove.ToArray()); EditorApplication.update += OnWaitForPackageUpdates; #else PackagesToAdd = new Queue(toAdd); AddRequest = Client.Add(PackagesToAdd.Dequeue()); EditorApplication.update += OnWaitForSinglePackageUpdate; #endif } #if UNITY_2021_2_OR_NEWER private static void OnWaitForPackageUpdates() { #if UNITY_2020_1_OR_NEWER Progress.Report(ProgressId, 0.5f, null); #endif if (!AddAndRemoveRequest.IsCompleted) { return; } EditorApplication.update -= OnWaitForPackageUpdates; switch (AddAndRemoveRequest.Status) { case StatusCode.Success: Console.WriteLine($"Packages updated successful: {AddAndRemoveRequest.Result}"); EndUpdate(0); break; case StatusCode.Failure: Console.WriteLine($"Updating package list failed! {AddAndRemoveRequest.Error}"); EndUpdate(201); break; case StatusCode.InProgress: Console.WriteLine("Retrieving package list is still in progress!"); EndUpdate(202); break; default: Console.WriteLine($"Unsupported status {AddAndRemoveRequest.Status}!"); EndUpdate(203); break; } } #else private static void OnWaitForSinglePackageUpdate() { #if UNITY_2020_1_OR_NEWER Progress.Report(ProgressId, 0.5f, null); #endif if (!AddRequest.IsCompleted) { return; } if (AddRequest.Status == StatusCode.Success) { if (PackagesToAdd.Count > 0) { // Update the next package AddRequest = Client.Add(PackagesToAdd.Dequeue()); return; } } EditorApplication.update -= OnWaitForSinglePackageUpdate; switch (AddRequest.Status) { case StatusCode.Success: Console.WriteLine($"Packages updated successful: {AddRequest.Result}"); EndUpdate(0); break; case StatusCode.Failure: Console.WriteLine($"Updating package list failed! {AddRequest.Error}"); EndUpdate(201); break; case StatusCode.InProgress: Console.WriteLine("Retrieving package list is still in progress!"); EndUpdate(202); break; default: Console.WriteLine($"Unsupported status {AddRequest.Status}!"); EndUpdate(203); break; } } #endif private static void EndUpdate(int returnValue) { #if UNITY_2020_1_OR_NEWER Progress.Finish(ProgressId, returnValue == 0 ? Progress.Status.Succeeded : Progress.Status.Failed); #endif Debug.Log($"Updating unity packages finished with exit code {returnValue}"); if (Application.isBatchMode) { // Hack for unity-builder v3, since it is expecting a build result output Console.WriteLine( $"{Environment.NewLine}" + $"###########################{Environment.NewLine}" + $"# Build results #{Environment.NewLine}" + $"###########################{Environment.NewLine}" + $"{Environment.NewLine}" + $"Duration: 0{Environment.NewLine}" + $"Warnings: 0{Environment.NewLine}" + $"Errors: 0{Environment.NewLine}" + $"Size: 0 bytes{Environment.NewLine}" + $"{Environment.NewLine}" ); EditorApplication.Exit(returnValue); } else { if (returnValue != 0) { throw new Exception($"BuildScript ended with non-zero exitCode: {returnValue}"); } } } } }