Skip to content

Commit cdc79ce

Browse files
authored
Touch up-to-date files to assist incremental builds (#116)
1 parent b833fff commit cdc79ce

File tree

5 files changed

+60
-23
lines changed

5 files changed

+60
-23
lines changed

src/Common/Caching/LocalCacheStateManager.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,26 @@ internal async Task<List<KeyValuePair<string, ContentHash>>> GetOutOfDateFilesAs
8787

8888
List<KeyValuePair<string, ContentHash>> outOfDateFiles = new(nodeBuildResult.Outputs.Count);
8989

90+
// When touching files, use the same timestamp for every file to ensure we don't end up with some outputs with slightly different timestamps, which may lead to missed incrementality.
91+
DateTime fileTimestamp = DateTime.Now;
92+
9093
foreach (KeyValuePair<string, ContentHash> kvp in nodeBuildResult.Outputs)
9194
{
9295
string relativeFilePath = kvp.Key;
9396
ContentHash contentHash = kvp.Value;
94-
if (!IsFileUpToDate(context, depFile, relativeFilePath, contentHash))
97+
if (IsFileUpToDate(context, depFile, relativeFilePath, contentHash))
98+
{
99+
// Avoid touching the reference assembly as incremental build functionality heavily depends on this file not being updated when it does not change.
100+
if (string.Equals(relativeFilePath, nodeContext.ReferenceAssemblyRelativePath, StringComparison.OrdinalIgnoreCase))
101+
{
102+
continue;
103+
}
104+
105+
// Touch the file to ensure incremental builds understand that the file is up to date
106+
string absoluteFilePath = Path.Combine(_repoRoot, relativeFilePath);
107+
File.SetLastWriteTime(absoluteFilePath, fileTimestamp);
108+
}
109+
else
95110
{
96111
outOfDateFiles.Add(kvp);
97112
}

src/Common/MSBuildCachePluginBase.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,15 @@ private async Task BeginBuildInnerAsync(CacheContext context, PluginLoggerBase l
293293
nodeDependencies.Add(node, dependencies);
294294

295295
NodeDescriptor nodeDescriptor = _nodeDescriptorFactory.Create(node.ProjectInstance);
296-
NodeContext nodeContext = new(Settings.LogDirectory, node.ProjectInstance, dependencies, parserInfo.ProjectFileRelativePath, nodeDescriptor.FilteredGlobalProperties, inputs, targetNames);
296+
NodeContext nodeContext = new(
297+
Settings.LogDirectory,
298+
node.ProjectInstance,
299+
dependencies,
300+
parserInfo.ProjectFileRelativePath,
301+
nodeDescriptor.FilteredGlobalProperties,
302+
inputs,
303+
parserInfo.ReferenceAssemblyRelativePath,
304+
targetNames);
297305

298306
dumpParserInfoTasks.Add(Task.Run(() => DumpParserInfoAsync(logger, nodeContext, parserInfo), cancellationToken));
299307
_nodeContexts.Add(nodeDescriptor, nodeContext);

src/Common/NodeContext.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ namespace Microsoft.MSBuildCache;
1212

1313
public sealed class NodeContext
1414
{
15-
private static readonly byte[] PropertyHashDelimiter = new byte[] { 0x01 };
16-
private static readonly byte[] PropertyValueHashDelimiter = new byte[] { 0x02 };
15+
private static readonly byte[] PropertyHashDelimiter = [0x01];
16+
private static readonly byte[] PropertyValueHashDelimiter = [0x02];
1717

1818
private readonly string _logDirectory;
1919
private bool _logDirectoryCreated;
@@ -25,6 +25,7 @@ public NodeContext(
2525
string projectFileRelativePath,
2626
IReadOnlyDictionary<string, string> filteredGlobalProperties,
2727
IReadOnlyList<string> inputs,
28+
string? referenceAssemblyRelativePath,
2829
HashSet<string> targetNames)
2930
{
3031
Id = GenerateId(projectFileRelativePath, filteredGlobalProperties);
@@ -34,6 +35,7 @@ public NodeContext(
3435
ProjectFileRelativePath = projectFileRelativePath;
3536
FilteredGlobalProperties = filteredGlobalProperties;
3637
Inputs = inputs;
38+
ReferenceAssemblyRelativePath = referenceAssemblyRelativePath;
3739
TargetNames = targetNames;
3840
}
3941

@@ -64,6 +66,8 @@ public string LogDirectory
6466

6567
public IReadOnlyList<string> Inputs { get; }
6668

69+
public string? ReferenceAssemblyRelativePath { get; }
70+
6771
public HashSet<string> TargetNames { get; }
6872

6973
public DateTime? StartTimeUtc { get; private set; }

src/Common/Parsing/Parser.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Diagnostics;
7+
using System.IO;
68
using System.Linq;
79
using Microsoft.Build.Execution;
810
using Microsoft.Build.Experimental.ProjectCache;
@@ -12,7 +14,10 @@
1214

1315
namespace Microsoft.MSBuildCache.Parsing;
1416

15-
internal record class ParserInfo(string ProjectFileRelativePath, IReadOnlyList<PredictedInput> Inputs);
17+
internal record class ParserInfo(
18+
string ProjectFileRelativePath,
19+
IReadOnlyList<PredictedInput> Inputs,
20+
string? ReferenceAssemblyRelativePath);
1621

1722
internal sealed class Parser
1823
{
@@ -37,7 +42,7 @@ public IReadOnlyDictionary<ProjectGraphNode, ParserInfo> Parse(ProjectGraph grap
3742
continue;
3843
}
3944

40-
ProjectPredictionCollector predictionCollector = new(node, _repoRoot);
45+
ProjectPredictionCollector predictionCollector = new(node);
4146
predictionCollectorForProjects.Add(node.ProjectInstance, predictionCollector);
4247
}
4348

@@ -53,8 +58,17 @@ public IReadOnlyDictionary<ProjectGraphNode, ParserInfo> Parse(ProjectGraph grap
5358
var parserInfoForNodes = new Dictionary<ProjectGraphNode, ParserInfo>(predictionCollectorForProjects.Count);
5459
foreach (KeyValuePair<ProjectInstance, ProjectPredictionCollector> kvp in predictionCollectorForProjects)
5560
{
61+
ProjectInstance projectInstance = kvp.Key;
5662
ProjectPredictionCollector predictionCollector = kvp.Value;
57-
parserInfoForNodes.Add(predictionCollector.Node, predictionCollector.ToParserInfo());
63+
64+
string projectFilePath = projectInstance.FullPath;
65+
string projectFileRelativePath = projectFilePath.MakePathRelativeTo(_repoRoot) ?? throw new InvalidOperationException($"Project \"{projectFilePath}\" is not under the repo root \"{_repoRoot}\"");
66+
67+
string targetRefPath = projectInstance.GetPropertyValue("TargetRefPath");
68+
string? referenceAssemblyPath = !string.IsNullOrEmpty(targetRefPath) ? Path.Combine(projectInstance.Directory, targetRefPath) : null;
69+
70+
ParserInfo parserInfo = new(projectFileRelativePath, predictionCollector.Inputs, referenceAssemblyPath);
71+
parserInfoForNodes.Add(predictionCollector.Node, parserInfo);
5872
}
5973

6074
return parserInfoForNodes;

src/Common/Parsing/ProjectPredictionCollector.cs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,35 @@ namespace Microsoft.MSBuildCache.Parsing;
1414
internal sealed class ProjectPredictionCollector : IProjectPredictionCollector
1515
{
1616
private static readonly char[] DirectorySeparatorChars = { Path.DirectorySeparatorChar };
17-
private readonly string _repoRoot;
1817
private readonly string _projectDirectory;
19-
private readonly string _projectFileRelativePath;
2018

2119
// A cache of paths (relative or absolute) to their absolute form.
2220
// This is scoped per build file since the paths are relative to different directories.
2321
private readonly Dictionary<string, string> _absolutePathFileCache = new(StringComparer.OrdinalIgnoreCase);
2422

2523
private readonly ConcurrentDictionary<string, PredictedInput> _inputs = new(StringComparer.OrdinalIgnoreCase);
2624

27-
public ProjectPredictionCollector(ProjectGraphNode node, string repoRoot)
25+
public ProjectPredictionCollector(ProjectGraphNode node)
2826
{
2927
Node = node;
30-
_repoRoot = repoRoot;
31-
32-
string projectFilePath = node.ProjectInstance.FullPath;
33-
_projectDirectory = Path.GetDirectoryName(projectFilePath)!;
34-
35-
_projectFileRelativePath = projectFilePath.MakePathRelativeTo(_repoRoot) ?? throw new ArgumentException($"Project \"{projectFilePath}\" is not under the repo root \"{repoRoot}\"", nameof(node));
28+
_projectDirectory = Path.GetDirectoryName(node.ProjectInstance.FullPath)!;
3629
}
3730

3831
public ProjectGraphNode Node { get; }
3932

40-
public ParserInfo ToParserInfo()
33+
public IReadOnlyList<PredictedInput> Inputs
4134
{
42-
var inputs = new PredictedInput[_inputs.Count];
43-
int inputIndex = 0;
44-
foreach (KeyValuePair<string, PredictedInput> kvp in _inputs)
35+
get
4536
{
46-
inputs[inputIndex++] = kvp.Value;
47-
}
37+
var inputs = new PredictedInput[_inputs.Count];
38+
int inputIndex = 0;
39+
foreach (KeyValuePair<string, PredictedInput> kvp in _inputs)
40+
{
41+
inputs[inputIndex++] = kvp.Value;
42+
}
4843

49-
return new ParserInfo(_projectFileRelativePath, inputs);
44+
return inputs;
45+
}
5046
}
5147

5248
public void AddInputFile(string path, ProjectInstance projectInstance, string predictorName)

0 commit comments

Comments
 (0)