Discovery.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Unity Technologies.
  3. * Copyright (c) Microsoft Corporation. All rights reserved.
  4. * Licensed under the MIT License. See License.txt in the project root for license information.
  5. *--------------------------------------------------------------------------------------------*/
  6. using System;
  7. using System.IO;
  8. using System.Collections.Generic;
  9. using System.Diagnostics;
  10. using System.Text.RegularExpressions;
  11. using System.Linq;
  12. using UnityEngine;
  13. namespace Microsoft.Unity.VisualStudio.Editor
  14. {
  15. internal static class Discovery
  16. {
  17. internal const string ManagedWorkload = "Microsoft.VisualStudio.Workload.ManagedGame";
  18. internal static string _vsWherePath;
  19. public static void FindVSWhere()
  20. {
  21. _vsWherePath = FileUtility.GetPackageAssetFullPath("Editor", "VSWhere", "vswhere.exe");
  22. }
  23. public static IEnumerable<IVisualStudioInstallation> GetVisualStudioInstallations()
  24. {
  25. if (VisualStudioEditor.IsWindows)
  26. {
  27. foreach (var installation in QueryVsWhere())
  28. yield return installation;
  29. }
  30. if (VisualStudioEditor.IsOSX)
  31. {
  32. var candidates = Directory.EnumerateDirectories("/Applications", "*.app");
  33. foreach (var candidate in candidates)
  34. {
  35. if (TryDiscoverInstallation(candidate, out var installation))
  36. yield return installation;
  37. }
  38. }
  39. }
  40. private static bool IsCandidateForDiscovery(string path)
  41. {
  42. if (File.Exists(path) && VisualStudioEditor.IsWindows && Regex.IsMatch(path, "devenv.exe$", RegexOptions.IgnoreCase))
  43. return true;
  44. if (Directory.Exists(path) && VisualStudioEditor.IsOSX && Regex.IsMatch(path, "Visual\\s?Studio(?!.*Code.*).*.app$", RegexOptions.IgnoreCase))
  45. return true;
  46. return false;
  47. }
  48. public static bool TryDiscoverInstallation(string editorPath, out IVisualStudioInstallation installation)
  49. {
  50. installation = null;
  51. if (string.IsNullOrEmpty(editorPath))
  52. return false;
  53. if (!IsCandidateForDiscovery(editorPath))
  54. return false;
  55. // On windows we use the executable directly, so we can query extra information
  56. var fvi = editorPath;
  57. // On Mac we use the .app folder, so we need to access to main assembly
  58. if (VisualStudioEditor.IsOSX)
  59. {
  60. fvi = Path.Combine(editorPath, "Contents/Resources/lib/monodevelop/bin/VisualStudio.exe");
  61. if (!File.Exists(fvi))
  62. fvi = Path.Combine(editorPath, "Contents/MonoBundle/VisualStudio.exe");
  63. if (!File.Exists(fvi))
  64. fvi = Path.Combine(editorPath, "Contents/MonoBundle/VisualStudio.dll");
  65. }
  66. if (!File.Exists(fvi))
  67. return false;
  68. // VS preview are not using the isPrerelease flag so far
  69. // On Windows FileDescription contains "Preview", but not on Mac
  70. var vi = FileVersionInfo.GetVersionInfo(fvi);
  71. var version = new Version(vi.ProductVersion);
  72. var isPrerelease = vi.IsPreRelease || string.Concat(editorPath, "/" + vi.FileDescription).ToLower().Contains("preview");
  73. installation = new VisualStudioInstallation()
  74. {
  75. IsPrerelease = isPrerelease,
  76. Name = $"{vi.FileDescription}{(isPrerelease && VisualStudioEditor.IsOSX ? " Preview" : string.Empty)} [{version.ToString(3)}]",
  77. Path = editorPath,
  78. Version = version
  79. };
  80. return true;
  81. }
  82. #region VsWhere Json Schema
  83. #pragma warning disable CS0649
  84. [Serializable]
  85. internal class VsWhereResult
  86. {
  87. public VsWhereEntry[] entries;
  88. public static VsWhereResult FromJson(string json)
  89. {
  90. return JsonUtility.FromJson<VsWhereResult>("{ \"" + nameof(VsWhereResult.entries) + "\": " + json + " }");
  91. }
  92. public IEnumerable<VisualStudioInstallation> ToVisualStudioInstallations()
  93. {
  94. foreach (var entry in entries)
  95. {
  96. yield return new VisualStudioInstallation()
  97. {
  98. Name = $"{entry.displayName} [{entry.catalog.productDisplayVersion}]",
  99. Path = entry.productPath,
  100. IsPrerelease = entry.isPrerelease,
  101. Version = Version.Parse(entry.catalog.buildVersion)
  102. };
  103. }
  104. }
  105. }
  106. [Serializable]
  107. internal class VsWhereEntry
  108. {
  109. public string displayName;
  110. public bool isPrerelease;
  111. public string productPath;
  112. public VsWhereCatalog catalog;
  113. }
  114. [Serializable]
  115. internal class VsWhereCatalog
  116. {
  117. public string productDisplayVersion; // non parseable like "16.3.0 Preview 3.0"
  118. public string buildVersion;
  119. }
  120. #pragma warning restore CS3021
  121. #endregion
  122. private static IEnumerable<VisualStudioInstallation> QueryVsWhere()
  123. {
  124. var progpath = _vsWherePath;
  125. if (string.IsNullOrWhiteSpace(progpath))
  126. return Enumerable.Empty<VisualStudioInstallation>();
  127. var process = new Process
  128. {
  129. StartInfo = new ProcessStartInfo
  130. {
  131. FileName = progpath,
  132. Arguments = "-prerelease -format json -utf8",
  133. UseShellExecute = false,
  134. CreateNoWindow = true,
  135. RedirectStandardOutput = true,
  136. RedirectStandardError = true,
  137. }
  138. };
  139. using (process)
  140. {
  141. var json = string.Empty;
  142. process.OutputDataReceived += (o, e) => json += e.Data;
  143. process.Start();
  144. process.BeginOutputReadLine();
  145. process.WaitForExit();
  146. var result = VsWhereResult.FromJson(json);
  147. return result.ToVisualStudioInstallations();
  148. }
  149. }
  150. }
  151. }