Packaging of a Mono application on Linux

How to package a Mono (.NET) applications for Debian-based Linux in command line. Packaging process can be easily automated on Continues Integration Server.

debian_package

Package building workflow is quite simple:

  • Assign a version number to a .NET assembly code
  • Build binaries
  • Copy binaries to package folder and build deb-package with the same version as an assembly

Build process will be controlled by MSBuild project file and run from a continues integrations server. MSBuild on Mono platform is substituted by xbuild utility.

Sample solution is available for download here https://github.com/mchudinov/PackagingMono. Solution is compatible with Visual Studio 2012, MonoDevelop/Xamarin Studio 5.

This is my third blogpost about automation of development workflow with Mono. Automated building and versioning were covered in my previous posts:

I need to create a Debian package from my .NET binaries. The result should be a deb-package that satisfy the following requirements:

  • Package version is the same as an assembly version of an application
  • While installation checks if another version of the same package is already installed
  • Requires Mono package. If there is no Mono installed then installation fails
  • Installs binary into predefined folder. I use /opt/{MyProgramName}
  • While installation checks if a old configuration file is present and asks user to overwrite or keep it

Official Debian packaging documentation is available here Debian Policy Manual – Binary Packages. I will keep the packaging process as simple and minimalistic as possible.

1. Prepare packaging environment
Use any Debian/Ubuntu-flavour Linux (I use Mint). A couple of programs are needed on build server to build deb packages. Let’s install them first:

dos2unix is a converter from DOS to UNIX text file format. I will need this program when assign a version number to my package.
fakeroot runs a command in an environment wherein it appears to have root privileges for file manipulation. It will be used for packaging.
rsync is a file copying tool. It will be used to copy files into package folder structure.
Lintian is optional. This program helps to identify bugs in Debian packages.

2. Prepare folders
Add a folder called Package to root of the C# solution and create the following structure inside it

Folder deb is placed just inside the Package folder. This is package root. There are more folders inside the package root: folder DEBIAN (in capital letters!) with two files control and conffiles. Folder DEBIAN contains files that are used while installation.

And another folder in my package root is opt. This is because I want to install my program to /opt folder of Linux operating system.

While installation all folders from package root will be copied to a Linux OS root. Thus folder structure must be exactly the same as we want it to be after installation. I will install my program to /opt/PackagingMono. Where PackagingMono is the name of my application. Thus I have /deb/opt/PackagingMono in my package folder structure.

DEBIAN/control file
This is the only file that is required to create a package. All the other files inside DEBIAN folder are optional. control file has mandatory fields, recommended fields and optional fields:

Attribute Description Status Examples
Package The name of the binary package. [a-zA-Z0-9-] – only Latin letters, numbers and dash. This name is used in installation: apt-get install {package} mandatory Package: PackagingMono
Version The version number of a package. Version is used while installation to determine should package be updated or not. mandatory Version: 1.0-1
Version: 2009.12.12-1
Architecture A unique single word identifying a Debian machine architecture or an architecture wildcard. Possible values: i386, amd64, all, source. mandatory Architecture: all
Architecture: amd64
Maintainer The package maintainer’s name and email address. The name must come first, then the email address inside angle brackets <> mandatory Maintainer:Mikael Chudinov <mikael@chudinovnet>
Description A description of the binary package, consisting of two parts, the synopsis or the short description, and the long description. It is a multiline field with the following format:Description: <single line synopsis>
<extended description over several lines>
mandatory Description: Short.
␣Long
␣goes here.
␣.
␣New line.
Section This field specifies an application area into which the package has been classified. Possible values: admin, base, comm, contrib, devel, doc, editors, electronics, embedded, games, gnome, graphics, hamradio, interpreters, kde, libs, libdevel, mail, math, misc, net, news, non-free, oldlibs, otherosfs, perl, python, science, shells, sound, tex, text, utils, web, x11 recommended Section: misc
Priority This field represents how important it is that the user have the package installed. Possible values: extra, optional, standard, important, required (these can not be uninstalled!) recommended Priority: optional
Depends Relationships to other packages Depends: dpkg, libz (>= 1.2.3), jpeg (= 6b), png (< 2.0)

More information in Debian documentation Binary package control files — DEBIAN/control.

Here is a simple DEBIAN/control file for a Mono application:

 

DEBIAN/conffiles contains a list of configuration files
Here is a section of Debian manual about configuration files Configuration files. If a configuration file is listed in DEBIAN/conffiles then local changes will be preserved during a package upgrade, and user will be asked about to keep or overwrite changes.

Manual says about location of configuration files that “Any configuration files created or used by your package must reside in /etc“. But for simplicity we place configuration file in the same folder as binary. The name template of a config file is {ProgramName}.exe.config.

My conffiles for this project contains just one line

 

DEBIAN/(preinst|postinst|prerm|postrm) installation scripts
There are 4 installation scripts may be used in a package:

Script Description
DEBIAN/preinst This script will be executed before the package installation. It should prepera the environment for successful installation. I use it to install mono.
DEBIAN/postinst It will be executed right after installation.
DEBIAN/prerm This script will be executed before package removal.
DEBIAN/postrm This script will be executed right after package removal.

My simple DEBIAN/preinst script for a Mono-based application installs Mono framework:

 

gitignore (hgignore)
Ignore files are not needed for packaging itself. But we need them to submit folder structure to Git/Hg source control. One -ignore file should be placed right in the /deb folder:

And another one should be placed in installation folder. This is /opt/PackagingMono in my case:

 

3. MSBuild building script file
Packaging is a part of a application building process. I control building process through a single MSBuild project file. This project file is used by continues integration server. Building file  contains the following simple steps (targets in MSBuild terminology):

  • Clean project
  • Restore NuGet packages
  • Assign version to the assembly
  • Build binaries
  • Packaging with following substeps:
  • Set version number to deb-package in DEBIAN/control
  • Copy binaries to package root
  • Start packaging process

Restoring NuGet packages, building binaries, testing and versioning of assembly steps were covered in my previous blogposts Building and Testing and Versioning.

My example MSBuild file

 

4. Copy binaries to package root
I want to install my program to /opt/PackagingMono folder. Thus I have /deb/opt/PackagingMono folder in my package folder structure. My program files must be placed in the package folder structure after building of assemblies and before packaging. This is a separated Copy step in my Build.proj MSBuild project file. I use a temporary folder to operate on it while the original Package/deb folder stays untouched.

Workflow:

  • Delete old deb package and old temporary folder if present
  • Create a new temporary folder Package/temp
  • Copy package structure from Package/deb to a temporary folder Package/temp using rsync
  • Copy all program files from …/bin/Release folder to Package/temp/opt/{MyProgramName} using Copy MSBuild task.

Here is a part of build script responsible for files copy:

 

5. Versioning
I like when my packages have the same version number as .NET assemblies it contains. Versioning of an assembly using an MSBuild task was covered in my previous post about Versioning.

Version number in ../Package/deb/DEBIAN/control file must be a pattern string, not a real number. Patter string can be for instance Version:{xxx}  The patter string will be changed to real version number by MSBuild script in temporary ../Package/temp/DEBIAN/control file.

Workflow:

  • Read version number from assembly using Assembly target from MSBuild.Extension.Pack
  • Update version number in temporary DEBIAN/control file using pattern {xxx} and FileUpdate target from MSBuild Community Tasks
  • Run dos2unix to change line ending to unix format in DEBIAN/control. It’s needed because FileUpdate task changes line endings in control file to Windows style after which dpkg utility can not use it.

Part of my build script that will change versio number in DEBIAN/control file.

 

6. Build a package!
Now all the files are placed where they should be. It’s time to build a package! First of all files and folders in the package must have root:root group:user access. The easiest way to achieve this is to use fakeroot command:

Workflow:

  • Create the package using dpkg-deb and fakeroot
  • Rename created deb file to a file with version number in name. Just copy created xxx.deb file to {SolutionName}_{Version}_{Architecture}.deb. {Architecture} is optional and can be amd64 for instance. It should be the same as Architecture parameter used in DEBIAN/config file.
  • Delete temporary ../Package/temp folder

Done! That is all we need to build a simple binary .NET/Mono application package by continues integration server. xbuild runs Build.proj file and creates deb-package in {solutionroot}/Package folder.

Let’s run build script from command line:

Package that was just created can be checked by Lintian program:

There are a couple of errors reported by Lintian. But as I mentioned I run a simple build process with a minimalistic package. The created package will work in spite of these errors.
Let’s test the installation and run the program: