OK, earlier in the year I was complaining about how I need to write a versioning scheme for my little project. In truth I was indirectly whining about the autotools suite. It’s a terribly confusing piece of software, what with the shell magick and the M4 macro language. I’ve been using it for years for compiling stuff as a user (./configure && make && make install) and as a developer, to compile personal and work projects. It’s an amazingly complete and mature piece of software. The downside of course, is it’s complexity. After years of using it, I can hack it and make things go but I yearn for a better alternative.
In comes CMake.
I’ve read about it a while ago. The problem, I thought, was the fact that CMake is not a native build system. It processes it’s config files and generates a set of build files. On Unix variants, it’s Makefiles. On Windows, it’s Microsoft Visual C, on OS X it’s XCode project files. Anyway, I was concerned that adding another layer of indirection will cause trouble. That made me uncomfortable and I decided to pass on CMake and continue with autotools.
That is, until recently when it was announced that KDE project has moved from autotools to CMake. At that point, autotools has made me angry enough to give CMake a try. I figured, if it’s good enough for a huge project like KDE it should be enough for my 1,000 line game.
I gave it a try and was pleasantly surprised. The syntax is remeniscent of Makefiles, but without any of it’s idiosynchracies. Nothing is case sensitive and doesn’t require weird tab indentation. Furthermore, Makefiles generated by CMake include eye candy (colors) and progress-meters. Bonus! Finally, there are nice things like pre/post-build hooks which means my versioning script actually works nice.
A few things must be said about the downsides. As I mentioned earlier, CMake doesn’t build anything on it’s own, it just generates files. I’m still concerned about this. The good thing is that CMake is heavily tested. Checkout the number of platforms their test distro runs on. Second downside is of course that you require CMake and Autotools (or whatever the native builder on your system is). I don’t know how much of a hassle this is going to be in the long run. On a debian system it was as easy as going apt-get install cmake. Looking at documentation, CMake only requires a C++ compiler, so it shouldn’t be too difficult to use it on a strange system..
Enough rambling, here’s an actual example from my project. I hope it’ll provide useful as a quick tutorial in CMake usage. It took me about 20 minutes to get this project to build under CMake. Using the autotools chain I spent a few hours. Sad.
My project is layed out in the following manner:
- ~/project/ contains the main CMakeLists.txt (like Makefile for CMake), READMEs and so on. It also contains src/ and tests/
- ~/project/src/ contains all the source code and another CMakeLists.txt which actually compiles the code and produces the main executable
- ~/projects/tests/ contains unit tests and another CMakeLists.txt which compiles unit tests
So the organization of the project is rather simple (and mirrors a few I’ve seen in the wild). Main CMakeLists.txt invokes MakeLists.txt in src/ and tests/.
project/CMakeLists.txt:
1 project(reversi) 2 add_subdirectory(src) 3 add_subdirectory(tests) 4 enable_testing()
- Line 1 specifies the name of the project
- Line 2,3 say that src/ and tests/ need to be processed for
CMakeLists.txt - Line 4 specifies that there are unit tests available below this directory (more on this later)
Pretty simple and straightforward. Now to add more “meat” and actually compile some code:
src/CMakeLists.txt:
1 include_directories (.) 2 add_definitions(`sdl-config --cflags`) 3 link_libraries(SGE `sdl-config --libs` log4cxx dl) 4 add_executable (reversi analyzer.cc analyzer.h board.cc board.h defs.h main.cc version.h) 5 add_custom_command(TARGET reversi PRE_BUILD COMMAND ./bn.sh)
This is the real deal. It takes the source code and generates the executable:
- Line 1 includes current directory (think of it as -I.)
- Line 2 adds a parameter that will be appended to compiler command line during compilation
- Line 3 is pretty self explanatory. It’s like the -l gcc flag.
- Line 4 builds reversi executable from the specified sources
- Line 5 is cool. It says that a specified script must be executed before building target “reversi“.
Really simple.
Finally in tests/CMakeLists.txt:
1 include_directories (. ../src/)
2 link_libraries(boost_unit_test_framework log4cxx)
3 add_definitions(-Wall -W -Wno-sign-compare -Wno-ctor-dtor-privacy -ggdb)
4 SET(COMMON ../src/board.h ../src/board.cc ../src/analyzer.cc ../src/analyzer.h ../src/defs.h)
5 add_executable(01_board ${COMMON} 01_board.cc)
6 add_executable(02_analyzer ${COMMON} 02_analyzer.cc)
7 add_executable(03_recursor ${COMMON} 03_recursor.cc)
8 add_executable(04_iterator_new ${COMMON} 04_iterator_new.cc)
9 add_executable(05_multi_flip ${COMMON} 05_multi_flip.cc)
10 ENABLE_TESTING()
11 #ADD_TEST(01_board 01_board)
12 ADD_TEST(02_analyzer 02_analyzer)
13 ADD_TEST(03_recursor 03_recursor)
14 ADD_TEST(04_iterator_new 04_iterator_new)
15 ADD_TEST(05_multi_flip 05_multi_flip)
- Lines 1-9 are pretty selfexplanatory so I’m going to ignore them.
- Lines 10-15 are interesting on the other hand. They signal to CMake system that there are unit tests available. Now we can run “ctest” and we’ll know the status of our unit tests.
So, to run the whole thing:
Generate all the Makefiles:
dmitry@river:~/workspace/project1$ cmake . -- Configuring done -- Generating done -- Build files have been written to: /home/dmitry/workspace/project1
Build everything:
dmitry@river:~/workspace/project1$ make [ 5%] Building CXX object src/CMakeFiles/reversi.dir/analyzer.o [ 11%] Building CXX object src/CMakeFiles/reversi.dir/board.o [ 16%] Building CXX object src/CMakeFiles/reversi.dir/main.o /usr/include/SDL/sge_collision.h:49: warning: type attributes are honored only at type definition /usr/include/SDL/sge_shape.h:38: warning: type attributes are honored only at type definition Linking CXX executable reversi Version: 0.836-20070219170421 [ 16%] Built target reversi [ 22%] Building CXX object tests/CMakeFiles/01_board.dir/__/src/board.o [ 27%] Building CXX object tests/CMakeFiles/01_board.dir/__/src/analyzer.o [ 33%] Building CXX object tests/CMakeFiles/01_board.dir/01_board.o Linking CXX executable 01_board [ 33%] Built target 01_board [ 38%] Building CXX object tests/CMakeFiles/02_analyzer.dir/__/src/board.o [ 44%] Building CXX object tests/CMakeFiles/02_analyzer.dir/__/src/analyzer.o [ 50%] Building CXX object tests/CMakeFiles/02_analyzer.dir/02_analyzer.o Linking CXX executable 02_analyzer [ 50%] Built target 02_analyzer [ 55%] Building CXX object tests/CMakeFiles/03_recursor.dir/__/src/board.o [ 61%] Building CXX object tests/CMakeFiles/03_recursor.dir/__/src/analyzer.o [ 66%] Building CXX object tests/CMakeFiles/03_recursor.dir/03_recursor.o Linking CXX executable 03_recursor [ 66%] Built target 03_recursor [ 72%] Building CXX object tests/CMakeFiles/04_iterator_new.dir/__/src/board.o [ 77%] Building CXX object tests/CMakeFiles/04_iterator_new.dir/__/src/analyzer.o [ 83%] Building CXX object tests/CMakeFiles/04_iterator_new.dir/04_iterator_new.o Linking CXX executable 04_iterator_new [ 83%] Built target 04_iterator_new [ 88%] Building CXX object tests/CMakeFiles/05_multi_flip.dir/__/src/board.o [ 94%] Building CXX object tests/CMakeFiles/05_multi_flip.dir/__/src/analyzer.o [100%] Building CXX object tests/CMakeFiles/05_multi_flip.dir/05_multi_flip.o Linking CXX executable 05_multi_flip [100%] Built target 05_multi_flip
Run the unit tests:
dmitry@river:~/workspace/project1$ ctest . Start processing tests Test project /home/dmitry/workspace/project1 1/ 4 Testing 02_analyzer Passed 2/ 4 Testing 03_recursor Passed 3/ 4 Testing 04_iterator_new Passed 4/ 4 Testing 05_multi_flip Passed 100% tests passed, 0 tests failed out of 4
So, CMake is a pretty neat build system. I still have some reservations (how does it handle Java or other non C/++ language) but overall using CMake has been a positive experience. I wish I found out about this earlier.
Relevant links:
- How and why KDE switched to CMake
- CMake homepage
- Make alternatives, a fairly old article on freshmeat. Still relevant though.
- Reinventing the wheel, my own rambling post, about, well, nothing at all.
Comments 1
Nice introduction, straight to the point! I have one comment.
The first is that now link_libraries is deprecated, and target_link_libraries should be used instead (http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:target_link_libraries).
The second one is a note, that is, linker flags can be added to the target_link_libraries command. For example, my tests require the -static flag to be built with the boost “main” test function. Therefore, I used the command:
Posted 03 Mar 2010 at 11:43 ¶target_link_libraries ( payroll_test boost_unit_test_framework-mt -static )
where payroll_test is the name of the test executable.
Post a Comment