Builds

CMake Build Trees Are Disposable

My KDE 4 build kept compiling code for a library I had just disabled. I checked the option twice, added a loud message to CMakeLists.txt, and still got the old generated header. The compiler was not being stubborn. I was looking at leftovers.

The first clue was CMakeCache.txt: the build directory had been configured against another source checkout. The second was a generated config.h whose timestamp did not move when I reran CMake. An earlier configure had put a copy in the source tree, and that directory appeared first in the include path.

make clean did not help. It removed compiled output known to the generated makefiles; it did not remove the cache, old makefiles, or a header generated by an obsolete rule. That distinction cost me most of the afternoon.

The useful diagnosis was simple. I configured a brand-new build directory from the same checkout and compared the generated command line and config.h. The fresh tree had the option I expected. At that point there was no reason to repair the old tree one cache entry at a time, so I discarded it and configured again.

I also removed the generated header from the source tree and made its destination unambiguous:

configure_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake
    ${CMAKE_CURRENT_BINARY_DIR}/config.h
)

Now the binary include directory supplies config.h, and a fresh checkout cannot accidentally inherit one. If the clean build disagrees with the old build, I check the old cache’s source path, generated-file timestamps, and include order before blaming CMake detection.

Build trees are cheap. An hour spent negotiating with stale generated CMake files is not.

Converting One CMake Target at a Time

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.

CMake Before Coffee

This morning I changed one header and watched the old build machinery reconsider what appeared to be western civilisation. That was enough encouragement to try the same small library with CMake.

The useful bit was not clever syntax. It was stating the target directly:

set(parser_SRCS parser.cpp token.cpp)
kde4_add_library(parser ${parser_SRCS})
target_link_libraries(parser ${QT_QTCORE_LIBRARY})
install(TARGETS parser DESTINATION ${LIB_INSTALL_DIR})

I configured outside the source tree:

mkdir build
cd build
cmake ..
make VERBOSE=1

That last command matters while converting. The generated command line tells me which include path, definition, or library I forgot. Guessing from a linker error is possible, but so is repairing a watch with a spoon.

I prefer one explicit target with its own sources and libraries over directory-wide flags. It makes dependencies visible and avoids accidentally linking every library against everything else. The caveat is that our CMake helper macros are still moving, so a tidy file today may need adjustment next week.

My practical rule is to convert one buildable directory at a time, compile it, then continue. A giant mechanical conversion produces a giant pile of errors with no useful ordering. Small green steps are less heroic and considerably faster.