com.unity.ide.visualstudio@2.0.9

## [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.
This commit is contained in:
Unity Technologies
2021-05-04 00:00:00 +00:00
parent 06b02acf9c
commit b0f2251c48
14 changed files with 347 additions and 54 deletions

76
Editor/AsyncOperation.cs Normal file
View File

@@ -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<T>
{
private readonly Func<T> _producer;
private readonly ManualResetEventSlim _resetEvent;
private T _result;
private Exception _exception;
private AsyncOperation(Func<T> producer)
{
_producer = producer;
_resetEvent = new ManualResetEventSlim(initialState: false);
}
public static AsyncOperation<T> Run(Func<T> producer)
{
var task = new AsyncOperation<T>(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;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c6c8b2f6152bd1348ae35f9f95719f75
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

88
Editor/Cli.cs Normal file
View File

@@ -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<VisualStudioInstallation>()
.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}");
}
}
}
}
}

11
Editor/Cli.cs.meta Normal file
View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f7b5530092b3a7646bdc7865f1f6ee94
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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<IVisualStudioInstallation> 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<VisualStudioInstallation> QueryVsWhere()
{
var progpath = FileUtility
.FindPackageAssetFullPath("VSWhere a:packages", "vswhere.exe")
.FirstOrDefault();
var progpath = _vsWherePath;
if (string.IsNullOrWhiteSpace(progpath))
return Enumerable.Empty<VisualStudioInstallation>();

14
Editor/KnownAssemblies.cs Normal file
View File

@@ -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";
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cbccb6292dce08a489e6e742243154e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>19H2</string>
<string>19H1030</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
@@ -27,19 +27,19 @@
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>12A7300</string>
<string>12D4e</string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>10.15.6</string>
<string>11.1</string>
<key>DTSDKBuild</key>
<string>19G68</string>
<string>20C63</string>
<key>DTSDKName</key>
<string>macosx10.15</string>
<string>macosx11.1</string>
<key>DTXcode</key>
<string>1201</string>
<string>1240</string>
<key>DTXcodeBuild</key>
<string>12A7300</string>
<string>12D4e</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
<key>NSHumanReadableCopyright</key>

View File

@@ -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<AssetPostprocessor>()
.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);
}

View File

@@ -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<IVisualStudioInstallation[]> _discoverInstallations;
private readonly IGenerator _generator = new ProjectGeneration();
static VisualStudioEditor()
{
if (IsWindows)
Discovery.FindVSWhere();
CodeEditor.Register(new VisualStudioEditor());
_discoverInstallations = AsyncOperation<IVisualStudioInstallation[]>.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<VisualStudioInstallation>();
}
CodeEditor.Register(new VisualStudioEditor());
}
internal static bool IsEnabled
{
get
{
return CodeEditor.CurrentEditor is VisualStudioEditor;
return Array.Empty<VisualStudioInstallation>();
}
}
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;
}

View File

@@ -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<string>(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)