about the style and (relative) verbosity of the CMakeLists.txt-files in kdelibs/

Alexander Neundorf neundorf at kde.org
Mon Feb 6 20:18:12 CET 2006


Hi,

I'd like to explain why the CMakeLists.txt-files in kdelibs/ look how they 
look. Some things are as they are because of cmake, most things are as they 
are because I decided to do it this way. So here we go (beware: this will be 
quite lengthy).

cmake itself is *not* a buildsystem, it is a generator for input files for 
various "native" buildsystems, like XCode, MSVC projects or makefiles. So it 
basically just translates an intermediate description into the actual build 
file. Writing a tool which would read another intermediate format, produce 
the input format for cmake, which will read these files and produce the 
actual build files wouldn't make sense. This would basically duplicate the 
functionality of cmake.
If you are writing files for cmake, imagine you are telling a person what he 
should put into your project description ("and now add an executable to my 
build" -> add_executable() ), and if you have told everything, the person 
will write a project file for *your* build tool.

The cmake files which are currently in kdelibs/ have been automatically 
translated by kdelibs/cmake/am2cmake and only been partially be edited.

Here's an example, kdeui/CMakeLists.txt:

-------8<------------8<--------------8<----------------------

project(libkdeui)

include_directories( ${CMAKE_SOURCE_DIR}/kdefx ${CMAKE_SOURCE_DIR}/interfaces 
${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ${CMAKE_CURRENT_SOURCE_DIR} 
${CMAKE_CURRENT_BINARY_DIR}  )


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

set(kdeui_LIB_SRCS
ksharedpixmap.cpp
kpixmapio.cpp
kmenu.cpp
ktoolbar.cpp
kaction.cpp
...long list of files skipped
kconfigdialogmanager.cpp
knotification.cpp
knotificationmanager.cpp
)

kde4_automoc(${kdeui_LIB_SRCS})

set( kdeui_UI
kspellui.ui
)

kde4_add_ui_files(kdeui_LIB_SRCS ${kdeui_UI} )

set( kdeui_UI3
kshortcutdialog_simple.ui3
kshortcutdialog_advanced.ui3
)

kde4_add_ui3_files(kdeui_LIB_SRCS ${kdeui_UI3} )

set( kdeui_DCOP_SKEL_SRCS
kmainwindowiface.h
knotificationmanager.h
)

kde4_add_dcop_skels(kdeui_LIB_SRCS ${kdeui_DCOP_SKEL_SRCS})

kde4_add_library(kdeui SHARED ${kdeui_LIB_SRCS})

target_link_libraries(kdeui  ${QT_AND_KDECORE_LIBS} ${QT_QTGUI_LIBRARY} 
${QT_QTXML_LIBRARY} kdefx )

set_target_properties(kdeui PROPERTIES VERSION 4.2.0 SOVERSION 4 )
install_targets(${KDE4_LIB_INSTALL_DIR} kdeui )

-------8<------------8<--------------8<----------------------

This is what the script created. Without changing anything in the cmake 
"modules) (except renaming some variables), it could also look like this when 
created manually:

-------8<------------8<--------------8<----------------------

project(libkdeui)

set(kdeuiSources 
   ksharedpixmap.cpp kpixmapio.cpp kmenu.cpp ktoolbar.cpp kaction.cpp
...long list of files skipped
   kconfigdialogmanager.cpp knotification.cpp knotificationmanager.cpp
   )

kde4_automoc(${kdeuiSources})

kde4_add_ui_files(kdeuiSources kspellui.ui )

kde4_add_ui3_files(kdeuiSources kshortcutdialog_simple.ui3
                                kshortcutdialog_advanced.ui3 )

kde4_add_dcop_skels(kdeuiSources kmainwindowiface.h knotificationmanager.h )

kde4_add_library(kdeui SHARED ${kdeuiSources})

target_link_libraries(kdeui  ${QtAndKDECoreLibs} ${QtGuiLibrary} 
${QtXMLLibrary} kdefx )

set_target_properties(kdeui PROPERTIES VERSION 4.2.0 SOVERSION 4 )
install_targets(${KDE4LibInstallDir} kdeui )

-------8<------------8<--------------8<----------------------

The long "include_directories()" command at the beginning has been removed, 
since this is already done in the toplevel CMakeLists.txt, and this is 
"inherited" down to the subdirs. The almost-all-uppercase style of the 
variables is in no way mandatory. I did it this way, because that's the 
"traditional" cmake style.
A lot of "set()" commands has been removed and instead the filenames are given 
directly to the commands (except kdeuiSources, since this is used several 
times).
You probably notice that there are special commands for each type of file:

kde4_add_dcop_skels()
kde4_add_dcop_stubs()
kde4_add_kcfg()
kde4_add_ui_files()
kde4_add_ui3_files()

At this point I'd like to give you my goals when "designing" the cmake stuff 
for kdelibs:

******************************************
1) make everything as obvious as possible
******************************************

Sometimes this means that more has to be typed, but IMO this is worth it. 
After all, we are using long and descriptive names in our C++ code as well.  
Something like the "principle of least surprise", maybe also a bit RISC: not 
too many specialized functions, but only few functions, which have to be 
remembered and can then be combined. 

Or as Matthias Ettrich puts it:

   The Convenience Trap 
   
   It is a common misconception that the less code you need to achieve 
   something, the better the API. Keep in mind that code is written more than
   once but has to be understood over and over again. For example, 
      QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical,
                                 0, "volume");
    
   is much harder to read (and even to write) than 
      QSlider *slider = new QSlider(Qt::Vertical);
      slider->setRange(12, 18);
      slider->setPageStep(3);
      slider->setValue(13);
      slider->setObjectName("volume");


**********************************
2) be as consistent as possible
**********************************

I did not want to handle similar things in different ways.
I implemented these functions:

kde4_add_dcop_skels()
kde4_add_dcop_stubs()
kde4_add_kcfg()
kde4_add_ui_files()
kde4_add_ui3_files()

It would have been perfectly possible with cmake to just do the following:

set(kdeuiSources ksharedpixmap.cpp kpixmapio.cpp kmenu.cpp ktoolbar.cpp 
   ...long list of files skipped
    kconfigdialogmanager.cpp knotification.cpp knotificationmanager.cpp
    kspellui.ui kshortcutdialog_simple.ui3
    kshortcutdialog_advanced.ui3 kmainwindowiface.h knotificationmanager.h )

kde4_add_library(kdeui ${kdeuiSources} )

The *single* reason why I didn't do it this way is the handling of the dcop 
stubs and skeletons. 
The kde4_add_library() command has no way to know that I want to have dcop 
skeletons generated from kmainwindowiface.h knotificationmanager.h .
I do not want to duplicate the ugly hack to invent names of non-existant 
files, which never exist and which never will be created, as it is done in 
Makefile.am's. There I would have added kmainwindowiface.skel and 
knotificationmanager.skel to the list of sources. IMO this is ugly beyond 
believe. How is one expected to understand how this works ?

I don't mean to understand what to put there, that's quite simple:
1) take the header you want
2) replace the ".h" with ".skel"

But this is how it has to be handled:
1) go through the list of source files (because basically it *is* a list of 
source files)
2) *know* that if you come to file file whose ending is "skel", that it is not 
a real file and that it doesn't exist
3) what to do if the file would exist anyway ?
4) if it doesn't exist: take the basename and *hope* that there is a file 
"basename.h" in this directory
5) generate the rules for basename.h, basename.kidl and basename_skel.cpp

With an extra command the problematic steps 2, 3 and 4 can be avoided and the 
user (developer) doesn't have to invent virtual filenames:

kde4_add_dcop_skels(kdeuiSources kmainwindowiface.h knotificationmanager.h)

For the person who reads this it's quite obvious what will happen: the two 
real files mentioned there will be used to generated dcop skeletons.
1) the command is "kde4_add_dcop_skels()
2) give it the source headers 

Also two steps. And for the implementation:
1) go through the list of headers
2) generate the rules for basename.h, basename.kidl and basename_skel.cpp

So, IMO this is 
-more obvious for the user (developer)
-as easy to remember
-much less ugly

So, with this in mind that there will be special commands for dcop stubs and 
skeletons, I decided that for the sake of consistency I should add these kind 
of function for all file types, i.e. for ui-files, kcfg-files, etc. While 
this requires more typing, it is consistent, and the developer can follow the 
rule:
"If I have a non-C/C++-source file which serves as a source for generating 
something, there will be a "kde4_add_something( VAR files)" command"

This is one rule, and it is not hard to remember.


*********************************************
3) Try to stay as close to cmake as possible
*********************************************

cmake is documented, it has it's style, and I did not want to become different 
from cmake just to be different or to be a bit more equal to the KDE 
makefile.am syntax. 
If somebody knows cmake, he should be able to understand our cmake files 
without problems. Otherwise *we* would have to put more into documentation. 
If it is as close to cmake as possible, he can use his original cmake 
knowledge (the man page, the wiki, the book). This has also the advantage 
that the stuff we write for cmake can and will be incorporated into official 
cmake cvs without problems. If we become too different, e.g. our 
FindFoo.cmake modules might not get added to cmake cvs, and we will end up 
with a partial "fork".

That's why:
-most variables are mostly uppercase
-e.g. our customized command for creating an executable is named
kde4_add_executable() and takes the same parameters as the original cmake 
command: add_executable().

It would be perfectly possible with cmake to do the following:

----------8<-----------8<---------------

set(binPrograms kde-config)
set(kde-configSources kde-config.cpp)

set(binLibs kdecore)
set(kdecoreSources kurl.cpp ...)

kde4_do_magic_stuff()

----------8<-----------8<---------------

Somebody who knows cmake would not understand this. And I never liked this 
style with our makefile.am's.
After all, if you are writing programs, would you consider this good style ? :

string bin("blah");
string sources("foo.cpp");

int main()
{
   magic();
}

These would only work via side effects and global variables and you can only 
understand it with implicit knowledge. And this is what we do in Makefile.am, 
and I never liked it. I prefer the following style:

int main()
{
   add_binary("blah" "foo.cpp")
}

I don't really care whether I have to write

ADD_EXECUTABLE(blah foo.cpp)

or 

blah=Executable.new("blah")
blah.setSources("foo.cpp")


After all, cmake is not hard to learn. I received patches from various people 
only a few hours after they saw the files the first time. And these patches 
were standard cmake patches, not only very basic things. I want to say, 
writing a module to find a software package with cmake *is* trivial.
In the case we decide for cmake, we should *not* try to hide the original 
cmake behind our extensions. If we would do this, our developers would know 
only our extensions, and they would not necessarily "learn" how to use cmake 
without our extension. This would be a pity, since cmake is easy to use. For 
a simple application it's:

add_executable(hello hello.cpp)


Ok, I hope I didn't bore you too  much.
One thing I am considering to do is to move the kde4_automoc() call into the 
kde4_add_application/library/plugin/kdeinit_executable() calls.
This would save one line per file. But actually I don't think that's worth it. 
Remembering a "kde4_automoc(${mySources})" isn't hard.

Within the next days I'll put documentation about the kde4 cmake commands and 
variables in the cmake wiki, so that everything will be nicely documented :-)

Ok, I guess that's all, maybe you understand now better why the files look as 
they look today.

Bye
Alex

P.S. some people don't like the fact that cmake generates makefiles, because: 
-make has no progress indication
-it doesn't separate between compile and link jobs when running in parallel
With cmake we are not ultimately bound to make. Since cmake knows everything 
about how the software should be built, it should be not too hard to write a 
simple buildtool which has progress indication and separates between compile 
and link jobs, and which has e.g. xml-files as input format (basically 
makefiles on steroids), and of course a generator for cmake :-) 
-- 
Work: alexander.neundorf AT jenoptik.com - http://www.jenoptik-los.de
Home: neundorf AT kde.org                - http://www.kde.org
      alex AT neundorf.net               - http://www.neundorf.net


More information about the Kde-buildsystem mailing list