Understanding the Complexity of the FreeBSD Ports System
The Ports System on FreeBSD
The ports system is a large collection of cooperating processes. It is quite impressive that it manages to produce up to date packages on hardware with limited resources.
Often it is not easy to grasp the complexity of the ports system. This article will try to simplify this complexity for the reader.
Makefiles
Makefiles contain a set of rules that, when run, perform all the steps required to build or modify a port. They are the basis for incremental INDEX builds and many other features of porting.
Each line of a makefile begins with a rule for the target file named in the first column, then lists the prerequisites for that target. If a prerequisite has been modified, make must recompile the corresponding source file.
A makefile may also declare CONFLICTS between ports. When conflicts are encountered, make will not execute the recipes in any target. This can simplify the maintenance of a system, although it introduces complexity into parsing names (e.g. gimp becomes emacs-nox or py24-tkinter). It also requires careful consideration of dependencies and architectures.
OPTIONS
Options can be set for individual ports, and for ports in a jail and/or ports tree. These affect how a port is built. For example, setting WITHOUT_GNOME in a port’s Makefile will cause it to exclude Gnome support from the final package, even if gnome is present as a precompiled package or as a dependency.
This is a very valuable feature that can be used to minimize the distance between the state of ports in the ports system and the binary packages released as part of the freebsd distribution. Such a setup will not be as stable as relying on the INDEX for packages, but it will be much less fragile than a ports system that depends on recursive subshells to follow MOVED files and recompute dependencies.
FETCH
A port’s config file contains its packing list, a list of the files that will be installed when the package is created. These files are sorted alphabetically for convenience and to make it easy for make to determine which version of each file to install.
The system will recursively walk its dependency tree and fetch, extract and patch the source code, configure it and build (compile) it. It will then create a fake directory to install the files into, and finally, it will call pkg_add(1) to actually make the package.
The whole process takes 0.1 to 1s, depending on the complexity of the port and your computer. All the while, you can check progress in a nice curses-based text UI provided by Synth. You can also see which ports have failed to build, and the reason why.
INSTALL
One problem in a ports system which is constantly evolving is that a port may acquire wildly different dependencies from time to time depending on the way it was built. For example a port may have been built with autodetection features which add support for features if they are present on the machine. Thus gimp may become gimp-gnome without user intervention.
Luckily there are tools which can help in this situation. The most well known is probably Portmaster, introduced recently by Doug Barton. Another is Synth, a nice curses based program. Synth can be used to manage ports, checking whether or not a given package needs upgrading. It can also be used to install packages and even back them up. It does all this while watching over the build process. If it looks like the build is going nowhere a watchdog feature will kill it and restart it from scratch.
PACKAGE
The collection of makefiles, patches, and distinfo files needed to build and install a unique application under FreeBSD is known as a port. Ports are kept under /usr/ports by default.
A port is numbered according to its FreeBSD version, e.g. editors/joe editors/emacs, or more tersely, its pkg_version number (determined by uname -r). The FreeBSD ports system has utilities which manage objects and data under /usr/db/pkg that are not in the base system, such as pkg_add, pkg_create, pkg_delete, pkg_version, etc.
These tools interact with pkg and pkg_repo in a fairly complex manner, making use of conditionals and string substitution operators to varying degrees. The result is a system which is very powerful and intricate, but not without its pitfalls and complexities. A quick glance at a make man page illustrates this complexity.