Skip to content
69 changes: 69 additions & 0 deletions src/Build.UnitTests/BackEnd/ResultsCache_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,75 @@ public void TestRetrieveSubsetTargetsFromResult()
Assert.Equal(BuildResultCode.Success, response.Results.OverallResult);
}

[Fact]
public void TestCacheOnDifferentBuildFlagsPerRequest()
{
string targetName = "testTarget1";
int submissionId = 1;
int nodeRequestId = 0;
int configurationId = 1;

BuildRequest requestWithNoBuildDataFlags = new BuildRequest(
submissionId,
nodeRequestId,
configurationId,
new string[1] { targetName } /* escapedTargets */,
null /* hostServices */,
BuildEventContext.Invalid /* parentBuildEventContext */,
null /* parentRequest */,
BuildRequestDataFlags.None);

BuildRequest requestWithProvideProjectStateAfterBuildFlag = new BuildRequest(
submissionId,
nodeRequestId,
configurationId,
new string[1] { targetName } /* escapedTargets */,
null /* hostServices */,
BuildEventContext.Invalid /* parentBuildEventContext */,
null /* parentRequest */,
BuildRequestDataFlags.ProvideProjectStateAfterBuild);

BuildRequest requestWithNoBuildDataFlags2 = new BuildRequest(
submissionId,
nodeRequestId,
configurationId,
new string[1] { targetName } /* escapedTargets */,
null /* hostServices */,
BuildEventContext.Invalid /* parentBuildEventContext */,
null /* parentRequest */,
BuildRequestDataFlags.None);

BuildResult resultForRequestWithNoBuildDataFlags = new(requestWithNoBuildDataFlags);
resultForRequestWithNoBuildDataFlags.AddResultsForTarget(targetName, BuildResultUtilities.GetEmptySucceedingTargetResult());
ResultsCache cache = new();
cache.AddResult(resultForRequestWithNoBuildDataFlags);

ResultsCacheResponse cacheResponseForRequestWithNoBuildDataFlags = cache.SatisfyRequest(
requestWithNoBuildDataFlags,
new List<string>(),
new List<string>(new string[] { targetName }),
skippedResultsDoNotCauseCacheMiss: false);

ResultsCacheResponse cacheResponseWithProvideProjectStateAfterBuild = cache.SatisfyRequest(
requestWithProvideProjectStateAfterBuildFlag,
new List<string>(),
new List<string>(new string[] { targetName }),
skippedResultsDoNotCauseCacheMiss: false);

ResultsCacheResponse cacheResponseForRequestWithNoBuildDataFlags2 = cache.SatisfyRequest(
requestWithNoBuildDataFlags2,
new List<string>(),
new List<string>(new string[] { targetName }),
skippedResultsDoNotCauseCacheMiss: false);

Assert.Equal(ResultsCacheResponseType.Satisfied, cacheResponseForRequestWithNoBuildDataFlags.Type);

// Because ProvideProjectStateAfterBuildFlag was provided as a part of BuildRequest
Assert.Equal(ResultsCacheResponseType.NotSatisfied, cacheResponseWithProvideProjectStateAfterBuild.Type);

Assert.Equal(ResultsCacheResponseType.Satisfied, cacheResponseForRequestWithNoBuildDataFlags2.Type);
}

[Fact]
public void TestClearResultsCache()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,10 @@ public void SubmitBuildRequest(BuildRequest request)
// Grab the results from the requested configuration
IResultsCache cache = (IResultsCache)_componentHost.GetComponent(BuildComponentType.ResultsCache);
BuildResult result = cache.GetResultsForConfiguration(request.ConfigurationId);
BuildResult resultToReport = new BuildResult(request, result, null);
BuildResult resultToReport = new BuildResult(request, result, null)
{
BuildRequestDataFlags = request.BuildRequestDataFlags,
};
BuildRequestConfiguration config = ((IConfigCache)_componentHost.GetComponent(BuildComponentType.ConfigCache))[request.ConfigurationId];

// Retrieve the config if it has been cached, since this would contain our instance data. It is safe to do this outside of a lock
Expand Down
13 changes: 8 additions & 5 deletions src/Build/BackEnd/Components/Caching/ResultsCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,15 @@ public ResultsCacheResponse SatisfyRequest(BuildRequest request, List<string> co
if (_resultsByConfiguration.TryGetValue(request.ConfigurationId, out BuildResult allResults))
{
// Check for targets explicitly specified.
bool explicitTargetsSatisfied = CheckResults(allResults, request.Targets, response.ExplicitTargetsToBuild, skippedResultsDoNotCauseCacheMiss);
bool explicitTargetsSatisfied = CheckResults(request.BuildRequestDataFlags, allResults, request.Targets, response.ExplicitTargetsToBuild, skippedResultsDoNotCauseCacheMiss);

if (explicitTargetsSatisfied)
{
// All of the explicit targets, if any, have been satisfied
response.Type = ResultsCacheResponseType.Satisfied;

// Check for the initial targets. If we don't know what the initial targets are, we assume they are not satisfied.
if (configInitialTargets == null || !CheckResults(allResults, configInitialTargets, null, skippedResultsDoNotCauseCacheMiss))
if (configInitialTargets == null || !CheckResults(request.BuildRequestDataFlags, allResults, configInitialTargets, null, skippedResultsDoNotCauseCacheMiss))
{
response.Type = ResultsCacheResponseType.NotSatisfied;
}
Expand All @@ -181,7 +181,7 @@ public ResultsCacheResponse SatisfyRequest(BuildRequest request, List<string> co
{
// Check for the default target, if necessary. If we don't know what the default targets are, we
// assume they are not satisfied.
if (configDefaultTargets == null || !CheckResults(allResults, configDefaultTargets, null, skippedResultsDoNotCauseCacheMiss))
if (configDefaultTargets == null || !CheckResults(request.BuildRequestDataFlags, allResults, configDefaultTargets, null, skippedResultsDoNotCauseCacheMiss))
{
response.Type = ResultsCacheResponseType.NotSatisfied;
}
Expand Down Expand Up @@ -295,18 +295,21 @@ internal static IBuildComponent CreateComponent(BuildComponentType componentType
/// <summary>
/// Looks for results for the specified targets.
/// </summary>
/// <param name="buildRequestDataFlags">The current request build flags.</param>
/// <param name="result">The result to examine</param>
/// <param name="targets">The targets to search for</param>
/// <param name="targetsMissingResults">An optional list to be populated with missing targets</param>
/// <param name="skippedResultsAreOK">If true, a status of "skipped" counts as having valid results
/// for that target. Otherwise, a skipped target is treated as equivalent to a missing target.</param>
/// <returns>False if there were missing results, true otherwise.</returns>
private static bool CheckResults(BuildResult result, List<string> targets, HashSet<string> targetsMissingResults, bool skippedResultsAreOK)
private static bool CheckResults(BuildRequestDataFlags buildRequestDataFlags, BuildResult result, List<string> targets, HashSet<string> targetsMissingResults, bool skippedResultsAreOK)
{
bool returnValue = true;
foreach (string target in targets)
{
if (!result.HasResultsForTarget(target) || (result[target].ResultCode == TargetResultCode.Skipped && !skippedResultsAreOK))
if (!result.HasResultsForTarget(target)
|| (result[target].ResultCode == TargetResultCode.Skipped && !skippedResultsAreOK)
|| result.BuildRequestDataFlags != buildRequestDataFlags)
{
if (targetsMissingResults != null)
{
Expand Down
6 changes: 6 additions & 0 deletions src/Build/BackEnd/Shared/BuildResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,12 @@ public ProjectInstance ProjectStateAfterBuild
set => _projectStateAfterBuild = value;
}

/// <summary>
/// Gets or sets the flags that provide additional control over the build request.
/// See <see cref="Execution.BuildRequestDataFlags"/> for examples of the available flags.
/// </summary>
public BuildRequestDataFlags BuildRequestDataFlags { get; set; }

/// <summary>
/// Returns the node packet type.
/// </summary>
Expand Down