You've already forked com.unity.ide.cursor
mirror of
https://github.com/boxqkrtm/com.unity.ide.cursor.git
synced 2026-05-14 14:20:09 +00:00
com.unity.ide.visualstudio@2.0.0
## [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.
This commit is contained in:
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,5 +1,23 @@
|
||||
# Code Editor Package for Visual Studio
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
# Contributing
|
||||
|
||||
## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement)
|
||||
By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions.
|
||||
## 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
|
||||
Binary file not shown.
BIN
Editor/COMIntegration/Release/COMIntegration.exe
Normal file
BIN
Editor/COMIntegration/Release/COMIntegration.exe
Normal file
Binary file not shown.
@@ -1,275 +1,165 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 System.Linq;
|
||||
using Unity.CodeEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Win32;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
|
||||
namespace VisualStudioEditor
|
||||
namespace Microsoft.Unity.VisualStudio.Editor
|
||||
{
|
||||
public interface IDiscovery {
|
||||
CodeEditor.Installation[] PathCallback();
|
||||
}
|
||||
internal static class Discovery
|
||||
{
|
||||
public static IEnumerable<VisualStudioInstallation> GetVisualStudioInstallations()
|
||||
{
|
||||
if (VisualStudioEditor.IsWindows)
|
||||
{
|
||||
foreach (var installation in QueryVsWhere())
|
||||
yield return installation;
|
||||
}
|
||||
|
||||
public class Discovery : IDiscovery {
|
||||
internal static string VisualStudioVersionToNiceName(VisualStudioVersion version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case VisualStudioVersion.Invalid: return "Invalid Version";
|
||||
case VisualStudioVersion.VisualStudio2008: return "Visual Studio 2008";
|
||||
case VisualStudioVersion.VisualStudio2010: return "Visual Studio 2010";
|
||||
case VisualStudioVersion.VisualStudio2012: return "Visual Studio 2012";
|
||||
case VisualStudioVersion.VisualStudio2013: return "Visual Studio 2013";
|
||||
case VisualStudioVersion.VisualStudio2015: return "Visual Studio 2015";
|
||||
case VisualStudioVersion.VisualStudio2017: return "Visual Studio 2017";
|
||||
case VisualStudioVersion.VisualStudio2019: return "Visual Studio 2019";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(version), version, null);
|
||||
}
|
||||
}
|
||||
if (VisualStudioEditor.IsOSX)
|
||||
{
|
||||
var candidates = Directory.EnumerateDirectories("/Applications", "*.app");
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (TryDiscoverInstallation(candidate, out var installation))
|
||||
yield return installation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CodeEditor.Installation[] PathCallback()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (VSEditor.IsWindows)
|
||||
{
|
||||
return GetInstalledVisualStudios().Select(pair => new CodeEditor.Installation
|
||||
{
|
||||
Path = pair.Value[0],
|
||||
Name = VisualStudioVersionToNiceName(pair.Key)
|
||||
}).ToArray();
|
||||
}
|
||||
if (VSEditor.IsOSX)
|
||||
{
|
||||
var installationList = new List<CodeEditor.Installation>();
|
||||
AddIfDirectoryExists("Visual Studio", "/Applications/Visual Studio.app", installationList);
|
||||
AddIfDirectoryExists("Visual Studio (Preview)", "/Applications/Visual Studio (Preview).app", installationList);
|
||||
return installationList.ToArray();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UnityEngine.Debug.Log($"Error detecting Visual Studio installations: {ex.Message}{Environment.NewLine}{ex.StackTrace}");
|
||||
}
|
||||
return new CodeEditor.Installation[0];
|
||||
}
|
||||
private static bool IsCandidateToDiscovery(string path)
|
||||
{
|
||||
if (File.Exists(path) && VisualStudioEditor.IsWindows && Regex.IsMatch(path, "devenv.exe$", RegexOptions.IgnoreCase))
|
||||
return true;
|
||||
|
||||
void AddIfDirectoryExists(string name, string path, List<CodeEditor.Installation> installations)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
installations.Add(new CodeEditor.Installation { Name = name, Path = path });
|
||||
}
|
||||
}
|
||||
if (Directory.Exists(path) && VisualStudioEditor.IsOSX && Regex.IsMatch(path, "Visual\\s?Studio(?!.*Code.*).*.app$", RegexOptions.IgnoreCase))
|
||||
return true;
|
||||
|
||||
static string GetRegistryValue(string path, string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Registry.GetValue(path, key, null) as string;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derives the Visual Studio installation path from the debugger path
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The Visual Studio installation path (to devenv.exe)
|
||||
/// </returns>
|
||||
/// <param name='debuggerPath'>
|
||||
/// The debugger path from the windows registry
|
||||
/// </param>
|
||||
static string DeriveVisualStudioPath(string debuggerPath)
|
||||
{
|
||||
string startSentinel = DeriveProgramFilesSentinel();
|
||||
string endSentinel = "Common7";
|
||||
bool started = false;
|
||||
string[] tokens = debuggerPath.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
|
||||
public static bool TryDiscoverInstallation(string editorPath, out VisualStudioInstallation installation)
|
||||
{
|
||||
installation = null;
|
||||
|
||||
string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||
if (string.IsNullOrEmpty(editorPath))
|
||||
return false;
|
||||
|
||||
// Walk directories in debugger path, chop out "Program Files\INSTALLATION\PATH\HERE\Common7"
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
if (!started && string.Equals(startSentinel, token, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
started = true;
|
||||
continue;
|
||||
}
|
||||
if (started)
|
||||
{
|
||||
path = Path.Combine(path, token);
|
||||
if (string.Equals(endSentinel, token, StringComparison.OrdinalIgnoreCase))
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!IsCandidateToDiscovery(editorPath))
|
||||
return false;
|
||||
|
||||
return Path.Combine(path, "IDE", "devenv.exe");
|
||||
}
|
||||
// On windows we use the executable directly, so we can query extra information
|
||||
var fvi = editorPath;
|
||||
|
||||
/// <summary>
|
||||
/// Derives the program files sentinel for grabbing the VS installation path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// From a path like 'c:\Archivos de programa (x86)', returns 'Archivos de programa'
|
||||
/// </remarks>
|
||||
static string DeriveProgramFilesSentinel()
|
||||
{
|
||||
string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
|
||||
.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
|
||||
.LastOrDefault();
|
||||
// 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 (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
// This needs to be the "real" Program Files regardless of 64bitness
|
||||
int index = path.LastIndexOf("(x86)");
|
||||
if (0 <= index)
|
||||
path = path.Remove(index);
|
||||
return path.TrimEnd();
|
||||
}
|
||||
if (!File.Exists(fvi))
|
||||
return false;
|
||||
|
||||
return "Program Files";
|
||||
}
|
||||
// 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");
|
||||
|
||||
public static void ParseRawDevEnvPaths(string[] rawDevEnvPaths, Dictionary<VisualStudioVersion, string[]> versions)
|
||||
{
|
||||
if (rawDevEnvPaths == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
installation = new VisualStudioInstallation()
|
||||
{
|
||||
IsPrerelease = isPrerelease,
|
||||
Name = $"{vi.FileDescription}{(isPrerelease && VisualStudioEditor.IsOSX ? " Preview" : string.Empty)} [{version.ToString(3)}]",
|
||||
Path = editorPath,
|
||||
Version = version
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
var v2017 = rawDevEnvPaths.Where(path => path.Contains("2017")).ToArray();
|
||||
var v2019 = rawDevEnvPaths.Where(path => path.Contains("2019")).ToArray();
|
||||
#region VsWhere Json Schema
|
||||
#pragma warning disable CS0649
|
||||
[Serializable]
|
||||
internal class VsWhereResult
|
||||
{
|
||||
public VsWhereEntry[] entries;
|
||||
|
||||
if (v2017.Length > 0)
|
||||
{
|
||||
versions[VisualStudioVersion.VisualStudio2017] = v2017;
|
||||
}
|
||||
public static VsWhereResult FromJson(string json)
|
||||
{
|
||||
return JsonUtility.FromJson<VsWhereResult>("{ \"" + nameof(VsWhereResult.entries) + "\": " + json + " }");
|
||||
}
|
||||
|
||||
if (v2019.Length > 0)
|
||||
{
|
||||
versions[VisualStudioVersion.VisualStudio2019] = v2019;
|
||||
}
|
||||
}
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects Visual Studio installations using the Windows registry
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The detected Visual Studio installations
|
||||
/// </returns>
|
||||
public static Dictionary<VisualStudioVersion, string[]> GetInstalledVisualStudios()
|
||||
{
|
||||
var versions = new Dictionary<VisualStudioVersion, string[]>();
|
||||
[Serializable]
|
||||
internal class VsWhereEntry
|
||||
{
|
||||
public string displayName;
|
||||
public bool isPrerelease;
|
||||
public string productPath;
|
||||
public VsWhereCatalog catalog;
|
||||
}
|
||||
|
||||
if (VSEditor.IsWindows)
|
||||
{
|
||||
foreach (VisualStudioVersion version in Enum.GetValues(typeof(VisualStudioVersion)))
|
||||
{
|
||||
if (version > VisualStudioVersion.VisualStudio2015)
|
||||
continue;
|
||||
[Serializable]
|
||||
internal class VsWhereCatalog
|
||||
{
|
||||
public string productDisplayVersion; // non parseable like "16.3.0 Preview 3.0"
|
||||
public string buildVersion;
|
||||
}
|
||||
#pragma warning restore CS3021
|
||||
#endregion
|
||||
|
||||
try
|
||||
{
|
||||
// Try COMNTOOLS environment variable first
|
||||
FindLegacyVisualStudio(version, versions);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"VS: {e.Message}");
|
||||
}
|
||||
}
|
||||
private static IEnumerable<VisualStudioInstallation> QueryVsWhere()
|
||||
{
|
||||
var progpath = FileUtility
|
||||
.FindPackageAssetFullPath("VSWhere a:packages", "vswhere.exe")
|
||||
.FirstOrDefault();
|
||||
|
||||
var raw = FindVisualStudioDevEnvPaths();
|
||||
if (string.IsNullOrWhiteSpace(progpath))
|
||||
return Enumerable.Empty<VisualStudioInstallation>();
|
||||
|
||||
ParseRawDevEnvPaths(raw.ToArray(), versions);
|
||||
}
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = progpath,
|
||||
Arguments = "-prerelease -format json",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
}
|
||||
};
|
||||
|
||||
return versions;
|
||||
}
|
||||
using (process)
|
||||
{
|
||||
var json = string.Empty;
|
||||
|
||||
static void FindLegacyVisualStudio(VisualStudioVersion version, Dictionary<VisualStudioVersion, string[]> versions)
|
||||
{
|
||||
string key = Environment.GetEnvironmentVariable($"VS{(int)version}0COMNTOOLS");
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
string path = Path.Combine(key, "..", "IDE", "devenv.exe");
|
||||
if (File.Exists(path))
|
||||
{
|
||||
versions[version] = new[] { path };
|
||||
return;
|
||||
}
|
||||
}
|
||||
process.OutputDataReceived += (o, e) => json += e.Data;
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.WaitForExit();
|
||||
|
||||
// Try the proper registry key
|
||||
key = GetRegistryValue(
|
||||
$@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\{(int)version}.0", "InstallDir");
|
||||
|
||||
// Try to fallback to the 32bits hive
|
||||
if (string.IsNullOrEmpty(key))
|
||||
key = GetRegistryValue(
|
||||
$@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\{(int)version}.0", "InstallDir");
|
||||
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
string path = Path.Combine(key, "devenv.exe");
|
||||
if (File.Exists(path))
|
||||
{
|
||||
versions[version] = new[] { path };
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to debugger key
|
||||
key = GetRegistryValue(
|
||||
// VS uses this key for the local debugger path
|
||||
$@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\{(int)version}.0\Debugger", "FEQARuntimeImplDll");
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
string path = DeriveVisualStudioPath(key);
|
||||
if (!string.IsNullOrEmpty(path) && File.Exists(path))
|
||||
versions[version] = new[] { DeriveVisualStudioPath(key) };
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<string> FindVisualStudioDevEnvPaths()
|
||||
{
|
||||
string asset = AssetDatabase.FindAssets("VSWhere a:packages").Select(AssetDatabase.GUIDToAssetPath).FirstOrDefault(assetPath => assetPath.Contains("vswhere.exe"));
|
||||
if (string.IsNullOrWhiteSpace(asset)) // This may be called too early where the asset database has not replicated this information yet.
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
UnityEditor.PackageManager.PackageInfo packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(asset);
|
||||
var progpath = packageInfo.resolvedPath + asset.Substring("Packages/com.unity.ide.visualstudio".Length);
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = progpath,
|
||||
Arguments = "-prerelease -property productPath",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
||||
while (!process.StandardOutput.EndOfStream)
|
||||
{
|
||||
yield return process.StandardOutput.ReadLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
var result = VsWhereResult.FromJson(json);
|
||||
return result.ToVisualStudioInstallations();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Editor/FileUtility.cs
Normal file
92
Editor/FileUtility.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
|
||||
return path.Replace(string.Concat(WinSeparator, WinSeparator), WinSeparator.ToString());
|
||||
}
|
||||
|
||||
internal static bool IsFileInProjectDirectory(string fileName)
|
||||
{
|
||||
var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
|
||||
fileName = Normalize(fileName);
|
||||
|
||||
if (!Path.IsPathRooted(fileName))
|
||||
fileName = Path.Combine(basePath, fileName);
|
||||
|
||||
return string.Equals(Path.GetDirectoryName(fileName), basePath, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static string FileNameWithoutExtension(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var indexOfDot = -1;
|
||||
var indexOfSlash = 0;
|
||||
for (var i = path.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (indexOfDot == -1 && path[i] == '.')
|
||||
{
|
||||
indexOfDot = i;
|
||||
}
|
||||
|
||||
if (indexOfSlash == 0 && path[i] == '/' || path[i] == '\\')
|
||||
{
|
||||
indexOfSlash = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (indexOfDot == -1)
|
||||
{
|
||||
indexOfDot = path.Length - 1;
|
||||
}
|
||||
|
||||
return path.Substring(indexOfSlash, indexOfDot - indexOfSlash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55aae143147983c4ca41af0f79695248
|
||||
guid: 2f820130c86c28547a0f1a2f4c73155b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
40
Editor/Messaging/Deserializer.cs
Normal file
40
Editor/Messaging/Deserializer.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
|
||||
// Max UDP packet size is 65507
|
||||
private const int MaxStringLength = 65000;
|
||||
|
||||
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 && length <= MaxStringLength
|
||||
? Encoding.UTF8.GetString(_reader.ReadBytes(length))
|
||||
: "";
|
||||
}
|
||||
|
||||
public bool CanReadMore()
|
||||
{
|
||||
return _reader.BaseStream.Position < _reader.BaseStream.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c70a64eb158b0104bb528b12e1d80c2a
|
||||
guid: 3eda7a83649158546826efb3ffe6c1e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
18
Editor/Messaging/ExceptionEventArgs.cs
Normal file
18
Editor/Messaging/ExceptionEventArgs.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
|
||||
{
|
||||
internal class ExceptionEventArgs
|
||||
{
|
||||
public Exception Exception { get; }
|
||||
|
||||
public ExceptionEventArgs(Exception exception)
|
||||
{
|
||||
Exception = exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Messaging/ExceptionEventArgs.cs.meta
Normal file
11
Editor/Messaging/ExceptionEventArgs.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 917a51fff055ce547b4ad1215321f3da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
23
Editor/Messaging/Message.cs
Normal file
23
Editor/Messaging/Message.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
|
||||
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
|
||||
{
|
||||
internal class Message
|
||||
{
|
||||
public MessageType Type { get; set; }
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public IPEndPoint Origin { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "<Message type:{0} value:{1}>", Type, Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Messaging/Message.cs.meta
Normal file
11
Editor/Messaging/Message.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de1c9ea7b82c9904d9e5fba2ee70a998
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
19
Editor/Messaging/MessageEventArgs.cs
Normal file
19
Editor/Messaging/MessageEventArgs.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
|
||||
{
|
||||
internal class MessageEventArgs
|
||||
{
|
||||
public Message Message
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public MessageEventArgs(Message message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Messaging/MessageEventArgs.cs.meta
Normal file
11
Editor/Messaging/MessageEventArgs.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 275143c81d816ef4286fdc67aabc20c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Editor/Messaging/MessageType.cs
Normal file
34
Editor/Messaging/MessageType.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
11
Editor/Messaging/MessageType.cs.meta
Normal file
11
Editor/Messaging/MessageType.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3edbdc86577af648a23263aa75161e1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
176
Editor/Messaging/Messenger.cs
Normal file
176
Editor/Messaging/Messenger.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
ReceiveMessage?.Invoke(this, new MessageEventArgs(message));
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
RaiseMessagerException(e);
|
||||
}
|
||||
|
||||
BeginReceiveMessage();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Messaging/Messenger.cs.meta
Normal file
11
Editor/Messaging/Messenger.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e249ae353801f043a6e4173410c6152
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
43
Editor/Messaging/Serializer.cs
Normal file
43
Editor/Messaging/Serializer.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
|
||||
{
|
||||
internal class Serializer
|
||||
{
|
||||
private readonly MemoryStream _stream;
|
||||
private readonly BinaryWriter _writer;
|
||||
|
||||
public Serializer()
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
_writer = new BinaryWriter(_stream);
|
||||
}
|
||||
|
||||
public void WriteInt32(int i)
|
||||
{
|
||||
_writer.Write(i);
|
||||
}
|
||||
|
||||
public void WriteString(string s)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(s ?? "");
|
||||
if (bytes.Length > 0)
|
||||
{
|
||||
_writer.Write(bytes.Length);
|
||||
_writer.Write(bytes);
|
||||
}
|
||||
else
|
||||
_writer.Write(0);
|
||||
}
|
||||
|
||||
public byte[] Buffer()
|
||||
{
|
||||
return _stream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Messaging/Serializer.cs.meta
Normal file
11
Editor/Messaging/Serializer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 369c09afe05d2c346af49faef943c773
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
53
Editor/Messaging/UdpSocket.cs
Normal file
53
Editor/Messaging/UdpSocket.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Messaging/UdpSocket.cs.meta
Normal file
11
Editor/Messaging/UdpSocket.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38cb3a4a17d2cfd41926da95ce675934
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd66e4390e06fc14e92b9822744f2fb1
|
||||
guid: a20df6e3467b24ed5b49c857ce39e096
|
||||
folderAsset: yes
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 179209ff257e808409c755d32ecf1086
|
||||
guid: 058b02c03ea09473aab4dc5fc50af1ef
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@@ -3,19 +3,17 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>18E226</string>
|
||||
<string>19B88</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>AppleEventIntegrationPlugin</string>
|
||||
<string>AppleEventIntegration</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.unity.AppleEventIntegrationPlugin</string>
|
||||
<string>com.unity.visualstudio.AppleEventIntegration</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>AppleEventIntegrationPlugin</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<string>AppleEventIntegration</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
@@ -27,7 +25,7 @@
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>10E1001</string>
|
||||
<string>10E125</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
@@ -37,7 +35,7 @@
|
||||
<key>DTXcode</key>
|
||||
<string>1020</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>10E1001</string>
|
||||
<string>10E125</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2019 Unity. All rights reserved.</string>
|
||||
</dict>
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2694cce95579ec47a939a1a3efb8f33
|
||||
guid: 680cf1008b4284eddbb82ec4d76644a1
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0f0c231a0e381e4ea37a97a8e7837a0
|
||||
guid: ba4355216f6c44abbb17503872c42c97
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b94b55fbf62c6bd42a2f3edf2fd52f83
|
||||
guid: 4e53c7f7b5c7e4a3d890cb596ed56c22
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
Binary file not shown.
@@ -1,115 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8529f5a60acb8cd4395bd1cec74495ae
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,3 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
@@ -6,16 +11,21 @@ using System.Security;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using Unity.CodeEditor;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEditor.PackageManager;
|
||||
using UnityEditor.Scripting.Compilers;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VisualStudioEditor
|
||||
namespace Microsoft.Unity.VisualStudio.Editor
|
||||
{
|
||||
public enum ScriptingLanguage
|
||||
{
|
||||
None,
|
||||
CSharp
|
||||
}
|
||||
|
||||
public interface IGenerator {
|
||||
bool SyncIfNeeded(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles);
|
||||
void Sync();
|
||||
@@ -23,6 +33,7 @@ namespace VisualStudioEditor
|
||||
string SolutionFile();
|
||||
string ProjectDirectory { get; }
|
||||
void GenerateAll(bool generateAll);
|
||||
bool IsSupportedFile(string path);
|
||||
}
|
||||
|
||||
public interface IAssemblyNameProvider
|
||||
@@ -63,31 +74,10 @@ namespace VisualStudioEditor
|
||||
|
||||
public class ProjectGeneration : IGenerator
|
||||
{
|
||||
enum ScriptingLanguage
|
||||
{
|
||||
None,
|
||||
CSharp
|
||||
}
|
||||
|
||||
public static readonly string MSBuildNamespaceUri = "http://schemas.microsoft.com/developer/msbuild/2003";
|
||||
|
||||
const string k_WindowsNewline = "\r\n";
|
||||
|
||||
/// <summary>
|
||||
/// Map source extensions to ScriptingLanguages
|
||||
/// </summary>
|
||||
static readonly Dictionary<string, ScriptingLanguage> k_BuiltinSupportedExtensions = new Dictionary<string, ScriptingLanguage>
|
||||
{
|
||||
{ "cs", ScriptingLanguage.CSharp },
|
||||
{ "uxml", ScriptingLanguage.None },
|
||||
{ "uss", ScriptingLanguage.None },
|
||||
{ "shader", ScriptingLanguage.None },
|
||||
{ "compute", ScriptingLanguage.None },
|
||||
{ "cginc", ScriptingLanguage.None },
|
||||
{ "hlsl", ScriptingLanguage.None },
|
||||
{ "glslinc", ScriptingLanguage.None }
|
||||
};
|
||||
|
||||
string m_SolutionProjectEntryTemplate = string.Join("\r\n",
|
||||
@"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""",
|
||||
@"EndProject").Replace(" ", "\t");
|
||||
@@ -105,6 +95,8 @@ namespace VisualStudioEditor
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
string[] m_ProjectSupportedExtensions = new string[0];
|
||||
string[] m_BuiltinSupportedExtensions = new string[0];
|
||||
|
||||
public string ProjectDirectory { get; }
|
||||
|
||||
public TestSettings Settings { get; set; }
|
||||
@@ -125,6 +117,8 @@ namespace VisualStudioEditor
|
||||
ProjectDirectory = tempDirectory.Replace('\\', '/');
|
||||
m_ProjectName = Path.GetFileName(ProjectDirectory);
|
||||
m_AssemblyNameProvider = assemblyNameProvider;
|
||||
|
||||
SetupProjectSupportedExtensions();
|
||||
}
|
||||
|
||||
public void GenerateAll(bool generateAll)
|
||||
@@ -187,60 +181,65 @@ namespace VisualStudioEditor
|
||||
void SetupProjectSupportedExtensions()
|
||||
{
|
||||
m_ProjectSupportedExtensions = EditorSettings.projectGenerationUserExtensions;
|
||||
m_BuiltinSupportedExtensions = EditorSettings.projectGenerationBuiltinExtensions;
|
||||
}
|
||||
|
||||
bool ShouldFileBePartOfSolution(string file)
|
||||
{
|
||||
string extension = Path.GetExtension(file);
|
||||
|
||||
// Exclude files coming from packages except if they are internalized.
|
||||
if (!m_ShouldGenerateAll && IsInternalizedPackagePath(file))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dll's are not scripts but still need to be included..
|
||||
if (extension == ".dll")
|
||||
return true;
|
||||
|
||||
if (file.ToLower().EndsWith(".asmdef"))
|
||||
return true;
|
||||
|
||||
return IsSupportedExtension(extension);
|
||||
return IsSupportedFile(file);
|
||||
}
|
||||
|
||||
bool IsSupportedExtension(string extension)
|
||||
static string GetExtensionWithoutDot(string path)
|
||||
{
|
||||
extension = extension.TrimStart('.');
|
||||
if (k_BuiltinSupportedExtensions.ContainsKey(extension))
|
||||
// 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;
|
||||
}
|
||||
|
||||
static ScriptingLanguage ScriptingLanguageFor(Assembly island)
|
||||
{
|
||||
return ScriptingLanguageFor(GetExtensionOfSourceFiles(island.sourceFiles));
|
||||
var files = island.sourceFiles;
|
||||
|
||||
if (files.Length == 0)
|
||||
return ScriptingLanguage.None;
|
||||
|
||||
return ScriptingLanguageFor(files[0]);
|
||||
}
|
||||
|
||||
static string GetExtensionOfSourceFiles(string[] files)
|
||||
static ScriptingLanguage ScriptingLanguageFor(string path)
|
||||
{
|
||||
return files.Length > 0 ? GetExtensionOfSourceFile(files[0]) : "NA";
|
||||
}
|
||||
|
||||
static string GetExtensionOfSourceFile(string file)
|
||||
{
|
||||
var ext = Path.GetExtension(file).ToLower();
|
||||
ext = ext.Substring(1); //strip dot
|
||||
return ext;
|
||||
}
|
||||
|
||||
static ScriptingLanguage ScriptingLanguageFor(string extension)
|
||||
{
|
||||
return k_BuiltinSupportedExtensions.TryGetValue(extension.TrimStart('.'), out var result)
|
||||
? result
|
||||
: ScriptingLanguage.None;
|
||||
return GetExtensionWithoutDot(path) == "cs" ? ScriptingLanguage.CSharp : ScriptingLanguage.None;
|
||||
}
|
||||
|
||||
static List<Type> SafeGetTypes(System.Reflection.Assembly a)
|
||||
@@ -319,8 +318,7 @@ namespace VisualStudioEditor
|
||||
continue;
|
||||
}
|
||||
|
||||
string extension = Path.GetExtension(asset);
|
||||
if (IsSupportedExtension(extension) && ScriptingLanguage.None == ScriptingLanguageFor(extension))
|
||||
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");
|
||||
@@ -330,7 +328,7 @@ namespace VisualStudioEditor
|
||||
continue;
|
||||
}
|
||||
|
||||
assemblyName = Utility.FileNameWithoutExtension(assemblyName);
|
||||
assemblyName = FileUtility.FileNameWithoutExtension(assemblyName);
|
||||
|
||||
if (!stringBuilders.TryGetValue(assemblyName, out var projectBuilder))
|
||||
{
|
||||
@@ -500,6 +498,7 @@ namespace VisualStudioEditor
|
||||
var references = new List<string>();
|
||||
var projectReferences = new List<Match>();
|
||||
|
||||
projectBuilder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
|
||||
foreach (string file in assembly.sourceFiles)
|
||||
{
|
||||
if (!ShouldFileBePartOfSolution(file))
|
||||
@@ -509,16 +508,18 @@ namespace VisualStudioEditor
|
||||
var fullFile = EscapedRelativePathFor(file);
|
||||
if (".dll" != extension)
|
||||
{
|
||||
projectBuilder.Append(" <Compile Include=\"").Append(fullFile).Append("\" />").Append(k_WindowsNewline);
|
||||
projectBuilder.Append(" <Compile Include=\"").Append(fullFile).Append("\" />").Append(k_WindowsNewline);
|
||||
}
|
||||
else
|
||||
{
|
||||
references.Add(fullFile);
|
||||
}
|
||||
}
|
||||
projectBuilder.Append(@" </ItemGroup>").Append(k_WindowsNewline);
|
||||
|
||||
var assemblyName = Utility.FileNameWithoutExtension(assembly.outputPath);
|
||||
var assemblyName = FileUtility.FileNameWithoutExtension(assembly.outputPath);
|
||||
|
||||
projectBuilder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
|
||||
// Append additional non-script files that should be included in project generation.
|
||||
if (allAssetsProjectParts.TryGetValue(assemblyName, out var additionalAssetsForProject))
|
||||
projectBuilder.Append(additionalAssetsForProject);
|
||||
@@ -558,11 +559,11 @@ namespace VisualStudioEditor
|
||||
{
|
||||
AppendReference(reference, projectBuilder);
|
||||
}
|
||||
projectBuilder.Append(@" </ItemGroup>").Append(k_WindowsNewline);
|
||||
|
||||
if (0 < projectReferences.Count)
|
||||
{
|
||||
projectBuilder.AppendLine(" </ItemGroup>");
|
||||
projectBuilder.AppendLine(" <ItemGroup>");
|
||||
projectBuilder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
|
||||
foreach (Match reference in projectReferences)
|
||||
{
|
||||
var referencedProject = reference.Groups["project"].Value;
|
||||
@@ -570,33 +571,48 @@ namespace VisualStudioEditor
|
||||
projectBuilder.Append(" <ProjectReference Include=\"").Append(referencedProject).Append(GetProjectExtension()).Append("\">").Append(k_WindowsNewline);
|
||||
projectBuilder.Append(" <Project>{").Append(ProjectGuid(Path.Combine("Temp", reference.Groups["project"].Value + ".dll"))).Append("}</Project>").Append(k_WindowsNewline);
|
||||
projectBuilder.Append(" <Name>").Append(referencedProject).Append("</Name>").Append(k_WindowsNewline);
|
||||
projectBuilder.AppendLine(" </ProjectReference>");
|
||||
projectBuilder.Append(" </ProjectReference>").Append(k_WindowsNewline);
|
||||
}
|
||||
projectBuilder.Append(@" </ItemGroup>").Append(k_WindowsNewline);
|
||||
}
|
||||
|
||||
projectBuilder.Append(ProjectFooter());
|
||||
return projectBuilder.ToString();
|
||||
}
|
||||
|
||||
static void AppendReference(string fullReference, StringBuilder projectBuilder)
|
||||
static string XmlFilename(string path)
|
||||
{
|
||||
//replace \ with / and \\ with /
|
||||
var escapedFullPath = SecurityElement.Escape(fullReference);
|
||||
escapedFullPath = escapedFullPath.Replace("\\", "/");
|
||||
escapedFullPath = escapedFullPath.Replace("\\\\", "/");
|
||||
projectBuilder.Append(" <Reference Include=\"").Append(Utility.FileNameWithoutExtension(escapedFullPath)).Append("\">").Append(k_WindowsNewline);
|
||||
projectBuilder.Append(" <HintPath>").Append(escapedFullPath).Append("</HintPath>").Append(k_WindowsNewline);
|
||||
projectBuilder.Append(" </Reference>").Append(k_WindowsNewline);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return path;
|
||||
|
||||
path = path.Replace(@"%", "%25");
|
||||
path = path.Replace(@";", "%3b");
|
||||
|
||||
return XmlEscape(path);
|
||||
}
|
||||
|
||||
static string XmlEscape(string s)
|
||||
{
|
||||
return SecurityElement.Escape(s);
|
||||
}
|
||||
|
||||
void AppendReference(string fullReference, StringBuilder projectBuilder)
|
||||
{
|
||||
var escapedFullPath = EscapedRelativePathFor(fullReference);
|
||||
projectBuilder.Append(" <Reference Include=\"").Append(FileUtility.FileNameWithoutExtension(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, $"{Utility.FileNameWithoutExtension(assembly.outputPath)}.csproj");
|
||||
return Path.Combine(ProjectDirectory, $"{FileUtility.FileNameWithoutExtension(assembly.outputPath)}.csproj");
|
||||
}
|
||||
|
||||
private static readonly Regex InvalidCharactersRegexPattern = new Regex(@"\?|&|\*|""|<|>|\||#|%|\^|;" + (VisualStudioEditor.IsWindows ? "" : "|:"));
|
||||
public string SolutionFile()
|
||||
{
|
||||
return Path.Combine(ProjectDirectory, $"{m_ProjectName}.sln");
|
||||
return Path.Combine(FileUtility.Normalize(ProjectDirectory), $"{InvalidCharactersRegexPattern.Replace(m_ProjectName,"_")}.sln");
|
||||
}
|
||||
|
||||
string ProjectHeader(
|
||||
@@ -611,19 +627,25 @@ namespace VisualStudioEditor
|
||||
var targetFrameworkVersion = "v4.7.1";
|
||||
var targetLanguageVersion = "latest";
|
||||
|
||||
var projectType = ProjectTypeOf(island.outputPath);
|
||||
|
||||
var arguments = new object[]
|
||||
{
|
||||
toolsVersion, productVersion, ProjectGuid(island.outputPath),
|
||||
InternalEditorUtility.GetEngineAssemblyPath(),
|
||||
InternalEditorUtility.GetEditorAssemblyPath(),
|
||||
XmlFilename(FileUtility.Normalize(InternalEditorUtility.GetEngineAssemblyPath())),
|
||||
XmlFilename(FileUtility.Normalize(InternalEditorUtility.GetEditorAssemblyPath())),
|
||||
string.Join(";", new[] { "DEBUG", "TRACE" }.Concat(EditorUserBuildSettings.activeScriptCompilationDefines).Concat(island.defines).Concat(responseFilesData.SelectMany(x => x.Defines)).Distinct().ToArray()),
|
||||
MSBuildNamespaceUri,
|
||||
Utility.FileNameWithoutExtension(island.outputPath),
|
||||
FileUtility.FileNameWithoutExtension(island.outputPath),
|
||||
EditorSettings.projectGenerationRootNamespace,
|
||||
targetFrameworkVersion,
|
||||
targetLanguageVersion,
|
||||
baseDirectory,
|
||||
island.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe)
|
||||
island.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe),
|
||||
// flavoring
|
||||
projectType + ":" + (int)projectType,
|
||||
EditorUserBuildSettings.activeBuildTarget + ":" + (int)EditorUserBuildSettings.activeBuildTarget,
|
||||
Application.unityVersion,
|
||||
};
|
||||
|
||||
try
|
||||
@@ -636,6 +658,29 @@ namespace VisualStudioEditor
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static string GetSolutionText()
|
||||
{
|
||||
return string.Join("\r\n",
|
||||
@@ -651,9 +696,7 @@ namespace VisualStudioEditor
|
||||
@" GlobalSection(ProjectConfigurationPlatforms) = postSolution",
|
||||
@"{3}",
|
||||
@" EndGlobalSection",
|
||||
@" GlobalSection(SolutionProperties) = preSolution",
|
||||
@" HideSolutionNode = FALSE",
|
||||
@" EndGlobalSection",
|
||||
@"{4}",
|
||||
@"EndGlobal",
|
||||
@"").Replace(" ", "\t");
|
||||
}
|
||||
@@ -661,8 +704,8 @@ namespace VisualStudioEditor
|
||||
static string GetProjectFooterTemplate()
|
||||
{
|
||||
return string.Join("\r\n",
|
||||
@" </ItemGroup>",
|
||||
@" <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"">",
|
||||
@@ -674,7 +717,7 @@ namespace VisualStudioEditor
|
||||
@"");
|
||||
}
|
||||
|
||||
static string GetProjectHeaderTemplate()
|
||||
string GetProjectHeaderTemplate()
|
||||
{
|
||||
var header = new[]
|
||||
{
|
||||
@@ -730,13 +773,20 @@ namespace VisualStudioEditor
|
||||
@" </PropertyGroup>"
|
||||
};
|
||||
|
||||
var itemGroupStart = new[]
|
||||
var flavoring = new[]
|
||||
{
|
||||
@" <ItemGroup>"
|
||||
@" <PropertyGroup>",
|
||||
@" <ProjectTypeGuids>{{E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1}};{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}</ProjectTypeGuids>",
|
||||
@" <UnityProjectGenerator>Package</UnityProjectGenerator>",
|
||||
@" <UnityProjectType>{13}</UnityProjectType>",
|
||||
@" <UnityBuildTarget>{14}</UnityBuildTarget>",
|
||||
@" <UnityVersion>{15}</UnityVersion>",
|
||||
@" </PropertyGroup>"
|
||||
};
|
||||
|
||||
var footer = new[]
|
||||
{
|
||||
@" <ItemGroup>",
|
||||
@" <Reference Include=""UnityEngine"">",
|
||||
@" <HintPath>{3}</HintPath>",
|
||||
@" </Reference>",
|
||||
@@ -744,28 +794,73 @@ namespace VisualStudioEditor
|
||||
@" <HintPath>{4}</HintPath>",
|
||||
@" </Reference>",
|
||||
@" </ItemGroup>",
|
||||
@" <ItemGroup>",
|
||||
@""
|
||||
};
|
||||
|
||||
var text = header.Concat(forceExplicitReferences).Concat(itemGroupStart).Concat(footer).ToArray();
|
||||
return string.Join("\r\n", text);
|
||||
var lines = header
|
||||
.Concat(forceExplicitReferences)
|
||||
.Concat(flavoring)
|
||||
.ToList();
|
||||
|
||||
// Only add analyzer block for compatible Visual Studio
|
||||
if (CodeEditor.CurrentEditor is VisualStudioEditor editor && editor.TryGetVisualStudioInstallationForPath(CodeEditor.CurrentEditorInstallation, out var installation))
|
||||
{
|
||||
if (installation.SupportsAnalyzers)
|
||||
{
|
||||
var analyzers = installation.GetAnalyzers();
|
||||
if (analyzers != null && analyzers.Length > 0)
|
||||
{
|
||||
lines.Add(@" <ItemGroup>");
|
||||
foreach (var analyzer in analyzers)
|
||||
lines.Add(string.Format(@" <Analyzer Include=""{0}"" />", EscapedRelativePathFor(analyzer)));
|
||||
lines.Add(@" </ItemGroup>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join("\r\n", lines
|
||||
.Concat(footer));
|
||||
}
|
||||
|
||||
void SyncSolution(IEnumerable<Assembly> islands)
|
||||
{
|
||||
SyncSolutionFileIfNotChanged(SolutionFile(), SolutionText(islands));
|
||||
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 = File.Exists(solutionFile) ? SolutionParser.ParseSolutionFile(solutionFile) : null;
|
||||
SyncSolutionFileIfNotChanged(solutionFile, SolutionText(islands, previousSolution));
|
||||
}
|
||||
|
||||
string SolutionText(IEnumerable<Assembly> islands)
|
||||
string SolutionText(IEnumerable<Assembly> islands, Solution previousSolution = null)
|
||||
{
|
||||
var fileversion = "11.00";
|
||||
var vsversion = "2010";
|
||||
const string fileversion = "12.00";
|
||||
const string vsversion = "15";
|
||||
|
||||
var relevantIslands = RelevantIslandsForMode(islands);
|
||||
string projectEntries = GetProjectEntries(relevantIslands);
|
||||
string projectConfigurations = string.Join(k_WindowsNewline, relevantIslands.Select(i => GetProjectActiveConfigurations(ProjectGuid(i.outputPath))).ToArray());
|
||||
return string.Format(GetSolutionText(), fileversion, vsversion, projectEntries, projectConfigurations);
|
||||
var generatedProjects = ToProjectEntries(relevantIslands);
|
||||
|
||||
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 => !FileUtility.IsFileInProjectDirectory(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);
|
||||
string projectConfigurationsText = string.Join(k_WindowsNewline, projects.Select(p => GetProjectActiveConfigurations(p.ProjectGuid)).ToArray());
|
||||
return string.Format(GetSolutionText(), fileversion, vsversion, projectEntriesText, projectConfigurationsText, propertiesText);
|
||||
}
|
||||
|
||||
static IEnumerable<Assembly> RelevantIslandsForMode(IEnumerable<Assembly> islands)
|
||||
@@ -774,20 +869,69 @@ namespace VisualStudioEditor
|
||||
return relevantIslands;
|
||||
}
|
||||
|
||||
private 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>
|
||||
string GetProjectEntries(IEnumerable<Assembly> islands)
|
||||
string GetProjectEntriesText(IEnumerable<SolutionProjectEntry> entries)
|
||||
{
|
||||
var projectEntries = islands.Select(i => string.Format(
|
||||
var projectEntries = entries.Select(entry => string.Format(
|
||||
m_SolutionProjectEntryTemplate,
|
||||
SolutionGuid(i), Utility.FileNameWithoutExtension(i.outputPath), Path.GetFileName(ProjectFile(i)), ProjectGuid(i.outputPath)
|
||||
entry.ProjectFactoryGuid, entry.Name, entry.FileName, entry.ProjectGuid
|
||||
));
|
||||
|
||||
return string.Join(k_WindowsNewline, projectEntries.ToArray());
|
||||
}
|
||||
|
||||
IEnumerable<SolutionProjectEntry> ToProjectEntries(IEnumerable<Assembly> islands)
|
||||
{
|
||||
foreach (var island in islands)
|
||||
yield return new SolutionProjectEntry()
|
||||
{
|
||||
ProjectFactoryGuid = SolutionGuid(island),
|
||||
Name = FileUtility.FileNameWithoutExtension(island.outputPath),
|
||||
FileName = Path.GetFileName(ProjectFile(island)),
|
||||
ProjectGuid = ProjectGuid(island.outputPath)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate the active configuration string for a given project guid
|
||||
/// </summary>
|
||||
@@ -800,44 +944,36 @@ namespace VisualStudioEditor
|
||||
|
||||
string EscapedRelativePathFor(string file)
|
||||
{
|
||||
var projectDir = ProjectDirectory.Replace('/', '\\');
|
||||
file = file.Replace('/', '\\');
|
||||
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(NormalizePath(path)).Replace('/', '\\');
|
||||
var absolutePath = Path.GetFullPath(FileUtility.Normalize(path));
|
||||
path = SkipPathPrefix(absolutePath, projectDir);
|
||||
}
|
||||
|
||||
return SecurityElement.Escape(path);
|
||||
return XmlFilename(path);
|
||||
}
|
||||
|
||||
static string SkipPathPrefix(string path, string prefix)
|
||||
{
|
||||
if (path.Replace("\\","/").StartsWith($"{prefix}/"))
|
||||
if (path.StartsWith($"{prefix}{Path.DirectorySeparatorChar}") && (path.Length > prefix.Length))
|
||||
return path.Substring(prefix.Length + 1);
|
||||
return path;
|
||||
}
|
||||
|
||||
static string NormalizePath(string path)
|
||||
{
|
||||
if (Path.DirectorySeparatorChar == '\\')
|
||||
return path.Replace('/', Path.DirectorySeparatorChar);
|
||||
return path.Replace('\\', Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
|
||||
string ProjectGuid(string assembly)
|
||||
{
|
||||
return SolutionGuidGenerator.GuidForProject(m_ProjectName + Utility.FileNameWithoutExtension(assembly));
|
||||
return SolutionGuidGenerator.GuidForProject(m_ProjectName + FileUtility.FileNameWithoutExtension(assembly));
|
||||
}
|
||||
|
||||
string SolutionGuid(Assembly island)
|
||||
{
|
||||
return SolutionGuidGenerator.GuidForSolution(m_ProjectName, GetExtensionOfSourceFiles(island.sourceFiles));
|
||||
return SolutionGuidGenerator.GuidForSolution(m_ProjectName, ScriptingLanguageFor(island));
|
||||
}
|
||||
|
||||
static string ProjectFooter()
|
||||
@@ -858,11 +994,13 @@ namespace VisualStudioEditor
|
||||
return ComputeGuidHashFor(projectName + "salt");
|
||||
}
|
||||
|
||||
public static string GuidForSolution(string projectName, string sourceFileExtension)
|
||||
public static string GuidForSolution(string projectName, ScriptingLanguage language)
|
||||
{
|
||||
if (sourceFileExtension.ToLower() == "cs")
|
||||
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);
|
||||
}
|
||||
|
||||
12
Editor/Solution.cs
Normal file
12
Editor/Solution.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
namespace Microsoft.Unity.VisualStudio.Editor
|
||||
{
|
||||
internal class Solution
|
||||
{
|
||||
public SolutionProjectEntry[] Projects { get; set; }
|
||||
public SolutionProperties[] Properties { get; set; }
|
||||
}
|
||||
}
|
||||
11
Editor/Solution.cs.meta
Normal file
11
Editor/Solution.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af4c2c762e1d8e949a6bc458973df6e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
80
Editor/SolutionParser.cs
Normal file
80
Editor/SolutionParser.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
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>.*?)}\""(?<content>.*?)\bEndProject\b", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
|
||||
private static readonly Regex PropertiesDeclaration = new Regex(@"GlobalSection\((?<name>[\w]+Properties)\)\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)
|
||||
{
|
||||
return ParseSolutionContent(File.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,
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/SolutionParser.cs.meta
Normal file
11
Editor/SolutionParser.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbbb1ee655846b043baf6c3502b5ce49
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Editor/SolutionProjectEntry.cs
Normal file
14
Editor/SolutionProjectEntry.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
namespace Microsoft.Unity.VisualStudio.Editor
|
||||
{
|
||||
internal class SolutionProjectEntry
|
||||
{
|
||||
public string ProjectFactoryGuid { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public string ProjectGuid { get; set; }
|
||||
}
|
||||
}
|
||||
11
Editor/SolutionProjectEntry.cs.meta
Normal file
11
Editor/SolutionProjectEntry.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c1b8a755d2c97640bbb207c43f4cf61
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
15
Editor/SolutionProperties.cs
Normal file
15
Editor/SolutionProperties.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Unity.VisualStudio.Editor
|
||||
{
|
||||
internal class SolutionProperties
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public IList<KeyValuePair<string, string>> Entries { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
||||
}
|
||||
11
Editor/SolutionProperties.cs.meta
Normal file
11
Editor/SolutionProperties.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 829d4d6bc39fd1044ba4c5fc2a9c911f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
30
Editor/Symbols.cs
Normal file
30
Editor/Symbols.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.Unity.VisualStudio.Editor
|
||||
{
|
||||
internal static class Symbols
|
||||
{
|
||||
public static bool IsPortableSymbolFile(string pdbFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = File.OpenRead(pdbFile))
|
||||
{
|
||||
return stream.ReadByte() == 'B'
|
||||
&& stream.ReadByte() == 'S'
|
||||
&& stream.ReadByte() == 'J'
|
||||
&& stream.ReadByte() == 'B';
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Symbols.cs.meta
Normal file
11
Editor/Symbols.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9308b762484008498bb5cd1886aa491
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,35 +0,0 @@
|
||||
namespace VisualStudioEditor
|
||||
{
|
||||
public static class Utility
|
||||
{
|
||||
public static string FileNameWithoutExtension(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var indexOfDot = -1;
|
||||
var indexOfSlash = 0;
|
||||
for (var i = path.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (indexOfDot == -1 && path[i] == '.')
|
||||
{
|
||||
indexOfDot = i;
|
||||
}
|
||||
if (indexOfSlash == 0 && path[i] == '/' || path[i] == '\\')
|
||||
{
|
||||
indexOfSlash = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (indexOfDot == -1)
|
||||
{
|
||||
indexOfDot = path.Length - 1;
|
||||
}
|
||||
|
||||
return path.Substring(indexOfSlash, indexOfDot - indexOfSlash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Win32;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Debug = System.Diagnostics.Debug;
|
||||
using Unity.CodeEditor;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
namespace VisualStudioEditor
|
||||
{
|
||||
public enum VisualStudioVersion
|
||||
{
|
||||
Invalid = 0,
|
||||
VisualStudio2008 = 9,
|
||||
VisualStudio2010 = 10,
|
||||
VisualStudio2012 = 11,
|
||||
VisualStudio2013 = 12,
|
||||
VisualStudio2015 = 14,
|
||||
VisualStudio2017 = 15,
|
||||
VisualStudio2019 = 16,
|
||||
}
|
||||
|
||||
[InitializeOnLoad]
|
||||
public class VSEditor : IExternalCodeEditor
|
||||
{
|
||||
static readonly string k_ExpressNotSupportedMessage = L10n.Tr(
|
||||
"Unfortunately Visual Studio Express does not allow itself to be controlled by external applications. " +
|
||||
"You can still use it by manually opening the Visual Studio project file, but Unity cannot automatically open files for you when you doubleclick them. " +
|
||||
"\n(This does work with Visual Studio Pro)"
|
||||
);
|
||||
|
||||
static VSEditor()
|
||||
{
|
||||
try
|
||||
{
|
||||
InstalledVisualStudios = Discovery.GetInstalledVisualStudios();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UnityEngine.Debug.Log($@"Error detecting Visual Studio installations: {ex.Message}{Environment.NewLine}{ex.StackTrace}");
|
||||
InstalledVisualStudios = new Dictionary<VisualStudioVersion, string[]>();
|
||||
}
|
||||
var editor = new VSEditor(new Discovery(), new ProjectGeneration());
|
||||
CodeEditor.Register(editor);
|
||||
var current = CodeEditor.CurrentEditorInstallation;
|
||||
if (editor.TryGetInstallationForPath(current, out var installation))
|
||||
{
|
||||
if (installation.Name != "MonoDevelop")
|
||||
{
|
||||
editor.Initialize(current);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const string unity_generate_all = "unity_generate_all_csproj";
|
||||
IDiscovery m_Discoverability;
|
||||
IGenerator m_Generation;
|
||||
CodeEditor.Installation m_Installation;
|
||||
VSInitializer m_Initializer = new VSInitializer();
|
||||
|
||||
public VSEditor(IDiscovery discovery, IGenerator projectGeneration)
|
||||
{
|
||||
m_Discoverability = discovery;
|
||||
m_Generation = projectGeneration;
|
||||
}
|
||||
|
||||
internal static Dictionary<VisualStudioVersion, string[]> InstalledVisualStudios { get; private set; }
|
||||
|
||||
internal static bool IsOSX => Application.platform == RuntimePlatform.OSXEditor;
|
||||
internal static bool IsWindows => !IsOSX && Path.DirectorySeparatorChar == '\\' && Environment.NewLine == "\r\n";
|
||||
|
||||
public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback();
|
||||
|
||||
public void CreateIfDoesntExist()
|
||||
{
|
||||
if (!m_Generation.HasSolutionBeenGenerated())
|
||||
{
|
||||
m_Generation.Sync();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation)
|
||||
{
|
||||
var lowerCasePath = editorPath.ToLower();
|
||||
if (lowerCasePath.EndsWith("vcsexpress.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
installation = new CodeEditor.Installation
|
||||
{
|
||||
Name = "VSExpress",
|
||||
Path = editorPath
|
||||
};
|
||||
m_Installation = installation;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowerCasePath.EndsWith("devenv.exe", StringComparison.OrdinalIgnoreCase)
|
||||
|| lowerCasePath.Replace(" ", "").EndsWith("visualstudio.app", StringComparison.OrdinalIgnoreCase)
|
||||
|| lowerCasePath.Replace(" ", "").EndsWith("visualstudio(preview).app", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
installation = new CodeEditor.Installation
|
||||
{
|
||||
Name = "VisualStudio",
|
||||
Path = editorPath
|
||||
};
|
||||
m_Installation = installation;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowerCasePath.Contains("monodevelop")
|
||||
|| lowerCasePath.Replace(" ", "").Contains("xamarinstudio"))
|
||||
{
|
||||
installation = new CodeEditor.Installation
|
||||
{
|
||||
Name = "MonoDevelop",
|
||||
Path = editorPath
|
||||
};
|
||||
m_Installation = installation;
|
||||
return true;
|
||||
}
|
||||
|
||||
installation = default;
|
||||
m_Installation = installation;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (m_Installation.Name.Equals("VSExpress"))
|
||||
{
|
||||
GUILayout.BeginHorizontal(EditorStyles.helpBox);
|
||||
GUILayout.Label("", "CN EntryWarn");
|
||||
GUILayout.Label(k_ExpressNotSupportedMessage, "WordWrappedLabel");
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
var prevGenerate = EditorPrefs.GetBool(unity_generate_all, false);
|
||||
var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate);
|
||||
if (generateAll != prevGenerate)
|
||||
{
|
||||
EditorPrefs.SetBool(unity_generate_all, generateAll);
|
||||
}
|
||||
m_Generation.GenerateAll(generateAll);
|
||||
}
|
||||
|
||||
public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles)
|
||||
{
|
||||
m_Generation.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles), importedFiles);
|
||||
}
|
||||
|
||||
public void SyncAll()
|
||||
{
|
||||
AssetDatabase.Refresh();
|
||||
m_Generation.Sync();
|
||||
}
|
||||
|
||||
public void Initialize(string editorInstallationPath)
|
||||
{
|
||||
m_Initializer.Initialize(editorInstallationPath, InstalledVisualStudios);
|
||||
}
|
||||
|
||||
public bool OpenProject(string path, int line, int column)
|
||||
{
|
||||
if (m_Installation.Name == "MonoDevelop") {
|
||||
return OpenAppMonoDev(path, line, column);
|
||||
}
|
||||
|
||||
if (IsOSX)
|
||||
{
|
||||
return OpenOSXApp(path, line, column);
|
||||
}
|
||||
|
||||
if (IsWindows)
|
||||
{
|
||||
return OpenWindowsApp(path, line);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool OpenWindowsApp(string path, int line)
|
||||
{
|
||||
var comAssetPath = AssetDatabase.FindAssets("COMIntegration a:packages").Select(AssetDatabase.GUIDToAssetPath).First(assetPath => assetPath.Contains("COMIntegration.dom"));
|
||||
if (string.IsNullOrWhiteSpace(comAssetPath)) // This may be called too early where the asset database has not replicated this information yet.
|
||||
{
|
||||
return false;
|
||||
}
|
||||
UnityEditor.PackageManager.PackageInfo packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(comAssetPath);
|
||||
var progpath = packageInfo.resolvedPath + comAssetPath.Substring("Packages/com.unity.ide.visualstudio".Length);
|
||||
string absolutePath = "";
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
absolutePath = Path.GetFullPath(path);
|
||||
}
|
||||
|
||||
|
||||
var solution = GetOrGenerateSolutionFile(path);
|
||||
solution = solution == "" ? "" : $"\"{solution}\"";
|
||||
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;
|
||||
}
|
||||
|
||||
bool OpenAppMonoDev(string path, int line, int column)
|
||||
{
|
||||
string absolutePath = "";
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
absolutePath = Path.GetFullPath(path);
|
||||
}
|
||||
|
||||
var solution = GetOrGenerateSolutionFile(path);
|
||||
solution = solution == "" ? "" : $"\"{solution}\"";
|
||||
var pathArguments = path == "" ? "" : $"\"{path}\";{line}";
|
||||
var fileName = IsOSX ? "open" : CodeEditor.CurrentEditorInstallation;
|
||||
var arguments = IsOSX
|
||||
? $"\"{CodeEditor.CurrentEditorInstallation}\" --args --nologo {solution} {pathArguments}"
|
||||
: $"--nologo {solution} {pathArguments}";
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = arguments,
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = true,
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[DllImport ("AppleEventIntegrationPlugin")]
|
||||
static extern void OpenVisualStudio(string appPath, string solutionPath, string filePath, int line, StringBuilder sb, int sbLength);
|
||||
|
||||
bool OpenOSXApp(string path, int line, int column)
|
||||
{
|
||||
string absolutePath = "";
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
absolutePath = Path.GetFullPath(path);
|
||||
}
|
||||
|
||||
string solution = GetOrGenerateSolutionFile(path);
|
||||
|
||||
StringBuilder sb = new StringBuilder(4096);
|
||||
|
||||
OpenVisualStudio(CodeEditor.CurrentEditorInstallation, solution, absolutePath, line, sb, sb.Capacity);
|
||||
|
||||
Console.WriteLine(sb.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetOrGenerateSolutionFile(string path)
|
||||
{
|
||||
var solution = GetSolutionFile(path);
|
||||
if (solution == "")
|
||||
{
|
||||
m_Generation.Sync();
|
||||
solution = GetSolutionFile(path);
|
||||
}
|
||||
|
||||
return solution;
|
||||
}
|
||||
|
||||
string GetSolutionFile(string path)
|
||||
{
|
||||
if (UnityEditor.Unsupported.IsDeveloperBuild())
|
||||
{
|
||||
var baseFolder = GetBaseUnityDeveloperFolder();
|
||||
var lowerPath = path.ToLowerInvariant();
|
||||
var isUnitySourceCode =
|
||||
lowerPath.Contains((baseFolder + "/Runtime").ToLowerInvariant())
|
||||
|| lowerPath.Contains((baseFolder + "/Editor").ToLowerInvariant());
|
||||
|
||||
if (isUnitySourceCode)
|
||||
{
|
||||
return Path.Combine(baseFolder, "Projects/CSharp/Unity.CSharpProjects.gen.sln");
|
||||
}
|
||||
}
|
||||
var solutionFile = m_Generation.SolutionFile();
|
||||
if (File.Exists(solutionFile))
|
||||
{
|
||||
return solutionFile;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static string GetBaseUnityDeveloperFolder()
|
||||
{
|
||||
return Directory.GetParent(EditorApplication.applicationPath).Parent.Parent.FullName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Win32;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace VisualStudioEditor
|
||||
{
|
||||
internal class VSInitializer
|
||||
{
|
||||
public void Initialize(string editorPath, Dictionary<VisualStudioVersion, string[]> installedVisualStudios)
|
||||
{
|
||||
switch (Application.platform) {
|
||||
case RuntimePlatform.OSXEditor:
|
||||
InitializeVSForMac(editorPath);
|
||||
break;
|
||||
case RuntimePlatform.WindowsEditor:
|
||||
InitializeVisualStudio(editorPath, installedVisualStudios);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void InitializeVSForMac(string externalEditor)
|
||||
{
|
||||
if (!IsVSForMac(externalEditor, out var vsfmVersion))
|
||||
return;
|
||||
|
||||
var bridgeFile = GetVSForMacBridgeAssembly(externalEditor, vsfmVersion);
|
||||
if (string.IsNullOrEmpty(bridgeFile) || !File.Exists(bridgeFile))
|
||||
{
|
||||
Debug.Log("Unable to find Tools for Unity bridge dll for Visual Studio for Mac " + externalEditor);
|
||||
return;
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(bridgeFile));
|
||||
}
|
||||
|
||||
static bool IsVisualStudioForMac(string path)
|
||||
{
|
||||
var lowerCasePath = path.ToLower();
|
||||
var filename = Path.GetFileName(lowerCasePath).Replace(" ", "");
|
||||
return filename.StartsWith("visualstudio") && !filename.Contains("code") && filename.EndsWith(".app");
|
||||
}
|
||||
|
||||
static bool IsVSForMac(string externalEditor, out Version vsfmVersion)
|
||||
{
|
||||
vsfmVersion = null;
|
||||
|
||||
if (!IsVisualStudioForMac(externalEditor))
|
||||
return false;
|
||||
|
||||
// We need to extract the version used by VS for Mac
|
||||
// to lookup its addin registry
|
||||
try
|
||||
{
|
||||
return GetVSForMacVersion(externalEditor, out vsfmVersion);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log("Failed to read Visual Studio for Mac information");
|
||||
Debug.LogException(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool GetVSForMacVersion(string externalEditor, out Version vsfmVersion)
|
||||
{
|
||||
vsfmVersion = null;
|
||||
|
||||
// Read the full VS for Mac version from the plist, it will look like this:
|
||||
//
|
||||
// <key>CFBundleShortVersionString</key>
|
||||
// <string>X.X.X.X</string>
|
||||
|
||||
var plist = Path.Combine(externalEditor, "Contents/Info.plist");
|
||||
if (!File.Exists(plist))
|
||||
return false;
|
||||
|
||||
const string versionStringRegex = @"\<key\>CFBundleShortVersionString\</key\>\s+\<string\>(?<version>\d+\.\d+\.\d+\.\d+?)\</string\>";
|
||||
|
||||
var file = File.ReadAllText(plist);
|
||||
var match = Regex.Match(file, versionStringRegex);
|
||||
var versionGroup = match.Groups["version"];
|
||||
if (!versionGroup.Success)
|
||||
return false;
|
||||
|
||||
vsfmVersion = new Version(versionGroup.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void InitializeVisualStudio(string externalEditor, Dictionary<VisualStudioVersion, string[]> installedVisualStudios)
|
||||
{
|
||||
if (!externalEditor.Contains("2017"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FindVisualStudio(externalEditor, out var vsVersion, installedVisualStudios);
|
||||
var bridgeFile = GetVstuBridgeAssembly(vsVersion);
|
||||
if (bridgeFile == null)
|
||||
{
|
||||
Debug.Log("Unable to find bridge dll in registry for Microsoft Visual Studio Tools for Unity for " + externalEditor);
|
||||
return;
|
||||
}
|
||||
if (!File.Exists(bridgeFile))
|
||||
{
|
||||
Debug.Log("Unable to find bridge dll on disk for Microsoft Visual Studio Tools for Unity for " + bridgeFile);
|
||||
return;
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(bridgeFile));
|
||||
}
|
||||
|
||||
static string GetVstuBridgeAssembly(VisualStudioVersion version)
|
||||
{
|
||||
try
|
||||
{
|
||||
var vsVersion = string.Empty;
|
||||
|
||||
switch (version)
|
||||
{
|
||||
// Starting with VS 15, the registry key is using the VS version
|
||||
// to avoid taking a dependency on the product name
|
||||
case VisualStudioVersion.VisualStudio2017:
|
||||
vsVersion = "15.0";
|
||||
break;
|
||||
case VisualStudioVersion.VisualStudio2019:
|
||||
vsVersion = "16.0";
|
||||
break;
|
||||
// VS 2015 and under are still installed in the registry
|
||||
// using their project names
|
||||
case VisualStudioVersion.VisualStudio2015:
|
||||
vsVersion = "2015";
|
||||
break;
|
||||
case VisualStudioVersion.VisualStudio2013:
|
||||
vsVersion = "2013";
|
||||
break;
|
||||
case VisualStudioVersion.VisualStudio2012:
|
||||
vsVersion = "2012";
|
||||
break;
|
||||
case VisualStudioVersion.VisualStudio2010:
|
||||
vsVersion = "2010";
|
||||
break;
|
||||
}
|
||||
|
||||
// search first for the current user with a fallback to machine wide setting
|
||||
return GetVstuBridgePathFromRegistry(vsVersion, true)
|
||||
?? GetVstuBridgePathFromRegistry(vsVersion, false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static string GetVstuBridgePathFromRegistry(string vsVersion, bool currentUser)
|
||||
{
|
||||
var registryKey = $@"{(currentUser ? "HKEY_CURRENT_USER" : "HKEY_LOCAL_MACHINE")}\Software\Microsoft\Microsoft Visual Studio {vsVersion} Tools for Unity";
|
||||
|
||||
return (string)Registry.GetValue(registryKey, "UnityExtensionPath", null);
|
||||
}
|
||||
|
||||
static void FindVisualStudio(string externalEditor, out VisualStudioVersion vsVersion, Dictionary<VisualStudioVersion, string[]> installedVisualStudios)
|
||||
{
|
||||
if (string.IsNullOrEmpty(externalEditor))
|
||||
{
|
||||
vsVersion = VisualStudioVersion.Invalid;
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a VS found through envvars or the registry
|
||||
var matches = installedVisualStudios.Where(kvp => kvp.Value.Any(v => Path.GetFullPath(v).Equals(Path.GetFullPath(externalEditor), StringComparison.OrdinalIgnoreCase))).ToArray();
|
||||
if (matches.Length > 0)
|
||||
{
|
||||
vsVersion = matches[0].Key;
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a side-by-side VS selected manually
|
||||
if (externalEditor.EndsWith("devenv.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (TryGetVisualStudioVersion(externalEditor, out vsVersion)) return;
|
||||
}
|
||||
|
||||
vsVersion = VisualStudioVersion.Invalid;
|
||||
}
|
||||
|
||||
static bool TryGetVisualStudioVersion(string externalEditor, out VisualStudioVersion vsVersion)
|
||||
{
|
||||
switch (ProductVersion(externalEditor).Major)
|
||||
{
|
||||
case 9:
|
||||
vsVersion = VisualStudioVersion.VisualStudio2008;
|
||||
return true;
|
||||
case 10:
|
||||
vsVersion = VisualStudioVersion.VisualStudio2010;
|
||||
return true;
|
||||
case 11:
|
||||
vsVersion = VisualStudioVersion.VisualStudio2012;
|
||||
return true;
|
||||
case 12:
|
||||
vsVersion = VisualStudioVersion.VisualStudio2013;
|
||||
return true;
|
||||
case 14:
|
||||
vsVersion = VisualStudioVersion.VisualStudio2015;
|
||||
return true;
|
||||
case 15:
|
||||
vsVersion = VisualStudioVersion.VisualStudio2017;
|
||||
return true;
|
||||
case 16:
|
||||
vsVersion = VisualStudioVersion.VisualStudio2019;
|
||||
return true;
|
||||
}
|
||||
|
||||
vsVersion = VisualStudioVersion.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
static Version ProductVersion(string externalEditor)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new Version(FileVersionInfo.GetVersionInfo(externalEditor).ProductVersion);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new Version(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static string GetVSForMacBridgeAssembly(string externalEditor, Version vsfmVersion)
|
||||
{
|
||||
// Check first if we're overriden
|
||||
// Useful when developing UnityVS for Mac
|
||||
var bridge = Environment.GetEnvironmentVariable("VSTUM_BRIDGE");
|
||||
if (!string.IsNullOrEmpty(bridge) && File.Exists(bridge))
|
||||
return bridge;
|
||||
|
||||
// Look for installed addin
|
||||
const string addinBridge = "Editor/SyntaxTree.VisualStudio.Unity.Bridge.dll";
|
||||
const string addinName = "MonoDevelop.Unity";
|
||||
|
||||
// Check if we're installed in the user addins repository
|
||||
// ~/Library/Application Support/VisualStudio/X.0/LocalInstall/Addins
|
||||
var localAddins = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
|
||||
"Library/Application Support/VisualStudio/" + vsfmVersion.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 = Path.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 = Path.Combine(externalEditor, "Contents/Resources/lib/monodevelop/AddIns/" + addinName + "/" + addinBridge);
|
||||
if (File.Exists(bridge))
|
||||
return bridge;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
258
Editor/VisualStudioEditor.cs
Normal file
258
Editor/VisualStudioEditor.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Unity.CodeEditor;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Unity.VisualStudio.Editor
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class VisualStudioEditor : IExternalCodeEditor
|
||||
{
|
||||
private static readonly VisualStudioInstallation[] _installations;
|
||||
|
||||
internal static bool IsOSX => Application.platform == RuntimePlatform.OSXEditor;
|
||||
internal static bool IsWindows => !IsOSX && Path.DirectorySeparatorChar == '\\' && 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.Log($"Error detecting Visual Studio installations: {ex}");
|
||||
_installations = Array.Empty<VisualStudioInstallation>();
|
||||
}
|
||||
|
||||
CodeEditor.Register(new VisualStudioEditor());
|
||||
}
|
||||
|
||||
public void CreateIfDoesntExist()
|
||||
{
|
||||
if (!_generator.HasSolutionBeenGenerated())
|
||||
_generator.Sync();
|
||||
}
|
||||
|
||||
public void Initialize(string editorInstallationPath)
|
||||
{
|
||||
}
|
||||
|
||||
internal bool TryGetVisualStudioInstallationForPath(string editorPath, out VisualStudioInstallation 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 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()
|
||||
{
|
||||
const string unity_generate_all = "unity_generate_all_csproj";
|
||||
|
||||
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();
|
||||
|
||||
var prevGenerate = EditorPrefs.GetBool(unity_generate_all, false);
|
||||
var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate);
|
||||
if (generateAll != prevGenerate)
|
||||
{
|
||||
EditorPrefs.SetBool(unity_generate_all, generateAll);
|
||||
}
|
||||
|
||||
_generator.GenerateAll(generateAll);
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
public bool OpenProject(string path, int line, int column)
|
||||
{
|
||||
if (!IsSupportedPath(path))
|
||||
return false;
|
||||
|
||||
if (IsOSX)
|
||||
return OpenOSXApp(path, line, column);
|
||||
|
||||
if (IsWindows)
|
||||
return OpenWindowsApp(path, line);
|
||||
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
126
Editor/VisualStudioInstallation.cs
Normal file
126
Editor/VisualStudioInstallation.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Win32;
|
||||
using Unity.CodeEditor;
|
||||
using IOPath = System.IO.Path;
|
||||
|
||||
namespace Microsoft.Unity.VisualStudio.Editor
|
||||
{
|
||||
internal class VisualStudioInstallation
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/VisualStudioInstallation.cs.meta
Normal file
11
Editor/VisualStudioInstallation.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb86eea06f54fb24caa7046a8a764945
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
174
Editor/VisualStudioIntegration.cs
Normal file
174
Editor/VisualStudioIntegration.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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.Net;
|
||||
using Microsoft.Unity.VisualStudio.Editor.Messaging;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using MessageType = Microsoft.Unity.VisualStudio.Editor.Messaging.MessageType;
|
||||
|
||||
namespace Microsoft.Unity.VisualStudio.Editor
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
internal class VisualStudioIntegration
|
||||
{
|
||||
private static Messager _messager;
|
||||
|
||||
private static readonly Queue<Message> Incoming = new Queue<Message>();
|
||||
private static readonly object IncomingLock = new object();
|
||||
|
||||
public static Application.LogCallback LogCallback = delegate { };
|
||||
|
||||
static VisualStudioIntegration()
|
||||
{
|
||||
RunOnceOnUpdate(() =>
|
||||
{
|
||||
_messager = Messager.BindTo(MessagingPort());
|
||||
_messager.ReceiveMessage += ReceiveMessage;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddMessage(Message message)
|
||||
{
|
||||
lock (IncomingLock)
|
||||
{
|
||||
Incoming.Enqueue(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessIncoming(Message 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;
|
||||
}
|
||||
}
|
||||
|
||||
private static string PackageVersion()
|
||||
{
|
||||
var package = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(VisualStudioIntegration).Assembly);
|
||||
return package.version;
|
||||
}
|
||||
|
||||
private static void Refresh()
|
||||
{
|
||||
RunOnceOnUpdate(AssetDatabase.Refresh);
|
||||
}
|
||||
|
||||
private static void OnMessage(Message message)
|
||||
{
|
||||
AddMessage(message);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/VisualStudioIntegration.cs.meta
Normal file
11
Editor/VisualStudioIntegration.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48fcd6ebd5ce8fd4cbe931895233677d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,7 @@
|
||||
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
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
"name": "com.unity.ide.visualstudio",
|
||||
"displayName": "Visual Studio Editor",
|
||||
"description": "Code editor integration for supporting Visual Studio as code editor for unity. Adds support for generating csproj files for intellisense purposes, auto discovery of installations, etc.",
|
||||
"version": "1.0.11",
|
||||
"unity": "2019.2",
|
||||
"version": "2.0.0",
|
||||
"unity": "2020.1",
|
||||
"unityRelease": "0a12",
|
||||
"dependencies": {},
|
||||
"relatedPackages": {
|
||||
"com.unity.ide.visualstudio.tests": "1.0.5"
|
||||
"com.unity.ide.visualstudio.tests": "2.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.cds.internal.unity3d.com:unity/com.unity.ide.visualstudio.git",
|
||||
"revision": "7378597cbd075d617aade65f1aa9c13a22c00d1c"
|
||||
"revision": "a3fbcc6d9504b41b441ecf6caeb3bbc753d6d010"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user