@cratis/pi 7.102.0 → 7.103.1
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.
- package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
- package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
- package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
- package/package/corpus/hooks/README.md +11 -1
- package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
- package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
- package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
- package/package/corpus/rules/profiles.md +10 -1
- package/package/corpus/rules/pull-requests.md +2 -0
- package/package/corpus/rules/secrets.md +4 -0
- package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
- package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
- package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
- package/package/corpus/skills/binlog-generation/LICENSE +23 -0
- package/package/corpus/skills/binlog-generation/NOTICE +22 -0
- package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
- package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
- package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
- package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
- package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
- package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
- package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
- package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
- package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
- package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
- package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
- package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
- package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
- package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
- package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
- package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +26 -1
- package/package/corpus/skills/cratis-screenplay-projections/SKILL.md +17 -0
- package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/SKILL.md +17 -0
- package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +8 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +1 -1
- package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet-example.md +1 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/references/fast-path.md +7 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/references/traps.md +1 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +40 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/references/response-continuations.md +1 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/references/worked-flow.md +1 -0
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +3 -2
- package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
- package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
- package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
- package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
- package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
- package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
- package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
- package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
- package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
- package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
- package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
- package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
- package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
- package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
- package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
- package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
- package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
- package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
- package/package/corpus/skills/dump-collect/LICENSE +23 -0
- package/package/corpus/skills/dump-collect/NOTICE +40 -0
- package/package/corpus/skills/dump-collect/SKILL.md +97 -0
- package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
- package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
- package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
- package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
- package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
- package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
- package/package/corpus/skills/extension-points/LICENSE +23 -0
- package/package/corpus/skills/extension-points/NOTICE +18 -0
- package/package/corpus/skills/extension-points/SKILL.md +248 -0
- package/package/corpus/skills/filter-syntax/LICENSE +23 -0
- package/package/corpus/skills/filter-syntax/NOTICE +17 -0
- package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
- package/package/corpus/skills/including-generated-files/LICENSE +23 -0
- package/package/corpus/skills/including-generated-files/NOTICE +17 -0
- package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
- package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
- package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
- package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
- package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
- package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
- package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
- package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
- package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
- package/package/corpus/skills/msbuild/LICENSE +23 -0
- package/package/corpus/skills/msbuild/NOTICE +55 -0
- package/package/corpus/skills/msbuild/SKILL.md +117 -0
- package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
- package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
- package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
- package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
- package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
- package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
- package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
- package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
- package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
- package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
- package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
- package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
- package/package/corpus/skills/orleans/LICENSE +21 -0
- package/package/corpus/skills/orleans/NOTICE +64 -0
- package/package/corpus/skills/orleans/SKILL.md +140 -0
- package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
- package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
- package/package/corpus/skills/orleans/references/examples.md +66 -0
- package/package/corpus/skills/orleans/references/grain-api.md +784 -0
- package/package/corpus/skills/orleans/references/grains.md +75 -0
- package/package/corpus/skills/orleans/references/hosting.md +71 -0
- package/package/corpus/skills/orleans/references/implementation.md +591 -0
- package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
- package/package/corpus/skills/orleans/references/patterns.md +811 -0
- package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
- package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
- package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
- package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
- package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
- package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
- package/package/corpus/skills/platform-detection/LICENSE +23 -0
- package/package/corpus/skills/platform-detection/NOTICE +17 -0
- package/package/corpus/skills/platform-detection/SKILL.md +230 -0
- package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
- package/package/corpus/skills/profiling/LICENSE +21 -0
- package/package/corpus/skills/profiling/NOTICE +50 -0
- package/package/corpus/skills/profiling/SKILL.md +144 -0
- package/package/corpus/skills/profiling/references/commands.md +439 -0
- package/package/corpus/skills/profiling/references/patterns.md +586 -0
- package/package/corpus/skills/profiling/references/profiling.md +190 -0
- package/package/corpus/skills/run-tests/LICENSE +23 -0
- package/package/corpus/skills/run-tests/NOTICE +19 -0
- package/package/corpus/skills/run-tests/SKILL.md +305 -0
- package/package/corpus/skills/target-authoring/LICENSE +23 -0
- package/package/corpus/skills/target-authoring/NOTICE +17 -0
- package/package/corpus/skills/target-authoring/SKILL.md +165 -0
- package/package/corpus/skills/vectorization/LICENSE +23 -0
- package/package/corpus/skills/vectorization/NOTICE +17 -0
- package/package/corpus/skills/vectorization/SKILL.md +189 -0
- package/package/profile-catalog.json +85 -1
- 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 "$(IntermediateOutputPath)generated.cs"" />
|
|
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.
|