2015-03-15

C++ project structure for CMake

Initial chalenges

I decided to create speech recognition library in C++ and I faced some challenges:
  • how C++ project structure should look
  • how to make cross-platform builds


I'm new to C++ so this simple task how to structure my project code wasn't straight forward. An idea how C++ project structure should look like I got looking at open source C++ libraries. Details what the idea is will be discussed in other sections.

For cross-platform builds I decided to use CMake which after some investigation looked to me quite widely used and well suited for my project. I basically will be using XCode as IDE and CMake can make XCode project from source code as well as Visual Studio project and so on.

Simple project structure

The main idea about project structure is that you have at least 2 folders include and src. Folders purpose is:
  • include - PUBLIC header files (.h files).
  • src - PRIVATE source files (.h and .m files).
  • test - tests files if you write tests (indefinitely you should).
  • libs - third party or your own libraries you depend on.
Headers files in include should be under folder named after your library domain. Reason behind this is that when you expose public header files you expose only include directory and when you #include files from library you do this #inlcude <HMM/Algorithm.h> instead of #include "Algorithm.h" if it was in root of include.

Idea example:
{library_name}
 ├── include
 │   └── {library_domain}
 ├── src
 ├── test
 └── libs

Concrete example:
HMM
 ├── include
 │   └── HMM
 │       └── Algorithm.h
 ├── src
 │   ├── Algorithm.cpp
 │   ├── FancyMath.h
 │   └── FancyMath.cpp
 └── test
     ├── AlgorithmTests.cpp
     └── FancyMathTests.cpp

In concrete example we have HMM library which exposed only Algorithm.h header file. FancyMath files are private and not exposed. Library domain is HMM, so when you want to include public header for example Algorithm.h you do it this way: #inlcude <HMM/Algorithm.h> instead of #include "Algorithm.h" .

Advanced project structure

When you have a very complex project with third party libraries dependencies you need a way to link it all together. For this purpose there is libs folder. You basically put there third party libraries which have project structure like in simple example above.

Concrete example:
Foo
 ├── include
 │   └── Foo
 │       ├── Foo.h
 │       └── ...
 ├── src
 │   ├── Foo.cpp
 │   └── ...
 ├── test
 │   └── ...
 └── libs
     ├── A
     │   ├── include
     │   │   └── A
     │   │       ├── A.h
     │   │       └── ...
     │   ├── src
     │   │   ├── A.cpp
     │   │   └── ...
     │   └── test
     │       └── ...
     └── B
         ├── include
         │   └── B
         │       ├── B.h
         │       └── ...
         ├── src
         │   ├── B.cpp
         │   └── ...
         └── test
             └── ...

CMake

At this point you should have good understanding how project structure should look like and so CMake comes to game. The building blocks of CMake are CMakeLists.txt files. You basically write those CMakeLists files and they define what should be included into generated build system. I won't go into details how CMake work, but instead I will note some thins that I came across. Link to Github repo with example is in Reference section, check it out!

PRIVATE/PUBLIC headers

As I mentioned in previous sections I wanted to import library with #include <HMM/Algorithm.h> instead of #include "Algorithm.h" when library is exposed to world. But when Algorithm.h is used internally I want to import it as #include "Algorithm.h", because it is my library scope. Thats defined in CMake as follows:
set(LIBRARY_NAME
    HMM
)
target_include_directories(${LIBRARY_NAME} PRIVATE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/${LIBRARY_NAME}>
    $<INSTALL_INTERFACE:include/${LIBRARY_NAME}>
)

target_include_directories(${LIBRARY_NAME} PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

This basically let me import Algorithm.h as #include "Algorithm.h" when I used it internally in my library and as #include <HMM/Algorithm.h> when library is exposed to someone else.

Google Test - GTest

I believe the biggest headache was with GTest, because I wanted that both static libraries A and B to have tests and when I added GTest library two times in A and B libraries I got error, because two identical GTest targets can't exist so I needed workaround. The solution was only add GTest once in Foo and check for existing GTest target in A and B libraries. This worked like a charm!

Project structure example
Foo
 ├── include
 ├── src
 ├── test
 └── libs
     ├── A
     ├── B
     └── gtest-1.7.0

GTest target check example
if (NOT (TARGET gtest AND TARGET gtest_main))
    message(FATAL_ERROR "gtest and gtest_main targets NOT found")
endif()
I wont go to details how to use GTest with CMake, you can check it in example project.

Reference

  1. CMake example. Github.

No comments:

Post a Comment