Wednesday, May 09, 2018

Assembly versioning is hard -- internals just keep on leaking

I've hit a couple of instances of this in recent weeks.

Assembly versioning is meant to be a strong contract, that given two distinct instances with the same version, one can be used as a drop-in replacement for the other, e.g. as a a bug-fixing patch, in all circumstances. A sufficiently strong contract, indeed, that in my experience in building commercial product with .net, our process erred on the side of caution, and bumped the build number facet of the version every commit, even if all that had changed were dependencies.

However, it doesn't always work that way.

The first instance I hit was when Mono.Cecil finally hit 0.10 final after having been in beta for years. That had indeed preserved its public contract -- but somewhere between beta-7 and final, one of the internal APIs consumed by the symbol-reading helper assemblies via InternalsVisibleTo was expanded. Consequently, if a beta version was loaded (e.g. by a tool -- in particular the NUnit3 test adapter for .net core) ahead of the final, the result was a MissingMethodException when indirectly invoking that internal path. I specify .net core here, because the lack of AppDomain isolation is what puts the tool's use of beta-6 into the pot ahead of the system under test's final.

The most recent has been with Visual Studio 2017, which also bundles an update to the F# core assembly version, and the FSharpLint MSBuild task. Here, the F# compiler's choice of generating synthetic names for compiler generated classes by tagging them with @lineNumber comes into its own -- the new file version "2018.04.25.1" includes an internal synthetic type related to event handling called Microsoft.FSharp.Control.CommonExtensions+SubscribeToObservable@1693; in the earlier "2018.01.25.1" build, the corresponding type is Microsoft.FSharp.Control.CommonExtensions+SubscribeToObservable@1741 and asking for the former when the latter is the one on offer gets you a TypeLoadException. Here, it looks like AppDomain isolation is working against us in the other direction, with different and thus incompatible builds of FSharp.Code.dll being loaded in two separate AppDomains.

No comments :