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