com.unity.ide.visualstudio@2.0.17

## [2.0.17] - 2022-12-06

Integration:

- Fix rare deadlocks while discovering or launching Visual Studio on Windows.
- Improve launching Visual Studio on macOs.

Project generation:

- Include analyzers from response files.
- Update supported C# versions.
- Performance improvements.
This commit is contained in:
Unity Technologies
2022-12-06 00:00:00 +00:00
parent 7c58d4170b
commit 4893e3bfe7
16 changed files with 410 additions and 216 deletions

View File

@@ -3,7 +3,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
@@ -11,7 +11,7 @@
#define keyFileSender 1179872868
// 16 bit aligned legacy struct - this should total 20 bytes
struct SelectionRange
typedef struct _SelectionRange
{
int16_t unused1; // 0 (not used)
int16_t lineNum; // line to select (<0 to specify range)
@@ -19,7 +19,7 @@ struct SelectionRange
int32_t endRange; // end of selection range (if line < 0)
int32_t unused2; // 0 (not used)
int32_t theDate; // modification date/time
} __attribute__((packed));
} __attribute__((packed)) SelectionRange;
static NSString* MakeNSString(const char* str)
{
@@ -200,18 +200,13 @@ static NSRunningApplication* QueryRunningApplicationOpenedOnSolution(NSString* a
static NSRunningApplication* LaunchApplicationOnSolution(NSString* appPath, NSString* solutionPath)
{
NSURL* appUrl = [NSURL fileURLWithPath: appPath];
NSMutableDictionary* config = [[NSMutableDictionary alloc] init];
NSRunningApplication* runningApp = [[NSWorkspace sharedWorkspace]
launchApplicationAtURL: appUrl
return [[NSWorkspace sharedWorkspace]
launchApplicationAtURL: [NSURL fileURLWithPath: appPath]
options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance
configuration: config
configuration: @{
NSWorkspaceLaunchConfigurationArguments: @[ solutionPath ],
}
error: nil];
OpenFileAtLineWithAppleEvent(runningApp, solutionPath, -1);
return runningApp;
}
static NSRunningApplication* QueryOrLaunchApplication(NSString* appPath, NSString* solutionPath)

View File

@@ -11,20 +11,24 @@ namespace Microsoft.Unity.VisualStudio.Editor
internal class AsyncOperation<T>
{
private readonly Func<T> _producer;
private readonly Func<Exception, T> _exceptionHandler;
private readonly Action _finalHandler;
private readonly ManualResetEventSlim _resetEvent;
private T _result;
private Exception _exception;
private AsyncOperation(Func<T> producer)
private AsyncOperation(Func<T> producer, Func<Exception, T> exceptionHandler, Action finalHandler)
{
_producer = producer;
_exceptionHandler = exceptionHandler;
_finalHandler = finalHandler;
_resetEvent = new ManualResetEventSlim(initialState: false);
}
public static AsyncOperation<T> Run(Func<T> producer)
public static AsyncOperation<T> Run(Func<T> producer, Func<Exception, T> exceptionHandler = null, Action finalHandler = null)
{
var task = new AsyncOperation<T>(producer);
var task = new AsyncOperation<T>(producer, exceptionHandler, finalHandler);
task.Run();
return task;
}
@@ -40,9 +44,15 @@ namespace Microsoft.Unity.VisualStudio.Editor
catch (Exception e)
{
_exception = e;
if (_exceptionHandler != null)
{
_result = _exceptionHandler(e);
}
}
finally
{
_finalHandler?.Invoke();
_resetEvent.Set();
}
});

View File

@@ -205,6 +205,32 @@ static bool StartVisualStudioProcess(
return true;
}
static bool
MonikerIsVisualStudioProcess(const win::ComPtr<IMoniker> &moniker, const win::ComPtr<IBindCtx> &bindCtx, const DWORD dwProcessId = 0) {
LPOLESTR oleMonikerName;
if (FAILED(moniker->GetDisplayName(bindCtx, nullptr, &oleMonikerName)))
return false;
std::wstring monikerName(oleMonikerName);
// VisualStudio Moniker is "!VisualStudio.DTE.$Version:$PID"
// Example "!VisualStudio.DTE.14.0:1234"
if (monikerName.find(L"!VisualStudio.DTE") != 0)
return false;
if (dwProcessId == 0)
return true;
std::wstringstream suffixStream;
suffixStream << ":";
suffixStream << dwProcessId;
std::wstring suffix(suffixStream.str());
return monikerName.length() - suffix.length() == monikerName.find(suffix);
}
static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithSolution(
const std::filesystem::path &visualStudioExecutablePath,
const std::filesystem::path &solutionPath)
@@ -232,6 +258,9 @@ static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithSolution(
win::ComPtr<IMoniker> moniker;
ULONG monikersFetched = 0;
while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) {
if (!MonikerIsVisualStudioProcess(moniker, bindCtx))
continue;
if (FAILED(ROT->GetObject(moniker, &punk)))
continue;
@@ -285,29 +314,6 @@ static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithSolution(
return nullptr;
}
static bool
MonikerIsVisualStudioProcess(const win::ComPtr<IMoniker> &moniker, const win::ComPtr<IBindCtx> &bindCtx, const DWORD dwProcessId) {
LPOLESTR oleMonikerName;
if (FAILED(moniker->GetDisplayName(bindCtx, nullptr, &oleMonikerName)))
return false;
std::wstring monikerName(oleMonikerName);
// VisualStudio Moniker is "!VisualStudio.DTE.$Version:$PID"
// Example "!VisualStudio.DTE.14.0:1234"
if (monikerName.find(L"!VisualStudio.DTE") != 0)
return false;
std::wstringstream suffixStream;
suffixStream << ":";
suffixStream << dwProcessId;
std::wstring suffix(suffixStream.str());
return monikerName.length() - suffix.length() == monikerName.find(suffix);
}
static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithPID(const DWORD dwProcessId) {
win::ComPtr<IUnknown> punk = nullptr;
win::ComPtr<EnvDTE::_DTE> dte = nullptr;
@@ -329,10 +335,10 @@ static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithPID(const DWORD dwPr
win::ComPtr<IMoniker> moniker;
ULONG monikersFetched = 0;
while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) {
if (FAILED(ROT->GetObject(moniker, &punk)))
if (!MonikerIsVisualStudioProcess(moniker, bindCtx, dwProcessId))
continue;
if (!MonikerIsVisualStudioProcess(moniker, bindCtx, dwProcessId))
if (FAILED(ROT->GetObject(moniker, &punk)))
continue;
punk.As(&dte);

View File

@@ -1,6 +1,9 @@
Direct style:
cl /EHsc /std:c++17 COMIntegration.cpp /link Shlwapi.lib /out:"..\Release\COMIntegration.exe"
For a debug build with PDB:
cl /EHsc /std:c++17 /Z7 /DEBUG:FULL COMIntegration.cpp /link Shlwapi.lib /out:"..\Release\COMIntegration.exe"
CMake style:
cmake ../COMIntegration~ -B ./build
cmake --build ./build --config=release -- /p:OutDir=..

View File

@@ -150,31 +150,15 @@ namespace Microsoft.Unity.VisualStudio.Editor
if (string.IsNullOrWhiteSpace(progpath))
return Enumerable.Empty<VisualStudioInstallation>();
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = progpath,
Arguments = "-prerelease -format json -utf8",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
}
};
var result = ProcessRunner.StartAndWaitForExit(progpath, "-prerelease -format json -utf8");
using (process)
{
var json = string.Empty;
if (!result.Success)
throw new Exception($"Failure while running vswhere: {result.Error}");
process.OutputDataReceived += (o, e) => json += e.Data;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
var result = VsWhereResult.FromJson(json);
return result.ToVisualStudioInstallations();
}
// Do not catch any JsonException here, this will be handled by the caller
return VsWhereResult
.FromJson(result.Output)
.ToVisualStudioInstallations();
}
}
}

96
Editor/ProcessRunner.cs Normal file
View File

@@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* 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.Text;
using System.Threading.Tasks;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal class ProcessRunnerResult
{
public bool Success { get; set; }
public string Output { get; set; }
public string Error { get; set; }
}
internal static class ProcessRunner
{
public const int DefaultTimeoutInMilliseconds = 300000;
public static ProcessStartInfo ProcessStartInfoFor(string filename, string arguments)
{
return new ProcessStartInfo
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
FileName = filename,
Arguments = arguments
};
}
public static ProcessRunnerResult StartAndWaitForExit(string filename, string arguments, int timeoutms = DefaultTimeoutInMilliseconds, Action<string> onOutputReceived = null)
{
return StartAndWaitForExit(ProcessStartInfoFor(filename, arguments), timeoutms, onOutputReceived);
}
public static ProcessRunnerResult StartAndWaitForExit(ProcessStartInfo processStartInfo, int timeoutms = DefaultTimeoutInMilliseconds, Action<string> onOutputReceived = null)
{
var process = new Process { StartInfo = processStartInfo };
using (process)
{
var sbOutput = new StringBuilder();
var sbError = new StringBuilder();
var outputSource = new TaskCompletionSource<bool>();
var errorSource = new TaskCompletionSource<bool>();
process.OutputDataReceived += (_, e) =>
{
Append(sbOutput, e.Data, outputSource);
if (onOutputReceived != null && e.Data != null)
onOutputReceived(e.Data);
};
process.ErrorDataReceived += (_, e) => Append(sbError, e.Data, errorSource);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var run = Task.Run(() => process.WaitForExit(timeoutms));
var processTask = Task.WhenAll(run, outputSource.Task, errorSource.Task);
if (Task.WhenAny(Task.Delay(timeoutms), processTask).Result == processTask && run.Result)
return new ProcessRunnerResult {Success = true, Error = sbError.ToString(), Output = sbOutput.ToString()};
try
{
process.Kill();
}
catch
{
/* ignore */
}
return new ProcessRunnerResult {Success = false, Error = sbError.ToString(), Output = sbOutput.ToString()};
}
}
private static void Append(StringBuilder sb, string data, TaskCompletionSource<bool> taskSource)
{
if (data == null)
{
taskSource.SetResult(true);
return;
}
sb?.Append(data);
}
}
}

View File

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

View File

@@ -43,6 +43,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
public IAssemblyNameProvider AssemblyNameProvider => m_AssemblyNameProvider;
public string ProjectDirectory { get; }
// Use this to have the same newline ending on all platforms for consistency.
const string k_WindowsNewline = "\r\n";
const string m_SolutionProjectEntryTemplate = @"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""{4}EndProject";
@@ -126,11 +127,11 @@ namespace Microsoft.Unity.VisualStudio.Editor
var affectedNames = affected
.Select(asset => m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset))
.Where(name => !string.IsNullOrWhiteSpace(name)).Select(name =>
name.Split(new[] {".dll"}, StringSplitOptions.RemoveEmptyEntries)[0]);
name.Split(new[] { ".dll" }, StringSplitOptions.RemoveEmptyEntries)[0]);
var reimportedNames = reimported
.Select(asset => m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset))
.Where(name => !string.IsNullOrWhiteSpace(name)).Select(name =>
name.Split(new[] {".dll"}, StringSplitOptions.RemoveEmptyEntries)[0]);
name.Split(new[] { ".dll" }, StringSplitOptions.RemoveEmptyEntries)[0]);
var affectedAndReimported = new HashSet<string>(affectedNames.Concat(reimportedNames));
foreach (var assembly in allProjectAssemblies)
@@ -381,19 +382,19 @@ namespace Microsoft.Unity.VisualStudio.Editor
{
var filename = EscapedRelativePathFor(asset, out var packageInfo);
builder.Append($" <{tag} Include=\"").Append(filename);
builder.Append(" <").Append(tag).Append(@" Include=""").Append(filename);
if (Path.IsPathRooted(filename) && packageInfo != null)
{
// We are outside the Unity project and using a package context
var linkPath = SkipPathPrefix(asset.NormalizePathSeparators(), packageInfo.assetPath.NormalizePathSeparators());
builder.Append("\">").Append(k_WindowsNewline);
builder.Append(@""">").Append(k_WindowsNewline);
builder.Append(" <Link>").Append(linkPath).Append("</Link>").Append(k_WindowsNewline);
builder.Append($" </{tag}>").Append(k_WindowsNewline);
}
else
{
builder.Append("\" />").Append(k_WindowsNewline);
builder.Append(@""" />").Append(k_WindowsNewline);
}
}
@@ -504,7 +505,8 @@ namespace Microsoft.Unity.VisualStudio.Editor
Dictionary<string, string> allAssetsProjectParts,
ResponseFileData[] responseFilesData)
{
var projectBuilder = new StringBuilder(ProjectHeader(assembly, responseFilesData));
ProjectHeader(assembly, responseFilesData, out StringBuilder projectBuilder);
var references = new List<string>();
projectBuilder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
@@ -563,7 +565,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
// If the current assembly is a Player project, we want to project-reference the corresponding Player project
var referenceName = m_AssemblyNameProvider.GetAssemblyName(assembly.outputPath, reference.name);
projectBuilder.Append(" <ProjectReference Include=\"").Append(referenceName).Append(GetProjectExtension()).Append("\">").Append(k_WindowsNewline);
projectBuilder.Append(@" <ProjectReference Include=""").Append(referenceName).Append(GetProjectExtension()).Append(@""">").Append(k_WindowsNewline);
projectBuilder.Append(" <Project>{").Append(ProjectGuid(referenceName)).Append("}</Project>").Append(k_WindowsNewline);
projectBuilder.Append(" <Name>").Append(referenceName).Append("</Name>").Append(k_WindowsNewline);
projectBuilder.Append(" </ProjectReference>").Append(k_WindowsNewline);
@@ -595,7 +597,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
private void AppendReference(string fullReference, StringBuilder projectBuilder)
{
var escapedFullPath = EscapedRelativePathFor(fullReference, out _);
projectBuilder.Append(" <Reference Include=\"").Append(Path.GetFileNameWithoutExtension(escapedFullPath)).Append("\">").Append(k_WindowsNewline);
projectBuilder.Append(@" <Reference Include=""").Append(Path.GetFileNameWithoutExtension(escapedFullPath)).Append(@""">").Append(k_WindowsNewline);
projectBuilder.Append(" <HintPath>").Append(escapedFullPath).Append("</HintPath>").Append(k_WindowsNewline);
projectBuilder.Append(" </Reference>").Append(k_WindowsNewline);
}
@@ -632,25 +634,77 @@ namespace Microsoft.Unity.VisualStudio.Editor
return targetLanguageVersion;
}
private string ProjectHeader(
private static IEnumerable<string> GetOtherArguments(ResponseFileData[] responseFilesData, HashSet<string> names)
{
var lines = responseFilesData
.SelectMany(x => x.OtherArguments)
.Where(l => !string.IsNullOrEmpty(l))
.Select(l => l.Trim())
.Where(l => l.StartsWith("/") || l.StartsWith("-"));
foreach (var argument in lines)
{
var index = argument.IndexOf(":", StringComparison.Ordinal);
if (index == -1)
continue;
var key = argument
.Substring(1, index - 1)
.Trim();
if (!names.Contains(key))
continue;
if (argument.Length <= index)
continue;
yield return argument
.Substring(index + 1)
.Trim();
}
}
private string[] GetAnalyzers(Assembly assembly, ResponseFileData[] responseFilesData, out string rulesetPath)
{
rulesetPath = null;
if (m_CurrentInstallation == null || !m_CurrentInstallation.SupportsAnalyzers)
return Array.Empty<string>();
// Analyzers provided by VisualStudio
List<string> analyzers = new List<string>(m_CurrentInstallation.GetAnalyzers());
#if UNITY_2020_2_OR_NEWER
// Analyzers + ruleset provided by Unity
analyzers.AddRange(assembly.compilerOptions.RoslynAnalyzerDllPaths);
rulesetPath = assembly
.compilerOptions
.RoslynAnalyzerRulesetPath
.MakeAbsolutePath()
.NormalizePathSeparators();
#endif
// Analyzers provided by csc.rsp
analyzers.AddRange(GetOtherArguments(responseFilesData, new HashSet<string>(new[] { "analyzer", "a" })));
return analyzers
.Where(a => !string.IsNullOrEmpty(a))
.Select(a => a.MakeAbsolutePath().NormalizePathSeparators())
.Distinct()
.ToArray();
}
private void ProjectHeader(
Assembly assembly,
ResponseFileData[] responseFilesData
ResponseFileData[] responseFilesData,
out StringBuilder headerBuilder
)
{
var projectType = ProjectTypeOf(assembly.name);
string rulesetPath = null;
var analyzers = Array.Empty<string>();
var analyzers = GetAnalyzers(assembly, responseFilesData, out var rulesetPath);
if (m_CurrentInstallation != null && m_CurrentInstallation.SupportsAnalyzers)
{
analyzers = m_CurrentInstallation.GetAnalyzers();
#if UNITY_2020_2_OR_NEWER
analyzers = analyzers != null ? analyzers.Concat(assembly.compilerOptions.RoslynAnalyzerDllPaths).ToArray() : assembly.compilerOptions.RoslynAnalyzerDllPaths;
rulesetPath = assembly.compilerOptions.RoslynAnalyzerRulesetPath;
#endif
}
var projectProperties = new ProjectProperties()
var projectProperties = new ProjectProperties
{
ProjectGuid = ProjectGuid(assembly),
LangVersion = GetLangVersion(assembly),
@@ -658,9 +712,9 @@ namespace Microsoft.Unity.VisualStudio.Editor
RootNamespace = GetRootNamespace(assembly),
OutputPath = assembly.outputPath,
// Analyzers
Analyzers = analyzers,
RulesetPath = rulesetPath,
// RSP alterable
Analyzers = analyzers,
Defines = assembly.defines.Concat(responseFilesData.SelectMany(x => x.Defines)).Distinct().ToArray(),
Unsafe = assembly.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe),
// VSTU Flavoring
@@ -670,7 +724,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
FlavoringPackageVersion = VisualStudioIntegration.PackageVersion(),
};
return GetProjectHeader(projectProperties);
GetProjectHeader(projectProperties, out headerBuilder);
}
private enum ProjectType
@@ -696,102 +750,86 @@ namespace Microsoft.Unity.VisualStudio.Editor
return ProjectType.Game;
}
private string GetProjectHeader(ProjectProperties properties)
private void GetProjectHeader(ProjectProperties properties, out StringBuilder headerBuilder)
{
var header = new[]
{
$@"<?xml version=""1.0"" encoding=""utf-8""?>",
$@"<Project ToolsVersion=""4.0"" DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">",
$@" <PropertyGroup>",
$@" <LangVersion>{properties.LangVersion}</LangVersion>",
$@" </PropertyGroup>",
$@" <PropertyGroup>",
$@" <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>",
$@" <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>",
$@" <ProductVersion>10.0.20506</ProductVersion>",
$@" <SchemaVersion>2.0</SchemaVersion>",
$@" <RootNamespace>{properties.RootNamespace}</RootNamespace>",
$@" <ProjectGuid>{{{properties.ProjectGuid}}}</ProjectGuid>",
$@" <OutputType>Library</OutputType>",
$@" <AppDesignerFolder>Properties</AppDesignerFolder>",
$@" <AssemblyName>{properties.AssemblyName}</AssemblyName>",
$@" <TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>",
$@" <FileAlignment>512</FileAlignment>",
$@" <BaseDirectory>.</BaseDirectory>",
$@" </PropertyGroup>",
$@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">",
$@" <DebugSymbols>true</DebugSymbols>",
$@" <DebugType>full</DebugType>",
$@" <Optimize>false</Optimize>",
$@" <OutputPath>{properties.OutputPath}</OutputPath>",
$@" <DefineConstants>{string.Join(";", properties.Defines)}</DefineConstants>",
$@" <ErrorReport>prompt</ErrorReport>",
$@" <WarningLevel>4</WarningLevel>",
$@" <NoWarn>0169</NoWarn>",
$@" <AllowUnsafeBlocks>{properties.Unsafe}</AllowUnsafeBlocks>",
$@" </PropertyGroup>",
$@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "">",
$@" <DebugType>pdbonly</DebugType>",
$@" <Optimize>true</Optimize>",
$@" <OutputPath>Temp\bin\Release\</OutputPath>",
$@" <ErrorReport>prompt</ErrorReport>",
$@" <WarningLevel>4</WarningLevel>",
$@" <NoWarn>0169</NoWarn>",
$@" <AllowUnsafeBlocks>{properties.Unsafe}</AllowUnsafeBlocks>",
$@" </PropertyGroup>"
};
headerBuilder = new StringBuilder();
var forceExplicitReferences = new[]
{
$@" <PropertyGroup>",
$@" <NoConfig>true</NoConfig>",
$@" <NoStdLib>true</NoStdLib>",
$@" <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>",
$@" <ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>",
$@" <ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>",
$@" </PropertyGroup>"
};
//Header
headerBuilder.Append(@"<?xml version=""1.0"" encoding=""utf-8""?>").Append(k_WindowsNewline);
headerBuilder.Append(@"<Project ToolsVersion=""4.0"" DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">").Append(k_WindowsNewline);
headerBuilder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
headerBuilder.Append(@" <LangVersion>").Append(properties.LangVersion).Append(@"</LangVersion>").Append(k_WindowsNewline);
headerBuilder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
headerBuilder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
headerBuilder.Append(@" <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>").Append(k_WindowsNewline);
headerBuilder.Append(@" <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>").Append(k_WindowsNewline);
headerBuilder.Append(@" <ProductVersion>10.0.20506</ProductVersion>").Append(k_WindowsNewline);
headerBuilder.Append(@" <SchemaVersion>2.0</SchemaVersion>").Append(k_WindowsNewline);
headerBuilder.Append(@" <RootNamespace>").Append(properties.RootNamespace).Append(@"</RootNamespace>").Append(k_WindowsNewline);
headerBuilder.Append(@" <ProjectGuid>{").Append(properties.ProjectGuid).Append(@"}</ProjectGuid>").Append(k_WindowsNewline);
headerBuilder.Append(@" <OutputType>Library</OutputType>").Append(k_WindowsNewline);
headerBuilder.Append(@" <AppDesignerFolder>Properties</AppDesignerFolder>").Append(k_WindowsNewline);
headerBuilder.Append(@" <AssemblyName>").Append(properties.AssemblyName).Append(@"</AssemblyName>").Append(k_WindowsNewline);
headerBuilder.Append(@" <TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>").Append(k_WindowsNewline);
headerBuilder.Append(@" <FileAlignment>512</FileAlignment>").Append(k_WindowsNewline);
headerBuilder.Append(@" <BaseDirectory>.</BaseDirectory>").Append(k_WindowsNewline);
headerBuilder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
headerBuilder.Append(@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">").Append(k_WindowsNewline);
headerBuilder.Append(@" <DebugSymbols>true</DebugSymbols>").Append(k_WindowsNewline);
headerBuilder.Append(@" <DebugType>full</DebugType>").Append(k_WindowsNewline);
headerBuilder.Append(@" <Optimize>false</Optimize>").Append(k_WindowsNewline);
headerBuilder.Append(@" <OutputPath>").Append(properties.OutputPath).Append(@"</OutputPath>").Append(k_WindowsNewline);
headerBuilder.Append(@" <DefineConstants>").Append(string.Join(";", properties.Defines)).Append(@"</DefineConstants>").Append(k_WindowsNewline);
headerBuilder.Append(@" <ErrorReport>prompt</ErrorReport>").Append(k_WindowsNewline);
headerBuilder.Append(@" <WarningLevel>4</WarningLevel>").Append(k_WindowsNewline);
headerBuilder.Append(@" <NoWarn>0169</NoWarn>").Append(k_WindowsNewline);
headerBuilder.Append(@" <AllowUnsafeBlocks>").Append(properties.Unsafe).Append(@"</AllowUnsafeBlocks>").Append(k_WindowsNewline);
headerBuilder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
headerBuilder.Append(@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "">").Append(k_WindowsNewline);
headerBuilder.Append(@" <DebugType>pdbonly</DebugType>").Append(k_WindowsNewline);
headerBuilder.Append(@" <Optimize>true</Optimize>").Append(k_WindowsNewline);
headerBuilder.Append(@" <OutputPath>Temp\bin\Release\</OutputPath>").Append(k_WindowsNewline);
headerBuilder.Append(@" <ErrorReport>prompt</ErrorReport>").Append(k_WindowsNewline);
headerBuilder.Append(@" <WarningLevel>4</WarningLevel>").Append(k_WindowsNewline);
headerBuilder.Append(@" <NoWarn>0169</NoWarn>").Append(k_WindowsNewline);
headerBuilder.Append(@" <AllowUnsafeBlocks>").Append(properties.Unsafe).Append(@"</AllowUnsafeBlocks>").Append(k_WindowsNewline);
headerBuilder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
var flavoring = new[]
{
$@" <PropertyGroup>",
$@" <ProjectTypeGuids>{{E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1}};{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}</ProjectTypeGuids>",
$@" <UnityProjectGenerator>Package</UnityProjectGenerator>",
$@" <UnityProjectGeneratorVersion>{properties.FlavoringPackageVersion}</UnityProjectGeneratorVersion>",
$@" <UnityProjectType>{properties.FlavoringProjectType}</UnityProjectType>",
$@" <UnityBuildTarget>{properties.FlavoringBuildTarget}</UnityBuildTarget>",
$@" <UnityVersion>{properties.FlavoringUnityVersion}</UnityVersion>",
$@" </PropertyGroup>"
};
// Explicit references
headerBuilder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
headerBuilder.Append(@" <NoConfig>true</NoConfig>").Append(k_WindowsNewline);
headerBuilder.Append(@" <NoStdLib>true</NoStdLib>").Append(k_WindowsNewline);
headerBuilder.Append(@" <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>").Append(k_WindowsNewline);
headerBuilder.Append(@" <ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>").Append(k_WindowsNewline);
headerBuilder.Append(@" <ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>").Append(k_WindowsNewline);
headerBuilder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
var footer = new[]
{
@""
};
var lines = header
.Concat(forceExplicitReferences)
.Concat(flavoring)
.ToList();
// Flavoring
headerBuilder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
headerBuilder.Append(@" <ProjectTypeGuids>{E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>").Append(k_WindowsNewline);
headerBuilder.Append(@" <UnityProjectGenerator>Package</UnityProjectGenerator>").Append(k_WindowsNewline);
headerBuilder.Append(@" <UnityProjectGeneratorVersion>").Append(properties.FlavoringPackageVersion).Append(@"</UnityProjectGeneratorVersion>").Append(k_WindowsNewline);
headerBuilder.Append(@" <UnityProjectType>").Append(properties.FlavoringProjectType).Append(@"</UnityProjectType>").Append(k_WindowsNewline);
headerBuilder.Append(@" <UnityBuildTarget>").Append(properties.FlavoringBuildTarget).Append(@"</UnityBuildTarget>").Append(k_WindowsNewline);
headerBuilder.Append(@" <UnityVersion>").Append(properties.FlavoringUnityVersion).Append(@"</UnityVersion>").Append(k_WindowsNewline);
headerBuilder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
if (!string.IsNullOrEmpty(properties.RulesetPath))
{
lines.Add(@" <PropertyGroup>");
lines.Add($" <CodeAnalysisRuleSet>{properties.RulesetPath.MakeAbsolutePath().NormalizePathSeparators()}</CodeAnalysisRuleSet>");
lines.Add(@" </PropertyGroup>");
headerBuilder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
headerBuilder.Append(@" <CodeAnalysisRuleSet>").Append(properties.RulesetPath).Append(@"</CodeAnalysisRuleSet>").Append(k_WindowsNewline);
headerBuilder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
}
if (properties.Analyzers.Any())
{
lines.Add(@" <ItemGroup>");
foreach (var analyzer in properties.Analyzers.Distinct())
headerBuilder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
foreach (var analyzer in properties.Analyzers)
{
lines.Add($@" <Analyzer Include=""{analyzer.MakeAbsolutePath().NormalizePathSeparators()}"" />");
headerBuilder.Append(@" <Analyzer Include=""").Append(analyzer).Append(@""" />").Append(k_WindowsNewline);
}
lines.Add(@" </ItemGroup>");
headerBuilder.Append(@" </ItemGroup>").Append(k_WindowsNewline);
}
return string.Join(k_WindowsNewline, lines.Concat(footer));
}
private static string GetProjectFooter()
@@ -885,7 +923,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
if (array == null || array.Length == 0)
{
// HideSolution by default
array = new [] {
array = new[] {
new SolutionProperties() {
Name = "SolutionProperties",
Type = "preSolution",

View File

@@ -10,9 +10,9 @@ namespace Microsoft.Unity.VisualStudio.Editor
internal static class SolutionParser
{
// Compared to the bridge implementation, we are not returning "{" "}" from Guids
private static readonly Regex ProjectDeclaration = new Regex(@"Project\(\""{(?<projectFactoryGuid>.*?)}\""\)\s+=\s+\""(?<name>.*?)\"",\s+\""(?<fileName>.*?)\"",\s+\""{(?<projectGuid>.*?)}\""(?<metadata>.*?)\bEndProject\b", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
private static readonly Regex PropertiesDeclaration = new Regex(@"GlobalSection\((?<name>([\w]+Properties|NestedProjects))\)\s+=\s+(?<type>(?:post|pre)Solution)(?<entries>.*?)EndGlobalSection", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
private static readonly Regex PropertiesEntryDeclaration = new Regex(@"^\s*(?<key>.*?)=(?<value>.*?)$", RegexOptions.Multiline | RegexOptions.ExplicitCapture);
private static readonly Regex ProjectDeclaration = new Regex(@"Project\(\""{(?<projectFactoryGuid>.*?)}\""\)\s+=\s+\""(?<name>.*?)\"",\s+\""(?<fileName>.*?)\"",\s+\""{(?<projectGuid>.*?)}\""(?<metadata>.*?)\bEndProject\b", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled);
private static readonly Regex PropertiesDeclaration = new Regex(@"GlobalSection\((?<name>([\w]+Properties|NestedProjects))\)\s+=\s+(?<type>(?:post|pre)Solution)(?<entries>.*?)EndGlobalSection", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled);
private static readonly Regex PropertiesEntryDeclaration = new Regex(@"^\s*(?<key>.*?)=(?<value>.*?)$", RegexOptions.Multiline | RegexOptions.ExplicitCapture | RegexOptions.Compiled);
public static Solution ParseSolutionFile(string filename, IFileIO fileIO)
{

Binary file not shown.

View File

@@ -4,7 +4,6 @@
* 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.Runtime.InteropServices;
@@ -12,6 +11,8 @@ using System.Runtime.CompilerServices;
using UnityEditor;
using UnityEngine;
using Unity.CodeEditor;
using System.Threading;
using System.Collections.Concurrent;
[assembly: InternalsVisibleTo("Unity.VisualStudio.EditorTests")]
[assembly: InternalsVisibleTo("Unity.VisualStudio.Standalone.EditorTests")]
@@ -287,6 +288,14 @@ namespace Microsoft.Unity.VisualStudio.Editor
return false;
}
private enum COMIntegrationState
{
Running,
DisplayProgressBar,
ClearProgressBar,
Exited
}
private bool OpenWindowsApp(string path, int line)
{
var progpath = FileUtility.GetPackageAssetFullPath("Editor", "COMIntegration", "Release", "COMIntegration.exe");
@@ -309,44 +318,67 @@ namespace Microsoft.Unity.VisualStudio.Editor
solution = solution.Replace("^", "^^");
}
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = progpath,
Arguments = $"\"{CodeEditor.CurrentEditorInstallation}\" {solution} \"{absolutePath}\" {line}",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
StandardOutputEncoding = System.Text.Encoding.Unicode,
RedirectStandardError = true,
StandardErrorEncoding = System.Text.Encoding.Unicode,
}
};
var result = process.Start();
var psi = ProcessRunner.ProcessStartInfoFor(progpath, $"\"{CodeEditor.CurrentEditorInstallation}\" {solution} \"{absolutePath}\" {line}");
psi.StandardOutputEncoding = System.Text.Encoding.Unicode;
psi.StandardErrorEncoding = System.Text.Encoding.Unicode;
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);
}
// inter thread communication
var messages = new BlockingCollection<COMIntegrationState>();
if (outputLine == "clearprogressbar")
var asyncStart = AsyncOperation<ProcessRunnerResult>.Run(
() => ProcessRunner.StartAndWaitForExit(psi, onOutputReceived: data => OnOutputReceived(data, messages)),
e => new ProcessRunnerResult {Success = false, Error = e.Message, Output = string.Empty},
() => messages.Add(COMIntegrationState.Exited)
);
MonitorCOMIntegration(messages);
var result = asyncStart.Result;
if (!result.Success && !string.IsNullOrWhiteSpace(result.Error))
Debug.LogError($"Error while starting Visual Studio: {result.Error}");
return result.Success;
}
private static void MonitorCOMIntegration(BlockingCollection<COMIntegrationState> messages)
{
var displayingProgress = false;
COMIntegrationState state;
do
{
state = messages.Take();
switch (state)
{
EditorUtility.ClearProgressBar();
case COMIntegrationState.ClearProgressBar:
EditorUtility.ClearProgressBar();
displayingProgress = false;
break;
case COMIntegrationState.DisplayProgressBar:
EditorUtility.DisplayProgressBar("Opening Visual Studio", "Starting up Visual Studio, this might take some time.", .5f);
displayingProgress = true;
break;
}
} while (state != COMIntegrationState.Exited);
// Make sure the progress bar is properly cleared in case of COMIntegration failure
if (displayingProgress)
EditorUtility.ClearProgressBar();
}
private static readonly COMIntegrationState[] ProgressBarCommands = {COMIntegrationState.DisplayProgressBar, COMIntegrationState.ClearProgressBar};
private static void OnOutputReceived(string data, BlockingCollection<COMIntegrationState> messages)
{
if (data == null)
return;
foreach (var cmd in ProgressBarCommands)
{
if (data.IndexOf(cmd.ToString(), StringComparison.OrdinalIgnoreCase) >= 0)
messages.Add(cmd);
}
var errorOutput = process.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(errorOutput))
{
Console.WriteLine("Error: \n" + errorOutput);
}
process.WaitForExit();
return result;
}
[DllImport("AppleEventIntegration")]

View File

@@ -43,6 +43,10 @@ namespace Microsoft.Unity.VisualStudio.Editor
// C# language version support for Visual Studio
private static VersionPair[] WindowsVersionTable =
{
// VisualStudio 2022
new VersionPair(17,4, /* => */ 11,0),
new VersionPair(17,0, /* => */ 10,0),
// VisualStudio 2019
new VersionPair(16,8, /* => */ 9,0),
new VersionPair(16,0, /* => */ 8,0),
@@ -57,6 +61,10 @@ namespace Microsoft.Unity.VisualStudio.Editor
// C# language version support for Visual Studio for Mac
private static VersionPair[] OSXVersionTable =
{
// VisualStudio for Mac 2022
new VersionPair(17,4, /* => */ 11,0),
new VersionPair(17,0, /* => */ 10,0),
// VisualStudio for Mac 8.x
new VersionPair(8,8, /* => */ 9,0),
new VersionPair(8,3, /* => */ 8,0),