Blog

Porting an Application to KDE 3

KDE 3.0 is out, which turned my experimental port into work that other people may actually try to compile. The application was modest, but it touched enough of KDE 2 and Qt 2 to make a straight rebuild unrealistic. I expected a grand rewrite. The successful approach was much duller: establish a clean baseline, move one boundary at a time, and keep the program runnable.

I started with a separate source copy and a fresh build directory. Building outside the source tree made stale generated files much easier to spot.

make -f Makefile.cvs
mkdir build-kde3
cd build-kde3
../configure --prefix="$KDEDIR" --enable-debug
make

The first pass was entirely mechanical. I fixed renamed headers, methods whose signatures changed, and code that depended on implicit conversions no longer accepted by Qt 3. I did not improve the design while doing this. Combining a port with a cleanup makes each failure ambiguous, and ambiguity is already supplied free of charge by C++ error messages.

Qt’s collection classes deserved special attention. Code that stored pointers in a QList-style class or relied on automatic deletion needed its ownership checked rather than merely adjusted until it compiled. Qt 3’s classes are familiar, but familiar is not identical. I wrote down who owned each object, especially widgets, list items, and objects parented to a QObject. Parent-child deletion is useful only when the parent is the intended owner.

Signals and slots were the next boundary. Every class using them needed Q_OBJECT, and every connection needed matching argument types. The string notation hides mistakes from the compiler:

connect(action, SIGNAL(activated()),
        this, SLOT(openDocument()));

I ran the application from a terminal and treated every QObject::connect warning as a real defect. It is tempting to ignore one because the window still appears. That tends to last until the ignored connection is the Save action.

The KDE layer was easier once Qt was quiet. I checked command-line handling through KCmdLineArgs, application setup through KApplication, actions, standard shortcuts, and XMLGUI resource files. An old .rc file can leave menus looking plausible while actions are missing or duplicated. Removing installed test copies before checking the new one prevented Konqueror and the application from finding an obsolete resource in another prefix.

Configuration code also needed a deliberate pass. I kept reads and writes together and checked defaults rather than assuming the old configuration file contained every key:

KConfig *config = KGlobal::config();
config->setGroup("View");
bool showStatus = config->readBoolEntry("ShowStatusBar", true);

For document handling, I resisted replacing everything at once. The application already separated its document widget reasonably well, so I first made that compile, then considered whether it should become a KPart. A port is a poor time to discover that the document, window, and network code have secretly been one object wearing three hats.

The most useful testing sequence was deliberately boring:

  1. Start with an empty configuration directory.
  2. Open a local file from the command line.
  3. Open it through the file dialog.
  4. Modify, save, and reopen it.
  5. Exercise every action and shortcut.
  6. Close with unsaved changes.
  7. Repeat with an existing KDE 2 configuration.

I also built with debugging enabled and ran under gdb. A port often exposes old lifetime errors because widget destruction order or an event sequence changes slightly. The new library did not necessarily create the dangling pointer; it merely stopped arranging the furniture around it.

Binary compatibility was not a goal for local plug-ins compiled against KDE 2. They had to be rebuilt against the KDE 3 and Qt 3 headers and libraries. Trying to preserve old C++ objects across that boundary would save a compilation and purchase a much stranger crash.

Before calling the port complete, I also built a source archive and compiled that archive in an empty directory. A working source tree can quietly depend on generated files, unlisted headers, or resources left by yesterday’s build. make distcheck is useful when the project supports it, but even the plain exercise of unpacking the archive elsewhere catches embarrassing omissions. I then installed into a temporary prefix and started a KDE session with that prefix in its search path. This tests what was shipped rather than what happens to be within reach of the compiler.

Warnings received their own pass after behavior was correct. I enabled the warnings supported by the older compiler and looked especially for suspicious casts, hidden overloads, and variables whose type changed under Qt 3. I did not turn every warning cleanup into part of the port, but I did investigate each one. A warning newly exposed by a library transition often points directly at an assumption that deserves daylight.

My preference after this exercise is to make the smallest source change that produces correct KDE 3 behavior, then do cleanup separately. The port remains reviewable, regressions have fewer hiding places, and users get a working application sooner. It is not heroic engineering, but heroic engineering has an alarming habit of requiring heroic debugging.

What moc Actually Generates

I added a signal to a small Qt 3 class today, rebuilt, and received an undefined reference to the class’s virtual table. That message sounds like a C++ problem. In this case it meant I had forgotten about moc.

Qt’s signals and slots need information that the C++ compiler does not produce. A class using them declares Q_OBJECT:

class Counter : public QObject
{
    Q_OBJECT
public:
    Counter(QObject *parent = 0, const char *name = 0);

signals:
    void changed(int value);

public slots:
    void setValue(int value);
};

The Meta Object Compiler reads that declaration and writes ordinary C++ containing the meta-object table and dispatch code. The generated file supplies, among other things, the pieces behind className(), signal activation, and calls made through the string-based connect() mechanism.

In a KDE autotools project the build rules normally run moc for headers containing Q_OBJECT. If I put the class declaration in a .cpp file, I include its generated output at the bottom:

#include "counter.moc"

After adding or removing Q_OBJECT, regenerating the build files is sometimes necessary. A clean rebuild is also cheaper than interpreting every vtable error as an omen.

I like signals and slots because the sender does not need to know the receiver. The caveat is that the compiler cannot check the old SIGNAL() and SLOT() strings fully. A connection can compile and then complain at run time, so I watch the diagnostic output and keep signatures simple.

GCC 2.95 Is Part of the Platform

I lost an afternoon to code that compiled perfectly on my machine and failed on the machine that actually had to ship it. Mine had a newer compiler. The other one had GCC 2.95, as do quite a few systems on which KDE software is expected to build.

The useful lesson is not to argue with the compiler. It is to treat it as part of the target platform. If the project says it supports GCC 2.95, compile with GCC 2.95 before sending the change anywhere.

I now keep a separate build tree for this:

mkdir build-gcc295
cd build-gcc295
CC=gcc CXX=g++ ../configure --enable-debug
make

The compiler is particularly good at finding ambitious template code. Partial specialization, dependent names, and clever overloads can all expose old parser bugs or incomplete language support. Usually the least surprising spelling wins: name types explicitly, keep templates small, and avoid making overload resolution solve a riddle.

One more trap is mixing C++ libraries. An object compiled against an incompatible libstdc++ is not made safe merely because the linker accepted it. I build the whole program and its local libraries with the same toolchain.

I would rather write plain code than maintain compiler-specific branches. It is less exciting, but so is a build finishing successfully, and I have learned to appreciate that particular lack of excitement.