Thursday, May 13, 2010

How to build an Ubuntu Package

The following text is meant to give a quick little overview on how to build a package for Ubuntu from preparing your source code to publishing the final results. The text assumes that you want to package a project of your own, so for packaging other peoples work, you can skip a few steps. It won't go into to much detail and leave out quite a bit, so have a look at the official manuals for anything not explained here:
1. Figure out the dependencies
Before one starts with the actual package building, one should cleanup and fix the projects source code itself. This basically means getting a rough overview of what the dependencies are and which of those are already available in Ubuntu. If something is not in Ubuntu it either needs to be moved into the source tree or packaged separately.

2. Write a Makefile and make sure that all targets work
You need to make sure that your Makefile can not only build the project, but also install it and handle PREFIX and DESTDIR properly, namely the following targets should work:
  • ./configure --prefix=/tmp/foobar
  • make install DESTDIR=/tmp/
  • make clean
If you are not using make, but some non-standard build setup, it can be useful to simply wrapper your build setup into a make file. This is not strictly needed, but can make things a little easier later on, see this example Makefile. You also need a way to build a proper tarball release file, I simply use git archive or svn export, but you can also make that part of your build system if you prefer, it doesn't really matter when it comes to building a .deb package.

3. Native vs non-native packages
Deb packages come in two forms, native ones and non-native ones. The core difference is that non-native ones come with an .orig.tar.gz, that contains the original source code, and a patch file, that contains your changes, while native ones just come with a single tarball. Native packages make only sense for things for which the .deb release is the primary and only release in existence, for software that is supposed to be released via a normal .tar.gz release a native package are never the right choice.

To make it short: You want to build a non-native package.

4. Version numbers
Version numbers for packages are build in the form of: UPSTREAMVERSION-DEBIANREVISION
UPSTREAMVERSION is the version number of your package, i.e. 1.2.3, while DEBIANREVISION is normally just an integer giving the version of your deb package. The package build scripts will take the version number out of the debian/changelog file.

For packages that are based on a development version directly out of SVN, Git, etc. you want a non-standard version number that smoothly integrates with official releases To accomplish that you have basically two ways "0.1.2~svn1234" and "0.1.2+svn1234". The tide (~) character is special in that it lowers your version number, not increases it, so "0.1.2" is larger then "0.1.2~svn1234", which makes this numbering scheme extremely useful for pre-releases, beta version and all that stuff that comes before a final release. The plus (+) on the other side increases the version number, so "1.2.3" is small then "1.2.3+svn1234". This is useful for cases where the next release is still far of, but SVN contains some small bug fixes that you want to package.

If you are not sure about version numbers, you can use dpkg to test them:

dpkg --compare-versions "0.1.2" ">" "0.1.2~svn123" && echo "Yes" || echo "No"

5. Where to store the debian/ directory
Deb packages are build by having some meta data and scripts in a debian/ directory in the source code of your project, however you do not want to have the debian/ directory to actually be in your repository. The reason for that is that it muddles the borders between Ubuntu specific hacks and your main source tree. Things are cleaner and easier when you keep them separate and all package build tools basically assume that.

Luckily you don't have to keep things managed manually git-buildpackage will do most of the work for you and automatically track what changes you do to the upstream source tree. So here is how it works:
  1. build a normal tarball release of your source code
  2. go to an empty directory (named ubuntu/ here) and create in that directory a directory named after your project, we need two levels of directories here since git-buildpackage will store files in "../"
  3. go to ubuntu/project/ and call: git init
  4. call: git-import-orig -u 0.1.0 ../../path/to/project-0.1.0.tar.gz
If you have done all that you can check git tag and git branch to see that git-import-orig has created a upstream branch and a master branch. The master branch is where you will do your work, the upstream branch is a copy of the content of your tarball, you should handle it as read-only and not touch it.

6. Creating the initial debian/ directory
Once you have imported the upstream source into git, you can start creating the debian/ directory. dh_make is a tool that will provide some guidance in the process, but you can also just copy a debian/ directory from another project and modify it, which can often be easier. The following files are important:
  • changelog: This contains the version number of the package as well as a comment for the release
  • control: This contains metadata, such as the project name, a description, etc. the Build-Depends field is where you have to enter all the dependencies, the Dependencies for the binary itself can be figured out mostly automatically
  • rules: This is the Makefile that will be used to build the package, you might notice that the default one is extremely short, containing not much more then "dh $@"
  • copyright: This describes the copyright of the package
When using dh_make, make sure to change unstable to lucid in debian/changelog, as the former one would be rejected by launchpad (see below).

7. Fine tuning debian/rules
The debian/rules script will call dh and automatically try to guess what build system your program is using and call the appropriated scripts, when things don't work, you don't need to get rid of dh, instead you can override steps of the build process. To override the install process you would simply add a target like this to debian/rules:

override_dh_auto_install:
make install PREFIX="/usr" DESTDIR="$(CURDIR)/debian/xboxdrv"

To override the build process you would use override_dh_auto_build.

8. Building the package
If you think the debian/ directory is finished, you can start to build the package. Go to the top level directory and execute:

git-buildpackage --git-ignore-new

Normally when your repository contains uncommited changed, the process would fail, but --git-ignore-new allows it to continue. When everything went well, you should end up with a bunch of packages in "../", if stuff went wrong, you should have gotten a easy to read error message.

When running git-buildpackage it will do a "make clean" before each build, which can be annoying when you try to find an error late in the build process, to work around that you can also simply call debian/rules binary directly.

9. Testing the package
To test the package just install it with dpkg -i, you might also want to look at the content with dpkg-deb -c, if the build script contains a bug, its easy to have a few files gone missing, this way thats easy to spot.

10. Building the package in a clean environment
If you have successfully build and tested your package, you are not done yet. You also want to make sure that it builds and works on a clean installation. Luckily you don't have to install a new Ubuntu or manually mess around with chroot, the pbuilder script will do that automatically for you. Simply apt-get install pbuilder and then call:

sudo pbuilder --create

Once pbuilder is finished downloading and installing the necessary components, you might also want to run:

sudo pbuilder update --components "main restricted universe multiverse" --override-config

This will allow you to not only use standard Ubuntu packages, but also things from universe and multiverse, which you will likely need for any more complex package.

Once pbuilder is completly finished the actual package building is just a matter of:

sudo pbuilder --build yourpackage_version.dsc

pbuilder will automatically grab all the build dependencies and build your package, if successful it will put the results in /var/cache/pbuilder/results/.

11. Building source packages and tagging your repository
Once you are completly done with the testing and building in a clean environment you want to finish up and publish your results, to do that you have to build a source package and while at it, you can also tag your repository automatically with the right version number with:

git-buildpackage --git-tag --git-builder="debuild -S"

12. Uploading your packages
You don't want to upload a raw .deb, instead you want to provide a complete apt repository, luckily this process is completly automatic, all you need is create yourself an account at https://launchpad.net/ and upload your GPG key. Once done, you can simply upload your package with:

dput ppa:your_username/ppa package_version-1_source.changes

Launchpad will take the source code and build it automatically for i386 and amd64.

Note: I am not sure why dput wants sources.changes, while pbuilder is happy with .dsc.

13. Using your package
To finally use your finished and release package you just have to add the PPA to your apt sources, which can be done with:

sudo add-apt-repository ppa:your_username/ppa
sudo apt-get update
sudo apt-get install your_package

14. Getting your package accepted into the official Debian/Ubuntu repositories
[TODO]

Trouble
  • Sometimes build files slip into the git repository, not sure when or why this happens, I guess it is at the git-import-orig step when you import into a non-cleaned working directory
  • Some files tend to constantly get in the way, namely .pc, debian/${yourproject}/, debian/${yourproject}.debhelper.log and debian/${yourproject}.substvars not sure why they get left over and not cleaned up

1 comment:

stevec said...

Thanks for this write-up.

-- steve