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
copyright
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" )" && 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 fromdist
, 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.