UnityGame/Library/PackageCache/com.unity.multiplayer.center/Editor/Features/PackageManagement.cs
2024-10-27 10:53:47 +03:00

326 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
namespace Unity.Multiplayer.Center
{
internal static class PackageManagement
{
static PackageInstaller s_Installer;
/// <summary>
/// Opens the package manager window with selected package name and hides error
/// </summary>
public static void OpenPackageManager(string packageName)
{
try
{
UnityEditor.PackageManager.UI.Window.Open(packageName);
}
catch (Exception)
{
// Hide the error in the PackageManager API until the team fixes it
// Debug.Log("Error opening Package Manager: " + e.Message);
}
}
/// <summary>
/// Checks if the package is a direct dependency of the project
/// </summary>
/// <param name="packageId">The package name/id e.g. com.unity.netcode</param>
/// <returns>True if the package is a direct dependency</returns>
public static bool IsDirectDependency(string packageId)
{
var package = GetInstalledPackage(packageId);
return package != null && package.isDirectDependency;
}
/// <summary>
/// Checks if a package is installed.
/// </summary>
/// <param name="packageId">The package name, e.g. com.unity.netcode</param>
/// <returns>True if the package is installed, false otherwise</returns>
public static bool IsInstalled(string packageId) => GetInstalledPackage(packageId) != null;
/// <summary>
/// Checks if a package is embedded, linked locally, installed via Git or local Tarball.
/// </summary>
/// <param name="packageId">The package name, e.g. com.unity.netcode</param>
/// <returns>True if the package is linked locally, false otherwise</returns>
public static bool IsLinkedLocallyOrEmbeddedOrViaGit(string packageId) =>
GetInstalledPackage(packageId) is { source: PackageSource.Embedded or PackageSource.Local or PackageSource.Git or PackageSource.LocalTarball };
/// <summary>
/// Finds the installed package with the given packageId or returns null.
/// </summary>
/// <param name="packageId">The package name/id e.g. com.unity.netcode</param>
/// <returns>The package info</returns>
public static UnityEditor.PackageManager.PackageInfo GetInstalledPackage(string packageId)
{
return UnityEditor.PackageManager.PackageInfo.FindForPackageName(packageId);
}
/// <summary>
/// Filters out the packages that are already embedded, linked locally, installed via Git or local Tarball and returns this new list.
/// </summary>
/// <param name="installCandidates">A list of package IDs that are candidates for installation.</param>
/// <returns>A new filtered list of packages.</returns>
public static IEnumerable<string> RemoveLocallyLinkedOrEmbeddedOrViaGitPackagesFromList(IEnumerable<string> installCandidates)
{
var filteredList = new List<string>();
foreach (var packageId in installCandidates)
{
if (!IsLinkedLocallyOrEmbeddedOrViaGit(packageId))
{
filteredList.Add(packageId);
}
else
{
Debug.Log($"Removing {packageId} from install candidates.\n" +
"This package is already embedded, linked locally, installed via Git, or from a local tarball. " +
"Please check the Package Manager for more information or to upgrade manually.");
}
}
return filteredList;
}
/// <summary>
/// Returns true if any of the given packageIds is installed.
/// </summary>
/// <param name="packageIds">List of package is e.g com.unity.netcode</param>
/// <returns>True if any package is installed, false otherwise</returns>
public static bool IsAnyPackageInstalled(params string[] packageIds)
{
var installedPackages = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages();
var hashset = new HashSet<string>();
foreach (var package in installedPackages)
{
hashset.Add(package.name);
}
foreach (var packageId in packageIds)
{
if (hashset.Contains(packageId))
{
return true;
}
}
return false;
}
/// <summary>
/// Installs a single package and invokes the callback when the package is installed/when the install failed.
/// </summary>
/// <param name="packageId">The package name/id e.g. com.unity.netcode</param>
/// <param name="onInstalled">The callback</param>
public static void InstallPackage(string packageId, Action<bool> onInstalled = null)
{
s_Installer = new PackageInstaller(packageId);
s_Installer.OnInstalled += onInstalled;
s_Installer.OnInstalled += _ => s_Installer = null;
}
/// <summary>
/// Register to an existing installation callback. This has no effect if no installation is ongoing (check
/// <see cref="IsInstallationFinished"/> to see if that is the case).
/// </summary>
/// <param name="onInstalled">The callback</param>
public static void RegisterToExistingInstaller(Action<bool> onInstalled)
{
if (s_Installer != null)
{
s_Installer.OnInstalled += onInstalled;
}
}
/// <summary>
/// Installs several packages and invokes the callback when all packages are installed/when the installation failed.
/// </summary>
/// <param name="packageIds">The package names/ids e.g. com.unity.netcode</param>
/// <param name="onAllInstalled">The callback</param>
/// <param name="packageIdsToRemove">Optional package name/ids to remove</param>
public static void InstallPackages(IEnumerable<string> packageIds, Action<bool> onAllInstalled = null, IEnumerable<string> packageIdsToRemove = null)
{
s_Installer = new PackageInstaller(RemoveLocallyLinkedOrEmbeddedOrViaGitPackagesFromList(packageIds), packageIdsToRemove);
s_Installer.OnInstalled += onAllInstalled;
s_Installer.OnInstalled += _ => s_Installer = null;
}
/// <summary>
/// Create a dictionary with package names as keys and versions as values
/// </summary>
/// <returns>The mapping (package id, installed version) </returns>
internal static Dictionary<string, string> InstalledPackageDictionary()
{
var installedPackages = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages();
var installedPackageDictionary = new Dictionary<string, string>();
foreach (var package in installedPackages)
{
var splitPackageId = package.packageId.Split('@');
if (splitPackageId.Length == 2)
{
installedPackageDictionary[splitPackageId[0]] = splitPackageId[1];
}
}
return installedPackageDictionary;
}
internal class VersionChecker
{
SearchRequest m_Request;
public VersionChecker(string packageID)
{
m_Request = Client.Search(packageID, false);
EditorApplication.update += Progress;
}
public event Action<UnityEditor.PackageManager.PackageInfo> OnVersionFound;
void Progress()
{
if (!m_Request.IsCompleted) return;
EditorApplication.update -= Progress;
var foundPackage = m_Request.Result;
foreach (var packageInfo in foundPackage)
{
OnVersionFound?.Invoke(packageInfo);
}
}
}
class PackageInstaller
{
Request m_Request;
string[] m_PackagesToAddIds;
public event Action<bool> OnInstalled;
public PackageInstaller(string packageId)
{
// Add a package to the project
m_Request = Client.Add(packageId);
m_PackagesToAddIds = new[] {packageId};
EditorApplication.update += Progress;
}
public PackageInstaller(IEnumerable<string> packageIds, IEnumerable<string> packageIdsToRemove = null)
{
var packageIdsList = new List<string>();
foreach (var id in packageIds)
{
packageIdsList.Add(id);
}
var packageIdsArray = packageIdsList.ToArray();
string[] packageIdsToRemoveArray = null;
if (packageIdsToRemove != null)
{
var packageIdsToRemoveList = new List<string>();
foreach (var id in packageIdsToRemove)
{
packageIdsToRemoveList.Add(id);
}
packageIdsToRemoveArray = packageIdsToRemoveList.ToArray();
}
// Add a package to the project
m_Request = Client.AddAndRemove(packageIdsArray, packageIdsToRemoveArray);
m_PackagesToAddIds = packageIdsArray;
EditorApplication.update += Progress;
}
public bool IsCompleted()
{
return m_Request == null || m_Request.IsCompleted;
}
void Progress()
{
if (!m_Request.IsCompleted) return;
EditorApplication.update -= Progress;
if (m_Request.Status == StatusCode.Success)
{
Debug.Log("Installed: " + GetInstalledPackageId());
}
else if (m_Request.Status >= StatusCode.Failure)
{
// if the request has more than one package, it will only prompt error message for one
// We should prompt all the failed packages
Debug.Log("Package installation request with selected packages: " + String.Join(", ", m_PackagesToAddIds) +
" failed. \n Reason: "+ m_Request.Error.message);
}
OnInstalled?.Invoke(m_Request.Status == StatusCode.Success);
}
string GetInstalledPackageId()
{
switch (m_Request)
{
case AddRequest addRequest:
return addRequest.Result.packageId;
case AddAndRemoveRequest addAndRemoveRequest:
var packageIds = new List<string>();
foreach (var packageInfo in addAndRemoveRequest.Result)
{
packageIds.Add(packageInfo.packageId);
}
return string.Join(", ", packageIds);
default:
throw new InvalidOperationException("Unknown request type");
}
}
}
/// <summary>
/// Detects if any multiplayer package is installed by checking for services and Netcode installed packages.
/// </summary>
/// <returns>True if any package was detected, False otherwise</returns>
public static bool IsAnyMultiplayerPackageInstalled()
{
var packagesToCheck = new []
{
"com.unity.netcode",
"com.unity.netcode.gameobjects",
"com.unity.services.multiplayer",
"com.unity.transport",
"com.unity.dedicated-server",
"com.unity.services.cloudcode",
"com.unity.multiplayer.playmode",
"com.unity.services.vivox"
// Note about "com.unity.services.core": it used to be installed only with multiplayer packages, but it is also a dependency of the analytics, which is now always installed.
};
foreach (var package in packagesToCheck)
{
if (IsInstalled(package))
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if the installation process has finished.
/// </summary>
/// <returns>True if there is no current installer instance or installation is finished on the installer</returns>
public static bool IsInstallationFinished()
{
return s_Installer == null || s_Installer.IsCompleted();
}
}
}