When I was dipping my toes for the first time in Debian world, I knew right off the bat, that I'll want to learn how to build proper packages for it. It took me much longer to actually learn my ways around it, and I don't think I ever actually mastered it (or, at least, felt confident in it), but I did, eventually, achieve that goal.

There was a point in time (2009/2010) when I tried to go through the Debian's mentorship, one package of mine did eventually get sponsored and landed in the official repositories, but then life entered the scene and there was not enough drive in me to carry it on. I was left with a skill just barely good enough to maintain a single package, but good enough to make me dangerous (figuratively speaking).

I was building variety of packages and, back then, I was using Debian Sid (unstable) on my desktop machine, it was very handy to be able to port something from other distros (cough cough Ubuntu), PPAs, as well as newer software versions that usually needed much longer to get into the official repos.

Yes, everything was constantly on fire, I'm glad you asked. But this was also helping me, in a way, in becoming quite adept in solving all sorts of problems in the Linux world. I also think this was the pinnacle of my rice game, just look at this thing:

Sleek, eh?

The point is, I was building loads of packages throughout the years. In my job-2 we had internal repository I was maintaining and building some custom packages we needed, backporting some others etc. In my job-1 I was able to just leverage my launchpad PPA for these kinds of purposes (i.e. I no longer needed to maintain the repository).

And on that note—isn't it weird that you can't host Debian PPAs on launchpad? What's up with that?

My setup for building stuff was ever-evolving, but eventually I prepped my Intel NUC (NUC7i3BNK) for exactly this purpose. Software stack as far as building process is concerned was going from pbuilder(8), cowbuilder(8), gbp-buildpackage(1) etc. I was always trying to follow the most up-to-date process recommended and outlined on Debian's official wiki. That led me to eventually adopt sbuild(1) with schroot(1) and never look back.

I wasn't (and still am not) building as many packages as I used to, but this setup always served me well whenever I needed it. Looking at my launchpad I can see that the last package I uploaded landed in the repo on 9th of December 2023. That's a while and I'm quite certain I did build some packages in the meantime, just for my own purposes.

Fast forward to couple of days ago, when I suddenly felt the urge to build nginx, latest stable version, for Debian 12 (bookworm). I grabbed the sources, I was just going to smash it as is directly from Sid and see whether anything requires adjustments. But it failed right from the get-go:

dpkg-buildpackage: info: source package nginx
dpkg-buildpackage: info: source version 1.26.3-2
dpkg-buildpackage: info: source distribution unstable
dpkg-buildpackage: info: source changed by Jérémy Lal <kapouer@melix.org>
dpkg-checkbuilddeps: error: unmet build dependencies: libgd-dev libgeoip-dev libpcre2-dev libxslt1-dev
dpkg-buildpackage: error: build dependencies/conflicts unsatisfied; aborting
dpkg-buildpackage: hint: satisfy build dependencies with your package manager frontend

Now isn't that odd? Those dependencies are perfectly "satisfiable", I double checked even inside the chroot environment to be 100% sure about it. So what gives?

I use Debian Sid on my build machine, so I always need to be on my toes with breaking changes. I quickly went to sbuild page on Debian Wiki, and I realized that there's now a new recommended setup for building packages. It's mean, lean and clean. I glanced over it and decided to give it a 5 minute spin, to see whether it fixes the issue.

It didn't. Same error. Even with the recommended setup. What's going on?

Quick Kagi search and I stumbled upon on a Debian bug report (1033626) that sounded terribly familiar:

The user expectation is that sbuild takes care of all the Build-Depends (by installing them in the chroot), but this apparently isn't 100% true: it runs "dh clean" outside the chroot, so any extra debhelper bits must be installed outside.

Can we fix this by not doing anything outside the chroot that sbuild itself doesn't Depend on? The simplest way to do that is to make --no-clean the default. Of we can run "dh clean" inside the chroot. Can we do something like that?

Yeah, this is exactly my problem—dh clean running outside my chroot environment and failing to do so due to build dependencies I don't have (nor want to) on the host machine. But clearly it wasn't always this way given the fact that it's the very first time when this happened to me while building package. So I kept on reading:

no. This is a feature not a bug. From your invocation I assume that you ran sbuild inside and unpacked source directory? The input for sbuild is a .dsc. To make it easier for the user, sbuild contains some code that automatically builds the .dsc for you if you run sbuild inside an unpacked source directory. To build the .dsc you need a clean source directory. Since sbuild cannot know whether or not your source directory is clean, it runs the clean target by default. If you know it's clean, then you can force sbuild to not do that by passing --no-clean. If you do not want to pass --no-clean, then you can instead create the .dsc in any other way you like and then pass the .dsc to sbuild. If you pass a .dsc to sbuild, then sbuild will not attempt to run the lean target.

Well, isn't that interesting? First of all, I have never once build package with sbuild by pointing it directly towards .dsc file. This is neat and I'm going to do it much, much more often from now on.

I also get the point about the .dsc file not being cleanly generated from the unpacked sources—something that is absolutely necessary for sbuild to build proper, clean packages.

There's a second bug report I found with the same struggle (1088269) to eventually land in the pull request discussion on Debian's GitLab (71). So what's up with all that?

The change happened in the way when sbuild triggers cleaning target. Everything that is defined under Build-Depends has to be available on the host now to run that clean target, but only if you are working in the unpacked sources. It's an important distinction:

# Unpacked source
apt-get source nginx
cd nginx-1.26.3
sbuild -d bookworm
ERROR! FAILURE!

# Nifty dsc way
apt-get source --download-only nginx
sbuild -d bookworm nginx_1.26.3-2.dsc
WORKS FINE!

I also learned there are many more Build- targets these days (they may have been there since forever...). Only what's in the Build-Depends has to be satisfied for the clean target, though, which means that some breaking dependencies may be moved into, for example, Build-Depends-Arch and the build from the unpacked source will succeed just fine.

There are more ways for avoiding this error as is nicely outlined by josch (one of the sbuild maintainers) in the original bug report:

a) add $clean_source=0; to your ~/.config/sbuild/config.pl
b) install those dependencies required to run the clean target on your machine
c) if you use gbp buildpackage to run sbuild, configure it such that it passes --no-clean-source to sbuild (gbp will only work in a clean state anyways)
d) move move build dependencies from Build-Depends to Build-Depends-Arch and Build-Depends-Indep leaving in Build-Depends only those dependencies which are required to run the clean target (see Debian Policy §7.7)
e) pack up the dsc yourself manually and call sbuild with the .dsc as input
f) teach sbuild to accept a source directory as input instead of the dsc (patches welcome) and then it can run the clean target inside the chroot
g) teach gbp to pass the --no-clean-source to sbuild if the directory is already clean (i'm sure guido would accept patches too)

That's plenty of valid options here. My main takeaway is that I probably need to be building packages directly from dsc files more often.

It's also likely time for me to work on my package building setup. Maybe it's also good opportunity to move it elsewhere and store it in some Ansible repo or something. But that's for another day.