com.unity.ide.visualstudio@1.0.9

## [1.0.9] - 2019-03-05

Updated MonoDevelop support, to pass correct arguments, and not import VSTU plugin
Use release build of COMIntegration for Visual Studio

## [1.0.7] - 2019-04-30

Ensure asset database is refreshed when generating csproj and solution files.

## [1.0.6] - 2019-04-27

Add support for generating all csproj files.

## [1.0.5] - 2019-04-18

Fix relative package paths.
Fix opening editor on mac.
This commit is contained in:
Unity Technologies
2019-03-05 00:00:00 +00:00
parent db5b2763de
commit 4494f57bd0
18 changed files with 641 additions and 254 deletions

View File

@@ -1,5 +1,24 @@
# Code Editor Package for Visual Studio # Code Editor Package for Visual Studio
## [1.0.9] - 2019-03-05
Updated MonoDevelop support, to pass correct arguments, and not import VSTU plugin
Use release build of COMIntegration for Visual Studio
## [1.0.7] - 2019-04-30
Ensure asset database is refreshed when generating csproj and solution files.
## [1.0.6] - 2019-04-27
Add support for generating all csproj files.
## [1.0.5] - 2019-04-18
Fix relative package paths.
Fix opening editor on mac.
## [1.0.4] - 2019-04-12 ## [1.0.4] - 2019-04-12
- Fixing null reference issue for callbacks to AssetPostProcessor. - Fixing null reference issue for callbacks to AssetPostProcessor.

View File

@@ -1,7 +1,12 @@
using System; using System;
using System.IO;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Unity.CodeEditor; using Unity.CodeEditor;
using UnityEngine; using UnityEngine;
using UnityEditor;
using System.Diagnostics;
using Microsoft.Win32;
namespace VisualStudioEditor namespace VisualStudioEditor
{ {
@@ -31,16 +36,239 @@ namespace VisualStudioEditor
{ {
try try
{ {
return VSEditor.GetInstalledVisualStudios().Select(pair => new CodeEditor.Installation if (VSEditor.IsWindows)
{ {
Path = pair.Value[0], return GetInstalledVisualStudios().Select(pair => new CodeEditor.Installation
Name = VisualStudioVersionToNiceName(pair.Key) {
}).ToArray(); 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) catch (Exception ex)
{ {
Debug.Log($"Error detecting Visual Studio installations: {ex.Message}{Environment.NewLine}{ex.StackTrace}"); UnityEngine.Debug.Log($"Error detecting Visual Studio installations: {ex.Message}{Environment.NewLine}{ex.StackTrace}");
return new CodeEditor.Installation[0]; }
return new CodeEditor.Installation[0];
}
void AddIfDirectoryExists(string name, string path, List<CodeEditor.Installation> installations)
{
if (Directory.Exists(path))
{
installations.Add(new CodeEditor.Installation { Name = name, Path = path });
}
}
static string GetRegistryValue(string path, string key)
{
try
{
return Registry.GetValue(path, key, null) as string;
}
catch (Exception)
{
return "";
}
}
/// <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);
string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
// 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;
}
}
return Path.Combine(path, "IDE", "devenv.exe");
}
/// <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();
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();
}
return "Program Files";
}
public static void ParseRawDevEnvPaths(string[] rawDevEnvPaths, Dictionary<VisualStudioVersion, string[]> versions)
{
if (rawDevEnvPaths == null)
{
return;
}
var v2017 = rawDevEnvPaths.Where(path => path.Contains("2017")).ToArray();
var v2019 = rawDevEnvPaths.Where(path => path.Contains("2019")).ToArray();
if (v2017.Length > 0)
{
versions[VisualStudioVersion.VisualStudio2017] = v2017;
}
if (v2019.Length > 0)
{
versions[VisualStudioVersion.VisualStudio2019] = v2019;
}
}
/// <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[]>();
if (VSEditor.IsWindows)
{
foreach (VisualStudioVersion version in Enum.GetValues(typeof(VisualStudioVersion)))
{
if (version > VisualStudioVersion.VisualStudio2015)
continue;
try
{
// Try COMNTOOLS environment variable first
FindLegacyVisualStudio(version, versions);
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"VS: {e.Message}");
}
}
var raw = FindVisualStudioDevEnvPaths();
ParseRawDevEnvPaths(raw.ToArray(), versions);
}
return versions;
}
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;
}
}
// 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();
} }
} }
} }

8
Editor/Plugins.meta Normal file
View File

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

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>18E226</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>AppleEventIntegrationPlugin</string>
<key>CFBundleIdentifier</key>
<string>com.unity.AppleEventIntegrationPlugin</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>AppleEventIntegrationPlugin</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>10E1001</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>18E219</string>
<key>DTSDKName</key>
<string>macosx10.14</string>
<key>DTXcode</key>
<string>1020</string>
<key>DTXcodeBuild</key>
<string>10E1001</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Unity. All rights reserved.</string>
</dict>
</plist>

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: cb67edc1800c2ec4ba8dfb1cf0dfc84a guid: d2694cce95579ec47a939a1a3efb8f33
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,6 +22,7 @@ namespace VisualStudioEditor
bool HasSolutionBeenGenerated(); bool HasSolutionBeenGenerated();
string SolutionFile(); string SolutionFile();
string ProjectDirectory { get; } string ProjectDirectory { get; }
void GenerateAll(bool generateAll);
} }
public interface IAssemblyNameProvider public interface IAssemblyNameProvider
@@ -29,6 +30,12 @@ namespace VisualStudioEditor
string GetAssemblyNameFromScriptPath(string path); string GetAssemblyNameFromScriptPath(string path);
IEnumerable<Assembly> GetAllAssemblies(Func<string, bool> shouldFileBePartOfSolution); IEnumerable<Assembly> GetAllAssemblies(Func<string, bool> shouldFileBePartOfSolution);
IEnumerable<string> GetAllAssetPaths(); IEnumerable<string> GetAllAssetPaths();
UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath);
}
public struct TestSettings {
public bool ShouldSync;
public Dictionary<string, string> SyncPath;
} }
class AssemblyNameProvider : IAssemblyNameProvider class AssemblyNameProvider : IAssemblyNameProvider
@@ -47,6 +54,11 @@ namespace VisualStudioEditor
{ {
return AssetDatabase.GetAllAssetPaths(); return AssetDatabase.GetAllAssetPaths();
} }
public UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath)
{
return UnityEditor.PackageManager.PackageInfo.FindForAssetPath(assetPath);
}
} }
public class ProjectGeneration : IGenerator public class ProjectGeneration : IGenerator
@@ -94,26 +106,32 @@ namespace VisualStudioEditor
string[] m_ProjectSupportedExtensions = new string[0]; string[] m_ProjectSupportedExtensions = new string[0];
public string ProjectDirectory { get; } public string ProjectDirectory { get; }
public TestSettings Settings { get; set; }
readonly string m_ProjectName; readonly string m_ProjectName;
readonly IAssemblyNameProvider m_AssemblyNameProvider; readonly IAssemblyNameProvider m_AssemblyNameProvider;
bool m_ShouldGenerateAll;
public ProjectGeneration() public ProjectGeneration() : this(Directory.GetParent(Application.dataPath).FullName, new AssemblyNameProvider())
{ {
var projectDirectory = Directory.GetParent(Application.dataPath).FullName;
ProjectDirectory = projectDirectory.Replace('\\', '/');
m_ProjectName = Path.GetFileName(ProjectDirectory);
m_AssemblyNameProvider = new AssemblyNameProvider();
} }
public ProjectGeneration(string tempDirectory) : this(tempDirectory, new AssemblyNameProvider()) { public ProjectGeneration(string tempDirectory) : this(tempDirectory, new AssemblyNameProvider()) {
} }
public ProjectGeneration(string tempDirectory, IAssemblyNameProvider assemblyNameProvider) { public ProjectGeneration(string tempDirectory, IAssemblyNameProvider assemblyNameProvider) {
Settings = new TestSettings { ShouldSync = true };
ProjectDirectory = tempDirectory.Replace('\\', '/'); ProjectDirectory = tempDirectory.Replace('\\', '/');
m_ProjectName = Path.GetFileName(ProjectDirectory); m_ProjectName = Path.GetFileName(ProjectDirectory);
m_AssemblyNameProvider = assemblyNameProvider; m_AssemblyNameProvider = assemblyNameProvider;
} }
public void GenerateAll(bool generateAll)
{
m_ShouldGenerateAll = generateAll;
}
/// <summary> /// <summary>
/// Syncs the scripting solution if any affected files are relevant. /// Syncs the scripting solution if any affected files are relevant.
/// </summary> /// </summary>
@@ -176,7 +194,7 @@ namespace VisualStudioEditor
string extension = Path.GetExtension(file); string extension = Path.GetExtension(file);
// Exclude files coming from packages except if they are internalized. // Exclude files coming from packages except if they are internalized.
if (IsInternalizedPackagePath(file)) if (!m_ShouldGenerateAll && IsInternalizedPackagePath(file))
{ {
return false; return false;
} }
@@ -296,7 +314,7 @@ namespace VisualStudioEditor
foreach (string asset in m_AssemblyNameProvider.GetAllAssetPaths()) foreach (string asset in m_AssemblyNameProvider.GetAllAssetPaths())
{ {
// Exclude files coming from packages except if they are internalized. // Exclude files coming from packages except if they are internalized.
if (IsInternalizedPackagePath(asset)) if (!m_ShouldGenerateAll && IsInternalizedPackagePath(asset))
{ {
continue; continue;
} }
@@ -306,8 +324,6 @@ namespace VisualStudioEditor
{ {
// Find assembly the asset belongs to by adding script extension and using compilation pipeline. // Find assembly the asset belongs to by adding script extension and using compilation pipeline.
var assemblyName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".cs"); var assemblyName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".cs");
assemblyName = assemblyName ?? m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".js");
assemblyName = assemblyName ?? m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".boo");
if (string.IsNullOrEmpty(assemblyName)) if (string.IsNullOrEmpty(assemblyName))
{ {
@@ -334,14 +350,14 @@ namespace VisualStudioEditor
return result; return result;
} }
static bool IsInternalizedPackagePath(string file) bool IsInternalizedPackagePath(string file)
{ {
if (string.IsNullOrWhiteSpace(file)) if (string.IsNullOrWhiteSpace(file))
{ {
return false; return false;
} }
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(file); var packageInfo = m_AssemblyNameProvider.FindForAssetPath(file);
if (packageInfo == null) { if (packageInfo == null) {
return false; return false;
} }
@@ -358,7 +374,7 @@ namespace VisualStudioEditor
SyncProjectFileIfNotChanged(ProjectFile(island), ProjectText(island, allAssetsProjectParts, responseFilesData, allProjectIslands)); SyncProjectFileIfNotChanged(ProjectFile(island), ProjectText(island, allAssetsProjectParts, responseFilesData, allProjectIslands));
} }
static void SyncProjectFileIfNotChanged(string path, string newContents) void SyncProjectFileIfNotChanged(string path, string newContents)
{ {
if (Path.GetExtension(path) == ".csproj") if (Path.GetExtension(path) == ".csproj")
{ {
@@ -368,7 +384,7 @@ namespace VisualStudioEditor
SyncFileIfNotChanged(path, newContents); SyncFileIfNotChanged(path, newContents);
} }
static void SyncSolutionFileIfNotChanged(string path, string newContents) void SyncSolutionFileIfNotChanged(string path, string newContents)
{ {
newContents = OnGeneratedSlnSolution(path, newContents); newContents = OnGeneratedSlnSolution(path, newContents);
@@ -455,7 +471,7 @@ namespace VisualStudioEditor
return content; return content;
} }
static void SyncFileIfNotChanged(string filename, string newContents) void SyncFileIfNotChanged(string filename, string newContents)
{ {
if (File.Exists(filename) && if (File.Exists(filename) &&
newContents == File.ReadAllText(filename)) newContents == File.ReadAllText(filename))
@@ -463,7 +479,16 @@ namespace VisualStudioEditor
return; return;
} }
File.WriteAllText(filename, newContents, Encoding.UTF8); if (Settings.ShouldSync)
{
File.WriteAllText(filename, newContents, Encoding.UTF8);
}
else
{
var utf8 = Encoding.UTF8;
byte[] utfBytes = utf8.GetBytes(newContents);
Settings.SyncPath[filename] = utf8.GetString(utfBytes, 0, utfBytes.Length);
}
} }
string ProjectText(Assembly assembly, string ProjectText(Assembly assembly,
@@ -779,7 +804,7 @@ namespace VisualStudioEditor
file = file.Replace('/', '\\'); file = file.Replace('/', '\\');
var path = SkipPathPrefix(file, projectDir); var path = SkipPathPrefix(file, projectDir);
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(path.Replace('\\', '/')); var packageInfo = m_AssemblyNameProvider.FindForAssetPath(path.Replace('\\', '/'));
if (packageInfo != null) { if (packageInfo != null) {
// We have to normalize the path, because the PackageManagerRemapper assumes // We have to normalize the path, because the PackageManagerRemapper assumes
// dir seperators will be os specific. // dir seperators will be os specific.
@@ -792,7 +817,7 @@ namespace VisualStudioEditor
static string SkipPathPrefix(string path, string prefix) static string SkipPathPrefix(string path, string prefix)
{ {
if (path.StartsWith(prefix)) if (path.Replace("\\","/").StartsWith($"{prefix}/"))
return path.Substring(prefix.Length + 1); return path.Substring(prefix.Length + 1);
return path; return path;
} }

View File

@@ -3,11 +3,14 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using Microsoft.Win32; using Microsoft.Win32;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using Debug = System.Diagnostics.Debug; using Debug = System.Diagnostics.Debug;
using Unity.CodeEditor; using Unity.CodeEditor;
using System.Runtime.InteropServices;
namespace VisualStudioEditor namespace VisualStudioEditor
{ {
@@ -32,42 +35,11 @@ namespace VisualStudioEditor
"\n(This does work with Visual Studio Pro)" "\n(This does work with Visual Studio Pro)"
); );
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();
}
}
static VSEditor() static VSEditor()
{ {
try try
{ {
InstalledVisualStudios = GetInstalledVisualStudios(); InstalledVisualStudios = Discovery.GetInstalledVisualStudios();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -79,16 +51,19 @@ namespace VisualStudioEditor
var current = CodeEditor.CurrentEditorInstallation; var current = CodeEditor.CurrentEditorInstallation;
if (editor.TryGetInstallationForPath(current, out var installation)) if (editor.TryGetInstallationForPath(current, out var installation))
{ {
editor.Initialize(current); if (installation.Name != "MonoDevelop")
{
editor.Initialize(current);
}
return; return;
} }
} }
const string unity_generate_all = "unity_generate_all_csproj";
IDiscovery m_Discoverability; IDiscovery m_Discoverability;
IGenerator m_Generation; IGenerator m_Generation;
CodeEditor.Installation m_Installation; CodeEditor.Installation m_Installation;
VSInitializer m_Initiliazer = new VSInitializer(); VSInitializer m_Initializer = new VSInitializer();
bool m_ExternalEditorSupportsUnityProj;
public VSEditor(IDiscovery discovery, IGenerator projectGeneration) public VSEditor(IDiscovery discovery, IGenerator projectGeneration)
{ {
@@ -98,186 +73,11 @@ namespace VisualStudioEditor
internal static Dictionary<VisualStudioVersion, string[]> InstalledVisualStudios { get; private set; } internal static Dictionary<VisualStudioVersion, string[]> InstalledVisualStudios { get; private set; }
static bool IsOSX => Environment.OSVersion.Platform == PlatformID.Unix; internal static bool IsOSX => Environment.OSVersion.Platform == PlatformID.Unix;
static bool IsWindows => !IsOSX && Path.DirectorySeparatorChar == '\\' && Environment.NewLine == "\r\n"; internal static bool IsWindows => !IsOSX && Path.DirectorySeparatorChar == '\\' && Environment.NewLine == "\r\n";
static readonly GUIContent k_AddUnityProjeToSln = EditorGUIUtility.TrTextContent("Add .unityproj's to .sln");
static string GetRegistryValue(string path, string key)
{
try
{
return Registry.GetValue(path, key, null) as string;
}
catch (Exception)
{
return "";
}
}
/// <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);
string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
// 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;
}
}
return Path.Combine(path, "IDE", "devenv.exe");
}
/// <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();
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();
}
return "Program Files";
}
public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback(); public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback();
public static void ParseRawDevEnvPaths(string[] rawDevEnvPaths, Dictionary<VisualStudioVersion, string[]> versions)
{
if (rawDevEnvPaths == null)
{
return;
}
var v2017 = rawDevEnvPaths.Where(path => path.Contains("2017")).ToArray();
var v2019 = rawDevEnvPaths.Where(path => path.Contains("2019")).ToArray();
if (v2017.Length > 0)
{
versions[VisualStudioVersion.VisualStudio2017] = v2017;
}
if (v2019.Length > 0)
{
versions[VisualStudioVersion.VisualStudio2019] = v2019;
}
}
/// <summary>
/// Detects Visual Studio installations using the Windows registry
/// </summary>
/// <returns>
/// The detected Visual Studio installations
/// </returns>
internal static Dictionary<VisualStudioVersion, string[]> GetInstalledVisualStudios()
{
var versions = new Dictionary<VisualStudioVersion, string[]>();
if (IsWindows)
{
foreach (VisualStudioVersion version in Enum.GetValues(typeof(VisualStudioVersion)))
{
if (version > VisualStudioVersion.VisualStudio2015)
continue;
try
{
// Try COMNTOOLS environment variable first
FindLegacyVisualStudio(version, versions);
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"VS: {e.Message}");
}
}
var raw = FindVisualStudioDevEnvPaths();
ParseRawDevEnvPaths(raw.ToArray(), versions);
}
return versions;
}
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;
}
}
// 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) };
}
}
public void CreateIfDoesntExist() public void CreateIfDoesntExist()
{ {
if (!m_Generation.HasSolutionBeenGenerated()) if (!m_Generation.HasSolutionBeenGenerated())
@@ -338,12 +138,13 @@ namespace VisualStudioEditor
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
if (m_Installation.Name.Equals("MonoDevelop")) var prevGenerate = EditorPrefs.GetBool(unity_generate_all, false);
var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate);
if (generateAll != prevGenerate)
{ {
m_ExternalEditorSupportsUnityProj = EditorGUILayout.Toggle( EditorPrefs.SetBool(unity_generate_all, generateAll);
k_AddUnityProjeToSln,
m_ExternalEditorSupportsUnityProj);
} }
m_Generation.GenerateAll(generateAll);
} }
public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles) public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles)
@@ -353,17 +154,37 @@ namespace VisualStudioEditor
public void SyncAll() public void SyncAll()
{ {
AssetDatabase.Refresh();
m_Generation.Sync(); m_Generation.Sync();
} }
public void Initialize(string editorInstallationPath) public void Initialize(string editorInstallationPath)
{ {
m_Initiliazer.Initialize(editorInstallationPath, InstalledVisualStudios); m_Initializer.Initialize(editorInstallationPath, InstalledVisualStudios);
} }
public bool OpenProject(string path, int line, int column) public bool OpenProject(string path, int line, int column)
{ {
var comAssetPath = AssetDatabase.FindAssets("COMIntegration a:packages").Select(AssetDatabase.GUIDToAssetPath).First(assetPath => assetPath.Contains("COMIntegration.exe")); 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. if (string.IsNullOrWhiteSpace(comAssetPath)) // This may be called too early where the asset database has not replicated this information yet.
{ {
return false; return false;
@@ -376,12 +197,8 @@ namespace VisualStudioEditor
absolutePath = Path.GetFullPath(path); absolutePath = Path.GetFullPath(path);
} }
var solution = GetSolutionFile(path);
if (solution == "") var solution = GetOrGenerateSolutionFile(path);
{
m_Generation.Sync();
solution = GetSolutionFile(path);
}
solution = solution == "" ? "" : $"\"{solution}\""; solution = solution == "" ? "" : $"\"{solution}\"";
var process = new Process var process = new Process
{ {
@@ -420,7 +237,72 @@ namespace VisualStudioEditor
return result; return result;
} }
private string GetSolutionFile(string path) 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()) if (UnityEditor.Unsupported.IsDeveloperBuild())
{ {
@@ -443,7 +325,7 @@ namespace VisualStudioEditor
return ""; return "";
} }
private static string GetBaseUnityDeveloperFolder() static string GetBaseUnityDeveloperFolder()
{ {
return Directory.GetParent(EditorApplication.applicationPath).Parent.Parent.FullName; return Directory.GetParent(EditorApplication.applicationPath).Parent.Parent.FullName;
} }

View File

@@ -2,16 +2,16 @@
"name": "com.unity.ide.visualstudio", "name": "com.unity.ide.visualstudio",
"displayName": "Visual Studio Editor", "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.", "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.4", "version": "1.0.9",
"unity": "2019.2", "unity": "2019.2",
"unityRelease": "0a7", "unityRelease": "0a12",
"dependencies": {}, "dependencies": {},
"relatedPackages": { "relatedPackages": {
"com.unity.ide.visualstudio.tests": "1.0.2" "com.unity.ide.visualstudio.tests": "1.0.5"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git@github.cds.internal.unity3d.com:unity/com.unity.ide.visualstudio.git", "url": "git@github.cds.internal.unity3d.com:unity/com.unity.ide.visualstudio.git",
"revision": "89fbb2a14dd60b56c2eb64eac1e574a40a105822" "revision": "e7d6cf461bbe2604836a0e783a1992651c28c535"
} }
} }