Repository navigation
Add find-repeated-work-within-one-build skill to dotnet-msbuild #1311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
YuliiaKovalova
wants to merge
58
commits into
main
Choose a base branch
from
yuliiakovalova/find-repeated-build-work
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+4,267
−6
Open
Changes from 6 commits
Commits
Show all changes
58 commits
Select commit
Hold shift + click to select a range
4f5d351
Add find-repeated-build-work skill to dotnet-msbuild
YuliiaKovalova 39e935e
Address repeated-build-work review feedback
YuliiaKovalova edceb93
Verify repeated-work fixes remain connected to builds
YuliiaKovalova 1ba9b01
Clarify binlog capture and preferred MCP analysis
YuliiaKovalova c7f5d27
Address repeated-work review summary findings
YuliiaKovalova 5c47b1b
Close repeated-build-work grader gaps
YuliiaKovalova 1b62da9
Preserve repeated-work outputs and parallelism routing
YuliiaKovalova b7d1ec0
Exercise the submitted shared-project build end to end
YuliiaKovalova 666524f
Clarify cross-command and second-build routing boundaries
YuliiaKovalova 28339f6
Preserve unchanged shared generation timestamps
YuliiaKovalova bad8932
Check generated timestamps at the build boundary
YuliiaKovalova 5449a22
Cover generic routing and schema output preservation
YuliiaKovalova bac5703
Accept CLI properties and partition instance diagnostics
YuliiaKovalova 94e7dab
Exercise per-project generation hooks in the race grader
YuliiaKovalova fe492e1
Preserve build invocation and exercise submitted generators
YuliiaKovalova 3243b0d
Check effective Compile items and accept offline summaries
YuliiaKovalova 76d4ca8
Prioritize isolated evaluation and restore routing exclusions
YuliiaKovalova 6d1d8d8
Merge branch 'main' into yuliiakovalova/find-repeated-build-work
YuliiaKovalova ed8ff5a
Merge main and reconcile build-performance routing
YuliiaKovalova 7e39802
Fix conditional graders and broaden repeated-work eval coverage
YuliiaKovalova bc10c27
Enforce schema incrementality and correct batching savings
YuliiaKovalova 8a152de
Merge branch 'main' into yuliiakovalova/find-repeated-build-work
YuliiaKovalova 952d24c
Fix orchestrated and conditional MSBuild graders
YuliiaKovalova c0f6200
Accept implicit schema output directory creation
YuliiaKovalova d8f24a5
Merge remote-tracking branch 'origin/main' into HEAD
YuliiaKovalova 087dbd0
Migrate repeated-build-work eval to Vally 0.18
YuliiaKovalova 2bf9eca
Fix dormancy metadata lookup in grader contract test
YuliiaKovalova fd81af9
Evaluate conditional Git stamp assignments through MSBuild
YuliiaKovalova 823b6b8
Replay CI build arguments in Git stamp probe
YuliiaKovalova fcb10c5
Keep MSBuild routing menu within runtime budget
YuliiaKovalova 9a2004b
Merge branch 'main' into yuliiakovalova/find-repeated-build-work
YuliiaKovalova c0b8b62
Judge fallback guards and schema hooks by MSBuild behavior
YuliiaKovalova 0ea5362
Trim repeated-build-work PR to skill-specific scope
YuliiaKovalova feaf8ae
Validate schema import reachability through MSBuild
YuliiaKovalova 52bea49
Exercise the SDK build graph in Git-stamp grader
YuliiaKovalova 92104cf
Verify compiled attributes and unchanged-build compilation
YuliiaKovalova 157821c
Accept native revision orchestrators and renamed schema items
YuliiaKovalova 8304af9
Validate schema rewrites through native MSBuild execution
YuliiaKovalova 777c892
Reject added schemas without restricting helper imports
YuliiaKovalova 1ace78e
Accept cached shared generation and reproduce the writer race
YuliiaKovalova da63404
Evaluate equivalent revision lookups using native Git
YuliiaKovalova f97403d
Strengthen the unchanged-build dormancy boundary
YuliiaKovalova 931e9c6
Preserve grader script entry points and compilation history
YuliiaKovalova 185e834
Validate native generation without a legacy helper requirement
YuliiaKovalova 4fe6b22
Accept native clocks and SDK revision metadata
YuliiaKovalova 9a4e320
Restart shared build probes across UTC rollover
YuliiaKovalova c60fd4d
Verify stamp changes against the complete warm cache
YuliiaKovalova 414667e
Verify submitted build-info UTC defaults
YuliiaKovalova b996f90
Clarify shared-value fallback and property inheritance
YuliiaKovalova e436649
Clarify repeated-work routing and no-op decisions
YuliiaKovalova 9b4f4b9
Preserve the complete generated schema source set
YuliiaKovalova 7d7957d
Allow SDK sources beside generated schema outputs
YuliiaKovalova b208b7e
Keep submitted generated sources visible to native grading
YuliiaKovalova 5e9eaeb
Clarify the within-build repeated-work skill scope
YuliiaKovalova c875608
Observe schema compiler inputs after SDK imports
YuliiaKovalova e46cdd4
Clarify once-only execution routing boundaries
YuliiaKovalova 9ef40c0
Harden native repeated-work evaluation probes
YuliiaKovalova 3f2d9f4
Scope negligible-repeat Copy expectations to available evidence
YuliiaKovalova File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
plugins/dotnet-msbuild/skills/find-repeated-build-work/SKILL.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| --- | ||
| name: find-repeated-build-work | ||
| description: "Remove work MSBuild repeats inside one build. USE FOR: same Exec or task in every project (git stamp), performance summary calls above project count, shared project rebuilt via global properties, per-item Exec, racy run-once guards. DO NOT USE FOR: no-op rebuild reruns, restores, slow evaluation." | ||
| license: MIT | ||
| --- | ||
|
|
||
| # Find Repeated Build Work | ||
|
|
||
| MSBuild runs targets once per project instance. Work that does not depend on the project, but sits in `Directory.Build.targets` or a shared target, therefore runs once per project: a `git rev-parse` in 40 projects is 40 process launches. A project built with different global properties is a different instance, so a shared library can compile several times in one build. The binlog's `N calls` column shows both. This skill finds the repeats, separates them from work that is legitimately per project, and removes only what is safe to remove. | ||
|
|
||
| ## When to Use | ||
|
|
||
| - The same `Exec` or task (version stamping, tool probes, file generation) runs in every project | ||
| - Target or Task Performance Summary shows a call count far above the number of projects that need the work | ||
| - One project appears many times in the Project Performance Summary with different global properties | ||
| - An `Exec` spawns one process per item in a list | ||
| - A "run once per build" guard (`Exists` check, flag property) still runs repeatedly or fails with file-in-use errors under `-m` | ||
|
|
||
| ## When Not to Use | ||
|
|
||
| - Targets that run again on a second, unchanged build: use `incremental-build` | ||
| - Slow evaluation before any target runs: use `eval-performance` | ||
| - Idle nodes or a serial dependency chain: use `build-parallelism` | ||
| - Repeated restores across build steps: use `build-perf-diagnostics` | ||
| - Unranked "why is my build slow": start with `build-perf-baseline`, then `build-perf-diagnostics` | ||
|
|
||
| ## Workflow | ||
|
|
||
| ### Step 1: Count executions | ||
|
|
||
| If no binlog is supplied, use the `binlog-generation` skill to capture one: add `/bl:{}` to the original MSBuild invocation, preserving its configuration, global properties, parallelism and restore behavior. In PowerShell, quote the complete switch as `'-bl:{}'`. If a script drives the build, instrument its underlying MSBuild invocation rather than replace it with a different build command. Confirm the binlog exists and record its path. | ||
|
|
||
| For analysis, prefer the bundled **binlog MCP server** (`Microsoft.AITools.BinlogMcp`, exposed under the `binlog` MCP namespace). Discover its available tools and query project, target and task execution counts and timings from the captured binlog. Do not read the binary file as text. | ||
|
|
||
| If MCP is unavailable or its tools do not expose execution counts, replay the captured binlog to a text performance summary and read the `N calls` column of the Target and Task Performance Summaries and the Project Performance Summary: | ||
|
|
||
| ```bash | ||
| dotnet msbuild build.binlog -noconlog "-flp:v=diag;logfile=full.log;performancesummary" | ||
| ``` | ||
|
|
||
| Compare each count with the project instances that genuinely need the operation. TFM-dependent work such as `Csc` normally runs once per project and target framework; copy-local work may also run in several targets. A multi-targeted project can legitimately show one outer build plus one inner build per TFM, so four project calls for three TFMs are not automatically duplicates. Build-invariant work is different: an identical git lookup needs one computation for the build, so 38 calls in 38 projects can still be waste. Confirm that inputs, working directories and relevant properties do not change the result; identical command text alone does not prove invariance. Investigate extra instances only after accounting for required TFMs and global properties. | ||
|
|
||
| ### Step 2: Classify each candidate | ||
|
|
||
| | Pattern | Signal | Fix | Never | | ||
| |---|---|---|---| | ||
| | Invariant per-project `Exec` or task (commit hash, tool version, generated file) | `Exec` calls equal the project count and the command line is identical | Compute it once outside the per-project targets: the build script passes `-p:Name=value`, or a pre-build step or orchestrator target runs first | A flag property or `Exists(stamp)` guard: properties are per project instance and `-m` worker nodes are separate processes, so the guard does not dedupe and races | | ||
| | Same project built several times | One project path with N calls and different global properties (for example `Properties="ComponentName=%(Component.Filename)"` on an `MSBuild` task). Any extra global property, even an unused one, creates a new instance | Stop passing the property, or add `GlobalPropertiesToRemove` metadata on the shared `ProjectReference` (`RemoveProperties` on the `MSBuild` task) | Removing a property the project actually reads | | ||
| | One process per item (`Exec` batched on `%(Item.Identity)`) | `Exec` calls equal the item count | One invocation that takes a list or response file, after checking the tool supports it; keep `Inputs`/`Outputs` (with a 1:1 transform MSBuild still passes only the stale items into the target) | Dropping `Inputs`/`Outputs` while merging | | ||
| | Legitimate per-project or per-TFM work | Count equals projects times target frameworks | Leave it alone | Changing it to look tidy | | ||
|
|
||
| ### Step 3: Size the win | ||
|
|
||
| Avoidable aggregate task time is `(calls - expected) x average ms`. Wall-clock saving depends on actual overlap and the critical path. Batched per-item `Exec` calls in one target run sequentially on one node, so removing their startup costs can save nearly the full aggregate time. Only divide by effective parallelism when the duplicate calls actually overlap across nodes; unrelated busy nodes do not reduce a sequential saving. Use the binlog timeline to establish overlap, and mark wall-clock savings unknown when that evidence is missing. Work off the critical path can yield no wall-clock gain. For performance-only changes, if even the aggregate upper bound is below about 1% of build wall-clock, report it as not worth changing. Correctness races still need a fix regardless of the timing gain. | ||
|
|
||
| ### Step 4: Fix without changing behavior | ||
|
|
||
| 1. Keep the value available before its first consumer. | ||
| 2. A fallback `Condition="'$(X)' == ''"` is valid only when the value arrives as a global property (command line or an orchestrator), because those are inherited by every project. It keeps single-project and IDE builds working. | ||
| 3. Define the computation in one place. | ||
|
|
||
| ```xml | ||
| <!-- Before: every project launches git --> | ||
| <Target Name="StampCommit" BeforeTargets="CoreCompile"> | ||
| <Exec Command="git rev-parse HEAD" ConsoleToMSBuild="true"> | ||
| <Output TaskParameter="ConsoleOutput" PropertyName="GitCommit" /> | ||
| </Exec> | ||
| </Target> | ||
|
|
||
| <!-- After: the build script computes the hash once and passes -p:GitCommit=<hash> --> | ||
| <Target Name="StampCommit" BeforeTargets="CoreCompile"> | ||
| <Exec Command="git rev-parse HEAD" ConsoleToMSBuild="true" Condition="'$(GitCommit)' == ''"> | ||
| <Output TaskParameter="ConsoleOutput" PropertyName="GitCommit" /> | ||
| </Exec> | ||
| </Target> | ||
| ``` | ||
|
|
||
| If the value is only a commit id for the informational version, source link already sets `SourceRevisionId` without an `Exec`. | ||
|
|
||
| ### Step 5: Validate and report truthfully | ||
|
|
||
| 1. Capture a new binlog of the same invocation. Calls must have dropped to the expected count. | ||
| 2. Confirm outputs are unchanged (the stamped value, generated file contents). | ||
| 3. Report timing as the median of at least three runs; if you could not run the build, say "not validated". | ||
|
|
||
| ## Output | ||
|
|
||
| For on-target requests, emit a findings table with at least one row and a `Verdict:` line. Use `unknown` for unavailable timing or critical-path evidence; distinguish estimated aggregate task time from measured wall-clock savings. | ||
|
|
||
| | Work | Calls | Expected | Avoidable ms | Cause | Fix | On critical path? | | ||
| |---|---|---|---|---|---|---| | ||
|
|
||
| After verification: `Verdict: removed N repeated executions, measured wall-clock saving X s`. | ||
|
|
||
| Without verification: `Verdict: proposed removal of N repeated executions; estimated task-time saving X s; wall-clock not validated`. | ||
|
|
||
| When no change is worthwhile: `Verdict: no repeated work worth changing`. | ||
|
|
||
| ## Common Pitfalls | ||
|
|
||
| | Pitfall | Solution | | ||
| |---|---| | ||
| | Treating `Csc` or copy-local counts as waste | Compare with projects times target frameworks first | | ||
| | Guarding with `Exists(file)` or a flag property | Move the work out of the per-project path | | ||
| | Fallback condition on a property that is not a global property | Pass it as a global property, or the fallback never applies | | ||
| | Claiming a saving from total task time | Check actual overlap and the critical path; do not divide sequential work by unrelated busy nodes | | ||
| | Merging per-item `Exec` and losing incrementality | Keep `Inputs` and `Outputs` on the target | | ||
5 changes: 5 additions & 0 deletions
5
tests/dotnet-msbuild/find-repeated-build-work/already-shared-work/Api/Api.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| </PropertyGroup> | ||
| </Project> |
25 changes: 25 additions & 0 deletions
25
tests/dotnet-msbuild/find-repeated-build-work/already-shared-work/Build.proj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <Project DefaultTargets="BuildAll"> | ||
| <PropertyGroup> | ||
| <BuildInfoPath>$(MSBuildThisFileDirectory)obj/buildinfo.g.cs</BuildInfoPath> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <Component Include="Api/Api.csproj;Worker/Worker.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| <Target Name="Restore"> | ||
| <MSBuild Projects="@(Component)" Targets="Restore" | ||
| Properties="BuildInfoPath=$(BuildInfoPath)" BuildInParallel="true" /> | ||
| </Target> | ||
|
|
||
| <Target Name="GenerateSharedBuildInfo" | ||
| Inputs="$(MSBuildThisFileDirectory)buildinfo.cs.in" | ||
| Outputs="$(BuildInfoPath)"> | ||
| <MakeDir Directories="$(MSBuildThisFileDirectory)obj" /> | ||
| <Copy SourceFiles="$(MSBuildThisFileDirectory)buildinfo.cs.in" DestinationFiles="$(BuildInfoPath)" /> | ||
| </Target> | ||
|
|
||
| <Target Name="BuildAll" DependsOnTargets="GenerateSharedBuildInfo"> | ||
| <MSBuild Projects="@(Component)" Targets="Build" | ||
| Properties="BuildInfoPath=$(BuildInfoPath)" BuildInParallel="true" /> | ||
| </Target> | ||
| </Project> |
5 changes: 5 additions & 0 deletions
5
tests/dotnet-msbuild/find-repeated-build-work/already-shared-work/Directory.Build.targets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <Project> | ||
| <ItemGroup> | ||
| <Compile Include="$(BuildInfoPath)" /> | ||
| </ItemGroup> | ||
| </Project> |
5 changes: 5 additions & 0 deletions
5
tests/dotnet-msbuild/find-repeated-build-work/already-shared-work/Worker/Worker.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| </PropertyGroup> | ||
| </Project> |
4 changes: 4 additions & 0 deletions
4
tests/dotnet-msbuild/find-repeated-build-work/already-shared-work/buildinfo.cs.in
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| internal static class BuildInfo | ||
| { | ||
| public const string Version = "3.2.0"; | ||
| } |
15 changes: 15 additions & 0 deletions
15
tests/dotnet-msbuild/find-repeated-build-work/already-shared-work/perf-summary.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| Performance summary from dotnet msbuild Build.proj -restore -m | ||
| (Api and Worker each compile the same generated buildinfo.g.cs; Time Elapsed 00:00:05.20) | ||
|
|
||
| Target Performance Summary: | ||
| 12 ms GenerateSharedBuildInfo 1 calls | ||
| 4310 ms CoreCompile 2 calls | ||
|
|
||
| Task Performance Summary: | ||
| 8 ms Copy 1 calls | ||
| 4290 ms Csc 2 calls | ||
|
|
||
| Project Performance Summary: | ||
| 5200 ms Build.proj 1 calls | ||
| 2140 ms Api/Api.csproj 1 calls | ||
| 2210 ms Worker/Worker.csproj 1 calls |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.