Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

5. What Roslyn Generates at Build Time

Compilation produces real C# types from previously acquired inputs. Ordinary consumer builds do not scrape a binary, start a container or inspect stdout to invent event schemas.

There are two Roslyn chains in the current implementation and a separate Vagrant maintenance tool. Knowing which chain owns a file is essential when debugging a missing property or message.

Build-time inputs

BinaryWrapperGenerator implements IIncrementalGenerator. It finds descriptor classes through ForAttributeWithMetadataName, collects JSON AdditionalFiles and combines those inputs to emit source.

For each descriptor it selects files matching <binary>-*.json, reads their command trees and obtains version identifiers from filenames. One snapshot uses VersionDiffer.FromSingle. Multiple snapshots use VersionDiffer.Merge with semantic-version ordering.

Names are resolved before emission. The generator then emits the descriptor marker and the selected command, builder and client artifacts using AddSource.

The filename convention matters: renaming an input can change the version used by the merger even when the JSON body's version field is unchanged.

Split command ownership

The current Vagrant descriptors show the package boundary. These are existing declaration excerpts from separate assemblies.

[BinaryWrapper("vagrant",
    Generation = BinaryWrapperGeneration.Commands,
    EnvironmentType = typeof(VagrantEnvironment))]
internal partial class VagrantContractsDescriptor;
[BinaryWrapper("vagrant",
    Generation = BinaryWrapperGeneration.Builders | BinaryWrapperGeneration.Client,
    EnvironmentType = typeof(VagrantEnvironment))]
public partial class VagrantDescriptor;

The contracts assembly owns the generated command classes. The implementation consumes those classes and owns their generated builders and client. Generating all artifacts in both projects would produce competing definitions and defeat the dependency split.

Both projects provide the relevant help files as AdditionalFiles. The namespace stays FrenchExDev.Net.Vagrant; changing the containing assembly does not require rewriting every using directive.

Environment catalogue generation

The second chain starts with a declarative C# catalogue. The Design project compiles that file as ordinary C#. The contracts project supplies the same file to the netstandard2.0 analyzer target of BinaryWrapper.Design.Lib.

The generator reads declaration syntax and produces an abstract environment record, any declared enums and a compatibility exception. It does not execute the catalogue's code. The concrete partial environment record inherits the generated base.

Diagram
Commands and environments are generated at build time from different evidence sources.

Attribute-based environment generation remains supported by EnvironmentClassEmitter. It recognizes an existing catalogue-provided version-aware serializer instead of generating a second one. Chapter 11 covers the catalogue language and assignment tracking.

What happens to message records

Output events do not come from help JSON. Tool-specific contracts define semantic records and wire observations according to separately reviewed output sources.

Vagrant additionally has tools/Vagrant.OutputCatalogGenerator. That developer tool inspects a pinned Vagrant source checkout and writes checked-in UI records, a matching catalogue, inventory, examples and documentation.

Its generated records live in Vagrant.Contracts; the recognition catalogue lives in the implementation. An ordinary build compiles these checked-in files. It does not clone Vagrant or interpret its Ruby and YAML.

Produced code Producer When it runs
Commands, builders and client BinaryWrapper Roslyn generator Consumer compilation
Environment base records and enums Design.Lib Roslyn generator Contracts compilation
Vagrant UI event records and matching catalogue Vagrant output-catalogue developer tool Explicit maintenance regeneration
Parser behavior and most semantic records Consumer source code Written and compiled normally

This distinction prevents a misleading claim that source generation automatically understands arbitrary runtime messages.

Inspecting compiler-generated output

Consumers enable EmitCompilerGeneratedFiles and direct output beneath obj/<configuration>/<framework>/Generated. Inspect the contracts project's generated directory for commands and environment bases, and the implementation project's directory for builders, clients and forwarding attributes.

The files are compiler outputs. Editing them changes an artifact, not its declaration, and the next build can replace that change. Fix the help snapshot, descriptor, catalogue or emitter responsible for the generated shape.

A representative generated command carries TargetVersion, a typed Environment, ToArguments() and ToEnvironment(). Its builder tracks whether each fluent setter was invoked, independently of the assigned value.

Diagnostics and failure attribution

The declaration files list diagnostics such as BW001 for missing help files, BW002 for JSON parsing, BW004 for a non-partial descriptor and BW005 for an invalid environment declaration. Catalogue generation declares BWD001 and BWD002 for invalid catalogues and duplicate output.

Declaration of a diagnostic is not proof that every malformed input reaches a reporting branch. Diagnose failures from actual generator output and focused tests, rather than relying only on the diagnostic inventory.

A normalized C# name collision is different from a CLI collision. NamingHelper resolves generated identifiers deterministically while preserving external command paths. GitLab CLI's hyphen/underscore command names are a useful case study.

Compatibility after moving contracts

ContractTypeForwarderEmitter discovers public types in the implementation's referenced .Contracts assembly and emits TypeForwardedTo attributes. Shared runtime forwarding is also present in source.

Forwarding helps preserve moved public type identities for consumers of the old implementation assembly. It does not eliminate the need to inspect package dependencies and version compatibility. Contracts and consumption explains the intended reference graph.

Source snapshot

Source inspection: 2 October 2026. These implementation paths are relative to Net/FrenchExDev in the local ecosystem checkout. The v2 changes are largely uncommitted; these paths identify inspected code, without claiming that public branch links contain it.

  • BinaryWrapper/src/FrenchExDev.Net.BinaryWrapper.SourceGenerator/BinaryWrapperGenerator.cs
  • BinaryWrapper/src/FrenchExDev.Net.BinaryWrapper.SourceGenerator/ContractTypeForwarderEmitter.cs
  • BinaryWrapper/src/FrenchExDev.Net.BinaryWrapper.Design.Lib/EnvironmentCatalogGenerator.cs
  • Vagrant/src/FrenchExDev.Net.Vagrant.Contracts/VagrantContractsDescriptor.cs
  • Vagrant/src/FrenchExDev.Net.Vagrant/VagrantDescriptor.cs
  • Vagrant/tools/Vagrant.OutputCatalogGenerator/README.md

Previous · Series index · Next

⬇ Download