MSBuild Integration¶
Deepstaging hooks into the standard .NET build pipeline through Roslyn source generators, analyzers, and MSBuild files shipped inside the NuGet package. No extra build steps or CLI tools are needed — everything runs as part of dotnet build.
How It Works¶
When you add a <PackageReference Include="Deepstaging" />, NuGet restores the package and MSBuild automatically imports the bundled .props and .targets files. The Roslyn compiler host then loads the generators and analyzers from the analyzers/ folder inside the package.
dotnet build
├─ MSBuild imports Deepstaging.props / Deepstaging.targets
├─ Roslyn loads Deepstaging.Generators.dll (source generators)
├─ Roslyn loads Deepstaging.Analyzers.dll (diagnostic analyzers)
├─ Roslyn loads Deepstaging.CodeFixes.dll (IDE code fixes)
└─ Generators emit source → compilation continues with generated code
Source generators run as part of the compilation itself — they see your syntax trees, produce new source files, and feed them back into the same compilation pass. Analyzers run in parallel, reporting diagnostics that surface as build warnings/errors.
NuGet Package Structure¶
The Deepstaging package is a metapackage that bundles everything consumers need:
Deepstaging.nupkg
├── analyzers/dotnet/cs/
│ ├── Deepstaging.Generators.dll ← source generators
│ ├── Deepstaging.Analyzers.dll ← diagnostic analyzers
│ ├── Deepstaging.CodeFixes.dll ← IDE code fix providers
│ ├── Deepstaging.Projection.dll ← shared models (generator dependency)
│ └── Deepstaging.Roslyn.dll ← Roslyn utilities
├── satellite/netstandard2.0/
│ └── Deepstaging.Projection.dll ← opt-in reference for downstream generators
└── build/
├── Deepstaging.props ← imported early (properties)
└── Deepstaging.targets ← imported late (items, file nesting)
The analyzers/ folder is a well-known NuGet convention — DLLs placed here are automatically loaded by the Roslyn compiler host without appearing as compile references in your project.
Runtime dependencies (Deepstaging.Abstractions, Deepstaging.Runtime) are delivered as standard lib/ references via package dependencies.
Shipped MSBuild Files¶
Deepstaging.props (runs early)¶
Sets compiler-visible properties and configures generated file output:
<PropertyGroup>
<EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>!generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<CompilerVisibleProperty Include="DeepstagingPreludeLanguageExt"/>
</ItemGroup>
This also handles satellite projection opt-in when DeepstagingSatellite is set.
Deepstaging.targets (runs late)¶
Handles file discovery and IDE nesting for the Configuration module:
- Adds
**/*.schema.jsonasAdditionalFilesso analyzers can detect and validate schemas - Copies
deepstaging.settings*.jsonto the output directory - Nests config files under
deepstaging.settings.jsonand schema files underdeepstaging.schema.jsonfor tidy IDE display
Directory.Build.props¶
The repo uses a modular Directory.Build.props that imports shared build configuration:
<Import Project="build/Build.Language.props"/> <!-- LangVersion, Nullable, TreatWarningsAsErrors -->
<Import Project="build/Build.Paths.props"/> <!-- Package output paths -->
<Import Project="build/Build.SourceLink.props"/> <!-- SourceLink, embedded symbols -->
<Import Project="build/Build.Generators.props"/> <!-- EmitCompilerGeneratedFiles → obj/Generated/ -->
<Import Project="build/Build.Packaging.props"/> <!-- NuGet metadata, versioning -->
Key properties set by these imports:
| Property | Value | Effect |
|---|---|---|
LangVersion |
latest |
C# 14 features including extension members |
Nullable |
enable |
Nullable reference types everywhere |
TreatWarningsAsErrors |
true |
Warnings are build errors — dotnet build is the lint step |
EmitCompilerGeneratedFiles |
true |
Generated source written to obj/Generated/ for inspection |
CompilerGeneratedFilesOutputPath |
obj/Generated |
Keeps generated files out of source globbing |
Local Development Overrides¶
Copy Directory.Build.Dev.props.template to Directory.Build.Dev.props (gitignored) for local customizations. This file is only imported during Debug builds:
<Import Project="Directory.Build.Dev.props"
Condition="Exists('...') AND '$(Configuration)' == 'Debug'"/>
Build Properties¶
Compiler-Visible Properties¶
Deepstaging uses <CompilerVisibleProperty> items to pass MSBuild properties into the Roslyn analyzer/generator context. Analyzers read these from AnalyzerConfigOptionsProvider.GlobalOptions:
| Property | Purpose |
|---|---|
DeepstagingPreludeLanguageExt |
Controls LanguageExt prelude generation |
DeepstagingSatellite |
Enables satellite projection for downstream generators |
The TrackedFileTypeAnalyzer base class (from Deepstaging.Roslyn) automatically forwards all MSBuild properties prefixed with Deepstaging into Diagnostic.Properties, so code fixes can read them without per-property wiring.
Prelude Generator¶
The PreludeGenerator automatically emits a Deepstaging.GlobalUsings.g.cs file with global using directives for common namespaces:
Deepstaging.EffectsLanguageExt(unlessDeepstagingPreludeLanguageExtisfalse)System.Collections.ImmutableSystem.Threading.TasksSystem.Linq
To suppress the LanguageExt global using (useful when it conflicts with test frameworks):
<PropertyGroup>
<DeepstagingPreludeLanguageExt>false</DeepstagingPreludeLanguageExt>
</PropertyGroup>
Controlling Generator Output¶
In consumer projects, the NuGet .props sets CompilerGeneratedFilesOutputPath to !generated. During repo development, the workspace-level Build.Generators.props overrides this to obj/Generated/ to keep generated files out of version control.
Suppressing Diagnostics¶
All Deepstaging diagnostics can be suppressed using standard .NET mechanisms:
Suppressing vs fixing
Before suppressing a diagnostic, check the diagnostics reference — many diagnostics have automated code fixes available in your IDE.
IDE Integration¶
Visual Studio¶
Generators and analyzers load automatically. Generated files appear under Dependencies → Analyzers → Deepstaging.Generators in Solution Explorer. Code fixes appear in the lightbulb menu.
JetBrains Rider¶
Rider supports source generators and Roslyn analyzers natively. Generated files are visible under Dependencies → Source Generators. Run Build → Rebuild Solution if generated code is not immediately visible after adding attributes.
VS Code (C# Dev Kit)¶
The C# extension loads analyzers and generators via OmniSharp or the C# Dev Kit language server. Diagnostics appear in the Problems panel; code fixes are available via Quick Fix (Ctrl+.).
Generated file visibility
Generated files are written to obj/Generated/ (or !generated/ in consumer projects). They are not checked into source control but can be inspected for debugging.
Debugging Generators¶
Prefer tests over debugger-attach
Deepstaging generators have comprehensive test coverage using snapshot testing and the RoslynTestBase harness. Writing a failing test that reproduces the issue is almost always faster and more reliable than attaching a debugger to the compiler host. See Testing for the test patterns.
If you still need to step through a generator interactively:
-
Inspect generated output — check
obj/Generated/Deepstaging.Generators/for the emitted.csfiles. Most issues are visible from the output alone. -
Attach a debugger as a last resort — add a launch point in the generator's
Initializemethod:Build the project and the JIT debugger dialog will appear.
Debugger.Launch() is disruptive
The debugger dialog fires on every build, including IDE background compilations. Remove the launch point as soon as you're done — or guard it behind an environment variable.
Satellite Projection¶
Deepstaging exposes its Projection layer — the models, query extensions, and attribute wrappers used by generators — to downstream satellite packages. This lets projects like Deepstaging.Web build upon DispatchModel, EffectsModuleModel, etc. without duplicating discovery logic.
Opting In¶
The Deepstaging NuGet package bundles Deepstaging.Projection.dll in a satellite/ folder (separate from the analyzers/ copy used by the core generators). Downstream projects opt in via a single MSBuild property:
This adds Deepstaging.Projection.dll as a compile reference, giving the project access to all projection types.
Convention: {PackageNameNoDots}Satellite¶
Every Deepstaging-family package follows this naming convention:
| Package | Property | Exposes |
|---|---|---|
Deepstaging |
DeepstagingSatellite |
Deepstaging.Projection.dll |
Deepstaging.Web |
DeepstagingWebSatellite |
Deepstaging.Web.Projection.dll |
A downstream project can compose multiple satellite references:
<PropertyGroup>
<DeepstagingSatellite>true</DeepstagingSatellite>
<DeepstagingWebSatellite>true</DeepstagingWebSatellite>
</PropertyGroup>
Why Not lib/?¶
Putting Deepstaging.Projection.dll in lib/netstandard2.0/ would expose Roslyn symbol wrappers and query extensions to all consumers via IntelliSense — types that only make sense for generator authors. The satellite/ folder keeps them invisible to regular consumers while making them available on demand.
Creating a Satellite Package¶
If you're building a package that others should be able to extend:
-
Ship build props — create
build/{PackageId}.propsthat checks for the satellite property: -
Bundle Projection in
satellite/— add to your packable.csproj: -
Suppress NU5100 — NuGet warns about DLLs outside
lib/:
The roslynkit template generates this structure automatically.