Search code examples
c++cmakebuildcliondirectory-structure

c++ CMake project structure for lib and executable


I have a question regarding how to structure a c++ project and build that using CMake (inside of CLion). I am very new to c++ (I started to learn the language only two weeks ago) and to CMake (I haven't ever configured a CMake file). I do however have a bunch of experience with other programming languages and their ecosystems like Java, JavaScript, Php, and .NET—perhaps analogies with those ecosystems could help?

We're building our own (small, 2d) game and game-engine for a school project. The game engine should be shippable on its own, the game should build on our own engine. The engine will have its own dependencies (built using SDL2, thats a requirement).

I imagine the engine will become a (static) library, while the actual game will compile to an executable that depends on the engine library.

For the purposes of simplifying project management we're looking to host both the engine and the game code in the same git repository.

Given that we might want to put a math module into our engine (probaby not the case, but for the purpose of this question) I imagine a folder structure like the following:

our-project/
├── README.md
├── engine
│   ├── src
│   │   ├── math.cpp
│   │   └── math.h
│   └── test
│       └── ...
└── game
    ├── src
    │   └── main.cpp
    └── test
        └── ...

The engine would then contain the following code:

// in math.h
namespace engine::math {
    int add(int a, int b);
}

// in math.cpp
#include "math.h"

namespace engine::math {
    int add(int a, int b) {
        return a + b;
    }
}

Let's say we wanted to make use of this engine code from our game, I imagne the following code:

// in game/main.cpp
#include <iostream>
#include "engine/math.h"

using namespace engine;

int main() {
    std::cout << math::add(10, 1) << std::endl;
}

Questions

  1. How do I setup CMake so that I can both build the engine (static library) individually, and build the game (executable) that depends on that same engine?
  2. How should I manage the engines header files? How do I make them accesible to the game code? Should I use a different structure for the header files?
  3. Is this folder structure manageable? Should I consider a different approach?

Solution

    1. You declare separate CMake targets, i.e., in engine/src/CMakeLists.txt:

      add_library(engine
          math.cpp)
      

      while in game/src/CMakeLists.txt, you have

      add_executable(my-game
         main.cpp)
      
      target_link_libraries(my-game
          PRIVATE
          engine)
      

      When using e.g. make, you can build the targets individually by

      make engine # build only the engine, not the executable
      make my-game # build engine if necessary, then build executable
      

      Separate test targets make sense, too.

    2. Include flags in CMake are propagated as follows.

      target_include_directories(engine
         INTERFACE
         ${CMAKE_CURRENT_SOURCE_DIR})
      

      The above setup propagates an include directory to all targets that link against engine. This is simple, but has a drawback: you can't distinguish between public headers and implementation details. As an alternative, you could have a directory engine/src/public/engine with all public headers that shall be used by the game:

      target_include_directories(engine
         INTERFACE
         ${CMAKE_CURRENT_SOURCE_DIR}/public)
      
      target_include_directories(engine
         PRIVATE
         ${CMAKE_CURRENT_SOURCE_DIR}/public/engine)
      

      This way, client code in game uses #include "engine/math.h", while in engine, you can just go with #include "math.h". I like such a setup, as it's easily visible what is the interface of a library and what is its implementation. But it's also somewhat a matter of taste.

    3. Again opinionated. But I think this is a good directory structure. Stick to it.