AddVuforiaEnginePackage.cs 16 KB

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