Skip to content
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
1 change: 1 addition & 0 deletions documentation/wiki/ChangeWaves.md
Comment thread
AlesProkop marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Change wave checks around features will be removed in the release that accompani

### 18.8
- [RAR task: across multiple input properties, resolve relative paths against the project directory (not the process current directory)](https://github.com/dotnet/msbuild/pull/13319)
- [Console, parallel console, and terminal loggers print the paths of log files written by registered loggers (e.g. file logger and binary logger) as part of the end-of-build summary.](https://github.com/dotnet/msbuild/pull/13577)

### 18.7
- [Copy task retries on ERROR_ACCESS_DENIED on non-Windows platforms to handle transient lock conflicts (e.g. macOS CoW filesystems)](https://github.com/dotnet/msbuild/issues/13463)
Expand Down
53 changes: 53 additions & 0 deletions src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Logging;
using Shouldly;
using Xunit;
using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
Expand Down Expand Up @@ -1046,6 +1047,52 @@ public void LogBuildFinished()
buildEvent = new BuildFinishedEventArgs(string.Empty, null /* no help keyword */, true, service.ProcessedBuildEvent.Timestamp);
Assert.True(((BuildFinishedEventArgs)service.ProcessedBuildEvent).IsEquivalent(buildEvent));
}
[Fact]
public void LogBuildStartedLogsLoggerNames()
{
ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1);
ConsoleLogger consoleLogger = new ConsoleLogger();
service.RegisterLogger(consoleLogger);

service.LogBuildStarted();
var enabledLogsEvent = service.AllProcessedBuildEvents
.OfType<BuildMessageEventArgs>()
.FirstOrDefault(e => e.Message?.Contains("ConsoleLogger") == true);
enabledLogsEvent.ShouldNotBeNull();
}
Comment thread
AlesProkop marked this conversation as resolved.

[Fact]
public void LogFilePathsPresentInFileLog()
{
using var env = TestEnvironment.Create();
var logFilePath = env.ExpectFile(".log").Path;

var fileLogger = new FileLogger { Parameters = "logfile=" + logFilePath };
var mockLogger = new MockLogger();

using (var collection = new ProjectCollection())
{
var project = ObjectModelHelpers.CreateInMemoryProject(collection, @"
<Project>
<Target Name=""Build"" />
</Project>");
project.Build(new ILogger[] { fileLogger, mockLogger }).ShouldBeTrue();
}

// Check that MockLogger captured a LoggersRegisteredEventArgs containing the file logger path
var registeredEvent = mockLogger.AllBuildEvents
.OfType<LoggersRegisteredEventArgs>()
.FirstOrDefault(e => e.Loggers.Any(l => l.LoggerName == nameof(FileLogger)));
registeredEvent.ShouldNotBeNull();
var fileLoggerDesc = registeredEvent.Loggers.First(l => l.LoggerName == nameof(FileLogger));
var expectedPath = Path.GetFullPath(logFilePath);
fileLoggerDesc.OutputFilePaths.ShouldContain(expectedPath);
fileLoggerDesc.Parameters.ShouldBe(fileLogger.Parameters);

// Check the file log itself contains the exact path
var fileLogContents = File.ReadAllText(logFilePath);
fileLogContents.ShouldContain(expectedPath);
}

[Fact]
public void LogBuildCanceled()
Expand Down Expand Up @@ -1795,6 +1842,11 @@ internal sealed class ProcessBuildEventHelper : LoggingService
/// to verify that a buildEvent was sent to ProcessLoggingEvent.
/// </summary>
private BuildEventArgs _processedBuildEvent;

/// <summary>
/// All events processed by ProcessLoggingEvent.
/// </summary>
internal List<BuildEventArgs> AllProcessedBuildEvents { get; } = new();
#endregion
#region Constructor
/// <summary>
Expand Down Expand Up @@ -1857,6 +1909,7 @@ protected internal override void ProcessLoggingEvent(object buildEvent)
if (buildEvent is BuildEventArgs buildEventArgs)
{
_processedBuildEvent = buildEventArgs;
AllProcessedBuildEvents.Add(buildEventArgs);
}
else if (buildEvent is KeyValuePair<int, BuildEventArgs> kvp)
{
Expand Down
17 changes: 17 additions & 0 deletions src/Build.UnitTests/BackEnd/NodePackets_Tests.cs
Comment thread
AlesProkop marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public void VerifyEventType()
BuildCheckTracingEventArgs buildCheckTracing = new();
BuildCanceledEventArgs buildCanceled = new("message", DateTime.UtcNow);
WorkerNodeTelemetryEventArgs workerNodeTelemetry = new();
LoggersRegisteredEventArgs loggersRegistered = new(new List<RegisteredLoggerInfo> { new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }) });

VerifyLoggingPacket(buildFinished, LoggingEventType.BuildFinishedEvent);
VerifyLoggingPacket(buildStarted, LoggingEventType.BuildStartedEvent);
Expand Down Expand Up @@ -119,6 +120,7 @@ public void VerifyEventType()
VerifyLoggingPacket(buildCheckTracing, LoggingEventType.BuildCheckTracingEvent);
VerifyLoggingPacket(buildCanceled, LoggingEventType.BuildCanceledEvent);
VerifyLoggingPacket(workerNodeTelemetry, LoggingEventType.WorkerNodeTelemetryEvent);
VerifyLoggingPacket(loggersRegistered, LoggingEventType.LoggersRegisteredEvent);
}

private static BuildEventContext CreateBuildEventContext()
Expand Down Expand Up @@ -321,6 +323,21 @@ public void TestTranslation()
BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7)
},
new GeneratedFileUsedEventArgs("path", "some content"),
new LoggersRegisteredEventArgs(new List<RegisteredLoggerInfo>
{
new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }),
new RegisteredLoggerInfo("BinaryLogger"),
Comment thread
AlesProkop marked this conversation as resolved.
new RegisteredLoggerInfo(
"ConsoleLogger",
outputFilePaths: null,
verbosity: LoggerVerbosity.Detailed,
parameters: "ShowTimestamp;ShowEventId"),
new RegisteredLoggerInfo(
"MultiFileLogger",
outputFilePaths: new[] { @"C:\logs\a.log", @"C:\logs\b.log" },
verbosity: LoggerVerbosity.Diagnostic,
parameters: "LogFile=a.log;LogFile=b.log"),
}),
};
foreach (BuildEventArgs arg in testArgs)
{
Expand Down
62 changes: 35 additions & 27 deletions src/Build.UnitTests/BackEnd/TaskHostCallback_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,13 @@ public void GlobalProperties_ForwardedToAutoEjectedTaskInMultiThreadedMode()
public void GlobalProperties_UseBuildLevelWhenChangeWaveDisabled()
{
using TestEnvironment env = TestEnvironment.Create(_output);
env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave18_6.ToString());
string testDir = env.CreateFolder().Path;
try
{
ChangeWaves.ResetStateForTests();
env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave18_6.ToString());
string testDir = env.CreateFolder().Path;

string projectContents = $@"
string projectContents = $@"
<Project>
<UsingTask TaskName=""{nameof(GetGlobalPropertiesTask)}"" AssemblyFile=""{typeof(GetGlobalPropertiesTask).Assembly.Location}"" />
<Target Name=""Test"">
Expand All @@ -290,32 +293,37 @@ public void GlobalProperties_UseBuildLevelWhenChangeWaveDisabled()
</Target>
</Project>";

string projectFile = Path.Combine(testDir, "Test.proj");
File.WriteAllText(projectFile, projectContents);

// These request-level properties should NOT be forwarded when the wave is disabled
var requestGlobalProperties = new Dictionary<string, string?>
{
["TestRequestProperty"] = "RequestValue",
};
string projectFile = Path.Combine(testDir, "Test.proj");
File.WriteAllText(projectFile, projectContents);

var logger = new MockLogger(_output);
BuildResult buildResult = BuildManager.DefaultBuildManager.Build(
new BuildParameters
// These request-level properties should NOT be forwarded when the wave is disabled
var requestGlobalProperties = new Dictionary<string, string?>
{
MultiThreaded = true,
MaxNodeCount = 4,
Loggers = [logger],
EnableNodeReuse = false,
},
new BuildRequestData(projectFile, requestGlobalProperties, null, ["Test"], null));

buildResult.OverallResult.ShouldBe(BuildResultCode.Success);

// With wave disabled, build-level properties are used (empty in this test),
// so request-level properties should NOT appear
logger.FullLog.ShouldNotContain("GlobalProperty: TestRequestProperty=RequestValue");
logger.FullLog.ShouldContain("GlobalPropertyCount = 0");
["TestRequestProperty"] = "RequestValue",
};

var logger = new MockLogger(_output);
BuildResult buildResult = BuildManager.DefaultBuildManager.Build(
new BuildParameters
{
MultiThreaded = true,
MaxNodeCount = 4,
Loggers = [logger],
EnableNodeReuse = false,
},
new BuildRequestData(projectFile, requestGlobalProperties, null, ["Test"], null));

buildResult.OverallResult.ShouldBe(BuildResultCode.Success);

// With wave disabled, build-level properties are used (empty in this test),
// so request-level properties should NOT appear
logger.FullLog.ShouldNotContain("GlobalProperty: TestRequestProperty=RequestValue");
logger.FullLog.ShouldContain("GlobalPropertyCount = 0");
}
finally
{
ChangeWaves.ResetStateForTests();
}
}

/// <summary>
Expand Down
27 changes: 27 additions & 0 deletions src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,33 @@ public void RoundtripBuildCanceledEventArgs()
e => e.Timestamp.ToString());
}

[Fact]
public void RoundtripLoggersRegisteredEventArgs()
{
var args = new LoggersRegisteredEventArgs(new List<RegisteredLoggerInfo>
{
new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }),
new RegisteredLoggerInfo("BinaryLogger"),
new RegisteredLoggerInfo(
"ConsoleLogger",
outputFilePaths: null,
verbosity: LoggerVerbosity.Detailed,
parameters: "ShowTimestamp;ShowEventId"),
new RegisteredLoggerInfo(
"MultiFileLogger",
outputFilePaths: new[] { @"C:\logs\a.log", @"C:\logs\b.log" },
verbosity: LoggerVerbosity.Diagnostic,
parameters: "LogFile=a.log;LogFile=b.log"),
});

Roundtrip(args,
e => e.Loggers.Count.ToString(CultureInfo.InvariantCulture),
e => string.Join("|", e.Loggers.Select(l => l.LoggerName)),
e => string.Join("|", e.Loggers.Select(l => l.Parameters ?? "<null>")),
e => string.Join("|", e.Loggers.Select(l => l.Verbosity?.ToString() ?? "<null>")),
e => string.Join("|", e.Loggers.Select(l => string.Join(",", l.OutputFilePaths))));
}

[Fact]
public void RoundtripBuildSubmissionStartedEventArgs()
{
Expand Down
78 changes: 78 additions & 0 deletions src/Build.UnitTests/ConsoleLogger_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1961,6 +1961,84 @@ public void TestPrintTargetNamePerMessage()
actualLog.ShouldContain("t:");
}

/// <summary>
/// <summary>
/// When the loggers print their output paths feature wave is enabled (default),
/// the ParallelConsoleLogger end-of-build summary should list each registered logger
/// that wrote to a file along with its output path(s).
/// </summary>
[Fact]
public void LogFileOutputPaths_PrintedInSummary_WhenWaveEnabled()
{
try
{
ChangeWaves.ResetStateForTests();

SimulatedConsole sc = new SimulatedConsole();
ParallelConsoleLogger cl = new ParallelConsoleLogger(LoggerVerbosity.Normal, sc.Write, null, null);
EventSourceSink es = new EventSourceSink();
cl.Initialize(es);

BuildStartedEventArgs bsea = new BuildStartedEventArgs("bs", null);
cl.BuildStartedHandler(null, bsea);

LoggersRegisteredEventArgs lrea = new LoggersRegisteredEventArgs(new List<RegisteredLoggerInfo>
{
new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }),
});
cl.StatusEventHandler(null, lrea);

BuildFinishedEventArgs bfea = new BuildFinishedEventArgs("bf", null, true);
cl.BuildFinishedHandler(null, bfea);

sc.ToString().ShouldContain("FileLogger");
sc.ToString().ShouldContain(@"C:\logs\build.log");
}
finally
{
ChangeWaves.ResetStateForTests();
}
}

/// <summary>
/// When the loggers print their output paths feature wave is disabled
/// via MSBUILDDISABLEFEATURESFROMVERSION, the ParallelConsoleLogger summary
/// must preserve the legacy behavior and NOT print logger output paths.
/// </summary>
[Fact]
public void LogFileOutputPaths_NotPrintedInSummary_WhenWaveDisabled()
{
using TestEnvironment env = TestEnvironment.Create();
try
{
ChangeWaves.ResetStateForTests();
env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave18_8.ToString());

SimulatedConsole sc = new SimulatedConsole();
ParallelConsoleLogger cl = new ParallelConsoleLogger(LoggerVerbosity.Normal, sc.Write, null, null);
EventSourceSink es = new EventSourceSink();
cl.Initialize(es);

BuildStartedEventArgs bsea = new BuildStartedEventArgs("bs", null);
cl.BuildStartedHandler(null, bsea);

LoggersRegisteredEventArgs lrea = new LoggersRegisteredEventArgs(new List<RegisteredLoggerInfo>
{
new RegisteredLoggerInfo("FileLogger", new[] { @"C:\logs\build.log" }),
});
cl.StatusEventHandler(null, lrea);

BuildFinishedEventArgs bfea = new BuildFinishedEventArgs("bf", null, true);
cl.BuildFinishedHandler(null, bfea);

sc.ToString().ShouldNotContain(@"C:\logs\build.log");
}
finally
{
ChangeWaves.ResetStateForTests();
}
}

/// <summary>
/// Check to see what kind of device we are outputting the log to, is it a character device, a file, or something else
/// this can be used by loggers to modify their outputs based on the device they are writing to
Expand Down
6 changes: 3 additions & 3 deletions src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void InvalidCacheFilesShouldLogError(byte[] cacheContents)
result.OverallResult.ShouldBe(BuildResultCode.Failure);

_logger.FullLog.ShouldContain("MSB4256:");
_logger.AllBuildEvents.Count.ShouldBe(6);
_logger.AllBuildEvents.Count.ShouldBe(8);
_logger.ErrorCount.ShouldBe(1);
}

Expand Down Expand Up @@ -564,8 +564,8 @@ public void NonExistingInputResultsCacheShouldLogError()

result.OverallResult.ShouldBe(BuildResultCode.Failure);

_logger.AllBuildEvents.Count.ShouldBe(6);
_logger.Errors.First().Message.ShouldContain("MSB4255:");
_logger.AllBuildEvents.Count.ShouldBe(8);
_logger.Errors.First().Message.ShouldContain("MSB4255:");
_logger.Errors.First().Message.ShouldContain("FileDoesNotExist1");
_logger.Errors.First().Message.ShouldContain("FileDoesNotExist2");
_logger.ErrorCount.ShouldBe(1);
Expand Down
Loading
Loading