/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ using System; using System.IO; using Microsoft.Win32; using Unity.CodeEditor; using IOPath = System.IO.Path; namespace Microsoft.Unity.VisualStudio.Editor { internal interface IVisualStudioInstallation { string Path { get; } bool SupportsAnalyzers { get; } Version LatestLanguageVersionSupported { get; } string[] GetAnalyzers(); CodeEditor.Installation ToCodeEditorInstallation(); } internal class VisualStudioInstallation : IVisualStudioInstallation { public string Name { get; set; } public string Path { get; set; } public Version Version { get; set; } public bool IsPrerelease { get; set; } public bool SupportsAnalyzers { get { if (VisualStudioEditor.IsWindows) return Version >= new Version(16, 3); if (VisualStudioEditor.IsOSX) return Version >= new Version(8, 3); return false; } } // C# language version support for Visual Studio private static VersionPair[] WindowsVersionTable = { // VisualStudio 2022 new VersionPair(17,4, /* => */ 11,0), new VersionPair(17,0, /* => */ 10,0), // VisualStudio 2019 new VersionPair(16,8, /* => */ 9,0), new VersionPair(16,0, /* => */ 8,0), // VisualStudio 2017 new VersionPair(15,7, /* => */ 7,3), new VersionPair(15,5, /* => */ 7,2), new VersionPair(15,3, /* => */ 7,1), new VersionPair(15,0, /* => */ 7,0), }; // C# language version support for Visual Studio for Mac private static VersionPair[] OSXVersionTable = { // VisualStudio for Mac 2022 new VersionPair(17,4, /* => */ 11,0), new VersionPair(17,0, /* => */ 10,0), // VisualStudio for Mac 8.x new VersionPair(8,8, /* => */ 9,0), new VersionPair(8,3, /* => */ 8,0), new VersionPair(8,0, /* => */ 7,3), }; public Version LatestLanguageVersionSupported { get { VersionPair[] versions = null; if (VisualStudioEditor.IsWindows) versions = WindowsVersionTable; if (VisualStudioEditor.IsOSX) versions = OSXVersionTable; if (versions != null) { foreach (var entry in versions) { if (Version >= entry.IdeVersion) return entry.LanguageVersion; } } // default to 7.0 given we support at least VS 2017 return new Version(7, 0); } } private static string ReadRegistry(RegistryKey hive, string keyName, string valueName) { try { var unitykey = hive.OpenSubKey(keyName); var result = (string)unitykey?.GetValue(valueName); return result; } catch (Exception) { return null; } } private string GetWindowsBridgeFromRegistry() { var keyName = $"Software\\Microsoft\\Microsoft Visual Studio {Version.Major}.0 Tools for Unity"; const string valueName = "UnityExtensionPath"; var bridge = ReadRegistry(Registry.CurrentUser, keyName, valueName); if (string.IsNullOrEmpty(bridge)) bridge = ReadRegistry(Registry.LocalMachine, keyName, valueName); return bridge; } // We only use this to find analyzers, we do not need to load this assembly anymore private string GetExtensionPath() { if (VisualStudioEditor.IsWindows) { const string extensionName = "Visual Studio Tools for Unity"; const string extensionAssembly = "SyntaxTree.VisualStudio.Unity.dll"; var vsDirectory = IOPath.GetDirectoryName(Path); var vstuDirectory = IOPath.Combine(vsDirectory, "Extensions", "Microsoft", extensionName); if (File.Exists(IOPath.Combine(vstuDirectory, extensionAssembly))) return vstuDirectory; } if (VisualStudioEditor.IsOSX) { const string addinName = "MonoDevelop.Unity"; const string addinAssembly = addinName + ".dll"; // user addins repository var localAddins = IOPath.Combine( Environment.GetFolderPath(Environment.SpecialFolder.Personal), $"Library/Application Support/VisualStudio/${Version.Major}.0" + "/LocalInstall/Addins"); // In the user addins repository, the addins are suffixed by their versions, like `MonoDevelop.Unity.1.0` // When installing another local user addin, MD will remove files inside the folder // So we browse all VSTUM addins, and return the one with an addin assembly if (Directory.Exists(localAddins)) { foreach (var folder in Directory.GetDirectories(localAddins, addinName + "*", SearchOption.TopDirectoryOnly)) { if (File.Exists(IOPath.Combine(folder, addinAssembly))) return folder; } } // Check in Visual Studio.app/ // In that case the name of the addin is used var addinPath = IOPath.Combine(Path, $"Contents/Resources/lib/monodevelop/AddIns/{addinName}"); if (File.Exists(IOPath.Combine(addinPath, addinAssembly))) return addinPath; addinPath = IOPath.Combine(Path, $"Contents/MonoBundle/Addins/{addinName}"); if (File.Exists(IOPath.Combine(addinPath, addinAssembly))) return addinPath; } return null; } private static string[] GetAnalyzers(string path) { var analyzersDirectory = IOPath.GetFullPath(IOPath.Combine(path, "Analyzers")); if (Directory.Exists(analyzersDirectory)) return Directory.GetFiles(analyzersDirectory, "*Analyzers.dll", SearchOption.AllDirectories); return Array.Empty(); } public string[] GetAnalyzers() { var vstuPath = GetExtensionPath(); if (string.IsNullOrEmpty(vstuPath)) return Array.Empty(); if (VisualStudioEditor.IsOSX) return GetAnalyzers(vstuPath); if (VisualStudioEditor.IsWindows) { var analyzers = GetAnalyzers(vstuPath); if (analyzers?.Length > 0) return analyzers; var bridge = GetWindowsBridgeFromRegistry(); if (File.Exists(bridge)) return GetAnalyzers(IOPath.Combine(IOPath.GetDirectoryName(bridge), "..")); } // Local assets // return FileUtility.FindPackageAssetFullPath("Analyzers a:packages", ".Analyzers.dll"); return Array.Empty(); } public CodeEditor.Installation ToCodeEditorInstallation() { return new CodeEditor.Installation() { Name = Name, Path = Path }; } } }