Skip to content

Commit 4256aed

Browse files
authored
Populate unproxied targets to avoid dropping requested targets (dotnet#9130)
Fixes dotnet#9117 For project cache plugins to only partially handle a build request, it makes sense for it to only proxy some targets and not others. For example, in VS the build request has: "Build" "BuiltProjectOutputGroup" "BuiltProjectOutputGroupDependencies" "DebugSymbolsProjectOutputGroup" "DebugSymbolsProjectOutputGroupDependencies" "DocumentationProjectOutputGroup" "DocumentationProjectOutputGroupDependencies" "SatelliteDllsProjectOutputGroup" "SatelliteDllsProjectOutputGroupDependencies" "SGenFilesOutputGroup" "SGenFilesOutputGroupDependencies" "Build" is the only relevant one that a plugin would want to handle, and would likely redirect to "GetTargetPath", while the rest are "information gathering" targets which should just be "passed through" and allowed to execute as-is. Today if the plugin does this, the targets it does not proxy are dropped entire, which would make the caller confused about not getting results for a target they specifically requested. This change instead just fills in the remaining targets which were not proxied. Example: Original request: Build, BuiltProjectOutputGroup, BuiltProjectOutputGroupDependencies, ... Cache plugin returns proxy targets Build -> GetTargetPath, and nothing more. New request: GetTargetPath, BuiltProjectOutputGroup, BuiltProjectOutputGroupDependencies, ... This fixes dotnet#9117 indirectly by not requiring a plugin to handle targets it wants to pass through at all, allowing it to just ignore them.
1 parent 2016d60 commit 4256aed

File tree

6 files changed

+109
-36
lines changed

6 files changed

+109
-36
lines changed

src/Build.UnitTests/BackEnd/Scheduler_Tests.cs

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7+
using System.Linq;
78
using System.Xml;
89
using Microsoft.Build.BackEnd;
910
using Microsoft.Build.Evaluation;
1011
using Microsoft.Build.Execution;
1112
using Microsoft.Build.Experimental.ProjectCache;
1213
using Microsoft.Build.Framework;
1314
using Microsoft.Build.Shared;
15+
using Microsoft.Build.Unittest;
1416
using Shouldly;
1517
using Xunit;
1618
using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem;
@@ -19,8 +21,6 @@
1921

2022
namespace Microsoft.Build.UnitTests.BackEnd
2123
{
22-
using Microsoft.Build.Unittest;
23-
2424
/// <summary>
2525
/// Tests of the scheduler.
2626
/// </summary>
@@ -544,7 +544,13 @@ public void TestProxyAffinityIsInProc()
544544

545545
CreateConfiguration(1, "foo.csproj");
546546

547-
BuildRequest request1 = CreateProxyBuildRequest(1, 1, new ProxyTargets(new Dictionary<string, string> { { "foo", "bar" } }), null);
547+
BuildRequest request1 = CreateBuildRequest(
548+
nodeRequestId: 1,
549+
configId: 1,
550+
targets: new[] { "foo" },
551+
NodeAffinity.Any,
552+
parentRequest: null,
553+
new ProxyTargets(new Dictionary<string, string> { { "foo", "bar" } }));
548554

549555
BuildRequestBlocker blocker = new BuildRequestBlocker(-1, Array.Empty<string>(), new[] { request1 });
550556
List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker));
@@ -806,8 +812,6 @@ private BuildRequest CreateBuildRequest(int nodeRequestId, int configId, string[
806812
/// </summary>
807813
private BuildRequest CreateBuildRequest(int nodeRequestId, int configId, string[] targets, NodeAffinity nodeAffinity, BuildRequest parentRequest, ProxyTargets proxyTargets = null)
808814
{
809-
(targets == null ^ proxyTargets == null).ShouldBeTrue();
810-
811815
HostServices hostServices = null;
812816

813817
if (nodeAffinity != NodeAffinity.Any)
@@ -816,36 +820,26 @@ private BuildRequest CreateBuildRequest(int nodeRequestId, int configId, string[
816820
hostServices.SetNodeAffinity(String.Empty, nodeAffinity);
817821
}
818822

819-
if (targets != null)
823+
if (proxyTargets != null)
820824
{
825+
parentRequest.ShouldBeNull();
821826
return new BuildRequest(
822827
submissionId: 1,
823828
nodeRequestId,
824829
configId,
825-
targets,
826-
hostServices,
827-
BuildEventContext.Invalid,
828-
parentRequest);
830+
proxyTargets,
831+
targets.ToList(),
832+
hostServices);
829833
}
830834

831-
parentRequest.ShouldBeNull();
832835
return new BuildRequest(
833836
submissionId: 1,
834837
nodeRequestId,
835838
configId,
836-
proxyTargets,
837-
hostServices);
838-
}
839-
840-
private BuildRequest CreateProxyBuildRequest(int nodeRequestId, int configId, ProxyTargets proxyTargets, BuildRequest parentRequest)
841-
{
842-
return CreateBuildRequest(
843-
nodeRequestId,
844-
configId,
845-
null,
846-
NodeAffinity.Any,
847-
parentRequest,
848-
proxyTargets);
839+
targets,
840+
hostServices,
841+
BuildEventContext.Invalid,
842+
parentRequest);
849843
}
850844

851845
/// <summary>

src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,6 @@ public void ProjectCacheByBuildParametersAndBottomUpBuildWorks(GraphCacheRespons
507507
}
508508
}
509509

510-
511510
AssertCacheBuild(graph, testData, mockCache, logger, nodesToBuildResults, targets: null);
512511
}
513512

@@ -766,6 +765,59 @@ public void RunningProxyBuildsOnOutOfProcNodesShouldIssueWarning(bool disableInp
766765
logger.AssertMessageCount("MSB4274", 1);
767766
}
768767

768+
// A common scenario is to get a request for N targets, but only some of them can be handled by the cache.
769+
// In this case, missing targets should be passed through.
770+
[Fact]
771+
public async Task PartialProxyTargets()
772+
{
773+
const string ProjectContent = """
774+
<Project>
775+
<Target Name="SomeTarget">
776+
<Message Text="SomeTarget running" />
777+
</Target>
778+
<Target Name="ProxyTarget">
779+
<Message Text="ProxyTarget running" />
780+
</Target>
781+
<Target Name="SomeOtherTarget">
782+
<Message Text="SomeOtherTarget running" />
783+
</Target>
784+
</Project>
785+
""";
786+
TransientTestFile project = _env.CreateFile($"project.proj", ProjectContent);
787+
788+
BuildParameters buildParameters = new()
789+
{
790+
ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
791+
new ConfigurableMockCache
792+
{
793+
GetCacheResultImplementation = (_, _, _) =>
794+
{
795+
return Task.FromResult(
796+
CacheResult.IndicateCacheHit(
797+
new ProxyTargets(
798+
new Dictionary<string, string>
799+
{
800+
{ "ProxyTarget", "SomeTarget" },
801+
})));
802+
}
803+
}),
804+
};
805+
806+
MockLogger logger;
807+
using (Helpers.BuildManagerSession buildSession = new(_env, buildParameters))
808+
{
809+
logger = buildSession.Logger;
810+
BuildResult buildResult = await buildSession.BuildProjectFileAsync(project.Path, new[] { "SomeTarget", "SomeOtherTarget" });
811+
812+
buildResult.Exception.ShouldBeNull();
813+
buildResult.ShouldHaveSucceeded();
814+
}
815+
816+
logger.BuildMessageEvents.Select(i => i.Message).ShouldNotContain("SomeTarget running");
817+
logger.BuildMessageEvents.Select(i => i.Message).ShouldContain("ProxyTarget running");
818+
logger.BuildMessageEvents.Select(i => i.Message).ShouldContain("SomeOtherTarget running");
819+
}
820+
769821
private void AssertCacheBuild(
770822
ProjectGraph graph,
771823
GraphCacheResponse testData,

src/Build/BackEnd/BuildManager/BuildManager.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,15 +1783,30 @@ private static void AddBuildRequestToSubmission(BuildSubmission submission, int
17831783

17841784
private static void AddProxyBuildRequestToSubmission(
17851785
BuildSubmission submission,
1786-
int configurationId,
1786+
BuildRequestConfiguration configuration,
17871787
ProxyTargets proxyTargets,
17881788
int projectContextId)
17891789
{
1790+
IReadOnlyDictionary<string, string> realTargetsToProxyTargets = proxyTargets.RealTargetToProxyTargetMap;
1791+
1792+
ICollection<string> requestedTargets = submission.BuildRequestData.TargetNames.Count > 0
1793+
? submission.BuildRequestData.TargetNames
1794+
: configuration.Project.DefaultTargets;
1795+
List<string> targets = new(requestedTargets.Count);
1796+
foreach (string requestedTarget in requestedTargets)
1797+
{
1798+
string effectiveTarget = realTargetsToProxyTargets.TryGetValue(requestedTarget, out string proxyTarget)
1799+
? proxyTarget
1800+
: requestedTarget;
1801+
targets.Add(effectiveTarget);
1802+
}
1803+
17901804
submission.BuildRequest = new BuildRequest(
17911805
submission.SubmissionId,
17921806
BackEnd.BuildRequest.InvalidNodeRequestId,
1793-
configurationId,
1807+
configuration.ConfigurationId,
17941808
proxyTargets,
1809+
targets,
17951810
submission.BuildRequestData.HostServices,
17961811
submission.BuildRequestData.Flags,
17971812
submission.BuildRequestData.RequestedProjectState,
@@ -2294,7 +2309,7 @@ void HandleCacheResult()
22942309
{
22952310
// Setup submission.BuildRequest with proxy targets. The proxy request is built on the inproc node (to avoid
22962311
// ProjectInstance serialization). The proxy target results are used as results for the real targets.
2297-
AddProxyBuildRequestToSubmission(submission, configuration.ConfigurationId, cacheResult.ProxyTargets, projectContextId);
2312+
AddProxyBuildRequestToSubmission(submission, configuration, cacheResult.ProxyTargets, projectContextId);
22982313
IssueBuildRequestForBuildSubmission(submission, configuration, allowMainThreadBuild: false);
22992314
}
23002315
else if (cacheResult.ResultType == CacheResultType.CacheHit && cacheResult.BuildResult != null)

src/Build/BackEnd/Components/ProjectCache/ProxyTargets.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@ public class ProxyTargets : ITranslatable
2727
/// </summary>
2828
public IReadOnlyDictionary<string, string> ProxyTargetToRealTargetMap => _proxyTargetToRealTargetMap;
2929

30+
internal IReadOnlyDictionary<string, string> RealTargetToProxyTargetMap
31+
{
32+
get
33+
{
34+
// The ProxyTargetToRealTargetMap is "backwards" from how most users would want to use it and doesn't provide as much flexibility as it could if reversed.
35+
// Unfortunately this is part of a public API so cannot easily change at this point.
36+
Dictionary<string, string> realTargetsToProxyTargets = new(ProxyTargetToRealTargetMap.Count, StringComparer.OrdinalIgnoreCase);
37+
foreach (KeyValuePair<string, string> kvp in ProxyTargetToRealTargetMap)
38+
{
39+
// In the case of multiple proxy targets pointing to the same real target, the last one wins. Another awkwardness of ProxyTargetToRealTargetMap being "backwards".
40+
realTargetsToProxyTargets[kvp.Value] = kvp.Key;
41+
}
42+
43+
return realTargetsToProxyTargets;
44+
}
45+
}
46+
3047
private ProxyTargets()
3148
{
3249
}

src/Build/BackEnd/Shared/BuildRequest.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ private BuildRequest(
128128
/// <param name="nodeRequestId">The id of the node issuing the request</param>
129129
/// <param name="configurationId">The configuration id to use.</param>
130130
/// <param name="proxyTargets"><see cref="ProxyTargets"/></param>
131+
/// <param name="targets">The set of targets to execute</param>
131132
/// <param name="hostServices">Host services if any. May be null.</param>
132133
/// <param name="buildRequestDataFlags">Additional flags for the request.</param>
133134
/// <param name="requestedProjectState">Filter for desired build results.</param>
@@ -137,14 +138,15 @@ public BuildRequest(
137138
int nodeRequestId,
138139
int configurationId,
139140
ProxyTargets proxyTargets,
141+
List<string> targets,
140142
HostServices hostServices,
141143
BuildRequestDataFlags buildRequestDataFlags = BuildRequestDataFlags.None,
142144
RequestedProjectState requestedProjectState = null,
143145
int projectContextId = BuildEventContext.InvalidProjectContextId)
144146
: this(submissionId, nodeRequestId, configurationId, hostServices, buildRequestDataFlags, requestedProjectState, projectContextId)
145147
{
146148
_proxyTargets = proxyTargets;
147-
_targets = proxyTargets.ProxyTargetToRealTargetMap.Keys.ToList();
149+
_targets = targets;
148150

149151
// Only root requests can have proxy targets.
150152
_parentGlobalRequestId = InvalidGlobalRequestId;

src/Build/BackEnd/Shared/BuildRequestConfiguration.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -745,13 +745,6 @@ public List<string> GetTargetsUsedToBuildRequest(BuildRequest request)
745745
ErrorUtilities.VerifyThrow(_projectInitialTargets != null, "Initial targets have not been set.");
746746
ErrorUtilities.VerifyThrow(_projectDefaultTargets != null, "Default targets have not been set.");
747747

748-
if (request.ProxyTargets != null)
749-
{
750-
ErrorUtilities.VerifyThrow(
751-
CollectionHelpers.SetEquivalent(request.Targets, request.ProxyTargets.ProxyTargetToRealTargetMap.Keys),
752-
"Targets must be same as proxy targets");
753-
}
754-
755748
List<string> initialTargets = _projectInitialTargets;
756749
List<string> nonInitialTargets = (request.Targets.Count == 0) ? _projectDefaultTargets : request.Targets;
757750

0 commit comments

Comments
 (0)