Building a Debian package properly involves many different tools and concepts that build on top of each other. You have already been introduced to the lowest-level tool – dpkg-deb – in last blog. In this blog we will learn how to use a higher-level tool called dpkg-buildpackage, which uses dpkg-deb under the hood.

Why dpkg-buildpackage?

Debian has some pretty detailed rules on what a “proper” Debian package looks like. These rules are specified in the Debian Policy Manual. All packages submitted to Debian’s (and Ubuntu’s) repositories must adhere to these rules. For example:

  • all packages are expected to contain basic documentation (even documentation on the packaging itself) in /usr/share/doc.
  • they are expected to be signed with digital signatures
  • they are expected to conform to the Filesystem Hierarchy Standard.
  • they are expected to include Systemd service units where applicable.
  • they are expected to exclude debugging information from all C/C++ binaries.
  • RubyGems and NPM modules are expected to live in very specific directories

…blah blah

Debian provides tools that help us conform to such rules (the most important one is debhelper, which we will describe later). But these tools depend on a specific workflow and a specific structure for our package specification. dpkg-buildpackage is a tool that enforces said workflow and structure. Dpkg-buildpackage replaces dpkg-deb: it calls dpkg-deb under the hood.

But this workflow and these structures take a while to learn compared to just using dpkg-deb. If you’re just packaging your own app, with no intention to submit your packages to the Debian/Ubuntu repositories, then you may wonder why you should care about dpkg-buildpackage instead of continuing to use dpkg-deb. The answer is tooling: pretty much all Debian packaging tools expect you to follow the dpkg-buildpackage workflow/structure. There are tools that check for common problems in packages; tools that help you build packages for multiple distributions and architectures from a single computer; tools that help you sign packages; and much more.

The dpkg-buildpackage workflow

Let’s say we have a hello world Python application named “hello”. It prints “hello 2.0.0” and we want it to be installed to /usr/bin.

dpkg-buildpackage requires you to specify:

  • Basic package metadata such as name, dependencies, description.
  • A changelog of how your package has evolved over time.
  • How your application should be compiled from source.
  • How the package file listing should be created from the application’s source directory and compiled products.

These specifications are stored in text files inside a directory named debian. This directory is expected to live inside the application’s source directory. So the structure you end up with looks as follows:

hello
...and other application source files...
debian/
    control
    changelog
    compat
    rules
    ...and other specification files...

Then you invoke dpkg-buildpackage (dpkg-buildpackage -b) from the application’s source directory. Dpkg-buildpackage compiles the application using your instructions. It also creates the package root directory (under debian/<source package name>) and create files in there based on your instructions on how that should be done. At this point you end up with a structure like so:

hello
...and other application source files...
debian/
    control
    changelog
    compat
    rules
    ...and other specification files...

    hello/   <--- this is the package root
        DEBIAN/
            control
            ...and other metadata files...
        usr/
            bin/
                hello
        ...and other application files...

Finally, it performs a bunch more postprocessing and runs dpkg-deb to create the .deb file in the parent directory:

../hello_2.0.0-1_all.deb

Now that you understand the workflow, let’s make a package.

Preparing the application

Create a directory for this blog. Inside the directory we place our simple hello world 2.0.0 application.

mkdir tutorial-2
cd tutorial-2
editor hello.py
chmod +x hello.py

hello should contain:

#!/usr/bin/env python
print("hello 2.0.0")

Creating the debian/ subdirectory

Dpkg-buildpackage expects a package specification under a subdirectory named debian/. This directory must contain at minimum the following files:

  • control – contains basic package metadata. It is similar to the one in last blog, yet there are differences.
  • changelog – contains a changelog of how the package has evolved over time. This is not just any plain text file – it must conform to a specific format that specifies a list of changelog entries and version numbers. Counterintuitively, dpkg-buildpackage infers the package version number from this file, and not from a “Version” field in control!
  • compat – specifies the minimum version of debhelper that your package needs. This probably means nothing to you right now, but that’s fine.
  • rules – a file, in Makefile format, that specifies how your application must be compiled and how the package root directory should be created.

debian/control

Put this in the control file:

Source: hello
Section: devel
Priority: optional
Maintainer: Shivam Singhal <championshuttler.com>
Build-Depends: debhelper (>= 9)

Package: hello
Architecture: all
Depends: python
Description: Shivam's Hello World Package written in python and prints greeting.

Note the differences from last blog:

  • A new section had been added at the beginning of the file. This section is called the source section, while the section at the end is called the package section.
  • A “Section” field has been added. It specifies which category this package falls in. In our case, we specify “devel” as in “developer tools”. See the Debian Policy Manual for a listing of section names.
  • A “Priority” field has been added. It specifies which how important this package is.
  • A “Build-Depends” field has been added.
  • The “Version” field is gone. As explained above, dpkg-buildpackage infers the version number from the changelog file.
  • The “Maintainer” field has been moved from the package section to the source section.

The “Source” field specifies the name of the source package, which may be distinct from the name of the binary package. A source package is a special kind of Debian package that does not contain binaries, but source code and packaging specification files, allowing anybody to build a binary package in a fully reproducible manner.

debian/changelog

Put this in the changelog file:

hello (2.0.0-1) stretch; urgency=medium

  * Initial packaging work with dpkg-buildpackage.

 -- Shivam Singhal <championshuttler@gmail.com>  Fri, 25 Dec 2018 09:19:24 +0000

The changelog file must conform to a specific format and is to contain a list of changelog entries. In this case, the changelog file only contains one such entry. The first line of the entry must be in the format of:

<source package name> (<version number>) <distribution name>; urgency=<urgency>

The middle of the entry must contain one or more bullet points that explain what has changed in this package version.

The last line specifies who made these changes, and must be in the format of:

 -- <name> <<email address>>  <date in RFC 2822 format>

Note that the file is extremely sensitive to spaces. For example there must be a space before the --, and there must be two spaces between the email address and the date.

Tip: you can obtain the current date in RFC 2822 format by running:

date -R

debian/compat

We’ll explain this file in next blog. For now just understand that it must contain the magic number “9”.

echo 9 > debian/compat

debian/rules

The rules file is a Makefile. Dpkg-buildpackage expects this file to contain three targets (and invokes them in this order):

  • clean – remove all compilation products.
  • build – compile the application.
  • binary – create a package root directory. This directory is expected to have the filename debian/<source package name>, so in this case debian/hello.

All of these targets are invoked from the application source directory, as described in section The dpkg-buildpackage workflow.

Put this in the rules file:

#!/usr/bin/make -f

clean:
	@# Do nothing

build:
	@# Do nothing

binary:
	mkdir -p debian/hello
	mkdir -p debian/hello/usr/bin
	cp hello debian/hello/usr/bin/
	dh_gencontrol
	dh_builddeb

Since hello is a Python application, there are no clean and build steps.

The binary step begins with a bunch of commands that create the package root directory and that populate it with files.

The binary step then ends with some boilerplate commands:

  • dh_gencontrol is a debhelper command which creates a DEBIAN/control file inside the package root directory. It takes the debian/control file that we wrote, performs some postprocessing (performs substitutions and adds some more fields) and writes the result to debian/hello/DEBIAN/control. For example, it automatically infers the size of the debian/hello package root directory and adds the “Installed-Size” field for you.
  • dh_builddeb is a debhelper command which invokes dpkg-deb to create the .deb file from the package root directory.

We will learn more about debhelper in next blog.

Building the package

Now you are ready to build the package:

dpkg-buildpackage -b

The -b flag tells dpkg-buildpackage to build a binary package.

Verifying that it works

When done, you will end up with a .deb file in the parent directory. Install it and verify that it works:

$ sudo gdebi -n ../hello_2.0.0-1_all.deb
$ hello
hello 2.0.0

Conclusion

Congratulations, you have learned how to use the dpkg-buildpackage workflow and structure to build a binary package! However this only concludes the beginning of your journey. In the next blog, let’s have a look at how we can package a C application, and introduce you to debhelper.

Thanks for reading. Cheers!!

I code at @championshuttler and tweet at @idkHTML. Let me know if you need any help.