AddVuforiaEnginePackage.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using UnityEditor;
  8. using UnityEngine;
  9. [InitializeOnLoad]
  10. public class AddVuforiaEnginePackage
  11. {
  12. static readonly string sPackagesPath = Path.Combine(Application.dataPath, "..", "Packages");
  13. static readonly string sManifestJsonPath = Path.Combine(sPackagesPath, "manifest.json");
  14. const string VUFORIA_VERSION = "10.13.3";
  15. const string VUFORIA_TAR_FILE_DIR = "Assets/Editor/Migration/";
  16. const string DEPENDENCIES_DIR = "Assets/Resources/VuforiaDependencies";
  17. const string PACKAGES_RELATIVE_PATH = "Packages";
  18. const string MRTK_PACKAGE = "com.microsoft.mixedreality.toolkit.foundation";
  19. static readonly ScopedRegistry sVuforiaRegistry = new ScopedRegistry
  20. {
  21. name = "Vuforia",
  22. url = "https://registry.packages.developer.vuforia.com/",
  23. scopes = new[] { "com.ptc.vuforia" }
  24. };
  25. static AddVuforiaEnginePackage()
  26. {
  27. if (Application.isBatchMode)
  28. return;
  29. var manifest = Manifest.JsonDeserialize(sManifestJsonPath);
  30. var packages = GetPackageDescriptions();
  31. if (!packages.All(p => IsVuforiaUpToDate(manifest, p.BundleId)))
  32. DisplayAddPackageDialog(manifest, packages);
  33. ResolveDependencies(manifest);
  34. }
  35. public static void ResolveDependenciesSilent()
  36. {
  37. var manifest = Manifest.JsonDeserialize(sManifestJsonPath);
  38. var packages = GetDependencyDescriptions();
  39. if (packages != null && packages.Count > 0)
  40. MoveDependencies(manifest, packages);
  41. CleanupDependenciesFolder();
  42. }
  43. static void ResolveDependencies(Manifest manifest)
  44. {
  45. var packages = GetDependencyDescriptions();
  46. if (packages != null && packages.Count > 0)
  47. DisplayDependenciesDialog(manifest, packages);
  48. }
  49. static bool IsVuforiaUpToDate(Manifest manifest, string bundleId)
  50. {
  51. var dependencies = manifest.Dependencies.Split(',').ToList();
  52. var upToDate = false;
  53. if(dependencies.Any(d => d.Contains(bundleId) && d.Contains("file:")))
  54. upToDate = IsUsingRightFileVersion(manifest, bundleId);
  55. return upToDate;
  56. }
  57. static bool IsUsingRightFileVersion(Manifest manifest, string bundleId)
  58. {
  59. var dependencies = manifest.Dependencies.Split(',').ToList();
  60. return dependencies.Any(d => d.Contains(bundleId) && d.Contains("file:") && VersionNumberIsTheLatestTarball(d));
  61. }
  62. static bool VersionNumberIsTheLatestTarball(string package)
  63. {
  64. var version = package.Split('-');
  65. if (version.Length >= 2)
  66. {
  67. version[1] = version[1].TrimEnd(".tgz\"".ToCharArray());
  68. return IsCurrentVersionHigher(version[1]);
  69. }
  70. return false;
  71. }
  72. static bool IsCurrentVersionHigher(string currentVersionString)
  73. {
  74. if (string.IsNullOrEmpty(currentVersionString) || string.IsNullOrEmpty(VUFORIA_VERSION))
  75. return false;
  76. var currentVersion = TryConvertStringToVersion(currentVersionString);
  77. var updatingVersion = TryConvertStringToVersion(VUFORIA_VERSION);
  78. if (currentVersion >= updatingVersion)
  79. return true;
  80. return false;
  81. }
  82. static Version TryConvertStringToVersion(string versionString)
  83. {
  84. Version res;
  85. try
  86. {
  87. res = new Version(versionString);
  88. }
  89. catch (Exception)
  90. {
  91. return new Version();
  92. }
  93. return new Version(res.Major, res.Minor, res.Build);
  94. }
  95. static void DisplayAddPackageDialog(Manifest manifest, IEnumerable<PackageDescription> packages)
  96. {
  97. if (EditorUtility.DisplayDialog("Add Vuforia Engine Package",
  98. $"Would you like to update your project to include the Vuforia Engine {VUFORIA_VERSION} package from the unitypackage?\n" +
  99. $"If an older Vuforia Engine package is already present in your project it will be upgraded to version {VUFORIA_VERSION}\n\n",
  100. "Update", "Cancel"))
  101. {
  102. foreach (var package in packages)
  103. {
  104. MovePackageFile(VUFORIA_TAR_FILE_DIR, package.FileName);
  105. UpdateManifest(manifest, package.BundleId, package.FileName);
  106. }
  107. }
  108. }
  109. static void DisplayDependenciesDialog(Manifest manifest, IEnumerable<PackageDescription> packages)
  110. {
  111. if (EditorUtility.DisplayDialog("Add Sample Dependencies",
  112. "Would you like to update your project to include all of its dependencies?\n" +
  113. "If a different version of the package is already present, it will be deleted.\n\n",
  114. "Update", "Cancel"))
  115. {
  116. MoveDependencies(manifest, packages);
  117. CleanupDependenciesFolder();
  118. if (ShouldProjectRestart(packages))
  119. DisplayRestartDialog();
  120. }
  121. }
  122. static void DisplayRestartDialog()
  123. {
  124. if (EditorUtility.DisplayDialog("Restart Unity Editor",
  125. "Due to a Unity lifecycle issue, this project needs to be closed and re-opened " +
  126. "after importing this Vuforia Engine sample.\n\n",
  127. "Restart", "Cancel"))
  128. {
  129. RestartEditor();
  130. }
  131. }
  132. static List<PackageDescription> GetPackageDescriptions()
  133. {
  134. var tarFilePaths = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), VUFORIA_TAR_FILE_DIR)).Where(f => f.EndsWith(".tgz"));
  135. // Define a regular expression for repeated words.
  136. var rx = new Regex(@"(([a-z]+)(\.[a-z]+)*)\-((\d+)\.(\d+)\.(\d+))", RegexOptions.Compiled | RegexOptions.IgnoreCase);
  137. var packageDescriptions = new List<PackageDescription>();
  138. foreach (var filePath in tarFilePaths)
  139. {
  140. var fileName = Path.GetFileName(filePath);
  141. // Find matches.
  142. var matches = rx.Matches(fileName);
  143. // Report on each match.
  144. foreach (Match match in matches)
  145. {
  146. var groups = match.Groups;
  147. var bundleId = groups[1].Value;
  148. var versionString = groups[4].Value;
  149. if (string.Equals(versionString, VUFORIA_VERSION))
  150. {
  151. packageDescriptions.Add(new PackageDescription()
  152. {
  153. BundleId = bundleId,
  154. FileName = fileName
  155. });
  156. }
  157. }
  158. }
  159. return packageDescriptions;
  160. }
  161. static List<PackageDescription> GetDependencyDescriptions()
  162. {
  163. var dependencyDirectory = Path.Combine(Directory.GetCurrentDirectory(), DEPENDENCIES_DIR);
  164. if (!Directory.Exists(dependencyDirectory))
  165. return null;
  166. var tarFilePaths = Directory.GetFiles(dependencyDirectory).Where(f => f.EndsWith(".tgz"));
  167. // Define a regular expression for repeated words.
  168. var rx = new Regex(@"(([a-z]+)(\.[a-z]+)+)(\-((\d+)\.(\d+)\.(\d+)))*", RegexOptions.Compiled | RegexOptions.IgnoreCase);
  169. var packageDescriptions = new List<PackageDescription>();
  170. foreach (var filePath in tarFilePaths)
  171. {
  172. var fileName = Path.GetFileName(filePath);
  173. // Find matches.
  174. var matches = rx.Matches(fileName);
  175. // Report on each match.
  176. foreach (Match match in matches)
  177. {
  178. var groups = match.Groups;
  179. var bundleId = groups[1].Value;
  180. bundleId = bundleId.Replace(".tgz", "");
  181. packageDescriptions.Add(new PackageDescription
  182. {
  183. BundleId = bundleId,
  184. FileName = fileName
  185. });
  186. }
  187. }
  188. return packageDescriptions;
  189. }
  190. static void MoveDependencies(Manifest manifest, IEnumerable<PackageDescription> packages)
  191. {
  192. foreach (var package in packages)
  193. {
  194. RemoveDependency(manifest, package.BundleId, package.FileName);
  195. MovePackageFile(DEPENDENCIES_DIR, package.FileName);
  196. UpdateManifest(manifest, package.BundleId, package.FileName);
  197. }
  198. }
  199. static void MovePackageFile(string folder, string fileName)
  200. {
  201. var sourceFile = Path.Combine(Directory.GetCurrentDirectory(), folder, fileName);
  202. var destFile = Path.Combine(Directory.GetCurrentDirectory(), PACKAGES_RELATIVE_PATH, fileName);
  203. File.Copy(sourceFile, destFile, true);
  204. File.Delete(sourceFile);
  205. File.Delete(sourceFile + ".meta");
  206. }
  207. static void UpdateManifest(Manifest manifest, string bundleId, string fileName)
  208. {
  209. //remove existing, outdated NPM scoped registry if present
  210. var registries = manifest.ScopedRegistries.ToList();
  211. if (registries.Contains(sVuforiaRegistry))
  212. {
  213. registries.Remove(sVuforiaRegistry);
  214. manifest.ScopedRegistries = registries.ToArray();
  215. }
  216. //add specified vuforia version via Git URL
  217. SetVuforiaVersion(manifest, bundleId, fileName);
  218. manifest.JsonSerialize(sManifestJsonPath);
  219. AssetDatabase.Refresh();
  220. }
  221. static void RemoveDependency(Manifest manifest, string bundleId, string fileName)
  222. {
  223. var destFile = Path.Combine(Directory.GetCurrentDirectory(), PACKAGES_RELATIVE_PATH, fileName);
  224. if (File.Exists(destFile))
  225. File.Delete(destFile);
  226. // remove existing
  227. var dependencies = manifest.Dependencies.Split(',').ToList();
  228. for (var i = 0; i < dependencies.Count; i++)
  229. {
  230. if (dependencies[i].Contains(bundleId))
  231. {
  232. dependencies.RemoveAt(i);
  233. break;
  234. }
  235. }
  236. manifest.Dependencies = string.Join(",", dependencies);
  237. manifest.JsonSerialize(sManifestJsonPath);
  238. AssetDatabase.Refresh();
  239. }
  240. static void CleanupDependenciesFolder()
  241. {
  242. if (!Directory.Exists(DEPENDENCIES_DIR))
  243. return;
  244. Directory.Delete(DEPENDENCIES_DIR);
  245. File.Delete(DEPENDENCIES_DIR + ".meta");
  246. AssetDatabase.Refresh();
  247. }
  248. static bool ShouldProjectRestart(IEnumerable<PackageDescription> packages)
  249. {
  250. return packages.Any(p => p.BundleId == MRTK_PACKAGE);
  251. }
  252. static void RestartEditor()
  253. {
  254. EditorApplication.OpenProject(Directory.GetCurrentDirectory());
  255. }
  256. static void SetVuforiaVersion(Manifest manifest, string bundleId, string fileName)
  257. {
  258. var dependencies = manifest.Dependencies.Split(',').ToList();
  259. var versionEntry = $"\"file:{fileName}\"";
  260. var versionSet = false;
  261. for (var i = 0; i < dependencies.Count; i++)
  262. {
  263. if (!dependencies[i].Contains(bundleId))
  264. continue;
  265. var kvp = dependencies[i].Split(':');
  266. dependencies[i] = kvp[0] + ": " + versionEntry;
  267. versionSet = true;
  268. }
  269. if (!versionSet)
  270. dependencies.Insert(0, $"\n \"{bundleId}\": {versionEntry}");
  271. manifest.Dependencies = string.Join(",", dependencies);
  272. }
  273. class Manifest
  274. {
  275. const int INDEX_NOT_FOUND = -1;
  276. const string DEPENDENCIES_KEY = "\"dependencies\"";
  277. public ScopedRegistry[] ScopedRegistries;
  278. public string Dependencies;
  279. public void JsonSerialize(string path)
  280. {
  281. var jsonString = GetJsonString();
  282. var startIndex = GetDependenciesStart(jsonString);
  283. var endIndex = GetDependenciesEnd(jsonString, startIndex);
  284. var stringBuilder = new StringBuilder();
  285. stringBuilder.Append(jsonString.Substring(0, startIndex));
  286. stringBuilder.Append(Dependencies);
  287. stringBuilder.Append(jsonString.Substring(endIndex, jsonString.Length - endIndex));
  288. File.WriteAllText(path, stringBuilder.ToString());
  289. }
  290. string GetJsonString()
  291. {
  292. if (ScopedRegistries.Length > 0)
  293. return JsonUtility.ToJson(
  294. new UnitySerializableManifest { scopedRegistries = ScopedRegistries, dependencies = new DependencyPlaceholder() },
  295. true);
  296. return JsonUtility.ToJson(
  297. new UnitySerializableManifestDependenciesOnly() { dependencies = new DependencyPlaceholder() },
  298. true);
  299. }
  300. public static Manifest JsonDeserialize(string path)
  301. {
  302. var jsonString = File.ReadAllText(path);
  303. var registries = JsonUtility.FromJson<UnitySerializableManifest>(jsonString).scopedRegistries ?? new ScopedRegistry[0];
  304. var dependencies = DeserializeDependencies(jsonString);
  305. return new Manifest { ScopedRegistries = registries, Dependencies = dependencies };
  306. }
  307. static string DeserializeDependencies(string json)
  308. {
  309. var startIndex = GetDependenciesStart(json);
  310. var endIndex = GetDependenciesEnd(json, startIndex);
  311. if (startIndex == INDEX_NOT_FOUND || endIndex == INDEX_NOT_FOUND)
  312. return null;
  313. var dependencies = json.Substring(startIndex, endIndex - startIndex);
  314. return dependencies;
  315. }
  316. static int GetDependenciesStart(string json)
  317. {
  318. var dependenciesIndex = json.IndexOf(DEPENDENCIES_KEY, StringComparison.InvariantCulture);
  319. if (dependenciesIndex == INDEX_NOT_FOUND)
  320. return INDEX_NOT_FOUND;
  321. var dependenciesStartIndex = json.IndexOf('{', dependenciesIndex + DEPENDENCIES_KEY.Length);
  322. if (dependenciesStartIndex == INDEX_NOT_FOUND)
  323. return INDEX_NOT_FOUND;
  324. dependenciesStartIndex++; //add length of '{' to starting point
  325. return dependenciesStartIndex;
  326. }
  327. static int GetDependenciesEnd(string jsonString, int dependenciesStartIndex)
  328. {
  329. return jsonString.IndexOf('}', dependenciesStartIndex);
  330. }
  331. }
  332. class UnitySerializableManifestDependenciesOnly
  333. {
  334. public DependencyPlaceholder dependencies;
  335. }
  336. class UnitySerializableManifest
  337. {
  338. public ScopedRegistry[] scopedRegistries;
  339. public DependencyPlaceholder dependencies;
  340. }
  341. [Serializable]
  342. struct ScopedRegistry
  343. {
  344. public string name;
  345. public string url;
  346. public string[] scopes;
  347. public override bool Equals(object obj)
  348. {
  349. if (!(obj is ScopedRegistry))
  350. return false;
  351. var other = (ScopedRegistry)obj;
  352. return name == other.name &&
  353. url == other.url &&
  354. scopes.SequenceEqual(other.scopes);
  355. }
  356. public static bool operator ==(ScopedRegistry a, ScopedRegistry b)
  357. {
  358. return a.Equals(b);
  359. }
  360. public static bool operator !=(ScopedRegistry a, ScopedRegistry b)
  361. {
  362. return !a.Equals(b);
  363. }
  364. public override int GetHashCode()
  365. {
  366. var hash = 17;
  367. foreach (var scope in scopes)
  368. hash = hash * 23 + (scope == null ? 0 : scope.GetHashCode());
  369. hash = hash * 23 + (name == null ? 0 : name.GetHashCode());
  370. hash = hash * 23 + (url == null ? 0 : url.GetHashCode());
  371. return hash;
  372. }
  373. }
  374. [Serializable]
  375. struct DependencyPlaceholder { }
  376. struct PackageDescription
  377. {
  378. public string BundleId;
  379. public string FileName;
  380. }
  381. }