Compiling Qt4 apps with CMake

From QtCentreWiki

Jump to:navigation, search

Introduction

CMake is an open-source cross-platform build system created by Kitware. After KDE started to use it I thought I will give it a try and here's what I've learned. At first CMake might seem a bit odd, but it's easy to learn. General idea behind it is similar to qmake, yet it's a bit more flexible.


Sample project

Just like qmake has .pro files, CMake uses CMakeList.txt files that contain description of your project (or part of it).

A sample CMakeList.txt might look like this:

  # set project's name
  PROJECT( sample )
  
  # with SET() command you can change variables or define new ones
  # here we define SAMPLE_SRCS variable that contains a list of all .cpp files
  # note that we don't need \ at the end of line
  SET( SAMPLE_SRCS
       ./src/file1.cpp
       ./src/file2.cpp
       ...
       ./src/fileN.cpp
  )
  
  # another list, this time it includes all header files that should be treated with moc
  SET( SAMPLE_MOC_HDRS
       ./src/header1.h
       ./src/header2.h
       ...
       ./src/headerM.h      
  )
  
  # some .ui files
  SET( SAMPLE_UIS
       ./src/ui/Dialog1.ui
       ./src/ui/Dialog2.ui
  )
  
  # and finally an resource file
  SET( SAMPLE_RCS
       ./src/rc/sample.qrc
  )
  
  # enable warnings
  ADD_DEFINITIONS( -Wall )
  
  # by default only QtCore and QtGui modules are enabled
  # other modules must be enabled like this:
  SET( QT_USE_QT3SUPPORT TRUE )   
  SET( QT_USE_QTXML TRUE )
  
  # this command finds Qt4 libraries and sets all required variables
  # note that it's Qt4, not QT4 or qt4
  FIND_PACKAGE( Qt4 REQUIRED )
  
  # add some useful macros and variables
  # (QT_USE_FILE is a variable defined by FIND_PACKAGE( Qt4 ) that contains a path to CMake script)
  INCLUDE( ${QT_USE_FILE} )
  
  # this command will generate rules that will run rcc on all files from SAMPLE_RCS
  # in result SAMPLE_RC_SRCS variable will contain paths to files produced by rcc
  QT4_ADD_RESOURCES( SAMPLE_RC_SRCS ${SAMPLE_RCS} )
  
  # this will run uic on .ui files:
  QT4_WRAP_UI( SAMPLE_UI_HDRS ${SAMPLE_UIS} )
  
  # and finally this will run moc:
  QT4_WRAP_CPP( SAMPLE_MOC_SRCS ${SAMPLE_MOC_HDRS} )
  
  # we need this to be able to include headers produced by uic in our code
  # (CMAKE_BINARY_DIR holds a path to the build directory, while INCLUDE_DIRECTORIES() works just like INCLUDEPATH from qmake)
  INCLUDE_DIRECTORIES( ${CMAKE_BINARY_DIR} )
  
  # here we instruct CMake to build "sample" executable from all of the source files
  ADD_EXECUTABLE( sample ${SAMPLE_SRCS} ${SAMPLE_MOC_SRCS} ${SAMPLE_RC_SRCS} ${SAMPLE_UI_HDRS} )
  
  # last thing we have to do is to tell CMake what libraries our executable needs,
  # luckily FIND_PACKAGE prepared QT_LIBRARIES variable for us:
  TARGET_LINK_LIBRARIES( sample ${QT_LIBRARIES} )

Instead of using QT_WRAP_CPP() and a list of headers that contain Q_OBJECT macro, you can use QT4_AUTOMOC() command, but I haven't investigated it yet.

Generating manpages and desktop entries

When distributing an application which runs on Unix platforms other than the Mac, it is useful for it to be displayed in the desktop menu. There is a standard by which all the main Unix desktops (KDE, GNOME and XFce4) find applications to display in their menus, which is through a desktop entry file. This file consists of the name of the application followed by .desktop, as in speedcrunch.desktop. You can find the standard at the freedesktop.org website.

Such a file can be written by hand. However, it is better for it to be generated at build time. This is because it can be written to include the exact file paths of the application itself and its resources, such as its icons. These may change, particularly as your application may be installed in the /usr/local/* directories if someone compiles it for him- or herself, or in the /usr/* directories when they install it from a RPM or DEB file.

First, write a shell script to generate the file (this example comes from the QTM blog management application, in which it is called qtm-desktop.sh):

  #!/bin/sh
  cat <<EOF
  [Desktop Entry]
  Version=1.0
  Type=Application
  Name=QTM
  GenericName=Blog editor
  Comment=Weblog management application
  TryExec=$1/bin/qtm
  Exec=$1/bin/qtm &
  Categories=Application;Network;X-MandrivaLinux-Internet-Other;
  Icon=$1/share/icons/qtm-logo1.png
  EOF

This script writes everything which appears before the EOF marker (which is the marker specified by the << symbol) to standard output. The $1 is the first command-line argument, which is the directory under which your application is to be installed, i.e. /usr or /usr/local. Standard output will be directed to the yourapp.desktop file. Please note that this standard exists only on Unix. We do not have to worry about going against the cross-platform nature of CMake by using a shell script for this purpose.

Then you need to tell CMake to build this file whenever you build your application:

  if( UNIX AND NOT APPLE )
    if( NOT DESKTOP_ENTRY )
      set( DESKTOP_ENTRY qtm.desktop )
    endif( NOT DESKTOP_ENTRY )
  
    add_custom_command( OUTPUT ${DESKTOP_ENTRY}
      COMMAND touch ${DESKTOP_ENTRY}
      COMMAND sh qtm-desktop.sh ${CMAKE_INSTALL_PREFIX} >${DESKTOP_ENTRY}
      DEPENDS qtm-desktop.sh
      COMMENT "Generating desktop entry file"
      )
    add_custom_target( DESKTOP_ENTRY_FILE ALL
      DEPENDS ${DESKTOP_ENTRY}
      )
  
    set( APP_ICON images/qtm-logo1.png )
  endif( UNIX AND NOT APPLE )

This code works only on Unix platforms other than Macs (however, we may need to rewrite this so that other Unix platforms, like Qtopia which is also Unix and not Apple, are excluded also). The first block sets the name of the desktop entry file if none has already been specified on the command line.

Then it sets up a custom command (see the CMake documentation for the full syntax of this command), which first "touches" the desktop entry file (which creates it if it is not there already, and clears it if it is), then runs the shell script, redirecting standard output to the file. It depends on the shell script, which means that if it is not present then the build fails.

Then we create a target based on this command, and add the target to the main target, i.e. our program. This code should be included in CMakeLists.txt where the source code files are specified (in the QTM CMakeLists.txt file, it appears at the end of that section).

The same method can be used for producing a manpage for your software. The Debian guidelines require the presence of a manpage, although the package will install without one. Manpages are written in the nroff markup language and usually stored in the directory /usr/share/man/man1, or man6 in the case of games; you can find a guide to writing them in [this PDF], with a Google HTML conversion [here]. They are stored compressed, either with gzip or bzip2. It may be more convenient to use a shell script to generate it, so that changeable material, such as dates and version numbers, can be added as needed.

Here is an example of a manpage generation script, again from the QTM project:

  #!/bin/sh
  cat <<EOF
  .TH qtm 1 "February 10, 2008" "version $1" "USER COMMANDS"
  .SH NAME
  qtm \- Desktop client for content management systems
  .SH SYNOPSIS
  .B qtm
  .SH DESCRIPTION
  QTM is a desktop client for content management systems such as Wordpress,
  Movable Type, Drupal and Textpattern and for online content platforms like
  wordpress.com and Squarespace (but not Blogger).  It uses XML-RPC to
  connect to and post entries to websites which use the standard APIs.
  .SH OPTIONS
  QTM does not offer command-line options of its own; at present one cannot
  open files from the command-line.  As a Qt application it supports Qt
  options; please see:
  
  http://doc.trolltech.com/4.2/qapplication.html#QApplication
  .SH AUTHOR
  Matthew Smith (indigojo (at) blogistan.co.uk)
  EOF

As with the desktop entry script above, it outputs whatever appears before the EOF token to standard output, redirected by the calling command. It substitutes the first argument, which is supposed to be the version number, for $1 in the first line of the nroff code.

The script is called in much the same way, by setting up a custom Make target in your CMake script:

  #Generate manpage
  
    if( NOT MANPAGE_DIRECTORY )
      set( MANPAGE_DIRECTORY /usr/share/man/man1 )
    endif( NOT MANPAGE_DIRECTORY)
  
    add_custom_command( OUTPUT qtm.1.gz
      COMMAND touch qtm.1
      COMMAND sh qtm-manpage.sh ${QTM_VERSION} >qtm.1
      COMMAND gzip -9 qtm.1
      DEPENDS qtm-manpage.sh
      COMMENT "Generating manpage"
      )
    add_custom_target( MANPAGE_FILE ALL
      DEPENDS qtm.1.gz
      )
  
    set_directory_properties( ADDITIONAL_MAKE_CLEAN_FILES qtm.1 qtm.1.gz )

(In the QTM example, this code appears within the same "IF( UNIX AND NOT APPLE )" code block as the desktop entry generation. There is no need for a manpage on Windows and the Mac.)

It first checks whether a manpage directory has already been set, as you may do from the command-line when running CMake; if not, it sets it to the usual Linux location. A variable has been used to specify the version number to the script, which you can set at the beginning of your CMake script and use it wherever you need a version number, for instance when building Mac bundles. As before, it adds a custom command, whose output - which should be called after your program's executable followed by .1.gz - becomes a dependency of a custom target, which appears afterwards. The third command gives the option -9 to gzip, the maximum compression factor, which is required by Debian for your package to be correct. At the end, it adds the generated nroff script and the compressed version to the list of files to be deleted when you call "make clean".

Some Linux distributions use bzip2 compression (PCLinuxOS does, which may also mean that Mandriva and Fedora/Red Hat do as well), and it may produce this automatically when you produce your package.

Both these files, the desktop entry and the manpage, should be added to your install targets. As with their generation, their install should be in an "if" block as they are only generated on non-Apple Unix OSs:

  if(UNIX AND NOT APPLE)
    install( FILES ${APP_ICON} DESTINATION share/icons )
    install( FILES qtm.1.gz DESTINATION ${MANPAGE_DIRECTORY} )
  endif(UNIX AND NOT APPLE)

Compilation

To compile your project, you have to configure it first by running cmake. It will create a CMakeCache.txt file with project's configuration, a Makefile and some other files that are used during build. An interesting feature is that CMake can build your project in a separate directory without polluting your sources with some intermediate files.

When you compile your project for the first time, you have to enter these commands (I assume that the current directory contains CMakeList.txt file):

  $ mkdir build
  $ cd build
  $ cmake ..
  $ make

Those ".." are a path to a directory with CMakeList.txt — you can create build directory wherever you want and you can name it whatever you want. For example you can create several build directories for different versions of your application (like "release" and "debug").

When you want to rebuild your project, all you need is:

  $ cd build
  $ make

make will run cmake when necessary.


Mac-related issues

It is also possible to use CMake to build Mac OS X "bundles" and universal binaries (bundles which can be run on Macs with both PowerPC and Intel Core series processors) out of Qt applications. The following example of a CMakeLists.txt file comes from the SpeedCrunch calculator application.

  PROJECT( speedcrunch )
  
  IF( APPLE )
    SET( PROGNAME Speedcrunch )
    SET( MACOSX_BUNDLE_ICON_FILE Speedcrunch.icns )
    SET( MACOSX_BUNDLE_SHORT_VERSION_STRING 0.7-beta2 )
    SET( MACOSX_BUNDLE_VERSION 0.7-beta2 )
    SET( MACOSX_BUNDLE_LONG_VERSION_STRING Version 0.7-beta2 )
    SET( CMAKE_OSX_ARCHITECTURES ppc;i386 ) #Comment out if not universal binary
  ELSE( APPLE )
    SET( PROGNAME speedcrunch )
  ENDIF( APPLE )

This tests to see whether the host system is an Apple Mac or not. If not, the variable PROGNAME — used by the author to hold the name of the resulting executable — is set to "speedcrunch". This is all lowercase, as it should be if the target is a common Unix-type OS which runs X11, because the name is likely to be typed into a terminal, and it is convenient for the name to be all-lowercase. On a Mac, the name will appear next to an icon in the Finder and then in the Mac menu bar, and if it is capitalised, it looks like a proper name and is thus more in line with the Mac's presentation style.

The next three lines set the version number, which appears in the Finder when you click the application's icon when in column mode, or when you activate the Info window for the application (by selecting "Get Info" from the File menu). Finally, the sixth "set" line tells CMake that you want to build a universal binary. Comment this out, with a # symbol at the beginning, if you do not want to do this.

  CMAKE_MINIMUM_REQUIRED( VERSION 2.4.0 )
  SET( CMAKE_COLOR_MAKEFILE ON )
  SET( CMAKE_VERBOSE_MAKEFILE ON )
  SET( CMAKE_INCLUDE_CURRENT_DIR TRUE )

The first of these lines is self-explanatory (modern Linux distributions are shipping version 2.4.3 of CMake and the optimum is 2.4.5, the most recent; note that 2.4.2 and 2.4.4 are buggy). The second specifies that the screen output when you enter "make" should consist of information in English telling you what the compiler is doing, rather than simply echoing what "make" is inputting to the compiler. Sample output:

  [ 19%] Generating moc_insertfunctiondlg.cxx
  [ 21%] Generating moc_insertvardlg.cxx
  [ 23%] Generating moc_keypad.cxx
  [ 26%] Generating moc_number.cxx
  /Users/indigojo_uk/speedcrunch/branches/speedcrunch/number.h:0: Warning: No relevant classes found. No output generated.
  [ 28%] Generating moc_result.cxx
  [ 30%] Generating moc_settings.cxx
  /Users/indigojo_uk/speedcrunch/branches/speedcrunch/settings.h:0: Warning: No relevant classes found. No output generated.
  [ 32%] Generating qrc_crunch.cxx
  Scanning dependencies of target Speedcrunch
  [ 34%] Building CXX object CMakeFiles/Speedcrunch.dir/aboutbox.o
  [ 36%] Building CXX object CMakeFiles/Speedcrunch.dir/configdlg.o

The third specifies that input to the compiler should be echoed; the fourth specifies that the current directory be added to the include file path.

After this follow the usual lines specifying the names of the source files, headers, forms and translations; these have been omitted for brevity's sake.

  # we still need those Qt3 classes
  SET( QT_USE_QT3SUPPORT TRUE )  
  
  IF( APPLE )
    IF( QT_USE_QT3SUPPORT )
      SET( QT_USE_QTSQL TRUE )
      SET( QT_USE_QTNETWORK TRUE )
      SET( QT_USE_QTXML TRUE )
    ENDIF( QT_USE_QT3SUPPORT )
  ENDIF( APPLE )

The author's experience is that, when building the SpeedCrunch universal binary, the compiler and linker could not resolve references in the Qt3Support module to the SQL, network and XML classes unless the frameworks had been specifically included in the Makefile. Hence, they need to be included. This appears not to have been a problem when building on Linux, hence the lines are only included on Apple. (The application itself does not use these modules, but they must be included as the Qt3Support module does.)

  # enable warnings
  ADD_DEFINITIONS( -Wall )
  
  # setup for Qt4
  FIND_PACKAGE( Qt4 REQUIRED )
  INCLUDE( ${QT_USE_FILE} )

See above for explanations of these lines.

  # build everything
  QT4_ADD_RESOURCES( speedcrunch_RESOURCES_SOURCES ${speedcrunch_RESOURCES} )
  QT4_WRAP_UI( speedcruch_FORMS_HEADERS ${speedcrunch_FORMS} )
  QT4_WRAP_CPP( speedcrunch_HEADERS_MOC ${speedcrunch_HEADERS} )
  INCLUDE_DIRECTORIES( ${CMAKE_BINARY_DIR} )
  IF( APPLE )
    ADD_EXECUTABLE( ${PROGNAME} MACOSX_BUNDLE ${speedcrunch_SOURCES} ${speedcrunch_HEADERS_MOC}
        ${speedcrunch_RESOURCES_SOURCES} ${speedcruch_FORMS_HEADERS} )
    ADD_CUSTOM_COMMAND( TARGET ${PROGNAME} POST_BUILD
      COMMAND mkdir ARGS ${CMAKE_CURRENT_BINARY_DIR}/${PROGNAME}.app/Contents/Resources
      COMMAND cp ARGS ${MACOSX_BUNDLE_ICON_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${PROGNAME}.app/Contents/Resources
      COMMAND cp ARGS *.qm ${CMAKE_CURRENT_BINARY_DIR}/${PROGNAME}.app/Contents/Resources 
  ELSE( APPLE )
    ADD_EXECUTABLE( ${PROGNAME} ${speedcrunch_SOURCES} ${speedcrunch_HEADERS_MOC} ${speedcrunch_RESOURCES_SOURCES} ${speedcrunch_FORMS_HEADERS} )
  ENDIF( APPLE )

The build process on the Mac needs to include the stage of copying translations, the icon file and other resources into the Resources directory in the bundle (here, Speedcrunch.app/Contents/Resources). GNU Make is instructed to create the directory and copy all the necessary files using common shell commands in the ADD_CUSTOM_COMMAND block. The syntax for ADD_CUSTOM_COMMAND, like all CMake directives, is to be found in the CMake documentation; POST_BUILD means that the command is executed after compiling and linking has taken place (other options are PRE_BUILD and PRE_LINK). Note that the author could have simply specified the names of the files and directories, but used the variables because it enhances the portability of the CMakeLists file.

Please note that a Qt/Mac application may not load the language you selected in System Preferences, as is normal on the Mac. It may be necessary to allow the user to select a language from within the application (see chapter 17 of Blanchette & Summerfield, 2006, pp371-376).

  TARGET_LINK_LIBRARIES( ${PROGNAME} ${QT_LIBRARIES} )

This is explained in section 2 above.

  # install executable and translation files
  # note: it will install to CMAKE_INSTALL_PREFIX, which can be set e.g
  #  cmake ../branches/0.7 -DCMAKE_INSTALL_PREFIX=/usr
  INSTALL( TARGETS ${PROGNAME} DESTINATION bin )
  INSTALL( FILES ${speedcrunch_TRANSLATIONS} DESTINATION share/crunch )

This establishes a "make install" target, copying the executable to the required binary directory and the translations to their own (normally /usr/share). Note that this is not needed when building Qt/Mac applications, which are not installed the way X11 apps are. They are typically distributed on disk images and installed by dragging and dropping the icon representing the application bundle to the Applications folder in the Mac Finder.

Windows-related issues

When building Qt applications on Windows using MinGW you get an .exe file with the standard icon. To get a custom icon embedded into the .exe file you need to add the following lines to your CMakeLists.txt file after the sources has been setup. The example is taken from the SpeedCrunch project.

  IF( MINGW )
    # resource compilation for MinGW
    ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/crunchico.o
                        COMMAND windres.exe -I${CMAKE_CURRENT_SOURCE_DIR} -i${CMAKE_CURRENT_SOURCE_DIR}/crunchico.rc 
                             -o ${CMAKE_CURRENT_BINARY_DIR}/crunchico.o )
    SET(speedcrunch_SOURCES ${speedcrunch_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/crunchico.o)
  ELSE( MINGW )
    SET(speedcrunch_SOURCES ${speedcrunch_SOURCES} crunchico.rc)
  ENDIF( MINGW )

This passes the Windows specific resource file (setup as described in the Qt documentation here) through the Windows resource compiler - creating an object file that can be linked together with the rest of the application. Kudos to Serhiy Kachanuk (sim-im) for showing me how to do it.

The ADD_EXECUTABLE line should also contain the WIN32 qualifier after your target's name:

  ADD_EXECUTABLE( target_name WIN32 source1 source2 ... )

If you leave this out, your program will run as if it were a console application, opening a "command prompt" window without a prompt if you run your application from the file manager, and not returning a prompt on opening the application if you run it from a prompt, and in both cases, if you close the command prompt window, it will suddenly terminate your application. Note that WIN32 will be ignored when building a program for a Unix platform, so it need not be isolated within an IF(MINGW) or similar block.

Further reading




jacek 02:32, 8 July 2006 (CEST)
e8johan 09:40, 20 Nov 2006 (Windows section only)
IndigoJo 19:55, 1 Jan 2007 (Mac section, desktop entry file and manpage info)
IndigoJo 19:49, 24 Mar 2008 (Windows section)