Debian packaging is something that is both simple and elegant, and hard to get into. There is a huge amount of information about it available online, a lot of it on Debian's own wiki. Ubuntu, being a Debian derivative, also uses Debian packaging. Their Launchpad website offers automatic building of packages, and providing a so called PPA, which is a personal Apt repository. So, Debian provides the techniques, and Ubuntu provides an easy platform to host packages.

To make creating packages somewhat easy, there is a toolset called debhelper. These tools all rely on specific folder structure, which contains control files and specifications about the package to build. Basically, if you have a folder called project which contains some sort of buildable project, project/debian must contain the files needed for debhelper to build the package.

I'll try to illustrate the process and steps that are needed to build a Debian package. I'll assume that you have a Java project, with an Ant build file that contains all actions necessary to build your project. We'll be extending this build script to automatically prepare for the debhelper tools.

Debian subdir

By far the most important files that you will need are changelog, control and rules. Apart from these, there are a few more files which are necessary.

changelog determines the version number of the package, and will be embedded to show all changes since the last version. A good idea is to autogenerate this file based on revision information in whatever SCM tool you're using. git-dch is such a tool for Git, and it's the one I will be using. The changelog will also be installed as /usr/share/doc/appname/changelog.Debian.gz.

control will contain a short summary and longer description, as well as all dependencies and conflicts the package requires and introduces.

rules can contain overrides and modifications to the standard package building process, more on this later.

compat contains some sort of compatability identifier for debhelper. “7” is the only value that I have ever needed to use.

copyright should contain the license information for your project. Will also be installed to /usr/share/doc/appname/copyright.

appname.install a list of files that will be installed by the package. Every line contains a source and destination path, seperated by whitespace. The source is relative to the root of the project, The destination is relative to the destination of the package, usally /.

appname.manpages a list of filenames which be installed as man pages. Paths are relative to the root of the project.

appname.README.Debian will be installed as /usr/share/doc/appname/README.Debian.gz.

source/format determines what build process debhelper will use to create the package. A few different formats exist, but I have found that “3.0 (native)” works best for Java projects.

appname/ will be the staging area for the ‘zipping’ part of packaging. Should be cleaned (automatically) between runs.

So far everything has been quite theoretical, let me show you how to apply the above in a Java project.

Actually doing it

The first thing you'll want to do, is determine what files you should generate, and what can be created as a static file. changelog, appname.install, appname.manpages are good candidates to auto-generate as part of the build, as they all contain information that will change between builds and versions. I personally also like to auto-generate my README, which means that appname.README.Debian is taken care of. All other files can be created statically. Lets get those out of the way first.

Static files

compat

echo "7" > debian/compat

source/format

mkdir -p debian/source/format
echo "3.0 (native)" > debian/source/format

Quite freeform, should contain your copyright and a short summary of the license. I personally use this format for GPLv3, each license has their own recommendations about this.

$ cat debian/copyright 
Copyright (c) year company/person < user [at] domain . com >
          
project is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

project is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
		  
You should have received a copy of the GNU General Public License
along with project. If not, see <http://www.gnu.org/licenses/>.

control

Information about the build and runtime requirements of your project.

Make sure you fill Build-Depends with architecture specific build dependencies, and Build-Depends-Indep with architecture independent build dependencies. As Java is usually cross-platform, most of your build dependencies will go into Build-Depends-Indep. javahelper is a set of tools that work in conjunction with debhelper, so you'll probably want that too.

Depends must contain all runtime dependencies. I usually package my Java libraries with my project, so this list will be quite short.

$ cat debian/control 
Source: appname
Priority: extra
Maintainer: person <user@domain.com>
Build-Depends: debhelper (>= 7.0.50~)
Build-Depends-Indep: default-jdk, ant, javahelper (>=0.25)
Standards-Version: 3.9.0
Section: utils

Package: appname
Architecture: all
Depends: ${misc:Depends}, coreutils, default-jre
Description: Short one line description.
 Longer, multi-line description. Empty lines must contain just '.'.
 .
 Second paragraph.
 .
 Third paragraph.

rules

A Makefile that's used by debhelper. Overrides should be put here. The %: target makes sure that javahelper is used in all steps. The jh_exec step doesn't work for me, so I override and disable it. dh_link is used to create a symlink in /usr/bin to the final location of the app in /usr/share/appname. dh_auto_build will call the default target of the Ant build file, but I actually want to use a different target called ‘dist’, so I override it. I also have a seperate build target called ‘package-debian’, which dynamically generates the other Debian package files, and should be called in the dh_install step.

$ cat debian/rules 
#!/usr/bin/make -f

JAVA_HOME=/usr/lib/jvm/default-java

%:
	dh $@ --with javahelper

override_dh_link:
	dh_link usr/share/appname/appname usr/bin/appname

override_dh_auto_build:
	dh_auto_build -- dist

override_dh_install:
	dh_auto_build -- package-debian
	dh_install

override_jh_exec:
	exit 0

Dynamic files

changelog

I prefer to use a tool that can generate a changelog based on my versioning history, because that makes a good changelog by nature. Because my usual SCM is Git, I use git-dch. Explaining how git-dch works is beyond the scope of this post, but I can give you the command line I use to update my changelog:

git dch --ignore-branch --snapshot --auto --git-author
git dch --ignore-branch --release --auto -N $(VERSION) --git-author

--auto will try to determine what the last commit in the changelog is, and update the changelog with all changes after that. The first command will create a so called snapshot update. This will allow you to create a package that is a pre-release of the next version. The second command will create a new version entry in the changelog, with the specified number.

appname.*

appname.install, appname.manpages and appname.README.Debian are all generated by my Ant build file. I usually include a README and manpage generator in my projects, which I will automatically call when building the project. If I can generate it, I also know what files are created, so I should just create appname.manpage and appname.README.Debian based on what I know. appname.install is also created by Ant. I tell Ant to determine my classpath, and I automatically copy these files to the right location, and generate the appname.install based on the classpath. Below are some excepts from my build.xml:

<?xml version="1.0" ?>
<!--

 Copyright (c) year company/person < user [at] domain . com >

 This file is part of appname.

 appname is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 appname is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with appname. If not, see <http://www.gnu.org/licenses/>.

-->
<project name="appname" basedir="." default="dist"> 

	<!-- Set up properties containing important project directories --> 
	<property name="dist.dir" value="dist"/>
	<property name="libs.dir" value="lib"/>
	<property name="executable" value="appname"/>
	<property name="debian.dir" value="debian"/>
	<property name="debian.tmp.dir" value="${debian.dir}/tmp"/>
	<property name="debian.bin.dir" value="usr/share/${executable}"/>
	<property name="debian.lib.dir" value="${debian.bin.dir}/lib"/>

	<path id="dist_libraries">
		<fileset dir="${libs.dir}">
			<include name="*.jar"/>
			<exclude name="*-javadoc.jar"/>
			<exclude name="*-sources.jar"/>
		</fileset>
	</path>
	
	<pathconvert property="" pathsep=" ">
		<path refid="dist_libraries"/>
		<map from="${basedir}" to="."/>
	</pathconvert>

	<pathconvert property="debian.class.path" pathsep=" ">
		<path refid="dist_libraries"/>
		<map from="${basedir}" to="/${debian.bin.dir}"/>
	</pathconvert>

	<pathconvert property="debian.pkg.jars" pathsep="${line.separator}">
		<path refid="dist_libraries"/>
		<mapper type="flatten"/>
		<map from="${basedir}" to=""/>
	</pathconvert>

	<pathconvert property="debian.pkg.jars.dest" pathsep="${line.separator}">
		<path refid="dist_libraries"/>
		<map from="${basedir}/${libs.dir}" to="${debian.lib.dir}"/>
	</pathconvert>

	<target name="print-classpath" description="Show the dependency class path">
		<property name="relpath" value="${project.class.path}" relative="yes" basedir="${basedir}"/>
		<echo>${relpath}</echo>
	</target>

	<target name="clean" description="Remove all generated files">
		<delete dir="${class.root}" />
		<delete dir="${dist.dir}" />
		<delete file="${executable}" />
		<delete file="${executable}-debug" />
		<delete>
			<fileset dir="${basedir}" includes="${executable}-*.tar.bz2" />
			<fileset dir="${basedir}/debian">
				<include name="${executable}.1" />
				<include name="${executable}.install" />
				<include name="${executable}.manifest" />
			</fileset>
		</delete>
		<delete dir="${debian.tmp.dir}" />
		<delete dir="${debian.dir}/${executable}" />
	</target>

	<!-- Create a clean dist directory -->
	<target name="dist" depends="man-page,readme" description="Create a clean dist directory">
		<delete dir="${dist.dir}"/>
		<mkdir dir="${dist.dir}"/>
		<mkdir dir="${dist.dir}/${docs.dir}"/>
		<mkdir dir="${dist.dir}/${libs.dir}"/>

		<copy file="${docs.dir}/configuration.example" tofile="${dist.dir}/${executable}.config.example"/>

		<copy todir="${dist.dir}/${libs.dir}">
			<fileset dir="${libs.dir}">
				<include name="*.jar"/>
				<exclude name="*-javadoc.jar"/>
				<exclude name="*-sources.jar"/>
			</fileset>
		</copy>

		<copy file="${docs.dir}/${executable}.1" tofile="${dist.dir}/${docs.dir}/${executable}.1" />
		<copy file="README.md" tofile="${dist.dir}/README.md" />
		<mkdir dir="${class.root}/META-INF" />
		<copy file="LICENSE" tofile="${class.root}/META-INF/LICENSE" />

		<jar destfile="${dist.dir}/${executable}-${version}.jar" basedir="${class.root}">
			<manifest>
				<attribute name="Implementation-Vendor" value="Cyso BV."/>
				<attribute name="Implementation-Title" value="${executable}"/>
				<attribute name="Implementation-Version" value="${version}"/>
				<attribute name="Built-By" value="${user.name}"/>
				<attribute name="Sealed" value="false"/>
				<attribute name="Class-Path" value="${jar.class.path}"/>
				<attribute name="Main-Class" value="${root.package}.${entry.point}"/>
			</manifest>
		</jar>

		<delete dir="${class.root}/META-INF" />

		<echo file="${dist.dir}/${executable}" append="false">#!/bin/sh
if [ -L "$0" ]; then
	CMD="$(readlink -f "$0")"
else
	CMD="$0"
fi

DIR="$( cd "$( dirname "$CMD" )" &amp;&amp; pwd )"
java -jar $DIR/${executable}-${version}.jar ${root.package}.${entry.point} "$$@"</echo>
		
		<chmod file="${dist.dir}/${executable}" perm="a+x"/>
	</target>

	<target name="package-debian" depends="dist" description="Prepare project for Debian packaging">
		<echo file="debian/${executable}.install" append="false">dist/${executable}-${version}.jar ${debian.bin.dir}
dist/${executable} ${debian.bin.dir}
</echo>
		<echo file="debian/jars" append="false">${debian.pkg.jars}</echo>
		<exec executable="sed" output="debian/${executable}.install" append="true">
			<arg value="s#^#${libs.dir}#;s#$$# ${debian.lib.dir}#" />
			<arg value="${debian.dir}/jars" />
		</exec>

 		<delete file="debian/jars" />
		<echo file="debian/${executable}.manpages" append="false">${docs.dir}/${executable}.1</echo>
		<copy file="README.md" tofile="debian/${executable}.README.Debian" />
	</target>

	<target name="man-page" depends="compile" description="Generate man page">
		<java classpath="${project.class.path}" classname="nl.something.something.docs.ManPage" output="${docs.dir}/${executable}.1" />
	</target>

	<target name="readme" depends="compile" description="Generate README">
		<java classpath="${project.class.path}" classname="nl.something.something.docs.Readme" output="README.md" />
	</target>
</project>

A lot of XML, but the main parts are:

  • The clean target. Automatically called by debhelper during dh_clean. Expects it to clean up unwanted run-specific files.
  • The dist target. Automatically called by debhelper during dh_autobuild. Compiles and packages the Java project.
  • The package-debian target. Automatically called by debhelper during dh_install. Takes the files from dist, generates Debian packaging specific files and create the .deb file.

I omitted the actual compiling step from the XML file, because that is too dependant on your specific Java project. The process as a whole stays the same, and I assume you know the right way to compile your project.

Putting it all together

Now that we have build target to auto-generate all necessary files, and have created the static files, it's time to tell debhelper to actually build the package. Call this command from the root of your project:

$ dpkg-buildpackage -A -us -uc

-A will tell it to build a architecture-independant package. -us and -uc will skip the package signing part. If all went well, the .deb file will be created in the folder above your project folder.

Conclusion

These were my own experiences with trying to package a Java project. I used the above knowledge to package glaciercmd, and have a PPA for that at Launchpad. I'm sure that parts of the above can be more streamlined or improved. Let me know if you have any questions or suggestions.