MSBuild Targets & Hooks
Add one line to your build to pin a package version and run `dotnet restore` on the project: it works. Run the same command on the solution and it quietly does nothing, no error, no pin.
- ▸Restore a project and its own restore runs; restore the whole solution and a generated stand-in runs instead, skipping it.
- ▸
BeforeTargets="Restore"fires on a project build and silently does nothing on solution restore;BeforeTargets="CollectPackageReferences"fires on both. - ▸A
<PackageReference>created inside a build step compiles but never reaches consumers: the graph was settled at evaluation.
A build is not a script that runs top to bottom. It is a graph of named work units called targets, and the order they run in is decided by three edge attributes, not by where they sit in the file. Write <Target Name="Stamp" AfterTargets="Build"> and Stamp runs after Build, no matter which file declared it. BeforeTargets puts a unit ahead of another; DependsOnTargets says "pull these in first." A unit with no incoming edge from whatever you invoked simply never runs.
Ask for the firing order with dotnet build -bl and open the msbuild.binlog it writes: every unit that actually ran is there by name, ResolveAssemblyReferences, CoreCompile, and dozens more, wired into one another. Remember that file, it is how you find out later that something did not run. The consequence that matters here: a unit can attach itself to another by name, and the target it attaches to has no idea it exists. That one-way wiring is exactly what a hook is, and it is also how a hook goes wrong.