ONLamp.com
oreilly.comSafari Books Online.Conferences.

advertisement


Making Packager-Friendly Software, Part 2
Pages: 1, 2

Build Infrastructure Oddities

The way your program is built also affects the packaging process. The following list describes the most important issues, most of which are related to your makefiles or the configuration script:



  • Avoid attempts to autodetect how to create shared libraries. This has diverged on Unix-like operating systems, so much as to render any package unportable when hard-coding any particular type of shared object handling. GNU Libtool may be ugly and imperfect, but it often gets things right with little pain.
  • Forget about linking shared libraries against static archives. Many platforms do not support this, for very good reasons. Consider two different shared libraries linked to the same static archive and a binary program that requires both of them. When they are loaded, they could provide overriding symbols, thus causing future problems.
  • Do not directly hard-code custom build flags in variables the compiler reads, such as CFLAGS, CXXFLAGS, CPPFLAGS, or LDFLAGS, unless you check whether the compiler supports them. If you do that, you will break the build on compilers different than yours.

    Furthermore, even if you check whether the compiler supports the options you want, let the user override them. Adding an option may seem an excellent choice, but somebody, somewhere won't like it.

    The following code illustrates how a configuration script could pass multiple debugging flags to the compiler following the two rules just described:

    AC_ARG_ENABLE(developer,
                  AS_HELP_STRING(--enable-developer,
                                 [enable developer features]),,
                  enable_developer=no)
    if test ${enable_developer} = yes; then
        for f in -g -Wall -Wstrict-prototypes \
                    -Wmissing-prototypes -Wpointer-arith \
                    -Wreturn-type -Wswitch -Wcast-qual \
                    -Wwrite-strings
        do
            AC_MSG_CHECKING(whether ${CC} supports ${f})
            saved_cflags="${CFLAGS}"
            CFLAGS="${CFLAGS} ${f}"
            AC_COMPILE_IFELSE([int main(void) { return 0; }],
                              AC_MSG_RESULT(yes),
                              AC_MSG_RESULT(no)
                              CFLAGS="${saved_cflags}")
        done
    else
        CPPFLAGS="${CPPFLAGS} -DNDEBUG"
    fi
  • If your software uses automatically generated files or documentation, please include the generated files in the distribution tarball, so that not every user has to generate the files again for themselves. This will simplify the build process by reducing the build-time dependency tree.

    For example, if you use GTK-Doc, your must make sure that your distfile includes all the generated HTML files. As a simpler example (although you generally don't have to care about it), when using GNU Autoconf, your distfile must include the configure script, or, when using GNU Automake, it has to include all the Makefile.in files.

    This is related to the build infrastructure of your package in the sense that it has to keep control of the generated files and be careful to include them in the distfile (usually when issuing a make dist in the source package.

Code Portability

Writing portable code is a difficult quest. Nowadays, the most popular Unix-like system is Linux; thus almost all software development happens under it. This drives to several portability issues for less than careful code. The most common ones are:

  • Use of GNU libc extensions. Many non-Linux systems, like the BSDs or Solaris, use their own version of the C library, which is not made by GNU. It often lacks all the extensions introduced in GNU libc. Usually, the functions' manual pages specify whether they conform to an existing specification (such as POSIX); this can help you decide which are safe to use and which are not.
  • Direct device access. Each system has its own set of devices under /dev. A few of them use common names, but the vast majority are different. Furthermore, even for the same device, ioctls may differ.
  • Access to /proc: Linux uses the /proc tree to hold not only processes but also a lot of information about the current system. On other systems (the BSDs, for example), this directory may not exist at all. Relying exclusively on it is not portable.

There is much, much more. Analyzing portability issues in detail could be worth another (very long) article.

How does this affect the packaging process? While writing your code, you may realize that a part of it will not be portable, especially if it has to deal with system intrinsics or hardware devices. You can either try to fix it, which may be difficult if you do not have other systems around, or you can clearly mark such code as not portable. The latter is possible by using independent source files for each system (for example, cdrom-linux.c and cdrom-netbsd.c) or with conditional compilation.

For instance, suppose you have a chunk of code that builds only if Linux's CD access interface is available; you aren't aware of how to do the same thing on non-Linux systems. The only right way to fix this is to detect the presence of the required feature from the configuration script (but avoid testing macros such as __linux__ whenever possible; see below). Once you've checked for it, you should do something like the following in your code:

#ifdef HAVE_LINUX_CD_INTERFACE
    /* Code that only works under Linux systems... */
#else
#  error Sorry, this part of the code is not yet ported \
         to non-Linux systems.
#endif

First of all, there is a clear separation of the code in OS-specific chunks, which eases the porting process. However, note the #error line in the #else clause, which is important. This is to make the compilation fail on non-Linux systems with an appropriate error message. The packager will quickly see where the error is, and he may attempt to patch your code to support another platform. (If he's able to do that, you will receive a patch to improve your program!) The rule to remember is that it's better to keep errors controlled than to let your code fail in unexpected places.

As I said two paragraphs ago, you shouldn't use macros like __linux__ or __i386__ and friends. Why not? These macros fail to detect changes across versions of the same OS. For example, you may be using a Linux-specific feature, but unfortunately it is available only with a 2.6 kernel (and you are not aware of this fact). Therefore, you use __linux__ to separate your code, but then it will mysteriously fail in older versions.

OSes have evolved so much, as has their support for different architectures, that relying on these definitions is bad. Check for the specific feature you need from the configuration script, and use the results. This means that your code will be not only more portable, but also easier to maintain. Examples of programs that rely on these definitions are Perl, OpenSSL, and Crypto++; they are a real nightmare to port and maintain.

As we are talking about conditional compilation definitions, let me mention some standards. Don't expect _XOPEN_SOURCE or _POSIX_SOURCE (and similar macros) to do anything useful across multiple platforms. Don't define them unless absolutely necessary; they likely will restrict the namespace further rather than provide portability. If you need to use a specific function, first verify whether it is available in the most common systems, and then add a check in your configuration script.

Another solution, which may be better in some situations, is to use hardware or system abstraction libraries. These often deal with portability details and remove that burden from you. Furthermore, they have wide testing and already have packages for other operating systems, so you have a better chance that your program will work properly. Examples include Glib, libgtop, and Qt.

The End

I have tried to describe many of the problems that have bothered packagers over time as well as possible; let me know if something is hard to understand and I will try to explain further.

I also may have left out other important problems. I can't be aware of them all, especially because something that another developer sees as a problem may look harmless to me.

Anyway, I (I think I can safely say we) hope you have found this interesting and that you will try to make your developments more packager friendly. Thanks in advance!

Julio M. Merino Vidal studies computer science at the FIB faculty in Barcelona, Spain.


Return to ONLamp.com.



Sponsored by: