lubuntu-packaging-guide/lubuntu-packaging-guide.rst
2025-12-28 23:11:59 -06:00

1540 lines
74 KiB
ReStructuredText

==============================
Packaging Software for Lubuntu
==============================
This document provides information about how to to create and maintain the
Debian packages that comprise the core of Lubuntu. We'll go over how to set up
your system for packaging, basic packaging concepts, best practices for
Lubuntu's packages, and advice for what to do and not do in order to make your
life as a packager easier.
Copyright and Licensing
-----------------------
The text of this document may be used under the terms of the CC-BY-SA 4.0
license. The copyright info for this document is as follows:
* Copyright (c) 2025 Aaron Rainbolt <arraybolt3@ubuntu.com>.
.. NOTE::
If you make a substantial contribution to this document, you should add
your own copyright info to the above list.
High-level overview of a Debian "package"
-----------------------------------------
There are two distinct kinds of packages in Debian, *binary packages* and
*source packages*. Binary packages are the packages you actually install on
your system. For the most part, you won't need to worry about the details of
how they work internally. Source packages are bundles of source code with
Debian packaging metadata. This metadata describes (among other things) how to
build the package, and where the files it installs should go. *Building* a
source package will create one or more binary packages, along with some other
files that we'll describe as we go.
To get an idea of how source packages are structured, we'll start with a
fairly simple package, ``lxqt-about``::
aaron@pkg-builder:~/vmshare/pkg/lxqt-about$ tree
.
├── aboutdialog
│   ...
├── AUTHORS
├── CHANGELOG
├── CMakeLists.txt
├── COPYING
├── debian
│   ├── changelog
│   ├── control
│   ├── copyright
│   ├── docs
│   ├── ...
│   ├── ...
│   ├── lxqt-about.install
│   ├── lxqt-about-l10n.install
│   ├── lxqt-about.lintian-overrides
│   ├── rules
│   ├── ...
│   ├── source
│   │   ├── format
│   │   └── options
│   ├── upstream
│   │   ├── metadata
│   │   └── signing-key.asc
│   └── watch
├── languages
├── main.cpp
├── README.md
├── resources
│   ...
├── translations
│   ...
└── translatorsinfo
...
(The contents of the ``aboutdialog``, ``translations``, and
``translatorsinfo`` dirs are omitted for brevity, and a few irrelevant files
from the ``debian`` directory have been hidden for clarity.)
If you look at `the lxqt-about Git repo
<https://github.com/lxqt/lxqt-about>`__, you'll see that this looks very
similar to the lxqt-about source code repository. The primary difference is
there is a ``debian`` directory here, with a number of files in it. This
``debian`` directory contains all the instructions necessary to take code, and
turn it into Debian binary packages.
We won't go into the details about what these files contain quite yet, but we
will briefly cover what each one does.
* ``changelog``: As one would expect, this contains a record of all changes
made to the package over time. It also specifies the version number of the
package, the version of Ubuntu (or Debian) the package is intended for, and
the identity of the person or people who made changes to the package.
* ``control``: This file defines critical information about the package,
including the names of the source package and each binary package, build and
runtime dependencies, what CPU each binary package is designed to run on,
and a description of each binary package.
* ``copyright``: This file contains copyright notices and authorship
information for (almost) every file in a source package. This is necessary
because many license agreements require or encourage a software distributor
(like Ubuntu) to include copyright notices, license text, and authorship
information for software they distribute. Thanks to the somewhat complex
nature of software licensing, this can be tricky and tedious to maintain,
but it is necessary.
* ``docs``: This file isn't present in all packages. When it is present, it
specifies files that should be installed in a subdirectory of
``/usr/share/doc`` for the user to reference later if they care to.
* ``lxqt-about.install``: This file contains information about what files
should be placed in the ``lxqt-about`` binary package. There are some simple
source packages that don't need a ``.install`` file for a binary package,
but those are rare.
* ``lxqt-about-l10n.install``: This file serves the same purpose as
``lxqt-about.install``, but for the ``lxqt-about-l10n`` binary package. (As
you can probably guess, the ``lxqt-about`` source package builds two binary
packages, named ``lxqt-about`` and ``lxqt-about-l10n``.)
* ``lxqt-about.lintian-overrides``: This file is used to silence
false-positive warnings from Lintian, a package checking tool that we'll
discuss later.
* ``rules``: This is a Makefile that provides the instructions for building
the source code of the source package into binaries. Previously this file
was rather large and complicated (and with some more advanced packages it's
still large and complicated), but thanks to some of Debian's more modern
tooling it is a lot easier to work with than it was in the past.
* ``source/format``: This file contains a string indicating what "format" the
package is in. We'll cover formats later.
* ``source/options``: You can usually ignore this file. It contains some
configuration for the ``dpkg-source`` tool, which you will very rarely have
to work with.
* ``upstream/metadata``: This file contains some basic information about the
source package's *upstream*, i.e. the entity that actually wrote the
software you are now packaging for Debian. It's good to know how to work
with these files, but you won't have to touch them very often.
* ``watch``: This file is used to locate, download, and sometimes repack
source code from upstream. It's rare that you'll have to change this file,
and it is usually very painful to work with it, but nonetheless it's good to
understand how to work with it.
Most of the simpler packages you'll work with (including the entire LXQt
stack) will have a ``debian`` directory that more-or-less follows this
structure. More complicated packages may feature a substantially different
package structure, but for now we won't worry about those.
The above is mainly meant to be a teaser of what you'll be working with as a
packager. Before we get into the details of packaging and how it works though,
you should have a good environment for packaging set up. That's what we'll
cover in the next few sections, starting with setting up PGP keys.
PGP keys in Ubuntu development
------------------------------
One critical tool you'll use as an Ubuntu packager is PGP. Put simply, PGP is
a powerful tool used to prove that you take responsibility for some data (i.e.
a Debian package), and to ensure confidential data you send to someone can
only be read by them. This is useful, because as a packager, you will
frequently be uploading packages to Launchpad, and you need to prove that you
are the one doing the uploads for them to be accepted. Rarely, you will also
run into instances where someone has to send you confidential data.
Using PGP effectively requires some understanding of how it works, so we'll
start with an overview of how PGP does what it does.
Overview of PGP concepts
^^^^^^^^^^^^^^^^^^^^^^^^
PGP is a system for encrypting and signing arbitrary data using public key
cryptography. The basic idea behind PGP is that:
* Each PGP user has two keys, a public key and a private key. The public key
is uploaded to a *keyserver* where others can download it, while the private
key is kept secret and is only accessible by the key's owner.
* Any PGP user can use the public key to *encrypt* data. Only the key owner's
secret key can *decrypt* and read the data.
* The key owner can use their secret key to *sign* data, proving that they
claim responsibility for that data. Anyone can use the key owner's public
key to *verify* the signature.
* It is (in practice) impossible to decrypt data encrypted by the public key
without the corresponding private key. It is similarly impossible to create
a signature that can be verified with a public key, without the
corresponding private key. The reason things work this way is beyond the
scope of this guide, but basically PGP uses some clever math tricks to do
what it does. It's been proven to work over years of production use, so it's
very widely used.
Ubuntu uses PGP for several operations, including signing packages. Launchpad
requires that all packages are signed by the packager who claims
responsibility for them, before those packages can be uploaded. The signature
ensures that only authorized users can upload packages, and makes it so that
each package upload can be reliably traced back to the uploader.
Because of PGP's reliance on public keys for verification and encryption, it
is necessary to distribute the public key as widely as possible, so that
individuals can find it and use it to send you encrypted data or verify things
that you send. To facilitate this, there are several *keyservers*, which PGP
software can upload keys to and fetch keys from. PGP keyservers share public
keys with each other, so uploading a key to one keyserver is enough for users
of virtually any other server to fetch that key.
Preparing your PGP keys
^^^^^^^^^^^^^^^^^^^^^^^
.. NOTE::
Ubuntu has `PGP key recommendations
<https://documentation.ubuntu.com/project/contributors/setup/pgp-key-storage/>`__
for secure key storage. This overview is *not* fully compliant with these
recommendations, as the recommendations are non-mandatory, require special
hardware (in the form of a PGP-compatible USB hardware security key), and
are not easy to follow for first-time users. Once you have experience
generating PGP keys in general, you may choose to purchase a hardware
security key and configure it the way the Ubuntu PGP key recommendations
document.
Before you create a PGP key, there are a few recommendations you should
consider:
* If the laptop you intend to do development work on does not already use full
disk encryption, consider backing up your data and reinstalling the machine
with full disk encryption enabled. (This can be accomplished by using the
"Encrypt system" checkbox on the "Partitions" screen of Lubuntu's
installer.) This adds an extra layer of protection to the key.
* You **MUST** back up your key somewhere. If the key should become stolen or
compromised in the future, you will need a copy of it in order to revoke the
key and prevent it from being misused.
* If you are familiar with PGP key generation already, consider ignoring the
rest of this section and following the Ubuntu PGP key recommendations linked
above. These recommendations will have you generate a key in a known-good
environment and upload it to a hardware security key such as a Yubikey. This
provides much greater protection against key theft than the instructions
here.
The standard tool for PGP key management under Ubuntu is GNU Privacy Guard
(GnuPG, usually accessed using the ``gpg`` command). This tool should already
installed on your system. You can use it to generate keys, sign and encrypt
data, verify signatures, and decrypt data sent to you.
To generate a new PGP key using GPG, complete the following steps on your
physical system (do NOT do this in a packaging VM):
1. Open a terminal window (i.e. QTerminal).
2. Run ``gpg --full-generate-key``.
3. When prompted to select the kind of key you want, press ``Return`` to
accept the default of ``ECC (sign and encrypt)``.
4. When prompted to select which elliptic curve you want, press ``Return`` to
accept the default of ``Curve 25519``.
5. When prompted to specify how long the key should be valid, enter a
reasonable amount of time and press ``Return``. (The default, ``0``, will
result in the key never expiring. This is acceptable, however setting a key
expiration data will make you review your key storage practices every so
often, which you may find useful for staying secure.)
6. When asked if the provided expiration data is correct, press ``y``, then
press ``Return``.
7. When prompted for your real name, enter it and press ``Return``. (If you
have a pseudonym that is recognizable in the Ubuntu community, you may use
that instead. What's important is that the key and things you sign with
that key can be clearly traced back to you as an individual.)
8. When prompted for your email address, enter the same email address you use
for your Ubuntu One account and press ``Return``.
9. When prompted for a comment, press ``Return`` to accept the default (empty)
value. (If you intend on using this PGP key only for Ubuntu packaging, you
might consider adding a comment like "Ubuntu packaging key".)
10. When asked for confirmation of your choices, press ``o``, then press
``Return`` to accept your settings.
11. When prompted for a passphrase to protect your key, choose a strong
passphrase and enter it. Note that anyone who gains access to both your
key and its passphrase will be able to forge signatures in your name and
decrypt data sent to you, so it is of vital importance that this
passphrase be very secure. The easiest way to create a strong passphrase
is to pick six to eight random words and use those as your passphrase.
See `XKCD 936 <https://xkcd.com/936/>`__ (but note that four-word
passphrases like suggested in the comic are not very secure). After
entering the passphrase, click "OK" in the popup window.
12. After clicking "OK", wait for the key to be generated. You will see
something like this once the key has been generated::
pub ed25519 2025-11-04 [SC]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
uid Your Name <name@example.com>
sub cv25519 2025-11-04 [E]
Congratulations, you have now generated a PGP key! You should immediately back
this key up to some "offline" storage (i.e. a storage device that won't be
actively connected to your computer most of the time; flash drives are good
for this). GnuPG saves all of its persistent data, including generated public
and private keys, in the directory ``~/.gnupg``. You can therefore copy this
entire directory to a backup drive to back up the key. This will also back up
any other PGP-related data that GnuPG is storing, such as imported public
keys.
Now that your key is generated and backed up, it's time to upload your public
key to a keyserver. This will of course allow users to verify things you sign
and encrypt data to you. More importantly for packaging though, uploading your
public key to a keyserver will let Launchpad discover it. This will allow you
to bind your key to your account in Launchpad, which will let you use that key
for package uploads later. Uploading a key is pretty simple::
gpg --keyserver keyserver.ubuntu.com --send-key name@example.com
(Replace ``name@example.com`` with the email address you specified when
generating the key.)
Your key is now on the Ubuntu keyserver. Now that you're done with that, it's
time to add the key to Launchpad. This is a bit tricky but not too difficult:
1. In a web browser, go to your user account page on Launchpad (for instance,
``https://launchpad.net/~example``).
2. Under the "User information" section, there is a subsection called "OpenPGP
keys". Click the pen icon next to this section.
3. Launchpad will require you to reauthenticate via Ubuntu One. Provide your
Launchpad account's username and password, click "Log in", then click "Yes,
log me in".
4. Open a terminal window (i.e., QTerminal), and type the command
``gpg --fingerprint | grep -C5 name@example.com``, replacing
``name@example.com`` with your email address. This will show some info
about your PGP key, including the fingerprint. The fingerprint will look
like ``xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx``, with
hexadecimal numbers in place of the "x" characters. Copy the fingerprint
to your clipboard (select it, then press Ctrl+Shift+C).
5. Back in your browser, you should be at the "Change your OpenPGP keys"
screen. Under the "Import an OpenPGP key" section, paste your fingerprint
into the "Fingerprint" field, then click "Import Key". This will cause
Launchpad to send you an email encrypted with your PGP public key, which
will contain an activation link you can use to confirm ownership of the
key.
6. You will receive an email with some instructions, and a block of text
starting with the string ``-----BEGIN PGP MESSAGE-----`` and ending with
the string ``-----END PGP MESSAGE-----``. Copy that block of text with
Ctrl+C.
7. Open a text editor (i.e. Featherpad), and paste the text in with Ctrl+V.
Then save the file to ``~/Documents/pgp-activate.txt``.
8. In a terminal window, run ``gpg --decrypt ~/Documents/pgp-activate.txt``.
Enter your key's passphrase when prompted. This will show you the link
needed to register your key with Launchpad. Copy the link (select it, then
press Ctrl+Shift+C).
9. Open a new tab in your browser, and paste the link into the address bar.
10. Click "Confirm" in the web page that appears.
Congratulations, your PGP key is now registered with Launchpad and is ready
for development use! With that out of the way, we can get to the fun part;
setting up your packaging environment.
Preparing a Debian packaging environment
----------------------------------------
There are a lot of different ways to set up a packaging environment, using
various different tools. The way documented here is a simplified version of my
packaging setup, which essentially uses a KVM-based VM running the latest
development release of Ubuntu, with ``sbuild`` as my primary build tool. This
method works well for me because:
* It uses a full installation of Ubuntu in a VM, rather than a container,
avoiding some of the numerous pitfalls of containers when it comes to
privileges, resource sharing, etc.
* KVM is more robust than much of the other virtualization software available
for Linux (in particular I've found VirtualBox to be problematic in some
instances).
* It avoids issues that often arise when trying to package for a newer version
of Ubuntu using an older version, while not requiring one to run that newer
version as their primary OS.
* It's fairly simple to get working and fairly easy to use.
* ``sbuild`` closely mimics how packages are actually built on Launchpad,
minimizing the chances of odd things happening because of your build
environment.
* It integrates several other packaging tools you should be using, such as
Lintian.
This method is not without disadvantages however:
* If you want to keep your PGP keys out of your VM (which is recommended), it
requires signing packages manually on the host prior to upload.
* Virtualization incurs some overhead, so it can be slower than other
solutions.
The steps are, roughly:
* Install your desired flavor of Ubuntu into a virtual machine.
* Create a shared folder for working on packages on both the VM and host.
* Install and configure packaging utilities.
* Test the system by building sample source and binary packages.
KVM preparation and VM installation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The most common way to use KVM is through virt-manager, a GUI app that is
somewhat similar to VirtualBox. Under the hood, it uses libvirt, QEMU, and KVM
to run VMs.
.. NOTE::
KVM virtualization, much like the rest of Linux, has a lot of different
pieces that work together. QEMU emulates hardware and CPU instructions that
can't be run at full speed, KVM runs CPU instructions that can be run at
full speed, and libvirt makes it easier for applications to work with
QEMU/KVM virtual machines. virt-manager then provides a graphical user
interface for libvirt.
Installation is fairly simple; open QTerminal and run::
sudo apt install virt-manager libvirt-clients libvirt-daemon virtiofsd
sudo systemctl enable libvirtd.service
sudo systemctl start libvirtd.service
You should now be able to create virtual machines using virt-manager.
.. NOTE::
When talking about virtual and physical systems, the physical machine that
runs the VMs is typically called the "host", while the VMs themselves are
typically called "guests".
The next thing to do is to download the latest pre-release image of Lubuntu.
Like mentioned above, it is important to use the latest possible version of
Ubuntu to prevent issues with package builds later. The latest ISO can be
found
`<on cdimage.ubuntu.com https://cdimage.ubuntu.com/lubuntu/daily-live/pending/>`__.
It is generally a good idea to verify your ISO download using GnuPG and
sha256sum. To do this:
1. Create a directory to store the ISO at. ``$HOME/ISO/Lubuntu`` is a good
location.
2. Download the ``CODENAME-desktop-amd64.iso``, ``SHA256SUMS``, and
``SHA256SUMS.gpg`` files for Lubuntu, and move them to the newly created
directory.
3. Open QTerminal in the new directory, and import the PGP key used to sign
Ubuntu ISO images:
``gpg --keyserver keyserver.ubuntu.com --recv-keys 843938DF228D22F7B3742BC0D94AA3F0EFE21092``
4. Verify the SHA256SUMS file is unmodified using GnuPG:
``gpg --keyid-format=long --verify SHA256SUMS.gpg SHA256SUMS``. This
command should show output that looks similar to this::
gpg: Signature made Tue 26 Aug 2025 12:14:14 PM CDT
gpg: using RSA key 843938DF228D22F7B3742BC0D94AA3F0EFE21092
gpg: Good signature from "Ubuntu CD Image Automatic Signing Key (2012) <cdimage@ubuntu.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 8439 38DF 228D 22F7 B374 2BC0 D94A A3F0 EFE2 1092
5. Ensure you see the words "Good signature" in the output of GnuPG. If you
see "No public key" or "BAD signature", your download is corrupt. If you
see "Good signature", the SHA256SUMS file is authentic and intact.
6. Verify the ISO file is unmodified using sha256sum:
``sha256sum -c SHA256SUMS``. This command should show output that looks
like ``resolute-desktop-amd64.iso: OK``. If you see this, your ISO download
is authentic and intact.
Once you have a verified Lubuntu download, it's finally time we can create the
VM! To do this:
1. Open the application menu, and launch "Virtual Machine Manager".
2. Wait until the "Connecting" notice near the top of the window disappears.
3. Click the "Create a new virtual machine" button (it's in the very top-left
corner of the window).
4. In the "Create a new virtual machine" window, under "Choose how you would
like to install the operating system", click "Local install media (ISO
image or CDROM), then click "Forward".
5. At the top-right corner of the window, under "Choose ISO or CDROM install
media", click "Browse...".
6. In the "Locate ISO media volume" window, click "Browse Local".
7. In the file selector, navigate to the directory containing the Lubuntu ISO,
then click the ISO file and click "Open".
8. Uncheck the "Automatically detect from the installation media / source"
checkbox.
9. In the "Choose the operating system you are installing" text box, type
"Ubuntu".
10. In the popup that papears, click "ubuntu 25.10 (ubuntu25.10)", then click
"Forward".
11. Set a reasonable amount of RAM and number of CPUs for your hardware.
Package builds usually don't require that much memory or CPU power, but
sometimes they can benefit from lots of memory and CPU power, so it's a
good idea to allocate at least 8192 MB RAM and 4 CPUs if possible.
12. Set a reasonable amount of disk space for the VM. Package builds can take
a lot of disk space, so allocating at least 64 GB is highly recommended
here.
13. Set the VM name to something descriptive (i.e. ``lubuntu-dev``), then
click "Finish". We'll refer to this VM as ``lubuntu-dev`` in the rest of
this document.
The Lubuntu ISO should boot, and you can install it like you normally would.
For best performance, the ``ext4`` filesystem should be used. We won't cover
the Lubuntu installation process here, it's fairly straightforward. Once the
installed system boots up, you should check for updates and install them using
Lubuntu Update (or by running ``sudo apt update && sudo apt full-upgrade`` in
a terminal).
Once all updates are installed, click the application menu inside
``lubuntu-dev``, then hover over "Leave" and click "Shutdown". When LXQt asks
if you want to switch off your computer, click "Yes". (It is important that
you shut down the VM using LXQt's GUI buttons, do NOT run ``shutdown now`` in
a terminal! Doing the first shutdown in this manner prevents an LXQt bug that
breaks several features of Lubuntu.)
Shared folder setup
^^^^^^^^^^^^^^^^^^^
When doing Debian packaging, you will frequently have to modify packages on
both the host and guest systems. Rather than moving files between the guest
and the host all the time, it's easier to use a shared folder. This allows
both the guest and host to see and work on a set of files in the shared
directory.
.. NOTE::
KVM shared folders understand UNIX file permissions, so if a file is owned
by a particular UID on the host, it will appear as being owned by that same
UID in the guest. The default UID for the first user in Lubuntu is 1000. If
your user on the host system has UID 1000, things should work normally, but
if your UID on the host is something other than 1000, you will almost
certainly run into file ownership issues when trying to modify files in the
shared folder within the guest. If this happens, you should create a new
user in the guest with the same UID as your user account on the host, then
do your packaging work using that user account.
.. WARNING::
Do not share your entire home directory with the guest. This can expose
your PGP key to the guest, which is a security hazard.
To add a shared folder to the VM:
1. In the host OS, create a new directory to share between the host and the
guest. We'll assume the shared folder is at ``$HOME/vmshare``.
2. Create a file named ``test-marker.txt`` in ``$HOME/vmshare``. Well use this
to ensure that the shared folder actually works later.
3. Ensure the ``lubuntu-dev`` window is open but the VM itself is powered off.
(You should see the words "Guest is not running." in the middle of the
window.)
4. Click the "Show virtual hardware details" button (it's in the top toolbar,
and is the second button from the left).
5. In the left-hand sidebar, click "Memory", then check the "Enable shared
memory" checkbox and click "Apply"."
6. Underneath the left-hand sidebar, click "Add Hardware".
7. In the "Add New Virtual Hardware" window, in the left-hand sidebar, click
"Filesystem".
8. Ensure that the "Driver" is set to "virtiofs", then set the "Source path"
to ``/home/USERNAME/vmshare`` and the "Target path" to ``vmshare``.
(Replace ``USERNAME`` with your username on the host system.)
9. Click "Finish" to add the shared folder to the VM.
Next, to set up the VM to automatically mount this shared folder on startup:
1. Ensure the ``lubuntu-dev`` window is open, then click the "Power on the
virtual machine" button (it's in the top toolbar, looks like a play button,
and is the third button from the left).
2. Log in if necessary.
3. In the guest, open QTerminal, and run the following commands, replacing
``USERNAME`` with the guest's account's username. This will create a
systemd unit for mounting the shared folder, enable it so it starts on
bootup, and then start it so that the shared folder immediately starts
working::
mkdir $HOME/vmshare
cat <<'EOF' | sudo tee /etc/systemd/system/mount-vmshare.service
[Unit]
Description=Mount shared folder on startup
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=mount -t virtiofs vmshare /home/USERNAME/vmshare
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable mount-vmshare.service
sudo systemctl start mount-vmshare.service
4. The shared folder should be immediately mounted. In the guest, run
``ls $HOME/vmshare``, and verify that you can see the file
``test-marker.txt`` in the directory.
5. In the guest, run ``touch $HOME/vmshare/test-marker-guest.txt``.
6. On the host, run ``ls $HOME/vmshare``, and verify that you can see the
``test-marker-guest.txt`` file.
If both test files can be seen on both the guest and the host, the shared
folder is working. With that out of the way, we can get to the fun part;
installing tools for Debian packaging!
Installing and configuring packaging utilities
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Like mentioned earlier, we're going to be using ``sbuild`` as our primary
packaging tool. We'll go into a bit of detail about what all ``sbuild`` does
and how later. For now, we're just going to get it and the rest of the Debian
packaging tools installed and ready to work.
1. Ensure the ``lubuntu-dev`` window is open and the VM is running.
2. In the guest, open QTerminal, and run the following commands to install
``sbuild`` and several other packaging utilities::
sudo apt install ubuntu-dev-tools debhelper devscripts mmdebstrap git sbuild
3. In the guest, configure ``sbuild``. The following configuration is
relatively basic, works for almost everything, and integrates automatic
package caching. Replace ``Your Name`` and ``name@example.com`` as
appropriate. Do NOT remove the ``1;`` at the end of this config, or it will
break ``sbuild``. To create the config file::
mkdir -p $HOME/.config/sbuild
cat <<'EOF' > $HOME/.config/sbuild/config.pl
$maintainer_name = 'Your Name <name@example.com>';
$distribution = "resolute";
$build_arch_all = 1;
$purge_build_directory = "successful";
$purge_session = "successful";
$purge_build_deps = "successful";
$log_dir = $ENV{HOME}."/ubuntu/logs";
$chroot_mode = "unshare";
$unshare_mmdebstrap_keep_tarball = 1;
$unshare_mmdebstrap_extra_args = [
"*" => [ "--include=debhelper,auto-apt-proxy,ca-certificates" ],
"resolute" => [ "--include=debhelper,auto-apt-proxy,ca-certificates", "--components=main,universe,restricted,multiverse" ],
"questing" => [ "--include=debhelper,auto-apt-proxy,ca-certificates", "--components=main,universe,restricted,multiverse" ],
"plucky" => [ "--include=debhelper,auto-apt-proxy,ca-certificates", "--components=main,universe,restricted,multiverse" ],
"noble" => [ "--include=debhelper,auto-apt-proxy,ca-certificates", "--components=main,universe,restricted,multiverse" ],
];
$lintian_opts = [ '-E', '-v', '-I', '-L', '+pedantic' ];
$clean_source = 0;
1;
EOF
4. In the guest, configure ``quilt``. We'll explain more about what this tool
is and what it does later::
cat <<'EOF' > $HOME/.quiltrc
for where in ./ ../ ../../ ../../../ ../../../../ ../../../../../; do
if [ -e ${where}debian/rules -a -d ${where}debian/patches ]; then
export QUILT_PATCHES=debian/patches
break
fi
done
QUILT_PUSH_ARGS="--color=auto"
QUILT_DIFF_ARGS="--no-timestamps --no-index -p ab --color=auto"
QUILT_REFRESH_ARGS="--no-timestamps --no-index -p ab"
QUILT_DIFF_OPTS='-p'
EOF
5. In the guest, configure ``git``. You may already know what this tool does,
but we'll explain more about it later. Replace ``Your Name`` and
``name@example.com`` as appropriate::
git config --global user.name 'Your Name'
git config --global user.email 'name@example.com'
6. In the guest, configure ``dch``. We'll explain more about what this tool is
and what it does later. Replace ``Your Name`` and ``name@example.com`` as
appropriate::
cat <<'EOF' >> $HOME/.bashrc
export EMAIL='name@example.com'
export DEBEMAIL='name@example.com'
export DEBFULLNAME='Your Name'
EOF
7. In the guest, run ``source $HOME/.bashrc`` so the new settings you've
created for ``dch`` take effect in the currently running shell.
8. On the host, run the following commands to install some necessary packaging
tools there as well::
sudo apt install debhelper devscripts
There's a lot going on here, most of which won't make sense right now. We'll
go into detail about what all these options do later on in this guide.
At this point, your packaging environment is configured and ready to use. It's
time for the last step of setup; building a test package!
Testing the packaging environment
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To make sure everything's working smoothly, we're going to use ``sbuild`` to
build binary packages for ``lxqt-about``, the same package we showed an
overview of at the beginning of this document. ``lxqt-about``'s packaging is
stored in the Lubuntu Git instance, so we're going to download the packaging
from there and build it.
1. Ensure the ``lubuntu-dev`` window is open and the VM is running.
2. In the guest, open QTerminal, and create a directory at
``$HOME/vmshare/pkg`` to do packaging in. Then change to the directory::
mkdir $HOME/vmshare/pkg
cd $HOME/vmshare/pkg
3. In the guest, download the Debian packaging for the ``lxqt-about`` package
from Lubuntu Git::
git clone https://git.lubuntu.me/Lubuntu/lxqt-about-packaging.git
4. In the guest, change to the new ``lxqt-about-packaging`` directory with
``cd lxqt-about-packaging``.
5. In the guest, run ``ls``, and ensure you see a single directory named
``debian``.
.. NOTE::
If you remember the overview of ``lxqt-about`` from the start of this
document, you might be confused here, since you only see a ``debian``
directory, not a full package. This is normal; Lubuntu does not duplicate
upstream source code in our packaging, since it's a waste of space and
isn't needed for package builds to still work.
6. In the guest, download the source code for ``lxqt-about`` by running
``uscan --download-current-version``. ``uscan`` is a packaging tool used
to download source code for packaged applications.
.. NOTE::
Due to a
`bug <https://bugs.launchpad.net/ubuntu/+source/devscripts/+bug/2122486>`__,
this might not work for all packages yet.
7. In the guest, build the package with the following command::
sbuild -d resolute
8. In the guest, run ``ls ..``. If all went well, you should see an
``lxqt-about`` deb file, alongside several other files.
That's it! If the above worked, you now have a working packaging environment!
We're now ready to move past system setup, and start learning how to do Debian
packaging.
Git essentials
--------------
.. NOTE::
If you already have a decent working knowledge of Git, you can skim this
section and only focus on the parts that have to do with Debian packages.
If you're working with Debian packages, you are almost certainly going to have
to work with Git. Git is, in essence, a tool for keeping track of changes that
are made to files in a directory. As changes are made to the directory,
developers use Git to take snapshots of what they changed and how. This is
very useful in both software development and packaging, because:
* Keeping track of changes in this way allows you to look at how things
changed in the past if needed.
* If something important is lost in the course of changes, you can get it
back out of Git's history.
* If some changes break everything, you can revert them and go back to a
previous commit.
* Each time someone changes something, their identity is attached to that
change, meaning you can determine who changed what, and when.
* If multiple people are changing the same file at the same time, Git can keep
track of those changes independently, then merge them together later so that
nothing is lost.
Because of the large number of uses Git has, it can be a tricky tool to get
used to. It has far too many features to fully explain in this guide, so we're
only going to go over the basics here. If you want to learn more about Git
than this guide covers, you should read the free
`Pro Git e-book <https://git-scm.com/book/en/v2>`__. Git also has a number of
manual pages you can browse through, see ``man git``.
Repos, commits, branches, tags, and remotes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The primary purpose of Git is manipulating Git *repositories*. A repository
(or "repo" for short) is nothing more than a directory full of files, with a
hidden ``.git`` subdirectory in it. The files in this directory are just
regular files, like you're already used to working with. All of Git's state
(the repo history and whatnot) is stored in the ``.git`` directory. Git is
intended to be used from the command line, and provides a number of commands
for repo manipulation. These commands usually affect whatever repo you are
"in". For instance, if ``$HOME/my-project`` is a Git repo, you are "in" the
``my-project`` repo if your current working directory is ``$HOME/my-project``
or any subdirectory underneath it.
As you make changes to a Git repository, you will want to *commit* your
changes from time to time. A Git commit is a bit of data that records what
files changed, what lines in those files changed, how those lines changed, and
who changed them. Each commit has a unique ID bound to it, so you can
unambiguously refer to any commit in the repository. When you create a new
commit, that's called "committing" a change. Commits can be *checked out*,
meaning that the files in the repository will be changed to reflect what the
repo looked like at the time that commit was made.
.. NOTE::
Git commits can track both large and small changes, but generally it's most
useful if you try to make each Git commit correspond to one logical change
in a project. For instance, if you need to change the version number of a
package, fix an issue that prevents the package from building, make the
README clearer, and introduce a patch to change default settings, each of
those changes should go into separate commits. On the other hand, if you
need to fix a common typo that occur in ten files, it's fine to put all of
those fixes in one commit.
Being able to keep track of changes as they're made is useful, but it isn't
enough for most projects. You might have multiple people making changes to a
project at the same time. You might want to make some experimental changes
without having to do a bunch of messy reverts if they don't work out. You
might even need to maintain different versions of the same project as if they
were separate sub-projects. To handle these scenarios, Git allows you to
create *branches* within a repo. Each branch is essentially a separate
timeline, tracking one or more commits entirely separate from other commits.
If one commit is made to one branch and another commit is made to another
branch, each branch will only "see" the commit that was made on them. If you
want to take all of the commits from one branch and integrate them into
another, you can *merge* branches. Each branch has a *tip*, which is the
commit most recently made to that branch. Similar to commits, branches can be
checked out. Whatever commit is on the tip of the checked-out branch will end
up checked out when you do this. (Note that checking out a branch is *not*
exactly the same as checking out the commit at the tip of a branch, we'll
cover the differences further later.)
Every so often, you'll create a commit that is, for one reason or another,
special. You may want to quickly look at this commit later, without having to
analyze the repo to figure out where it's at. Git provides *tags* for marking
these special commits. One common use of tags is to mark the last commit made
before releasing a new version of a project. The tagged commit can then be
used to look at the state the repo was in when that version of the project was
released. Tags can be checked out too; checking out a tag is exactly identical
to checking out the commit a tag is associated with.
All of the above features work locally, on your system. Again, a Git repo is
nothing more than a directory with a hidden ``.git`` subdirectory. So what
happens when you want to publish your changes for others to see? How do you
receive changes from others? This is where Git remotes come into play. A Git
remote is a server that has a copy of a Git repo on it. As you make changes to
your copy of the repo, you can *push* a branch to the remote; this will send
commits that you've created to the remote, which will then integrate those
commits into a branch of its copy of the repo. If other people have made
changes to a branch on the remote, and you want to download them, you can
*pull* that branch. Last, but not least, if you want a copy of a Git repo you
don't have yet, you can *clone* the repo from the remote.
Armed with an understanding of these concepts, you are now ready to work with
Git.
Analyzing a Git repo
^^^^^^^^^^^^^^^^^^^^
Remember running ``git clone`` to download the ``lxqt-about-packaging``
project earlier? (See "Testing the packaging environment" above if you don't
remember this.) The ``git clone`` command *clones* a repository. That means we
have a fully functional Git repo already downloaded, so let's take a closer
look at it and see what's in there!
Ensure the ``lubuntu-dev`` window is open and the VM is running. Then open
QTerminal, and change to the ``lxqt-about-packaging`` directory::
cd $HOME/vmshare/pkg/lxqt-about-packaging
Now that we're here, let's look at some basic info about the repo, and see if
any changes have been made to it that haven't been comitted yet. To do this,
run ``git status``. Assuming you haven't changed the repo since we ran the
``sbuild`` command previously, you should see something like this::
On branch ubuntu/resolute
Your branch is up to date with 'origin/ubuntu/resolute'.
nothing to commit, working tree clean
Let's break down what this means:
* One of the branches in this repo is called ``ubuntu/resolute``. This is the
branch we are actively "on", meaning that all the files in the project's
directory reflect Git's idea of the files on the ``ubuntu/resolute`` branch.
If we make any changes to these files and commit them, our new commit will
end up added to the ``ubuntu/resolute`` branch.
* The ``ubuntu/resolute`` branch does not just exist on our machine, it also
exists on the Git remote named ``origin``. To Git's awareness, our copy of
``ubuntu/resolute`` matches ``origin``'s copy of ``ubuntu/resolute``.
* All of the files in the repo match exactly what's been committed to the
``ubuntu/resolute`` branch (well, all of the files Git cares about anyway,
we'll look at that closer in a bit). We don't have any changes that need
committed. (What we've been calling the "files in the repo", Git calls the
"working tree".)
Most of this is straightforward, except... what is this remote named
``origin``? Let's see what remotes we have::
git remote
Running this command should print the following::
origin
Well that's not particularly useful. Let's look at what the ``origin`` remote
actually points to::
git remote get-url origin
This will output the following::
https://git.lubuntu.me/Lubuntu/lxqt-about-packaging.git
That's much more useful. The ``origin`` remote points to the
``lxqt-about-packaging`` repo on Lubuntu's Gitea instance.
.. NOTE::
When you clone a repository, Git automatically takes the URL you cloned the
repo from, and turns it into the ``origin`` remote. You can have more than
one remote in a repository, and if you spend enough time packaging you'll
probably run into situations where being able to do this is useful, but for
now we'll focus on managing a repo with just one remote.
Git repos generally are worked on by multiple people. Lubuntu's packaging
repos are no exception; each packager needs to be able to upload their changes
to a packaging repo after preparing the packaging for a Lubuntu component.
For this reason, it's generally a good idea to download the latest changes to
a repo before beginning work on it. Let's do that now, just in case someone's
modified ``lxqt-about-packaging``::
git pull
If there were changes, you'll see some info about the download, that starts
with something like this::
remote: Enumerating objects: 54, done.
remote: Counting objects: 100% (54/54), done.
remote: Compressing objects: 100% (29/29), done.
remote: Total 54 (delta 26), reused 51 (delta 23), pack-reused 0 (from 0)
Unpacking objects: 100% (54/54), 331.53 KiB | 1.03 MiB/s, done.
From git.lubuntu.me:Lubuntu/lxqt-about-packaging
Updating 98c3794..fe38a66
Fast-forward
debian/changelog | 1 +
debian/control | 2 +-
debian/copyright | 2 +-
debian/watch | 11 ++++++-----
4 files changed, 9 insertions(+), 7 deletions(-)
Exactly what Git shows here may vary, since it's showing a summary of the
download process, the changes that were downloaded, whether they apply to the
active branch or not, etc. For now we don't have to worry about most of this,
we'll cover how to deal with the edge cases later.
Alternatively, if there weren't any changes, you'll see::
Already up to date.
Now that we have our repo up-to-date, let's look at some of the most recent
commits and see what people have been doing lately::
git log
This will output something like this::
commit fe38a662e5c7e0c87d31a25097e9dc29dfe23554 (HEAD -> ubuntu/resolute, origin/ubuntu/resolute)
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Tue Nov 18 13:16:56 2025 -0600
Use debian/watch version 5 (taken from Debian's packages)
commit f90b89bffb8c04c1a0a9979cc02bd077a1d2deff (tag: ubuntu/2.2.0-0ubuntu1, origin/ubuntu/questing)
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Thu Jul 31 16:16:32 2025 -0500
Update build deps
commit 98c37947c4abdcda7487893cf15a3b73a26d3572
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Thu Jul 31 16:16:00 2025 -0500
Bump Standards-Version
commit 65c030aacf544aa46ca0c9159853e3da05edc3e2
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Thu Jul 31 16:15:30 2025 -0500
Update copyright file
commit 9f9ab07b6c8483ad890dda9b16f9883f6815a49d
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Thu Jul 31 16:14:31 2025 -0500
Bump version for new upstream release
You'll also notice your Bash prompt is gone, and instead your cursor is
sitting directly after a ``:`` character. That's because you're in a *pager*,
allowing you to scroll the Git log. You can scroll in any direction (including
left and right) using the arrow keys, and can scroll up and down quickly using
the Page Up and Page Down keys. To exit the pager and get back to a normal
Bash prompt, press ``q``.
Now that we're out of the pager, let's look closer at what all this info
means. You don't have to fully understand all of this yet, you can come back
and reference this later.
* Each "section" (separated from adjacent sections by newlines) corresponds to
a single commit.
* Each commit has a unique ID (this is that long hexadecimal number you see
after ``commit`` in each commit entry).
* Each commit has an author, the person who actually wrote the changes that
went into the commit. Authors are identified by name and email address.
(This is why we configured your name and email into Git when we set up the
packaging environment; this info will end up attached to commits you
create.)
* Each commit has a date. This is the exact date and time at which the commit
was created.
* Each commit has a *commit message*. Generally when you make changes to a
repository, you want to document *what* you changed, and *why*. These
records should be put into commit messages. Many repositories (especially
Lubuntu's packaging repositories) just have one-line commit messages briefly
summarizing what was done.
* Some commits have one or more branches displayed next to them. These commits
are on the tip of each respective branch. For instance, if we were to check
out ``ubuntu/resolute``, we'd end up on commit
``fe38a662e5c7e0c87d31a25097e9dc29dfe23554``.
.. NOTE::
Commit IDs are ridiculously long, hard to type, and just about impossible
to remember. To work around this, you can use the first several characters
of a commit ID in place of a full commit ID in most instances. It's
traditional to use the first seven or eight characters, since it's rare for
two commits in the same repo to ever have the same first seven characters,
and it's still short enough to be temporarily memorized and typed without
too much trouble.
* Some commits also have *remote* branches displayed next to them. Remember
when we saw ``origin/ubuntu/resolute`` earlier? That was referring to
``origin``'s copy of ``ubuntu/resolute``. Well, we see it again here. We
also see ``origin/ubuntu/questing``, which is ``origin``'s copy of
the ``ubuntu/questing`` branch.
* We can also see a tag, ``ubuntu/2.2.0-0ubuntu1``. This means that someone
decided that commit ``f90b89b`` should have this "name" associated with it.
In Lubuntu's packaging repos, we use tags to keep track of which commits
correspond to which package version numbers. Whoever packaged ``lxqt-about``
version ``2.2.0-0ubuntu1``, made this tag to say "if you check out
``f90b89b``, you will see the packaging that I uploaded to Ubuntu when I
created version 2.2.0-0ubuntu1 of this package."
* Finally, what's this ``HEAD`` thing? In Git, the "head" is a pointer that
tracks whatever commit you are currently "on". In this instance, HEAD is on
commit ``fe38a66``. The ``->`` in ``HEAD -> ubuntu/resolute`` means that
HEAD is currently *attached* to the branch ``ubuntu/resolute``. That means
that if you make a new commit, not only will ``HEAD`` move to the new
commit, the commit will also be added to the ``ubuntu/resolute`` branch,
thus changing the tip of the branch.
Now that we see what's going on with the log, let's take a look at the
repository's branches. We saw earlier that ``origin`` has an
``ubuntu/questing`` branch, so let's list off all the branches in the
repository and see what all we have::
git branch
This will output::
* ubuntu/resolute
Well that can't be right! There's an ``ubuntu/questing`` branch, so where is
it? To answer that question, we need to think a bit more about how Git works.
* Every developer has their own copy of the repo, and the remote has a copy
too.
* Every developer can modify their repo as they see fit. They have to manually
upload and download changes that have been made to the repo on the remote.
* Developers can create and upload new branches.
* Therefore, **the remote repo might have branches your repo doesn't have.**
Every time you download changes from the remote using ``git pull``, Git finds
out what branches the remote has available, downloads any commits associated
with them, and keeps track of those for you. However, it doesn't change the
branches in your repo. This makes it so that if you and someone else both make
a branch with the same name, you don't end up with serious problems the next
time both of you pull changes from the remote. Instead, Git keeps track of
your repo's branches and the remote repo's branches *separately*.
When you see ``origin/ubuntu/resolute``, that's because there's actually a
branch named ``origin/ubuntu/resolute``. You also have a branch named
``ubuntu/resolute``, and that branch happens to be *tracking*
``origin/ubuntu/resolute``. That means that when changes to
``origin/ubuntu/resolute`` are downloaded, ``ubuntu/resolute`` gets updated
with those changes. It also means if you change ``ubuntu/resolute``, and
upload your changes, those changes will end up in ``origin/ubuntu/resolute``.
It's a two-way sync.
With this in mind, we can better understand why ``ubuntu/questing`` is
missing; we don't have a branch named ``ubuntu/questing`` yet. We do know that
the ``origin`` remote has such a branch though. So let's see what branches
``origin`` has::
git branch -r
This will output quite a bit more information::
origin/HEAD -> origin/ubuntu/resolute
origin/backports/focal
origin/backports/jammy
origin/backports/mantic
origin/ci/stable
origin/ci/unstable
origin/ubuntu/cosmic
origin/ubuntu/disco
origin/ubuntu/eoan
origin/ubuntu/focal
origin/ubuntu/groovy
origin/ubuntu/hirsute
origin/ubuntu/impish
origin/ubuntu/kinetic
origin/ubuntu/lunar
origin/ubuntu/mantic
origin/ubuntu/noble
origin/ubuntu/oracular
origin/ubuntu/plucky
origin/ubuntu/questing
origin/ubuntu/resolute
We see that ``origin`` has a whole lot of branches, including the elusive
``origin/ubuntu/questing`` one we're looking for. There's also
``origin/HEAD``, which can be safely ignored.
.. NOTE::
If you're wondering what ``origin/HEAD`` is, see
`<this answer on Stack Exchange https://superuser.com/a/1192881/1734781>`__.
Now, the question is, how do we add one of these remote branches to our repo?
To do this, we'll simply *check out* the branch::
git checkout ubuntu/questing
This will output the following::
branch 'ubuntu/questing' set up to track 'origin/ubuntu/questing'.
Switched to a new branch 'ubuntu/questing'
And now if we run ``git branch`` again, we'll see::
* ubuntu/questing
ubuntu/resolute
Perfect! Now we have ``ubuntu/questing`` in our repo. We've also switched to
the ``ubuntu/questing`` branch, meaning our repo's working tree matches the
commit at the tip of ``ubuntu/questing``. To illustrate, let's look at a file
in the repository that will be different in ``ubuntu/questing`` and
``ubuntu/resolute``::
cat debian/watch
This will output the following::
version=4
opts="searchmode=plain, \
pgpsigurlmangle=s/$/.asc/, \
uversionmangle=s/(\d+\.\d+\.\d+).*/$1/" \
https://api.github.com/repos/lxqt/@PACKAGE@/releases https:\/\/github.com\/lxqt\/@PACKAGE@\/releases\/download\/@ANY_VERSION@\/@PACKAGE@-@ANY_VERSION@.tar.xz
We'll cover what all of this means once we get into Debian packaging itself.
For the time being though, let's switch back to ``ubuntu/resolute`` and see
what happens::
git checkout ubuntu/resolute
This will output the following::
Switched to branch 'ubuntu/resolute'
Your branch is up to date with 'origin/ubuntu/resolute'.
Now let's look at the ``debian/watch`` file again::
cat debian/watch
This will output the following::
Version: 5
Template: GitHub
Owner: lxqt
Project: @PACKAGE@
Download-Url-Mangle: s%https://api.github.com/repos/([^/]+)/@PACKAGE@/git/refs/tags/@ANY_VERSION@%https://github.com/$1/@PACKAGE@/releases/download/$2/@PACKAGE@-$2.tar.xz%g
Pgp-Mode: auto
That looks entirely different than what we saw a bit ago! What happened there?
We can find out what happened, by taking a look at the Git history. First,
let's find out who made the changes to this file::
git blame debian/watch
This will show us a line-by-line breakdown of the file, indicating what
commits changed each line in the file, who made them, and when they made
them::
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 1) Version: 5
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 2) Template: GitHub
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 3) Owner: lxqt
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 4) Project: @PACKAGE@
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 5) Download-Url-Mangle: s%https://api.github.com/repos/([^/]+)/@PACKAGE@/git/refs/tags/@ANY_VERSION@%https://github.com/$1/@PACKAGE@/releases/download/$2/@PACKAGE@-$2.tar.xz%g
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 6) Pgp-Mode: auto
See the ``fe38a662``? That's a partial commit ID! If we look back at the
output of ``git log`` from earlier, we can see the full commit ID, and
corresponding commit message::
commit fe38a662e5c7e0c87d31a25097e9dc29dfe23554 (HEAD -> ubuntu/resolute, origin/ubuntu/resolute)
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Tue Nov 18 13:16:56 2025 -0600
Use debian/watch version 5 (taken from Debian's packages)
Now that we know who changed the file and when, let's see what their changes
looked like. Each Git commit corresponds to a *state* the repo was in when the
commit was made, so we can't simply say "show me commit XYZ". What we *can*
say is "show me everything that changed between commit ABC and commit XYZ".
Furthermore, Git allows us to refer to the commit *before* a particular commit
by adding a caret (``^``) after a commit ID. That is, ``fe38a662^`` refers to
the commit immediately *before* ``fe38a662``. Now that we have two commits to
compare to each other, we can tell Git to show us what changed between those
commits::
git diff fe38a662^ fe38a662
This will output the following::
diff --git a/debian/copyright b/debian/copyright
index 7b74a60..6ee271d 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -11,10 +11,10 @@ License: LGPL-2.1+
Files: debian/*
Copyright: 2021-2025 Lubuntu Developers <lubuntu-devel@lists.ubuntu.com>
- 2022 ChangZhuo Chen (陳昌倬) <czchen@debian.org>
2014-2019 Alf Gaida <agaida@siduction.org>
2015-2021 Andrew Lee (李健秋) <ajqlee@debian.org>
2025 Aaron Rainbolt <arraybolt3@ubuntu.com>
+ 2022-2025 ChangZhuo Chen (陳昌倬) <czchen@debian.org>
License: LGPL-2.1+
License: LGPL-2.1+
diff --git a/debian/watch b/debian/watch
index a544020..4070de7 100644
--- a/debian/watch
+++ b/debian/watch
@@ -1,5 +1,6 @@
-version=4
-opts="searchmode=plain, \
-pgpsigurlmangle=s/$/.asc/, \
-uversionmangle=s/(\d+\.\d+\.\d+).*/$1/" \
- https://api.github.com/repos/lxqt/@PACKAGE@/releases https:\/\/github.com\/lxqt\/@PACKAGE@\/releases\/download\/@ANY_VERSION@\/@PACKAGE@-@ANY_VERSION@.tar.xz
+Version: 5
+Template: GitHub
+Owner: lxqt
+Project: @PACKAGE@
+Download-Url-Mangle: s%https://api.github.com/repos/([^/]+)/@PACKAGE@/git/refs/tags/@ANY_VERSION@%https://github.com/$1/@PACKAGE@/releases/download/$2/@PACKAGE@-$2.tar.xz%g
+Pgp-Mode: auto
We won't go into all the intricacies of the diff format used here. Suffice to
say, lines prefixed with a ``-`` are lines that were *removed*, and lines
prefixed with a ``+`` are lines that were *added*. In this case we can see
that ``debian/watch`` got completely rewritten. We also see that in
``debian/copyright``, ChangZhuo Chen was moved to the bottom of the
``Files: debian/*`` section, and the ``2022`` copyright reference was changed
to ``2022-2025``. Why exactly this was done, only the committer knows, but
that's what was done. The commit log conveniently lists Aaron's email, so now
you can now email Aaron Rainbolt and ask him why he did this.
.. NOTE::
The reason Aaron (me) made this change was because the ``debian/watch``
file in this package was previously broken, and the Debian LXQt Team's
``debian/watch`` file was working. I copied their file, and plugged it into
Lubuntu's packaging to get things to work right. The Git repo for Debian's
``lxqt-about`` package listed ChangZhuo Chen as the author of the new watch
file contents, so I moved their copyright info to the bottom and updated
their copyright date accordingly.
.. WARNING::
If you're security-conscious, you might be asking yourself, "what's to stop
me from pretending to be Aaron or anyone else when I make a change to the
repo?" The answer is, **nothing prevents this by default.** Impersonating
others in Git logs is very easy. This is part of why projects don't hand
out Git commit access to just anyone; it would be too easy to do something
malicious and make someone else look like they were at fault. Even this
isn't bullet-proof though, a contributor might become malicious, or someone
might hack the server that hosts the remote Git repo. Thankfully, there is
a good way to make it impossible for others to impersonate you, which is to
PGP-sign your Git commits. More on that later.
This is all well and good if we just want to analyze the differences between
individual commits. But what if we want to diff something else? Perhaps
someone recently pushed five commits to the ``ubuntu/resolute`` branch, and we
want to see what the repo looked like before and after their changes. There
are a few ways we can do this:
* The most obvious way is to use ``git log`` to identify the commits we want
to compare, and then compare them with ``git diff``. For instance, if the
commit HEAD is on is ``12345678``, and the commit immediately before the
batch of changes is ``abcdefab``, we can compare them with
``git diff abcdefab 12345678``.
* As a shortcut, if we want to refer to the HEAD commit, we don't have to type
out the full commit ID HEAD is on. We can just use ``HEAD``, like so:
``git diff abcdefab HEAD``.
* We can also combine ``HEAD`` with ``^``. If we want to compare the commit
HEAD is on with the commit before it, we can use ``git diff HEAD^ HEAD``.
This only goes back one commit though, and we want to go back five, so...
* You can actually use ``^`` multiple times to go back more than one commit!
To compare the commit that is five commits back from HEAD, with the commit
HEAD is on, we can use ``git diff HEAD^^^^^ HEAD``.
* Typing ``^`` over and over can become cumbersome. As a shortcut, you can use
``~N`` after a commit ID, where ``N`` is the number of commits to go
backward. This works with ``HEAD`` too, so we can use
``git diff HEAD~5 HEAD``. This will give the exact same results as
``git diff HEAD^^^^^ HEAD``.
It's possible to diff "things" other than commits also, so long as those
"things" point at commits. Branches and tags can be compared to other
branches, other tags, commits IDs, or HEAD. Git calls all of these things
"refs". Diffing two refs will diff the commits the refs point to.
Modifying a Git repo
^^^^^^^^^^^^^^^^^^^^
Most of the above covers how to look at existing commits and refs. What if we
want to make some changes to the repo and commit them? Let's try doing that
now, by changing the ``debian/control`` file. Open the file in a text editor
such as Featherpad, by running ``featherpad debian/control`` Scroll to the
bottom of the file, then change the description for ``lxqt-about-l10n`` to
say::
The about screen for LXQt
.
This package contains the l10n files needed by the lxqt-about.
It also contains an extra line in the description for qwerty.
(You will be adding the last line to the description here. Mind the extra
space at the start of each line, those are intentional. We'll cover those
further when we get into packaging itself.)
.. NOTE::
You can use whatever text editor you want, but once you start doing a lot
of packaging, you'll probably want to learn how to use a text editor that
runs directly in the terminal. This is a lot more efficient than having to
switch between a terminal window and a GUI text editor. The editor
generally recommended for Lubuntu development is Vim, which comes
preinstalled on Lubuntu. You can get started with learning to use Vim by
running ``vimtutor`` in a terminal window.
Save and close the file. Now let's see what Git thinks about what we've just
done, by running ``git status``. It will output the following::
On branch ubuntu/resolute
Your branch is up to date with 'origin/ubuntu/resolute'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: debian/control
no changes added to commit (use "git add" and/or "git commit -a")
Git sees that we modified the file, but claims that the changes we've made
aren't "staged for commit". In order to allow users to break up large changes
into multiple commits, Git uses the concept of a staging area (called the
"index"). Changes made to a repo have to be "staged" before they can be
committed, and when a new commit is made, only staged changes go into the
commit. This means if you make two unrelated changes to different parts of the
repo, you can stage one of those changes and commit it, while leaving the other
change unstaged. Once the first commit is made, you can stage and commit the
other change.
Changes can be staged by using ``git add``. You'll generally want to use
``git add`` in one of three ways:
* To stage an individual file, use ``git add path/to/file``. For instance, to
stage the changes we just made to ``debian/control``, you can use
``git add debian/control``.
* To stage all of the files that changed within a directory, use
``git add path/to/dir/*``. You can stage the changes made to
``debian/control`` using ``git add debian/*``. Note that this recurses into
subdirectories; if ``debian/control`` and ``debian/upstream/metadata`` were
both changed, ``git add debian/*`` would stage both those files.
* Finally, to stage *all* changes made within the repo, use ``git add -A``.
This is probably the way you'll use ``git add`` the most.
Now that we've staged the change, let's see what ``git status`` reports now::
On branch ubuntu/resolute
Your branch is up to date with 'origin/ubuntu/resolute'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: debian/control
We're now ready to commit a change finally! As one might expect, changes are
committed by using ``git commit``.
* The easiest way to use this command is to just run ``git commit``; a text
editor will be opened, allowing you to write a description of the changes
you've made. This is good for if you need to write a long, detailed
explanation of what you've changed, but most of the time you won't need to
do that.
* If you only want a short description for the changes, you can give Git the
description by using ``git commit -m 'description here'``.
Let's commit the change we just made, by running
``git commit -m 'update lxqt-about-l10n description'``. You should now see
something similar to the following::
[ubuntu/resolute 4686cc1] update lxqt-about-l10n description
1 file changed, 1 insertion(+)
Congratulations, you have now committed your first change! If we look at
``git status`` now, we'll see::
On branch ubuntu/resolute
Your branch is ahead of 'origin/ubuntu/resolute' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
This should all look familiar by now, except for the line
``Your branch is ahead of 'origin/ubuntu/resolute' by 1 commit.`` This means
that our copy of ``ubuntu/resolute`` has one commit in it that ``origin``'s
copy of ``ubuntu/resolute`` doesn't have yet.
Let's see what happens if we create a new file in the repo. Make sure you're
in the ``lxqt-about-packaging`` directory, and then create a file named
``test.md`` with the following contents::
# Test file
This is a test file.
Save and close the file, then run ``git status`` again. You should see::
On branch ubuntu/resolute
Your branch is ahead of 'origin/ubuntu/resolute' by 1 commit.
(use "git push" to publish your local commits)
Untracked files:
(use "git add <file>..." to include in what will be committed)
test.md
nothing added to commit but untracked files present (use "git add" to track)
This is a bit different than what we saw earlier. That's because previously,
we modified a file Git already knew about in this repo, namely
``debian/control``. Git does not yet know that ``test.md`` is actually part of
the repo, so it's considered *untracked*. Git treats untracked files and
unstaged changes to tracked files in subtly different ways we'll investigate
later, but for now, you can mostly treat untracked files the same way you'd
treat any other modified file. We don't actually need to commit this file to
continue learning how Git works, so go ahead and delete the file with
``rm test.md``.
Inevitably, as you work on packaging, you are going to make mistakes, and
inevitably, you're going to end up committing some of those mistakes. Let's
look at the line we added to ``debian/control`` a bit ago::
It also contains an extra line in the description for qwerty.
This probably looked a bit strange to you. "qwerty" really doesn't describe
what we're doing accurately. "testing" would be a better word to use. Go ahead
and change ``qwerty`` to ``testing`` now, using Featherpad or your preferred
text editor. Stage the change with ``git add -A`` when you're done.
Now, we *could* just add the fix as a new commit. However, in this instance,
that's probably not the best thing to do since the commit exists only on your
computer. No one else knows that we wrote "qwerty" when we meant "testing", so
we can simply recreate the commit. To do that, use
``git commit --amend --no-edit``. This will discard the previous commit, and
create a new one that contains all of the changes from the previous commit
plus all of the changes in the index. It will also preserve our commit message
unchanged (that's what ``--no-edit`` does). If you want to change the commit
message too (or if the only thing you want to change is the commit message),
you can leave off ``--no-edit`` to get a commit message editor, or you can use
``-m`` to pass a one-line commit message, just like you would when using ``git
commit`` normally.
.. NOTE::
I intentionally avoided saying that we're *changing* the commit here.
Commits are immutable, and each commit links to the commit immediately
behind it. This allows Git to provides a number of helpful features, but it
also means that one shouldn't think in terms of "changing" a commit.
.. NOTE::
``git commit --amend`` is useful for recreating the last commit. But what
if we need to fix something from several commits ago? Because commits are
linked together and immutable, we have to recreate the bad commit and every
commit ahead of it. Git provides a utility for this, ``git rebase``, but
you'll rarely need it and it is tricky to use, so we won't cover this yet.
Amending a commit is useful for fixing something wrong with a commit. But what
if the commit is so far gone you'd rather just get rid of it and start over?
This extra line in the description of ``lxqt-about-l10n`` isn't really useful,
and it's probably not something that can be made useful in the future. This is
where ``git reset`` comes in handy. Whereas ``git commit`` creates commits,
``git reset`` destroys them. You can use ``git reset`` in a few different
ways, the two most useful are:
* Mixed reset, which gets rid of commits but leaves the actual changes made in
those commits in your working tree. Mixed reset is the default mode.
* Hard reset, which gets rid of commits *and* resets all tracked files to
a previous state. Note well that this resets *tracked* files. Files that are
untracked will be left alone even by a hard reset. This is one of the ways
Git treats tracked and untracked files differently.
To do a mixed reset to the previous commit, run::
git reset HEAD^
This will leave our changes intact, but the commit itself will be undone and
the changes will *not* be staged for commit. You can now make more changes and
then make a new commit, or you can completely undo all of our changes with::
git add -A
git reset --hard HEAD
This will first stage all modifications, so that any untracked files are
caught. Then it will hard-reset the repo back to the commit HEAD is pointing
to, discarding our changes.
When you're doing work in a Git repo, it's generally a good idea to work on a
branch other than the "primary" branch (``ubuntu/resolute`` in this instance).
This makes it easier to avoid running into conflicts with other developers.
It is possible to use ``git branch`` to create branches, but most of the time
when you're making a branch, you also want to switch to the branch. For this,
you can use the ``-b`` option of ``git checkout``, like so::
git checkout -b my-new-branch
Note that ``git checkout -b`` will fail if the branch already exists.
This is a good enough start for now. There are a few more things you'll want
to know about Git in your work as a packager (namely rebasing, tagging,
forking, and pushing), but it will be easiest to cover those as we need them.
Miscellaneous useful things to know about Git
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* You can diff the contents of your working tree with the HEAD commit by using
``git diff HEAD``. This is useful to see what all you've changed before
committing it. Note that untracked files will be ignored when you do this.
If you want to include those in the diff too, ``git add`` them first.
* Raw diffs are oftentimes hard to read. If you want a more comfortable
viewing experience, install Meld (``sudo apt install meld``), then use
``git difftool --tool=meld --dir-diff`` instead of ``git diff``. This will
let you visualize the changes between two commits in much more detail than a
raw diff allows. You can even make (limited) changes to your working tree
here by modifying files on the right side of the Meld window. (Be warned
that changes made to the left side of the window will be silently discarded,
and that copying files from the left side of the window to the right side
will not work; those files will be silently discarded as well.)
* You can checkout more than just branches. ``git checkout`` can be used on
any ref. If you check out something other than a branch however, you will
enter what Git calls "detached HEAD state". This means that any commits you
make will not be integrated into any existing branch, and if you check
something else out later, you'll probably have a hard time finding those
commits later. If you decide you want to keep the changes you've made, use
``git switch -c new-branch-name`` to make a new branch with any changes
you've made so far.
* As a general rule, do not use force-push unless you're fixing commits you
just pushed. Other users may have pulled those commits, and will have to fix
their repos if you do this. There are exception to this rule, you'll be able
to spot them as you gain experience.
Foundations of Debian packaging
-------------------------------
TODO