Wednesday, January 13, 2010

Porting Cabal packages to FreeBSD - Part 1

It seems that all the Haskell ports on freshports have been committed by only a handful of people. This is unfortunate since porting a cabal-package to FreeBSD is almost as easy as creating it in the first place. There are some manual steps involved, but overall it's fairly simple once you have a basic understanding of cabal-packages and the FreeBSD ports. The FreeBSD side of things is a little bit more complicated, but it is also excellently documented in the Porter's Handbook. In this post I will try to give an overview of BSD's port system, what's necessary to create a port and mention a couple of tools that help creating new ports. Hopefully it will motivate some people to join the FreeBSD maintainers.

The 1st Part is intended as quick introduction. I will assume that you have very little knowledge about the BSD Ports, but I will also expect that you consult the handbook sooner rather than later. If you have questions that are not covered in the handbook, you can ask on the freebsd-porters mailing list. In the second part I will walk you through the complete process of porting a Haskell cabal package.

Cabal Package System
As you probably know, Haskell uses it's very own cabal package system. It does everything you expect from a package management system (and a little more). As porters we can't rely on cabal, however, and have to manually install cabal packages (see the cabal manual for more information) through BSD's ports so that the system is aware of the installed software. But before jumping straight to BSD, let's first look at how you manually install a cabal-package. All you need are these commands and you're done.
  • setup configure
  • setup build 
  • setup register
  • setup install 
Now that is simple, isn't it? Theoretically, these commands are sufficient. In reality, the configure step will fail if there are some unresolved dependencies to other packages as often will be the case. Why am I even bothering you with all of this? As you will see later, we are going to use these steps at the core of the Makefile of our port. Issuing these commands from within our Makefile is gonna be as complicated as it gets. But I'm getting ahead of myself again. Next I'll give you the 10'000 feet overview of the FreeBSD ports. 

Anatomy of a FreeBSD Port
All ports are located in one of the sub directories of /usr/ports. Where exactly is determined by which category the port belongs to. A typical FreeBSD port consists of four files:

pkg-descr
This file contains a description of the port, what it does and so on. Typically that will be one or two paragraphs.

pkg-plist
The packing list contains a listing of files that will be installed as part of the port. This is important, e.g. to cleanly deinstall a port. Not all files have to be added explicitly to pkg-plist, however. Man pages are a notable exception - those have to be declared in the Makefile. Generally, I find keeping track of these files a bit tedious, but fortunately there are tools that help you generate the packing list automatically.

distinfo
This file contains md5 and SHA checksums to verify the data integrity of the port. It too, can be comfortably auto-generated by typing make makesum.

Makefile
The Port's Makefile does all the heavy lifting. Make is a relatively old utility for automatically building executable programs and libraries from source code. Normally a makefile defines a set of operations it supports so called targets. They're used to download the source tarballs, check their checksums, resolve any dependencies to other ports and compile and install everything. Don't worry, most of these functions will be imported from the master file bsd.port.mk. As Haskell porters we only have to fill in a couple of blanks in form of control variables and a couple of targets.

Many of the variables you need to provide are self-explanatory (all of them are covered in more detail in chapter 5 of the Porter's Handbook so I won't duplicate everything here)
  • PORTNAME: contains the name under which the port will be installed
  • PORTVERSION: contains the version number of the port.
  • PORTREVISION: The PORTREVISION variable is a monotonically increasing value which is reset to 0 with every increase of PORTVERSION (i.e. every time a new official vendor release is made), and appended to the package name if non-zero.
  • CATEGORIES: contains a list of categories the port belongs too. This list also determines where in the ports tree the port is imported. If you put more than one category here, it is assumed that the port files will be put in the sub directory with the name in the first category. Please have a look at Chapter 5.3 of the Porter's Handbook, which explains the port categorization in detail.
  • MASTER_SITES: contains a list of locations of the source tarball. Specifying more than one site is recommended for redundancy reasons.
  • PKGNAMEPREFIX: The prefix for all Haskell ports is 'hs-'.
  • MAINTAINER: Your email address.
  • COMMENT: This is a one-line description of the port. Please do not include the package name (or version number of the software) in the comment. The comment should begin with a capital and end without a period.
  • LIB_DEPENDS: This variable specifies the shared libraries this port depends on.
  • RUN_DEPENDS: This variable specifies executables or files this port depends on during run-time. GHC is a typical run dependency.
  • BUILD_DEPENDS: You guessed it. This variable specifies executables or files this port requires to build. GHC is also a typical build dependency.
This concludes the broad overview of the most important variables. Of course, as maintainer you can also define your own variables for convenience. We'll see some of this in the next paragraph. So let's move on to the interesting stuff. The custom build actions. They're executed by the ports system in the order they're listed below.
  • do-configure: The configuration step. The first step is to actually build the setup binary. If that succeeds, setup configure will be called just as in the manual installation.
  • do-build: setup build is called after successful configuration. Additionally, generate a script to register the new package with the Haskell compiler. At this stage, we can also generate HTML documentation through haddock.
  • do-install: the next step is to call setup install. Besides that, we also copy the register.sh script that was created before into directory that contains all the compiled sources. This is necessary so that you can register the libraries when installing a pre-compiled *.tbz package.
  • post-install: We use the post-installation stage to clean up a temporarily file that was created outside our working directory. Namely package.conf.old
Most (if not all) Haskell ports I've seen in the ports define these four targets.
For example here are the four target from the Makefile of archivers/hs-zlib:

do-configure:
        cd ${WRKSRC} && ${GHC_CMD} --make Setup.lhs -o setup -package Cabal \
                                 && ${SETUP_CMD} configure --haddock-options=-w --prefix=${PREFIX}

do-build:
        cd ${WRKSRC} && ${SETUP_CMD} build \
                                  && ${SETUP_CMD} register --gen-script

.if !defined(NOPORTDOCS)
        cd ${WRKSRC} && ${SETUP_CMD} haddock --hyperlink-source \
                                          --hscolour-css=${HSCOLOUR_DATADIR}/hscolour.css
.endif

do-install:
        cd ${WRKSRC} && ${SETUP_CMD} install \
                                  && ${INSTALL_SCRIPT} register.sh ${PREFIX}/${SOME_LIBDIR_REL}/register.sh

post-install:
        ${RM} -f ${PREFIX}/lib/ghc-${GHC_VERSION}/package.conf.old


The only thing that changed compared to the manual installation process outlined above is that the maintainer defined some variables for more flexibility. Rather than ./setup, ${SETUP} is used the same was done for ${GHC_CMD}.

The Toolbox
There are also a couple of tools I previously alluded to that help you create and test new ports:
  • porttools: ports-mgmt/porttools provides tools for testing and submitting port updates and new ports.
  • genplist: ports-mgmt/genplist automatically creates a static plist for a port by installing it into a temporary directory, and then examining the directory tree. The process is based on the instructions for plist generation in the FreeBSD Porter's Handbook.
  • portlint: Please use portlint to see if your port conforms to our guidelines. The ports-mgmt/portlint program is part of the ports collection. In particular, you may want to check if the Makefile is in the right shape and the package is named appropriately
I'll demonstrate how they work in part 2.

No comments:

Post a Comment