How not to maintain an API

So I've been working on my graphing code for EDB. I was eventually able to create a Qt widget that natively renders a graphviz graph layout. It actually works quite nicely, you can create an ordinary Graphviz graph either in memory or from a file like usual. The code can simply create a "GraphWidget" and the code will display the graph perfectly (there are some constructs that it doesn't support, but the basics are there) with nice things such as zooming and rotating.

All of this works great, except for the fact that Graphviz decided to change some of the structures used to represent the laid-out graph.


The most annoying issue I found is that the older versions of the Graphviz headers did not have any defines for versioning. This means that there is no good way to detect at compile time which version you are trying to compile against. Later one was introduced, which means that you can coarsely determine if you are compiling against a version before or after that macro was introduced. But this isn't really good enough given that there have been several changes over time before and after this macro was introduced.

So rule #1: If you plan to change anything in your publicly installed headers, ever, have a version macro that is unique for each and every version of structures and API.

Of course, the fun doesn't stop there, otherwise, I wouldn't have a whole lot to write about here. In addition to that, they decided to make the version macro they did create resolve to a string! This is great and all for displaying the version to the user. But is not easily comparable at compile time at all. The most creative solution I can think of is to have a configure script that will emit run a program that includes the header and outputs the version if available, then finally the configure script will parse the result and produce a compile-time comparable macro. This is some of what the autotools scripts do to produce things like config.h. But is less than fun to deal with.

Rule #2: Make the version macro numeric, and encode things in a way that x >= y always means that x is newer.

Something like this feels ideal.

#define VERSION 0x00010203 /* version 1.2.3 */

Here I am suggesting using a 32-bit constant for the version, dividing on byte boundaries for trivial decoding and ease of human readability. There is no need to stick to the traditional versioning schemes either. Lately, I've been preferring using the year and release number like this:

#define VERSION 0x00200901 /* version 2009.1 */

Both work equally well. But wait there is more nonsense! The Graphviz people decided to use macros to access the members of the primary structures (node, edge, graph). This way they can change the implementation details of these structures but the code using these structures can remain the same. This was a good idea. Unfortunately, they didn't do it for all of the structures. Only those three. But there are a whole bunch of structures representing text labels, coordinates, and other fun stuff that didn't use this system.

Rule #3: Be consistent with your API.

Finally, they used to have both floating-point and non-floating-point versions of certain data members. At some point, they decided to switch to only floating-point. The problem is that some things changed type, but not the name. So if I wanted to store the value of one of these, I can't conveniently. For example:

struct T { point *pos; };

became

struct T { pointf *pos; };

This makes code such as this unfortunately broken across versions.

point *p = x.pos;

If I had a usable version macro, I could make the type depend on the version. Even worse some things changed names and types. For example:

ND_coord_i /* returns "struct point " */

was replaced by:

ND_coord /* returns "struct pointf" */

The former no longer exists. At least I can test for the existence of the ND_coord macros to figure out which to use, but the net result is messier code.

Rule #4: Do everything possible to avoid changing datatypes of structure members and return types of functions. It makes storing the result ugly and convoluted.

One could argue that not all of the structures and functions are part of their public API. After all, it is pretty rare that someone implements their own rendering engine for something as big, complex as Graphviz. But these structures are in the headers that every distribution installs into /usr/include/Graphviz/.

Rule #5: If it is in a header that gets installed for the user, it is part of the API. If you have internal details you wish to hide use the PIMPL pattern.

So the end of the story is that I've got my Qt-based graphing widget I sought after. But the deployment is less than fun. Because I distribute sources I need to test that it compiles on all sorts of distributions. All of this could have been so much simpler if they just planned ahead and had a version-friendly API.