Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
## [2.0.20] - 2023-06-27

Integration:

- Internal API refactoring.

## [2.0.19] - 2023-06-14

Integration:

- Add support for Visual Studio Code.

Project generation:

- Add support for Sdk Style poject generation.
- Fix an issue related to missing properties with 2021.3.
  • Loading branch information
Unity Technologies committed Jun 27, 2023
1 parent ebe8973 commit d1e4dd0
Show file tree
Hide file tree
Showing 24 changed files with 1,386 additions and 621 deletions.
22 changes: 20 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Code Editor Package for Visual Studio

## [2.0.20] - 2023-06-27

Integration:

- Internal API refactoring.



## [2.0.19] - 2023-06-14

Integration:

- Add support for Visual Studio Code.

Project generation:

- Add support for Sdk Style poject generation.
- Fix an issue related to missing properties with 2021.3.


## [2.0.18] - 2023-03-17

Integration:
Expand All @@ -10,7 +30,6 @@ Project generation:

- Add extra compiler options for analyzers and source generators.


## [2.0.17] - 2022-12-06

Integration:
Expand All @@ -24,7 +43,6 @@ Project generation:
- Update supported C# versions.
- Performance improvements.


## [2.0.16] - 2022-06-08

Integration:
Expand Down
Binary file modified Editor/COMIntegration/Release/COMIntegration.exe
Binary file not shown.
2 changes: 1 addition & 1 deletion Editor/Cli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal static string GetInstallationDetails(IVisualStudioInstallation installa

internal static void GenerateSolutionWith(VisualStudioEditor vse, string installationPath)
{
if (vse != null && vse.TryGetVisualStudioInstallationForPath(installationPath, searchInstallations: true, out var vsi))
if (vse != null && vse.TryGetVisualStudioInstallationForPath(installationPath, lookupDiscoveredInstallations: true, out var vsi))
{
Log($"Using {GetInstallationDetails(vsi)}");
vse.SyncAll();
Expand Down
154 changes: 22 additions & 132 deletions Editor/Discovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,162 +3,52 @@
* 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.Diagnostics;
using System.Text.RegularExpressions;
using System.Linq;
using UnityEngine;
using System.IO;

namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class Discovery
{
internal const string ManagedWorkload = "Microsoft.VisualStudio.Workload.ManagedGame";

internal static string _vsWherePath;

public static void FindVSWhere()
{
_vsWherePath = FileUtility.GetPackageAssetFullPath("Editor", "VSWhere", "vswhere.exe");
}

public static IEnumerable<IVisualStudioInstallation> GetVisualStudioInstallations()
{
if (VisualStudioEditor.IsWindows)
{
foreach (var installation in QueryVsWhere())
yield return installation;
}
foreach (var installation in VisualStudioForWindowsInstallation.GetVisualStudioInstallations())
yield return installation;

if (VisualStudioEditor.IsOSX)
{
var candidates = Directory.EnumerateDirectories("/Applications", "*.app");
foreach (var candidate in candidates)
{
if (TryDiscoverInstallation(candidate, out var installation))
yield return installation;
}
}
}

private static bool IsCandidateForDiscovery(string path)
{
if (File.Exists(path) && VisualStudioEditor.IsWindows && Regex.IsMatch(path, "devenv.exe$", RegexOptions.IgnoreCase))
return true;

if (Directory.Exists(path) && VisualStudioEditor.IsOSX && Regex.IsMatch(path, "Visual\\s?Studio(?!.*Code.*).*.app$", RegexOptions.IgnoreCase))
return true;
foreach (var installation in VisualStudioForMacInstallation.GetVisualStudioInstallations())
yield return installation;

return false;
foreach (var installation in VisualStudioCodeInstallation.GetVisualStudioInstallations())
yield return installation;
}

public static bool TryDiscoverInstallation(string editorPath, out IVisualStudioInstallation installation)
{
installation = null;

if (string.IsNullOrEmpty(editorPath))
return false;

if (!IsCandidateForDiscovery(editorPath))
return false;

// On windows we use the executable directly, so we can query extra information
var fvi = editorPath;

// On Mac we use the .app folder, so we need to access to main assembly
if (VisualStudioEditor.IsOSX)
try
{
fvi = Path.Combine(editorPath, "Contents/Resources/lib/monodevelop/bin/VisualStudio.exe");
if (VisualStudioForWindowsInstallation.TryDiscoverInstallation(editorPath, out installation))
return true;

if (!File.Exists(fvi))
fvi = Path.Combine(editorPath, "Contents/MonoBundle/VisualStudio.exe");
if (VisualStudioForMacInstallation.TryDiscoverInstallation(editorPath, out installation))
return true;

if (!File.Exists(fvi))
fvi = Path.Combine(editorPath, "Contents/MonoBundle/VisualStudio.dll");
if (VisualStudioCodeInstallation.TryDiscoverInstallation(editorPath, out installation))
return true;
}

if (!File.Exists(fvi))
return false;

// VS preview are not using the isPrerelease flag so far
// On Windows FileDescription contains "Preview", but not on Mac
var vi = FileVersionInfo.GetVersionInfo(fvi);
var version = new Version(vi.ProductVersion);
var isPrerelease = vi.IsPreRelease || string.Concat(editorPath, "/" + vi.FileDescription).ToLower().Contains("preview");

installation = new VisualStudioInstallation()
catch (IOException)
{
IsPrerelease = isPrerelease,
Name = $"{vi.FileDescription}{(isPrerelease && VisualStudioEditor.IsOSX ? " Preview" : string.Empty)} [{version.ToString(3)}]",
Path = editorPath,
Version = version
};
return true;
}

#region VsWhere Json Schema
#pragma warning disable CS0649
[Serializable]
internal class VsWhereResult
{
public VsWhereEntry[] entries;

public static VsWhereResult FromJson(string json)
{
return JsonUtility.FromJson<VsWhereResult>("{ \"" + nameof(VsWhereResult.entries) + "\": " + json + " }");
installation = null;
}

public IEnumerable<VisualStudioInstallation> ToVisualStudioInstallations()
{
foreach (var entry in entries)
{
yield return new VisualStudioInstallation()
{
Name = $"{entry.displayName} [{entry.catalog.productDisplayVersion}]",
Path = entry.productPath,
IsPrerelease = entry.isPrerelease,
Version = Version.Parse(entry.catalog.buildVersion)
};
}
}
}

[Serializable]
internal class VsWhereEntry
{
public string displayName;
public bool isPrerelease;
public string productPath;
public VsWhereCatalog catalog;
}

[Serializable]
internal class VsWhereCatalog
{
public string productDisplayVersion; // non parseable like "16.3.0 Preview 3.0"
public string buildVersion;
return false;
}
#pragma warning restore CS3021
#endregion

private static IEnumerable<VisualStudioInstallation> QueryVsWhere()
public static void Initialize()
{
var progpath = _vsWherePath;

if (string.IsNullOrWhiteSpace(progpath))
return Enumerable.Empty<VisualStudioInstallation>();

var result = ProcessRunner.StartAndWaitForExit(progpath, "-prerelease -format json -utf8");

if (!result.Success)
throw new Exception($"Failure while running vswhere: {result.Error}");

// Do not catch any JsonException here, this will be handled by the caller
return VsWhereResult
.FromJson(result.Output)
.ToVisualStudioInstallations();
VisualStudioForWindowsInstallation.Initialize();
VisualStudioForMacInstallation.Initialize();
VisualStudioCodeInstallation.Initialize();
}
}
}
Binary file not shown.
24 changes: 20 additions & 4 deletions Editor/ProcessRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Unity.VisualStudio.Editor
Expand All @@ -21,19 +22,34 @@ internal static class ProcessRunner
{
public const int DefaultTimeoutInMilliseconds = 300000;

public static ProcessStartInfo ProcessStartInfoFor(string filename, string arguments)
public static ProcessStartInfo ProcessStartInfoFor(string filename, string arguments, bool redirect = true, bool shell = false)
{
return new ProcessStartInfo
{
UseShellExecute = false,
UseShellExecute = shell,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardOutput = redirect,
RedirectStandardError = redirect,
FileName = filename,
Arguments = arguments
};
}

public static void Start(string filename, string arguments)
{
Start(ProcessStartInfoFor(filename, arguments, false));
}

public static void Start(ProcessStartInfo processStartInfo)
{
var process = new Process { StartInfo = processStartInfo };

using (process)
{
process.Start();
}
}

public static ProcessRunnerResult StartAndWaitForExit(string filename, string arguments, int timeoutms = DefaultTimeoutInMilliseconds, Action<string> onOutputReceived = null)
{
return StartAndWaitForExit(ProcessStartInfoFor(filename, arguments), timeoutms, onOutputReceived);
Expand Down
6 changes: 6 additions & 0 deletions Editor/ProjectGeneration/AssemblyNameProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ public class AssemblyNameProvider : IAssemblyNameProvider
public string ProjectGenerationRootNamespace => EditorSettings.projectGenerationRootNamespace;

public ProjectGenerationFlag ProjectGenerationFlag
{
get { return ProjectGenerationFlagImpl; }
private set { ProjectGenerationFlagImpl = value;}
}

internal virtual ProjectGenerationFlag ProjectGenerationFlagImpl
{
get => m_ProjectGenerationFlag;
private set
Expand Down
98 changes: 98 additions & 0 deletions Editor/ProjectGeneration/LegacyStyleProjectGeneration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* 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.Text;
using UnityEditor.Compilation;

namespace Microsoft.Unity.VisualStudio.Editor
{

internal class LegacyStyleProjectGeneration : ProjectGeneration
{
public LegacyStyleProjectGeneration(string tempDirectory, IAssemblyNameProvider assemblyNameProvider, IFileIO fileIoProvider, IGUIDGenerator guidGenerator) : base(tempDirectory, assemblyNameProvider, fileIoProvider, guidGenerator)
{
}

public LegacyStyleProjectGeneration(string tempDirectory) : base(tempDirectory)
{
}

public LegacyStyleProjectGeneration()
{
}

internal override void GetProjectHeader(ProjectProperties properties, out StringBuilder headerBuilder)
{
headerBuilder = new StringBuilder();

//Header
headerBuilder.Append(@"<?xml version=""1.0"" encoding=""utf-8""?>").Append(k_WindowsNewline);
headerBuilder.Append($@"<Project ToolsVersion=""4.0"" DefaultTargets=""Build"" xmlns=""{MSBuildNamespaceUri}"">").Append(k_WindowsNewline);
headerBuilder.Append(@" <!-- Generated file, do not modify, your changes will be overwritten (use AssetPostprocessor.OnGeneratedCSProject) -->").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);
// In the end, given we use NoConfig/NoStdLib (see below), hardcoding the target framework version with the legacy format will have no impact, even when targeting netstandard/net48 from Unity.
// And VSTU/Unity Game workload has a dependency towards net471 reference assemblies, so IDE will not complain that this specific SDK is not available.
// Unity already selected proper API surface through referenced DLLs for us.
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);

GetProjectHeaderConfigurations(properties, headerBuilder);

// 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);

GetProjectHeaderVstuFlavoring(properties, headerBuilder);
GetProjectHeaderAnalyzers(properties, headerBuilder);
}

internal override void AppendProjectReference(Assembly assembly, Assembly reference, StringBuilder projectBuilder)
{
// 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(" <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);
}

internal override void GetProjectFooter(StringBuilder footerBuilder)
{
footerBuilder.Append(string.Join(k_WindowsNewline,
@" <Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />",
@" <Target Name=""GenerateTargetFrameworkMonikerAttribute"" />",
@" <!-- To modify your build process, add your task inside one of the targets below and uncomment it.",
@" Other similar extension points exist, see Microsoft.Common.targets.",
@" <Target Name=""BeforeBuild"">",
@" </Target>",
@" <Target Name=""AfterBuild"">",
@" </Target>",
@" -->",
@"</Project>",
@""));
}
}
}
Loading

0 comments on commit d1e4dd0

Please sign in to comment.