diff --git a/src/Build.UnitTests/BinaryLogger_Tests.cs b/src/Build.UnitTests/BinaryLogger_Tests.cs index cb255f7ae9b..71983787418 100644 --- a/src/Build.UnitTests/BinaryLogger_Tests.cs +++ b/src/Build.UnitTests/BinaryLogger_Tests.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Logging; - +using Microsoft.Build.UnitTests.Shared; using Shouldly; using Xunit; using Xunit.Abstractions; @@ -138,6 +139,45 @@ public void BinaryLoggerShouldSupportFilePathExplicitParameter() ObjectModelHelpers.BuildProjectExpectSuccess(s_testProject, binaryLogger); } + [Fact] + public void UnusedEnvironmentVariablesDoNotAppearInBinaryLog() + { + using (TestEnvironment env = TestEnvironment.Create()) + { + env.SetEnvironmentVariable("EnvVar1", "itsValue"); + env.SetEnvironmentVariable("EnvVar2", "value2"); + env.SetEnvironmentVariable("EnvVar3", "value3"); + string contents = @" + + + +value +$(EnvVar2) + + + + + + +"; + TransientTestFolder logFolder = env.CreateFolder(createFolder: true); + TransientTestFile projectFile = env.CreateFile(logFolder, "myProj.proj", contents); + BinaryLogger logger = new(); + logger.Parameters = _logFile; + RunnerUtilities.ExecMSBuild($"{projectFile.Path} -bl:{logger.Parameters}", out bool success); + success.ShouldBeTrue(); + RunnerUtilities.ExecMSBuild($"{logger.Parameters} -flp:logfile={Path.Combine(logFolder.Path, "logFile.log")};verbosity=diagnostic", out success); + success.ShouldBeTrue(); + string text = File.ReadAllText(Path.Combine(logFolder.Path, "logFile.log")); + text.ShouldContain("EnvVar2"); + text.ShouldContain("value2"); + text.ShouldContain("EnvVar3"); + text.ShouldContain("value3"); + text.ShouldNotContain("EnvVar1"); + text.ShouldNotContain("itsValue"); + } + } + [Fact] public void BinaryLoggerShouldNotThrowWhenMetadataCannotBeExpanded() { diff --git a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs index 4ad1bb35cac..9b9deeef04a 100644 --- a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs +++ b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs @@ -24,30 +24,36 @@ public BuildEventArgsSerializationTests() _ = ItemGroupLoggingHelper.ItemGroupIncludeLogMessagePrefix; } - [Fact] - public void RoundtripBuildStartedEventArgs() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void RoundtripBuildStartedEventArgs(bool serializeAllEnvironmentVariables) { - var args = new BuildStartedEventArgs( - "Message", - "HelpKeyword", - DateTime.Parse("3/1/2017 11:11:56 AM")); - Roundtrip(args, - e => e.Message, - e => e.HelpKeyword, - e => e.Timestamp.ToString()); - - args = new BuildStartedEventArgs( - "M", - null, - new Dictionary - { + using (TestEnvironment env = TestEnvironment.Create()) + { + env.SetEnvironmentVariable("MSBUILDLOGALLENVIRONMENTVARIABLES", serializeAllEnvironmentVariables ? "1" : null); + var args = new BuildStartedEventArgs( + "Message", + "HelpKeyword", + DateTime.Parse("3/1/2017 11:11:56 AM")); + Roundtrip(args, + e => e.Message, + e => e.HelpKeyword, + e => e.Timestamp.ToString()); + + args = new BuildStartedEventArgs( + "M", + null, + new Dictionary + { { "SampleName", "SampleValue" } - }); - Roundtrip(args, - e => TranslationHelpers.ToString(e.BuildEnvironment), - e => e.HelpKeyword, - e => e.ThreadId.ToString(), - e => e.SenderName); + }); + Roundtrip(args, + e => serializeAllEnvironmentVariables ? TranslationHelpers.ToString(e.BuildEnvironment) : null, + e => e.HelpKeyword, + e => e.ThreadId.ToString(), + e => e.SenderName); + } } [Fact] @@ -173,6 +179,17 @@ public void RoundtripTaskStartedEventArgs() e => e.ColumnNumber.ToString()); } + [Fact] + public void RoundtripEnvironmentVariableReadEventArgs() + { + EnvironmentVariableReadEventArgs args = new("VarName", "VarValue"); + args.BuildEventContext = new BuildEventContext(4, 5, 6, 7); + Roundtrip(args, + e => e.Message, + e => e.EnvironmentVariableName, + e => e.BuildEventContext.ToString()); + } + [Fact] public void RoundtripTaskFinishedEventArgs() { diff --git a/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs b/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs index 06324b51b52..cd869baccc2 100644 --- a/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs @@ -4518,7 +4518,7 @@ public void VerifyMSBuildLogsAMessageWhenLocalPropertyCannotOverrideValueOfGloba [Fact] public void VerifyPropertyTrackingLoggingDefault() { - // Having nothing defined should default to nothing being logged. + // Having just environment variables defined should default to nothing being logged except one environment variable read. this.VerifyPropertyTrackingLoggingScenario( null, logger => @@ -4531,7 +4531,9 @@ public void VerifyPropertyTrackingLoggingDefault() logger .AllBuildEvents .OfType() - .ShouldBeEmpty(); + .ShouldHaveSingleItem() + .EnvironmentVariableName + .ShouldBe("DEFINED_ENVIRONMENT_VARIABLE2"); logger .AllBuildEvents @@ -4560,7 +4562,9 @@ public void VerifyPropertyTrackingLoggingPropertyReassignment() logger .AllBuildEvents .OfType() - .ShouldBeEmpty(); + .ShouldHaveSingleItem() + .EnvironmentVariableName + .ShouldBe("DEFINED_ENVIRONMENT_VARIABLE2"); logger .AllBuildEvents @@ -4589,7 +4593,9 @@ public void VerifyPropertyTrackingLoggingNone() logger .AllBuildEvents .OfType() - .ShouldBeEmpty(); + .ShouldHaveSingleItem() + .EnvironmentVariableName + .ShouldBe("DEFINED_ENVIRONMENT_VARIABLE2"); logger .AllBuildEvents @@ -4618,7 +4624,9 @@ public void VerifyPropertyTrackingLoggingPropertyInitialValue() logger .AllBuildEvents .OfType() - .ShouldBeEmpty(); + .ShouldHaveSingleItem() + .EnvironmentVariableName + .ShouldBe("DEFINED_ENVIRONMENT_VARIABLE2"); logger .AllBuildEvents @@ -4702,7 +4710,9 @@ public void VerifyPropertyTrackingLoggingUninitializedPropertyRead() logger .AllBuildEvents .OfType() - .ShouldBeEmpty(); + .ShouldHaveSingleItem() + .EnvironmentVariableName + .ShouldBe("DEFINED_ENVIRONMENT_VARIABLE2"); logger .AllBuildEvents diff --git a/src/Build.UnitTests/Utilities_Tests.cs b/src/Build.UnitTests/Utilities_Tests.cs index c6f7af84e57..6159f3af2c1 100644 --- a/src/Build.UnitTests/Utilities_Tests.cs +++ b/src/Build.UnitTests/Utilities_Tests.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using System.IO; using Xunit.Abstractions; +using Shouldly; #nullable disable @@ -80,55 +81,41 @@ public UtilitiesTestReadOnlyLoad() [Fact] public void CommentsInPreprocessing() { - Microsoft.Build.Construction.XmlDocumentWithLocation.ClearReadOnlyFlags_UnitTestsOnly(); + using TestEnvironment env = TestEnvironment.Create(); + XmlDocumentWithLocation.ClearReadOnlyFlags_UnitTestsOnly(); - string input = FileUtilities.GetTemporaryFile(); - string output = FileUtilities.GetTemporaryFile(); + TransientTestFile inputFile = env.CreateFile("tempInput.tmp", ObjectModelHelpers.CleanupFileContents(@" + + +")); + TransientTestFile outputFile = env.CreateFile("tempOutput.tmp"); - string _initialLoadFilesWriteable = Environment.GetEnvironmentVariable("MSBUILDLOADALLFILESASWRITEABLE"); - try - { - Environment.SetEnvironmentVariable("MSBUILDLOADALLFILESASWRITEABLE", "1"); - - string content = ObjectModelHelpers.CleanupFileContents(@" - - -"); - File.WriteAllText(input, content); + env.SetEnvironmentVariable("MSBUILDLOADALLFILESASWRITEABLE", "1"); #if FEATURE_GET_COMMANDLINE - Assert.Equal(MSBuildApp.ExitType.Success, MSBuildApp.Execute(@"c:\bin\msbuild.exe """ + input + - (NativeMethodsShared.IsUnixLike ? @""" -pp:""" : @""" /pp:""") + output + @"""")); + MSBuildApp.Execute(@"c:\bin\msbuild.exe """ + inputFile.Path + + (NativeMethodsShared.IsUnixLike ? @""" -pp:""" : @""" /pp:""") + outputFile.Path + @"""") + .ShouldBe(MSBuildApp.ExitType.Success); #else - Assert.Equal( - MSBuildApp.ExitType.Success, - MSBuildApp.Execute( - new[] { @"c:\bin\msbuild.exe", '"' + input + '"', - '"' + (NativeMethodsShared.IsUnixLike ? "-pp:" : "/pp:") + output + '"'})); + Assert.Equal( + MSBuildApp.ExitType.Success, + MSBuildApp.Execute( + new[] { @"c:\bin\msbuild.exe", '"' + inputFile.Path + '"', + '"' + (NativeMethodsShared.IsUnixLike ? "-pp:" : "/pp:") + outputFile.Path + '"'})); #endif - bool foundDoNotModify = false; - foreach (string line in File.ReadLines(output)) + bool foundDoNotModify = false; + foreach (string line in File.ReadLines(outputFile.Path)) + { + line.ShouldNotContain("", "This is what it will look like if we're loading read/only"); + + if (line.Contains("DO NOT MODIFY")) // this is in a comment in our targets { - if (line.Contains("")) // This is what it will look like if we're loading read/only - { - Assert.True(false); - } - - if (line.Contains("DO NOT MODIFY")) // this is in a comment in our targets - { - foundDoNotModify = true; - } + foundDoNotModify = true; } - - Assert.True(foundDoNotModify); - } - finally - { - File.Delete(input); - File.Delete(output); - Environment.SetEnvironmentVariable("MSBUILDLOADALLFILESASWRITEABLE", _initialLoadFilesWriteable); } + + foundDoNotModify.ShouldBeTrue(); } [Fact] diff --git a/src/Build/BackEnd/Components/Logging/LoggingService.cs b/src/Build/BackEnd/Components/Logging/LoggingService.cs index 2f08099b333..6cf71eac4e9 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingService.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingService.cs @@ -1432,20 +1432,8 @@ private void LoggingEventProcessor(object loggingEvent) /// private void RouteBuildEvent(object loggingEvent) { - BuildEventArgs buildEventArgs = null; - - if (loggingEvent is BuildEventArgs bea) - { - buildEventArgs = bea; - } - else if (loggingEvent is KeyValuePair kvp) - { - buildEventArgs = kvp.Value; - } - else - { - ErrorUtilities.ThrowInternalError("Unknown logging item in queue:" + loggingEvent.GetType().FullName); - } + BuildEventArgs buildEventArgs = loggingEvent as BuildEventArgs ?? (loggingEvent as KeyValuePair?)?.Value; + ErrorUtilities.VerifyThrow(buildEventArgs is not null, "Unknown logging item in queue:" + loggingEvent.GetType().FullName); if (buildEventArgs is BuildWarningEventArgs warningEvent) { @@ -1489,14 +1477,12 @@ private void RouteBuildEvent(object loggingEvent) }; } } - - if (loggingEvent is BuildErrorEventArgs errorEvent) + else if (loggingEvent is BuildErrorEventArgs errorEvent) { // Keep track of build submissions that have logged errors. If there is no build context, add BuildEventContext.InvalidSubmissionId. _buildSubmissionIdsThatHaveLoggedErrors.Add(errorEvent.BuildEventContext?.SubmissionId ?? BuildEventContext.InvalidSubmissionId); } - - if (loggingEvent is ProjectFinishedEventArgs projectFinishedEvent && projectFinishedEvent.BuildEventContext != null) + else if (loggingEvent is ProjectFinishedEventArgs projectFinishedEvent && projectFinishedEvent.BuildEventContext != null) { int key = GetWarningsAsErrorOrMessageKey(projectFinishedEvent); _warningsAsErrorsByProject?.Remove(key); @@ -1541,7 +1527,7 @@ private void RouteBuildEvent(BuildEventArgs eventArg) TryRaiseProjectStartedEvent(eventArg); // The event has not been through a filter yet. All events must go through a filter before they make it to a logger - if (_filterEventSource != null) // Loggers may not be registered + if (_filterEventSource != null) // Loggers may not be registered { // Send the event to the filter, the Consume will not return until all of the loggers which have registered to the event have process // them. @@ -1562,7 +1548,7 @@ private void RouteBuildEvent(BuildEventArgs eventArg) { if (!sink.HaveLoggedBuildStartedEvent) { - sink.Consume(eventArg, (int)pair.Key); + sink.Consume(eventArg, pair.Key); } // Reset the HaveLoggedBuildStarted event because no one else will be sending a build started event to any loggers at this time. diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 214a5118c2c..b9348623878 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -388,14 +388,11 @@ public void LogBuildStarted() message = ResourceUtilities.GetResourceString("BuildStarted"); } - IDictionary environmentProperties = null; + IDictionary environmentProperties = _componentHost?.BuildParameters != null && Traits.Instance.LogAllEnvironmentVariables ? + _componentHost.BuildParameters.BuildProcessEnvironment + : null; - if (_componentHost?.BuildParameters != null) - { - environmentProperties = _componentHost.BuildParameters.BuildProcessEnvironment; - } - - BuildStartedEventArgs buildEvent = new BuildStartedEventArgs(message, null /* no help keyword */, environmentProperties); + BuildStartedEventArgs buildEvent = new(message, helpKeyword: null, environmentProperties); // Raise the event with the filters ProcessLoggingEvent(buildEvent); diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index bc39a5ccd32..b7f2fcf4bc2 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -8,6 +8,7 @@ using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Shared; +using static Microsoft.Build.Execution.ProjectPropertyInstance; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; #nullable disable @@ -71,6 +72,17 @@ internal ProjectLoggingContext( { } + private IEnumerable FilterEnvironmentDerivedProperties(PropertyDictionary properties) + { + foreach (ProjectPropertyInstance property in properties) + { + if (property is not EnvironmentDerivedProjectPropertyInstance) + { + yield return new DictionaryEntry(property.Name, property.EvaluatedValue); + } + } + } + /// /// Constructs a project logging contexts. /// @@ -100,7 +112,19 @@ private ProjectLoggingContext( !LoggingService.IncludeEvaluationPropertiesAndItems && (!LoggingService.RunningOnRemoteNode || LoggingService.SerializeAllProperties)) { - properties = projectProperties?.GetCopyOnReadEnumerable(property => new DictionaryEntry(property.Name, property.EvaluatedValue)) ?? Enumerable.Empty(); + if (projectProperties is null) + { + properties = Enumerable.Empty(); + } + else if (Traits.Instance.LogAllEnvironmentVariables) + { + properties = projectProperties.GetCopyOnReadEnumerable(property => new DictionaryEntry(property.Name, property.EvaluatedValue)); + } + else + { + properties = FilterEnvironmentDerivedProperties(projectProperties); + } + items = projectItems?.GetCopyOnReadEnumerable(item => new DictionaryEntry(item.ItemType, new TaskItem(item))) ?? Enumerable.Empty(); } diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs index 505bc1d2df1..32b7c6b1b1a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs @@ -15,6 +15,7 @@ using ProjectItemInstanceFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.ProjectItemInstanceFactory; using EngineFileUtilities = Microsoft.Build.Internal.EngineFileUtilities; using TargetLoggingContext = Microsoft.Build.BackEnd.Logging.TargetLoggingContext; +using Microsoft.Build.BackEnd.Logging; #nullable disable @@ -83,7 +84,7 @@ internal override void ExecuteTask(Lookup lookup) if (!String.IsNullOrEmpty(child.KeepMetadata)) { - var keepMetadataEvaluated = bucket.Expander.ExpandIntoStringListLeaveEscaped(child.KeepMetadata, ExpanderOptions.ExpandAll, child.KeepMetadataLocation).ToList(); + var keepMetadataEvaluated = bucket.Expander.ExpandIntoStringListLeaveEscaped(child.KeepMetadata, ExpanderOptions.ExpandAll, child.KeepMetadataLocation, LoggingContext).ToList(); if (keepMetadataEvaluated.Count > 0) { keepMetadata = new HashSet(keepMetadataEvaluated); @@ -92,7 +93,7 @@ internal override void ExecuteTask(Lookup lookup) if (!String.IsNullOrEmpty(child.RemoveMetadata)) { - var removeMetadataEvaluated = bucket.Expander.ExpandIntoStringListLeaveEscaped(child.RemoveMetadata, ExpanderOptions.ExpandAll, child.RemoveMetadataLocation).ToList(); + var removeMetadataEvaluated = bucket.Expander.ExpandIntoStringListLeaveEscaped(child.RemoveMetadata, ExpanderOptions.ExpandAll, child.RemoveMetadataLocation, LoggingContext).ToList(); if (removeMetadataEvaluated.Count > 0) { removeMetadata = new HashSet(removeMetadataEvaluated); @@ -101,7 +102,7 @@ internal override void ExecuteTask(Lookup lookup) if (!String.IsNullOrEmpty(child.MatchOnMetadata)) { - var matchOnMetadataEvaluated = bucket.Expander.ExpandIntoStringListLeaveEscaped(child.MatchOnMetadata, ExpanderOptions.ExpandAll, child.MatchOnMetadataLocation).ToList(); + var matchOnMetadataEvaluated = bucket.Expander.ExpandIntoStringListLeaveEscaped(child.MatchOnMetadata, ExpanderOptions.ExpandAll, child.MatchOnMetadataLocation, LoggingContext).ToList(); if (matchOnMetadataEvaluated.Count > 0) { matchOnMetadata = new HashSet(matchOnMetadataEvaluated); @@ -114,7 +115,7 @@ internal override void ExecuteTask(Lookup lookup) (child.Exclude.Length != 0)) { // It's an item -- we're "adding" items to the world - ExecuteAdd(child, bucket, keepMetadata, removeMetadata); + ExecuteAdd(child, bucket, keepMetadata, removeMetadata, LoggingContext); } else if (child.Remove.Length != 0) { @@ -124,7 +125,7 @@ internal override void ExecuteTask(Lookup lookup) else { // It's a modify -- changing existing items - ExecuteModify(child, bucket, keepMetadata, removeMetadata); + ExecuteModify(child, bucket, keepMetadata, removeMetadata, LoggingContext); } } } @@ -150,7 +151,8 @@ internal override void ExecuteTask(Lookup lookup) /// The batching bucket. /// An of metadata names to keep. /// An of metadata names to remove. - private void ExecuteAdd(ProjectItemGroupTaskItemInstance child, ItemBucket bucket, ISet keepMetadata, ISet removeMetadata) + /// Context for logging + private void ExecuteAdd(ProjectItemGroupTaskItemInstance child, ItemBucket bucket, ISet keepMetadata, ISet removeMetadata, LoggingContext loggingContext = null) { // First, collect up the appropriate metadata collections. We need the one from the item definition, if any, and // the one we are using for this batching bucket. @@ -164,7 +166,7 @@ private void ExecuteAdd(ProjectItemGroupTaskItemInstance child, ItemBucket bucke bucket.Expander.Metadata = metadataTable; // Second, expand the item include and exclude, and filter existing metadata as appropriate. - List itemsToAdd = ExpandItemIntoItems(child, bucket.Expander, keepMetadata, removeMetadata); + List itemsToAdd = ExpandItemIntoItems(child, bucket.Expander, keepMetadata, removeMetadata, loggingContext); // Third, expand the metadata. foreach (ProjectItemGroupTaskMetadataInstance metadataInstance in child.Metadata) @@ -179,11 +181,12 @@ private void ExecuteAdd(ProjectItemGroupTaskItemInstance child, ItemBucket bucke metadataInstance.Location, LoggingContext.LoggingService, LoggingContext.BuildEventContext, - FileSystems.Default); + FileSystems.Default, + loggingContext: loggingContext); if (condition) { - string evaluatedValue = bucket.Expander.ExpandIntoStringLeaveEscaped(metadataInstance.Value, ExpanderOptions.ExpandAll, metadataInstance.Location); + string evaluatedValue = bucket.Expander.ExpandIntoStringLeaveEscaped(metadataInstance.Value, ExpanderOptions.ExpandAll, metadataInstance.Location, loggingContext); // This both stores the metadata so we can add it to all the items we just created later, and // exposes this metadata to further metadata evaluations in subsequent loop iterations. @@ -245,7 +248,7 @@ private void ExecuteRemove(ProjectItemGroupTaskItemInstance child, ItemBucket bu List itemsToRemove; if (matchOnMetadata == null) { - itemsToRemove = FindItemsMatchingSpecification(group, child.Remove, child.RemoveLocation, bucket.Expander); + itemsToRemove = FindItemsMatchingSpecification(group, child.Remove, child.RemoveLocation, bucket.Expander, LoggingContext); } else { @@ -277,7 +280,8 @@ private void ExecuteRemove(ProjectItemGroupTaskItemInstance child, ItemBucket bu /// The batching bucket. /// An of metadata names to keep. /// An of metadata names to remove. - private void ExecuteModify(ProjectItemGroupTaskItemInstance child, ItemBucket bucket, ISet keepMetadata, ISet removeMetadata) + /// Context for this operation. + private void ExecuteModify(ProjectItemGroupTaskItemInstance child, ItemBucket bucket, ISet keepMetadata, ISet removeMetadata, LoggingContext loggingContext = null) { ICollection group = bucket.Lookup.GetItems(child.ItemType); if (group == null || group.Count == 0) @@ -317,11 +321,12 @@ private void ExecuteModify(ProjectItemGroupTaskItemInstance child, ItemBucket bu metadataInstance.ConditionLocation, LoggingContext.LoggingService, LoggingContext.BuildEventContext, - FileSystems.Default); + FileSystems.Default, + loggingContext: loggingContext); if (condition) { - string evaluatedValue = bucket.Expander.ExpandIntoStringLeaveEscaped(metadataInstance.Value, ExpanderOptions.ExpandAll, metadataInstance.Location); + string evaluatedValue = bucket.Expander.ExpandIntoStringLeaveEscaped(metadataInstance.Value, ExpanderOptions.ExpandAll, metadataInstance.Location, loggingContext); metadataToSet[metadataInstance.Name] = Lookup.MetadataModification.CreateFromNewValue(evaluatedValue); } } @@ -357,6 +362,7 @@ private void GetBatchableValuesFromBuildItemGroupChild(List parameterVal /// The expander to use. /// An of metadata names to keep. /// An of metadata names to remove. + /// Context for logging /// /// This code is very close to that which exists in the Evaluator.EvaluateItemXml method. However, because /// it invokes type constructors, and those constructors take arguments of fundamentally different types, it has not @@ -368,7 +374,8 @@ private List ExpandItemIntoItems ProjectItemGroupTaskItemInstance originalItem, Expander expander, ISet keepMetadata, - ISet removeMetadata + ISet removeMetadata, + LoggingContext loggingContext = null ) { // todo this is duplicated logic with the item computation logic from evaluation (in LazyIncludeOperation.SelectItems) @@ -376,7 +383,7 @@ ISet removeMetadata List items = new List(); // Expand properties and metadata in Include - string evaluatedInclude = expander.ExpandIntoStringLeaveEscaped(originalItem.Include, ExpanderOptions.ExpandPropertiesAndMetadata, originalItem.IncludeLocation); + string evaluatedInclude = expander.ExpandIntoStringLeaveEscaped(originalItem.Include, ExpanderOptions.ExpandPropertiesAndMetadata, originalItem.IncludeLocation, loggingContext); if (evaluatedInclude.Length == 0) { @@ -387,7 +394,7 @@ ISet removeMetadata var excludes = ImmutableList.Empty.ToBuilder(); if (originalItem.Exclude.Length > 0) { - string evaluatedExclude = expander.ExpandIntoStringLeaveEscaped(originalItem.Exclude, ExpanderOptions.ExpandAll, originalItem.ExcludeLocation); + string evaluatedExclude = expander.ExpandIntoStringLeaveEscaped(originalItem.Exclude, ExpanderOptions.ExpandAll, originalItem.ExcludeLocation, loggingContext); if (evaluatedExclude.Length > 0) { @@ -516,13 +523,15 @@ ISet removeMetadata /// The specification to match against the items. /// The specification to match against the provided items /// The expander to use + /// Context for logging /// A list of matching items private List FindItemsMatchingSpecification ( ICollection items, string specification, ElementLocation specificationLocation, - Expander expander + Expander expander, + LoggingContext loggingContext = null ) { if (items.Count == 0 || specification.Length == 0) @@ -535,7 +544,7 @@ Expander expander HashSet specificationsToFind = new HashSet(StringComparer.OrdinalIgnoreCase); // Split by semicolons - var specificationPieces = expander.ExpandIntoStringListLeaveEscaped(specification, ExpanderOptions.ExpandAll, specificationLocation); + var specificationPieces = expander.ExpandIntoStringListLeaveEscaped(specification, ExpanderOptions.ExpandAll, specificationLocation, loggingContext); foreach (string piece in specificationPieces) { diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs index 49a849c4313..e1479768ff2 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs @@ -353,7 +353,8 @@ internal List GetDependencies(ProjectLoggingContext project _target.ConditionLocation, projectLoggingContext.LoggingService, projectLoggingContext.BuildEventContext, - FileSystems.Default); + FileSystems.Default, + loggingContext: projectLoggingContext); if (!condition) { @@ -392,7 +393,7 @@ internal List GetDependencies(ProjectLoggingContext project return new List(); } - var dependencies = _expander.ExpandIntoStringListLeaveEscaped(_target.DependsOnTargets, ExpanderOptions.ExpandPropertiesAndItems, _target.DependsOnTargetsLocation); + var dependencies = _expander.ExpandIntoStringListLeaveEscaped(_target.DependsOnTargets, ExpanderOptions.ExpandPropertiesAndItems, _target.DependsOnTargetsLocation, projectLoggingContext); List dependencyTargets = new List(); foreach (string escapedDependency in dependencies) { diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index ec05dfd174f..4835778b4c1 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -381,7 +381,8 @@ private async Task ExecuteBucket(TaskHost taskHost, ItemBucket b _targetChildInstance.ConditionLocation, _targetLoggingContext.LoggingService, _targetLoggingContext.BuildEventContext, - FileSystems.Default); + FileSystems.Default, + loggingContext: _targetLoggingContext); if (!condition) { @@ -623,7 +624,7 @@ private void LogSkippedTask(ItemBucket bucket, TaskExecutionMode howToExecuteTas if (!_targetLoggingContext.LoggingService.OnlyLogCriticalEvents) { // Expand the expression for the Log. Since we know the condition evaluated to false, leave unexpandable properties in the condition so as not to cause an error - string expanded = bucket.Expander.ExpandIntoStringAndUnescape(_targetChildInstance.Condition, ExpanderOptions.ExpandAll | ExpanderOptions.LeavePropertiesUnexpandedOnError | ExpanderOptions.Truncate, _targetChildInstance.ConditionLocation); + string expanded = bucket.Expander.ExpandIntoStringAndUnescape(_targetChildInstance.Condition, ExpanderOptions.ExpandAll | ExpanderOptions.LeavePropertiesUnexpandedOnError | ExpanderOptions.Truncate, _targetChildInstance.ConditionLocation, loggingContext: _targetLoggingContext); // Whilst we are within the processing of the task, we haven't actually started executing it, so // our skip task message needs to be in the context of the target. However any errors should be reported diff --git a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs index cdb1553f181..bfbe4b83f41 100644 --- a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs +++ b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs @@ -1174,7 +1174,7 @@ out bool taskParameterSet else { // Expand out all the metadata, properties, and item vectors in the string. - string expandedParameterValue = _batchBucket.Expander.ExpandIntoStringAndUnescape(parameterValue, ExpanderOptions.ExpandAll, parameterLocation); + string expandedParameterValue = _batchBucket.Expander.ExpandIntoStringAndUnescape(parameterValue, ExpanderOptions.ExpandAll, parameterLocation, _targetLoggingContext); if (expandedParameterValue.Length == 0) { diff --git a/src/Build/Definition/Project.cs b/src/Build/Definition/Project.cs index 6fb05e271d0..294b523e07e 100644 --- a/src/Build/Definition/Project.cs +++ b/src/Build/Definition/Project.cs @@ -2924,7 +2924,7 @@ public override bool SetGlobalProperty(string name, string escapedValue) string originalValue = (existing == null) ? String.Empty : ((IProperty)existing).EvaluatedValueEscaped; _data.GlobalPropertiesDictionary.Set(ProjectPropertyInstance.Create(name, escapedValue)); - _data.Properties.Set(ProjectProperty.Create(Owner, name, escapedValue, true /* is global */, false /* may not be reserved name */)); + _data.Properties.Set(ProjectProperty.Create(Owner, name, escapedValue, isGlobalProperty: true, mayBeReserved: false, loggingContext: null)); ProjectCollection.AfterUpdateLoadedProjectGlobalProperties(Owner); MarkDirty(); @@ -4394,9 +4394,9 @@ public IItemDefinition GetItemDefinition(string itemType) /// /// Sets a property which is not derived from Xml. /// - public ProjectProperty SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, bool isEnvironmentVariable = false) + public ProjectProperty SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, bool isEnvironmentVariable = false, BackEnd.Logging.LoggingContext loggingContext = null) { - ProjectProperty property = ProjectProperty.Create(Project, name, evaluatedValueEscaped, isGlobalProperty, mayBeReserved); + ProjectProperty property = ProjectProperty.Create(Project, name, evaluatedValueEscaped, isGlobalProperty, mayBeReserved, loggingContext); Properties.Set(property); AddToAllEvaluatedPropertiesList(property); diff --git a/src/Build/Definition/ProjectProperty.cs b/src/Build/Definition/ProjectProperty.cs index b3e808c3c9a..157fc4366ec 100644 --- a/src/Build/Definition/ProjectProperty.cs +++ b/src/Build/Definition/ProjectProperty.cs @@ -3,8 +3,10 @@ using System; using System.Diagnostics; +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Collections; using Microsoft.Build.Construction; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames; @@ -94,7 +96,18 @@ public string EvaluatedValue string IProperty.EvaluatedValueEscaped { [DebuggerStepThrough] - get => EvaluatedValueEscapedInternal; + get + { + if (this is EnvironmentDerivedProjectProperty environmentProperty && environmentProperty.loggingContext is { IsValid: true } loggingContext && !environmentProperty._loggedEnvProperty) + { + EnvironmentVariableReadEventArgs args = new(Name, EvaluatedValueEscapedInternal); + args.BuildEventContext = loggingContext.BuildEventContext; + loggingContext.LogBuildEvent(args); + environmentProperty._loggedEnvProperty = true; + } + + return EvaluatedValueEscapedInternal; + } } /// @@ -237,9 +250,10 @@ bool IEquatable.Equals(ProjectProperty other) /// This is ONLY to be used by the Evaluator (and Project.SetGlobalProperty) and ONLY for Global, Environment, and Built-in properties. /// All other properties originate in XML, and should have a backing XML object. /// - internal static ProjectProperty Create(Project project, string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved) + internal static ProjectProperty Create(Project project, string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, LoggingContext loggingContext = null) { - return new ProjectPropertyNotXmlBacked(project, name, evaluatedValueEscaped, isGlobalProperty, mayBeReserved); + return !isGlobalProperty && !mayBeReserved ? new EnvironmentDerivedProjectProperty(project, name, evaluatedValueEscaped, isGlobalProperty, mayBeReserved, loggingContext) : + new ProjectPropertyNotXmlBacked(project, name, evaluatedValueEscaped, isGlobalProperty, mayBeReserved); } /// @@ -628,5 +642,18 @@ public override bool IsImported get { return false; } } } + + private class EnvironmentDerivedProjectProperty : ProjectPropertyNotXmlBacked + { + internal bool _loggedEnvProperty = false; + internal LoggingContext loggingContext; + + internal EnvironmentDerivedProjectProperty( + Project project, string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, LoggingContext loggingContext) + : base(project, name, evaluatedValueEscaped, isGlobalProperty, mayBeReserved) + { + this.loggingContext = loggingContext; + } + } } } diff --git a/src/Build/Evaluation/ConditionEvaluator.cs b/src/Build/Evaluation/ConditionEvaluator.cs index 31dd956ed2e..183ce5117d6 100644 --- a/src/Build/Evaluation/ConditionEvaluator.cs +++ b/src/Build/Evaluation/ConditionEvaluator.cs @@ -17,6 +17,7 @@ namespace Microsoft.Build.Evaluation using ElementLocation = Microsoft.Build.Construction.ElementLocation; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; + using Microsoft.Build.BackEnd.Logging; internal static class ConditionEvaluator { @@ -181,7 +182,8 @@ internal static bool EvaluateCondition ILoggingService loggingServices, BuildEventContext buildEventContext, IFileSystem fileSystem, - ProjectRootElementCacheBase projectRootElementCache = null) + ProjectRootElementCacheBase projectRootElementCache = null, + LoggingContext loggingContext = null) where P : class, IProperty where I : class, IItem { @@ -196,7 +198,8 @@ internal static bool EvaluateCondition loggingServices, buildEventContext, fileSystem, - projectRootElementCache); + projectRootElementCache, + loggingContext); } /// @@ -218,7 +221,8 @@ internal static bool EvaluateConditionCollectingConditionedProperties ILoggingService loggingServices, BuildEventContext buildEventContext, IFileSystem fileSystem, - ProjectRootElementCacheBase projectRootElementCache = null + ProjectRootElementCacheBase projectRootElementCache = null, + LoggingContext loggingContext = null ) where P : class, IProperty where I : class, IItem @@ -279,7 +283,7 @@ internal static bool EvaluateConditionCollectingConditionedProperties { try { - result = parsedExpression.Evaluate(state); + result = parsedExpression.Evaluate(state, loggingContext); } finally { @@ -353,7 +357,7 @@ internal interface IConditionEvaluationState /// May return null if the expression would expand to non-empty and it broke out early. /// Otherwise, returns the correctly expanded expression. /// - string ExpandIntoStringBreakEarly(string expression); + string ExpandIntoStringBreakEarly(string expression, LoggingContext loggingContext = null); /// /// Expands the specified expression into a list of TaskItem's. @@ -363,7 +367,7 @@ internal interface IConditionEvaluationState /// /// Expands the specified expression into a string. /// - string ExpandIntoString(string expression); + string ExpandIntoString(string expression, LoggingContext loggingContext = null); /// /// PRE cache @@ -440,11 +444,11 @@ internal ConditionEvaluationState /// May return null if the expression would expand to non-empty and it broke out early. /// Otherwise, returns the correctly expanded expression. /// - public string ExpandIntoStringBreakEarly(string expression) + public string ExpandIntoStringBreakEarly(string expression, LoggingContext loggingContext = null) { var originalValue = _expander.WarnForUninitializedProperties; - expression = _expander.ExpandIntoStringAndUnescape(expression, _expanderOptions | ExpanderOptions.BreakOnNotEmpty, ElementLocation); + expression = _expander.ExpandIntoStringAndUnescape(expression, _expanderOptions | ExpanderOptions.BreakOnNotEmpty, ElementLocation, loggingContext); _expander.WarnForUninitializedProperties = originalValue; @@ -471,12 +475,13 @@ public IList ExpandIntoTaskItems(string expression) /// Expands the specified expression into a string. /// /// The expression to expand. + /// /// The expanded string. - public string ExpandIntoString(string expression) + public string ExpandIntoString(string expression, LoggingContext loggingContext = null) { var originalValue = _expander.WarnForUninitializedProperties; - expression = _expander.ExpandIntoStringAndUnescape(expression, _expanderOptions, ElementLocation); + expression = _expander.ExpandIntoStringAndUnescape(expression, _expanderOptions, ElementLocation, loggingContext); _expander.WarnForUninitializedProperties = originalValue; diff --git a/src/Build/Evaluation/Conditionals/AndExpressionNode.cs b/src/Build/Evaluation/Conditionals/AndExpressionNode.cs index 94513436e9c..47481c625c3 100644 --- a/src/Build/Evaluation/Conditionals/AndExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/AndExpressionNode.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; using System.Diagnostics; @@ -18,9 +19,9 @@ internal sealed class AndExpressionNode : OperatorExpressionNode /// /// Evaluate as boolean /// - internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state) + internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { - if (!LeftChild.TryBoolEvaluate(state, out bool leftBool)) + if (!LeftChild.TryBoolEvaluate(state, out bool leftBool, loggingContext)) { ProjectErrorUtilities.ThrowInvalidProject( state.ElementLocation, @@ -37,7 +38,7 @@ internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState } else { - if (!RightChild.TryBoolEvaluate(state, out bool rightBool)) + if (!RightChild.TryBoolEvaluate(state, out bool rightBool, loggingContext)) { ProjectErrorUtilities.ThrowInvalidProject( state.ElementLocation, diff --git a/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs b/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs index e0b57181361..4ec222243fd 100644 --- a/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; @@ -29,7 +30,7 @@ internal FunctionCallExpressionNode(string functionName, List /// Evaluate node as boolean /// - internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state) + internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { if (String.Equals(_functionName, "exists", StringComparison.OrdinalIgnoreCase)) { diff --git a/src/Build/Evaluation/Conditionals/GenericExpressionNode.cs b/src/Build/Evaluation/Conditionals/GenericExpressionNode.cs index d6007133404..50efb172cf7 100644 --- a/src/Build/Evaluation/Conditionals/GenericExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/GenericExpressionNode.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; - +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable @@ -14,9 +14,9 @@ namespace Microsoft.Build.Evaluation /// internal abstract class GenericExpressionNode { - internal abstract bool TryBoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, out bool result); - internal abstract bool TryNumericEvaluate(ConditionEvaluator.IConditionEvaluationState state, out double result); - internal abstract bool TryVersionEvaluate(ConditionEvaluator.IConditionEvaluationState state, out Version result); + internal abstract bool TryBoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, out bool result, LoggingContext loggingContext = null); + internal abstract bool TryNumericEvaluate(ConditionEvaluator.IConditionEvaluationState state, out double result, LoggingContext loggingContext = null); + internal abstract bool TryVersionEvaluate(ConditionEvaluator.IConditionEvaluationState state, out Version result, LoggingContext loggingContext = null); /// /// Returns true if this node evaluates to an empty string, @@ -25,7 +25,7 @@ internal abstract class GenericExpressionNode /// to empty than to fully evaluate it.) /// Implementations should cache the result so that calls after the first are free. /// - internal virtual bool EvaluatesToEmpty(ConditionEvaluator.IConditionEvaluationState state) + internal virtual bool EvaluatesToEmpty(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { return false; } @@ -34,7 +34,7 @@ internal virtual bool EvaluatesToEmpty(ConditionEvaluator.IConditionEvaluationSt /// Value after any item and property expressions are expanded /// /// - internal abstract string GetExpandedValue(ConditionEvaluator.IConditionEvaluationState state); + internal abstract string GetExpandedValue(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null); /// /// Value before any item and property expressions are expanded @@ -52,16 +52,17 @@ internal virtual bool EvaluatesToEmpty(ConditionEvaluator.IConditionEvaluationSt /// The main evaluate entry point for expression trees /// /// + /// /// - internal bool Evaluate(ConditionEvaluator.IConditionEvaluationState state) + internal bool Evaluate(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { - if (!TryBoolEvaluate(state, out bool boolValue)) + if (!TryBoolEvaluate(state, out bool boolValue, loggingContext)) { ProjectErrorUtilities.ThrowInvalidProject( state.ElementLocation, "ConditionNotBooleanDetail", state.Condition, - GetExpandedValue(state)); + GetExpandedValue(state, loggingContext)); } return boolValue; diff --git a/src/Build/Evaluation/Conditionals/MultipleComparisonExpressionNode.cs b/src/Build/Evaluation/Conditionals/MultipleComparisonExpressionNode.cs index 65e23c7718f..1ef13748bdc 100644 --- a/src/Build/Evaluation/Conditionals/MultipleComparisonExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/MultipleComparisonExpressionNode.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable @@ -36,7 +37,7 @@ internal abstract class MultipleComparisonNode : OperatorExpressionNode /// Order in which comparisons are attempted is numeric, boolean, then string. /// Updates conditioned properties table. /// - internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state) + internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext) { ProjectErrorUtilities.VerifyThrowInvalidProject (LeftChild != null && RightChild != null, @@ -50,8 +51,8 @@ internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState // and we know which do, then we already have enough information to evaluate this expression. // That means we don't have to fully expand a condition like " '@(X)' == '' " // which is a performance advantage if @(X) is a huge item list. - bool leftEmpty = LeftChild.EvaluatesToEmpty(state); - bool rightEmpty = RightChild.EvaluatesToEmpty(state); + bool leftEmpty = LeftChild.EvaluatesToEmpty(state, loggingContext); + bool rightEmpty = RightChild.EvaluatesToEmpty(state, loggingContext); if (leftEmpty || rightEmpty) { UpdateConditionedProperties(state); @@ -68,13 +69,13 @@ internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState // is 17.0). return Compare(leftNumericValue, rightNumericValue); } - else if (LeftChild.TryBoolEvaluate(state, out bool leftBoolValue) && RightChild.TryBoolEvaluate(state, out bool rightBoolValue)) + else if (LeftChild.TryBoolEvaluate(state, out bool leftBoolValue, loggingContext) && RightChild.TryBoolEvaluate(state, out bool rightBoolValue, loggingContext)) { return Compare(leftBoolValue, rightBoolValue); } - string leftExpandedValue = LeftChild.GetExpandedValue(state); - string rightExpandedValue = RightChild.GetExpandedValue(state); + string leftExpandedValue = LeftChild.GetExpandedValue(state, loggingContext); + string rightExpandedValue = RightChild.GetExpandedValue(state, loggingContext); ProjectErrorUtilities.VerifyThrowInvalidProject (leftExpandedValue != null && rightExpandedValue != null, diff --git a/src/Build/Evaluation/Conditionals/NotExpressionNode.cs b/src/Build/Evaluation/Conditionals/NotExpressionNode.cs index e5b00ac781a..1d2e1d6311e 100644 --- a/src/Build/Evaluation/Conditionals/NotExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/NotExpressionNode.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; using System.Diagnostics; @@ -18,9 +19,9 @@ internal sealed class NotExpressionNode : OperatorExpressionNode /// /// Evaluate as boolean /// - internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state) + internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { - if (!LeftChild.TryBoolEvaluate(state, out bool boolValue)) + if (!LeftChild.TryBoolEvaluate(state, out bool boolValue, loggingContext)) { ProjectErrorUtilities.ThrowInvalidProject( state.ElementLocation, @@ -44,9 +45,9 @@ internal override string GetUnexpandedValue(ConditionEvaluator.IConditionEvaluat /// /// Returns expanded value with '!' prepended. Useful for error messages. /// - internal override string GetExpandedValue(ConditionEvaluator.IConditionEvaluationState state) + internal override string GetExpandedValue(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { - return "!" + LeftChild.GetExpandedValue(state); + return "!" + LeftChild.GetExpandedValue(state, loggingContext); } internal override string DebuggerDisplay => $"(not {LeftChild.DebuggerDisplay})"; diff --git a/src/Build/Evaluation/Conditionals/NumericComparisonExpressionNode.cs b/src/Build/Evaluation/Conditionals/NumericComparisonExpressionNode.cs index 02c242ceef5..f161ad5d18b 100644 --- a/src/Build/Evaluation/Conditionals/NumericComparisonExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/NumericComparisonExpressionNode.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; - +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable @@ -38,7 +38,7 @@ internal abstract class NumericComparisonExpressionNode : OperatorExpressionNode /// /// Evaluate as boolean /// - internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state) + internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { bool isLeftNum = LeftChild.TryNumericEvaluate(state, out double leftNum); bool isLeftVersion = LeftChild.TryVersionEvaluate(state, out Version leftVersion); @@ -53,7 +53,7 @@ internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state.Condition, /* helpfully display unexpanded token and expanded result in error message */ isLeftNum ? RightChild.GetUnexpandedValue(state) : LeftChild.GetUnexpandedValue(state), - isLeftNum ? RightChild.GetExpandedValue(state) : LeftChild.GetExpandedValue(state)); + isLeftNum ? RightChild.GetExpandedValue(state, loggingContext) : LeftChild.GetExpandedValue(state, loggingContext)); } return (isLeftNum, isLeftVersion, isRightNum, isRightVersion) switch diff --git a/src/Build/Evaluation/Conditionals/NumericExpressionNode.cs b/src/Build/Evaluation/Conditionals/NumericExpressionNode.cs index 66fa552443d..7725d9962dc 100644 --- a/src/Build/Evaluation/Conditionals/NumericExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/NumericExpressionNode.cs @@ -3,7 +3,7 @@ using System; using System.Diagnostics; - +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable @@ -23,18 +23,18 @@ internal NumericExpressionNode(string value) _value = value; } - internal override bool TryBoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, out bool result) + internal override bool TryBoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, out bool result, LoggingContext loggingContext = null) { result = default; return false; } - internal override bool TryNumericEvaluate(ConditionEvaluator.IConditionEvaluationState state, out double result) + internal override bool TryNumericEvaluate(ConditionEvaluator.IConditionEvaluationState state, out double result, LoggingContext loggingContext = null) { return ConversionUtilities.TryConvertDecimalOrHexToDouble(_value, out result); } - internal override bool TryVersionEvaluate(ConditionEvaluator.IConditionEvaluationState state, out Version result) + internal override bool TryVersionEvaluate(ConditionEvaluator.IConditionEvaluationState state, out Version result, LoggingContext loggingContext = null) { return Version.TryParse(_value, out result); } @@ -50,7 +50,7 @@ internal override string GetUnexpandedValue(ConditionEvaluator.IConditionEvaluat /// /// Get the expanded value /// - internal override string GetExpandedValue(ConditionEvaluator.IConditionEvaluationState state) + internal override string GetExpandedValue(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { return _value; } diff --git a/src/Build/Evaluation/Conditionals/OperatorExpressionNode.cs b/src/Build/Evaluation/Conditionals/OperatorExpressionNode.cs index c6e4b4c349c..a3f76ff20ff 100644 --- a/src/Build/Evaluation/Conditionals/OperatorExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/OperatorExpressionNode.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using Microsoft.Build.BackEnd.Logging; #nullable disable @@ -12,21 +13,21 @@ namespace Microsoft.Build.Evaluation /// internal abstract class OperatorExpressionNode : GenericExpressionNode { - internal override bool TryBoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, out bool result) + internal override bool TryBoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, out bool result, LoggingContext loggingContext = null) { - result = BoolEvaluate(state); + result = BoolEvaluate(state, loggingContext); return true; } - internal abstract bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state); + internal abstract bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null); - internal override bool TryNumericEvaluate(ConditionEvaluator.IConditionEvaluationState state, out double result) + internal override bool TryNumericEvaluate(ConditionEvaluator.IConditionEvaluationState state, out double result, LoggingContext loggingContext = null) { result = default; return false; } - internal override bool TryVersionEvaluate(ConditionEvaluator.IConditionEvaluationState state, out Version result) + internal override bool TryVersionEvaluate(ConditionEvaluator.IConditionEvaluationState state, out Version result, LoggingContext loggingContext = null) { result = default; return false; @@ -36,7 +37,7 @@ internal override bool TryVersionEvaluate(ConditionEvaluator.IConditionEvaluatio /// Value after any item and property expressions are expanded /// /// - internal override string GetExpandedValue(ConditionEvaluator.IConditionEvaluationState state) + internal override string GetExpandedValue(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { return null; } diff --git a/src/Build/Evaluation/Conditionals/OrExpressionNode.cs b/src/Build/Evaluation/Conditionals/OrExpressionNode.cs index 250e8c9602c..fa1816c2e1d 100644 --- a/src/Build/Evaluation/Conditionals/OrExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/OrExpressionNode.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; using System.Diagnostics; @@ -18,15 +19,15 @@ internal sealed class OrExpressionNode : OperatorExpressionNode /// /// Evaluate as boolean /// - internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state) + internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { - if (!LeftChild.TryBoolEvaluate(state, out bool leftBool)) + if (!LeftChild.TryBoolEvaluate(state, out bool leftBool, loggingContext)) { ProjectErrorUtilities.ThrowInvalidProject( state.ElementLocation, "ExpressionDoesNotEvaluateToBoolean", LeftChild.GetUnexpandedValue(state), - LeftChild.GetExpandedValue(state), + LeftChild.GetExpandedValue(state, loggingContext), state.Condition); } @@ -37,7 +38,7 @@ internal override bool BoolEvaluate(ConditionEvaluator.IConditionEvaluationState } else { - if (!RightChild.TryBoolEvaluate(state, out bool rightBool)) + if (!RightChild.TryBoolEvaluate(state, out bool rightBool, loggingContext)) { ProjectErrorUtilities.ThrowInvalidProject( state.ElementLocation, diff --git a/src/Build/Evaluation/Conditionals/StringExpressionNode.cs b/src/Build/Evaluation/Conditionals/StringExpressionNode.cs index 7017b0b5023..43dd324e7a8 100644 --- a/src/Build/Evaluation/Conditionals/StringExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/StringExpressionNode.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable @@ -30,34 +31,34 @@ internal StringExpressionNode(string value, bool expandable) _expandable = expandable; } - internal override bool TryBoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, out bool result) + internal override bool TryBoolEvaluate(ConditionEvaluator.IConditionEvaluationState state, out bool result, LoggingContext loggingContext = null) { - return ConversionUtilities.TryConvertStringToBool(GetExpandedValue(state), out result); + return ConversionUtilities.TryConvertStringToBool(GetExpandedValue(state, loggingContext), out result); } - internal override bool TryNumericEvaluate(ConditionEvaluator.IConditionEvaluationState state, out double result) + internal override bool TryNumericEvaluate(ConditionEvaluator.IConditionEvaluationState state, out double result, LoggingContext loggingContext = null) { - if (ShouldBeTreatedAsVisualStudioVersion(state)) + if (ShouldBeTreatedAsVisualStudioVersion(state, loggingContext)) { result = ConversionUtilities.ConvertDecimalOrHexToDouble(MSBuildConstants.CurrentVisualStudioVersion); return true; } else { - return ConversionUtilities.TryConvertDecimalOrHexToDouble(GetExpandedValue(state), out result); + return ConversionUtilities.TryConvertDecimalOrHexToDouble(GetExpandedValue(state, loggingContext), out result); } } - internal override bool TryVersionEvaluate(ConditionEvaluator.IConditionEvaluationState state, out Version result) + internal override bool TryVersionEvaluate(ConditionEvaluator.IConditionEvaluationState state, out Version result, LoggingContext loggingContext = null) { - if (ShouldBeTreatedAsVisualStudioVersion(state)) + if (ShouldBeTreatedAsVisualStudioVersion(state, loggingContext)) { result = Version.Parse(MSBuildConstants.CurrentVisualStudioVersion); return true; } else { - return Version.TryParse(GetExpandedValue(state), out result); + return Version.TryParse(GetExpandedValue(state, loggingContext), out result); } } @@ -68,7 +69,7 @@ internal override bool TryVersionEvaluate(ConditionEvaluator.IConditionEvaluatio /// to empty than to fully evaluate it. /// Implementations should cache the result so that calls after the first are free. /// - internal override bool EvaluatesToEmpty(ConditionEvaluator.IConditionEvaluationState state) + internal override bool EvaluatesToEmpty(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { if (_cachedExpandedValue == null) { @@ -93,7 +94,7 @@ internal override bool EvaluatesToEmpty(ConditionEvaluator.IConditionEvaluationS break; } - string expandBreakEarly = state.ExpandIntoStringBreakEarly(_value); + string expandBreakEarly = state.ExpandIntoStringBreakEarly(_value, loggingContext); if (expandBreakEarly == null) { @@ -129,13 +130,13 @@ internal override string GetUnexpandedValue(ConditionEvaluator.IConditionEvaluat /// Value after any item and property expressions are expanded /// /// - internal override string GetExpandedValue(ConditionEvaluator.IConditionEvaluationState state) + internal override string GetExpandedValue(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { if (_cachedExpandedValue == null) { if (_expandable) { - _cachedExpandedValue = state.ExpandIntoString(_value); + _cachedExpandedValue = state.ExpandIntoString(_value, loggingContext); } else { @@ -168,7 +169,7 @@ internal override void ResetState() /// but now cause the project to throw InvalidProjectException when /// ToolsVersion is "Current". https://github.com/dotnet/msbuild/issues/4150 /// - private bool ShouldBeTreatedAsVisualStudioVersion(ConditionEvaluator.IConditionEvaluationState state) + private bool ShouldBeTreatedAsVisualStudioVersion(ConditionEvaluator.IConditionEvaluationState state, LoggingContext loggingContext = null) { if (!_shouldBeTreatedAsVisualStudioVersion.HasValue) { @@ -176,7 +177,7 @@ private bool ShouldBeTreatedAsVisualStudioVersion(ConditionEvaluator.IConditionE // Do this check first, because if it's not (common) we can early-out and the next // expansion will be cheap because this will populate the cached expanded value. - if (string.Equals(GetExpandedValue(state), MSBuildConstants.CurrentToolsVersion, StringComparison.Ordinal)) + if (string.Equals(GetExpandedValue(state, loggingContext), MSBuildConstants.CurrentToolsVersion, StringComparison.Ordinal)) { // and it is just an expansion of MSBuildToolsVersion _shouldBeTreatedAsVisualStudioVersion = string.Equals(_value, "$(MSBuildToolsVersion)", StringComparison.OrdinalIgnoreCase); diff --git a/src/Build/Evaluation/Evaluator.cs b/src/Build/Evaluation/Evaluator.cs index b3154221e9f..ebce24983ad 100644 --- a/src/Build/Evaluation/Evaluator.cs +++ b/src/Build/Evaluation/Evaluator.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using ObjectModel = System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; @@ -25,13 +24,15 @@ using Microsoft.Build.Internal; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; -using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService; -using SdkResult = Microsoft.Build.BackEnd.SdkResolution.SdkResult; -using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; +using static Microsoft.Build.Execution.ProjectPropertyInstance; using Constants = Microsoft.Build.Internal.Constants; using EngineFileUtilities = Microsoft.Build.Internal.EngineFileUtilities; +using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService; +using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; +using ObjectModel = System.Collections.ObjectModel; using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames; using SdkReferencePropertyExpansionMode = Microsoft.Build.Framework.EscapeHatches.SdkReferencePropertyExpansionMode; +using SdkResult = Microsoft.Build.BackEnd.SdkResolution.SdkResult; #nullable disable @@ -811,13 +812,30 @@ private void Evaluate() if (this._evaluationLoggingContext.LoggingService.IncludeEvaluationPropertiesAndItems) { globalProperties = _data.GlobalPropertiesDictionary; - properties = _data.Properties; + properties = Traits.Instance.LogAllEnvironmentVariables ? _data.Properties : FilterOutEnvironmentDerivedProperties(_data.Properties); items = _data.Items; } _evaluationLoggingContext.LogProjectEvaluationFinished(globalProperties, properties, items, _evaluationProfiler.ProfiledResult); } + private IEnumerable FilterOutEnvironmentDerivedProperties(PropertyDictionary

dictionary) + { + List

list = new(dictionary.Count); + foreach (P p in dictionary) + { + if (p is EnvironmentDerivedProjectPropertyInstance || + (p is ProjectProperty pp && pp.IsEnvironmentProperty)) + { + continue; + } + + list.Add(p); + } + + return list; + } + private void CollectProjectCachePlugins() { foreach (var item in _data.GetItems(ItemTypeNames.ProjectCachePlugin)) @@ -1051,8 +1069,8 @@ private void ReadTargetElement(ProjectTargetElement targetElement, LinkedList private void AddBeforeAndAfterTargetMappings(ProjectTargetElement targetElement, Dictionary> activeTargets, Dictionary> targetsWhichRunBeforeByTarget, Dictionary> targetsWhichRunAfterByTarget) { - var beforeTargets = _expander.ExpandIntoStringListLeaveEscaped(targetElement.BeforeTargets, ExpanderOptions.ExpandPropertiesAndItems, targetElement.BeforeTargetsLocation); - var afterTargets = _expander.ExpandIntoStringListLeaveEscaped(targetElement.AfterTargets, ExpanderOptions.ExpandPropertiesAndItems, targetElement.AfterTargetsLocation); + var beforeTargets = _expander.ExpandIntoStringListLeaveEscaped(targetElement.BeforeTargets, ExpanderOptions.ExpandPropertiesAndItems, targetElement.BeforeTargetsLocation, _evaluationLoggingContext); + var afterTargets = _expander.ExpandIntoStringListLeaveEscaped(targetElement.AfterTargets, ExpanderOptions.ExpandPropertiesAndItems, targetElement.AfterTargetsLocation, _evaluationLoggingContext); foreach (string beforeTarget in beforeTargets) { @@ -1197,7 +1215,7 @@ private void AddEnvironmentProperties() { foreach (ProjectPropertyInstance environmentProperty in _environmentProperties) { - _data.SetProperty(environmentProperty.Name, ((IProperty)environmentProperty).EvaluatedValueEscaped, isGlobalProperty: false, mayBeReserved: false, isEnvironmentVariable: true); + _data.SetProperty(environmentProperty.Name, ((IProperty)environmentProperty).EvaluatedValueEscaped, isGlobalProperty: false, mayBeReserved: false, isEnvironmentVariable: true, loggingContext: _evaluationLoggingContext); } } @@ -1298,7 +1316,7 @@ private void EvaluatePropertyElement(ProjectPropertyElement propertyElement) // it is the same as what we are setting the value on. Note: This needs to be set before we expand the property we are currently setting. _expander.UsedUninitializedProperties.CurrentlyEvaluatingPropertyElementName = propertyElement.Name; - string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(propertyElement.Value, ExpanderOptions.ExpandProperties, propertyElement.Location); + string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(propertyElement.Value, ExpanderOptions.ExpandProperties, propertyElement.Location, _evaluationLoggingContext); // If we are going to set a property to a value other than null or empty we need to check to see if it has been used // during evaluation. @@ -2003,7 +2021,7 @@ private LoadImportsResult ExpandAndLoadImportsFromUnescapedImportExpression(stri { imports = null; - string importExpressionEscaped = _expander.ExpandIntoStringLeaveEscaped(unescapedExpression, ExpanderOptions.ExpandProperties, importElement.ProjectLocation); + string importExpressionEscaped = _expander.ExpandIntoStringLeaveEscaped(unescapedExpression, ExpanderOptions.ExpandProperties, importElement.ProjectLocation, _evaluationLoggingContext); ElementLocation importLocationInProject = importElement.Location; if (String.IsNullOrWhiteSpace(importExpressionEscaped)) @@ -2428,7 +2446,8 @@ private bool EvaluateCondition(ProjectElement element, string condition, Expande element.ConditionLocation, _evaluationLoggingContext.LoggingService, _evaluationLoggingContext.BuildEventContext, - _evaluationContext.FileSystem + _evaluationContext.FileSystem, + loggingContext: _evaluationLoggingContext ); return result; diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index 46b24084309..5c60e58b0ed 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -28,6 +28,7 @@ using TaskItemFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.TaskItemFactory; using Microsoft.NET.StringTools; +using Microsoft.Build.BackEnd.Logging; #nullable disable @@ -420,9 +421,9 @@ internal static bool ExpressionContainsItemVector(string expression) /// /// If ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and it broke out early, returns null. Otherwise the result can be trusted. /// - internal string ExpandIntoStringAndUnescape(string expression, ExpanderOptions options, IElementLocation elementLocation) + internal string ExpandIntoStringAndUnescape(string expression, ExpanderOptions options, IElementLocation elementLocation, LoggingContext loggingContext = null) { - string result = ExpandIntoStringLeaveEscaped(expression, options, elementLocation); + string result = ExpandIntoStringLeaveEscaped(expression, options, elementLocation, loggingContext); return (result == null) ? null : EscapingUtilities.UnescapeAll(result); } @@ -434,7 +435,7 @@ internal string ExpandIntoStringAndUnescape(string expression, ExpanderOptions o /// /// If ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and it broke out early, returns null. Otherwise the result can be trusted. /// - internal string ExpandIntoStringLeaveEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation) + internal string ExpandIntoStringLeaveEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation, LoggingContext loggingContext = null) { if (expression.Length == 0) { @@ -444,7 +445,7 @@ internal string ExpandIntoStringLeaveEscaped(string expression, ExpanderOptions ErrorUtilities.VerifyThrowInternalNull(elementLocation, nameof(elementLocation)); string result = MetadataExpander.ExpandMetadataLeaveEscaped(expression, _metadata, options, elementLocation); - result = PropertyExpander

.ExpandPropertiesLeaveEscaped(result, _properties, options, elementLocation, _usedUninitializedProperties, _fileSystem); + result = PropertyExpander

.ExpandPropertiesLeaveEscaped(result, _properties, options, elementLocation, _usedUninitializedProperties, _fileSystem, loggingContext); result = ItemExpander.ExpandItemVectorsIntoString(this, result, _items, options, elementLocation); result = FileUtilities.MaybeAdjustFilePath(result); @@ -474,11 +475,11 @@ internal object ExpandPropertiesLeaveTypedAndEscaped(string expression, Expander /// Use this form when the result is going to be processed further, for example by matching against the file system, /// so literals must be distinguished, and you promise to unescape after that. /// - internal SemiColonTokenizer ExpandIntoStringListLeaveEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation) + internal SemiColonTokenizer ExpandIntoStringListLeaveEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation, LoggingContext loggingContext = null) { ErrorUtilities.VerifyThrow((options & ExpanderOptions.BreakOnNotEmpty) == 0, "not supported"); - return ExpressionShredder.SplitSemiColonSeparatedList(ExpandIntoStringLeaveEscaped(expression, options, elementLocation)); + return ExpressionShredder.SplitSemiColonSeparatedList(ExpandIntoStringLeaveEscaped(expression, options, elementLocation, loggingContext)); } ///

@@ -1078,7 +1079,8 @@ internal static string ExpandPropertiesLeaveEscaped( ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties, - IFileSystem fileSystem) + IFileSystem fileSystem, + LoggingContext loggingContext = null) { return ConvertToString( @@ -1088,7 +1090,8 @@ internal static string ExpandPropertiesLeaveEscaped( options, elementLocation, usedUninitializedProperties, - fileSystem)); + fileSystem, + loggingContext)); } /// @@ -1114,7 +1117,8 @@ internal static object ExpandPropertiesLeaveTypedAndEscaped( ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties, - IFileSystem fileSystem) + IFileSystem fileSystem, + LoggingContext loggingContext = null) { if (((options & ExpanderOptions.ExpandProperties) == 0) || String.IsNullOrEmpty(expression)) { @@ -1229,7 +1233,7 @@ internal static object ExpandPropertiesLeaveTypedAndEscaped( } else // This is a regular property { - propertyValue = LookupProperty(properties, expression, propertyStartIndex + 2, propertyEndIndex - 1, elementLocation, usedUninitializedProperties); + propertyValue = LookupProperty(properties, expression, propertyStartIndex + 2, propertyEndIndex - 1, elementLocation, usedUninitializedProperties, loggingContext); } if (propertyValue != null) @@ -1467,7 +1471,7 @@ private static object LookupProperty(IPropertyProvider properties, string pro /// /// Look up a simple property reference by the name of the property, e.g. "Foo" when expanding $(Foo). /// - private static object LookupProperty(IPropertyProvider properties, string propertyName, int startIndex, int endIndex, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties) + private static object LookupProperty(IPropertyProvider properties, string propertyName, int startIndex, int endIndex, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties, LoggingContext loggingContext = null) { T property = properties.GetProperty(propertyName, startIndex, endIndex); @@ -1512,6 +1516,11 @@ private static object LookupProperty(IPropertyProvider properties, string pro } else { + if (property is ProjectPropertyInstance.EnvironmentDerivedProjectPropertyInstance environmentDerivedProperty) + { + environmentDerivedProperty.loggingContext = loggingContext; + } + propertyValue = property.EvaluatedValueEscaped; } diff --git a/src/Build/Evaluation/IEvaluatorData.cs b/src/Build/Evaluation/IEvaluatorData.cs index cf0b17d457f..497207e7024 100644 --- a/src/Build/Evaluation/IEvaluatorData.cs +++ b/src/Build/Evaluation/IEvaluatorData.cs @@ -267,7 +267,7 @@ List EvaluatedItemElements /// /// Sets a property which does not come from the Xml. /// - P SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, bool isEnvironmentVariable = false); + P SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, bool isEnvironmentVariable = false, BackEnd.Logging.LoggingContext loggingContext = null); /// /// Sets a property which comes from the Xml. diff --git a/src/Build/Evaluation/ItemSpec.cs b/src/Build/Evaluation/ItemSpec.cs index 65c1fd8b486..d7f58c8088a 100644 --- a/src/Build/Evaluation/ItemSpec.cs +++ b/src/Build/Evaluation/ItemSpec.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Globbing; using Microsoft.Build.Internal; using Microsoft.Build.Shared; @@ -156,21 +157,23 @@ private bool InitReferencedItemsIfNecessary() /// The xml location the itemspec comes from /// The directory that the project is in. /// Expand properties before breaking down fragments. Defaults to true + /// Context in which to log public ItemSpec( string itemSpec, Expander expander, IElementLocation itemSpecLocation, string projectDirectory, - bool expandProperties = true) + bool expandProperties = true, + LoggingContext loggingContext = null) { ItemSpecString = itemSpec; Expander = expander; ItemSpecLocation = itemSpecLocation; - Fragments = BuildItemFragments(itemSpecLocation, projectDirectory, expandProperties); + Fragments = BuildItemFragments(itemSpecLocation, projectDirectory, expandProperties, loggingContext); } - private List BuildItemFragments(IElementLocation itemSpecLocation, string projectDirectory, bool expandProperties) + private List BuildItemFragments(IElementLocation itemSpecLocation, string projectDirectory, bool expandProperties, LoggingContext loggingContext) { // Code corresponds to Evaluator.CreateItemsFromInclude var evaluatedItemspecEscaped = ItemSpecString; @@ -186,7 +189,8 @@ private List BuildItemFragments(IElementLocation itemSpecLocat evaluatedItemspecEscaped = Expander.ExpandIntoStringLeaveEscaped( ItemSpecString, ExpanderOptions.ExpandProperties, - itemSpecLocation); + itemSpecLocation, + loggingContext); } var semicolonCount = 0; diff --git a/src/Build/Evaluation/LazyItemEvaluator.EvaluatorData.cs b/src/Build/Evaluation/LazyItemEvaluator.EvaluatorData.cs index 0255a8fbf2d..ecae588cd20 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.EvaluatorData.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.EvaluatorData.cs @@ -9,6 +9,7 @@ using Microsoft.Build.Construction; using Microsoft.Build.Evaluation.Context; using Microsoft.Build.Execution; +using Microsoft.Build.BackEnd.Logging; #nullable disable @@ -307,9 +308,9 @@ public P SetProperty(ProjectPropertyElement propertyElement, string evaluatedVal return _wrappedData.SetProperty(propertyElement, evaluatedValueEscaped); } - public P SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, bool isEnvironmentVariable = false) + public P SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, bool isEnvironmentVariable = false, LoggingContext loggingContext = null) { - return _wrappedData.SetProperty(name, evaluatedValueEscaped, isGlobalProperty, mayBeReserved); + return _wrappedData.SetProperty(name, evaluatedValueEscaped, isGlobalProperty, mayBeReserved, loggingContext: loggingContext); } } } diff --git a/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs b/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs index f78cc28be71..f0c19a5c681 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs @@ -262,7 +262,7 @@ protected void DecorateItemsWithMetadata(IEnumerable itemBa continue; } - string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(metadataElement.Value, metadataExpansionOptions, metadataElement.Location); + string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(metadataElement.Value, metadataExpansionOptions, metadataElement.Location, _lazyEvaluator._loggingContext); evaluatedValue = FileUtilities.MaybeAdjustFilePath(evaluatedValue, metadataElement.ContainingProject.DirectoryPath); metadataTable.SetValue(metadataElement, evaluatedValue); diff --git a/src/Build/Evaluation/LazyItemEvaluator.cs b/src/Build/Evaluation/LazyItemEvaluator.cs index 28fe97350da..871ad820e6a 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.cs @@ -99,7 +99,8 @@ LazyItemEvaluator lazyEvaluator element.ConditionLocation, lazyEvaluator._loggingContext.LoggingService, lazyEvaluator._loggingContext.BuildEventContext, - lazyEvaluator.FileSystem + lazyEvaluator.FileSystem, + loggingContext: lazyEvaluator._loggingContext ); MSBuildEventSource.Log.EvaluateConditionStop(condition, result); @@ -624,7 +625,7 @@ private RemoveOperation BuildRemoveOperation(string rootDirectory, ProjectItemEl private void ProcessItemSpec(string rootDirectory, string itemSpec, IElementLocation itemSpecLocation, OperationBuilder builder) { - builder.ItemSpec = new ItemSpec(itemSpec, _outerExpander, itemSpecLocation, rootDirectory); + builder.ItemSpec = new ItemSpec(itemSpec, _outerExpander, itemSpecLocation, rootDirectory, loggingContext: _loggingContext); foreach (ItemSpecFragment fragment in builder.ItemSpec.Fragments) { @@ -635,7 +636,7 @@ private void ProcessItemSpec(string rootDirectory, string itemSpec, IElementLoca } } - private static IEnumerable GetExpandedMetadataValuesAndConditions(ICollection metadata, Expander expander) + private static IEnumerable GetExpandedMetadataValuesAndConditions(ICollection metadata, Expander expander, LoggingContext loggingContext = null) { // Since we're just attempting to expand properties in order to find referenced items and not expanding metadata, // unexpected errors may occur when evaluating property functions on unexpanded metadata. Just ignore them if that happens. @@ -649,7 +650,8 @@ private static IEnumerable GetExpandedMetadataValuesAndConditions(IColle yield return expander.ExpandIntoStringLeaveEscaped( metadatumElement.Value, expanderOptions, - metadatumElement.Location); + metadatumElement.Location, + loggingContext); yield return expander.ExpandIntoStringLeaveEscaped( metadatumElement.Condition, @@ -664,7 +666,7 @@ private void ProcessMetadataElements(ProjectItemElement itemElement, OperationBu { operationBuilder.Metadata.AddRange(itemElement.Metadata); - var itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(GetExpandedMetadataValuesAndConditions(itemElement.Metadata, _expander)); + var itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(GetExpandedMetadataValuesAndConditions(itemElement.Metadata, _expander, _loggingContext)); if (itemsAndMetadataFound.Items != null) { foreach (var itemType in itemsAndMetadataFound.Items) diff --git a/src/Build/Evaluation/PropertyTrackingEvaluatorDataWrapper.cs b/src/Build/Evaluation/PropertyTrackingEvaluatorDataWrapper.cs index da81aaf21b5..b113e73a6ec 100644 --- a/src/Build/Evaluation/PropertyTrackingEvaluatorDataWrapper.cs +++ b/src/Build/Evaluation/PropertyTrackingEvaluatorDataWrapper.cs @@ -79,10 +79,10 @@ public P GetProperty(string name, int startIndex, int endIndex) /// /// Sets a property which does not come from the Xml. /// - public P SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, bool isEnvironmentVariable = false) + public P SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, bool isEnvironmentVariable = false, BackEnd.Logging.LoggingContext loggingContext = null) { P originalProperty = _wrapped.GetProperty(name); - P newProperty = _wrapped.SetProperty(name, evaluatedValueEscaped, isGlobalProperty, mayBeReserved, isEnvironmentVariable); + P newProperty = _wrapped.SetProperty(name, evaluatedValueEscaped, isGlobalProperty, mayBeReserved, isEnvironmentVariable, loggingContext); this.TrackPropertyWrite( originalProperty, diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index 94344843b96..0237d7f0ad8 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -1475,10 +1475,10 @@ IItemDefinition IEvaluatorData - ProjectPropertyInstance IEvaluatorData.SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, bool isEnvironmentVariable) + ProjectPropertyInstance IEvaluatorData.SetProperty(string name, string evaluatedValueEscaped, bool isGlobalProperty, bool mayBeReserved, bool isEnvironmentVariable, LoggingContext loggingContext) { // Mutability not verified as this is being populated during evaluation - ProjectPropertyInstance property = ProjectPropertyInstance.Create(name, evaluatedValueEscaped, mayBeReserved, _isImmutable); + ProjectPropertyInstance property = ProjectPropertyInstance.Create(name, evaluatedValueEscaped, mayBeReserved, _isImmutable, isEnvironmentVariable, loggingContext); _properties.Set(property); return property; } @@ -2966,7 +2966,7 @@ private void CreatePropertiesSnapshot(ICollection properties, b { // Allow reserved property names, since this is how they are added to the project instance. // The caller has prevented users setting them themselves. - ProjectPropertyInstance instance = ProjectPropertyInstance.Create(property.Name, ((IProperty)property).EvaluatedValueEscaped, true /* MAY be reserved name */, isImmutable); + ProjectPropertyInstance instance = ProjectPropertyInstance.Create(property.Name, ((IProperty)property).EvaluatedValueEscaped, true /* MAY be reserved name */, isImmutable, property.IsEnvironmentProperty); _properties.Set(instance); } } diff --git a/src/Build/Instance/ProjectPropertyInstance.cs b/src/Build/Instance/ProjectPropertyInstance.cs index e935b611067..c6fd6293e5d 100644 --- a/src/Build/Instance/ProjectPropertyInstance.cs +++ b/src/Build/Instance/ProjectPropertyInstance.cs @@ -10,6 +10,8 @@ using Microsoft.Build.BackEnd; using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.Framework; #nullable disable @@ -84,8 +86,21 @@ public string EvaluatedValue /// Setter assumes caller has protected global properties, if necessary. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - string IProperty.EvaluatedValueEscaped => _escapedValue; - + string IProperty.EvaluatedValueEscaped + { + get + { + if (this is EnvironmentDerivedProjectPropertyInstance envProperty && envProperty.loggingContext?.IsValid == true && !envProperty._loggedEnvProperty) + { + EnvironmentVariableReadEventArgs args = new(Name, _escapedValue); + args.BuildEventContext = envProperty.loggingContext.BuildEventContext; + envProperty.loggingContext.LogBuildEvent(args); + envProperty._loggedEnvProperty = true; + } + + return _escapedValue; + } + } /// /// Implementation of IKeyed exposing the property name /// @@ -182,9 +197,9 @@ internal static ProjectPropertyInstance Create(string name, string escapedValue, /// This flags should ONLY be set by the evaluator or by cloning; after the ProjectInstance is created, they must be illegal. /// If name is invalid or reserved, throws ArgumentException. /// - internal static ProjectPropertyInstance Create(string name, string escapedValue, bool mayBeReserved, bool isImmutable) + internal static ProjectPropertyInstance Create(string name, string escapedValue, bool mayBeReserved, bool isImmutable, bool isEnvironmentProperty = false, LoggingContext loggingContext = null) { - return Create(name, escapedValue, mayBeReserved, null, isImmutable); + return Create(name, escapedValue, mayBeReserved, null, isImmutable, isEnvironmentProperty, loggingContext); } /// @@ -212,7 +227,7 @@ internal static ProjectPropertyInstance Create(string name, string escapedValue, /// internal static ProjectPropertyInstance Create(ProjectPropertyInstance that) { - return Create(that._name, that._escapedValue, mayBeReserved: true /* already validated */, isImmutable: that.IsImmutable); + return Create(that._name, that._escapedValue, mayBeReserved: true /* already validated */, isImmutable: that.IsImmutable, that is EnvironmentDerivedProjectPropertyInstance); } /// @@ -221,7 +236,7 @@ internal static ProjectPropertyInstance Create(ProjectPropertyInstance that) /// internal static ProjectPropertyInstance Create(ProjectPropertyInstance that, bool isImmutable) { - return Create(that._name, that._escapedValue, mayBeReserved: true /* already validated */, isImmutable: isImmutable); + return Create(that._name, that._escapedValue, mayBeReserved: true /* already validated */, isImmutable: isImmutable, that is EnvironmentDerivedProjectPropertyInstance); } /// @@ -278,7 +293,7 @@ internal ProjectPropertyElement ToProjectPropertyElement(ProjectElementContainer /// as it should never be needed for any subsequent messages, and is just extra bulk. /// Inherits mutability from project if any. /// - private static ProjectPropertyInstance Create(string name, string escapedValue, bool mayBeReserved, ElementLocation location, bool isImmutable) + private static ProjectPropertyInstance Create(string name, string escapedValue, bool mayBeReserved, ElementLocation location, bool isImmutable, bool isEnvironmentProperty = false, LoggingContext loggingContext = null) { // Does not check immutability as this is only called during build (which is already protected) or evaluation ErrorUtilities.VerifyThrowArgumentNull(escapedValue, nameof(escapedValue)); @@ -295,11 +310,10 @@ private static ProjectPropertyInstance Create(string name, string escapedValue, XmlUtilities.VerifyThrowProjectValidElementName(name, location); } - if (isImmutable) - { - return new ProjectPropertyInstanceImmutable(name, escapedValue); - } - return new ProjectPropertyInstance(name, escapedValue); + ProjectPropertyInstance instance = isEnvironmentProperty ? new EnvironmentDerivedProjectPropertyInstance(name, escapedValue, loggingContext) : + isImmutable ? new ProjectPropertyInstanceImmutable(name, escapedValue) : + new ProjectPropertyInstance(name, escapedValue); + return instance; } /// @@ -327,5 +341,27 @@ internal ProjectPropertyInstanceImmutable(string name, string escapedValue) /// public override bool IsImmutable => true; } + + internal class EnvironmentDerivedProjectPropertyInstance : ProjectPropertyInstance + { + internal EnvironmentDerivedProjectPropertyInstance(string name, string escapedValue, LoggingContext loggingContext) + : base(name, escapedValue) + { + this.loggingContext = loggingContext; + } + + /// + /// Whether this object can be changed. An immutable object cannot be made mutable. + /// + /// + /// The environment is captured at the start of the build, so environment-derived + /// properties can't change. + /// + public override bool IsImmutable => true; + + internal bool _loggedEnvProperty = false; + + internal LoggingContext loggingContext; + } } } diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs index e9db5412a24..1089888ff12 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs @@ -4,18 +4,14 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; -using System.Linq; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; -using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Framework.Profiler; -using Microsoft.Build.Internal; using Microsoft.Build.Shared; #nullable disable @@ -251,7 +247,14 @@ private void Write(BuildStartedEventArgs e) { Write(BinaryLogRecordKind.BuildStarted); WriteBuildEventArgsFields(e); - Write(e.BuildEnvironment); + if (Traits.Instance.LogAllEnvironmentVariables) + { + Write(e.BuildEnvironment); + } + else + { + Write(0); + } } private void Write(BuildFinishedEventArgs e) diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index b0d0c7eb7b3..4c147958cb5 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -1204,16 +1204,21 @@ private void DisplayDeferredStartedEvents(BuildEventContext e) /// private void PrintMessage(BuildMessageEventArgs e, bool lightenText) { - string nonNullMessage; + string nonNullMessage = null; + + if (e is EnvironmentVariableReadEventArgs environmentPropertyReadEventArgs) + { + nonNullMessage = ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("EnvironmentDerivedPropertyRead", environmentPropertyReadEventArgs.EnvironmentVariableName, e.Message); + } // Include file information if present. if (e.File != null) { - nonNullMessage = EventArgsFormatting.FormatEventMessage(e, showProjectFile, FindLogOutputProperties(e)); + nonNullMessage = EventArgsFormatting.FormatEventMessage(e, showProjectFile, FindLogOutputProperties(e), nonNullMessage); } else { - nonNullMessage = e.Message ?? string.Empty; + nonNullMessage ??= e.Message ?? string.Empty; } int prefixAdjustment = 0; diff --git a/src/Build/Logging/SerialConsoleLogger.cs b/src/Build/Logging/SerialConsoleLogger.cs index e677526406b..4404357b05e 100644 --- a/src/Build/Logging/SerialConsoleLogger.cs +++ b/src/Build/Logging/SerialConsoleLogger.cs @@ -511,18 +511,15 @@ public override void MessageHandler(object sender, BuildMessageEventArgs e) setColor(ConsoleColor.DarkGray); } - string nonNullMessage; + string nonNullMessage = e is EnvironmentVariableReadEventArgs environmentDerivedProperty ? + ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("EnvironmentDerivedPropertyRead", environmentDerivedProperty.EnvironmentVariableName, e.Message) + : e.Message ?? String.Empty; // Include file information if present. if (e.File != null) { nonNullMessage = EventArgsFormatting.FormatEventMessage(e, showProjectFile); } - else - { - // null messages are ok -- treat as blank line - nonNullMessage = e.Message ?? String.Empty; - } WriteLinePretty(nonNullMessage); diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 10018e5f8f1..db8651cce57 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -142,6 +142,9 @@ The operation cannot be completed because EndBuild has already been called but existing submissions have not yet completed. + + Property '{0}' with value '{1}' expanded from the environment. + The operation cannot be completed because the submission has already been executed. diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index bc4f28a26e1..295e744c203 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -102,6 +102,11 @@ MSB4257: Zadaný výstupní soubor mezipaměti pro výsledky je prázdný. + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" Číst proměnnou prostředí {0} diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 2d5371cd4e4..354bbd025b6 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -102,6 +102,11 @@ MSB4257: Die angegebene Cachedatei für Ausgabeergebnisse ist leer. + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" Umgebungsvariable "{0}" lesen diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index ae8a6a1e189..0ff85f2cfc1 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -102,6 +102,11 @@ MSB4257: El archivo de caché de resultados de salida especificado está vacío. + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" Leer la variable de entorno "{0}" diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 0ffddfbe011..bd86334a1d6 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -102,6 +102,11 @@ MSB4257: Le fichier cache des résultats de sortie spécifié est vide. + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" Lire la variable d'environnement "{0}" diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 11aec49efc6..b3ffd7d5a8f 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -102,6 +102,11 @@ MSB4257: il file della cache dei risultati di output specificato è vuoto. + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" Legge la variabile di ambiente "{0}" diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 1bf7a2f0d24..20194a207f9 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -102,6 +102,11 @@ MSB4257: 指定された出力結果キャッシュ ファイルは空です。 + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" 環境変数 "{0}" の読み取り diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 8f5d34f5cce..64edef66ee4 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -102,6 +102,11 @@ MSB4257: 지정한 출력 결과 캐시 파일이 비어 있습니다. + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" 환경 변수 "{0}" 읽기 diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index b893482a44e..ef425e1af33 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -102,6 +102,11 @@ MSB4257: Określony plik wyjściowej pamięci podręcznej wyników jest pusty. + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" Odczytaj zmienną środowiskową „{0}” diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index ad14ba37ca1..57edd51b8b6 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -102,6 +102,11 @@ MSB4257: o arquivo de cache do resultado de saída especificado está vazio. + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" Ler a variável de ambiente "{0}" diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 04004fbdfe1..9355b218d8e 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -102,6 +102,11 @@ MSB4257: указанный выходной файл кэша результатов пустой. + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" Чтение переменной среды "{0}" diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 5fc8f54ba4e..439af5cefad 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -102,6 +102,11 @@ MSB4257: Belirtilen çıkış sonucu önbellek dosyası boş. + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" "{0}" ortam değişkenini oku diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 4a9d18a374e..ca550fb5c8d 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -102,6 +102,11 @@ MSB4257: 指定的输出结果缓存文件为空。 + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" 读取环境变量“{0}” diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 214240db01c..f1f0c28e519 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -102,6 +102,11 @@ MSB4257: 指定的輸出結果快取檔案是空的。 + + Property '{0}' with value '{1}' expanded from the environment. + Property '{0}' with value '{1}' expanded from the environment. + + Read environment variable "{0}" 讀取環境變數 "{0}" diff --git a/src/Framework.UnitTests/BuildFinishedEventArgs_Tests.cs b/src/Framework.UnitTests/BuildFinishedEventArgs_Tests.cs index 2bf43e98406..eca867cd1cc 100644 --- a/src/Framework.UnitTests/BuildFinishedEventArgs_Tests.cs +++ b/src/Framework.UnitTests/BuildFinishedEventArgs_Tests.cs @@ -27,7 +27,7 @@ public void EventArgsCtors() buildFinishedEvent = new BuildFinishedEventArgs("{0}", "HelpKeyword", true, new DateTime(), "Message"); buildFinishedEvent = new BuildFinishedEventArgs(null, null, true); buildFinishedEvent = new BuildFinishedEventArgs(null, null, true, new DateTime()); - buildFinishedEvent = new BuildFinishedEventArgs(null, null, true, new DateTime(), null); + buildFinishedEvent = new BuildFinishedEventArgs(null, null, true, new DateTime(), messageArgs: null); } /// diff --git a/src/Framework/Traits.cs b/src/Framework/Traits.cs index cf60eb140c9..e9f5083db68 100644 --- a/src/Framework/Traits.cs +++ b/src/Framework/Traits.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using Microsoft.Build.Framework; #nullable disable @@ -92,6 +91,15 @@ public Traits() /// public readonly bool LogPropertyFunctionsRequiringReflection = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildLogPropertyFunctionsRequiringReflection")); + /// + /// Log all environment variables whether or not they are used in a build in the binary log. + /// + public readonly bool LogAllEnvironmentVariables = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGALLENVIRONMENTVARIABLES")) +#if !TASKHOST + && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_4) +#endif + ; + /// /// Log property tracking information. /// diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index beca5404f56..ec7f04055ae 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -1,4 +1,4 @@ - + @@ -39,6 +39,10 @@ BuildEnvironmentHelper.cs + + + EnvironmentVariableReadEventArgs.cs + BuildEnvironmentState.cs diff --git a/src/Shared/EnvironmentUtilities.cs b/src/Shared/EnvironmentUtilities.cs index e9e0482619f..01cf349f2f8 100644 --- a/src/Shared/EnvironmentUtilities.cs +++ b/src/Shared/EnvironmentUtilities.cs @@ -4,8 +4,6 @@ using System; using System.Runtime.InteropServices; -#nullable disable - namespace Microsoft.Build.Shared { internal static partial class EnvironmentUtilities diff --git a/src/Shared/EventArgsFormatting.cs b/src/Shared/EventArgsFormatting.cs index e3bcdf9fcba..7c0db803f70 100644 --- a/src/Shared/EventArgsFormatting.cs +++ b/src/Shared/EventArgsFormatting.cs @@ -53,10 +53,11 @@ internal static string FormatEventMessage(BuildWarningEventArgs e, bool showProj /// Message to format /// true to show the project file which issued the event, otherwise false. /// Properties to Print along with message + /// The complete message (including property name) for an environment-derived property /// The formatted message string. - internal static string FormatEventMessage(BuildMessageEventArgs e, bool showProjectFile, string projectConfigurationDescription) + internal static string FormatEventMessage(BuildMessageEventArgs e, bool showProjectFile, string projectConfigurationDescription, string nonNullMessage = null) { - return FormatEventMessage("message", e.Subcategory, e.Message, + return FormatEventMessage("message", e.Subcategory, nonNullMessage ?? e.Message, e.Code, e.File, showProjectFile ? e.ProjectFile : null, e.LineNumber, e.EndLineNumber, e.ColumnNumber, e.EndColumnNumber, e.ThreadId, projectConfigurationDescription); } @@ -144,13 +145,14 @@ internal static string FormatEventMessage(BuildMessageEventArgs e) /// /// Message to format /// Show project file or not + /// For an EnvironmentVariableReadEventArgs, adds an explanatory note and the name of the variable. /// The formatted message string. - internal static string FormatEventMessage(BuildMessageEventArgs e, bool showProjectFile) + internal static string FormatEventMessage(BuildMessageEventArgs e, bool showProjectFile, string nonNullMessage = null) { ErrorUtilities.VerifyThrowArgumentNull(e, nameof(e)); // "message" should not be localized - return FormatEventMessage("message", e.Subcategory, e.Message, + return FormatEventMessage("message", e.Subcategory, nonNullMessage ?? e.Message, e.Code, e.File, showProjectFile ? e.ProjectFile : null, e.LineNumber, e.EndLineNumber, e.ColumnNumber, e.EndColumnNumber, e.ThreadId, null); } diff --git a/src/Shared/LogMessagePacketBase.cs b/src/Shared/LogMessagePacketBase.cs index 7e592d7513d..bd010ed294f 100644 --- a/src/Shared/LogMessagePacketBase.cs +++ b/src/Shared/LogMessagePacketBase.cs @@ -130,6 +130,11 @@ internal enum LoggingEventType : int /// Event is a TelemetryEventArgs /// Telemetry = 18, + + /// + /// Event is an EnvironmentVariableReadEventArgs + /// + EnvironmentVariableReadEvent = 19, } #endregion @@ -323,7 +328,8 @@ internal void WriteToStream(ITranslator translator) #if !TASKHOST && !MSBUILDENTRYPOINTEXE if (_buildEvent is ProjectEvaluationStartedEventArgs || - _buildEvent is ProjectEvaluationFinishedEventArgs) + _buildEvent is ProjectEvaluationFinishedEventArgs || + _buildEvent is EnvironmentVariableReadEventArgs) { // switch to serialization methods that we provide in this file // and don't use the WriteToStream inherited from LazyFormattedBuildEventArgs @@ -403,6 +409,7 @@ internal void ReadFromStream(ITranslator translator) else { _buildEvent = ReadEventFromStream(_eventType, translator); + ErrorUtilities.VerifyThrow(_buildEvent is not null, "Not Supported LoggingEventType {0}", _eventType.ToString()); } } else @@ -509,6 +516,7 @@ private BuildEventArgs GetBuildEventArgFromId() LoggingEventType.TaskStartedEvent => new TaskStartedEventArgs(null, null, null, null, null), LoggingEventType.TaskFinishedEvent => new TaskFinishedEventArgs(null, null, null, null, null, false), LoggingEventType.TaskCommandLineEvent => new TaskCommandLineEventArgs(null, null, MessageImportance.Normal), + LoggingEventType.EnvironmentVariableReadEvent => new EnvironmentVariableReadEventArgs(), #if !TASKHOST // MSBuildTaskHost is targeting Microsoft.Build.Framework.dll 3.5 LoggingEventType.TaskParameterEvent => new TaskParameterEventArgs(0, null, null, true, default), LoggingEventType.ProjectEvaluationStartedEvent => new ProjectEvaluationStartedEventArgs(), @@ -607,6 +615,10 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) { return LoggingEventType.BuildErrorEvent; } + else if (eventType == typeof(EnvironmentVariableReadEventArgs)) + { + return LoggingEventType.EnvironmentVariableReadEvent; + } else { return LoggingEventType.CustomEvent; @@ -661,12 +673,29 @@ private void WriteEventToStream(BuildEventArgs buildEvent, LoggingEventType even case LoggingEventType.ProjectFinishedEvent: WriteExternalProjectFinishedEventToStream((ExternalProjectFinishedEventArgs)buildEvent, translator); break; + case LoggingEventType.EnvironmentVariableReadEvent: + WriteEnvironmentVariableReadEventArgs((EnvironmentVariableReadEventArgs)buildEvent, translator); + break; default: ErrorUtilities.ThrowInternalError("Not Supported LoggingEventType {0}", eventType.ToString()); break; } } + /// + /// Serializes EnvironmentVariableRead Event argument to the stream. Does not work properly on TaskHosts due to BuildEventContext serialization not being + /// enabled on TaskHosts, but that shouldn't matter, as this should never be called from a TaskHost anyway. + /// + private void WriteEnvironmentVariableReadEventArgs(EnvironmentVariableReadEventArgs environmentVariableReadEventArgs, ITranslator translator) + { + string name = environmentVariableReadEventArgs.EnvironmentVariableName; + translator.Translate(ref name); + BuildEventContext context = environmentVariableReadEventArgs.BuildEventContext; +#if !CLR2COMPATIBILITY + translator.Translate(ref context); +#endif + } + /// /// Serialize ExternalProjectFinished Event Argument to the stream /// @@ -1001,33 +1030,33 @@ private BuildEventArgs ReadEventFromStream(LoggingEventType eventType, ITranslat translator.Translate(ref helpKeyword); translator.Translate(ref senderName); - BuildEventArgs buildEvent = null; - switch (eventType) + return eventType switch { - case LoggingEventType.TaskCommandLineEvent: - buildEvent = ReadTaskCommandLineEventFromStream(translator, message, helpKeyword, senderName); - break; - case LoggingEventType.BuildErrorEvent: - buildEvent = ReadTaskBuildErrorEventFromStream(translator, message, helpKeyword, senderName); - break; - case LoggingEventType.ProjectStartedEvent: - buildEvent = ReadExternalProjectStartedEventFromStream(translator, message, helpKeyword, senderName); - break; - case LoggingEventType.ProjectFinishedEvent: - buildEvent = ReadExternalProjectFinishedEventFromStream(translator, message, helpKeyword, senderName); - break; - case LoggingEventType.BuildMessageEvent: - buildEvent = ReadBuildMessageEventFromStream(translator, message, helpKeyword, senderName); - break; - case LoggingEventType.BuildWarningEvent: - buildEvent = ReadBuildWarningEventFromStream(translator, message, helpKeyword, senderName); - break; - default: - ErrorUtilities.ThrowInternalError("Not Supported LoggingEventType {0}", eventType.ToString()); - break; - } + LoggingEventType.TaskCommandLineEvent => ReadTaskCommandLineEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.BuildErrorEvent => ReadTaskBuildErrorEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.ProjectStartedEvent => ReadExternalProjectStartedEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.ProjectFinishedEvent => ReadExternalProjectFinishedEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.BuildMessageEvent => ReadBuildMessageEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.BuildWarningEvent => ReadBuildWarningEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.EnvironmentVariableReadEvent => ReadEnvironmentVariableReadEventFromStream(translator, message, helpKeyword, senderName), + _ => null, + }; + } - return buildEvent; + /// + /// Read and reconstruct an EnvironmentVariableReadEventArgs from the stream. This message should never be called from a TaskHost, so although the context translation does not work, that's ok. + /// + private EnvironmentVariableReadEventArgs ReadEnvironmentVariableReadEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) + { + string environmentVariableName = null; + translator.Translate(ref environmentVariableName); + BuildEventContext context = null; +#if !CLR2COMPATIBILITY + translator.Translate(ref context); +#endif + EnvironmentVariableReadEventArgs args = new(environmentVariableName, message); + args.BuildEventContext = context; + return args; } /// diff --git a/src/Shared/UnitTests/TestEnvironment.cs b/src/Shared/UnitTests/TestEnvironment.cs index 6ede3f2d7fb..49e86433de5 100644 --- a/src/Shared/UnitTests/TestEnvironment.cs +++ b/src/Shared/UnitTests/TestEnvironment.cs @@ -258,17 +258,6 @@ public TransientTestFile ExpectFile(string extension = ".tmp") return WithTransientTestState(new TransientTestFile(extension, createFile: false, expectedAsOutput: true)); } - /// - /// Create a temp file name under a specific temporary folder. The file is expected to exist when the test completes. - /// - /// Temp folder - /// Extension of the file (defaults to '.tmp') - /// - public TransientTestFile ExpectFile(TransientTestFolder transientTestFolder, string extension = ".tmp") - { - return WithTransientTestState(new TransientTestFile(transientTestFolder.Path, extension, createFile: false, expectedAsOutput: true)); - } - /// /// Creates a test variant used to add a unique temporary folder during a test. Will be deleted when the test /// completes. diff --git a/src/Utilities/MuxLogger.cs b/src/Utilities/MuxLogger.cs index 2629634f436..f9532864e15 100644 --- a/src/Utilities/MuxLogger.cs +++ b/src/Utilities/MuxLogger.cs @@ -862,7 +862,7 @@ private void RaiseProjectStartedEvent(object sender, ProjectStartedEventArgs bui _firstProjectStartedEventContext = buildEvent.BuildEventContext; // We've never seen a project started event, so raise the build started event and save this project started event. - BuildStartedEventArgs startedEvent = new BuildStartedEventArgs(_buildStartedEvent.Message, _buildStartedEvent.HelpKeyword, _buildStartedEvent.BuildEnvironment); + BuildStartedEventArgs startedEvent = new BuildStartedEventArgs(_buildStartedEvent.Message, _buildStartedEvent.HelpKeyword, Traits.Instance.LogAllEnvironmentVariables ? _buildStartedEvent.BuildEnvironment : null); RaiseBuildStartedEvent(sender, startedEvent); }