Skip to content

Building KDE 3 software using cmake

Friday, 13 January 2006  |  alexander neundorf

Some weeks ago I blogged how I managed to compile kpager using cmake. In the meantime the am2cmake script was a bit fine tuned and now even more works. The script and the KDE 3 support files for cmake have finally entered KDE svn, so you can get it from here: http://websvn.kde.org/trunk/KDE/kdesdk/cmake/ So here we go:

How to build an application:

Checkout kpager and run the am2cmake script, this will produce a CMakeLists.txt. Just run "cmake ." in this directory, followed by make, and it will produce a working kpager binary without any manual tweaking. More details about the generated CMakeLists.txt you can find here: http://blogs.kde.org/node/1668 So let's proceed with something new...

How to build an KDE ioslave and a KPart:

Checkout kdebase/kioslave/man, the man-page ioslave. As before, run the am2cmake script and try to compile it. This time it won't work, it can't find config.h. This means, the man-ioslave needs a config.h with defines figured out by a configure script. kio_man contains ifdefs for HAVE_UNISTD_H and HAVE_STRING_H. Creating this file is the job of autoconf/configure when using the autotools. How to do this with cmake ? It's easy:

INCLUDE(CheckIncludeFiles)

CHECK_INCLUDE_FILES( unistd.h HAVE_UNISTD_H)
CHECK_INCLUDE_FILES( string.h HAVE_STRING_H)

CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/config.h.cmake ${CMAKE_BINARY_DIR}/config.h)

CheckIncludeFiles is a script file (in cmake language a module) which contains the function "CHECK_INCLUDE_FILES(filename outputVariable)". This function is called once to check for the existance of unistd.h and once for string.h, and the results are stored respectively on HAVE_UNISTD_H and HAVE_STRING_H. If they were found, they are set to "1", if not they are set to an invalid state. Finally the line CONFIGURE_FILE() creates a config.h from a config.h.cmake (similar as configure creates a config.h from a config.h.in). How does the config.h.cmake look?

#ifndef CONFIG_H
#define CONFIG_H

#cmakedefine HAVE_UNISTD_H
#cmakedefine HAVE_STRING_H

#endif

The CONFIGURE_FILE() command will read this file and replace "#cmakedefine HAVE_UNISTD_H" with "#define HAVE_UNISTD_H 1" if unistd.h was found, or "#undef HAVE_UNISTD_H" if not (and respectively for string.h).

Now run make again and it should compile without problems. After "make install" as root you will have a cmake-compiled version of the man-ioslave on your system :-) This means, if everything was installed in the correct directory. By default cmake uses /usr/local as install prefix, just as the autotools do. To change this, start "ccmake ." (yes, double c make). This opens a ncurses "GUI" where you can adjust the cmake variables, among them CMAKE_INSTALL_PREFIX. Set this to the directory where your KDE is installed.

Ok, and now let's have a look at the generated CMakeLists.txt:

FIND_PACKAGE(KDE3 REQUIRED)

SET(CMAKE_VERBOSE_MAKEFILE ON)

ADD_DEFINITIONS(${QT_DEFINITIONS} ${KDE3_DEFINITIONS})

LINK_DIRECTORIES(${KDE3_LIB_DIR})

INCLUDE_DIRECTORIES( ${KDE3_INCLUDE_DIR} ${QT_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}  )

The part upto here has changed slightly since the last version, now the FindKDE3.cmake module doesn't actively set the values, it justs sets the variables, which can then be used with ADD_DEFINITIONS(), INCLUDE_DIRECTORIES() and LINK_DIRECTORIES(). This is more conform to standard cmake-behaviour.

INCLUDE(CheckIncludeFiles) # module for testing for headers coming with cmake

CHECK_INCLUDE_FILES( unistd.h HAVE_UNISTD_H)
CHECK_INCLUDE_FILES( string.h HAVE_STRING_H)

CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/config.h.cmake ${CMAKE_BINARY_DIR}/config.h)

These lines were added manually, configure checks are not converted by am2cmake.

########### next target ###############

SET(kio_man_test_SRCS
kio_man_test.cpp
)

KDE3_AUTOMOC(${kio_man_test_SRCS})

IF(KDE3_BUILD_TESTS)
KDE3_ADD_EXECUTABLE(kio_man_test ${kio_man_test_SRCS})

TARGET_LINK_LIBRARIES(kio_man_test  ${QT_AND_KDECORE_LIBS} )

ENDIF(KDE3_BUILD_TESTS)

########### next target ###############

SET(man2html_SRCS
dummy.cpp
)

KDE3_AUTOMOC(${man2html_SRCS})

FILE(WRITE dummy.cpp "//autogenerated file by cmake\n")
IF(KDE3_BUILD_TESTS)
KDE3_ADD_EXECUTABLE(man2html ${man2html_SRCS})

TARGET_LINK_LIBRARIES(man2html  ${QT_AND_KDECORE_LIBS} )

ENDIF(KDE3_BUILD_TESTS)

This Makefile.am contained two test binaries, this is translated correctly to cmake. They will only be built if the cmake option "KDE3_BUILD_TESTS" is enabled. This can be done with ccmake.

########### next target ###############

SET(kio_man_PART_SRCS
man2html.cpp
kio_man.cpp
)

KDE3_AUTOMOC(${kio_man_PART_SRCS})

KDE3_ADD_KPART(kio_man ${kio_man_PART_SRCS})

TARGET_LINK_LIBRARIES(kio_man  ${QT_AND_KDECORE_LIBS} )

INSTALL_TARGETS(/lib/kde3 kio_man )

Here comes the actual man-ioslave. At first the source files are listed, then they are "automoced". The KDE3_ADD_KPART() creates the loadable plugin named kio_man.so. Maybe the name "KDE3_ADD_KPART" could be changed to something like "KDE3_ADD_PLUGIN" or something like that, not sure. Then INSTALL_TARGETS() creates the install rule for the target "kio_man", i.e. kio_man.so will be installed to ${CMAKE_INSTALL_PREFIX}/lib/kde3. What you can't see in this snippet, is that KDE3_ADD_KPART() also creates an appropriate .la file together with the accompanying install rule. AFAIK this is required for the dynamic loading by KDE.

########### next target ###############

SET(kmanpart_PART_SRCS
kmanpart.cpp
)

KDE3_AUTOMOC(${kmanpart_PART_SRCS})

KDE3_ADD_KPART(kmanpart WITH_PREFIX ${kmanpart_PART_SRCS})

TARGET_LINK_LIBRARIES(kmanpart  ${QT_AND_KDECORE_LIBS} kparts )

INSTALL_TARGETS(/lib/kde3 kmanpart )

And the next target, this time a real KPart, which can be used to view man-pages directly within konqueror (i.e. not via the man-ioslave). As you can see here KDE3_ADD_KPART() is called with the special parameter "WITH_PREFIX", which has the effect that the resulting file will be named "libkmanpart.so", just as it was in the original Makefile.am The rest is basically the same, additionally it links to libkparts.

########### install files ###############

INSTALL_FILES( /share/apps/kio_man FILES kio_man.css )
INSTALL_FILES( /share/services FILES man.protocol kmanpart.desktop )

KDE3_PLACEHOLDER()

And here you can see the autmatically converted install rules for the remaining files.
IMO this is definitely more obvious than the syntax we had in the Makefile.am's. Compare e.g. the lines above to the following lines from the Makefile.am:

kdelnk_DATA = man.protocol kmanpart.desktop
kdelnkdir = $(kde_servicesdir)

Do these two lines tell you what they do ? As Matthias puts it: "An API is intuitive [...] if a programmer who doesn't know the API can understand code written using it."

How to build a KDEInit-enabled application

Now let's try to build another application, kcalc. Check it out, run am2cmake, cmake and make. It will fail. Again, a config.h is missing, so we have to add the required configure checks:

INCLUDE(CheckIncludeFiles)  

CHECK_INCLUDE_FILES( unistd.h HAVE_UNISTD_H)
CHECK_INCLUDE_FILES( string.h HAVE_STRING_H)
CHECK_INCLUDE_FILES( ieeefp.h HAVE_IEEEFP_H)

INCLUDE(CheckFunctionExists) 

CHECK_FUNCTION_EXISTS( isinf HAVE_FUNC_ISINF)

CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/config.h.cmake ${CMAKE_BINARY_DIR}/config.h)

You know already most of it from the man-ioslave. Additionally here a test for the function isinf() was required, this is done similar to the header check using the CHECK_FUNCTION_EXIST() function. I guess you already know how config.h.cmake looks now:

#ifndef CONFIG_H
#define CONFIG_H

#cmakedefine HAVE_UNISTD_H
#cmakedefine HAVE_STRING_H
#cmakedefine HAVE_FUNC_ISINF

#endif

But it still won't compile, some include directories have to be added and some libs to link to. Once this is done, it compiles cleanly. Here kcalc/CMakeLists.txt:

FIND_PACKAGE(KDE3 REQUIRED)

SET(CMAKE_VERBOSE_MAKEFILE ON)

ADD_DEFINITIONS(${QT_DEFINITIONS} ${KDE3_DEFINITIONS})

LINK_DIRECTORIES(${KDE3_LIB_DIR})

ADD_SUBDIRECTORY( knumber )

INCLUDE_DIRECTORIES( ${KDE3_INCLUDE_DIR} ${QT_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}  )

INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/knumber ) # added manually

Nothing fancy up to here, the above line had to be added manually, and the configure checks below too.

# configure checks, added manually

INCLUDE(CheckIncludeFiles)  #module to test for headers, coming with cmake

CHECK_INCLUDE_FILES( unistd.h HAVE_UNISTD_H)
CHECK_INCLUDE_FILES( string.h HAVE_STRING_H)
CHECK_INCLUDE_FILES( ieeefp.h HAVE_IEEEFP_H)

INCLUDE(CheckFunctionExists) #module to test for existence of functions, coming with cmake

CHECK_FUNCTION_EXISTS( isinf HAVE_FUNC_ISINF)

CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/config.h.cmake ${CMAKE_BINARY_DIR}/config.h)

Now comes the first target, kcalc as a KDEInit Loadable Module, short KLM (i.e.an application which can be started by forking from kdeinit)


########### next target ###############

SET(kcalc_KDEINIT_SRCS
kcalc.cpp
kcalc_button.cpp
kcalc_const_button.cpp
kcalc_const_menu.cpp
kcalc_core.cpp
kcalcdisplay.cpp
dlabel.cpp
stats.cpp
)

KDE3_AUTOMOC(${kcalc_KDEINIT_SRCS})

SET( kcalc_UI
colors.ui
general.ui
constants.ui
)

KDE3_ADD_UI_FILES(kcalc_KDEINIT_SRCS ${kcalc_UI} )

SET( kcalc_KCFG_SRCS
kcalc_settings.kcfgc
)

KDE3_ADD_KCFG_FILES(kcalc_KDEINIT_SRCS ${kcalc_KCFG_SRCS})

KDE3_ADD_KLM( kcalc ${kcalc_KDEINIT_SRCS})

# gmp, knumber and kdeui added manually
TARGET_LINK_LIBRARIES(kdeinit_kcalc  ${QT_AND_KDECORE_LIBS} kdeui knumber gmp)
INSTALL_TARGETS(/lib kdeinit_kcalc )

TARGET_LINK_LIBRARIES( kcalc kdeinit_kcalc )
INSTALL_TARGETS(/bin kcalc )

Here is some new stuff. kcalc uses some designer ui files. These are added to the target using KDE3_ADD_UI_FILES(). kcalc also uses kconfig_compiler. The kcfgc file is processed using KDE3_ADD_KCFGC_FILES(). Finally the KLM is created using KDE3_ADD_KLM(). This command creates both a KDE loadable module and a normal executable. Notable is the command TARGET_LINK_LIBRARIES(). am2cmake didn't recognize that kcalc links to libkdeui.so, libgmp.so and the static library libknumber.a. As you can see it is enough to specify the actual library name, i.e. "knumber" in the link command. This is enough for cmake, since libknumber.a is part of this project, cmake knows where to find and how to link to it. The other lines are basically only install instructions.



########### install files ###############

INSTALL_FILES( /share/applications/kde FILES kcalc.desktop )
INSTALL_FILES(  /share/config.kcfg FILES kcalc.kcfg )
INSTALL_FILES( /share/apps/kcalc FILES kcalcui.rc )
INSTALL_FILES( /share/apps/kconf_update FILES kcalcrc.upd )

KDE3_INSTALL_ICONS( hicolor )

And now a short look at kcalc/knumber/CMakeLists.txt:

ADD_SUBDIRECTORY( tests )

INCLUDE_DIRECTORIES( ${KDE3_INCLUDE_DIR} ${QT_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}/.. ) # added manually

ADD_DEFINITIONS(-D_GNU_SOURCE -D_ISOC99_SOURCE ) # added manually

########### next target ###############

SET(knumber_STAT_SRCS
knumber.cpp
knumber_priv.cpp
)

KDE3_AUTOMOC(${knumber_STAT_SRCS})

ADD_LIBRARY(knumber STATIC ${knumber_STAT_SRCS})


########### install files ###############


KDE3_PLACEHOLDER()

Nothing very exciting here. Here a static library is built (the "STATIC" keyword in the ADD_LIBRARY() command). The two lines marked as such had to be added manually.

So, that's it now for, more news later :-)

Alex