Saturday, June 8, 2013

Hello World From CMake!

I have recently been placed on a team which is developing an embedded application in C++.  To be honest I haven't touched C++ in years (as in not since college), and most of my career has been spent either in the JVM (predominantly Java and Groovy) or in the Ruby world.  Now I'm finding myself having to deal with things I've given scant thought to previously, such as pointers, destructors, operator implementations and other such nonsensical things that programmers as a whole shouldn't have to care about in this day and age.  In short I spent the last week getting my head handed to me and by a much more experienced developer (Who luckily is a great pair-partner who has been gracious and willing to share his knowledge with me.  Thank you Ben!), and so I decided it was time to bone up on my tech stack.

Aside from C++ I'm also picking up a new build tool; CMake.    CMake is certainly not a new tool, but this is the first time I've had to use it, and I thought I would share what I've learned about it so far.  As part of the learning process I have created a "Hello, World!" application using CMake, which I've made available at GitHub at https://github.com/jameskbride/cmake-hello-world .  This extremely simple application does exactly what you would expect, which is spitting out "Hello, world!" to command line, but it was a great opportunity to explore CMake.  So, lets get to it and dive into the CMake world!

Project Root CMakeLists.txt

CMake is primarily configured via 'CMakeLists.txt', which lives in the root of the project, as well as the root of every sub-project.  Lets take a look at the CMakeLists.txt file in the root of this project shall we?


cmake_minimum_required (VERSION 2.8)
project (CMakeHelloWorld)

#version number
set (CMakeHelloWorld_VERSION_MAJOR 1)
set (CMakeHelloWorld_VERSION_MINOR 0)

#include the subdirectory containing our libs
add_subdirectory (Hello)

#indicate the entry point for the executable
add_executable (CMakeHelloWorld Hello HelloWorld.cpp)

# Indicate which libraries to include during the link process.
target_link_libraries (CMakeHelloWorld Hello)

install (TARGETS CMakeHelloWorld DESTINATION bin)

As you can see this is a simple text file which contains CMake specific commands.  The first interesting bit here is line 2, which indicates the project name.:
project (CMakeHelloWorld)
Line 9 tells CMake where to find the header and .cpp files for the subproject.
add_subdirectory (Hello)

Line 12 indicates where the executable go resides, as well as the entry point for the application (HelloWorld.cpp).
add_executable (CMakeHelloWorld Hello HelloWorld.cpp)

I probably did not have to have the entry point in the root directory of the project, but I thought it would clearly separate it from the library code if I placed it there. Line 15 tells the linker which projects to use when wiring up the compiled units.
target_link_libraries (CMakeHelloWorld Hello)

Finally, line 17 tells CMake where to install the target project binaries.
install (TARGETS CMakeHelloWorld DESTINATION bin)

Subproject (Hello) CMakeLists.txt

In order to modularize the code I created a subdirectory which contains the Speaker.h and Speaker.cpp source files, which contain the logic for the class which actually produces the "Hello, World!" output. Here is the CMakeLists.txt file for that subproject:
add_library (Hello 
  Speaker.h
  Speaker.cpp)

install (TARGETS Hello DESTINATION bin)
install (FILES Speaker.h DESTINATION include)
Again, this is pretty straightforward. First we declare we are adding a library, "Hello", followed by a listing of the .h and .cpp files to be included as part of the build. We then define an install target for the subproject (Note: this does *not* create a new executable binary as part of the build.). Finally we define the list of header files to be included during the build.

Building the Project

During my first attempt to use CMake followed the instructions I found in various blogs which stated that I should simply go to the root of the project and execute
cmake . && make && make install
While this does build the project it has the undesirable effect of splattering build gore all over the walls of your project. Your directories will be littered with generated directories and files which you otherwise wouldn't care about. Luckily, the solution is simple. CMake allows you to point to where your CMakeLists.txt files live (the "cmake ." piece of the previous command). As such, creating a build directory, changing to it and executing
cmake  && make && make install
will execute the build and cause the resulting output to be dumped into your cleanly encapsulated build directory.

The build will now generate an executable file, CMakeHelloWorld, which can be called using the standard "./CMakeHelloWorld" command.

You now know as much as I currently know about CMake, so go forth and make! There will be more of these types of posts in the future, so I hope you found this informative and I hope you'll come back. For more detail be sure to check out the GitHub repo, and please feel free to leave feedback. Thanks!

2 comments:

  1. Oh goodness me, who said time travel was not possible :) Good work Jim, I enjoyed it!

    ReplyDelete
  2. Thanks Boyko! It's interesting, CMake has been around for a while but it definitely still in use. Stick around, I plan to do more of these.

    ReplyDelete