Skip to content

Commit 5121db3

Browse files
committed
Refactor
1 parent 836c2d9 commit 5121db3

File tree

8 files changed

+571
-436
lines changed

8 files changed

+571
-436
lines changed

Packages/com.unity.ide.visualstudio/Editor/Discovery.cs

Lines changed: 16 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -3,162 +3,44 @@
33
* Copyright (c) Microsoft Corporation. All rights reserved.
44
* Licensed under the MIT License. See License.txt in the project root for license information.
55
*--------------------------------------------------------------------------------------------*/
6-
using System;
7-
using System.IO;
86
using System.Collections.Generic;
9-
using System.Diagnostics;
10-
using System.Text.RegularExpressions;
11-
using System.Linq;
12-
using UnityEngine;
7+
using System.IO;
138

149
namespace Microsoft.Unity.VisualStudio.Editor
1510
{
1611
internal static class Discovery
1712
{
18-
internal const string ManagedWorkload = "Microsoft.VisualStudio.Workload.ManagedGame";
19-
20-
internal static string _vsWherePath;
21-
22-
public static void FindVSWhere()
23-
{
24-
_vsWherePath = FileUtility.GetPackageAssetFullPath("Editor", "VSWhere", "vswhere.exe");
25-
}
26-
2713
public static IEnumerable<IVisualStudioInstallation> GetVisualStudioInstallations()
2814
{
29-
if (VisualStudioEditor.IsWindows)
30-
{
31-
foreach (var installation in QueryVsWhere())
32-
yield return installation;
33-
}
34-
35-
if (VisualStudioEditor.IsOSX)
36-
{
37-
var candidates = Directory.EnumerateDirectories("/Applications", "*.app");
38-
foreach (var candidate in candidates)
39-
{
40-
if (TryDiscoverInstallation(candidate, out var installation))
41-
yield return installation;
42-
}
43-
}
44-
}
45-
46-
private static bool IsCandidateForDiscovery(string path)
47-
{
48-
if (File.Exists(path) && VisualStudioEditor.IsWindows && Regex.IsMatch(path, "devenv.exe$", RegexOptions.IgnoreCase))
49-
return true;
50-
51-
if (Directory.Exists(path) && VisualStudioEditor.IsOSX && Regex.IsMatch(path, "Visual\\s?Studio(?!.*Code.*).*.app$", RegexOptions.IgnoreCase))
52-
return true;
15+
foreach (var installation in VisualStudioForWindowsInstallation.GetVisualStudioInstallations())
16+
yield return installation;
5317

54-
return false;
18+
foreach (var installation in VisualStudioForMacInstallation.GetVisualStudioInstallations())
19+
yield return installation;
5520
}
5621

5722
public static bool TryDiscoverInstallation(string editorPath, out IVisualStudioInstallation installation)
5823
{
59-
installation = null;
60-
61-
if (string.IsNullOrEmpty(editorPath))
62-
return false;
63-
64-
if (!IsCandidateForDiscovery(editorPath))
65-
return false;
66-
67-
// On windows we use the executable directly, so we can query extra information
68-
var fvi = editorPath;
69-
70-
// On Mac we use the .app folder, so we need to access to main assembly
71-
if (VisualStudioEditor.IsOSX)
24+
try
7225
{
73-
fvi = Path.Combine(editorPath, "Contents/Resources/lib/monodevelop/bin/VisualStudio.exe");
74-
75-
if (!File.Exists(fvi))
76-
fvi = Path.Combine(editorPath, "Contents/MonoBundle/VisualStudio.exe");
26+
if (VisualStudioForWindowsInstallation.TryDiscoverInstallation(editorPath, out installation))
27+
return true;
7728

78-
if (!File.Exists(fvi))
79-
fvi = Path.Combine(editorPath, "Contents/MonoBundle/VisualStudio.dll");
29+
if (VisualStudioForMacInstallation.TryDiscoverInstallation(editorPath, out installation))
30+
return true;
8031
}
81-
82-
if (!File.Exists(fvi))
83-
return false;
84-
85-
// VS preview are not using the isPrerelease flag so far
86-
// On Windows FileDescription contains "Preview", but not on Mac
87-
var vi = FileVersionInfo.GetVersionInfo(fvi);
88-
var version = new Version(vi.ProductVersion);
89-
var isPrerelease = vi.IsPreRelease || string.Concat(editorPath, "/" + vi.FileDescription).ToLower().Contains("preview");
90-
91-
installation = new VisualStudioInstallation()
32+
catch (IOException)
9233
{
93-
IsPrerelease = isPrerelease,
94-
Name = $"{vi.FileDescription}{(isPrerelease && VisualStudioEditor.IsOSX ? " Preview" : string.Empty)} [{version.ToString(3)}]",
95-
Path = editorPath,
96-
Version = version
97-
};
98-
return true;
99-
}
100-
101-
#region VsWhere Json Schema
102-
#pragma warning disable CS0649
103-
[Serializable]
104-
internal class VsWhereResult
105-
{
106-
public VsWhereEntry[] entries;
107-
108-
public static VsWhereResult FromJson(string json)
109-
{
110-
return JsonUtility.FromJson<VsWhereResult>("{ \"" + nameof(VsWhereResult.entries) + "\": " + json + " }");
34+
installation = null;
11135
}
11236

113-
public IEnumerable<VisualStudioInstallation> ToVisualStudioInstallations()
114-
{
115-
foreach (var entry in entries)
116-
{
117-
yield return new VisualStudioInstallation()
118-
{
119-
Name = $"{entry.displayName} [{entry.catalog.productDisplayVersion}]",
120-
Path = entry.productPath,
121-
IsPrerelease = entry.isPrerelease,
122-
Version = Version.Parse(entry.catalog.buildVersion)
123-
};
124-
}
125-
}
126-
}
127-
128-
[Serializable]
129-
internal class VsWhereEntry
130-
{
131-
public string displayName;
132-
public bool isPrerelease;
133-
public string productPath;
134-
public VsWhereCatalog catalog;
135-
}
136-
137-
[Serializable]
138-
internal class VsWhereCatalog
139-
{
140-
public string productDisplayVersion; // non parseable like "16.3.0 Preview 3.0"
141-
public string buildVersion;
37+
return false;
14238
}
143-
#pragma warning restore CS3021
144-
#endregion
14539

146-
private static IEnumerable<VisualStudioInstallation> QueryVsWhere()
40+
public static void Initialize()
14741
{
148-
var progpath = _vsWherePath;
149-
150-
if (string.IsNullOrWhiteSpace(progpath))
151-
return Enumerable.Empty<VisualStudioInstallation>();
152-
153-
var result = ProcessRunner.StartAndWaitForExit(progpath, "-prerelease -format json -utf8");
154-
155-
if (!result.Success)
156-
throw new Exception($"Failure while running vswhere: {result.Error}");
157-
158-
// Do not catch any JsonException here, this will be handled by the caller
159-
return VsWhereResult
160-
.FromJson(result.Output)
161-
.ToVisualStudioInstallations();
42+
VisualStudioForWindowsInstallation.Initialize();
43+
VisualStudioForMacInstallation.Initialize();
16244
}
16345
}
16446
}

Packages/com.unity.ide.visualstudio/Editor/ProjectGeneration/ProjectGeneration.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ public bool SyncIfNeeded(IEnumerable<string> affectedFiles, IEnumerable<string>
150150

151151
private void CreateVsConfigIfNotFound()
152152
{
153+
const string ManagedWorkload = "Microsoft.VisualStudio.Workload.ManagedGame";
154+
153155
try
154156
{
155157
var vsConfigFile = VsConfigFile();
@@ -159,7 +161,7 @@ private void CreateVsConfigIfNotFound()
159161
var content = $@"{{
160162
""version"": ""1.0"",
161163
""components"": [
162-
""{Discovery.ManagedWorkload}""
164+
""{ManagedWorkload}""
163165
]
164166
}}
165167
";

Packages/com.unity.ide.visualstudio/Editor/VisualStudioEditor.cs

Lines changed: 8 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ static VisualStudioEditor()
3838
if (!UnityInstallation.IsMainUnityEditorProcess)
3939
return;
4040

41-
if (IsWindows)
42-
Discovery.FindVSWhere();
43-
41+
Discovery.Initialize();
4442
CodeEditor.Register(new VisualStudioEditor());
4543

4644
_discoverInstallations = AsyncOperation<IVisualStudioInstallation[]>.Run(DiscoverInstallations);
@@ -193,38 +191,23 @@ bool IsSupportedPath(string path)
193191
return false;
194192
}
195193

196-
private static void CheckCurrentEditorInstallation()
194+
public bool OpenProject(string path, int line, int column)
197195
{
198196
var editorPath = CodeEditor.CurrentEditorInstallation;
199-
try
200-
{
201-
if (Discovery.TryDiscoverInstallation(editorPath, out _))
202-
return;
203-
}
204-
catch (IOException)
205-
{
206-
}
207197

208-
Debug.LogWarning($"Visual Studio executable {editorPath} is not found. Please change your settings in Edit > Preferences > External Tools.");
209-
}
210-
211-
public bool OpenProject(string path, int line, int column)
212-
{
213-
CheckCurrentEditorInstallation();
198+
if (!Discovery.TryDiscoverInstallation(editorPath, out var installation)) {
199+
Debug.LogWarning($"Visual Studio executable {editorPath} is not found. Please change your settings in Edit > Preferences > External Tools.");
200+
return false;
201+
}
214202

215203
if (!IsSupportedPath(path))
216204
return false;
217205

218206
if (!IsProjectGeneratedFor(path, out var missingFlag))
219207
Debug.LogWarning($"You are trying to open {path} outside a generated project. This might cause problems with IntelliSense and debugging. To avoid this, you can change your .csproj preferences in Edit > Preferences > External Tools and enable {GetProjectGenerationFlagDescription(missingFlag)} generation.");
220208

221-
if (IsOSX)
222-
return OpenOSXApp(path, line, column);
223-
224-
if (IsWindows)
225-
return OpenWindowsApp(path, line);
226-
227-
return false;
209+
var solution = GetOrGenerateSolutionFile(path);
210+
return installation.Open(path, line, column, solution);
228211
}
229212

230213
private static string GetProjectGenerationFlagDescription(ProjectGenerationFlag flag)
@@ -287,114 +270,6 @@ private bool IsProjectGeneratedFor(string path, out ProjectGenerationFlag missin
287270
return false;
288271
}
289272

290-
private enum COMIntegrationState
291-
{
292-
Running,
293-
DisplayProgressBar,
294-
ClearProgressBar,
295-
Exited
296-
}
297-
298-
private bool OpenWindowsApp(string path, int line)
299-
{
300-
var progpath = FileUtility.GetPackageAssetFullPath("Editor", "COMIntegration", "Release", "COMIntegration.exe");
301-
302-
if (string.IsNullOrWhiteSpace(progpath))
303-
return false;
304-
305-
string absolutePath = "";
306-
if (!string.IsNullOrWhiteSpace(path))
307-
{
308-
absolutePath = Path.GetFullPath(path);
309-
}
310-
311-
// We remove all invalid chars from the solution filename, but we cannot prevent the user from using a specific path for the Unity project
312-
// So process the fullpath to make it compatible with VS
313-
var solution = GetOrGenerateSolutionFile(path);
314-
if (!string.IsNullOrWhiteSpace(solution))
315-
{
316-
solution = $"\"{solution}\"";
317-
solution = solution.Replace("^", "^^");
318-
}
319-
320-
321-
var psi = ProcessRunner.ProcessStartInfoFor(progpath, $"\"{CodeEditor.CurrentEditorInstallation}\" {solution} \"{absolutePath}\" {line}");
322-
psi.StandardOutputEncoding = System.Text.Encoding.Unicode;
323-
psi.StandardErrorEncoding = System.Text.Encoding.Unicode;
324-
325-
// inter thread communication
326-
var messages = new BlockingCollection<COMIntegrationState>();
327-
328-
var asyncStart = AsyncOperation<ProcessRunnerResult>.Run(
329-
() => ProcessRunner.StartAndWaitForExit(psi, onOutputReceived: data => OnOutputReceived(data, messages)),
330-
e => new ProcessRunnerResult {Success = false, Error = e.Message, Output = string.Empty},
331-
() => messages.Add(COMIntegrationState.Exited)
332-
);
333-
334-
MonitorCOMIntegration(messages);
335-
336-
var result = asyncStart.Result;
337-
338-
if (!result.Success && !string.IsNullOrWhiteSpace(result.Error))
339-
Debug.LogError($"Error while starting Visual Studio: {result.Error}");
340-
341-
return result.Success;
342-
}
343-
344-
private static void MonitorCOMIntegration(BlockingCollection<COMIntegrationState> messages)
345-
{
346-
var displayingProgress = false;
347-
COMIntegrationState state;
348-
349-
do
350-
{
351-
state = messages.Take();
352-
switch (state)
353-
{
354-
case COMIntegrationState.ClearProgressBar:
355-
EditorUtility.ClearProgressBar();
356-
displayingProgress = false;
357-
break;
358-
case COMIntegrationState.DisplayProgressBar:
359-
EditorUtility.DisplayProgressBar("Opening Visual Studio", "Starting up Visual Studio, this might take some time.", .5f);
360-
displayingProgress = true;
361-
break;
362-
}
363-
} while (state != COMIntegrationState.Exited);
364-
365-
// Make sure the progress bar is properly cleared in case of COMIntegration failure
366-
if (displayingProgress)
367-
EditorUtility.ClearProgressBar();
368-
}
369-
370-
private static readonly COMIntegrationState[] ProgressBarCommands = {COMIntegrationState.DisplayProgressBar, COMIntegrationState.ClearProgressBar};
371-
private static void OnOutputReceived(string data, BlockingCollection<COMIntegrationState> messages)
372-
{
373-
if (data == null)
374-
return;
375-
376-
foreach (var cmd in ProgressBarCommands)
377-
{
378-
if (data.IndexOf(cmd.ToString(), StringComparison.OrdinalIgnoreCase) >= 0)
379-
messages.Add(cmd);
380-
}
381-
}
382-
383-
[DllImport("AppleEventIntegration")]
384-
static extern bool OpenVisualStudio(string appPath, string solutionPath, string filePath, int line);
385-
386-
bool OpenOSXApp(string path, int line, int column)
387-
{
388-
string absolutePath = "";
389-
if (!string.IsNullOrWhiteSpace(path))
390-
{
391-
absolutePath = Path.GetFullPath(path);
392-
}
393-
394-
var solution = GetOrGenerateSolutionFile(path);
395-
return OpenVisualStudio(CodeEditor.CurrentEditorInstallation, solution, absolutePath, line);
396-
}
397-
398273
private string GetOrGenerateSolutionFile(string path)
399274
{
400275
_generator.Sync();

0 commit comments

Comments
 (0)