Within this article we want to describe needed information on how to create a package for Hyperbola as system and assist users in a way forward to be also active developing and deploying their own software. Please have in mind not to be mistaken with our packaging guidelines, where we describe generic rules for the software included. This article here is in its focus only oriented on the technical details and the corresponding usecases.
Especially also to note that this article is meant as common guidance for users doing their own packaging the first time. When you feel confident and safe enough you can also use this to look for common notes. If you are experienced within packaging this guide is surely not your first entrypoint and is also not meant to be!
Required experiences and knowledge for this guide:
If you do not have any knowledge within those named fields, this guide is not meant for you at this moment.
You may ask for sure: Do I really need all of this and manage packaging? The clear answer here is surely: No, you do not need to. But in fact you will also have to manage all the compiled and installed data on your own. Minimizing that effort is to create as said clear packaged software and data for you to install, remove, up- or downgrade all the time again on your own will with the clear point of knowledge what you are doing and what data is next added to your system running.
Are there disadvantages? Not really, besides you learn much more about the file-system and its structures. It just depends on your will doing so. If you have no interest? Sure, it is your choice doing so. But you can also share the scripts for others to review them, helping you with possible problems running software and in the end you can give also something back when you share your package-script and the files around for others to understand, learn, modify and share them again. Or you get hosting your own repository offering software and ports for others to download and install? As said: That is your decision, but we believe clearly within the model to share information, data and more, giving something back. We believe not within seeing free, libre software only as “gratis offered software” including full-time support at any given point. If you want that, Hyperbola is not the project and system for you and we kindly ask you to search for a different solution.
Packages for Hyperbola are built using the makepkg command and the information needed to do so is stored in a PKGBUILD file.
When the command makepkg is called, it searches for a corresponding file named PKGBUILD in the current directory and follows the instructions therein to acquire the required files and afterwards compile the sources and data to be packaged within a package file. The resulting package is compressed with lzip following therefore a clear naming-scheme (pkgname.pkg.tar.lz). The resulting package contains binaries and further needed data files and also installation instructions; readily installed with our package-manager.
Now what is inside that compressed tarball (file)?
Before starting to develop a package you surely need also the corresponding tools installed to configure and also use your computer for building and compiling software.
Please first make sure that all necessary tools are installed: At best you use the package-group base-devel as it includes essentials needed for compiling from sources.
Surely one of the key tools for building packages is makepkg (provided by pacman) which does the following:
Please remember that Hyperbola is not allowing packages being built from unstable repositories, also not general from any kind of checkout through any VCS (version control system). So we always speak about stable released tarballs. To test your preferred software to be later released as package first download a concrete source-tarball, extract it and follow the author's / maintainer's steps to install the program. Make a note of all commands and / or steps needed to compile and install it: You will be repeating those same commands in the PKGBUILD file for sure later on.
Most software authors stick to the 3-step build cycle:
./configure make make install
Or as alternative:
cmake . make make install
This is a good time to make sure the program is working correctly, for example with just a local installation done in your $HOME-folder.
When you run makepkg, it will look for a PKGBUILD file in the present working directory. If a PKGBUILD file is found it will download the software's source code and compile it according to the instructions specified in the PKGBUILD file. After successful completion, the resulting binaries and data of the package, i.e. package version and dependencies, are packed in a compressed file.
You can checkout your created package with a local installation:
doas pacman -U <package file>
To begin with a new package, you should first create an empty working directory (preferably ~/pkgname), change into that directory, and create an empty file named PKGBUILD. You can also either copy the prototype PKGBUILD (to be found under: /usr/share/pacman/PKGBUILD.proto) to your working directory or copy a PKGBUILD from a similar package. The latter may be useful if you only need to change a few options or just want to upgrade / modify an existing package.
The following are variables that can be filled out in the PKGBUILD file.
It is common practice to define the variables in the PKGBUILD in same order as given here. However this is not mandatory as long as correct syntax is used.
The name of the package. It should consist of alphanumeric characters and dashes ('-') and all letters should be lowercase. For the sake of consistency, pkgname should match the name of the source tarball of the software you are packaging. For instance, if the software is in foobar-2.5.tar.gz, the pkgname value should be foobar. The current working directory the PKGBUILD file is in should also match the pkgname.
The version of the package. The value should be the same as the version released by the author of the package. It can contain letters, numbers and periods but can't contain a hyphen. If the author of the package uses a hyphen in their version numbering scheme, replace it with an underscore. For instance, if the version is 0.99-10, it should be changed to 0.99_10. If the pkgver variable is used later in the PKGBUILD then the underscore can easily be substituted for a dash on usage e.g.:
source=($pkgname-${pkgver//_/-}.tar.gz)
The release number of the package specific to Hyperbola. This value allows users to differentiate between consecutive builds of the same version of a package. When a new package version is first released, the release number starts at 1. As fixes and optimizations are made to the PKGBUILD file, the package will be re-released and the release number will increment by 1. When a new version of the package comes out, the release number resets to 1.
An integer value, specific to Hyperbola, representing what “lifetime” to compare version numbers against. This value allows overrides of the normal version comparison rules for packages that have inconsistent version numbering, require a downgrade, change numbering schemes, etc. By default, packages are assumed to have an epoch value of 0. Do not use this unless you know what you are doing.
The description of the package. The description should be about 80 characters or less and should not include the package name in a self-referencing way.
An array of architectures that the PKGBUILD file is known to build and work on. Currently, it should contain i686 and / or x86_64.
arch=('i686' 'x86_64')
The value any can also be used for architechture-independent packages:
arch=('any')
You can access the target architecture with the variable $CARCH during the further script, and even when defining variables:
depends=(foobar) if test "$CARCH" == x86_64; then depends=("${depends[@]}" glibc) fi
The URL of the official site of the software being packaged.
The license under which the software is distributed. You can use the package licenses for reference. The alternative way is to enumerate the used licenses under the path /usr/share/licenses/common/.
The group the package belongs in. For instance, when you install the lumina-extra package, it installs all packages that belong in the named group lumina-extra.
An array of package names that must be installed before this software can be run. If a software requires a minimum version of a dependency, the >= operator should be used to point this out. For example:
depends=('foobar>=1.8.0')
You do not need to list packages that your software depends on if other packages your software depends on already have those packages listed in their dependency. For instance, gtk2 depends on glib2 and glibc. However glibc does not need to be listed as a dependency for gtk2 because it is a dependency for glib2. So look careful on the needed dependency-tree within the construction of your PKGBUILD.
An array of package names that must be installed to build the software but unnecessary for using the software after installation. You can specify the minimum version dependency of the packages in the same format as the depends array before.
An array of packages this package depends on to run its test suite but are not needed at runtime. Packages in this list follow the same format as depends. These dependencies are only considered when the check() function is present and is to be run by makepkg.
An array of package names that are not needed for the software to function but provides additional features. A short description of what each package provides should also be noted. An optdepends may look like this:
optdepends=('cups: printing support' 'sane: scanners support' 'libgphoto2: digital cameras support' 'alsa-lib: sound support' 'giflib: GIF images support' 'libjpeg-turbo: JPEG images support' 'libpng: PNG images support')
An array of package names that this package provides the features of (or a virtual package such as cron or sh). If you use this variable, you should add the version (pkgver and perhaps the pkgrel) that this package will provide if dependencies may be affected by it. For instance, if you are providing a modified qt package named qt-foobar version 3.3.8 which provides qt then the provides array should look like:
provides=('qt=3.3.8')
An array of package names that may cause problems with this package if installed. You can also specify the version properties of the conflicting packages in the same format as the variable depends.
An array of obsolete package names that are replaced by this package, for example:
replaces=('xscreensaver')
After syncing, it will immediately replace an installed package upon encountering another package with the matching replaces in the repositories. If you are providing an alternate version of an already existing package, use the conflicts variable which is only evaluated when actually installing the conflicting package.
An array of files to be backed up as file.pacsave when the package is removed. This is commonly used for packages placing configuration files in /etc. The file paths in this array should be relative paths:
etc/pacman.conf
Do not use absolute paths likewise
/etc/pacman.conf
Now for a concrete example:
backup=('etc/lighttpd/lighttpd.conf' 'etc/logrotate.d/lighttpd')
This array allows you to override some of the default behavior of makepkg. To set an option, include the option name in the array. To reverse the default behavior, place an ! at the front of the option. The following options may be placed in the array:
The name of the .install script to be included in the package. pacman has the ability to store and execute a package-specific script when it installs, removes or upgrades a package. The script contains the following functions which run at different times:
This is an array of files which are needed to build the package. It must contain the location of the software source, which in most cases is a full HTTP, HTTPS or FTP URL. The previously set variables pkgname and pkgver can be used effectively here:
source=("http://example.com/$pkgname-$pkgver.tar.gz")
If you need to supply files which are not downloadable on the fly, e.g. self-made patches or other files, you simply put those into the same directory where your PKGBUILD file is in and add the filename to this array. Any paths you add here are resolved relative to the directory where the PKGBUILD is located. Before the actual build process is started, all of the files referenced in this array will be downloaded or checked for existence, and makepkg will not proceed if any are missing.
An array of files listed under the source array which should not be extracted from their archive format by makepkg.
An array of SHA-2 checksums with digest size 512 bits. Hyperbola is demanding per default those as all files corresponding to the source array needs to be listed also within here in the exact same order. You can generate this array quickly and easily using the following command in the directory that contains the PKGBUILD file:
makepkg -g
The command makepkg defines three variables that you should use as part of the build and install process:
startdir: This contains the absolute path to the directory where the PKGBUILD file is located. This variable used to be used in combination with /src or /pkg postfixes, but the use of srcdir and pkgdir variables is the modern method. $startdir/src is not guaranteed to be the same as $srcdir, and likewise for $pkgdir. Use of this variable is deprecated and strongly discouraged.
srcdir: This points to the directory where makepkg extracts or copies all source files.
pkgdir: This points to the directory where makepkg bundles the installed package, which becomes the root-directory of your built package.
Common to note is the point that it is always possible to create own variables within your PKGBUILD file, either local or global used at the whole time building and generating. Using those mechanisms is elementary needed for having a convinient execution of your package building successfully.
Hyperbola is using Debian-patchsets so you can find for example the variables _debver and _debrel without our PKGBUILD files. As presented example the package libmtp is used here (PKGBUILD):
pkgname=libmtp pkgver=1.1.17 _debver=$pkgver _debrel=3 pkgrel=2 pkgdesc="Library implementation of the Media Transfer Protocol" arch=('i686' 'x86_64') url='http://libmtp.sourceforge.net' license=('LGPL-2.1') depends=('libusb') makedepends=('quilt') source=("https://downloads.sourceforge.net/$pkgname/${pkgname}-${pkgver}.tar.gz" "https://deb.debian.org/debian/pool/main/libm/libmtp/libmtp_${_debver}-${_debrel}.debian.tar.xz") sha512sums=('f2648e259529bd3dfe74a7049a79c4b0042bcaf63cc1fec8b232b66312d62e9620280e4f725312c9ef8207f1f1ceac19f460a0a8772a3cc6c7f0b00ead01add2' 'e0f94795cc48b7f7e91147ac39baf323398a18a07a6a7aaff1ca21bf321a8b58e7dede70634ce10ef20ee5d410d0b5ac31bf44445d9aaa554dd461c040009e46')
You can see here the effective usage of the predefined and our own defined variables to create the used source array for further downloading and processing later on.
As already mentioned in the introduction of this article a PKGBUILD as shellscript consists of predefined variables and functions. Now we are describing the needed functions being either not mandatory but useful or absolutely needed for processing the packaging.
The prepare() function is not mandatory and is used for preparing the uncompressed sources and files for the further processing. Common within this first function called is to add further patches, add some further needed files missing and generate the configuration.
Example finalized (PKGBUILD for libmtp):
prepare() { cd $pkgname-$pkgver if [[ ${pkgver%.*} = ${_debver%.*} ]]; then # Debian patches export QUILT_PATCHES=debian/patches export QUILT_REFRESH_ARGS='-p ab --no-timestamps --no-index' export QUILT_DIFF_ARGS='--no-timestamps' mv "$srcdir"/debian . # Doesn't apply rm -v debian/patches/1002-udev_rules.patch || true quilt push -av fi }
We need to implement the build() function in the PKGBUILD file. This function uses common shell commands to automatically compile software and create a directory named /pkg to install the software to. This allows makepkg to package files without having to sift through your filesystem.
The first step in the build() function is to change into the directory created by uncompressing the source tarballs named before in the source array. In most common cases the first command will look like this:
cd "$srcdir/$pkgname-$pkgver"
Here to see the clear usage of defined and common used variables. Now you need to list the same commands you used when you manually compiled the software. The build() function in essence automates everything you did by hand and compiles the software in the fakeroot build environment. If the software you are packaging uses a configure script, it is good practice to use the options needed. Example:
./configure --prefix=/usr
A lot of software installs files relative to the /usr/local directory, which should only be done if you are manually building from source. All of our packages should use the /usr directory when directly used within the system. When you create a package for your local usage you can leave the prefix-definition out as this is working with the Filesystem Hierarchy Standard.
Example finalized (PKGBUILD for libmtp):
build() { cd $pkgname-$pkgver ./configure \ --prefix=/usr \ --with-udev=/lib/udev make }
The check() function is not mandatory and is used for testing routines. Example:
make check
Users can disable it also in PKGBUILD via options variable.
The final step is within the function package() to put the compiled files in a directory where makepkg can retrieve them to create a package. This by default is the /pkg directory - a simple fakeroot environment. The /pkg directory replicates the hierarchy of the root file system of the software's installation paths. If you have to manually place files under the root of your filesystem, you should install them in the /pkg directory under the same directory structure. For example, if you want to install a file to /usr/bin, it should instead be placed under $pkgdir/usr/bin. Very few install procedures require the user to copy dozens of files manually. Instead, for most software, calling make install will do so. The final line should look like the following in order to correctly install the software in the /pkg directory:
make DESTDIR="$pkgdir" install
make prefix="$pkgdir/usr/" install
If that does not work, you will have to look further into the install commands that are executed by “make <…> install”.
Example finalized (PKGBUILD for libmtp):
package() { cd $pkgname-$pkgver make DESTDIR="$pkgdir" install install -Dm644 COPYING -t "${pkgdir}/usr/share/licenses/$pkgname" }
Before we close now this paragraph the so-called split packages should be mentioned as there are some special notations needed. Following up another example based on the package spacefm (PKGBUILD):
You can also see that it is possible to enhance variables or overwrite them later on direct in the functions, so the resulting packages follow an own individual definition for their metadata:
package_spacefm() { pkgdesc+=" (GTK+ 3 version)" depends=('gtk' 'startup-notification' 'ffmpegthumbnailer') optdepends=('util-linux: disk eject support' 'lsof: device processes' 'pmount: mount as non-root user') cd gtk3 make DESTDIR="${pkgdir}" install rm -f "$pkgdir"/usr/bin/spacefm-installer install -Dm644 COPYING -t "${pkgdir}/usr/share/licenses/$pkgname" } package_spacefm-gtk2() { pkgdesc+=" (GTK+ 2 version)" depends=('gtk2' 'desktop-file-utils' 'startup-notification' 'ffmpegthumbnailer') optdepends=('lsof: device processes' 'pmount: mount as non-root user') cd gtk2 make DESTDIR="${pkgdir}" install install -Dm644 COPYING -t "${pkgdir}/usr/share/licenses/$pkgname" }
Those split definitions are only possible and needed when the software itself supports different definitions at time building and you have the intention to offer also those as dedicated package.
Until now we have described makepkg being executed local for building your packages. Sure this would be enough for yourself on your current installed system, but when you want to handover the script and files to others you should recognize this as not sufficient. Remember here that your system is absolutely your own and therefore unique with its configuration and installed data. So if you handover your scripts for packaging you need to make absolutely sure that this is going to work flawless for everyone else.
You will need to test your packaging within a clean chroot environment. For this Hyperbola is offering the package libretools to grant needed tools and commands doing so. With libretools you can:
So the generic goal here is to recreate a clean installation with the handover for full control towards you as acting person for packages at any given time. Especially because of a standard definition for the hierarchy of files and folders we can make sure that a chroot-operation is possible in that way. Redefining this is a violation of the standards and not allowed in the way Hyperbola wants to grant usage.
Now we describe the needed steps to create a working local chroot-environment. At first you should make yourself sure for what kind of environment you want to build and therefore also with what source-base on Hyperbola. We are offering stable and testing for that purpose. Your libretools installed will always orient on your local defined mirrorlist, to be found under /etc/pacman.d/mirrorlist. If you want to build with the testing branch you need to activate those mirrors and deactivate stable. If you want to build with stable branch you need to activate those mirrors and keep testing deactivated.
Now for the installation after you have made sure about your target environment:
doas pacman -S libretools
Creating the initial chroot-enviroment after libretools was installed:
doas librechroot clean-repo
Creating your local chroot-copy within you will later create the packages:
(32bit)
doas librechroot -C /etc/pacman.conf -M /usr/share/pacman/defaults/makepkg.conf.i686 -n i686 make
(64bit)
doas librechroot -C /etc/pacman.conf -M /usr/share/pacman/defaults/makepkg.conf.x86_64 -n x86_64 make
Architecture = auto
To this value:
Architecture = i686
You can change this back after the creation of your chroot-environment.
We have now possible environments named x86_64 and i686. At this point it is needed to mention that you can clearly choose also different names for your environments when creating them. Those are only first recommendations resulting from the experiences made.
You can update and synchronize your chroot-enviroments with the following commands:
doas librechroot -n i686 update
doas librechroot -n i686 sync
We recommend doing this regular especially when you are using testing as your base.
Now having one or more environments you can again start with creating your package but from now on with the command following that syntax, as example within our created chroot-environment named i686:
doas libremakepkg -n i686
You can create your complete own repository for local or remote usage. The difference in between is for sure that local usage means only yourself as user for packages while remote usage includes everyone else. So let's assume that you have created now two packages:
ls armagetronad jumpnbump
In every folder listed is a compiled package ready for its further usage. For armagetronad this may be:
armagetronad-0.2.9.1.0-1-i686.pkg.tar.lz
Let's copy all package-files to a newly created folder where our database will reside:
cp ./*.pkg.tar.lz /var/local/mypkgs
We enter now the folder for our new repository:
cd /var/local/mypkgs
And afterwards create our package-database:
repo-add mypkgs.db.tar.lz *.pkg.tar.lz
Example for resulting output:
# repo-add mypkgs.db.tar.lz *.pkg.tar.lz ==> adding package 'armagetronad-0.2.9.1.0-1-i686.pkg.tar.lz' -> Computing checksums... -> Creating 'desc' db entry... -> Creating 'files' db entry... ==> adding package 'jumpnbump-1.61-2-i686.pkg.tar.lz' -> Computing checksums... -> Creating 'desc' db entry... -> Creating 'files' db entry... ==> Creating updated database file 'mypkgs.db.tar.lz'
Now you can add your own repository to your pacman.conf under /etc/pacman.conf:
[mypkgs] Server = file:///var/local/mypkgs
Or you can share this via HTTPS / HTTP:
[mypkgs] Server = https://<your-ip-address>/mypkgs