Skip to content
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

Emit eval props if requested by any sink #10243

Merged
merged 12 commits into from
Jul 25, 2024
1 change: 1 addition & 0 deletions documentation/wiki/ChangeWaves.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ A wave of features is set to "rotate out" (i.e. become standard functionality) t
- [Convert.ToString during a property evaluation uses the InvariantCulture for all types](https://github.com/dotnet/msbuild/pull/9874)
- [Fix oversharing of build results in ResultsCache](https://github.com/dotnet/msbuild/pull/9987)
- [Add ParameterName and PropertyName to TaskParameterEventArgs](https://github.com/dotnet/msbuild/pull/10130)
- [Emit eval props if requested by any sink](https://github.com/dotnet/msbuild/pull/10243)

### 17.10
- [AppDomain configuration is serialized without using BinFmt](https://github.com/dotnet/msbuild/pull/9320) - feature can be opted out only if [BinaryFormatter](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter) is allowed at runtime by editing `MSBuild.runtimeconfig.json`
Expand Down
59 changes: 40 additions & 19 deletions src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public BuildManager_Tests(ITestOutputHelper output)
EnableNodeReuse = false
};
_buildManager = new BuildManager();
_projectCollection = new ProjectCollection();
_projectCollection = new ProjectCollection(globalProperties: null, _parameters.Loggers, ToolsetDefinitionLocations.Default);

_env = TestEnvironment.Create(output);
_inProcEnvCheckTransientEnvironmentVariable = _env.SetEnvironmentVariable("MSBUILDINPROCENVCHECK", "1");
Expand Down Expand Up @@ -137,8 +137,8 @@ public void SimpleBuild()
_logger.AssertLogContains("[success]");
Assert.Single(_logger.ProjectStartedEvents);

ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
ProjectEvaluationFinishedEventArgs evalFinishedEvent = _logger.EvaluationFinishedEvents[0];
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(evalFinishedEvent.Properties);

Assert.True(properties.TryGetValue("InitialProperty1", out string propertyValue));
Assert.Equal("InitialProperty1", propertyValue);
Expand Down Expand Up @@ -254,8 +254,8 @@ public void SimpleGraphBuild()
_logger.AssertLogContains("[success]");
_logger.ProjectStartedEvents.Count.ShouldBe(1);

ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
ProjectEvaluationFinishedEventArgs evalFinishedEvent = _logger.EvaluationFinishedEvents[0];
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(evalFinishedEvent.Properties);

properties.TryGetValue("InitialProperty1", out string propertyValue).ShouldBeTrue();
propertyValue.ShouldBe("InitialProperty1", StringCompareShould.IgnoreCase);
Expand Down Expand Up @@ -571,8 +571,8 @@ public void InProcForwardPropertiesFromChild()
_logger.AssertLogContains("[success]");
Assert.Single(_logger.ProjectStartedEvents);

ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
ProjectEvaluationFinishedEventArgs evalFinishedEvent = _logger.EvaluationFinishedEvents[0];
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(evalFinishedEvent.Properties);

Assert.True(properties.TryGetValue("InitialProperty1", out string propertyValue));
Assert.Equal("InitialProperty1", propertyValue);
Expand Down Expand Up @@ -611,8 +611,8 @@ public void InProcMsBuildForwardAllPropertiesFromChild()
_logger.AssertLogContains("[success]");
Assert.Single(_logger.ProjectStartedEvents);

ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
ProjectEvaluationFinishedEventArgs evalFinishedEvent = _logger.EvaluationFinishedEvents[0];
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(evalFinishedEvent.Properties);

Assert.True(properties.TryGetValue("InitialProperty1", out string propertyValue));
Assert.Equal("InitialProperty1", propertyValue);
Expand Down Expand Up @@ -655,8 +655,8 @@ public void MsBuildForwardAllPropertiesFromChildLaunchChildNode()
_logger.AssertLogContains("[success]");
Assert.Single(_logger.ProjectStartedEvents);

ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
ProjectEvaluationFinishedEventArgs evalFinishedEvent = _logger.EvaluationFinishedEvents[0];
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(evalFinishedEvent.Properties);

Assert.True(properties.TryGetValue("InitialProperty1", out string propertyValue));
Assert.Equal("InitialProperty1", propertyValue);
Expand Down Expand Up @@ -704,7 +704,15 @@ public void OutOfProcNodeForwardCertainproperties()
var data = new BuildRequestData(project.FullPath, new Dictionary<string, string>(),
MSBuildDefaultToolsVersion, Array.Empty<string>(), null);

BuildResult result = _buildManager.Build(_parameters, data);
// We need to recreate build parameters to ensure proper capturing of newly set environment variables
BuildParameters parameters = new BuildParameters
{
ShutdownInProcNodeOnBuildFinish = true,
Loggers = new ILogger[] { _logger },
EnableNodeReuse = false
};

BuildResult result = _buildManager.Build(parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
Assert.Single(_logger.ProjectStartedEvents);
Expand Down Expand Up @@ -760,11 +768,21 @@ public void OutOfProcNodeForwardCertainpropertiesAlsoGetResultsFromCache()
_env.SetEnvironmentVariable("MsBuildForwardPropertiesFromChild", "InitialProperty3;IAMNOTREAL");
_env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1");

_env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "0");

var project = CreateProject(contents, null, _projectCollection, false);
var data = new BuildRequestData(project.FullPath, new Dictionary<string, string>(),
MSBuildDefaultToolsVersion, Array.Empty<string>(), null);

BuildResult result = _buildManager.Build(_parameters, data);
// We need to recreate build parameters to ensure proper capturing of newly set environment variables
BuildParameters parameters = new BuildParameters
{
ShutdownInProcNodeOnBuildFinish = true,
Loggers = new ILogger[] { _logger },
EnableNodeReuse = false
};

BuildResult result = _buildManager.Build(parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
Assert.Equal(3, _logger.ProjectStartedEvents.Count);
Expand All @@ -785,7 +803,8 @@ public void OutOfProcNodeForwardCertainpropertiesAlsoGetResultsFromCache()
Assert.Equal("InitialProperty3", propertyValue);

projectStartedEvent = _logger.ProjectStartedEvents[2];
Assert.Null(projectStartedEvent.Properties);
properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
(properties == null || properties.Count == 0).ShouldBeTrue();
}

/// <summary>
Expand Down Expand Up @@ -822,7 +841,7 @@ public void ForwardNoPropertiesLaunchChildNode()

ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
Assert.Null(properties);
(properties == null || properties.Count == 0).ShouldBeTrue();
}

/// <summary>
Expand Down Expand Up @@ -919,7 +938,7 @@ public void ForwardNoPropertiesLaunchChildNodeDefault()

ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary<string, string> properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
Assert.Null(properties);
(properties == null || properties.Count == 0).ShouldBeTrue();
}

/// <summary>
Expand Down Expand Up @@ -3475,9 +3494,11 @@ private static string BuildAndCheckCache(BuildManager localBuildManager, IEnumer
/// </summary>
private static Dictionary<string, string> ExtractProjectStartedPropertyList(IEnumerable properties)
{
// Gather a sorted list of all the properties.
return properties?.Cast<DictionaryEntry>()
.ToDictionary(prop => (string)prop.Key, prop => (string)prop.Value, StringComparer.OrdinalIgnoreCase);
Dictionary<string, string> propertiesLookup = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
Internal.Utilities.EnumerateProperties(properties, propertiesLookup,
static (dict, kvp) => dict.Add(kvp.Key, kvp.Value));

return propertiesLookup;
}

/// <summary>
Expand Down
19 changes: 13 additions & 6 deletions src/Build.UnitTests/BackEnd/MockLoggingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,21 @@ public bool IncludeEvaluationProfile
set { }
}

/// <summary>
/// Log properties and items on ProjectEvaluationFinishedEventArgs
/// instead of ProjectStartedEventArgs.
/// </summary>
public bool IncludeEvaluationPropertiesAndItems
/// <inheritdoc cref="ILoggingService.SetIncludeEvaluationPropertiesAndItemsInEvents"/>
public void SetIncludeEvaluationPropertiesAndItemsInEvents(bool inProjectStartedEvent,
bool inEvaluationFinishedEvent)
{ }

/// <inheritdoc cref="ILoggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent"/>
public bool IncludeEvaluationPropertiesAndItemsInProjectStartedEvent
{
get => false;
}

/// <inheritdoc cref="ILoggingService.IncludeEvaluationPropertiesAndItemsInEvaluationFinishedEvent"/>
public bool IncludeEvaluationPropertiesAndItemsInEvaluationFinishedEvent
{
get => false;
set { }
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Build.UnitTests/ConsoleLogger_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public void ErrorMessageWithMultiplePropertiesInMessage(bool includeEvaluationPr

if (includeEvaluationPropertiesAndItems)
{
pc.Collection.LoggingService.IncludeEvaluationPropertiesAndItems = true;
pc.Collection.LoggingService.SetIncludeEvaluationPropertiesAndItemsInEvents(inProjectStartedEvent: false, inEvaluationFinishedEvent: true);
}

var project = env.CreateTestProjectWithFiles(@"
Expand Down
3 changes: 2 additions & 1 deletion src/Build/BackEnd/BuildManager/BuildManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2757,7 +2757,8 @@ private NodeConfiguration GetNodeConfiguration()
, new LoggingNodeConfiguration(
loggingService.IncludeEvaluationMetaprojects,
loggingService.IncludeEvaluationProfile,
loggingService.IncludeEvaluationPropertiesAndItems,
loggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent,
loggingService.IncludeEvaluationPropertiesAndItemsInEvaluationFinishedEvent,
loggingService.IncludeTaskInputs));
}

Expand Down
18 changes: 15 additions & 3 deletions src/Build/BackEnd/Components/Logging/ILoggingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,24 @@ bool IncludeEvaluationProfile

/// <summary>
/// Should properties and items be logged on <see cref="ProjectEvaluationFinishedEventArgs"/>
/// instead of <see cref="ProjectStartedEventArgs"/>?
/// or/and <see cref="ProjectStartedEventArgs"/>?
/// </summary>
bool IncludeEvaluationPropertiesAndItems
void SetIncludeEvaluationPropertiesAndItemsInEvents(bool inProjectStartedEvent, bool inEvaluationFinishedEvent);

/// <summary>
/// Indicates whether properties and items should be logged on <see cref="ProjectStartedEventArgs"/>.
/// </summary>
bool IncludeEvaluationPropertiesAndItemsInProjectStartedEvent
{
get;
}

/// <summary>
/// Indicates whether properties and items should be logged on <see cref="ProjectEvaluationFinishedEventArgs"/>.
/// </summary>
bool IncludeEvaluationPropertiesAndItemsInEvaluationFinishedEvent
{
get;
set;
}

/// <summary>
Expand Down
91 changes: 65 additions & 26 deletions src/Build/BackEnd/Components/Logging/LoggingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,6 @@ internal partial class LoggingService : ILoggingService, INodePacketHandler
/// </summary>
private bool? _includeEvaluationProfile;

/// <summary>
/// Whether properties and items should be logged on <see cref="ProjectEvaluationFinishedEventArgs"/>
/// instead of <see cref="ProjectStartedEventArgs"/>.
/// </summary>
private bool? _includeEvaluationPropertiesAndItems;

/// <summary>
/// Whether to include task inputs in task events.
/// </summary>
Expand Down Expand Up @@ -541,33 +535,77 @@ public bool IncludeTaskInputs
set => _includeTaskInputs = value;
}

/// <summary>
/// Should properties and items be logged on <see cref="ProjectEvaluationFinishedEventArgs"/>
/// instead of <see cref="ProjectStartedEventArgs"/>?
/// </summary>
public bool IncludeEvaluationPropertiesAndItems
/// <inheritdoc cref="ILoggingService.SetIncludeEvaluationPropertiesAndItemsInEvents"/>
public void SetIncludeEvaluationPropertiesAndItemsInEvents(bool inProjectStartedEvent, bool inEvaluationFinishedEvent)
{
get
_evalDataBehaviorSet = true;
IncludeEvaluationPropertiesAndItemsInEvaluationFinishedEvent = inEvaluationFinishedEvent;
IncludeEvaluationPropertiesAndItemsInProjectStartedEvent = inProjectStartedEvent;
}

private bool _evalDataBehaviorSet;
private bool _includeEvaluationPropertiesAndItemsInProjectStartedEvent;
private bool _includeEvaluationPropertiesAndItemsInEvaluationFinishedEvent;
private void InferEvalDataBehavior()
{
if (_evalDataBehaviorSet)
{
if (_includeEvaluationPropertiesAndItems == null)
return;
}
// Set this right away - to prevent SO exception in case of any future refactoring
// that would refer to the IncludeEvaluation... properties here
_evalDataBehaviorSet = true;

bool? escapeHatch = Traits.Instance.EscapeHatches.LogPropertiesAndItemsAfterEvaluation;
if (escapeHatch.HasValue)
{
IncludeEvaluationPropertiesAndItemsInEvaluationFinishedEvent = escapeHatch.Value;
IncludeEvaluationPropertiesAndItemsInProjectStartedEvent = !escapeHatch.Value;
}
else
{
var sinks = _eventSinkDictionary.Values.OfType<EventSourceSink>().ToList();

if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_12))
{
var escapeHatch = Traits.Instance.EscapeHatches.LogPropertiesAndItemsAfterEvaluation;
if (escapeHatch.HasValue)
{
_includeEvaluationPropertiesAndItems = escapeHatch.Value;
}
else
{
var sinks = _eventSinkDictionary.Values.OfType<EventSourceSink>();
// .All() on an empty list defaults to true, we want to default to false
_includeEvaluationPropertiesAndItems = sinks.Any() && sinks.All(sink => sink.IncludeEvaluationPropertiesAndItems);
}
// If any logger requested the data - we need to emit them
IncludeEvaluationPropertiesAndItemsInEvaluationFinishedEvent =
sinks.Any(sink => sink.IncludeEvaluationPropertiesAndItems);
// If any logger didn't request the data - hence it's likely legacy logger
// - we need to populate the data in legacy way
IncludeEvaluationPropertiesAndItemsInProjectStartedEvent =
sinks.Any(sink => !sink.IncludeEvaluationPropertiesAndItems);
}
else
{
bool allSinksIncludeEvalData = sinks.Any() && sinks.All(sink => sink.IncludeEvaluationPropertiesAndItems);

IncludeEvaluationPropertiesAndItemsInEvaluationFinishedEvent = allSinksIncludeEvalData;
IncludeEvaluationPropertiesAndItemsInProjectStartedEvent = !allSinksIncludeEvalData;
}
}
}

return _includeEvaluationPropertiesAndItems ?? false;
/// <inheritdoc cref="ILoggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent"/>
public bool IncludeEvaluationPropertiesAndItemsInProjectStartedEvent
{
get
{
InferEvalDataBehavior();
return _includeEvaluationPropertiesAndItemsInProjectStartedEvent;
}
private set => _includeEvaluationPropertiesAndItemsInProjectStartedEvent = value;
}

set => _includeEvaluationPropertiesAndItems = value;
/// <inheritdoc cref="ILoggingService.IncludeEvaluationPropertiesAndItemsInEvaluationFinishedEvent"/>
public bool IncludeEvaluationPropertiesAndItemsInEvaluationFinishedEvent
{
get
{
InferEvalDataBehavior();
return _includeEvaluationPropertiesAndItemsInEvaluationFinishedEvent;
}
private set => _includeEvaluationPropertiesAndItemsInEvaluationFinishedEvent = value;
}

/// <summary>
Expand Down Expand Up @@ -609,6 +647,7 @@ public ICollection<string> GetWarningsNotAsErrors(BuildEventContext context)
return GetWarningsForProject(context, _warningsNotAsErrorsByProject, WarningsNotAsErrors);
}


/// <summary>
/// Returns a collection of warnings to be demoted to messages for the specified build context.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private ProjectLoggingContext(

// If we are only logging critical events lets not pass back the items or properties
if (!LoggingService.OnlyLogCriticalEvents &&
!LoggingService.IncludeEvaluationPropertiesAndItems &&
LoggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent &&
(!LoggingService.RunningOnRemoteNode || LoggingService.SerializeAllProperties))
{
if (projectProperties is null)
Expand All @@ -116,7 +116,7 @@ private ProjectLoggingContext(
}

if (projectProperties != null &&
!LoggingService.IncludeEvaluationPropertiesAndItems &&
LoggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent &&
propertiesToSerialize?.Length > 0 &&
!LoggingService.SerializeAllProperties)
{
Expand Down
Loading