Patching Ubuntu Packages with dgit and pbuilder
The Situation¶
What I want from my open-source operating system (Ubuntu) is a sensible way to make "many eyes make all bugs shallow" a reality. At least for the obvious stuff - like the UI.
To this end I need a couple of things:
- the ability to get the source code
- the ability to build and package the code
- the ability to install that package onto my own system safely to test it - and uninstall it and revert back to the regular package if I cannot.
- the ability to upstream my changes
This is, in my experience, hard with Ubuntu - or at least certainly not a streamlined process like I believe it should be.
Fortunately the situation today is a lot better then it was, but there are many false starts out there on the web. In this post I am recreating the notes and code which I used to do this again recently, along with the justifications why. There are obvious shortcomings, that aren't easy to fill (namely, rootless Podman should mean I can do this all without sudo except for the final sudo apt upgrade
- but that's not the case today, not flexibly).
Problem¶
Our problem is that we want to be able to pull the source code of a given package - easily. We may need to pull the code of several packages so we can patch one, and rebuild many against it - so things we build need to become available to the next builds.
And we'd like to be able to track this in source control, and public PPAs from it if it proves useful. Because it's just the thing to do.
Tools¶
You will need the following:
dgit
pbuilder
devscripts
apt install -y dgit pbuilder devscripts
dgit
is the real magic in this process - it takes a bunch of things which should be possible but are not talked about extensively online and makes them automated, and well documented - the dgit man pages are excellent.
The second part of this mix is pbuilder
. pbuilder
has a lot of variants, but when you get right down to it I've found that basic pbuilder
is the easiest to get running: the tooling for things like cowbuilder
qemubuilder
and dbocker
all tend to feel either under-developed (dbocker
) or have limitations which limit easy usage (qemubuilder
can't use bind mounts, so what we're doing here won't work.)
Setting up pbuilder¶
Getting pbuilder working smoothly on your system is key to making this a pleasant experience. Anyone can, with some effort, somehow get a package to build - but doing it in a way where you can get your patched code running quickly, and restore it just as easily is the key to making it accessible.
Testing environments are good and all, but for the "I just need this one feature" sort of user patch of system-level or networking software (in my case NetworkManager), it's too much work for too little reward and tends not to properly fix the problem. The hacker spirit needs a duct-taped proof before it's productionized (and for bugs the question is "have you fixed it in the circumstance - your system - where it comes up").
My solution for setting up pbuilder is in this Github repository here (by me).
In short, ./setup-pbuilder-repo
here will install a .pbuilderrc
file plus a bunch of hook scripts, and setup a local repository for apt with a signing key marked as pbuilder-repo
. This is the standard chroot based classic pbuilder - there is some support for trying to use qemubuilder or cowbuilder, but in my experience both of these created more problems then they solved. Something clever could be done here, but for the time and effort you could also just run a full Ubuntu VM and do your building there - our goal is packaging, not reinventing containerization.
Simply clone the repository, run ./setup-pbuilder-repo
and then run sudo pbuilder create
to setup a local repository.
Building a local package which upgrades the existing one¶
With the pbuilder
environment setup, the process for building a new version of a local package looks like this (pretty much from the dgit-user
page):
Increment the version numbers by the amount needed (and write a new commit log):
gbp dch -S --local wrouesnel --since=dgit/dgit/jammy --ignore-branch --commit
Build with our pbuilder setup:
dgit pbuilder
If you get a message like:
Format `3.0 (quilt)', need to check/update patch stack
Would remove .idea/.gitignore
Would remove .idea/misc.xml
Would remove .idea/vcs.xml
Would remove configure~
Would remove install-sh~
dgit: error: tree contains uncommitted files (NB dgit didn't run rules clean)
dgit: If this is just missing .gitignore entries, use a different clean
dgit: mode, eg --clean=dpkg-source,no-check (-wdn/-wddn) to ignore them
dgit: or --clean=git (-wg/-wgf) to use `git clean' instead.
then as noted dgit is telling you the answer - you have extraneous files (common if like me you used a Jetbrains IDE to browse and edit). Run git --reset hard
to get rid of fluff (modified autotools files usually) and then run:
dgit --clean=dpkg-source,no-check pbuilder
to build while ignoring anything extra.
If you're just building binaries for your local system, then with this setup this is enough. After the build you can
apt upgrade
and your new build will be installed.
Setting up a PPA¶
For ease of use (or if you're doing this because you want to apply it to a cluster) then first build and sign a source package:
First you'll want to declare a release for the version of your OS (I use Ubuntu so these will be Ubuntu specific):
gbp dch -R --local wrouesnel --since=dgit/dgit/jammy --ignore-branch --commit
This lets you edit the changelog (on my system despite my efforts dch is convinced it should use my old email address from somewhere and I cannot figure out from where). Note that I had a lot of trouble getting a perfectly clean history out of git automatically with gbp
here - it liked to pick up it's own changelog commits as things it wanted to include - I assume I'm using it wrong somehow, but documentation on other tools is tricky.
Then, you want to build a signed source package for your changelog:
dgit --clean=dpkg-source,no-check -k${PPA_SIGNING_KEYID} build -S
where ${PPA_SIGNING_KEYID}
is the key ID you registered with Launchpad.
Then just push it to the PPA:
dput ppa:${PPA_NAME} <your package.changes file>
If dput
complains about missing or incorrect signatures (I had an issue with missing signatures despite the gbp commands above) then it's easily fixed by running the debsign
command on the *.dsc
and *.changes
files:
debsign -k${PPA_SIGNING_KEYID} <your package name>.dsc
debsign -k${PPA_SIGNING_KEYID} <your package name>_source.changes
This will either sign it in place, or detect a signature and ask if you want to replace it (which is useful if you missigned initially). Then just retry the dput
upload.