Skip to main content

How It Works

The pipeline from annotated source to generated F#.

Myriad is a small standalone CLI (Myriad.dll), driven either directly or via the MSBuild SDK, which shells out to that same CLI as a pre-build step. The pipeline is the same either way.

1. Plugins are loaded, not linked
#

Myriad doesn’t ship generators built in: even the “built-in” fields/lenses/DuCases plugins are ordinary plugin assemblies (Myriad.Plugins.dll), loaded the same way a third-party plugin would be. Each --plugin <path-to-dll> argument is loaded via McMaster.NETCore.Plugins, and Myriad reflects over the loaded assembly for types carrying a generator-marker attribute. Since v0.8.5, the loader shares already-loaded assemblies like FSharp.Core and Fantomas.FCS with the plugin (PreferSharedTypes = true) rather than loading a second copy; that used to throw ReflectionTypeLoadException when a plugin was built against a slightly different target framework.

2. One codegen unit at a time, one process for the whole project
#

A codegen unit is one input file with everything needed to process it: the file itself, an optional output file, an optional config key, any additional parameters, and an optional list of generator names to filter to. Units come from one of two places:

  • a single --inputfile/--outputfile pair, for direct CLI use, or
  • a --manifest <file>.toml listing many units in one TOML file: this is what the MSBuild SDK uses. As of the 0.9.0 release, the whole project’s worth of files is processed in one Myriad.dll invocation instead of one process launch per attributed file, since the old per-file approach already had to reload plugins fresh every time anyway.

3. Each unit is run against every loaded generator
#

For every discovered generator type, Myriad checks whether its ValidInputExtensions includes the input file’s extension, and, unless the unit’s GeneratorFilters list excludes it by name, instantiates it and calls Generate with a GeneratorContext (see Configuration). A generator returns either Output.Ast (a list of F# AST module fragments) or Output.Source (a raw string); most built-in plugins return Ast. A generator can also implement IMyriadGeneratorWithDiagnostics instead of the plain interface, to report non-fatal warnings/info alongside its output rather than only being able to fail the whole build by throwing; these get printed as MSBuild-style diagnostic lines (path(line,col): severity CODE: message) so they surface the same way a compiler warning does.

If a generator throws, or reports an error-severity diagnostic, Myriad fails that unit with a formatted !CompilationError block rather than a raw exception dump, but other units in the same batch aren’t affected.

4. AST becomes source, source gets formatted, formatting gets written
#

Output.Ast results are wrapped into a parse tree and handed to Fantomas (CodeFormatter.FormatASTAsync) to become source text; Output.Source results are used as-is. All of a unit’s generator outputs are concatenated, a fixed header comment is prepended (This code was generated by myriad. Changes to this file will be lost when the code is regenerated.), and the result is written to the unit’s output file, or, if MyriadInlineGeneration is set, spliced onto the end of the input file itself (via a temp file: read the input, strip anything after a previously-generated header if one’s already there, append the new generated block, then atomically replace the original).

5. MSBuild integration is just automation around the same CLI
#

The Compile element’s MyriadFile/MyriadNamespace/MyriadConfigKey/MyriadInlineGeneration metadata gets collected into the manifest passed via --manifest, and an MSBuild Inputs/Outputs-driven target only re-runs generation when something’s actually changed: regenerating F# source is a normal part of the build, not a separate step you remember to run. --verbose and --wait-for-debugger are available for debugging the generator process itself, surfaced as MyriadSdkVerboseOutput/MyriadSdkWaitForDebugger MSBuild properties.

Source: src/Myriad/Program.fs, src/Myriad.Core/Types.fs, and the project’s CHANGELOG.