Today’s CMake conversion failed at the final link with an undefined reference from a file I had not changed. Naturally, I first blamed the linker. Linkers enjoy this arrangement because they never have to attend the meeting.

The actual cause was dependency order. The old build inherited libraries from a parent directory, so this target had never declared what it used. Moving it exposed the omission.

I started recording requirements next to the target:

set(indexer_SRCS
    indexer.cpp
    document.cpp
    tokenizer.cpp
)

kde4_add_executable(indexer ${indexer_SRCS})
target_link_libraries(indexer
    ${KDE4_KDECORE_LIBS}
    searchcore
)

Then I configured from an empty build directory. Reusing yesterday’s cache can conceal a missing check or preserve a path that no clean machine has. During conversion, this sequence is worth the extra minute:

rm -rf build
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=debugfull ..
make VERBOSE=1

I am not recommending casual rm -rf; I am recommending a disposable directory whose purpose is printed on the tin. Source remains elsewhere.

Generated files need similar care. If a header is produced during the build, its output belongs in the binary tree and the target must depend on the generation step. Writing generated headers into the source tree makes an apparently clean checkout depend on debris from an earlier build. It also makes parallel builds unusually creative.

I prefer explicit source lists over collecting every *.cpp automatically. Adding a file should change the build description, which gives review and version control a chance to notice it. The caveat is maintenance: source lists can drift if developers forget them, and globbing can be reasonable for generated collections. For hand-written application code, explicit still wins for me.

I am applying the same rule to include directories. A target should receive only paths it needs; a global include path can let one directory compile by accident against another directory’s private header.

The best conversion checkpoint is not “CMake completed.” It is a clean configure, complete build, and one small executable run from the binary tree. Configuration only proves that CMake understood my instructions, not that my instructions were wise.

One target now builds without inherited flags. Nine remain. This is slower to describe than a bulk conversion and faster to debug than one.