Search code examples
.netubuntumonorhelmkbundle

Build self-contained Mono application


A Linux toolchain I'm managing contains a legacy .Net application. This is fine on Ubuntu, where an official Mono runtime environment to run .Net applications is available. But I would like to run my toolchain also on other Linux distributions (e.g. RHEL/Rocky Linux) without official Mono support. And I'd rather keep the list of runtime dependencies as small as possible, i.e. I neither want to depend on 3rd party Mono builds nor on Docker.

Theoretically, my scenario is well supported: Mono's mkbundle is able to package any .Net application into a platform-specific binary that does not rely on an installed Mono runtime. However, it does introduce a dependency on a libmono-native.so (this is a dynamic dependency, so it doesn't show up in the output of ldd) that I haven't been able to fulfill.

Minimal example:

test.cs

using System;

public class HelloWorld
{
    public static void Main(string[] args)
    {
        Console.WriteLine ("Hello Mono World");
    }
}

I compiled it to a .Net executable via csc hello.cs to a test.exe. This executable runs fine with Mono, but - obviously - not without a Mono runtime.

Approach 1: Cross-Compilation

mkbundle --cross mono-6.6.0-ubuntu-18.04-x64 -o test test.exe

This creates a native test ELF binary (I pretty much just selected an arbitrary x64 cross-compilation target, since neither RHEL/Rocky Linux nor any newer Ubuntu targets are available). This test runs on Ubuntu (I tested "Jammy" 22.04) when a Mono runtime is installed, but fails with a System.DllNotFoundException: /libmono-native.so on systems without an Mono runtime.

So, I tried to embed that library as well via mkbundle --cross mono-6.6.0-ubuntu-18.04-x64 --library /usr/lib/libmono-native.so -o test test.exe, but this fails on all tested systems (both Ubuntu and Rocky Linux) with

Error loading shared library: /tmp/mono-bundle-SSDDWc/libmono-native.so /tmp/mono-bundle-SSDDWc/libmono-native.so: cannot open shared object file: No such file or directory

Approach 2: Generic Static Compilation

mkbundle --static -L /usr/lib/mono/4.8-api -o test test.exe

This likewise creates a test ELF binary, but executing it crashes with a SEGFAULT already on the Ubuntu host system. gdb output:

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000055555556c314 in mono_mkbundle_init () at temp.c:147
#2  0x000055555556c652 in main (argc=1, argv=0x7fffffffddd8) at temp.c:224

Am I doing something wrong, or is my approach simply not possible?


Solution

  • Don't use Mono, use .NET Core

    There are two completely independent generations of implementations of the .NET framework:

    1. The legacy ".NET Framework" for Windows by Microsoft, whose functionality was independently reimplemented by the open-source Mono project to also support non-Windows platforms.
    2. The new ".NET Core" by Microsoft, which is open source and supports multiple platforms (e.g. Windows, Linux, MacOS) directly.

    Mono receives little development support these days, and it's possible that building native applications is broken in Mono and won't be fixed. However, .Net Core essentially supersedes it and is available in the official package repositories of many Linux distributions (e.g. dotnet-sdk-7.0 for Ubuntu).

    With .NET Core, native application for the host platform can be built by executing dotnet publish --configuration=release --self-contained from the application's source code root folder (the one that contains the Visual Studio Solution1). Binaries for different platforms can be generated by appending the -r <platform> parameter. The native application can be packaged as a single executable (instead of as a set of libraries and executables) by adding the

    <PublishSingleFile>true</PublishSingleFile>
    

    configuration line to the corresponding .csproj/.vbproj project file. Optionally, this single executable can be trimmed to reduce its size by also adding the

    <PublishTrimmed>true</PublishTrimmed>
    

    configuration line - but this only works for a subset of .NET language features.


    1If the Solution is still in the format of the legacy .NET Framework then it must be migrated first, e.g. using Microsoft's `upgrade-assistant` tool.