Autofools

A lot of people, when confronted with the prospect of porting a program to a different platform think, "I know, I'll use GNU Autotools." To riff on jwz: Now you have five problems.

You might think that's harsh, considering that defaulting to using regular expressions only makes two problems, but it's true. When you decide to go with GNU Autotools, you now have to deal with:

  1. autoconf
  2. automake
  3. libtool
  4. pkg-config
  5. actual portability concerns in your code

Let's address these one by one.

autoconf

On the surface, autoconf seems nice. You can use it to check whether certain headers or libraries exist, and automatically #define some symbolic constants based on that. You can also create your own options to configure that let the person building your tool change things that are hard-coded into your program. So that's actually two additional subproblems.

Subproblem the first: You identify that the system you're building on doesn't support "ANSI C Prototypes". It's the year 2019 (or later, depending on when you read this). And your compiler isn't up to the state of 1989. Are you going to try charging on, riddling your code with crap like #define __P(params) /**/ to force all of your functions to K&R declarations? You damn well shouldn't. Better to fix your build environment than make your program less maintainable. Ditto for stuff like checking whether <sys/types.h> exists. If it's a system that implements literally any version of POSIX, starting with 1003.1-1988, it has this header. What, exactly, do you hope to gain by checking for it?

Subproblem the second: You can make all sorts of options to your configure script to control what gets hard-coded into your program. To control what gets hard-coded into your program. You should be avoiding hard-coding things into your program. If something does need to be hard-coded, like a default buffer size or something, take charge of your own code and make the decision. Don't waffle about whether the user might want to change it. If they do need to, they can edit the code where it's set as a #define or const themselves. And if it doesn't need to be hard-coded, it shouldn't be. Things like configuration file names. Yes, you should hard-code a reasonable default, but if the user might legitimately need to specify a different one, it should be a run-time option, not a compile time. Really, you should strive to avoid compile-time configuration whenever run-time configuration is possible (hint: it usually is).

automake

Ah yes, the tool that takes all the complexity out of building makefiles. The problem here is that makefiles are not hard. No, really, they aren't. Go read the POSIX specification for make. Stop believing all the cargo-cult nonsense you hear people spread about makefiles being difficult. You don't need automake to make makefiles. I promise you don't. Which is good, since automake refuses to run without autoconf, which is a lot of m4 and sh scripts with very little benefit.

libtool

Once upon a time, using libtool was a forgivable sin. Pretty much every different platform had a different syntax for generating shared libraries, which made distributing source for shared libraries quite difficult. Nowadays, pretty much everyone has agreed to use -shared as a linker flag to accomplish this. Granted, it isn't strictly portable since it isn't specified by POSIX, but POSIX doesn't specify any means of generating shared libraries - only archive libraries (.a files).

pkg-config

Why do so many open source libraries feel the need to add their own requirements to my CFLAGS, LDFLAGS, and LIBS? In this case, I feel like pkg-config is a symptom rather than the disease. If you're writing a library, please just install your headers in a standard place for the system (usually /usr/local/include), your libraries in a standard place (granted this is a complicated issue on GNU/Linux, but /usr/local/lib works well), and bundle it in a single .so or .a file with the name of your library. If I need to specify multiple -l operands to link your library, it isn't one library.

actual portability concerns

Even if you do deal with all the above, you're still left with issues in your code itself. If you've been doing all of your development on GNU/Linux, there's a decent chance you've let some GNUisms creep into your codebase. Stuff like using %m as a format specifier in a call to printf(). Is the lack of portability worth not taking the few extra seconds and keystrokes to use a %s format specifier paired with a strerror(errno) parameter? And GNU/Linux isn't the only culprit here. I've noticed a trend in OpenBSD circles to use reallocarray() when realloc() is appropriate. Here's a tip: if there's a possibility that reallocating your array could cause integer overflow in a size_t, maybe an array isn't your ideal data structure.

Solutions

I'd like to summarize solutions so this isn't purely a diatribe against GNU autotools, but a path away from them towards more maintainable and portable code.

Don't bother testing for every header and every language feature that your codebase uses. Especially if you haven't written an alternative for when the feature isn't found. If a system doesn't declare the prototype for malloc() in <stdlib.h>, after 30 (or more) years, that system isn't worth your porting time and effort to support. It really isn't.

Take the time you would have spent writing custom m4 macros for your configure script to accept, and use it to make your program run-time configurable instead of compile-time. Your end users will thank you for it.

Learn POSIX make. Printed at a reasonable font size, that specification is less than 30 pages, which includes the rationale and a number of examples. You can internalize that over a long lunch. It's worth it. Plus, you can impress your friends who still think makefiles are deep magic.

Building a static library is easy: ar -r libwhatever.a file.o…. (Or, in your makefile: $(AR) $(ARFLAGS) $@ $(OBJECTS).) Dynamic libraries are almost as easy: $(LD) -static -o $@ $(LDFLAGS) $(OBJECTS). There, now you never have to even think about libtool.

If you're thinking of writing a pkg-config file, don't. Just install your headers to /usr/local/include and libraries to /usr/local/lib. Then your users won't need any special CFLAGS or LDFLAGS. Give your library a name of least surprise so it's easy to add as a -l operand.

Avoid GNUisms and other extensions when possible. Double check man pages. If it says _GNU_SOURCE in the SYNOPSIS, or "x is a y extension" under CONFORMANCE, it might not port. Consider the tradeoff between convenience and portability.

Copyright © 2019 Jakob Kaivo <jakob@kaivo.net>