Why ?

Why did we ever write makefiles, CMakeLists.txt or configure IDE project settings? And why are we still doing it ?

Andrew Koenig once coined FTSE, the Fundamental theorem of software engineering:

"We can solve any problem by introducing an extra level of indirection."

Building applications is an extremely complex problem, and the layers are almost infinite: linker, assembler, compiler, frontend, build-system ( I.e. make ), meta-build-system ( I.e. cmake ) to quote only a few.

Tipi leverages all the fabulous work done in these layer to finally make building C++ a humane task.


  • C++ code expresses explicitely enough what is an application entrypoint and what is reusable library code.
  • There's no need to learn a new language to specify how to build.
  • We are tired of specifying each single file that should be built.

Convention by example

Take the idea of building a game project. This game project will contain:

  1. the game domain with the player characters, game menu and so on
  2. the game app itself
  3. tools apps like game maps and texture editor

The directory could look like this

game directory tree as in tipi demo

game.cpp has a main function each, therefore they are apps by convention.

The main function may look like:

game main function

During the can of src/ tipi classifies the *.hpp and *.cpp which do not have any entrypoint to the library code ( i.e. the game domain).

In tools/ map_editor.cpp and texture_editor.cpp are found to both have main() functions, which has the apps convention kick-in.

The apps convention allows to have supporting file residing locally, therefore common.cpp is linked to map_editor and texture_editor. The header common.hpp is accessible via #include "common.hpp" or #include <common.hpp> while the classes in src/game_classes/* are exported on the compiler include dirs which makes them usable via #include <game_classes/*.hpp>.

tipi will give the following summary:

build summary

Resulting in a bin folder like the following:

  ├── game.exe                // game.cpp
  ├── demo.lib                // game_classes *.cpp
  └── tools
      ├── map_editor.exe      // common.cpp, map_editor.cpp
      └── texture_editor.exe  // common.cpp, texture_editor.cpp

Conventions Types

Main conventions:

All the conventions described are applied if required and/or possible based on tipi's smart detection

It is possible to specify or reduce the scope of a library within a project in case the detection fails or hinting is required:

  • by adding parameters: tipi -s library-dir
  • by adding dependency descriptors in the .tipi/deps file: "dependency/repo": { "s" : [ "library-dir" ] }

...with -s (or the s key) specifying the additional search path for source files. Multiple entries can be specified both a multiple -s parameters or multiple entries in the s key.

Split libraries

  ├── include
  │   └── header.hpp
  └── src
      └── impl.cpp

One typical kind of C++ project is when headers and implementation files are split in different folders. tipi will consider the include/ folder to be the publicly installable headers and src/ folder to contain the implementation files constituing the library.

tipi checks the presence of include/, inc/, src/ and sources/ to infer this convention.

Samedir libs

  └── src
      ├── header.hpp
      └── impl.cpp

Another typical convention C++ programmers use is having implementation and headers residing at the same level of the project's directory hierarchy.

tipi checks the presence of src/ and sources/ to infer this convention and the absence of main() functions in the files.

toplevel libs

These are libraries that don't have any special source folder, their headers are directly rooted at the top of their repositories.

When this is detected the same mechanism as for samedir libs applies.

In this kind of structure disambiguation it might be required to to tell which directories are part of the lib using the tipi -s switch or the matching .tipi/deps configuration.

headeronly libs

It is possible to have code which is completely header only while application entrypoints are in .cpp files aside materializing either lib examples or a corresponding app.

The headers will be put at disposal like in the aforementioned conventions with #include <>.


Any .cpp file with an entrypoint is considered to be an app.

For example any file containing a main() function or a macro instantiating a main() function (as commonly used in unit testing frameworks) will be compiled as an application.

apps are always linked to the project library.

if others .cpp files reside at same or deeper filesystem directories they get linked with the applications in question. Exception to this rule are those directories explicitely declared in the s/ -s configuration.

tests or examples

Equivalent to the apps convention, however they will registered within the CMake CTest test driver and calling tipi . --test all will run them all and report result status.

This convention kicks-in when files with main() functions in parent folder are named after one of test, tests, example, examples


Any .html file containing <script type="text/c++"></script> in it is compiled using the app convention.

When conventions are not enough

In some cases you may find that the convention based build doesn't suit your needs or fails on certain edge-cases.

In such a case please reach out using our community support forum or your premium support option.

Alternatively you can also try to tweak the build as explained in the chapters below. We'd really love to hear about your issues with the smart build algorithm still in order to improve tipi and find a way for the convention build to work, because we firmly believe that less CMakeLists is more time for your C++.

Override convention build for specific directories

Add an empty marker file use-cmake.tipi and a valid CMakeLists.txt to the folder you want to exclude from the convention based build.

This can be useful for custom test framework or in cases you need to use specific CMake features not yet supported by tipi.

Tweaking convention build

Tipi relies on CMake and the way me use it can be customized by adding CMakeLists.txt.tpl files in your project.

The CMakeLists.txt.tpl can be placed anywhere in the project and are applied to the matching sub-tree.

To generate a sample CMakeLists.txt.tpl with the docs embedded of the different variables at your disposal run tipi cmaketpl in any project folder.

Found an error or want to add more info? Write an issue or contribute changes to this documentation at tipi-build/docs on