Skip to content

Set session failure with reason when applicable #2018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,7 @@ protected override void EndProcessing()
// Create the configuration from parameters
EditorServicesConfig editorServicesConfig = CreateConfigObject();

SessionFileWriter sessionFileWriter = new(_logger, SessionDetailsPath);
_logger.Log(PsesLogLevel.Diagnostic, "Session file writer created");

using EditorServicesLoader psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, sessionFileWriter, _loggerUnsubscribers);
using EditorServicesLoader psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, SessionDetailsPath, _loggerUnsubscribers);
_logger.Log(PsesLogLevel.Verbose, "Loading EditorServices");
// Synchronously start editor services and wait here until it shuts down.
#pragma warning disable VSTHRD002
Expand Down Expand Up @@ -394,7 +391,7 @@ private string GetProfilePathFromProfileObject(PSObject profileObject, ProfileUs
$"{HostProfileId}_profile.ps1");
}

// We should only use PSReadLine if we specificied that we want a console repl
// We should only use PSReadLine if we specified that we want a console repl
// and we have not explicitly said to use the legacy ReadLine.
// We also want it if we are either:
// * On Windows on any version OR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ public interface ISessionFileWriter
/// Write a session file describing a failed startup.
/// </summary>
/// <param name="reason">The reason for the startup failure.</param>
/// <param name="details">Any details to accompany the reason.</param>
void WriteSessionFailure(string reason, object details);
void WriteSessionFailure(string reason);

/// <summary>
/// Write a session file describing a successful startup.
Expand All @@ -43,23 +42,26 @@ public sealed class SessionFileWriter : ISessionFileWriter

private readonly string _sessionFilePath;

private readonly Version _powerShellVersion;

/// <summary>
/// Construct a new session file writer for the given session file path.
/// </summary>
/// <param name="logger">The logger to log actions with.</param>
/// <param name="sessionFilePath">The path to write the session file path to.</param>
public SessionFileWriter(HostLogger logger, string sessionFilePath)
/// <param name="powerShellVersion">The process's PowerShell version object.</param>
public SessionFileWriter(HostLogger logger, string sessionFilePath, Version powerShellVersion)
{
_logger = logger;
_sessionFilePath = sessionFilePath;
_powerShellVersion = powerShellVersion;
}

/// <summary>
/// Write a startup failure to the session file.
/// </summary>
/// <param name="reason">The reason for the startup failure.</param>
/// <param name="details">Any extra details, which will be serialized as JSON.</param>
public void WriteSessionFailure(string reason, object details)
public void WriteSessionFailure(string reason)
{
_logger.Log(PsesLogLevel.Diagnostic, "Writing session failure");

Expand All @@ -69,11 +71,6 @@ public void WriteSessionFailure(string reason, object details)
{ "reason", reason },
};

if (details != null)
{
sessionObject["details"] = details;
}

WriteSessionObject(sessionObject);
}

Expand All @@ -91,11 +88,11 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran
{ "status", "started" },
};

if (languageServiceTransport != null)
if (languageServiceTransport is not null)
{
sessionObject["languageServiceTransport"] = languageServiceTransport.SessionFileTransportName;

if (languageServiceTransport.SessionFileEntries != null)
if (languageServiceTransport.SessionFileEntries is not null)
{
foreach (KeyValuePair<string, object> sessionEntry in languageServiceTransport.SessionFileEntries)
{
Expand All @@ -104,7 +101,7 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran
}
}

if (debugAdapterTransport != null)
if (debugAdapterTransport is not null)
{
sessionObject["debugServiceTransport"] = debugAdapterTransport.SessionFileTransportName;

Expand All @@ -126,6 +123,8 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran
/// <param name="sessionObject">The dictionary representing the session file.</param>
private void WriteSessionObject(Dictionary<string, object> sessionObject)
{
sessionObject["powerShellVersion"] = _powerShellVersion.ToString();

string psModulePath = Environment.GetEnvironmentVariable("PSModulePath");
string content = null;
using (SMA.PowerShell pwsh = SMA.PowerShell.Create(RunspaceMode.NewRunspace))
Expand Down
103 changes: 51 additions & 52 deletions src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public sealed class EditorServicesLoader : IDisposable
{
#if !CoreCLR
// See https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed
private const int Net462Version = 394802;
private const int Net48Version = 528040;

private static readonly string s_psesBaseDirPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
#endif
Expand All @@ -49,37 +49,28 @@ public sealed class EditorServicesLoader : IDisposable
/// </summary>
/// <param name="logger">The host logger to use.</param>
/// <param name="hostConfig">The host configuration to start editor services with.</param>
/// <param name="sessionFileWriter">The session file writer to write the session file with.</param>
/// <returns></returns>
public static EditorServicesLoader Create(
HostLogger logger,
EditorServicesConfig hostConfig,
ISessionFileWriter sessionFileWriter) => Create(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe: null);

/// <summary>
/// Create a new Editor Services loader.
/// </summary>
/// <param name="logger">The host logger to use.</param>
/// <param name="hostConfig">The host configuration to start editor services with.</param>
/// <param name="sessionFileWriter">The session file writer to write the session file with.</param>
/// <param name="sessionDetailsPath">Path to the session file to create on startup or startup failure.</param>
/// <param name="loggersToUnsubscribe">The loggers to unsubscribe form writing to the terminal.</param>
/// <returns></returns>
public static EditorServicesLoader Create(
HostLogger logger,
EditorServicesConfig hostConfig,
ISessionFileWriter sessionFileWriter,
string sessionDetailsPath,
IReadOnlyCollection<IDisposable> loggersToUnsubscribe)
{
if (logger == null)
if (logger is null)
{
throw new ArgumentNullException(nameof(logger));
}

if (hostConfig == null)
if (hostConfig is null)
{
throw new ArgumentNullException(nameof(hostConfig));
}

Version powerShellVersion = GetPSVersion();
SessionFileWriter sessionFileWriter = new(logger, sessionDetailsPath, powerShellVersion);
logger.Log(PsesLogLevel.Diagnostic, "Session file writer created");

#if CoreCLR
// In .NET Core, we add an event here to redirect dependency loading to the new AssemblyLoadContext we load PSES' dependencies into
logger.Log(PsesLogLevel.Verbose, "Adding AssemblyResolve event handler for new AssemblyLoadContext dependency loading");
Expand Down Expand Up @@ -167,7 +158,7 @@ public static EditorServicesLoader Create(
};
#endif

return new EditorServicesLoader(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe);
return new EditorServicesLoader(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe, powerShellVersion);
}

private readonly EditorServicesConfig _hostConfig;
Expand All @@ -178,33 +169,38 @@ public static EditorServicesLoader Create(

private readonly IReadOnlyCollection<IDisposable> _loggersToUnsubscribe;

private readonly Version _powerShellVersion;

private EditorServicesRunner _editorServicesRunner;

private EditorServicesLoader(
HostLogger logger,
EditorServicesConfig hostConfig,
ISessionFileWriter sessionFileWriter,
IReadOnlyCollection<IDisposable> loggersToUnsubscribe)
IReadOnlyCollection<IDisposable> loggersToUnsubscribe,
Version powerShellVersion)
{
_logger = logger;
_hostConfig = hostConfig;
_sessionFileWriter = sessionFileWriter;
_loggersToUnsubscribe = loggersToUnsubscribe;
_powerShellVersion = powerShellVersion;
}

/// <summary>
/// Load Editor Services and its dependencies in an isolated way and start it.
/// This method's returned task will end when Editor Services shuts down.
/// </summary>
/// <returns></returns>
public Task LoadAndRunEditorServicesAsync()
{
// Log important host information here
LogHostInformation();

CheckPowerShellVersion();

#if !CoreCLR
// Make sure the .NET Framework version supports .NET Standard 2.0
CheckNetFxVersion();
CheckDotNetVersion();
#endif

// Add the bundled modules to the PSModulePath
Expand Down Expand Up @@ -241,10 +237,30 @@ private static void LoadEditorServices() =>
// The call within this method is therefore a total no-op
EditorServicesLoading.LoadEditorServicesForHost();

private void CheckPowerShellVersion()
{
PSLanguageMode languageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode;

_logger.Log(PsesLogLevel.Verbose, $@"
== PowerShell Details ==
- PowerShell version: {_powerShellVersion}
- Language mode: {languageMode}
");

if ((_powerShellVersion < new Version(5, 1))
|| (_powerShellVersion >= new Version(6, 0) && _powerShellVersion < new Version(7, 2)))
{
_logger.Log(PsesLogLevel.Error, $"PowerShell {_powerShellVersion} is not supported, please update!");
_sessionFileWriter.WriteSessionFailure("powerShellVersion");
}

// TODO: Check if language mode still matters for support.
}

#if !CoreCLR
private void CheckNetFxVersion()
private void CheckDotNetVersion()
{
_logger.Log(PsesLogLevel.Diagnostic, "Checking that .NET Framework version is at least 4.6.2");
_logger.Log(PsesLogLevel.Verbose, "Checking that .NET Framework version is at least 4.8");
using RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full");
object netFxValue = key?.GetValue("Release");
if (netFxValue == null || netFxValue is not int netFxVersion)
Expand All @@ -254,9 +270,10 @@ private void CheckNetFxVersion()

_logger.Log(PsesLogLevel.Verbose, $".NET registry version: {netFxVersion}");

if (netFxVersion < Net462Version)
if (netFxVersion < Net48Version)
{
_logger.Log(PsesLogLevel.Warning, $".NET Framework version {netFxVersion} lower than .NET 4.6.2. This runtime is not supported and you may experience errors. Please update your .NET runtime version.");
_logger.Log(PsesLogLevel.Error, $".NET Framework {netFxVersion} is out-of-date, please install at least 4.8: https://dotnet.microsoft.com/en-us/download/dotnet-framework");
_sessionFileWriter.WriteSessionFailure("dotNetVersion");
}
}
#endif
Expand Down Expand Up @@ -322,30 +339,6 @@ private void LogHostInformation()
- PowerShell output encoding: {GetPSOutputEncoding()}
");

LogPowerShellDetails();

LogOperatingSystemDetails();
}

private static string GetPSOutputEncoding()
{
using SMA.PowerShell pwsh = SMA.PowerShell.Create();
return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke<string>()[0];
}

private void LogPowerShellDetails()
{
PSLanguageMode languageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode;

_logger.Log(PsesLogLevel.Verbose, $@"
== PowerShell Details ==
- PowerShell version: {GetPSVersion()}
- Language mode: {languageMode}
");
}

private void LogOperatingSystemDetails()
{
_logger.Log(PsesLogLevel.Verbose, $@"
== Environment Details ==
- OS description: {RuntimeInformation.OSDescription}
Expand All @@ -354,6 +347,12 @@ private void LogOperatingSystemDetails()
");
}

private static string GetPSOutputEncoding()
{
using SMA.PowerShell pwsh = SMA.PowerShell.Create();
return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke<string>()[0];
}

// TODO: Deduplicate this with VersionUtils.
private static string GetOSArchitecture()
{
Expand Down Expand Up @@ -401,7 +400,7 @@ private void ValidateConfiguration()
}
}

private static object GetPSVersion()
private static Version GetPSVersion()
{
// In order to read the $PSVersionTable variable,
// we are forced to create a new runspace to avoid concurrency issues,
Expand All @@ -412,7 +411,7 @@ private static object GetPSVersion()
return typeof(PSObject).Assembly
.GetType("System.Management.Automation.PSVersionInfo")
.GetMethod("get_PSVersion", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
.Invoke(null, new object[0] /* Cannot use Array.Empty, since it must work in net452 */);
.Invoke(null, new object[0] /* Cannot use Array.Empty, since it must work in net452 */) as Version;
#pragma warning restore CA1825
}
}
Expand Down