1. Introduction

Frugalware consists of thousands of packages. Each file in the distribution belongs to a package and you can easily query to which package a file belongs. For example, if you want to know which package contains /etc/frugalware-release, you should use:

$ pacman-g2 -Qo /etc/frugalware-release
/etc/frugalware-release is owned by frugalware 0.6rc1-1

If you browse the FST (Frugalware Source Tree), you can see, that in the source directory there are category and category-extra dirs. The dirs without -extra tag contains the basic packages of the given category and the dependencies of the basic packages. So a package in these directories can not depend on a package in extra directories. The same is true for console/graphical applications: if your application/library is graphical, then use xapps/xlib, if not then use apps/lib. For each task there is a default package. For example postfix is our default MTA, so exim, sendmail, etc must be in some extra dir.

The repo has a source and a binary directory. The frugalware repo’s directories are source/ and frugalware-$arch/. The binary packages are in the binary directory of the repo. The sources of packages are a little bit more complex. Each package has a category, and each category and package has its own directory in the source dir.

Let’s see an example. You are searching for the cabextract package. The binary package is named frugalware-<arch>/cabextract-<version>-<release>-<arch>.fpm and its source is placed in the source/apps/cabextract dir.

In the package’s own dir, we store everything required to compile the package. You may say we should store only the patches and so, but in our opinion, it’s very annoying when you want to recompile a package and the original server is slow or even unreachable, due to some other reasons. Also it may be illegal that we would provide only binary packages without storing the source (since then it may be possible that we are not able to send the source to you even if you ask us by mail).

Besides, there is a FrugalBuild file in each package’s source directory. This is a simple bash shell script, that will be included by makepkg. So in the FrugalBuild script you can use everything that can be used in a shell script.

Note
During the package database generation we source all the FrugalBuilds, so it must be a very short time to do so for each FrugalBuild. Because of this, you should not use something like:
sha1sums=(`lynx -dump http://foo.com/bar.sha1`)

but you should use:

# http://foo.com/bar.sha1
sha1sums=('094e3afb2fe8dfe82f63731cdcd3b999f4856cff')

This way gensync will be fast even if reaching foo.com takes a lot of time. Also using the -u option an offline build is possible.

Briefly, packaging means collecting the sources, adding additional files (for example init scripts or config files) and writing the FrugalBuild script.

2. Recompiling packages

Before creating a new package, first we will recompile an existing package in this howto. It’s very simple. In our example we will recompile the mplayer package.

First, you have to download the current FST.

  • Getting the FST as root

    This is the most simple, you only have to issue:

    # repoman upd
  • Getting the FST as a simple user

    If you want to do it as a regular user, create the ~/.repoman.conf file and edit it, change the fst_root dir in it (by default, it would download the files to /var/fst, and it is not writable as a user, of course).

    The ~/.repoman.conf file should look like:

    fst_root=~/git

    Thought fst_root can point to any directory writeable by the user.

    And finally to get the FST, issue:

    $ repoman upd

Before building the chroot environment, you should make sure about that the fst user exists on your system. Check your /etc/passwd file. If not, then please check your /etc/passwd.pacnew file, that contains the relevant entry, just copy that line to /etc/passwd.

Now that you have the fst user, continue with

$ cd $fst_root/source/xapps/mplayer
$ sudo makepkg [<options>]
Note
If you are using stable, you probably want to use the -t stable option!

First we enter the directory of mplayer then (like make and Makefile) we run makepkg that will build the package according to the parameters described in FrugalBuild. We once had to use the -R option to build the package in a chroot-ed environment. Since 0.5, building in chroot is the default method, you have to use -H if you want to build on the host system. Chroot requires root privileges. To allow a group (for example the devels group) to use sudo makepkg, start visudo as root, and add the following line:

%devels ALL=NOPASSWD:/usr/bin/makepkg

The chroot will be placed by default in /var/chroot. Only one package can be built in a chroot at a time, so maybe you’ll want to specify a separate chroot for each user. In order to do this, set the $CHROOTDIR variable in your /etc/makepkg.conf from:

export CHROOTDIR="/var/chroot"

to

export CHROOTDIR="/var/chroot.`echo $HOME|sed \'s|.\*/\(.*\)$|\1|'"`

This way the one parallel build / one system limit is increased to one parallel build / one user.

(See man makepkg for more info about the benefits of building in a chroot).

3. Use variables

You can alter the result of the build process using environment variables without touching the FrugalBuild itself. The git package is a good example. Using

$ sudo makepkg [<options>] USE_DEVEL=y

for that package results in a build of git’s development version. Here is what you need if you want so for your package:

# set the variable to false by default
USE_DEVEL=${USE_DEVEL:-"n"}

(...)

# these commands will be evaluated only in case USE_DEVEL is set to true
if Fuse $USE_DEVEL; then
        _F_scm_type="git"
        _F_scm_url="git://git.kernel.org/pub/scm/git/git.git"
        Finclude scm
fi

In the next section we will see an example for a simple FrugalBuild script.

4. A simple example

Let’s see a simple example, the FrugalBuild script of the cabextract package.

# Compiling Time: 0.06 SBU
# Maintainer: Miklos Vajna <vmiklos@frugalware.org>

pkgname=cabextract
pkgver=1.2
pkgrel=1
pkgdesc="a program to extract Microsoft Cabinet files"
url="http://www.kyz.uklinux.net/cabextract.php"
depends=('glibc')
groups=('apps')
archs=('i686' 'x86_64')
up2date="lynx -dump http://www.kyz.uklinux.net/cabextract.php |grep 'cabextract \
        source code'|tr -s ' '|cut -d ' ' -f 6"
source=(http://www.kyz.uklinux.net/downloads/$pkgname-$pkgver.tar.gz)
sha1sums=('871b3db4bc2629eb5726659c147aecea1af6a6d0')

# optimization OK

And here comes the description for each line:

# Compiling Time: 0.06 SBU

You should write here how much time it took to build the package. Of course, it depends on your hardware, so we use SBUs instead of minutes as a unit.

SBU is the Static Binutils Unit, which means the time repoman merge binutils takes on your machine. By default makepkg will print out how many seconds the build took. After you built binutils, you should update your /etc/makepkg.conf:

SBU="257"

The line above means compiling binutils on your machine took 257 seconds. From this point, makepkg will print out SBUs instead of seconds after successful builds, and this SBU value will be equal on anyone’s machine.

# Maintainer: Miklos Vajna <vmiklos@frugalware.org>

If you are the maintainer of the package, write your name or nick and e-mail address here. If you probably you won’t maintain the package, write Contributor instead of Maintainer, and then the Maintainer will add his/her line later. A package may have only one contributor: the first person who wrote FrugalBuild for it. The maintainer is the current maintainer. The other names should not be included in the FrugalBuild, anyone can use the version control features to look for them.

pkgname=cabextract

This will be the name of the package. It’s allowed to include numbers, hyphens (-), etc., and should be lowercase.

pkgver=1.2

The package’s version. Hyphens are not allowed, so a 1.0-6111 will be usually converted to 1.0_6111.

pkgrel=1

Release number marks Frugalware-specific changes. If you recompile a package, you should increase this number. If you upgrade to a newer version, don’t forget to reset this number back to 1. If you design a new package, set this to 1.

pkgdesc="a program to extract Microsoft Cabinet files"

A short one-line description for the package. Usually taken from the project’s homepage or manpage.

url="http://www.kyz.uklinux.net/cabextract.php"

The website of the project.

depends=(\'glibc\')

List of dependencies of the package, defined in a bash array. Usually you should compile a package at least two times: first with depends=(), then you should run chkdep -p foo.fpm that will suggest the dependencies, but handle that information with caution! Reading the README, INSTALL and configure.ac files is also a good idea to find out dependencies.

groups=(\'apps\')

It is needed to know where, in which category the package belongs. The most important thing: don’t put your package in apps, base, devel, lib, multimedia or network, if it depends on X (or on a pkg depending on X, of course). Packages in the extra repository get the -extra suffix to the group name.

You should use groups for creating metapackages. The method is the following: put each package to an existing group (group without a hyphen or with the -extra suffix), then add the packages to a new group, something like foo-suite or whatever your want, provided that the name is not an existing group.

Example:

groups=(\'lib-extra\' \'foo-suite\')

archs=(\'i686\' \'x86_64\')

This array defines for which architectures the given package is available. If it’s not available, it means that gensync will skip it when generating package databases. If you are not able to provide a binary package for a given arch, don’t include that in archs()! For example, no matter if the package could be compiled in x86_64, if you haven’t compiled it yourself, don’t include it.

up2date="lynx -dump http://www.kyz.uklinux.net/cabextract.php |grep 'cabextract \
        source code' |sed 's/.*-\(.*\).t.*/\1/'"

A short command that will give us the latest stable version of the package. This helps maintainers to keep the FST up to date. Usually this string consists of three parts: a lynx -dump someurl, a grep foo, and a sed command. We use the http protocol if possible, but sometimes we have to use ftp. In that case instead of lynx -dump you should use wget -O - -q. Of course, you could use wget all the time, but lynx is simpler. The sed command could be replaced with the combination of tr and cut if you prefer them instead of sed. The example used above would be the following with cut and tr:

up2date="lynx -dump http://www.kyz.uklinux.net/cabextract.php |grep \
        'cabextractsource code'|tr -s ' '|cut -d ' ' -f 6"

source=(http://www.kyz.uklinux.net/downloads/$pkgname-$pkgver.tar.gz)

Here you define the sources of the package in a bash array. You can use simple filenames for patches, or additional files when you place them in the same directory as the FrugalBuild script. You can use URLs if you want makepkg to download them automatically. It’s important to place all sources in the package’s directory including the source files that you can download from a site. Also when dowloading from sourceforge, please use Finclude sourceforge! If you use various random patches from unknown sources, don’t expect that somebody else will port those patches to a newer version. You will have to do the work yourself. You have been warned! Actually try to avoid patches unless they are really necessary (eg: secfix, bugfix).

A few words about the size of the sources. If you use an URL then the size is almost unlimited, but if the source is not an url then the source will be added to the FST when the package is accepted. We don’t allow files bigger than 100KB in FST. To solve this problem, the sources for a given package are placed in the /pub/other/sources/pkgname dir for each package. If the source is not compressed, we use gzip or bzip2 to compress it first. After this you can use a http://ftp.frugalware.org/pub/other/sources/pkgname/foo-styled URL for those big sources.

sha1sums=(\'094e3afb2fe8dfe82f63731cdcd3b999f4856cff\')

Another bash array to prevent compiling from the wrong source. Of course this is useless if you just run sha1sum foo.tar.gz after download. Try fetching original sha1sums from the projects website, if possible. It’s a good idea to leave a comment above this line about where to find these sha1sums.

As you can see there in no build() function in this FB. It’s because we wrote some F* functions to make our work easier. It’s something similar you can see in Gentoo for example. These functions can be found in source/include/util.sh file inside the FST. An empty build actually means:

build() {
        Fpatchall
        Fmake "$@"
        Fmakeinstall
        if echo ${source[@]}|grep -q README.Frugalware; then
                Fdoc README.Frugalware
        fi
}

So Fpatchall will apply all the patches in source() array, then Fmake calls the configure script and make command, then Fmakeinstall acts like make install, finally if a README.Frugalware file is given it will also add that to the package. For details see the utils.sh file, it’s well documented.

Note
You don’t have to use these F* commands, but we highly recommend it. Also if you use simple commands do not forget to add || return 1 after each command, so the build will stop on error!

# optimization OK

This line will be added automatically to the end of the FrugalBuild if the build() function used your $CFLAGS or $CXXFLAGS. This is handy if you want to cross-compile on a faster machine for a slower architecture. If the package doesn’t use our $CFLAGS we can’t cross-compile it, so please try to avoid creating "unoptimized" packages. If the package doesn’t contain any architecture-dependent file, then you can add this line manually as makepkg will not detect this.

5. Full reference

Now here is a full list of directives available.

First, let’s start with the install directive. Here you can refer to an install file (usually $pkgname.install) to use. If there is a $pkgname.install in the FrugalBuild’s directory, it will be used automatically. In the install file, you can define actions to be executed before/after installing/upgrading/removing the package. A skeleton of this file can be found under /docs/skel in FST.

Of course, you probably will not need all of these functions, just remove what you don’t need. If you want to do exactly the same after upgrading as after installing, feel free to use post_install $1 in the post_upgrade() function.

Save this file as $pkgname.install, thus makepkg will automatically use it. You should not specify the install script in the source array as it is not used in build().

The pkgname, pkgver, pkgrel, url, source and sha1sums directives were discussed in the previous section.

The backup array is used to make some files in the package as config files. If possible, we don’t modify config files during an upgrade. Example:

backup=(\'etc/pacman-g2.conf\')

Note that the leading slash is missing!

For more information about this, see the handling config files section in the pacman-g2 manpage

The depends array has been discussed already, except I haven’t mentioned before that the elements may include version information, for example:

pkgname=kdewebdev
depends=('kdelibs=3.3.0')

Here you can use <>, ⇐, >= or = operators.

The makedepends array defines packages required only in build time. For example if the source is in SRPM format, probably alien is a build-time requirement.

The rodepends array defines packages required only in runtime. It must be used in any case when putting the given package in the depends() array would cause circular dependency.

In the conflicts array, you can define a list of packages that shouldn’t be installed if you want to install this package. Let’s see an another example:

pkgname=mutt-devel
conflicts=('mutt')

It is necessary as the two packages are almost the same, but the binaries differ. In this case the mutt package must also contain this line: conflicts=(\'mutt-devel\'). Of course, if two or more packages conflict eachother, only one of them can be placed in a non-extra group.

The provides array is used to create virtual dependencies. It means both postfix and sendmail provides mta, so we can do:

pkgname=mailman
rodepends=('mta')

The user has a choice between postfix and sendmail.

The last one in this list is the replaces directive. The module-init-tools package is a good example:

pkgname=module-init-tools
replaces=('modutils')
conflicts=('modutils')

As you can see, we often make such new packages which also conflict with each other. Using the replaces directive when users use pacman-g2 -Su next time, if modutils is installed (probably :)), they will be asked to remove modutils and install module-init-tools.

license=(\'GPL2\')

This directive is optional. At the moment, you may add such a field, but copy the LICENSE field from the source root to the packages’s documentation dir, so this isn’t really necessary.

6. Subpackages

Since 0.5 makepkg can also create subpackages. It is very useful when your package has graphical parts based on qt for example. It’s a pain for gnome users as they want the package, but they do not want the qt part. So you create a subpackage for qt part and both side is happy. Let’s see an example:

# Compiling Time: 1.43 SBU
# Maintainer: crazy <crazy@frugalware.org>

pkgname=djvulibre
pkgver=3.5.18
pkgrel=2
pkgdesc="DjVu is a web-centric format for distributing documents and images."
depends=('libtiff' 'libjpeg')
makedepends=('kdelibs' 'gnome-mime-data' 'gnome-icon-theme' 'htop')
rodepends=('xdg-utils')
groups=('xapps')
archs=('i686' 'x86_64')
options=('scriptlet')
_F_sourceforge_dirname="djvu"
_F_sourceforge_broken_up2date=1
Finclude sourceforge
url="http://djvulibre.djvuzone.org/"
source=(${source[@]} head_-n1.patch  no-OPTS-FLAGS-thx.patch)

subpkgs=('djview')
subdescs=('DjVu viewer for qt and mozilla plugins.')
subdepends=('libxi libgl qt libxmu')
subrodepends=('djvulibre')
subgroups=('xapps-extra')
subarchs=('i686 x86_64')

build()
{
        Fcd
        Fpatchall
        Fautoreconf
        export CFLAGS="$CFLAGS"
        export CXXFLAGS="$CXXFLAGS"
        Fconf \
                --enable-threads \
                --disable-desktopfiles \
                --enable-xmltools \
                --enable-djview
        make depend || Fdie
        make || Fdie
        Fmakeinstall
        Fln /usr/lib/netscape/plugins/nsdejavu.so \
                /usr/lib/mozilla/plugins/nsdejavu.so
        Fln djview3.1.gz usr/share/man/man1/djview.1

        Fsplit djview usr/bin/djview
        Fsplit djview usr/bin/djview3
        Fsplit djview usr/lib/mozilla
        Fsplit djview usr/lib/netscape
        for i in . ja; do
                [[ $i == . ]] && Fsplit djview usr/share/man/$i/man1/djview.1
                Fsplit djview usr/share/man/$i/man1/djview3.1
                Fsplit djview usr/share/man/$i/man1/nsdejavu.1
        done
        Fsplit djview usr/share/djvu/djview3
}

Here you can see the djvulibre FrugalBuild. Note subpkgs, subdescs, subdepends, subgroups and subarchs. These 5 value is lethal for a subpackage. There are other subpackage variables too of course. See man FrugalBuild for details. Also note that bash does not support two-dimensional arrays, so when defining the array of arrays, then quotes are the major separators and spaces are the minor ones.

Defining the subpackage is only the first part of creating a subpackage. You have to tell makepkg which files you want to put in the subpackage. We use Fsplit command for this. First parameter is the subpackage name, second is the file you want to move. Please never use a trailing slash when defining file patterns, especially if you use wildcards in it!

If you need more example just take a look on avahi FrugalBuild in network group.

Note
Use subpackages when they are necessary, but do not start making foo-devel, foo-common, foo-not-so-common, foo-quite-common-but-not-that-common packages :) Making too much subpackage makes maintaining too hard and simplicity is the frugal way.

7. Compiling the package

That’s fairly simple. In the package directory you should do exactly the same as described in the Recompiling packages section. If you want to contribute this package to the Frugalware project, then go to BTS, open a feature request and upload each non-downloadable file (ie. FrugalBuild, install scriptlet, patches) as an attachement. Please do not forget to check your FrugalBuild with fblint command before uploading it. Fblint is available in pacman-tools package.

Happy packaging!

8. Kernel modules

A few words about kernel modules. They’re special as even if you installed the correct version of the kernel (and kernel-source) package, sometimes the modules are compiled for the running kernel, so you have to check if compiling against other kernel version than the running one works or not. You can use the modinfo command for this. If crosscompiling does not work always add Fcheckkernel to the build(). So here is the list of conditions a kernel module package have to satisfy:

1) Should depend on kernel=version, where version is the version of the kernel defined in $fst_root/source/include/kernel-module.sh. (Always use up-to-date FST!)

2) Should Finclude the kernel-module scheme.

3) If you want to use a custom install script (saying running just depmod -a after the install/upgrade is not enough for you) then the install script should run depmod -a. Otherwise the scheme will provide so a scriptlet which does so.

4) build() should call Fcheckkernel to ensure the module will be compiled for the right kernel version or it should be commented if you have checked the compiling for other kernel version. It is good for out build servers as they may not run the kernel provided by the given package tree. (They can’t run -stable and -current kernels at the same time :) ).

5) Kernel modules may be installed for the not-currently-running kernel. To ensure they are registered properly, you need to use the Fbuild_kernelmod_scriptlet function. It generates the proper install scriptlet for you.

See man kernel-module.sh for more info.

9. Repoman

Repoman is simple tool to download all packages' buildscript and compile programs from source.

The most commonly used repoman commands are the following:

repoman merge package

or simply

repoman m package

builds a package from source and installs it. You can configure the build options in the makepkg_opts directive of /etc/repoman.conf.

By default repoman will install the missing dependencies with pacman, clean up the leftover work files, install the package, and write the resulting package to the current working directory.

repoman update

or simply

repoman upd

updates FST in /var/fst (or the directory set in ~/.repoman.conf). First time repoman will download it (it may take some time!).