зеркало из https://github.com/microsoft/BuildXL.git
Merged PR 799117: Print the log directory as a hyperlink in the BuildXL console
Windows Terminal and other terminals support clickable hyperlinks. This change makes the link to the BuildXL log directory a clickable hyperlink to be opened by the operating system's file explorer of choice. This works on Windows and Linux. I disabled it when run through an SSH session since the link would be a path on the host and the files may not exist on the SSH client. If run in a VSCode SSH session, VSCode has parsing for paths and does the right thing anyway. This does change the behavior if you're running through a VSCode wrapped terminal locally. Previously, VSCode would attempt to show it through its file browser. Now it will use the file explorer of the OS instead. I didn't think this was a big deal and wasn't worthy of a config option.
This commit is contained in:
Родитель
267a6c0b73
Коммит
d9beb69fcb
|
@ -55,10 +55,12 @@ namespace BuildXL
|
|||
|
||||
// Message prefixes for the app-server protocol.
|
||||
private const byte ServerHelloMessage = 0xD0;
|
||||
private const byte ConsoleOutputMessage = 0xC0;
|
||||
private const byte ConsoleOutputLine = 0xC0;
|
||||
private const byte ConsoleProgressMessage = 0xC1;
|
||||
private const byte ConsoleTemporaryMessage = 0xC2;
|
||||
private const byte ConsoleTemporaryOnlyMessage = 0xC3;
|
||||
private const byte ConsoleOutput = 0xC4;
|
||||
private const byte ConsoleHyperlink = 0xC5;
|
||||
private const byte ExitCodeMessage = 0xEC;
|
||||
private const byte CancelMessage = 0xCD;
|
||||
private const byte TerminateMessage = 0xCE;
|
||||
|
@ -432,7 +434,7 @@ namespace BuildXL
|
|||
{
|
||||
WriteMessage(() =>
|
||||
{
|
||||
m_writer.Write(ConsoleOutputMessage);
|
||||
m_writer.Write(ConsoleOutputLine);
|
||||
m_writer.Write((byte)messageLevel);
|
||||
m_writer.Write(line);
|
||||
});
|
||||
|
@ -528,11 +530,32 @@ namespace BuildXL
|
|||
});
|
||||
}
|
||||
|
||||
public void WriteHyperlink(MessageLevel messageLevel, string text, string target)
|
||||
{
|
||||
WriteMessage(() =>
|
||||
{
|
||||
m_writer.Write(ConsoleHyperlink);
|
||||
m_writer.Write((byte)messageLevel);
|
||||
m_writer.Write(text);
|
||||
m_writer.Write(target);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetRecoverableErrorAction(Action<Exception> errorAction)
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
public void WriteOutput(MessageLevel messageLevel, string text)
|
||||
{
|
||||
WriteMessage(() =>
|
||||
{
|
||||
m_writer.Write(ConsoleOutput);
|
||||
m_writer.Write((byte)messageLevel);
|
||||
m_writer.Write(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -970,7 +993,7 @@ namespace BuildXL
|
|||
|
||||
switch (messageId)
|
||||
{
|
||||
case ConsoleOutputMessage:
|
||||
case ConsoleOutputLine:
|
||||
ReadAndForwardConsoleMessage(reader);
|
||||
break;
|
||||
case ConsoleProgressMessage:
|
||||
|
@ -982,6 +1005,12 @@ namespace BuildXL
|
|||
case ConsoleTemporaryOnlyMessage:
|
||||
ReadAndForwardTemporaryOnlyMessage(reader);
|
||||
break;
|
||||
case ConsoleOutput:
|
||||
ReadAndForwardConsoleOutput(reader);
|
||||
break;
|
||||
case ConsoleHyperlink:
|
||||
ReadAndForwardHyperlink(reader);
|
||||
break;
|
||||
case ExitCodeMessage:
|
||||
ExitKind exit;
|
||||
if (!EnumTraits<ExitKind>.TryConvert(reader.ReadByte(), out exit))
|
||||
|
@ -1019,27 +1048,30 @@ namespace BuildXL
|
|||
|
||||
private void ReadAndForwardConsoleMessage(BinaryReader reader)
|
||||
{
|
||||
MessageLevel messageLevel;
|
||||
if (!EnumTraits<MessageLevel>.TryConvert(reader.ReadByte(), out messageLevel))
|
||||
{
|
||||
throw new BuildXLException("Unknown console message level received from app server");
|
||||
}
|
||||
|
||||
m_console.WriteOutputLine(messageLevel, reader.ReadString());
|
||||
m_console.WriteOutputLine(ReadMessageLevel(reader), reader.ReadString());
|
||||
}
|
||||
|
||||
private void ReadAndForwardTemporaryMessage(BinaryReader reader)
|
||||
{
|
||||
MessageLevel messageLevel;
|
||||
if (!EnumTraits<MessageLevel>.TryConvert(reader.ReadByte(), out messageLevel))
|
||||
{
|
||||
throw new BuildXLException("Unknown console message level received from app server");
|
||||
}
|
||||
|
||||
m_console.WriteOverwritableOutputLine(messageLevel, reader.ReadString(), reader.ReadString());
|
||||
m_console.WriteOverwritableOutputLine(ReadMessageLevel(reader), reader.ReadString(), reader.ReadString());
|
||||
}
|
||||
|
||||
private void ReadAndForwardTemporaryOnlyMessage(BinaryReader reader)
|
||||
{
|
||||
m_console.WriteOverwritableOutputLineOnlyIfSupported(ReadMessageLevel(reader), reader.ReadString(), reader.ReadString());
|
||||
}
|
||||
|
||||
private void ReadAndForwardConsoleOutput(BinaryReader reader)
|
||||
{
|
||||
m_console.WriteOutput(ReadMessageLevel(reader), reader.ReadString());
|
||||
}
|
||||
|
||||
private void ReadAndForwardHyperlink(BinaryReader reader)
|
||||
{
|
||||
m_console.WriteHyperlink(ReadMessageLevel(reader), reader.ReadString(), reader.ReadString());
|
||||
}
|
||||
|
||||
private static MessageLevel ReadMessageLevel(BinaryReader reader)
|
||||
{
|
||||
MessageLevel messageLevel;
|
||||
if (!EnumTraits<MessageLevel>.TryConvert(reader.ReadByte(), out messageLevel))
|
||||
|
@ -1047,7 +1079,7 @@ namespace BuildXL
|
|||
throw new BuildXLException("Unknown console message level received from app server");
|
||||
}
|
||||
|
||||
m_console.WriteOverwritableOutputLineOnlyIfSupported(messageLevel, reader.ReadString(), reader.ReadString());
|
||||
return messageLevel;
|
||||
}
|
||||
|
||||
private void ReadAndForwardProgress(BinaryReader reader)
|
||||
|
|
|
@ -169,6 +169,8 @@ namespace BuildXL
|
|||
|
||||
private TimeSpan TelemetryFlushTimeout => m_configuration.Logging.RemoteTelemetryFlushTimeout ?? AriaV2StaticState.DefaultShutdownTimeout;
|
||||
|
||||
private static readonly Lazy<bool> m_isSShSession = new Lazy<bool>(() => Environment.GetEnvironmentVariable("SSH_CLIENT") != null);
|
||||
|
||||
/// <nodoc />
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
|
||||
public BuildXLApp(
|
||||
|
@ -716,16 +718,16 @@ namespace BuildXL
|
|||
try
|
||||
{
|
||||
string filePath = buildSummary.RenderMarkdown();
|
||||
WriteToConsole("##vso[task.uploadsummary]" + filePath);
|
||||
WriteLineToConsole("##vso[task.uploadsummary]" + filePath);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
WriteErrorToConsole(Strings.App_Main_FailedToWriteSummary, e.Message);
|
||||
WriteErrorLineToConsole(Strings.App_Main_FailedToWriteSummary, e.Message);
|
||||
// No need to change exit code, only behavior is lack of log in the extensions page.
|
||||
}
|
||||
catch (UnauthorizedAccessException e)
|
||||
{
|
||||
WriteErrorToConsole(Strings.App_Main_FailedToWriteSummary, e.Message);
|
||||
WriteErrorLineToConsole(Strings.App_Main_FailedToWriteSummary, e.Message);
|
||||
// No need to change exit code, only behavior is lack of log in the extensions page.
|
||||
}
|
||||
}
|
||||
|
@ -735,7 +737,7 @@ namespace BuildXL
|
|||
|
||||
if (appLoggers.TrackingEventListener.HasFailures)
|
||||
{
|
||||
WriteErrorToConsoleWithDefaultColor(Strings.App_Main_BuildFailed);
|
||||
WriteErrorLineToConsoleWithDefaultColor(Strings.App_Main_BuildFailed);
|
||||
|
||||
LogGeneratedFiles(pm.LoggingContext, appLoggers.TrackingEventListener, translator: appLoggers.PathTranslatorForLogging);
|
||||
|
||||
|
@ -749,7 +751,7 @@ namespace BuildXL
|
|||
internalWarnings: internalWarnings);
|
||||
}
|
||||
|
||||
WriteToConsole(Strings.App_Main_BuildSucceeded);
|
||||
WriteLineToConsole(Strings.App_Main_BuildSucceeded);
|
||||
|
||||
LogGeneratedFiles(pm.LoggingContext, appLoggers.TrackingEventListener, translator: appLoggers.PathTranslatorForLogging);
|
||||
|
||||
|
@ -758,17 +760,13 @@ namespace BuildXL
|
|||
var translator = appLoggers.PathTranslatorForLogging;
|
||||
var configFile = m_initialConfiguration.Startup.ConfigFile;
|
||||
IdeGenerator.WriteCmd(GetExpandedCmdLine(m_commandLineArguments), m_configuration.Ide, configFile, m_pathTable, translator);
|
||||
var solutionFile = IdeGenerator.GetSolutionPath(m_configuration.Ide, m_pathTable).ToString(m_pathTable);
|
||||
if (translator != null)
|
||||
{
|
||||
solutionFile = translator.Translate(solutionFile);
|
||||
}
|
||||
|
||||
WriteToConsole(Strings.App_Vs_SolutionFile, solutionFile);
|
||||
WriteToConsole(Strings.App_Vs_SolutionFile);
|
||||
WritePathLineAsLinkToConsole(IdeGenerator.GetSolutionPath(m_configuration.Ide, m_pathTable).ToString(m_pathTable), translator);
|
||||
var vsVersions = IdeGenerator.GetVersionsNotHavingLatestPlugin();
|
||||
if (vsVersions != null)
|
||||
{
|
||||
WriteWarningToConsole(Strings.App_Vs_InstallPlugin, vsVersions, IdeGenerator.LatestPluginVersion);
|
||||
WriteWarningLineToConsole(Strings.App_Vs_InstallPlugin, vsVersions, IdeGenerator.LatestPluginVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -909,7 +907,8 @@ namespace BuildXL
|
|||
if (m_configuration.Logging.LogsDirectory.IsValid)
|
||||
{
|
||||
// When using the new style logging configuration, just show the path to the logs directory
|
||||
WriteToConsole(Strings.App_LogsDirectory, m_configuration.Logging.LogsDirectory.ToString(m_pathTable));
|
||||
WriteToConsole(Strings.App_LogsDirectory);
|
||||
WritePathLineAsLinkToConsole(m_configuration.Logging.LogsDirectory.ToString(m_pathTable), translator);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -931,11 +930,11 @@ namespace BuildXL
|
|||
{
|
||||
if (trackingListener.HasFailures)
|
||||
{
|
||||
WriteToConsole(message, path);
|
||||
WriteLineToConsole(message, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteErrorToConsoleWithDefaultColor(message, path);
|
||||
WriteErrorLineToConsoleWithDefaultColor(message, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1333,7 +1332,7 @@ namespace BuildXL
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteErrorToConsole(
|
||||
WriteErrorLineToConsole(
|
||||
Strings.App_RootMapping_CantCreateDirectory,
|
||||
mapping.Value,
|
||||
mapping.Key,
|
||||
|
@ -1347,7 +1346,7 @@ namespace BuildXL
|
|||
!ProcessNativeMethods.ApplyDriveMappings(
|
||||
rootMappings.Select(kvp => new PathMapping(kvp.Key[0], kvp.Value.ToString(m_pathTable))).ToArray()))
|
||||
{
|
||||
WriteErrorToConsole(Strings.App_RootMapping_CantApplyRootMappings);
|
||||
WriteErrorLineToConsole(Strings.App_RootMapping_CantApplyRootMappings);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -2297,8 +2296,9 @@ namespace BuildXL
|
|||
break;
|
||||
default:
|
||||
Logger.Log.CatastrophicFailure(pm.LoggingContext, failureMessage, s_buildInfo?.CommitId ?? string.Empty, s_buildInfo?.Build ?? string.Empty);
|
||||
WriteToConsole(Strings.App_LogsDirectory, loggers.RootLogDirectory);
|
||||
WriteToConsole("Collecting some information about this crash...");
|
||||
WriteToConsole(Strings.App_LogsDirectory);
|
||||
WritePathLineAsLinkToConsole(loggers.RootLogDirectory, GetPathTranslator(m_configuration.Logging, m_pathTable));
|
||||
WriteLineToConsole("Collecting some information about this crash...");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2380,8 +2380,8 @@ namespace BuildXL
|
|||
catch (Exception ex)
|
||||
{
|
||||
// Oh my, this isn't going very well.
|
||||
WriteErrorToConsole("Unhandled exception in exception handler");
|
||||
WriteErrorToConsole(ex.DemystifyToString());
|
||||
WriteErrorLineToConsole("Unhandled exception in exception handler");
|
||||
WriteErrorLineToConsole(ex.DemystifyToString());
|
||||
}
|
||||
#pragma warning restore ERP022 // Unobserved exception in generic exception handler
|
||||
finally
|
||||
|
@ -2848,22 +2848,44 @@ namespace BuildXL
|
|||
return new StandardConsole(loggingConfiguration.Color, loggingConfiguration.AnimateTaskbar, loggingConfiguration.FancyConsole, translator);
|
||||
}
|
||||
|
||||
private void WriteToConsole(string format, params object[] args)
|
||||
private void WriteToConsole(string text)
|
||||
{
|
||||
m_console.WriteOutput(MessageLevel.Info, text);
|
||||
}
|
||||
|
||||
private void WritePathLineAsLinkToConsole(string path, PathTranslator translator)
|
||||
{
|
||||
path = translator?.Translate(path) ?? path;
|
||||
|
||||
// Don't make the path a hyperlink if running in an SSH session since that link won't
|
||||
// be opened reasonably on the client of that SSH session.
|
||||
if (m_configuration.Logging.FancyConsole && !m_isSShSession.Value)
|
||||
{
|
||||
m_console.WriteHyperlink(MessageLevel.Info, path, @"file://" + path.TrimStart('/'));
|
||||
WriteLineToConsole(string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLineToConsole(path);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteLineToConsole(string format, params object[] args)
|
||||
{
|
||||
m_console.WriteOutputLine(MessageLevel.Info, string.Format(CultureInfo.InvariantCulture, format, args));
|
||||
}
|
||||
|
||||
private void WriteWarningToConsole(string format, params object[] args)
|
||||
private void WriteWarningLineToConsole(string format, params object[] args)
|
||||
{
|
||||
m_console.WriteOutputLine(MessageLevel.Warning, string.Format(CultureInfo.InvariantCulture, format, args));
|
||||
}
|
||||
|
||||
private void WriteErrorToConsole(string format, params object[] args)
|
||||
private void WriteErrorLineToConsole(string format, params object[] args)
|
||||
{
|
||||
m_console.WriteOutputLine(MessageLevel.Error, string.Format(CultureInfo.InvariantCulture, format, args));
|
||||
}
|
||||
|
||||
private void WriteErrorToConsoleWithDefaultColor(string format, params object[] args)
|
||||
private void WriteErrorLineToConsoleWithDefaultColor(string format, params object[] args)
|
||||
{
|
||||
m_console.WriteOutputLine(MessageLevel.ErrorNoColor, string.Format(CultureInfo.InvariantCulture, format, args));
|
||||
}
|
||||
|
|
|
@ -686,10 +686,10 @@ Example: ad2d42d2ec5d2ca0c0b7ad65402d07c7ef40b91e</value>
|
|||
<value>Standard help shown. For complete help options, use /help:verbose</value>
|
||||
</data>
|
||||
<data name="App_Vs_SolutionFile" xml:space="preserve">
|
||||
<value> VS Solution File: {0}</value>
|
||||
<value> VS Solution File: </value>
|
||||
</data>
|
||||
<data name="App_LogsDirectory" xml:space="preserve">
|
||||
<value> Log Directory: {0}</value>
|
||||
<value> Log Directory: </value>
|
||||
</data>
|
||||
<data name="HelpText_DisplayHelp_SubstSource" xml:space="preserve">
|
||||
<value>Path of the original root that has been substituted to another. Log messages will be converted back to this path root. Must be specified with /substTarget.</value>
|
||||
|
|
|
@ -138,5 +138,17 @@ namespace Test.BuildXL
|
|||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteHyperlink(MessageLevel messageLevel, string text, string target)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteOutput(MessageLevel messageLevel, string text)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,16 @@ namespace BuildXL.Utilities.Tracing
|
|||
/// </remarks>
|
||||
bool UpdatingConsole { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Writes a hyperlink with the given message level.
|
||||
/// </summary>
|
||||
void WriteHyperlink(MessageLevel messageLevel, string text, string target);
|
||||
|
||||
/// <summary>
|
||||
/// Writes output with the given message level.
|
||||
/// </summary>
|
||||
void WriteOutput(MessageLevel messageLevel, string text);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line with the given message level.
|
||||
/// </summary>
|
||||
|
|
|
@ -121,6 +121,19 @@ namespace BuildXL.Utilities.Tracing
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteHyperlink(MessageLevel messageLevel, string text, string target)
|
||||
{
|
||||
const string Esc = "\x1b";
|
||||
WriteOutput(messageLevel, $@"{Esc}]8;;{target}{Esc}\{text}{Esc}]8;;{Esc}\");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteOutput(MessageLevel messageLevel, string text)
|
||||
{
|
||||
Console.Write(text);
|
||||
}
|
||||
|
||||
public static int GetConsoleWidth()
|
||||
{
|
||||
// Not all consoles have a width defined, therefore return 150 which is a reasonable default on Developer boxes.
|
||||
|
|
Загрузка…
Ссылка в новой задаче