--- name: msbuild description: Writes and reviews MSBuild evaluation, properties, items, imports, custom targets and build/buildTransitive package assets for SDK-style .NET repositories. Use for ignored or empty properties/items, generated files, analyzer/build-asset packaging, build-file reviews, binlog diagnosis and incremental-build problems. Focused MSTest project setup, runner configuration and test commands belong to mstest-extensions. license: Apache-2.0 --- # MSBuild for SDK-style repositories For MSTest-specific project or runner work, use the [companion routing table](../audit/references/mstest-routing.md). ## 1. Evaluation order decides where things go SDK-style projects import SDK props before their body and SDK targets afterward. `Directory.Build.props` and `Directory.Build.targets` are imported *inside* that SDK/common chain; the latter is late, but later SDK imports can still replace its values or target definitions. Trace the actual order with `-preprocess`. - `.props` files run before the project body. Put overridable defaults there, conditioned on emptiness: `x`. - Read project/SDK-derived values where they are available. A project-body `TargetFramework` is usually unset in early props; global properties and inner builds can already supply it. Check the actual evaluation before moving a condition. - Keep build logic in `.targets`. A target declared in `.props` is legal; imported target definitions can replace it, rather than hooks requiring an already-declared target. Command-line global properties normally resist local reassignment; inspect `TreatAsLocalProperty` when that behavior differs. - An inner `Directory.Build.props` stops the upward search. Import the parent explicitly: ``. - Use exact discovery names (`Directory.Build.props`, `Directory.Packages.props`) for portability to case-sensitive filesystems. ## 2. Rules for everyday edits - Omit restated defaults after checking the effective SDK settings. Preserve deliberate overrides and explicit source ordering when the project requires them. - List-like properties keep their previous value: `$(NoWarn);X`, `$(DefineConstants);X`, `$(CompileDependsOn);CustomStep`. - Quote both sides of conditions: `'$(X)' == 'true'`. - Paths come from properties (`$(MSBuildThisFileDirectory)`, `$(MSBuildProjectDirectory)`, `$(IntermediateOutputPath)`, `$(ArtifactsPath)`). Never use absolute paths, `obj/` literals or backslashes inside `Exec`. - Use MSBuild tasks for file work: `MakeDir`, `Copy`, `Delete`, `WriteLinesToFile`, `Touch`. Use property functions for string work (`$([System.IO.Path]::Combine(...))`, `$(X.Replace(...))`). `Exec` is for real external tools, and an OS-specific one is guarded with `$([MSBuild]::IsOSPlatform('Windows'))`. - A value shared by three or more projects moves to `Directory.Build.props`. A value the SDK of the repository already owns (for ANcpLua.NET.Sdk: `LangVersion`, `Nullable`, `TreatWarningsAsErrors`, `ImplicitUsings` and the analyzer set) is never restated in a project. - Analyzer and build-tool packages (`*.Analyzers`, SourceLink, MinVer, Nerdbank.GitVersioning) are `PrivateAssets="all"`, or a `GlobalPackageReference` from the SDK or `Directory.Packages.props`. - Use [items and outputs](references/items-and-outputs.md) for Include/Update/Remove, metadata batching and the copy contract, including outputs mutated by the app. ## 3. Custom targets ```xml ``` - `Inputs` and `Outputs` on every target that produces files, or it runs on every build. `Outputs` must not contain times or GUIDs. - Generated code goes under `$(IntermediateOutputPath)`, is added to `Compile` inside the target (it doesn't exist at evaluation time), and is registered in `FileWrites` so `dotnet clean` removes it. - Hook with `BeforeTargets`/`AfterTargets` or by appending to `$(XDependsOn)`. Never redefine an SDK target name (`Build`, `CoreCompile`) and never overwrite a `DependsOn` list without self-reference. - Query targets return data with `Returns=`, not `Outputs=`. - Calling `` on the same project starts a second build into the same bin/obj and races. Use `DependsOnTargets` instead. ## 4. Packaging build assets and analyzers Read `references/packaging.md` before changing a package's `build/`, `buildTransitive/` or `analyzers/` content. In brief: file names equal the PackageId, analyzers go to `analyzers/dotnet/cs`, and their dependencies ship next to them. ## 5. Diagnosing 1. Reproduce with a binary log: `dotnet build -bl:artifacts/logs/build.binlog` (one name per run). 2. Read the first error, not the last. For restore errors, see the `nuget-versions` skill. 3. Inspect with the binlog tools available in the session (a binlog MCP server: call its tool list first) or `dotnet msbuild -preprocess:pp.xml` for the fully evaluated project. 4. For "why is this property X?", use `dotnet msbuild -getProperty:X -getItem:Y` on the project. 5. For slow builds, use [performance evidence](references/performance.md): controlled cold/incremental/no-op samples, evaluation versus execution, task time and dependency waits. 6. Duplicate outputs: two projects or two global-property sets writing the same `bin`/`obj` (shared `BaseIntermediateOutputPath`, `SetTargetFramework` to the project's own single TFM, the same project in two solutions built in one script). ## 6. Review Check changes against `references/antipatterns.md`. A build-defect finding identifies the affected supported environment/configuration and evidence of failure. A Linux-only or conditional-build failure is still a defect; distinguish it from an unverified risk. Report findings as `file:line | rule | evidence | fix`, most severe first.