DependencyResolver.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using MongoDB.Bson.Serialization;
  6. using MongoDB.Bson.Serialization.Attributes;
  7. using UnityEditor;
  8. using UnityEditor.PackageManager;
  9. using UnityEngine;
  10. using PackageInfo = UnityEditor.PackageManager.PackageInfo;
  11. using UnityEditor.PackageManager.Requests;
  12. namespace Hibzz.DependencyResolver
  13. {
  14. [BsonIgnoreExtraElements]
  15. public class PackageGitDependency
  16. {
  17. public Dictionary<string, string> gitDependencies;
  18. }
  19. [InitializeOnLoad]
  20. public class DependencyResolver
  21. {
  22. static AddAndRemoveRequest packageInstallationRequest;
  23. // called by the attribute [InitializeOnLoad]
  24. static DependencyResolver()
  25. {
  26. Events.registeredPackages += OnPackagesRegistered;
  27. }
  28. // Invoked when the package manager completes registering new packages
  29. static void OnPackagesRegistered(PackageRegistrationEventArgs packageRegistrationInfo)
  30. {
  31. // stores all the dependencies that needs to be installed in this step
  32. Dictionary<string, string> dependencies = new ();
  33. // loop through all of the added packages and get their git
  34. // dependencies and add it to the list that contains all the
  35. // dependencies that need to be installed
  36. foreach (var package in packageRegistrationInfo.added)
  37. {
  38. // get the dependencies of the added package
  39. if (!GetDependencies(package, out PackageGitDependency package_dependencies))
  40. {
  41. continue;
  42. }
  43. foreach (var gitDependency in package_dependencies.gitDependencies)
  44. {
  45. dependencies[gitDependency.Key] = gitDependency.Value;
  46. }
  47. }
  48. // remove any dependencies that's already installed
  49. var installed_packages = PackageInfo.GetAllRegisteredPackages().ToList();
  50. foreach (string dependency in dependencies.Keys.ToArray())
  51. {
  52. if (IsInCollection(dependency, installed_packages))
  53. {
  54. dependencies.Remove(dependency);
  55. }
  56. }
  57. // Install the dependencies
  58. InstallDependencies(dependencies);
  59. }
  60. /// <summary>
  61. /// Request a list of git dependencies in the package
  62. /// </summary>
  63. /// <param name="packageInfo">The package to get the git dependencies from</param>
  64. /// <param name="dependencies">The retrieved list of git dependencies </param>
  65. /// <returns>Was the request successful?</returns>
  66. static bool GetDependencies(PackageInfo packageInfo, out PackageGitDependency dependencies)
  67. {
  68. // Read the contents of the package.json file
  69. string package_json_path = $"{packageInfo.resolvedPath}/package.json";
  70. string package_json_content = File.ReadAllText(package_json_path);
  71. PackageGitDependency packageGitDependency = BsonSerializer.Deserialize<PackageGitDependency>(package_json_content);
  72. // if no token with the key git-dependecies is found, failed to get git dependencies
  73. if (packageGitDependency.gitDependencies is null || packageGitDependency.gitDependencies.Count == 0)
  74. {
  75. dependencies = null;
  76. return false;
  77. }
  78. // convert the git dependency token to a list of strings...
  79. // maybe we should check for errors in this process? what if git-dependency isn't array of string?
  80. dependencies = packageGitDependency;
  81. return true;
  82. }
  83. /// <summary>
  84. /// Is the given dependency url found in the given collection
  85. /// </summary>
  86. /// <param name="dependency">The url the dependency to check for</param>
  87. /// <param name="collection">The collection to look through</param>
  88. /// <returns></returns>
  89. static bool IsInCollection(string dependency, List<PackageInfo> collection)
  90. {
  91. // when package collection given is null, it's inferred that the dependency is not in the collection
  92. if (collection == null) { return false; }
  93. // check if any of the installed package has the dependency
  94. foreach (var package in collection)
  95. {
  96. // the package id for a package installed with git is `package_name@package_giturl`
  97. // get the repo url by performing some string manipulation on the package id
  98. //string repourl = package.packageId.Substring(package.packageId.IndexOf('@') + 1);
  99. // Found!
  100. if (package.name == dependency)
  101. {
  102. return true;
  103. }
  104. }
  105. // the dependency wasn't found in the package collection
  106. return false;
  107. }
  108. /// <summary>
  109. /// Install all the given dependencies
  110. /// </summary>
  111. /// <param name="dependencies">A list of dependencies to install</param>
  112. static void InstallDependencies(Dictionary<string, string> dependencies)
  113. {
  114. // there are no dependencies to install, skip
  115. if (dependencies == null || dependencies.Count <= 0)
  116. {
  117. return;
  118. }
  119. // before installing the packages, make sure that user knows what
  120. // the dependencies to install are... additionally, check if the
  121. // application is being run on batch mode so that we can skip the
  122. // installation dialog
  123. if (!Application.isBatchMode &&
  124. !EditorUtility.DisplayDialog(
  125. $"Dependency Resolver",
  126. $"The following dependencies are required: \n\n{GetPrintFriendlyName(dependencies.Keys.ToList())}",
  127. "Install Dependencies",
  128. "Cancel"))
  129. {
  130. // user decided to cancel the installation of the dependencies...
  131. return;
  132. }
  133. // the user pressed install, perform the actual installation
  134. // (or the application was in batch mode)
  135. packageInstallationRequest = Client.AddAndRemove(dependencies.Values.ToArray(), null);
  136. // show the progress bar till the installation is complete
  137. EditorUtility.DisplayProgressBar("Dependency Resolver", "Preparing installation of dependencies...", 0);
  138. EditorApplication.update += DisplayProgress;
  139. }
  140. /// <summary>
  141. /// Get a print friendly name of all dependencies to show in the dialog box
  142. /// </summary>
  143. /// <param name="dependencies">The list of dependencies to parse through</param>
  144. /// <returns>A print friendly string representing all the dependencies</returns>
  145. static string GetPrintFriendlyName(List<string> dependencies)
  146. {
  147. // ideally, we want the package name, but that requires downloading the package.json and parsing through
  148. // it, which is kinda too much... i could ask for the users to give a package name along with the url in
  149. // package.json, but again too complicated just for a dialog message... username/repo will do fine for now
  150. string result = string.Join("\n", dependencies); // concatenate dependencies on a new line
  151. return result;
  152. }
  153. /// <summary>
  154. /// Shows a progress bar till the AddAndRemoveRequest is completed
  155. /// </summary>
  156. static void DisplayProgress()
  157. {
  158. if(packageInstallationRequest.IsCompleted)
  159. {
  160. EditorUtility.ClearProgressBar();
  161. EditorApplication.update -= DisplayProgress;
  162. }
  163. }
  164. }
  165. }