Initial Commit
This commit is contained in:
parent
53eb92e9af
commit
270ab7d11f
15341 changed files with 700234 additions and 0 deletions
|
@ -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.
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6022bdd5b2896a54f94123b812705321
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cd78ce5b75b8be84d8b94cd99903161f
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4a707af59e3522d43afe34dfc4429b86
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly:InternalsVisibleTo("Unity.VisualStudio.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d791d407901442e49862d3aa783ce8af
|
||||
timeCreated: 1602756877
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 36422aa067e092e45b9820da2ee3e728
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 41b2d972bdac29e4a89ef08b3b52c248
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cb67edc1800c2ec4ba8dfb1cf0dfc84a
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: abe003ac6fee32e4892100a78f555011
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6f1dc05fb6e7d3e4f89ae9ca482735be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8e6c7ea7c059fb547b6723aaf225900b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2f820130c86c28547a0f1a2f4c73155b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3eda7a83649158546826efb3ffe6c1e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 917a51fff055ce547b4ad1215321f3da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de1c9ea7b82c9904d9e5fba2ee70a998
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 275143c81d816ef4286fdc67aabc20c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f3edbdc86577af648a23263aa75161e1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e249ae353801f043a6e4173410c6152
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 369c09afe05d2c346af49faef943c773
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f6674c38820d12a49ac116d416521d85
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ded625cf0d03fa94c9f939fd13ced18d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 38cb3a4a17d2cfd41926da95ce675934
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1e5abb64fdd0542b38f4dc1b60343e8a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1
Library/PackageCache/com.unity.ide.visualstudio@2.0.7/Editor/Plugins/.gitattributes
vendored
Normal file
1
Library/PackageCache/com.unity.ide.visualstudio@2.0.7/Editor/Plugins/.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration binary
|
|
@ -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:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 543eb5eeeb1d5424ca8876b93fad5326
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 29239d79a3471495e9d270601006dad7
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e811c7e1c1e9a4b50b237772d317959f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Binary file not shown.
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9c3599bc139404df2955d3ffd39d60d6
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 11ca2399a9422473eb66bca747f3ad52
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3379e8bd711774041a330f218af69b7a
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8beeeeebc0857854d8b4e2c2895dd7a9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 57537f08f8e923f488e4aadabb831c9b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ec80b1fb8938b3b4ab442d10390c5315
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7652904b1008e324fb7cfb952ea87656
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9f3705b95d031e84c82f140d8e980867
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 555fcccd6b79a864f83e7a319daa1c3e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: af4c2c762e1d8e949a6bc458973df6e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fbbb1ee655846b043baf6c3502b5ce49
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5c1b8a755d2c97640bbb207c43f4cf61
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 829d4d6bc39fd1044ba4c5fc2a9c911f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b9308b762484008498bb5cd1886aa491
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7f9f1d015d7a8ba46b7d71acfcda3ae7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b73b3de0d473d4a1c887ab31f69b1a8d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f47f2d030bc1d415a8d15a51dbcc39a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0b59b40c84c6a5348a188c16b17c7b40
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fae6007c1ac2cc744b2891fd4d279c96
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace Microsoft.Unity.VisualStudio.Editor.Testing
|
||||
{
|
||||
[Serializable]
|
||||
internal enum TestStatusAdaptor
|
||||
{
|
||||
Passed,
|
||||
Skipped,
|
||||
Inconclusive,
|
||||
Failed,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0719f1a8b2a284e1182b352e6c8c3c60
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a8c76505bcc613640ade706bdb0f1cba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5a7aba2d3d458e04eb4210c0303fbf64
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5b17896803f77494da73d73448fb6cb4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 585c3fb85b32bd64e8814074e754163e
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ffe1bdf971d321f4db593c4c6ebd6e47
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0173aff8c07e06b42af07ebdd7f08032
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bb86eea06f54fb24caa7046a8a764945
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 48fcd6ebd5ce8fd4cbe931895233677d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b93f844d45cfcc44fa2b0eed5c9ec6bb
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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.
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1fe9e6e1ae93cf64bbdfbd29300752e1
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue