Live logger properly report errors during restore. (#8707)

Fixes #8704

Context
Errors during restore was not reported when using /tl (live logger) leaving user without actionable information.

Changes Made
When restore success without error or warn - same as before
When restore success with warnings - report success with warnins and list warnings bellow that report
When restore fails - report errors and warnings THEN report Restore failed in 1.5s and do not report Build summary
This commit is contained in:
Roman Konecny 2023-05-04 20:14:02 +02:00 коммит произвёл GitHub
Родитель 6882ab9de1
Коммит b313662f92
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 248 добавлений и 102 удалений

Просмотреть файл

@ -4,6 +4,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging.LiveLogger;
using Shouldly;
@ -182,7 +184,7 @@ namespace Microsoft.Build.UnitTests
public void PrintsBuildSummary_Succeeded()
{
InvokeLoggerCallbacksForSimpleProject(succeeded: true, () => { });
_mockTerminal.GetLastLine().ShouldBe("Build succeeded in 5.0s");
_mockTerminal.GetLastLine().WithoutAnsiCodes().ShouldBe("Build succeeded in 5.0s");
}
[Fact]
@ -192,14 +194,14 @@ namespace Microsoft.Build.UnitTests
{
WarningRaised?.Invoke(_eventSender, MakeWarningEventArgs("Warning!"));
});
_mockTerminal.GetLastLine().ShouldBe("Build succeeded with warnings in 5.0s");
_mockTerminal.GetLastLine().WithoutAnsiCodes().ShouldBe("Build succeeded with warnings in 5.0s");
}
[Fact]
public void PrintBuildSummary_Failed()
{
InvokeLoggerCallbacksForSimpleProject(succeeded: false, () => { });
_mockTerminal.GetLastLine().ShouldBe("Build failed in 5.0s");
_mockTerminal.GetLastLine().WithoutAnsiCodes().ShouldBe("Build failed in 5.0s");
}
[Fact]
@ -209,9 +211,20 @@ namespace Microsoft.Build.UnitTests
{
ErrorRaised?.Invoke(_eventSender, MakeErrorEventArgs("Error!"));
});
_mockTerminal.GetLastLine().ShouldBe("Build failed with errors in 5.0s");
_mockTerminal.GetLastLine().WithoutAnsiCodes().ShouldBe("Build failed with errors in 5.0s");
}
#endregion
}
internal static class StringVT100Extensions
{
private static Regex s_removeAnsiCodes = new Regex("\\x1b\\[[0-9;]*[mGKHF]");
public static string WithoutAnsiCodes(this string text)
{
return s_removeAnsiCodes.Replace(text, string.Empty);
}
}
}

Просмотреть файл

@ -99,7 +99,6 @@ namespace Microsoft.Build.UnitTests
public void Write(ReadOnlySpan<char> text) { AddOutput(text.ToString()); }
public void WriteColor(TerminalColor color, string text) => AddOutput(text);
public void WriteColorLine(TerminalColor color, string text) { AddOutput(text); AddOutput("\n"); }
public string RenderColor(TerminalColor color, string text) => text;
public void WriteLine(string text) { AddOutput(text); AddOutput("\n"); }
public void WriteLineFitToWidth(ReadOnlySpan<char> text)

Просмотреть файл

@ -63,9 +63,4 @@ internal interface ITerminal : IDisposable
/// Writes a string to the output using the given color. Or buffers it if <see cref="BeginUpdate"/> was called.
/// </summary>
void WriteColorLine(TerminalColor color, string text);
/// <summary>
/// Return string representing text wrapped in VT100 color codes.
/// </summary>
string RenderColor(TerminalColor color, string text);
}

Просмотреть файл

@ -109,6 +109,11 @@ internal sealed class LiveLogger : INodeLogger
/// </summary>
private bool _buildHasWarnings;
/// <summary>
/// True if restore failed and this failure has already been reported.
/// </summary>
private bool _restoreFailed;
/// <summary>
/// The project build context corresponding to the <c>Restore</c> initial target, or null if the build is currently
/// bot restoring.
@ -235,12 +240,22 @@ internal sealed class LiveLogger : INodeLogger
Terminal.BeginUpdate();
try
{
double duration = (e.Timestamp - _buildStartTime).TotalSeconds;
string duration = (e.Timestamp - _buildStartTime).TotalSeconds.ToString("F1");
string buildResult = RenderBuildResult(e.Succeeded, _buildHasErrors, _buildHasWarnings);
Terminal.WriteLine("");
Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("BuildFinished",
RenderBuildResult(e.Succeeded, _buildHasErrors, _buildHasWarnings),
duration.ToString("F1")));
if (_restoreFailed)
{
Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreCompleteWithMessage",
buildResult,
duration));
}
else
{
Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("BuildFinished",
buildResult,
duration));
}
}
finally
{
@ -249,6 +264,7 @@ internal sealed class LiveLogger : INodeLogger
_buildHasErrors = false;
_buildHasWarnings = false;
_restoreFailed = false;
}
/// <summary>
@ -302,35 +318,7 @@ internal sealed class LiveLogger : INodeLogger
ProjectContext c = new(buildEventContext);
// First check if we're done restoring.
if (_restoreContext is ProjectContext restoreContext && c == restoreContext)
{
lock (_lock)
{
_restoreContext = null;
Stopwatch projectStopwatch = _projects[restoreContext].Stopwatch;
double duration = projectStopwatch.Elapsed.TotalSeconds;
projectStopwatch.Stop();
Terminal.BeginUpdate();
try
{
EraseNodes();
Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreComplete",
duration.ToString("F1")));
DisplayNodes();
}
finally
{
Terminal.EndUpdate();
}
return;
}
}
// If this was a notable project build, we print it as completed only if it's produced an output or warnings/error.
if (_projects.TryGetValue(c, out Project? project) && (project.OutputPath is not null || project.BuildMessages is not null))
if (_projects.TryGetValue(c, out Project? project))
{
lock (_lock)
{
@ -350,65 +338,97 @@ internal sealed class LiveLogger : INodeLogger
// reported during build.
bool haveErrors = project.BuildMessages?.Exists(m => m.Severity == MessageSeverity.Error) == true;
bool haveWarnings = project.BuildMessages?.Exists(m => m.Severity == MessageSeverity.Warning) == true;
string buildResult = RenderBuildResult(e.Succeeded, haveErrors, haveWarnings);
if (string.IsNullOrEmpty(project.TargetFramework))
// Check if we're done restoring.
if (c == _restoreContext)
{
Terminal.Write(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectFinished_NoTF",
Indentation,
projectFile,
buildResult,
duration));
}
else
{
Terminal.Write(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectFinished_WithTF",
Indentation,
projectFile,
AnsiCodes.Colorize(project.TargetFramework, TargetFrameworkColor),
buildResult,
duration));
}
// Print the output path as a link if we have it.
if (outputPath is not null)
{
ReadOnlySpan<char> outputPathSpan = outputPath.Value.Span;
ReadOnlySpan<char> url = outputPathSpan;
try
if (e.Succeeded)
{
// If possible, make the link point to the containing directory of the output.
url = Path.GetDirectoryName(url);
}
catch
{
// Ignore any GetDirectoryName exceptions.
}
// Generates file:// schema url string which is better handled by various Terminal clients than raw folder name.
string urlString = url.ToString();
if (Uri.TryCreate(urlString, UriKind.Absolute, out Uri? uri))
{
urlString = uri.AbsoluteUri;
}
// If the output path is under the initial working directory, make the console output relative to that to save space.
if (outputPathSpan.StartsWith(_initialWorkingDirectory.AsSpan(), FileUtilities.PathComparison))
{
if (outputPathSpan.Length > _initialWorkingDirectory.Length
&& (outputPathSpan[_initialWorkingDirectory.Length] == Path.DirectorySeparatorChar
|| outputPathSpan[_initialWorkingDirectory.Length] == Path.AltDirectorySeparatorChar))
if (haveErrors || haveWarnings)
{
outputPathSpan = outputPathSpan.Slice(_initialWorkingDirectory.Length + 1);
Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreCompleteWithMessage",
buildResult,
duration));
}
else
{
Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreComplete",
duration));
}
}
else
{
// It will be reported after build finishes.
_restoreFailed = true;
}
Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectFinished_OutputPath",
$"{AnsiCodes.LinkPrefix}{urlString}{AnsiCodes.LinkInfix}{outputPathSpan.ToString()}{AnsiCodes.LinkSuffix}"));
_restoreContext = null;
}
else
// If this was a notable project build, we print it as completed only if it's produced an output or warnings/error.
else if (project.OutputPath is not null || project.BuildMessages is not null)
{
Terminal.WriteLine(string.Empty);
// Show project build complete and its output
if (string.IsNullOrEmpty(project.TargetFramework))
{
Terminal.Write(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectFinished_NoTF",
Indentation,
projectFile,
buildResult,
duration));
}
else
{
Terminal.Write(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectFinished_WithTF",
Indentation,
projectFile,
AnsiCodes.Colorize(project.TargetFramework, TargetFrameworkColor),
buildResult,
duration));
}
// Print the output path as a link if we have it.
if (outputPath is not null)
{
ReadOnlySpan<char> outputPathSpan = outputPath.Value.Span;
ReadOnlySpan<char> url = outputPathSpan;
try
{
// If possible, make the link point to the containing directory of the output.
url = Path.GetDirectoryName(url);
}
catch
{
// Ignore any GetDirectoryName exceptions.
}
// Generates file:// schema url string which is better handled by various Terminal clients than raw folder name.
string urlString = url.ToString();
if (Uri.TryCreate(urlString, UriKind.Absolute, out Uri? uri))
{
urlString = uri.AbsoluteUri;
}
// If the output path is under the initial working directory, make the console output relative to that to save space.
if (outputPathSpan.StartsWith(_initialWorkingDirectory.AsSpan(), FileUtilities.PathComparison))
{
if (outputPathSpan.Length > _initialWorkingDirectory.Length
&& (outputPathSpan[_initialWorkingDirectory.Length] == Path.DirectorySeparatorChar
|| outputPathSpan[_initialWorkingDirectory.Length] == Path.AltDirectorySeparatorChar))
{
outputPathSpan = outputPathSpan.Slice(_initialWorkingDirectory.Length + 1);
}
}
Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectFinished_OutputPath",
$"{AnsiCodes.LinkPrefix}{urlString}{AnsiCodes.LinkInfix}{outputPathSpan.ToString()}{AnsiCodes.LinkSuffix}"));
}
else
{
Terminal.WriteLine(string.Empty);
}
}
// Print diagnostic output under the Project -> Output line.
@ -762,15 +782,15 @@ internal sealed class LiveLogger : INodeLogger
(false, true) => ResourceUtilities.GetResourceString("BuildResult_FailedWithWarnings"),
_ => ResourceUtilities.GetResourceString("BuildResult_Failed"),
};
return Terminal.RenderColor(TerminalColor.Red, text);
return AnsiCodes.Colorize(text, TerminalColor.Red);
}
else if (hasWarning)
{
return Terminal.RenderColor(TerminalColor.Yellow, ResourceUtilities.GetResourceString("BuildResult_SucceededWithWarnings"));
return AnsiCodes.Colorize(ResourceUtilities.GetResourceString("BuildResult_SucceededWithWarnings"), TerminalColor.Yellow);
}
else
{
return Terminal.RenderColor(TerminalColor.Green, ResourceUtilities.GetResourceString("BuildResult_Succeeded"));
return AnsiCodes.Colorize(ResourceUtilities.GetResourceString("BuildResult_Succeeded"), TerminalColor.Green);
}
}

Просмотреть файл

@ -132,16 +132,10 @@ internal sealed class Terminal : ITerminal
}
else
{
Write(RenderColor(color, text));
Write(AnsiCodes.Colorize(text, color));
}
}
/// <inheritdoc/>
public string RenderColor(TerminalColor color, string text)
{
return $"{AnsiCodes.CSI}{(int)color}{AnsiCodes.SetColor}{text}{AnsiCodes.SetDefaultColor}";
}
/// <inheritdoc/>
public void WriteColorLine(TerminalColor color, string text)
{

Просмотреть файл

@ -1373,6 +1373,14 @@
{0}: duration in seconds with 1 decimal point
</comment>
</data>
<data name="RestoreCompleteWithMessage" xml:space="preserve">
<value>Restore {0} in {1}s</value>
<comment>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</comment>
</data>
<data name="BuildFinished" xml:space="preserve">
<value>Build {0} in {1}s</value>
<comment>

9
src/MSBuild/Resources/xlf/Strings.cs.xlf сгенерированный
Просмотреть файл

@ -1484,6 +1484,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.de.xlf сгенерированный
Просмотреть файл

@ -1476,6 +1476,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.es.xlf сгенерированный
Просмотреть файл

@ -1483,6 +1483,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.fr.xlf сгенерированный
Просмотреть файл

@ -1476,6 +1476,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.it.xlf сгенерированный
Просмотреть файл

@ -1487,6 +1487,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.ja.xlf сгенерированный
Просмотреть файл

@ -1476,6 +1476,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.ko.xlf сгенерированный
Просмотреть файл

@ -1476,6 +1476,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.pl.xlf сгенерированный
Просмотреть файл

@ -1485,6 +1485,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.pt-BR.xlf сгенерированный
Просмотреть файл

@ -1477,6 +1477,15 @@ arquivo de resposta.
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.ru.xlf сгенерированный
Просмотреть файл

@ -1475,6 +1475,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.tr.xlf сгенерированный
Просмотреть файл

@ -1480,6 +1480,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf сгенерированный
Просмотреть файл

@ -1476,6 +1476,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">

9
src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf сгенерированный
Просмотреть файл

@ -1476,6 +1476,15 @@
<target state="new">Restore complete ({0}s)</target>
<note>
{0}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="RestoreCompleteWithMessage">
<source>Restore {0} in {1}s</source>
<target state="new">Restore {0} in {1}s</target>
<note>
Restore summary when finished with warning or error
{0}: BuildResult_X (below)
{1}: duration in seconds with 1 decimal point
</note>
</trans-unit>
<trans-unit id="SchemaFileLocation">