";
@@ -3104,7 +3104,7 @@ public void GetItemProvenanceWhenIncludeHasIndirectReferences()
-
1;2;3;@(B)
+
1;2;3;@(B)
";
@@ -3130,7 +3130,7 @@ public void GetItemProvenanceWhenIncludeHasIndirectItemReferencesAndOnlyGlobsExi
-
@(B)
+
@(B)
";
@@ -3158,7 +3158,7 @@ public void GetItemProvenanceShouldReturnInconclusiveWhenIndirectPropertyDoesNot
-
+
";
@@ -3918,7 +3918,7 @@ public void ProjectImportedEventFalseCondition()
eventArgs.LineNumber.ShouldBe(6);
eventArgs.ColumnNumber.ShouldBe(3);
- logger.AssertLogContains($"Project \"{import.Project}\" was not imported by \"{pre.FullPath}\" at ({eventArgs.LineNumber},{eventArgs.ColumnNumber}), due to false condition; ( \'$(Something)\' == \'nothing\' ) was evaluated as ( \'\' == \'nothing\' ).");
+ logger.AssertLogContains($"Project \"{import.Project}\" was not imported by \"{pre.FullPath}\" at ({eventArgs.LineNumber},{eventArgs.ColumnNumber}), due to false condition; ( \'$(Something)\' == \'nothing\' ) was evaluated as ( \'\' == \'nothing\' ).");
}
}
}
diff --git a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj
index 18ad711bac4..7c478d2a782 100644
--- a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj
+++ b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj
@@ -1,4 +1,4 @@
-
+
@@ -21,6 +21,7 @@
+
diff --git a/src/Build.OM.UnitTests/NugetRestoreTests.cs b/src/Build.OM.UnitTests/NugetRestoreTests.cs
index 2ba75effddc..93d56ae6aaf 100644
--- a/src/Build.OM.UnitTests/NugetRestoreTests.cs
+++ b/src/Build.OM.UnitTests/NugetRestoreTests.cs
@@ -23,7 +23,7 @@ public NugetRestoreTests(ITestOutputHelper output)
// This NuGet version cannot locate other assemblies when parsing solutions at restore time. This includes localized strings required in debug mode.
// NuGet version 4.1.0 was somewhat arbitrarily chosen. 3.5 breaks with an unrelated error, and 4.8.2 does not fail when a new dependency is introduced. This is a safe middle point.
#if !DEBUG
- [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp | TargetFrameworkMonikers.Mono)]
+ [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)]
[Fact]
public void TestOldNuget()
{
diff --git a/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs b/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs
index 34b73c37ac7..e14afa3919a 100644
--- a/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs
@@ -25,11 +25,11 @@ public class BinaryTranslator_Tests
public void TestSerializationMode()
{
MemoryStream stream = new MemoryStream();
- ITranslator translator = BinaryTranslator.GetReadTranslator(stream, null);
- Assert.Equal(TranslationDirection.ReadFromStream, translator.Mode);
+ using ITranslator readTranslator = BinaryTranslator.GetReadTranslator(stream, null);
+ Assert.Equal(TranslationDirection.ReadFromStream, readTranslator.Mode);
- translator = BinaryTranslator.GetWriteTranslator(stream);
- Assert.Equal(TranslationDirection.WriteToStream, translator.Mode);
+ using ITranslator writeTranslator = BinaryTranslator.GetWriteTranslator(stream);
+ Assert.Equal(TranslationDirection.WriteToStream, writeTranslator.Mode);
}
///
@@ -547,7 +547,6 @@ public void AssemblyNameWithAllFields()
HashAlgorithm = System.Configuration.Assemblies.AssemblyHashAlgorithm.SHA256,
VersionCompatibility = AssemblyVersionCompatibility.SameMachine,
CodeBase = "C:\\src",
- KeyPair = new StrongNameKeyPair(new byte[] { 4, 3, 2, 1 }),
ContentType = AssemblyContentType.WindowsRuntime,
CultureName = "zh-HK",
};
diff --git a/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs b/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
index b09519f9f71..1587123afc0 100644
--- a/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
@@ -8,6 +8,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Threading;
using System.Xml;
@@ -277,10 +278,10 @@ public void VerifyEnvironmentSavedBetweenCalls()
-
+
-
+
");
@@ -311,7 +312,7 @@ public void VerifyEnvironmentSavedBetweenCalls()
///
/// Verify if idle nodes are shutdown when BuildManager.ShutdownAllNodes is evoked.
- /// The final number of nodes has to be less or equal the number of nodes already in
+ /// The final number of nodes has to be less or equal the number of nodes already in
/// the system before this method was called.
///
#if RUNTIME_TYPE_NETCORE
@@ -344,11 +345,11 @@ public void ShutdownNodesAfterParallelBuild(int numberOfParallelProjectsToBuild,
// Generate a theoretically unique directory to put our dummy projects in.
string shutdownProjectDirectory = Path.Combine(Path.GetTempPath(), String.Format(CultureInfo.InvariantCulture, "VSNodeShutdown_{0}_UnitTest", Process.GetCurrentProcess().Id));
- // Create the dummy projects we'll be "building" as our excuse to connect to and shut down
- // all the nodes.
+ // Create the dummy projects we'll be "building" as our excuse to connect to and shut down
+ // all the nodes.
ProjectInstance rootProject = GenerateDummyProjects(shutdownProjectDirectory, numberOfParallelProjectsToBuild, projectCollection);
- // Build the projects.
+ // Build the projects.
var buildParameters = new BuildParameters(projectCollection)
{
OnlyLogCriticalEvents = true,
@@ -362,9 +363,9 @@ public void ShutdownNodesAfterParallelBuild(int numberOfParallelProjectsToBuild,
// Tell the build manager to not disturb process wide state
var requestData = new BuildRequestData(rootProject, new[] { "Build" }, null);
- // Use a separate BuildManager for the node shutdown build, so that we don't have
- // to worry about taking dependencies on whether or not the existing ones have already
- // disappeared.
+ // Use a separate BuildManager for the node shutdown build, so that we don't have
+ // to worry about taking dependencies on whether or not the existing ones have already
+ // disappeared.
var shutdownManager = new BuildManager("IdleNodeShutdown");
shutdownManager.Build(buildParameters, requestData);
@@ -551,7 +552,7 @@ public void RequestedResultsAreSatisfied()
}
///
- /// Make sure when we are doing an in-process build that even if the environment variable MSBUILDFORWARDPROPERTIESFROMCHILD is set that we still
+ /// Make sure when we are doing an in-process build that even if the environment variable MSBUILDFORWARDPROPERTIESFROMCHILD is set that we still
/// get all of the initial properties.
///
[Fact]
@@ -631,7 +632,7 @@ public void InProcMsBuildForwardAllPropertiesFromChild()
}
///
- /// Make sure when we launch a child node and set MsBuildForwardAllPropertiesFromChild that we get all of our properties. This needs to happen
+ /// Make sure when we launch a child node and set MsBuildForwardAllPropertiesFromChild that we get all of our properties. This needs to happen
/// even if the msbuildforwardpropertiesfromchild is set to something.
///
[Fact]
@@ -703,6 +704,11 @@ public void OutOfProcNodeForwardCertainproperties()
_env.SetEnvironmentVariable("MsBuildForwardPropertiesFromChild", "InitialProperty3;IAMNOTREAL");
_env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1");
+ // ProjectEvaluationFinished automatically and always forwards all properties, so we'd
+ // end up with all ~136 properties. Since this test is explicitly testing forwarding specific
+ // properties on ProjectStarted, turn off the new behavior.
+ _env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "0");
+
var project = CreateProject(contents, null, _projectCollection, false);
var data = new BuildRequestData(project.FullPath, new Dictionary(),
MSBuildDefaultToolsVersion, new string[] { }, null);
@@ -835,7 +841,7 @@ public void ForwardNoPropertiesLaunchChildNode()
}
///
- /// We want to pass the toolsets from the parent to the child nodes so that any custom toolsets
+ /// We want to pass the toolsets from the parent to the child nodes so that any custom toolsets
/// defined on the parent are also available on the child nodes for tasks which use the global project
/// collection
///
@@ -858,7 +864,7 @@ public void VerifyCustomToolSetsPropagated()
-
+
");
@@ -1019,7 +1025,7 @@ public void DeferredMessageShouldBeLogged()
}
///
- /// A build with a message, error and warning, verify that
+ /// A build with a message, error and warning, verify that
/// we only get errors, warnings, and project started and finished when OnlyLogCriticalEvents is true
///
[Fact]
@@ -1029,7 +1035,7 @@ public void SimpleBuildWithFailureAndWarningOnlyLogCriticalEventsTrue()
-
+
@@ -1053,7 +1059,7 @@ public void SimpleBuildWithFailureAndWarningOnlyLogCriticalEventsTrue()
}
///
- /// A build with a message, error and warning, verify that
+ /// A build with a message, error and warning, verify that
/// we only get errors, warnings, messages, task and target messages OnlyLogCriticalEvents is false
///
[Fact]
@@ -1063,7 +1069,7 @@ public void SimpleBuildWithFailureAndWarningOnlyLogCriticalEventsFalse()
-
+
@@ -1386,9 +1392,9 @@ public void OverlappingBuildSubmissions()
}
///
- /// If two overlapping submissions are executed against the same project, with at least one
- /// target involved that skipped, make sure that the second one successfully completes
- /// (retrieved from the cache).
+ /// If two overlapping submissions are executed against the same project, with at least one
+ /// target involved that skipped, make sure that the second one successfully completes
+ /// (retrieved from the cache).
///
[Fact]
public void OverlappingIdenticalBuildSubmissions()
@@ -1419,9 +1425,9 @@ public void OverlappingIdenticalBuildSubmissions()
}
///
- /// With two overlapping submissions, the first of which skips a target and the second
- /// of which should not, ensure that the second submission does not, in fact, skip
- /// the target. (E.g. despite the fact that the target results are in the cache already
+ /// With two overlapping submissions, the first of which skips a target and the second
+ /// of which should not, ensure that the second submission does not, in fact, skip
+ /// the target. (E.g. despite the fact that the target results are in the cache already
/// as 'skipped', ensure that we retry execution in case conditions have changed.)
///
[Fact]
@@ -1516,6 +1522,7 @@ public void CancelledBuildWithUnexecutedSubmission()
[Fact(Timeout = 20_000)]
public void CancelledBuild()
{
+ Console.WriteLine("Starting CancelledBuild test that is known to hang.");
string contents = CleanupFileContents(@"
@@ -1524,18 +1531,36 @@ public void CancelledBuild()
");
+
+ BuildParameters parameters = new ()
+ {
+ ShutdownInProcNodeOnBuildFinish = true,
+ Loggers = new ILogger[] { _logger, new MockLogger(printEventsToStdout: true) },
+ EnableNodeReuse = false
+ };
+
BuildRequestData data = GetBuildRequestData(contents, new string[] { }, MSBuildDefaultToolsVersion);
+
+ Console.WriteLine("CancelledBuild: beginning build");
_buildManager.BeginBuild(_parameters);
+ Console.WriteLine("CancelledBuild: build begun");
+
BuildSubmission asyncResult = _buildManager.PendBuildRequest(data);
+ Console.WriteLine("CancelledBuild: pend build returned");
+
asyncResult.ExecuteAsync(null, null);
+ Console.WriteLine("CancelledBuild: ExecuteAsync called");
_buildManager.CancelAllSubmissions();
+ Console.WriteLine("CancelledBuild: submissions cancelled");
+
// This test intermittently hangs. This timeout is designed to prevent that, turning a hang into a failure.
// Todo: Investigate why this test sometimes hangs.
- asyncResult.WaitHandle.WaitOne(TimeSpan.FromSeconds(10));
+ asyncResult.WaitHandle.WaitOne(TimeSpan.FromSeconds(10)).ShouldBeTrue();
asyncResult.IsCompleted.ShouldBeTrue("Failing to complete by this point indicates a hang.");
BuildResult result = asyncResult.BuildResult;
_buildManager.EndBuild();
+ Console.WriteLine("CancelledBuild: build ended");
Assert.Equal(BuildResultCode.Failure, result.OverallResult); // "Build should have failed."
_logger.AssertLogDoesntContain("[errormessage]");
@@ -1619,7 +1644,7 @@ public void CancelledBuildInTaskHostWithDelay20()
///
/// A canceled build which waits for the task to get started before canceling. Because it is a 12.. task, we should
- /// cancel the task and exit out after a short period wherein we wait for the task to exit cleanly.
+ /// cancel the task and exit out after a short period wherein we wait for the task to exit cleanly.
///
[Fact]
public void CancelledBuildWithDelay40()
@@ -1650,7 +1675,7 @@ public void CancelledBuildWithDelay40()
#if FEATURE_TASKHOST
///
/// A canceled build which waits for the task to get started before canceling. Because it is a 12.0 task, we should
- /// cancel the task and exit out after a short period wherein we wait for the task to exit cleanly.
+ /// cancel the task and exit out after a short period wherein we wait for the task to exit cleanly.
///
[Fact]
public void CancelledBuildInTaskHostWithDelay40()
@@ -2122,7 +2147,7 @@ public void Regress239661()
");
-
+
string fileName = _env.CreateFile(".proj").Path;
File.WriteAllText(fileName, contents);
var data = new BuildRequestData(fileName, _projectCollection.GlobalProperties, MSBuildDefaultToolsVersion, new string[0], null);
@@ -2133,10 +2158,10 @@ public void Regress239661()
}
///
- /// Verify that disabling the in-proc node when a project requires it will cause the build to fail, but not crash.
+ /// Verify that disabling the in-proc node when a project requires it will cause the project to build on the out of proc node.
///
[Fact]
- public void Regress239661_NodeUnavailable()
+ public void ExplicitInprocAffinityGetsOverruledByDisableInprocNode()
{
string contents = CleanupFileContents(@"
@@ -2151,14 +2176,15 @@ public void Regress239661_NodeUnavailable()
");
BuildRequestData data = GetBuildRequestData(contents);
+ _env.CreateFile(data.ProjectFullPath, data.ProjectInstance.ToProjectRootElement().RawXml);
_parameters.DisableInProcNode = true;
// Require that this project build on the in-proc node, which will not be available.
data.HostServices.SetNodeAffinity(data.ProjectFullPath, NodeAffinity.InProc);
BuildResult result = _buildManager.Build(_parameters, data);
- Assert.Equal(BuildResultCode.Failure, result.OverallResult);
- _logger.AssertLogDoesntContain("[success]");
- _logger.AssertLogContains("MSB4223");
+ Assert.Equal(BuildResultCode.Success, result.OverallResult);
+ _logger.AssertLogContains("[success]");
+ _logger.AssertLogDoesntContain("MSB4223");
}
///
@@ -2180,9 +2206,9 @@ public void ProjectInstanceTransfersToOOPNode()
-
+
-
+
@@ -2242,7 +2268,7 @@ public void ProjectInstanceLimitedTransferToOOPNode()
unmodifiedoriginal
-
+
@@ -2293,7 +2319,7 @@ public void CacheLifetime()
{
innerBuildCacheDirectory = BuildAndCheckCache(innerBuildManager, new[] { outerBuildCacheDirectory });
- // Force the cache for this build manager (and only this build manager) to be cleared. It should leave
+ // Force the cache for this build manager (and only this build manager) to be cleared. It should leave
// behind the results from the other one.
innerBuildManager.ResetCaches();
}
@@ -2309,9 +2335,9 @@ public void CacheLifetime()
}
///
- /// If there's a P2P that otherwise succeeds, but has an AfterTarget that errors out, the
+ /// If there's a P2P that otherwise succeeds, but has an AfterTarget that errors out, the
/// overall build result -- and thus the return value of the MSBuild task -- should reflect
- /// that failure.
+ /// that failure.
///
[Theory]
[InlineData(false)]
@@ -2325,9 +2351,9 @@ public void FailedAfterTargetInP2PShouldCauseOverallBuildFailure(bool disableInP
-
+
-
+
";
@@ -2357,10 +2383,10 @@ public void FailedAfterTargetInP2PShouldCauseOverallBuildFailure(bool disableInP
}
///
- /// If there's a P2P that otherwise succeeds, but has an AfterTarget that errors out, the
+ /// If there's a P2P that otherwise succeeds, but has an AfterTarget that errors out, the
/// overall build result -- and thus the return value of the MSBuild task -- should reflect
- /// that failure. Specifically tests where there are multiple entrypoint targets with
- /// AfterTargets, only one of which fails.
+ /// that failure. Specifically tests where there are multiple entrypoint targets with
+ /// AfterTargets, only one of which fails.
///
[Theory]
[InlineData(false)]
@@ -2374,9 +2400,9 @@ public void FailedAfterTargetInP2PShouldCauseOverallBuildFailure_MultipleEntrypo
-
+
-
+
";
@@ -2423,9 +2449,9 @@ public void FailedAfterTargetInP2PShouldCauseOverallBuildFailure_MultipleEntrypo
}
///
- /// If there's a P2P that otherwise succeeds, but has an AfterTarget that errors out, the
+ /// If there's a P2P that otherwise succeeds, but has an AfterTarget that errors out, the
/// overall build result -- and thus the return value of the MSBuild task -- should reflect
- /// that failure. This should also be true if the AfterTarget is an AfterTarget of the
+ /// that failure. This should also be true if the AfterTarget is an AfterTarget of the
/// entrypoint target.
///
[Theory]
@@ -2440,9 +2466,9 @@ public void FailedNestedAfterTargetInP2PShouldCauseOverallBuildFailure(bool disa
-
+
-
+
";
@@ -2476,9 +2502,9 @@ public void FailedNestedAfterTargetInP2PShouldCauseOverallBuildFailure(bool disa
}
///
- /// If a project is called into twice, with two different entrypoint targets that
- /// depend on non-overlapping sets of targets, and the first fails, the second
- /// should not inherit that failure if all the targets it calls succeed.
+ /// If a project is called into twice, with two different entrypoint targets that
+ /// depend on non-overlapping sets of targets, and the first fails, the second
+ /// should not inherit that failure if all the targets it calls succeed.
///
[Fact]
public void NonOverlappingEnusingTrypointTargetsShouldNotInfluenceEachOthersResults()
@@ -2489,7 +2515,7 @@ public void NonOverlappingEnusingTrypointTargetsShouldNotInfluenceEachOthersResu
string contentsA = @"
-
+
@@ -2503,8 +2529,8 @@ public void NonOverlappingEnusingTrypointTargetsShouldNotInfluenceEachOthersResu
-
-
+
+
@@ -2524,9 +2550,9 @@ public void NonOverlappingEnusingTrypointTargetsShouldNotInfluenceEachOthersResu
}
///
- /// In a situation where we have two requests calling into the same project, with different entry point
- /// targets, one of which depends on "A;B", the other of which depends on "B", which has a dependency of
- /// its own on "A", that we still properly build.
+ /// In a situation where we have two requests calling into the same project, with different entry point
+ /// targets, one of which depends on "A;B", the other of which depends on "B", which has a dependency of
+ /// its own on "A", that we still properly build.
///
#if RUNTIME_TYPE_NETCORE
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
@@ -2545,7 +2571,7 @@ public void Regress473114()
string contentsA = @"
-
+
@@ -2615,19 +2641,19 @@ public void Regress473114()
}
///
- /// If two requests are made for the same project, and they call in with
- /// just the right timing such that:
+ /// If two requests are made for the same project, and they call in with
+ /// just the right timing such that:
/// - request 1 builds for a while, reaches a P2P, and blocks
- /// - request 2 starts building, skips for a while, reaches the above P2P, and
- /// blocks waiting for request 1's results
+ /// - request 2 starts building, skips for a while, reaches the above P2P, and
+ /// blocks waiting for request 1's results
/// - request 1 resumes building, errors, and exits
/// - request 2 resumes building
- ///
- /// Then request 2 should end up exiting in the same fashion.
- ///
- /// This simple test verifies that if there are two error targets in a row, the
- /// second request will bail out where the first request did, as though it had
- /// executed the target, rather than skipping and continuing.
+ ///
+ /// Then request 2 should end up exiting in the same fashion.
+ ///
+ /// This simple test verifies that if there are two error targets in a row, the
+ /// second request will bail out where the first request did, as though it had
+ /// executed the target, rather than skipping and continuing.
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
@@ -2704,9 +2730,9 @@ public void VerifyMultipleRequestForSameProjectWithErrors_Simple()
/// blocks waiting for request 1's results
/// - request 1 resumes building, errors, and exits
/// - request 2 resumes building
- ///
+ ///
/// Then request 2 should end up exiting in the same fashion.
- ///
+ ///
/// This simple test verifies that if there are two error targets in a row, and the
/// first has a chain of OnError targets, the OnError targets will all execute as
/// expected in the first request, but be skipped by the second (since if it's "skipping
@@ -2824,9 +2850,9 @@ public void VerifyMultipleRequestForSameProjectWithErrors_OnErrorChain()
/// blocks waiting for request 1's results
/// - request 1 resumes building, errors, and exits
/// - request 2 resumes building
- ///
+ ///
/// Then request 2 should end up exiting in the same fashion.
- ///
+ ///
/// This simple test verifies that if there are two error targets in a row, AND
/// they're marked as ContinueOnError=ErrorAndContinue, then we won't bail, but
/// will continue executing (on the first request) or skipping (on the second)
@@ -2908,18 +2934,18 @@ public void VerifyMultipleRequestForSameProjectWithErrors_ErrorAndContinue()
}
///
- /// If two requests are made for the same project, and they call in with
- /// just the right timing such that:
+ /// If two requests are made for the same project, and they call in with
+ /// just the right timing such that:
/// - request 1 builds for a while, reaches a P2P, and blocks
- /// - request 2 starts building, skips for a while, reaches the above P2P, and
- /// blocks waiting for request 1's results
+ /// - request 2 starts building, skips for a while, reaches the above P2P, and
+ /// blocks waiting for request 1's results
/// - request 1 resumes building, errors, and exits
/// - request 2 resumes building
- ///
- /// Then request 2 should end up exiting in the same fashion.
- ///
- /// This test verifies that if the errors are in AfterTargets, we still
- /// exit as though the target that those targets run after has already run.
+ ///
+ /// Then request 2 should end up exiting in the same fashion.
+ ///
+ /// This test verifies that if the errors are in AfterTargets, we still
+ /// exit as though the target that those targets run after has already run.
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
@@ -2989,10 +3015,10 @@ public void VerifyMultipleRequestForSameProjectWithErrors_AfterTargets()
}
///
- /// Related to the two tests above, if two requests are made for the same project, but
- /// for different entry targets, and a target fails in the first request, if the second
- /// request also runs that target, its skip-unsuccessful should behave in the same
- /// way as if the target had actually errored.
+ /// Related to the two tests above, if two requests are made for the same project, but
+ /// for different entry targets, and a target fails in the first request, if the second
+ /// request also runs that target, its skip-unsuccessful should behave in the same
+ /// way as if the target had actually errored.
///
[Fact]
public void VerifyMultipleRequestForSameProjectWithErrors_DifferentEntrypoints()
@@ -3002,7 +3028,7 @@ public void VerifyMultipleRequestForSameProjectWithErrors_DifferentEntrypoints()
string contentsA = @"
-
+ Build
@@ -3119,7 +3145,7 @@ public void TestSimultaneousSubmissionsWithLegacyThreadingData()
}
///
- /// Verify that we can submit multiple simultaneous submissions with
+ /// Verify that we can submit multiple simultaneous submissions with
/// legacy threading mode active and successfully build, and that one of those
/// submissions can P2P to the other.
///
@@ -3200,12 +3226,12 @@ public void TestSimultaneousSubmissionsWithLegacyThreadingData_P2P()
}
///
- /// Verify that we can submit multiple simultaneous submissions with
+ /// Verify that we can submit multiple simultaneous submissions with
/// legacy threading mode active and successfully build, and that one of those
/// submissions can P2P to the other.
- ///
- /// A variation of the above test, where multiple nodes are available, so the
- /// submissions aren't restricted to running strictly serially by the single in-proc
+ ///
+ /// A variation of the above test, where multiple nodes are available, so the
+ /// submissions aren't restricted to running strictly serially by the single in-proc
/// node.
///
#if MONO
@@ -3308,7 +3334,7 @@ public void Regress265010()
-
+
@@ -3319,7 +3345,7 @@ public void Regress265010()
-
+
@@ -3452,13 +3478,13 @@ private static string BuildAndCheckCache(BuildManager localBuildManager, IEnumer
{
string contents = CleanupFileContents(@"
-
+
-
+
-
+
");
@@ -3580,8 +3606,8 @@ private static ProjectInstance GenerateDummyProjects(string shutdownProjectDirec
Directory.CreateDirectory(shutdownProjectDirectory);
// Generate the project. It will have the following format. Setting the AdditionalProperties
- // causes the projects to be built to be separate configs, which allows us to build the same project
- // a bunch of times in parallel.
+ // causes the projects to be built to be separate configs, which allows us to build the same project
+ // a bunch of times in parallel.
//
//
//
@@ -3934,7 +3960,7 @@ public void OutOfProcEvaluationIdsUnique()
/// Regression test for https://github.com/Microsoft/msbuild/issues/3047
///
[Fact]
- [SkipOnTargetFramework(TargetFrameworkMonikers.Mono, "out-of-proc nodes not working on mono yet")]
+ [SkipOnMono("out-of-proc nodes not working on mono yet")]
public void MultiProcReentrantProjectWithCallTargetDoesNotFail()
{
var a =
@@ -4317,5 +4343,52 @@ public void GraphBuildShouldBeAbleToConstructGraphButSkipBuild()
logger.FullLog.ShouldContain("Static graph loaded in");
logger.FullLog.ShouldContain("3 nodes, 2 edges");
}
+
+ ///
+ /// Helper task used by to verify .
+ ///
+ public class LogTaskInputsCheckingTask : Task
+ {
+ public bool ExpectedTaskInputLoggingEnabled { get; set; }
+
+ public override bool Execute()
+ {
+ return Log.IsTaskInputLoggingEnabled == ExpectedTaskInputLoggingEnabled;
+ }
+ }
+
+ [Theory]
+ [InlineData("", false)] // regular task host, input logging disabled
+ [InlineData("", true)] // regular task host, input logging enabled
+#if NETFRAMEWORK // https://github.com/microsoft/msbuild/issues/5158
+ [InlineData("TaskHostFactory", false)] // OOP task host, input logging disabled
+ [InlineData("TaskHostFactory", true)] // OOP task host, input logging enabled
+#endif
+ public void TaskInputLoggingIsExposedToTasks(string taskFactory, bool taskInputLoggingEnabled)
+ {
+ string projectContents = ObjectModelHelpers.CleanupFileContents(@"
+
+
+
+
+
+
+
+");
+
+ _parameters.LogTaskInputs = taskInputLoggingEnabled;
+
+ Project project = CreateProject(projectContents, MSBuildDefaultToolsVersion, _projectCollection, true);
+ ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
+ _buildManager.BeginBuild(_parameters);
+ BuildResult result = _buildManager.BuildRequest(new BuildRequestData(instance, new[] { "target1" }));
+ _buildManager.EndBuild();
+
+ Assert.Equal(BuildResultCode.Success, result.OverallResult);
+ }
}
}
diff --git a/src/Build.UnitTests/BackEnd/BuildRequestConfiguration_Tests.cs b/src/Build.UnitTests/BackEnd/BuildRequestConfiguration_Tests.cs
index 5be64a17d58..eb37f60b029 100644
--- a/src/Build.UnitTests/BackEnd/BuildRequestConfiguration_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/BuildRequestConfiguration_Tests.cs
@@ -26,7 +26,6 @@ public class BuildRequestConfiguration_Tests : IDisposable
public BuildRequestConfiguration_Tests(ITestOutputHelper testOutput)
{
_env = TestEnvironment.Create(testOutput);
- _env.DoNotLaunchDebugger();
}
public void Dispose()
@@ -260,6 +259,51 @@ public void TestTranslation()
Assert.Equal(config, deserializedConfig);
}
+ [Fact]
+ public void TestTranslationWithEntireProjectState()
+ {
+ string projectBody = ObjectModelHelpers.CleanupFileContents(@"
+
+
+ 1
+ 2
+ $(ThreeIn)
+
+
+
+
+");
+
+ Dictionary globalProperties = new (StringComparer.OrdinalIgnoreCase);
+ globalProperties["ThreeIn"] = "3";
+
+ Project project = new Project(
+ XmlReader.Create(new StringReader(projectBody)),
+ globalProperties,
+ ObjectModelHelpers.MSBuildDefaultToolsVersion,
+ new ProjectCollection());
+ project.FullPath = "foo";
+ ProjectInstance instance = project.CreateProjectInstance();
+
+ instance.TranslateEntireState = true;
+
+ BuildRequestConfiguration configuration = new BuildRequestConfiguration(new BuildRequestData(instance, new string[] { }, null), "2.0");
+ configuration.ConfigurationId = 1;
+
+ ((ITranslatable)configuration).Translate(TranslationHelpers.GetWriteTranslator());
+ INodePacket packet = BuildRequestConfiguration.FactoryForDeserialization(TranslationHelpers.GetReadTranslator());
+
+ BuildRequestConfiguration deserializedConfig = packet as BuildRequestConfiguration;
+
+ deserializedConfig.ShouldNotBeNull();
+ deserializedConfig.ShouldBe(configuration);
+ deserializedConfig.Project.ShouldNotBeNull();
+
+ // Verify that at least some data from 'entire project state' has been deserialized.
+ deserializedConfig.Project.Directory.ShouldNotBeEmpty();
+ deserializedConfig.Project.Directory.ShouldBe(configuration.Project.Directory);
+ }
+
[Fact]
public void TestProperties()
{
diff --git a/src/Build.UnitTests/BackEnd/BuildRequest_Tests.cs b/src/Build.UnitTests/BackEnd/BuildRequest_Tests.cs
index 75e018b7cdc..9cede0f7298 100644
--- a/src/Build.UnitTests/BackEnd/BuildRequest_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/BuildRequest_Tests.cs
@@ -138,7 +138,7 @@ public void TestTranslation()
#if FEATURE_COM_INTEROP
[Fact]
- [SkipOnTargetFramework(TargetFrameworkMonikers.Mono, "disable com tests on mono")]
+ [SkipOnMono("disable com tests on mono")]
public void TestTranslationRemoteHostObjects()
{
var stateInHostObject = 3;
diff --git a/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs b/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs
new file mode 100644
index 00000000000..07c9c392b41
--- /dev/null
+++ b/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using Microsoft.Build.Shared;
+using Shouldly;
+using Xunit;
+
+namespace Microsoft.Build.UnitTests
+{
+ public class DebugUtils_Tests
+ {
+ [Fact]
+ public void DumpExceptionToFileShouldWriteInTempPathByDefault()
+ {
+ Directory.GetFiles(Path.GetTempPath(), "MSBuild_*failure.txt").ShouldBeEmpty();
+
+ string[] exceptionFiles = null;
+
+ try
+ {
+ ExceptionHandling.DumpExceptionToFile(new Exception("hello world"));
+ exceptionFiles = Directory.GetFiles(Path.GetTempPath(), "MSBuild_*failure.txt");
+ }
+ finally
+ {
+ exceptionFiles.ShouldNotBeNull();
+ exceptionFiles.ShouldHaveSingleItem();
+
+ var exceptionFile = exceptionFiles.First();
+ File.ReadAllText(exceptionFile).ShouldContain("hello world");
+ File.Delete(exceptionFile);
+ }
+ }
+ }
+}
diff --git a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs
index 9a485cb0d49..4d84c33b60f 100644
--- a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs
@@ -665,6 +665,11 @@ public void Properties()
Assert.Equal(1, loggingService.MaxCPUCount);
loggingService.MaxCPUCount = 5;
Assert.Equal(5, loggingService.MaxCPUCount);
+
+ // Test MinimumRequiredMessageImportance
+ Assert.Equal(MessageImportance.Low, loggingService.MinimumRequiredMessageImportance);
+ loggingService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Normal));
+ Assert.Equal(MessageImportance.Normal, loggingService.MinimumRequiredMessageImportance);
}
#endregion
@@ -718,6 +723,8 @@ public void LoggingPacketReceived()
#endregion
+ #region WarningsAsErrors Tests
+
private static readonly BuildWarningEventArgs BuildWarningEventForTreatAsErrorOrMessageTests = new BuildWarningEventArgs("subcategory", "C94A41A90FFB4EF592BF98BA59BEE8AF", "file", 1, 2, 3, 4, "message", "helpKeyword", "senderName");
///
@@ -1000,6 +1007,76 @@ private MockLogger GetLoggedEventsWithWarningsAsErrorsOrMessages(
return logger;
}
+ #endregion
+
+ #region MinimumRequiredMessageImportance Tests
+
+ [Fact]
+ public void ImportanceReflectsConsoleLoggerVerbosity()
+ {
+ _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Quiet));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.High - 1);
+ _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Minimal));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.High);
+ _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Normal));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Normal);
+ _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Detailed));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
+ _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Diagnostic));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
+ }
+
+ [Fact]
+ public void ImportanceReflectsConfigurableForwardingLoggerVerbosity()
+ {
+ _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Quiet));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.High - 1);
+ _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Minimal));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.High);
+ _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Normal));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Normal);
+ _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Detailed));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
+ _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Diagnostic));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
+ }
+
+ [Fact]
+ public void ImportanceReflectsCentralForwardingLoggerVerbosity()
+ {
+ MockHost mockHost = new MockHost();
+ ILoggingService node1LoggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);
+ ((IBuildComponent)node1LoggingService).InitializeComponent(mockHost);
+ ILoggingService node2LoggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 2);
+ ((IBuildComponent)node2LoggingService).InitializeComponent(mockHost);
+
+ // CentralForwardingLogger is always registered in in-proc nodes and it does not affect minimum importance.
+ node1LoggingService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Minimal));
+ node1LoggingService.RegisterLogger(new CentralForwardingLogger());
+ node1LoggingService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.High);
+
+ // CentralForwardingLogger in out-of-proc nodes means that we are forwarding everything and the minimum importance
+ // is Low regardless of what other loggers are registered.
+ node2LoggingService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Minimal));
+ node2LoggingService.RegisterLogger(new CentralForwardingLogger());
+ node2LoggingService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
+ // Register another ConsoleLogger and verify that minimum importance hasn't changed.
+ node2LoggingService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Minimal));
+ node2LoggingService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
+ }
+
+ [Fact]
+ public void ImportanceReflectsUnknownLoggerVerbosity()
+ {
+ // Minimum message importance is Low (i.e. we're logging everything) even when all registered loggers have
+ // Normal verbosity if at least of one them is not on our whitelist.
+ _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Normal));
+ _initializedService.RegisterLogger(new MockLogger() { Verbosity = LoggerVerbosity.Normal });
+ _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Normal));
+ _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
+ }
+ #endregion
+
#region PrivateMethods
///
@@ -1084,6 +1161,17 @@ private LoggerDescription CreateLoggerDescription(string loggerClassName, string
);
return centralLoggerDescrption;
}
+
+ ///
+ /// Creates a new with the given verbosity.
+ ///
+ private ConfigurableForwardingLogger CreateConfigurableForwardingLogger(LoggerVerbosity verbosity)
+ {
+ return new ConfigurableForwardingLogger()
+ {
+ Verbosity = verbosity
+ };
+ }
#endregion
#region HelperClasses
diff --git a/src/Build.UnitTests/BackEnd/MockLoggingService.cs b/src/Build.UnitTests/BackEnd/MockLoggingService.cs
index d428e3fdac6..2d4ed73b93d 100644
--- a/src/Build.UnitTests/BackEnd/MockLoggingService.cs
+++ b/src/Build.UnitTests/BackEnd/MockLoggingService.cs
@@ -223,6 +223,11 @@ public bool IncludeTaskInputs
set { }
}
+ public MessageImportance MinimumRequiredMessageImportance
+ {
+ get => MessageImportance.Low;
+ }
+
public void AddWarningsAsMessages(BuildEventContext buildEventContext, ISet codes)
{
throw new NotImplementedException();
@@ -553,7 +558,7 @@ public void LogTaskStarted(BuildEventContext targetBuildEventContext, string tas
/// The project file
/// The project file containing the task node.
/// The task logging context
- public BuildEventContext LogTaskStarted2(BuildEventContext targetBuildEventContext, string taskName, string projectFile, string projectFileOfTaskNode)
+ public BuildEventContext LogTaskStarted2(BuildEventContext targetBuildEventContext, string taskName, string projectFile, string projectFileOfTaskNode, int line, int column)
{
return new BuildEventContext(0, 0, 0, 0);
}
diff --git a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs
index a3137f5b399..78d73604056 100644
--- a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs
@@ -52,6 +52,7 @@ public void VerifyEventType()
BuildErrorEventArgs error = new BuildErrorEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender");
TargetStartedEventArgs targetStarted = new TargetStartedEventArgs("message", "help", "targetName", "ProjectFile", "targetFile");
TargetFinishedEventArgs targetFinished = new TargetFinishedEventArgs("message", "help", "targetName", "ProjectFile", "targetFile", true);
+ TargetSkippedEventArgs targetSkipped = CreateTargetSkipped();
ProjectStartedEventArgs projectStarted = new ProjectStartedEventArgs(-1, "message", "help", "ProjectFile", "targetNames", null, null, null);
ProjectFinishedEventArgs projectFinished = new ProjectFinishedEventArgs("message", "help", "ProjectFile", true);
ExternalProjectStartedEventArgs externalStartedEvent = new ExternalProjectStartedEventArgs("message", "help", "senderName", "projectFile", "targetNames");
@@ -69,6 +70,7 @@ public void VerifyEventType()
VerifyLoggingPacket(error, LoggingEventType.BuildErrorEvent);
VerifyLoggingPacket(targetStarted, LoggingEventType.TargetStartedEvent);
VerifyLoggingPacket(targetFinished, LoggingEventType.TargetFinishedEvent);
+ VerifyLoggingPacket(targetSkipped, LoggingEventType.TargetSkipped);
VerifyLoggingPacket(projectStarted, LoggingEventType.ProjectStartedEvent);
VerifyLoggingPacket(projectFinished, LoggingEventType.ProjectFinishedEvent);
VerifyLoggingPacket(evaluationStarted, LoggingEventType.ProjectEvaluationStartedEvent);
@@ -158,6 +160,8 @@ private static TaskParameterEventArgs CreateTaskParameter()
items,
logItemMetadata: true,
DateTime.MinValue);
+ result.LineNumber = 30000;
+ result.ColumnNumber = 50;
// normalize line endings as we can't rely on the line endings of NodePackets_Tests.cs
Assert.Equal(@"Task Parameter:
@@ -174,6 +178,26 @@ private static TaskParameterEventArgs CreateTaskParameter()
return result;
}
+ private static TargetSkippedEventArgs CreateTargetSkipped()
+ {
+ var result = new TargetSkippedEventArgs(message: null)
+ {
+ BuildReason = TargetBuiltReason.DependsOn,
+ SkipReason = TargetSkipReason.PreviouslyBuiltSuccessfully,
+ BuildEventContext = CreateBuildEventContext(),
+ OriginalBuildEventContext = CreateBuildEventContext(),
+ Condition = "$(Condition) == 'true'",
+ EvaluatedCondition = "'true' == 'true'",
+ Importance = MessageImportance.Normal,
+ OriginallySucceeded = true,
+ ProjectFile = "1.proj",
+ TargetFile = "1.proj",
+ TargetName = "Build",
+ ParentTarget = "ParentTarget"
+ };
+ return result;
+ }
+
///
/// Tests serialization of LogMessagePacket with each kind of event type.
///
@@ -195,7 +219,11 @@ public void TestTranslation()
new BuildFinishedEventArgs("Message", "Keyword", true),
new BuildStartedEventArgs("Message", "Help"),
new BuildMessageEventArgs("Message", "help", "sender", MessageImportance.Low),
- new TaskStartedEventArgs("message", "help", "projectFile", "taskFile", "taskName"),
+ new TaskStartedEventArgs("message", "help", "projectFile", "taskFile", "taskName")
+ {
+ LineNumber = 345,
+ ColumnNumber = 123
+ },
new TaskFinishedEventArgs("message", "help", "projectFile", "taskFile", "taskName", true),
new TaskCommandLineEventArgs("commandLine", "taskName", MessageImportance.Low),
CreateTaskParameter(),
@@ -207,7 +235,8 @@ public void TestTranslation()
new ProjectFinishedEventArgs("message", "help", "ProjectFile", true),
new ExternalProjectStartedEventArgs("message", "help", "senderName", "projectFile", "targetNames"),
CreateProjectEvaluationStarted(),
- CreateProjectEvaluationFinished()
+ CreateProjectEvaluationFinished(),
+ CreateTargetSkipped()
};
foreach (BuildEventArgs arg in testArgs)
@@ -412,6 +441,23 @@ private void CompareLogMessagePackets(LogMessagePacket left, LogMessagePacket ri
Assert.Equal(leftTargetStarted.TargetName, rightTargetStarted.TargetName);
break;
+ case LoggingEventType.TargetSkipped:
+ TargetSkippedEventArgs leftTargetSkipped = left.NodeBuildEvent.Value.Value as TargetSkippedEventArgs;
+ TargetSkippedEventArgs rightTargetSkipped = right.NodeBuildEvent.Value.Value as TargetSkippedEventArgs;
+ Assert.Equal(leftTargetSkipped.BuildReason, rightTargetSkipped.BuildReason);
+ Assert.Equal(leftTargetSkipped.SkipReason, rightTargetSkipped.SkipReason);
+ Assert.Equal(leftTargetSkipped.BuildEventContext, rightTargetSkipped.BuildEventContext);
+ Assert.Equal(leftTargetSkipped.OriginalBuildEventContext, rightTargetSkipped.OriginalBuildEventContext);
+ Assert.Equal(leftTargetSkipped.Condition, rightTargetSkipped.Condition);
+ Assert.Equal(leftTargetSkipped.EvaluatedCondition, rightTargetSkipped.EvaluatedCondition);
+ Assert.Equal(leftTargetSkipped.Importance, rightTargetSkipped.Importance);
+ Assert.Equal(leftTargetSkipped.OriginallySucceeded, rightTargetSkipped.OriginallySucceeded);
+ Assert.Equal(leftTargetSkipped.ProjectFile, rightTargetSkipped.ProjectFile);
+ Assert.Equal(leftTargetSkipped.TargetFile, rightTargetSkipped.TargetFile);
+ Assert.Equal(leftTargetSkipped.TargetName, rightTargetSkipped.TargetName);
+ Assert.Equal(leftTargetSkipped.ParentTarget, rightTargetSkipped.ParentTarget);
+ break;
+
case LoggingEventType.TaskCommandLineEvent:
TaskCommandLineEventArgs leftCommand = left.NodeBuildEvent.Value.Value as TaskCommandLineEventArgs;
TaskCommandLineEventArgs rightCommand = right.NodeBuildEvent.Value.Value as TaskCommandLineEventArgs;
@@ -433,6 +479,8 @@ private void CompareLogMessagePackets(LogMessagePacket left, LogMessagePacket ri
Assert.Equal(leftTaskParameter.Message, rightTaskParameter.Message);
Assert.Equal(leftTaskParameter.BuildEventContext, rightTaskParameter.BuildEventContext);
Assert.Equal(leftTaskParameter.Timestamp, rightTaskParameter.Timestamp);
+ Assert.Equal(leftTaskParameter.LineNumber, rightTaskParameter.LineNumber);
+ Assert.Equal(leftTaskParameter.ColumnNumber, rightTaskParameter.ColumnNumber);
break;
case LoggingEventType.TaskFinishedEvent:
@@ -454,6 +502,8 @@ private void CompareLogMessagePackets(LogMessagePacket left, LogMessagePacket ri
Assert.Equal(leftTaskStarted.ProjectFile, rightTaskStarted.ProjectFile);
Assert.Equal(leftTaskStarted.TaskFile, rightTaskStarted.TaskFile);
Assert.Equal(leftTaskStarted.TaskName, rightTaskStarted.TaskName);
+ Assert.Equal(leftTaskStarted.LineNumber, rightTaskStarted.LineNumber);
+ Assert.Equal(leftTaskStarted.ColumnNumber, rightTaskStarted.ColumnNumber);
break;
default:
diff --git a/src/Build.UnitTests/BackEnd/Scheduler_Tests.cs b/src/Build.UnitTests/BackEnd/Scheduler_Tests.cs
index dc86010e269..ddb926d7d7b 100644
--- a/src/Build.UnitTests/BackEnd/Scheduler_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/Scheduler_Tests.cs
@@ -10,6 +10,8 @@
using Microsoft.Build.Shared;
using Microsoft.Build.Execution;
using Microsoft.Build.Evaluation;
+using Microsoft.Build.Experimental.ProjectCache;
+using Shouldly;
using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem;
using Xunit;
@@ -496,6 +498,14 @@ public void TestMaxNodeCountNodesNotExceededWithSomeOOPRequests2()
Assert.Equal(2, response[1].NumberOfNodesToCreate);
}
+ [Fact]
+ public void SchedulerShouldHonorDisableInprocNode()
+ {
+ var s = new Scheduler();
+ s.InitializeComponent(new MockHost(new BuildParameters {DisableInProcNode = true}));
+ s.ForceAffinityOutOfProc.ShouldBeTrue();
+ }
+
///
/// Make sure that traversal projects are marked with an affinity of "InProc", which means that
/// even if multiple are available, we should still only have the single inproc node.
@@ -520,6 +530,31 @@ public void TestTraversalAffinityIsInProc()
Assert.Equal(request1, response[0].BuildRequest);
}
+ ///
+ /// Make sure that traversal projects are marked with an affinity of "InProc", which means that
+ /// even if multiple are available, we should still only have the single inproc node.
+ ///
+ [Fact]
+ public void TestProxyAffinityIsInProc()
+ {
+ _host.BuildParameters.MaxNodeCount = 4;
+ ReportDefaultParentRequestIsFinished();
+
+ CreateConfiguration(1, "foo.csproj");
+
+ BuildRequest request1 = CreateProxyBuildRequest(1, 1, new ProxyTargets(new Dictionary {{"foo", "bar"}}), null);
+
+ BuildRequestBlocker blocker = new BuildRequestBlocker(-1, new string[] { }, new[] { request1 });
+ List response = new List(_scheduler.ReportRequestBlocked(1, blocker));
+
+ // There will be no request to create a new node, because both of the above requests are proxy build requests,
+ // which have an affinity of "inproc", and the inproc node already exists.
+ Assert.Single(response);
+ Assert.Equal(ScheduleActionType.ScheduleWithConfiguration, response[0].Action);
+ Assert.Equal(request1, response[0].BuildRequest);
+ Assert.Equal(Scheduler.InProcNodeId, response[0].NodeId);
+ }
+
///
/// With something approximating the BuildManager's build loop, make sure that we don't end up
/// trying to create more nodes than we can actually support.
@@ -729,8 +764,10 @@ private BuildRequest CreateBuildRequest(int nodeRequestId, int configId, string[
///
/// Creates a build request.
///
- private BuildRequest CreateBuildRequest(int nodeRequestId, int configId, string[] targets, NodeAffinity nodeAffinity, BuildRequest parentRequest)
+ private BuildRequest CreateBuildRequest(int nodeRequestId, int configId, string[] targets, NodeAffinity nodeAffinity, BuildRequest parentRequest, ProxyTargets proxyTargets = null)
{
+ (targets == null ^ proxyTargets == null).ShouldBeTrue();
+
HostServices hostServices = null;
if (nodeAffinity != NodeAffinity.Any)
@@ -739,8 +776,36 @@ private BuildRequest CreateBuildRequest(int nodeRequestId, int configId, string[
hostServices.SetNodeAffinity(String.Empty, nodeAffinity);
}
- BuildRequest request = new BuildRequest(1 /* submissionId */, nodeRequestId, configId, targets, hostServices, BuildEventContext.Invalid, parentRequest);
- return request;
+ if (targets != null)
+ {
+ return new BuildRequest(
+ submissionId: 1,
+ nodeRequestId,
+ configId,
+ targets,
+ hostServices,
+ BuildEventContext.Invalid,
+ parentRequest);
+ }
+
+ parentRequest.ShouldBeNull();
+ return new BuildRequest(
+ submissionId: 1,
+ nodeRequestId,
+ configId,
+ proxyTargets,
+ hostServices);
+ }
+
+ private BuildRequest CreateProxyBuildRequest(int nodeRequestId, int configId, ProxyTargets proxyTargets, BuildRequest parentRequest)
+ {
+ return CreateBuildRequest(
+ nodeRequestId,
+ configId,
+ null,
+ NodeAffinity.Any,
+ parentRequest,
+ proxyTargets);
}
///
@@ -778,5 +843,11 @@ private void MockPerformSchedulingActions(IEnumerable response
MockPerformSchedulingActions(moreResponses, ref nodeId, ref inProcNodeExists);
}
}
+
+ private void ReportDefaultParentRequestIsFinished()
+ {
+ var buildResult = new BuildResult(_defaultParentRequest);
+ _scheduler.ReportResult(_defaultParentRequest.NodeRequestId, buildResult);
+ }
}
}
diff --git a/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs b/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs
index c90d6e8a60c..cbfd97c5f83 100644
--- a/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs
@@ -85,16 +85,16 @@ public void AssertResolutionWarnsIfResolvedVersionIsDifferentFromReferencedVersi
}
[Fact]
- public void AssertErrorLoggedWhenResolverThrows()
+ public void AssertResolverThrows()
{
SdkResolverService.Instance.InitializeForTests(new MockLoaderStrategy(includeErrorResolver: true));
SdkReference sdk = new SdkReference("1sdkName", "version1", "minimumVersion");
- var result = SdkResolverService.Instance.ResolveSdk(BuildEventContext.InvalidSubmissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath", interactive: false, isRunningInVisualStudio: false);
-
- result.Path.ShouldBe("resolverpath1");
- _logger.Warnings.Select(i => i.Message).ShouldBe(new [] { "The SDK resolver \"MockSdkResolverThrows\" failed to run. EXMESSAGE" });
+ // When an SDK resolver throws, the expander will catch it and stop the build.
+ SdkResolverException e = Should.Throw(() => SdkResolverService.Instance.ResolveSdk(BuildEventContext.InvalidSubmissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath", interactive: false, isRunningInVisualStudio: false));
+ e.Resolver.Name.ShouldBe("MockSdkResolverThrows");
+ e.Sdk.Name.ShouldBe("1sdkName");
}
[Fact]
diff --git a/src/Build.UnitTests/BackEnd/TargetResult_Tests.cs b/src/Build.UnitTests/BackEnd/TargetResult_Tests.cs
index 346da6846b9..67950cf5716 100644
--- a/src/Build.UnitTests/BackEnd/TargetResult_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/TargetResult_Tests.cs
@@ -89,8 +89,12 @@ public void TestTranslationNoException()
{
TaskItem item = new TaskItem("foo", "bar.proj");
item.SetMetadata("a", "b");
+ var buildEventContext = new Framework.BuildEventContext(1, 2, 3, 4, 5, 6, 7);
- TargetResult result = new TargetResult(new TaskItem[] { item }, BuildResultUtilities.GetStopWithErrorResult());
+ TargetResult result = new TargetResult(
+ new TaskItem[] { item },
+ BuildResultUtilities.GetStopWithErrorResult(),
+ buildEventContext);
((ITranslatable)result).Translate(TranslationHelpers.GetWriteTranslator());
TargetResult deserializedResult = TargetResult.FactoryForDeserialization(TranslationHelpers.GetReadTranslator());
@@ -98,6 +102,7 @@ public void TestTranslationNoException()
Assert.Equal(result.ResultCode, deserializedResult.ResultCode);
Assert.True(TranslationHelpers.CompareCollections(result.Items, deserializedResult.Items, TaskItemComparer.Instance));
Assert.True(TranslationHelpers.CompareExceptions(result.Exception, deserializedResult.Exception));
+ Assert.Equal(result.OriginalBuildEventContext, deserializedResult.OriginalBuildEventContext);
}
///
diff --git a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs
index 2520bc41bb6..486352af3f2 100644
--- a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs
@@ -589,7 +589,7 @@ public void NullMetadataOnOutputItems_InlineTask()
///
/// If an item being output from a task has null metadata, we shouldn't crash.
///
- [Fact]
+ [Fact(Skip = "https://github.com/dotnet/msbuild/issues/6521")]
[Trait("Category", "non-mono-tests")]
public void NullMetadataOnLegacyOutputItems_InlineTask()
{
diff --git a/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs
index 144f30bea1a..94c7c23f23d 100644
--- a/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs
@@ -55,6 +55,7 @@ public void ConstructorWithNullName()
continueOnError: _continueOnErrorDefault,
taskName: null,
taskLocation: @"c:\my tasks\mytask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: null,
globalParameters: null,
warningsAsErrors: null,
@@ -89,6 +90,7 @@ public void ConstructorWithEmptyName()
continueOnError: _continueOnErrorDefault,
taskName: String.Empty,
taskLocation: @"c:\my tasks\mytask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: null,
globalParameters: null,
warningsAsErrors: null,
@@ -123,6 +125,7 @@ public void ConstructorWithNullLocation()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: null,
+ isTaskInputLoggingEnabled: false,
taskParameters: null,
globalParameters: null,
warningsAsErrors: null,
@@ -159,6 +162,7 @@ public void ConstructorWithEmptyLocation()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: String.Empty,
+ isTaskInputLoggingEnabled: false,
taskParameters: null,
globalParameters: null,
warningsAsErrors: null,
@@ -193,6 +197,7 @@ public void TestValidConstructors()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: null,
globalParameters: null,
warningsAsErrors: null,
@@ -217,6 +222,7 @@ public void TestValidConstructors()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: null,
globalParameters: null,
warningsAsErrors: null,
@@ -242,6 +248,7 @@ public void TestValidConstructors()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: parameters,
globalParameters: null,
warningsAsErrors: null,
@@ -272,6 +279,7 @@ public void TestValidConstructors()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: parameters2,
globalParameters: null,
warningsAsErrors: null,
@@ -302,6 +310,7 @@ public void TestValidConstructors()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: parameters2,
globalParameters: null,
warningsAsErrors: WarningsAsErrors,
@@ -339,6 +348,7 @@ public void TestTranslationWithNullDictionary()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: null,
globalParameters: expectedGlobalProperties,
warningsAsErrors: null,
@@ -383,6 +393,7 @@ public void TestTranslationWithEmptyDictionary()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: new Dictionary(),
globalParameters: new Dictionary(),
warningsAsErrors: null,
@@ -432,6 +443,7 @@ public void TestTranslationWithValueTypesInDictionary()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: parameters,
globalParameters: null,
warningsAsErrors: null,
@@ -479,6 +491,7 @@ public void TestTranslationWithITaskItemInDictionary()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: parameters,
globalParameters: null,
warningsAsErrors: null,
@@ -525,6 +538,7 @@ public void TestTranslationWithITaskItemArrayInDictionary()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: parameters,
globalParameters: null,
warningsAsErrors: null,
@@ -578,6 +592,7 @@ public void TestTranslationWithWarningsAsErrors()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: null,
globalParameters: null,
warningsAsErrors: WarningsAsErrors,
@@ -627,6 +642,7 @@ public void TestTranslationWithWarningsAsMessages()
continueOnError: _continueOnErrorDefault,
taskName: "TaskName",
taskLocation: @"c:\MyTasks\MyTask.dll",
+ isTaskInputLoggingEnabled: false,
taskParameters: null,
globalParameters: null,
warningsAsErrors: null,
diff --git a/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs b/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs
index 8382f53d95d..c6f59eea74f 100644
--- a/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs
+++ b/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs
@@ -57,51 +57,14 @@ public void FindBuildEnvironmentByEnvironmentVariable()
/// If MSBUILD_EXE_PATH is explicitly set, we should detect it as a VisualStudio instance even in older scenarios
/// (for example when the install path is under 15.0).
///
- /// When true, run the test pointing to amd64 msbuild.exe.
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
+ [Fact]
[SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp, "No Visual Studio install for netcore")]
[PlatformSpecific(TestPlatforms.Windows)]
- public void FindVisualStudioEnvironmentByEnvironmentVariable(bool is64BitMSbuild)
+ public void FindVisualStudioEnvironmentByEnvironmentVariable()
{
using (var env = new EmptyVSEnviroment())
{
- var msbuildBinDirectory = is64BitMSbuild
- ? Path.Combine(env.BuildDirectory, "amd64")
- : env.BuildDirectory;
-
- var msBuildPath = Path.Combine(msbuildBinDirectory, MSBuildExeName);
- var msBuildConfig = Path.Combine(msbuildBinDirectory, $"{MSBuildExeName}.config");
- var vsMSBuildDirectory = Path.Combine(env.TempFolderRoot, "MSBuild");
-
- env.WithEnvironment("MSBUILD_EXE_PATH", msBuildPath);
- BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);
-
- BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
- BuildEnvironmentHelper.Instance.MSBuildExtensionsPath.ShouldBe(vsMSBuildDirectory);
- BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory.ShouldBe(msbuildBinDirectory);
- BuildEnvironmentHelper.Instance.CurrentMSBuildExePath.ShouldBe(msBuildPath);
- BuildEnvironmentHelper.Instance.CurrentMSBuildConfigurationFile.ShouldBe(msBuildConfig);
- // This code is not running inside the Visual Studio devenv.exe process
- BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeFalse();
- BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
- BuildEnvironmentHelper.Instance.RunningTests.ShouldBeFalse();
- }
- }
-
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp, "No Visual Studio install for netcore")]
- [PlatformSpecific(TestPlatforms.Windows)]
- public void FindOlderVisualStudioEnvironmentByEnvironmentVariable(bool is64BitMSbuild)
- {
- using (var env = new EmptyVSEnviroment("15.0"))
- {
- var msbuildBinDirectory = is64BitMSbuild
- ? Path.Combine(env.BuildDirectory, "amd64")
- : env.BuildDirectory;
+ var msbuildBinDirectory = env.BuildDirectory;
var msBuildPath = Path.Combine(msbuildBinDirectory, MSBuildExeName);
var msBuildConfig = Path.Combine(msbuildBinDirectory, $"{MSBuildExeName}.config");
@@ -307,9 +270,9 @@ public void BuildEnvironmentDetectsVisualStudioByMSBuildProcessAmd64()
[Theory]
[SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp, "No Visual Studio install for netcore")]
[PlatformSpecific(TestPlatforms.Windows)]
- [InlineData("16.0", true)]
- [InlineData("16.3", true)]
- [InlineData("15.0", false)]
+ [InlineData("17.0", true)]
+ [InlineData("17.3", true)]
+ [InlineData("16.0", false)]
public void BuildEnvironmentDetectsVisualStudioFromSetupInstance(string visualStudioVersion, bool shouldBeValid)
{
using (var env = new EmptyVSEnviroment())
diff --git a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs
index f1b59b48e1f..1d06b86c43b 100644
--- a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs
+++ b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs
@@ -160,11 +160,15 @@ public void RoundtripTaskStartedEventArgs()
projectFile: "C:\\project.proj",
taskFile: "C:\\common.targets",
taskName: "Csc");
+ args.LineNumber = 42;
+ args.ColumnNumber = 999;
Roundtrip(args,
e => e.ProjectFile,
e => e.TaskFile,
- e => e.TaskName);
+ e => e.TaskName,
+ e => e.LineNumber.ToString(),
+ e => e.ColumnNumber.ToString());
}
[Fact]
@@ -185,8 +189,10 @@ public void RoundtripTaskFinishedEventArgs()
e => e.ThreadId.ToString());
}
- [Fact]
- public void RoundtripBuildErrorEventArgs()
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void RoundtripBuildErrorEventArgs(bool useArguments)
{
var args = new BuildErrorEventArgs(
"Subcategory",
@@ -196,9 +202,11 @@ public void RoundtripBuildErrorEventArgs()
2,
3,
4,
- "Message",
+ "Message with arguments: '{0}'",
"Help",
- "SenderName");
+ "SenderName",
+ DateTime.Parse("9/1/2021 12:02:07 PM"),
+ useArguments ? new object[] { "argument0" } : null);
Roundtrip(args,
e => e.Code,
@@ -209,11 +217,14 @@ public void RoundtripBuildErrorEventArgs()
e => e.LineNumber.ToString(),
e => e.Message,
e => e.ProjectFile,
- e => e.Subcategory);
+ e => e.Subcategory,
+ e => string.Join(", ", e.RawArguments ?? Array.Empty()));
}
- [Fact]
- public void RoundtripBuildWarningEventArgs()
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void RoundtripBuildWarningEventArgs(bool useArguments)
{
var args = new BuildWarningEventArgs(
"Subcategory",
@@ -223,9 +234,11 @@ public void RoundtripBuildWarningEventArgs()
2,
3,
4,
- "Message",
+ "Message with arguments: '{0}'",
"Help",
- "SenderName");
+ "SenderName",
+ DateTime.Parse("9/1/2021 12:02:07 PM"),
+ useArguments ? new object[] { "argument0" } : null);
Roundtrip(args,
e => e.Code,
@@ -236,11 +249,14 @@ public void RoundtripBuildWarningEventArgs()
e => e.LineNumber.ToString(),
e => e.Message,
e => e.ProjectFile,
- e => e.Subcategory);
+ e => e.Subcategory,
+ e => string.Join(", ", e.RawArguments ?? Array.Empty()));
}
- [Fact]
- public void RoundtripBuildMessageEventArgs()
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void RoundtripBuildMessageEventArgs(bool useArguments)
{
var args = new BuildMessageEventArgs(
"Subcategory",
@@ -254,7 +270,8 @@ public void RoundtripBuildMessageEventArgs()
"Help",
"SenderName",
MessageImportance.High,
- DateTime.Parse("12/12/2015 06:11:56 PM"));
+ DateTime.Parse("12/12/2015 06:11:56 PM"),
+ useArguments ? new object[] { "argument0" } : null);
Roundtrip(args,
e => e.Code,
@@ -266,7 +283,8 @@ public void RoundtripBuildMessageEventArgs()
e => e.Message,
e => e.Importance.ToString(),
e => e.ProjectFile,
- e => e.Subcategory);
+ e => e.Subcategory,
+ e => string.Join(", ", e.RawArguments ?? Array.Empty()));
}
[Fact]
@@ -327,11 +345,15 @@ public void RoundtripTaskParameterEventArgs()
new TaskItemData("ItemSpec2", Enumerable.Range(1,3).ToDictionary(i => i.ToString(), i => i.ToString() + "value"))
};
var args = new TaskParameterEventArgs(TaskParameterMessageKind.TaskOutput, "ItemName", items, true, DateTime.MinValue);
+ args.LineNumber = 265;
+ args.ColumnNumber = 6;
Roundtrip(args,
e => e.Kind.ToString(),
e => e.ItemType,
e => e.LogItemMetadata.ToString(),
+ e => e.LineNumber.ToString(),
+ e => e.ColumnNumber.ToString(),
e => TranslationHelpers.GetItemsString(e.Items));
}
@@ -443,20 +465,31 @@ public void RoundtripTargetSkippedEventArgs()
ProjectFile = "foo.csproj",
TargetName = "target",
ParentTarget = "bar",
- BuildReason = TargetBuiltReason.DependsOn
+ BuildReason = TargetBuiltReason.DependsOn,
+ SkipReason = TargetSkipReason.PreviouslyBuiltSuccessfully,
+ Condition = "$(condition) == true",
+ EvaluatedCondition = "true == true",
+ OriginalBuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7),
+ OriginallySucceeded = false,
+ TargetFile = "foo.csproj"
};
Roundtrip(args,
+ e => e.BuildEventContext.ToString(),
e => e.ParentTarget,
e => e.Importance.ToString(),
e => e.LineNumber.ToString(),
e => e.ColumnNumber.ToString(),
- e => e.LineNumber.ToString(),
e => e.Message,
e => e.ProjectFile,
e => e.TargetFile,
e => e.TargetName,
- e => e.BuildReason.ToString());
+ e => e.BuildReason.ToString(),
+ e => e.SkipReason.ToString(),
+ e => e.Condition,
+ e => e.EvaluatedCondition,
+ e => e.OriginalBuildEventContext.ToString(),
+ e => e.OriginallySucceeded.ToString());
}
[Fact]
diff --git a/src/Build.UnitTests/ConsoleLogger_Tests.cs b/src/Build.UnitTests/ConsoleLogger_Tests.cs
index 31c8209404d..023d77e74b8 100644
--- a/src/Build.UnitTests/ConsoleLogger_Tests.cs
+++ b/src/Build.UnitTests/ConsoleLogger_Tests.cs
@@ -318,9 +318,9 @@ public void ErrorMessageWithMultiplePropertiesInMessage(bool includeEvaluationPr
output.ShouldContain("source_of_error : error : Hello from project 2 [" + project.ProjectFile + ":: Number=2 TargetFramework=netcoreapp2.1]");
}
- [Fact]
+ [Fact(Skip = "https://github.com/dotnet/msbuild/issues/6518")]
[SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp, "Minimal path validation in Core allows expanding path containing quoted slashes.")]
- [SkipOnTargetFramework(TargetFrameworkMonikers.Mono, "Minimal path validation in Mono allows expanding path containing quoted slashes.")]
+ [SkipOnMono("Minimal path validation in Mono allows expanding path containing quoted slashes.")]
public void TestItemsWithUnexpandableMetadata()
{
SimulatedConsole sc = new SimulatedConsole();
@@ -338,7 +338,8 @@ public void TestItemsWithUnexpandableMetadata()
", logger);
- sc.ToString().ShouldContain("\"a\\b\\%(Filename).c\"");
+ var text = sc.ToString();
+ text.ShouldContain("\"a\\b\\%(Filename).c\"");
}
///
diff --git a/src/Build.UnitTests/Definition/ItemDataCollectionValue_Tests.cs b/src/Build.UnitTests/Definition/ItemDataCollectionValue_Tests.cs
new file mode 100644
index 00000000000..b0699e3ab43
--- /dev/null
+++ b/src/Build.UnitTests/Definition/ItemDataCollectionValue_Tests.cs
@@ -0,0 +1,122 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Build.Evaluation;
+using Shouldly;
+using Xunit;
+
+namespace Microsoft.Build.UnitTests.OM.Definition
+{
+ ///
+ /// Tests the data type.
+ ///
+ public class ItemDataCollectionValue_Tests
+ {
+ private int[] MakeArray(ItemDataCollectionValue value)
+ {
+ List result = new List();
+ foreach (int i in value)
+ {
+ result.Add(i);
+ }
+ return result.ToArray();
+ }
+
+ [Fact]
+ public void RepresentsSingleItem()
+ {
+ var value = new ItemDataCollectionValue(1);
+ value.IsEmpty.ShouldBeFalse();
+ MakeArray(value).ShouldBe(new[] { 1 });
+ }
+
+ [Fact]
+ public void AddsSecondItem()
+ {
+ var value = new ItemDataCollectionValue(1);
+ value.Add(2);
+ value.IsEmpty.ShouldBeFalse();
+ MakeArray(value).ShouldBe(new[] { 1, 2 });
+ }
+
+ [Fact]
+ public void DeletesSingleItem()
+ {
+ var value = new ItemDataCollectionValue(1);
+ value.Delete(1);
+ value.IsEmpty.ShouldBeTrue();
+ MakeArray(value).ShouldBe(Array.Empty());
+ }
+
+ [Fact]
+ public void DeletesFirstItem()
+ {
+ var value = new ItemDataCollectionValue(1);
+ value.Add(2);
+ value.Delete(1);
+ value.IsEmpty.ShouldBeFalse();
+ MakeArray(value).ShouldBe(new[] { 2 });
+ }
+
+ [Fact]
+ public void DeletesSecondItem()
+ {
+ var value = new ItemDataCollectionValue(1);
+ value.Add(2);
+ value.Delete(2);
+ value.IsEmpty.ShouldBeFalse();
+ MakeArray(value).ShouldBe(new[] { 1 });
+ }
+
+ [Fact]
+ public void DeletesNonExistentItem()
+ {
+ var value = new ItemDataCollectionValue(1);
+ value.Add(2);
+ value.Delete(3);
+ value.IsEmpty.ShouldBeFalse();
+ MakeArray(value).ShouldBe(new[] { 1, 2 });
+ }
+
+ [Fact]
+ public void ReplacesSingleItem()
+ {
+ var value = new ItemDataCollectionValue(1);
+ value.Replace(1, 11);
+ value.IsEmpty.ShouldBeFalse();
+ MakeArray(value).ShouldBe(new[] { 11 });
+ }
+
+ [Fact]
+ public void ReplacesFirstItem()
+ {
+ var value = new ItemDataCollectionValue(1);
+ value.Add(2);
+ value.Replace(1, 11);
+ value.IsEmpty.ShouldBeFalse();
+ MakeArray(value).ShouldBe(new[] { 11, 2 });
+ }
+
+ [Fact]
+ public void ReplacesSecondItem()
+ {
+ var value = new ItemDataCollectionValue(1);
+ value.Add(2);
+ value.Replace(2, 22);
+ value.IsEmpty.ShouldBeFalse();
+ MakeArray(value).ShouldBe(new[] { 1, 22 });
+ }
+
+ [Fact]
+ public void ReplacesNonExistentItem()
+ {
+ var value = new ItemDataCollectionValue(1);
+ value.Add(2);
+ value.Replace(3, 33);
+ value.IsEmpty.ShouldBeFalse();
+ MakeArray(value).ShouldBe(new[] { 1, 2 });
+ }
+ }
+}
diff --git a/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs b/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs
index 00dc1bb6f61..232d22c62dd 100644
--- a/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs
+++ b/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs
@@ -119,14 +119,12 @@ public void PassedInFileSystemShouldBeReusedInSharedContext()
{Path.Combine(_env.DefaultTestDirectory.Path, "2.file"), 1}
}.OrderBy(kvp => kvp.Key));
- fileSystem.DirectoryEntryExistsCalls.ShouldBe(2);
+ fileSystem.FileOrDirectoryExistsCalls.ShouldBe(2);
}
[Fact]
public void IsolatedContextShouldNotSupportBeingPassedAFileSystem()
{
- _env.DoNotLaunchDebugger();
-
var fileSystem = new Helpers.LoggingFileSystem();
Should.Throw(() => EvaluationContext.Create(EvaluationContext.SharingPolicy.Isolated, fileSystem));
}
diff --git a/src/Build.UnitTests/Definition/ToolsetRegistryReader_Tests.cs b/src/Build.UnitTests/Definition/ToolsetRegistryReader_Tests.cs
index e365cb9265f..41e2d74ef72 100644
--- a/src/Build.UnitTests/Definition/ToolsetRegistryReader_Tests.cs
+++ b/src/Build.UnitTests/Definition/ToolsetRegistryReader_Tests.cs
@@ -36,7 +36,7 @@ public class ToolsetRegistryReader_Tests : IDisposable
private const string testRegistryPath = @"msbuildUnitTests";
///
- /// Store the value of the "VisualStudioVersion" environment variable here so that
+ /// Store the value of the "VisualStudioVersion" environment variable here so that
/// we can unset it for the duration of the test.
///
private string _oldVisualStudioVersion;
@@ -341,8 +341,8 @@ public void ReadRegistry_IgnoreSubToolsetSubKeys()
}
///
- /// Verifies that if a value is defined in both the base toolset and the
- /// selected subtoolset, the subtoolset value overrides -- even if that
+ /// Verifies that if a value is defined in both the base toolset and the
+ /// selected subtoolset, the subtoolset value overrides -- even if that
/// value is empty.
///
[Fact]
@@ -381,8 +381,8 @@ public void ReadRegistry_SubToolsetOverridesBaseToolsetEntries()
}
///
- /// Verifies that if a value is defined in both the base toolset and the
- /// selected subtoolset, the subtoolset value overrides -- even if that
+ /// Verifies that if a value is defined in both the base toolset and the
+ /// selected subtoolset, the subtoolset value overrides -- even if that
/// value is empty.
///
[Fact]
diff --git a/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs b/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs
index ca20d9c66cb..8ac6ba52f14 100644
--- a/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs
+++ b/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs
@@ -629,7 +629,7 @@ public void UsePropertyBeforeSet()
$(baz) $(bar)Something
- Something
+ Something
@@ -675,7 +675,7 @@ public void UsePropertyBeforeSetDuplicates()
$(baz) $(bar)$(baz) $(bar)Something
- Something
+ Something
@@ -1860,12 +1860,12 @@ public void AllEvaluatedItems()
-
+
-
+ m2
@@ -1876,7 +1876,7 @@ public void AllEvaluatedItems()
-
+
@@ -2587,6 +2587,8 @@ public void MSBuildVersion()
Project project = new Project(xml);
string msbuildVersionProperty = project.GetPropertyValue("MSBuildVersion");
+ string msbuildFileVersionProperty = project.GetPropertyValue("MSBuildFileVersion");
+ string msbuildSemanticVersionProperty = project.GetPropertyValue("MSBuildSemanticVersion");
Version.TryParse(msbuildVersionProperty, out Version msbuildVersionAsVersion).ShouldBeTrue();
@@ -2596,9 +2598,11 @@ public void MSBuildVersion()
// Version parses missing elements into -1, and this property should be Major.Minor.Patch only
msbuildVersionAsVersion.Revision.ShouldBe(-1);
+ msbuildFileVersionProperty.ShouldBe(ProjectCollection.Version.ToString());
ProjectCollection.Version.ToString().ShouldStartWith(msbuildVersionProperty,
"ProjectCollection.Version should match the property MSBuildVersion, but can contain another version part");
+ msbuildSemanticVersionProperty.ShouldBe(ProjectCollection.DisplayVersion);
ProjectCollection.DisplayVersion.ShouldStartWith(msbuildVersionProperty,
"DisplayVersion is semver2 while MSBuildVersion is Major.Minor.Build but should be a prefix match");
}
@@ -4340,7 +4344,7 @@ public void ThrownInvalidProjectExceptionProperlyHandled()
{
string projectContents = ObjectModelHelpers.CleanupFileContents(@"
-
+
");
@@ -4392,9 +4396,9 @@ public void ThrownInvalidProjectExceptionProperlyHandled()
///
/// Tests that an import, target, or task with a condition that contains an error but is short-circuited does not fail the build. This can happen when you have a condition like:
/// 'true' == 'false' AND '$([MSBuild]::GetDirectoryNameOfFileAbove($(NonExistentProperty), init.props))' != ''
- ///
+ ///
/// The first condition is false so the second condition is not evaluated. But in some cases we double evaluate the condition to log it. The second evaluation will fail because it evaluates the whole string.
- ///
+ ///
/// https://github.com/Microsoft/msbuild/issues/2259
///
[Theory]
@@ -4765,6 +4769,23 @@ public void VerifyPropertyTrackingLoggingAll()
});
}
+ [Fact]
+ public void VerifyGetTypeEvaluationBlocked()
+ {
+ string projectContents = ObjectModelHelpers.CleanupFileContents(@"
+
+
+ $(MSBuildRuntimeType.GetType())
+
+ ");
+
+ ProjectCollection fakeProjectCollection =
+ GetProjectCollectionWithFakeToolset(null /* no global properties */);
+
+ Should.Throw(() =>
+ new Project(XmlReader.Create(new StringReader(projectContents)), null, "Fake", fakeProjectCollection));
+ }
+
private void VerifyPropertyTrackingLoggingScenario(string envVarValue, Action loggerEvaluatorAction)
{
// The default is that only reassignments are logged.
diff --git a/src/Build.UnitTests/Evaluation/Expander_Tests.cs b/src/Build.UnitTests/Evaluation/Expander_Tests.cs
index 71d15858f3c..b58a72d0843 100644
--- a/src/Build.UnitTests/Evaluation/Expander_Tests.cs
+++ b/src/Build.UnitTests/Evaluation/Expander_Tests.cs
@@ -1547,76 +1547,6 @@ public void ExpandAllIntoStringTruncated()
Assert.Equal(expected, expander.ExpandIntoStringAndUnescape(xmlattribute.Value, ExpanderOptions.ExpandAll | ExpanderOptions.Truncate, MockElementLocation.Instance));
}
- ///
- /// Exercises ExpandIntoStringAndUnescape and ExpanderOptions.Truncate
- ///
- [Fact]
- public void ExpandAllIntoStringNotTruncated()
- {
- using (TestEnvironment env = TestEnvironment.Create())
- {
- ChangeWaves.ResetStateForTests();
- env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave16_8.ToString());
- BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly();
- ProjectInstance project = ProjectHelpers.CreateEmptyProjectInstance();
- var manySpaces = "".PadLeft(2000);
- var pg = new PropertyDictionary();
- pg.Set(ProjectPropertyInstance.Create("ManySpacesProperty", manySpaces));
- var itemMetadataTable = new Dictionary(StringComparer.OrdinalIgnoreCase)
- {
- { "ManySpacesMetadata", manySpaces }
- };
- var itemMetadata = new StringMetadataTable(itemMetadataTable);
- var projectItemGroups = new ItemDictionary();
- var itemGroup = new List();
- StringBuilder longFileName = new StringBuilder();
- StringBuilder longMetadataName = new StringBuilder();
- for (int i = 0; i < 50; i++)
- {
- var item = new ProjectItemInstance(project, "ManyItems", $"ThisIsAFairlyLongFileName_{i}.bmp", project.FullPath);
- item.SetMetadata("Foo", $"ThisIsAFairlyLongMetadataValue_{i}");
- longFileName.Append($"ThisIsAFairlyLongFileName_{i}.bmp" + (i == 49 ? string.Empty : ";"));
- longMetadataName.Append($"ThisIsAFairlyLongMetadataValue_{i}" + (i == 49 ? string.Empty : ";"));
- itemGroup.Add(item);
- }
- var lookup = new Lookup(projectItemGroups, pg);
- lookup.EnterScope("x");
- lookup.PopulateWithItems("ManySpacesItem", new[]
- {
- new ProjectItemInstance (project, "ManySpacesItem", "Foo", project.FullPath),
- new ProjectItemInstance (project, "ManySpacesItem", manySpaces, project.FullPath),
- new ProjectItemInstance (project, "ManySpacesItem", "Bar", project.FullPath),
- });
- lookup.PopulateWithItems("Exactly1024", new[]
- {
- new ProjectItemInstance (project, "Exactly1024", "".PadLeft(1024), project.FullPath),
- new ProjectItemInstance (project, "Exactly1024", "Foo", project.FullPath),
- });
- lookup.PopulateWithItems("ManyItems", itemGroup);
-
- Expander expander = new Expander(lookup, lookup, itemMetadata, FileSystems.Default);
-
- XmlAttribute xmlattribute = (new XmlDocument()).CreateAttribute("dummy");
- xmlattribute.Value = "'%(ManySpacesMetadata)' != '' and '$(ManySpacesProperty)' != '' and '@(ManySpacesItem)' != '' and '@(Exactly1024)' != '' and '@(ManyItems)' != '' and '@(ManyItems->'%(Foo)')' != '' and '@(ManyItems->'%(Nonexistent)')' != ''";
-
- var expected =
- $"'{"",2000}' != '' and " +
- $"'{"",2000}' != '' and " +
- $"'Foo;{"",2000};Bar' != '' and " +
- $"'{"",1024};Foo' != '' and " +
- $"'{longFileName}' != '' and " +
- $"'{longMetadataName}' != '' and " +
- "';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;' != ''";
- var actual = expander.ExpandIntoStringAndUnescape(xmlattribute.Value, ExpanderOptions.ExpandAll | ExpanderOptions.Truncate, MockElementLocation.Instance);
- // NOTE: semicolons in the last part are *weird* because they don't actually mean anything and you get logging like
- // Target "Build" skipped, due to false condition; ( '@(I->'%(nonexistent)')' == '' ) was evaluated as ( ';' == '' ).
- // but that goes back to MSBuild 4.something so I'm codifying it in this test. If you're here because you cleaned it up
- // and want to fix the test my current opinion is that's fine.
- actual.ShouldBe(expected);
- ChangeWaves.ResetStateForTests();
- }
- }
-
///
/// Exercises ExpandAllIntoString with a string that does not need expanding.
/// In this case the expanded string should be reference identical to the passed in string.
@@ -1998,7 +1928,7 @@ public void PropertyFunctionNullArgument()
Expander expander = new Expander(pg, FileSystems.Default);
- string result = expander.ExpandIntoStringLeaveEscaped("$([System.Convert]::ChangeType('null',$(SomeStuff.GetType())))", ExpanderOptions.ExpandProperties, MockElementLocation.Instance);
+ string result = expander.ExpandIntoStringLeaveEscaped("$([System.Convert]::ChangeType('null',$(SomeStuff.GetTypeCode())))", ExpanderOptions.ExpandProperties, MockElementLocation.Instance);
Assert.Equal("null", result);
}
diff --git a/src/Build.UnitTests/Evaluation/ItemEvaluation_Tests.cs b/src/Build.UnitTests/Evaluation/ItemEvaluation_Tests.cs
index 73ba89b0e9b..2aa10111eeb 100644
--- a/src/Build.UnitTests/Evaluation/ItemEvaluation_Tests.cs
+++ b/src/Build.UnitTests/Evaluation/ItemEvaluation_Tests.cs
@@ -110,7 +110,7 @@ public void RemoveShouldPreserveIntermediaryReferences(string content)
{"m1", "m1_contents"},
{"m2", "m2_contents"}
};
-
+
var itemsForI = items.Where(i => i.ItemType == "i").ToList();
ObjectModelHelpers.AssertItems(new[] { "a", "b", "c" }, itemsForI, expectedMetadata);
@@ -508,7 +508,7 @@ public void MultipleInterItemDependenciesOnSameItemOperation()
new Dictionary
{
{"m", "i2"}
- },
+ },
i1BaseMetadata,
i1BaseMetadata
};
@@ -528,7 +528,7 @@ public void MultipleInterItemDependenciesOnSameItemOperation()
public void LongIncludeChain()
{
const int INCLUDE_COUNT = 10000;
-
+
// This was about the minimum count needed to repro a StackOverflowException
//const int INCLUDE_COUNT = 4000;
@@ -605,7 +605,7 @@ public void LazyWildcardExpansionDoesNotEvaluateWildCardsIfNotReferenced()
}
Assert.Equal(expectedItems, project.GetConcatenatedItemsOfType("i2"));
-
+
var fullPathItems = project.GetConcatenatedItemsOfType("FullPath");
Assert.Contains("a.cs", fullPathItems);
Assert.Contains("b.cs", fullPathItems);
diff --git a/src/Build.UnitTests/Evaluation/SimpleProjectRootElementCache_Tests.cs b/src/Build.UnitTests/Evaluation/SimpleProjectRootElementCache_Tests.cs
index 92794939793..f38192ebbb3 100644
--- a/src/Build.UnitTests/Evaluation/SimpleProjectRootElementCache_Tests.cs
+++ b/src/Build.UnitTests/Evaluation/SimpleProjectRootElementCache_Tests.cs
@@ -60,7 +60,6 @@ public void Get_GivenOpenFuncWhichAddsRootElement_ReturnsRootElement()
ProjectRootElement rootElementToCache = ProjectRootElement.Create(projectFileToCache);
ProjectRootElement OpenFunc(string pathArg, ProjectRootElementCacheBase cacheArg)
{
- cacheArg.AddEntry(rootElementToCache);
return rootElementToCache;
}
@@ -79,7 +78,6 @@ public void Get_GivenOpenFuncWhichAddsRootElementWithDifferentCasing_ReturnsRoot
ProjectRootElement rootElementToCache = ProjectRootElement.Create(projectFileToCache);
ProjectRootElement OpenFunc(string pathArg, ProjectRootElementCacheBase cacheArg)
{
- cacheArg.AddEntry(rootElementToCache);
return rootElementToCache;
}
@@ -112,7 +110,6 @@ public void Get_GivenOpenFuncWhichReturnsIncorrectProject_ThrowsInternalErrorExc
ProjectRootElement rootElementToCache = ProjectRootElement.Create(projectFileToCache);
ProjectRootElement OpenFunc(string pathArg, ProjectRootElementCacheBase cacheArg)
{
- cacheArg.AddEntry(rootElementToCache);
return rootElementToCache;
}
@@ -123,21 +120,5 @@ ProjectRootElement OpenFunc(string pathArg, ProjectRootElementCacheBase cacheArg
cache.Get(projectFile, OpenFunc, false, null);
});
}
-
- [Fact]
- public void Get_GivenOpenFuncWhichDoesNotAddToCache_ThrowsInternalErrorException()
- {
- string projectFile = NativeMethodsShared.IsUnixLike ? "/foo" : "c:\\foo";
- string openFuncPath = NativeMethodsShared.IsUnixLike ? "/foo" : "c:\\foo";
- ProjectRootElement openFuncElement = ProjectRootElement.Create(openFuncPath);
- ProjectRootElement OpenFunc(string pathArg, ProjectRootElementCacheBase cacheArg) => openFuncElement;
-
- var cache = new SimpleProjectRootElementCache();
-
- Should.Throw(() =>
- {
- cache.Get(projectFile, OpenFunc, false, null);
- });
- }
}
}
diff --git a/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs b/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs
index c4f8ab84bea..d357c472750 100644
--- a/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs
+++ b/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs
@@ -326,20 +326,39 @@ public void GlobMatchingShouldWorkWithComplexRelativeLiterals()
[InlineData(
@"a/b\c",
@"d/e\f/**\a.cs",
- @"d\e/f\g/h\i/a.cs")]
+ @"d\e/f\g/h\i/a.cs",
+ @"d\e/f\", @"g/h\i/", @"a.cs")]
[InlineData(
@"a/b\c",
@"d/e\f/*b*\*.cs",
- @"d\e/f\abc/a.cs")]
+ @"d\e/f\abc/a.cs",
+ @"d\e/f\", @"abc/", @"a.cs")]
[InlineData(
@"a/b/\c",
@"d/e\/*b*/\*.cs",
- @"d\e\\abc/\a.cs")]
- public void GlobMatchingIgnoresSlashOrientationAndRepetitions(string globRoot, string fileSpec, string stringToMatch)
+ @"d\e\\abc/\a.cs",
+ @"d\e\\", @"abc\\", @"a.cs")]
+ public void GlobMatchingIgnoresSlashOrientationAndRepetitions(string globRoot, string fileSpec, string stringToMatch,
+ string fixedDirectoryPart, string wildcardDirectoryPart, string filenamePart)
{
var glob = MSBuildGlob.Parse(globRoot, fileSpec);
Assert.True(glob.IsMatch(stringToMatch));
+
+ MSBuildGlob.MatchInfoResult result = glob.MatchInfo(stringToMatch);
+ Assert.True(result.IsMatch);
+
+ string NormalizeSlashes(string path)
+ {
+ string normalizedPath = path.Replace(Path.DirectorySeparatorChar == '/' ? '\\' : '/', Path.DirectorySeparatorChar);
+ return NativeMethodsShared.IsWindows ? normalizedPath.Replace("\\\\", "\\") : normalizedPath;
+ }
+
+ var rootedFixedDirectoryPart = Path.Combine(FileUtilities.NormalizePath(globRoot), fixedDirectoryPart);
+
+ Assert.Equal(FileUtilities.GetFullPathNoThrow(rootedFixedDirectoryPart), result.FixedDirectoryPartMatchGroup);
+ Assert.Equal(NormalizeSlashes(wildcardDirectoryPart), result.WildcardDirectoryPartMatchGroup);
+ Assert.Equal(NormalizeSlashes(filenamePart), result.FilenamePartMatchGroup);
}
}
}
diff --git a/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs b/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs
index c1f0161e91d..bed09d043ec 100644
--- a/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs
+++ b/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs
@@ -33,8 +33,6 @@ public GraphLoadedFromSolutionTests(ITestOutputHelper output)
[InlineData("1.sln", "2.proj")]
public void ASolutionShouldBeTheSingleEntryPoint(params string[] files)
{
- _env.DoNotLaunchDebugger();
-
for (var i = 0; i < files.Length; i++)
{
files[i] = _env.CreateFile(files[i], string.Empty).Path;
@@ -52,8 +50,6 @@ public void ASolutionShouldBeTheSingleEntryPoint(params string[] files)
[Fact]
public void GraphConstructionFailsOnNonExistentSolution()
{
- _env.DoNotLaunchDebugger();
-
var exception = Should.Throw(
() =>
{
@@ -80,8 +76,6 @@ public void StaticGraphShouldNotSupportNestedSolutions()
defaultTargets: null,
extraContent: referenceToSolution);
- _env.DoNotLaunchDebugger();
-
var exception = Should.Throw(
() =>
{
@@ -621,8 +615,6 @@ IEnumerable GetIncomingEdgeItemsToNode(ProjectGraphNode nod
[Fact]
public void GraphConstructionShouldThrowOnMissingSolutionDependencies()
{
- _env.DoNotLaunchDebugger();
-
var solutionContents = SolutionFileBuilder.FromGraphEdges(
_env,
new Dictionary {{1, null}, {2, null}},
diff --git a/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs b/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs
index 548a25b3858..28af920a861 100644
--- a/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs
+++ b/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs
@@ -106,7 +106,6 @@ public void ConstructWithSingleNodeWithProjectInstanceFactory()
[Fact]
public void ProjectGraphNodeConstructorNoNullArguments()
{
- _env.DoNotLaunchDebugger();
Assert.Throws(() => new ProjectGraphNode(null));
}
@@ -1477,12 +1476,21 @@ public void DotNotationShouldRepresentGraph(Dictionary edges)
var graph = Helpers.CreateProjectGraph(
_env,
edges,
- new Dictionary {{"a", "b"}});
+ globalProperties: new Dictionary {{"a", "b"}},
+ createProjectFile: (env, projectId, references, _, _, _) => Helpers.CreateProjectFile(
+ env,
+ projectId,
+ references,
+ projectReferenceTargets: new Dictionary
+ {
+ {"Build", new[] {$"TargetFrom{projectId}", "Build"}}
+ }));
+ var targetsPerNode = graph.GetTargetLists(new []{ "Build" });
Func nodeIdProvider = GetProjectFileName;
- var dot = graph.ToDot(nodeIdProvider);
+ var dot = graph.ToDot(nodeIdProvider, targetsPerNode);
var edgeCount = 0;
@@ -1490,9 +1498,12 @@ public void DotNotationShouldRepresentGraph(Dictionary edges)
{
var nodeId = nodeIdProvider(node);
+ var targets = string.Join(".*", targetsPerNode[node]);
+ targets.ShouldNotBeNullOrEmpty();
+
foreach (var globalProperty in node.ProjectInstance.GlobalProperties)
{
- dot.ShouldMatch($@"{nodeId}\s*\[.*{globalProperty.Key}.*{globalProperty.Value}.*\]");
+ dot.ShouldMatch($@"{nodeId}\s*\[.*{targets}.*{globalProperty.Key}.*{globalProperty.Value}.*\]");
}
foreach (var reference in node.ProjectReferences)
diff --git a/src/Build.UnitTests/Instance/HostServices_Tests.cs b/src/Build.UnitTests/Instance/HostServices_Tests.cs
index 0bd541928cd..318d56ef351 100644
--- a/src/Build.UnitTests/Instance/HostServices_Tests.cs
+++ b/src/Build.UnitTests/Instance/HostServices_Tests.cs
@@ -245,7 +245,7 @@ public void TestContradictoryAffinityCausesException_Any()
/// Test which ensures that setting an Any affinity for a project with a remote host object does not throws.
///
[Fact]
- [SkipOnTargetFramework(TargetFrameworkMonikers.Mono, "disable com tests on mono")]
+ [SkipOnMono("disable com tests on mono")]
public void TestNoContradictoryRemoteHostObjectAffinity()
{
HostServices hostServices = new HostServices();
@@ -301,7 +301,7 @@ public void TestNonContraditcoryHostObjectAllowed_Any()
/// Test which ensures the remote host object cannot affect a project which has the Any affinity specifically set.
///
[Fact]
- [SkipOnTargetFramework(TargetFrameworkMonikers.Mono, "disable com tests on mono")]
+ [SkipOnMono("disable com tests on mono")]
public void TestRegisterRemoteHostObjectNoAffect_Any2()
{
HostServices hostServices = new HostServices();
@@ -341,7 +341,7 @@ public void TestNonContraditcoryHostObjectAllowed_InProc()
/// Test which ensures the affinity for a project can be changed once the in process host object is registered
///
[Fact]
- [SkipOnTargetFramework(TargetFrameworkMonikers.Mono, "disable com tests on mono")]
+ [SkipOnMono("disable com tests on mono")]
public void TestAffinityChangeAfterRegisterInprocessHostObject()
{
HostServices hostServices = new HostServices();
@@ -452,7 +452,7 @@ public void UnloadedProjectDiscardsHostServices()
/// Tests that register overrides existing reigsted remote host object.
///
[Fact]
- [SkipOnTargetFramework(TargetFrameworkMonikers.Mono, "disable com tests on mono")]
+ [SkipOnMono("disable com tests on mono")]
public void TestRegisterOverrideExistingRegisted()
{
var hostServices = new HostServices();
diff --git a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj
index 52f699668fb..dc3a40ef3ce 100644
--- a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj
+++ b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj
@@ -18,6 +18,7 @@
+ all
@@ -38,12 +39,12 @@
TargetFramework=$(FullFrameworkTFM)TargetFramework=netstandard2.0
-
+ TargetFramework=$(FullFrameworkTFM)TargetFramework=$(FullFrameworkTFM)
- TargetFramework=net5.0
+ TargetFramework=net6.0
diff --git a/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs b/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs
index 8ccde2767a9..c994edab0ff 100644
--- a/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs
+++ b/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs
@@ -7,9 +7,11 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Build.Construction;
using Microsoft.Build.Execution;
using Microsoft.Build.Experimental.ProjectCache;
using Microsoft.Build.Framework;
@@ -62,6 +64,7 @@ public void Dispose()
public class GraphCacheResponse
{
+ private readonly IDictionary? _extraContentPerProjectNumber;
public const string CacheHitByProxy = nameof(CacheHitByProxy);
public const string CacheHitByTargetResult = nameof(CacheHitByTargetResult);
@@ -95,8 +98,9 @@ public class GraphCacheResponse
public Dictionary NonCacheMissResults { get; }
- public GraphCacheResponse(Dictionary graphEdges, Dictionary? nonCacheMissResults = null)
+ public GraphCacheResponse(Dictionary graphEdges, Dictionary? nonCacheMissResults = null, IDictionary? extraContentPerProjectNumber = null)
{
+ _extraContentPerProjectNumber = extraContentPerProjectNumber;
GraphEdges = graphEdges;
NonCacheMissResults = nonCacheMissResults ?? new Dictionary();
}
@@ -106,7 +110,7 @@ public ProjectGraph CreateGraph(TestEnvironment env)
return Helpers.CreateProjectGraph(
env,
GraphEdges,
- null,
+ _extraContentPerProjectNumber,
P2PTargets);
}
@@ -191,6 +195,31 @@ char Chr(int projectNumber)
}
}
+ public class DelegatingMockCache : ProjectCachePluginBase
+ {
+ private readonly Func> _getCacheResultDelegate;
+
+ public DelegatingMockCache(Func> getCacheResultDelegate)
+ {
+ _getCacheResultDelegate = getCacheResultDelegate;
+ }
+
+ public override Task BeginBuildAsync(CacheContext context, PluginLoggerBase logger, CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+
+ public override async Task GetCacheResultAsync(BuildRequestData buildRequest, PluginLoggerBase logger, CancellationToken cancellationToken)
+ {
+ return await _getCacheResultDelegate(buildRequest, logger, cancellationToken);
+ }
+
+ public override Task EndBuildAsync(PluginLoggerBase logger, CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+ }
+
[Flags]
public enum ErrorLocations
{
@@ -206,17 +235,50 @@ public enum ErrorKind
LoggedError
}
+ public class ConfigurableMockCache : ProjectCachePluginBase
+ {
+ public Func? BeginBuildImplementation { get; set; }
+ public Func>? GetCacheResultImplementation { get; set; }
+
+ public override Task BeginBuildAsync(CacheContext context, PluginLoggerBase logger, CancellationToken cancellationToken)
+ {
+ return BeginBuildImplementation != null
+ ? BeginBuildImplementation(context, logger, cancellationToken)
+ : Task.CompletedTask;
+ }
+
+ public override Task GetCacheResultAsync(
+ BuildRequestData buildRequest,
+ PluginLoggerBase logger,
+ CancellationToken cancellationToken)
+ {
+ return GetCacheResultImplementation != null
+ ? GetCacheResultImplementation(buildRequest, logger, cancellationToken)
+ : Task.FromResult(CacheResult.IndicateNonCacheHit(CacheResultType.CacheNotApplicable));
+ }
+
+ public override Task EndBuildAsync(PluginLoggerBase logger, CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+ }
+
public class InstanceMockCache : ProjectCachePluginBase
{
private readonly GraphCacheResponse? _testData;
- public ConcurrentQueue Requests { get; } = new ConcurrentQueue();
+ private readonly TimeSpan? _projectQuerySleepTime;
+ public ConcurrentQueue Requests { get; } = new();
public bool BeginBuildCalled { get; set; }
public bool EndBuildCalled { get; set; }
- public InstanceMockCache(GraphCacheResponse? testData = null)
+ private int _nextId;
+ public ConcurrentQueue QueryStartStops = new();
+
+ public InstanceMockCache(GraphCacheResponse? testData = null, TimeSpan? projectQuerySleepTime = null)
{
_testData = testData;
+ _projectQuerySleepTime = projectQuerySleepTime;
}
public override Task BeginBuildAsync(CacheContext context, PluginLoggerBase logger, CancellationToken cancellationToken)
@@ -228,18 +290,29 @@ public override Task BeginBuildAsync(CacheContext context, PluginLoggerBase logg
return Task.CompletedTask;
}
- public override Task GetCacheResultAsync(
+ public override async Task GetCacheResultAsync(
BuildRequestData buildRequest,
PluginLoggerBase logger,
CancellationToken cancellationToken)
{
+ var queryId = Interlocked.Increment(ref _nextId);
+
Requests.Enqueue(buildRequest);
+ QueryStartStops.Enqueue(queryId);
+
logger.LogMessage($"MockCache: GetCacheResultAsync for {buildRequest.ProjectFullPath}", MessageImportance.High);
- return
- Task.FromResult(
- _testData?.GetExpectedCacheResultForProjectNumber(GetProjectNumber(buildRequest.ProjectFullPath))
- ?? CacheResult.IndicateNonCacheHit(CacheResultType.CacheMiss));
+ buildRequest.ProjectInstance.ShouldNotBeNull("The cache plugin expects evaluated projects.");
+
+ if (_projectQuerySleepTime is not null)
+ {
+ await Task.Delay(_projectQuerySleepTime.Value);
+ }
+
+ QueryStartStops.Enqueue(queryId);
+
+ return _testData?.GetExpectedCacheResultForProjectNumber(GetProjectNumber(buildRequest.ProjectFullPath))
+ ?? CacheResult.IndicateNonCacheHit(CacheResultType.CacheMiss);
}
public override Task EndBuildAsync(PluginLoggerBase logger, CancellationToken cancellationToken)
@@ -389,16 +462,20 @@ public void ProjectCacheByBuildParametersAndGraphBuildWorks(GraphCacheResponse t
var graph = testData.CreateGraph(_env);
var mockCache = new InstanceMockCache(testData);
- buildParameters.ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
- mockCache,
- null,
- graph);
+ // Reset the environment variables stored in the build params to take into account TestEnvironmentChanges.
+ buildParameters = new BuildParameters(buildParameters, resetEnvironment: true)
+ {
+ ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
+ mockCache,
+ null,
+ graph)
+ };
using var buildSession = new Helpers.BuildManagerSession(_env, buildParameters);
var graphResult = buildSession.BuildGraph(graph);
- graphResult.OverallResult.ShouldBe(BuildResultCode.Success);
+ graphResult.ShouldHaveSucceeded();
buildSession.Dispose();
@@ -414,18 +491,26 @@ public void ProjectCacheByBuildParametersAndBottomUpBuildWorks(GraphCacheRespons
var graph = testData.CreateGraph(_env);
var mockCache = new InstanceMockCache(testData);
- buildParameters.ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
+ var projectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
mockCache,
null,
graph);
+ // Reset the environment variables stored in the build params to take into account TestEnvironmentChanges.
+ buildParameters = new BuildParameters(buildParameters, resetEnvironment: true)
+ {
+ ProjectCacheDescriptor = projectCacheDescriptor
+ };
+
+
using var buildSession = new Helpers.BuildManagerSession(_env, buildParameters);
var nodesToBuildResults = new Dictionary();
foreach (var node in graph.ProjectNodesTopologicallySorted)
{
var buildResult = buildSession.BuildProjectFile(node.ProjectInstance.FullPath);
- buildResult.OverallResult.ShouldBe(BuildResultCode.Success);
+
+ buildResult.ShouldHaveSucceeded();
nodesToBuildResults[node] = buildResult;
}
@@ -439,8 +524,86 @@ public void ProjectCacheByBuildParametersAndBottomUpBuildWorks(GraphCacheRespons
[Theory]
[MemberData(nameof(SuccessfulGraphsWithBuildParameters))]
- public void ProjectCacheByVSWorkaroundWorks(GraphCacheResponse testData, BuildParameters buildParameters)
+ public void ProjectCacheByVsWorkaroundWorks(GraphCacheResponse testData, BuildParameters buildParameters)
+ {
+ ProjectGraph? graph = null;
+
+ var (logger, nodesToBuildResults) = BuildGraphByVsWorkaround(
+ () =>
+ {
+ graph = testData.CreateGraph(_env);
+ return graph;
+ },
+ buildParameters);
+
+ graph.ShouldNotBeNull();
+
+ AssertCacheBuild(graph!, testData, null, logger, nodesToBuildResults);
+ }
+
+ [Fact]
+ public void ProjectCacheByVsWorkaroundIgnoresSlnDisabledProjects()
+ {
+ var testData = new GraphCacheResponse(
+ new Dictionary
+ {
+ {1, new[] {2}}
+ },
+ extraContentPerProjectNumber: new Dictionary()
+ {
+ {1, "false"}
+ });
+
+ ProjectGraph? graph = null;
+
+ var (logger, nodesToBuildResults) = BuildGraphByVsWorkaround(
+ graphProducer: () =>
+ {
+ graph = testData.CreateGraph(_env);
+ return graph;
+ },
+ assertBuildResults: false
+ );
+
+ graph.ShouldNotBeNull();
+
+ logger.FullLog.ShouldNotContain($"EntryPoint: {graph!.GraphRoots.First().ProjectInstance.FullPath}");
+ logger.FullLog.ShouldContain($"EntryPoint: {graph.GraphRoots.First().ProjectReferences.First().ProjectInstance.FullPath}");
+ }
+
+ [Fact]
+ public void ProjectCacheByVsWorkaroundShouldNotSupportSolutionOnlyDependencies()
{
+ var testData = new GraphCacheResponse(
+ new Dictionary
+ {
+ {1, Array.Empty()}
+ },
+ extraContentPerProjectNumber: new Dictionary()
+ {
+ {1, $"{Guid.NewGuid()}"}
+ });
+
+ var (logger, nodeResults) = BuildGraphByVsWorkaround(
+ graphProducer: () => testData.CreateGraph(_env),
+ assertBuildResults: false);
+
+ nodeResults.ShouldHaveSingleItem();
+
+ var buildResult = nodeResults.First().Value;
+ buildResult.OverallResult.ShouldBe(BuildResultCode.Failure);
+ buildResult.Exception.Message.ShouldContain("Project cache service does not support solution only dependencies when running under Visual Studio.");
+ }
+
+ private (MockLogger logger, Dictionary nodesToBuildResults) BuildGraphByVsWorkaround(
+ Func graphProducer,
+ BuildParameters? buildParameters = null,
+ bool assertBuildResults = true
+ )
+ {
+ var nodesToBuildResults = new Dictionary();
+ MockLogger? logger;
+
var currentBuildEnvironment = BuildEnvironmentHelper.Instance;
try
@@ -450,32 +613,174 @@ public void ProjectCacheByVSWorkaroundWorks(GraphCacheResponse testData, BuildPa
currentBuildEnvironment.Mode,
currentBuildEnvironment.CurrentMSBuildExePath,
currentBuildEnvironment.RunningTests,
- true,
- currentBuildEnvironment.VisualStudioInstallRootDirectory));
+ runningInVisualStudio: true,
+ visualStudioPath: currentBuildEnvironment.VisualStudioInstallRootDirectory));
+
+ // Reset the environment variables stored in the build params to take into account TestEnvironmentChanges.
+ buildParameters = buildParameters is null
+ ? new BuildParameters()
+ : new BuildParameters(buildParameters, resetEnvironment: true);
BuildManager.ProjectCacheItems.ShouldBeEmpty();
- var graph = testData.CreateGraph(_env);
+ var graph = graphProducer.Invoke();
BuildManager.ProjectCacheItems.ShouldHaveSingleItem();
+ var projectPaths = graph.ProjectNodes.Select(n => n.ProjectInstance.FullPath).ToArray();
+
+ // VS sets this global property on every project it builds.
+ var solutionConfigurationGlobalProperty = CreateSolutionConfigurationProperty(graph.ProjectNodes);
+
using var buildSession = new Helpers.BuildManagerSession(_env, buildParameters);
- var nodesToBuildResults = new Dictionary();
foreach (var node in graph.ProjectNodesTopologicallySorted)
{
var buildResult = buildSession.BuildProjectFile(
node.ProjectInstance.FullPath,
globalProperties:
- new Dictionary {{"SolutionPath", graph.GraphRoots.First().ProjectInstance.FullPath}});
- buildResult.OverallResult.ShouldBe(BuildResultCode.Success);
+ new Dictionary
+ {
+ { SolutionProjectGenerator.SolutionPathPropertyName, graph.GraphRoots.First().ProjectInstance.FullPath },
+ { SolutionProjectGenerator.CurrentSolutionConfigurationContents, solutionConfigurationGlobalProperty },
+ { PropertyNames.InnerBuildProperty, "TheInnerBuildProperty"},
+ { "TheInnerBuildProperty", "FooBar"},
+ });
+
+ if (assertBuildResults)
+ {
+ buildResult.ShouldHaveSucceeded();
+ }
nodesToBuildResults[node] = buildResult;
}
- buildSession.Logger.FullLog.ShouldContain("Graph entrypoint based");
+ logger = buildSession.Logger;
+
+ if (assertBuildResults)
+ {
+ logger.FullLog.ShouldContain("Visual Studio Workaround based");
+ logger.FullLog.ShouldContain("Running project cache with Visual Studio workaround");
+
+ foreach (var node in graph.ProjectNodes)
+ {
+ var projectPath = node.ProjectInstance.FullPath;
+ var projectName = Path.GetFileNameWithoutExtension(projectPath);
+
+ // Ensure MSBuild passes config / platform information set by VS.
+ logger.FullLog.ShouldContain($"EntryPoint: {projectPath}");
+ logger.FullLog.ShouldContain($"Configuration:{projectName}Debug");
+ logger.FullLog.ShouldContain($"Platform:{projectName}x64");
+
+ // Ensure MSBuild removes the inner build property if present.
+ logger.FullLog.ShouldContain($"{PropertyNames.InnerBuildProperty}:TheInnerBuildProperty");
+ logger.FullLog.ShouldNotContain("TheInnerBuildProperty:FooBar");
+ }
+ }
+ }
+ finally
+ {
+ BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(currentBuildEnvironment);
+ BuildManager.ProjectCacheItems.Clear();
+ }
+
+ return (logger, nodesToBuildResults);
+ }
+
+ private static string CreateSolutionConfigurationProperty(IReadOnlyCollection projectNodes)
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine("");
+
+ foreach (var node in projectNodes)
+ {
+ var projectPath = node.ProjectInstance.FullPath;
+ var projectName = Path.GetFileNameWithoutExtension(projectPath);
+
+ var buildProjectInSolutionValue = node.ProjectInstance.GetPropertyValue("BuildProjectInSolution");
+ var buildProjectInSolutionAttribute = string.IsNullOrWhiteSpace(buildProjectInSolutionValue)
+ ? string.Empty
+ : $"BuildProjectInSolution=\"{buildProjectInSolutionValue}\"";
+
+ var projectDependencyValue = node.ProjectInstance.GetPropertyValue("ProjectDependency");
+ var projectDependencyElement = string.IsNullOrWhiteSpace(projectDependencyValue)
+ ? string.Empty
+ : $"";
+
+ sb.AppendLine($"{projectName}Debug|{projectName}x64{projectDependencyElement}");
+ }
+
+ sb.AppendLine("");
+
+ return sb.ToString();
+ }
+
+ [Fact]
+ public void DesignTimeBuildsDuringVsWorkaroundShouldDisableTheCache()
+ {
+ var currentBuildEnvironment = BuildEnvironmentHelper.Instance;
+
+ var designTimeBuildProperty = $" <{DesignTimeProperties.DesignTimeBuild}>true{DesignTimeProperties.DesignTimeBuild}> ";
+
+ // Use a few references to stress test the design time build workaround logic.
+ var referenceNumbers = Enumerable.Range(2, NativeMethodsShared.GetLogicalCoreCount()).ToArray();
+
+ var testData = new GraphCacheResponse(
+ graphEdges: new Dictionary
+ {
+ {1, referenceNumbers}
+ },
+ nonCacheMissResults: null,
+ extraContentPerProjectNumber: referenceNumbers.ToDictionary(r => r, _ => designTimeBuildProperty));
+
+ try
+ {
+ BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(
+ new BuildEnvironment(
+ currentBuildEnvironment.Mode,
+ currentBuildEnvironment.CurrentMSBuildExePath,
+ currentBuildEnvironment.RunningTests,
+ runningInVisualStudio: true,
+ visualStudioPath: currentBuildEnvironment.VisualStudioInstallRootDirectory));
+
+ var graph = testData.CreateGraph(_env);
+
+ var rootNode = graph.GraphRoots.First();
+ var globalProperties = new Dictionary { { "SolutionPath", rootNode.ProjectInstance.FullPath } };
+
+ using var buildSession = new Helpers.BuildManagerSession(_env);
+
+ // Build references in parallel.
+ var referenceBuildTasks = rootNode.ProjectReferences.Select(
+ r => buildSession.BuildProjectFileAsync(
+ r.ProjectInstance.FullPath,
+ globalProperties: globalProperties));
+
+ foreach (var task in referenceBuildTasks)
+ {
+ var buildResult = task.Result;
+ buildResult.ShouldHaveSucceeded();
+ }
+
+ buildSession
+ .BuildProjectFile(rootNode.ProjectInstance.FullPath, globalProperties: globalProperties)
+ .ShouldHaveSucceeded();
+
+ buildSession.Dispose();
+
+ buildSession.Logger.FullLog.ShouldContain("Visual Studio Workaround based");
- AssertCacheBuild(graph, testData, null, buildSession.Logger, nodesToBuildResults);
+ // Design time builds should not initialize the plugin.
+ buildSession.Logger.FullLog.ShouldNotContain("Running project cache with Visual Studio workaround");
+
+ // Cache doesn't get initialized and queried.
+ buildSession.Logger.FullLog.ShouldNotContain("BeginBuildAsync");
+ buildSession.Logger.FullLog.ShouldNotContain("GetCacheResultAsync for");
+ buildSession.Logger.FullLog.ShouldNotContain("Querying project cache for project");
+
+ // Cache does get disposed.
+ StringShouldContainSubstring(buildSession.Logger.FullLog, "EndBuildAsync", 1);
}
finally
{
@@ -484,6 +789,57 @@ public void ProjectCacheByVSWorkaroundWorks(GraphCacheResponse testData, BuildPa
}
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void RunningProxyBuildsOnOutOfProcNodesShouldIssueWarning(bool disableInprocNodeViaEnvironmentVariable)
+ {
+ var testData = new GraphCacheResponse(
+ new Dictionary
+ {
+ {1, new[] {2}}
+ },
+ new Dictionary
+ {
+ {1, GraphCacheResponse.SuccessfulProxyTargetResult()},
+ {2, GraphCacheResponse.SuccessfulProxyTargetResult()}
+ });
+
+ var graph = testData.CreateGraph(_env);
+ var mockCache = new InstanceMockCache(testData);
+
+ var buildParameters = new BuildParameters
+ {
+ MaxNodeCount = Environment.ProcessorCount,
+ ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
+ mockCache,
+ null,
+ graph)
+ };
+
+ if (disableInprocNodeViaEnvironmentVariable)
+ {
+ _env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1");
+ }
+ else
+ {
+ buildParameters.DisableInProcNode = true;
+ }
+
+ using var buildSession = new Helpers.BuildManagerSession(_env, buildParameters);
+
+ var graphResult = buildSession.BuildGraph(graph);
+
+ graphResult.ShouldHaveSucceeded();
+
+ buildSession.Dispose();
+
+ buildSession.Logger.FullLog.ShouldContain("Static graph based");
+
+ buildSession.Logger.AssertMessageCount("MSB4274", 1);
+
+ }
+
private void AssertCacheBuild(
ProjectGraph graph,
GraphCacheResponse testData,
@@ -619,7 +975,7 @@ public void CacheShouldNotGetQueriedForNestedBuildRequests(BuildParameters build
var buildResult = buildSession.BuildProjectFile(project1.Path);
- buildResult.OverallResult.ShouldBe(BuildResultCode.Success);
+ buildResult.ShouldHaveSucceeded();
buildSession.Logger.ProjectStartedEvents.Count.ShouldBe(2);
@@ -651,9 +1007,9 @@ public void CacheViaBuildParametersCanDiscoverAndLoadPluginFromAssembly()
var graphResult = buildSession.BuildGraph(graph);
- graphResult.OverallResult.ShouldBe(BuildResultCode.Success);
+ graphResult.ShouldHaveSucceeded();
- buildSession.Logger.FullLog.ShouldContain("Graph entrypoint based");
+ buildSession.Logger.FullLog.ShouldContain("Explicit entry-point based");
AssertCacheBuild(graph, testData, null, buildSession.Logger, graphResult.ResultsByNode);
}
@@ -674,7 +1030,7 @@ public void GraphBuildCanDiscoverAndLoadPluginFromAssembly()
var graphResult = buildSession.BuildGraph(graph);
- graphResult.OverallResult.ShouldBe(BuildResultCode.Success);
+ graphResult.ShouldHaveSucceeded();
buildSession.Logger.FullLog.ShouldContain("Static graph based");
@@ -728,10 +1084,10 @@ public void BuildFailsWhenCacheBuildResultIsWrong()
mockCache.Requests.Count.ShouldBe(2);
- buildResult.ResultsByNode.First(r => GetProjectNumber(r.Key) == 2).Value.OverallResult.ShouldBe(BuildResultCode.Success);
- buildResult.ResultsByNode.First(r => GetProjectNumber(r.Key) == 1).Value.OverallResult.ShouldBe(BuildResultCode.Failure);
+ buildResult.ResultsByNode.First(r => GetProjectNumber(r.Key) == 2).Value.ShouldHaveSucceeded();
+ buildResult.ResultsByNode.First(r => GetProjectNumber(r.Key) == 1).Value.ShouldHaveFailed();
- buildResult.OverallResult.ShouldBe(BuildResultCode.Failure);
+ buildResult.ShouldHaveFailed();
buildSession.Logger.FullLog.ShouldContain("Reference file [Invalid file] does not exist");
}
@@ -739,8 +1095,6 @@ public void BuildFailsWhenCacheBuildResultIsWrong()
[Fact]
public void GraphBuildErrorsIfMultiplePluginsAreFound()
{
- _env.DoNotLaunchDebugger();
-
var graph = Helpers.CreateProjectGraph(
_env,
new Dictionary
@@ -757,16 +1111,13 @@ public void GraphBuildErrorsIfMultiplePluginsAreFound()
using var buildSession = new Helpers.BuildManagerSession(_env);
var graphResult = buildSession.BuildGraph(graph);
-
- graphResult.OverallResult.ShouldBe(BuildResultCode.Failure);
- graphResult.Exception.Message.ShouldContain("A single project cache plugin must be specified but multiple where found:");
+
+ graphResult.ShouldHaveFailed("A single project cache plugin must be specified but multiple where found:");
}
[Fact]
public void GraphBuildErrorsIfNotAllNodeDefineAPlugin()
{
- _env.DoNotLaunchDebugger();
-
var graph = Helpers.CreateProjectGraph(
_env,
dependencyEdges: new Dictionary
@@ -788,9 +1139,8 @@ public void GraphBuildErrorsIfNotAllNodeDefineAPlugin()
using var buildSession = new Helpers.BuildManagerSession(_env);
var graphResult = buildSession.BuildGraph(graph);
-
- graphResult.OverallResult.ShouldBe(BuildResultCode.Failure);
- graphResult.Exception.Message.ShouldContain("When any static graph node defines a project cache, all nodes must define the same project cache.");
+
+ graphResult.ShouldHaveFailed("When any static graph node defines a project cache, all nodes must define the same project cache.");
}
public static IEnumerable CacheExceptionLocationsTestData
@@ -800,7 +1150,7 @@ public static IEnumerable CacheExceptionLocationsTestData
// Plugin constructors cannot log errors, they can only throw exceptions.
yield return new object[] { ErrorLocations.Constructor, ErrorKind.Exception };
- foreach (var errorKind in new[]{ErrorKind.Exception, ErrorKind.LoggedError})
+ foreach (var errorKind in new[] { ErrorKind.Exception, ErrorKind.LoggedError })
{
yield return new object[] { ErrorLocations.BeginBuildAsync, errorKind };
yield return new object[] { ErrorLocations.BeginBuildAsync | ErrorLocations.GetCacheResultAsync, errorKind };
@@ -819,8 +1169,6 @@ public static IEnumerable CacheExceptionLocationsTestData
[MemberData(nameof(CacheExceptionLocationsTestData))]
public void EngineShouldHandleExceptionsFromCachePluginViaBuildParameters(ErrorLocations errorLocations, ErrorKind errorKind)
{
- _env.DoNotLaunchDebugger();
-
SetEnvironmentForErrorLocations(errorLocations, errorKind.ToString());
var project = _env.CreateFile("1.proj", @$"
@@ -874,11 +1222,11 @@ public void EngineShouldHandleExceptionsFromCachePluginViaBuildParameters(ErrorL
// so the build submission should be successful.
if (errorLocations == ErrorLocations.EndBuildAsync)
{
- buildResult.OverallResult.ShouldBe(BuildResultCode.Success);
+ buildResult.ShouldHaveSucceeded();
}
else
{
- buildResult.OverallResult.ShouldBe(BuildResultCode.Failure);
+ buildResult.ShouldHaveFailed();
}
}
finally
@@ -940,8 +1288,6 @@ public void EngineShouldHandleExceptionsFromCachePluginViaBuildParameters(ErrorL
[MemberData(nameof(CacheExceptionLocationsTestData))]
public void EngineShouldHandleExceptionsFromCachePluginViaGraphBuild(ErrorLocations errorLocations, ErrorKind errorKind)
{
- _env.DoNotLaunchDebugger();
-
SetEnvironmentForErrorLocations(errorLocations, errorKind.ToString());
var graph = Helpers.CreateProjectGraph(
@@ -981,7 +1327,7 @@ public void EngineShouldHandleExceptionsFromCachePluginViaGraphBuild(ErrorLocati
logger.FullLog.ShouldContain("Loading the following project cache plugin:");
// Static graph build initializes and tears down the cache plugin so all cache plugin exceptions should end up in the GraphBuildResult
- buildResult.OverallResult.ShouldBe(BuildResultCode.Failure);
+ buildResult.ShouldHaveFailed();
buildResult.Exception.ShouldBeOfType();
@@ -1029,8 +1375,6 @@ public void EngineShouldHandleExceptionsFromCachePluginViaGraphBuild(ErrorLocati
[Fact]
public void EndBuildShouldGetCalledOnceWhenItThrowsExceptionsFromGraphBuilds()
{
- _env.DoNotLaunchDebugger();
-
var project = _env.CreateFile(
"1.proj",
@$"
@@ -1054,14 +1398,14 @@ public void EndBuildShouldGetCalledOnceWhenItThrowsExceptionsFromGraphBuilds()
var logger = buildSession.Logger;
- GraphBuildResult? buildResult = null;
+ GraphBuildResult buildResult = null!;
Should.NotThrow(
() =>
{
buildResult = buildSession.BuildGraph(new ProjectGraph(project.Path));
});
- buildResult!.OverallResult.ShouldBe(BuildResultCode.Failure);
+ buildResult.ShouldHaveFailed();
buildResult.Exception.InnerException!.ShouldNotBeNull();
buildResult.Exception.InnerException!.Message.ShouldContain("Cache plugin exception from EndBuildAsync");
@@ -1070,6 +1414,296 @@ public void EndBuildShouldGetCalledOnceWhenItThrowsExceptionsFromGraphBuilds()
StringShouldContainSubstring(logger.FullLog, $"{nameof(AssemblyMockCache)}: EndBuildAsync", expectedOccurrences: 1);
}
+ [Theory]
+ [InlineData(false, false)]
+ [InlineData(true, true)]
+ public void CacheShouldBeQueriedInParallelDuringGraphBuilds(bool useSynchronousLogging, bool disableInprocNode)
+ {
+ var referenceNumbers = new []{2, 3, 4};
+
+ var testData = new GraphCacheResponse(
+ new Dictionary
+ {
+ {1, referenceNumbers}
+ },
+ referenceNumbers.ToDictionary(k => k, k => GraphCacheResponse.SuccessfulProxyTargetResult())
+ );
+
+ var graph = testData.CreateGraph(_env);
+
+ var completedCacheRequests = new ConcurrentBag();
+ var task2Completion = new TaskCompletionSource();
+ task2Completion.Task.IsCompleted.ShouldBeFalse();
+
+ var cache = new DelegatingMockCache(
+ async (buildRequest, _, _) =>
+ {
+ var projectNumber = GetProjectNumber(buildRequest.ProjectFullPath);
+
+ try
+ {
+ if (projectNumber == 2)
+ {
+ await task2Completion.Task;
+ }
+
+ return testData.GetExpectedCacheResultForProjectNumber(projectNumber);
+ }
+ finally
+ {
+ completedCacheRequests.Add(projectNumber);
+ }
+ });
+
+ using var buildSession = new Helpers.BuildManagerSession(_env, new BuildParameters()
+ {
+ MaxNodeCount = NativeMethodsShared.GetLogicalCoreCount(),
+ ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
+ cache,
+ entryPoints: null,
+ graph),
+ UseSynchronousLogging = useSynchronousLogging,
+ DisableInProcNode = disableInprocNode
+ });
+
+ var task2 = BuildProjectFileAsync(2);
+ var task3 = BuildProjectFileAsync(3);
+ var task4 = BuildProjectFileAsync(4);
+
+ task3.Result.ShouldHaveSucceeded();
+ completedCacheRequests.ShouldContain(3);
+ task4.Result.ShouldHaveSucceeded();
+ completedCacheRequests.ShouldContain(4);
+
+ // task 2 hasn't been instructed to finish yet
+ task2.IsCompleted.ShouldBeFalse();
+ completedCacheRequests.ShouldNotContain(2);
+
+ task2Completion.SetResult(true);
+
+ task2.Result.ShouldHaveSucceeded();
+ completedCacheRequests.ShouldContain(2);
+
+ var task1 = BuildProjectFileAsync(1);
+ task1.Result.ShouldHaveSucceeded();
+ completedCacheRequests.ShouldContain(1);
+
+ Task BuildProjectFileAsync(int projectNumber)
+ {
+ return buildSession.BuildProjectFileAsync(graph.ProjectNodes.First(n => GetProjectNumber(n) == projectNumber).ProjectInstance.FullPath);
+ }
+ }
+
+ [Theory]
+ [InlineData(false, false)]
+ // TODO: Reenable when this gets into the main branch.
+ //[InlineData(true, true)]
+ public void ParallelStressTestForVsWorkaround(bool useSynchronousLogging, bool disableInprocNode)
+ {
+ var currentBuildEnvironment = BuildEnvironmentHelper.Instance;
+
+ try
+ {
+ BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(
+ new BuildEnvironment(
+ currentBuildEnvironment.Mode,
+ currentBuildEnvironment.CurrentMSBuildExePath,
+ currentBuildEnvironment.RunningTests,
+ runningInVisualStudio: true,
+ visualStudioPath: currentBuildEnvironment.VisualStudioInstallRootDirectory));
+
+ BuildManager.ProjectCacheItems.ShouldBeEmpty();
+
+ var referenceNumbers = Enumerable.Range(2, NativeMethodsShared.GetLogicalCoreCount() * 2).ToArray();
+
+ var testData = new GraphCacheResponse(
+ new Dictionary
+ {
+ {1, referenceNumbers}
+ },
+ referenceNumbers.ToDictionary(k => k, k => GraphCacheResponse.SuccessfulProxyTargetResult())
+ );
+
+ var graph = testData.CreateGraph(_env);
+
+ // Even though the assembly cache is discovered, we'll be overriding it with a descriptor based cache.
+ BuildManager.ProjectCacheItems.ShouldHaveSingleItem();
+
+ var solutionConfigurationGlobalProperty =
+ CreateSolutionConfigurationProperty(graph.ProjectNodes);
+
+ using var buildSession = new Helpers.BuildManagerSession(_env, new BuildParameters
+ {
+ MaxNodeCount = NativeMethodsShared.GetLogicalCoreCount(),
+ UseSynchronousLogging = useSynchronousLogging,
+ DisableInProcNode = disableInprocNode
+ });
+
+ var buildResultTasks = new List>();
+
+ foreach (var node in graph.ProjectNodes.Where(n => referenceNumbers.Contains(GetProjectNumber(n))))
+ {
+ var buildResultTask = buildSession.BuildProjectFileAsync(
+ node.ProjectInstance.FullPath,
+ globalProperties:
+ new Dictionary
+ {
+ { SolutionProjectGenerator.SolutionPathPropertyName, graph.GraphRoots.First().ProjectInstance.FullPath },
+ { SolutionProjectGenerator.CurrentSolutionConfigurationContents, solutionConfigurationGlobalProperty }
+ });
+
+ buildResultTasks.Add(buildResultTask);
+ }
+
+ foreach (var buildResultTask in buildResultTasks)
+ {
+ buildResultTask.Result.ShouldHaveSucceeded();
+ }
+
+ buildSession.BuildProjectFile(
+ graph.GraphRoots.First().ProjectInstance.FullPath,
+ globalProperties:
+ new Dictionary {{"SolutionPath", graph.GraphRoots.First().ProjectInstance.FullPath}})
+ .ShouldHaveSucceeded();
+
+ StringShouldContainSubstring(buildSession.Logger.FullLog, $"{AssemblyMockCache}: GetCacheResultAsync for", graph.ProjectNodes.Count);
+
+ buildSession.Logger.FullLog.ShouldContain("Visual Studio Workaround based");
+ buildSession.Logger.FullLog.ShouldContain("Running project cache with Visual Studio workaround");
+ }
+ finally
+ {
+ BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(currentBuildEnvironment);
+ BuildManager.ProjectCacheItems.Clear();
+ }
+ }
+
+ [Theory]
+ [InlineData(false, false)]
+ [InlineData(true, true)]
+ public void ParallelStressTest(bool useSynchronousLogging, bool disableInprocNode)
+ {
+ var referenceNumbers = Enumerable.Range(2, NativeMethodsShared.GetLogicalCoreCount() * 2).ToArray();
+
+ var testData = new GraphCacheResponse(
+ new Dictionary
+ {
+ {1, referenceNumbers}
+ },
+ referenceNumbers.ToDictionary(k => k, k => GraphCacheResponse.SuccessfulProxyTargetResult())
+ );
+
+ var graph = testData.CreateGraph(_env);
+ var cache = new InstanceMockCache(testData, TimeSpan.FromMilliseconds(50));
+
+ using var buildSession = new Helpers.BuildManagerSession(_env, new BuildParameters()
+ {
+ MaxNodeCount = NativeMethodsShared.GetLogicalCoreCount(),
+ ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
+ cache,
+ entryPoints: null,
+ graph),
+ UseSynchronousLogging = useSynchronousLogging,
+ DisableInProcNode = disableInprocNode
+ });
+
+ var graphResult = buildSession.BuildGraph(graph);
+
+ graphResult.ShouldHaveSucceeded();
+ cache.QueryStartStops.Count.ShouldBe(graph.ProjectNodes.Count * 2);
+ }
+
+ [Fact]
+ // Schedules different requests for the same BuildRequestConfiguration in parallel.
+ // The first batch of the requests are cache misses, the second batch are cache hits via proxy builds.
+ // The first batch is delayed so it starts intermingling with the second batch.
+ // This test ensures that scheduling proxy builds on the inproc node works nicely within the Scheduler
+ // if the BuildRequestConfigurations for those proxy builds have built before (or are still building) on
+ // the out of proc node.
+ // More details: https://github.com/dotnet/msbuild/pull/6635
+ public void ProxyCacheHitsOnPreviousCacheMissesShouldWork()
+ {
+ var cacheNotApplicableTarget = "NATarget";
+ var cacheHitTarget = "CacheHitTarget";
+ var proxyTarget = "ProxyTarget";
+
+ var project =
+@$"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+".Cleanup();
+
+ var projectPaths = Enumerable.Range(0, NativeMethodsShared.GetLogicalCoreCount())
+ .Select(i => _env.CreateFile($"project{i}.proj", project).Path)
+ .ToArray();
+
+ var cacheHitCount = 0;
+ var nonCacheHitCount = 0;
+
+ var buildParameters = new BuildParameters
+ {
+ ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
+ new ConfigurableMockCache
+ {
+ GetCacheResultImplementation = (request, _, _) =>
+ {
+ var projectFile = request.ProjectFullPath;
+
+ if (request.TargetNames.Contains(cacheNotApplicableTarget))
+ {
+ Interlocked.Increment(ref nonCacheHitCount);
+ return Task.FromResult(CacheResult.IndicateNonCacheHit(CacheResultType.CacheNotApplicable));
+ }
+ else
+ {
+ Interlocked.Increment(ref cacheHitCount);
+ return Task.FromResult(
+ CacheResult.IndicateCacheHit(
+ new ProxyTargets(new Dictionary {{proxyTarget, cacheHitTarget}})));
+ }
+ }
+ },
+ projectPaths.Select(p => new ProjectGraphEntryPoint(p)).ToArray(),
+ projectGraph: null),
+ MaxNodeCount = NativeMethodsShared.GetLogicalCoreCount()
+ };
+
+ using var buildSession = new Helpers.BuildManagerSession(_env, buildParameters);
+
+ var buildRequests = new List<(string, string)>();
+ buildRequests.AddRange(projectPaths.Select(r => (r, cacheNotApplicableTarget)));
+ buildRequests.AddRange(projectPaths.Select(r => (r, cacheHitTarget)));
+
+ var buildTasks = new List>();
+ foreach (var (projectPath, target) in buildRequests)
+ {
+ buildTasks.Add(buildSession.BuildProjectFileAsync(projectPath, new[] {target}));
+ }
+
+ foreach (var buildResult in buildTasks.Select(buildTask => buildTask.Result))
+ {
+ buildResult.Exception.ShouldBeNull();
+ buildResult.ShouldHaveSucceeded();
+ }
+
+ buildSession.Logger.ProjectStartedEvents.Count.ShouldBe(2 * projectPaths.Length);
+
+ cacheHitCount.ShouldBe(projectPaths.Length);
+ nonCacheHitCount.ShouldBe(projectPaths.Length);
+ }
+
private static void StringShouldContainSubstring(string aString, string substring, int expectedOccurrences)
{
aString.ShouldContain(substring);
diff --git a/src/Build/AssemblyInfo.cs b/src/Build/AssemblyInfo.cs
index 5717eb380fb..21e9b159651 100644
--- a/src/Build/AssemblyInfo.cs
+++ b/src/Build/AssemblyInfo.cs
@@ -26,14 +26,7 @@
// so that we don't run into known security issues with loading libraries from unsafe locations
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
-// Needed for the "hub-and-spoke model to locate and retrieve localized resources": https://msdn.microsoft.com/en-us/library/21a15yht(v=vs.110).aspx
-// We want "en" to require a satellite assembly for debug builds in order to flush out localization
-// issues, but we want release builds to work without it. Also, .net core does not have resource fallbacks
-#if (DEBUG && !RUNTIME_TYPE_NETCORE)
-[assembly: NeutralResourcesLanguage("en", UltimateResourceFallbackLocation.Satellite)]
-#else
[assembly: NeutralResourcesLanguage("en")]
-#endif
[assembly: ComVisible(false)]
[assembly: CLSCompliant(true)]
diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs
index ef032ea7791..ddabf21013b 100644
--- a/src/Build/BackEnd/BuildManager/BuildManager.cs
+++ b/src/Build/BackEnd/BuildManager/BuildManager.cs
@@ -30,7 +30,9 @@
using Microsoft.Build.Internal;
using Microsoft.Build.Logging;
using Microsoft.Build.Shared;
+using Microsoft.Build.Shared.Debugging;
using Microsoft.Build.Shared.FileSystem;
+using Microsoft.Build.Utilities;
using ForwardingLoggerRecord = Microsoft.Build.Logging.ForwardingLoggerRecord;
using LoggerDescription = Microsoft.Build.Logging.LoggerDescription;
@@ -244,7 +246,6 @@ public class BuildManager : INodePacketHandler, IBuildComponentHost, IDisposable
private IEnumerable _deferredBuildMessages;
private Task _projectCacheService;
- private bool _projectCacheServiceInstantiatedByVSWorkaround;
#if DEBUG
///
@@ -287,7 +288,7 @@ public BuildManager(string hostName)
_projectStartedEventHandler = OnProjectStarted;
_projectFinishedEventHandler = OnProjectFinished;
- _loggingThreadExceptionEventHandler = OnThreadException;
+ _loggingThreadExceptionEventHandler = OnLoggingThreadException;
_legacyThreadingData = new LegacyThreadingData();
_instantiationTimeUtc = DateTime.UtcNow;
}
@@ -410,6 +411,8 @@ public void BeginBuild(BuildParameters parameters)
{
lock (_syncLock)
{
+ AttachDebugger();
+
// Check for build in progress.
RequireState(BuildManagerState.Idle, "BuildInProgress");
@@ -485,7 +488,7 @@ public void BeginBuild(BuildParameters parameters)
ILoggingService InitializeLoggingService()
{
ILoggingService loggingService = CreateLoggingService(
- _buildParameters.Loggers,
+ AppendDebuggingLoggers(_buildParameters.Loggers),
_buildParameters.ForwardingLoggers,
_buildParameters.WarningsAsErrors,
_buildParameters.WarningsAsMessages);
@@ -517,8 +520,26 @@ ILoggingService InitializeLoggingService()
return loggingService;
}
+ // VS builds discard many msbuild events so attach a binlogger to capture them all.
+ IEnumerable AppendDebuggingLoggers(IEnumerable loggers)
+ {
+ if (DebugUtils.ShouldDebugCurrentProcess is false ||
+ Traits.Instance.DebugEngine is false)
+ {
+ return loggers;
+ }
+
+ var binlogPath = DebugUtils.FindNextAvailableDebugFilePath($"{DebugUtils.ProcessInfoString}_BuildManager_{_hostName}.binlog");
+
+ var logger = new BinaryLogger { Parameters = binlogPath };
+
+ return (loggers ?? Enumerable.Empty()).Concat(new[] { logger });
+ }
+
void InitializeCaches()
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
var usesInputCaches = _buildParameters.UsesInputCaches();
if (usesInputCaches)
@@ -558,10 +579,40 @@ void InitializeCaches()
}
}
+ private static void AttachDebugger()
+ {
+ if (Debugger.IsAttached)
+ {
+ return;
+ }
+
+ if (!DebugUtils.ShouldDebugCurrentProcess)
+ {
+ return;
+ }
+
+ switch (Environment.GetEnvironmentVariable("MSBuildDebugBuildManagerOnStart"))
+ {
+#if FEATURE_DEBUG_LAUNCH
+ case "1":
+ Debugger.Launch();
+ break;
+#endif
+ case "2":
+ // Sometimes easier to attach rather than deal with JIT prompt
+ Process currentProcess = Process.GetCurrentProcess();
+ Console.WriteLine($"Waiting for debugger to attach ({currentProcess.MainModule.FileName} PID {currentProcess.Id}). Press enter to continue...");
+ Console.ReadLine();
+ break;
+ }
+ }
+
private void InitializeProjectCacheService(
ProjectCacheDescriptor pluginDescriptor,
CancellationToken cancellationToken)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
if (_projectCacheService != null)
{
ErrorUtilities.ThrowInternalError("Only one project cache plugin may be set on the BuildManager during a begin / end build session");
@@ -781,10 +832,15 @@ public void EndBuild()
ShutdownConnectedNodes(false /* normal termination */);
_noNodesActiveEvent.WaitOne();
- // Wait for all of the actions in the work queue to drain. Wait() could throw here if there was an unhandled exception
- // in the work queue, but the top level exception handler there should catch everything and have forwarded it to the
+ // Wait for all of the actions in the work queue to drain.
+ // _workQueue.Completion.Wait() could throw here if there was an unhandled exception in the work queue,
+ // but the top level exception handler there should catch everything and have forwarded it to the
// OnThreadException method in this class already.
_workQueue.Complete();
+ if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0))
+ {
+ _workQueue.Completion.Wait();
+ }
// Stop the graph scheduling thread(s)
_graphSchedulingCancellationSource?.Cancel();
@@ -1018,30 +1074,10 @@ internal void ExecuteSubmission(BuildSubmission submission, bool allowMainThread
ErrorUtilities.VerifyThrowArgumentNull(submission, nameof(submission));
ErrorUtilities.VerifyThrow(!submission.IsCompleted, "Submission already complete.");
- bool thisMethodIsAsync = false;
-
- if (ProjectCacheIsPresent())
- {
- thisMethodIsAsync = true;
-
- // Potential long running operations:
- // - submission may need evaluation
- // - project cache may need initializing
- // - project cache will be queried
- // Use separate thread to unblock calling thread.
- Task.Factory.StartNew(
- ExecuteSubmissionImpl,
- CancellationToken.None,
- TaskCreationOptions.LongRunning,
- TaskScheduler.Default
- );
- }
- else
- {
- ExecuteSubmissionImpl();
- }
+ BuildRequestConfiguration resolvedConfiguration = null;
+ bool shuttingDown = false;
- void ExecuteSubmissionImpl()
+ try
{
lock (_syncLock)
{
@@ -1070,271 +1106,181 @@ void ExecuteSubmissionImpl()
VerifyStateInternal(BuildManagerState.Building);
- try
+ // If we have an unnamed project, assign it a temporary name.
+ if (string.IsNullOrEmpty(submission.BuildRequestData.ProjectFullPath))
{
- // If we have an unnamed project, assign it a temporary name.
- if (string.IsNullOrEmpty(submission.BuildRequestData.ProjectFullPath))
- {
- ErrorUtilities.VerifyThrow(
- submission.BuildRequestData.ProjectInstance != null,
- "Unexpected null path for a submission with no ProjectInstance.");
-
- // If we have already named this instance when it was submitted previously during this build, use the same
- // name so that we get the same configuration (and thus don't cause it to rebuild.)
- if (!_unnamedProjectInstanceToNames.TryGetValue(submission.BuildRequestData.ProjectInstance, out var tempName))
- {
- tempName = "Unnamed_" + _nextUnnamedProjectId++;
- _unnamedProjectInstanceToNames[submission.BuildRequestData.ProjectInstance] = tempName;
- }
-
- submission.BuildRequestData.ProjectFullPath = Path.Combine(
- submission.BuildRequestData.ProjectInstance.GetProperty(ReservedPropertyNames.projectDirectory).EvaluatedValue,
- tempName);
- }
-
- // Create/Retrieve a configuration for each request
- var buildRequestConfiguration = new BuildRequestConfiguration(submission.BuildRequestData, _buildParameters.DefaultToolsVersion);
- var matchingConfiguration = _configCache.GetMatchingConfiguration(buildRequestConfiguration);
- var newConfiguration = ResolveConfiguration(
- buildRequestConfiguration,
- matchingConfiguration,
- submission.BuildRequestData.Flags.HasFlag(BuildRequestDataFlags.ReplaceExistingProjectInstance));
-
- newConfiguration.ExplicitlyLoaded = true;
-
- submission.BuildRequest = CreateRealBuildRequest(submission, newConfiguration.ConfigurationId);
+ ErrorUtilities.VerifyThrow(
+ submission.BuildRequestData.ProjectInstance != null,
+ "Unexpected null path for a submission with no ProjectInstance.");
- // TODO: Remove this when VS gets updated to setup project cache plugins.
- AutomaticallyDetectAndInstantiateProjectCacheServiceForVisualStudio(submission, newConfiguration);
-
- CacheResult cacheResult = null;
- if (_projectCacheService != null)
+ // If we have already named this instance when it was submitted previously during this build, use the same
+ // name so that we get the same configuration (and thus don't cause it to rebuild.)
+ if (!_unnamedProjectInstanceToNames.TryGetValue(submission.BuildRequestData.ProjectInstance, out var tempName))
{
- cacheResult = QueryCache(submission, newConfiguration);
+ tempName = "Unnamed_" + _nextUnnamedProjectId++;
+ _unnamedProjectInstanceToNames[submission.BuildRequestData.ProjectInstance] = tempName;
}
- if (cacheResult == null || cacheResult.ResultType != CacheResultType.CacheHit)
- {
- // Issue the real build request.
- SubmitBuildRequest();
- }
- else if (cacheResult?.ResultType == CacheResultType.CacheHit && cacheResult.ProxyTargets != null)
- {
- // Setup submission.BuildRequest with proxy targets. The proxy request is built on the inproc node (to avoid ProjectInstance serialization).
- // The proxy target results are used as results for the real targets.
+ submission.BuildRequestData.ProjectFullPath = Path.Combine(
+ submission.BuildRequestData.ProjectInstance.GetProperty(ReservedPropertyNames.projectDirectory).EvaluatedValue,
+ tempName);
+ }
- submission.BuildRequest = CreateProxyBuildRequest(
- submission,
- newConfiguration.ConfigurationId,
- cacheResult.ProxyTargets);
+ // Create/Retrieve a configuration for each request
+ var buildRequestConfiguration = new BuildRequestConfiguration(submission.BuildRequestData, _buildParameters.DefaultToolsVersion);
+ var matchingConfiguration = _configCache.GetMatchingConfiguration(buildRequestConfiguration);
+ resolvedConfiguration = ResolveConfiguration(
+ buildRequestConfiguration,
+ matchingConfiguration,
+ submission.BuildRequestData.Flags.HasFlag(BuildRequestDataFlags.ReplaceExistingProjectInstance));
- SubmitBuildRequest();
- }
- else if (cacheResult?.ResultType == CacheResultType.CacheHit && cacheResult.BuildResult != null)
- {
- // Mark the build submission as complete with the provided results and return.
- var result = new BuildResult(submission.BuildRequest);
+ resolvedConfiguration.ExplicitlyLoaded = true;
- foreach (var targetResult in cacheResult.BuildResult.ResultsByTarget)
- {
- result.AddResultsForTarget(targetResult.Key, targetResult.Value);
- }
-
- _resultsCache.AddResult(result);
- submission.CompleteLogging(false);
- ReportResultsToSubmission(result);
- }
- }
- // This catch should always be the first one because when this method runs in a separate thread
- // and throws an exception there is nobody there to observe the exception.
- catch (Exception ex) when (thisMethodIsAsync)
- {
- HandleExecuteSubmissionException(submission, ex);
- }
- catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
- {
- HandleExecuteSubmissionException(submission, ex);
- throw;
- }
- void SubmitBuildRequest()
+ // assign shutting down to local variable to avoid race condition: "setting _shuttingDown after this point during this method execution"
+ shuttingDown = _shuttingDown;
+ if (!shuttingDown)
{
- if (CheckForShutdown())
+ if (ProjectCacheIsPresent())
{
- return;
+ IssueCacheRequestForBuildSubmission(new CacheRequest(submission, resolvedConfiguration));
+ }
+ else
+ {
+ AddBuildRequestToSubmission(submission, resolvedConfiguration.ConfigurationId);
+ IssueBuildRequestForBuildSubmission(submission, resolvedConfiguration, allowMainThreadBuild);
}
-
- _workQueue.Post(
- () =>
- {
- try
- {
- IssueBuildSubmissionToScheduler(submission, allowMainThreadBuild);
- }
- catch (BuildAbortedException bae)
- {
- // We were canceled before we got issued by the work queue.
- var result = new BuildResult(submission.BuildRequest, bae);
- submission.CompleteResults(result);
- submission.CompleteLogging(true);
- CheckSubmissionCompletenessAndRemove(submission);
- }
- catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
- {
- HandleExecuteSubmissionException(submission, ex);
- }
- });
}
}
}
-
- bool ProjectCacheIsPresent()
+ catch (ProjectCacheException ex)
{
- return _projectCacheService != null ||
- _buildParameters.ProjectCacheDescriptor != null ||
- (BuildEnvironmentHelper.Instance.RunningInVisualStudio && ProjectCacheItems.Count > 0);
+ ErrorUtilities.VerifyThrow(resolvedConfiguration is not null, "Cannot call project cache without having BuildRequestConfiguration");
+ CompleteSubmissionWithException(submission, resolvedConfiguration, ex);
}
-
- bool CheckForShutdown()
+ catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
{
- if (!_shuttingDown)
+ if (resolvedConfiguration is not null)
{
- return false;
+ CompleteSubmissionWithException(submission, resolvedConfiguration, ex);
}
+ else
+ {
+ HandleSubmissionException(submission, ex);
+ throw;
+ }
+ }
+ // We are shutting down so submission has to be completed with BuildAbortedException
+ Debug.Assert(!Monitor.IsEntered(_syncLock));
+ if (shuttingDown)
+ {
+ ErrorUtilities.VerifyThrow(resolvedConfiguration is not null, "Cannot call project cache without having BuildRequestConfiguration");
// We were already canceled!
- var result = new BuildResult(submission.BuildRequest, new BuildAbortedException());
- submission.CompleteResults(result);
- submission.CompleteLogging(true);
- CheckSubmissionCompletenessAndRemove(submission);
-
- return true;
+ CompleteSubmissionWithException(submission, resolvedConfiguration, new BuildAbortedException());
}
+ }
+
+ bool ProjectCacheIsPresent()
+ {
+ // TODO: remove after we change VS to set the cache descriptor via build parameters.
+ // TODO: no need to access the service when there's no design time builds.
+ var projectCacheService = GetProjectCacheService();
- CacheResult QueryCache(BuildSubmission buildSubmission, BuildRequestConfiguration newConfiguration)
+ if (projectCacheService != null && projectCacheService.DesignTimeBuildsDetected)
{
- ProjectCacheService cacheService = null;
+ return false;
+ }
+
+ return
+ projectCacheService != null ||
+ _buildParameters.ProjectCacheDescriptor != null ||
+ ProjectCachePresentViaVisualStudioWorkaround();
+ }
+
+ private static bool ProjectCachePresentViaVisualStudioWorkaround()
+ {
+ return BuildEnvironmentHelper.Instance.RunningInVisualStudio && ProjectCacheItems.Count > 0;
+ }
+
+ // Cache requests on configuration N do not block future build submissions depending on configuration N.
+ // It is assumed that the higher level build orchestrator (static graph scheduler, VS, quickbuild) submits a
+ // project build request only when its references have finished building.
+ private void IssueCacheRequestForBuildSubmission(CacheRequest cacheRequest)
+ {
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+ _workQueue.Post(() =>
+ {
try
{
- cacheService = _projectCacheService.Result;
+ var projectCacheService = GetProjectCacheService();
+
+ ErrorUtilities.VerifyThrow(
+ projectCacheService != null,
+ "This method should not get called if there's no project cache.");
+
+ ErrorUtilities.VerifyThrow(
+ !projectCacheService.DesignTimeBuildsDetected,
+ "This method should not get called if design time builds are detected.");
+
+ projectCacheService.PostCacheRequest(cacheRequest);
}
- catch
+ catch (Exception e)
{
- // Set to null so that EndBuild does not try to shut it down and thus rethrow the exception.
- _projectCacheService = null;
- throw;
+ CompleteSubmissionWithException(cacheRequest.Submission, cacheRequest.Configuration, e);
}
+ });
+ }
- // Project cache plugins require an evaluated project. Evaluate the submission if it's by path.
- LoadSubmissionProjectIntoConfiguration(buildSubmission, newConfiguration);
-
- var cacheResult = cacheService.GetCacheResultAsync(
- new BuildRequestData(
- newConfiguration.Project,
- buildSubmission.BuildRequestData.TargetNames.ToArray()))
- .GetAwaiter()
- .GetResult();
-
- return cacheResult;
- }
+ private ProjectCacheService GetProjectCacheService()
+ {
+ // TODO: remove after we change VS to set the cache descriptor via build parameters.
+ AutomaticallyDetectAndInstantiateProjectCacheServiceForVisualStudio();
- static BuildRequest CreateRealBuildRequest(BuildSubmission submission, int configurationId)
+ try
{
- return new BuildRequest(
- submission.SubmissionId,
- BackEnd.BuildRequest.InvalidNodeRequestId,
- configurationId,
- submission.BuildRequestData.TargetNames,
- submission.BuildRequestData.HostServices,
- BuildEventContext.Invalid,
- null,
- submission.BuildRequestData.Flags,
- submission.BuildRequestData.RequestedProjectState);
+ return _projectCacheService?.Result;
}
-
- static BuildRequest CreateProxyBuildRequest(
- BuildSubmission submission,
- int configurationId,
- ProxyTargets proxyTargets)
+ catch(Exception ex)
{
- return new BuildRequest(
- submission.SubmissionId,
- BackEnd.BuildRequest.InvalidNodeRequestId,
- configurationId,
- proxyTargets,
- submission.BuildRequestData.HostServices,
- submission.BuildRequestData.Flags,
- submission.BuildRequestData.RequestedProjectState);
+ if (ex is AggregateException ae && ae.InnerExceptions.Count == 1)
+ {
+ ex = ae.InnerExceptions.First();
+ }
+
+ // These are exceptions thrown during project cache startup (assembly load issues or cache BeginBuild exceptions).
+ // Set to null so that EndBuild does not try to shut it down and thus rethrow the exception.
+ Interlocked.Exchange(ref _projectCacheService, null);
+ throw ex;
}
}
- private void AutomaticallyDetectAndInstantiateProjectCacheServiceForVisualStudio(
- BuildSubmission submission,
- BuildRequestConfiguration config)
+ private void AutomaticallyDetectAndInstantiateProjectCacheServiceForVisualStudio()
{
if (BuildEnvironmentHelper.Instance.RunningInVisualStudio &&
ProjectCacheItems.Count > 0 &&
- !_projectCacheServiceInstantiatedByVSWorkaround &&
_projectCacheService == null &&
_buildParameters.ProjectCacheDescriptor == null)
{
- _projectCacheServiceInstantiatedByVSWorkaround = true;
-
- if (ProjectCacheItems.Count != 1)
- {
- ProjectCacheException.ThrowForMSBuildIssueWithTheProjectCache(
- "OnlyOneCachePluginMustBeSpecified",
- string.Join("; ", ProjectCacheItems.Values.Select(c => c.PluginPath)));
- }
-
- // Plugin needs the graph root (aka top BuildSubmission path, aka the solution path when in VS) which, under VS, is accessible
- // only by evaluating the submission and retrieving the 'SolutionPath' property set by VS. This is also the reason why
- // this method cannot be called from BeginBuild, because no build submissions are available there to extract the solution path from.
- LoadSubmissionProjectIntoConfiguration(submission, config);
-
- if (IsDesignTimeBuild(config.Project))
+ lock (_syncLock)
{
- // Design time builds do not use the project cache.
- return;
- }
-
- var solutionPath = config.Project.GetPropertyValue(SolutionProjectGenerator.SolutionPathPropertyName);
-
- ErrorUtilities.VerifyThrow(
- solutionPath != null && !string.IsNullOrWhiteSpace(solutionPath) && solutionPath != "*Undefined*",
- $"Expected VS to set a valid SolutionPath property but got: {solutionPath}");
-
- ErrorUtilities.VerifyThrow(
- FileSystems.Default.FileExists(solutionPath),
- $"Solution file does not exist: {solutionPath}");
-
- var projectCacheItem = ProjectCacheItems.First().Value;
+ if (_projectCacheService != null)
+ {
+ return;
+ }
- InitializeProjectCacheService(
- ProjectCacheDescriptor.FromAssemblyPath(
- projectCacheItem.PluginPath,
- new[]
- {
- new ProjectGraphEntryPoint(
- solutionPath,
- config.Project.GlobalProperties)
- },
- null,
- projectCacheItem.PluginSettings),
- CancellationToken.None);
- }
+ if (ProjectCacheItems.Count != 1)
+ {
+ ProjectCacheException.ThrowForMSBuildIssueWithTheProjectCache(
+ "OnlyOneCachePluginMustBeSpecified",
+ string.Join("; ", ProjectCacheItems.Values.Select(c => c.PluginPath)));
+ }
- static bool IsDesignTimeBuild(ProjectInstance project)
- {
- var designTimeBuild = project.GetPropertyValue(DesignTimeProperties.DesignTimeBuild);
- var buildingProject = project.GlobalPropertiesDictionary[DesignTimeProperties.BuildingProject]?.EvaluatedValue;
+ var projectCacheItem = ProjectCacheItems.First().Value;
- return MSBuildStringIsTrue(designTimeBuild) ||
- buildingProject != null && !MSBuildStringIsTrue(buildingProject);
+ InitializeProjectCacheService(ProjectCacheDescriptor.FromVisualStudioWorkaround(projectCacheItem), CancellationToken.None);
+ }
}
-
- static bool MSBuildStringIsTrue(string msbuildString) =>
- ConversionUtilities.ConvertStringToBool(msbuildString, nullOrWhitespaceIsFalse: true);
}
///
@@ -1375,7 +1321,7 @@ internal void ExecuteSubmission(GraphBuildSubmission submission)
}
catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
{
- HandleExecuteSubmissionException(submission, ex);
+ HandleSubmissionException(submission, ex);
}
},
_graphSchedulingCancellationSource.Token,
@@ -1384,7 +1330,7 @@ internal void ExecuteSubmission(GraphBuildSubmission submission)
}
catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
{
- HandleExecuteSubmissionException(submission, ex);
+ HandleSubmissionException(submission, ex);
throw;
}
}
@@ -1412,6 +1358,8 @@ private void LoadSubmissionProjectIntoConfiguration(BuildSubmission submission,
///
private void LoadSolutionIntoConfiguration(BuildRequestConfiguration config, BuildRequest request)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
if (config.IsLoaded)
{
// We've already processed it, nothing to do.
@@ -1578,11 +1526,34 @@ private void ProcessPacket(int node, INodePacket packet)
}
}
+ ///
+ /// To avoid deadlock possibility, this method MUST NOT be called inside of 'lock (_syncLock)'
+ ///
+ private void CompleteSubmissionWithException(BuildSubmission submission, BuildRequestConfiguration configuration, Exception exception)
+ {
+ Debug.Assert(!Monitor.IsEntered(_syncLock));
+
+ lock (_syncLock)
+ {
+ if (submission.BuildRequest is null)
+ {
+ AddBuildRequestToSubmission(submission, configuration.ConfigurationId);
+ }
+ }
+
+ HandleSubmissionException(submission, exception);
+ }
+
///
- /// Deals with exceptions that may be thrown as a result of ExecuteSubmission.
+ /// Deals with exceptions that may be thrown when handling a submission.
///
- private void HandleExecuteSubmissionException(BuildSubmission submission, Exception ex)
+ ///
+ /// To avoid deadlock possibility, this method MUST NOT be called inside of 'lock (_syncLock)'
+ ///
+ private void HandleSubmissionException(BuildSubmission submission, Exception ex)
{
+ Debug.Assert(!Monitor.IsEntered(_syncLock));
+
if (ex is AggregateException ae && ae.InnerExceptions.Count == 1)
{
ex = ae.InnerExceptions.First();
@@ -1597,22 +1568,54 @@ private void HandleExecuteSubmissionException(BuildSubmission submission, Except
}
}
- // BuildRequest may be null if the submission fails early on.
- if (submission.BuildRequest != null)
+ bool submissionNeedsCompletion;
+ lock (_syncLock)
{
- var result = new BuildResult(submission.BuildRequest, ex);
- submission.CompleteResults(result);
- submission.CompleteLogging(true);
+ // BuildRequest may be null if the submission fails early on.
+ submissionNeedsCompletion = submission.BuildRequest != null;
+ if (submissionNeedsCompletion)
+ {
+ var result = new BuildResult(submission.BuildRequest, ex);
+ submission.CompleteResults(result);
+ }
}
- _overallBuildSuccess = false;
- CheckSubmissionCompletenessAndRemove(submission);
+ if (submissionNeedsCompletion)
+ {
+ WaitForAllLoggingServiceEventsToBeProcessed();
+ }
+
+ lock (_syncLock)
+ {
+ if (submissionNeedsCompletion)
+ {
+ submission.CompleteLogging();
+ }
+
+ _overallBuildSuccess = false;
+ CheckSubmissionCompletenessAndRemove(submission);
+ }
+ }
+
+ ///
+ /// Waits to drain all events of logging service.
+ /// This method shall be used carefully because during draining, LoggingService will block all incoming events.
+ ///
+ ///
+ /// To avoid deadlock possibility, this method MUST NOT be called inside of 'lock (_syncLock)'
+ ///
+ private void WaitForAllLoggingServiceEventsToBeProcessed()
+ {
+ // this has to be called out of the lock (_syncLock)
+ // because processing events can callback to 'this' instance and cause deadlock
+ Debug.Assert(!Monitor.IsEntered(_syncLock));
+ ((LoggingService) ((IBuildComponentHost) this).LoggingService).WaitForThreadToProcessEvents();
}
///
/// Deals with exceptions that may be thrown as a result of ExecuteSubmission.
///
- private void HandleExecuteSubmissionException(GraphBuildSubmission submission, Exception ex)
+ private void HandleSubmissionException(GraphBuildSubmission submission, Exception ex)
{
if (ex is InvalidProjectFileException projectException)
{
@@ -1628,76 +1631,135 @@ private void HandleExecuteSubmissionException(GraphBuildSubmission submission, E
? ae.InnerExceptions.First()
: ex;
- if (submission.IsStarted)
+ lock (_syncLock)
{
- submission.CompleteResults(new GraphBuildResult(submission.SubmissionId, ex));
+ if (submission.IsStarted)
+ {
+ submission.CompleteResults(new GraphBuildResult(submission.SubmissionId, ex));
+ }
+
+ _overallBuildSuccess = false;
+ CheckSubmissionCompletenessAndRemove(submission);
}
+ }
- _overallBuildSuccess = false;
- CheckSubmissionCompletenessAndRemove(submission);
+ private static void AddBuildRequestToSubmission(BuildSubmission submission, int configurationId)
+ {
+ submission.BuildRequest = new BuildRequest(
+ submission.SubmissionId,
+ BackEnd.BuildRequest.InvalidNodeRequestId,
+ configurationId,
+ submission.BuildRequestData.TargetNames,
+ submission.BuildRequestData.HostServices,
+ BuildEventContext.Invalid,
+ null,
+ submission.BuildRequestData.Flags,
+ submission.BuildRequestData.RequestedProjectState);
+ }
+
+ private static void AddProxyBuildRequestToSubmission(BuildSubmission submission, int configurationId, ProxyTargets proxyTargets)
+ {
+ submission.BuildRequest = new BuildRequest(
+ submission.SubmissionId,
+ BackEnd.BuildRequest.InvalidNodeRequestId,
+ configurationId,
+ proxyTargets,
+ submission.BuildRequestData.HostServices,
+ submission.BuildRequestData.Flags,
+ submission.BuildRequestData.RequestedProjectState);
}
///
/// The submission is a top level build request entering the BuildManager.
/// Sends the request to the scheduler with optional legacy threading semantics behavior.
///
- private void IssueBuildSubmissionToScheduler(BuildSubmission submission, bool allowMainThreadBuild)
+ private void IssueBuildRequestForBuildSubmission(BuildSubmission submission, BuildRequestConfiguration configuration, bool allowMainThreadBuild = false)
{
- bool resetMainThreadOnFailure = false;
- try
- {
- lock (_syncLock)
+ _workQueue.Post(
+ () =>
{
- if (_shuttingDown)
+ try
+ {
+ IssueBuildSubmissionToSchedulerImpl(submission, allowMainThreadBuild);
+ }
+ catch (BuildAbortedException bae)
+ {
+ CompleteSubmissionWithException(submission, configuration, bae);
+ }
+ catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
{
- throw new BuildAbortedException();
+ HandleSubmissionException(submission, ex);
}
+ });
- if (allowMainThreadBuild && _buildParameters.LegacyThreadingSemantics)
+ void IssueBuildSubmissionToSchedulerImpl(BuildSubmission submission, bool allowMainThreadBuild)
+ {
+ var resetMainThreadOnFailure = false;
+ try
+ {
+ lock (_syncLock)
{
- if (_legacyThreadingData.MainThreadSubmissionId == -1)
+ if (_shuttingDown)
{
- resetMainThreadOnFailure = true;
- _legacyThreadingData.MainThreadSubmissionId = submission.SubmissionId;
+ throw new BuildAbortedException();
}
- }
- BuildRequestBlocker blocker = new BuildRequestBlocker(-1, Array.Empty(), new[] {submission.BuildRequest});
+ if (allowMainThreadBuild && _buildParameters.LegacyThreadingSemantics)
+ {
+ if (_legacyThreadingData.MainThreadSubmissionId == -1)
+ {
+ resetMainThreadOnFailure = true;
+ _legacyThreadingData.MainThreadSubmissionId = submission.SubmissionId;
+ }
+ }
+
+ BuildRequestBlocker blocker = new BuildRequestBlocker(-1, Array.Empty(), new[] {submission.BuildRequest});
- HandleNewRequest(Scheduler.VirtualNode, blocker);
+ HandleNewRequest(Scheduler.VirtualNode, blocker);
+ }
}
- }
- catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
- {
- InvalidProjectFileException projectException = ex as InvalidProjectFileException;
- if (projectException != null)
+ catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
{
- if (!projectException.HasBeenLogged)
+ var projectException = ex as InvalidProjectFileException;
+ if (projectException != null)
{
- BuildEventContext projectBuildEventContext = new BuildEventContext(submission.SubmissionId, 1, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId);
- ((IBuildComponentHost)this).LoggingService.LogInvalidProjectFileError(projectBuildEventContext, projectException);
- projectException.HasBeenLogged = true;
+ if (!projectException.HasBeenLogged)
+ {
+ BuildEventContext projectBuildEventContext = new BuildEventContext(submission.SubmissionId, 1, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId);
+ ((IBuildComponentHost)this).LoggingService.LogInvalidProjectFileError(projectBuildEventContext, projectException);
+ projectException.HasBeenLogged = true;
+ }
+ }
+ else if ((ex is BuildAbortedException) || ExceptionHandling.NotExpectedException(ex))
+ {
+ throw;
}
- }
- else if ((ex is BuildAbortedException) || ExceptionHandling.NotExpectedException(ex))
- {
- throw;
- }
- if (resetMainThreadOnFailure)
- {
- _legacyThreadingData.MainThreadSubmissionId = -1;
- }
+ lock (_syncLock)
+ {
- if (projectException == null)
- {
- BuildEventContext buildEventContext = new BuildEventContext(submission.SubmissionId, 1, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId);
- ((IBuildComponentHost)this).LoggingService.LogFatalBuildError(buildEventContext, ex, new BuildEventFileInfo(submission.BuildRequestData.ProjectFullPath));
- }
+ if (resetMainThreadOnFailure)
+ {
+ _legacyThreadingData.MainThreadSubmissionId = -1;
+ }
- submission.CompleteLogging(true);
- ReportResultsToSubmission(new BuildResult(submission.BuildRequest, ex));
- _overallBuildSuccess = false;
+ if (projectException == null)
+ {
+ var buildEventContext = new BuildEventContext(submission.SubmissionId, 1, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId);
+ ((IBuildComponentHost)this).LoggingService.LogFatalBuildError(buildEventContext, ex, new BuildEventFileInfo(submission.BuildRequestData.ProjectFullPath));
+ }
+ }
+
+ WaitForAllLoggingServiceEventsToBeProcessed();
+
+ lock (_syncLock)
+ {
+ submission.CompleteLogging();
+ ReportResultsToSubmission(new BuildResult(submission.BuildRequest, ex));
+ _overallBuildSuccess = false;
+ }
+
+ }
}
}
@@ -1761,11 +1823,17 @@ private void ExecuteGraphBuildScheduler(GraphBuildSubmission submission)
if (submission.BuildRequestData.GraphBuildOptions.Build)
{
var cacheServiceTask = Task.Run(() => SearchAndInitializeProjectCachePluginFromGraph(projectGraph));
- var targetListTask = Task.Run(() => projectGraph.GetTargetLists(submission.BuildRequestData.TargetNames));
+ var targetListTask = projectGraph.GetTargetLists(submission.BuildRequestData.TargetNames);
+
+ DumpGraph(projectGraph, targetListTask);
using DisposablePluginService cacheService = cacheServiceTask.Result;
- resultsPerNode = BuildGraph(projectGraph, targetListTask.Result, submission.BuildRequestData);
+ resultsPerNode = BuildGraph(projectGraph, targetListTask, submission.BuildRequestData);
+ }
+ else
+ {
+ DumpGraph(projectGraph);
}
ErrorUtilities.VerifyThrow(
@@ -1824,7 +1892,22 @@ private void ExecuteGraphBuildScheduler(GraphBuildSubmission submission)
ReportResultsToSubmission(result);
- _overallBuildSuccess = false;
+ lock (_syncLock)
+ {
+ _overallBuildSuccess = false;
+ }
+ }
+
+ static void DumpGraph(ProjectGraph graph, IReadOnlyDictionary> targetList = null)
+ {
+ if (Traits.Instance.DebugEngine is false)
+ {
+ return;
+ }
+
+ var logPath = DebugUtils.FindNextAvailableDebugFilePath($"{DebugUtils.ProcessInfoString}_ProjectGraph.dot");
+
+ File.WriteAllText(logPath, graph.ToDot(targetList));
}
}
@@ -2016,17 +2099,20 @@ public void Dispose()
///
private void ShutdownConnectedNodes(bool abort)
{
- _shuttingDown = true;
+ lock (_syncLock)
+ {
+ _shuttingDown = true;
- // If we are aborting, we will NOT reuse the nodes because their state may be compromised by attempts to shut down while the build is in-progress.
- _nodeManager.ShutdownConnectedNodes(!abort && _buildParameters.EnableNodeReuse);
+ // If we are aborting, we will NOT reuse the nodes because their state may be compromised by attempts to shut down while the build is in-progress.
+ _nodeManager.ShutdownConnectedNodes(!abort && _buildParameters.EnableNodeReuse);
- // if we are aborting, the task host will hear about it in time through the task building infrastructure;
- // so only shut down the task host nodes if we're shutting down tidily (in which case, it is assumed that all
- // tasks are finished building and thus that there's no risk of a race between the two shutdown pathways).
- if (!abort)
- {
- _taskHostNodeManager.ShutdownConnectedNodes(_buildParameters.EnableNodeReuse);
+ // if we are aborting, the task host will hear about it in time through the task building infrastructure;
+ // so only shut down the task host nodes if we're shutting down tidily (in which case, it is assumed that all
+ // tasks are finished building and thus that there's no risk of a race between the two shutdown pathways).
+ if (!abort)
+ {
+ _taskHostNodeManager.ShutdownConnectedNodes(_buildParameters.EnableNodeReuse);
+ }
}
}
@@ -2092,7 +2178,6 @@ private void Reset()
_workQueue = null;
_graphSchedulingCancellationSource = null;
_projectCacheService = null;
- _projectCacheServiceInstantiatedByVSWorkaround = false;
_acquiredProjectRootElementCacheFromProjectInstance = false;
_unnamedProjectInstanceToNames.Clear();
@@ -2141,6 +2226,8 @@ private int GetNewConfigurationId()
///
private BuildRequestConfiguration ResolveConfiguration(BuildRequestConfiguration unresolvedConfiguration, BuildRequestConfiguration matchingConfigurationFromCache, bool replaceProjectInstance)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
BuildRequestConfiguration resolvedConfiguration = matchingConfigurationFromCache ?? _configCache.GetMatchingConfiguration(unresolvedConfiguration);
if (resolvedConfiguration == null)
{
@@ -2172,12 +2259,16 @@ private BuildRequestConfiguration ResolveConfiguration(BuildRequestConfiguration
private void ReplaceExistingProjectInstance(BuildRequestConfiguration newConfiguration, BuildRequestConfiguration existingConfiguration)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
existingConfiguration.Project = newConfiguration.Project;
_resultsCache.ClearResultsForConfiguration(existingConfiguration.ConfigurationId);
}
private BuildRequestConfiguration AddNewConfiguration(BuildRequestConfiguration unresolvedConfiguration)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
var newConfigurationId = _scheduler.GetConfigurationIdFromPlan(unresolvedConfiguration.ProjectFullPath);
if (_configCache.HasConfiguration(newConfigurationId) || (newConfigurationId == BuildRequestConfiguration.InvalidConfigurationId))
@@ -2193,6 +2284,67 @@ private BuildRequestConfiguration AddNewConfiguration(BuildRequestConfiguration
return newConfiguration;
}
+ internal void PostCacheResult(CacheRequest cacheRequest, CacheResult cacheResult)
+ {
+ _workQueue.Post(() =>
+ {
+ if (cacheResult.Exception is not null)
+ {
+ CompleteSubmissionWithException(cacheRequest.Submission, cacheRequest.Configuration, cacheResult.Exception);
+ return;
+ }
+
+ HandleCacheResult();
+ });
+
+ void HandleCacheResult()
+ {
+ lock (_syncLock)
+ {
+ try
+ {
+ var submission = cacheRequest.Submission;
+ var configuration = cacheRequest.Configuration;
+
+ if (cacheResult.ResultType != CacheResultType.CacheHit)
+ {
+ // Issue the real build request.
+ AddBuildRequestToSubmission(submission, configuration.ConfigurationId);
+ IssueBuildRequestForBuildSubmission(submission, configuration, allowMainThreadBuild: false);
+ }
+ else if (cacheResult.ResultType == CacheResultType.CacheHit && cacheResult.ProxyTargets != null)
+ {
+ // Setup submission.BuildRequest with proxy targets. The proxy request is built on the inproc node (to avoid
+ // ProjectInstance serialization). The proxy target results are used as results for the real targets.
+ AddProxyBuildRequestToSubmission(submission, configuration.ConfigurationId, cacheResult.ProxyTargets);
+ IssueBuildRequestForBuildSubmission(submission, configuration, allowMainThreadBuild: false);
+ }
+ else if (cacheResult.ResultType == CacheResultType.CacheHit && cacheResult.BuildResult != null)
+ {
+ // Mark the build submission as complete with the provided results and return.
+
+ // There must be a build request for the results, so fake one.
+ AddBuildRequestToSubmission(submission, configuration.ConfigurationId);
+ var result = new BuildResult(submission.BuildRequest);
+
+ foreach (var cacheResult in cacheResult.BuildResult.ResultsByTarget)
+ {
+ result.AddResultsForTarget(cacheResult.Key, cacheResult.Value);
+ }
+
+ _resultsCache.AddResult(result);
+ submission.CompleteLogging();
+ ReportResultsToSubmission(result);
+ }
+ }
+ catch (Exception e)
+ {
+ CompleteSubmissionWithException(cacheRequest.Submission, cacheRequest.Configuration, e);
+ }
+ }
+ }
+ }
+
///
/// Handles a new request coming from a node.
///
@@ -2232,6 +2384,8 @@ private void HandleNewRequest(int node, BuildRequestBlocker blocker)
///
private void HandleResourceRequest(int node, ResourceRequest request)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
if (request.IsResourceAcquire)
{
// Resource request requires a response and may be blocking. Our continuation is effectively a callback
@@ -2256,6 +2410,8 @@ private void HandleResourceRequest(int node, ResourceRequest request)
///
private void HandleConfigurationRequest(int node, BuildRequestConfiguration unresolvedConfiguration)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
BuildRequestConfiguration resolvedConfiguration = ResolveConfiguration(unresolvedConfiguration, null, false);
var response = new BuildRequestConfigurationResponse(unresolvedConfiguration.ConfigurationId, resolvedConfiguration.ConfigurationId, resolvedConfiguration.ResultsNodeId);
@@ -2304,6 +2460,8 @@ private void HandleResult(int node, BuildResult result)
///
private void HandleNodeShutdown(int node, NodeShutdown shutdownPacket)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
_shuttingDown = true;
ErrorUtilities.VerifyThrow(_activeNodes.Contains(node), "Unexpected shutdown from node {0} which shouldn't exist.", node);
_activeNodes.Remove(node);
@@ -2366,6 +2524,8 @@ private void HandleNodeShutdown(int node, NodeShutdown shutdownPacket)
///
private void CheckForActiveNodesAndCleanUpSubmissions()
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
if (_activeNodes.Count == 0)
{
var submissions = new List(_buildSubmissions.Values);
@@ -2387,7 +2547,7 @@ private void CheckForActiveNodesAndCleanUpSubmissions()
// If we never received a project started event, consider logging complete anyhow, since the nodes have
// shut down.
- submission.CompleteLogging(waitForLoggingThread: false);
+ submission.CompleteLogging();
_overallBuildSuccess = _overallBuildSuccess && (submission.BuildResult.OverallResult == BuildResultCode.Success);
CheckSubmissionCompletenessAndRemove(submission);
@@ -2416,6 +2576,8 @@ private void CheckForActiveNodesAndCleanUpSubmissions()
///
private void PerformSchedulingActions(IEnumerable responses)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
foreach (ScheduleResponse response in responses)
{
switch (response.Action)
@@ -2514,7 +2676,7 @@ private void ReportResultsToSubmission(BuildResult result)
*/
if (!submission.LoggingCompleted && result.Exception != null)
{
- submission.CompleteLogging(waitForLoggingThread: false);
+ submission.CompleteLogging();
}
submission.CompleteResults(result);
@@ -2587,6 +2749,8 @@ private void CheckSubmissionCompletenessAndRemove(GraphBuildSubmission submissio
private void CheckAllSubmissionsComplete(BuildRequestDataFlags? flags)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
if (_buildSubmissions.Count == 0 && _graphBuildSubmissions.Count == 0)
{
if (flags.HasValue && flags.Value.HasFlag(BuildRequestDataFlags.ClearCachesAfterBuild))
@@ -2611,6 +2775,8 @@ private void CheckAllSubmissionsComplete(BuildRequestDataFlags? flags)
///
private NodeConfiguration GetNodeConfiguration()
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
if (_nodeConfiguration == null)
{
// Get the remote loggers
@@ -2637,7 +2803,7 @@ private NodeConfiguration GetNodeConfiguration()
}
///
- /// Handler for thread exceptions (logging thread, communications thread). This handler will only get called if the exception did not previously
+ /// Handler for thread exceptions. This handler will only get called if the exception did not previously
/// get handled by a node exception handlers (for instance because the build is complete for the node.) In this case we
/// get the exception and will put it into the OverallBuildResult so that the host can see what happened.
///
@@ -2667,7 +2833,7 @@ private void OnThreadException(Exception e)
{
submission.BuildResult.Exception = e;
}
- submission.CompleteLogging(waitForLoggingThread: false);
+ submission.CompleteLogging();
submission.CompleteResults(new BuildResult(submission.BuildRequest, e));
CheckSubmissionCompletenessAndRemove(submission);
@@ -2694,22 +2860,49 @@ private void OnThreadException(Exception e)
}
}
+ ///
+ /// Handler for LoggingService thread exceptions.
+ ///
+ private void OnLoggingThreadException(Exception e)
+ {
+ if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0))
+ {
+ _workQueue.Post(() => OnThreadException(e));
+ }
+ else
+ {
+ OnThreadException(e);
+ }
+ }
+
///
/// Raised when a project finished logging message has been processed.
///
private void OnProjectFinished(object sender, ProjectFinishedEventArgs e)
{
- lock (_syncLock)
+ if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0))
{
- if (_projectStartedEvents.TryGetValue(e.BuildEventContext.SubmissionId, out var originalArgs))
+ _workQueue.Post(() => OnProjectFinishedBody(e));
+ }
+ else
+ {
+ OnProjectFinishedBody(e);
+ }
+
+ void OnProjectFinishedBody(ProjectFinishedEventArgs e)
+ {
+ lock (_syncLock)
{
- if (originalArgs.BuildEventContext.Equals(e.BuildEventContext))
+ if (_projectStartedEvents.TryGetValue(e.BuildEventContext.SubmissionId, out var originalArgs))
{
- _projectStartedEvents.Remove(e.BuildEventContext.SubmissionId);
- if (_buildSubmissions.TryGetValue(e.BuildEventContext.SubmissionId, out var submission))
+ if (originalArgs.BuildEventContext.Equals(e.BuildEventContext))
{
- submission.CompleteLogging(false);
- CheckSubmissionCompletenessAndRemove(submission);
+ _projectStartedEvents.Remove(e.BuildEventContext.SubmissionId);
+ if (_buildSubmissions.TryGetValue(e.BuildEventContext.SubmissionId, out var submission))
+ {
+ submission.CompleteLogging();
+ CheckSubmissionCompletenessAndRemove(submission);
+ }
}
}
}
@@ -2721,9 +2914,24 @@ private void OnProjectFinished(object sender, ProjectFinishedEventArgs e)
///
private void OnProjectStarted(object sender, ProjectStartedEventArgs e)
{
- if (!_projectStartedEvents.ContainsKey(e.BuildEventContext.SubmissionId))
+ if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0))
+ {
+ _workQueue.Post(() => OnProjectStartedBody(e));
+ }
+ else
{
- _projectStartedEvents[e.BuildEventContext.SubmissionId] = e;
+ OnProjectStartedBody(e);
+ }
+
+ void OnProjectStartedBody(ProjectStartedEventArgs e)
+ {
+ lock (_syncLock)
+ {
+ if (!_projectStartedEvents.ContainsKey(e.BuildEventContext.SubmissionId))
+ {
+ _projectStartedEvents[e.BuildEventContext.SubmissionId] = e;
+ }
+ }
}
}
@@ -2732,6 +2940,8 @@ private void OnProjectStarted(object sender, ProjectStartedEventArgs e)
///
private ILoggingService CreateLoggingService(IEnumerable loggers, IEnumerable forwardingLoggers, ISet warningsAsErrors, ISet warningsAsMessages)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
int cpuCount = _buildParameters.MaxNodeCount;
LoggerMode loggerMode = cpuCount == 1 && _buildParameters.UseSynchronousLogging
@@ -2899,6 +3109,8 @@ private void Dispose(bool disposing)
private bool ReuseOldCaches(string[] inputCacheFiles)
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
ErrorUtilities.VerifyThrowInternalNull(inputCacheFiles, nameof(inputCacheFiles));
ErrorUtilities.VerifyThrow(_configCache == null, "caches must not be set at this point");
ErrorUtilities.VerifyThrow(_resultsCache == null, "caches must not be set at this point");
@@ -2978,6 +3190,8 @@ private void LogErrorAndShutdown(string message)
private void CancelAndMarkAsFailure()
{
+ Debug.Assert(Monitor.IsEntered(_syncLock));
+
CancelAllSubmissions();
// CancelAllSubmissions also ends up setting _shuttingDown and _overallBuildSuccess but it does so in a separate thread to avoid deadlocks.
@@ -2989,7 +3203,7 @@ private void CancelAndMarkAsFailure()
///
/// The logger registered to the logging service when no other one is.
///
- private class NullLogger : ILogger
+ internal class NullLogger : ILogger
{
#region ILogger Members
diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs
index 1259648e255..93d21956172 100644
--- a/src/Build/BackEnd/BuildManager/BuildParameters.cs
+++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs
@@ -253,7 +253,7 @@ private BuildParameters(ITranslator translator)
///
/// Copy constructor
///
- private BuildParameters(BuildParameters other)
+ internal BuildParameters(BuildParameters other, bool resetEnvironment = false)
{
ErrorUtilities.VerifyThrowInternalNull(other, nameof(other));
@@ -261,7 +261,11 @@ private BuildParameters(BuildParameters other)
_culture = other._culture;
_defaultToolsVersion = other._defaultToolsVersion;
_enableNodeReuse = other._enableNodeReuse;
- _buildProcessEnvironment = other._buildProcessEnvironment != null ? new Dictionary(other._buildProcessEnvironment) : null;
+ _buildProcessEnvironment = resetEnvironment
+ ? CommunicationsUtilities.GetEnvironmentVariables()
+ : other._buildProcessEnvironment != null
+ ? new Dictionary(other._buildProcessEnvironment)
+ : null;
_environmentProperties = other._environmentProperties != null ? new PropertyDictionary(other._environmentProperties) : null;
_forwardingLoggers = other._forwardingLoggers != null ? new List(other._forwardingLoggers) : null;
_globalProperties = other._globalProperties != null ? new PropertyDictionary(other._globalProperties) : null;
diff --git a/src/Build/BackEnd/BuildManager/BuildSubmission.cs b/src/Build/BackEnd/BuildManager/BuildSubmission.cs
index 91356f814c6..5b574b82b20 100644
--- a/src/Build/BackEnd/BuildManager/BuildSubmission.cs
+++ b/src/Build/BackEnd/BuildManager/BuildSubmission.cs
@@ -165,13 +165,8 @@ internal void CompleteResults(BuildResult result)
///
/// Indicates that all logging events for this submission are complete.
///
- internal void CompleteLogging(bool waitForLoggingThread)
+ internal void CompleteLogging()
{
- if (waitForLoggingThread)
- {
- ((BackEnd.Logging.LoggingService)((IBuildComponentHost)BuildManager).LoggingService).WaitForThreadToProcessEvents();
- }
-
LoggingCompleted = true;
CheckForCompletion();
}
diff --git a/src/Build/BackEnd/BuildManager/CacheSerialization.cs b/src/Build/BackEnd/BuildManager/CacheSerialization.cs
index 4ac2ae28805..8bce00b3a26 100644
--- a/src/Build/BackEnd/BuildManager/CacheSerialization.cs
+++ b/src/Build/BackEnd/BuildManager/CacheSerialization.cs
@@ -79,7 +79,7 @@ public static (IConfigCache ConfigCache, IResultsCache ResultsCache, Exception e
using (var fileStream = File.OpenRead(inputCacheFile))
{
- var translator = BinaryTranslator.GetReadTranslator(fileStream, null);
+ using var translator = BinaryTranslator.GetReadTranslator(fileStream, null);
translator.Translate(ref configCache);
translator.Translate(ref resultsCache);
diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs
index 1038643d11f..2dbcd31ea0e 100644
--- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs
+++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs
@@ -12,6 +12,8 @@
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Execution;
using Microsoft.Build.Shared;
+using Microsoft.Build.Shared.Debugging;
+using Microsoft.Build.Utilities;
using BuildAbortedException = Microsoft.Build.Exceptions.BuildAbortedException;
namespace Microsoft.Build.BackEnd
@@ -115,8 +117,10 @@ internal class BuildRequestEngine : IBuildRequestEngine, IBuildComponent
///
internal BuildRequestEngine()
{
- _debugDumpState = Environment.GetEnvironmentVariable("MSBUILDDEBUGSCHEDULER") == "1";
- _debugDumpPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH");
+ _debugDumpState = Traits.Instance.DebugScheduler;
+ _debugDumpPath = ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0)
+ ? DebugUtils.DebugPath
+ : Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH");
_debugForceCaching = Environment.GetEnvironmentVariable("MSBUILDDEBUGFORCECACHING") == "1";
if (String.IsNullOrEmpty(_debugDumpPath))
diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
index 9dc051c711e..ef21df23454 100644
--- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
+++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
@@ -27,7 +27,6 @@
using BackendNativeMethods = Microsoft.Build.BackEnd.NativeMethods;
using Task = System.Threading.Tasks.Task;
-using DotNetFrameworkArchitecture = Microsoft.Build.Shared.DotNetFrameworkArchitecture;
using Microsoft.Build.Framework;
using Microsoft.Build.BackEnd.Logging;
@@ -394,7 +393,7 @@ private Stream TryConnectToProcess(int nodeProcessId, int timeout, Handshake han
CommunicationsUtilities.Trace("Reading handshake from pipe {0}", pipeName);
-#if NETCOREAPP2_1 || MONO
+#if NETCOREAPP2_1_OR_GREATER || MONO
nodeStream.ReadEndOfHandshakeSignal(true, timeout);
#else
nodeStream.ReadEndOfHandshakeSignal(true);
@@ -434,9 +433,9 @@ private Process LaunchNode(string msbuildLocation, string commandLineArgs)
// Repeat the executable name as the first token of the command line because the command line
// parser logic expects it and will otherwise skip the first argument
- commandLineArgs = msbuildLocation + " " + commandLineArgs;
+ commandLineArgs = $"\"{msbuildLocation}\" {commandLineArgs}";
- BackendNativeMethods.STARTUP_INFO startInfo = new BackendNativeMethods.STARTUP_INFO();
+ BackendNativeMethods.STARTUP_INFO startInfo = new();
startInfo.cb = Marshal.SizeOf();
// Null out the process handles so that the parent process does not wait for the child process
@@ -466,11 +465,6 @@ private Process LaunchNode(string msbuildLocation, string commandLineArgs)
creationFlags |= BackendNativeMethods.CREATE_NEW_CONSOLE;
}
- BackendNativeMethods.SECURITY_ATTRIBUTES processSecurityAttributes = new BackendNativeMethods.SECURITY_ATTRIBUTES();
- BackendNativeMethods.SECURITY_ATTRIBUTES threadSecurityAttributes = new BackendNativeMethods.SECURITY_ATTRIBUTES();
- processSecurityAttributes.nLength = Marshal.SizeOf();
- threadSecurityAttributes.nLength = Marshal.SizeOf();
-
CommunicationsUtilities.Trace("Launching node from {0}", msbuildLocation);
string exeName = msbuildLocation;
@@ -481,7 +475,6 @@ private Process LaunchNode(string msbuildLocation, string commandLineArgs)
{
// Run the child process with the same host as the currently-running process.
exeName = GetCurrentHost();
- commandLineArgs = "\"" + msbuildLocation + "\" " + commandLineArgs;
}
#endif
@@ -526,14 +519,15 @@ private Process LaunchNode(string msbuildLocation, string commandLineArgs)
else
{
#if RUNTIME_TYPE_NETCORE
- if (NativeMethodsShared.IsWindows)
- {
- // Repeat the executable name in the args to suit CreateProcess
- commandLineArgs = "\"" + exeName + "\" " + commandLineArgs;
- }
+ // Repeat the executable name in the args to suit CreateProcess
+ commandLineArgs = $"\"{exeName}\" {commandLineArgs}";
#endif
- BackendNativeMethods.PROCESS_INFORMATION processInfo = new BackendNativeMethods.PROCESS_INFORMATION();
+ BackendNativeMethods.PROCESS_INFORMATION processInfo = new();
+ BackendNativeMethods.SECURITY_ATTRIBUTES processSecurityAttributes = new();
+ BackendNativeMethods.SECURITY_ATTRIBUTES threadSecurityAttributes = new();
+ processSecurityAttributes.nLength = Marshal.SizeOf();
+ threadSecurityAttributes.nLength = Marshal.SizeOf();
bool result = BackendNativeMethods.CreateProcess
(
@@ -596,9 +590,18 @@ private static string GetCurrentHost()
#if RUNTIME_TYPE_NETCORE || MONO
if (CurrentHost == null)
{
- using (Process currentProcess = Process.GetCurrentProcess())
+ string dotnetExe = Path.Combine(FileUtilities.GetFolderAbove(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, 2),
+ NativeMethodsShared.IsWindows ? "dotnet.exe" : "dotnet");
+ if (File.Exists(dotnetExe))
+ {
+ CurrentHost = dotnetExe;
+ }
+ else
{
- CurrentHost = currentProcess.MainModule.FileName;
+ using (Process currentProcess = Process.GetCurrentProcess())
+ {
+ CurrentHost = currentProcess.MainModule.FileName;
+ }
}
}
diff --git a/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs b/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs
index 4116039458a..f10fbf8dc50 100644
--- a/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs
+++ b/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs
@@ -4,10 +4,11 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
+using System.Reflection;
using Microsoft.Build.Collections;
using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
-using System.Reflection;
namespace Microsoft.Build.BackEnd
{
@@ -103,5 +104,17 @@ public static T FactoryForDeserializingTypeWithName(this ITranslator translat
return (T) targetInstanceChild;
}
+
+ public static void TranslateOptionalBuildEventContext(this ITranslator translator, ref BuildEventContext buildEventContext)
+ {
+ if (translator.Mode == TranslationDirection.ReadFromStream)
+ {
+ buildEventContext = translator.Reader.ReadOptionalBuildEventContext();
+ }
+ else
+ {
+ translator.Writer.WriteOptionalBuildEventContext(buildEventContext);
+ }
+ }
}
}
diff --git a/src/Build/BackEnd/Components/Logging/ILoggingService.cs b/src/Build/BackEnd/Components/Logging/ILoggingService.cs
index 7d489ca8b21..096fb99283f 100644
--- a/src/Build/BackEnd/Components/Logging/ILoggingService.cs
+++ b/src/Build/BackEnd/Components/Logging/ILoggingService.cs
@@ -207,6 +207,15 @@ bool IncludeTaskInputs
set;
}
+ ///
+ /// Returns the minimum logging importance that must be logged because there is a possibility that
+ /// at least one registered logger consumes it.
+ ///
+ MessageImportance MinimumRequiredMessageImportance
+ {
+ get;
+ }
+
#endregion
///
@@ -525,8 +534,10 @@ void LogProjectEvaluationFinished(
/// The name of the task
/// The project file which is being built
/// The file in which the task is defined - typically a .targets file
+ /// The line number in the file where the task invocation is located.
+ /// The column number in the file where the task invocation is located.
/// The task build event context
- BuildEventContext LogTaskStarted2(BuildEventContext targetBuildEventContext, string taskName, string projectFile, string projectFileOfTaskNode);
+ BuildEventContext LogTaskStarted2(BuildEventContext targetBuildEventContext, string taskName, string projectFile, string projectFileOfTaskNode, int line, int column);
///
/// Log that a task has just completed
diff --git a/src/Build/BackEnd/Components/Logging/LoggingContext.cs b/src/Build/BackEnd/Components/Logging/LoggingContext.cs
index 6194726d5f4..5fd43fbbfb7 100644
--- a/src/Build/BackEnd/Components/Logging/LoggingContext.cs
+++ b/src/Build/BackEnd/Components/Logging/LoggingContext.cs
@@ -213,6 +213,12 @@ internal void LogFatalError(Exception exception, BuildEventFileInfo file, string
_hasLoggedErrors = true;
}
+ internal void LogWarning(string messageResourceName, params object[] messageArgs)
+ {
+ ErrorUtilities.VerifyThrow(_isValid, "must be valid");
+ _loggingService.LogWarning(_eventContext, null, BuildEventFileInfo.Empty, messageResourceName, messageArgs);
+ }
+
///
/// Log a warning
///
diff --git a/src/Build/BackEnd/Components/Logging/LoggingService.cs b/src/Build/BackEnd/Components/Logging/LoggingService.cs
index 7f8aa44f50f..19ed313b922 100644
--- a/src/Build/BackEnd/Components/Logging/LoggingService.cs
+++ b/src/Build/BackEnd/Components/Logging/LoggingService.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
@@ -222,6 +223,12 @@ internal partial class LoggingService : ILoggingService, INodePacketHandler, IBu
///
private IDictionary> _warningsAsMessagesByProject;
+ ///
+ /// The minimum message importance that must be logged because there is a possibility that a logger consumes it.
+ /// Null means that the optimization is disabled or no relevant logger has been registered.
+ ///
+ private MessageImportance? _minimumRequiredMessageImportance;
+
#region LoggingThread Data
///
@@ -514,7 +521,18 @@ public bool IncludeTaskInputs
///
public bool IncludeEvaluationPropertiesAndItems
{
- get => _includeEvaluationPropertiesAndItems ??= _eventSinkDictionary.Values.OfType().Any(sink => sink.IncludeEvaluationPropertiesAndItems);
+ get
+ {
+ if (_includeEvaluationPropertiesAndItems == null)
+ {
+ var sinks = _eventSinkDictionary.Values.OfType();
+ // .All() on an empty list defaults to true, we want to default to false
+ _includeEvaluationPropertiesAndItems = sinks.Any() && sinks.All(sink => sink.IncludeEvaluationPropertiesAndItems);
+ }
+
+ return _includeEvaluationPropertiesAndItems ?? false;
+ }
+
set => _includeEvaluationPropertiesAndItems = value;
}
@@ -691,6 +709,19 @@ public ICollection RegisteredSinkNames
}
}
+ ///
+ /// Returns the minimum logging importance that must be logged because there is a possibility that
+ /// at least one registered logger consumes it.
+ ///
+ public MessageImportance MinimumRequiredMessageImportance
+ {
+ get
+ {
+ // If we haven't set the field return the default of "all messages must be logged".
+ return _minimumRequiredMessageImportance ?? MessageImportance.Low;
+ }
+ }
+
#endregion
#region Members
@@ -1109,7 +1140,7 @@ public void LogBuildEvent(BuildEventArgs buildEvent)
#endregion
///
- /// This method will becalled from multiple threads in asynchronous mode.
+ /// This method will be called from multiple threads in asynchronous mode.
///
/// Determine where to send the buildevent either to the filters or to a specific sink.
/// When in Asynchronous mode the event should to into the logging queue (as long as we are initialized).
@@ -1152,8 +1183,29 @@ internal void WaitForThreadToProcessEvents()
// shutdown and nulled out the events we were going to wait on.
if (_logMode == LoggerMode.Asynchronous && _loggingQueue != null)
{
- TerminateLoggingEventQueue();
- CreateLoggingEventQueue();
+ BufferBlock loggingQueue = null;
+ ActionBlock loggingQueueProcessor = null;
+
+ lock (_lockObject)
+ {
+ loggingQueue = _loggingQueue;
+ loggingQueueProcessor = _loggingQueueProcessor;
+
+ // Replaces _loggingQueue and _loggingQueueProcessor with new one, this will assure that
+ // no further messages could possibly be trying to be added into queue we are about to drain
+ CreateLoggingEventQueue();
+ }
+
+ // Drain queue.
+ // This shall not be locked to avoid possible deadlock caused by
+ // event handlers to reenter 'this' instance while trying to log something.
+ if (loggingQueue != null)
+ {
+ Debug.Assert(!Monitor.IsEntered(_lockObject));
+
+ loggingQueue.Complete();
+ loggingQueueProcessor.Completion.Wait();
+ }
}
}
@@ -1214,21 +1266,27 @@ private void CreateLoggingEventQueue()
BoundedCapacity = Convert.ToInt32(_queueCapacity)
};
- _loggingQueue = new BufferBlock(dataBlockOptions);
+ var loggingQueue = new BufferBlock(dataBlockOptions);
var executionDataBlockOptions = new ExecutionDataflowBlockOptions
{
BoundedCapacity = 1
};
- _loggingQueueProcessor = new ActionBlock(loggingEvent => LoggingEventProcessor(loggingEvent), executionDataBlockOptions);
+ var loggingQueueProcessor = new ActionBlock(loggingEvent => LoggingEventProcessor(loggingEvent), executionDataBlockOptions);
var dataLinkOptions = new DataflowLinkOptions
{
PropagateCompletion = true
};
- _loggingQueue.LinkTo(_loggingQueueProcessor, dataLinkOptions);
+ loggingQueue.LinkTo(loggingQueueProcessor, dataLinkOptions);
+
+ lock (_lockObject)
+ {
+ _loggingQueue = loggingQueue;
+ _loggingQueueProcessor = loggingQueueProcessor;
+ }
}
///
@@ -1542,10 +1600,57 @@ private void InitializeLogger(ILogger logger, IEventSource sourceForLogger)
InternalLoggerException.Throw(e, null, "FatalErrorWhileInitializingLogger", true, logger.GetType().Name);
}
+ // Update the minimum guaranteed message importance based on the newly added logger.
+ UpdateMinimumMessageImportance(logger);
+
// Keep track of the loggers so they can be unregistered later on
_loggers.Add(logger);
}
+ ///
+ /// Updates based on the given .
+ ///
+ /// The newly registered logger.
+ ///
+ /// This method contains knowledge about several logger classes used by MSBuild. The goal is to optimize common scenarios,
+ /// such as building on the command line with normal or minimum verbosity. If the user registers an external custom logger,
+ /// we will fall back to "minimum importance" == Low because we don't know how the logger processes messages, therefore we
+ /// must feed it everything.
+ ///
+ private void UpdateMinimumMessageImportance(ILogger logger)
+ {
+ var innerLogger = (logger is Evaluation.ProjectCollection.ReusableLogger reusableLogger) ? reusableLogger.OriginalLogger : logger;
+
+ MessageImportance? minimumImportance = innerLogger switch
+ {
+ Build.Logging.ConsoleLogger consoleLogger => consoleLogger.GetMinimumMessageImportance(),
+ Build.Logging.ConfigurableForwardingLogger forwardingLogger => forwardingLogger.GetMinimumMessageImportance(),
+
+ // Central forwarding loggers are used in worker nodes if logging verbosity could not be optimized, i.e. in cases
+ // where we must log everything. They can be ignored in inproc nodes.
+ CentralForwardingLogger => (_nodeId > 1 ? MessageImportance.Low : null),
+
+ // The null logger has no effect on minimum verbosity.
+ Execution.BuildManager.NullLogger => null,
+
+ // If the logger is not on our whitelist, there are no importance guarantees. Fall back to "any importance".
+ _ => MessageImportance.Low
+ };
+
+ if (minimumImportance != null)
+ {
+ if (_minimumRequiredMessageImportance == null)
+ {
+ _minimumRequiredMessageImportance = minimumImportance;
+ }
+ else
+ {
+ int newMinImportance = Math.Max((int)_minimumRequiredMessageImportance, (int)minimumImportance);
+ _minimumRequiredMessageImportance = (MessageImportance)newMinImportance;
+ }
+ }
+ }
+
///
/// When an exception is raised in the logging thread, we do not want the application to terminate right away.
/// Whidbey and orcas msbuild have the logger exceptions occurring on the engine thread so that the host can
@@ -1598,8 +1703,7 @@ private void TryRaiseProjectFinishedEvent(BuildEventArgs args)
///
private string GetAndVerifyProjectFileFromContext(BuildEventContext context)
{
- string projectFile;
- _projectFileMap.TryGetValue(context.ProjectContextId, out projectFile);
+ _projectFileMap.TryGetValue(context.ProjectContextId, out string projectFile);
// PERF: Not using VerifyThrow to avoid boxing an int in the non-error case.
if (projectFile == null)
diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs
index 77de428ef63..c5c3555f201 100644
--- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs
+++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs
@@ -450,13 +450,10 @@ public void LogBuildStarted()
// Raise the event with the filters
ProcessLoggingEvent(buildEvent);
-
- // Make sure we process this event before going any further
- if (_logMode == LoggerMode.Asynchronous)
- {
- WaitForThreadToProcessEvents();
- }
}
+
+ // Make sure we process this event before going any further
+ WaitForThreadToProcessEvents();
}
///
@@ -478,12 +475,10 @@ public void LogBuildFinished(bool success)
BuildFinishedEventArgs buildEvent = new BuildFinishedEventArgs(message, null /* no help keyword */, success);
ProcessLoggingEvent(buildEvent);
-
- if (_logMode == LoggerMode.Asynchronous)
- {
- WaitForThreadToProcessEvents();
- }
}
+
+ // Make sure we process this event before going any further
+ WaitForThreadToProcessEvents();
}
///
@@ -754,9 +749,11 @@ public void LogTaskStarted(BuildEventContext taskBuildEventContext, string taskN
/// Task Name
/// Project file being built
/// Project file which contains the task
+ /// The line number in the file where the task invocation is located.
+ /// The column number in the file where the task invocation is located.
/// The build event context for the task.
/// BuildEventContext is null
- public BuildEventContext LogTaskStarted2(BuildEventContext targetBuildEventContext, string taskName, string projectFile, string projectFileOfTaskNode)
+ public BuildEventContext LogTaskStarted2(BuildEventContext targetBuildEventContext, string taskName, string projectFile, string projectFileOfTaskNode, int line, int column)
{
lock (_lockObject)
{
@@ -782,6 +779,8 @@ public BuildEventContext LogTaskStarted2(BuildEventContext targetBuildEventConte
taskName
);
buildEvent.BuildEventContext = taskBuildEventContext;
+ buildEvent.LineNumber = line;
+ buildEvent.ColumnNumber = column;
ProcessLoggingEvent(buildEvent);
}
diff --git a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs
index 0b55b80359a..6852343e9be 100644
--- a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs
+++ b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs
@@ -86,18 +86,26 @@ internal void LogRequestHandledFromCache(BuildRequest request, BuildRequestConfi
{
ProjectLoggingContext projectLoggingContext = LogProjectStarted(request, configuration);
- // When pulling a request from the cache, we want to make sure we log a task skipped message for any targets which
- // were used to build the request including default and inital targets.
+ // When pulling a request from the cache, we want to make sure we log a target skipped event for any targets which
+ // were used to build the request including default and initial targets.
foreach (string target in configuration.GetTargetsUsedToBuildRequest(request))
{
- projectLoggingContext.LogComment
- (
- MessageImportance.Low,
- result[target].ResultCode == TargetResultCode.Failure ? "TargetAlreadyCompleteFailure" : "TargetAlreadyCompleteSuccess",
- target
- );
-
- if (result[target].ResultCode == TargetResultCode.Failure)
+ var targetResult = result[target];
+ bool isFailure = targetResult.ResultCode == TargetResultCode.Failure;
+
+ var skippedTargetEventArgs = new TargetSkippedEventArgs(message: null)
+ {
+ BuildEventContext = projectLoggingContext.BuildEventContext,
+ TargetName = target,
+ BuildReason = TargetBuiltReason.None,
+ SkipReason = isFailure ? TargetSkipReason.PreviouslyBuiltUnsuccessfully : TargetSkipReason.PreviouslyBuiltSuccessfully,
+ OriginallySucceeded = !isFailure,
+ OriginalBuildEventContext = (targetResult as TargetResult)?.OriginalBuildEventContext
+ };
+
+ projectLoggingContext.LogBuildEvent(skippedTargetEventArgs);
+
+ if (targetResult.ResultCode == TargetResultCode.Failure)
{
break;
}
diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs
index 846c0b3a643..4c5e5934c83 100644
--- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs
+++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs
@@ -135,7 +135,12 @@ private ProjectLoggingContext(NodeLoggingContext nodeLoggingContext, int submiss
properties,
items,
evaluationId);
- LoggingService.LogComment(this.BuildEventContext, MessageImportance.Low, "ToolsVersionInEffectForBuild", toolsVersion);
+
+ // No need to log a redundant message in the common case
+ if (toolsVersion != "Current")
+ {
+ LoggingService.LogComment(this.BuildEventContext, MessageImportance.Low, "ToolsVersionInEffectForBuild", toolsVersion);
+ }
this.IsValid = true;
}
diff --git a/src/Build/BackEnd/Components/Logging/TaskLoggingContext.cs b/src/Build/BackEnd/Components/Logging/TaskLoggingContext.cs
index 6dc62c389ce..51c266e6485 100644
--- a/src/Build/BackEnd/Components/Logging/TaskLoggingContext.cs
+++ b/src/Build/BackEnd/Components/Logging/TaskLoggingContext.cs
@@ -69,7 +69,9 @@ internal TaskLoggingContext(TargetLoggingContext targetLoggingContext, string pr
targetLoggingContext.BuildEventContext,
_taskName,
projectFullPath,
- task.Location.File
+ task.Location.File,
+ task.Location.Line,
+ task.Location.Column
);
this.IsValid = true;
}
diff --git a/src/Build/BackEnd/Components/ProjectCache/CacheResult.cs b/src/Build/BackEnd/Components/ProjectCache/CacheResult.cs
index 7f8a69ecbbd..50ac573159f 100644
--- a/src/Build/BackEnd/Components/ProjectCache/CacheResult.cs
+++ b/src/Build/BackEnd/Components/ProjectCache/CacheResult.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#nullable enable
+using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.BackEnd;
@@ -46,6 +47,11 @@ public enum CacheResultType
///
public class CacheResult
{
+ public CacheResultType ResultType { get; }
+ public BuildResult? BuildResult { get; }
+ public ProxyTargets? ProxyTargets { get; }
+ internal Exception? Exception { get; }
+
private CacheResult(
CacheResultType resultType,
BuildResult? buildResult = null,
@@ -63,9 +69,11 @@ private CacheResult(
ProxyTargets = proxyTargets;
}
- public CacheResultType ResultType { get; }
- public BuildResult? BuildResult { get; }
- public ProxyTargets? ProxyTargets { get; }
+ private CacheResult(Exception exception)
+ {
+ ResultType = CacheResultType.None;
+ Exception = exception;
+ }
public static CacheResult IndicateCacheHit(BuildResult buildResult)
{
@@ -90,6 +98,11 @@ public static CacheResult IndicateNonCacheHit(CacheResultType resultType)
return new CacheResult(resultType);
}
+ internal static CacheResult IndicateException(Exception e)
+ {
+ return new CacheResult(e);
+ }
+
private static BuildResult ConstructBuildResult(IReadOnlyCollection targetResults)
{
var buildResult = new BuildResult();
diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheDescriptor.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheDescriptor.cs
index 22d98780b2b..10c97621ef5 100644
--- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheDescriptor.cs
+++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheDescriptor.cs
@@ -30,6 +30,8 @@ public class ProjectCacheDescriptor
public ProjectCachePluginBase? PluginInstance { get; }
+ internal bool VsWorkaround { get; }
+
private ProjectCacheDescriptor(
IReadOnlyCollection? entryPoints,
ProjectGraph? projectGraph,
@@ -62,6 +64,19 @@ private ProjectCacheDescriptor(
PluginInstance = pluginInstance;
}
+ private ProjectCacheDescriptor(ProjectCacheItem projectCacheItem)
+ {
+ VsWorkaround = true;
+ PluginAssemblyPath = projectCacheItem.PluginPath;
+ PluginSettings = projectCacheItem.PluginSettings;
+ }
+
+ // TODO: remove after we change VS to set the cache descriptor via build parameters.
+ internal static ProjectCacheDescriptor FromVisualStudioWorkaround(ProjectCacheItem projectCacheItem)
+ {
+ return new ProjectCacheDescriptor(projectCacheItem);
+ }
+
public static ProjectCacheDescriptor FromAssemblyPath(
string pluginAssemblyPath,
IReadOnlyCollection? entryPoints,
@@ -87,18 +102,22 @@ public string GetDetailedDescription()
: $"Assembly path based: {PluginAssemblyPath}";
var entryPointStyle = EntryPoints != null
- ? "Graph entrypoint based"
- : "Static graph based";
+ ? "Explicit entry-point based"
+ : ProjectGraph != null
+ ? "Static graph based"
+ : "Visual Studio Workaround based";
var entryPoints = EntryPoints != null
? string.Join(
"\n",
EntryPoints.Select(e => $"{e.ProjectFile} {{{FormatGlobalProperties(e.GlobalProperties)}}}"))
- : string.Join(
- "\n",
- ProjectGraph!.EntryPointNodes.Select(
- n =>
- $"{n.ProjectInstance.FullPath} {{{FormatGlobalProperties(n.ProjectInstance.GlobalProperties)}}}"));
+ : ProjectGraph != null
+ ? string.Join(
+ "\n",
+ ProjectGraph!.EntryPointNodes.Select(
+ n =>
+ $"{n.ProjectInstance.FullPath} {{{FormatGlobalProperties(n.ProjectInstance.GlobalProperties)}}}"))
+ : "Solution file";
return $"{loadStyle}\nEntry-point style: {entryPointStyle}\nEntry-points:\n{entryPoints}";
diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs
index ad7259723ce..42e912b95ae 100644
--- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs
+++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs
@@ -4,19 +4,42 @@
#nullable enable
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using System.Xml;
+using Microsoft.Build.BackEnd;
using Microsoft.Build.BackEnd.Logging;
+using Microsoft.Build.Construction;
using Microsoft.Build.Execution;
using Microsoft.Build.FileSystem;
using Microsoft.Build.Framework;
+using Microsoft.Build.Graph;
+using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
namespace Microsoft.Build.Experimental.ProjectCache
{
+ internal record CacheRequest(BuildSubmission Submission, BuildRequestConfiguration Configuration);
+
+ internal record NullableBool(bool Value)
+ {
+ public static implicit operator bool(NullableBool? d) => d is not null && d.Value;
+ }
+
+ internal enum ProjectCacheServiceState
+ {
+ NotInitialized,
+ BeginBuildStarted,
+ BeginBuildFinished,
+ ShutdownStarted,
+ ShutdownFinished
+ }
+
internal class ProjectCacheService
{
private readonly BuildManager _buildManager;
@@ -24,6 +47,20 @@ internal class ProjectCacheService
private readonly ProjectCacheDescriptor _projectCacheDescriptor;
private readonly CancellationToken _cancellationToken;
private readonly ProjectCachePluginBase _projectCachePlugin;
+ private ProjectCacheServiceState _serviceState = ProjectCacheServiceState.NotInitialized;
+
+ ///
+ /// An instanatiable version of MSBuildFileSystemBase not overriding any methods,
+ /// i.e. falling back to FileSystem.Default.
+ ///
+ private sealed class DefaultMSBuildFileSystem : MSBuildFileSystemBase { }
+
+ // Use NullableBool to make it work with Interlock.CompareExchange (doesn't accept bool?).
+ // Assume that if one request is a design time build, all of them are.
+ // Volatile because it is read by the BuildManager thread and written by one project cache service thread pool thread.
+ // TODO: remove after we change VS to set the cache descriptor via build parameters.
+ public volatile NullableBool? DesignTimeBuildsDetected;
+ private TaskCompletionSource? LateInitializationForVSWorkaroundCompleted;
private ProjectCacheService(
ProjectCachePluginBase projectCachePlugin,
@@ -49,22 +86,55 @@ public static async Task FromDescriptorAsync(
var plugin = await Task.Run(() => GetPluginInstance(pluginDescriptor), cancellationToken)
.ConfigureAwait(false);
- // TODO: Detect and use the highest verbosity from all the user defined loggers. That's tricky because right now we can't discern between user set loggers and msbuild's internally added loggers.
+ // TODO: Detect and use the highest verbosity from all the user defined loggers. That's tricky because right now we can't query loggers about
+ // their verbosity levels.
var loggerFactory = new Func(() => new LoggingServiceToPluginLoggerAdapter(LoggerVerbosity.Normal, loggingService));
- var logger = loggerFactory();
+ var service = new ProjectCacheService(plugin, buildManager, loggerFactory, pluginDescriptor, cancellationToken);
+
+ // TODO: remove the if after we change VS to set the cache descriptor via build parameters and always call BeginBuildAsync in FromDescriptorAsync.
+ // When running under VS we can't initialize the plugin until we evaluate a project (any project) and extract
+ // further information (set by VS) from it required by the plugin.
+ if (!pluginDescriptor.VsWorkaround)
+ {
+ await service.BeginBuildAsync();
+ }
+
+ return service;
+ }
+
+ // TODO: remove vsWorkaroundOverrideDescriptor after we change VS to set the cache descriptor via build parameters.
+ private async Task BeginBuildAsync(ProjectCacheDescriptor? vsWorkaroundOverrideDescriptor = null)
+ {
+ var logger = _loggerFactory();
try
{
- await plugin.BeginBuildAsync(
+ SetState(ProjectCacheServiceState.BeginBuildStarted);
+
+ logger.LogMessage("Initializing project cache plugin", MessageImportance.Low);
+ var timer = Stopwatch.StartNew();
+
+ if (_projectCacheDescriptor.VsWorkaround)
+ {
+ logger.LogMessage("Running project cache with Visual Studio workaround");
+ }
+
+ var projectDescriptor = vsWorkaroundOverrideDescriptor ?? _projectCacheDescriptor;
+ await _projectCachePlugin.BeginBuildAsync(
new CacheContext(
- pluginDescriptor.PluginSettings,
- new IFileSystemAdapter(FileSystems.Default),
- pluginDescriptor.ProjectGraph,
- pluginDescriptor.EntryPoints),
+ projectDescriptor.PluginSettings,
+ new DefaultMSBuildFileSystem(),
+ projectDescriptor.ProjectGraph,
+ projectDescriptor.EntryPoints),
// TODO: Detect verbosity from logging service.
logger,
- cancellationToken);
+ _cancellationToken);
+
+ timer.Stop();
+ logger.LogMessage($"Finished initializing project cache plugin in {timer.Elapsed.TotalMilliseconds} ms", MessageImportance.Low);
+
+ SetState(ProjectCacheServiceState.BeginBuildFinished);
}
catch (Exception e)
{
@@ -75,8 +145,6 @@ await plugin.BeginBuildAsync(
{
ProjectCacheException.ThrowForErrorLoggedInsideTheProjectCache("ProjectCacheInitializationFailed");
}
-
- return new ProjectCacheService(plugin, buildManager, loggerFactory, pluginDescriptor, cancellationToken);
}
private static ProjectCachePluginBase GetPluginInstance(ProjectCacheDescriptor pluginDescriptor)
@@ -127,7 +195,7 @@ Assembly LoadAssembly(string resolverPath)
#if !FEATURE_ASSEMBLYLOADCONTEXT
return Assembly.LoadFrom(resolverPath);
#else
- return _loader.LoadFromPath(resolverPath);
+ return s_loader.LoadFromPath(resolverPath);
#endif
}
@@ -145,12 +213,259 @@ IEnumerable GetTypes(Assembly assembly)
}
#if FEATURE_ASSEMBLYLOADCONTEXT
- private static readonly CoreClrAssemblyLoader _loader = new CoreClrAssemblyLoader();
+ private static readonly CoreClrAssemblyLoader s_loader = new CoreClrAssemblyLoader();
#endif
- public async Task GetCacheResultAsync(BuildRequestData buildRequest)
+ public void PostCacheRequest(CacheRequest cacheRequest)
+ {
+ Task.Run(async () =>
+ {
+ try
+ {
+ var cacheResult = await ProcessCacheRequest(cacheRequest);
+ _buildManager.PostCacheResult(cacheRequest, cacheResult);
+ }
+ catch (Exception e)
+ {
+ _buildManager.PostCacheResult(cacheRequest, CacheResult.IndicateException(e));
+ }
+ }, _cancellationToken);
+
+ async Task ProcessCacheRequest(CacheRequest request)
+ {
+ // Prevent needless evaluation if design time builds detected.
+ if (_projectCacheDescriptor.VsWorkaround && DesignTimeBuildsDetected)
+ {
+ // The BuildManager should disable the cache when it finds its servicing design time builds.
+ return CacheResult.IndicateNonCacheHit(CacheResultType.CacheMiss);
+ }
+
+ EvaluateProjectIfNecessary(request);
+
+ // Detect design time builds.
+ if (_projectCacheDescriptor.VsWorkaround)
+ {
+ var isDesignTimeBuild = IsDesignTimeBuild(request.Configuration.Project);
+
+ var previousValue = Interlocked.CompareExchange(
+ ref DesignTimeBuildsDetected,
+ new NullableBool(isDesignTimeBuild),
+ null);
+
+ ErrorUtilities.VerifyThrowInternalError(
+ previousValue is null || previousValue == false || isDesignTimeBuild,
+ "Either all builds in a build session or design time builds, or none");
+
+ // No point progressing with expensive plugin initialization or cache query if design time build detected.
+ if (DesignTimeBuildsDetected)
+ {
+ // The BuildManager should disable the cache when it finds its servicing design time builds.
+ return CacheResult.IndicateNonCacheHit(CacheResultType.CacheMiss);
+ }
+ }
+
+ // TODO: remove after we change VS to set the cache descriptor via build parameters.
+ // VS workaround needs to wait until the first project is evaluated to extract enough information to initialize the plugin.
+ // No cache request can progress until late initialization is complete.
+ if (_projectCacheDescriptor.VsWorkaround)
+ {
+ if (Interlocked.CompareExchange(
+ ref LateInitializationForVSWorkaroundCompleted,
+ new TaskCompletionSource(),
+ null) is null)
+ {
+ await LateInitializePluginForVsWorkaround(request);
+ LateInitializationForVSWorkaroundCompleted.SetResult(true);
+ }
+ else
+ {
+ // Can't be null. If the thread got here it means another thread initialized the completion source.
+ await LateInitializationForVSWorkaroundCompleted!.Task;
+ }
+ }
+
+ ErrorUtilities.VerifyThrowInternalError(
+ LateInitializationForVSWorkaroundCompleted is null ||
+ _projectCacheDescriptor.VsWorkaround && LateInitializationForVSWorkaroundCompleted.Task.IsCompleted,
+ "Completion source should be null when this is not the VS workaround");
+
+ return await GetCacheResultAsync(
+ new BuildRequestData(
+ request.Configuration.Project,
+ request.Submission.BuildRequestData.TargetNames.ToArray()));
+ }
+
+ static bool IsDesignTimeBuild(ProjectInstance project)
+ {
+ var designTimeBuild = project.GetPropertyValue(DesignTimeProperties.DesignTimeBuild);
+ var buildingProject = project.GlobalPropertiesDictionary[DesignTimeProperties.BuildingProject]?.EvaluatedValue;
+
+ return MSBuildStringIsTrue(designTimeBuild) ||
+ buildingProject != null && !MSBuildStringIsTrue(buildingProject);
+ }
+
+ void EvaluateProjectIfNecessary(CacheRequest request)
+ {
+ // TODO: only do this if the project cache requests evaluation. QB needs evaluations, but the Anybuild implementation
+ // TODO: might not need them, so no point evaluating if it's not necessary. As a caveat, evaluations would still be optimal
+ // TODO: when proxy builds are issued by the plugin ( scheduled on the inproc node, no point re-evaluating on out-of-proc nodes).
+ lock (request.Configuration)
+ {
+ if (!request.Configuration.IsLoaded)
+ {
+ request.Configuration.LoadProjectIntoConfiguration(
+ _buildManager,
+ request.Submission.BuildRequestData.Flags,
+ request.Submission.SubmissionId,
+ Scheduler.InProcNodeId
+ );
+
+ // If we're taking the time to evaluate, avoid having other nodes to repeat the same evaluation.
+ // Based on the assumption that ProjectInstance serialization is faster than evaluating from scratch.
+ request.Configuration.Project.TranslateEntireState = true;
+ }
+ }
+ }
+
+ async Task LateInitializePluginForVsWorkaround(CacheRequest request)
+ {
+ var (_, configuration) = request;
+ var solutionPath = configuration.Project.GetPropertyValue(SolutionProjectGenerator.SolutionPathPropertyName);
+ var solutionConfigurationXml = configuration.Project.GetPropertyValue(SolutionProjectGenerator.CurrentSolutionConfigurationContents);
+
+ ErrorUtilities.VerifyThrow(
+ solutionPath != null && !string.IsNullOrWhiteSpace(solutionPath) && solutionPath != "*Undefined*",
+ $"Expected VS to set a valid SolutionPath property but got: {solutionPath}");
+
+ ErrorUtilities.VerifyThrow(
+ FileSystems.Default.FileExists(solutionPath),
+ $"Solution file does not exist: {solutionPath}");
+
+ ErrorUtilities.VerifyThrow(
+ string.IsNullOrWhiteSpace(solutionConfigurationXml) is false,
+ "Expected VS to set a xml with all the solution projects' configurations for the currently building solution configuration.");
+
+ // A solution supports multiple solution configurations (different values for Configuration and Platform).
+ // Each solution configuration generates a different static graph.
+ // Therefore, plugin implementations that rely on creating static graphs in their BeginBuild methods need access to the
+ // currently solution configuration used by VS.
+ //
+ // In this VS workaround, however, we do not have access to VS' solution configuration. The only information we have is a global property
+ // named "CurrentSolutionConfigurationContents" that VS sets on each built project. It does not contain the solution configuration itself, but
+ // instead it contains information on how the solution configuration maps to each project's configuration.
+ //
+ // So instead of using the solution file as the entry point, we parse this VS property and extract graph entry points from it, for every project
+ // mentioned in the "CurrentSolutionConfigurationContents" global property.
+ //
+ // Ideally, when the VS workaround is removed from MSBuild and moved into VS, VS should create ProjectGraphDescriptors with the solution path as
+ // the graph entrypoint file, and the VS solution configuration as the entry point's global properties.
+ var graphEntryPointsFromSolutionConfig = GenerateGraphEntryPointsFromSolutionConfigurationXml(
+ solutionConfigurationXml,
+ configuration.Project);
+
+ await BeginBuildAsync(
+ ProjectCacheDescriptor.FromAssemblyPath(
+ _projectCacheDescriptor.PluginAssemblyPath!,
+ graphEntryPointsFromSolutionConfig,
+ projectGraph: null,
+ _projectCacheDescriptor.PluginSettings));
+ }
+
+ static IReadOnlyCollection GenerateGraphEntryPointsFromSolutionConfigurationXml(
+ string solutionConfigurationXml,
+ ProjectInstance project
+ )
+ {
+ // TODO: fix code clone for parsing CurrentSolutionConfiguration xml: https://github.com/dotnet/msbuild/issues/6751
+ var doc = new XmlDocument();
+ doc.LoadXml(solutionConfigurationXml);
+
+ var root = doc.DocumentElement!;
+ var projectConfigurationNodes = root.GetElementsByTagName("ProjectConfiguration");
+
+ ErrorUtilities.VerifyThrow(projectConfigurationNodes.Count > 0, "Expected at least one project in solution");
+
+ var definingProjectPath = project.FullPath;
+ var graphEntryPoints = new List(projectConfigurationNodes.Count);
+
+ var templateGlobalProperties = new Dictionary(project.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+ RemoveProjectSpecificGlobalProperties(templateGlobalProperties, project);
+
+ foreach (XmlNode node in projectConfigurationNodes)
+ {
+ ErrorUtilities.VerifyThrowInternalNull(node.Attributes, nameof(node.Attributes));
+
+ var buildProjectInSolution = node.Attributes!["BuildProjectInSolution"];
+ if (buildProjectInSolution is not null &&
+ string.IsNullOrWhiteSpace(buildProjectInSolution.Value) is false &&
+ bool.TryParse(buildProjectInSolution.Value, out var buildProject) &&
+ buildProject is false)
+ {
+ continue;
+ }
+
+ ErrorUtilities.VerifyThrow(
+ node.ChildNodes.OfType().FirstOrDefault(e => e.Name == "ProjectDependency") is null,
+ "Project cache service does not support solution only dependencies when running under Visual Studio.");
+
+ var projectPathAttribute = node.Attributes!["AbsolutePath"];
+ ErrorUtilities.VerifyThrow(projectPathAttribute is not null, "Expected VS to set the project path on each ProjectConfiguration element.");
+
+ var projectPath = projectPathAttribute!.Value;
+
+ var (configuration, platform) = SolutionFile.ParseConfigurationName(node.InnerText, definingProjectPath, 0, solutionConfigurationXml);
+
+ // Take the defining project global properties and override the configuration and platform.
+ // It's sufficient to only set Configuration and Platform.
+ // But we send everything to maximize the plugins' potential to quickly workaround potential MSBuild issues while the MSBuild fixes flow into VS.
+ var globalProperties = new Dictionary(templateGlobalProperties, StringComparer.OrdinalIgnoreCase)
+ {
+ ["Configuration"] = configuration,
+ ["Platform"] = platform
+ };
+
+ graphEntryPoints.Add(new ProjectGraphEntryPoint(projectPath, globalProperties));
+ }
+
+ return graphEntryPoints;
+
+ // If any project specific property is set, it will propagate down the project graph and force all nodes to that property's specific side effects, which is incorrect.
+ void RemoveProjectSpecificGlobalProperties(Dictionary globalProperties, ProjectInstance project)
+ {
+ // InnerBuildPropertyName is TargetFramework for the managed sdk.
+ var innerBuildPropertyName = ProjectInterpretation.GetInnerBuildPropertyName(project);
+
+ IEnumerable projectSpecificPropertyNames = new []{innerBuildPropertyName, "Configuration", "Platform", "TargetPlatform", "OutputType"};
+
+ foreach (var propertyName in projectSpecificPropertyNames)
+ {
+ if (!string.IsNullOrWhiteSpace(propertyName) && globalProperties.ContainsKey(propertyName))
+ {
+ globalProperties.Remove(propertyName);
+ }
+ }
+ }
+ }
+
+ static bool MSBuildStringIsTrue(string msbuildString) =>
+ ConversionUtilities.ConvertStringToBool(msbuildString, nullOrWhitespaceIsFalse: true);
+ }
+
+ private async Task GetCacheResultAsync(BuildRequestData buildRequest)
{
- // TODO: Parent these logs under the project build event so they appear nested under the project in the binlog viewer.
+ lock (this)
+ {
+ CheckNotInState(ProjectCacheServiceState.NotInitialized);
+ CheckNotInState(ProjectCacheServiceState.BeginBuildStarted);
+
+ if (_serviceState is ProjectCacheServiceState.ShutdownStarted or ProjectCacheServiceState.ShutdownFinished)
+ {
+ return CacheResult.IndicateNonCacheHit(CacheResultType.CacheNotApplicable);
+ }
+ }
+
+ ErrorUtilities.VerifyThrowInternalNull(buildRequest.ProjectInstance, nameof(buildRequest.ProjectInstance));
+
var queryDescription = $"{buildRequest.ProjectFullPath}" +
$"\n\tTargets:[{string.Join(", ", buildRequest.TargetNames)}]" +
$"\n\tGlobal Properties: {{{string.Join(",", buildRequest.GlobalProperties.Select(kvp => $"{kvp.Name}={kvp.EvaluatedValue}"))}}}";
@@ -176,7 +491,7 @@ public async Task GetCacheResultAsync(BuildRequestData buildRequest
ProjectCacheException.ThrowForErrorLoggedInsideTheProjectCache("ProjectCacheQueryFailed", queryDescription);
}
- var message = $"Plugin result: {cacheResult.ResultType}.";
+ var message = $"------ Plugin result: {cacheResult.ResultType}.";
switch (cacheResult.ResultType)
{
@@ -206,16 +521,28 @@ public async Task ShutDown()
try
{
+ SetState(ProjectCacheServiceState.ShutdownStarted);
+
+ logger.LogMessage("Shutting down project cache plugin", MessageImportance.Low);
+ var timer = Stopwatch.StartNew();
+
await _projectCachePlugin.EndBuildAsync(logger, _cancellationToken);
+
+ timer.Stop();
+ logger.LogMessage($"Finished shutting down project cache plugin in {timer.Elapsed.TotalMilliseconds} ms", MessageImportance.Low);
+
+ if (logger.HasLoggedErrors)
+ {
+ ProjectCacheException.ThrowForErrorLoggedInsideTheProjectCache("ProjectCacheShutdownFailed");
+ }
}
- catch (Exception e)
+ catch (Exception e) when (e is not ProjectCacheException)
{
HandlePluginException(e, nameof(ProjectCachePluginBase.EndBuildAsync));
}
-
- if (logger.HasLoggedErrors)
+ finally
{
- ProjectCacheException.ThrowForErrorLoggedInsideTheProjectCache("ProjectCacheShutdownFailed");
+ SetState(ProjectCacheServiceState.ShutdownFinished);
}
}
@@ -232,6 +559,52 @@ private static void HandlePluginException(Exception e, string apiExceptionWasThr
apiExceptionWasThrownFrom);
}
+ private void SetState(ProjectCacheServiceState newState)
+ {
+ lock (this)
+ {
+ switch (newState)
+ {
+ case ProjectCacheServiceState.NotInitialized:
+ ErrorUtilities.ThrowInternalError($"Cannot transition to {ProjectCacheServiceState.NotInitialized}");
+ break;
+ case ProjectCacheServiceState.BeginBuildStarted:
+ CheckInState(ProjectCacheServiceState.NotInitialized);
+ break;
+ case ProjectCacheServiceState.BeginBuildFinished:
+ CheckInState(ProjectCacheServiceState.BeginBuildStarted);
+ break;
+ case ProjectCacheServiceState.ShutdownStarted:
+ CheckNotInState(ProjectCacheServiceState.ShutdownStarted);
+ CheckNotInState(ProjectCacheServiceState.ShutdownFinished);
+ break;
+ case ProjectCacheServiceState.ShutdownFinished:
+ CheckInState(ProjectCacheServiceState.ShutdownStarted);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(newState), newState, null);
+ }
+
+ _serviceState = newState;
+ }
+ }
+
+ private void CheckInState(ProjectCacheServiceState expectedState)
+ {
+ lock (this)
+ {
+ ErrorUtilities.VerifyThrowInternalError(_serviceState == expectedState, $"Expected state {expectedState}, actual state {_serviceState}");
+ }
+ }
+
+ private void CheckNotInState(ProjectCacheServiceState unexpectedState)
+ {
+ lock (this)
+ {
+ ErrorUtilities.VerifyThrowInternalError(_serviceState != unexpectedState, $"Unexpected state {_serviceState}");
+ }
+ }
+
private class LoggingServiceToPluginLoggerAdapter : PluginLoggerBase
{
private readonly ILoggingService _loggingService;
diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs
index 66195775b22..109cb49b9bf 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs
@@ -218,7 +218,8 @@ private void ExecuteAdd(ProjectItemGroupTaskItemInstance child, ItemBucket bucke
TaskParameterMessageKind.AddItem,
child.ItemType,
itemsToAdd,
- logItemMetadata: true);
+ logItemMetadata: true,
+ child.Location);
}
// Now add the items we created to the lookup.
@@ -261,7 +262,8 @@ private void ExecuteRemove(ProjectItemGroupTaskItemInstance child, ItemBucket bu
TaskParameterMessageKind.RemoveItem,
child.ItemType,
itemsToRemove,
- logItemMetadata: true);
+ logItemMetadata: true,
+ child.Location);
}
bucket.Lookup.RemoveItems(itemsToRemove);
diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs
index 4f7440b61e3..b672732ee39 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs
@@ -252,7 +252,8 @@ internal static void LogTaskParameter(
TaskParameterMessageKind messageKind,
string itemType,
IList items,
- bool logItemMetadata)
+ bool logItemMetadata,
+ IElementLocation location = null)
{
var args = CreateTaskParameterEventArgs(
loggingContext.BuildEventContext,
@@ -260,7 +261,10 @@ internal static void LogTaskParameter(
itemType,
items,
logItemMetadata,
- DateTime.UtcNow);
+ DateTime.UtcNow,
+ location?.Line ?? 0,
+ location?.Column ?? 0);
+
loggingContext.LogBuildEvent(args);
}
@@ -270,7 +274,9 @@ internal static TaskParameterEventArgs CreateTaskParameterEventArgs(
string itemType,
IList items,
bool logItemMetadata,
- DateTime timestamp)
+ DateTime timestamp,
+ int line = 0,
+ int column = 0)
{
// Only create a snapshot of items if we use AppDomains
#if FEATURE_APPDOMAIN
@@ -284,6 +290,8 @@ internal static TaskParameterEventArgs CreateTaskParameterEventArgs(
logItemMetadata,
timestamp);
args.BuildEventContext = buildEventContext;
+ args.LineNumber = line;
+ args.ColumnNumber = column;
return args;
}
diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs
index 21bdf35cb01..6b4b24dfa81 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs
@@ -555,6 +555,7 @@ private bool CheckSkipTarget(ref bool stopProcessingStack, TargetEntry currentTa
{
// If we've already dealt with this target and it didn't skip, let's log appropriately
// Otherwise we don't want anything more to do with it.
+ bool success = targetResult.ResultCode == TargetResultCode.Success;
var skippedTargetEventArgs = new TargetSkippedEventArgs(message: null)
{
BuildEventContext = _projectLoggingContext.BuildEventContext,
@@ -562,7 +563,9 @@ private bool CheckSkipTarget(ref bool stopProcessingStack, TargetEntry currentTa
TargetFile = currentTargetEntry.Target.Location.File,
ParentTarget = currentTargetEntry.ParentEntry?.Target.Name,
BuildReason = currentTargetEntry.BuildReason,
- OriginallySucceeded = targetResult.ResultCode == TargetResultCode.Success
+ OriginallySucceeded = success,
+ SkipReason = success ? TargetSkipReason.PreviouslyBuiltSuccessfully : TargetSkipReason.PreviouslyBuiltUnsuccessfully,
+ OriginalBuildEventContext = targetResult.OriginalBuildEventContext
};
_projectLoggingContext.LogBuildEvent(skippedTargetEventArgs);
diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs
index f7543d5d91d..d3a925b34a8 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs
@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.Build.Collections;
using Microsoft.Build.Evaluation;
+using Microsoft.Build.Eventing;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
@@ -354,7 +355,10 @@ internal List GetDependencies(ProjectLoggingContext project
if (!condition)
{
- _targetResult = new TargetResult(Array.Empty(), new WorkUnitResult(WorkUnitResultCode.Skipped, WorkUnitActionCode.Continue, null));
+ _targetResult = new TargetResult(
+ Array.Empty(),
+ new WorkUnitResult(WorkUnitResultCode.Skipped, WorkUnitActionCode.Continue, null),
+ projectLoggingContext.BuildEventContext);
_state = TargetEntryState.Completed;
if (!projectLoggingContext.LoggingService.OnlyLogCriticalEvents)
@@ -375,6 +379,7 @@ internal List GetDependencies(ProjectLoggingContext project
TargetFile = _target.Location.File,
ParentTarget = ParentEntry?.Target?.Name,
BuildReason = BuildReason,
+ SkipReason = TargetSkipReason.ConditionWasFalse,
Condition = _target.Condition,
EvaluatedCondition = expanded
};
@@ -460,8 +465,10 @@ internal async Task ExecuteTarget(ITaskBuilder taskBuilder, BuildRequestEntry re
Lookup lookupForExecution;
// UNDONE: (Refactor) Refactor TargetUpToDateChecker to take a logging context, not a logging service.
+ MSBuildEventSource.Log.TargetUpToDateStart();
TargetUpToDateChecker dependencyAnalyzer = new TargetUpToDateChecker(requestEntry.RequestConfiguration.Project, _target, targetLoggingContext.LoggingService, targetLoggingContext.BuildEventContext);
DependencyAnalysisResult dependencyResult = dependencyAnalyzer.PerformDependencyAnalysis(bucket, out changedTargetInputs, out upToDateTargetInputs);
+ MSBuildEventSource.Log.TargetUpToDateStop((int)dependencyResult);
switch (dependencyResult)
{
@@ -640,14 +647,11 @@ internal async Task ExecuteTarget(ITaskBuilder taskBuilder, BuildRequestEntry re
}
finally
{
-
-
- // log the last target finished since we now have the target outputs.
- targetLoggingContext?.LogTargetBatchFinished(projectFullPath, targetSuccess, targetOutputItems?.Count > 0 ? targetOutputItems : null);
-
+ // log the last target finished since we now have the target outputs.
+ targetLoggingContext?.LogTargetBatchFinished(projectFullPath, targetSuccess, targetOutputItems?.Count > 0 ? targetOutputItems : null);
}
- _targetResult = new TargetResult(targetOutputItems.ToArray(), aggregateResult);
+ _targetResult = new TargetResult(targetOutputItems.ToArray(), aggregateResult, targetLoggingContext?.BuildEventContext);
if (aggregateResult.ResultCode == WorkUnitResultCode.Failed && aggregateResult.ActionCode == WorkUnitActionCode.Stop)
{
diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs
index 335dcf77097..9013bc11272 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs
@@ -233,9 +233,17 @@ out ItemDictionary upToDateTargetInputs
if (result == DependencyAnalysisResult.SkipUpToDate)
{
- _loggingService.LogComment(_buildEventContext, MessageImportance.Normal,
- "SkipTargetBecauseOutputsUpToDate",
- TargetToAnalyze.Name);
+ var skippedTargetEventArgs = new TargetSkippedEventArgs(message: null)
+ {
+ BuildEventContext = _buildEventContext,
+ TargetName = TargetToAnalyze.Name,
+ BuildReason = TargetBuiltReason.None,
+ SkipReason = TargetSkipReason.OutputsUpToDate,
+ OriginallySucceeded = true,
+ Importance = MessageImportance.Normal
+ };
+
+ _loggingService.LogBuildEvent(skippedTargetEventArgs);
// Log the target inputs & outputs
if (!_loggingService.OnlyLogCriticalEvents)
@@ -337,7 +345,7 @@ private static string GetIncrementalBuildReason(DependencyAnalysisLogDetail logD
///
/// Extract only the unique inputs and outputs from all the inputs and outputs gathered
- /// during depedency analysis
+ /// during dependency analysis
///
private void LogUniqueInputsAndOutputs()
{
diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs
index 5390d169443..5fdd1a3e145 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs
@@ -847,7 +847,9 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta
}
else if (type == typeof(ThreadAbortException))
{
+#if !NET6_0_OR_GREATER && !NET6_0 // This is redundant but works around https://github.com/dotnet/sdk/issues/20700
Thread.ResetAbort();
+#endif
_continueOnError = ContinueOnError.ErrorAndStop;
// Cannot rethrow wrapped as ThreadAbortException is sealed and has no appropriate constructor
diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs
index 9222e6e9baf..154ace42f96 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs
@@ -22,6 +22,7 @@
using Microsoft.Build.BackEnd.Components.Caching;
using System.Reflection;
using Microsoft.Build.Eventing;
+using Microsoft.Build.Utilities;
namespace Microsoft.Build.BackEnd
{
@@ -33,12 +34,12 @@ internal class TaskHost :
#if FEATURE_APPDOMAIN
MarshalByRefObject,
#endif
- IBuildEngine9
+ IBuildEngine10
{
///
/// True if the "secret" environment variable MSBUILDNOINPROCNODE is set.
///
- private static bool s_onlyUseOutOfProcNodes = Environment.GetEnvironmentVariable("MSBUILDNOINPROCNODE") == "1";
+ private static bool s_disableInprocNodeByEnvironmentVariable = Environment.GetEnvironmentVariable("MSBUILDNOINPROCNODE") == "1";
///
/// Help diagnose tasks that log after they return.
@@ -104,6 +105,8 @@ internal class TaskHost :
///
private int _yieldThreadId = -1;
+ private bool _disableInprocNode;
+
///
/// Constructor
///
@@ -123,7 +126,11 @@ public TaskHost(IBuildComponentHost host, BuildRequestEntry requestEntry, Elemen
_targetBuilderCallback = targetBuilderCallback;
_continueOnError = false;
_activeProxy = true;
- _callbackMonitor = new Object();
+ _callbackMonitor = new object();
+ _disableInprocNode = ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0)
+ ? s_disableInprocNodeByEnvironmentVariable || host.BuildParameters.DisableInProcNode
+ : s_disableInprocNodeByEnvironmentVariable;
+ EngineServices = new EngineServicesImpl(this);
}
///
@@ -137,7 +144,7 @@ public bool IsRunningMultipleNodes
get
{
VerifyActiveProxy();
- return _host.BuildParameters.MaxNodeCount > 1 || s_onlyUseOutOfProcNodes;
+ return _host.BuildParameters.MaxNodeCount > 1 || _disableInprocNode;
}
}
@@ -868,6 +875,42 @@ internal void ReleaseAllCores()
#endregion
+ #region IBuildEngine10 Members
+
+ [Serializable]
+ private sealed class EngineServicesImpl : EngineServices
+ {
+ private readonly TaskHost _taskHost;
+
+ internal EngineServicesImpl(TaskHost taskHost)
+ {
+ _taskHost = taskHost;
+ }
+
+ ///
+ public override bool LogsMessagesOfImportance(MessageImportance importance)
+ {
+#if FEATURE_APPDOMAIN
+ if (RemotingServices.IsTransparentProxy(_taskHost))
+ {
+ // If the check would be a cross-domain call, chances are that it wouldn't be worth it.
+ // Simply disable the optimization in such a case.
+ return true;
+ }
+#endif
+ MessageImportance minimumImportance = _taskHost._taskLoggingContext?.LoggingService.MinimumRequiredMessageImportance ?? MessageImportance.Low;
+ return importance <= minimumImportance;
+
+ }
+
+ ///
+ public override bool IsTaskInputLoggingEnabled => _taskHost._host.BuildParameters.LogTaskInputs;
+ }
+
+ public EngineServices EngineServices { get; }
+
+ #endregion
+
///
/// Called by the internal MSBuild task.
/// Does not take the lock because it is called by another request builder thread.
diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs b/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs
index 9305abe7c66..994f3b155e9 100644
--- a/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs
+++ b/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs
@@ -463,6 +463,8 @@ public void VerifyOneOfStates(SchedulableRequestState[] requiredStates)
ErrorUtilities.ThrowInternalError("State {0} is not one of the expected states.", _state);
}
+ public bool IsProxyBuildRequest() => BuildRequest.IsProxyBuildRequest();
+
///
/// Change to the specified state. Update internal counters.
///
diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs
index 1e335cedb85..65af9d8c8a7 100644
--- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs
+++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs
@@ -11,9 +11,11 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.Execution;
+using Microsoft.Build.Experimental.ProjectCache;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
-
+using Microsoft.Build.Shared.Debugging;
+using Microsoft.Build.Utilities;
using BuildAbortedException = Microsoft.Build.Exceptions.BuildAbortedException;
using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService;
using NodeLoggingContext = Microsoft.Build.BackEnd.Logging.NodeLoggingContext;
@@ -140,7 +142,7 @@ internal class Scheduler : IScheduler
///
/// Flag used for debugging by forcing all scheduling to go out-of-proc.
///
- private bool _forceAffinityOutOfProc;
+ internal bool ForceAffinityOutOfProc { get; private set; }
///
/// The path into which debug files will be written.
@@ -166,14 +168,20 @@ internal class Scheduler : IScheduler
///
private AssignUnscheduledRequestsDelegate _customRequestSchedulingAlgorithm;
+ private NodeLoggingContext _inprocNodeContext;
+
+ private int _loggedWarningsForProxyBuildsOnOutOfProcNodes = 0;
+
///
/// Constructor.
///
public Scheduler()
{
- _debugDumpState = Environment.GetEnvironmentVariable("MSBUILDDEBUGSCHEDULER") == "1";
- _forceAffinityOutOfProc = Environment.GetEnvironmentVariable("MSBUILDNOINPROCNODE") == "1";
- _debugDumpPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH");
+ // Be careful moving these to Traits, changing the timing of reading environment variables has a breaking potential.
+ _debugDumpState = Traits.Instance.DebugScheduler;
+ _debugDumpPath = ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0)
+ ? DebugUtils.DebugPath
+ : Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH");
_schedulingUnlimitedVariable = Environment.GetEnvironmentVariable("MSBUILDSCHEDULINGUNLIMITED");
_nodeLimitOffset = 0;
@@ -610,6 +618,11 @@ public void InitializeComponent(IBuildComponentHost host)
_componentHost = host;
_resultsCache = (IResultsCache)_componentHost.GetComponent(BuildComponentType.ResultsCache);
_configCache = (IConfigCache)_componentHost.GetComponent(BuildComponentType.ConfigCache);
+ _inprocNodeContext = new NodeLoggingContext(_componentHost.LoggingService, InProcNodeId, true);
+ var inprocNodeDisabledViaEnvironmentVariable = Environment.GetEnvironmentVariable("MSBUILDNOINPROCNODE") == "1";
+ ForceAffinityOutOfProc = ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0)
+ ? inprocNodeDisabledViaEnvironmentVariable || _componentHost.BuildParameters.DisableInProcNode
+ : inprocNodeDisabledViaEnvironmentVariable;
}
///
@@ -791,6 +804,9 @@ private void AssignUnscheduledRequestsToNodes(List responses,
{
// We want to find more work first, and we assign traversals to the in-proc node first, if possible.
AssignUnscheduledRequestsByTraversalsFirst(responses, idleNodes);
+
+ AssignUnscheduledProxyBuildRequestsToInProcNode(responses, idleNodes);
+
if (idleNodes.Count == 0)
{
return;
@@ -972,6 +988,27 @@ private void AssignUnscheduledRequestsByTraversalsFirst(List r
}
}
+ ///
+ /// Proxy build requests should be really cheap (only return properties and items) and it's not worth
+ /// paying the IPC cost and re-evaluating them on out of proc nodes (they are guaranteed to be evaluated in the Scheduler process).
+ ///
+ private void AssignUnscheduledProxyBuildRequestsToInProcNode(List responses, HashSet idleNodes)
+ {
+ if (idleNodes.Contains(InProcNodeId))
+ {
+ List unscheduledRequests = new List(_schedulingData.UnscheduledRequestsWhichCanBeScheduled);
+ foreach (SchedulableRequest request in unscheduledRequests)
+ {
+ if (CanScheduleRequestToNode(request, InProcNodeId) && request.IsProxyBuildRequest())
+ {
+ AssignUnscheduledRequestToNode(request, InProcNodeId, responses);
+ idleNodes.Remove(InProcNodeId);
+ break;
+ }
+ }
+ }
+ }
+
///
/// Returns true if the request is for a traversal project. Traversals are used to find more work.
///
@@ -1325,12 +1362,6 @@ private void AssignUnscheduledRequestToNode(SchedulableRequest request, int node
ErrorUtilities.VerifyThrowArgumentNull(responses, nameof(responses));
ErrorUtilities.VerifyThrow(nodeId != InvalidNodeId, "Invalid node id specified.");
- // Currently we cannot move certain kinds of traversals (notably solution metaprojects) to other nodes because
- // they only have a ProjectInstance representation, and besides these kinds of projects build very quickly
- // and produce more references (more work to do.) This just verifies we do not attempt to send a traversal to
- // an out-of-proc node because doing so is inefficient and presently will cause the engine to fail on the remote
- // node because these projects cannot be found.
- ErrorUtilities.VerifyThrow(nodeId == InProcNodeId || _forceAffinityOutOfProc || !IsTraversalRequest(request.BuildRequest), "Can't assign traversal request to out-of-proc node!");
request.VerifyState(SchedulableRequestState.Unscheduled);
// Determine if this node has seen our configuration before. If not, we must send it along with this request.
@@ -1348,7 +1379,27 @@ private void AssignUnscheduledRequestToNode(SchedulableRequest request, int node
responses.Add(ScheduleResponse.CreateScheduleResponse(nodeId, request.BuildRequest, mustSendConfigurationToNode));
TraceScheduler("Executing request {0} on node {1} with parent {2}", request.BuildRequest.GlobalRequestId, nodeId, (request.Parent == null) ? -1 : request.Parent.BuildRequest.GlobalRequestId);
+
+ WarnWhenProxyBuildsGetScheduledOnOutOfProcNode();
+
request.ResumeExecution(nodeId);
+
+ void WarnWhenProxyBuildsGetScheduledOnOutOfProcNode()
+ {
+ if (request.IsProxyBuildRequest() && nodeId != InProcNodeId && _schedulingData.CanScheduleRequestToNode(request, InProcNodeId))
+ {
+ ErrorUtilities.VerifyThrow(
+ _componentHost.BuildParameters.DisableInProcNode || ForceAffinityOutOfProc,
+ "Proxy requests should only get scheduled to out of proc nodes when the inproc node is disabled");
+
+ var loggedWarnings = Interlocked.CompareExchange(ref _loggedWarningsForProxyBuildsOnOutOfProcNodes, 1, 0);
+
+ if (loggedWarnings == 0)
+ {
+ _inprocNodeContext.LogWarning("ProxyRequestNotScheduledOnInprocNode");
+ }
+ }
+ }
}
///
@@ -1713,7 +1764,25 @@ private void HandleRequestBlockedByNewRequests(SchedulableRequest parentRequest,
if (affinityMismatch)
{
- BuildResult result = new BuildResult(request, new InvalidOperationException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("AffinityConflict", requestAffinity, existingRequestAffinity)));
+ ErrorUtilities.VerifyThrowInternalError(
+ _configCache.HasConfiguration(request.ConfigurationId),
+ "A request should have a configuration if it makes it this far in the build process.");
+
+ var config = _configCache[request.ConfigurationId];
+ var globalProperties = string.Join(
+ ";",
+ config.GlobalProperties.ToDictionary().Select(kvp => $"{kvp.Key}={kvp.Value}"));
+
+ var result = new BuildResult(
+ request,
+ new InvalidOperationException(
+ ResourceUtilities.FormatResourceStringStripCodeAndKeyword(
+ "AffinityConflict",
+ requestAffinity,
+ existingRequestAffinity,
+ config.ProjectFullPath,
+ globalProperties
+ )));
response = GetResponseForResult(nodeForResults, request, result);
responses.Add(response);
continue;
@@ -2047,7 +2116,7 @@ private int ComputeClosureOfWaitingRequests(SchedulableRequest request)
///
private NodeAffinity GetNodeAffinityForRequest(BuildRequest request)
{
- if (_forceAffinityOutOfProc)
+ if (ForceAffinityOutOfProc)
{
return NodeAffinity.OutOfProc;
}
@@ -2057,6 +2126,15 @@ private NodeAffinity GetNodeAffinityForRequest(BuildRequest request)
return NodeAffinity.InProc;
}
+ ErrorUtilities.VerifyThrow(request.ConfigurationId != BuildRequestConfiguration.InvalidConfigurationId, "Requests should have a valid configuration id at this point");
+ // If this configuration has been previously built on an out of proc node, scheduling it on the inproc node can cause either an affinity mismatch error when
+ // there are other pending requests for the same configuration or "unscheduled requests remain in the presence of free out of proc nodes" errors if there's no pending requests.
+ // So only assign proxy builds to the inproc node if their config hasn't been previously assigned to an out of proc node.
+ if (_schedulingData.CanScheduleConfigurationToNode(request.ConfigurationId, InProcNodeId) && request.IsProxyBuildRequest())
+ {
+ return NodeAffinity.InProc;
+ }
+
BuildRequestConfiguration configuration = _configCache[request.ConfigurationId];
// The affinity may have been specified by the host services.
diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs
index 0edc83f296e..9aeb9009c80 100644
--- a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs
+++ b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs
@@ -647,7 +647,12 @@ public int GetAssignedNodeForRequestConfiguration(int configurationId)
///
public bool CanScheduleRequestToNode(SchedulableRequest request, int nodeId)
{
- int requiredNodeId = GetAssignedNodeForRequestConfiguration(request.BuildRequest.ConfigurationId);
+ return CanScheduleConfigurationToNode(request.BuildRequest.ConfigurationId, nodeId);
+ }
+
+ public bool CanScheduleConfigurationToNode(int configurationId, int nodeId)
+ {
+ int requiredNodeId = GetAssignedNodeForRequestConfiguration(configurationId);
return requiredNodeId == Scheduler.InvalidNodeId || requiredNodeId == nodeId;
}
diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs b/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs
index c719a51d2df..b8546bbe1b6 100644
--- a/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs
+++ b/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs
@@ -10,6 +10,7 @@
using Microsoft.Build.Shared;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Shared.FileSystem;
+using Microsoft.Build.Utilities;
namespace Microsoft.Build.BackEnd
{
@@ -316,7 +317,7 @@ private int GetConfigWithComparison(IEnumerable realConfigsToSchedule, Comp
private void AnalyzeData()
{
DoRecursiveAnalysis();
- if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDEBUGSCHEDULER")))
+ if (Traits.Instance.DebugScheduler)
{
DetermineExpensiveConfigs();
DetermineConfigsByNumberOfOccurrences();
diff --git a/src/Build/BackEnd/Components/SdkResolution/SdkResolverException.cs b/src/Build/BackEnd/Components/SdkResolution/SdkResolverException.cs
new file mode 100644
index 00000000000..d4f5b35a3d7
--- /dev/null
+++ b/src/Build/BackEnd/Components/SdkResolution/SdkResolverException.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+//
+
+using System;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Shared;
+
+namespace Microsoft.Build.BackEnd.SdkResolution
+{
+ ///
+ /// Represents an exception that occurs when an SdkResolver throws an unhandled exception.
+ ///
+ public class SdkResolverException : Exception
+ {
+ public SdkResolver Resolver { get; private set; }
+
+ public SdkReference Sdk { get; private set; }
+
+ public SdkResolverException(string resourceName, SdkResolver resolver, SdkReference sdk, Exception innerException, params string[] args)
+ : base(string.Format(ResourceUtilities.GetResourceString(resourceName), args), innerException)
+ {
+ Resolver = resolver;
+ Sdk = sdk;
+ }
+ }
+}
diff --git a/src/Build/BackEnd/Components/SdkResolution/SdkResolverLoader.cs b/src/Build/BackEnd/Components/SdkResolution/SdkResolverLoader.cs
index 97b4e83a02c..9c2fca19031 100644
--- a/src/Build/BackEnd/Components/SdkResolution/SdkResolverLoader.cs
+++ b/src/Build/BackEnd/Components/SdkResolution/SdkResolverLoader.cs
@@ -17,7 +17,7 @@ namespace Microsoft.Build.BackEnd.SdkResolution
internal class SdkResolverLoader
{
#if FEATURE_ASSEMBLYLOADCONTEXT
- private readonly CoreClrAssemblyLoader _loader = new CoreClrAssemblyLoader();
+ private static readonly CoreClrAssemblyLoader s_loader = new CoreClrAssemblyLoader();
#endif
private readonly string IncludeDefaultResolver = Environment.GetEnvironmentVariable("MSBUILDINCLUDEDEFAULTSDKRESOLVER");
@@ -35,7 +35,7 @@ internal class SdkResolverLoader
internal virtual IList LoadResolvers(LoggingContext loggingContext,
ElementLocation location)
{
- var resolvers = !String.Equals(IncludeDefaultResolver, "false", StringComparison.OrdinalIgnoreCase) ?
+ var resolvers = !String.Equals(IncludeDefaultResolver, "false", StringComparison.OrdinalIgnoreCase) ?
new List {new DefaultSdkResolver()}
: new List();
@@ -192,7 +192,7 @@ protected virtual Assembly LoadResolverAssembly(string resolverPath, LoggingCont
#if !FEATURE_ASSEMBLYLOADCONTEXT
return Assembly.LoadFrom(resolverPath);
#else
- return _loader.LoadFromPath(resolverPath);
+ return s_loader.LoadFromPath(resolverPath);
#endif
}
diff --git a/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs b/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs
index 237ea72d01e..6f90dacaeb4 100644
--- a/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs
+++ b/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs
@@ -117,21 +117,19 @@ public virtual SdkResult ResolveSdk(int submissionId, SdkReference sdk, LoggingC
{
result = (SdkResult)sdkResolver.Resolve(sdk, context, resultFactory);
}
- catch (Exception e) when (e is FileNotFoundException || (e is FileLoadException && sdkResolver.GetType().GetTypeInfo().Name.Equals("NuGetSdkResolver", StringComparison.Ordinal)))
+ catch (Exception e) when ((e is FileNotFoundException || e is FileLoadException) && sdkResolver.GetType().GetTypeInfo().Name.Equals("NuGetSdkResolver", StringComparison.Ordinal))
{
// Since we explicitly add the NuGetSdkResolver, we special case this. The NuGetSdkResolver has special logic
// to load NuGet assemblies at runtime which could fail if the user is not running installed MSBuild. Rather
// than give them a generic error, we want to give a more specific message. This exception cannot be caught by
// the resolver itself because it is usually thrown before the class is loaded
- // MSB4243: The NuGet-based SDK resolver failed to run because NuGet assemblies could not be located. Check your installation of MSBuild or set the environment variable "{0}" to the folder that contains the required NuGet assemblies. {1}
- loggingContext.LogWarning(null, new BuildEventFileInfo(sdkReferenceLocation), "CouldNotRunNuGetSdkResolver", MSBuildConstants.NuGetAssemblyPathEnvironmentVariableName, e.Message);
- continue;
+ // The NuGet-based SDK resolver failed to run because NuGet assemblies could not be located. Check your installation of MSBuild or set the environment variable "{0}" to the folder that contains the required NuGet assemblies. {1}
+ throw new SdkResolverException("CouldNotRunNuGetSdkResolver", sdkResolver, sdk, e, MSBuildConstants.NuGetAssemblyPathEnvironmentVariableName, e.ToString());
}
catch (Exception e)
{
- // MSB4242: The SDK resolver "{0}" failed to run. {1}
- loggingContext.LogWarning(null, new BuildEventFileInfo(sdkReferenceLocation), "CouldNotRunSdkResolver", sdkResolver.Name, e.Message);
- continue;
+ // The SDK resolver "{0}" failed while attempting to resolve the SDK "{1}": {2}
+ throw new SdkResolverException("SDKResolverFailed", sdkResolver, sdk, e, sdkResolver.Name, sdk.ToString(), e.ToString());
}
SetResolverState(submissionId, sdkResolver, context.State);
diff --git a/src/Build/BackEnd/Node/OutOfProcNode.cs b/src/Build/BackEnd/Node/OutOfProcNode.cs
index 80e36648f23..9e500181510 100644
--- a/src/Build/BackEnd/Node/OutOfProcNode.cs
+++ b/src/Build/BackEnd/Node/OutOfProcNode.cs
@@ -118,11 +118,6 @@ public class OutOfProcNode : INode, IBuildComponentHost, INodePacketFactory, INo
///
private Exception _shutdownException;
- ///
- /// Flag indicating if we should debug communications or not.
- ///
- private readonly bool _debugCommunications;
-
///
/// Data for the use of LegacyThreading semantics.
///
@@ -140,8 +135,6 @@ public OutOfProcNode()
{
s_isOutOfProcNode = true;
- _debugCommunications = (Environment.GetEnvironmentVariable("MSBUILDDEBUGCOMM") == "1");
-
_receivedPackets = new ConcurrentQueue();
_packetReceivedEvent = new AutoResetEvent(false);
_shutdownEvent = new ManualResetEvent(false);
diff --git a/src/Build/BackEnd/Shared/BuildRequest.cs b/src/Build/BackEnd/Shared/BuildRequest.cs
index 4a0a4efb7f1..2bde7843447 100644
--- a/src/Build/BackEnd/Shared/BuildRequest.cs
+++ b/src/Build/BackEnd/Shared/BuildRequest.cs
@@ -419,5 +419,10 @@ internal static INodePacket FactoryForDeserialization(ITranslator translator)
}
#endregion
+
+ public bool IsProxyBuildRequest()
+ {
+ return ProxyTargets != null;
+ }
}
}
diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs
index a72435698c9..2cc6ab1f7b4 100644
--- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs
+++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs
@@ -131,8 +131,6 @@ internal class BuildRequestConfiguration : IEquatable
///
private string _savedCurrentDirectory;
- private bool _translateEntireProjectInstanceState;
-
#endregion
///
@@ -178,7 +176,6 @@ internal BuildRequestConfiguration(int configId, BuildRequestData data, string d
_project = data.ProjectInstance;
_projectInitialTargets = data.ProjectInstance.InitialTargets;
_projectDefaultTargets = data.ProjectInstance.DefaultTargets;
- _translateEntireProjectInstanceState = data.ProjectInstance.TranslateEntireState;
if (data.PropertiesToTransfer != null)
{
@@ -216,7 +213,6 @@ internal BuildRequestConfiguration(int configId, ProjectInstance instance)
_project = instance;
_projectInitialTargets = instance.InitialTargets;
_projectDefaultTargets = instance.DefaultTargets;
- _translateEntireProjectInstanceState = instance.TranslateEntireState;
IsCacheable = false;
}
@@ -230,7 +226,6 @@ private BuildRequestConfiguration(int configId, BuildRequestConfiguration other)
ErrorUtilities.VerifyThrow(other._transferredState == null, "Unexpected transferred state still set on other configuration.");
_project = other._project;
- _translateEntireProjectInstanceState = other._translateEntireProjectInstanceState;
_transferredProperties = other._transferredProperties;
_projectDefaultTargets = other._projectDefaultTargets;
_projectInitialTargets = other._projectInitialTargets;
@@ -410,7 +405,6 @@ private void SetProjectBasedState(ProjectInstance project)
ProjectDefaultTargets = _project.DefaultTargets;
ProjectInitialTargets = _project.InitialTargets;
- _translateEntireProjectInstanceState = _project.TranslateEntireState;
if (IsCached)
{
@@ -498,7 +492,7 @@ private void InitializeProject(BuildParameters buildParameters, Func
private CacheInfo _cacheInfo;
+ ///
+ /// The (possibly null) from the original target build
+ ///
+ private BuildEventContext _originalBuildEventContext;
+
///
/// Initializes the results with specified items and result.
///
/// The items produced by the target.
/// The overall result for the target.
- internal TargetResult(TaskItem[] items, WorkUnitResult result)
+ /// The original build event context from when the target was first built, if available.
+ /// Non-null when creating a after building the target initially (or skipping due to false condition).
+ /// Null when the is being created in other scenarios:
+ /// * Target that never ran because a dependency had an error
+ /// * in when Cancellation was requested
+ /// * in ProjectCache.CacheResult.ConstructBuildResult
+ ///
+ internal TargetResult(TaskItem[] items, WorkUnitResult result, BuildEventContext originalBuildEventContext = null)
{
ErrorUtilities.VerifyThrowArgumentNull(items, nameof(items));
ErrorUtilities.VerifyThrowArgumentNull(result, nameof(result));
_items = items;
_result = result;
+ _originalBuildEventContext = originalBuildEventContext;
}
///
@@ -130,6 +143,11 @@ internal WorkUnitResult WorkUnitResult
get => _result;
}
+ ///
+ /// The (possibly null) from the original target build
+ ///
+ internal BuildEventContext OriginalBuildEventContext => _originalBuildEventContext;
+
///
/// Sets or gets a flag indicating whether or not a failure results should cause the build to fail.
///
@@ -225,22 +243,15 @@ internal void CacheItems(int configId, string targetName)
return;
}
- ITranslator translator = GetResultsCacheTranslator(configId, targetName, TranslationDirection.WriteToStream);
+ using ITranslator translator = GetResultsCacheTranslator(configId, targetName, TranslationDirection.WriteToStream);
// If the translator is null, it means these results were cached once before. Since target results are immutable once they
// have been created, there is no point in writing them again.
if (translator != null)
{
- try
- {
- TranslateItems(translator);
- _items = null;
- _cacheInfo = new CacheInfo(configId, targetName);
- }
- finally
- {
- translator.Writer.BaseStream.Dispose();
- }
+ TranslateItems(translator);
+ _items = null;
+ _cacheInfo = new CacheInfo(configId, targetName);
}
}
}
@@ -253,6 +264,7 @@ private void InternalTranslate(ITranslator translator)
translator.Translate(ref _result, WorkUnitResult.FactoryForDeserialization);
translator.Translate(ref _targetFailureDoesntCauseBuildFailure);
translator.Translate(ref _afterTargetsHaveFailed);
+ translator.TranslateOptionalBuildEventContext(ref _originalBuildEventContext);
TranslateItems(translator);
}
@@ -265,17 +277,10 @@ private void RetrieveItemsFromCache()
{
if (_items == null)
{
- ITranslator translator = GetResultsCacheTranslator(_cacheInfo.ConfigId, _cacheInfo.TargetName, TranslationDirection.ReadFromStream);
-
- try
- {
- TranslateItems(translator);
- _cacheInfo = new CacheInfo();
- }
- finally
- {
- translator.Reader.BaseStream.Dispose();
- }
+ using ITranslator translator = GetResultsCacheTranslator(_cacheInfo.ConfigId, _cacheInfo.TargetName, TranslationDirection.ReadFromStream);
+
+ TranslateItems(translator);
+ _cacheInfo = new CacheInfo();
}
}
}
@@ -320,7 +325,7 @@ private void TranslateItems(ITranslator translator)
ErrorUtilities.VerifyThrow(buffer != null, "Unexpected null items buffer during translation.");
using MemoryStream itemsStream = new MemoryStream(buffer, 0, buffer.Length, writable: false, publiclyVisible: true);
- var itemTranslator = BinaryTranslator.GetReadTranslator(itemsStream, null);
+ using var itemTranslator = BinaryTranslator.GetReadTranslator(itemsStream, null);
_items = new TaskItem[itemsCount];
for (int i = 0; i < _items.Length; i++)
{
diff --git a/src/Build/Construction/ProjectElement.cs b/src/Build/Construction/ProjectElement.cs
index 129e9ae3f35..8327feda09e 100644
--- a/src/Build/Construction/ProjectElement.cs
+++ b/src/Build/Construction/ProjectElement.cs
@@ -415,7 +415,7 @@ public virtual void CopyFrom(ProjectElement element)
}
///
- /// Hook for subclasses to specify whether the given should be cloned or not
+ /// Hook for subclasses to specify whether the given should be cloned or not
///
protected virtual bool ShouldCloneXmlAttribute(XmlAttribute attribute) => true;
diff --git a/src/Build/Construction/ProjectElementContainer.cs b/src/Build/Construction/ProjectElementContainer.cs
index b55ba655ff4..3c4c0d6f269 100644
--- a/src/Build/Construction/ProjectElementContainer.cs
+++ b/src/Build/Construction/ProjectElementContainer.cs
@@ -316,7 +316,7 @@ public void RemoveChild(ProjectElement child)
///
///
/// It is safe to modify the children in this way
- /// during enumeration. See RemoveChild.
+ /// during enumeration. See .
///
public void RemoveAllChildren()
{
diff --git a/src/Build/Construction/ProjectRootElement.cs b/src/Build/Construction/ProjectRootElement.cs
index 9d86a4731dd..389f1f3cf6e 100644
--- a/src/Build/Construction/ProjectRootElement.cs
+++ b/src/Build/Construction/ProjectRootElement.cs
@@ -207,8 +207,12 @@ private ProjectRootElement(ProjectRootElementCacheBase projectRootElementCache,
/// Assumes path is already normalized.
/// May throw InvalidProjectFileException.
///
- private ProjectRootElement(string path, ProjectRootElementCacheBase projectRootElementCache,
- bool preserveFormatting)
+ private ProjectRootElement
+ (
+ string path,
+ ProjectRootElementCacheBase projectRootElementCache,
+ bool preserveFormatting
+ )
{
ErrorUtilities.VerifyThrowArgumentLength(path, nameof(path));
ErrorUtilities.VerifyThrowInternalRooted(path);
@@ -222,8 +226,6 @@ private ProjectRootElement(string path, ProjectRootElementCacheBase projectRootE
XmlDocumentWithLocation document = LoadDocument(path, preserveFormatting, projectRootElementCache.LoadProjectsReadOnly);
ProjectParser.Parse(document, this);
-
- projectRootElementCache.AddEntry(this);
}
///
@@ -1677,19 +1679,33 @@ private void ReloadFrom(Func documentProducer, bo
{
ThrowIfUnsavedChanges(throwIfUnsavedChanges);
- XmlDocumentWithLocation document = documentProducer(preserveFormatting ?? PreserveFormatting);
-
- // Reload should only mutate the state if there are no parse errors.
- ThrowIfDocumentHasParsingErrors(document);
-
- // Do not clear the string cache.
- // Based on the assumption that Projects are reloaded repeatedly from their file with small increments,
- // and thus most strings would get reused
- //this.XmlDocument.ClearAnyCachedStrings();
+ var oldDocument = XmlDocument;
+ XmlDocumentWithLocation newDocument = documentProducer(preserveFormatting ?? PreserveFormatting);
+ try
+ {
+ // Reload should only mutate the state if there are no parse errors.
+ ThrowIfDocumentHasParsingErrors(newDocument);
- RemoveAllChildren();
+ RemoveAllChildren();
- ProjectParser.Parse(document, this);
+ ProjectParser.Parse(newDocument, this);
+ }
+ finally
+ {
+ // Whichever document didn't become this element's document must be removed from the string cache.
+ // We do it after the fact based on the assumption that Projects are reloaded repeatedly from their
+ // file with small increments, and thus most strings would get reused avoiding unnecessary churn in
+ // the string cache.
+ var currentDocument = XmlDocument;
+ if (!object.ReferenceEquals(currentDocument, oldDocument))
+ {
+ oldDocument.ClearAnyCachedStrings();
+ }
+ if (!object.ReferenceEquals(currentDocument, newDocument))
+ {
+ newDocument.ClearAnyCachedStrings();
+ }
+ }
MarkDirty("Project reloaded", null);
}
diff --git a/src/Build/Construction/Solution/SolutionConfigurationInSolution.cs b/src/Build/Construction/Solution/SolutionConfigurationInSolution.cs
index d5e869cb5eb..e35d36a9330 100644
--- a/src/Build/Construction/Solution/SolutionConfigurationInSolution.cs
+++ b/src/Build/Construction/Solution/SolutionConfigurationInSolution.cs
@@ -14,7 +14,7 @@ public sealed class SolutionConfigurationInSolution
///
internal const char ConfigurationPlatformSeparator = '|';
- internal static readonly char[] ConfigurationPlatformSeparatorArray = new char[] { '|' };
+ internal static readonly char[] ConfigurationPlatformSeparatorArray = { '|' };
///
/// Constructor
diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs
index 364e449dcbe..f3e2c331c50 100644
--- a/src/Build/Construction/Solution/SolutionFile.cs
+++ b/src/Build/Construction/Solution/SolutionFile.cs
@@ -1384,7 +1384,6 @@ internal void ParseNestedProjects()
internal void ParseSolutionConfigurations()
{
var nameValueSeparators = '=';
- var configPlatformSeparators = new[] { SolutionConfigurationInSolution.ConfigurationPlatformSeparator };
do
{
@@ -1419,15 +1418,26 @@ internal void ParseSolutionConfigurations()
ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(fullConfigurationName == configurationNames[1].Trim(), "SubCategoryForSolutionParsingErrors",
new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseInvalidSolutionConfigurationEntry", str);
- string[] configurationPlatformParts = fullConfigurationName.Split(configPlatformSeparators);
+ var (configuration, platform) = ParseConfigurationName(fullConfigurationName, FullPath, _currentLineNumber, str);
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(configurationPlatformParts.Length == 2, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseInvalidSolutionConfigurationEntry", str);
-
- _solutionConfigurations.Add(new SolutionConfigurationInSolution(configurationPlatformParts[0], configurationPlatformParts[1]));
+ _solutionConfigurations.Add(new SolutionConfigurationInSolution(configuration, platform));
} while (true);
}
+ internal static (string Configuration, string Platform) ParseConfigurationName(string fullConfigurationName, string projectPath, int lineNumber, string containingString)
+ {
+ string[] configurationPlatformParts = fullConfigurationName.Split(SolutionConfigurationInSolution.ConfigurationPlatformSeparatorArray);
+
+ ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(
+ configurationPlatformParts.Length == 2,
+ "SubCategoryForSolutionParsingErrors",
+ new BuildEventFileInfo(projectPath, lineNumber, 0),
+ "SolutionParseInvalidSolutionConfigurationEntry",
+ containingString);
+
+ return (configurationPlatformParts[0], configurationPlatformParts[1]);
+ }
+
///
/// Read project configurations in solution configurations section.
///
diff --git a/src/Build/Construction/Solution/SolutionProjectGenerator.cs b/src/Build/Construction/Solution/SolutionProjectGenerator.cs
index 4b1edd904a4..625352ba374 100644
--- a/src/Build/Construction/Solution/SolutionProjectGenerator.cs
+++ b/src/Build/Construction/Solution/SolutionProjectGenerator.cs
@@ -49,6 +49,11 @@ internal class SolutionProjectGenerator
private const string WebProjectOverrideFolder = "_PublishedWebsites";
#endif // FEATURE_ASPNET_COMPILER
+ ///
+ /// Property set by VS when building projects. It's an XML containing the project configurations for ALL projects in the solution for the currently selected solution configuration.
+ ///
+ internal const string CurrentSolutionConfigurationContents = nameof(CurrentSolutionConfigurationContents);
+
///
/// The set of properties all projects in the solution should be built with
///
@@ -66,7 +71,11 @@ internal class SolutionProjectGenerator
"Build",
"Clean",
"Rebuild",
- "Publish"
+ "Publish",
+ "ValidateSolutionConfiguration",
+ "ValidateToolsVersions",
+ "ValidateProjects",
+ "GetSolutionConfigurationContents"
);
#if FEATURE_ASPNET_COMPILER
@@ -226,6 +235,7 @@ internal static void AddPropertyGroupForSolutionConfiguration(ProjectRootElement
};
using (XmlWriter xw = XmlWriter.Create(solutionConfigurationContents, settings))
{
+ // TODO: fix code clone for parsing CurrentSolutionConfiguration xml: https://github.com/dotnet/msbuild/issues/6751
xw.WriteStartElement("SolutionConfiguration");
// add a project configuration entry for each project in the solution
@@ -975,6 +985,10 @@ private ProjectInstance CreateTraversalInstance(string wrapperProjectToolsVersio
_submissionId
);
+ // Traversal meta project entire state has to be serialized as it was generated and hence
+ // does not have disk representation to load project from.
+ traversalInstance.TranslateEntireState = true;
+
// Make way for the real ones
foreach (string targetName in dummyTargetsForEvaluationTime)
{
@@ -1182,6 +1196,10 @@ private ProjectInstance CreateMetaproject(ProjectInstance traversalProject, Proj
// Create a new project instance with global properties and tools version from the existing project
ProjectInstance metaprojectInstance = new ProjectInstance(EscapingUtilities.UnescapeAll(GetMetaprojectName(project)), traversalProject, GetMetaprojectGlobalProperties(traversalProject));
+ // Traversal meta project entire state has to be serialized as it was generated and hence
+ // does not have disk representation to load project from.
+ metaprojectInstance.TranslateEntireState = true;
+
// Add the project references which must build before this one.
AddMetaprojectReferenceItems(traversalProject, metaprojectInstance, project);
diff --git a/src/Build/Definition/Project.cs b/src/Build/Definition/Project.cs
index f158ca5a2bb..d842373c86c 100644
--- a/src/Build/Definition/Project.cs
+++ b/src/Build/Definition/Project.cs
@@ -43,7 +43,7 @@ namespace Microsoft.Build.Evaluation
/// Edits to this project always update the backing XML.
///
// UNDONE: (Multiple configurations.) Protect against problems when attempting to edit, after edits were made to the same ProjectRootElement either directly or through other projects evaluated from that ProjectRootElement.
- [DebuggerDisplay("{FullPath} EffectiveToolsVersion={ToolsVersion} #GlobalProperties={_data.GlobalPropertiesDictionary.Count} #Properties={_data.Properties.Count} #ItemTypes={_data.ItemTypes.Count} #ItemDefinitions={_data.ItemDefinitions.Count} #Items={_data.Items.Count} #Targets={_data.Targets.Count}")]
+ [DebuggerDisplay("{FullPath} EffectiveToolsVersion={ToolsVersion} #GlobalProperties={implementation._data.GlobalPropertiesDictionary.Count} #Properties={implementation._data.Properties.Count} #ItemTypes={implementation._data.ItemTypes.Count} #ItemDefinitions={implementation._data.ItemDefinitions.Count} #Items={implementation._data.Items.Count} #Targets={implementation._data.Targets.Count}")]
public class Project : ILinkableObject
{
///
@@ -88,7 +88,7 @@ public class Project : ILinkableObject
/// -
/// -
///
- /// When this property is set to true, the previous item operations throw an
+ /// When this property is set to true, the previous item operations throw an
/// instead of expanding the item element.
///
public bool ThrowInsteadOfSplittingItemElement
@@ -461,7 +461,7 @@ private Project(string projectFile, IDictionary globalProperties
{
// If possible, clear out the XML we just loaded into the XML cache:
// if we had loaded the XML from disk into the cache within this constructor,
- // and then are are bailing out because there is a typo in the XML such that
+ // and then are are bailing out because there is a typo in the XML such that
// evaluation failed, we don't want to leave the bad XML in the cache;
// the user wouldn't be able to fix the XML file and try again.
if (!ExceptionHandling.IsCriticalException(ex))
@@ -2406,7 +2406,7 @@ public override bool IsBuildEnabled
/// their previously stored value to find out, and if so perhaps decide to update their own state.
/// Note that the number may not increase monotonically.
///
- /// This number corresponds to the and can be used to connect
+ /// This number corresponds to the and can be used to connect
/// evaluation logging events back to the Project instance.
///
public override int LastEvaluationId => _data.EvaluationId;
@@ -2503,7 +2503,7 @@ private List GetAllGlobs(List projectItemElement
// 5.
// 6. // this remove applies to the includes at 1, 3, 5
// So A's applicable removes are composed of:
- //
+ //
// The applicable removes for the element at position 1 (xml element A) are composed of:
// - all the removes seen by the next include statement of I's type (xml element B, position 3, which appears after A in file order). In this example that's Removes at positions 4 and 6.
// - new removes between A and B. In this example that's Remove 2.
@@ -2634,7 +2634,7 @@ public override List GetItemProvenance(string itemToMatch, str
///
/// See .
///
- /// ///
+ /// ///
/// The ProjectItem object that indicates: the itemspec to match and the item type to constrain the search in.
/// The search is also constrained on item elements appearing before the item element that produced this .
/// The element that produced this is included in the results.
@@ -3163,7 +3163,7 @@ public override void RemoveItems(IEnumerable items)
ErrorUtilities.VerifyThrowArgumentNull(items, nameof(items));
// Copying to a list makes it possible to remove
- // all items of a particular type with
+ // all items of a particular type with
// RemoveItems(p.GetItems("mytype"))
// without modifying the collection during enumeration.
var itemsList = new List(items);
@@ -3586,7 +3586,7 @@ private void ReevaluateIfNecessary(
EvaluationContext evaluationContext = null)
{
// We will skip the evaluation if the flag is set. This will give us better performance on scenarios
- // that we know we don't have to reevaluate. One example is project conversion bulk addfiles and set attributes.
+ // that we know we don't have to reevaluate. One example is project conversion bulk addfiles and set attributes.
if (!SkipEvaluation && !ProjectCollection.SkipEvaluation && IsDirty)
{
try
@@ -3684,9 +3684,9 @@ internal void Initialize(IDictionary globalProperties, string to
{
if (String.Equals(pair.Key, Constants.SubToolsetVersionPropertyName, StringComparison.OrdinalIgnoreCase) && subToolsetVersion != null)
{
- // if we have a sub-toolset version explicitly provided by the ProjectInstance constructor, AND a sub-toolset version provided as a global property,
- // make sure that the one passed in with the constructor wins. If there isn't a matching global property, the sub-toolset version will be set at
- // a later point.
+ // if we have a sub-toolset version explicitly provided by the ProjectInstance constructor, AND a sub-toolset version provided as a global property,
+ // make sure that the one passed in with the constructor wins. If there isn't a matching global property, the sub-toolset version will be set at
+ // a later point.
globalPropertiesCollection.Set(ProjectPropertyInstance.Create(pair.Key, subToolsetVersion));
}
else
@@ -4185,7 +4185,7 @@ public void InitializeForEvaluation(IToolsetProvider toolsetProvider, IFileSyste
_globalPropertiesToTreatAsLocal?.Clear();
- // Include the main project in the list of imports, as this list is
+ // Include the main project in the list of imports, as this list is
// used to figure out if any of them have changed.
RecordImport(null, Project.Xml, Project.Xml.Version, null);
@@ -4225,7 +4225,7 @@ out var usingDifferentToolsVersionFromProjectFile
SubToolsetVersion = Toolset.GenerateSubToolsetVersion(GlobalPropertiesDictionary);
}
- // Create a task registry which will fall back on the toolset task registry if necessary.
+ // Create a task registry which will fall back on the toolset task registry if necessary.
TaskRegistry = new TaskRegistry(Toolset, Project.ProjectCollection.ProjectRootElementCache);
}
@@ -4235,7 +4235,7 @@ out var usingDifferentToolsVersionFromProjectFile
///
public void FinishEvaluation()
{
- // We assume there will be no further changes to the targets collection
+ // We assume there will be no further changes to the targets collection
// This also makes sure that we are thread safe
Targets.MakeReadOnly();
@@ -4254,7 +4254,7 @@ public void FinishEvaluation()
}
else
{
- // Else we'll guess that this latest one is a potential match for the next,
+ // Else we'll guess that this latest one is a potential match for the next,
// if it actually has any elements (eg., it's not a .user or .filters file)
if (Targets.Count > 0)
{
@@ -4489,7 +4489,7 @@ internal bool RemoveItem(ProjectItem item)
// This remove will not succeed if the item include was changed.
// If many items are modified and then removed, this will leak them
- // until the next reevaluation.
+ // until the next reevaluation.
ItemsByEvaluatedIncludeCache.Remove(item.EvaluatedInclude, item);
ItemsIgnoringCondition.Remove(item);
diff --git a/src/Build/Definition/ProjectCollection.cs b/src/Build/Definition/ProjectCollection.cs
index 07519d583ba..8629bd94317 100644
--- a/src/Build/Definition/ProjectCollection.cs
+++ b/src/Build/Definition/ProjectCollection.cs
@@ -1801,6 +1801,11 @@ internal class ReusableLogger : INodeLogger, IEventSource4
///
private readonly ILogger _originalLogger;
+ ///
+ /// Returns the logger we are wrapping.
+ ///
+ internal ILogger OriginalLogger => _originalLogger;
+
///
/// The design-time event source
///
diff --git a/src/Build/Definition/ProjectImportPathMatch.cs b/src/Build/Definition/ProjectImportPathMatch.cs
index f31d63fb957..1c670eb06a4 100644
--- a/src/Build/Definition/ProjectImportPathMatch.cs
+++ b/src/Build/Definition/ProjectImportPathMatch.cs
@@ -62,4 +62,4 @@ internal static ProjectImportPathMatch FactoryForDeserialization(ITranslator tra
return new ProjectImportPathMatch(translator);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Build/Definition/Toolset.cs b/src/Build/Definition/Toolset.cs
index a4e4d21565b..77d32963bf6 100644
--- a/src/Build/Definition/Toolset.cs
+++ b/src/Build/Definition/Toolset.cs
@@ -485,7 +485,7 @@ public string DefaultSubToolsetVersion
return Constants.Dev10SubToolsetValue;
}
- // 2) Otherwise, just pick the highest available.
+ // 2) Otherwise, just pick the highest available.
SortedDictionary subToolsetsWithVersion = new SortedDictionary();
List additionalSubToolsetNames = new List();
@@ -499,7 +499,7 @@ public string DefaultSubToolsetVersion
}
else
{
- // if it doesn't parse to an actual version number, shrug and just add it to the end.
+ // if it doesn't parse to an actual version number, shrug and just add it to the end.
additionalSubToolsetNames.Add(subToolsetName);
}
}
@@ -538,11 +538,11 @@ internal static bool Dev10IsInstalled
{
try
{
- // Figure out whether Dev10 is currently installed using the following heuristic:
- // - Check whether the overall key (installed if any version of Dev10 is installed) is there.
- // - If it's not, no version of Dev10 exists or has ever existed on this machine, so return 'false'.
- // - If it is, we know that some version of Dev10 has been installed at some point, but we don't know
- // for sure whether it's still there or not. Check the inndividual keys for {Pro, Premium, Ultimate,
+ // Figure out whether Dev10 is currently installed using the following heuristic:
+ // - Check whether the overall key (installed if any version of Dev10 is installed) is there.
+ // - If it's not, no version of Dev10 exists or has ever existed on this machine, so return 'false'.
+ // - If it is, we know that some version of Dev10 has been installed at some point, but we don't know
+ // for sure whether it's still there or not. Check the inndividual keys for {Pro, Premium, Ultimate,
// C# Express, VB Express, C++ Express, VWD Express, LightSwitch} 2010
// - If even one of them exists, return 'true'.
// - Otherwise, return 'false.
@@ -804,7 +804,7 @@ internal string GenerateSubToolsetVersion(int visualStudioVersionFromSolution)
}
}
- // Next, try the toolset environment properties
+ // Next, try the toolset environment properties
if (_environmentProperties != null)
{
ProjectPropertyInstance visualStudioVersionProperty = _environmentProperties[Constants.SubToolsetVersionPropertyName];
@@ -823,8 +823,8 @@ internal string GenerateSubToolsetVersion(int visualStudioVersionFromSolution)
subToolsetVersion = SubToolsets.Keys.FirstOrDefault(version => visualStudioVersionFromSolutionAsVersion.Equals(VersionUtilities.ConvertToVersion(version)));
}
- // Solution version also didn't work out, so fall back to default.
- // If subToolsetVersion is null, there simply wasn't a matching solution version.
+ // Solution version also didn't work out, so fall back to default.
+ // If subToolsetVersion is null, there simply wasn't a matching solution version.
return subToolsetVersion ?? (DefaultSubToolsetVersion);
}
@@ -920,12 +920,12 @@ private void InitializeProperties(ILoggingService loggingServices, BuildEventCon
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.assemblyVersion, Constants.AssemblyVersion, mayBeReserved: true));
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.version, MSBuildAssemblyFileVersion.Instance.MajorMinorBuild, mayBeReserved: true));
- // Add one for the subtoolset version property -- it may or may not be set depending on whether it has already been set by the
- // environment or global properties, but it's better to create a dictionary that's one too big than one that's one too small.
+ // Add one for the subtoolset version property -- it may or may not be set depending on whether it has already been set by the
+ // environment or global properties, but it's better to create a dictionary that's one too big than one that's one too small.
int count = _environmentProperties.Count + reservedProperties.Count + Properties.Values.Count + _globalProperties.Count + 1;
- // GenerateSubToolsetVersion checks the environment and global properties, so it's safe to go ahead and gather the
- // subtoolset properties here without fearing that we'll have somehow come up with the wrong subtoolset version.
+ // GenerateSubToolsetVersion checks the environment and global properties, so it's safe to go ahead and gather the
+ // subtoolset properties here without fearing that we'll have somehow come up with the wrong subtoolset version.
string subToolsetVersion = this.GenerateSubToolsetVersion();
SubToolset subToolset;
ICollection subToolsetProperties = null;
@@ -941,10 +941,10 @@ private void InitializeProperties(ILoggingService loggingServices, BuildEventCon
_propertyBag = new PropertyDictionary(count);
- // Should be imported in the same order as in the evaluator:
+ // Should be imported in the same order as in the evaluator:
// - Environment
// - Toolset
- // - Subtoolset (if any)
+ // - Subtoolset (if any)
// - Global
_propertyBag.ImportProperties(_environmentProperties);
diff --git a/src/Build/Definition/ToolsetConfigurationReader.cs b/src/Build/Definition/ToolsetConfigurationReader.cs
index 5665b1aaf09..990a8b686cb 100644
--- a/src/Build/Definition/ToolsetConfigurationReader.cs
+++ b/src/Build/Definition/ToolsetConfigurationReader.cs
@@ -10,6 +10,7 @@
using Microsoft.Build.Execution;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
+using Microsoft.Build.Utilities;
using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities;
using InvalidToolsetDefinitionException = Microsoft.Build.Exceptions.InvalidToolsetDefinitionException;
@@ -41,6 +42,13 @@ internal class ToolsetConfigurationReader : ToolsetReader
///
private static readonly char[] s_separatorForExtensionsPathSearchPaths = MSBuildConstants.SemicolonChar;
+ ///
+ /// Caching MSBuild exe configuration.
+ /// Used only by ReadApplicationConfiguration factory function (default) as oppose to unit tests config factory functions
+ /// which must not cache configs.
+ ///
+ private static readonly Lazy s_configurationCache = new Lazy(ReadOpenMappedExeConfiguration);
+
///
/// Cached values of tools version -> project import search paths table
///
@@ -250,6 +258,18 @@ private Dictionary ComputeDistinctListOfSearchPa
/// Unit tests wish to avoid reading (nunit.exe) application configuration file.
///
private static Configuration ReadApplicationConfiguration()
+ {
+ if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0))
+ {
+ return s_configurationCache.Value;
+ }
+ else
+ {
+ return ReadOpenMappedExeConfiguration();
+ }
+ }
+
+ private static Configuration ReadOpenMappedExeConfiguration()
{
// When running from the command-line or from VS, use the msbuild.exe.config file.
if (BuildEnvironmentHelper.Instance.Mode != BuildEnvironmentMode.None &&
diff --git a/src/Build/Definition/ToolsetRegistryReader.cs b/src/Build/Definition/ToolsetRegistryReader.cs
index e2081dc5ad7..d49b10b1cf2 100644
--- a/src/Build/Definition/ToolsetRegistryReader.cs
+++ b/src/Build/Definition/ToolsetRegistryReader.cs
@@ -323,7 +323,7 @@ private static string GetValue(RegistryKeyWrapper wrapper, string valueName)
object result = wrapper.GetValue(valueName);
// RegistryKey.GetValue returns null if the value is not present
- // and String.Empty if the value is present and no data is defined.
+ // and String.Empty if the value is present and no data is defined.
// We preserve this distinction, because a string property in the registry with
// no value really has an empty string for a value (which is a valid property value)
// rather than null for a value (which is an invalid property value)
diff --git a/src/Build/Evaluation/Context/EvaluationContext.cs b/src/Build/Evaluation/Context/EvaluationContext.cs
index 470b4f0cb1e..827d9465d75 100644
--- a/src/Build/Evaluation/Context/EvaluationContext.cs
+++ b/src/Build/Evaluation/Context/EvaluationContext.cs
@@ -91,7 +91,7 @@ public static EvaluationContext Create(SharingPolicy policy, MSBuildFileSystemBa
{
var context = new EvaluationContext(
policy,
- fileSystem == null ? null : new MSBuildFileSystemAdapter(fileSystem));
+ fileSystem);
TestOnlyHookOnCreate?.Invoke(context);
diff --git a/src/Build/Evaluation/Evaluator.cs b/src/Build/Evaluation/Evaluator.cs
index d47f970c517..712307191f4 100644
--- a/src/Build/Evaluation/Evaluator.cs
+++ b/src/Build/Evaluation/Evaluator.cs
@@ -627,7 +627,7 @@ private void Evaluate()
}
_data.InitialTargets = initialTargets;
- MSBuildEventSource.Log.EvaluatePass1Stop(projectFile, _projectRootElement.Properties.Count, _projectRootElement.Imports.Count);
+ MSBuildEventSource.Log.EvaluatePass1Stop(projectFile);
// Pass2: evaluate item definitions
// Don't box via IEnumerator and foreach; cache count so not to evaluate via interface each iteration
MSBuildEventSource.Log.EvaluatePass2Start(projectFile);
@@ -641,7 +641,7 @@ private void Evaluate()
}
}
}
- MSBuildEventSource.Log.EvaluatePass2Stop(projectFile, _itemDefinitionGroupElements.Count);
+ MSBuildEventSource.Log.EvaluatePass2Stop(projectFile);
LazyItemEvaluator
lazyEvaluator = null;
using (_evaluationProfiler.TrackPass(EvaluationPass.Items))
{
@@ -684,7 +684,7 @@ private void Evaluate()
lazyEvaluator = null;
}
- MSBuildEventSource.Log.EvaluatePass3Stop(projectFile, _itemGroupElements.Count);
+ MSBuildEventSource.Log.EvaluatePass3Stop(projectFile);
// Pass4: evaluate using-tasks
MSBuildEventSource.Log.EvaluatePass4Start(projectFile);
@@ -696,7 +696,7 @@ private void Evaluate()
}
}
- // If there was no DefaultTargets attribute found in the depth first pass,
+ // If there was no DefaultTargets attribute found in the depth first pass,
// use the name of the first target. If there isn't any target, don't error until build time.
if (_data.DefaultTargets == null)
@@ -714,7 +714,7 @@ private void Evaluate()
Dictionary> targetsWhichRunAfterByTarget = new Dictionary>(StringComparer.OrdinalIgnoreCase);
LinkedList activeTargetsByEvaluationOrder = new LinkedList();
Dictionary> activeTargets = new Dictionary>(StringComparer.OrdinalIgnoreCase);
- MSBuildEventSource.Log.EvaluatePass4Stop(projectFile, _usingTaskElements.Count);
+ MSBuildEventSource.Log.EvaluatePass4Stop(projectFile);
using (_evaluationProfiler.TrackPass(EvaluationPass.Targets))
{
@@ -748,7 +748,7 @@ private void Evaluate()
if (Traits.Instance.EscapeHatches.DebugEvaluation)
{
- // This is so important for VS performance it's worth always tracing; accidentally having
+ // This is so important for VS performance it's worth always tracing; accidentally having
// inconsistent sets of global properties will cause reevaluations, which are wasteful and incorrect
if (_projectRootElement.Count > 0) // VB/C# will new up empty projects; they aren't worth recording
{
@@ -773,7 +773,7 @@ private void Evaluate()
}
_data.FinishEvaluation();
- MSBuildEventSource.Log.EvaluatePass5Stop(projectFile, targetElementsCount);
+ MSBuildEventSource.Log.EvaluatePass5Stop(projectFile);
}
}
@@ -1002,7 +1002,7 @@ private void EvaluateUsingTaskElement(string directoryOfImportingFile, ProjectUs
///
private void ReadTargetElement(ProjectTargetElement targetElement, LinkedList activeTargetsByEvaluationOrder, Dictionary> activeTargets)
{
- // If we already have read a target instance for this element, use that.
+ // If we already have read a target instance for this element, use that.
ProjectTargetInstance targetInstance = targetElement.TargetInstance ?? ReadNewTargetElement(targetElement, _projectSupportsReturnsAttribute[(ProjectRootElement)targetElement.Parent], _evaluationProfiler);
string targetName = targetElement.Name;
@@ -1091,6 +1091,8 @@ private void ValidateChangeWaveState()
}
}
+ private static readonly string CachedFileVersion = ProjectCollection.Version.ToString();
+
///
/// Set the built-in properties, most of which are read-only
///
@@ -1106,6 +1108,8 @@ private void AddBuiltInProperties()
SetBuiltInProperty(ReservedPropertyNames.programFiles32, FrameworkLocationHelper.programFiles32);
SetBuiltInProperty(ReservedPropertyNames.assemblyVersion, Constants.AssemblyVersion);
SetBuiltInProperty(ReservedPropertyNames.version, MSBuildAssemblyFileVersion.Instance.MajorMinorBuild);
+ SetBuiltInProperty(ReservedPropertyNames.fileVersion, CachedFileVersion);
+ SetBuiltInProperty(ReservedPropertyNames.semanticVersion, ProjectCollection.DisplayVersion);
ValidateChangeWaveState();
@@ -1192,9 +1196,9 @@ private void AddToolsetProperties()
}
else
{
- // Make the subtoolset version itself available as a property -- but only if it's not already set.
+ // Make the subtoolset version itself available as a property -- but only if it's not already set.
// Because some people may be depending on this value even if there isn't a matching sub-toolset,
- // set the property even if there is no matching sub-toolset.
+ // set the property even if there is no matching sub-toolset.
if (!_data.Properties.Contains(Constants.SubToolsetVersionPropertyName))
{
_data.SetProperty(Constants.SubToolsetVersionPropertyName, _data.SubToolsetVersion, false /* NOT global property */, false /* may NOT be a reserved name */);
@@ -1249,8 +1253,8 @@ private void EvaluatePropertyElement(ProjectPropertyElement propertyElement)
using (_evaluationProfiler.TrackElement(propertyElement))
{
// Global properties cannot be overridden. We silently ignore them if we try. Legacy behavior.
- // That is, unless this global property has been explicitly labeled as one that we want to treat as overridable for the duration
- // of this project (or import).
+ // That is, unless this global property has been explicitly labeled as one that we want to treat as overridable for the duration
+ // of this project (or import).
if (
((IDictionary)_data.GlobalPropertiesDictionary).ContainsKey(propertyElement.Name) &&
!_data.GlobalPropertiesToTreatAsLocal.Contains(propertyElement.Name)
@@ -1781,7 +1785,16 @@ static string EvaluateProperty(string value, IElementLocation location,
}
// Combine SDK path with the "project" relative path
- sdkResult = _sdkResolverService.ResolveSdk(_submissionId, sdkReference, _evaluationLoggingContext, importElement.Location, solutionPath, projectPath, _interactive, _isRunningInVisualStudio);
+ try
+ {
+ sdkResult = _sdkResolverService.ResolveSdk(_submissionId, sdkReference, _evaluationLoggingContext, importElement.Location, solutionPath, projectPath, _interactive, _isRunningInVisualStudio);
+ }
+ catch (SdkResolverException e)
+ {
+ // We throw using e.Message because e.Message already contains the stack trace
+ // https://github.com/dotnet/msbuild/pull/6763
+ ProjectErrorUtilities.ThrowInvalidProject(importElement.SdkLocation, "SDKResolverCriticalFailure", e.Message);
+ }
if (!sdkResult.Success)
{
@@ -1926,8 +1939,6 @@ ProjectRootElement InnerCreate(string _, ProjectRootElementCacheBase __)
}
}
- _projectRootElementCache.AddEntry(project);
-
return project;
}
@@ -2278,8 +2289,8 @@ private LoadImportsResult ExpandAndLoadImportsFromUnescapedImportExpression(stri
}
}
- // Because these expressions will never be expanded again, we
- // can store the unescaped value. The only purpose of escaping is to
+ // Because these expressions will never be expanded again, we
+ // can store the unescaped value. The only purpose of escaping is to
// avoid undesired splitting or expansion.
_importsSeen.Add(importFileUnescaped, importElement);
}
diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs
index 80ddea0b0c2..fe397e4469a 100644
--- a/src/Build/Evaluation/Expander.cs
+++ b/src/Build/Evaluation/Expander.cs
@@ -610,7 +610,7 @@ private static bool IsValidPropertyName(string propertyName)
///
private static bool IsTruncationEnabled(ExpanderOptions options)
{
- return (options & ExpanderOptions.Truncate) != 0 && !Traits.Instance.EscapeHatches.DoNotTruncateConditions && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_8);
+ return (options & ExpanderOptions.Truncate) != 0 && !Traits.Instance.EscapeHatches.DoNotTruncateConditions;
}
///
@@ -1759,7 +1759,7 @@ internal static ExpressionShredder.ItemExpressionCapture ExpandSingleItemVectorE
}
List matches;
- if (s_invariantCompareInfo.IndexOf(expression, '@') == -1)
+ if (expression.IndexOf('@') == -1)
{
return null;
}
@@ -2539,7 +2539,7 @@ internal static IEnumerable> Metadata(Expander
expander, I
{
// It may be that the itemspec has unescaped ';'s in it so we need to split here to handle
// that case.
- if (s_invariantCompareInfo.IndexOf(metadataValue, ';') >= 0)
+ if (metadataValue.IndexOf(';') >= 0)
{
var splits = ExpressionShredder.SplitSemiColonSeparatedList(metadataValue);
@@ -3352,6 +3352,12 @@ internal object Execute(object objectInstance, IPropertyProvider properties,
}
else
{
+ // Check that the function that we're going to call is valid to call
+ if (!IsInstanceMethodAvailable(_methodMethodName))
+ {
+ ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionMethodUnavailable", _methodMethodName, _receiverType.FullName);
+ }
+
_bindingFlags |= BindingFlags.Instance;
// The object that we're about to call methods on may have escaped characters
@@ -5017,6 +5023,19 @@ private static bool IsStaticMethodAvailable(Type receiverType, string methodName
return AvailableStaticMethods.GetTypeInformationFromTypeCache(receiverType.FullName, methodName) != null;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsInstanceMethodAvailable(string methodName)
+ {
+ if (Traits.Instance.EnableAllPropertyFunctions)
+ {
+ // anything goes
+ return true;
+ }
+
+ // This could be expanded to an allow / deny list.
+ return methodName != "GetType";
+ }
+
///
/// Construct and instance of objectType based on the constructor or method arguments provided.
/// Arguments must never be null.
diff --git a/src/Build/Evaluation/ExpressionShredder.cs b/src/Build/Evaluation/ExpressionShredder.cs
index aa5f8b40442..ffa04158d9d 100644
--- a/src/Build/Evaluation/ExpressionShredder.cs
+++ b/src/Build/Evaluation/ExpressionShredder.cs
@@ -65,7 +65,7 @@ internal static SemiColonTokenizer SplitSemiColonSeparatedList(string expression
/// where metadata key is like "itemname.metadataname" or "metadataname".
/// PERF: Tables are null if there are no entries, because this is quite a common case.
///
- internal static ItemsAndMetadataPair GetReferencedItemNamesAndMetadata(List expressions)
+ internal static ItemsAndMetadataPair GetReferencedItemNamesAndMetadata(IEnumerable expressions)
{
ItemsAndMetadataPair pair = new ItemsAndMetadataPair(null, null);
diff --git a/src/Build/Evaluation/ItemDataCollectionValue.cs b/src/Build/Evaluation/ItemDataCollectionValue.cs
new file mode 100644
index 00000000000..15807cc996d
--- /dev/null
+++ b/src/Build/Evaluation/ItemDataCollectionValue.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Build.Evaluation
+{
+ ///
+ /// An efficient multi-value wrapper holding one or more items.
+ ///
+ internal struct ItemDataCollectionValue
+ {
+ ///
+ /// A non-allocating enumerator for the multi-value.
+ ///
+ public struct Enumerator : IEnumerator
+ {
+ private object _value;
+ private int _index;
+
+ public Enumerator(object value)
+ {
+ _value = value;
+ _index = -1;
+ }
+
+ public I Current => (_value is IList list) ? list[_index] : (I)_value;
+ object System.Collections.IEnumerator.Current => Current;
+
+ public void Dispose()
+ { }
+
+ public bool MoveNext()
+ {
+ // If value is not a list, it is either null or a single item.
+ int count = (_value is IList list) ? list.Count : (_value is null ? 0 : 1);
+ if (_index + 1 < count)
+ {
+ _index++;
+ return true;
+ }
+ return false;
+ }
+
+ public void Reset()
+ {
+ _index = -1;
+ }
+ }
+
+ ///
+ /// Holds one value or a list of values.
+ ///
+ private object _value;
+
+ public bool IsEmpty => _value == null || (_value is List list && list.Count == 0);
+
+ public ItemDataCollectionValue(I item)
+ {
+ _value = item;
+ }
+
+ public void Add(I item)
+ {
+ if (_value is null)
+ {
+ _value = item;
+ }
+ else
+ {
+ if (_value is not List list)
+ {
+ list = new List()
+ {
+ (I)_value
+ };
+ _value = list;
+ }
+ list.Add(item);
+ }
+ }
+
+ public void Delete(I item)
+ {
+ if (_value is List list)
+ {
+ list.Remove(item);
+ }
+ else if (object.Equals(_value, item))
+ {
+ _value = null;
+ }
+ }
+
+ public void Replace(I oldItem, I newItem)
+ {
+ if (_value is List list)
+ {
+ int index = list.IndexOf(oldItem);
+ if (index >= 0)
+ {
+ list[index] = newItem;
+ }
+ }
+ else if (object.Equals(_value, oldItem))
+ {
+ _value = newItem;
+ }
+ }
+
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(_value);
+ }
+ }
+}
diff --git a/src/Build/Evaluation/ItemSpec.cs b/src/Build/Evaluation/ItemSpec.cs
index 7a1cb4db89b..fbc0b6f6b1a 100644
--- a/src/Build/Evaluation/ItemSpec.cs
+++ b/src/Build/Evaluation/ItemSpec.cs
@@ -85,6 +85,11 @@ public override bool IsMatch(string itemToMatch)
return ReferencedItems.Any(v => v.ItemAsValueFragment.IsMatch(itemToMatch));
}
+ public override IEnumerable GetReferencedItems()
+ {
+ return ReferencedItems.Select(v => EscapingUtilities.UnescapeAll(v.ItemAsValueFragment.TextFragment));
+ }
+
public override IMSBuildGlob ToMSBuildGlob()
{
return MsBuildGlob;
@@ -316,6 +321,48 @@ public IEnumerable FragmentsMatchingItem(string itemToMatch, o
return result;
}
+ ///
+ /// Returns a list of normalized paths that are common between this itemspec and keys of the given dictionary.
+ ///
+ /// The dictionary to match this itemspec against.
+ /// The keys of that are also referenced by this itemspec.
+ public IList IntersectsWith(IReadOnlyDictionary> itemsByNormalizedValue)
+ {
+ IList matches = null;
+
+ foreach (var fragment in Fragments)
+ {
+ IEnumerable referencedItems = fragment.GetReferencedItems();
+ if (referencedItems != null)
+ {
+ // The fragment can enumerate its referenced items, we can do dictionary lookups.
+ foreach (var spec in referencedItems)
+ {
+ string key = FileUtilities.NormalizePathForComparisonNoThrow(spec, fragment.ProjectDirectory);
+ if (itemsByNormalizedValue.TryGetValue(key, out var multiValue))
+ {
+ matches ??= new List();
+ matches.Add(key);
+ }
+ }
+ }
+ else
+ {
+ // The fragment cannot enumerate its referenced items. Iterate over the dictionary and test each item.
+ foreach (var kvp in itemsByNormalizedValue)
+ {
+ if (fragment.IsMatchNormalized(kvp.Key))
+ {
+ matches ??= new List();
+ matches.Add(kvp.Key);
+ }
+ }
+ }
+ }
+
+ return matches ?? Array.Empty();
+ }
+
///
/// Return an MSBuildGlob that represents this ItemSpec.
///
@@ -415,6 +462,16 @@ public virtual bool IsMatch(string itemToMatch)
return FileMatcher.IsMatch(itemToMatch);
}
+ public virtual bool IsMatchNormalized(string normalizedItemToMatch)
+ {
+ return FileMatcher.IsMatchNormalized(normalizedItemToMatch);
+ }
+
+ public virtual IEnumerable GetReferencedItems()
+ {
+ return Enumerable.Repeat(EscapingUtilities.UnescapeAll(TextFragment), 1);
+ }
+
public virtual IMSBuildGlob ToMSBuildGlob()
{
return MsBuildGlob;
@@ -446,6 +503,12 @@ public GlobFragment(string textFragment, string projectDirectory)
{
}
+ public override IEnumerable GetReferencedItems()
+ {
+ // This fragment cannot efficiently enumerate its referenced items.
+ return null;
+ }
+
///
/// True if TextFragment starts with /**/ or a variation thereof with backslashes.
///
@@ -462,7 +525,7 @@ public GlobFragment(string textFragment, string projectDirectory)
/// on multiple metadata. If one item specifies NotTargetFramework to be net46 and TargetFramework to
/// be netcoreapp3.1, we wouldn't want to match that to an item with TargetFramework 46 and
/// NotTargetFramework netcoreapp3.1.
- ///
+ ///
/// Implementing this as a list of sets where each metadatum key has its own set also would not work
/// because different items could match on different metadata, and we want to check to see if any
/// single item matches on all the metadata. As an example, consider this scenario:
@@ -474,10 +537,10 @@ public GlobFragment(string textFragment, string projectDirectory)
/// should match none of them because Forgind doesn't match all three metadata of any of the items.
/// With a list of sets, Forgind would match Baby on BadAt, Child on GoodAt, and Adolescent on OkAt,
/// and Forgind would be erroneously removed.
- ///
+ ///
/// With a Trie as below, Items specify paths in the tree, so going to any child node eliminates all
/// items that don't share that metadatum. This ensures the match is proper.
- ///
+ ///
/// Todo: Tries naturally can have different shapes depending on in what order the metadata are considered.
/// Specifically, if all the items share a single metadata value for the one metadatum and have different
/// values for a second metadatum, it will have only one node more than the number of items if the first
diff --git a/src/Build/Evaluation/LazyItemEvaluator.IItemOperation.cs b/src/Build/Evaluation/LazyItemEvaluator.IItemOperation.cs
index 66f927b4025..40ee16b1d88 100644
--- a/src/Build/Evaluation/LazyItemEvaluator.IItemOperation.cs
+++ b/src/Build/Evaluation/LazyItemEvaluator.IItemOperation.cs
@@ -9,7 +9,7 @@ internal partial class LazyItemEvaluator
{
internal interface IItemOperation
{
- void Apply(ImmutableList.Builder listBuilder, ImmutableHashSet globsToIgnore);
+ void Apply(OrderedItemDataCollection.Builder listBuilder, ImmutableHashSet globsToIgnore);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Build/Evaluation/LazyItemEvaluator.IncludeOperation.cs b/src/Build/Evaluation/LazyItemEvaluator.IncludeOperation.cs
index 233156392bd..da5f61449b6 100644
--- a/src/Build/Evaluation/LazyItemEvaluator.IncludeOperation.cs
+++ b/src/Build/Evaluation/LazyItemEvaluator.IncludeOperation.cs
@@ -6,6 +6,7 @@
using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
+using Microsoft.CodeAnalysis.Collections;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -21,7 +22,7 @@ class IncludeOperation : LazyItemOperation
readonly string _rootDirectory;
- readonly ImmutableList _excludes;
+ readonly ImmutableSegmentedList _excludes;
readonly ImmutableList _metadata;
@@ -35,7 +36,7 @@ public IncludeOperation(IncludeOperationBuilder builder, LazyItemEvaluator
SelectItems(ImmutableList.Builder listBuilder, ImmutableHashSet globsToIgnore)
+ protected override ImmutableList SelectItems(OrderedItemDataCollection.Builder listBuilder, ImmutableHashSet globsToIgnore)
{
var itemsToAdd = ImmutableList.CreateBuilder();
@@ -92,7 +93,7 @@ protected override ImmutableList SelectItems(ImmutableList.Builder
{
// If this item is behind a false condition and represents a full drive/filesystem scan, expanding it is
// almost certainly undesired. It should be skipped to avoid evaluation taking an excessive amount of time.
- bool skipGlob = !_conditionResult && globFragment.IsFullFileSystemScan && !Traits.Instance.EscapeHatches.AlwaysEvaluateDangerousGlobs && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_8);
+ bool skipGlob = !_conditionResult && globFragment.IsFullFileSystemScan && !Traits.Instance.EscapeHatches.AlwaysEvaluateDangerousGlobs;
if (!skipGlob)
{
string glob = globFragment.TextFragment;
@@ -153,7 +154,7 @@ protected override void MutateItems(ImmutableList items)
DecorateItemsWithMetadata(items.Select(i => new ItemBatchingContext(i)), _metadata);
}
- protected override void SaveItems(ImmutableList items, ImmutableList.Builder listBuilder)
+ protected override void SaveItems(ImmutableList items, OrderedItemDataCollection.Builder listBuilder)
{
foreach (var item in items)
{
@@ -167,7 +168,7 @@ class IncludeOperationBuilder : OperationBuilderWithMetadata
public int ElementOrder { get; set; }
public string RootDirectory { get; set; }
- public ImmutableList.Builder Excludes { get; } = ImmutableList.CreateBuilder();
+ public ImmutableSegmentedList.Builder Excludes { get; } = ImmutableSegmentedList.CreateBuilder();
public IncludeOperationBuilder(ProjectItemElement itemElement, bool conditionResult) : base(itemElement, conditionResult)
{
diff --git a/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs b/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs
index b072a36f854..288d11ce9b9 100644
--- a/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs
+++ b/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs
@@ -51,7 +51,7 @@ protected LazyItemOperation(OperationBuilder builder, LazyItemEvaluator
_lazyEvaluator.EngineFileUtilities;
- public void Apply(ImmutableList.Builder listBuilder, ImmutableHashSet globsToIgnore)
+ public void Apply(OrderedItemDataCollection.Builder listBuilder, ImmutableHashSet globsToIgnore)
{
MSBuildEventSource.Log.ApplyLazyItemOperationsStart(_itemElement.ItemType);
using (_lazyEvaluator._evaluationProfiler.TrackElement(_itemElement))
@@ -61,7 +61,7 @@ public void Apply(ImmutableList.Builder listBuilder, ImmutableHashSet<
MSBuildEventSource.Log.ApplyLazyItemOperationsStop(_itemElement.ItemType);
}
- protected virtual void ApplyImpl(ImmutableList.Builder listBuilder, ImmutableHashSet globsToIgnore)
+ protected virtual void ApplyImpl(OrderedItemDataCollection.Builder listBuilder, ImmutableHashSet globsToIgnore)
{
var items = SelectItems(listBuilder, globsToIgnore);
MutateItems(items);
@@ -71,7 +71,7 @@ protected virtual void ApplyImpl(ImmutableList.Builder listBuilder, Im
///
/// Produce the items to operate on. For example, create new ones or select existing ones
///
- protected virtual ImmutableList SelectItems(ImmutableList.Builder listBuilder, ImmutableHashSet globsToIgnore)
+ protected virtual ImmutableList SelectItems(OrderedItemDataCollection.Builder listBuilder, ImmutableHashSet globsToIgnore)
{
return listBuilder.Select(itemData => itemData.Item)
.ToImmutableList();
@@ -80,7 +80,7 @@ protected virtual ImmutableList SelectItems(ImmutableList.Builder l
// todo Refactoring: MutateItems should clone each item before mutation. See https://github.com/Microsoft/msbuild/issues/2328
protected virtual void MutateItems(ImmutableList items) { }
- protected virtual void SaveItems(ImmutableList items, ImmutableList.Builder listBuilder) { }
+ protected virtual void SaveItems(ImmutableList items, OrderedItemDataCollection.Builder listBuilder) { }
private IList GetReferencedItems(string itemType, ImmutableHashSet globsToIgnore)
{
@@ -230,7 +230,6 @@ protected void DecorateItemsWithMetadata(IEnumerable itemBa
// End of legal area for metadata expressions.
_expander.Metadata = null;
}
-
// End of pseudo batching
////////////////////////////////////////////////////
// Start of old code
@@ -283,17 +282,18 @@ protected void DecorateItemsWithMetadata(IEnumerable itemBa
}
}
- protected bool NeedToExpandMetadataForEachItem(ImmutableList metadata, out ItemsAndMetadataPair itemsAndMetadataFound)
+ private static IEnumerable GetMetadataValuesAndConditions(ImmutableList metadata)
{
- List values = new List(metadata.Count * 2);
-
foreach (var metadataElement in metadata)
{
- values.Add(metadataElement.Value);
- values.Add(metadataElement.Condition);
+ yield return metadataElement.Value;
+ yield return metadataElement.Condition;
}
+ }
- itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(values);
+ protected bool NeedToExpandMetadataForEachItem(ImmutableList metadata, out ItemsAndMetadataPair itemsAndMetadataFound)
+ {
+ itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(GetMetadataValuesAndConditions(metadata));
bool needToExpandMetadataForEachItem = false;
diff --git a/src/Build/Evaluation/LazyItemEvaluator.OrderedItemDataCollection.cs b/src/Build/Evaluation/LazyItemEvaluator.OrderedItemDataCollection.cs
new file mode 100644
index 00000000000..e56334b81a7
--- /dev/null
+++ b/src/Build/Evaluation/LazyItemEvaluator.OrderedItemDataCollection.cs
@@ -0,0 +1,218 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+
+namespace Microsoft.Build.Evaluation
+{
+ internal partial class LazyItemEvaluator
+ {
+ ///
+ /// A collection of ItemData that maintains insertion order and internally optimizes some access patterns, e.g. bulk removal
+ /// based on normalized item values.
+ ///
+ internal sealed class OrderedItemDataCollection
+ {
+ #region Inner types
+
+ ///
+ /// A mutable and enumerable version of .
+ ///
+ internal sealed class Builder : IEnumerable
+ {
+ ///
+ /// The list of items in the collection. Defines the enumeration order.
+ ///
+ private ImmutableList.Builder _listBuilder;
+
+ ///
+ /// A dictionary of items keyed by their normalized value.
+ ///
+ private Dictionary> _dictionaryBuilder;
+
+ internal Builder(ImmutableList.Builder listBuilder)
+ {
+ _listBuilder = listBuilder;
+ }
+
+ #region IEnumerable implementation
+
+ ImmutableList.Enumerator GetEnumerator() => _listBuilder.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => _listBuilder.GetEnumerator();
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _listBuilder.GetEnumerator();
+
+ #endregion
+
+ public int Count => _listBuilder.Count;
+
+ public ItemData this[int index]
+ {
+ get { return _listBuilder[index]; }
+ set
+ {
+ // Update the dictionary if it exists.
+ if (_dictionaryBuilder is not null)
+ {
+ ItemData oldItemData = _listBuilder[index];
+ string oldNormalizedValue = oldItemData.NormalizedItemValue;
+ string newNormalizedValue = value.NormalizedItemValue;
+ if (!string.Equals(oldNormalizedValue, newNormalizedValue, StringComparison.OrdinalIgnoreCase))
+ {
+ // Normalized values are different - delete from the old entry and add to the new entry.
+ ItemDataCollectionValue oldDictionaryEntry = _dictionaryBuilder[oldNormalizedValue];
+ oldDictionaryEntry.Delete(oldItemData.Item);
+ if (oldDictionaryEntry.IsEmpty)
+ {
+ _dictionaryBuilder.Remove(oldNormalizedValue);
+ }
+ else
+ {
+ _dictionaryBuilder[oldNormalizedValue] = oldDictionaryEntry;
+ }
+
+ ItemDataCollectionValue newDictionaryEntry = _dictionaryBuilder[newNormalizedValue];
+ newDictionaryEntry.Add(value.Item);
+ _dictionaryBuilder[newNormalizedValue] = newDictionaryEntry;
+
+ }
+ else
+ {
+ // Normalized values are the same - replace the item in the entry.
+ ItemDataCollectionValue dictionaryEntry = _dictionaryBuilder[newNormalizedValue];
+ dictionaryEntry.Replace(oldItemData.Item, value.Item);
+ _dictionaryBuilder[newNormalizedValue] = dictionaryEntry;
+ }
+ }
+ _listBuilder[index] = value;
+ }
+ }
+
+ ///
+ /// Gets or creates a dictionary keyed by normalized values.
+ ///
+ public Dictionary> Dictionary
+ {
+ get
+ {
+ if (_dictionaryBuilder == null)
+ {
+ _dictionaryBuilder = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+ for (int i = 0; i < _listBuilder.Count; i++)
+ {
+ ItemData itemData = _listBuilder[i];
+ AddToDictionary(ref itemData);
+ _listBuilder[i] = itemData;
+ }
+ }
+ return _dictionaryBuilder;
+ }
+ }
+
+ public void Add(ItemData data)
+ {
+ if (_dictionaryBuilder is not null)
+ {
+ AddToDictionary(ref data);
+ }
+ _listBuilder.Add(data);
+ }
+
+ public void Clear()
+ {
+ _listBuilder.Clear();
+ _dictionaryBuilder?.Clear();
+ }
+
+ ///
+ /// Removes all items passed in a collection.
+ ///
+ public void RemoveAll(ICollection itemsToRemove)
+ {
+ _listBuilder.RemoveAll(item => itemsToRemove.Contains(item.Item));
+ // This is a rare operation, don't bother updating the dictionary for now. It will be recreated as needed.
+ _dictionaryBuilder = null;
+ }
+
+ ///
+ /// Removes all items whose normalized path is passed in a collection.
+ ///
+ public void RemoveAll(ICollection itemPathsToRemove)
+ {
+ var dictionary = Dictionary;
+ HashSet itemsToRemove = null;
+ foreach (string itemValue in itemPathsToRemove)
+ {
+ if (dictionary.TryGetValue(itemValue, out var multiItem))
+ {
+ foreach (I item in multiItem)
+ {
+ itemsToRemove ??= new HashSet();
+ itemsToRemove.Add(item);
+ }
+ _dictionaryBuilder.Remove(itemValue);
+ }
+ }
+
+ if (itemsToRemove is not null)
+ {
+ _listBuilder.RemoveAll(item => itemsToRemove.Contains(item.Item));
+ }
+ }
+
+ ///
+ /// Creates an immutable view of this collection.
+ ///
+ public OrderedItemDataCollection ToImmutable()
+ {
+ return new OrderedItemDataCollection(_listBuilder.ToImmutable());
+ }
+
+ private void AddToDictionary(ref ItemData itemData)
+ {
+ string key = itemData.NormalizedItemValue;
+
+ if (!_dictionaryBuilder.TryGetValue(key, out var dictionaryValue))
+ {
+ dictionaryValue = new ItemDataCollectionValue(itemData.Item);
+ }
+ else
+ {
+ dictionaryValue.Add(itemData.Item);
+ }
+ _dictionaryBuilder[key] = dictionaryValue;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// The list of items in the collection. Defines the enumeration order.
+ ///
+ private ImmutableList _list;
+
+ private OrderedItemDataCollection(ImmutableList list)
+ {
+ _list = list;
+ }
+
+ ///
+ /// Creates a new mutable collection.
+ ///
+ public static Builder CreateBuilder()
+ {
+ return new Builder(ImmutableList.CreateBuilder());
+ }
+
+ ///
+ /// Creates a mutable view of this collection. Changes made to the returned builder are not reflected in this collection.
+ ///
+ public Builder ToBuilder()
+ {
+ return new Builder(_list.ToBuilder());
+ }
+ }
+ }
+}
diff --git a/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs b/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs
index 5a2d19ad7b2..1db3ddfd3ce 100644
--- a/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs
+++ b/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs
@@ -3,6 +3,8 @@
using Microsoft.Build.Construction;
using Microsoft.Build.Shared;
+using Microsoft.Build.Utilities;
+using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
@@ -35,33 +37,52 @@ public RemoveOperation(RemoveOperationBuilder builder, LazyItemEvaluator
///
- /// This operation is mostly implemented in terms of the default .
- /// This override exists to apply the removing-everything short-circuit.
+ /// This override exists to apply the removing-everything short-circuit and to avoid creating a redundant list of items to remove.
///
- protected override void ApplyImpl(ImmutableList.Builder listBuilder, ImmutableHashSet globsToIgnore)
+ protected override void ApplyImpl(OrderedItemDataCollection.Builder listBuilder, ImmutableHashSet globsToIgnore)
{
- if (_matchOnMetadata.IsEmpty && ItemspecContainsASingleBareItemReference(_itemSpec, _itemElement.ItemType) && _conditionResult)
+ if (!_conditionResult)
{
- // Perf optimization: If the Remove operation references itself (e.g. )
- // then all items are removed and matching is not necessary
- listBuilder.Clear();
return;
}
- base.ApplyImpl(listBuilder, globsToIgnore);
- }
+ bool matchingOnMetadata = !_matchOnMetadata.IsEmpty;
+ if (!matchingOnMetadata)
+ {
+ if (ItemspecContainsASingleBareItemReference(_itemSpec, _itemElement.ItemType))
+ {
+ // Perf optimization: If the Remove operation references itself (e.g. )
+ // then all items are removed and matching is not necessary
+ listBuilder.Clear();
+ return;
+ }
+
+ if (listBuilder.Count >= Traits.Instance.DictionaryBasedItemRemoveThreshold)
+ {
+ // Perf optimization: If the number of items in the running list is large, construct a dictionary,
+ // enumerate all items referenced by the item spec, and perform dictionary look-ups to find items
+ // to remove.
+ IList matches = _itemSpec.IntersectsWith(listBuilder.Dictionary);
+ listBuilder.RemoveAll(matches);
+ return;
+ }
+ }
- // todo Perf: do not match against the globs: https://github.com/Microsoft/msbuild/issues/2329
- protected override ImmutableList SelectItems(ImmutableList.Builder listBuilder, ImmutableHashSet globsToIgnore)
- {
- var items = ImmutableHashSet.CreateBuilder();
+ // todo Perf: do not match against the globs: https://github.com/Microsoft/msbuild/issues/2329
+ HashSet items = null;
foreach (ItemData item in listBuilder)
{
- if (_matchOnMetadata.IsEmpty ? _itemSpec.MatchesItem(item.Item) : MatchesItemOnMetadata(item.Item))
+ bool isMatch = matchingOnMetadata ? MatchesItemOnMetadata(item.Item) : _itemSpec.MatchesItem(item.Item);
+ if (isMatch)
+ {
+ items ??= new HashSet();
items.Add(item.Item);
+ }
+ }
+ if (items is not null)
+ {
+ listBuilder.RemoveAll(items);
}
-
- return items.ToImmutableList();
}
private bool MatchesItemOnMetadata(I item)
@@ -69,16 +90,6 @@ private bool MatchesItemOnMetadata(I item)
return _metadataSet.Contains(_matchOnMetadata.Select(m => item.GetMetadataValue(m)));
}
- protected override void SaveItems(ImmutableList items, ImmutableList.Builder listBuilder)
- {
- if (!_conditionResult)
- {
- return;
- }
-
- listBuilder.RemoveAll(itemData => items.Contains(itemData.Item));
- }
-
public ImmutableHashSet.Builder GetRemovedGlobs()
{
var builder = ImmutableHashSet.CreateBuilder();
diff --git a/src/Build/Evaluation/LazyItemEvaluator.UpdateOperation.cs b/src/Build/Evaluation/LazyItemEvaluator.UpdateOperation.cs
index 5423bcf0286..35b2ffa899f 100644
--- a/src/Build/Evaluation/LazyItemEvaluator.UpdateOperation.cs
+++ b/src/Build/Evaluation/LazyItemEvaluator.UpdateOperation.cs
@@ -39,7 +39,7 @@ public MatchResult(bool isMatch, Dictionary capturedItemsFromReferenc
delegate MatchResult ItemSpecMatchesItem(ItemSpec
itemSpec, I itemToMatch);
- protected override void ApplyImpl(ImmutableList.Builder listBuilder, ImmutableHashSet globsToIgnore)
+ protected override void ApplyImpl(OrderedItemDataCollection.Builder listBuilder, ImmutableHashSet globsToIgnore)
{
if (!_conditionResult)
{
diff --git a/src/Build/Evaluation/LazyItemEvaluator.cs b/src/Build/Evaluation/LazyItemEvaluator.cs
index e5f2a72f61a..9fd3eec87e7 100644
--- a/src/Build/Evaluation/LazyItemEvaluator.cs
+++ b/src/Build/Evaluation/LazyItemEvaluator.cs
@@ -16,6 +16,7 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
+using System.Threading;
namespace Microsoft.Build.Evaluation
{
@@ -124,12 +125,13 @@ private static string GetCurrentDirectoryForConditionEvaluation(ProjectElement e
public struct ItemData
{
- public ItemData(I item, ProjectItemElement originatingItemElement, int elementOrder, bool conditionResult)
+ public ItemData(I item, ProjectItemElement originatingItemElement, int elementOrder, bool conditionResult, string normalizedItemValue = null)
{
Item = item;
OriginatingItemElement = originatingItemElement;
ElementOrder = elementOrder;
ConditionResult = conditionResult;
+ _normalizedItemValue = normalizedItemValue;
}
public ItemData Clone(IItemFactory itemFactory, ProjectItemElement initialItemElementForFactory)
@@ -140,19 +142,37 @@ public ItemData Clone(IItemFactory itemFactory, ProjectItemElement initial
var clonedItem = itemFactory.CreateItem(Item, OriginatingItemElement.ContainingProject.FullPath);
itemFactory.ItemElement = initialItemElementForFactory;
- return new ItemData(clonedItem, OriginatingItemElement, ElementOrder, ConditionResult);
+ return new ItemData(clonedItem, OriginatingItemElement, ElementOrder, ConditionResult, _normalizedItemValue);
}
public I Item { get; }
public ProjectItemElement OriginatingItemElement { get; }
public int ElementOrder { get; }
public bool ConditionResult { get; }
+
+ ///
+ /// Lazily created normalized item value.
+ ///
+ private string _normalizedItemValue;
+ public string NormalizedItemValue
+ {
+ get
+ {
+ var normalizedItemValue = Volatile.Read(ref _normalizedItemValue);
+ if (normalizedItemValue == null)
+ {
+ normalizedItemValue = FileUtilities.NormalizePathForComparisonNoThrow(Item.EvaluatedInclude, Item.ProjectDirectory);
+ Volatile.Write(ref _normalizedItemValue, normalizedItemValue);
+ }
+ return normalizedItemValue;
+ }
+ }
}
private class MemoizedOperation : IItemOperation
{
public LazyItemOperation Operation { get; }
- private Dictionary, ImmutableList> _cache;
+ private Dictionary, OrderedItemDataCollection> _cache;
private bool _isReferenced;
#if DEBUG
@@ -164,7 +184,7 @@ public MemoizedOperation(LazyItemOperation operation)
Operation = operation;
}
- public void Apply(ImmutableList.Builder listBuilder, ImmutableHashSet globsToIgnore)
+ public void Apply(OrderedItemDataCollection.Builder listBuilder, ImmutableHashSet globsToIgnore)
{
#if DEBUG
CheckInvariant();
@@ -200,7 +220,7 @@ private void CheckInvariant()
}
#endif
- public bool TryGetFromCache(ISet globsToIgnore, out ImmutableList items)
+ public bool TryGetFromCache(ISet globsToIgnore, out OrderedItemDataCollection items)
{
if (_cache != null)
{
@@ -219,11 +239,11 @@ public void MarkAsReferenced()
_isReferenced = true;
}
- private void AddItemsToCache(ImmutableHashSet globsToIgnore, ImmutableList items)
+ private void AddItemsToCache(ImmutableHashSet globsToIgnore, OrderedItemDataCollection items)
{
if (_cache == null)
{
- _cache = new Dictionary, ImmutableList>();
+ _cache = new Dictionary, OrderedItemDataCollection>();
}
_cache[globsToIgnore] = items;
@@ -253,7 +273,7 @@ public ImmutableList GetMatchedItems(ImmutableHashSet globsToIgnore)
return items.ToImmutable();
}
- public ImmutableList.Builder GetItemData(ImmutableHashSet globsToIgnore)
+ public OrderedItemDataCollection.Builder GetItemData(ImmutableHashSet globsToIgnore)
{
// Cache results only on the LazyItemOperations whose results are required by an external caller (via GetItems). This means:
// - Callers of GetItems who have announced ahead of time that they would reference an operation (via MarkAsReferenced())
@@ -275,7 +295,7 @@ public ImmutableList.Builder GetItemData(ImmutableHashSet glob
// does not mutate: one can add operations on top, but the base never changes, and (ii) the globsToIgnore passed to the tail is the concatenation between
// the globsToIgnore received as an arg, and the globsToIgnore produced by the head (if the head is a Remove operation)
- ImmutableList items;
+ OrderedItemDataCollection items;
if (_memoizedOperation.TryGetFromCache(globsToIgnore, out items))
{
return items.ToBuilder();
@@ -299,12 +319,12 @@ public ImmutableList.Builder GetItemData(ImmutableHashSet glob
/// is to optimize the case in which as series of UpdateOperations, each of which affects a single ItemSpec, are applied to all
/// items in the list, leading to a quadratic-time operation.
///
- private static ImmutableList.Builder ComputeItems(LazyItemList lazyItemList, ImmutableHashSet globsToIgnore)
+ private static OrderedItemDataCollection.Builder ComputeItems(LazyItemList lazyItemList, ImmutableHashSet globsToIgnore)
{
// Stack of operations up to the first one that's cached (exclusive)
Stack itemListStack = new Stack();
- ImmutableList.Builder items = null;
+ OrderedItemDataCollection.Builder items = null;
// Keep a separate stack of lists of globs to ignore that only gets modified for Remove operations
Stack> globsToIgnoreStack = null;
@@ -313,7 +333,7 @@ private static ImmutableList.Builder ComputeItems(LazyItemList lazyIte
{
var globsToIgnoreFromFutureOperations = globsToIgnoreStack?.Peek() ?? globsToIgnore;
- ImmutableList itemsFromCache;
+ OrderedItemDataCollection itemsFromCache;
if (currentList._memoizedOperation.TryGetFromCache(globsToIgnoreFromFutureOperations, out itemsFromCache))
{
// the base items on top of which to apply the uncached operations are the items of the first operation that is cached
@@ -341,7 +361,7 @@ private static ImmutableList.Builder ComputeItems(LazyItemList lazyIte
if (items == null)
{
- items = ImmutableList.CreateBuilder();
+ items = OrderedItemDataCollection.CreateBuilder();
}
ImmutableHashSet currentGlobsToIgnore = globsToIgnoreStack == null ? globsToIgnore : globsToIgnoreStack.Peek();
@@ -419,7 +439,7 @@ private static ImmutableList.Builder ComputeItems(LazyItemList lazyIte
return items;
}
- private static void ProcessNonWildCardItemUpdates(Dictionary itemsWithNoWildcards, ImmutableList.Builder items)
+ private static void ProcessNonWildCardItemUpdates(Dictionary itemsWithNoWildcards, OrderedItemDataCollection.Builder items)
{
#if DEBUG
ErrorUtilities.VerifyThrow(itemsWithNoWildcards.All(fragment => !MSBuildConstants.CharactersForExpansion.Any(fragment.Key.Contains)), $"{nameof(itemsWithNoWildcards)} should not contain any text fragments with wildcards.");
@@ -612,38 +632,36 @@ private void ProcessItemSpec(string rootDirectory, string itemSpec, IElementLoca
}
}
+ private static IEnumerable GetExpandedMetadataValuesAndConditions(ICollection metadata, Expander
expander)
+ {
+ // 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.
+ // See: https://github.com/Microsoft/msbuild/issues/3460
+ const ExpanderOptions expanderOptions = ExpanderOptions.ExpandProperties | ExpanderOptions.LeavePropertiesUnexpandedOnError;
+
+ // Expand properties here, because a property may have a value which is an item reference (ie "@(Bar)"), and
+ // if so we need to add the right item reference.
+ foreach (var metadatumElement in metadata)
+ {
+ yield return expander.ExpandIntoStringLeaveEscaped(
+ metadatumElement.Value,
+ expanderOptions,
+ metadatumElement.Location);
+
+ yield return expander.ExpandIntoStringLeaveEscaped(
+ metadatumElement.Condition,
+ expanderOptions,
+ metadatumElement.ConditionLocation);
+ }
+ }
+
private void ProcessMetadataElements(ProjectItemElement itemElement, OperationBuilderWithMetadata operationBuilder)
{
if (itemElement.HasMetadata)
{
operationBuilder.Metadata.AddRange(itemElement.Metadata);
- var values = new List(itemElement.Metadata.Count * 2);
-
- // Expand properties here, because a property may have a value which is an item reference (ie "@(Bar)"), and
- // if so we need to add the right item reference.
- foreach (var metadatumElement in itemElement.Metadata)
- {
- // 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.
- // See: https://github.com/Microsoft/msbuild/issues/3460
- const ExpanderOptions expanderOptions = ExpanderOptions.ExpandProperties | ExpanderOptions.LeavePropertiesUnexpandedOnError;
-
- var valueWithPropertiesExpanded = _expander.ExpandIntoStringLeaveEscaped(
- metadatumElement.Value,
- expanderOptions,
- metadatumElement.Location);
-
- var conditionWithPropertiesExpanded = _expander.ExpandIntoStringLeaveEscaped(
- metadatumElement.Condition,
- expanderOptions,
- metadatumElement.ConditionLocation);
-
- values.Add(valueWithPropertiesExpanded);
- values.Add(conditionWithPropertiesExpanded);
- }
-
- var itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(values);
+ var itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(GetExpandedMetadataValuesAndConditions(itemElement.Metadata, _expander));
if (itemsAndMetadataFound.Items != null)
{
foreach (var itemType in itemsAndMetadataFound.Items)
diff --git a/src/Build/Evaluation/ProjectRootElementCache.cs b/src/Build/Evaluation/ProjectRootElementCache.cs
index b75de665c00..a526ce4540b 100644
--- a/src/Build/Evaluation/ProjectRootElementCache.cs
+++ b/src/Build/Evaluation/ProjectRootElementCache.cs
@@ -21,37 +21,37 @@ namespace Microsoft.Build.Evaluation
/// Maintains a cache of all loaded ProjectRootElement's for design time purposes.
/// Weak references are held to add added ProjectRootElement's.
/// Strong references are held to a limited number of added ProjectRootElement's.
- ///
+ ///
/// 1. Loads of a ProjectRootElement will share any existing loaded ProjectRootElement, rather
/// than loading and parsing a new one. This is the case whether the ProjectRootElement
/// is loaded directly or imported.
- ///
+ ///
/// 2. For design time, only a weak reference needs to be held, because all users have a strong reference.
- ///
+ ///
/// 3. Because all loads of a ProjectRootElement consult this cache, they can be assured that any
/// entries in this cache are up to date. For example, if a ProjectRootElement is modified and saved,
/// the cached ProjectRootElement will be the loaded one that was saved, so it will be up to date.
- ///
+ ///
/// 4. If, after a project has been loaded, an external app changes the project file content on disk, it is
/// important that a subsequent load of that project does not return stale ProjectRootElement. To avoid this, the
/// timestamp of the file on disk is compared to the timestamp of the file at the time that the ProjectRootElement loaded it.
- ///
+ ///
/// 5. For build time, some strong references need to be held, as otherwise the ProjectRootElement's for reuseable
/// imports will be collected, and time will be wasted reparsing them. However we do not want to hold strong references
/// to all ProjectRootElement's, consuming memory without end. So a simple priority queue is used. All Adds and Gets boost their
/// entry to the top. As the queue gets too big, low priority entries are dropped.
- ///
+ ///
/// No guesses are made at which files are more interesting to cache, beyond the most-recently-used list. For example, ".targets" files
/// or imported files are not treated specially, as this is a potentially unreliable heuristic. Besides, caching a project file itself could
/// be useful, if for example you want to build it twice with different sets of properties.
- ///
+ ///
/// Because of the strongly typed list, some ProjectRootElement's will be held onto indefinitely. This is an acceptable price to pay for
/// being able to provide a commonly used ProjectRootElement immediately it's needed. It is mitigated by the list being finite and small, and
/// because we allow ProjectCollection.UnloadAllProjects to hint to us to clear the list.
- ///
+ ///
/// Implicit references are those which were loaded as a result of a build, and not explicitly loaded through, for instance, the project
/// collection.
- ///
+ ///
///
internal class ProjectRootElementCache : ProjectRootElementCacheBase
{
@@ -59,22 +59,31 @@ internal class ProjectRootElementCache : ProjectRootElementCacheBase
/// The maximum number of entries to keep strong references to.
/// This has to be strong enough to make sure that key .targets files aren't pushed
/// off by transient loads of non-reusable files like .user files.
- ///
- /// Made this as large as 50 because VC has a large number of
- /// regularly used property sheets and other imports.
- /// If you change this, update the unit tests.
///
///
+ /// Made this as large as 200 because ASP.NET Core (6.0) projects have
+ /// something like 80-90 imports. This was observed to give a noticeable
+ /// performance improvement compared to a mid-17.0 MSBuild with the old
+ /// value of 50.
+ ///
/// If this number is increased much higher, the datastructure may
/// need to be changed from a linked list, since it's currently O(n).
///
- private static readonly int s_maximumStrongCacheSize = 50;
+ private static readonly int s_maximumStrongCacheSize = 200;
///
/// Whether the cache should log activity to the Debug.Out stream
///
private static bool s_debugLogCacheActivity;
+ ///
+ /// Whether the cache should check file content for cache entry invalidation.
+ ///
+ ///
+ /// Value shall be true only in case of testing. Outside QA tests it shall be false.
+ ///
+ private static bool s_сheckFileContent;
+
///
/// The map of weakly-held ProjectRootElement's
///
@@ -116,6 +125,7 @@ static ProjectRootElementCache()
}
s_debugLogCacheActivity = Environment.GetEnvironmentVariable("MSBUILDDEBUGXMLCACHE") == "1";
+ s_сheckFileContent = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDCACHECHECKFILECONTENT"));
}
///
@@ -131,15 +141,66 @@ internal ProjectRootElementCache(bool autoReloadFromDisk, bool loadProjectsReadO
LoadProjectsReadOnly = loadProjectsReadOnly;
}
+
+ ///
+ /// Returns true if given cache entry exists and is outdated.
+ ///
+ private bool IsInvalidEntry(string projectFile, ProjectRootElement projectRootElement)
+ {
+ if (projectRootElement != null && _autoReloadFromDisk)
+ {
+ FileInfo fileInfo = FileUtilities.GetFileInfoNoThrow(projectFile);
+
+ // If the file doesn't exist on disk, go ahead and use the cached version.
+ // It's an in-memory project that hasn't been saved yet.
+ if (fileInfo != null)
+ {
+ if (fileInfo.LastWriteTime != projectRootElement.LastWriteTimeWhenRead)
+ {
+ // File was changed on disk by external means. Cached version is no longer valid.
+ // We could throw here or ignore the problem, but it is a common and reasonable pattern to change a file
+ // externally and load a new project over it to see the new content. So we dump it from the cache
+ // to force a load from disk. There might then exist more than one ProjectRootElement with the same path,
+ // but clients ought not get themselves into such a state - and unless they save them to disk,
+ // it may not be a problem.
+ return true;
+ }
+ else if (s_сheckFileContent)
+ {
+ // QA tests run too fast for the timestamp check to work. This environment variable is for their
+ // use: it checks the file content as well as the timestamp. That's better than completely disabling
+ // the cache as we get test coverage of the rest of the cache code.
+ XmlDocument document = new XmlDocument();
+ document.PreserveWhitespace = projectRootElement.XmlDocument.PreserveWhitespace;
+
+ using (var xtr = XmlReaderExtension.Create(projectRootElement.FullPath, projectRootElement.ProjectRootElementCache.LoadProjectsReadOnly))
+ {
+ document.Load(xtr.Reader);
+ }
+
+ string diskContent = document.OuterXml;
+ string cacheContent = projectRootElement.XmlDocument.OuterXml;
+
+ if (diskContent != cacheContent)
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
///
/// Returns an existing ProjectRootElement for the specified file path, if any.
/// If none exists, calls the provided delegate to load one, and adds that to the cache.
/// The reason that it calls back to do this is so that the cache is locked between determining
/// that the entry does not exist and adding the entry.
- ///
+ ///
/// If was set to true, and the file on disk has changed since it was cached,
/// it will be reloaded before being returned.
- ///
+ ///
/// Thread safe.
///
///
@@ -148,7 +209,7 @@ internal ProjectRootElementCache(bool autoReloadFromDisk, bool loadProjectsReadO
/// If item is found, boosts it to the top of the strong cache.
///
/// The project file which contains the ProjectRootElement. Must be a full path.
- /// The delegate to use to load if necessary. May be null.
+ /// The delegate to use to load if necessary. May be null. Must not update the cache.
/// true if the project is explicitly loaded, otherwise false.
/// true to the project was loaded with the formated preserved, otherwise false.
/// The ProjectRootElement instance if one exists. Null otherwise.
@@ -158,91 +219,82 @@ internal override ProjectRootElement Get(string projectFile, OpenProjectRootElem
// Should already have been canonicalized
ErrorUtilities.VerifyThrowInternalRooted(projectFile);
+ ProjectRootElement projectRootElement;
lock (_locker)
{
- ProjectRootElement projectRootElement;
_weakCache.TryGetValue(projectFile, out projectRootElement);
- if (preserveFormatting != null && projectRootElement != null && projectRootElement.XmlDocument.PreserveWhitespace != preserveFormatting)
+ if (projectRootElement != null)
{
- // Cached project doesn't match preserveFormatting setting, so reload it
- projectRootElement.Reload(true, preserveFormatting);
- }
-
- if (projectRootElement != null && _autoReloadFromDisk)
- {
- FileInfo fileInfo = FileUtilities.GetFileInfoNoThrow(projectFile);
+ BoostEntryInStrongCache(projectRootElement);
- // If the file doesn't exist on disk, go ahead and use the cached version.
- // It's an in-memory project that hasn't been saved yet.
- if (fileInfo != null)
+ // An implicit load will never reset the explicit flag.
+ if (isExplicitlyLoaded)
{
- bool forgetEntry = false;
-
- if (fileInfo.LastWriteTime != projectRootElement.LastWriteTimeWhenRead)
- {
- // File was changed on disk by external means. Cached version is no longer reliable.
- // We could throw here or ignore the problem, but it is a common and reasonable pattern to change a file
- // externally and load a new project over it to see the new content. So we dump it from the cache
- // to force a load from disk. There might then exist more than one ProjectRootElement with the same path,
- // but clients ought not get themselves into such a state - and unless they save them to disk,
- // it may not be a problem.
- forgetEntry = true;
- }
- else if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDCACHECHECKFILECONTENT")))
- {
- // QA tests run too fast for the timestamp check to work. This environment variable is for their
- // use: it checks the file content as well as the timestamp. That's better than completely disabling
- // the cache as we get test coverage of the rest of the cache code.
- XmlDocument document = new XmlDocument();
- document.PreserveWhitespace = projectRootElement.XmlDocument.PreserveWhitespace;
-
- using (var xtr = XmlReaderExtension.Create(projectRootElement.FullPath, projectRootElement.ProjectRootElementCache.LoadProjectsReadOnly))
- {
- document.Load(xtr.Reader);
- }
-
- string diskContent = document.OuterXml;
- string cacheContent = projectRootElement.XmlDocument.OuterXml;
-
- if (diskContent != cacheContent)
- {
- forgetEntry = true;
- }
- }
-
- if (forgetEntry)
- {
- ForgetEntry(projectRootElement);
-
- DebugTraceCache("Out of date dropped from XML cache: ", projectFile);
- projectRootElement = null;
- }
+ projectRootElement.MarkAsExplicitlyLoaded();
}
}
+ else
+ {
+ DebugTraceCache("Not found in cache: ", projectFile);
+ }
- if (projectRootElement == null && openProjectRootElement != null)
+ if (preserveFormatting != null && projectRootElement != null && projectRootElement.XmlDocument.PreserveWhitespace != preserveFormatting)
{
- projectRootElement = openProjectRootElement(projectFile, this);
+ // Cached project doesn't match preserveFormatting setting, so reload it
+ projectRootElement.Reload(true, preserveFormatting);
+ }
+ }
- ErrorUtilities.VerifyThrowInternalNull(projectRootElement, "projectRootElement");
- ErrorUtilities.VerifyThrow(projectRootElement.FullPath == projectFile, "Got project back with incorrect path");
- ErrorUtilities.VerifyThrow(_weakCache.Contains(projectFile), "Open should have renamed into cache and boosted");
+ bool projectRootElementIsInvalid = IsInvalidEntry(projectFile, projectRootElement);
+ if (projectRootElementIsInvalid)
+ {
+ DebugTraceCache("Not satisfied from cache: ", projectFile);
+ ForgetEntryIfExists(projectRootElement);
+ }
+
+ if (openProjectRootElement == null)
+ {
+ if (projectRootElement == null || projectRootElementIsInvalid)
+ {
+ return null;
}
- else if (projectRootElement != null)
+ else
{
DebugTraceCache("Satisfied from XML cache: ", projectFile);
- BoostEntryInStrongCache(projectRootElement);
+ return projectRootElement;
}
+ }
+
+ // Use openProjectRootElement to reload the element if the cache element does not exist or need to be reloaded.
+ if (projectRootElement == null || projectRootElementIsInvalid)
+ {
+ // We do not lock loading with common _locker of the cache, to avoid lock contention.
+ // Decided also not to lock this section with the key specific locker to avoid the overhead and code overcomplication, as
+ // it is not likely that two threads would use Get function for the same project simulteniously and it is not a big deal if in some cases we load the same project twice.
+
+ projectRootElement = openProjectRootElement(projectFile, this);
+ ErrorUtilities.VerifyThrowInternalNull(projectRootElement, "projectRootElement");
+ ErrorUtilities.VerifyThrow(projectRootElement.FullPath == projectFile, "Got project back with incorrect path");
// An implicit load will never reset the explicit flag.
- if (projectRootElement != null && isExplicitlyLoaded)
+ if (isExplicitlyLoaded)
{
projectRootElement.MarkAsExplicitlyLoaded();
}
- return projectRootElement;
+ // Update cache element.
+ // It is unlikely, but it might be that while without the lock, the projectRootElement in cache was updated by another thread.
+ // And here its entry will be replaced with the loaded projectRootElement. This is fine:
+ // if loaded projectRootElement is out of date (so, it changed since the time we loaded it), it will be updated the next time some thread calls Get function.
+ AddEntry(projectRootElement);
}
+ else
+ {
+ DebugTraceCache("Satisfied from XML cache: ", projectFile);
+ }
+
+ return projectRootElement;
}
///
@@ -317,7 +369,7 @@ internal override void DiscardStrongReferences()
RaiseProjectRootElementRemovedFromStrongCache(projectRootElement);
}
- // A scavenge of the weak cache is probably not worth it as
+ // A scavenge of the weak cache is probably not worth it as
// the GC would have had to run immediately after the line above.
}
}
@@ -348,7 +400,7 @@ internal override void DiscardImplicitReferences()
{
lock (_locker)
{
- // Make a new Weak cache only with items that have been explicitly loaded, this will be a small number, there will most likely
+ // Make a new Weak cache only with items that have been explicitly loaded, this will be a small number, there will most likely
// be many items which were not explicitly loaded (ie p2p references).
WeakValueDictionary oldWeakCache = _weakCache;
_weakCache = new WeakValueDictionary(StringComparer.OrdinalIgnoreCase);
@@ -429,7 +481,7 @@ private void RenameEntryInternal(string oldFullPathIfAny, ProjectRootElement pro
// (and thus gone from the client's point of view) that merely remains
// in the cache because we still have a reference to it from our strong cache.
// Another possibility is that there are two, unrelated, un-saved, in-memory projects that were given the same path.
- // Replacing the cache entry does not in itself cause a problem -- if there are any actual users of the old
+ // Replacing the cache entry does not in itself cause a problem -- if there are any actual users of the old
// entry they will not be affected. There would then exist more than one ProjectRootElement with the same path,
// but clients ought not get themselves into such a state - and unless they save them to disk,
// it may not be a problem. Replacing also doesn't cause a problem for the strong cache,
@@ -508,6 +560,22 @@ private void ForgetEntry(ProjectRootElement projectRootElement)
_strongCache.Remove(strongCacheEntry);
RaiseProjectRootElementRemovedFromStrongCache(strongCacheEntry.Value);
}
+
+ DebugTraceCache("Out of date dropped from XML cache: ", projectRootElement.FullPath);
+ }
+
+ ///
+ /// Completely remove an entry from this cache if it exists.
+ ///
+ private void ForgetEntryIfExists(ProjectRootElement projectRootElement)
+ {
+ lock (_locker)
+ {
+ if (_weakCache.TryGetValue(projectRootElement.FullPath, out var cached) && cached == projectRootElement)
+ {
+ ForgetEntry(projectRootElement);
+ }
+ }
}
///
diff --git a/src/Build/Evaluation/ProjectStringCache.cs b/src/Build/Evaluation/ProjectStringCache.cs
index 699964700c6..32277f91fec 100644
--- a/src/Build/Evaluation/ProjectStringCache.cs
+++ b/src/Build/Evaluation/ProjectStringCache.cs
@@ -61,6 +61,20 @@ internal int Count
}
}
+ ///
+ /// Obtain the number of documents contained in the cache.
+ ///
+ internal int DocumentCount
+ {
+ get
+ {
+ lock (_locker)
+ {
+ return _documents.Count;
+ }
+ }
+ }
+
///
/// Add the given string to the cache or return the existing string if it is already
/// in the cache.
diff --git a/src/Build/Evaluation/SimpleProjectRootElementCache.cs b/src/Build/Evaluation/SimpleProjectRootElementCache.cs
index 6890d8bd75b..7e7700d1467 100644
--- a/src/Build/Evaluation/SimpleProjectRootElementCache.cs
+++ b/src/Build/Evaluation/SimpleProjectRootElementCache.cs
@@ -63,8 +63,11 @@ private ProjectRootElement GetFromOrAddToCache(string projectFile, OpenProjectRo
ErrorUtilities.VerifyThrowInternalNull(rootElement, "projectRootElement");
ErrorUtilities.VerifyThrow(rootElement.FullPath.Equals(key, StringComparison.OrdinalIgnoreCase),
"Got project back with incorrect path");
+
+ AddEntry(rootElement);
+
ErrorUtilities.VerifyThrow(_cache.TryGetValue(key, out _),
- "Open should have renamed into cache and boosted");
+ "Project should have been added into cache and boosted");
return rootElement;
});
diff --git a/src/Build/FileSystem/IFileSystemAdapter.cs b/src/Build/FileSystem/IFileSystemAdapter.cs
deleted file mode 100644
index dce1574702c..00000000000
--- a/src/Build/FileSystem/IFileSystemAdapter.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Microsoft.Build.Shared.FileSystem;
-
-namespace Microsoft.Build.FileSystem
-{
- internal class IFileSystemAdapter : MSBuildFileSystemBase
- {
- private readonly IFileSystem _wrappedFileSystem;
-
- public IFileSystemAdapter(IFileSystem wrappedFileSystem)
- {
- _wrappedFileSystem = wrappedFileSystem;
- }
-
- public override TextReader ReadFile(string path)
- {
- return _wrappedFileSystem.ReadFile(path);
- }
-
- public override Stream GetFileStream(
- string path,
- FileMode mode,
- FileAccess access,
- FileShare share)
- {
- return _wrappedFileSystem.GetFileStream(
- path,
- mode,
- access,
- share);
- }
-
- public override string ReadFileAllText(string path)
- {
- return _wrappedFileSystem.ReadFileAllText(path);
- }
-
- public override byte[] ReadFileAllBytes(string path)
- {
- return _wrappedFileSystem.ReadFileAllBytes(path);
- }
-
- public override IEnumerable EnumerateFiles(
- string path,
- string searchPattern = "*",
- SearchOption searchOption = SearchOption.TopDirectoryOnly)
- {
- return _wrappedFileSystem.EnumerateFiles(path, searchPattern, searchOption);
- }
-
- public override IEnumerable EnumerateDirectories(
- string path,
- string searchPattern = "*",
- SearchOption searchOption = SearchOption.TopDirectoryOnly)
- {
- return _wrappedFileSystem.EnumerateDirectories(path, searchPattern, searchOption);
- }
-
- public override IEnumerable EnumerateFileSystemEntries(
- string path,
- string searchPattern = "*",
- SearchOption searchOption = SearchOption.TopDirectoryOnly)
- {
- return _wrappedFileSystem.EnumerateFileSystemEntries(path, searchPattern, searchOption);
- }
-
- public override FileAttributes GetAttributes(string path)
- {
- return _wrappedFileSystem.GetAttributes(path);
- }
-
- public override DateTime GetLastWriteTimeUtc(string path)
- {
- return _wrappedFileSystem.GetLastWriteTimeUtc(path);
- }
-
- public override bool DirectoryExists(string path)
- {
- return _wrappedFileSystem.DirectoryExists(path);
- }
-
- public override bool FileExists(string path)
- {
- return _wrappedFileSystem.FileExists(path);
- }
-
- public override bool FileOrDirectoryExists(string path)
- {
- return _wrappedFileSystem.DirectoryEntryExists(path);
- }
- }
-}
diff --git a/src/Build/FileSystem/MSBuildFileSystemAdapter.cs b/src/Build/FileSystem/MSBuildFileSystemAdapter.cs
deleted file mode 100644
index 4c69284d955..00000000000
--- a/src/Build/FileSystem/MSBuildFileSystemAdapter.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Microsoft.Build.Shared.FileSystem;
-
-namespace Microsoft.Build.FileSystem
-{
- internal class MSBuildFileSystemAdapter : IFileSystem
- {
- private readonly MSBuildFileSystemBase _msbuildFileSystem;
- public MSBuildFileSystemAdapter(MSBuildFileSystemBase msbuildFileSystem)
- {
- _msbuildFileSystem = msbuildFileSystem;
- }
- public TextReader ReadFile(string path) => _msbuildFileSystem.ReadFile(path);
-
- public Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) => _msbuildFileSystem.GetFileStream(path, mode, access, share);
-
- public string ReadFileAllText(string path) => _msbuildFileSystem.ReadFileAllText(path);
-
- public byte[] ReadFileAllBytes(string path) => _msbuildFileSystem.ReadFileAllBytes(path);
-
- public IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
- {
- return _msbuildFileSystem.EnumerateFiles(path, searchPattern, searchOption);
- }
-
- public IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
- {
- return _msbuildFileSystem.EnumerateDirectories(path, searchPattern, searchOption);
- }
-
- public IEnumerable EnumerateFileSystemEntries(
- string path,
- string searchPattern = "*",
- SearchOption searchOption = SearchOption.TopDirectoryOnly)
- {
- return _msbuildFileSystem.EnumerateFileSystemEntries(path, searchPattern, searchOption);
- }
-
- public FileAttributes GetAttributes(string path) => _msbuildFileSystem.GetAttributes(path);
-
- public DateTime GetLastWriteTimeUtc(string path) => _msbuildFileSystem.GetLastWriteTimeUtc(path);
-
- public bool DirectoryExists(string path) => _msbuildFileSystem.DirectoryExists(path);
-
- public bool FileExists(string path) => _msbuildFileSystem.FileExists(path);
-
- public bool DirectoryEntryExists(string path) => _msbuildFileSystem.FileOrDirectoryExists(path);
- }
-}
diff --git a/src/Build/FileSystem/MSBuildFileSystemBase.cs b/src/Build/FileSystem/MSBuildFileSystemBase.cs
index 5383e717a9b..8855cd50da4 100644
--- a/src/Build/FileSystem/MSBuildFileSystemBase.cs
+++ b/src/Build/FileSystem/MSBuildFileSystemBase.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.Shared.FileSystem;
using System;
using System.Collections.Generic;
using System.IO;
@@ -14,66 +15,73 @@ namespace Microsoft.Build.FileSystem
/// - must be thread safe
/// - may cache some or all the calls.
///
- public abstract class MSBuildFileSystemBase
+ public abstract class MSBuildFileSystemBase : IFileSystem
{
+ #region IFileSystem implementation
+
///
/// Use this for var sr = new StreamReader(path)
///
- public abstract TextReader ReadFile(string path);
+ public virtual TextReader ReadFile(string path) => FileSystems.Default.ReadFile(path);
///
/// Use this for new FileStream(path, mode, access, share)
///
- public abstract Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share);
+ public virtual Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) => FileSystems.Default.GetFileStream(path, mode, access, share);
///
/// Use this for File.ReadAllText(path)
///
- public abstract string ReadFileAllText(string path);
+ public virtual string ReadFileAllText(string path) => FileSystems.Default.ReadFileAllText(path);
///
/// Use this for File.ReadAllBytes(path)
///
- public abstract byte[] ReadFileAllBytes(string path);
+ public virtual byte[] ReadFileAllBytes(string path) => FileSystems.Default.ReadFileAllBytes(path);
///
/// Use this for Directory.EnumerateFiles(path, pattern, option)
///
- public abstract IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly);
+ public virtual IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
+ => FileSystems.Default.EnumerateFiles(path, searchPattern, searchOption);
///
/// Use this for Directory.EnumerateFolders(path, pattern, option)
///
- public abstract IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly);
+ public virtual IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
+ => FileSystems.Default.EnumerateDirectories(path, searchPattern, searchOption);
///
/// Use this for Directory.EnumerateFileSystemEntries(path, pattern, option)
///
- public abstract IEnumerable EnumerateFileSystemEntries(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly);
+ public virtual IEnumerable EnumerateFileSystemEntries(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
+ => FileSystems.Default.EnumerateFileSystemEntries(path, searchPattern, searchOption);
///
/// Use this for File.GetAttributes()
///
- public abstract FileAttributes GetAttributes(string path);
+ public virtual FileAttributes GetAttributes(string path) => FileSystems.Default.GetAttributes(path);
///
/// Use this for File.GetLastWriteTimeUtc(path)
///
- public abstract DateTime GetLastWriteTimeUtc(string path);
+ public virtual DateTime GetLastWriteTimeUtc(string path) => FileSystems.Default.GetLastWriteTimeUtc(path);
///
/// Use this for Directory.Exists(path)
///
- public abstract bool DirectoryExists(string path);
+ public virtual bool DirectoryExists(string path) => FileSystems.Default.DirectoryExists(path);
///
/// Use this for File.Exists(path)
///
- public abstract bool FileExists(string path);
+ public virtual bool FileExists(string path) => FileSystems.Default.FileExists(path);
///
/// Use this for File.Exists(path) || Directory.Exists(path)
///
- public abstract bool FileOrDirectoryExists(string path);
+ public virtual bool FileOrDirectoryExists(string path) => FileSystems.Default.FileOrDirectoryExists(path);
+
+ #endregion
}
}
diff --git a/src/Build/Globbing/MSBuildGlob.cs b/src/Build/Globbing/MSBuildGlob.cs
index 0420aa9edd3..9460958a40f 100644
--- a/src/Build/Globbing/MSBuildGlob.cs
+++ b/src/Build/Globbing/MSBuildGlob.cs
@@ -7,6 +7,7 @@
using System.Text.RegularExpressions;
using Microsoft.Build.Collections;
using Microsoft.Build.Shared;
+using Microsoft.Build.Utilities;
using Microsoft.NET.StringTools;
namespace Microsoft.Build.Globbing
@@ -126,10 +127,13 @@ public MatchInfoResult MatchInfo(string stringToMatch)
normalizedInput,
_state.Value.Regex,
out bool isMatch,
- out string fixedDirectoryPart,
out string wildcardDirectoryPart,
out string filenamePart);
+ // We don't capture the fixed directory part in the regex but we can infer it from the other two.
+ int fixedDirectoryPartLength = normalizedInput.Length - wildcardDirectoryPart.Length - filenamePart.Length;
+ string fixedDirectoryPart = normalizedInput.Substring(0, fixedDirectoryPartLength);
+
return new MatchInfoResult(isMatch, fixedDirectoryPart, wildcardDirectoryPart, filenamePart);
}
@@ -202,8 +206,20 @@ public static MSBuildGlob Parse(string globRoot, string fileSpec)
if (regex == null)
{
+ RegexOptions regexOptions = FileMatcher.DefaultRegexOptions;
// compile the regex since it's expected to be used multiple times
- Regex newRegex = new Regex(matchFileExpression, FileMatcher.DefaultRegexOptions | RegexOptions.Compiled);
+ // For the kind of regexes used here, compilation on .NET Framework tends to be expensive and not worth the small
+ // run-time boost so it's enabled only on .NET Core by default.
+#if RUNTIME_TYPE_NETCORE
+ bool compileRegex = true;
+#else
+ bool compileRegex = !ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0);
+#endif
+ if (compileRegex)
+ {
+ regexOptions |= RegexOptions.Compiled;
+ }
+ Regex newRegex = new Regex(matchFileExpression, regexOptions);
lock (s_regexCache)
{
if (!s_regexCache.TryGetValue(matchFileExpression, out regex))
diff --git a/src/Build/Graph/ProjectGraph.cs b/src/Build/Graph/ProjectGraph.cs
index b445f72d83d..40cf6aee0ed 100644
--- a/src/Build/Graph/ProjectGraph.cs
+++ b/src/Build/Graph/ProjectGraph.cs
@@ -10,19 +10,20 @@
using System.Linq;
using System.Text;
using System.Threading;
-using Microsoft.Build.BackEnd;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Eventing;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
using Microsoft.Build.Shared;
+using Microsoft.Build.Shared.Debugging;
+using Microsoft.Build.Utilities;
namespace Microsoft.Build.Graph
{
///
/// Represents a graph of evaluated projects.
///
- [DebuggerDisplay(@"#roots={GraphRoots.Count}, #nodes={ProjectNodes.Count}, #entryPoints={EntryPointNodes.Count}")]
+ [DebuggerDisplay(@"{DebuggerDisplayString()}")]
public sealed class ProjectGraph
{
///
@@ -475,13 +476,16 @@ GraphConstructionMetrics EndMeasurement()
}
}
- internal string ToDot()
+ internal string ToDot(IReadOnlyDictionary> targetsPerNode = null)
{
var nodeCount = 0;
- return ToDot(node => nodeCount++.ToString());
+ return ToDot(node => nodeCount++.ToString(), targetsPerNode);
}
- internal string ToDot(Func nodeIdProvider)
+ internal string ToDot(
+ Func nodeIdProvider,
+ IReadOnlyDictionary> targetsPerNode = null
+ )
{
ErrorUtilities.VerifyThrowArgumentNull(nodeIdProvider, nameof(nodeIdProvider));
@@ -489,31 +493,56 @@ internal string ToDot(Func nodeIdProvider)
var sb = new StringBuilder();
- sb.Append("digraph g\n{\n\tnode [shape=box]\n");
+ sb.AppendLine($"/* {DebuggerDisplayString()} */");
+
+ sb.AppendLine("digraph g")
+ .AppendLine("{")
+ .AppendLine("\tnode [shape=box]");
foreach (var node in ProjectNodes)
{
- var nodeId = nodeIds.GetOrAdd(node, (n, idProvider) => idProvider(n), nodeIdProvider);
+ var nodeId = GetNodeId(node);
var nodeName = Path.GetFileNameWithoutExtension(node.ProjectInstance.FullPath);
+
var globalPropertiesString = string.Join(
" ",
node.ProjectInstance.GlobalProperties.OrderBy(kvp => kvp.Key)
.Select(kvp => $"{kvp.Key}={kvp.Value}"));
- sb.Append('\t').Append(nodeId).Append(" [label=<").Append(nodeName).Append(" ").Append(globalPropertiesString).AppendLine(">]");
+ var targetListString = GetTargetListString(node);
+
+ sb.AppendLine($"\t{nodeId} [label=<{nodeName} ({targetListString}) {globalPropertiesString}>]");
foreach (var reference in node.ProjectReferences)
{
- var referenceId = nodeIds.GetOrAdd(reference, (n, idProvider) => idProvider(n), nodeIdProvider);
+ var referenceId = GetNodeId(reference);
- sb.Append('\t').Append(nodeId).Append(" -> ").AppendLine(referenceId);
+ sb.AppendLine($"\t{nodeId} -> {referenceId}");
}
}
sb.Append("}");
return sb.ToString();
+
+ string GetNodeId(ProjectGraphNode node)
+ {
+ return nodeIds.GetOrAdd(node, (n, idProvider) => idProvider(n), nodeIdProvider);
+ }
+
+ string GetTargetListString(ProjectGraphNode node)
+ {
+ var targetListString = targetsPerNode is null
+ ? string.Empty
+ : string.Join(", ", targetsPerNode[node]);
+ return targetListString;
+ }
+ }
+
+ private string DebuggerDisplayString()
+ {
+ return $"#roots={GraphRoots.Count}, #nodes={ProjectNodes.Count}, #entryPoints={EntryPointNodes.Count}";
}
private static IReadOnlyCollection TopologicalSort(
diff --git a/src/Build/Graph/ProjectInterpretation.cs b/src/Build/Graph/ProjectInterpretation.cs
index ae88e1064b0..99362abab30 100644
--- a/src/Build/Graph/ProjectInterpretation.cs
+++ b/src/Build/Graph/ProjectInterpretation.cs
@@ -100,17 +100,17 @@ public IEnumerable GetReferences(ProjectInstance requesterInstanc
}
}
- private static string GetInnerBuildPropertyValue(ProjectInstance project)
+ internal static string GetInnerBuildPropertyValue(ProjectInstance project)
{
return project.GetPropertyValue(GetInnerBuildPropertyName(project));
}
- private static string GetInnerBuildPropertyName(ProjectInstance project)
+ internal static string GetInnerBuildPropertyName(ProjectInstance project)
{
return project.GetPropertyValue(PropertyNames.InnerBuildProperty);
}
- private static string GetInnerBuildPropertyValues(ProjectInstance project)
+ internal static string GetInnerBuildPropertyValues(ProjectInstance project)
{
return project.GetPropertyValue(project.GetPropertyValue(PropertyNames.InnerBuildPropertyValues));
}
diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs
index e29d3346621..b67aba3de21 100644
--- a/src/Build/Instance/ProjectInstance.cs
+++ b/src/Build/Instance/ProjectInstance.cs
@@ -564,8 +564,7 @@ private ProjectInstance(ProjectInstance that, bool isImmutable, RequestedProject
_hostServices = that._hostServices;
_isImmutable = isImmutable;
_evaluationId = that.EvaluationId;
-
- TranslateEntireState = that.TranslateEntireState;
+ _translateEntireState = that._translateEntireState;
if (filter == null)
{
@@ -849,23 +848,8 @@ public List EvaluatedItemElements
///
public bool TranslateEntireState
{
- get
- {
- return Traits.Instance.EscapeHatches.ProjectInstanceTranslation switch
- {
- EscapeHatches.ProjectInstanceTranslationMode.Full => true,
- EscapeHatches.ProjectInstanceTranslationMode.Partial => false,
- _ => _translateEntireState,
- };
- }
-
- set
- {
- if (Traits.Instance.EscapeHatches.ProjectInstanceTranslation == null)
- {
- _translateEntireState = value;
- }
- }
+ get => _translateEntireState;
+ set => _translateEntireState = value;
}
///
@@ -899,8 +883,7 @@ public string Directory
public string FullPath
{
[DebuggerStepThrough]
- get
- { return _projectFileLocation.File; }
+ get => _projectFileLocation?.File ?? string.Empty;
}
///
@@ -2019,9 +2002,15 @@ internal void LateInitialize(ProjectRootElementCacheBase projectRootElementCache
///
void ITranslatable.Translate(ITranslator translator)
{
+ if (translator.Mode == TranslationDirection.WriteToStream)
+ {
+ // When serializing into stream apply Traits.Instance.EscapeHatches.ProjectInstanceTranslation if defined.
+ MaybeForceTranslateEntireStateMode();
+ }
+
translator.Translate(ref _translateEntireState);
- if (TranslateEntireState)
+ if (_translateEntireState)
{
TranslateAllState(translator);
}
@@ -2031,6 +2020,27 @@ void ITranslatable.Translate(ITranslator translator)
}
}
+ private void MaybeForceTranslateEntireStateMode()
+ {
+ var forcedProjectInstanceTranslationMode = Traits.Instance.EscapeHatches.ProjectInstanceTranslation;
+ if (forcedProjectInstanceTranslationMode != null)
+ {
+ switch (forcedProjectInstanceTranslationMode)
+ {
+ case EscapeHatches.ProjectInstanceTranslationMode.Full:
+ _translateEntireState = true;
+ break;
+ case EscapeHatches.ProjectInstanceTranslationMode.Partial:
+ _translateEntireState = false;
+ break;
+ default:
+ // if EscapeHatches.ProjectInstanceTranslation has an unexpected value, do not force TranslateEntireStateMode.
+ // Just leave it as is.
+ break;
+ }
+ }
+ }
+
internal void TranslateMinimalState(ITranslator translator)
{
translator.TranslateDictionary(ref _globalProperties, ProjectPropertyInstance.FactoryForDeserialization);
diff --git a/src/Build/Instance/TaskFactories/TaskHostTask.cs b/src/Build/Instance/TaskFactories/TaskHostTask.cs
index c080e9338e3..99bc7663895 100644
--- a/src/Build/Instance/TaskFactories/TaskHostTask.cs
+++ b/src/Build/Instance/TaskFactories/TaskHostTask.cs
@@ -268,6 +268,7 @@ public bool Execute()
BuildEngine.ContinueOnError,
_taskType.Type.FullName,
AssemblyUtilities.GetAssemblyLocation(_taskType.Type.GetTypeInfo().Assembly),
+ _buildComponentHost.BuildParameters.LogTaskInputs,
_setParameters,
new Dictionary(_buildComponentHost.BuildParameters.GlobalProperties),
_taskLoggingContext.GetWarningsAsErrors(),
diff --git a/src/Build/Logging/BaseConsoleLogger.cs b/src/Build/Logging/BaseConsoleLogger.cs
index 5a3752d7b04..d23ea16eca0 100644
--- a/src/Build/Logging/BaseConsoleLogger.cs
+++ b/src/Build/Logging/BaseConsoleLogger.cs
@@ -2,19 +2,21 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
using System.Text;
+
+using Microsoft.Build.Evaluation;
using Microsoft.Build.Framework;
using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
-using System.Collections;
-using System.Globalization;
-using System.IO;
using ColorSetter = Microsoft.Build.Logging.ColorSetter;
using ColorResetter = Microsoft.Build.Logging.ColorResetter;
using WriteHandler = Microsoft.Build.Logging.WriteHandler;
-using Microsoft.Build.Exceptions;
namespace Microsoft.Build.BackEnd.Logging
{
@@ -329,6 +331,33 @@ internal void IsRunningWithCharacterFileType()
///
internal bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) => Verbosity >= checkVerbosity;
+ ///
+ /// Returns the minimum logger verbosity required to log a message with the given importance.
+ ///
+ /// The message importance.
+ /// True if the message should be rendered using lighter colored text.
+ /// The logger verbosity required to log a message of the given .
+ internal static LoggerVerbosity ImportanceToMinimumVerbosity(MessageImportance importance, out bool lightenText)
+ {
+ switch (importance)
+ {
+ case MessageImportance.High:
+ lightenText = false;
+ return LoggerVerbosity.Minimal;
+ case MessageImportance.Normal:
+ lightenText = true;
+ return LoggerVerbosity.Normal;
+ case MessageImportance.Low:
+ lightenText = true;
+ return LoggerVerbosity.Detailed;
+
+ default:
+ ErrorUtilities.VerifyThrow(false, "Impossible");
+ lightenText = false;
+ return LoggerVerbosity.Detailed;
+ }
+ }
+
///
/// Sets foreground color to color specified
///
@@ -713,42 +742,56 @@ internal SortedList ExtractItemList(IEnumerable items)
///
internal virtual void OutputItems(string itemType, ArrayList itemTypeList)
{
- // Write each item, one per line
- bool haveWrittenItemType = false;
- setColor(ConsoleColor.DarkGray);
- foreach (ITaskItem item in itemTypeList)
+ WriteItemType(itemType);
+
+ foreach (var item in itemTypeList)
{
- if (!haveWrittenItemType)
+ string itemSpec = item switch
{
- setColor(ConsoleColor.Gray);
- WriteLinePretty(itemType);
- haveWrittenItemType = true;
- setColor(ConsoleColor.DarkGray);
- }
- WriteLinePretty(" " /* indent slightly*/ + item.ItemSpec);
+ ITaskItem taskItem => taskItem.ItemSpec,
+ IItem iitem => iitem.EvaluatedInclude,
+ { } misc => Convert.ToString(misc),
+ null => "null"
+ };
- IDictionary metadata = item.CloneCustomMetadata();
+ WriteItemSpec(itemSpec);
- foreach (DictionaryEntry metadatum in metadata)
+ var metadata = item switch
{
- string valueOrError;
- try
- {
- valueOrError = item.GetMetadata(metadatum.Key as string);
- }
- catch (InvalidProjectFileException e)
+ IMetadataContainer metadataContainer => metadataContainer.EnumerateMetadata(),
+ IItem iitem => iitem.Metadata?.Select(m => new KeyValuePair(m.Name, m.EvaluatedValue)),
+ _ => null
+ };
+
+ if (metadata != null)
+ {
+ foreach (var metadatum in metadata)
{
- valueOrError = e.Message;
+ WriteMetadata(metadatum.Key, metadatum.Value);
}
-
- // A metadatum's "value" is its escaped value, since that's how we represent them internally.
- // So unescape before returning to the world at large.
- WriteLinePretty(" " + metadatum.Key + " = " + valueOrError);
}
}
+
resetColor();
}
+ protected virtual void WriteItemType(string itemType)
+ {
+ setColor(ConsoleColor.Gray);
+ WriteLinePretty(itemType);
+ setColor(ConsoleColor.DarkGray);
+ }
+
+ protected virtual void WriteItemSpec(string itemSpec)
+ {
+ WriteLinePretty(" " + itemSpec);
+ }
+
+ protected virtual void WriteMetadata(string name, string value)
+ {
+ WriteLinePretty(" " + name + " = " + value);
+ }
+
///
/// Returns a performance counter for a given scope (either task name or target name)
/// from the given table.
@@ -1030,6 +1073,12 @@ public virtual void Initialize(IEventSource eventSource)
eventSource.MessageRaised += MessageHandler;
eventSource.CustomEventRaised += CustomEventHandler;
eventSource.StatusEventRaised += StatusEventHandler;
+
+ bool logPropertiesAndItemsAfterEvaluation = Utilities.Traits.Instance.EscapeHatches.LogPropertiesAndItemsAfterEvaluation ?? true;
+ if (logPropertiesAndItemsAfterEvaluation && eventSource is IEventSource4 eventSource4)
+ {
+ eventSource4.IncludeEvaluationPropertiesAndItems();
+ }
}
}
diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs
index 6bcd2951e2d..dac426db8b4 100644
--- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs
+++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs
@@ -48,7 +48,10 @@ public sealed class BinaryLogger : ILogger
// version 13:
// - don't log Message where it can be recovered
// - log arguments for LazyFormattedBuildEventArgs
- internal const int FileFormatVersion = 13;
+ // - TargetSkippedEventArgs: added OriginallySucceeded, Condition, EvaluatedCondition
+ // version 14:
+ // - TargetSkippedEventArgs: added SkipReason, OriginalBuildEventContext
+ internal const int FileFormatVersion = 14;
private Stream stream;
private BinaryWriter binaryWriter;
diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs
index 47c1f6d8306..c6be1d59db3 100644
--- a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs
+++ b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs
@@ -51,8 +51,6 @@ public class BuildEventArgsReader : IDisposable
typeof(BuildEventArgs).GetField("threadId", BindingFlags.Instance | BindingFlags.NonPublic);
private static FieldInfo buildEventArgsFieldSenderName =
typeof(BuildEventArgs).GetField("senderName", BindingFlags.Instance | BindingFlags.NonPublic);
- private static FieldInfo buildEventArgsFieldTimestamp =
- typeof(BuildEventArgs).GetField("timestamp", BindingFlags.Instance | BindingFlags.NonPublic);
///
/// Initializes a new instance of BuildEventArgsReader using a BinaryReader instance
@@ -297,15 +295,30 @@ private BuildEventArgs ReadTargetSkippedEventArgs()
string condition = null;
string evaluatedCondition = null;
bool originallySucceeded = false;
+ TargetSkipReason skipReason = TargetSkipReason.None;
+ BuildEventContext originalBuildEventContext = null;
if (fileFormatVersion >= 13)
{
condition = ReadOptionalString();
evaluatedCondition = ReadOptionalString();
originallySucceeded = ReadBoolean();
+
+ // Attempt to infer skip reason from the data we have
+ skipReason = condition != null ?
+ TargetSkipReason.ConditionWasFalse // condition expression only stored when false
+ : originallySucceeded ?
+ TargetSkipReason.PreviouslyBuiltSuccessfully
+ : TargetSkipReason.PreviouslyBuiltUnsuccessfully;
}
var buildReason = (TargetBuiltReason)ReadInt32();
+ if (fileFormatVersion >= 14)
+ {
+ skipReason = (TargetSkipReason)ReadInt32();
+ originalBuildEventContext = binaryReader.ReadOptionalBuildEventContext();
+ }
+
var e = new TargetSkippedEventArgs(
fields.Message,
fields.Arguments);
@@ -320,6 +333,8 @@ private BuildEventArgs ReadTargetSkippedEventArgs()
e.Condition = condition;
e.EvaluatedCondition = evaluatedCondition;
e.OriginallySucceeded = originallySucceeded;
+ e.SkipReason = skipReason;
+ e.OriginalBuildEventContext = originalBuildEventContext;
return e;
}
@@ -535,6 +550,8 @@ private BuildEventArgs ReadTaskStartedEventArgs()
taskFile,
taskName,
fields.Timestamp);
+ e.LineNumber = fields.LineNumber;
+ e.ColumnNumber = fields.ColumnNumber;
SetCommonFields(e, fields);
return e;
}
@@ -658,7 +675,9 @@ private BuildEventArgs ReadTaskParameterEventArgs()
itemType,
items,
logItemMetadata: true,
- fields.Timestamp);
+ fields.Timestamp,
+ fields.LineNumber,
+ fields.ColumnNumber);
e.ProjectFile = fields.ProjectFile;
return e;
}
diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs
index 20cd4232cb2..20583b991b0 100644
--- a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs
+++ b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs
@@ -370,7 +370,9 @@ private void Write(TargetFinishedEventArgs e)
private void Write(TaskStartedEventArgs e)
{
Write(BinaryLogRecordKind.TaskStarted);
- WriteBuildEventArgsFields(e, writeMessage: false);
+ WriteBuildEventArgsFields(e, writeMessage: false, writeLineAndColumn: true);
+ Write(e.LineNumber);
+ Write(e.ColumnNumber);
WriteDeduplicatedString(e.TaskName);
WriteDeduplicatedString(e.ProjectFile);
WriteDeduplicatedString(e.TaskFile);
@@ -390,6 +392,7 @@ private void Write(BuildErrorEventArgs e)
{
Write(BinaryLogRecordKind.Error);
WriteBuildEventArgsFields(e);
+ WriteArguments(e.RawArguments);
WriteDeduplicatedString(e.Subcategory);
WriteDeduplicatedString(e.Code);
WriteDeduplicatedString(e.File);
@@ -404,6 +407,7 @@ private void Write(BuildWarningEventArgs e)
{
Write(BinaryLogRecordKind.Warning);
WriteBuildEventArgsFields(e);
+ WriteArguments(e.RawArguments);
WriteDeduplicatedString(e.Subcategory);
WriteDeduplicatedString(e.Code);
WriteDeduplicatedString(e.File);
@@ -454,6 +458,8 @@ private void Write(TargetSkippedEventArgs e)
WriteDeduplicatedString(e.EvaluatedCondition);
Write(e.OriginallySucceeded);
Write((int)e.BuildReason);
+ Write((int)e.SkipReason);
+ binaryWriter.WriteOptionalBuildEventContext(e.OriginalBuildEventContext);
}
private void Write(CriticalBuildMessageEventArgs e)
@@ -512,9 +518,14 @@ private void Write(TaskParameterEventArgs e)
WriteTaskItemList(e.Items, e.LogItemMetadata);
}
- private void WriteBuildEventArgsFields(BuildEventArgs e, bool writeMessage = true)
+ private void WriteBuildEventArgsFields(BuildEventArgs e, bool writeMessage = true, bool writeLineAndColumn = false)
{
var flags = GetBuildEventArgsFieldFlags(e, writeMessage);
+ if (writeLineAndColumn)
+ {
+ flags |= BuildEventArgsFieldFlags.LineNumber | BuildEventArgsFieldFlags.ColumnNumber;
+ }
+
Write((int)flags);
WriteBaseFields(e, flags);
}
@@ -555,7 +566,7 @@ private void WriteBaseFields(BuildEventArgs e, BuildEventArgsFieldFlags flags)
private void WriteMessageFields(BuildMessageEventArgs e, bool writeMessage = true, bool writeImportance = false)
{
var flags = GetBuildEventArgsFieldFlags(e, writeMessage);
- flags = GetMessageFlags(e, flags, writeMessage, writeImportance);
+ flags = GetMessageFlags(e, flags, writeImportance);
Write((int)flags);
@@ -603,12 +614,7 @@ private void WriteMessageFields(BuildMessageEventArgs e, bool writeMessage = tru
if ((flags & BuildEventArgsFieldFlags.Arguments) != 0)
{
- Write(e.RawArguments.Length);
- for (int i = 0; i < e.RawArguments.Length; i++)
- {
- string argument = Convert.ToString(e.RawArguments[i], CultureInfo.CurrentCulture);
- WriteDeduplicatedString(argument);
- }
+ WriteArguments(e.RawArguments);
}
if ((flags & BuildEventArgsFieldFlags.Importance) != 0)
@@ -617,7 +623,23 @@ private void WriteMessageFields(BuildMessageEventArgs e, bool writeMessage = tru
}
}
- private static BuildEventArgsFieldFlags GetMessageFlags(BuildMessageEventArgs e, BuildEventArgsFieldFlags flags, bool writeMessage = true, bool writeImportance = false)
+ private void WriteArguments(object[] arguments)
+ {
+ if (arguments == null || arguments.Length == 0)
+ {
+ return;
+ }
+
+ int count = arguments.Length;
+ Write(count);
+ for (int i = 0; i < count; i++)
+ {
+ string argument = Convert.ToString(arguments[i], CultureInfo.CurrentCulture);
+ WriteDeduplicatedString(argument);
+ }
+ }
+
+ private static BuildEventArgsFieldFlags GetMessageFlags(BuildMessageEventArgs e, BuildEventArgsFieldFlags flags, bool writeImportance = false)
{
if (e.Subcategory != null)
{
@@ -659,11 +681,6 @@ private static BuildEventArgsFieldFlags GetMessageFlags(BuildMessageEventArgs e,
flags |= BuildEventArgsFieldFlags.EndColumnNumber;
}
- if (writeMessage && e.RawArguments != null && e.RawArguments.Length > 0)
- {
- flags |= BuildEventArgsFieldFlags.Arguments;
- }
-
if (writeImportance && e.Importance != MessageImportance.Low)
{
flags |= BuildEventArgsFieldFlags.Importance;
@@ -688,6 +705,14 @@ private static BuildEventArgsFieldFlags GetBuildEventArgsFieldFlags(BuildEventAr
if (writeMessage)
{
flags |= BuildEventArgsFieldFlags.Message;
+
+ // We're only going to write the arguments for messages,
+ // warnings and errors. Only set the flag for these.
+ if (e is LazyFormattedBuildEventArgs { RawArguments: { Length: > 0 } } and
+ (BuildMessageEventArgs or BuildWarningEventArgs or BuildErrorEventArgs))
+ {
+ flags |= BuildEventArgsFieldFlags.Arguments;
+ }
}
// no need to waste space for the default sender name
@@ -696,12 +721,6 @@ private static BuildEventArgsFieldFlags GetBuildEventArgsFieldFlags(BuildEventAr
flags |= BuildEventArgsFieldFlags.SenderName;
}
- // ThreadId never seems to be used or useful for anything.
- //if (e.ThreadId > 0)
- //{
- // flags |= BuildEventArgsFieldFlags.ThreadId;
- //}
-
if (e.Timestamp != default(DateTime))
{
flags |= BuildEventArgsFieldFlags.Timestamp;
diff --git a/src/Build/Logging/ConsoleLogger.cs b/src/Build/Logging/ConsoleLogger.cs
index 3af25e3ce19..c6358a9badb 100644
--- a/src/Build/Logging/ConsoleLogger.cs
+++ b/src/Build/Logging/ConsoleLogger.cs
@@ -164,8 +164,6 @@ private void InitializeBaseConsoleLogger()
_parameters = null;
}
-
-
_consoleLogger.SkipProjectStartedText = _skipProjectStartedText;
}
@@ -477,6 +475,31 @@ public void CustomEventHandler(object sender, CustomBuildEventArgs e)
_consoleLogger.CustomEventHandler(sender, e);
}
+ ///
+ /// Returns the minimum importance of messages logged by this logger.
+ ///
+ ///
+ /// The minimum message importance corresponding to this logger's verbosity or (MessageImportance.High - 1)
+ /// if this logger does not log messages of any importance.
+ ///
+ internal MessageImportance GetMinimumMessageImportance()
+ {
+ if (Verbosity >= BaseConsoleLogger.ImportanceToMinimumVerbosity(MessageImportance.Low, out _))
+ {
+ return MessageImportance.Low;
+ }
+ else if (Verbosity >= BaseConsoleLogger.ImportanceToMinimumVerbosity(MessageImportance.Normal, out _))
+ {
+ return MessageImportance.Normal;
+ }
+ else if (Verbosity >= BaseConsoleLogger.ImportanceToMinimumVerbosity(MessageImportance.High, out _))
+ {
+ return MessageImportance.High;
+ }
+ // The logger does not log messages of any importance.
+ return MessageImportance.High - 1;
+ }
+
#endregion
}
}
diff --git a/src/Build/Logging/DistributedLoggers/ConfigurableForwardingLogger.cs b/src/Build/Logging/DistributedLoggers/ConfigurableForwardingLogger.cs
index d530bd07264..83c2499aefa 100644
--- a/src/Build/Logging/DistributedLoggers/ConfigurableForwardingLogger.cs
+++ b/src/Build/Logging/DistributedLoggers/ConfigurableForwardingLogger.cs
@@ -75,7 +75,7 @@ public int NodeId
///
private void InitializeForwardingTable()
{
- _forwardingTable = new Dictionary(15, StringComparer.OrdinalIgnoreCase);
+ _forwardingTable = new Dictionary(17, StringComparer.OrdinalIgnoreCase);
_forwardingTable[BuildStartedEventDescription] = 0;
_forwardingTable[BuildFinishedEventDescription] = 0;
_forwardingTable[ProjectStartedEventDescription] = 0;
@@ -258,6 +258,31 @@ private void SetForwardingBasedOnVerbosity()
}
}
+ ///
+ /// Returns the minimum importance of messages logged by this logger.
+ ///
+ ///
+ /// The minimum message importance corresponding to this logger's verbosity or (MessageImportance.High - 1)
+ /// if this logger does not log messages of any importance.
+ ///
+ internal MessageImportance GetMinimumMessageImportance()
+ {
+ if (_forwardingTable[LowMessageEventDescription] == 1)
+ {
+ return MessageImportance.Low;
+ }
+ if (_forwardingTable[NormalMessageEventDescription] == 1)
+ {
+ return MessageImportance.Normal;
+ }
+ if (_forwardingTable[HighMessageEventDescription] == 1)
+ {
+ return MessageImportance.High;
+ }
+ // The logger does not log messages of any importance.
+ return MessageImportance.High - 1;
+ }
+
///
/// Reset the states of per-build member variables.
/// Used when a build is finished, but the logger might be needed for the next build.
diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs
index edcbe439efe..26ed295bc8b 100644
--- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs
+++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs
@@ -540,20 +540,38 @@ public override void ProjectStartedHandler(object sender, ProjectStartedEventArg
}
}
- ReadProjectConfigurationDescription(e.BuildEventContext, e.Items);
+ var projectKey = (e.BuildEventContext.NodeId, e.BuildEventContext.ProjectContextId);
+
+ // If the value is available at all, it will be either in the items
+ // from ProjectStarted (old behavior), or the items from ProjectEvaluationFinished (new behavior).
+ // First try the old behavior, and fallback to the new behavior.
+ var result = ReadProjectConfigurationDescription(e.Items);
+ if (result != null)
+ {
+ // Found the items directly on ProjectStarted
+ propertyOutputMap[projectKey] = result;
+ }
+ else
+ {
+ // Try to see if we saw the items on the corresponding ProjectEvaluationFinished
+ var evaluationKey = GetEvaluationKey(e.BuildEventContext);
+
+ // if the value was set from ProjectEvaluationFinished, copy it into the entry
+ // for this project
+ if (propertyOutputMap.TryGetValue(evaluationKey, out string value))
+ {
+ propertyOutputMap[projectKey] = value;
+ }
+ }
}
- private void ReadProjectConfigurationDescription(BuildEventContext buildEventContext, IEnumerable items)
+ private string ReadProjectConfigurationDescription(IEnumerable items)
{
- if (buildEventContext == null || items == null)
+ if (items == null)
{
- return;
+ return null;
}
- // node and project context ids for the propertyOutputMap key.
- int nodeID = buildEventContext.NodeId;
- int projectContextId = buildEventContext.ProjectContextId;
-
ReuseableStringBuilder projectConfigurationDescription = null;
Internal.Utilities.EnumerateItems(items, item =>
@@ -578,14 +596,27 @@ private void ReadProjectConfigurationDescription(BuildEventContext buildEventCon
}
});
- // Add the finished dictionary to propertyOutputMap.
if (projectConfigurationDescription != null)
{
- propertyOutputMap.Add((nodeID, projectContextId), projectConfigurationDescription.ToString());
+ var result = projectConfigurationDescription.ToString();
(projectConfigurationDescription as IDisposable)?.Dispose();
+ return result;
}
+
+ return null;
}
+ ///
+ /// In case the items are stored on ProjectEvaluationFinishedEventArgs
+ /// (new behavior), we first store the value per evaluation, and then
+ /// in ProjectStarted, find the value from the project's evaluation
+ /// and use that.
+ ///
+ private (int, int) GetEvaluationKey(BuildEventContext buildEventContext)
+ // note that we use a negative number for evaluations so that we don't conflict
+ // with project context ids.
+ => (buildEventContext.NodeId, -buildEventContext.EvaluationId);
+
///
/// Handler for project finished events
///
@@ -751,44 +782,23 @@ internal void WriteItems(BuildEventArgs e, IEnumerable items)
ShownBuildEventContext(e.BuildEventContext);
}
- internal override void OutputItems(string itemType, ArrayList itemTypeList)
+ protected override void WriteItemType(string itemType)
{
- // Write each item, one per line
- bool haveWrittenItemType = false;
- foreach (ITaskItem item in itemTypeList)
- {
- if (!haveWrittenItemType)
- {
- setColor(ConsoleColor.DarkGray);
- WriteMessageAligned(itemType, false);
- haveWrittenItemType = true;
- }
- setColor(ConsoleColor.Gray);
-
- // Indent the text by two tab lengths
- StringBuilder result = new StringBuilder((2 * tabWidth) + item.ItemSpec.Length);
- result.Append(' ', 2 * tabWidth).Append(item.ItemSpec);
- WriteMessageAligned(result.ToString(), false);
-
- IDictionary metadata = item.CloneCustomMetadata();
+ setColor(ConsoleColor.DarkGray);
+ WriteMessageAligned(itemType, prefixAlreadyWritten: false);
+ setColor(ConsoleColor.Gray);
+ }
- foreach (DictionaryEntry metadatum in metadata)
- {
- string valueOrError;
- try
- {
- valueOrError = item.GetMetadata(metadatum.Key as string);
- }
- catch (InvalidProjectFileException e)
- {
- valueOrError = e.Message;
- }
+ protected override void WriteItemSpec(string itemSpec)
+ {
+ WriteMessageAligned(new string(' ', 2 * tabWidth) + itemSpec, prefixAlreadyWritten: false);
+ }
- WriteMessageAligned($"{new string(' ', 4 * tabWidth)}{metadatum.Key} = {valueOrError}", false);
- }
- }
- resetColor();
+ protected override void WriteMetadata(string name, string value)
+ {
+ WriteMessageAligned($"{new string(' ', 4 * tabWidth)}{name} = {value}", prefixAlreadyWritten: false);
}
+
///
/// Handler for target started events
///
@@ -962,16 +972,16 @@ public override void TaskFinishedHandler(object sender, TaskFinishedEventArgs e)
///
/// Finds the LogOutProperty string to be printed in messages.
///
- /// Build event to extract context information from.
+ /// Build event to extract context information from.
internal string FindLogOutputProperties(BuildEventArgs e)
{
string projectConfigurationDescription = String.Empty;
if (e.BuildEventContext != null)
{
- int nodeId = e.BuildEventContext.NodeId;
- int projectContextId = e.BuildEventContext.ProjectContextId;
- propertyOutputMap.TryGetValue((nodeId, projectContextId), out projectConfigurationDescription);
+ var key = (e.BuildEventContext.NodeId, e.BuildEventContext.ProjectContextId);
+ propertyOutputMap.TryGetValue(key, out projectConfigurationDescription);
}
+
return projectConfigurationDescription;
}
@@ -1090,23 +1100,8 @@ public override void MessageHandler(object sender, BuildMessageEventArgs e)
}
else
{
- switch (e.Importance)
- {
- case MessageImportance.High:
- print = IsVerbosityAtLeast(LoggerVerbosity.Minimal);
- break;
- case MessageImportance.Normal:
- print = IsVerbosityAtLeast(LoggerVerbosity.Normal);
- lightenText = true;
- break;
- case MessageImportance.Low:
- print = IsVerbosityAtLeast(LoggerVerbosity.Detailed);
- lightenText = true;
- break;
- default:
- ErrorUtilities.VerifyThrow(false, "Impossible");
- break;
- }
+ LoggerVerbosity minimumVerbosity = ImportanceToMinimumVerbosity(e.Importance, out lightenText);
+ print = IsVerbosityAtLeast(minimumVerbosity);
}
if (print)
@@ -1168,7 +1163,12 @@ public override void StatusEventHandler(object sender, BuildStatusEventArgs e)
}
}
- ReadProjectConfigurationDescription(projectEvaluationFinished.BuildEventContext, projectEvaluationFinished.Items);
+ var value = ReadProjectConfigurationDescription(projectEvaluationFinished.Items);
+ if (value != null)
+ {
+ var evaluationKey = GetEvaluationKey(e.BuildEventContext);
+ propertyOutputMap[evaluationKey] = value;
+ }
}
}
diff --git a/src/Build/Logging/SerialConsoleLogger.cs b/src/Build/Logging/SerialConsoleLogger.cs
index c47c5ed976d..9deedd88b0e 100644
--- a/src/Build/Logging/SerialConsoleLogger.cs
+++ b/src/Build/Logging/SerialConsoleLogger.cs
@@ -497,28 +497,8 @@ public override void WarningHandler(object sender, BuildWarningEventArgs e)
///
public override void MessageHandler(object sender, BuildMessageEventArgs e)
{
- bool print = false;
- bool lightenText = false;
- switch (e.Importance)
- {
- case MessageImportance.High:
- print = IsVerbosityAtLeast(LoggerVerbosity.Minimal);
- break;
-
- case MessageImportance.Normal:
- print = IsVerbosityAtLeast(LoggerVerbosity.Normal);
- lightenText = true;
- break;
-
- case MessageImportance.Low:
- print = IsVerbosityAtLeast(LoggerVerbosity.Detailed);
- lightenText = true;
- break;
-
- default:
- ErrorUtilities.VerifyThrow(false, "Impossible");
- break;
- }
+ LoggerVerbosity minimumVerbosity = ImportanceToMinimumVerbosity(e.Importance, out bool lightenText);
+ bool print = IsVerbosityAtLeast(minimumVerbosity);
if (print)
{
diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj
index 43d9b794046..d08e337c5da 100644
--- a/src/Build/Microsoft.Build.csproj
+++ b/src/Build/Microsoft.Build.csproj
@@ -4,8 +4,8 @@
- net5.0
- $(FullFrameworkTFM);net5.0
+ net6.0
+ $(FullFrameworkTFM);net6.0$(RuntimeOutputTargetFrameworks)Microsoft.BuildMicrosoft.Build
@@ -20,7 +20,7 @@
trueThis package contains the $(MSBuildProjectName) assembly which is used to create, edit, and evaluate MSBuild projects.false
- partial
+ full$(NoWarn);NU5104
@@ -157,6 +157,7 @@
+
@@ -264,6 +265,8 @@
+
+
diff --git a/src/Build/Resources/Constants.cs b/src/Build/Resources/Constants.cs
index 8d03cf5a8a2..a327906f7ef 100644
--- a/src/Build/Resources/Constants.cs
+++ b/src/Build/Resources/Constants.cs
@@ -51,6 +51,8 @@ internal static class ReservedPropertyNames
internal const string programFiles32 = "MSBuildProgramFiles32";
internal const string localAppData = "LocalAppData";
internal const string assemblyVersion = "MSBuildAssemblyVersion";
+ internal const string fileVersion = "MSBuildFileVersion";
+ internal const string semanticVersion = "MSBuildSemanticVersion";
internal const string version = "MSBuildVersion";
internal const string osName = "OS";
internal const string frameworkToolsRoot = "MSBuildFrameworkToolsRoot";
diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx
index 9bce56de852..db27d0712c8 100644
--- a/src/Build/Resources/Strings.resx
+++ b/src/Build/Resources/Strings.resx
@@ -1285,13 +1285,15 @@
MSB4244: The SDK resolver assembly "{0}" could not be loaded. {1}{StrBegin="MSB4244: "}
-
- MSB4242: The SDK resolver "{0}" failed to run. {1}
+
+ MSB4242: SDK Resolver Failure: "{0}"{StrBegin="MSB4242: "}
+
+ The SDK resolver "{0}" failed while attempting to resolve the SDK "{1}". Exception: "{2}"
+
- MSB4243: The NuGet-based SDK resolver failed to run because NuGet assemblies could not be located. Check your installation of MSBuild or set the environment variable "{0}" to the folder that contains the required NuGet assemblies. {1}
- {StrBegin="MSB4243: "}
+ The NuGet-based SDK resolver failed to run because NuGet assemblies could not be located. Check your installation of MSBuild or set the environment variable "{0}" to the folder that contains the required NuGet assemblies. {1}MSB4245: SDK Resolver manifest file is invalid. This may indicate a corrupt or invalid installation of MSBuild. Manifest file path '{0}'. Message: {1}
@@ -1353,7 +1355,7 @@
{StrBegin="MSB4209: "}
- MSB4213: The specified request affinity {0} conflicts with a previous affinity {1} specified for this project.
+ MSB4213: The specified request affinity {0} conflicts with a previous affinity {1} specified for project {2} with global properties {3}{StrBegin="MSB4213: "}
@@ -1894,4 +1896,7 @@ Utilization: {0} Average Utilization: {1:###.0}
Killing process with pid = {0}.
+
+ MSB4274: Disabling the inproc node leads to performance degradation when using project cache plugins that emit proxy build requests.
+
diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf
index f223fa5d580..bdf7ebe0340 100644
--- a/src/Build/Resources/xlf/Strings.cs.xlf
+++ b/src/Build/Resources/xlf/Strings.cs.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
Počáteční hodnota vlastnosti: $({0})={1} Zdroj: {2}
+
+
+ MSB4274: Zakázání uzlu inproc způsobí snížení výkonu při používání modulů plug-in mezipaměti projektu, které vysílají žádosti o sestavení proxy serveru.
+
+
+
+
+ MSB4242: Selhání překladače sady SDK: {0}
+ {StrBegin="MSB4242: "}
+
+
+
+ Překladač sady SDK {0} selhal při pokusu o překlad sady SDK {1}. Výjimka: {2}
+
+ MSB4260: Projekt {0} přeskočil omezení izolace grafu v odkazovaném projektu {1}.
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: Určené spřažení požadavku {0} je v konfliktu s předchozím spřažením {1} určeným pro tento projekt.
+
+ MSB4213: Určené spřažení požadavku {0} je v konfliktu s předchozím spřažením {1} určeným pro projekt {2} s globálními vlastnostmi {3}.{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Využití: Průměrné využití {0}: {1:###.0}
MSB4244: Sestavení překladače sady SDK {0} nebylo možné načíst. {1}{StrBegin="MSB4244: "}
-
-
- MSB4242: Překladač sady SDK {0} se nepodařilo spustit. {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: Překladač sady SDK založený na NuGet se nepodařilo spustit, protože sestavení NuGet se nenašla. Zkontrolujte instalaci nástroje MSBuild nebo nastavte proměnnou prostředí {0} na složku, která obsahuje požadovaná sestavení NuGet. {1}
- {StrBegin="MSB4243: "}
+
+ Překladač sady SDK založený na NuGet se nepodařilo spustit, protože sestavení NuGet se nenašla. Zkontrolujte instalaci nástroje MSBuild nebo nastavte proměnnou prostředí {0} na složku, která obsahuje požadovaná sestavení NuGet. {1}
+
diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf
index 14323a1cb89..189016ff8b6 100644
--- a/src/Build/Resources/xlf/Strings.de.xlf
+++ b/src/Build/Resources/xlf/Strings.de.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
Anfangswert der Eigenschaft: $({0})="{1}", Quelle: {2}
+
+
+ MSB4274: Das Deaktivieren des In-Process-Knotens führt zu Leistungseinbußen bei der Verwendung von Projektcache-Plug-Ins, die Proxybuildanforderungen ausgeben.
+
+
+
+
+ MSB4242: Fehler bei SDK-Resolver: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ Ausfall beim Versuch des SDK-Resolver "{0}", das SDK "{1}" aufzulösen. Ausnahme: "{2}"
+
+ MSB4260: Das Projekt "{0}" hat Graphisolationseinschränkungen für das referenzierte Projekt "{1}" übersprungen.
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: Die angegebene Anforderungsaffinität {0} steht mit einer früheren Affinität {1} in Konflikt, die für dieses Projekt angegeben wurde.
+
+ MSB4213: Die angegebene Anforderungsaffinität {0} verursacht einen Konflikt mit einer vorherigen Affinität {1} speziell für das Projekt {2} mit globalen Eigenschaften {3}{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Auslastung: {0} Durchschnittliche Auslastung: {1:###.0}
MSB4244: Die SDK-Resolverassembly "{0}" konnte nicht geladen werden. {1}{StrBegin="MSB4244: "}
-
-
- MSB4242: Der SDK-Resolver "{0}" konnte nicht ausgeführt werden. {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: Fehler beim NuGet-basierten SDK-Resolver, weil die NuGet-Assemblys nicht gefunden wurden. Überprüfen Sie Ihre Installation von MSBuild, oder legen Sie die Umgebungsvariable "{0}" auf den Ordner fest, der die erforderlichen NuGet-Assemblys enthält. {1}
- {StrBegin="MSB4243: "}
+
+ Fehler beim NuGet-basierten SDK-Resolver, weil die NuGet-Assemblies nicht gefunden wurden. Überprüfen Sie Ihre Installation von MSBuild, oder legen Sie die Umgebungsvariable "{0}" auf den Ordner fest, der die erforderlichen NuGet-Assemblies enthält. {1}
+
diff --git a/src/Build/Resources/xlf/Strings.en.xlf b/src/Build/Resources/xlf/Strings.en.xlf
index 035b1fdf1cb..cff665d540f 100644
--- a/src/Build/Resources/xlf/Strings.en.xlf
+++ b/src/Build/Resources/xlf/Strings.en.xlf
@@ -257,6 +257,21 @@
Property initial value: $({0})="{1}" Source: {2}
+
+
+ MSB4274: Disabling the inproc node leads to performance degradation when using project cache plugins that emit proxy build requests.
+
+
+
+
+ MSB4242: SDK Resolver Failure: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ The SDK resolver "{0}" failed while attempting to resolve the SDK "{1}". Exception: "{2}"
+
+ MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}"
@@ -1865,8 +1880,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: The specified request affinity {0} conflicts with a previous affinity {1} specified for this project.
+
+ MSB4213: The specified request affinity {0} conflicts with a previous affinity {1} specified for project {2} with global properties {3}{StrBegin="MSB4213: "}
@@ -2394,15 +2409,10 @@ Utilization: {0} Average Utilization: {1:###.0}
Done writing report.
-
-
- MSB4242: The SDK resolver "{0}" failed to run. {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: The NuGet-based SDK resolver failed to run because NuGet assemblies could not be located. Check your installation of MSBuild or set the environment variable "{0}" to the folder that contains the required NuGet assemblies. {1}
- {StrBegin="MSB4243: "}
+
+ The NuGet-based SDK resolver failed to run because NuGet assemblies could not be located. Check your installation of MSBuild or set the environment variable "{0}" to the folder that contains the required NuGet assemblies. {1}
+
diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf
index 3ada5cb3c4a..01daa41d62b 100644
--- a/src/Build/Resources/xlf/Strings.es.xlf
+++ b/src/Build/Resources/xlf/Strings.es.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
Valor inicial de la propiedad: $({0})="{1}" Origen: {2}
+
+
+ MSB4274: Al deshabilitar el nodo InProc, se degrada el rendimiento cuando use los complementos de caché de proyectos que emiten solicitudes de compilación de proxy.
+
+
+
+
+ MSB4242: Error del solucionador del SDK: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ Error en el solucionador del SDK "{0}" al intentar resolver el SDK "{1}". Excepción: "{2}".
+
+ MSB4260: El proyecto "{0}" ha omitido las restricciones de aislamiento de gráficos en el proyecto "{1}" al que se hace referencia.
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: La afinidad de solicitud {0} especificada está en conflicto con una afinidad {1} anterior especificada para este proyecto.
+
+ MSB4213: La afinidad de solicitud {0} especificada está en conflicto con una afinidad {1} anterior especificada para el proyecto {2} con las propiedades globales {3}.{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Utilización: Utilización media de {0}: {1:###.0}
MSB4244: El ensamblado de la resolución del SDK "{0}" no se pudo cargar. {1}{StrBegin="MSB4244: "}
-
-
- MSB4242: La resolución del SDK "{0}" no se pudo ejecutar. {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: La resolución del SDK basado en NuGet no se pudo ejecutar porque no se encontraron los ensamblados de NuGet. Compruebe su instalación de MSBuild o configure la variable del entorno "{0}" en la carpeta que contiene los ensamblados de NuGet requeridos. {1}
- {StrBegin="MSB4243: "}
+
+ No se pudo ejecutar la resolución del SDK basado en NuGet porque no se encontraron los ensamblados de NuGet. Compruebe su instalación de MSBuild o configure la variable del entorno "{0}" en la carpeta que contiene los ensamblados de NuGet requeridos. {1}
+
diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf
index 90105ef4478..84593fc74d6 100644
--- a/src/Build/Resources/xlf/Strings.fr.xlf
+++ b/src/Build/Resources/xlf/Strings.fr.xlf
@@ -1,4 +1,4 @@
-
+
@@ -221,7 +221,7 @@
- MSB4273: The project cache threw an unhandled exception from the {0} method.
+ MSB4273: le cache de projet a levé une exception non gérée à partir de la méthode {0}.
@@ -257,6 +257,21 @@
Valeur initiale de la propriété : $({0})="{1}" Source : {2}
+
+
+ MSB4274: la désactivation du nœud inproc entraîne une détérioration des performances lors de l’utilisation de plug-ins de cache de projet qui émettent des requêtes de build proxy.
+
+
+
+
+ MSB4242: Échec du Programme de Résolution SDK : «{0}»
+ {StrBegin="MSB4242: "}
+
+
+
+ Échec du programme de résolution SDK «{0}» lors de la tentative de résolution du kit de développement logiciel (SDK) «{1}». Exception : "{2}"
+
+ MSB4260: le projet "{0}" a ignoré les contraintes d'isolement de graphe dans le projet référencé "{1}"
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: L'affinité de requête spécifiée {0} est en conflit avec une affinité précédente {1} spécifiée pour ce projet.
+
+ MSB4213: L'affinité de requête spécifiée {0} est en conflit avec une affinité précédente {1} spécifiée pour ce projet {2} avec des propriétés globales {3}{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Utilisation : {0} Utilisation moyenne : {1:###.0}
MSB4244: Impossible de charger l'assembly de résolution de SDK "{0}". {1}{StrBegin="MSB4244: "}
-
-
- MSB4242: Impossible d'exécuter la résolution de SDK "{0}". {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: Impossible d'exécuter la résolution de SDK NuGet parce que les assemblys NuGet n'ont pas pu être localisés. Vérifiez votre installation de MSBuild ou définissez la variable d'environnement "{0}" dans le dossier qui contient les assemblys NuGet obligatoires. {1}
- {StrBegin="MSB4243: "}
+
+ Le résolveur SDK basé sur NuGet n'a pas pu s'exécuter car les assemblys NuGet n'ont pas pu être localisés. Vérifiez votre installation de MSBuild ou définissez la variable d'environnement "{0}" dans le dossier qui contient les assemblys NuGet obligatoires. {1}
+
diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf
index 6711613ae06..a3c2a92531e 100644
--- a/src/Build/Resources/xlf/Strings.it.xlf
+++ b/src/Build/Resources/xlf/Strings.it.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
Valore iniziale della proprietà: $({0})="{1}". Origine: {2}
+
+
+ MSB4274: la disabilitazione del nodo InProc porta a una riduzione del livello delle prestazioni quando si usano plug-in della cache del progetto che emettono richieste di compilazione proxy.
+
+
+
+
+ MSB4242: errore sistema di risoluzione SDK: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ Il sistema di risoluzione SDK "{0}" non è riuscito durante il tentativo di risolvere l'SDK "{1}". Eccezione: "{2}"
+
+ MSB4260: il progetto "{0}" ha ignorato i vincoli di isolamento del grafico nel progetto di riferimento "{1}"
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: l'affinità della richiesta specificata {0} è in conflitto con l'affinità {1} precedentemente specificata per il progetto.
+
+ MSB4213: l'affinità della richiesta specificata {0} è in conflitto con l'affinità {1} precedentemente specificata per il progetto {2} con le proprietà globali {3}{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Utilizzo: {0} Utilizzo medio: {1:###.0}
MSB4244: non è stato possibile caricare l'assembly "{0}" del resolver SDK. {1}{StrBegin="MSB4244: "}
-
-
- MSB4242: non è stato possibile eseguire il resolver SDK "{0}". {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: non è stato possibile eseguire il resolver SDK basato su NuGet perché non sono stati trovati assembly NuGet. Controllare l'installazione di MSBuild oppure impostare la variabile di ambiente "{0}" sulla cartella che contiene gli assembly NuGet richiesti. {1}
- {StrBegin="MSB4243: "}
+
+ Non è stato possibile eseguire il resolver SDK basato su NuGet perché non sono stati trovati assembly NuGet. Controllare l'installazione di MSBuild oppure impostare la variabile di ambiente "{0}" sulla cartella che contiene gli assembly NuGet richiesti. {1}
+
diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf
index aa758e3a206..66e3a864ded 100644
--- a/src/Build/Resources/xlf/Strings.ja.xlf
+++ b/src/Build/Resources/xlf/Strings.ja.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
プロパティの初期値: $({0})="{1}" ソース: {2}
+
+
+ MSB4274: プロキシ・ビルド要求を出すプロジェクト キャッシュ プラグインを使用する場合、InProc ノードを無効にするとパフォーマンスが低下します。
+
+
+
+
+ MSB4242: SDK リゾルバー エラー: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ SDK "{1}" を解決しようとしているときに、SDK リゾルバー "{0}" に失敗しました。例外: "{2}"
+
+ MSB4260: プロジェクト "{0}" は、参照先のプロジェクト "{1}" で、グラフの分離制約をスキップしました
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: 指定された要求の関係 {0} は、このプロジェクトに対して以前に指定された関係 {1} と競合しています。
+
+ MSB4213: 指定された要求の関係 {0} は、グローバル プロパティ {3} を持つプロジェクト {2} に対して以前に指定された関係 {1} と競合しています。{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Utilization: {0} Average Utilization: {1:###.0}
MSB4244: SDK 競合回避モジュールのアセンブリ "{0}" を読み込めませんでした。{1}{StrBegin="MSB4244: "}
-
-
- MSB4242: SDK 競合回避モジュール "{0}" を実行できませんでした。{1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: NuGet アセンブリを見つけることができなかったため、NuGet ベースの SDK 競合回避モジュールを実行できませんでした。MSBuild のインストールを確認するか、環境変数 "{0}" を、必要な NuGet アセンブリが含まれているフォルダーに設定してください。{1}
- {StrBegin="MSB4243: "}
+
+ NuGet アセンブリを見つけることができなかったため、NuGet ベースの SDK 競合回避モジュールを実行できませんでした。MSBuild のインストールを確認するか、環境変数 "{0}" を、必要な NuGet アセンブリが含まれているフォルダーに設定してください。{1}
+
diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf
index af1e71f0f9a..a7d18597b51 100644
--- a/src/Build/Resources/xlf/Strings.ko.xlf
+++ b/src/Build/Resources/xlf/Strings.ko.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
속성 초기 값: $({0})="{1}" 소스: {2}
+
+
+ MSB4274: 프록시 빌드 요청을 내보내는 프로젝트 캐시 플러그 인을 사용할 때 inproc 노드를 사용하지 않도록 설정하면 성능이 저하됩니다.
+
+
+
+
+ MSB4242: SDK 해결 프로그램 오류: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ SDK "{1}"을(를) 확인하는 동안 SDK 확인자 "{0}"이(가) 실패했습니다. 예외: "{2}"
+
+ MSB4260: 프로젝트 "{0}"에서 참조된 프로젝트 "{1}"의 그래프 격리 제약 조건을 건너뛰었습니다.
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: 지정한 요청 선호도 {0}이(가) 이 프로젝트에 대해 이전에 지정한 선호도 {1}과(와) 충돌합니다.
+
+ MSB4213: 지정된 요청 선호도 {0}이(가) 전역 속성이 {3}인 프로젝트 {2}에 대해 지정된 이전 선호도 {1}와(과) 충돌합니다.{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Utilization: {0} Average Utilization: {1:###.0}
MSB4244: SDK 확인자 어셈블리 "{0}"을(를) 로드할 수 없습니다. {1}{StrBegin="MSB4244: "}
-
-
- MSB4242: SDK 확인자 "{0}"을(를) 실행하지 못했습니다. {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: NuGet 어셈블리를 찾을 수 없어 NuGet 기반 SDK 확인자를 실행하지 못했습니다. MSBuild 설치를 확인하거나 환경 변수 "{0}"을(를) 필요한 NuGet 어셈블리가 포함된 폴더로 설정하세요. {1}
- {StrBegin="MSB4243: "}
+
+ NuGet 어셈블리를 찾을 수 없어 NuGet 기반 SDK 확인자를 실행하지 못했습니다. MSBuild 설치를 확인하거나 환경 변수 "{0}"을(를) 필요한 NuGet 어셈블리가 포함된 폴더로 설정하세요. {1}
+
diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf
index 791c924d51e..7430b42c9b1 100644
--- a/src/Build/Resources/xlf/Strings.pl.xlf
+++ b/src/Build/Resources/xlf/Strings.pl.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
Wartość początkowa właściwości: $({0})=„{1}” Źródło: {2}
+
+
+ MSB4274: wyłączenie węzła InProc prowadzi do obniżenia wydajności, gdy używane są wtyczki pamięci podręcznej projektu, które emitują żądania kompilowania serwera proxy.
+
+
+
+
+ MSB4242: niepowodzenia programu do rozpoznawania zestawu SDK: „{0}”
+ {StrBegin="MSB4242: "}
+
+
+
+ Wystąpił błąd programu do rozpoznawania zestawu SDK „{0}” podczas próby rozpoznania zestawu SDK „{1}”. Wyjątek: „{2}”
+
+ MSB4260: W przypadku projektu „{0}” pominięto ograniczenia izolacji grafu dla przywoływanego projektu „{1}”
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: Podana koligacja żądania {0} jest w konflikcie z poprzednią koligacją {1} określoną dla tego projektu.
+
+ MSB4213: podana koligacja żądania {0} jest w konflikcie z poprzednią koligacją {1} określoną dla projektu {2} za pomocą właściwości globalnych {3}{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Wykorzystanie: Średnie wykorzystanie {0}: {1:###.0}
MSB4244: Nie można załadować zestawu programu rozpoznawania zestawu SDK „{0}”. {1}{StrBegin="MSB4244: "}
-
-
- MSB4242: Nie można uruchomić programu rozpoznawania zestawu SDK „{0}”. {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: Nie można uruchomić programu rozpoznawania zestawu SDK opartego na narzędziu NuGet, ponieważ nie można zlokalizować zestawów NuGet. Sprawdź instalację programu MSBuild lub ustaw zmienną środowiskową „{0}” na folder zawierający wymagane zestawy NuGet. {1}
- {StrBegin="MSB4243: "}
+
+ Nie można uruchomić programu do rozpoznawania zestawu SDK opartego na narzędziu NuGet, ponieważ nie można zlokalizować zestawów NuGet. Sprawdź instalację programu MSBuild lub ustaw zmienną środowiskową „{0}” na folder zawierający wymagane zestawy NuGet. {1}
+
diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf
index a3c9bafe4a3..511d8aa41d5 100644
--- a/src/Build/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
Valor inicial da propriedade: $({0})="{1}" Origem: {2}
+
+
+ MSB4274: desativar o nó inproc leva à degradação do desempenho ao usar plug-ins de cache de projeto que emitem solicitações de construção de proxy.
+
+
+
+
+ MSB4242: Falha no Resolvedor do SDK: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ O resolvedor do SDK "{0}" falhou ao tentar resolver o SDK "{1}". Exceção: "{2}"
+
+ MSB4260: o projeto "{0}" ignorou as restrições de isolamento do gráfico no projeto referenciado "{1}"
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: A afinidade de solicitação especificada {0} está em conflito com uma afinidade anterior {1} especificada para este projeto.
+
+ MSB4213: A afinidade de solicitação especificada {0} está em conflito com uma afinidade anterior {1} especificada para o projeto {2} com propriedades globais {3}{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Utilização: {0} Utilização Média: {1:###.0}
MSB4244: não foi possível carregar o assembly do resolvedor "{0}" do SDK. {1}{StrBegin="MSB4244: "}
-
-
- MSB4242: falha ao executar o resolvedor "{0}" do SDK. {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: falha ao executar o resolvedor do SDK baseado em NuGet porque não foi possível localizar os assemblies do NuGet. Verifique a instalação do MSBuild ou defina a variável de ambiente "{0}" para a pasta que contém os assemblies necessários do NuGet. {1}
- {StrBegin="MSB4243: "}
+
+ O resolvedor do SDK baseado em NuGet falhou ao executar porque os assemblies do NuGet não puderam ser localizados. Verifique sua instalação do MSBuild ou defina a variável de ambiente "{0}" para a pasta que contém os assemblies do NuGet necessários. {1}
+
diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf
index ea7ac3894a8..f19945c2240 100644
--- a/src/Build/Resources/xlf/Strings.ru.xlf
+++ b/src/Build/Resources/xlf/Strings.ru.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
Начальное значение свойства: $({0})="{1}" Источник: {2}
+
+
+ MSB4274: Отключение внутрипроцессного узла приводит к замедлению при использовании плагинов кэша проекта, которые создают запросы на сборку прокси-сервера.
+
+
+
+
+ MSB4242: сбой сопоставителя SDK: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ Сбой сопоставителя SDK "{0}" при попытке сопоставить пакет SDK "{1}". Исключение: "{2}"
+
+ MSB4260: проект "{0}" пропустил ограничения изоляции графа в проекте "{1}", на который указывает ссылка.
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: указанное сходство запроса {0} конфликтует с предыдущим сходством {1}, заданным для данного проекта.
+
+ MSB4213: указанное сходство запроса {0} конфликтует с предыдущим сходством {1}, заданным для проекта {2} с глобальными свойствами {3}{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Utilization: {0} Average Utilization: {1:###.0}
MSB4244: не удалось загрузить сборку сопоставителя SDK типа "{0}". {1}{StrBegin="MSB4244: "}
-
-
- MSB4242: не удалось запустить сопоставитель SDK "{0}". {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: не удалось запустить сопоставитель SDK на базе NuGet, так как не удалось найти сборки NuGet. Проверьте свою установку MSBuild или укажите папку, содержащую требуемые сборки NuGet, в качестве значения переменной среды "{0}". {1}
- {StrBegin="MSB4243: "}
+
+ Не удалось запустить сопоставитель SDK на базе NuGet, так как не удалось найти сборки NuGet. Проверьте свою установку MSBuild или укажите папку, содержащую требуемые сборки NuGet, в качестве значения переменной среды "{0}". {1}
+
diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf
index 095ce39a05d..6ba8540f344 100644
--- a/src/Build/Resources/xlf/Strings.tr.xlf
+++ b/src/Build/Resources/xlf/Strings.tr.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
Özellik başlangıç değeri: $({0})="{1}" Kaynak: {2}
+
+
+ MSB4274: InProc düğümünün devre dışı bırakılması, ara sunucu oluşturma istekleri gönderen proje önbelleği eklentileri kullanılırken performans düşüşüne yol açar.
+
+
+
+
+ MSB4242: SDK Çözümleyici Hatası: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ "{0}" SDK çözümleyicisi, "{1}" SDK'sını çözümlemeye çalışırken başarısız oldu. İstisna: "{2}"
+
+ MSB4260: "{0}" projesi, başvurulan "{1}" projesindeki graf yalıtımı kısıtlamalarını atladı
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: Belirtilen istek benzeşimi {0} bu proje için daha önce belirtilen {1} benzeşimi ile çakışıyor.
+
+ MSB4213: Belirtilen istek benzeşimi {0}, {3} genel özelliklerine sahip {2} projesi için belirtilen önceki bir {1} benzeşimiyle çakışıyor{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Kullanım: {0} Ortalama Kullanım: {1:###.0}
MSB4244: "{0}" SDK çözümleyicisi bütünleştirilmiş kodu yüklenemedi. {1}{StrBegin="MSB4244: "}
-
-
- MSB4242: "{0}" SDK çözümleyicisi çalıştırılamadı. {1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: NuGet bütünleştirilmiş kodları bulunamadığından NuGet tabanlı SDK çözümleyicisi çalıştırılamadı. MSBuild yüklemenizi denetleyin veya "{0}" ortam değişkenini gerekli NuGet bütünleştirilmiş kodlarını içeren klasör olarak ayarlayın. {1}
- {StrBegin="MSB4243: "}
+
+ NuGet derlemeleri bulunamadığından NuGet tabanlı SDK çözümleyicisi çalıştırılamadı. MSBuild yüklemenizi kontrol edin veya "{0}" ortam değişkenini gerekli NuGet derlemelerini içeren klasöre ayarlayın. {1}
+
diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf
index b976c8e4d9d..bed465c1a19 100644
--- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
属性初始值: $({0})=“{1}”,源: {2}
+
+
+ MSB4274: 使用发出代理构建请求的项目缓存插件时,禁用 inproc 节点会导致性能下降。
+
+
+
+
+ MSB4242: SDK 解析程序失败: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ 尝试解析 SDK "{1}" 时,SDK 解析程序 "{0}" 失败。异常: "{2}"
+
+ MSB4260: 项目“{0}”已跳过所引用的项目“{1}”上的图形隔离约束
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: 指定的请求关联 {0} 与先前为此项目指定的关联 {1} 冲突。
+
+ MSB4213: 指定的请求关联 {0} 与先前为具有全局属性 {3} 的项目 {2} 指定的关联 {1} 冲突。{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Utilization: {0} Average Utilization: {1:###.0}
MSB4244: 无法加载 SDK 解析程序程序集“{0}”。{1}{StrBegin="MSB4244: "}
-
-
- MSB4242: SDK 解析程序“{0}”运行失败。{1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: 基于 NuGet 的 SDK 解析程序运行失败,因为无法找到 NuGet 程序集。请检查你安装的 MSBuild 或将环境变量“{0}”设置为包含所需 NuGet 程序集的文件夹。{1}
- {StrBegin="MSB4243: "}
+
+ 基于 NuGet 的 SDK 解析程序运行失败,因为无法找到 NuGet 程序集。请检查你安装的 MSBuild 或将环境变量“{0}”设置为包含所需 NuGet 程序集的文件夹。{1}
+
diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf
index ddebd381fbe..fa8a07b3ae3 100644
--- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf
@@ -1,4 +1,4 @@
-
+
@@ -257,6 +257,21 @@
屬性初始值: $({0})="{1}" 來源: {2}
+
+
+ MSB4274: 停用 inproc 節點會在使用可發出 proxy 組建要求的專案快取外掛程式時,導致效能降低。
+
+
+
+
+ MSB4242: SDK 解析程式失敗: "{0}"
+ {StrBegin="MSB4242: "}
+
+
+
+ SDK 解析程式 "{0}" 在嘗試解析 SDK "{1}" 時失敗。例外狀況: "{2}"
+
+ MSB4260: 專案 "{0}" 已跳過參考專案 "{1}" 上的圖形隔離條件約束
@@ -1785,8 +1800,8 @@
{StrBegin="MSB4209: "}
-
- MSB4213: 指定的要求親和性 {0} 與先前為這個專案指定的親和性 {1} 衝突。
+
+ MSB4213: 指定的要求親和性 {0} 與先前為專案 {2} 指定、具有全域屬性 {3} 的親和性 {1} 衝突。{StrBegin="MSB4213: "}
@@ -2374,15 +2389,10 @@ Utilization: {0} Average Utilization: {1:###.0}
MSB4244: 無法載入 SDK 解析程式組件 "{0}"。{1}{StrBegin="MSB4244: "}
-
-
- MSB4242: SDK 解析程式 "{0}" 無法執行。{1}
- {StrBegin="MSB4242: "}
-
-
- MSB4243: 因為找不到 NuGet 組件,所以 NuGet 型 SDK 解析程式無法執行。請檢查您的 MSBuild 安裝,或將環境變數 "{0}" 設定為包含必要 NuGet 組件的資料夾。{1}
- {StrBegin="MSB4243: "}
+
+ 因為找不到 NuGet 組件,NuGet 型 SDK 解析程式無法執行。請檢查您的 MSBuild 安裝,或將環境變數 "{0}" 設定為包含必要 NuGet 組件的資料夾。{1}
+
diff --git a/src/Build/System.Text.Encodings.Web.pkgdef b/src/Build/System.Text.Encodings.Web.pkgdef
deleted file mode 100644
index bee4d6921fe..00000000000
--- a/src/Build/System.Text.Encodings.Web.pkgdef
+++ /dev/null
@@ -1,7 +0,0 @@
-[$RootKey$\RuntimeConfiguration\dependentAssembly\bindingRedirection\{1A1A9DA4-9F25-4AC8-89BF-BCEF74875CA8}]
-"name"="System.Text.Encodings.Web"
-"codeBase"="$BaseInstallDir$\MSBuild\Current\Bin\System.Text.Encodings.Web.dll"
-"publicKeyToken"="cc7b13ffcd2ddd51"
-"culture"="neutral"
-"oldVersion"="0.0.0.0-4.0.5.0"
-"newVersion"="4.0.5.0"
diff --git a/src/Build/System.Text.Json.pkgdef b/src/Build/System.Text.Json.pkgdef
deleted file mode 100644
index f20fee293fd..00000000000
--- a/src/Build/System.Text.Json.pkgdef
+++ /dev/null
@@ -1,7 +0,0 @@
-[$RootKey$\RuntimeConfiguration\dependentAssembly\bindingRedirection\{1F1A9DA4-9F25-4AB8-89BF-BCEF73875178}]
-"name"="System.Text.Json"
-"codeBase"="$BaseInstallDir$\MSBuild\Current\Bin\System.Text.Json.dll"
-"publicKeyToken"="cc7b13ffcd2ddd51"
-"culture"="neutral"
-"oldVersion"="0.0.0.0-4.0.1.0"
-"newVersion"="4.0.1.0"
diff --git a/src/Build/Utilities/EngineFileUtilities.cs b/src/Build/Utilities/EngineFileUtilities.cs
index 02b46c31efa..da8165d3369 100644
--- a/src/Build/Utilities/EngineFileUtilities.cs
+++ b/src/Build/Utilities/EngineFileUtilities.cs
@@ -168,7 +168,7 @@ private string[] GetFileList
if (returnEscaped)
{
- // We must now go back and make sure all special characters are escaped because we always
+ // We must now go back and make sure all special characters are escaped because we always
// store data in the engine in escaped form so it doesn't interfere with our parsing.
// Note that this means that characters that were not escaped in the original filespec
// may now be escaped, but that's not easy to avoid.
diff --git a/src/Build/Utilities/FileSpecMatchTester.cs b/src/Build/Utilities/FileSpecMatchTester.cs
index 41aaea15e97..281b0278888 100644
--- a/src/Build/Utilities/FileSpecMatchTester.cs
+++ b/src/Build/Utilities/FileSpecMatchTester.cs
@@ -15,7 +15,7 @@ internal readonly struct FileSpecMatcherTester
private readonly string _unescapedFileSpec;
private readonly string _filenamePattern;
private readonly Regex _regex;
-
+
private FileSpecMatcherTester(string currentDirectory, string unescapedFileSpec, string filenamePattern, Regex regex)
{
Debug.Assert(!string.IsNullOrEmpty(unescapedFileSpec));
@@ -25,6 +25,13 @@ private FileSpecMatcherTester(string currentDirectory, string unescapedFileSpec,
_unescapedFileSpec = unescapedFileSpec;
_filenamePattern = filenamePattern;
_regex = regex;
+
+ if (_regex == null && _filenamePattern == null)
+ {
+ // We'll be testing files by comparing their normalized paths. Normalize our file spec right away
+ // to avoid doing this work on each IsMatch call.
+ _unescapedFileSpec = FileUtilities.NormalizePathForComparisonNoThrow(_unescapedFileSpec, _currentDirectory);
+ }
}
public static FileSpecMatcherTester Parse(string currentDirectory, string fileSpec)
@@ -41,31 +48,52 @@ public static FileSpecMatcherTester Parse(string currentDirectory, string fileSp
return new FileSpecMatcherTester(currentDirectory, unescapedFileSpec, filenamePattern, regex);
}
+ ///
+ /// Returns true if the given file matches this file spec.
+ ///
public bool IsMatch(string fileToMatch)
{
Debug.Assert(!string.IsNullOrEmpty(fileToMatch));
+ // Historically we've used slightly different normalization logic depending on the type of matching
+ // performed in IsMatchNormalized. We have to keep doing it for compat.
+ if (_regex == null && _filenamePattern == null)
+ {
+ fileToMatch = FileUtilities.NormalizePathForComparisonNoThrow(fileToMatch, _currentDirectory);
+ }
+ else
+ {
+ fileToMatch = FileUtilities.GetFullPathNoThrow(Path.Combine(_currentDirectory, fileToMatch));
+ }
+ return IsMatchNormalized(fileToMatch);
+ }
+
+ ///
+ /// Same as but the argument is expected to be a normalized path.
+ ///
+ public bool IsMatchNormalized(string normalizedFileToMatch)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(normalizedFileToMatch));
+
// We do the matching using one of three code paths, depending on the value of _filenamePattern and _regex.
if (_regex != null)
{
- string normalizedFileToMatch = FileUtilities.GetFullPathNoThrow(Path.Combine(_currentDirectory, fileToMatch));
return _regex.IsMatch(normalizedFileToMatch);
}
if (_filenamePattern != null)
{
// Check file name first as it's more likely to not match.
- string filename = Path.GetFileName(fileToMatch);
+ string filename = Path.GetFileName(normalizedFileToMatch);
if (!FileMatcher.IsMatch(filename, _filenamePattern))
{
return false;
}
- var normalizedFileToMatch = FileUtilities.GetFullPathNoThrow(Path.Combine(_currentDirectory, fileToMatch));
return normalizedFileToMatch.StartsWith(_currentDirectory, StringComparison.OrdinalIgnoreCase);
}
- return FileUtilities.ComparePathsNoThrow(_unescapedFileSpec, fileToMatch, _currentDirectory, alwaysIgnoreCase: true);
+ return string.Equals(_unescapedFileSpec, normalizedFileToMatch, StringComparison.OrdinalIgnoreCase);
}
// this method parses the glob and extracts the fixed directory part in order to normalize it and make it absolute
diff --git a/src/Build/Xml/XmlReaderExtension.cs b/src/Build/Xml/XmlReaderExtension.cs
index 424e7dea8a9..4bf4944e94c 100644
--- a/src/Build/Xml/XmlReaderExtension.cs
+++ b/src/Build/Xml/XmlReaderExtension.cs
@@ -1,11 +1,8 @@
using System;
-using System.Diagnostics;
using System.IO;
-using System.Reflection;
using System.Text;
using System.Xml;
using Microsoft.Build.Shared;
-using Microsoft.Build.Utilities;
namespace Microsoft.Build.Internal
{
@@ -29,16 +26,6 @@ internal static XmlReaderExtension Create(string filePath, bool loadAsReadOnly)
private readonly Stream _stream;
private readonly StreamReader _streamReader;
- ///
- /// Caches a representing the "Normalization" internal property on the -derived
- /// type returned from . The cache is process/AppDomain-wide
- /// and lock-free, so we use volatile access for thread safety, i.e. to ensure that when the field is updated the PropertyInfo
- /// it's pointing to is seen as fully initialized by all CPUs.
- ///
- private static volatile PropertyInfo _normalizationPropertyInfo;
-
- private static bool _disableReadOnlyLoad;
-
private XmlReaderExtension(string file, bool loadAsReadOnly)
{
try
@@ -84,61 +71,15 @@ public void Dispose()
_stream?.Dispose();
}
- ///
- /// Returns of the "Normalization" internal property on the given -derived type.
- ///
- private static PropertyInfo GetNormalizationPropertyInfo(Type xmlReaderType)
- {
- PropertyInfo propertyInfo = _normalizationPropertyInfo;
- if (propertyInfo == null)
- {
- BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance;
- propertyInfo = xmlReaderType.GetProperty("Normalization", bindingFlags);
- _normalizationPropertyInfo = propertyInfo;
- }
-
- return propertyInfo;
- }
-
private static XmlReader GetXmlReader(string file, StreamReader input, bool loadAsReadOnly, out Encoding encoding)
{
string uri = new UriBuilder(Uri.UriSchemeFile, string.Empty) { Path = file }.ToString();
- XmlReader reader = null;
- if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_10) && loadAsReadOnly && !_disableReadOnlyLoad)
- {
- // Create an XML reader with IgnoreComments and IgnoreWhitespace set if we know that we won't be asked
- // to write the DOM back to a file. This is a performance optimization.
- XmlReaderSettings settings = new XmlReaderSettings
- {
- DtdProcessing = DtdProcessing.Ignore,
- IgnoreComments = true,
- IgnoreWhitespace = true,
- };
- reader = XmlReader.Create(input, settings, uri);
-
- // Try to set Normalization to false. We do this to remain compatible with earlier versions of MSBuild
- // where we constructed the reader with 'new XmlTextReader()' which has normalization enabled by default.
- PropertyInfo normalizationPropertyInfo = GetNormalizationPropertyInfo(reader.GetType());
- if (normalizationPropertyInfo != null)
- {
- normalizationPropertyInfo.SetValue(reader, false);
- }
- else
- {
- // Fall back to using XmlTextReader if the prop could not be bound.
- Debug.Fail("Could not set Normalization to false on the result of XmlReader.Create");
- _disableReadOnlyLoad = true;
-
- reader.Dispose();
- reader = null;
- }
- }
-
- if (reader == null)
- {
- reader = new XmlTextReader(uri, input) { DtdProcessing = DtdProcessing.Ignore };
- }
+
+ // Ignore loadAsReadOnly for now; using XmlReader.Create results in whitespace changes
+ // of attribute text, specifically newline removal.
+ // https://github.com/Microsoft/msbuild/issues/4210
+ XmlReader reader = new XmlTextReader(uri, input) { DtdProcessing = DtdProcessing.Ignore };
reader.Read();
encoding = input.CurrentEncoding;
diff --git a/src/Deprecated/Conversion/AssemblyInfo.cs b/src/Deprecated/Conversion/AssemblyInfo.cs
index e9b874dd1c5..fd4bc5df27b 100644
--- a/src/Deprecated/Conversion/AssemblyInfo.cs
+++ b/src/Deprecated/Conversion/AssemblyInfo.cs
@@ -23,11 +23,4 @@
[assembly: CLSCompliant(true)]
-// Needed for the "hub-and-spoke model to locate and retrieve localized resources": https://msdn.microsoft.com/en-us/library/21a15yht(v=vs.110).aspx
-// We want "en" to require a satellite assembly for debug builds in order to flush out localization
-// issues, but we want release builds to work without it. Also, .net core does not have resource fallbacks
-#if (DEBUG && !RUNTIME_TYPE_NETCORE)
-[assembly: NeutralResourcesLanguage("en", UltimateResourceFallbackLocation.Satellite)]
-#else
[assembly: NeutralResourcesLanguage("en")]
-#endif
diff --git a/src/Deprecated/Conversion/Resources/xlf/Strings.en.xlf b/src/Deprecated/Conversion/Resources/xlf/Strings.en.xlf
deleted file mode 100644
index 73f4ab526f7..00000000000
--- a/src/Deprecated/Conversion/Resources/xlf/Strings.en.xlf
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
-
-
- MSB2013: The project-to-project reference with GUID {0} could not be converted because a valid .SLN file containing all projects could not be found.
- {StrBegin="MSB2013: "}
-
-
-
- MSB2016: Found an empty .RESX file in the project ({0}). Removing it from the converted project.
- {StrBegin="MSB2016: "}
-
-
-
- MSB2015: Found an <Exclude> element in the original project file. This cannot be converted to Visual Studio .NET and is being ignored.
- {StrBegin="MSB2015: "}
-
-
-
- MSB2001: Element <{0}> does not contain the required attribute "{1}".
- {StrBegin="MSB2001: "}It appears we've been asked to convert a project that either wasn't created by Visual Studio, or that has been hand-modified or corrupted.
-
-
-
- MSB2002: The file name of the new project must be specified.
- {StrBegin="MSB2002: "}This error shouldn't be possible when converting through Visual Studio, but we have it just in case.
-
-
-
- MSB2003: The file name of the old project must be specified.
- {StrBegin="MSB2003: "}This error shouldn't be possible when converting through Visual Studio, but we have it just in case.
-
-
-
- MSB2004: Element <{0}> cannot contain more than one language node.
- {StrBegin="MSB2004: "}It appears we've been asked to convert a project that either wasn't created by Visual Studio, or that has been hand-modified or corrupted. The {0} in this case is going to be "VisualStudioProject".
-
-
-
- MSB2005: Element <{0}> cannot contain attributes.
- {StrBegin="MSB2005: "}It appears we've been asked to convert a project that either wasn't created by Visual Studio, or that has been hand-modified or corrupted.
-
-
-
- MSB2006: The project file does not contain the root element <{0}>.
- {StrBegin="MSB2006: "}It appears we've been asked to convert a project that either wasn't created by Visual Studio, or that has been hand-modified or corrupted.
-
-
-
- MSB2007: Visual Studio cannot find the project file "{0}".
- {StrBegin="MSB2007: "}This error shouldn't be possible when converting through Visual Studio, but we have it just in case.
-
-
-
- MSB2014: The project-to-project reference with GUID {0} cannot be converted because it is not listed in the file '{1}'.
- {StrBegin="MSB2014: "}
-
-
-
- MSB2008: This Visual Studio project system cannot convert "{0}" projects. It can only be used to convert C#, VB, and VJ# client projects.
- {StrBegin="MSB2008: "}It appears we've been asked to convert a project that either wasn't created by the Visual Studio managed client project system (C#, VB, J#), or that has been hand-modified or corrupted. You would get this error if this conversion utility was invoked on a VC++ project (.VCPROJ) for example. In this case, the {0} would be replaced with "Visual C++".
-
-
-
- MSB2009: Attribute "{0}" of element <{1}> is not valid.
- {StrBegin="MSB2009: "}It appears we've been asked to convert a project that either wasn't created by Visual Studio, or that has been hand-modified or corrupted.
-
-
-
- MSB2010: Child element <{0}> of element <{1}> is not valid.
- {StrBegin="MSB2010: "}It appears we've been asked to convert a project that either wasn't created by Visual Studio, or that has been hand-modified or corrupted.
-
-
-
- MSB2011: Element <{0}> is not valid.
- {StrBegin="MSB2011: "}It appears we've been asked to convert a project that either wasn't created by Visual Studio, or that has been hand-modified or corrupted.
-
-
-
- MSB2012: Project-to-project references to web projects are no longer supported and therefore cannot be converted. Please remove the reference to project {0} and add it again.
- {StrBegin="MSB2012: "}
-
-
-
-
\ No newline at end of file
diff --git a/src/Deprecated/Conversion/Resources/xlf/Strings.zh-Hans.xlf b/src/Deprecated/Conversion/Resources/xlf/Strings.zh-Hans.xlf
index 4134008b926..e49ab19ee4d 100644
--- a/src/Deprecated/Conversion/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/Deprecated/Conversion/Resources/xlf/Strings.zh-Hans.xlf
@@ -1,4 +1,4 @@
-
+
diff --git a/src/Deprecated/Conversion/Resources/xlf/Strings.zh-Hant.xlf b/src/Deprecated/Conversion/Resources/xlf/Strings.zh-Hant.xlf
index c5ae79dc07c..e99a3a187a1 100644
--- a/src/Deprecated/Conversion/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/Deprecated/Conversion/Resources/xlf/Strings.zh-Hant.xlf
@@ -1,4 +1,4 @@
-
+
diff --git a/src/Deprecated/Engine.UnitTests/Project_Tests.cs b/src/Deprecated/Engine.UnitTests/Project_Tests.cs
index 38f2844b922..1144ce87b6f 100644
--- a/src/Deprecated/Engine.UnitTests/Project_Tests.cs
+++ b/src/Deprecated/Engine.UnitTests/Project_Tests.cs
@@ -26,8 +26,8 @@ public class AddItem
{
///
/// This loads an existing project, and uses the MSBuild object model to
- /// add a new item (Type="Compile" Include="c.cs") to the project. Then
- /// it compares the final project XML to make sure the item was added in
+ /// add a new item (Type="Compile" Include="c.cs") to the project. Then
+ /// it compares the final project XML to make sure the item was added in
/// the correct place.
///
///
@@ -49,7 +49,7 @@ string newItemInclude
// The project shouldn't be marked dirty yet.
Assertion.Assert("Project shouldn't be dirty", !project.IsDirtyNeedToReevaluate);
- // Add a new item (Type="Compile", Include="c.cs") to the project using
+ // Add a new item (Type="Compile", Include="c.cs") to the project using
// the object model.
BuildItem newItem = project.AddNewItem(newItemType, newItemInclude);
@@ -143,7 +143,7 @@ public void AddNewItemToNewItemGroup()
-
+
";
@@ -151,7 +151,7 @@ public void AddNewItemToNewItemGroup()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
@@ -170,7 +170,7 @@ public void AddNewItemToNewItemGroup()
-
+
";
@@ -179,7 +179,7 @@ public void AddNewItemToNewItemGroup()
}
///
- /// This loads an existing project that did not contain any items previously.
+ /// This loads an existing project that did not contain any items previously.
/// It then uses the MSBuild object model to
/// add a new item to the project. Then it compares the final project
/// XML to make sure the item was added in the correct place.
@@ -267,9 +267,9 @@ public void AddNewItemAndQueryForNonExistentMetadata()
}
///
- /// Add a new item of the same name and include path of an item that already
+ /// Add a new item of the same name and include path of an item that already
/// exists in the project. Current behavior is that we add the duplicated item,
- /// although there's no great reason for this. If we wanted, we could have
+ /// although there's no great reason for this. If we wanted, we could have
/// made it so that adding a dup results in a no-op to the project file.
///
/// RGoel
@@ -701,7 +701,7 @@ public void AddNewItemThatMatchesWildcardWithMetadata()
///
/// There's a wildcard in the project already, but it's part of a semicolon-separated
- /// list of items. Now the user tries to add an item that matches that wildcard.
+ /// list of items. Now the user tries to add an item that matches that wildcard.
/// In this case, we don't touch the project at all.
///
/// RGoel
@@ -741,7 +741,7 @@ public void AddNewItemThatMatchesWildcardInSemicolonList()
///
/// There's a wildcard in the project already, but it's part of a semicolon-separated
- /// list of items, and it uses a property reference. Now the user tries to add a new
+ /// list of items, and it uses a property reference. Now the user tries to add a new
/// item that matches that wildcard. In this case, we don't touch the project at all.
/// We're so smart.
///
@@ -803,7 +803,7 @@ public void AddNewItemGroup()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
@@ -818,7 +818,7 @@ public void AddNewItemGroup()
-
+
";
@@ -826,7 +826,7 @@ public void AddNewItemGroup()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
@@ -843,7 +843,7 @@ public void AddNewItemGroup()
-
+
";
@@ -865,17 +865,17 @@ public class RemoveItem
///
/// This loads an existing project, and uses the MSBuild object model to
/// remove an item of a particular item spec (e.g., "b.cs"). It then
- /// compares the final project XML to make sure the item was added in
+ /// compares the final project XML to make sure the item was added in
/// the correct place.
///
///
///
///
/// RGoel
- private void RemoveItemHelper
+ private void RemoveItemHelper
(
- string originalProjectContents,
- string newExpectedProjectContents,
+ string originalProjectContents,
+ string newExpectedProjectContents,
string itemSpecToRemove
)
{
@@ -889,7 +889,7 @@ string itemSpecToRemove
// The VS IDE does a few re-evaluations with different sets of global properties
// (i.e., Configuration=Debug, Configuration=Release, etc.). This is to simulate
- // that. If there's a bug in the Project object, then re-evaluation can
+ // that. If there's a bug in the Project object, then re-evaluation can
// potentially mess up the number of items hanging around.
project.MarkProjectAsDirty ();
BuildItemGroup evaluatedItems2 = project.EvaluatedItemsIgnoringCondition;
@@ -936,9 +936,9 @@ public void RemoveItemBySpec()
-
+
-
+
";
@@ -957,12 +957,12 @@ public void RemoveItemBySpec()
-
+
-
+
";
-
+
this.RemoveItemHelper (projectOriginalContents, projectNewExpectedContents, "b.cs");
}
@@ -978,7 +978,7 @@ public void RemoveItemBySpecFromMultiItemSpec()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
@@ -993,9 +993,9 @@ public void RemoveItemBySpecFromMultiItemSpec()
-
+
-
+
";
@@ -1003,7 +1003,7 @@ public void RemoveItemBySpecFromMultiItemSpec()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
@@ -1019,12 +1019,12 @@ public void RemoveItemBySpecFromMultiItemSpec()
-
+
-
+
";
-
+
this.RemoveItemHelper (projectOriginalContents, projectNewExpectedContents, "b.cs");
}
@@ -1042,7 +1042,7 @@ public void RemoveItemBySpecFromMultiItemSpecWithMetadata()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
@@ -1052,7 +1052,7 @@ public void RemoveItemBySpecFromMultiItemSpecWithMetadata()
-
+
";
@@ -1060,7 +1060,7 @@ public void RemoveItemBySpecFromMultiItemSpecWithMetadata()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
@@ -1073,10 +1073,10 @@ public void RemoveItemBySpecFromMultiItemSpecWithMetadata()
-
+
";
-
+
this.RemoveItemHelper (projectOriginalContents, projectNewExpectedContents, "b.cs");
}
@@ -1101,7 +1101,7 @@ public void RemoveItemBySpecWhenMultiItemSpecExists()
-
+
";
@@ -1119,10 +1119,10 @@ public void RemoveItemBySpecWhenMultiItemSpecExists()
-
+
";
-
+
this.RemoveItemHelper (projectOriginalContents, projectNewExpectedContents, "d.cs");
}
@@ -1153,7 +1153,7 @@ public void RemoveSpecificItem()
-
+
";
@@ -1174,7 +1174,7 @@ public void RemoveSpecificItem()
-
+
";
@@ -1226,7 +1226,7 @@ public void RemoveItemsByName()
-
+
";
@@ -1246,7 +1246,7 @@ public void RemoveItemsByName()
-
+
";
@@ -1287,7 +1287,7 @@ public void RemoveItemGroup()
-
+
";
@@ -1303,7 +1303,7 @@ public void RemoveItemGroup()
-
+
";
@@ -1667,8 +1667,8 @@ public class ModifyItem
{
///
/// This loads an existing project, and uses the MSBuild object model to
- /// modify the "Include" attribute of an item of a particular item spec (e.g.,
- /// "b.cs"). It then compares the final project XML to make sure the item was
+ /// modify the "Include" attribute of an item of a particular item spec (e.g.,
+ /// "b.cs"). It then compares the final project XML to make sure the item was
/// modified correctly.
///
///
@@ -1676,10 +1676,10 @@ public class ModifyItem
///
///
/// RGoel
- internal static void ModifyItemIncludeHelper
+ internal static void ModifyItemIncludeHelper
(
- string originalProjectContents,
- string newExpectedProjectContents,
+ string originalProjectContents,
+ string newExpectedProjectContents,
string oldItemSpec,
string newIncludePath
)
@@ -1694,7 +1694,7 @@ string newIncludePath
// The VS IDE does a few re-evaluations with different sets of global properties
// (i.e., Configuration=Debug, Configuration=Release, etc.). This is to simulate
- // that. If there's a bug in the Project object, then re-evaluation can
+ // that. If there's a bug in the Project object, then re-evaluation can
// potentially mess up the number of items hanging around.
project.MarkProjectAsDirty ();
BuildItemGroup evaluatedItems2 = project.EvaluatedItemsIgnoringCondition;
@@ -1729,7 +1729,7 @@ public void ModifyItemIncludeWithEmbeddedProperty()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
@@ -1749,7 +1749,7 @@ public void ModifyItemIncludeWithEmbeddedProperty()
-
+
";
@@ -1757,7 +1757,7 @@ public void ModifyItemIncludeWithEmbeddedProperty()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
@@ -1777,7 +1777,7 @@ public void ModifyItemIncludeWithEmbeddedProperty()
-
+
";
@@ -1796,7 +1796,7 @@ public void ModifyItemIncludeWithinMultiItemSpec()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
@@ -1809,7 +1809,7 @@ public void ModifyItemIncludeWithinMultiItemSpec()
-
+
";
@@ -1817,7 +1817,7 @@ public void ModifyItemIncludeWithinMultiItemSpec()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
@@ -1836,7 +1836,7 @@ public void ModifyItemIncludeWithinMultiItemSpec()
-
+
";
@@ -1888,17 +1888,17 @@ public void ModifyItemIncludeWithinNonMatchingWildcard()
{
// Populate the project directory with three physical files on disk -- a.weirdo, b.weirdo, c.weirdo.
CreateThreeWeirdoFilesHelper();
-
+
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
-
+
";
@@ -1906,7 +1906,7 @@ public void ModifyItemIncludeWithinNonMatchingWildcard()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
@@ -1914,7 +1914,7 @@ public void ModifyItemIncludeWithinNonMatchingWildcard()
-
+
";
@@ -1940,13 +1940,13 @@ public void ModifyItemIncludeWithinMatchingWildcard()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
-
+
";
@@ -1954,13 +1954,13 @@ public void ModifyItemIncludeWithinMatchingWildcard()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
-
+
";
@@ -1988,13 +1988,13 @@ public void ModifyRawItemIncludeWithinMatchingWildcard()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
-
+
";
@@ -2002,13 +2002,13 @@ public void ModifyRawItemIncludeWithinMatchingWildcard()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
-
+
";
@@ -2073,8 +2073,8 @@ string itemSpec
///
/// This loads an existing project, and uses the MSBuild object model to
- /// modify the Name of an item of a particular item spec (e.g.,
- /// "b.cs"). It then compares the final project XML to make sure the item was
+ /// modify the Name of an item of a particular item spec (e.g.,
+ /// "b.cs"). It then compares the final project XML to make sure the item was
/// modified correctly.
///
///
@@ -2100,7 +2100,7 @@ string newItemType
// The VS IDE does a few re-evaluations with different sets of global properties
// (i.e., Configuration=Debug, Configuration=Release, etc.). This is to simulate
- // that. If there's a bug in the Project object, then re-evaluation can
+ // that. If there's a bug in the Project object, then re-evaluation can
// potentially mess up the number of items hanging around.
project.MarkProjectAsDirty();
BuildItemGroup evaluatedItems2 = project.EvaluatedItemsIgnoringCondition;
@@ -2270,7 +2270,7 @@ public void ModifyItemMetadata()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
@@ -2285,7 +2285,7 @@ public void ModifyItemMetadata()
-
+
";
@@ -2293,7 +2293,7 @@ public void ModifyItemMetadata()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
@@ -2311,7 +2311,7 @@ public void ModifyItemMetadata()
-
+
";
@@ -2338,7 +2338,7 @@ public void ModifyItemMetadata()
public class AddProperty
{
///
- /// Tests that the object model correctly adds a new property to the correct
+ /// Tests that the object model correctly adds a new property to the correct
/// existing PropertyGroup.
///
/// RGoel
@@ -2348,23 +2348,23 @@ public void SetPropertyOnNewPropertyInExistingPropertyGroup()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
c:\blah
-
+
1
-
+
true
-
+
-
+
";
@@ -2372,24 +2372,24 @@ public void SetPropertyOnNewPropertyInExistingPropertyGroup()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
c:\blah
-
+
1woohoo
-
+
true
-
+
-
+
";
@@ -2398,7 +2398,7 @@ public void SetPropertyOnNewPropertyInExistingPropertyGroup()
// The project shouldn't be marked dirty yet.
Assertion.Assert("Project shouldn't be dirty", !project.IsDirtyNeedToReevaluate);
- // Set the given new property in the project file using
+ // Set the given new property in the project file using
// the object model.
project.SetProperty("MyNewProperty", "woohoo", "");
@@ -2419,7 +2419,7 @@ public void AddNewPropertyThroughPropertyGroup()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
@@ -2434,7 +2434,7 @@ public void AddNewPropertyThroughPropertyGroup()
-
+
";
@@ -2442,7 +2442,7 @@ public void AddNewPropertyThroughPropertyGroup()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
@@ -2458,7 +2458,7 @@ public void AddNewPropertyThroughPropertyGroup()
-
+
";
@@ -2484,7 +2484,7 @@ public void AddNewPropertyGroup()
// ************************************
// BEFORE
// ************************************
- string projectOriginalContents = @"
+ string projectOriginalContents = @"
@@ -2499,7 +2499,7 @@ public void AddNewPropertyGroup()
-
+
";
@@ -2507,7 +2507,7 @@ public void AddNewPropertyGroup()
// ************************************
// AFTER
// ************************************
- string projectNewExpectedContents = @"
+ string projectNewExpectedContents = @"
@@ -2524,7 +2524,7 @@ public void AddNewPropertyGroup()
-
+
";
@@ -2640,7 +2640,7 @@ public void RemovePropertyByName()
-
+
";
@@ -2663,7 +2663,7 @@ public void RemovePropertyByName()
-
+
";
@@ -2914,7 +2914,7 @@ public void RemoveAllPropertyGroupsByConditionWithChoose()
c:\foobar
-
+
c:\foobar
@@ -2923,7 +2923,7 @@ public void RemoveAllPropertyGroupsByConditionWithChoose()
c:\foobar
-
+
c:\foobar
@@ -3112,7 +3112,7 @@ public void SetPropertyOnExistingProperty()
-
+
";
@@ -3135,7 +3135,7 @@ public void SetPropertyOnExistingProperty()
-
+
";
@@ -3446,7 +3446,7 @@ public void VerifyMsbuildProgramFiles32ReservedProperty()
$(MsBuildProgramFiles32)
-
+
@@ -3513,7 +3513,7 @@ public void ModifyPropertyInImportedProjectFileAfterRename()
Assertion.AssertEquals(@"c:\boobah", importedProj.EvaluatedProperties["ReferencePath"].FinalValueEscaped);
importedProj.Save(Path.Combine(ObjectModelHelpers.TempProjectDir, "newimported.proj"));
-
+
// Now we add a new imported property to the main file, into an existing imported
// property group.
mainProj.SetImportedProperty("ReferencePath", @"c:\hoohah", null, importedProj);
@@ -3785,7 +3785,7 @@ public void RegistryProperties()
Project p = ObjectModelHelpers.CreateInMemoryProject(@"
-
+