diff --git a/CHANGELOG.md b/CHANGELOG.md index 15701ec..32f1b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,34 @@ # Code Editor Package for Visual Studio +## [2.0.9] - 2021-05-04 + +Project generation: + +Added support for CLI. + +Integration: + +Improved performance when discovering Visual Studio installations. +Warn when legacy assemblies are present in the project. +Warn when the package version is not up-to-date. + + ## [2.0.8] - 2021-04-09 Project generation: -Improved generation performance (especially with DOTS enabled projects). -Improved stability. -Updated Analyzers lookup strategy. -Fixed .vsconfig file not generated when using "regenerate all". +- Improved generation performance (especially with DOTS enabled projects). +- Improved stability. +- Updated Analyzers lookup strategy. +- Fixed .vsconfig file not generated when using "regenerate all". -Integration +Integration: -Improved automation plugins. +- Improved automation plugins. -Documentation - -Open sourced automation plugins. +Documentation: +- Open sourced automation plugins. ## [2.0.7] - 2021-02-02 @@ -24,7 +36,6 @@ Integration: - Remove com.unity.nuget.newtonsoft-json dependency in favor of the built-in JsonUtility for the VS Test Runner. - ## [2.0.6] - 2021-01-20 Project generation: diff --git a/Editor/AsyncOperation.cs b/Editor/AsyncOperation.cs new file mode 100644 index 0000000..0644347 --- /dev/null +++ b/Editor/AsyncOperation.cs @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Unity Technologies. + * 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.Threading; + +namespace Microsoft.Unity.VisualStudio.Editor +{ + internal class AsyncOperation + { + private readonly Func _producer; + private readonly ManualResetEventSlim _resetEvent; + + private T _result; + private Exception _exception; + + private AsyncOperation(Func producer) + { + _producer = producer; + _resetEvent = new ManualResetEventSlim(initialState: false); + } + + public static AsyncOperation Run(Func producer) + { + var task = new AsyncOperation(producer); + task.Run(); + return task; + } + + private void Run() + { + ThreadPool.QueueUserWorkItem(_ => + { + try + { + _result = _producer(); + } + catch (Exception e) + { + _exception = e; + } + finally + { + _resetEvent.Set(); + } + }); + } + + private void CheckCompletion() + { + if (!_resetEvent.IsSet) + _resetEvent.Wait(); + } + + + public T Result + { + get + { + CheckCompletion(); + return _result; + } + } + + public Exception Exception + { + get + { + CheckCompletion(); + return _exception; + } + } + } +} diff --git a/Editor/AsyncOperation.cs.meta b/Editor/AsyncOperation.cs.meta new file mode 100644 index 0000000..e304746 --- /dev/null +++ b/Editor/AsyncOperation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c6c8b2f6152bd1348ae35f9f95719f75 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Cli.cs b/Editor/Cli.cs new file mode 100644 index 0000000..f941551 --- /dev/null +++ b/Editor/Cli.cs @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * 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.Linq; +using Unity.CodeEditor; + +namespace Microsoft.Unity.VisualStudio.Editor +{ + internal static class Cli + { + internal static void Log(string message) + { + // Use writeline here, instead of UnityEngine.Debug.Log to not include the stacktrace in the editor.log + Console.WriteLine($"[VisualStudio.Editor.{nameof(Cli)}] {message}"); + } + + internal static string GetInstallationDetails(IVisualStudioInstallation installation) + { + return $"{installation.ToCodeEditorInstallation().Name} Path:{installation.Path}, LanguageVersionSupport:{installation.LatestLanguageVersionSupported} AnalyzersSupport:{installation.SupportsAnalyzers}"; + } + + internal static void GenerateSolutionWith(VisualStudioEditor vse, string installationPath) + { + if (vse != null && vse.TryGetVisualStudioInstallationForPath(installationPath, searchInstallations: true, out var vsi)) + { + Log($"Using {GetInstallationDetails(vsi)}"); + vse.SyncAll(); + } + else + { + Log($"No Visual Studio installation found in ${installationPath}!"); + } + } + + internal static void GenerateSolution() + { + if (CodeEditor.CurrentEditor is VisualStudioEditor vse) + { + Log($"Using default editor settings for Visual Studio installation"); + GenerateSolutionWith(vse, CodeEditor.CurrentEditorInstallation); + } + else + { + Log($"Visual Studio is not set as your default editor, looking for installations"); + try + { + var installations = Discovery + .GetVisualStudioInstallations() + .Cast() + .OrderByDescending(vsi => !vsi.IsPrerelease) + .ThenBy(vsi => vsi.Version) + .ToArray(); + + foreach(var vsi in installations) + { + Log($"Detected {GetInstallationDetails(vsi)}"); + } + + var installation = installations + .FirstOrDefault(); + + if (installation != null) + { + var current = CodeEditor.CurrentEditorInstallation; + try + { + CodeEditor.SetExternalScriptEditor(installation.Path); + GenerateSolutionWith(CodeEditor.CurrentEditor as VisualStudioEditor, installation.Path); + } + finally + { + CodeEditor.SetExternalScriptEditor(current); + } + } else + { + Log($"No Visual Studio installation found!"); + } + } + catch (Exception ex) + { + Log($"Error detecting Visual Studio installations: {ex}"); + } + } + } + } +} diff --git a/Editor/Cli.cs.meta b/Editor/Cli.cs.meta new file mode 100644 index 0000000..74d2884 --- /dev/null +++ b/Editor/Cli.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7b5530092b3a7646bdc7865f1f6ee94 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Discovery.cs b/Editor/Discovery.cs index 893ed3a..6c4a676 100644 --- a/Editor/Discovery.cs +++ b/Editor/Discovery.cs @@ -6,10 +6,10 @@ using System; using System.IO; using System.Collections.Generic; -using UnityEngine; using System.Diagnostics; using System.Text.RegularExpressions; using System.Linq; +using UnityEngine; namespace Microsoft.Unity.VisualStudio.Editor { @@ -17,6 +17,14 @@ namespace Microsoft.Unity.VisualStudio.Editor { internal const string ManagedWorkload = "Microsoft.VisualStudio.Workload.ManagedGame"; + internal static string _vsWherePath; + + public static void FindVSWhere() + { + _vsWherePath = FileUtility + .FindPackageAssetFullPath("VSWhere a:packages", "vswhere.exe") + .FirstOrDefault(); + } public static IEnumerable GetVisualStudioInstallations() { @@ -37,7 +45,7 @@ namespace Microsoft.Unity.VisualStudio.Editor } } - private static bool IsCandidateToDiscovery(string path) + private static bool IsCandidateForDiscovery(string path) { if (File.Exists(path) && VisualStudioEditor.IsWindows && Regex.IsMatch(path, "devenv.exe$", RegexOptions.IgnoreCase)) return true; @@ -55,7 +63,7 @@ namespace Microsoft.Unity.VisualStudio.Editor if (string.IsNullOrEmpty(editorPath)) return false; - if (!IsCandidateToDiscovery(editorPath)) + if (!IsCandidateForDiscovery(editorPath)) return false; // On windows we use the executable directly, so we can query extra information @@ -131,9 +139,7 @@ namespace Microsoft.Unity.VisualStudio.Editor private static IEnumerable QueryVsWhere() { - var progpath = FileUtility - .FindPackageAssetFullPath("VSWhere a:packages", "vswhere.exe") - .FirstOrDefault(); + var progpath = _vsWherePath; if (string.IsNullOrWhiteSpace(progpath)) return Enumerable.Empty(); diff --git a/Editor/KnownAssemblies.cs b/Editor/KnownAssemblies.cs new file mode 100644 index 0000000..2cd7ba1 --- /dev/null +++ b/Editor/KnownAssemblies.cs @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +namespace Microsoft.Unity.VisualStudio.Editor +{ + internal static class KnownAssemblies + { + public const string Bridge = "SyntaxTree.VisualStudio.Unity.Bridge"; + public const string Messaging = "SyntaxTree.VisualStudio.Unity.Messaging"; + public const string UnityVS = "UnityVS.VersionSpecific"; + } +} diff --git a/Editor/KnownAssemblies.cs.meta b/Editor/KnownAssemblies.cs.meta new file mode 100644 index 0000000..9e2d1ca --- /dev/null +++ b/Editor/KnownAssemblies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cbccb6292dce08a489e6e742243154e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Plugins/AppleEventIntegration.bundle/Contents/Info.plist b/Editor/Plugins/AppleEventIntegration.bundle/Contents/Info.plist index 9d34eaf..3d8975b 100644 --- a/Editor/Plugins/AppleEventIntegration.bundle/Contents/Info.plist +++ b/Editor/Plugins/AppleEventIntegration.bundle/Contents/Info.plist @@ -3,7 +3,7 @@ BuildMachineOSBuild - 19H2 + 19H1030 CFBundleDevelopmentRegion en CFBundleExecutable @@ -27,19 +27,19 @@ DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild - 12A7300 + 12D4e DTPlatformName macosx DTPlatformVersion - 10.15.6 + 11.1 DTSDKBuild - 19G68 + 20C63 DTSDKName - macosx10.15 + macosx11.1 DTXcode - 1201 + 1240 DTXcodeBuild - 12A7300 + 12D4e LSMinimumSystemVersion 10.13 NSHumanReadableCopyright diff --git a/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration b/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration index 2b35f07..3ffd3e0 100644 Binary files a/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration and b/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration differ diff --git a/Editor/ProjectGeneration/ProjectGeneration.cs b/Editor/ProjectGeneration/ProjectGeneration.cs index 2e61e10..7a12909 100644 --- a/Editor/ProjectGeneration/ProjectGeneration.cs +++ b/Editor/ProjectGeneration/ProjectGeneration.cs @@ -148,7 +148,7 @@ namespace Microsoft.Unity.VisualStudio.Editor private void RefreshCurrentInstallation() { var editor = CodeEditor.CurrentEditor as VisualStudioEditor; - editor?.TryGetVisualStudioInstallationForPath(CodeEditor.CurrentEditorInstallation, out m_CurrentInstallation); + editor?.TryGetVisualStudioInstallationForPath(CodeEditor.CurrentEditorInstallation, searchInstallations: true, out m_CurrentInstallation); } public void Sync() @@ -370,7 +370,7 @@ namespace Microsoft.Unity.VisualStudio.Editor { return TypeCache .GetTypesDerivedFrom() - .Where(t => t.Assembly.GetName().Name != "SyntaxTree.VisualStudio.Unity.Bridge") // never call into the bridge if loaded with the package + .Where(t => t.Assembly.GetName().Name != KnownAssemblies.Bridge) // never call into the bridge if loaded with the package .Select(t => t.GetMethod(name, SR.BindingFlags.Public | SR.BindingFlags.NonPublic | SR.BindingFlags.Static)) .Where(m => m != null); } diff --git a/Editor/VisualStudioEditor.cs b/Editor/VisualStudioEditor.cs index 3ba37cf..60f5410 100644 --- a/Editor/VisualStudioEditor.cs +++ b/Editor/VisualStudioEditor.cs @@ -7,11 +7,11 @@ using System; using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; using UnityEditor; using UnityEngine; using Unity.CodeEditor; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Unity.VisualStudio.EditorTests")] [assembly: InternalsVisibleTo("Unity.VisualStudio.Standalone.EditorTests")] @@ -22,42 +22,44 @@ namespace Microsoft.Unity.VisualStudio.Editor [InitializeOnLoad] public class VisualStudioEditor : IExternalCodeEditor { - private static readonly IVisualStudioInstallation[] _installations; - internal static bool IsOSX => Application.platform == RuntimePlatform.OSXEditor; internal static bool IsWindows => !IsOSX && Path.DirectorySeparatorChar == FileUtility.WinSeparator && Environment.NewLine == "\r\n"; - CodeEditor.Installation[] IExternalCodeEditor.Installations => _installations + CodeEditor.Installation[] IExternalCodeEditor.Installations => _discoverInstallations.Result .Select(i => i.ToCodeEditorInstallation()) .ToArray(); + private static readonly AsyncOperation _discoverInstallations; + private readonly IGenerator _generator = new ProjectGeneration(); static VisualStudioEditor() + { + if (IsWindows) + Discovery.FindVSWhere(); + + CodeEditor.Register(new VisualStudioEditor()); + + _discoverInstallations = AsyncOperation.Run(DiscoverInstallations); + } + + private static IVisualStudioInstallation[] DiscoverInstallations() { try { - _installations = Discovery + return Discovery .GetVisualStudioInstallations() .ToArray(); } catch (Exception ex) { UnityEngine.Debug.LogError($"Error detecting Visual Studio installations: {ex}"); - _installations = Array.Empty(); - } - - CodeEditor.Register(new VisualStudioEditor()); - } - - internal static bool IsEnabled - { - get - { - return CodeEditor.CurrentEditor is VisualStudioEditor; + return Array.Empty(); } } + internal static bool IsEnabled => CodeEditor.CurrentEditor is VisualStudioEditor; + public void CreateIfDoesntExist() { if (!_generator.HasSolutionBeenGenerated()) @@ -68,16 +70,19 @@ namespace Microsoft.Unity.VisualStudio.Editor { } - internal virtual bool TryGetVisualStudioInstallationForPath(string editorPath, out IVisualStudioInstallation installation) + internal virtual bool TryGetVisualStudioInstallationForPath(string editorPath, bool searchInstallations, out IVisualStudioInstallation installation) { - // lookup for well known installations - foreach (var candidate in _installations) + if (searchInstallations) { - if (!string.Equals(Path.GetFullPath(editorPath), Path.GetFullPath(candidate.Path), StringComparison.OrdinalIgnoreCase)) - continue; + // lookup for well known installations + foreach (var candidate in _discoverInstallations.Result) + { + if (!string.Equals(Path.GetFullPath(editorPath), Path.GetFullPath(candidate.Path), StringComparison.OrdinalIgnoreCase)) + continue; - installation = candidate; - return true; + installation = candidate; + return true; + } } return Discovery.TryDiscoverInstallation(editorPath, out installation); @@ -85,7 +90,7 @@ namespace Microsoft.Unity.VisualStudio.Editor public virtual bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation) { - var result = TryGetVisualStudioInstallationForPath(editorPath, out var vsi); + var result = TryGetVisualStudioInstallationForPath(editorPath, searchInstallations: false, out var vsi); installation = vsi == null ? default : vsi.ToCodeEditorInstallation(); return result; } diff --git a/Editor/VisualStudioIntegration.cs b/Editor/VisualStudioIntegration.cs index 08616ab..50fbaf5 100644 --- a/Editor/VisualStudioIntegration.cs +++ b/Editor/VisualStudioIntegration.cs @@ -11,6 +11,8 @@ using System.Net.Sockets; using Microsoft.Unity.VisualStudio.Editor.Messaging; using Microsoft.Unity.VisualStudio.Editor.Testing; using UnityEditor; +using UnityEditor.PackageManager; +using UnityEditor.PackageManager.Requests; using UnityEngine; using MessageType = Microsoft.Unity.VisualStudio.Editor.Messaging.MessageType; @@ -32,11 +34,15 @@ namespace Microsoft.Unity.VisualStudio.Editor private static readonly object _incomingLock = new object(); private static readonly object _clientsLock = new object(); + private static ListRequest _listRequest; + static VisualStudioIntegration() { if (!VisualStudioEditor.IsEnabled) return; + _listRequest = UnityEditor.PackageManager.Client.List(); + RunOnceOnUpdate(() => { // Despite using ReuseAddress|!ExclusiveAddressUse, we can fail here: @@ -59,6 +65,35 @@ namespace Microsoft.Unity.VisualStudio.Editor }); EditorApplication.update += OnUpdate; + + CheckLegacyAssemblies(); + } + + private static void CheckLegacyAssemblies() + { + var checkList = new HashSet(new[] { KnownAssemblies.UnityVS, KnownAssemblies.Messaging, KnownAssemblies.Bridge }); + + try + { + var assemblies = AppDomain + .CurrentDomain + .GetAssemblies() + .Where(a => checkList.Contains(a.GetName().Name)); + + foreach (var assembly in assemblies) + { + // for now we only want to warn against local assemblies, do not check externals. + var relativePath = FileUtility.MakeRelativeToProjectPath(assembly.Location); + if (relativePath == null) + continue; + + Debug.LogWarning($"Project contains legacy assembly that could interfere with the Visual Studio Package. You should delete {relativePath}"); + } + } + catch (Exception) + { + // abandon legacy check + } } private static void RunOnceOnUpdate(Action action) @@ -98,8 +133,33 @@ namespace Microsoft.Unity.VisualStudio.Editor OnMessage(args.Message); } + private static void HandleListRequestCompletion() + { + const string packageName = "com.unity.ide.visualstudio"; + + if (_listRequest.Status == StatusCode.Success) + { + var package = _listRequest.Result.FirstOrDefault(p => p.name == packageName); + + if (package != null + && Version.TryParse(package.version, out var packageVersion) + && Version.TryParse(package.versions.latest, out var latestVersion) + && packageVersion < latestVersion) + { + Debug.LogWarning($"Visual Studio Editor Package version {package.versions.latest} is available, we strongly encourage you to update from the Unity Package Manager for a better Visual Studio integration"); + } + } + + _listRequest = null; + } + private static void OnUpdate() { + if (_listRequest != null && _listRequest.IsCompleted) + { + HandleListRequestCompletion(); + } + lock (_incomingLock) { while (_incoming.Count > 0) diff --git a/package.json b/package.json index db7b9b9..60df51b 100644 --- a/package.json +++ b/package.json @@ -2,21 +2,21 @@ "name": "com.unity.ide.visualstudio", "displayName": "Visual Studio Editor", "description": "Code editor integration for supporting Visual Studio as code editor for unity. Adds support for generating csproj files for intellisense purposes, auto discovery of installations, etc.", - "version": "2.0.8", + "version": "2.0.9", "unity": "2019.4", "unityRelease": "21f1", "dependencies": { "com.unity.test-framework": "1.1.9" }, "relatedPackages": { - "com.unity.ide.visualstudio.tests": "2.0.8" + "com.unity.ide.visualstudio.tests": "2.0.9" }, "upmCi": { - "footprint": "c51cc3db630d4f769464e8f12c2154af3ca5242c" + "footprint": "b1cf463b7fca9fa8a65053f6120bce69640f5cea" }, "repository": { "url": "https://github.cds.internal.unity3d.com/unity/com.unity.ide.visualstudio.git", "type": "git", - "revision": "5cdf5d932e6ecbbd83bbe6a40eec2a94b4979501" + "revision": "c7cd4c6319806423b3d918b3e9599244e3993d41" } }