Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,60 @@ public void RunningProxyBuildsOnOutOfProcNodesShouldIssueWarning(bool disableInp
logger.AssertMessageCount("MSB4274", 1);
}

[Fact]
public async Task ProxyTargetsToSameTarget()
{
const string ProjectContent = """
<Project>
<Target Name="SomeTarget">
<Message Text="SomeTarget running" />
</Target>
<Target Name="ProxyTarget">
<Message Text="ProxyTarget running" />
</Target>
<Target Name="SomeOtherTarget">
<Message Text="SomeOtherTarget running" />
</Target>
</Project>
""";
TransientTestFile project = _env.CreateFile($"project.proj", ProjectContent);

BuildParameters buildParameters = new()
{
ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(
new ConfigurableMockCache
{
GetCacheResultImplementation = (_, _, _) =>
{
// A common scenario is to get a request for N targets, but only some of them can be handled by the cache.
// In this case, proxy targets may reference themselves.
return Task.FromResult(
CacheResult.IndicateCacheHit(
new ProxyTargets(
new Dictionary<string, string>
{
{ "ProxyTarget", "SomeTarget" },
{ "SomeOtherTarget", "SomeOtherTarget" },
})));
}
}),
};

MockLogger logger;
using (Helpers.BuildManagerSession buildSession = new(_env, buildParameters))
{
logger = buildSession.Logger;
BuildResult buildResult = await buildSession.BuildProjectFileAsync(project.Path, new[] { "SomeTarget", "SomeOtherTarget" });

buildResult.Exception.ShouldBeNull();
buildResult.ShouldHaveSucceeded();
}

logger.BuildMessageEvents.Select(i => i.Message).ShouldNotContain("SomeTarget running");
logger.BuildMessageEvents.Select(i => i.Message).ShouldContain("ProxyTarget running");
logger.BuildMessageEvents.Select(i => i.Message).ShouldContain("SomeOtherTarget running");
}

private void AssertCacheBuild(
ProjectGraph graph,
GraphCacheResponse testData,
Expand Down
6 changes: 4 additions & 2 deletions src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1215,12 +1215,14 @@ BuildResult CopyTargetResultsFromProxyTargetsToRealTargets(BuildResult resultFro
// Update the results cache.
cachedResult.AddResultsForTarget(
realTarget,
proxyTargetResult);
proxyTargetResult,
allowReplacement: true);

// Update and return this one because TargetBuilder.BuildTargets did some mutations on it not present in the cached result.
resultFromTargetBuilder.AddResultsForTarget(
realTarget,
proxyTargetResult);
proxyTargetResult,
allowReplacement: true);
}

return resultFromTargetBuilder;
Expand Down
12 changes: 10 additions & 2 deletions src/Build/BackEnd/Shared/BuildResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,15 @@ public ITargetResult this[string target]
/// </summary>
/// <param name="target">The target to which these results apply.</param>
/// <param name="result">The results for the target.</param>
public void AddResultsForTarget(string target, TargetResult result)
public void AddResultsForTarget(string target, TargetResult result) => AddResultsForTarget(target, result, allowReplacement: false);

/// <summary>
/// Adds the results for the specified target to this result collection.
/// </summary>
/// <param name="target">The target to which these results apply.</param>
/// <param name="result">The results for the target.</param>
/// <param name="allowReplacement">Whether to allow replacing existing results.</param>
internal void AddResultsForTarget(string target, TargetResult result, bool allowReplacement)
{
ErrorUtilities.VerifyThrowArgumentNull(target, nameof(target));
ErrorUtilities.VerifyThrowArgumentNull(result, nameof(result));
Expand All @@ -488,7 +496,7 @@ public void AddResultsForTarget(string target, TargetResult result)
_resultsByTarget ??= CreateTargetResultDictionary(1);
}

if (_resultsByTarget.TryGetValue(target, out TargetResult targetResult))
if (!allowReplacement && _resultsByTarget.TryGetValue(target, out TargetResult targetResult))
{
ErrorUtilities.VerifyThrow(targetResult.ResultCode == TargetResultCode.Skipped, "Items already exist for target {0}.", target);
}
Expand Down