Moving from qmake to CMake

After working with Qt5.x for a few months and really starting to enjoy the flexibility of qmake and QtCreator I decided to try using CMake to build XCode and MSVC proejcts.

After turning on C++11 a few weeks ago I found that QtCreator debugging became more difficult as it couldn’t show me the contents of any of the std containers (vector, list, map etc) without me manually digging in the the internal structure of the class which is quite tedious.

I was worried about leaving begind qmake which to be honst is one of the better make systems out there but I couldn’t tolerate not being able to easily see the data in my app and there has always been a nagging doubt that qmake isn’t flexible enough to allow me to build an editor and a game engine from the same source code with the minimum of fuss.

There are a lot of posts on the internet about CMake not working with the lastest XCode but, bar a few gripes, those issues seem to have been ironed out with the latest CMake (I’m using CMake 2.8.12.1).

If you’ve never used CMake before it uses a heirachy of CMakeLists.txt files to generate platform specific make file, XCode projects or MSVC Solutions (amongst other targets).

The qmake project I started with used the template subdirs which means it was a folder with sub folders with the actual code in it. This is the most common scenario for a reasonably large Qt project.

Creating the CMakeLists.txt files was a manual process of taking the .pro file and copying across the SOURCEand HEADERS definitions to the add_executable statement within the CMakeLists.txt file. For example the glew library:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cache()
! include( ../common.pri ) {
    error( "Couldn't find the common.pri file!" )
}

DEFINES += GLEW_STATIC

TARGET = glew
TEMPLATE = lib
CONFIG += staticlib

SOURCES += \
    glew.c

HEADERS += \
    glew.h \
    glxew.h \
    wglew.h
unix:!symbian {
    target.path = /usr/lib
    INSTALLS += target
}

OTHER_FILES += \
    readme.txt

is transformed to:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
add_definitions(-DGLEW_STATIC)

#disable all warning for third party libs
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w")
endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")


add_library(glew
    glew.c
    glew.h
    glxew.h
    wglew.h)

Nice and short and fairly simple to read.

CMake has specific support building and linking Qt5 applications and it makes the process of working with Qt reasonably simple:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
    set(CMAKE_PREFIX_PATH "C:\\Qt\\Qt5.2.0\\5.2.0\\msvc2010_opengl" "C:\\WinDDK\\7600.16385.1\\lib\\win7\\i386")
else(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
    set(CMAKE_PREFIX_PATH "~/Qt5.2.0/5.2.0/clang_64/")
endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows")


# Find includes in corresponding build directories
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Instruct CMake to run moc automatically when needed.
set(CMAKE_AUTOMOC ON)

# Find the QtWidgets library
find_package(Qt5Widgets)
find_package(Qt5Declarative)
find_package(OpenGL REQUIRED)

add_definitions(-DGLEW_STATIC)


qt5_wrap_ui(UI_HEADERS mainwindow.ui)
qt5_add_resources(UI_RESOURCES bsed.qrc)
add_executable(BSEd
    MACOSX_BUNDLE
    mainwindow.cpp
    ...
    BSEd.icns
    ${UI_HEADERS}
    ${UI_RESOURCES})

set(MACOSX_BUNDLE_ICON_FILE BSEd.icns)
SET_SOURCE_FILES_PROPERTIES(
  BSEd.icns
  PROPERTIES
  MACOSX_PACKAGE_LOCATION Resources
  )

target_link_libraries(BSEd Bullet glew LibPng LibZLib
    Polyvox voxcol voxcore voxdata voxmath voxrend voxscene
    ${OPENGL_LIBRARY}
    ${Qt5Core_QTMAIN_LIBRARIES})

if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
    set_target_properties(BSEd PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
    set_target_properties(BSEd PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows")

qt5_use_modules(BSEd Widgets OpenGL)

The important parts are:

  • set CMAKE_PREFIX_PATH to include the path to Qt. Otherwise find_package will fail. It’s probably better to use some sort of environment variable for this but since I work on my own I can hard code things.
  • set(CMAKE_INCLUDE_CURRENT_DIR ON) so that moc’ed files can be found during the build.
  • set(CMAKE_AUTOMOC ON) enable the mocing of files with QOBJECT defined in them.
  • use find_package to find the Qt libraries that the app uses.
  • tell cmake which UI files you are using. qt5_wrap_ui(UI_HEADERS mainwindow.ui) stores the result in the variable UI_HEADERS
  • tell cmake which resource files you are compiling. qt5_add_resources(UI_RESOURCES bsed.qrc) stores the result in the variableUI_RESOURCES.
  • make sure you add UI_HEADERS and UI_RESOURCES to the add_executable command.
  • tell cmake to link with the Qt libraires in target_link_libraries using {Qt5Core_QTMAIN_LIBRARIES}

On windows I found that the default application type was a console app. I didn’t want a little dos box to appear when I ran my app so I changed the subsystem from CONSOLE to WINDOWS with the following:

1
2
3
4
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
    set_target_properties(BSEd PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
    set_target_properties(BSEd PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows")

All of this took about three hours to figure out and get working on OSX and Windows7.

What I’ve gained:

  • I get to use the native debugging tools which on OSX and Windows7 are faster than QtCreator.
  • I get to use Xcode and MSVC for editting code both of which has useful features. Especially Xcodes real time error detection. It’s nice to be told your code is fail before you’ve even finished typing it all in.
  • I get to see inside std containers on both platforms with C++11 enabled.
  • I much more powerful build system that can cater for multiple targets and build configurations.
  • A blog post about something ;)

what I’ve lost:

  • the same editor/debugger (QtCreator) on all platforms. It’s quite nice to move from one machine to the next and the default key bindings are the same.
  • precompiled headers. This is a big one. CMake doesn’t support PCH out of the box, there is a plugin for CMake called cotire but I haven’t tried it yet.
  • the simplicity of qmake. CMake is much more powerful and consequently much more tricky to use. Cmake has a lot of documentation but you’ll need good google-fu to find answers quickly.
  • If you use QtCreator and qmake it feels like you are inside the Qt club. Using CMake seems more brittle and I am now worried that any upgrade to XCode (in particular) will break the build process.
  • A few hours of my time.

Hope that helps someone out there.