Search code examples
c#fodyfody-costura

How does the Fody NuGet package merge assemblies at the end of the compile process?


Recently I came across a NuGet package for modifying .NET assemblies, in particular, there was a Costura NuGet "plugin" for the Fody package which merges assemblies after the C# compiler completes.

I was not able to find any modifications to the .csproj file (besides the NuGet imports) which show a task which executes after the assembly has been built.

I'd like to know how Fody is able to run code after build without any noticeable configuration changes anywhere.


Solution

  • Fody adds this .targets file to your project. If you have a classic NET framework project you'll see it added to the .csproj file. If you have a netstandard project (new project format) it's automatically picked up during the build directly from the nuget package so you won't see it in the project (but you'll see it in the \obj\ folder, slightly renamed).

    Simplified: That target executes after the "AfterCompile" target. It searches for a Weavers.xml file. For each weaver (ex. Costura) it finds in the list, it invokes the weaver's assembly. The weavers use the Fody infrastructure which is based on Mono/Cecil to process your assembly's IL, post build, and rewrite the IL.

    In the case of Costura (again, simplified), it turns each reference assembly into an embedded resource. It then adds a Module Initializer to your assembly. The module initializer (something you could not write in pure C# prior to C#9) is guaranteed to run before any code in your assembly is called--think of it like a static constructor, but for an assembly.

    The code it adds to the module initializer attaches an AssemblyResolveEvent to the AppDomain. This handler will use a manifest resource stream to satisfy the resolution of the assemblies Costura has embedded by calling Assembly.Load using the bytes from the stream. It then returns that Assembly object from the handler, thus satisfying the assembly's resolution. Optionally, Costura can extract the DLLs to a temp location on disk and then use that path to satisfy the resolve instead (sometimes necessary depending on the DLLs involved).

    The Fody and Costura documentation explains how this works in detail.