Discovery.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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. }
  64. if (!File.Exists(fvi))
  65. return false;
  66. // VS preview are not using the isPrerelease flag so far
  67. // On Windows FileDescription contains "Preview", but not on Mac
  68. var vi = FileVersionInfo.GetVersionInfo(fvi);
  69. var version = new Version(vi.ProductVersion);
  70. var isPrerelease = vi.IsPreRelease || string.Concat(editorPath, "/" + vi.FileDescription).ToLower().Contains("preview");
  71. installation = new VisualStudioInstallation()
  72. {
  73. IsPrerelease = isPrerelease,
  74. Name = $"{vi.FileDescription}{(isPrerelease && VisualStudioEditor.IsOSX ? " Preview" : string.Empty)} [{version.ToString(3)}]",
  75. Path = editorPath,
  76. Version = version
  77. };
  78. return true;
  79. }
  80. #region VsWhere Json Schema
  81. #pragma warning disable CS0649
  82. [Serializable]
  83. internal class VsWhereResult
  84. {
  85. public VsWhereEntry[] entries;
  86. public static VsWhereResult FromJson(string json)
  87. {
  88. return JsonUtility.FromJson<VsWhereResult>("{ \"" + nameof(VsWhereResult.entries) + "\": " + json + " }");
  89. }
  90. public IEnumerable<VisualStudioInstallation> ToVisualStudioInstallations()
  91. {
  92. foreach (var entry in entries)
  93. {
  94. yield return new VisualStudioInstallation()
  95. {
  96. Name = $"{entry.displayName} [{entry.catalog.productDisplayVersion}]",
  97. Path = entry.productPath,
  98. IsPrerelease = entry.isPrerelease,
  99. Version = Version.Parse(entry.catalog.buildVersion)
  100. };
  101. }
  102. }
  103. }
  104. [Serializable]
  105. internal class VsWhereEntry
  106. {
  107. public string displayName;
  108. public bool isPrerelease;
  109. public string productPath;
  110. public VsWhereCatalog catalog;
  111. }
  112. [Serializable]
  113. internal class VsWhereCatalog
  114. {
  115. public string productDisplayVersion; // non parseable like "16.3.0 Preview 3.0"
  116. public string buildVersion;
  117. }
  118. #pragma warning restore CS3021
  119. #endregion
  120. private static IEnumerable<VisualStudioInstallation> QueryVsWhere()
  121. {
  122. var progpath = _vsWherePath;
  123. if (string.IsNullOrWhiteSpace(progpath))
  124. return Enumerable.Empty<VisualStudioInstallation>();
  125. var process = new Process
  126. {
  127. StartInfo = new ProcessStartInfo
  128. {
  129. FileName = progpath,
  130. Arguments = "-prerelease -format json -utf8",
  131. UseShellExecute = false,
  132. CreateNoWindow = true,
  133. RedirectStandardOutput = true,
  134. RedirectStandardError = true,
  135. }
  136. };
  137. using (process)
  138. {
  139. var json = string.Empty;
  140. process.OutputDataReceived += (o, e) => json += e.Data;
  141. process.Start();
  142. process.BeginOutputReadLine();
  143. process.WaitForExit();
  144. var result = VsWhereResult.FromJson(json);
  145. return result.ToVisualStudioInstallations();
  146. }
  147. }
  148. }
  149. }