Packaging j4-dmenu-desktop
- Why do we care about package management?
- The better way
- What is a package, anyway?
- Before we begin…
- xbps-src
- Improving the template
This section of the xbps-src packaging tutorial focuses on packaging j4-dmenu-desktop version r2.18. This version was the latest released version when this tutorial was written, but it has since been superseded by release r3.0, which addresses many of the issues discussed in this tutorial.
As described in the introduction of this tutorial, specific packages discussed in this tutorial may be out of date by the time you are reading this. But the choice to package version r2.18 is deliberate here. This version showcases many useful features of xbps-src that help with fixing upstream packages.
You’ve heard about a cool program called
j4-dmenu-desktop, a desktop menu
that uses dmenu
and you want to try it out. To do so, you have to:
- download it,
- build it and
- install it
You read their README and come up with the following process to do this:
git clone https://github.com/enkore/j4-dmenu-desktop.git
cd j4-dmenu-desktop
# Setup a builddir (this is CMake specific stuff)
mkdir build
cd build
# Configure j4-dmenu-desktop
cmake ..
# Build
make
# Install j4-dmenu-desktop to system directories
sudo make install
All is well, everything works. But this approach has some disadvantages.
Why do we care about package management?
sudo make install
can do a lot of things. It can install the executable to the
system (that’s why we’re running it), it can install some libraries needed for
j4-dmenu-desktop
, it can install configuration files, documentation, shell
completions, examples, etc. You should keep track of all the files that have
been installed.
Why do you have to keep track of them? Let’s say that you have tried
j4-dmenu-desktop
out and you weren’t satisfied with its features, so you want
to uninstall it. You will have to sudo rm
each file that has been installed.
If you don’t care much about a clean uninstall, you might choose to just rm
the executable. The rest of j4-dmenu-desktop
’s files will just sit there. They
will still be installed, but unused. This can waste your storage if, for example,
j4-dmenu-desktop
includes a large pregenerated documentation collection like
some programs do.
But let’s say you’ve been diligent and you know which files to remove. Still,
wandering around with sudo rm
in your system directories isn’t the best idea.
One typo, one poorly thought-out glob and you’ve bricked your system.
Let’s consider another scenario. You’ve tried j4-dmenu-desktop
and you were
awestruck by its features, so you didn’t even think about uninstalling it. But
a new version has been released. You don’t hesitate and clone the newer version
and redo the download, build, and install process above.
But the new version doesn’t install the same set of files to the system as the older one. There could be some files added and some files missing. This means that you will also have to keep track of these. You should also remove the files that were in the older version but are no longer in the new version. These files will not be overridden by the new install, so they will again just sit there.
If you are a more knowledgeable Linux user, you could set up a separate destdir
for j4-dmenu-desktop
. You will have to add it to $PATH
and potentially handle
shared libraries. This is cumbersome.
Let’s say that you were so amazed by j4-dmenu-desktop
that you rushed to tell
your friends and colleagues about it. They naturally want to try it out too. But
the program could be big, it could take hours to build and its build system
could be complicated and involved. All these problems are avoidable because
your friends only need the executables (and shared libraries + possibly some
supplementary data files like /etc/
config). You already have all of this, you
just need to send it to them. They don’t need the source.
This means that you (again) have to know which files are installed by
j4-dmenu-desktop
to know what to send and they will also have to keep track
of it for the same reasons you have to.
But they have managed to install it with your provided build. But there’s a small problem: it doesn’t run! It’s missing dependencies. But which ones? No one knows.
If only there was a better way…
The better way
This is why we have package management. Each package owns its files and keeps track of them. This means that completely removing a package is as simple as running
sudo xbps-remove j4-dmenu-desktop
Because the package owns its files, it can verify their state. Let’s say that
you have modified some j4-dmenu-desktop
files by accident. If you have noticed
it, you can uninstall it and reinstall it. However, you might not have even
noticed.
The package manager can check the state of the package and it can print all
filenames which do not match the package. This is done with xbps-pkgdb
. It can
also detect missing files.
You can see which files a package provides with xbps-query -f
:
> xbps-query -Rf j4-dmenu-desktop
/usr/bin/j4-dmenu-desktop
/usr/share/man/man1/j4-dmenu-desktop.1
Another very important aspect of package management that I have partly
overlooked until now is dependencies. Each package maintains a list of its
dependencies. You can query then with xbps-query -x
:
> xbps-query -Rx j4-dmenu-desktop
libstdc++>=4.4.0_1
libgcc>=4.4.0_1
glibc>=2.32_1
You can’t1 install a package without its dependencies, so breakage cannot occur.
What is a package, anyway?
You don’t necessarily have to know the internals of XBPS to be able to package, but it’s interesting to know nonetheless. Feel free to skip this section.
An XBPS package is a file usually ending with .xbps
. They are managed by the
xbps-*
utilities, mainly xbps-install
. Packages are downloaded to
/var/cache/xbps
.
Packages are signed and checksumed to prevent tampering and to verify
authenticity. The signature is usually contained in a file with the same name as
the package with .sig
or the newer .sig2
added to the end of the filename.
A .xbps
file (like most other “complicated” file formats) is just a disguised
archive. At the time of writing this tutorial, mainly tar zstd archives are
used, but XBPS supports many
formats. You can
simply tar -xf /var/cache/xbps/j4-dmenu-desktop-2.18_3.x86_64.xbps
(if you
have it downloaded).
For example, j4-dmenu-desktop
’s package looks like this:
.
├── files.plist
├── props.plist
└── usr
├── bin
│ └── j4-dmenu-desktop
└── share
└── man
└── man1
└── j4-dmenu-desktop.1
files.plist
contains the list of files in a package and props.plist
contains
package metadata (version, homepage…). They are both in
plist format, which is derived
from XML.
If you want to examine individual files of a package, you don’t have to extract them. You can use
xbps-query -R --cat /usr/share/man/man1/j4-dmenu-desktop.1 j4-dmenu-desktop
The archive can include additional special files like INSTALL
and REMOVE
. I
won’t cover them, read the
Manual
for more info.
A collection of packages is a repository. Official repositories are described here.
A repository is characterized by its repodata
file. It is named
<architecture>-repodata
. Repodata files are saved to /var/db/xbps/
locally.
They contain metadata for all packages in the repository.
They are (again) tar zstd archives. They contain two files: index.plist
and
index-meta.plist
. index-meta.plist
contains the public key. index.plist
contains metadata of all packages in the repository.
Repodata is managed by xbps-rindex
.
Before we begin…
Quality requirements
This section is primarily intended for people who want to contribute their packages to void-packages. If you want to use your package yourself and don’t want to publish them, you can ignore this section.
Before I even begin describing xbps-src
, I have to mention quality
requirements. Not all packages get accepted. All new packages must fulfill the
package requirements.
Package requirements can be found in the package requirements section of CONTRIBUTING.
Namely, software without a release or tag, cryptocurrency packages, browser forks, simple shell scripts, themes, prebuilt packages, and more won’t likely get accepted. I recommend you read the requirements in full linked above.
Has it been packaged already?
You should check whether it has been packaged already, if a pull request providing the package is already open, or if someone has already packaged it but it was rejected or abandoned. If there already is a pull request but it has been abandoned, you can base your package off of it, which will save you time.
xbps-src
Now, we really begin.
First, you need to clone the
void-packages
repository.
CONTRIBUTING
recommends using SSH. This needs to be set up for it to
work.
If you have SSH set up, you can clone the repo with:
git clone git@github.com:void-linux/void-packages.git
If you do not want to set up SSH keys, you can clone with HTTPS:
git clone https://github.com/void-linux/void-packages.git
The git repository contains everything needed to build an XBPS package2 and a collection of templates for packages in the official repositories.
If you have experience with other package managers, I should note that void-packages keep all package templates in one repository (it isn’t one repo per package like the AUR for example).
The repo is pretty big because of this. There are also 189 334 commits of history at the time of writing. That’s a lot of commits.
At the time of writing this tutorial, cloning the repo took me 15 minutes on my notebook. It had 626 MB.
There are faster ways to clone, but this way should be preferred.
While you wait for it to clone, you can learn more about void-packages:
void-packages structure
This is the directory hierarchy of void-packages:
/void-packages
|- xbps-src
|- common
|- etc
|- srcpkgs
| |- <PACKAGE NAME>
| |- template
|
|- hostdir
| |- binpkgs ...
| |- ccache ...
| |- distcc-<arch> ...
| |- repocache ...
| |- sources ...
|
|- masterdir-<arch>
| |- builddir -> ...
| |- destdir -> ...
| |- host -> bind mounted from <hostdir>
| |- void-packages -> bind mounted from <void-packages>
Here is a more graphic and simplified version of it showing j4-dmenu-desktop
,
bat
, oniguruma
and rofimoji
packages:
The most important is xbps-src
. It is a Bash script which uses XBPS to
build and package programs.
The srcpkgs
directory contains all templates used by xbps-src
to build
packages. Templates contain metadata of a package and instructions for building
it.
The hostdir
directory has two main purposes: it stores the source archives (or
other files that have to be downloaded for the package to build) and it stores
the built .xbps
packages.
Source archives are stored in hostdir/sources/<package name>-<version>/
. It is
influenced by the distfiles
variable (this is explained later).
The resulting .xbps
packages will end up in hostdir/binpkgs
or
hostdir/binpkgs/<branch>
if you are on a custom git branch (not on master
).
But the most important directory is masterdir-<arch>
(I will from now on
assume that your computer is x86_64 and refer to this directory as
masterdir-x86_64
like on the diagram above3). The package is built within
it. It is isolated from the rest of the computer. It contains a small Void Linux
install suitable for chrooting (no kernel included, no firmware included, no
user utilities). It has the base-chroot
base package4 installed
(normal Void Linux systems have base-system
installed).
The source of the package will be extracted (or put by other means) to
masterdir-x86_64/builddir/<package name>-<version>/
and it will install the
built executable and supplementary files to
masterdir-x86_64/destdir/<package name>-<version>
as if it was
installing to /
. This is called “fake destdir” and it is supported by most
major build systems. For instance, if a program would be normally installed to
/usr/bin/j4-dmenu-desktop
, it will be installed to
builddir/destdir/j4-dmenu-desktop-2.18/usr/bin/j4-dmenu-desktop
instead
(relative to masterdir-x86_64/
).
xbps-src
requires the installed files to be in the masterdir to know which
files belong to the package.
This is a lot of information. The best way to take it all in is by practising writing templates. But first, we need to know how to build a package:
Basic usage of xbps-src
To build packages, a masterdir must be installed. You can bootstrap the masterdir with this command:
./xbps-src binary-bootstrap
This installs a minimal version of Void Linux to masterdir-x86_64
. This takes
about two minutes on my notebook.
You can then build packages in void-packages with
./xbps-src pkg <package>
You can install built packages with
sudo xi -f <package>
(The xi
utility is provided by the xtools
package.)
When xi
is run as a normal user, it elevates itself using sudo
, doas
or
su
(in this order). This means that you can omit sudo
above.
If a package build fails, the files are still kept in place for inspection. You should run
./xbps-src clean
to clean builddir
, destdir
and to remove installed dependencies from
masterdir.
As noted in the troubleshooting page, you should run
./xbps-src clean
often when debugging a failing build.
This is pretty useful by the way. If an xbps-src
build fails, you can always
look into masterdir-x86_64/builddir/<package name>-<version>
to find the
package in the state xbps-src
left it in if the package build fails.
Introduction to templates (aka the return of j4-dmenu-desktop)
j4-dmenu-desktop
is already packaged in
void-packages.
You’ll have to remove it if you want to follow along. You can remove it by
running:
rm -r srcpkgs/j4-dmenu-desktop
Let’s package j4-dmenu-desktop.
You may remember the build steps from before:
git clone https://github.com/enkore/j4-dmenu-desktop.git
cd j4-dmenu-desktop
# Setup a builddir (this is CMake specific stuff)
mkdir build
cd build
# Configure j4-dmenu-desktop
cmake ..
# Build
make
# Install j4-dmenu-desktop to system directories
sudo make install
We just have to put it into a template and we’re done5.
A template contains metadata of the package and instructions for xbps-src
to
build the package.
xnew
is used to make a new template. xnew
isn’t a part of void-packages
,
but it’s from the xtools
package. xtools
contains many useful utilities for
building packages and for using Void Linux in general. Install it:
sudo xbps-install -S xtools
A new template is created like this:
xnew j4-dmenu-desktop
Make sure you have removed srcpkgs/j4-dmenu-desktop
as mentioned at the
beginning of this section if you want to follow along with packaging
j4-dmenu-desktop
.
xnew
will open srcpkgs/j4-dmenu-desktop/template
in your editor (you can set
your editor with $EDITOR
or $VISUAL
) and prefill some variables:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=
revision=1
#archs="i686 x86_64"
#build_wrksrc=
build_style=gnu-configure
#configure_args=""
#make_build_args=""
#make_install_args=""
#conf_files=""
#make_dirs="/var/log/dir 0755 root root"
hostmakedepends=""
makedepends=""
depends=""
short_desc=""
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage=""
#changelog=""
distfiles=""
checksum=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb
This template will contain instructions for xbps-src
to build your package.
A template is (like xbps-src
) a Bash script. Templates are sourced by
xbps-src
, so they do not contain shebang.
xbps-src
expects some variables to be set in the template. All templates
should include pkgname
, version
, revision
, short_desc
, maintainer
,
license
and homepage
.
Most of these variables contain metadata of the package. Here is their explanation:
-
pkgname
= name of the packageChoosing a name for a package can be sometimes tricky. It should follow the upstream name. If you’re unsure, read Void’s package naming conventions.
-
version
= version of the package; it cannot contain dashes or underscore and at least one digit is requiredThe version shouldn’t contain unnecessary characters:
But all rules have exceptions. I’ve recently seen a package that uses letters for versioning (
1.6.2
,1.6.2b
,1.6.2c
,1.6.2d
…). The letters should of course be included inversion
for this specific package, because they are necessary to differentiate versions. And then there is the legendaryfont-adobe-source-code-pro-2.038R~ro+1.058R~it+1.018R~VAR_1
. This package’spkgver
is so long that it breaks the UI of some XBPS utilities (but it’s only visual). -
revision
= revision of the package; it might sometimes be necessary to update a package without changing its version (this is a so-called “revbump”); this is useful when the currently packaged version of the program needs fixingThe
pkgver
of a package consists of${pkgname}-${version}_${revision}
. For examplej4-dmenu-desktop-2.18_3
. As you can see,revision
is specified alongside theversion
.New packages should always have
revision=1
. -
build_style
= we’ll come back to this later -
hostmakedepends
,makedepends
,depends
= the different types of dependencies of the package; we’ll come back to this later -
short_desc
= a short description of the package; the maximum is 72 characters, it mustn’t start with an article and it should start with an uppercase letter:The description should be terse (it is called
short_desc
for a reason):short_desc="This is a desktop menu"
short_desc="Desktop menu with support for dmenu-compatible menu programs"
A good choice for
short_desc
is the GitHub short description (if the packaged project has one):(The “A” article has to be removed here.)
-
maintainer
= you;xnew
prefills this field using git, so you don’t need to change it -
license
= this has to be a SPDX identifierIdentifying license(s) can be difficult sometimes. There are some programs which specialise in identifying licenses of projects. Some of them can return SPDX identifiers. You can find a list at https://github.com/todogroup/awesome-ospo/blob/main/README.md#licensing
j4-dmenu-desktop
is licensed under the GNU General Public License version 3 or later, which corresponds to theGPL-3.0-or-later
SPDX identifierVoid Linux provides
spdx-licenses-html
,spdx-licenses-json
,spdx-licenses-list
andspdx-licenses-text
packages if you don’t want to look up the licenses online.spdx-licenses-list
is a dependency ofxtools
, so you will already have that installed.Where does
spdx-licenses-list
put the license list?A project can have multiple licenses. They have to be all specified in
license
delimited by “, “ (comma and space). -
homepage
= project’s homepage; a link to the GitHub/GitLab etc. repository is usually sufficient (if the project doesn’t have a custom website) -
distfiles
= project source; we’ll come back to this later -
checksum
= sha256sum ofdistfiles
-
changelog
= a (preferably plaintext) changelog of the project; it is optional, not all packages have itIf the project has a
CHANGELOG
orCHANGELOG.md
(or something similar) in its repository, you should link to it inchangelog
, but you should link to a plaintext version. GitHub has a button for that:You can then copy the link.
We need to gather all of this information about j4-dmenu-desktop
to be able to
package it.
We will be packaging the release 2.18
(see note)
of j4-dmenu-desktop
. Its short_desc
is Fast desktop menu
, maintainer
is prefilled, j4-dmenu-desktop
’s license is GPL-3.0-or-later
, its homepage
is https://github.com/enkore/j4-dmenu-desktop and its changelog is
https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG.
This is all we need for now.
Some progress
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
#archs="i686 x86_64"
#build_wrksrc=
build_style=gnu-configure
#configure_args=""
#make_build_args=""
#make_install_args=""
#conf_files=""
#make_dirs="/var/log/dir 0755 root root"
hostmakedepends=""
makedepends=""
depends=""
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
# We have uncommented the changelog variable.
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
distfiles=""
checksum=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb
We won’t need any of the commented-out variables. We’ll also not need
build-style
, distfiles
and checksum
(Or will we? foreshadowing):
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
hostmakedepends=""
makedepends=""
depends=""
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
I have left the hostmakedepends
, makedepends
and depends
variables. We
will fill these out later.
That’s looking nice and simple. Now, we just have to add the build instructions. For that, we will need build phases.
Build phases
An xbps-src
build has 10 phases:
(Phases in italic are less important for writing templates.)
Almost all phases have a pre_<phase>()
and a post_<phase>()
functions. These
get executed before and after said phase. The phase itself is the function
do_<phase>()
. Here are all phases that can be overridden in a template:
Only the do_install()
function is mandatory, all other functions are
optional.
I’ll summarize the important ones:
fetch
- download source archiveextract
- extract itpatch
- apply patchesconfigure
- run configure stage of build (not all build systems have it)build
- run build stage of buildcheck
- check programinstall
- install project todestdir
You can read about the rest in the Manual here and here.
Some progress
These are the original build steps:
git clone https://github.com/enkore/j4-dmenu-desktop.git
cd j4-dmenu-desktop
# Setup a builddir (this is CMake specific stuff)
mkdir build
cd build
# Configure j4-dmenu-desktop
cmake ..
# Build
make
# Install j4-dmenu-desktop to system directories
sudo make install
Now, we incorporate them into the template:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
hostmakedepends=""
makedepends=""
depends=""
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
do_fetch() {
git clone https://github.com/enkore/j4-dmenu-desktop.git
cd j4-dmenu-desktop
}
do_configure() {
mkdir build
cd build
cmake ..
}
do_build() {
make
}
do_install() {
sudo make install
}
This is a great start, but it needs some changes. First, the masterdir is a
separated system. Its default user already has access to everything necessary,
so sudo
isn’t necessary.
Second, this template fails, because git
and cmake
aren’t installed in
masterdir-x86_64
. As I have mentioned earlier, the masterdir
is minimal and
includes little out of the box. Both have to be added as a dependency to
hostmakedepends
. (Why hostmakedepends
and not makedepends
or depends
will be explained later.)
Third, $CWD
could be utilised better. You might wonder where is the current
working directory of the template. The answer depends on the function.
CWD
Here are the important directories for demonstration:
void-packages
├── srcpkgs
│ └── j4-dmenu-desktop
│ └── template
├── masterdir-x86_64
│ ├── builddir
│ │ └── j4-dmenu-desktop-2.18
│ │ ├── CMakeLists.txt
│ │ ├── README.md
│ │ ├── src
│ │ └── ...
│ └── destdir
│ └── j4-dmenu-desktop-2.18
│ ├── rdeps
│ ├── shlib-requires
│ └── usr
│ └── ...
...
pre_fetch()
,pre_extract()
anddo_clean()
are executed in<masterdir>
i.e.masterdir-x86_64/destdir/
do_fetch()
andpost_fetch()
are executed inXBPS_BUILDDIR
.XBPS_BUILDDIR
is a special variable set byxbps-src
that shouldn’t be overridden. It is set to<masterdir>/builddir
, heremasterdir-x86_64/builddir/
do_extract()
throughdo_patch()
(4 stages) are executed inwrksrc
post_patch()
throughpost_install()
(14 stages) are executed inbuild_wrksrc
if it’s defined, otherwise inwrksrc
We can see here that most stages are executed with $CWD
in $wrksrc
.
wrksrc
xbps-src
also defines some variables itself for us to use. One of them is
wrksrc
.
The wrksrc
of a package is defined as
<masterdir>/builddir/${pkgname}-${version}
. For j4-dmenu-desktop
it’s
masterdir-x86_64/builddir/j4-dmenu-desktop-2.18
. xbps-src
expects files to
be in $wrksrc
, but git puts them into j4-dmenu-desktop
(without the
version part -2.18
). This must be fixed.
Now we know that the current working directory is managed by xbps-src
, so the
cd
call in do_fetch()
is unnecessary. But it is necessary in do_build()
and do_install()
, because their CWD will be set to $wrksrc
and not
$wrksrc/build
Here is the updated template:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
hostmakedepends="git cmake"
makedepends=""
depends=""
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
do_fetch() {
git clone https://github.com/enkore/j4-dmenu-desktop.git "$wrksrc"
}
do_configure() {
mkdir build
cd build
cmake ..
}
do_build() {
cd build
make
}
do_install() {
# sudo was removed
cd build
make install
}
But wait, why didn’t we have to install make
? We don’t have to add make
to
dependencies because it is one of the basic dependencies of base-chroot
, so
make
is automatically installed in every masterdir
.
Now, there are only two issues that are preventing j4-dmenu-desktop
from
building:
Destdir
Remember when I mentioned “fake destdir”? All files must be installed to fake
destdir and not to /
. The current template installs it to /
(because it’s
the default location for CMake).
The current template doesn’t install it to your computer’s root, but to
masterdir
’s root, because masterdir
is isolated.
Installing things to masterdir
is not tolerable in templates. It pollutes the
masterdir
and it can no longer be safely used afterwards6.
In CMake, this can be solved by calling make install
like this:
make DESTDIR=$DESTDIR install
$DESTDIR
, like $wrksrc
, is a variable defined by xbps-src
. In this case,
it points to destdir/j4-dmenu-desktop-2.18
(it’s relative to the masterdir).
Prefix
Another issue is the prefix. Most build systems dealing with C or C++ assume by
default that you want to install things to /usr/local
instead of /usr
. This
is the directory model on FreeBSD for example. /usr
is for system programs and
/usr/local
is for user programs. This is nice when you want to install
something manually (without using a package manager), but now, we are packaging.
Our package is a system package. We have to tell cmake
that the prefix is
/usr
. This is done with -DCMAKE_INSTALL_PREFIX=/usr
in CMake. Here is the
fixed template:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
hostmakedepends="git cmake"
makedepends=""
depends=""
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
do_fetch() {
git clone https://github.com/enkore/j4-dmenu-desktop.git "$wrksrc"
}
do_configure() {
mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
}
do_build() {
cd build
make
}
do_install() {
cd build
# DESTDIR was added
make DESTDIR="$DESTDIR" install
}
And we’ve done it! This template is buildable. You can run:
./xbps-src pkg j4-dmenu-desktop
and a package will be outputted to hostdir/binpkgs
.
But this package will not get accepted to void-packages, because the template is badly written. Before we improve it, let’s summarize what we know:
Summary
We use
./xbps-src pkg j4-dmenu-desktop
to build a template.
We use
sudo xi -f j4-dmenu-desktop
to install a template.
A template must include these things:
pkgname=name
version=1234
revision=1
short_desc="Description"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://example.com/"
Templates are built in stages.
The source of the package must be put to $wrksrc
, which resides in
masterdir-x86_64/builddir/<pkgname>-<version>
.
Package files must be installed into $DESTDIR
, which resides in
masterdir-x86_64/destdir/<pkgname>-<version>
.
Stages are usually executed in $wrksrc
.
Improving the template
What is cross-compilation?
Concepts described here hold for compiled packages and libraries. They do not apply to packages which aren’t compiled (like Python packages7). This is further described in packaging rofimoji.
If you intend to package non-compiled packages, you should still read the following sections, but you can pay less attention to them.
Cross-compilation is the compilation of code using a cross compiler.
What is a cross compiler? Let’s ask Wikipedia:
A cross compiler is a compiler capable of creating executable code for a platform other than the one on which the compiler is running. For example, a compiler that runs on a PC but generates code that runs on an Android smartphone is a cross compiler.
A cross compiler is useful to compile code for multiple platforms from one development host.
Normally, when you compile on x86_64
architecture, you run the program on
x86_64
. But Void Linux supports more than x86_64
, it supports a lot of
architectures and even two libc implementations, glibc and musl. All of these
are incompatible with each other by default.
How does Void Linux support all of these? Does it have a dedicated build server for each architecture?
No! It has just x86_64
and it cross-compiles to all other architectures.
The one rule of cross-compiling is that you cannot execute what you build.
When your host (your computer) is x86_64
and you are cross-compiling to let’s
say armv6l
, the compiled result can be executed only on armv6l
, not on
x86_64
(unless you use QEMU, but that method
cannot be used everywhere).
Sometimes the package’s build system tries to run what it compiles. Software using such build systems is not cross-compilation friendly. Such build systems have to be patched to allow cross-compilation. This requires knowledge of the package’s build system and a basic understanding of the program’s structure. This isn’t trivial to solve, it depends on the size of the program and the complexity of its build system, but it’s doable.
How does cross-compilation work?
This describes the cross-compilation of C/C++ code. Other programming languages
can do it differently, but as you will soon learn, it doesn’t actually matter
in xbps-src
.
Cross-compilation works pretty much the same as normal compilation, but a cross
compiler must be used instead of a normal one. Void Linux provides many cross
compilers in the cross-*
packages (for example cross-armv7l-linux-gnueabihf
cross compiles to armv7l
with glibc
libc).
You might wonder how can you modify the j4-dmenu-desktop
template to support
these cross-
packages. But you do not have to, xbps-src
does it for you if
you let it. It is handled by build styles.
Build styles
Most programs use a build system like CMake
, Meson
, Make
, Cargo
etc.
All of these provide a way to build and install things.
All packages using CMake
follow a pretty much identical build process. A
builddir is created, cmake -DCMAKE_INSTALL_PREFIX=/usr ..
is run, then make
builds the project and make install
installs it.
Because it is repetitive, xbps-src
provides a way to do it for you. This is
handled using a build style.
Build styles are set with the build_style
variable. All available build styles
are at
common/build-style/
.
They are described in the
Manual.
Build scripts override several build phases by providing appropriate
do_<phase>()
functions.
But the build process for every CMake
package isn’t identical. There are some
small differences. To tweak the build style, you can set variables provided by
the build style. Each build style can provide some configuration variables. They
are described in the appropriate section of the
Manual.
If that isn’t enough, you can always override pre_<phase>()
and
post_<phase>()
functions. If you choose to define your own do_<phase>()
functions that would conflict with the ones provided by the build style,
functions defined in your template take precedence.
These build styles do much more than cmake ..
. They handle the aforementioned
/usr
prefix and they even handle cross-compilation. Then there are some
Void-specific things that you thanks to build styles don’t even have to know nor
care about, because the build styles handle it for you.
Build styles also add necessary dependencies for them to
function8. This means that we can remove cmake
from
hostmakedepends
.
How to cross-compile?
Instead of
./xbps-src pkg j4-dmenu-desktop
you run
./xbps-src -a <ARCH> pkg j4-dmenu-desktop
This will download a cross-*
cross compiler if appropriate and it will set
some xbps-src
configuration variables. The build style will then adapt to
them.
If you want to be extra diligent, you should not only test whether your template
compiles with ./xbps-src pkg <package>
, but you should also test whether
it cross compiles by for example running ./xbps-src pkg -a aarch64 <pkg>
.
Testing cross-compiling for a single target is usually enough. If the template
cross-compiles to one target, it will likely cross-compile to all of them (but
it depends).
Testing for all architectures in a pull request is made simple by GitHub PR checks.
Some progress
This is the old template:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
hostmakedepends="git"
makedepends=""
depends=""
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
do_fetch() {
git clone https://github.com/enkore/j4-dmenu-desktop.git "$wrksrc"
}
do_configure() {
mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
}
do_build() {
cd build
make
}
do_install() {
cd build
# DESTDIR was added
make DESTDIR="$DESTDIR" install
}
This is the template with build_style=cmake
:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
hostmakedepends="git cmake"
build_style=cmake
makedepends=""
depends=""
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
do_fetch() {
git clone https://github.com/enkore/j4-dmenu-desktop.git "$wrksrc"
}
A lot of the functions we have created are now gone. Only do_fetch()
remains.
Let’s get rid of that one too.
Downloading source
Using git
to download sources is not acceptable in 99% of situations. As
mentioned in quality requirements, only released,
stable programs are packaged.
As mentioned in the beginning of this page, this tutorial focuses on packaging
j4-dmenu-desktop version r2.18. But that is not what we’ve been packaging
so far. git clone https://github.com/enkore/j4-dmenu-desktop.git
was
invoked to fetch j4-dmenu-desktop, which downloads the latest development
version9. This hinders reproducibility, and it makes the package less
reliable.
Pulling directly from HEAD
is wrong. Releases or tags should be preferred.
Releases can be found on the right-side panel on GitHub:
Scroll down until you see r2.18:
Some projects include prebuilt binaries in their releases (j4-dmenu-desktop
doesn’t, but bat
described in the next chapter of this tutorial
does). You mustn’t use them if you want your package to be included in
void-packages. You should choose
the Source code (tar.gz)
option:
You can then put this into the distfiles
variable.
distfiles
An advantage of using distfiles over downloading the archive manually in
do_fetch()
is that distfiles
are conveniently managed by xbps-src
.
They are automatically downloaded and extracted to $wrksrc
.
xbps-src
keeps distfiles in hostdir/sources
. This means that if you build
the package several times, the distfile(s) will be downloaded once.
Another feature of distfiles is checksum checking using the checksum
template
variable. It doesn’t happen often on GitHub, but self-hosted releases can be
retroactively changed. This shouldn’t happen, a versioned release should always
be the same, but this can happen by accident.
The less likely scenario is that the distfile has been changed maliciously and some unwanted code has been added.
To make sure that neither of these scenarios happens, every distfile must have
an accompanying checksum. Checksums are defined in the checksum
variable in
the same order as distfiles
(multiple distfiles
can be specified).
The type of checksum is sha256sum
. You can use xgensum
from the xtools
package to automatically download the distfile and retrieve its checksum.
Here is the modified template:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
# git is still needed
hostmakedepends="git"
build_style=cmake
makedepends=""
depends=""
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
distfiles="https://github.com/enkore/j4-dmenu-desktop/archive/refs/tags/r2.18.tar.gz"
checksum=77c5605d0c1291bcf1e13b186ea3b32ddf4753de0d0e39127b4a7d2098393e25
You may have noticed that git is still needed. Why? Because j4-dmenu-desktop
’s
build system itself uses git to fetch a dependency. This is not desirable,
xbps-src
should manage all dependencies. To fix this, we first need to learn
more about dependencies.
Dependencies
There are four types of dependencies: hostmakedepends
, makedepends
,
depends
and checkdepends
.
hostmakedepends
are dependencies (usually programs) that must run on the
host when cross-compiling.
makedepends
are dependencies (usually -devel
packages of libraries) that are
required for cross-compiling. They are compiled for target architecture
(cannot be used on the host).
depends
are runtime dependencies. In contrast to hostmakedepends
and
makedepends
, xbps-src
does nothing with depends
, it just adds the
dependencies to package metadata. xbps-install
will then read that and
download the dependencies.
checkdepends
are (host) dependencies of the check
phase. This is described
below.
xbps-src
tries to download hostmakedepends
and makedepends
from the remote
repos. If the dependencies aren’t there, xbps-src
will build them using
templates in srcpkgs
.
Some progress
Both git
and cmake
needed to run on the host machine, so hostmakedepends
was appropriate. j4-dmenu-desktop
has another dependency that has been
forgotten: dmenu
itself. dmenu
is not needed for compilation, so it belongs
to depends
.
Here is the modified template:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
hostmakedepends="git"
build_style=cmake
depends="dmenu"
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
distfiles="https://github.com/enkore/j4-dmenu-desktop/archive/refs/tags/r2.18.tar.gz"
checksum=77c5605d0c1291bcf1e13b186ea3b32ddf4753de0d0e39127b4a7d2098393e25
There is another missing dependency, but it’s much harder to find. It’s Catch2.
j4-dmenu-desktop
downloads Catch2 while in the configure
step when cmake
is initialized. This is convenient for developers, but not so much for package
managers.
All dependencies of a package should be handled via the package manager if possible (this isn’t possible in some languages like Go and Rust whose build systems are much more tied to the programming language).
catch2
is already packaged, so we have no excuse not to use it. Catch2 is a
library (that needs to be in target architecture), so it belongs to
makedepends
.
When you add catch2
to the makedepends
and carefully observe build output,
you might notice that Catch2 is still being downloaded and the catch2
dependency is ignored. This happens because j4-dmenu-desktop
’s CMake isn’t
configured ideally, its build system isn’t perfect. But it provides a way to
use the system Catch2. If you read their build system definition file
CMakeLists.txt
, you will notice the WITH_GIT_CATCH
and CATCH_INCLUDE_DIR
options.
WITH_GIT_CATCH
is a YES/NO option. We don’t want Catch2 through git, so we’ll
set it to NO. This means that we will be able to get rid of git
in
hostmakedepends
.
But CATCH_INCLUDE_DIR
requires a Catch2 header file.
To locate it, let’s look at catch2
’s files:
> xbps-query -Rf catch2
/usr/include/catch2/benchmark/catch_benchmark.hpp
/usr/include/catch2/benchmark/catch_benchmark_all.hpp
/usr/include/catch2/benchmark/catch_chronometer.hpp
/usr/include/catch2/benchmark/catch_clock.hpp
/usr/include/catch2/benchmark/catch_constructor.hpp
/usr/include/catch2/benchmark/catch_environment.hpp
...
You can see that they are in /usr/include/catch2
, so CATCH_INCLUDE_DIR
should be set to /usr/include/catch2
. Or should it? Remember that catch2
is a makedepends
dependency for the target architecture. It won’t
be installed to /usr/include/
in masterdir-x86_64
because that’s where
host things go. (catch2
will be installed to /usr/include/catch2
if
j4-dmenu-desktop
isn’t being cross-compiled because host = target when that is
the case, but it won’t work otherwise.)
Where do target dependencies go? xbps-src
provides a lot of useful
global variables,
and one of them is $XBPS_CROSS_BASE
. The best thing about it is that it
works when compiling normally too. It is set to an appropriate directory when
cross-compiling (for example for aarch64
it’s /usr/aarch64-linux-gnu
) and
it’s set to nothing when compiling normally. This means that
$XBPS_CROSS_BASE/usr/include/catch2
will be always valid. And this is our value of
CATCH_INCLUDE_DIR
.
So a corrected cmake
command line would be constructed like this:
cmake -DWITH_GIT_CATCH=NO -DCATCH_INCLUDE_DIR=$XBPS_CROSS_BASE/usr/include/catch2 ..
But there’s a problem: We are no longer executing cmake
, the build style
does. A solution is to use one of the configuration variables the cmake
build
style provides, namely configure_args
.
Here is the modified template:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
# git is removed from hostmakedepends
makedepends="catch2"
configure_args="-DWITH_GIT_CATCH=NO
-DCATCH_INCLUDE_DIR=$XBPS_CROSS_BASE/usr/include/catch2"
build_style=cmake
depends="dmenu"
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
distfiles="https://github.com/enkore/j4-dmenu-desktop/archive/refs/tags/r2.18.tar.gz"
checksum=77c5605d0c1291bcf1e13b186ea3b32ddf4753de0d0e39127b4a7d2098393e25
version
and distfiles
A package update procedure (which by the way has its own tutorial here) usually consists of two things:
- changing
version
- updating
checksum
distfiles
should depend on version
so that when version
is updated,
distfiles
is updated too. With this method, it is much harder to have a
package whose version
doesn’t match the real version of the distfile.
Here is the modified template:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
makedepends="catch2"
configure_args="-DWITH_GIT_CATCH=NO
-DCATCH_INCLUDE_DIR=$XBPS_CROSS_BASE/usr/include/catch2"
build_style=cmake
depends="dmenu"
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
changelog="https://raw.githubusercontent.com/enkore/j4-dmenu-desktop/develop/CHANGELOG"
distfiles="https://github.com/enkore/j4-dmenu-desktop/archive/refs/tags/r${version}.tar.gz"
checksum=77c5605d0c1291bcf1e13b186ea3b32ddf4753de0d0e39127b4a7d2098393e25
Checks
When describing build steps, you might have noticed a check
step. Its purpose is to run a package check if the package has any checks or
unit tests.
The check
phase should ideally verify whether the package has been installed
in the right environment and that no dependencies are missing.
Running checks might require additional dependencies. They can be specified in
checkdepends
. They won’t be installed to destdir if checks aren’t enabled.
Checks can be enabled with the -Q
or -K
flags. -Q
enables “basic” checks,
and -K
enables all checks. Most packages have only basic checks so both of
these flags do the same thing.
Checks can be run like this:
./xbps-src -Q pkg j4-dmenu-tests
j4-dmenu-desktop
has unit tests and the cmake
build style picks them up (you
don’t have to define do_check()
which is another advantage of using build
styles), but they depend on archaic Catch1. This version of Catch is long
deprecated. Checks should therefore be turned off. This means that all our work
of figuring out WITH_GIT_CATCH
and CATCH_INCLUDE_DIR
came to nothing, but
now we know how the tests would have been fixed if they had worked.
j4-dmenu-desktop
’s build system now has to be instructed not to build checks.
They luckily provide another CMake option for that, WITH_TESTS
. This will get
rid of the catch2
dependency altogether.
A comment near configure_args
explaining this situation should be added to
show good manners to other packagers reading the template.
If j4-dmenu-desktop
’s build system wouldn’t provide a way to turn off checks,
we could tell xbps-src
to skip them. Checks can be turned off with the
make_check
variable. See
Manual.
“This option has to be accompanied by a comment explaining why the tests fail.”
This is the modified template:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
# Tests depend on archaic Catch1
configure_args="-DWITH_TESTS=NO"
build_style=cmake
depends=dmenu
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
distfiles="https://github.com/enkore/j4-dmenu-desktop/archive/refs/tags/r${version}.tar.gz"
checksum=77c5605d0c1291bcf1e13b186ea3b32ddf4753de0d0e39127b4a7d2098393e25
When their build system is broken…
Many developers don’t bother testing their programs on different architectures or libc’s. Their program might not build or function on these. This can happen for two main reasons:
- Their program is functionally incompatible with the architecture or cross-compilation isn’t possible for some reason.
- Their program could very well be cross-compiled, but their code or build system isn’t following best practices and it just doesn’t build there. This can happen because of poor quality build system definitions or by use of non-standard language or library constructs. Both of these should ideally not happen in perfect code, but little in life is perfect.
When 1. happens, you can do nothing about it. You can use the nocross
or
archs
variables accompanied by a comment that gives a very good reason why it
is impossible to support cross-building or other architectures.
Void Linux tries really hard to support all its official architectures and you should too.
Using nocross
or archs
because you didn’t care to test the package in these
conditions will likely lead to package rejection in official repos. Testing for
all architectures is made simple by GitHub PR
checks.
When 2. happens, you should try to fix it or at least notify upstream developers (by creating an issue in the repository for example). They may or may not be willing to fix it.
There are two mechanisms commonly used to fix things manually: patches and
vsed
.
vsed
vsed
is a wrapper around sed
that makes sure that specified files
were really modified. It is well suited for simple changes or as a search and
replace.
Patches
Patches can be put into a patches/
subdirectory in the directory where the
template file is located. If the template file is in
srcpkgs/j4-dmenu-desktop/template
, it will go to
srcpkgs/j4-dmenu-desktop/patches/
. All files in this directory ending in
.diff
or .patch
will be applied to $wrksrc
using patch -Np1
. This
mechanism can be modified (although that isn’t needed often), but that is
outside the scope of this tutorial. See the
Manual
for more info.
When you try to build the template provided above, you will notice that it fails
on the do_build()
step. Here is the error message:
=> xbps-src: updating repositories for host (x86_64)...
[*] Updating repository `https://repo-default.voidlinux.org/current/bootstrap/x86_64-repodata' ...
[*] Updating repository `https://repo-default.voidlinux.org/current/x86_64-repodata' ...
[*] Updating repository `https://repo-default.voidlinux.org/current/nonfree/x86_64-repodata' ...
[*] Updating repository `https://repo-default.voidlinux.org/current/debug/x86_64-repodata' ...
[*] Updating repository `https://repo-default.voidlinux.org/current/multilib/bootstrap/x86_64-repodata' ...
[*] Updating repository `https://repo-default.voidlinux.org/current/multilib/x86_64-repodata' ...
x86_64-repodata: [681KB 0%] 58MB/s ETA: 00m00s
x86_64-repodata: [681KB 54%] 399KB/s ETA: 00m00s
x86_64-repodata: 681KB [avg rate: 739KB/s]
[*] Updating repository `https://repo-default.voidlinux.org/current/multilib/nonfree/x86_64-repodata' ...
=> xbps-src: updating software in / masterdir...
=> xbps-src: cleaning up / masterdir...
=> j4-dmenu-desktop-2.18_1: removing autodeps, please wait...
=> j4-dmenu-desktop-2.18_1: building with [cmake] for x86_64...
[host] cmake-bootstrap-3.27.6_2: found (https://repo-default.voidlinux.org/current/bootstrap)
[host] ninja-1.11.1_4: found (https://repo-default.voidlinux.org/current)
[runtime] dmenu-5.2_1: found (https://repo-default.voidlinux.org/current)
=> j4-dmenu-desktop-2.18_1: installing host dependencies: cmake-bootstrap-3.27.6_2 ninja-1.11.1_4 ...
=> j4-dmenu-desktop-2.18_1: running do-fetch hook: 00-distfiles ...
=> j4-dmenu-desktop-2.18_1: running do-extract hook: 00-distfiles ...
=> j4-dmenu-desktop-2.18_1: extracting distfile(s), please wait...
=> j4-dmenu-desktop-2.18_1: running do-patch hook: 00-patches ...
=> j4-dmenu-desktop-2.18_1: running pre-configure hook: 00-gnu-configure-asneeded ...
=> j4-dmenu-desktop-2.18_1: running pre-configure hook: 01-override-config ...
=> j4-dmenu-desktop-2.18_1: running pre-configure hook: 02-script-wrapper ...
=> j4-dmenu-desktop-2.18_1: running do_configure ...
CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required):
Compatibility with CMake < 3.5 will be removed from a future version of
CMake.
Update the VERSION argument <min> value or use a ...<max> suffix to tell
CMake that the project does not need compatibility with older versions.
-- The C compiler identification is GNU 13.2.0
-- The CXX compiler identification is GNU 13.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/lib/ccache/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/lib/ccache/bin/g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.9s)
-- Generating done (0.0s)
CMake Warning:
Manually-specified variables were not used by the project:
CMAKE_INSTALL_LIBDIR
CMAKE_INSTALL_SBINDIR
CMAKE_INSTALL_SYSCONFDIR
-- Build files have been written to: /builddir/j4-dmenu-desktop-2.18/build
=> j4-dmenu-desktop-2.18_1: running pre-build hook: 02-script-wrapper ...
=> j4-dmenu-desktop-2.18_1: running do_build ...
[1/2] Building CXX object CMakeFiles/j4-dmenu-desktop.dir/src/main.cc.o
FAILED: CMakeFiles/j4-dmenu-desktop.dir/src/main.cc.o
/usr/lib/ccache/bin/g++ -DNDEBUG -fstack-clash-protection -D_FORTIFY_SOURCE=2 -mtune=generic -O2 -ffile-prefix-map=/builddir/j4-dmenu-desktop-2.18/build=. -std=c++11 -Wall -Wextra -pedantic -O2 -D_WITH_GETLINE -MD -MT CMakeFiles/j4-dmenu-desktop.dir/src/main.cc.o -MF CMakeFiles/j4-dmenu-desktop.dir/src/main.cc.o.d -o CMakeFiles/j4-dmenu-desktop.dir/src/main.cc.o -c /builddir/j4-dmenu-desktop-2.18/src/main.cc
In file included from ../src/Main.hh:16,
from ../src/main.cc:2:
../src/Application.hh:31:25: error: 'uint32_t' does not name a type
31 | static inline constexpr uint32_t make_istring(const char *s)
| ^~~~~~~~
../src/Application.hh:27:1: note: 'uint32_t' is defined in header '<cstdint>'; did you forget to '#include <cstdint>'?
26 | #include "LocaleSuffixes.hh"
+++ |+#include <cstdint>
27 |
../src/Application.hh:36:11: error: 'uint32_t' does not name a type
36 | constexpr uint32_t operator "" _istr(const char *s, size_t)
| ^~~~~~~~
../src/Application.hh:36:11: note: 'uint32_t' is defined in header '<cstdint>'; did you forget to '#include <cstdint>'?
../src/Application.hh: In member function 'bool Application::read(const char*, char**, size_t*)':
../src/Application.hh:142:24: error: 'make_istring' was not declared in this scope
142 | switch(make_istring(key)) {
| ^~~~~~~~~~~~
../src/Application.hh:143:22: error: unable to find string literal operator 'operator""_istr' with 'const char [5]', 'long unsigned int' arguments
143 | case "Name"_istr:
| ^~~~~~~~~~~
../src/Application.hh:146:22: error: unable to find string literal operator 'operator""_istr' with 'const char [12]', 'long unsigned int' arguments
146 | case "GenericName"_istr:
| ^~~~~~~~~~~~~~~~~~
../src/Application.hh:149:22: error: unable to find string literal operator 'operator""_istr' with 'const char [5]', 'long unsigned int' arguments
149 | case "Exec"_istr:
| ^~~~~~~~~~~
../src/Application.hh:152:22: error: unable to find string literal operator 'operator""_istr' with 'const char [5]', 'long unsigned int' arguments
152 | case "Path"_istr:
| ^~~~~~~~~~~
../src/Application.hh:155:22: error: unable to find string literal operator 'operator""_istr' with 'const char [11]', 'long unsigned int' arguments
155 | case "OnlyShowIn"_istr:
| ^~~~~~~~~~~~~~~~~
../src/Application.hh:167:22: error: unable to find string literal operator 'operator""_istr' with 'const char [10]', 'long unsigned int' arguments
167 | case "NotShowIn"_istr:
| ^~~~~~~~~~~~~~~~
../src/Application.hh:179:22: error: unable to find string literal operator 'operator""_istr' with 'const char [7]', 'long unsigned int' arguments
179 | case "Hidden"_istr:
| ^~~~~~~~~~~~~
../src/Application.hh:180:22: error: unable to find string literal operator 'operator""_istr' with 'const char [10]', 'long unsigned int' arguments
180 | case "NoDisplay"_istr:
| ^~~~~~~~~~~~~~~~
../src/Application.hh:188:22: error: unable to find string literal operator 'operator""_istr' with 'const char [9]', 'long unsigned int' arguments
188 | case "Terminal"_istr:
| ^~~~~~~~~~~~~~~
../src/Application.hh:189:61: error: unable to find string literal operator 'operator""_istr' with 'const char [5]', 'long unsigned int' arguments
189 | this->terminal = make_istring(value) == "true"_istr;
| ^~~~~~~~~~~
ninja: build stopped: subcommand failed.
=> ERROR: j4-dmenu-desktop-2.18_1: do_build: '${make_cmd} ${makejobs} ${make_build_args} ${make_build_target}' exited with 1
=> ERROR: in do_build() at common/build-style/cmake.sh:95
Note that this error isn’t even caused by cross-compilation or compilation on Musl, this is just bad code.
If you have experience with C or C++, you can see that upstream forgot to include a header file. The release has probably worked on their computer, but it doesn’t work now because it depends on nonstandard header file pulling which is not a sign of good C++ code. Thankfully the solution is pretty simple.
If you do not have experience with C or C++, you’ll have to ask for help or try to look for a patch elsewhere. Sometimes the fix has been already implemented in upstream’s development version. You can then get this patch and respectfully ask upstream to make a newer release including the fix.
Or you can get “inspired” by other repositories like the official Arch repo, the AUR, Alpine repos or elsewhere. https://repology.org/ is your friend.
I’ll give you the right patch:
--- a/src/Application.hh
+++ b/src/Application.hh
@@ -21,6 +21,7 @@
#include <algorithm>
#include <string.h>
#include <unistd.h>
+#include <cstdint>
#include "Utilities.hh"
#include "LocaleSuffixes.hh"
You should also include a concise and informative header to the patch (if the patch doesn’t already have one):
Fixes failing build on missing header file. Fixed in master.
--- a/src/Application.hh
+++ b/src/Application.hh
@@ -21,6 +21,7 @@
#include <algorithm>
#include <string.h>
#include <unistd.h>
+#include <cstdint>
#include "Utilities.hh"
#include "LocaleSuffixes.hh"
If you obtained the patch from somewhere else (such as being “inspired” by another distro’s repositories, as mentioned above), you should include a link to the original patch in the header.
You can put the patch in srcpkgs/j4-dmenu-desktop/patches/fix_headers.patch
(you’ll have to mkdir
the srcpkgs/j4-dmenu-desktop/patches/
directory).
j4-dmenu-desktop
should be buildable now.
Linting
This should be the last step of every packaging session.
You should run xlint
. This script is provided by the xtools
package. It
checks for common errors and enforces some stylistic choices to make templates
consistent. When you run xlint
on the template above, it prints this:
j4-dmenu-desktop:6: Place configure_args= after build_style=
Moving variables won’t change the functionality of the template, but this advice should be followed nonetheless.
Here is the final template:
# Template file for 'j4-dmenu-desktop'
pkgname=j4-dmenu-desktop
version=2.18
revision=1
build_style=cmake
configure_args="-DWITH_TESTS=NO"
depends=dmenu
short_desc="Fast desktop menu"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://github.com/enkore/j4-dmenu-desktop"
distfiles="https://github.com/enkore/j4-dmenu-desktop/archive/refs/tags/r${version}.tar.gz"
checksum=77c5605d0c1291bcf1e13b186ea3b32ddf4753de0d0e39127b4a7d2098393e25
with the following patch file:
Fixes failing build on missing header file. Fixed in master.
--- a/src/Application.hh
+++ b/src/Application.hh
@@ -21,6 +21,7 @@
#include <algorithm>
#include <string.h>
#include <unistd.h>
+#include <cstdint>
#include "Utilities.hh"
#include "LocaleSuffixes.hh"
It should build successfully with or without checks and it should cross-compile well. If that isn’t the case, consider cleaning the masterdir.
Summary
We use
./xbps-src pkg j4-dmenu-desktop
to build a template.
We use
./xbps-src -a <arch> pkg j4-dmenu-desktop
to build a template for a different architecture using cross-compilation.
We use
sudo xi -f j4-dmenu-desktop
to install a template.
A template must include these things:
pkgname=name
version=1234
revision=1
short_desc="Description"
maintainer="meator <meator.dev@gmail.com>"
license="GPL-3.0-or-later"
homepage="https://example.com/"
Templates are built in stages.
The source of the package must be put to $wrksrc
, which resides in
masterdir-x86_64/builddir/<pkgname>-<version>
.
Package files must be installed into $DESTDIR
, which resides in
masterdir-x86_64/destdir/<pkgname>-<version>
.
Stages are usually executed in $wrksrc
.
Building and installation should be handled by build styles.
They can be modified with variables like configure_args
.
Code should be downloaded using distfiles
and not git. The Source code (tar.gz)
GitHub archive should be chosen.
hostmakedepends
is for host, makedepends
is for target, and
depends
are runtime dependencies.
distfiles
should always reference ${version}
.
You can build with checks using
./xbps-src -Q pkg <PACKAGE>
Patches should be in the patches/
subdirectory and they should end in .diff
or .patch
. They are applied automatically, template file doesn’t have to be
modified.
You should run xlint
on your templates.
What now?
Packaging j4-dmenu-desktop
shows the most useful capabilities of xbps-src
but not all of them. I’ve written a (significantly shorter) walkthrough through
packaging another program, bat
:
I’m sure there’s a way to install a package without its dependencies, but you’ll have to try pretty hard to do that.
You also of course need XBPS and some very basic dependencies mentioned in README’s Requirements, which you probably already have installed.
You can check with uname -m
The base-chroot
package isn’t present in normal repositories.
void-packages masterdirs access a special repository called
bootstrap
.
Or not!
If the masterdir (the small isolated Void linux install that does all the building) gets into an inconsistent state, you can zap it as described in troubleshooting.
Python packages are byte compiled on Void, but that is something different.
Not all build styles do that.
git
can be invoked with --branch r2.18
to package the correct
version, but that also pulls unneeded git history, and it is more
difficult to verify its state (SHA256SUMs are used for tarballs).