Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge F# and C# assemblies with ILMerge so that all types would be available when referenced?

Tags:

c#

f#

ilmerge

I have a solution with many F# and C# projects. My goal is to merge them all to one library using ILMerge. Resulting merged dll will be put in a NuGet package and referenced in other projects. However, I'm running into few issues when merged dll is referenced in F# projects.

The problem I have is that if primary assembly given to ILMerge is F# then referencing resulting dll in F# project allows to only access F# types. If C# dll is chosen as primary assembly for merge then extension methods from merged F# assemblies were not available when referencing in F# project. Also modules with AutoOpen attribute were no longer implicitly opened when opening enclosing namespace.

Is there a way to merge F# and C# assemblies so that all types (including extension methods) would be available?

like image 216
Domas Avatar asked Oct 06 '22 15:10

Domas


1 Answers

In part of our code base, a big chunk of the library is done in F# and the rest in C#. Both F# and C# code are front facing.

We have a hellish batch file to take care of mergeing and what I see is that we are merging with this code:

echo merging %mergeapp% /keyfile:"%keyfile%" /target:library /attr:"%dstpath%%csharpdll%" /targetplatform:%targetplatform%,%targetlib% /lib:%sllib% /lib:%targetlib% /lib:"%libpath%lib" /out:"%mergedpath%..\%csharpdll%" "%dstpath%%csharpdll%" "%dstpath%%fsharpdll%"
%mergeapp% /keyfile:"%keyfile%" /target:library /attr:"%dstpath%%csharpdll%" /targetplatform:%targetplatform%,%targetlib% /lib:%sllib% /lib:%targetlib% /lib:"%libpath%lib" /out:"%mergedpath%..\%csharpdll%" "%dstpath%%csharpdll%" "%dstpath%%fsharpdll%"

and that does what we intend. However, we do not publish any extension methods nor do we do any AutoOpen. What we discovered was a bug in the F# compiler that, up until we started putting obfuscation in the mix, required us to run ildasm on the F# assembly and rip out the offending code. The other issue is that F# doesn't properly support the protected modifier on members (F# makes them public) so we created an attribute that we could hang on class members that were meant to be protected. Then we wrote a tool that uses Cecil to blow the assembly apart, rip out our attribute and change the access to those members to protected (code is in the accepted answer here).

I didn't know about AutoOpen, but I had to do a similar task, so I created class called a registrant that did that kind of work like this:

type FSharpRegistrant() =
    do
        // do whatever I need to get going

Then in a static constructor within the C# module, I wrote some code that instantiates the F# registrant using reflection to find the class (since in my code base the C# code builds first and doesn't know there's F# code at all). This is ugly code with a lot of error checking, but it works.

like image 173
plinth Avatar answered Oct 10 '22 02:10

plinth