Simple C++ Project Setup With CMake
As a Java developer, I wanted to dabble with a C++ project, something I’ve not done for a while. I was looking into how to structure my project with a view to getting started quickly, but ensuring that if I wanted to expand the project, I wouldn’t have to restructure it in order to keep things organised. This post talks about that project and the source is available from my blog post code repository.
Coming from a Java background, we are pretty lucky that Java has had multiple build tools that can help manage Java projects such as organising class and test sources, handling dependencies, running tests and so on. It was not always this way, originally, there were IDE specific builders that put the source in their own structures and knew how to build them. This was problematic if you wanted to build outside of the IDE, and wouldn’t work in today’s world of continuous integration. Ant came along and let us write build scripts that could take source code of any structure and produce class, source and javadoc jar files for them, and IDEs could take advantage of such scripts.
With Maven, and later Gradle, we were able to create projects with a predefined structure that could be compiled easily just adding a class or test class file in a specific directory. IDEs were able to provide plugins to support these frameworks creating a fairly rich environment where it is easy to get started with Java.
With C++ (and of course plain old C), we don’t have the same solutions, historically, tools like Make have provided Ant-like build systems where you can put your source code where you like and write a script to find and build it. C++ has many more options with regards to compilation for things like build types (Release, Debug etc), linking dynamic & static libs and the target platform to name a few. Some of this complexity was been wrapped up in a tool called CMake which is a higher level abstraction of the build process. Technically, its not a build system, it creates a configuration from which the project can be built.
So I wanted to come up with a basic template for a C++ project that can be built with CMake, and used as the basis for other projects. I wanted to be able to compile and run from the command line and also be able to run from an IDE such as Eclipse and/or KDevelop. I also wanted it to be structured such that it wasn’t overly verbose for small projects but flexible enough that I can expand it later on and refactor as needed. Longer term I want to add testing in, but we’ll start without for now.
I went for a container project which contains sub projects for the main application, and a static linked library. The library could always be spun out as a dynamic library and moved into its own project.
Sample Project
Our project will have the following directory structure:
Sample Project
|
| -- calcApp
|
| -- calcLib
CMake works by having CMakeLists.txt
files in each folder of the project and sub projects. In the top level folder we put a CMakeLists.txt
file that is for the project, and it includes the two subdirectories below it. CMake will drill down, find the CMakeLists.txt
file in the subdirectories and build elements in those folders.
cmake_minimum_required(VERSION 3.5)
project(someProject)
add_subdirectory(calcLib)
add_subdirectory(calcApp)
The first line defines the minimum version of CMake required to build this project. The project() definition defines the project name. We then add the two subdirectories for our sub-projects.
calcLib
Our next step is to implement the calcLib
and set up the CMake files for it. We will have a simple Calc
class that just has a couple of methods.
C++ often has header files and source files separately, the header defines the interface to a method or class while the source implements it. Our header will go in the include directory of the lib, and goes in a subdirectory so we can make include file names more unique. The following is the content of calcLib/include/calc
:
#ifndef CALCLIB_INCLUDE_CALC_CALC_H_
#define CALCLIB_INCLUDE_CALC_CALC_H_
class Calc {
public:
int add(int a,int b);
int sub(int a,int b);
};
#endif /* CALCLIB_INCLUDE_CALC_CALC_H_ */
We can create an implementation of this in the calcLib/src/Calc.cpp
file:
#include "calc/Calc.h"
int Calc::add(int a, int b) {
return a+b;
}
int Calc::sub(int a, int b) {
return a-b;
}
We include our header file, and then proceed to complete the implementations of the interface defined in the header file. Note that we prefix our header file with calc
from the calc
subdirectory in our include directory.
Our last piece for the lib is the calcLib/CMakeLists.txt
:
add_library(calcLib src/Calc.cpp)
message("CalcLib current source dir = ${CMAKE_CURRENT_SOURCE_DIR}")
target_include_directories( calcLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
Since C++ lets us build executables or libraries we need to tell CMake what we want our target to be. add_library is used to define a library target, give it a name, and define the source files for it. In this case, we are creating a library called calcLib
and adding our src/Calc.cpp
source file.
The message command is used to log messages back to the user. CMake internal or user defined variables can be used inside the string to log them as part of the message. This isn’t needed, I just included to show you how to create output.
target_include_directories is used to define the include directory for this lib. Note that we use the target name calcLib
to attach the include directory to the target. Because of the nature of C++ with regards to libraries and linking there are a few options on this command to define the visibility of the includes. We have defined ours as PUBLIC for now.
calcApp
Now we have our library all completed, we can work on our main app in the calcApp directory. This is a simple main.cpp
class which prints a message and invokes our calculator to show it is all hooked up.
calcApp/main.cpp
:
#include <iostream>
#include "calc/Calc.h"
int main() {
Calc calc;
std::cout << "Hello World 12+7 = " << calc.add(12,7) << "\n";
}
Pretty simple stuff. We need to also add a CMakeLists.txt file which will define the executable and pull in our library.
calcApp/CMakeLists.txt
add_executable(calcApp main.cpp)
target_link_libraries(calcApp calcLib)
add_executable is like add_library, it lets us define an executable we want to create, gives it a target name and lets us specify the source files to be added to it.
target_link_libraries lets us link our library into the executable when it gets built. In this case we are doing a static link and the library will be pulled into the executable.
Building It All
That should be everything, and we should be able to build and run our app. You can build the project by opening a command line, going to the project root folder and typing:
mkdir build
cd build
cmake ..
make
That should build the project for you, and you can run the executable from calcApp/calApp
That about wraps it up, you can get the source code from my blog post code git repository under cmake-basic.
There are a lot of knobs to play with with regards to building C++ projects, and how they are structured. With Java, while we have the option of adding jars to executables at runtime through the class path, its often the case that we just build a jar containing all the code we need. Similarly, we could bind java libraries at run time, but often choose not to. With C++ here we have a basic outline of a project that should cover simple cases.
Next I’m going to be looking at incorporating a test framework to give us a real base project to work with.