Source Generation
Source Generation and Dovetails [New]
Section titled “Source Generation and Dovetails [New]”We have built a Source Generator to use with Dovetail that allows us to export joints and import joints with easy, in a way that is statically compiled.
Exporting Dovetails
Section titled “Exporting Dovetails”Any joint marked with [assembly: Dovetail(typeof(MyDovetail))] will automatically be added to a public partial static class named Exports that will exist in the root namespace of the assembly being built.
Each exported joint carries the host type it declares with [LiveJoint], [UnitTestJoint] or [DovetailHostType], the same
as when a joint is added through reflection at runtime. See which attributes are honoured where.
A joint with constructor parameters is exported as a factory, because the exported method has no context to read from: the context builder that holds the metadata creates the joint from its properties, and an optional parameter falls back to its default. See how constructor parameters are resolved.
Importing Dovetails
Section titled “Importing Dovetails”The generator creates an internal static partial Imports class in the assembly’s root namespace. Imports.Joints is a
DovetailContextBuilderFactory that creates a context builder loaded with the assembly’s own joints and those of every
referenced assembly that exports joints. The DovetailImportNamespace, DovetailImportClassName and
DovetailImportMethodName MSBuild properties change those names.
You can also import the GetDovetails method onto a given class by using the [ImportDovetails] attribute on the class.
Which projects import and export
Section titled “Which projects import and export”Two MSBuild properties decide what the generator emits for a project:
| Property | When true |
|---|---|
ImportDovetailAssembly | The Imports class is generated. |
ExportDovetailAssembly | Referencing assemblies import this assembly’s joints. When false, the joints still reach this assembly’s own Imports class. |
Joinery.Dovetail.Sdk sets both as part of its host detection. Projects that don’t use the SDK get these defaults:
- test projects (
IsTestProject) import but don’t export; - applications (
OutputTypeofExeorWinExe) import and export; - libraries export but don’t import.
Set either property in a project file to override its default.
The generator runs in every project that references a Dovetail package, including projects that only reference one
indirectly, for example through Joinery.Dovetail.Hosting. Its diagnostics (DT0001 to DT0005, such as the error for a joint
with more than one constructor, and DT0017, the error for a joint with no constructor it can be created with) apply there too. The code it generates needs C# 12 or later: in a project whose LangVersion is older, it
generates nothing and reports warning DT0006 instead.
Specular’s provider
Section titled “Specular’s provider”An application that references Specular doesn’t set its provider by hand. Specular generates
an internal SpecularProvider into each project that emits one, and the generated Imports seeds that provider into the context
builder it creates, so a joint reads it with context.Get<ISpecularProvider>(). Scanning through it covers the assemblies the
application references, including the joints of libraries that don’t generate a provider of their own (SpecularEmitProvider set to
false) and so keep receiving the application’s through the context.
public sealed class PluginJoint : ISetupJoint{ public void Register(IDovetailContext context) { var provider = context.Get<ISpecularProvider>()!; var plugins = provider.GetTypes(z => z.FromAssemblyOf<IPlugin>().GetTypes(f => f.AssignableTo<IPlugin>())); }}Dovetail has no dependency on Specular, so the seeding is generated code that exists only where Specular does:
- The generator adds it when the project’s compilation references the assembly that defines
Specular.ISpecularProviderand theSpecularEmitProviderMSBuild property isn’tfalse, the two things Specular’s own generator looks at before it generates aSpecularProvider. It can’t look forSpecularProvideritself: a generator doesn’t see what another one adds. - In every other project nothing is generated and no diagnostic is raised.
- A provider you set yourself wins. The generated code only adds the provider when the context doesn’t hold one, so
builder.ConfigureDovetail(b => b.Set<ISpecularProvider>(provider)), or a provider passed in with the properties, is never overwritten. - Set
SpecularEmitProvidertofalsefor a project that references Specular without generating a provider (say it excludes Specular’s analyzers) and Dovetail stops naming one. Otherwise the build fails with CS0103 forSpecularProvider.
Native AOT and trimming
Section titled “Native AOT and trimming”The seeding adds no reflection: it is a generic call to AddIfMissing<ISpecularProvider> with the value of a static property, both of
which the trimmer and the AOT compiler can follow. Specular generates SpecularProvider without reflection as well. The
Joinery.Dovetail.Sdk.Tests publish an application that uses it trimmed and with Native AOT, without trim or AOT warnings, and the
published application starts with the provider in its context and scans across assemblies.