@cratis/pi 7.102.0 → 7.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
  2. package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
  3. package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
  4. package/package/corpus/hooks/README.md +11 -1
  5. package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
  6. package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
  7. package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
  8. package/package/corpus/rules/profiles.md +10 -1
  9. package/package/corpus/rules/pull-requests.md +2 -0
  10. package/package/corpus/rules/secrets.md +4 -0
  11. package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
  12. package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
  13. package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
  14. package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
  15. package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
  16. package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
  17. package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
  18. package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
  19. package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
  20. package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
  21. package/package/corpus/skills/binlog-generation/LICENSE +23 -0
  22. package/package/corpus/skills/binlog-generation/NOTICE +22 -0
  23. package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
  24. package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
  25. package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
  26. package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
  27. package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
  28. package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
  29. package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
  30. package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
  31. package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
  32. package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
  33. package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
  34. package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
  35. package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
  36. package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
  37. package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
  38. package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
  39. package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
  40. package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
  41. package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
  42. package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
  43. package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
  44. package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
  45. package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
  46. package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
  47. package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
  48. package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
  49. package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
  50. package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
  51. package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
  52. package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
  53. package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
  54. package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
  55. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
  56. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
  57. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
  58. package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
  59. package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
  60. package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
  61. package/package/corpus/skills/dump-collect/LICENSE +23 -0
  62. package/package/corpus/skills/dump-collect/NOTICE +40 -0
  63. package/package/corpus/skills/dump-collect/SKILL.md +97 -0
  64. package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
  65. package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
  66. package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
  67. package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
  68. package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
  69. package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
  70. package/package/corpus/skills/extension-points/LICENSE +23 -0
  71. package/package/corpus/skills/extension-points/NOTICE +18 -0
  72. package/package/corpus/skills/extension-points/SKILL.md +248 -0
  73. package/package/corpus/skills/filter-syntax/LICENSE +23 -0
  74. package/package/corpus/skills/filter-syntax/NOTICE +17 -0
  75. package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
  76. package/package/corpus/skills/including-generated-files/LICENSE +23 -0
  77. package/package/corpus/skills/including-generated-files/NOTICE +17 -0
  78. package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
  79. package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
  80. package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
  81. package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
  82. package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
  83. package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
  84. package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
  85. package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
  86. package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
  87. package/package/corpus/skills/msbuild/LICENSE +23 -0
  88. package/package/corpus/skills/msbuild/NOTICE +55 -0
  89. package/package/corpus/skills/msbuild/SKILL.md +117 -0
  90. package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
  91. package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
  92. package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
  93. package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
  94. package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
  95. package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
  96. package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
  97. package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
  98. package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
  99. package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
  100. package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
  101. package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
  102. package/package/corpus/skills/orleans/LICENSE +21 -0
  103. package/package/corpus/skills/orleans/NOTICE +64 -0
  104. package/package/corpus/skills/orleans/SKILL.md +140 -0
  105. package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
  106. package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
  107. package/package/corpus/skills/orleans/references/examples.md +66 -0
  108. package/package/corpus/skills/orleans/references/grain-api.md +784 -0
  109. package/package/corpus/skills/orleans/references/grains.md +75 -0
  110. package/package/corpus/skills/orleans/references/hosting.md +71 -0
  111. package/package/corpus/skills/orleans/references/implementation.md +591 -0
  112. package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
  113. package/package/corpus/skills/orleans/references/patterns.md +811 -0
  114. package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
  115. package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
  116. package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
  117. package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
  118. package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
  119. package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
  120. package/package/corpus/skills/platform-detection/LICENSE +23 -0
  121. package/package/corpus/skills/platform-detection/NOTICE +17 -0
  122. package/package/corpus/skills/platform-detection/SKILL.md +230 -0
  123. package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
  124. package/package/corpus/skills/profiling/LICENSE +21 -0
  125. package/package/corpus/skills/profiling/NOTICE +50 -0
  126. package/package/corpus/skills/profiling/SKILL.md +144 -0
  127. package/package/corpus/skills/profiling/references/commands.md +439 -0
  128. package/package/corpus/skills/profiling/references/patterns.md +586 -0
  129. package/package/corpus/skills/profiling/references/profiling.md +190 -0
  130. package/package/corpus/skills/run-tests/LICENSE +23 -0
  131. package/package/corpus/skills/run-tests/NOTICE +19 -0
  132. package/package/corpus/skills/run-tests/SKILL.md +305 -0
  133. package/package/corpus/skills/target-authoring/LICENSE +23 -0
  134. package/package/corpus/skills/target-authoring/NOTICE +17 -0
  135. package/package/corpus/skills/target-authoring/SKILL.md +165 -0
  136. package/package/corpus/skills/vectorization/LICENSE +23 -0
  137. package/package/corpus/skills/vectorization/NOTICE +17 -0
  138. package/package/corpus/skills/vectorization/SKILL.md +189 -0
  139. package/package/profile-catalog.json +85 -1
  140. package/package.json +1 -1
@@ -0,0 +1,281 @@
1
+ # MSBuild extension points
2
+
3
+ ## Contents
4
+
5
+ - CustomBefore / CustomAfter hooks
6
+ - Wildcard import directories
7
+ - Import gating and control properties
8
+ - NuGet package build extension layout
9
+ - Source tree vs packed layout
10
+ - Import guard pattern
11
+ - Directory.Build discovery
12
+ - Creating your own extension point
13
+ - Verify the extension
14
+
15
+ Based on the original `extension-points` skill: import hooks, wildcard extensions, control
16
+ properties, NuGet build assets, import guards, and Directory.Build discovery.
17
+ Use it for a specific import/hook failure or requested authoring modernization.
18
+
19
+ ## CustomBefore / CustomAfter hooks
20
+
21
+ Common MSBuild targets expose hooks such as:
22
+
23
+ ```xml
24
+ <PropertyGroup>
25
+ <CustomBeforeMicrosoftCommonTargets Condition="'$(CustomBeforeMicrosoftCommonTargets)' == ''">
26
+ $(MSBuildExtensionsPath)\v$(MSBuildToolsVersion)\Custom.Before.Microsoft.Common.targets
27
+ </CustomBeforeMicrosoftCommonTargets>
28
+ </PropertyGroup>
29
+
30
+ <Import Project="$(CustomBeforeMicrosoftCommonTargets)"
31
+ Condition="'$(CustomBeforeMicrosoftCommonTargets)' != '' and Exists('$(CustomBeforeMicrosoftCommonTargets)')" />
32
+ <Import Project="$(CustomAfterMicrosoftCommonTargets)"
33
+ Condition="'$(CustomAfterMicrosoftCommonTargets)' != '' and Exists('$(CustomAfterMicrosoftCommonTargets)')" />
34
+ ```
35
+
36
+ Inspect the actual importing file and version; do not assume every targets file exposes the same
37
+ hooks. Although `Import` supports semicolon-separated paths, the surrounding `Exists()` tests
38
+ the whole property before the import runs. If any optional path in the list is absent, the
39
+ guard can skip the entire import, including a valid new hook. Do not append a new path blindly
40
+ to `CustomBeforeMicrosoftCommonTargets`.
41
+
42
+ For a hook with one existing optional path, use an aggregation file. In an early
43
+ `Directory.Build.props`, before the common targets consume the hook, preserve that path (or
44
+ its optional installed default) and set the hook to the aggregation file:
45
+
46
+ ```xml
47
+ <PropertyGroup>
48
+ <_RepoPreviousCommonTargetsHook>$(CustomBeforeMicrosoftCommonTargets)</_RepoPreviousCommonTargetsHook>
49
+ <_RepoPreviousCommonTargetsHook Condition="'$(_RepoPreviousCommonTargetsHook)' == ''">$(MSBuildExtensionsPath)\v$(MSBuildToolsVersion)\Custom.Before.Microsoft.Common.targets</_RepoPreviousCommonTargetsHook>
50
+ <CustomBeforeMicrosoftCommonTargets>$(MSBuildThisFileDirectory)BuildHooks.targets</CustomBeforeMicrosoftCommonTargets>
51
+ </PropertyGroup>
52
+ ```
53
+
54
+ Place `BuildHooks.targets` and the required `MyExtension.targets` beside that props file:
55
+
56
+ ```xml
57
+ <!-- BuildHooks.targets -->
58
+ <Project>
59
+ <Import Project="$(_RepoPreviousCommonTargetsHook)"
60
+ Condition="'$(_RepoPreviousCommonTargetsHook)' != '' and Exists('$(_RepoPreviousCommonTargetsHook)')" />
61
+ <Import Project="$(MSBuildThisFileDirectory)MyExtension.targets" />
62
+ </Project>
63
+ ```
64
+
65
+ The optional prior hook has its own guard; its absence cannot suppress the new required hook.
66
+ If the prior value is already a list, inspect its contract and give each optional import its
67
+ own guard in the aggregation file instead of passing that list to a single `Exists()`.
68
+ Verify both the present and absent optional-hook cases, preserving the intended import order.
69
+
70
+ Guard **optional** imports; required imports should fail explicitly when missing. Defaults that
71
+ refer to installed MSBuild extensions can include a toolset/version segment for side-by-side
72
+ installations. Do not overwrite a configured hook or disable all imports to hide one bad file.
73
+
74
+ For target execution rather than file imports, prefer uniquely named targets with
75
+ `BeforeTargets`/`AfterTargets`, or extend a documented `...DependsOn` property while preserving its
76
+ existing value. Define the list before a hook extends it, and check for dependency cycles.
77
+
78
+ ## Wildcard import directories
79
+
80
+ Wildcard imports are sorted by filename:
81
+
82
+ ```xml
83
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Imports\Microsoft.Common.props\ImportBefore\*"
84
+ Condition="'$(ImportByWildcardBeforeMicrosoftCommonProps)' == 'true'
85
+ and Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Imports\Microsoft.Common.props\ImportBefore')" />
86
+ ```
87
+
88
+ | Property | Typical location | Scope |
89
+ | --- | --- | --- |
90
+ | `MSBuildUserExtensionsPath` | Per-user MSBuild extension directory | User-specific |
91
+ | `MSBuildExtensionsPath` | MSBuild installation | Machine/toolset |
92
+ | `MSBuildProjectExtensionsPath` | Intermediate directory, normally `obj` | Project/NuGet |
93
+
94
+ Use names such as `01-defaults.props` and `02-overrides.props` when order is part of the contract.
95
+ Check the expanded imports. A machine- or user-wide extension affects more than the current repo.
96
+
97
+ ## Import gating and control properties
98
+
99
+ Standard extension/discovery points use control properties with defaults such as:
100
+
101
+ ```xml
102
+ <PropertyGroup>
103
+ <ImportByWildcardBeforeMicrosoftCommonProps
104
+ Condition="'$(ImportByWildcardBeforeMicrosoftCommonProps)' == ''">true</ImportByWildcardBeforeMicrosoftCommonProps>
105
+ <ImportDirectoryBuildProps
106
+ Condition="'$(ImportDirectoryBuildProps)' == ''">true</ImportDirectoryBuildProps>
107
+ </PropertyGroup>
108
+ ```
109
+
110
+ | Property | Controlled behavior |
111
+ | --- | --- |
112
+ | `ImportDirectoryBuildProps` | Directory.Build.props discovery/import |
113
+ | `ImportDirectoryBuildTargets` | Directory.Build.targets discovery/import |
114
+ | `ImportProjectExtensionProps` | Generated project-extension props, including NuGet |
115
+ | `ImportProjectExtensionTargets` | Generated project-extension targets, including NuGet |
116
+ | `ImportByWildcardBefore*` | Matching ImportBefore extension point |
117
+ | `ImportByWildcardAfter*` | Matching ImportAfter extension point |
118
+
119
+ Set a control property before its consumer evaluates. These switches change build behavior:
120
+ disabling NuGet-generated imports or shared settings is not a general correctness/performance fix.
121
+
122
+ ## NuGet package build extension layout
123
+
124
+ Automatically imported entry files use the package ID as their basename:
125
+
126
+ ```text
127
+ MyPackage\
128
+ build\
129
+ MyPackage.props
130
+ MyPackage.targets
131
+ buildTransitive\
132
+ MyPackage.props
133
+ MyPackage.targets
134
+ ```
135
+
136
+ - `build` serves direct consumers.
137
+ - `buildTransitive` can flow to transitive consumers with PackageReference.
138
+ - `buildMultiTargeting` serves the outer cross-targeting build, not ordinary per-TFM execution.
139
+ - Props are imported early and targets late. Framework-specific files can be placed in TFM
140
+ subdirectories; NuGet selects compatible assets rather than necessarily an exact TFM match.
141
+ - A differently named entry file can cause `NU5129` and not be imported. Supporting files can
142
+ have other names when an entry file explicitly imports them. Inspect the generated
143
+ `*.nuget.g.props`/`*.nuget.g.targets` and restored assets to confirm discovery.
144
+
145
+ Package build files must not redefine restore inputs such as `TargetFramework`, `PackageReference`,
146
+ or `PackageVersion`. See the
147
+ [NuGet build-file conventions](https://learn.microsoft.com/en-us/nuget/concepts/msbuild-props-and-targets).
148
+
149
+ ### Forwarding chain
150
+
151
+ When sharing implementation, keep a clear `buildTransitive -> build -> shared` chain instead of
152
+ duplicating direct/transitive logic or forwarding blindly to `buildMultiTargeting`.
153
+ For per-TFM layouts, include the matched asset folder segment. Derive it from the importing file,
154
+ not the consumer's `TargetFramework`: a `net10.0` project may consume the package's `net8.0` folder.
155
+
156
+ ```xml
157
+ <!-- Inside packed buildTransitive/<tfm>/MyPackage.props. -->
158
+ <Import Project="$(MSBuildThisFileDirectory)..\..\build\$([System.IO.Path]::GetFileName($([System.IO.Path]::GetDirectoryName('$(MSBuildThisFileDirectory)'))))\MyPackage.props" />
159
+ ```
160
+
161
+ Only use that path for that actual packed layout. Non-TFM-specific assets need a different
162
+ relative path, not an invented framework directory.
163
+
164
+ ## Source tree vs packed layout
165
+
166
+ A source folder is not necessarily the layout of its `.nupkg`. Before reporting a missing
167
+ `build` or `buildTransitive` import:
168
+
169
+ 1. Inspect the effective `NuspecFile`, `NuspecBasePath`, and `NuspecProperties` for the original
170
+ pack configuration, including imported assignments and command-line global properties.
171
+ Follow the configured relative or absolute `NuspecFile` even when it is outside the project
172
+ directory and its parent. Resolve its `<file src="..." target="...">` mappings using the
173
+ effective base path and property substitutions. An unavailable configured file is missing
174
+ evidence, not proof that the packed import is absent.
175
+ 2. When no custom nuspec is configured, inspect `.nuspec` files in the project directory and its
176
+ **immediate parent** as a bounded discovery fallback; do not search unrelated ancestors.
177
+ 3. Inspect the project's `Pack`/`PackagePath` item metadata and SDK packaging properties such as
178
+ `BuildOutputTargetFolder` and `IncludeBuildOutput`.
179
+ 4. Project the destination paths, including renamed files and per-TFM copies. Prefer inspecting
180
+ the actual package when one is available.
181
+ 5. Flag the import only if its target is absent from both the applicable source layout and the
182
+ packed contract. A required file guaranteed by packaging must not gain an `Exists()` guard
183
+ that would silently hide a broken package.
184
+
185
+ One shared source can be packed to multiple framework-specific destinations:
186
+
187
+ ```xml
188
+ <files>
189
+ <file src="buildTransitive\common\MyAdapter.props" target="buildTransitive\net462\MyAdapter.props" />
190
+ <file src="buildTransitive\common\MyAdapter.props" target="buildTransitive\net8.0\MyAdapter.props" />
191
+ </files>
192
+ ```
193
+
194
+ Equivalent SDK pack metadata can place the same source at each destination:
195
+
196
+ ```xml
197
+ <ItemGroup>
198
+ <None Include="buildTransitive\common\MyAdapter.props"
199
+ Pack="true" PackagePath="buildTransitive\net462\MyAdapter.props" />
200
+ <None Include="buildTransitive\common\MyAdapter.props"
201
+ Pack="true" PackagePath="buildTransitive\net8.0\MyAdapter.props" />
202
+ </ItemGroup>
203
+ ```
204
+
205
+ The absence of those TFM directories in source is not a missing-import defect. Preserve required
206
+ package contracts; do not add an existence guard to silently conceal a broken published layout.
207
+ See [msbuild-antipatterns](msbuild-antipatterns.md) AP-13.
208
+
209
+ ## Import guard pattern
210
+
211
+ An imported file can record successful initialization:
212
+
213
+ ```xml
214
+ <!-- At the end of the relevant props initialization. -->
215
+ <PropertyGroup>
216
+ <MicrosoftCommonPropsHasBeenImported>true</MicrosoftCommonPropsHasBeenImported>
217
+ </PropertyGroup>
218
+
219
+ <!-- In the corresponding targets file. -->
220
+ <Import Project="Microsoft.Common.props"
221
+ Condition="'$(MicrosoftCommonPropsHasBeenImported)' != 'true'" />
222
+ ```
223
+
224
+ This pattern supports projects that enter through the targets file. Do not set another SDK's
225
+ guard to pretend its initialization happened; custom SDKs should use their own guard property.
226
+
227
+ ## Directory.Build discovery
228
+
229
+ Only the nearest `Directory.Build.props` and nearest `Directory.Build.targets` are discovered
230
+ automatically. Nested files must explicitly import parents when inheritance is intended:
231
+
232
+ ```xml
233
+ <Project>
234
+ <PropertyGroup>
235
+ <_ParentPropsPath>$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..\'))</_ParentPropsPath>
236
+ </PropertyGroup>
237
+ <Import Project="$(_ParentPropsPath)" Condition="'$(_ParentPropsPath)' != ''" />
238
+ </Project>
239
+ ```
240
+
241
+ Start the search above the current file to avoid recursion. Whether an absent parent is optional
242
+ is a repository contract; fail if it is required. Defaults belong before their consumers, while
243
+ values depending on project-defined properties must be evaluated late enough to see them.
244
+ In particular, early property conditions and later item conditions differ; see AP-21 in
245
+ [additional anti-patterns](antipatterns/additional-antipatterns.md).
246
+
247
+ ## Creating your own extension point
248
+
249
+ For a custom SDK whose required `MySDK.props` sets `MySDKPropsImported`, a targets file can expose
250
+ before/after imports and target hooks:
251
+
252
+ ```xml
253
+ <Project>
254
+ <Import Project="$(MSBuildThisFileDirectory)MySDK.props"
255
+ Condition="'$(MySDKPropsImported)' != 'true'" />
256
+ <PropertyGroup>
257
+ <CustomBeforeMySDK Condition="'$(CustomBeforeMySDK)' == ''">$(MSBuildProjectDirectory)\MySDK.Before.targets</CustomBeforeMySDK>
258
+ <CustomAfterMySDK Condition="'$(CustomAfterMySDK)' == ''">$(MSBuildProjectDirectory)\MySDK.After.targets</CustomAfterMySDK>
259
+ <MySDKBuildDependsOn Condition="'$(MySDKBuildDependsOn)' == ''">BeforeMySDKBuild;CoreMySDKBuild;AfterMySDKBuild</MySDKBuildDependsOn>
260
+ </PropertyGroup>
261
+ <Import Project="$(CustomBeforeMySDK)" Condition="Exists('$(CustomBeforeMySDK)')" />
262
+ <Target Name="MySDKBuild" DependsOnTargets="$(MySDKBuildDependsOn)" />
263
+ <Target Name="BeforeMySDKBuild" />
264
+ <Target Name="CoreMySDKBuild" />
265
+ <Target Name="AfterMySDKBuild" />
266
+ <Import Project="$(CustomAfterMySDK)" Condition="Exists('$(CustomAfterMySDK)')" />
267
+ </Project>
268
+ ```
269
+
270
+ Initialize the dependency list **before** importing extensions so a before-hook's appended
271
+ targets are not overwritten afterward. An extension can preserve the list and add its own
272
+ target, or use a uniquely named `BeforeTargets`/`AfterTargets` hook. Test that prior extensions,
273
+ the core work, and the new work all remain scheduled.
274
+
275
+ ## Verify the extension
276
+
277
+ Check the import graph (a preprocessed project can show import ordering), evaluated values, and
278
+ target execution with the original scenario. Preprocessing is not a substitute for evaluated
279
+ property/item values. Exercise both single-targeted and multi-targeted consumers when affected,
280
+ and direct/transitive consumers of package assets. Confirm pre-existing hooks still run, new hooks
281
+ run exactly where intended, and optional versus required missing files behave differently.
@@ -0,0 +1,365 @@
1
+ # Optimizing MSBuild Incremental Builds
2
+
3
+ ## Contents
4
+
5
+ - How MSBuild Incremental Build Works
6
+ - Why Incremental Builds Break (Top Causes)
7
+ - Diagnosing "Why Did This Rebuild?"
8
+ - FileWrites and Clean Build
9
+ - Visual Studio Fast Up-to-Date Check
10
+ - Making Custom Targets Incremental
11
+ - Performance Summary and Preprocess
12
+ - Common Fixes
13
+ - Validation
14
+
15
+ Source: original incremental build skill.
16
+
17
+ ## How MSBuild Incremental Build Works
18
+
19
+ MSBuild can skip targets whose outputs are up to date, reducing work on subsequent
20
+ ordinary builds.
21
+
22
+ - **`Inputs` and `Outputs`:** file-producing targets use file timestamps to
23
+ determine freshness. Outputs must exist and be at least as new as the relevant
24
+ inputs. One-to-one item transforms can support partial incremental execution
25
+ rather than requiring every output to be rebuilt.
26
+ - **Without file-based checks:** a target with neither attribute has no such
27
+ freshness test when reached. It can still be skipped by conditions or because
28
+ it already ran for that project instance. Cheap orchestration or discovery
29
+ targets may correctly execute on every invocation that reaches them.
30
+ - **No `Incremental` target attribute:** `Incremental="false"` is not a valid
31
+ MSBuild `Target` attribute. Do not add it to force execution.
32
+ - **Timestamps, not hashes:** touching an input can invalidate the target even
33
+ without content changes. Conversely, unchanged timestamps can hide changes.
34
+
35
+ ```xml
36
+ <!-- A one-to-one input/output mapping -->
37
+ <Target Name="Transform"
38
+ Inputs="@(TransformFiles)"
39
+ Outputs="@(TransformFiles->'$(OutputPath)%(Filename).out')">
40
+ <!-- Existing transformation task goes here -->
41
+ </Target>
42
+
43
+ <!-- No file-based freshness check when this target is invoked -->
44
+ <Target Name="PrintMessage">
45
+ <Message Text="This runs when the target is reached" />
46
+ </Target>
47
+ ```
48
+
49
+ Give transformed outputs distinct paths if inputs can have duplicate basenames.
50
+ Do not add fictitious output files merely to skip necessary work.
51
+
52
+ ## Why Incremental Builds Break (Top Causes)
53
+
54
+ 1. **Missing Inputs/Outputs on file-producing custom targets.** Declare meaningful
55
+ file dependencies for expensive repeatable work, not indiscriminately for every
56
+ orchestration target.
57
+ 2. **Volatile output paths.** Timestamps, build counters, or GUIDs in a path prevent
58
+ finding the prior output.
59
+ 3. **Writes outside declared Outputs.** A target may skip even when an undeclared
60
+ generated file is missing or stale; track all required outputs.
61
+ 4. **Missing FileWrites registration.** Standard Clean tracking may miss custom
62
+ generated files. Merely putting a file under `obj` does not register it.
63
+ 5. **Glob changes.** Additions and removals alter item membership, but general
64
+ timestamp checks do not remember the previous set. Removing an input, or
65
+ adding an older input, need not invalidate a custom target. Use appropriate
66
+ input-set tracking and obsolete-output cleanup when the output depends on
67
+ membership.
68
+ 6. **Property changes.** MSBuild does not automatically compare prior property
69
+ values used by a generator. Track relevant values explicitly. Changed
70
+ configuration/TFM/RID paths can select missing outputs, but returning to an
71
+ already-built configuration can reuse its isolated output set.
72
+ 7. **NuGet package updates.** Assets and resolved reference changes can correctly
73
+ invalidate compilation and resolution work. Preserve necessary invalidation.
74
+ 8. **Compiler-server state.** A recycled `VBCSCompiler` can make a compilation that
75
+ executes slower. It does not by itself explain why an up-to-date `CoreCompile`
76
+ ran; separate execution decisions from compiler warm-up.
77
+
78
+ ## Diagnosing "Why Did This Rebuild?"
79
+
80
+ Use binlogs to identify exactly why a target executed instead of skipping.
81
+
82
+ ### Step-by-Step Using Binlog
83
+
84
+ 1. Reuse matching first/second-build artifacts if available. Otherwise follow
85
+ [binlog generation](binlog-generation.md) and capture two ordinary builds:
86
+
87
+ Choose unused explicit names for both logs; the names below are illustrative.
88
+
89
+ ```powershell
90
+ dotnet build "-bl:first.binlog"
91
+ if ($LASTEXITCODE -ne 0) { throw "First build failed; no valid incremental baseline." }
92
+ dotnet build "-bl:second.binlog"
93
+ ```
94
+
95
+ Keep configuration, TFM/RID, properties, restore policy, and inputs unchanged.
96
+ Analyze the second successful build. `Rebuild` and `--no-incremental` force
97
+ full work and cannot establish a no-change skip result.
98
+ 2. [Replay the second binlog](binlog-failure-analysis.md#replay-a-binary-log)
99
+ locally with diagnostic verbosity and a performance summary, substituting `second.binlog`
100
+ for `build.binlog`. The searches below use that command's `full-01.log`; substitute the
101
+ actual path if you chose another name. Replay the first build to a different file when comparing.
102
+ 3. Find executed/skipped targets and the accompanying reasons:
103
+
104
+ ```powershell
105
+ Select-String -Path .\full-01.log -Pattern 'Building target|Skipping target|was not skipped|is newer than output' -Context 0,5
106
+ ```
107
+
108
+ 4. Interpret messages in context (`...` stands for the target name):
109
+ - **`Building target "..." completely.`:** full target execution; read the reason
110
+ instead of assuming all outputs were missing.
111
+ - **`Building target "..." partially, because some output files are out of date
112
+ with respect to their input files.`:** partial execution for an applicable
113
+ input/output mapping.
114
+ - **`Skipping target "..." because all output files are up-to-date with respect
115
+ to the input files.`:** freshness checks permitted a skip; verify the declared
116
+ dependency set is complete.
117
+ 5. Record the precise input/output path, timestamp, missing output, or condition
118
+ responsible, for the correct project/global-property instance.
119
+
120
+ ### Additional Diagnostic Techniques
121
+
122
+ - Compare the two replayed logs for changed properties, items, paths, and reasons.
123
+ - Find expensive second-build targets:
124
+
125
+ ```powershell
126
+ Select-String -Path .\full-01.log -Pattern 'Target Performance Summary|Task Performance Summary' -Context 0,30
127
+ ```
128
+
129
+ - Aggregate/nested timings are not additive wall time. Follow orchestration waits
130
+ to actual task work using [performance diagnostics](build-perf-diagnostics.md).
131
+ - A zero-duration target that runs is not automatically a defect. Inspect its
132
+ dependencies and actual cost before changing it.
133
+ - If evaluation dominates while compilation correctly skips, use
134
+ [evaluation performance](eval-performance.md), not a fake incremental shortcut.
135
+
136
+ ## FileWrites and Clean Build
137
+
138
+ `FileWrites` lets the standard Clean infrastructure track build-generated files.
139
+ It is separate from target freshness and compiler inclusion.
140
+
141
+ - **`FileWrites`:** register owned generated files so the normal Clean pipeline
142
+ can discover them.
143
+ - **`FileWritesShareable`:** do not assume reference-counted ownership or protection
144
+ because another project still uses a file. Inspect the selected Clean targets,
145
+ locations, and shared-file ownership before relying on it.
146
+ - **Missing registration:** files can accumulate, leaving stale inputs or outputs.
147
+ A directory named `obj` is not automatic per-file tracking.
148
+
149
+ ### Pattern for Registering Generated Files
150
+
151
+ For a generator with declared input files and generated line content:
152
+
153
+ ```xml
154
+ <Target Name="MyGenerator"
155
+ Inputs="$(MSBuildProjectFullPath);@(GeneratorInput)"
156
+ Outputs="$(IntermediateOutputPath)generated.cs">
157
+ <WriteLinesToFile File="$(IntermediateOutputPath)generated.cs"
158
+ Lines="@(GeneratedLines)"
159
+ Overwrite="true" />
160
+ <ItemGroup>
161
+ <FileWrites Include="$(IntermediateOutputPath)generated.cs" />
162
+ </ItemGroup>
163
+ </Target>
164
+ ```
165
+
166
+ Ensure the directory exists and that all settings/tool/template dependencies are
167
+ modeled; project-file timestamps do not track arbitrary property values. Use
168
+ `Overwrite="true"` when replacing generated content rather than appending it.
169
+
170
+ An `ItemGroup` inside a target with an up-to-date skip can still be evaluated by
171
+ **output inference**. Thus the source pattern of registering `FileWrites` and
172
+ `Compile` inside that target is valid when the paths/items can be inferred.
173
+ Do not move it solely because the task skipped. If an item depends only on task
174
+ output that cannot be inferred, make that item's definition available separately.
175
+
176
+ Use only authorized, scoped Clean operations. Never register user-authored files
177
+ or another project's outputs merely to make cleanup more aggressive.
178
+
179
+ ## Visual Studio Fast Up-to-Date Check
180
+
181
+ Visual Studio's Fast Up-to-Date Check (FUTDC) is distinct from MSBuild's
182
+ `Inputs`/`Outputs`: it can skip invoking MSBuild entirely.
183
+
184
+ - **Fast checks:** the project system examines known inputs, outputs, copy items,
185
+ and timestamps without a full build.
186
+ - **Custom work:** custom targets, generated files, or nonstandard/dynamic items
187
+ may not be visible to that check.
188
+ - **Diagnostic comparison:** temporarily force MSBuild checks when investigating
189
+ a Visual Studio-only discrepancy:
190
+
191
+ ```xml
192
+ <PropertyGroup>
193
+ <DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
194
+ </PropertyGroup>
195
+ ```
196
+
197
+ Do not leave FUTDC disabled as a default performance fix.
198
+ - **Logging:** in Visual Studio's **Tools > Options > Projects and Solutions >
199
+ SDK-Style Projects**, enable verbose up-to-date-check logging and inspect the
200
+ Output window for the actual file/reason.
201
+ - **Common cases:** unregistered custom build actions, items added only during
202
+ target execution, and changed `Content`/`None` copy items. Diagnose FUTDC's
203
+ decision separately from MSBuild target skip reasons.
204
+
205
+ ## Making Custom Targets Incremental
206
+
207
+ The following example combines C# templates under `config\$(ConfigFlavor)` into a
208
+ generated compilation input. The small state file tracks both the selected flavor
209
+ and input membership; a plain timestamp list would miss some changes to either.
210
+ Adapt the generator step and declared dependencies to the repository's real tool.
211
+
212
+ ```xml
213
+ <PropertyGroup>
214
+ <ConfigFlavor Condition="'$(ConfigFlavor)' == ''">Default</ConfigFlavor>
215
+ </PropertyGroup>
216
+ <ItemGroup>
217
+ <ConfigInput Include="config\$(ConfigFlavor)\*.cs.in" />
218
+ </ItemGroup>
219
+
220
+ <Target Name="WriteConfigInputs">
221
+ <MakeDir Directories="$(IntermediateOutputPath)" />
222
+ <WriteLinesToFile File="$(IntermediateOutputPath)config.inputs"
223
+ Lines="Flavor=$(ConfigFlavor);@(ConfigInput->'%(FullPath)')"
224
+ Overwrite="true"
225
+ WriteOnlyWhenDifferent="true" />
226
+ <ItemGroup>
227
+ <FileWrites Include="$(IntermediateOutputPath)config.inputs" />
228
+ </ItemGroup>
229
+ </Target>
230
+
231
+ <Target Name="GenerateConfig"
232
+ DependsOnTargets="WriteConfigInputs"
233
+ Inputs="$(MSBuildProjectFullPath);@(ConfigInput);$(IntermediateOutputPath)config.inputs"
234
+ Outputs="$(IntermediateOutputPath)config.generated.cs"
235
+ BeforeTargets="CoreCompile">
236
+ <ReadLinesFromFile File="%(ConfigInput.Identity)"
237
+ Condition="'@(ConfigInput)' != ''">
238
+ <Output TaskParameter="Lines" ItemName="_ConfigLines" />
239
+ </ReadLinesFromFile>
240
+ <WriteLinesToFile File="$(IntermediateOutputPath)config.generated.cs"
241
+ Lines="@(_ConfigLines)"
242
+ Overwrite="true" />
243
+ <ItemGroup>
244
+ <FileWrites Include="$(IntermediateOutputPath)config.generated.cs" />
245
+ <Compile Include="$(IntermediateOutputPath)config.generated.cs" />
246
+ </ItemGroup>
247
+ </Target>
248
+ ```
249
+
250
+ **Key points:**
251
+
252
+ - The project file, actual input files, and state file participate in freshness.
253
+ Also declare imported generator logic, tool binaries, templates, and relevant
254
+ settings when they affect the real generator.
255
+ - `WriteOnlyWhenDifferent` prevents the **state file** from causing churn on every
256
+ build. Include every relevant property/input-set value in an unambiguous,
257
+ stable representation.
258
+ - Generated output is overwritten when regeneration is necessary. If a real
259
+ generator preserves output timestamps for identical content, use appropriate
260
+ completion-state tracking so newer inputs do not make it execute forever;
261
+ still detect missing real outputs.
262
+ - `IntermediateOutputPath` should isolate configurations/TFMs/RIDs; inspect custom
263
+ overrides. `FileWrites`, not the directory name, supplies clean registration.
264
+ - `BeforeTargets="CoreCompile"` makes the file available before compilation.
265
+ `Compile` inclusion and `FileWrites` registration can be inferred on a skip.
266
+ - Do not duplicate the generated item or rely on an evaluation-time glob to find
267
+ a file that does not yet exist.
268
+ - For per-input generated outputs, detect removal and clean obsolete **owned**
269
+ outputs as well as updating the input-set state.
270
+
271
+ ### Common Mistakes to Avoid
272
+
273
+ ```xml
274
+ <!-- Expensive generation without file-based freshness checks -->
275
+ <Target Name="BadTarget" BeforeTargets="CoreCompile">
276
+ <Exec Command="generate-code.exe" />
277
+ </Target>
278
+
279
+ <!-- Volatile output path: the next invocation cannot reuse it -->
280
+ <Target Name="BadTarget2"
281
+ Inputs="@(Compile)"
282
+ Outputs="$(OutputPath)gen_$([System.DateTime]::Now.Ticks).cs">
283
+ <Exec Command="generate-code.exe" />
284
+ </Target>
285
+
286
+ <!-- Stable-output pattern; declare the real tool/configuration inputs too -->
287
+ <Target Name="GoodTarget"
288
+ Inputs="$(MSBuildProjectFullPath);@(GeneratorInput)"
289
+ Outputs="$(IntermediateOutputPath)generated.cs"
290
+ BeforeTargets="CoreCompile">
291
+ <Exec Command="generate-code.exe -o &quot;$(IntermediateOutputPath)generated.cs&quot;" />
292
+ <ItemGroup>
293
+ <FileWrites Include="$(IntermediateOutputPath)generated.cs" />
294
+ <Compile Include="$(IntermediateOutputPath)generated.cs" />
295
+ </ItemGroup>
296
+ </Target>
297
+ ```
298
+
299
+ These external-tool patterns assume the actual generator exists and produces the
300
+ declared file. Add property/input-set tracking when needed, as in the full example.
301
+ Do not include generated outputs in their own input set.
302
+
303
+ ## Performance Summary and Preprocess
304
+
305
+ - **`-clp:PerformanceSummary`:** identifies cumulative project/target/task costs:
306
+
307
+ ```powershell
308
+ dotnet build -clp:PerformanceSummary
309
+ ```
310
+
311
+ Compare the relevant second-build work; inclusive durations are not exclusive
312
+ CPU time.
313
+ - **`-pp:preprocess.xml`:** expands imports to locate definitions and their origin:
314
+
315
+ ```powershell
316
+ dotnet msbuild .\MyProject.csproj "-pp:preprocess.xml"
317
+ ```
318
+
319
+ It is not a dump of fully evaluated property/item values. Find the definitions
320
+ of `Inputs`, `Outputs`, and imports, then inspect the matching build instance.
321
+ - Use the summary for what ran and `/pp` for where it was defined, together with
322
+ replayed skip reasons.
323
+
324
+ ## Common Fixes
325
+
326
+ - **Declare meaningful Inputs/Outputs** for repeatable file-producing work,
327
+ including missing-output recovery and all real dependencies.
328
+ - **Use stable intermediate paths** while preserving configuration/TFM/RID
329
+ isolation and avoiding output collisions.
330
+ - **Register owned generated files in FileWrites**; validate the applicable
331
+ scoped Clean behavior rather than assuming every intermediate file is tracked.
332
+ - **Avoid unnecessary volatile data.** If per-build timestamps are required,
333
+ preserve that contract and isolate their downstream impact. For Git revision
334
+ state, a project-file timestamp or `.git\HEAD` alone is insufficient: branch
335
+ refs can change without either changing. Use the repository's reliable state
336
+ mechanism or a cheap probe that writes only changed content; report probe
337
+ failures instead of freezing a stale value.
338
+ - **Use Returns for return-only item collections.** These are alternative
339
+ definitions, not two targets to install together:
340
+
341
+ ```xml
342
+ <!-- Outputs also participates in file-based freshness -->
343
+ <Target Name="GetFiles" Outputs="@(DiscoveredFiles)" />
344
+
345
+ <!-- Returns supplies items without a file-based freshness contract -->
346
+ <Target Name="GetFiles" Returns="@(DiscoveredFiles)" />
347
+ ```
348
+
349
+ - **Choose copy metadata deliberately** using
350
+ [CopyToOutputDirectory modes](copy-to-output-directory.md), especially when
351
+ consumers mutate copied files.
352
+
353
+ ## Validation
354
+
355
+ Compare equivalent ordinary builds using the
356
+ [baseline](build-perf-baseline.md). In an authorized disposable/scoped workspace,
357
+ verify: a no-change second build; input modification, addition, and removal;
358
+ property/template/tool changes; deletion of a real generated output; downstream
359
+ compilation and required diagnostics; relevant configuration/TFM/RID variants;
360
+ and approved Clean behavior. For copied content, include destination mutation.
361
+
362
+ Report the target/project instance, exact reason/file evidence, the correction,
363
+ before/after timings, and checks actually completed. A faster build that misses
364
+ required regeneration, leaves stale outputs, or changes an agreed metadata
365
+ contract is not a successful incremental-build fix.