Skip to content

Bundling Mono apps

probonopd edited this page Nov 8, 2020 · 13 revisions

Using 'dotnet publish'

.NET Core AppImage example - Example of how to deploy .NET Core (Mono) applications as an AppImage using dotnet publish -f netcoreapp3.1 -r linux-x64 from within a .cs program.

Especially check out /p:PublishSingleFile=true:

dotnet publish -r linux-x64 -p:PublishSingleFile=true -c Release

Using mkbundle.exe

The official way to produce portable bundles of Mono/Ximian applications seems to be mkbundle.exe, a tool that comes with Mono that converts "assemblies" (non-native exe and dll files) to native Linux executables. The result can then be put into an AppImage, along with resources such as shared libraries (including Mono ones), icons, and such.

Here is an example:

#!/bin/bash

# Run this on the oldest still-supported distribution,
# e.g., a Docker container with Ubuntu 14.04

sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates git wget
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
echo "deb https://download.mono-project.com/repo/ubuntu stable-trusty main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list
sudo apt-get update
sudo apt-get -y install mono-complete nuget gcc
git clone --recursive https://github.com/MatterHackers/MatterControl.git
cd MatterControl/
nuget restore MatterControl.sln
sed -i -e 's|Directory.SetCurrentDirectory|// Directory.SetCurrentDirectory|g' Program.cs # https://github.com/MatterHackers/MatterControl/issues/3536#issuecomment-406704443
xbuild /p:Configuration=Release MatterControl.sln
cd ./bin/Release/
cp -r ../../StaticData/ .
/usr/lib/mono/4.5/mkbundle.exe MatterSlice.exe --deps -L  /usr/lib/mono/4.5/ -o MatterSlice
cp MatterSlice.exe MatterSlice.dll # FIXME
/usr/lib/mono/4.5/mkbundle.exe MatterControl.exe --deps -L  /usr/lib/mono/4.5/ -L /usr/lib/mono/4.5/Facades/ -L /usr/lib/mono/4.5-api/ -o MatterControl
mkdir -p appdir/usr/bin ; mv MatterSlice MatterControl StaticData appdir/usr/bin
mkdir -p appdir/usr/share/applications/ ; cp ../../../mattercontrol.desktop appdir/usr/share/applications/ # FIXME
mkdir -p appdir/usr/share/icons/hicolor/256x256/apps/ ; touch appdir/usr/share/icons/hicolor/256x256/apps/mattercontrol.png # FIXME
wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" 
chmod a+x linuxdeployqt*.AppImage ; ./linuxdeployqt-*.AppImage --appimage-extract ; rm linuxdeployqt*.AppImage
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
./squashfs-root/AppRun ./appdir/usr/share/applications/*.desktop -appimage -verbose=2 -executable=./appdir/usr/bin/MatterSlice
#find ./appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq # fyi
./*.AppImage --appimage-updateinformation # fyi

The key here is /usr/lib/mono/4.5/mkbundle.exe MatterSlice.exe --deps -L /usr/lib/mono/4.5/ -o MatterSlice which transforms the MatterSlice.exe assembly into the MatterSlice Linux executable.

Older information

Just unpacking Mono-based applications almost never works because they clutter libraries all over the place which need to be specifically installed, otherwise we get errors like

Unhandled Exception:
System.IO.FileNotFoundException: Could not load file or assembly 'log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=a5715cc6d5c3540b' or one of its dependencies.
File name: 'log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=a5715cc6d5c3540b'
[ERROR] FATAL UNHANDLED EXCEPTION: System.IO.FileNotFoundException: Could not load file or assembly 'log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=a5715cc6d5c3540b' or one of its dependencies.
File name: 'log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=a5715cc6d5c3540b'

For example, liblog4net1.2-cil_1.2.10+dfsg-6_all.deb has a nasty postinstall script that does

#!/bin/sh
set -e
# Automatically added by dh_installcligac
if [ "$1" = "configure" ] && [ -x /usr/share/cli-common/gac-package-install ]; then
	/usr/share/cli-common/gac-package-install liblog4net1.2-cil
fi
# End automatically added section
# Automatically added by dh_cligacpolicy
if [ "$1" = "configure" ] && [ -x /usr/share/cli-common/policy-install ]; then
	/usr/share/cli-common/policy-install log4net 1.2
fi
# End automatically added section

Why such things are needed is completely beyond me. Have they never heard of $PATH and /usr/lib?

What are we supposed to do with this when putting together an AppImange?

cat ./usr/share/cli-common/packages.d/liblog4net1.2-cil.installcligac
/usr/lib/cli/log4net-1.2/log4net.dll

Hence we seemingly need to do something along the lines of

cat ./usr/share/cli-common/packages.d/*.installcligac > DLLS
sed -i -e 's|/usr|./usr|g' DLLS
DLLS=$(cat DLLS)
cp $DLLS usr/lib/
rm DLLS

Please edit this page if you know more.

Reader comments

@Juniorsnet reports

I success do a app.image from my C#/Gtk# mono app. (by hand)

  1. Download a runtime from mono servers https://download.mono-project.com/runtimes/raw/ in my case 4.8.0-linux-libc2.12-i386.zip

  2. The Zip file has 3 folder (etc, lib, bin), put lib/ and bin/ under AppDir/usr/ and etc/ folder in AppDir

  3. make AppDir/usr/bin/mono executable

  4. In my case aditionally y grab the GTK# libs from ubuntu packages and put in AppDir/usr/lib/cli

  5. Important: Edit all Dll Maping of GTK# libs (info) and remove the absolute path.

The File gtk-sharp.dll.config has:

<configuration>
  <dllmap dll="libglib-2.0-0.dll" target="libglib-2.0.so.0"/>
  <dllmap dll="libgobject-2.0-0.dll" target="libgobject-2.0.so.0"/>
  <dllmap dll="libatk-1.0-0.dll" target="libatk-1.0.so.0"/>
  <dllmap dll="libgtk-win32-2.0-0.dll" target="libgtk-x11-2.0.so.0"/>
  <dllmap dll="gtksharpglue-2" target="/usr/lib/cli/gtk-sharp-2.0/libgtksharpglue-2.so"/>
  <dllmap dll="glibsharpglue-2" target="/usr/lib/cli/glib-sharp-2.0/libglibsharpglue-2.so"/>
</configuration>

Remove the absolute path.

<configuration>
  <dllmap dll="libglib-2.0-0.dll" target="libglib-2.0.so.0"/>
  <dllmap dll="libgobject-2.0-0.dll" target="libgobject-2.0.so.0"/>
  <dllmap dll="libatk-1.0-0.dll" target="libatk-1.0.so.0"/>
  <dllmap dll="libgtk-win32-2.0-0.dll" target="libgtk-x11-2.0.so.0"/>
  <dllmap dll="gtksharpglue-2" target="libgtksharpglue-2.so"/>
  <dllmap dll="glibsharpglue-2" target="libglibsharpglue-2.so"/>
</configuration>
  1. Create a bash that set the MONO_PATH environment variable and the LD_PATH_LIBRARY to add de folder where library has, for example in my script:
#!/bin/bash
echo "Ejecutando VisorDatosTaco en ${0}"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib/mono/4.5:./lib/cli/gtk-sharp-2.0:./lib/cli/atk-sharp-2.0:./lib/cli/gdk-sharp-2.0:./lib/cli/glade-sharp-2.0:./lib/cli/glib-sharp-2.0:./lib/cli/gtk-dotnet-2.0:./lib/cli/pango-sharp-2.0:./lib/cli/webkit-sharp-1.1
export MONO_PATH=./lib/mono/4.5:./lib/cli/gtk-sharp-2.0:./lib/cli/atk-sharp-2.0:./lib/cli/gdk-sharp-2.0:./lib/cli/glade-sharp-2.0:./lib/cli/glib-sharp-2.0:./lib/cli/gtk-dotnet-2.0:./lib/cli/pango-sharp-2.0:./lib/cli/webkit-sharp-1.1
MONO_LOG_LEVEL=debug ./bin/mono --config ../etc/mono/config ./local/bin/VisorDatosTaco.exe

And thats it, at least is a start point.

Here is my app.image Visor.appimage you can decompress and see how is the structure.

AppImage is really great.

Another approach using ENVs

According to http://stackoverflow.com/questions/14452550/how-can-i-configure-mono-to-use-the-correct-paths-to-etc-directory-when-the-pre someone was able to get this working. It turns out there are a number of variables that need to be set to make the relocation work correctly. The following code assumes the mono installation was moved to ${PKG_DIR}. Some of these may not be necessary:

export LD_LIBRARY_PATH=$PKG_DIR/lib64:$PKG_DIR/lib:$LD_LIBRARY_PATH
export LD_RUN_PATH=$LD_LIBRARY_PATH
export PKG_CONFIG_PATH=$PKG_DIR/lib64/pkgconfig:$PKG_CONFIG_PATH
export MONO_GAC_PREFIX=${PKG_DIR}
export MONO_PATH=${PKG_DIR}/lib/mono/4.0:${PKG_DIR}/lib/mono/3.5:${PKG_DIR}/lib/mono/2.0:${PKG_DIR}/lib/mono/compat-2.0
export MONO_CONFIG=${PKG_DIR}/etc/mono/config
export MONO_CFG_DIR=${PKG_DIR}/etc
export C_INCLUDE_PATH=${PKG_DIR}/include
export ACLOCAL_PATH=${PKG_DIR}/share/aclocal
export FONTCONFIG_PATH=${PKG_DIR}/etc/fonts
export XDG_DATA_HOME=${PKG_DIR}/etc/fonts
export MONO_REGISTRY_PATH=~/.mono/registry

Mono runtimes

These are all but documented and seemingly cannot be downloaded using a normal browser but wget. It is almost like Microsoft is actively hiding them:

Apparently these are originally meant for a tool called mkbundle which is supposed to do a very similar thing like AppImage but often fails at doing the job: https://forum.ubuntuusers.de/topic/monodevelop-mkbundle-bug-tipp/ Can AppImage succeed where mkbundle fails?

Clone this wiki locally