VisualStudioInstallation.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. using System;
  6. using System.IO;
  7. using Microsoft.Win32;
  8. using Unity.CodeEditor;
  9. using IOPath = System.IO.Path;
  10. namespace Microsoft.Unity.VisualStudio.Editor
  11. {
  12. internal interface IVisualStudioInstallation
  13. {
  14. string Path { get; }
  15. bool SupportsAnalyzers { get; }
  16. Version LatestLanguageVersionSupported { get; }
  17. string[] GetAnalyzers();
  18. CodeEditor.Installation ToCodeEditorInstallation();
  19. }
  20. internal class VisualStudioInstallation : IVisualStudioInstallation
  21. {
  22. public string Name { get; set; }
  23. public string Path { get; set; }
  24. public Version Version { get; set; }
  25. public bool IsPrerelease { get; set; }
  26. public bool SupportsAnalyzers
  27. {
  28. get
  29. {
  30. if (VisualStudioEditor.IsWindows)
  31. return Version >= new Version(16, 3);
  32. if (VisualStudioEditor.IsOSX)
  33. return Version >= new Version(8, 3);
  34. return false;
  35. }
  36. }
  37. // C# language version support for Visual Studio
  38. private static VersionPair[] WindowsVersionTable =
  39. {
  40. // VisualStudio 2019
  41. new VersionPair(16,8, /* => */ 9,0),
  42. new VersionPair(16,0, /* => */ 8,0),
  43. // VisualStudio 2017
  44. new VersionPair(15,7, /* => */ 7,3),
  45. new VersionPair(15,5, /* => */ 7,2),
  46. new VersionPair(15,3, /* => */ 7,1),
  47. new VersionPair(15,0, /* => */ 7,0),
  48. };
  49. // C# language version support for Visual Studio for Mac
  50. private static VersionPair[] OSXVersionTable =
  51. {
  52. // VisualStudio for Mac 8.x
  53. new VersionPair(8,8, /* => */ 9,0),
  54. new VersionPair(8,3, /* => */ 8,0),
  55. new VersionPair(8,0, /* => */ 7,3),
  56. };
  57. public Version LatestLanguageVersionSupported
  58. {
  59. get
  60. {
  61. VersionPair[] versions = null;
  62. if (VisualStudioEditor.IsWindows)
  63. versions = WindowsVersionTable;
  64. if (VisualStudioEditor.IsOSX)
  65. versions = OSXVersionTable;
  66. if (versions != null)
  67. {
  68. foreach (var entry in versions)
  69. {
  70. if (Version >= entry.IdeVersion)
  71. return entry.LanguageVersion;
  72. }
  73. }
  74. // default to 7.0 given we support at least VS 2017
  75. return new Version(7, 0);
  76. }
  77. }
  78. private static string ReadRegistry(RegistryKey hive, string keyName, string valueName)
  79. {
  80. try
  81. {
  82. var unitykey = hive.OpenSubKey(keyName);
  83. var result = (string)unitykey?.GetValue(valueName);
  84. return result;
  85. }
  86. catch (Exception)
  87. {
  88. return null;
  89. }
  90. }
  91. private string GetWindowsBridgeFromRegistry()
  92. {
  93. var keyName = $"Software\\Microsoft\\Microsoft Visual Studio {Version.Major}.0 Tools for Unity";
  94. const string valueName = "UnityExtensionPath";
  95. var bridge = ReadRegistry(Registry.CurrentUser, keyName, valueName);
  96. if (string.IsNullOrEmpty(bridge))
  97. bridge = ReadRegistry(Registry.LocalMachine, keyName, valueName);
  98. return bridge;
  99. }
  100. // We only use this to find analyzers, we do not need to load this assembly anymore
  101. private string GetExtensionPath()
  102. {
  103. if (VisualStudioEditor.IsWindows)
  104. {
  105. const string extensionName = "Visual Studio Tools for Unity";
  106. const string extensionAssembly = "SyntaxTree.VisualStudio.Unity.dll";
  107. var vsDirectory = IOPath.GetDirectoryName(Path);
  108. var vstuDirectory = IOPath.Combine(vsDirectory, "Extensions", "Microsoft", extensionName);
  109. if (File.Exists(IOPath.Combine(vstuDirectory, extensionAssembly)))
  110. return vstuDirectory;
  111. }
  112. if (VisualStudioEditor.IsOSX)
  113. {
  114. const string addinName = "MonoDevelop.Unity";
  115. const string addinAssembly = addinName + ".dll";
  116. // user addins repository
  117. var localAddins = IOPath.Combine(
  118. Environment.GetFolderPath(Environment.SpecialFolder.Personal),
  119. $"Library/Application Support/VisualStudio/${Version.Major}.0" + "/LocalInstall/Addins");
  120. // In the user addins repository, the addins are suffixed by their versions, like `MonoDevelop.Unity.1.0`
  121. // When installing another local user addin, MD will remove files inside the folder
  122. // So we browse all VSTUM addins, and return the one with an addin assembly
  123. if (Directory.Exists(localAddins))
  124. {
  125. foreach (var folder in Directory.GetDirectories(localAddins, addinName + "*", SearchOption.TopDirectoryOnly))
  126. {
  127. if (File.Exists(IOPath.Combine(folder, addinAssembly)))
  128. return folder;
  129. }
  130. }
  131. // Check in Visual Studio.app/
  132. // In that case the name of the addin is used
  133. var addinPath = IOPath.Combine(Path, $"Contents/Resources/lib/monodevelop/AddIns/{addinName}");
  134. if (File.Exists(IOPath.Combine(addinPath, addinAssembly)))
  135. return addinPath;
  136. addinPath = IOPath.Combine(Path, $"Contents/MonoBundle/Addins/{addinName}");
  137. if (File.Exists(IOPath.Combine(addinPath, addinAssembly)))
  138. return addinPath;
  139. }
  140. return null;
  141. }
  142. private static string[] GetAnalyzers(string path)
  143. {
  144. var analyzersDirectory = IOPath.GetFullPath(IOPath.Combine(path, "Analyzers"));
  145. if (Directory.Exists(analyzersDirectory))
  146. return Directory.GetFiles(analyzersDirectory, "*Analyzers.dll", SearchOption.AllDirectories);
  147. return Array.Empty<string>();
  148. }
  149. public string[] GetAnalyzers()
  150. {
  151. var vstuPath = GetExtensionPath();
  152. if (string.IsNullOrEmpty(vstuPath))
  153. return Array.Empty<string>();
  154. if (VisualStudioEditor.IsOSX)
  155. return GetAnalyzers(vstuPath);
  156. if (VisualStudioEditor.IsWindows)
  157. {
  158. var analyzers = GetAnalyzers(vstuPath);
  159. if (analyzers?.Length > 0)
  160. return analyzers;
  161. var bridge = GetWindowsBridgeFromRegistry();
  162. if (File.Exists(bridge))
  163. return GetAnalyzers(IOPath.Combine(IOPath.GetDirectoryName(bridge), ".."));
  164. }
  165. // Local assets
  166. // return FileUtility.FindPackageAssetFullPath("Analyzers a:packages", ".Analyzers.dll");
  167. return Array.Empty<string>();
  168. }
  169. public CodeEditor.Installation ToCodeEditorInstallation()
  170. {
  171. return new CodeEditor.Installation() { Name = Name, Path = Path };
  172. }
  173. }
  174. }