aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/_posts/2021-03-10-ubuntu-packaging.md
diff options
context:
space:
mode:
Diffstat (limited to '_posts/2021-03-10-ubuntu-packaging.md')
-rw-r--r--_posts/2021-03-10-ubuntu-packaging.md320
1 files changed, 320 insertions, 0 deletions
diff --git a/_posts/2021-03-10-ubuntu-packaging.md b/_posts/2021-03-10-ubuntu-packaging.md
new file mode 100644
index 0000000..431651e
--- /dev/null
+++ b/_posts/2021-03-10-ubuntu-packaging.md
@@ -0,0 +1,320 @@
+---
+title: Basic Ubuntu packaging
+excerpt: >
+ Packaging your software to be published in a PPA for the first time is a
+ pain.
+custom_css:
+ - snippets.css
+snippets_root_directory: snippets/ubuntu_packaging
+snippets_language: plain
+snippets:
+ basic:
+ - basic/changelog
+ - basic/control
+ - basic/copyright
+ - basic/rules
+ install:
+ - install/test.install
+ gbp:
+ - gbp/gbp.conf
+---
+It took me about an hour to make a PKGBUILD for my simple, non-compiled
+piece of software to be published on [AUR].
+In contrast, it took me a few days to figure out how to build suitable .deb
+packages for publishing in a PPA on [Launchpad].
+In this post, I'll try to describe some of the initial pain points of mine.
+
+[AUR]: https://aur.archlinux.org/
+[Launchpad]: https://launchpad.net/
+
+Basics
+------
+
+The Debian package format is really old, and it shows.
+There's a billion of metadata files to take care of, and barely any suitable
+tutorials for beginners.
+At best, you'll learn how to build _binary_ packages, not suitable for
+publishing in a PPA (which only accept _source_ packages and builds the
+binaries itself).
+
+First, you need to realise that there're source packages and binary packages.
+Binary packages are the .deb files that actually contain the software.
+A source package is, confusingly, multiple files, and you need to submit them
+all to Launchpad.
+You can distribute binary packages directly to your users, but they would have
+to fetch & install the new version manually every time there's an upgrade.
+If you could set up a repository and just point the users to it, they would get
+the new versions naturally via the package manager (`apt`).
+
+Canonical's Launchpad provides a very handy PPA (Personal Package Archive)
+service so that anyone can set up a repository.
+Users could then use `add-apt-repository ppa:...` and get the packages in a
+standard and convinient way.
+
+Tools
+-----
+
+There's a myriad of tools to build and maintain Debian packages.
+The [Debian New Maintainers' Guide] provides a [short summary] of how these
+tools interact.
+This tutorial assumes that your software lives in a Git repository and you'd
+like to use Git to maintain the packaging metadata in the same repository.
+This process is greatly aided by the [git-buildpackage] tool.
+We still need to install a bunch of other stuff though; the complete command
+line to install the required tools would be something like
+
+ sudo apt install -y build-essential devscripts dh-make git-buildpackage
+
+Many of the tools pick up particular metadata (like the maintainer name and
+email address) from environment variables.
+You can put something like
+
+ export DEBFULLNAME='John Doe'
+ epxort DEBEMAIL='John.Doe@example.com'
+
+in your .bashrc to set them globally.
+
+[Debian New Maintainers' Guide]: https://www.debian.org/doc/manuals/maint-guide
+[short summary]: https://www.debian.org/doc/manuals/maint-guide/build.en.html#hierarchy
+[git-buildpackage]: http://honk.sigxcpu.org/projects/git-buildpackage/manual-html/gbp.html
+
+Getting started
+---------------
+
+Let's create a repository to try things out.
+It'll contain a single executable shell script test.sh, which only outputs the
+string "test".
+
+ mkdir test
+ cd test
+ git init
+ cat <<'EOF' > test.sh
+ #!/usr/bin/env bash
+ echo test
+ EOF
+ chmod +x test.sh
+ git add .
+ git commit -m 'initial commit'
+
+This is going to be version 1.0 of our project, let's tag it as such.
+
+ git tag -a -m 'Release 1.0' v1.0
+
+All of the Debian packaging tools are tailored to the following use-case.
+
+1. There's an upstream distribution, which releases the software in tarballs.
+2. There's a maintainer (who's not the software author), who takes care of
+packaging and is disconnected from the development.
+
+This disconnect means that maintaining the Debian packaging files in the
+`master` branch is inconvinient using the existing tools.
+At the very least, you should create a separate branch for doing packaging
+work.
+
+In addition, Debian (and hence, Ubuntu) is not a rolling-release distribution.
+That means that there're regular releases, and the software version shouldn't
+change too much during a lifetime of a single release.
+Once Debian makes a release, the software version is more or less fixed, and
+security fixes from future versions should be backported separately for each of
+the supported Debian/Ubuntu releases.
+
+Except there _is_ a rolling-release distribution of Debian, and it's called
+"unstable" or "sid".
+The bleeding-edge packaging work should target the "unstable" distribution.
+
+So, let's create a new branch `debian` for our packaging work:
+
+ git checkout -b debian
+
+All the packaging tools assume there's a separate folder "debian" that contains
+the package metadata files.
+There's a handy tool `dh_make` that creates the directory and populates it with
+a number of template metadata files.
+Using it is not so simple though.
+First of all, it assumes that there's a properly named tarball with the project
+sources available in the parent directory.
+Why?
+Who knows.
+Let's create said tarball:
+
+ git archive --format=tar --prefix=test_1.0/ v1.0 | gzip -c > ../test_1.0.orig.tar.gz
+
+The tarball name should follow the NAME_VERSION.orig.tar.gz pattern exactly!
+Anyway, now is the time to run `dh_make`:
+
+ dh_make --indep --copyright mit --packagename test_1.0 --yes
+
+I'm using the MIT License for our little script, hence the `--copyright mit`
+argument.
+In addition, every package in Debian is either "single", "arch-independent",
+"library" or "python".
+I'm not sure what the exact differences between those are, but a shell script
+is clearly CPU architecture-independent, hence the `--indep` argument.
+If it was a compiled executable, it would be a "single" (`--single`) package.
+
+`dh_make` created the "debian" directory for us, filled with all kinds of
+files.
+The only required ones are "changelog", "control", "source", "rules" and the
+"source" directory.
+Let's remove every other file for now:
+
+ rm -f -- debian/*.ex debian/*.EX debian/README.* debian/*.docs
+
+You can study the exact format of the metadata files in the [Debian New
+Maintainers' Guide], but for now let's make keep it simple:
+
+{% include snippets/section.html section_id='basic' %}
+
+The "control" and "copyright" files are fairly straighforward.
+The "changelog" file has a strict format and is supposed to be maintained using
+the `dch` tool (luckily, git-buildpackage helps with that; more on that later).
+
+The "rules" file is an _executable_ Makefile, and actually controls how the
+software is built.
+Building a package involves invoking many predefined targets in this Makefile;
+for now, we'll resort to delegating everything to the `dh` tool.
+It's the Debhelper tool; it's a magic set of scripts that contain an
+unbelievable amount of hidden logic that's supposed to aid package maintainers
+in building the software.
+For example, if the package is supposed to be built using the standard
+`./configure && make && make install` sequence, it'll do this automatically.
+If it's a Python package with setup.py, it'll use the Python package-building
+utilities, etc.
+We don't want any of that, we just want to copy test.sh to /usr/bin.
+It can be taked care of using the `dh_install` script.
+While building the package, it'll get executed by `dh`, read the
+"debian/test.install" file and copy the files listed there to the specified
+directories.
+Our test.install should look like this:
+
+{% include snippets/section.html section_id='install' %}
+
+At this point, we can actually build a proper Debian package!
+
+ dpkg-buildpackage -uc -us
+
+This command will generate a bunch of files in the parent directory.
+The one of interest to us is "test_1.0-1_all.deb".
+We can install it using `dpkg`:
+
+ sudo dpkg -i ../test_1.0-1_all.deb
+
+We can now execute `test.sh`, and it'll hopefully print the string "test".
+
+This .deb file can be distributed to other users, but is no good for uploading
+to Launchpad.
+For one, it's a binary package, and we need source packages for Launchpad to
+build itself.
+Second, it's unsigned, which is also a no-no.
+
+I'm actually not going to describe how to set up a GnuPG key and upload it to
+the Ubuntu keyserver (keyserver.ubuntu.com), but it's pretty straightforward
+once you know the basics of GnuPG key handling.
+
+One disadvantage of the `dpkg-buildpackage` tool is that it creates a lot of
+files in the "debian" directory; their purpose is unclear to me.
+For now, you can delete them, leaving only the original "changelog", "control",
+"copyright", "rules", "test.install" and the "source" directory.
+
+git-buildpackage
+----------------
+
+git-buildpackage is a wonderful tool that helps with managing the packaging
+work in the upstream repository.
+Please refer to its manual to learn how to use it properly.
+We need to configure it so that it knows how the release tags look like
+(`vVERSION`), how the packaging branch is called (`debian`) and where to put
+the generated files.
+Create "debian/gbp.conf" with the following contents:
+
+{% include snippets/section.html section_id='gbp' %}
+
+One unclear line here is `pristine-tar = False`.
+It turns out, a lot of Debian package maintainers use the `pristine-tar` tool
+to create "pristine", byte-for-byte reproducible tarballs of the upstream
+software.
+This is just more headache for us, so we're not going to use that;
+git-buildpackage will just use the normal `git archive` to create tarballs.
+
+First, commit the packaging work we just made:
+
+ git add debian/
+ git commit -m 'initial Debian release'
+
+We can now build the package using git-buildpackage:
+
+ gbp buildpackage
+
+The tool will try to sign the packages, so this assumes that you have your
+GnuPG key set up!
+
+If all went right, it just built the packages in the ../build-area directory.
+And it hasn't crapped all over the working directory too!
+Similar to `dpkg-buildpackage`, it builds binary packages by default.
+To build _source_ packages, it needs to be invoked with the `-S` argument:
+
+ gbp buildpackage -S
+
+It'll build the source package in the same directory (you'll notice a lot of
+files having the "_source" suffix).
+If all is well, we can tag the packaging work we've just completed:
+
+ gbp buildpackage --git-tag-only
+
+This will create the `debian/1.0-1` tag in the repository.
+
+We are now ready to upload the source package to Launchpad.
+It's done using the `dput` tool.
+The naive way would fail:
+
+ dput ppa:john-doe/test ../build-area/test_1.0-1_source.changes
+
+This is due to the fact that we've specified that we're targetting the
+"unstable" distribution in debian/changelog.
+There's no "unstable" distribution of Ubuntu though; we need to manually
+specify the minimal-supported version (e.g. "bionic"):
+
+ dput ppa:john-doe/test/ubuntu/bionic ../build-area/test_1.0-1_source.changes
+
+What about other distributions?
+Well, if the binary package doesn't need recompiling, we can use Launchpad's
+"Copy packages" feature; this is well-described in this [Ask Ubuntu question].
+
+[Ask Ubuntu question]: https://askubuntu.com/q/23227/844205
+
+New versions
+------------
+
+When a new version is released, git-buildpackage helps to integrate it to the
+packaging branch.
+Let's say the new version is tagged `v1.1`:
+
+ git checkout debian
+ git merge v1.1
+ gbp dch
+
+The above command will update debian/changelog; modify it manually to target
+the usual "unstable" distribution instead of "UNRELEASED" and update the
+version to something like "1.1-1".
+
+ git add debian/
+ git commit -m 'Debian release 1.1'
+ gbp buildpackage -S
+
+This will build the source package for the new version in the ../build-area
+directory; you can then upload it Launchpad and copy the built binary packages.
+
+Aftermath
+---------
+
+This fucking sucks.
+What's the way to sanely manage the repository if the build/runtime
+dependencies are different for different Ubuntu versions?
+I have no idea.
+Some pointers to help you understand what's going on in this tutorial more
+deeply:
+
+* [When upstream uses Git: Building Debian Packages with git-buildpackage](https://honk.sigxcpu.org/projects/git-buildpackage/manual-html/gbp.import.upstream-git.html)
+* [Using Git for Debian packaging](https://www.eyrie.org/~eagle/notes/debian/git.html)
+
+Good luck with this, because I'm definitely overwhelmed.