Skip to content

Commit 0aa8c5f

Browse files
authored
Add question flag to "question" the build if it is incremental (#8012)
This PR adds "question" switch to msbuild.exe that will error out if a target or a task fails incremental check. Targets will fail if both Inputs and Outputs are present and not skip. Tasks changes are individually modified to support the interface IIncrementalTask, which sets the question boolean. Each task will need to be updated this interface take part. I have started with the following tasks and fixed some of the issues within MSBuild enlistment. And there are more, see the notes below. Tasks updated: ToolTask, Copy, MakeDir, Touch, WriteLinesTofile, RemoveDir, DownloadFile, Move, ZipDirectory, Unzip, GenerateResource, GenerateBindingRedirects. Using question investigate incremental issues is orders of magnitude easier. Reading the logs is simpler and repros are more consistent. In this PR, it includes a few fixes to the common targets which address some issues. Fixes #7348
1 parent 82fa6f4 commit 0aa8c5f

File tree

79 files changed

+1352
-140
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+1352
-140
lines changed

eng/BootStrapMSBuild.targets

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -122,63 +122,82 @@
122122

123123
<!-- Copy in props and targets from the machine-installed MSBuildExtensionsPath -->
124124
<Copy SourceFiles="@(InstalledVersionedExtensions)"
125-
DestinationFiles="@(InstalledVersionedExtensions->'$(BootstrapDestination)$(TargetMSBuildToolsVersion)\%(RecursiveDir)%(Filename)%(Extension)')" />
125+
DestinationFiles="@(InstalledVersionedExtensions->'$(BootstrapDestination)$(TargetMSBuildToolsVersion)\%(RecursiveDir)%(Filename)%(Extension)')"
126+
SkipUnchangedFiles="true" />
126127
<Copy SourceFiles="@(SdkResolverFiles)"
127-
DestinationFiles="@(SdkResolverFiles->'$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\SdkResolvers\Microsoft.DotNet.MSBuildSdkResolver\%(RecursiveDir)%(Filename)%(Extension)')" />
128+
DestinationFiles="@(SdkResolverFiles->'$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\SdkResolvers\Microsoft.DotNet.MSBuildSdkResolver\%(RecursiveDir)%(Filename)%(Extension)')"
129+
SkipUnchangedFiles="true" />
128130

129131
<Copy SourceFiles="@(InstalledMicrosoftExtensions)"
130-
DestinationFiles="@(InstalledMicrosoftExtensions->'$(BootstrapDestination)Microsoft\%(RecursiveDir)%(Filename)%(Extension)')" />
132+
DestinationFiles="@(InstalledMicrosoftExtensions->'$(BootstrapDestination)Microsoft\%(RecursiveDir)%(Filename)%(Extension)')"
133+
SkipUnchangedFiles="true" />
131134

132135
<Copy SourceFiles="@(InstalledSdks)"
133136
DestinationFiles="@(InstalledSdks -> '$(BootstrapDestination)Sdks\%(RecursiveDir)%(Filename)%(Extension)')"
134-
Condition="'$(MonoBuild)' != 'true'" />
137+
Condition="'$(MonoBuild)' != 'true'"
138+
SkipUnchangedFiles="true" />
135139
<Copy SourceFiles="@(InstalledSdks)"
136140
DestinationFiles="@(InstalledSdks -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\Sdks\%(RecursiveDir)%(Filename)%(Extension)')"
137-
Condition="'$(MonoBuild)' == 'true'" />
141+
Condition="'$(MonoBuild)' == 'true'"
142+
SkipUnchangedFiles="true" />
138143

139144
<Copy SourceFiles="@(InstalledStaticAnalysisTools)"
140-
DestinationFiles="@(InstalledStaticAnalysisTools -> '$(BootstrapDestination)..\Team Tools\Static Analysis Tools\%(RecursiveDir)%(Filename)%(Extension)')" />
145+
DestinationFiles="@(InstalledStaticAnalysisTools -> '$(BootstrapDestination)..\Team Tools\Static Analysis Tools\%(RecursiveDir)%(Filename)%(Extension)')"
146+
SkipUnchangedFiles="true" />
141147

142148
<Copy SourceFiles="@(InstalledNuGetFiles)"
143-
DestinationFiles="@(InstalledNuGetFiles->'$(BootstrapDestination)Microsoft\NuGet\%(Filename)%(Extension)')" />
149+
DestinationFiles="@(InstalledNuGetFiles->'$(BootstrapDestination)Microsoft\NuGet\%(Filename)%(Extension)')"
150+
SkipUnchangedFiles="true" />
144151

145152
<Copy Condition="'$(MonoBuild)' != 'true'"
146153
SourceFiles="@(_NuGetRuntimeDependencies)"
147-
DestinationFolder="$(BootstrapDestination)..\Common7\IDE\CommonExtensions\Microsoft\NuGet\" />
154+
DestinationFolder="$(BootstrapDestination)..\Common7\IDE\CommonExtensions\Microsoft\NuGet\"
155+
SkipUnchangedFiles="true" />
148156
<Copy Condition="'$(MonoBuild)' == 'true'"
149157
SourceFiles="@(_NuGetRuntimeDependencies)"
150-
DestinationFolder="$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin" />
158+
DestinationFolder="$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin"
159+
SkipUnchangedFiles="true" />
151160

152161
<Copy SourceFiles="@(NuGetSdkResolverManifest)"
153-
DestinationFolder="$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\SdkResolvers\Microsoft.Build.NuGetSdkResolver" />
162+
DestinationFolder="$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\SdkResolvers\Microsoft.Build.NuGetSdkResolver"
163+
SkipUnchangedFiles="true" />
154164

155165
<!-- Delete shim projects, because they point where we can't follow. -->
156166
<!-- It would be better to just not copy these. -->
157167
<Delete Files="@(ShimTargets->'$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\%(FileName)%(Extension)')" />
158168

159169
<!-- Copy our binaries -->
160170
<Copy SourceFiles="@(FreshlyBuiltBinaries)"
161-
DestinationFiles="@(FreshlyBuiltBinaries -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\%(RecursiveDir)%(Filename)%(Extension)')" />
171+
DestinationFiles="@(FreshlyBuiltBinaries -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\%(RecursiveDir)%(Filename)%(Extension)')"
172+
SkipUnchangedFiles="true" />
173+
162174
<Copy SourceFiles="@(RoslynBinaries)"
163-
DestinationFiles="@(RoslynBinaries -> '$(BootstrapDestination)15.0\Bin\Roslyn\%(RecursiveDir)%(Filename)%(Extension)')" />
175+
DestinationFiles="@(RoslynBinaries -> '$(BootstrapDestination)15.0\Bin\Roslyn\%(RecursiveDir)%(Filename)%(Extension)')"
176+
SkipUnchangedFiles="true" />
164177

165178
<!-- Copy our binaries to the x64 location. -->
166-
<Copy SourceFiles="@(FreshlyBuiltBinariesx64)"
167-
DestinationFiles="@(FreshlyBuiltBinariesx64 -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\amd64\%(RecursiveDir)%(Filename)%(Extension)')" />
179+
<Copy SourceFiles="@(FreshlyBuiltBinariesx64)"
180+
DestinationFiles="@(FreshlyBuiltBinariesx64 -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\amd64\%(RecursiveDir)%(Filename)%(Extension)')"
181+
SkipUnchangedFiles="true" />
168182

169183
<!-- Copy our binaries to the arm64 location. -->
170-
<Copy SourceFiles="@(FreshlyBuiltBinariesArm64)"
171-
DestinationFiles="@(FreshlyBuiltBinariesArm64 -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\arm64\%(RecursiveDir)%(Filename)%(Extension)')" />
184+
<Copy SourceFiles="@(FreshlyBuiltBinariesArm64)"
185+
DestinationFiles="@(FreshlyBuiltBinariesArm64 -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\arm64\%(RecursiveDir)%(Filename)%(Extension)')"
186+
SkipUnchangedFiles="true" />
172187

173188
<!-- Copy our freshly-built props and targets, overwriting anything we copied from the machine -->
174189
<Copy SourceFiles="@(FreshlyBuiltRootProjects)"
175-
DestinationFiles="@(FreshlyBuiltRootProjects -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\%(Filename)%(Extension)')" />
190+
DestinationFiles="@(FreshlyBuiltRootProjects -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\%(Filename)%(Extension)')"
191+
SkipUnchangedFiles="true" />
176192
<Copy SourceFiles="@(FreshlyBuiltProjects)"
177-
DestinationFiles="@(FreshlyBuiltProjects -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\%(RecursiveDir)%(Filename)%(Extension)')" />
193+
DestinationFiles="@(FreshlyBuiltProjects -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\%(RecursiveDir)%(Filename)%(Extension)')"
194+
SkipUnchangedFiles="true" />
178195
<Copy SourceFiles="@(FreshlyBuiltProjects)"
179-
DestinationFiles="@(FreshlyBuiltProjects -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\amd64\%(RecursiveDir)%(Filename)%(Extension)')" />
196+
DestinationFiles="@(FreshlyBuiltProjects -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\amd64\%(RecursiveDir)%(Filename)%(Extension)')"
197+
SkipUnchangedFiles="true" />
180198
<Copy SourceFiles="@(FreshlyBuiltProjects)"
181-
DestinationFiles="@(FreshlyBuiltProjects -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\arm64\%(RecursiveDir)%(Filename)%(Extension)')" />
199+
DestinationFiles="@(FreshlyBuiltProjects -> '$(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin\arm64\%(RecursiveDir)%(Filename)%(Extension)')"
200+
SkipUnchangedFiles="true" />
182201

183202
</Target>
184203

src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,41 @@ Skipping target ""Build"" because all output files are up-to-date with respect t
236236
}
237237
}
238238

239+
[Fact]
240+
public void TestErrorForSkippedTargetInputsAndOutputs()
241+
{
242+
string projectContents = @"
243+
<Project>
244+
<Target Name=""Build"" Inputs=""a.txt;b.txt"" Outputs=""c.txt"">
245+
<Message Text=""test"" Importance=""High"" />
246+
</Target>
247+
</Project>";
248+
249+
using (var env = TestEnvironment.Create())
250+
{
251+
var buildParameters = new BuildParameters()
252+
{
253+
Question = true,
254+
};
255+
256+
using (var buildSession = new Helpers.BuildManagerSession(env, buildParameters))
257+
{
258+
var files = env.CreateTestProjectWithFiles(projectContents, new[] { "a.txt", "b.txt", "c.txt" });
259+
var fileA = new FileInfo(files.CreatedFiles[0]);
260+
var fileB = new FileInfo(files.CreatedFiles[1]);
261+
var fileC = new FileInfo(files.CreatedFiles[2]);
262+
263+
var now = DateTime.UtcNow;
264+
fileA.LastWriteTimeUtc = now - TimeSpan.FromSeconds(10);
265+
fileB.LastWriteTimeUtc = now + TimeSpan.FromSeconds(10);
266+
fileC.LastWriteTimeUtc = now;
267+
268+
var result = buildSession.BuildProjectFile(files.ProjectFile);
269+
result.OverallResult.ShouldBe(BuildResultCode.Failure);
270+
}
271+
}
272+
}
273+
239274
/// <summary>
240275
/// Ensure that skipped targets only infer outputs once
241276
/// </summary>

src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ private DependencyAnalysisResult PerformDependencyAnalysisTestHelper(
572572
ItemBucket itemBucket = new ItemBucket(null, null, new Lookup(itemsByName, new PropertyDictionary<ProjectPropertyInstance>()), 0);
573573
TargetUpToDateChecker analyzer = new TargetUpToDateChecker(p, p.Targets["Build"], _mockHost, BuildEventContext.Invalid);
574574

575-
return analyzer.PerformDependencyAnalysis(itemBucket, out changedTargetInputs, out upToDateTargetInputs);
575+
return analyzer.PerformDependencyAnalysis(itemBucket, false, out changedTargetInputs, out upToDateTargetInputs);
576576
}
577577
finally
578578
{

src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ public void CanceledTasksDoNotLogMSB4181()
164164
Loggers = new ILogger[] { logger },
165165
EnableNodeReuse = false
166166
};
167-
;
168167

169168
BuildRequestData data = new BuildRequestData(project.CreateProjectInstance(), new string[] { "test" }, collection.HostServices);
170169
manager.BeginBuild(_parameters);

src/Build/BackEnd/BuildManager/BuildParameters.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ public class BuildParameters : ITranslatable
205205
/// </summary>
206206
private bool _logInitialPropertiesAndItems;
207207

208+
private bool _question;
209+
208210
/// <summary>
209211
/// The settings used to load the project under build
210212
/// </summary>
@@ -303,6 +305,7 @@ internal BuildParameters(BuildParameters other, bool resetEnvironment = false)
303305
_outputResultsCacheFile = other._outputResultsCacheFile;
304306
DiscardBuildResults = other.DiscardBuildResults;
305307
LowPriority = other.LowPriority;
308+
Question = other.Question;
306309
ProjectCacheDescriptor = other.ProjectCacheDescriptor;
307310
}
308311

@@ -808,6 +811,15 @@ public string OutputResultsCacheFile
808811
/// </summary>
809812
public bool LowPriority { get; set; }
810813

814+
/// <summary>
815+
/// Gets or sets a value that will error when the build process fails an incremental check.
816+
/// </summary>
817+
public bool Question
818+
{
819+
get => _question;
820+
set => _question = value;
821+
}
822+
811823
/// <summary>
812824
/// Gets or sets the project cache description to use for all <see cref="BuildSubmission"/> or <see cref="GraphBuildSubmission"/>
813825
/// in addition to any potential project caches described in each project.
@@ -871,6 +883,7 @@ void ITranslatable.Translate(ITranslator translator)
871883
translator.Translate(ref _logInitialPropertiesAndItems);
872884
translator.TranslateEnum(ref _projectLoadSettings, (int)_projectLoadSettings);
873885
translator.Translate(ref _interactive);
886+
translator.Translate(ref _question);
874887
translator.TranslateEnum(ref _projectIsolationMode, (int)_projectIsolationMode);
875888

876889
// ProjectRootElementCache is not transmitted.

src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ internal async Task ExecuteTarget(ITaskBuilder taskBuilder, BuildRequestEntry re
462462
// UNDONE: (Refactor) Refactor TargetUpToDateChecker to take a logging context, not a logging service.
463463
MSBuildEventSource.Log.TargetUpToDateStart();
464464
TargetUpToDateChecker dependencyAnalyzer = new TargetUpToDateChecker(requestEntry.RequestConfiguration.Project, _target, targetLoggingContext.LoggingService, targetLoggingContext.BuildEventContext);
465-
DependencyAnalysisResult dependencyResult = dependencyAnalyzer.PerformDependencyAnalysis(bucket, out changedTargetInputs, out upToDateTargetInputs);
465+
DependencyAnalysisResult dependencyResult = dependencyAnalyzer.PerformDependencyAnalysis(bucket, _host.BuildParameters.Question, out changedTargetInputs, out upToDateTargetInputs);
466466
MSBuildEventSource.Log.TargetUpToDateStop((int)dependencyResult);
467467

468468
switch (dependencyResult)
@@ -471,6 +471,13 @@ internal async Task ExecuteTarget(ITaskBuilder taskBuilder, BuildRequestEntry re
471471
case DependencyAnalysisResult.FullBuild:
472472
case DependencyAnalysisResult.IncrementalBuild:
473473
case DependencyAnalysisResult.SkipUpToDate:
474+
if (dependencyResult != DependencyAnalysisResult.SkipUpToDate && _host.BuildParameters.Question && !string.IsNullOrEmpty(_target.Inputs) && !string.IsNullOrEmpty(_target.Outputs))
475+
{
476+
targetSuccess = false;
477+
aggregateResult = aggregateResult.AggregateResult(new WorkUnitResult(WorkUnitResultCode.Canceled, WorkUnitActionCode.Stop, null));
478+
break;
479+
}
480+
474481
// Create the lookups used to hold the current set of properties and items
475482
lookupForInference = bucket.Lookup;
476483
lookupForExecution = bucket.Lookup.Clone();

src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ private string TargetOutputSpecification
118118
/// incremental build is needed.
119119
/// </remarks>
120120
/// <param name="bucket"></param>
121+
/// <param name="question"></param>
121122
/// <param name="changedTargetInputs"></param>
122123
/// <param name="upToDateTargetInputs"></param>
123124
/// <returns>
@@ -129,6 +130,7 @@ private string TargetOutputSpecification
129130
/// </returns>
130131
internal DependencyAnalysisResult PerformDependencyAnalysis(
131132
ItemBucket bucket,
133+
bool question,
132134
out ItemDictionary<ProjectItemInstance> changedTargetInputs,
133135
out ItemDictionary<ProjectItemInstance> upToDateTargetInputs)
134136
{
@@ -252,7 +254,7 @@ internal DependencyAnalysisResult PerformDependencyAnalysis(
252254
}
253255
}
254256

255-
LogReasonForBuildingTarget(result);
257+
LogReasonForBuildingTarget(result, question);
256258

257259
return result;
258260
}
@@ -261,15 +263,23 @@ internal DependencyAnalysisResult PerformDependencyAnalysis(
261263
/// Does appropriate logging to indicate why this target is being built fully or partially.
262264
/// </summary>
263265
/// <param name="result"></param>
264-
private void LogReasonForBuildingTarget(DependencyAnalysisResult result)
266+
/// <param name="question"></param>
267+
private void LogReasonForBuildingTarget(DependencyAnalysisResult result, bool question)
265268
{
266269
// Only if we are not logging just critical events should we be logging the details
267270
if (!_loggingService.OnlyLogCriticalEvents)
268271
{
269272
if (result == DependencyAnalysisResult.FullBuild && _dependencyAnalysisDetail.Count > 0)
270273
{
271-
// For the full build decision the are three possible outcomes
272-
_loggingService.LogComment(_buildEventContext, MessageImportance.Low, "BuildTargetCompletely", _targetToAnalyze.Name);
274+
if (question)
275+
{
276+
_loggingService.LogError(_buildEventContext, new BuildEventFileInfo(String.Empty), "BuildTargetCompletely", _targetToAnalyze.Name);
277+
}
278+
else
279+
{
280+
// For the full build decision, there are three possible outcomes
281+
_loggingService.LogComment(_buildEventContext, MessageImportance.Low, "BuildTargetCompletely", _targetToAnalyze.Name);
282+
}
273283

274284
foreach (DependencyAnalysisLogDetail logDetail in _dependencyAnalysisDetail)
275285
{
@@ -279,8 +289,15 @@ private void LogReasonForBuildingTarget(DependencyAnalysisResult result)
279289
}
280290
else if (result == DependencyAnalysisResult.IncrementalBuild)
281291
{
282-
// For the partial build decision the are three possible outcomes
283-
_loggingService.LogComment(_buildEventContext, MessageImportance.Normal, "BuildTargetPartially", _targetToAnalyze.Name);
292+
if (question)
293+
{
294+
_loggingService.LogError(_buildEventContext, new BuildEventFileInfo(String.Empty), "BuildTargetPartially", _targetToAnalyze.Name);
295+
}
296+
else
297+
{
298+
// For the partial build decision the are three possible outcomes
299+
_loggingService.LogComment(_buildEventContext, MessageImportance.Normal, "BuildTargetPartially", _targetToAnalyze.Name);
300+
}
284301
foreach (DependencyAnalysisLogDetail logDetail in _dependencyAnalysisDetail)
285302
{
286303
string reason = GetIncrementalBuildReason(logDetail);

src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,11 @@ bool ITaskExecutionHost.SetTaskParameters(IDictionary<string, (string, ElementLo
369369
}
370370
}
371371

372+
if (this.TaskInstance is IIncrementalTask incrementalTask)
373+
{
374+
incrementalTask.FailIfNotIncremental = _buildComponentHost.BuildParameters.Question;
375+
}
376+
372377
if (taskInitialized)
373378
{
374379
// See if any required properties were not set

src/Directory.Build.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
</ItemGroup>
130130
</Target>
131131

132-
<Target Name="CreateTypeLib" BeforeTargets="AfterBuild" Condition="'$(BuildingInsideVisualStudio)' != 'true' and '$(CreateTlb)' == 'true' and $([MSBuild]::IsOSPlatform('windows')) and '$(TargetFrameworkIdentifier)' == '.NETFramework' and '$(MSBuildRuntimeType)' != 'Core'">
132+
<Target Name="CreateTypeLib" BeforeTargets="AfterBuild" Inputs="$(TargetPath)" Outputs="$(TargetDir)$(TargetName).tlb;$(TargetDir)x64\$(TargetName).tlb" Condition="'$(BuildingInsideVisualStudio)' != 'true' and '$(CreateTlb)' == 'true' and $([MSBuild]::IsOSPlatform('windows')) and '$(TargetFrameworkIdentifier)' == '.NETFramework' and '$(MSBuildRuntimeType)' != 'Core'">
133133
<PropertyGroup>
134134
<TlbExpPath>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToDotNetFrameworkSdkFile('tlbexp.exe'))</TlbExpPath>
135135
<!-- Provide a mechanism for turning on verbose TlbExp output for diagnosing issues -->

src/Framework/IIncrementalTask.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable disable
5+
6+
namespace Microsoft.Build.Framework
7+
{
8+
/// <summary>
9+
/// Interface for tasks which is supports incrementality.
10+
/// </summary>
11+
/// <remarks>The tasks implementing this interface should return false to stop the build when in <see cref="FailIfNotIncremental"/> is true and task is not fully incremental. Try to provide helpful information to diagnose incremental behavior.</remarks>
12+
public interface IIncrementalTask
13+
{
14+
/// <summary>
15+
/// Set by MSBuild when Question flag is used.
16+
/// </summary>
17+
bool FailIfNotIncremental { set; }
18+
}
19+
}

0 commit comments

Comments
 (0)