Initial Commit

This commit is contained in:
Sebastian Cabrera 2021-08-02 05:44:37 -04:00
parent 53eb92e9af
commit 270ab7d11f
15341 changed files with 700234 additions and 0 deletions

View file

@ -0,0 +1,125 @@
# Code Editor Package for Visual Studio
## [2.0.7] - 2021-02-02
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:
- Improved language version detection.
Integration:
- Added support for the VS Test Runner.
- Added initial support for displaying asset usage.
- Fixed remaining issues with special characters in file/path.
## [2.0.5] - 2020-10-30
Integration:
- Disable legacy pdb symbol checking for Unity packages.
## [2.0.4] - 2020-10-15
Project generation:
- Added support for embedded Roslyn analyzer DLLs and ruleset files.
- Warn the user when the opened script is not part of the generation scope.
- Warn the user when the selected Visual Studio installation is not found.
- Generate a .vsconfig file to ensure Visual Studio installation is compatible.
Integration:
- Fix automation issues on MacOS, where a new Visual Studio instance is opened every time.
## [2.0.3] - 2020-09-09
Project generation:
- Added C#8 language support.
- Added UnityProjectGeneratorVersion property.
- Local and Embedded packages are now selected by default for generation.
- Added support for asmdef root namespace.
Integration:
- When the user disabled auto-refresh in Unity, do not try to force refresh the Asset database.
- Fix Visual Studio detection issues with languages using special characters.
## [2.0.2] - 2020-05-27
- Added support for solution folders.
- Only bind the messenger when the VS editor is selected.
- Warn when unable to create the messenger.
- Fixed an initialization issue triggering legacy code generation.
- Allow package source in assembly to be generated when referenced from asmref.
## [2.0.1] - 2020-03-19
- When Visual Studio installation is compatible with C# 8.0, setup the language version to not prompt the user with unsupported constructs. (So far Unity only supports C# 7.3).
- Use Unity's TypeCache to improve project generation speed.
- Properly check for a managed assembly before displaying a warning regarding legacy PDB usage.
- Add support for selective project generation (embedded, local, registry, git, builtin, player).
## [2.0.0] - 2019-11-06
- Improved Visual Studio and Visual Studio for Mac automatic discovery.
- Added support for the VSTU messaging system (start/stop features from Visual Studio).
- Added support for solution roundtrip (preserves references to external projects and solution properties).
- Added support for VSTU Analyzers (requires Visual Studio 2019 16.3, Visual Studio for Mac 8.3).
- Added a warning when using legacy pdb symbol files.
- Fixed issues while Opening Visual Studio on Windows.
- Fixed issues while Opening Visual Studio on Mac.
## [1.1.1] - 2019-05-29
- Fix Bridge assembly loading with non VS2017 editors.
## [1.1.0] - 2019-05-27
- Move internal extension handling to package.
## [1.0.11] - 2019-05-21
- Fix detection of visual studio for mac installation.
## [1.0.10] - 2019-05-04
- Fix ignored comintegration executable.
## [1.0.9] - 2019-03-05
- Updated MonoDevelop support, to pass correct arguments, and not import VSTU plugin.
- Use release build of COMIntegration for Visual Studio.
## [1.0.7] - 2019-04-30
- Ensure asset database is refreshed when generating csproj and solution files.
## [1.0.6] - 2019-04-27
- Add support for generating all csproj files.
## [1.0.5] - 2019-04-18
- Fix relative package paths.
- Fix opening editor on mac.
## [1.0.4] - 2019-04-12
- Fixing null reference issue for callbacks to AssetPostProcessor.
- Ensure Path.GetFullPath does not get an empty string.
## [1.0.3] - 2019-01-01
### This is the first release of *Unity Package visualstudio_editor*.
- Using the newly created api to integrate Visual Studio with Unity.

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6022bdd5b2896a54f94123b812705321
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,6 @@
# Contributing
## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement) and [Microsoft Contributor License Agreement (CLA)](https://cla.opensource.microsoft.com/)
By making a pull request, you are confirming agreement to the terms and conditions of the UCA and CLA, including that your contributions are your original creation and that you have complete right and authority to make your contributions.
## Once you have a change ready following these ground rules. Simply make a pull request

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: cd78ce5b75b8be84d8b94cd99903161f
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4a707af59e3522d43afe34dfc4429b86
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("Unity.VisualStudio.EditorTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d791d407901442e49862d3aa783ce8af
timeCreated: 1602756877

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 36422aa067e092e45b9820da2ee3e728
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 41b2d972bdac29e4a89ef08b3b52c248
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: cb67edc1800c2ec4ba8dfb1cf0dfc84a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,168 @@
/*---------------------------------------------------------------------------------------------
* 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.IO;
using System.Collections.Generic;
using UnityEngine;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Linq;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class Discovery
{
internal const string ManagedWorkload = "Microsoft.VisualStudio.Workload.ManagedGame";
public static IEnumerable<IVisualStudioInstallation> GetVisualStudioInstallations()
{
if (VisualStudioEditor.IsWindows)
{
foreach (var installation in QueryVsWhere())
yield return installation;
}
if (VisualStudioEditor.IsOSX)
{
var candidates = Directory.EnumerateDirectories("/Applications", "*.app");
foreach (var candidate in candidates)
{
if (TryDiscoverInstallation(candidate, out var installation))
yield return installation;
}
}
}
private static bool IsCandidateToDiscovery(string path)
{
if (File.Exists(path) && VisualStudioEditor.IsWindows && Regex.IsMatch(path, "devenv.exe$", RegexOptions.IgnoreCase))
return true;
if (Directory.Exists(path) && VisualStudioEditor.IsOSX && Regex.IsMatch(path, "Visual\\s?Studio(?!.*Code.*).*.app$", RegexOptions.IgnoreCase))
return true;
return false;
}
public static bool TryDiscoverInstallation(string editorPath, out IVisualStudioInstallation installation)
{
installation = null;
if (string.IsNullOrEmpty(editorPath))
return false;
if (!IsCandidateToDiscovery(editorPath))
return false;
// On windows we use the executable directly, so we can query extra information
var fvi = editorPath;
// On Mac we use the .app folder, so we need to access to main assembly
if (VisualStudioEditor.IsOSX)
fvi = Path.Combine(editorPath, "Contents", "Resources", "lib", "monodevelop", "bin", "VisualStudio.exe");
if (!File.Exists(fvi))
return false;
// VS preview are not using the isPrerelease flag so far
// On Windows FileDescription contains "Preview", but not on Mac
var vi = FileVersionInfo.GetVersionInfo(fvi);
var version = new Version(vi.ProductVersion);
var isPrerelease = vi.IsPreRelease || string.Concat(editorPath, "/" + vi.FileDescription).ToLower().Contains("preview");
installation = new VisualStudioInstallation()
{
IsPrerelease = isPrerelease,
Name = $"{vi.FileDescription}{(isPrerelease && VisualStudioEditor.IsOSX ? " Preview" : string.Empty)} [{version.ToString(3)}]",
Path = editorPath,
Version = version
};
return true;
}
#region VsWhere Json Schema
#pragma warning disable CS0649
[Serializable]
internal class VsWhereResult
{
public VsWhereEntry[] entries;
public static VsWhereResult FromJson(string json)
{
return JsonUtility.FromJson<VsWhereResult>("{ \"" + nameof(VsWhereResult.entries) + "\": " + json + " }");
}
public IEnumerable<VisualStudioInstallation> ToVisualStudioInstallations()
{
foreach (var entry in entries)
{
yield return new VisualStudioInstallation()
{
Name = $"{entry.displayName} [{entry.catalog.productDisplayVersion}]",
Path = entry.productPath,
IsPrerelease = entry.isPrerelease,
Version = Version.Parse(entry.catalog.buildVersion)
};
}
}
}
[Serializable]
internal class VsWhereEntry
{
public string displayName;
public bool isPrerelease;
public string productPath;
public VsWhereCatalog catalog;
}
[Serializable]
internal class VsWhereCatalog
{
public string productDisplayVersion; // non parseable like "16.3.0 Preview 3.0"
public string buildVersion;
}
#pragma warning restore CS3021
#endregion
private static IEnumerable<VisualStudioInstallation> QueryVsWhere()
{
var progpath = FileUtility
.FindPackageAssetFullPath("VSWhere a:packages", "vswhere.exe")
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(progpath))
return Enumerable.Empty<VisualStudioInstallation>();
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = progpath,
Arguments = "-prerelease -format json -utf8",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
}
};
using (process)
{
var json = string.Empty;
process.OutputDataReceived += (o, e) => json += e.Data;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
var result = VsWhereResult.FromJson(json);
return result.ToVisualStudioInstallations();
}
}
}
}

View file

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

View file

@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* 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.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class FileUtility
{
public const char WinSeparator = '\\';
public const char UnixSeparator = '/';
// Safe for packages as we use packageInfo.resolvedPath, so this should work in library package cache as well
public static string[] FindPackageAssetFullPath(string assetfilter, string filefilter)
{
return AssetDatabase.FindAssets(assetfilter)
.Select(AssetDatabase.GUIDToAssetPath)
.Where(assetPath => assetPath.Contains(filefilter))
.Select(asset =>
{
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(asset);
return Normalize(packageInfo.resolvedPath + asset.Substring(packageInfo.assetPath.Length));
})
.ToArray();
}
public static string GetAssetFullPath(string asset)
{
var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
return Path.GetFullPath(Path.Combine(basePath, Normalize(asset)));
}
public static string Normalize(string path)
{
if (string.IsNullOrEmpty(path))
return path;
if (Path.DirectorySeparatorChar == WinSeparator)
path = path.Replace(UnixSeparator, WinSeparator);
if (Path.DirectorySeparatorChar == UnixSeparator)
path = path.Replace(WinSeparator, UnixSeparator);
return path.Replace(string.Concat(WinSeparator, WinSeparator), WinSeparator.ToString());
}
public static string NormalizeWindowsToUnix(string path)
{
if (string.IsNullOrEmpty(path))
return path;
return path.Replace(WinSeparator, UnixSeparator);
}
internal static bool IsFileInProjectRootDirectory(string fileName)
{
var relative = MakeRelativeToProjectPath(fileName);
if (string.IsNullOrEmpty(relative))
return false;
return relative == Path.GetFileName(relative);
}
// returns null if outside of the project scope
internal static string MakeRelativeToProjectPath(string fileName)
{
var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
fileName = Normalize(fileName);
if (!Path.IsPathRooted(fileName))
fileName = Path.Combine(basePath, fileName);
if (!fileName.StartsWith(basePath, StringComparison.OrdinalIgnoreCase))
return null;
return fileName
.Substring(basePath.Length)
.Trim(Path.DirectorySeparatorChar);
}
}
}

View file

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

View file

@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* 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;
namespace Microsoft.Unity.VisualStudio.Editor
{
public sealed class Image : IDisposable
{
long position;
Stream stream;
Image(Stream stream)
{
this.stream = stream;
this.position = stream.Position;
this.stream.Position = 0;
}
bool Advance(int length)
{
if (stream.Position + length >= stream.Length)
return false;
stream.Seek(length, SeekOrigin.Current);
return true;
}
bool MoveTo(uint position)
{
if (position >= stream.Length)
return false;
stream.Position = position;
return true;
}
void IDisposable.Dispose()
{
stream.Position = position;
}
ushort ReadUInt16()
{
return (ushort)(stream.ReadByte()
| (stream.ReadByte() << 8));
}
uint ReadUInt32()
{
return (uint)(stream.ReadByte()
| (stream.ReadByte() << 8)
| (stream.ReadByte() << 16)
| (stream.ReadByte() << 24));
}
bool IsManagedAssembly()
{
if (stream.Length < 318)
return false;
if (ReadUInt16() != 0x5a4d)
return false;
if (!Advance(58))
return false;
if (!MoveTo(ReadUInt32()))
return false;
if (ReadUInt32() != 0x00004550)
return false;
if (!Advance(20))
return false;
if (!Advance(ReadUInt16() == 0x20b ? 222 : 206))
return false;
return ReadUInt32() != 0;
}
public static bool IsAssembly(string file)
{
if (file == null)
throw new ArgumentNullException("file");
using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
return IsAssembly(stream);
}
public static bool IsAssembly(Stream stream)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
if (!stream.CanRead)
throw new ArgumentException(nameof(stream));
if (!stream.CanSeek)
throw new ArgumentException(nameof(stream));
using (var image = new Image(stream))
return image.IsManagedAssembly();
}
}
}

View file

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

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2f820130c86c28547a0f1a2f4c73155b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System.IO;
using System.Text;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class Deserializer
{
private readonly BinaryReader _reader;
public Deserializer(byte[] buffer)
{
_reader = new BinaryReader(new MemoryStream(buffer));
}
public int ReadInt32()
{
return _reader.ReadInt32();
}
public string ReadString()
{
var length = ReadInt32();
return length > 0
? Encoding.UTF8.GetString(_reader.ReadBytes(length))
: "";
}
public bool CanReadMore()
{
return _reader.BaseStream.Position < _reader.BaseStream.Length;
}
}
}

View file

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

View file

@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class ExceptionEventArgs
{
public Exception Exception { get; }
public ExceptionEventArgs(Exception exception)
{
Exception = exception;
}
}
}

View file

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

View file

@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System.Globalization;
using System.Net;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class Message
{
public MessageType Type { get; set; }
public string Value { get; set; }
public IPEndPoint Origin { get; set; }
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "<Message type:{0} value:{1}>", Type, Value);
}
}
}

View file

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

View file

@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* 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.Messaging
{
internal class MessageEventArgs
{
public Message Message
{
get;
}
public MessageEventArgs(Message message)
{
Message = message;
}
}
}

View file

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

View file

@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* 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.Messaging
{
internal enum MessageType
{
None = 0,
Ping,
Pong,
Play,
Stop,
Pause,
Unpause,
Build,
Refresh,
Info,
Error,
Warning,
Open,
Opened,
Version,
UpdatePackage,
ProjectPath,
// This message is a technical one for big messages, not intended to be used directly
Tcp,
RunStarted,
RunFinished,
TestStarted,
TestFinished,
TestListRetrieved,
RetrieveTestList,
ExecuteTests,
ShowUsage
}
}

View file

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

View file

@ -0,0 +1,220 @@
/*---------------------------------------------------------------------------------------------
* 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.Net;
using System.Net.Sockets;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class Messager : IDisposable
{
public event EventHandler<MessageEventArgs> ReceiveMessage;
public event EventHandler<ExceptionEventArgs> MessagerException;
private readonly UdpSocket _socket;
private readonly object _disposeLock = new object();
private bool _disposed;
protected Messager(int port)
{
_socket = new UdpSocket();
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false);
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_socket.Bind(IPAddress.Any, port);
BeginReceiveMessage();
}
private void BeginReceiveMessage()
{
var buffer = new byte[UdpSocket.BufferSize];
var any = UdpSocket.Any();
try
{
lock (_disposeLock)
{
if (_disposed)
return;
_socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref any, ReceiveMessageCallback, buffer);
}
}
catch (SocketException se)
{
MessagerException?.Invoke(this, new ExceptionEventArgs(se));
BeginReceiveMessage();
}
catch (ObjectDisposedException)
{
}
}
private void ReceiveMessageCallback(IAsyncResult result)
{
try
{
var endPoint = UdpSocket.Any();
lock (_disposeLock)
{
if (_disposed)
return;
_socket.EndReceiveFrom(result, ref endPoint);
}
var message = DeserializeMessage(UdpSocket.BufferFor(result));
if (message != null)
{
message.Origin = (IPEndPoint)endPoint;
int port;
int bufferSize;
if (IsValidTcpMessage(message, out port, out bufferSize))
{
// switch to TCP mode to handle big messages
TcpClient.Queue(message.Origin.Address, port, bufferSize, buffer =>
{
var originalMessage = DeserializeMessage(buffer);
originalMessage.Origin = message.Origin;
ReceiveMessage?.Invoke(this, new MessageEventArgs(originalMessage));
});
}
else
{
ReceiveMessage?.Invoke(this, new MessageEventArgs(message));
}
}
}
catch (ObjectDisposedException)
{
return;
}
catch (Exception e)
{
RaiseMessagerException(e);
}
BeginReceiveMessage();
}
private static bool IsValidTcpMessage(Message message, out int port, out int bufferSize)
{
port = 0;
bufferSize = 0;
if (message.Value == null)
return false;
if (message.Type != MessageType.Tcp)
return false;
var parts = message.Value.Split(':');
if (parts.Length != 2)
return false;
if (!int.TryParse(parts[0], out port))
return false;
return int.TryParse(parts[1], out bufferSize);
}
private void RaiseMessagerException(Exception e)
{
MessagerException?.Invoke(this, new ExceptionEventArgs(e));
}
private static Message MessageFor(MessageType type, string value)
{
return new Message { Type = type, Value = value };
}
public void SendMessage(IPEndPoint target, MessageType type, string value = "")
{
var message = MessageFor(type, value);
var buffer = SerializeMessage(message);
try
{
lock (_disposeLock)
{
if (_disposed)
return;
if (buffer.Length >= UdpSocket.BufferSize)
{
// switch to TCP mode to handle big messages
var port = TcpListener.Queue(buffer);
if (port > 0)
{
// success, replace original message with "switch to tcp" marker + port information + buffer length
message = MessageFor(MessageType.Tcp, string.Concat(port, ':', buffer.Length));
buffer = SerializeMessage(message);
}
}
_socket.BeginSendTo(buffer, 0, Math.Min(buffer.Length, UdpSocket.BufferSize), SocketFlags.None, target, SendMessageCallback, null);
}
}
catch (SocketException se)
{
MessagerException?.Invoke(this, new ExceptionEventArgs(se));
}
}
private void SendMessageCallback(IAsyncResult result)
{
try
{
lock (_disposeLock)
{
if (_disposed)
return;
_socket.EndSendTo(result);
}
}
catch (SocketException se)
{
MessagerException?.Invoke(this, new ExceptionEventArgs(se));
}
catch (ObjectDisposedException)
{
}
}
private static byte[] SerializeMessage(Message message)
{
var serializer = new Serializer();
serializer.WriteInt32((int)message.Type);
serializer.WriteString(message.Value);
return serializer.Buffer();
}
private static Message DeserializeMessage(byte[] buffer)
{
if (buffer.Length < 4)
return null;
var deserializer = new Deserializer(buffer);
var type = (MessageType)deserializer.ReadInt32();
var value = deserializer.ReadString();
return new Message { Type = type, Value = value };
}
public static Messager BindTo(int port)
{
return new Messager(port);
}
public void Dispose()
{
lock (_disposeLock)
{
_disposed = true;
_socket.Close();
}
}
}
}

View file

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

View file

@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System.IO;
using System.Text;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class Serializer
{
private readonly MemoryStream _stream;
private readonly BinaryWriter _writer;
public Serializer()
{
_stream = new MemoryStream();
_writer = new BinaryWriter(_stream);
}
public void WriteInt32(int i)
{
_writer.Write(i);
}
public void WriteString(string s)
{
var bytes = Encoding.UTF8.GetBytes(s ?? "");
if (bytes.Length > 0)
{
_writer.Write(bytes.Length);
_writer.Write(bytes);
}
else
_writer.Write(0);
}
public byte[] Buffer()
{
return _stream.ToArray();
}
}
}

View file

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

View file

@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* 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.Net;
using System.Net.Sockets;
using System.Threading;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class TcpClient
{
private const int ConnectOrReadTimeoutMilliseconds = 5000;
private class State
{
public System.Net.Sockets.TcpClient TcpClient;
public NetworkStream NetworkStream;
public byte[] Buffer;
public Action<byte[]> OnBufferAvailable;
}
public static void Queue(IPAddress address, int port, int bufferSize, Action<byte[]> onBufferAvailable)
{
var client = new System.Net.Sockets.TcpClient();
var state = new State {OnBufferAvailable = onBufferAvailable, TcpClient = client, Buffer = new byte[bufferSize]};
try
{
ThreadPool.QueueUserWorkItem(_ =>
{
var handle = client.BeginConnect(address, port, OnClientConnected, state);
if (!handle.AsyncWaitHandle.WaitOne(ConnectOrReadTimeoutMilliseconds))
Cleanup(state);
});
}
catch (Exception)
{
Cleanup(state);
}
}
private static void OnClientConnected(IAsyncResult result)
{
var state = (State)result.AsyncState;
try
{
state.TcpClient.EndConnect(result);
state.NetworkStream = state.TcpClient.GetStream();
var handle = state.NetworkStream.BeginRead(state.Buffer, 0, state.Buffer.Length, OnEndRead, state);
if (!handle.AsyncWaitHandle.WaitOne(ConnectOrReadTimeoutMilliseconds))
Cleanup(state);
}
catch (Exception)
{
Cleanup(state);
}
}
private static void OnEndRead(IAsyncResult result)
{
var state = (State)result.AsyncState;
try
{
var count = state.NetworkStream.EndRead(result);
if (count == state.Buffer.Length)
state.OnBufferAvailable?.Invoke(state.Buffer);
}
catch (Exception)
{
// Ignore and cleanup
}
finally
{
Cleanup(state);
}
}
private static void Cleanup(State state)
{
state.NetworkStream?.Dispose();
state.TcpClient?.Close();
state.NetworkStream = null;
state.TcpClient = null;
state.Buffer = null;
state.OnBufferAvailable = null;
}
}
}

View file

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

View file

@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* 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.Net;
using System.Threading;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class TcpListener
{
private const int ListenTimeoutMilliseconds = 5000;
private class State
{
public System.Net.Sockets.TcpListener TcpListener;
public byte[] Buffer;
}
public static int Queue(byte[] buffer)
{
var tcpListener = new System.Net.Sockets.TcpListener(IPAddress.Any, 0);
var state = new State {Buffer = buffer, TcpListener = tcpListener};
try
{
tcpListener.Start();
int port = ((IPEndPoint)tcpListener.LocalEndpoint).Port;
ThreadPool.QueueUserWorkItem(_ =>
{
bool listening = true;
while (listening)
{
var handle = tcpListener.BeginAcceptTcpClient(OnIncomingConnection, state);
listening = handle.AsyncWaitHandle.WaitOne(ListenTimeoutMilliseconds);
}
Cleanup(state);
});
return port;
}
catch (Exception)
{
Cleanup(state);
return -1;
}
}
private static void OnIncomingConnection(IAsyncResult result)
{
var state = (State)result.AsyncState;
try
{
using (var client = state.TcpListener.EndAcceptTcpClient(result))
{
using (var stream = client.GetStream())
{
stream.Write(state.Buffer, 0, state.Buffer.Length);
}
}
}
catch (Exception)
{
// Ignore and cleanup
}
}
private static void Cleanup(State state)
{
state.TcpListener?.Stop();
state.TcpListener = null;
state.Buffer = null;
}
}
}

View file

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

View file

@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* 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.Net;
using System.Net.Sockets;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class UdpSocket : Socket
{
// Maximum UDP payload is 65507 bytes.
// TCP mode will be used when the payload is bigger than this BufferSize
public const int BufferSize = 1024 * 8;
internal UdpSocket()
: base(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
{
SetIOControl();
}
public void Bind(IPAddress address, int port = 0)
{
Bind(new IPEndPoint(address ?? IPAddress.Any, port));
}
private void SetIOControl()
{
if (!VisualStudioEditor.IsWindows)
return;
try
{
const int SIO_UDP_CONNRESET = -1744830452;
IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, new byte[0]);
}
catch
{
// fallback
}
}
public static byte[] BufferFor(IAsyncResult result)
{
return (byte[])result.AsyncState;
}
public static EndPoint Any()
{
return new IPEndPoint(IPAddress.Any, 0);
}
}
}

View file

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

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1e5abb64fdd0542b38f4dc1b60343e8a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1 @@
AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration binary

View file

@ -0,0 +1,28 @@
fileFormatVersion: 2
guid: a20df6e3467b24ed5b49c857ce39e096
folderAsset: yes
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 543eb5eeeb1d5424ca8876b93fad5326
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>19H2</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>AppleEventIntegration</string>
<key>CFBundleIdentifier</key>
<string>com.unity.visualstudio.AppleEventIntegration</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>AppleEventIntegration</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>12A7300</string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>10.15.6</string>
<key>DTSDKBuild</key>
<string>19G68</string>
<key>DTSDKName</key>
<string>macosx10.15</string>
<key>DTXcode</key>
<string>1201</string>
<key>DTXcodeBuild</key>
<string>12A7300</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Unity. All rights reserved.</string>
</dict>
</plist>

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 29239d79a3471495e9d270601006dad7
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e811c7e1c1e9a4b50b237772d317959f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9c3599bc139404df2955d3ffd39d60d6
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 11ca2399a9422473eb66bca747f3ad52
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict/>
<key>files2</key>
<dict/>
<key>rules</key>
<dict>
<key>^Resources/</key>
<true/>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^.*</key>
<true/>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3379e8bd711774041a330f218af69b7a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8beeeeebc0857854d8b4e2c2895dd7a9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,190 @@
/*---------------------------------------------------------------------------------------------
* 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.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEditor.PackageManager;
namespace Microsoft.Unity.VisualStudio.Editor
{
public interface IAssemblyNameProvider
{
string[] ProjectSupportedExtensions { get; }
string ProjectGenerationRootNamespace { get; }
ProjectGenerationFlag ProjectGenerationFlag { get; }
string GetAssemblyNameFromScriptPath(string path);
string GetAssemblyName(string assemblyOutputPath, string assemblyName);
bool IsInternalizedPackagePath(string path);
IEnumerable<Assembly> GetAssemblies(Func<string, bool> shouldFileBePartOfSolution);
IEnumerable<string> GetAllAssetPaths();
UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath);
ResponseFileData ParseResponseFile(string responseFilePath, string projectDirectory, string[] systemReferenceDirectories);
void ToggleProjectGeneration(ProjectGenerationFlag preference);
}
public class AssemblyNameProvider : IAssemblyNameProvider
{
ProjectGenerationFlag m_ProjectGenerationFlag = (ProjectGenerationFlag)EditorPrefs.GetInt(
"unity_project_generation_flag",
(int)(ProjectGenerationFlag.Local | ProjectGenerationFlag.Embedded));
public string[] ProjectSupportedExtensions => EditorSettings.projectGenerationUserExtensions;
public string ProjectGenerationRootNamespace => EditorSettings.projectGenerationRootNamespace;
public ProjectGenerationFlag ProjectGenerationFlag
{
get => m_ProjectGenerationFlag;
private set
{
EditorPrefs.SetInt("unity_project_generation_flag", (int)value);
m_ProjectGenerationFlag = value;
}
}
public string GetAssemblyNameFromScriptPath(string path)
{
return CompilationPipeline.GetAssemblyNameFromScriptPath(path);
}
public IEnumerable<Assembly> GetAssemblies(Func<string, bool> shouldFileBePartOfSolution)
{
foreach (var assembly in CompilationPipeline.GetAssemblies())
{
if (assembly.sourceFiles.Any(shouldFileBePartOfSolution))
{
var options = new ScriptCompilerOptions
{
ResponseFiles = assembly.compilerOptions.ResponseFiles,
AllowUnsafeCode = assembly.compilerOptions.AllowUnsafeCode,
ApiCompatibilityLevel = assembly.compilerOptions.ApiCompatibilityLevel
};
yield return new Assembly(assembly.name, @"Temp\Bin\Debug\",
assembly.sourceFiles, new[] { "DEBUG", "TRACE" }.Concat(assembly.defines).Concat(EditorUserBuildSettings.activeScriptCompilationDefines).ToArray(),
assembly.assemblyReferences,
assembly.compiledAssemblyReferences,
assembly.flags,
#if UNITY_2020_2_OR_NEWER
options,
assembly.rootNamespace);
#else
options);
#endif
}
}
if (ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.PlayerAssemblies))
{
foreach (var assembly in CompilationPipeline.GetAssemblies(AssembliesType.Player).Where(assembly => assembly.sourceFiles.Any(shouldFileBePartOfSolution)))
{
var options = new ScriptCompilerOptions
{
ResponseFiles = assembly.compilerOptions.ResponseFiles,
AllowUnsafeCode = assembly.compilerOptions.AllowUnsafeCode,
ApiCompatibilityLevel = assembly.compilerOptions.ApiCompatibilityLevel
};
yield return
new Assembly(assembly.name, @"Temp\Bin\Debug\Player\",
assembly.sourceFiles,
new[] { "DEBUG", "TRACE" }.Concat(assembly.defines).ToArray(),
assembly.assemblyReferences,
assembly.compiledAssemblyReferences,
assembly.flags,
#if UNITY_2020_2_OR_NEWER
options,
assembly.rootNamespace);
#else
options);
#endif
}
}
}
public string GetCompileOutputPath(string assemblyName)
{
return assemblyName.EndsWith(".Player", StringComparison.Ordinal) ? @"Temp\Bin\Debug\Player\" : @"Temp\Bin\Debug\";
}
public IEnumerable<string> GetAllAssetPaths()
{
return AssetDatabase.GetAllAssetPaths();
}
public UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath)
{
return UnityEditor.PackageManager.PackageInfo.FindForAssetPath(assetPath);
}
public bool IsInternalizedPackagePath(string path)
{
if (string.IsNullOrEmpty(path.Trim()))
{
return false;
}
var packageInfo = FindForAssetPath(path);
if (packageInfo == null)
{
return false;
}
var packageSource = packageInfo.source;
switch (packageSource)
{
case PackageSource.Embedded:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Embedded);
case PackageSource.Registry:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Registry);
case PackageSource.BuiltIn:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.BuiltIn);
case PackageSource.Unknown:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Unknown);
case PackageSource.Local:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Local);
case PackageSource.Git:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Git);
case PackageSource.LocalTarball:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.LocalTarBall);
}
return false;
}
public ResponseFileData ParseResponseFile(string responseFilePath, string projectDirectory, string[] systemReferenceDirectories)
{
return CompilationPipeline.ParseResponseFile(
responseFilePath,
projectDirectory,
systemReferenceDirectories
);
}
public void ToggleProjectGeneration(ProjectGenerationFlag preference)
{
if (ProjectGenerationFlag.HasFlag(preference))
{
ProjectGenerationFlag ^= preference;
}
else
{
ProjectGenerationFlag |= preference;
}
}
public void ResetProjectGenerationFlag()
{
ProjectGenerationFlag = ProjectGenerationFlag.None;
}
public string GetAssemblyName(string assemblyOutputPath, string assemblyName)
{
return assemblyOutputPath.EndsWith(@"\Player\", StringComparison.Ordinal) ? assemblyName + ".Player" : assemblyName;
}
}
}

View file

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

View file

@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* 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.IO;
using System.Text;
namespace Microsoft.Unity.VisualStudio.Editor
{
public interface IFileIO
{
bool Exists(string fileName);
string ReadAllText(string fileName);
void WriteAllText(string fileName, string content);
}
class FileIOProvider : IFileIO
{
public bool Exists(string fileName)
{
return File.Exists(fileName);
}
public string ReadAllText(string fileName)
{
return File.ReadAllText(fileName);
}
public void WriteAllText(string fileName, string content)
{
File.WriteAllText(fileName, content, Encoding.UTF8);
}
}
}

View file

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

View file

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* 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.
*--------------------------------------------------------------------------------------------*/
namespace Microsoft.Unity.VisualStudio.Editor
{
public interface IGUIDGenerator
{
string ProjectGuid(string projectName, string assemblyName);
string SolutionGuid(string projectName, ScriptingLanguage scriptingLanguage);
}
class GUIDProvider : IGUIDGenerator
{
public string ProjectGuid(string projectName, string assemblyName)
{
return SolutionGuidGenerator.GuidForProject(projectName + assemblyName);
}
public string SolutionGuid(string projectName, ScriptingLanguage scriptingLanguage)
{
return SolutionGuidGenerator.GuidForSolution(projectName, scriptingLanguage);
}
}
}

View file

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

View file

@ -0,0 +1,993 @@
/*---------------------------------------------------------------------------------------------
* 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.Collections.Generic;
using System.IO;
using System.Linq;
using SR = System.Reflection;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Unity.CodeEditor;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEditorInternal;
using UnityEngine;
namespace Microsoft.Unity.VisualStudio.Editor
{
public enum ScriptingLanguage
{
None,
CSharp
}
public interface IGenerator
{
bool SyncIfNeeded(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles);
void Sync();
bool HasSolutionBeenGenerated();
bool IsSupportedFile(string path);
string SolutionFile();
string ProjectDirectory { get; }
IAssemblyNameProvider AssemblyNameProvider { get; }
}
public class ProjectGeneration : IGenerator
{
public static readonly string MSBuildNamespaceUri = "http://schemas.microsoft.com/developer/msbuild/2003";
public IAssemblyNameProvider AssemblyNameProvider => m_AssemblyNameProvider;
public string ProjectDirectory { get; }
const string k_WindowsNewline = "\r\n";
string m_SolutionProjectEntryTemplate = @"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""{4}EndProject";
string m_SolutionProjectConfigurationTemplate = string.Join("\r\n",
@" {{{0}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU",
@" {{{0}}}.Debug|Any CPU.Build.0 = Debug|Any CPU",
@" {{{0}}}.Release|Any CPU.ActiveCfg = Release|Any CPU",
@" {{{0}}}.Release|Any CPU.Build.0 = Release|Any CPU").Replace(" ", "\t");
static readonly string[] k_ReimportSyncExtensions = { ".dll", ".asmdef" };
static readonly Regex k_ScriptReferenceExpression = new Regex(
@"^Library.ScriptAssemblies.(?<dllname>(?<project>.*)\.dll$)",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
string[] m_ProjectSupportedExtensions = Array.Empty<string>();
string[] m_BuiltinSupportedExtensions = Array.Empty<string>();
readonly string m_ProjectName;
readonly IAssemblyNameProvider m_AssemblyNameProvider;
readonly IFileIO m_FileIOProvider;
readonly IGUIDGenerator m_GUIDGenerator;
bool m_ShouldGenerateAll;
IVisualStudioInstallation m_CurrentInstallation;
public ProjectGeneration() : this(Directory.GetParent(Application.dataPath).FullName)
{
}
public ProjectGeneration(string tempDirectory) : this(tempDirectory, new AssemblyNameProvider(), new FileIOProvider(), new GUIDProvider())
{
}
public ProjectGeneration(string tempDirectory, IAssemblyNameProvider assemblyNameProvider, IFileIO fileIoProvider, IGUIDGenerator guidGenerator)
{
ProjectDirectory = FileUtility.NormalizeWindowsToUnix(tempDirectory);
m_ProjectName = Path.GetFileName(ProjectDirectory);
m_AssemblyNameProvider = assemblyNameProvider;
m_FileIOProvider = fileIoProvider;
m_GUIDGenerator = guidGenerator;
SetupProjectSupportedExtensions();
}
/// <summary>
/// Syncs the scripting solution if any affected files are relevant.
/// </summary>
/// <returns>
/// Whether the solution was synced.
/// </returns>
/// <param name='affectedFiles'>
/// A set of files whose status has changed
/// </param>
/// <param name="reimportedFiles">
/// A set of files that got reimported
/// </param>
public bool SyncIfNeeded(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles)
{
SetupProjectSupportedExtensions();
// See https://devblogs.microsoft.com/setup/configure-visual-studio-across-your-organization-with-vsconfig/
// We create a .vsconfig file to make sure our ManagedGame workload is installed
CreateVsConfigIfNotFound();
// Don't sync if we haven't synced before
if (HasSolutionBeenGenerated() && HasFilesBeenModified(affectedFiles, reimportedFiles))
{
Sync();
return true;
}
return false;
}
private void CreateVsConfigIfNotFound()
{
try
{
var vsConfigFile = VsConfigFile();
if (m_FileIOProvider.Exists(vsConfigFile))
return;
var content = $@"{{
""version"": ""1.0"",
""components"": [
""{Discovery.ManagedWorkload}""
]
}}
";
m_FileIOProvider.WriteAllText(vsConfigFile, content);
}
catch (IOException)
{
}
}
private bool HasFilesBeenModified(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles)
{
return affectedFiles.Any(ShouldFileBePartOfSolution) || reimportedFiles.Any(ShouldSyncOnReimportedAsset);
}
private static bool ShouldSyncOnReimportedAsset(string asset)
{
return k_ReimportSyncExtensions.Contains(new FileInfo(asset).Extension);
}
private void RefreshCurrentInstallation()
{
var editor = CodeEditor.CurrentEditor as VisualStudioEditor;
editor?.TryGetVisualStudioInstallationForPath(CodeEditor.CurrentEditorInstallation, out m_CurrentInstallation);
}
public void Sync()
{
// We need the exact VS version/capabilities to tweak project generation (analyzers/langversion)
RefreshCurrentInstallation();
SetupProjectSupportedExtensions();
var externalCodeAlreadyGeneratedProjects = OnPreGeneratingCSProjectFiles();
if (!externalCodeAlreadyGeneratedProjects)
{
GenerateAndWriteSolutionAndProjects();
}
OnGeneratedCSProjectFiles();
}
public bool HasSolutionBeenGenerated()
{
return m_FileIOProvider.Exists(SolutionFile());
}
private void SetupProjectSupportedExtensions()
{
m_ProjectSupportedExtensions = m_AssemblyNameProvider.ProjectSupportedExtensions;
m_BuiltinSupportedExtensions = EditorSettings.projectGenerationBuiltinExtensions;
}
private bool ShouldFileBePartOfSolution(string file)
{
// Exclude files coming from packages except if they are internalized.
if (m_AssemblyNameProvider.IsInternalizedPackagePath(file))
{
return false;
}
return IsSupportedFile(file);
}
private static string GetExtensionWithoutDot(string path)
{
// Prevent re-processing and information loss
if (!Path.HasExtension(path))
return path;
return Path
.GetExtension(path)
.TrimStart('.')
.ToLower();
}
public bool IsSupportedFile(string path)
{
var extension = GetExtensionWithoutDot(path);
// Dll's are not scripts but still need to be included
if (extension == "dll")
return true;
if (extension == "asmdef")
return true;
if (m_BuiltinSupportedExtensions.Contains(extension))
return true;
if (m_ProjectSupportedExtensions.Contains(extension))
return true;
return false;
}
private static ScriptingLanguage ScriptingLanguageFor(Assembly assembly)
{
var files = assembly.sourceFiles;
if (files.Length == 0)
return ScriptingLanguage.None;
return ScriptingLanguageFor(files[0]);
}
internal static ScriptingLanguage ScriptingLanguageFor(string path)
{
return GetExtensionWithoutDot(path) == "cs" ? ScriptingLanguage.CSharp : ScriptingLanguage.None;
}
public void GenerateAndWriteSolutionAndProjects()
{
// Only synchronize assemblies that have associated source files and ones that we actually want in the project.
// This also filters out DLLs coming from .asmdef files in packages.
var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution);
var allAssetProjectParts = GenerateAllAssetProjectParts();
var assemblyList = assemblies.ToList();
SyncSolution(assemblyList);
var allProjectAssemblies = RelevantAssembliesForMode(assemblyList).ToList();
foreach (Assembly assembly in allProjectAssemblies)
{
SyncProject(assembly,
allAssetProjectParts,
responseFilesData: ParseResponseFileData(assembly),
allProjectAssemblies,
#if UNITY_2020_2_OR_NEWER
assembly.compilerOptions.RoslynAnalyzerDllPaths);
#else
Array.Empty<string>());
#endif
}
}
private IEnumerable<ResponseFileData> ParseResponseFileData(Assembly assembly)
{
var systemReferenceDirectories = CompilationPipeline.GetSystemAssemblyDirectories(assembly.compilerOptions.ApiCompatibilityLevel);
Dictionary<string, ResponseFileData> responseFilesData = assembly.compilerOptions.ResponseFiles.ToDictionary(x => x, x => m_AssemblyNameProvider.ParseResponseFile(
x,
ProjectDirectory,
systemReferenceDirectories
));
Dictionary<string, ResponseFileData> responseFilesWithErrors = responseFilesData.Where(x => x.Value.Errors.Any())
.ToDictionary(x => x.Key, x => x.Value);
if (responseFilesWithErrors.Any())
{
foreach (var error in responseFilesWithErrors)
foreach (var valueError in error.Value.Errors)
{
Debug.LogError($"{error.Key} Parse Error : {valueError}");
}
}
return responseFilesData.Select(x => x.Value);
}
private Dictionary<string, string> GenerateAllAssetProjectParts()
{
Dictionary<string, StringBuilder> stringBuilders = new Dictionary<string, StringBuilder>();
foreach (string asset in m_AssemblyNameProvider.GetAllAssetPaths())
{
// Exclude files coming from packages except if they are internalized.
if (m_AssemblyNameProvider.IsInternalizedPackagePath(asset))
{
continue;
}
if (IsSupportedFile(asset) && ScriptingLanguage.None == ScriptingLanguageFor(asset))
{
// Find assembly the asset belongs to by adding script extension and using compilation pipeline.
var assemblyName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".cs");
if (string.IsNullOrEmpty(assemblyName))
{
continue;
}
assemblyName = Path.GetFileNameWithoutExtension(assemblyName);
if (!stringBuilders.TryGetValue(assemblyName, out var projectBuilder))
{
projectBuilder = new StringBuilder();
stringBuilders[assemblyName] = projectBuilder;
}
projectBuilder.Append(" <None Include=\"").Append(EscapedRelativePathFor(asset)).Append("\" />").Append(k_WindowsNewline);
}
}
var result = new Dictionary<string, string>();
foreach (var entry in stringBuilders)
result[entry.Key] = entry.Value.ToString();
return result;
}
private void SyncProject(
Assembly assembly,
Dictionary<string, string> allAssetsProjectParts,
IEnumerable<ResponseFileData> responseFilesData,
List<Assembly> allProjectAssemblies,
string[] roslynAnalyzerDllPaths)
{
SyncProjectFileIfNotChanged(
ProjectFile(assembly),
ProjectText(assembly, allAssetsProjectParts, responseFilesData, allProjectAssemblies, roslynAnalyzerDllPaths));
}
private void SyncProjectFileIfNotChanged(string path, string newContents)
{
if (Path.GetExtension(path) == ".csproj")
{
newContents = OnGeneratedCSProject(path, newContents);
}
SyncFileIfNotChanged(path, newContents);
}
private void SyncSolutionFileIfNotChanged(string path, string newContents)
{
newContents = OnGeneratedSlnSolution(path, newContents);
SyncFileIfNotChanged(path, newContents);
}
private static IEnumerable<SR.MethodInfo> GetPostProcessorCallbacks(string name)
{
return TypeCache
.GetTypesDerivedFrom<AssetPostprocessor>()
.Select(t => t.GetMethod(name, SR.BindingFlags.Public | SR.BindingFlags.NonPublic | SR.BindingFlags.Static))
.Where(m => m != null);
}
static void OnGeneratedCSProjectFiles()
{
foreach (var method in GetPostProcessorCallbacks(nameof(OnGeneratedCSProjectFiles)))
{
method.Invoke(null, Array.Empty<object>());
}
}
private static bool OnPreGeneratingCSProjectFiles()
{
bool result = false;
foreach (var method in GetPostProcessorCallbacks(nameof(OnPreGeneratingCSProjectFiles)))
{
var retValue = method.Invoke(null, Array.Empty<object>());
if (method.ReturnType == typeof(bool))
{
result |= (bool)retValue;
}
}
return result;
}
private static string InvokeAssetPostProcessorGenerationCallbacks(string name, string path, string content)
{
foreach (var method in GetPostProcessorCallbacks(name))
{
var args = new[] { path, content };
var returnValue = method.Invoke(null, args);
if (method.ReturnType == typeof(string))
{
// We want to chain content update between invocations
content = (string)returnValue;
}
}
return content;
}
private static string OnGeneratedCSProject(string path, string content)
{
return InvokeAssetPostProcessorGenerationCallbacks(nameof(OnGeneratedCSProject), path, content);
}
private static string OnGeneratedSlnSolution(string path, string content)
{
return InvokeAssetPostProcessorGenerationCallbacks(nameof(OnGeneratedSlnSolution), path, content);
}
private void SyncFileIfNotChanged(string filename, string newContents)
{
try
{
if (m_FileIOProvider.Exists(filename) && newContents == m_FileIOProvider.ReadAllText(filename))
{
return;
}
}
catch (Exception exception)
{
Debug.LogException(exception);
}
m_FileIOProvider.WriteAllText(filename, newContents);
}
private string ProjectText(Assembly assembly,
Dictionary<string, string> allAssetsProjectParts,
IEnumerable<ResponseFileData> responseFilesData,
List<Assembly> allProjectAssemblies,
string[] roslynAnalyzerDllPaths)
{
var projectBuilder = new StringBuilder(ProjectHeader(assembly, responseFilesData, roslynAnalyzerDllPaths));
var references = new List<string>();
projectBuilder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
foreach (string file in assembly.sourceFiles)
{
if (!IsSupportedFile(file))
continue;
var extension = Path.GetExtension(file).ToLower();
var fullFile = EscapedRelativePathFor(file);
if (".dll" != extension)
{
projectBuilder.Append(" <Compile Include=\"").Append(fullFile).Append("\" />").Append(k_WindowsNewline);
}
else
{
references.Add(fullFile);
}
}
projectBuilder.Append(@" </ItemGroup>").Append(k_WindowsNewline);
projectBuilder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
// Append additional non-script files that should be included in project generation.
if (allAssetsProjectParts.TryGetValue(assembly.name, out var additionalAssetsForProject))
projectBuilder.Append(additionalAssetsForProject);
var responseRefs = responseFilesData.SelectMany(x => x.FullPathReferences.Select(r => r));
var internalAssemblyReferences = assembly.assemblyReferences
.Where(i => !i.sourceFiles.Any(ShouldFileBePartOfSolution)).Select(i => i.outputPath);
var allReferences =
assembly.compiledAssemblyReferences
.Union(responseRefs)
.Union(references)
.Union(internalAssemblyReferences);
foreach (var reference in allReferences)
{
string fullReference = Path.IsPathRooted(reference) ? reference : Path.Combine(ProjectDirectory, reference);
AppendReference(fullReference, projectBuilder);
}
projectBuilder.Append(@" </ItemGroup>").Append(k_WindowsNewline);
if (0 < assembly.assemblyReferences.Length)
{
projectBuilder.Append(" <ItemGroup>").Append(k_WindowsNewline);
foreach (Assembly reference in assembly.assemblyReferences.Where(i => i.sourceFiles.Any(ShouldFileBePartOfSolution)))
{
projectBuilder.Append(" <ProjectReference Include=\"").Append(reference.name).Append(GetProjectExtension()).Append("\">").Append(k_WindowsNewline);
projectBuilder.Append(" <Project>{").Append(ProjectGuid(reference)).Append("}</Project>").Append(k_WindowsNewline);
projectBuilder.Append(" <Name>").Append(reference.name).Append("</Name>").Append(k_WindowsNewline);
projectBuilder.Append(" </ProjectReference>").Append(k_WindowsNewline);
}
projectBuilder.Append(@" </ItemGroup>").Append(k_WindowsNewline);
}
projectBuilder.Append(ProjectFooter());
return projectBuilder.ToString();
}
private static string XmlFilename(string path)
{
if (string.IsNullOrEmpty(path))
return path;
path = path.Replace(@"%", "%25");
path = path.Replace(@";", "%3b");
return XmlEscape(path);
}
private static string XmlEscape(string s)
{
return SecurityElement.Escape(s);
}
private void AppendReference(string fullReference, StringBuilder projectBuilder)
{
var escapedFullPath = EscapedRelativePathFor(fullReference);
projectBuilder.Append(" <Reference Include=\"").Append(Path.GetFileNameWithoutExtension(escapedFullPath)).Append("\">").Append(k_WindowsNewline);
projectBuilder.Append(" <HintPath>").Append(escapedFullPath).Append("</HintPath>").Append(k_WindowsNewline);
projectBuilder.Append(" </Reference>").Append(k_WindowsNewline);
}
public string ProjectFile(Assembly assembly)
{
return Path.Combine(ProjectDirectory, $"{m_AssemblyNameProvider.GetAssemblyName(assembly.outputPath, assembly.name)}.csproj");
}
private static readonly Regex InvalidCharactersRegexPattern = new Regex(@"\?|&|\*|""|<|>|\||#|%|\^|;" + (VisualStudioEditor.IsWindows ? "" : "|:"));
public string SolutionFile()
{
return Path.Combine(FileUtility.Normalize(ProjectDirectory), $"{InvalidCharactersRegexPattern.Replace(m_ProjectName, "_")}.sln");
}
internal string VsConfigFile()
{
return Path.Combine(FileUtility.Normalize(ProjectDirectory), ".vsconfig");
}
private string ProjectHeader(
Assembly assembly,
IEnumerable<ResponseFileData> responseFilesData,
string[] roslynAnalyzerDllPaths
)
{
var toolsVersion = "4.0";
var productVersion = "10.0.20506";
const string baseDirectory = ".";
var targetFrameworkVersion = "v4.7.1";
var targetLanguageVersion = "latest"; // danger: latest is not the same absolute value depending on the VS version.
if (m_CurrentInstallation != null)
{
var vsLanguageSupport = m_CurrentInstallation.LatestLanguageVersionSupported;
var unityLanguageSupport = UnityInstallation.LatestLanguageVersionSupported(assembly);
// Use the minimal supported version between VS and Unity, so that compilation will work in both
targetLanguageVersion = (vsLanguageSupport <= unityLanguageSupport ? vsLanguageSupport : unityLanguageSupport).ToString(2); // (major, minor) only
}
var projectType = ProjectTypeOf(assembly.name);
var arguments = new object[]
{
toolsVersion,
productVersion,
ProjectGuid(assembly),
XmlFilename(FileUtility.Normalize(InternalEditorUtility.GetEngineAssemblyPath())),
XmlFilename(FileUtility.Normalize(InternalEditorUtility.GetEditorAssemblyPath())),
string.Join(";", assembly.defines.Concat(responseFilesData.SelectMany(x => x.Defines)).Distinct().ToArray()),
MSBuildNamespaceUri,
assembly.name,
assembly.outputPath,
GetRootNamespace(assembly),
targetFrameworkVersion,
targetLanguageVersion,
baseDirectory,
assembly.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe),
// flavoring
projectType + ":" + (int)projectType,
EditorUserBuildSettings.activeBuildTarget + ":" + (int)EditorUserBuildSettings.activeBuildTarget,
Application.unityVersion,
VisualStudioIntegration.PackageVersion()
};
try
{
#if UNITY_2020_2_OR_NEWER
return string.Format(GetProjectHeaderTemplate(roslynAnalyzerDllPaths, assembly.compilerOptions.RoslynAnalyzerRulesetPath), arguments);
#else
return string.Format(GetProjectHeaderTemplate(Array.Empty<string>(), null), arguments);
#endif
}
catch (Exception)
{
throw new NotSupportedException("Failed creating c# project because the c# project header did not have the correct amount of arguments, which is " + arguments.Length);
}
}
private enum ProjectType
{
GamePlugins = 3,
Game = 1,
EditorPlugins = 7,
Editor = 5,
}
private static ProjectType ProjectTypeOf(string fileName)
{
var plugins = fileName.Contains("firstpass");
var editor = fileName.Contains("Editor");
if (plugins && editor)
return ProjectType.EditorPlugins;
if (plugins)
return ProjectType.GamePlugins;
if (editor)
return ProjectType.Editor;
return ProjectType.Game;
}
private static string GetSolutionText()
{
return string.Join("\r\n",
@"",
@"Microsoft Visual Studio Solution File, Format Version {0}",
@"# Visual Studio {1}",
@"{2}",
@"Global",
@" GlobalSection(SolutionConfigurationPlatforms) = preSolution",
@" Debug|Any CPU = Debug|Any CPU",
@" Release|Any CPU = Release|Any CPU",
@" EndGlobalSection",
@" GlobalSection(ProjectConfigurationPlatforms) = postSolution",
@"{3}",
@" EndGlobalSection",
@"{4}",
@"EndGlobal",
@"").Replace(" ", "\t");
}
private static string GetProjectFooterTemplate()
{
return string.Join("\r\n",
@" <Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />",
@" <Target Name=""GenerateTargetFrameworkMonikerAttribute"" />",
@" <!-- To modify your build process, add your task inside one of the targets below and uncomment it.",
@" Other similar extension points exist, see Microsoft.Common.targets.",
@" <Target Name=""BeforeBuild"">",
@" </Target>",
@" <Target Name=""AfterBuild"">",
@" </Target>",
@" -->",
@"</Project>",
@"");
}
private string GetProjectHeaderTemplate(string[] roslynAnalyzerDllPaths, string roslynAnalyzerRulesetPath)
{
var header = new[]
{
@"<?xml version=""1.0"" encoding=""utf-8""?>",
@"<Project ToolsVersion=""{0}"" DefaultTargets=""Build"" xmlns=""{6}"">",
@" <PropertyGroup>",
@" <LangVersion>{11}</LangVersion>",
@" </PropertyGroup>",
@" <PropertyGroup>",
@" <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>",
@" <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>",
@" <ProductVersion>{1}</ProductVersion>",
@" <SchemaVersion>2.0</SchemaVersion>",
@" <RootNamespace>{9}</RootNamespace>",
@" <ProjectGuid>{{{2}}}</ProjectGuid>",
@" <OutputType>Library</OutputType>",
@" <AppDesignerFolder>Properties</AppDesignerFolder>",
@" <AssemblyName>{7}</AssemblyName>",
@" <TargetFrameworkVersion>{10}</TargetFrameworkVersion>",
@" <FileAlignment>512</FileAlignment>",
@" <BaseDirectory>{12}</BaseDirectory>",
@" </PropertyGroup>",
@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">",
@" <DebugSymbols>true</DebugSymbols>",
@" <DebugType>full</DebugType>",
@" <Optimize>false</Optimize>",
@" <OutputPath>{8}</OutputPath>",
@" <DefineConstants>{5}</DefineConstants>",
@" <ErrorReport>prompt</ErrorReport>",
@" <WarningLevel>4</WarningLevel>",
@" <NoWarn>0169</NoWarn>",
@" <AllowUnsafeBlocks>{13}</AllowUnsafeBlocks>",
@" </PropertyGroup>",
@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "">",
@" <DebugType>pdbonly</DebugType>",
@" <Optimize>true</Optimize>",
@" <OutputPath>Temp\bin\Release\</OutputPath>",
@" <ErrorReport>prompt</ErrorReport>",
@" <WarningLevel>4</WarningLevel>",
@" <NoWarn>0169</NoWarn>",
@" <AllowUnsafeBlocks>{13}</AllowUnsafeBlocks>",
@" </PropertyGroup>"
};
var forceExplicitReferences = new[]
{
@" <PropertyGroup>",
@" <NoConfig>true</NoConfig>",
@" <NoStdLib>true</NoStdLib>",
@" <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>",
@" <ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>",
@" <ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>",
@" </PropertyGroup>"
};
var flavoring = new[]
{
@" <PropertyGroup>",
@" <ProjectTypeGuids>{{E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1}};{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}</ProjectTypeGuids>",
@" <UnityProjectGenerator>Package</UnityProjectGenerator>",
@" <UnityProjectGeneratorVersion>{17}</UnityProjectGeneratorVersion>",
@" <UnityProjectType>{14}</UnityProjectType>",
@" <UnityBuildTarget>{15}</UnityBuildTarget>",
@" <UnityVersion>{16}</UnityVersion>",
@" </PropertyGroup>"
};
var footer = new[]
{
@""
};
var lines = header.Concat(forceExplicitReferences).Concat(flavoring).ToList();
// Only add analyzer block for compatible Visual Studio
if (m_CurrentInstallation != null && m_CurrentInstallation.SupportsAnalyzers)
{
#if UNITY_2020_2_OR_NEWER
if (roslynAnalyzerRulesetPath != null)
{
lines.Add(@" <PropertyGroup>");
lines.Add($" <CodeAnalysisRuleSet>{roslynAnalyzerRulesetPath}</CodeAnalysisRuleSet>");
lines.Add(@" </PropertyGroup>");
}
#endif
string[] analyzers = m_CurrentInstallation.GetAnalyzers();
string[] allAnalyzers = analyzers != null ? analyzers.Concat(roslynAnalyzerDllPaths).ToArray() : roslynAnalyzerDllPaths;
if (allAnalyzers.Any())
{
lines.Add(@" <ItemGroup>");
foreach (var analyzer in allAnalyzers)
{
lines.Add($@" <Analyzer Include=""{EscapedRelativePathFor(analyzer)}"" />");
}
lines.Add(@" </ItemGroup>");
}
}
return string.Join("\r\n", lines.Concat(footer));
}
private void SyncSolution(IEnumerable<Assembly> assemblies)
{
if (InvalidCharactersRegexPattern.IsMatch(ProjectDirectory))
Debug.LogWarning("Project path contains special characters, which can be an issue when opening Visual Studio");
var solutionFile = SolutionFile();
var previousSolution = m_FileIOProvider.Exists(solutionFile) ? SolutionParser.ParseSolutionFile(solutionFile, m_FileIOProvider) : null;
SyncSolutionFileIfNotChanged(solutionFile, SolutionText(assemblies, previousSolution));
}
private string SolutionText(IEnumerable<Assembly> assemblies, Solution previousSolution = null)
{
const string fileversion = "12.00";
const string vsversion = "15";
var relevantAssemblies = RelevantAssembliesForMode(assemblies);
var generatedProjects = ToProjectEntries(relevantAssemblies).ToList();
SolutionProperties[] properties = null;
// First, add all projects generated by Unity to the solution
var projects = new List<SolutionProjectEntry>();
projects.AddRange(generatedProjects);
if (previousSolution != null)
{
// Add all projects that were previously in the solution and that are not generated by Unity, nor generated in the project root directory
var externalProjects = previousSolution.Projects
.Where(p => p.IsSolutionFolderProjectFactory() || !FileUtility.IsFileInProjectRootDirectory(p.FileName))
.Where(p => generatedProjects.All(gp => gp.FileName != p.FileName));
projects.AddRange(externalProjects);
properties = previousSolution.Properties;
}
string propertiesText = GetPropertiesText(properties);
string projectEntriesText = GetProjectEntriesText(projects);
// do not generate configurations for SolutionFolders
var configurableProjects = projects.Where(p => !p.IsSolutionFolderProjectFactory());
string projectConfigurationsText = string.Join(k_WindowsNewline, configurableProjects.Select(p => GetProjectActiveConfigurations(p.ProjectGuid)).ToArray());
return string.Format(GetSolutionText(), fileversion, vsversion, projectEntriesText, projectConfigurationsText, propertiesText);
}
private static IEnumerable<Assembly> RelevantAssembliesForMode(IEnumerable<Assembly> assemblies)
{
return assemblies.Where(i => ScriptingLanguage.CSharp == ScriptingLanguageFor(i));
}
private static string GetPropertiesText(SolutionProperties[] array)
{
if (array == null || array.Length == 0)
{
// HideSolution by default
array = new SolutionProperties[] {
new SolutionProperties() {
Name = "SolutionProperties",
Type = "preSolution",
Entries = new List<KeyValuePair<string,string>>() { new KeyValuePair<string, string> ("HideSolutionNode", "FALSE") }
}
};
}
var result = new StringBuilder();
for (var i = 0; i < array.Length; i++)
{
if (i > 0)
result.Append(k_WindowsNewline);
var properties = array[i];
result.Append($"\tGlobalSection({properties.Name}) = {properties.Type}");
result.Append(k_WindowsNewline);
foreach (var entry in properties.Entries)
{
result.Append($"\t\t{entry.Key} = {entry.Value}");
result.Append(k_WindowsNewline);
}
result.Append("\tEndGlobalSection");
}
return result.ToString();
}
/// <summary>
/// Get a Project("{guid}") = "MyProject", "MyProject.unityproj", "{projectguid}"
/// entry for each relevant language
/// </summary>
private string GetProjectEntriesText(IEnumerable<SolutionProjectEntry> entries)
{
var projectEntries = entries.Select(entry => string.Format(
m_SolutionProjectEntryTemplate,
entry.ProjectFactoryGuid, entry.Name, entry.FileName, entry.ProjectGuid, entry.Metadata
));
return string.Join(k_WindowsNewline, projectEntries.ToArray());
}
private IEnumerable<SolutionProjectEntry> ToProjectEntries(IEnumerable<Assembly> assemblies)
{
foreach (var assembly in assemblies)
yield return new SolutionProjectEntry()
{
ProjectFactoryGuid = SolutionGuid(assembly),
Name = assembly.name,
FileName = Path.GetFileName(ProjectFile(assembly)),
ProjectGuid = ProjectGuid(assembly),
Metadata = k_WindowsNewline
};
}
/// <summary>
/// Generate the active configuration string for a given project guid
/// </summary>
private string GetProjectActiveConfigurations(string projectGuid)
{
return string.Format(
m_SolutionProjectConfigurationTemplate,
projectGuid);
}
private string EscapedRelativePathFor(string file)
{
var projectDir = FileUtility.Normalize(ProjectDirectory);
file = FileUtility.Normalize(file);
var path = SkipPathPrefix(file, projectDir);
var packageInfo = m_AssemblyNameProvider.FindForAssetPath(path.Replace('\\', '/'));
if (packageInfo != null)
{
// We have to normalize the path, because the PackageManagerRemapper assumes
// dir seperators will be os specific.
var absolutePath = Path.GetFullPath(FileUtility.Normalize(path));
path = SkipPathPrefix(absolutePath, projectDir);
}
return XmlFilename(path);
}
private static string SkipPathPrefix(string path, string prefix)
{
if (path.StartsWith($"{prefix}{Path.DirectorySeparatorChar}") && (path.Length > prefix.Length))
return path.Substring(prefix.Length + 1);
return path;
}
private static string ProjectFooter()
{
return GetProjectFooterTemplate();
}
static string GetProjectExtension()
{
return ".csproj";
}
private string ProjectGuid(Assembly assembly)
{
return m_GUIDGenerator.ProjectGuid(
m_ProjectName,
m_AssemblyNameProvider.GetAssemblyName(assembly.outputPath, assembly.name));
}
private string SolutionGuid(Assembly assembly)
{
return m_GUIDGenerator.SolutionGuid(m_ProjectName, ScriptingLanguageFor(assembly));
}
private static string GetRootNamespace(Assembly assembly)
{
#if UNITY_2020_2_OR_NEWER
return assembly.rootNamespace;
#else
return EditorSettings.projectGenerationRootNamespace;
#endif
}
}
public static class SolutionGuidGenerator
{
public static string GuidForProject(string projectName)
{
return ComputeGuidHashFor(projectName + "salt");
}
public static string GuidForSolution(string projectName, ScriptingLanguage language)
{
if (language == ScriptingLanguage.CSharp)
{
// GUID for a C# class library: http://www.codeproject.com/Reference/720512/List-of-Visual-Studio-Project-Type-GUIDs
return "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC";
}
return ComputeGuidHashFor(projectName);
}
private static string ComputeGuidHashFor(string input)
{
var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(input));
return HashAsGuid(HashToString(hash));
}
private static string HashAsGuid(string hash)
{
var guid = hash.Substring(0, 8) + "-" + hash.Substring(8, 4) + "-" + hash.Substring(12, 4) + "-" + hash.Substring(16, 4) + "-" + hash.Substring(20, 12);
return guid.ToUpper();
}
private static string HashToString(byte[] bs)
{
var sb = new StringBuilder();
foreach (byte b in bs)
sb.Append(b.ToString("x2"));
return sb.ToString();
}
}
}

View file

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

View file

@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* 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;
namespace Microsoft.Unity.VisualStudio.Editor
{
[Flags]
public enum ProjectGenerationFlag
{
None = 0,
Embedded = 1,
Local = 2,
Registry = 4,
Git = 8,
BuiltIn = 16,
Unknown = 32,
PlayerAssemblies = 64,
LocalTarBall = 128,
}
}

View file

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

View file

@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* 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 class Solution
{
public SolutionProjectEntry[] Projects { get; set; }
public SolutionProperties[] Properties { get; set; }
}
}

View file

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

View file

@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class SolutionParser
{
// Compared to the bridge implementation, we are not returning "{" "}" from Guids
private static readonly Regex ProjectDeclaration = new Regex(@"Project\(\""{(?<projectFactoryGuid>.*?)}\""\)\s+=\s+\""(?<name>.*?)\"",\s+\""(?<fileName>.*?)\"",\s+\""{(?<projectGuid>.*?)}\""(?<metadata>.*?)\bEndProject\b", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
private static readonly Regex PropertiesDeclaration = new Regex(@"GlobalSection\((?<name>([\w]+Properties|NestedProjects))\)\s+=\s+(?<type>(?:post|pre)Solution)(?<entries>.*?)EndGlobalSection", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
private static readonly Regex PropertiesEntryDeclaration = new Regex(@"^\s*(?<key>.*?)=(?<value>.*?)$", RegexOptions.Multiline | RegexOptions.ExplicitCapture);
public static Solution ParseSolutionFile(string filename, IFileIO fileIO)
{
return ParseSolutionContent(fileIO.ReadAllText(filename));
}
public static Solution ParseSolutionContent(string content)
{
return new Solution
{
Projects = ParseSolutionProjects(content),
Properties = ParseSolutionProperties(content)
};
}
private static SolutionProjectEntry[] ParseSolutionProjects(string content)
{
var projects = new List<SolutionProjectEntry>();
var mc = ProjectDeclaration.Matches(content);
foreach (Match match in mc)
{
projects.Add(new SolutionProjectEntry
{
ProjectFactoryGuid = match.Groups["projectFactoryGuid"].Value,
Name = match.Groups["name"].Value,
FileName = match.Groups["fileName"].Value,
ProjectGuid = match.Groups["projectGuid"].Value,
Metadata = match.Groups["metadata"].Value
});
}
return projects.ToArray();
}
private static SolutionProperties[] ParseSolutionProperties(string content)
{
var properties = new List<SolutionProperties>();
var mc = PropertiesDeclaration.Matches(content);
foreach (Match match in mc)
{
var sp = new SolutionProperties
{
Entries = new List<KeyValuePair<string, string>>(),
Name = match.Groups["name"].Value,
Type = match.Groups["type"].Value
};
var entries = match.Groups["entries"].Value;
var mec = PropertiesEntryDeclaration.Matches(entries);
foreach (Match entry in mec)
{
var key = entry.Groups["key"].Value.Trim();
var value = entry.Groups["value"].Value.Trim();
sp.Entries.Add(new KeyValuePair<string, string>(key, value));
}
properties.Add(sp);
}
return properties.ToArray();
}
}
}

View file

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

View file

@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal class SolutionProjectEntry
{
public string ProjectFactoryGuid { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
public string ProjectGuid { get; set; }
public string Metadata { get; set; }
public bool IsSolutionFolderProjectFactory()
{
return ProjectFactoryGuid != null && ProjectFactoryGuid.Equals("2150E333-8FDC-42A3-9474-1A3956D46DE8", StringComparison.OrdinalIgnoreCase);
}
}
}

View file

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

View file

@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System.Collections.Generic;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal class SolutionProperties
{
public string Name { get; set; }
public IList<KeyValuePair<string, string>> Entries { get; set; }
public string Type { get; set; }
}
}

View file

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

View file

@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* 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;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class Symbols
{
public static bool IsPortableSymbolFile(string pdbFile)
{
try
{
using (var stream = File.OpenRead(pdbFile))
{
return stream.ReadByte() == 'B'
&& stream.ReadByte() == 'S'
&& stream.ReadByte() == 'J'
&& stream.ReadByte() == 'B';
}
}
catch (Exception)
{
return false;
}
}
}
}

View file

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

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7f9f1d015d7a8ba46b7d71acfcda3ae7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,39 @@
using System;
using UnityEditor.TestTools.TestRunner.Api;
namespace Microsoft.Unity.VisualStudio.Editor.Testing
{
[Serializable]
internal class TestAdaptorContainer
{
public TestAdaptor[] TestAdaptors;
}
[Serializable]
internal class TestAdaptor
{
public string Id;
public string Name;
public string FullName;
public string Type;
public string Method;
public string Assembly;
public int Parent;
public TestAdaptor(ITestAdaptor testAdaptor, int parent)
{
Id = testAdaptor.Id;
Name = testAdaptor.Name;
FullName = testAdaptor.FullName;
Type = testAdaptor.TypeInfo?.FullName;
Method = testAdaptor?.Method?.Name;
Assembly = testAdaptor.TypeInfo?.Assembly?.Location;
Parent = parent;
}
}
}

View file

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

View file

@ -0,0 +1,60 @@
using System;
using UnityEditor.TestTools.TestRunner.Api;
namespace Microsoft.Unity.VisualStudio.Editor.Testing
{
[Serializable]
internal class TestResultAdaptorContainer
{
public TestResultAdaptor[] TestResultAdaptors;
}
[Serializable]
internal class TestResultAdaptor
{
public string Name;
public string FullName;
public int PassCount;
public int FailCount;
public int InconclusiveCount;
public int SkipCount;
public string ResultState;
public string StackTrace;
public TestStatusAdaptor TestStatus;
public int Parent;
public TestResultAdaptor(ITestResultAdaptor testResultAdaptor, int parent)
{
Name = testResultAdaptor.Name;
FullName = testResultAdaptor.FullName;
PassCount = testResultAdaptor.PassCount;
FailCount = testResultAdaptor.FailCount;
InconclusiveCount = testResultAdaptor.InconclusiveCount;
SkipCount = testResultAdaptor.SkipCount;
switch (testResultAdaptor.TestStatus)
{
case UnityEditor.TestTools.TestRunner.Api.TestStatus.Passed:
TestStatus = TestStatusAdaptor.Passed;
break;
case UnityEditor.TestTools.TestRunner.Api.TestStatus.Skipped:
TestStatus = TestStatusAdaptor.Skipped;
break;
case UnityEditor.TestTools.TestRunner.Api.TestStatus.Inconclusive:
TestStatus = TestStatusAdaptor.Inconclusive;
break;
case UnityEditor.TestTools.TestRunner.Api.TestStatus.Failed:
TestStatus = TestStatusAdaptor.Failed;
break;
}
Parent = parent;
}
}
}

View file

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

View file

@ -0,0 +1,52 @@
using System;
using UnityEditor;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;
namespace Microsoft.Unity.VisualStudio.Editor.Testing
{
[InitializeOnLoad]
internal class TestRunnerApiListener
{
private static TestRunnerApi _testRunnerApi;
private static TestRunnerCallbacks _testRunnerCallbacks;
static TestRunnerApiListener()
{
_testRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
_testRunnerCallbacks = new TestRunnerCallbacks();
_testRunnerApi.RegisterCallbacks(_testRunnerCallbacks);
}
public static void RetrieveTestList(string mode)
{
RetrieveTestList((TestMode) Enum.Parse(typeof(TestMode), mode));
}
private static void RetrieveTestList(TestMode mode)
{
_testRunnerApi.RetrieveTestList(mode, (ta) => _testRunnerCallbacks.TestListRetrieved(mode, ta));
}
public static void ExecuteTests(string command)
{
// ExecuteTests format:
// TestMode:FullName
var index = command.IndexOf(':');
if (index < 0)
return;
var testMode = (TestMode)Enum.Parse(typeof(TestMode), command.Substring(0, index));
var filter = command.Substring(index + 1);
ExecuteTests(new Filter() { testMode = testMode, testNames = new string[] { filter } });
}
private static void ExecuteTests(Filter filter)
{
_testRunnerApi.Execute(new ExecutionSettings(filter));
}
}
}

View file

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

View file

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;
namespace Microsoft.Unity.VisualStudio.Editor.Testing
{
internal class TestRunnerCallbacks : ICallbacks
{
private string Serialize<TContainer, TSource, TAdaptor>(
TSource source,
Func<TSource, int, TAdaptor> createAdaptor,
Func<TSource, IEnumerable<TSource>> children,
Func<TAdaptor[], TContainer> container)
{
var adaptors = new List<TAdaptor>();
void AddAdaptor(TSource item, int parentIndex)
{
var index = adaptors.Count;
adaptors.Add(createAdaptor(item, parentIndex));
foreach (var child in children(item))
AddAdaptor(child, index);
}
AddAdaptor(source, -1);
return JsonUtility.ToJson(container(adaptors.ToArray()));
}
private string Serialize(ITestAdaptor testAdaptor)
{
return Serialize(
testAdaptor,
(a, parentIndex) => new TestAdaptor(a, parentIndex),
(a) => a.Children,
(r) => new TestAdaptorContainer { TestAdaptors = r });
}
private string Serialize(ITestResultAdaptor testResultAdaptor)
{
return Serialize(
testResultAdaptor,
(a, parentIndex) => new TestResultAdaptor(a, parentIndex),
(a) => a.Children,
(r) => new TestResultAdaptorContainer { TestResultAdaptors = r });
}
public void RunFinished(ITestResultAdaptor testResultAdaptor)
{
VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.RunFinished, Serialize(testResultAdaptor));
}
public void RunStarted(ITestAdaptor testAdaptor)
{
VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.RunStarted, Serialize(testAdaptor));
}
public void TestFinished(ITestResultAdaptor testResultAdaptor)
{
VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.TestFinished, Serialize(testResultAdaptor));
}
public void TestStarted(ITestAdaptor testAdaptor)
{
VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.TestStarted, Serialize(testAdaptor));
}
private static string TestModeName(TestMode testMode)
{
switch (testMode)
{
case TestMode.EditMode: return "EditMode";
case TestMode.PlayMode: return "PlayMode";
}
throw new ArgumentOutOfRangeException();
}
internal void TestListRetrieved(TestMode testMode, ITestAdaptor testAdaptor)
{
// TestListRetrieved format:
// TestMode:Json
var value = TestModeName(testMode) + ":" + Serialize(testAdaptor);
VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.TestListRetrieved, value);
}
}
}

View file

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

View file

@ -0,0 +1,13 @@
using System;
namespace Microsoft.Unity.VisualStudio.Editor.Testing
{
[Serializable]
internal enum TestStatusAdaptor
{
Passed,
Skipped,
Inconclusive,
Failed,
}
}

View file

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

View file

@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* 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 UnityEditor.Compilation;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class UnityInstallation
{
public static Version LatestLanguageVersionSupported(Assembly assembly)
{
#if UNITY_2020_2_OR_NEWER
if (assembly?.compilerOptions != null && Version.TryParse(assembly.compilerOptions.LanguageVersion, out var result))
return result;
// if parsing fails, we know at least we have support for 8.0
return new Version(8, 0);
#else
return new Version(7, 3);
#endif
}
}
}

View file

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

View file

@ -0,0 +1,118 @@
/*---------------------------------------------------------------------------------------------
* 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 System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
namespace Microsoft.Unity.VisualStudio.Editor
{
[Serializable]
internal class FileUsage
{
public string Path;
public string[] GameObjectPath;
}
internal static class UsageUtility
{
internal static void ShowUsage(string json)
{
try
{
var usage = JsonUtility.FromJson<FileUsage>(json);
ShowUsage(usage.Path, usage.GameObjectPath);
}
catch (Exception)
{
// ignore malformed request
}
}
internal static void ShowUsage(string path, string[] gameObjectPath)
{
path = FileUtility.MakeRelativeToProjectPath(path);
if (path == null)
return;
path = FileUtility.NormalizeWindowsToUnix(path);
var extension = Path.GetExtension(path).ToLower();
EditorUtility.FocusProjectWindow();
switch (extension)
{
case ".unity":
ShowSceneUsage(path, gameObjectPath);
break;
default:
var asset = AssetDatabase.LoadMainAssetAtPath(path);
Selection.activeObject = asset;
EditorGUIUtility.PingObject(asset);
break;
}
}
private static void ShowSceneUsage(string scenePath, string[] gameObjectPath)
{
var scene = SceneManager.GetSceneByPath(scenePath.Replace(Path.DirectorySeparatorChar, '/'));
if (!scene.isLoaded)
{
var result = UnityEditor.EditorUtility.DisplayDialogComplex("Show Usage",
$"Do you want to open \"{Path.GetFileName(scenePath)}\"?",
"Open Scene",
"Cancel",
"Open Scene (additive)");
switch (result)
{
case 0:
EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
break;
case 1:
return;
case 2:
scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
break;
}
}
ShowSceneUsage(scene, gameObjectPath);
}
private static void ShowSceneUsage(Scene scene, string[] gameObjectPath)
{
if (gameObjectPath == null || gameObjectPath.Length == 0)
return;
var go = scene.GetRootGameObjects().FirstOrDefault(g => g.name == gameObjectPath[0]);
if (go == null)
return;
for (var ni = 1; ni < gameObjectPath.Length; ni++)
{
var transform = go.transform;
for (var i = 0; i < transform.childCount; i++)
{
var child = transform.GetChild(i);
var childgo = child.gameObject;
if (childgo.name == gameObjectPath[ni])
{
go = childgo;
break;
}
}
}
Selection.activeObject = go;
EditorGUIUtility.PingObject(go);
}
}
}

View file

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

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5b17896803f77494da73d73448fb6cb4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 585c3fb85b32bd64e8814074e754163e
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,16 @@
using System;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal struct VersionPair
{
public Version IdeVersion;
public Version LanguageVersion;
public VersionPair(int idemajor, int ideminor, int languageMajor, int languageMinor)
{
IdeVersion = new Version(idemajor, ideminor);
LanguageVersion = new Version(languageMajor, languageMinor);
}
}
}

View file

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

View file

@ -0,0 +1,382 @@
/*---------------------------------------------------------------------------------------------
* 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.Diagnostics;
using System.IO;
using System.Linq;
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")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
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
.Select(i => i.ToCodeEditorInstallation())
.ToArray();
private readonly IGenerator _generator = new ProjectGeneration();
static VisualStudioEditor()
{
try
{
_installations = 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;
}
}
public void CreateIfDoesntExist()
{
if (!_generator.HasSolutionBeenGenerated())
_generator.Sync();
}
public void Initialize(string editorInstallationPath)
{
}
internal virtual bool TryGetVisualStudioInstallationForPath(string editorPath, out IVisualStudioInstallation installation)
{
// lookup for well known installations
foreach (var candidate in _installations)
{
if (!string.Equals(Path.GetFullPath(editorPath), Path.GetFullPath(candidate.Path), StringComparison.OrdinalIgnoreCase))
continue;
installation = candidate;
return true;
}
return Discovery.TryDiscoverInstallation(editorPath, out installation);
}
public virtual bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation)
{
var result = TryGetVisualStudioInstallationForPath(editorPath, out var vsi);
installation = vsi == null ? default : vsi.ToCodeEditorInstallation();
return result;
}
public void OnGUI()
{
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
var package = UnityEditor.PackageManager.PackageInfo.FindForAssembly(GetType().Assembly);
var style = new GUIStyle
{
richText = true,
margin = new RectOffset(0, 4, 0, 0)
};
GUILayout.Label($"<size=10><color=grey>{package.displayName} v{package.version} enabled</color></size>", style);
GUILayout.EndHorizontal();
EditorGUILayout.LabelField("Generate .csproj files for:");
EditorGUI.indentLevel++;
SettingsButton(ProjectGenerationFlag.Embedded, "Embedded packages", "");
SettingsButton(ProjectGenerationFlag.Local, "Local packages", "");
SettingsButton(ProjectGenerationFlag.Registry, "Registry packages", "");
SettingsButton(ProjectGenerationFlag.Git, "Git packages", "");
SettingsButton(ProjectGenerationFlag.BuiltIn, "Built-in packages", "");
SettingsButton(ProjectGenerationFlag.LocalTarBall, "Local tarball", "");
SettingsButton(ProjectGenerationFlag.Unknown, "Packages from unknown sources", "");
SettingsButton(ProjectGenerationFlag.PlayerAssemblies, "Player projects", "For each player project generate an additional csproj with the name 'project-player.csproj'");
RegenerateProjectFiles();
EditorGUI.indentLevel--;
}
void RegenerateProjectFiles()
{
var rect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect(new GUILayoutOption[] { }));
rect.width = 252;
if (GUI.Button(rect, "Regenerate project files"))
{
_generator.Sync();
}
}
void SettingsButton(ProjectGenerationFlag preference, string guiMessage, string toolTip)
{
var prevValue = _generator.AssemblyNameProvider.ProjectGenerationFlag.HasFlag(preference);
var newValue = EditorGUILayout.Toggle(new GUIContent(guiMessage, toolTip), prevValue);
if (newValue != prevValue)
{
_generator.AssemblyNameProvider.ToggleProjectGeneration(preference);
}
}
public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles)
{
_generator.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles), importedFiles);
foreach (var file in importedFiles.Where(a => Path.GetExtension(a) == ".pdb"))
{
var pdbFile = FileUtility.GetAssetFullPath(file);
// skip Unity packages like com.unity.ext.nunit
if (pdbFile.IndexOf($"{Path.DirectorySeparatorChar}com.unity.", StringComparison.OrdinalIgnoreCase) > 0)
continue;
var asmFile = Path.ChangeExtension(pdbFile, ".dll");
if (!File.Exists(asmFile) || !Image.IsAssembly(asmFile))
continue;
if (Symbols.IsPortableSymbolFile(pdbFile))
continue;
UnityEngine.Debug.LogWarning($"Unity is only able to load mdb or portable-pdb symbols. {file} is using a legacy pdb format.");
}
}
public void SyncAll()
{
AssetDatabase.Refresh();
_generator.Sync();
}
bool IsSupportedPath(string path)
{
// Path is empty with "Open C# Project", as we only want to open the solution without specific files
if (string.IsNullOrEmpty(path))
return true;
// cs, uxml, uss, shader, compute, cginc, hlsl, glslinc, template are part of Unity builtin extensions
// txt, xml, fnt, cd are -often- par of Unity user extensions
// asdmdef is mandatory included
if (_generator.IsSupportedFile(path))
return true;
return false;
}
private static void CheckCurrentEditorInstallation()
{
var editorPath = CodeEditor.CurrentEditorInstallation;
try
{
if (Discovery.TryDiscoverInstallation(editorPath, out _))
return;
}
catch (IOException)
{
}
UnityEngine.Debug.LogWarning($"Visual Studio executable {editorPath} is not found. Please change your settings in Edit > Preferences > External Tools.");
}
public bool OpenProject(string path, int line, int column)
{
CheckCurrentEditorInstallation();
if (!IsSupportedPath(path))
return false;
if (!IsProjectGeneratedFor(path, out var missingFlag))
UnityEngine.Debug.LogWarning($"You are trying to open {path} outside a generated project. This might cause problems with IntelliSense and debugging. To avoid this, you can change your .csproj preferences in Edit > Preferences > External Tools and enable {GetProjectGenerationFlagDescription(missingFlag)} generation.");
if (IsOSX)
return OpenOSXApp(path, line, column);
if (IsWindows)
return OpenWindowsApp(path, line);
return false;
}
private static string GetProjectGenerationFlagDescription(ProjectGenerationFlag flag)
{
switch (flag)
{
case ProjectGenerationFlag.BuiltIn:
return "Built-in packages";
case ProjectGenerationFlag.Embedded:
return "Embedded packages";
case ProjectGenerationFlag.Git:
return "Git packages";
case ProjectGenerationFlag.Local:
return "Local packages";
case ProjectGenerationFlag.LocalTarBall:
return "Local tarball";
case ProjectGenerationFlag.PlayerAssemblies:
return "Player projects";
case ProjectGenerationFlag.Registry:
return "Registry packages";
case ProjectGenerationFlag.Unknown:
return "Packages from unknown sources";
case ProjectGenerationFlag.None:
default:
return string.Empty;
}
}
private bool IsProjectGeneratedFor(string path, out ProjectGenerationFlag missingFlag)
{
missingFlag = ProjectGenerationFlag.None;
// No need to check when opening the whole solution
if (string.IsNullOrEmpty(path))
return true;
// We only want to check for cs scripts
if (ProjectGeneration.ScriptingLanguageFor(path) != ScriptingLanguage.CSharp)
return true;
// Even on windows, the package manager requires relative path + unix style separators for queries
var basePath = _generator.ProjectDirectory;
var relativePath = FileUtility
.NormalizeWindowsToUnix(path)
.Replace(basePath, string.Empty)
.Trim(FileUtility.UnixSeparator);
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(relativePath);
if (packageInfo == null)
return true;
var source = packageInfo.source;
if (!Enum.TryParse<ProjectGenerationFlag>(source.ToString(), out var flag))
return true;
if (_generator.AssemblyNameProvider.ProjectGenerationFlag.HasFlag(flag))
return true;
// Return false if we found a source not flagged for generation
missingFlag = flag;
return false;
}
private bool OpenWindowsApp(string path, int line)
{
var progpath = FileUtility
.FindPackageAssetFullPath("COMIntegration a:packages", "COMIntegration.exe")
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(progpath))
return false;
string absolutePath = "";
if (!string.IsNullOrWhiteSpace(path))
{
absolutePath = Path.GetFullPath(path);
}
// We remove all invalid chars from the solution filename, but we cannot prevent the user from using a specific path for the Unity project
// So process the fullpath to make it compatible with VS
var solution = GetOrGenerateSolutionFile(path);
if (!string.IsNullOrWhiteSpace(solution))
{
solution = $"\"{solution}\"";
solution = solution.Replace("^", "^^");
}
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = progpath,
Arguments = $"\"{CodeEditor.CurrentEditorInstallation}\" \"{absolutePath}\" {solution} {line}",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
}
};
var result = process.Start();
while (!process.StandardOutput.EndOfStream)
{
var outputLine = process.StandardOutput.ReadLine();
if (outputLine == "displayProgressBar")
{
EditorUtility.DisplayProgressBar("Opening Visual Studio", "Starting up Visual Studio, this might take some time.", .5f);
}
if (outputLine == "clearprogressbar")
{
EditorUtility.ClearProgressBar();
}
}
var errorOutput = process.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(errorOutput))
{
Console.WriteLine("Error: \n" + errorOutput);
}
process.WaitForExit();
return result;
}
[DllImport("AppleEventIntegration")]
static extern bool OpenVisualStudio(string appPath, string solutionPath, string filePath, int line);
bool OpenOSXApp(string path, int line, int column)
{
string absolutePath = "";
if (!string.IsNullOrWhiteSpace(path))
{
absolutePath = Path.GetFullPath(path);
}
string solution = GetOrGenerateSolutionFile(path);
return OpenVisualStudio(CodeEditor.CurrentEditorInstallation, solution, absolutePath, line);
}
private string GetOrGenerateSolutionFile(string path)
{
var solution = GetSolutionFile(path);
if (solution == "")
{
_generator.Sync();
solution = GetSolutionFile(path);
}
return solution;
}
string GetSolutionFile(string path)
{
var solutionFile = _generator.SolutionFile();
if (File.Exists(solutionFile))
{
return solutionFile;
}
return "";
}
}
}

View file

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

View file

@ -0,0 +1,185 @@
/*---------------------------------------------------------------------------------------------
* 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 System.Linq;
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 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 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;
}
}
// We only use this to find analyzers, we do not need to load this assembly anymore
private string GetBridgeLocation()
{
if (VisualStudioEditor.IsWindows)
{
// Registry, using legacy bridge location
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;
}
if (VisualStudioEditor.IsOSX)
{
// Environment, useful when developing UnityVS for Mac
var bridge = Environment.GetEnvironmentVariable("VSTUM_BRIDGE");
if (!string.IsNullOrEmpty(bridge) && File.Exists(bridge))
return bridge;
const string addinBridge = "Editor/SyntaxTree.VisualStudio.Unity.Bridge.dll";
const string addinName = "MonoDevelop.Unity";
// 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 a bridge, which is the one MD will load
if (Directory.Exists(localAddins))
{
foreach (var folder in Directory.GetDirectories(localAddins, addinName + "*", SearchOption.TopDirectoryOnly))
{
bridge = IOPath.Combine(folder, addinBridge);
if (File.Exists(bridge))
return bridge;
}
}
// Check in Visual Studio.app/
// In that case the name of the addin is used
bridge = IOPath.Combine(Path, $"Contents/Resources/lib/monodevelop/AddIns/{addinName}/{addinBridge}");
if (File.Exists(bridge))
return bridge;
}
return null;
}
public string[] GetAnalyzers()
{
var bridge = GetBridgeLocation();
if (!string.IsNullOrEmpty(bridge))
{
var baseLocation = IOPath.Combine(IOPath.GetDirectoryName(bridge), "..");
var analyzerLocation = IOPath.GetFullPath(IOPath.Combine(baseLocation, "Analyzers"));
if (Directory.Exists(analyzerLocation))
return Directory.GetFiles(analyzerLocation, "*Analyzers.dll", SearchOption.AllDirectories);
}
// Local assets
// return FileUtility.FindPackageAssetFullPath("Analyzers a:packages", ".Analyzers.dll");
return Array.Empty<string>();
}
public CodeEditor.Installation ToCodeEditorInstallation()
{
return new CodeEditor.Installation() { Name = Name, Path = Path };
}
}
}

View file

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

View file

@ -0,0 +1,263 @@
/*---------------------------------------------------------------------------------------------
* 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using Microsoft.Unity.VisualStudio.Editor.Messaging;
using Microsoft.Unity.VisualStudio.Editor.Testing;
using UnityEditor;
using UnityEngine;
using MessageType = Microsoft.Unity.VisualStudio.Editor.Messaging.MessageType;
namespace Microsoft.Unity.VisualStudio.Editor
{
[InitializeOnLoad]
internal class VisualStudioIntegration
{
class Client
{
public IPEndPoint EndPoint { get; set; }
public DateTime LastMessage { get; set; }
}
private static Messager _messager;
private static readonly Queue<Message> _incoming = new Queue<Message>();
private static readonly Dictionary<IPEndPoint, Client> _clients = new Dictionary<IPEndPoint, Client>();
private static readonly object _incomingLock = new object();
private static readonly object _clientsLock = new object();
static VisualStudioIntegration()
{
if (!VisualStudioEditor.IsEnabled)
return;
RunOnceOnUpdate(() =>
{
// Despite using ReuseAddress|!ExclusiveAddressUse, we can fail here:
// - if another application is using this port with exclusive access
// - or if the firewall is not properly configured
var messagingPort = MessagingPort();
try
{
_messager = Messager.BindTo(messagingPort);
_messager.ReceiveMessage += ReceiveMessage;
}
catch (SocketException)
{
// We'll have a chance to try to rebind on next domain reload
Debug.LogWarning($"Unable to use UDP port {messagingPort} for VS/Unity messaging. You should check if another process is already bound to this port or if your firewall settings are compatible.");
}
RunOnShutdown(Shutdown);
});
EditorApplication.update += OnUpdate;
}
private static void RunOnceOnUpdate(Action action)
{
var callback = null as EditorApplication.CallbackFunction;
callback = () =>
{
EditorApplication.update -= callback;
action();
};
EditorApplication.update += callback;
}
private static void RunOnShutdown(Action action)
{
// Mono on OSX has all kinds of quirks on AppDomain shutdown
if (!VisualStudioEditor.IsWindows)
return;
AppDomain.CurrentDomain.DomainUnload += (_, __) => action();
}
private static int DebuggingPort()
{
return 56000 + (System.Diagnostics.Process.GetCurrentProcess().Id % 1000);
}
private static int MessagingPort()
{
return DebuggingPort() + 2;
}
private static void ReceiveMessage(object sender, MessageEventArgs args)
{
OnMessage(args.Message);
}
private static void OnUpdate()
{
lock (_incomingLock)
{
while (_incoming.Count > 0)
{
ProcessIncoming(_incoming.Dequeue());
}
}
lock (_clientsLock)
{
foreach (var client in _clients.Values.ToArray())
{
if (DateTime.Now.Subtract(client.LastMessage) > TimeSpan.FromMilliseconds(4000))
_clients.Remove(client.EndPoint);
}
}
}
private static void AddMessage(Message message)
{
lock (_incomingLock)
{
_incoming.Enqueue(message);
}
}
private static void ProcessIncoming(Message message)
{
lock (_clientsLock)
{
CheckClient(message);
}
switch (message.Type)
{
case MessageType.Ping:
Answer(message, MessageType.Pong);
break;
case MessageType.Play:
Shutdown();
EditorApplication.isPlaying = true;
break;
case MessageType.Stop:
EditorApplication.isPlaying = false;
break;
case MessageType.Pause:
EditorApplication.isPaused = true;
break;
case MessageType.Unpause:
EditorApplication.isPaused = false;
break;
case MessageType.Build:
// Not used anymore
break;
case MessageType.Refresh:
Refresh();
break;
case MessageType.Version:
Answer(message, MessageType.Version, PackageVersion());
break;
case MessageType.UpdatePackage:
// Not used anymore
break;
case MessageType.ProjectPath:
Answer(message, MessageType.ProjectPath, Path.GetFullPath(Path.Combine(Application.dataPath, "..")));
break;
case MessageType.ExecuteTests:
TestRunnerApiListener.ExecuteTests(message.Value);
break;
case MessageType.RetrieveTestList:
TestRunnerApiListener.RetrieveTestList(message.Value);
break;
case MessageType.ShowUsage:
UsageUtility.ShowUsage(message.Value);
break;
}
}
private static void CheckClient(Message message)
{
Client client;
var endPoint = message.Origin;
if (!_clients.TryGetValue(endPoint, out client))
{
client = new Client
{
EndPoint = endPoint,
LastMessage = DateTime.Now
};
_clients.Add(endPoint, client);
}
else
{
client.LastMessage = DateTime.Now;
}
}
internal static string PackageVersion()
{
var package = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(VisualStudioIntegration).Assembly);
return package.version;
}
private static void Refresh()
{
// If the user disabled auto-refresh in Unity, do not try to force refresh the Asset database
if (!EditorPrefs.GetBool("kAutoRefresh", true))
return;
RunOnceOnUpdate(AssetDatabase.Refresh);
}
private static void OnMessage(Message message)
{
AddMessage(message);
}
private static void Answer(Client client, MessageType answerType, string answerValue)
{
Answer(client.EndPoint, answerType, answerValue);
}
private static void Answer(Message message, MessageType answerType, string answerValue = "")
{
var targetEndPoint = message.Origin;
Answer(
targetEndPoint,
answerType,
answerValue);
}
private static void Answer(IPEndPoint targetEndPoint, MessageType answerType, string answerValue)
{
_messager?.SendMessage(targetEndPoint, answerType, answerValue);
}
private static void Shutdown()
{
if (_messager == null)
return;
_messager.ReceiveMessage -= ReceiveMessage;
_messager.Dispose();
_messager = null;
}
internal static void BroadcastMessage(MessageType type, string value)
{
lock (_clientsLock)
{
foreach (var client in _clients.Values.ToArray())
{
Answer(client, type, value);
}
}
}
}
}

View file

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

View file

@ -0,0 +1,17 @@
{
"name": "Unity.VisualStudio.Editor",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"Newtonsoft.Json.dll"
],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b93f844d45cfcc44fa2b0eed5c9ec6bb
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2019 Unity Technologies
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1fe9e6e1ae93cf64bbdfbd29300752e1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,31 @@
This package contains third-party software components governed by the license(s) indicated below:
---------
Component Name: VSWhere
License Type: "MIT"
The MIT License (MIT)
Copyright (C) Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------
Component Name: benbuck/EnvDTE
License Type: Zero-Clause BSD
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

Some files were not shown because too many files have changed in this diff Show more