How do I get started on setting up my development platform?

To begin, make sure you have the following tools available on your development platform:

  • A C++ compiler with C++17 support. Clang or the GNU compiler (gcc) both work.
  • CMake build tool.
  • A build system. You can use GNU Make or Ninja .
  • A documentation generator. You can use Doxygen .

On a Ubuntu machine, they can be installed with:

    

        
        
            sudo apt-get install build-essential clang ninja-build cmake doxygen -y
        
    

In addition to these tools, you also need an IDE (Integrated Development Environment).

Visual Studio Code is a popular choice, and you can install it by following these instructions . Alternatively, you can use a text editor like Vim , GNU Emacs , or Sublime Text , which are also popular and they all support extensions that make C++ development easy.

What are the differences between configuring the project and building the code?

When developing software, there are two separate, but linked, stages:

  • Configuring the project.
  • Building the source code.

Configuring the project includes making decisions and preparatory work around:

  • Selecting a platform, such as Windows, macOS, or Linux - each platform has its own specific requirements and usage.
  • Discovering what is available on the platform - for example, libpng might be required by the project to process images in PNG format, but it might not be available on the platform. Alternatively, it might be available, but not with a suitable configuration for the project requirements. You may need to maintain a customized version of required dependencies.
  • Selecting project features - in some cases, projects might offer some degree of configuration, for example, disabling support for a feature that will never be used. This is done to avoid bloating the application with never-used code and functionality.

Building the source code includes:

  • Compiling the human-readable source code to produce binaries (executables and libraries) that can be run. This involves invoking tools like compilers and linkers.
  • Managing dependencies - this ensures that the different parts of the project are built in the correct order, and that when a rebuild is necessary, such as when a file changes, only the required parts of the project are rebuilt. It is an important optimization to save build time, especially for developers who spend most of their time in an edit-compile-run loop.

These two considerations are fundamental to the process of getting set up. Accordingly, tools are available to ease development and cover a wide variety of situations and platforms. The tool used for this project is CMake. CMake is available on all platforms and used by numerous projects, from very small projects to large projects like LLVM or Qt .

Directory structure

Organizing the files in a project is important because it allows you to:

  • Easily navigate the structure and find information.
  • Organize information for the tools, such as compilers and linkers.
  • Make a distinction between information that is exported or installed, and what is only relevant for building the project.
  • Accommodate for future growth - over time, features are added to the project, which usually translates to more source files. Having an organized directory structure improves the efficiency of the project.

The directory structure of this project is:

    

        
        
            Matrix/
├── build/
├── include/
│   └── Matrix/
├── lib/
│   └── Matrix/
└── src/
        
    

CMake recommends that you build projects outside of the source tree for the following reasons:

  • Reduces clutter in the source tree, which is often under version control.
  • Provides an easy way to remove the build artifacts, such as object files, libraries, and executables.
  • Allows several build trees to co-exist. For example, to have debug and optimized builds, or builds with differing configuration options.

In this case, the project is built in the build/ directory.

Note

If the project is under version control, then the build/ directory can be ignored as the contents can always be regenerated. If you are using git, the way to configure git to ignore the build/ directory is to add it to a .gitignore file. Other revision control systems have a similar way to ignore files and directories.

You will also note that the Matrix library headers are located in a Matrix/ directory. It is highly probable that some of the header files (Matrix.h for example) will collide with some other file for some users. In order to be sure that compilers pick the right files, all the headers are placed in a Matrix/ directory, effectively providing some form of namespace to the include file look-up by the tools.

The source code for the Matrix library will live in lib/Matrix and the applications’ source code will be located in src/.

Add a demo application

There is nothing like creating the canonical Hello, World! application!

Use your favorite text editor or IDE to create the file src/howdy.cpp and add the following content:

    

        
        
            #include <cstdlib>
#include <iostream>

using namespace std;

int main(int argc, char *argv[]) {
    cout << "Hello, World !\n";

    return EXIT_SUCCESS;
}
        
    

Setup CMake

CMake reads a file named CMakeLists.txt to get the project description and all of its instructions.

At the top of your project, create file CMakeLists.txt and add the following content to it, using your text editor:

    

        
        
            cmake_minimum_required(VERSION 3.5)

project(Matrix LANGUAGES CXX)

add_executable(howdy src/howdy.cpp)
        
    

The cmake_minimum_required command at the first line tells CMake that the project requires at least version 3.5. This is an old version because your Matrix project is not using any new commands or options from CMake. cmake_minimum_required is required, as it helps diagnose version mismatches.

The second command, project is also required by CMake: it gives a name (Matrix) to the project and specifies which language is used (C++).

The last command, add_executable instructs CMake to build an executable named howdy from source file src/howdy.cpp.

From this minimalistic project description, you are ready to configure and build your Matrix project.

Configure and build the application

At this stage, your project contains the following directories and files:

    

        
        
            Matrix/
├── CMakeLists.txt
├── include/
│   └── Matrix/
├── lib/
│   └── Matrix/
└── src/
    └── howdy.cpp
        
    

Copy and paste the command below to configure your project, using clang++ as the C++ compiler, Ninja as the build system, build as the out-of-tree build directory, and the current directory (.) as the project source directory:

    

        
        CXX=clang++ cmake -G Ninja -B build -S .
__output__-- The CXX compiler identification is AppleClang 15.0.0.15000100
__output__-- Detecting CXX compiler ABI info
__output__-- Detecting CXX compiler ABI info - done
__output__-- Check for working CXX compiler: /opt/homebrew/opt/ccache/libexec/clang++ - skipped
__output__-- Detecting CXX compile features
__output__-- Detecting CXX compile features - done
__output__-- Configuring done (0.9s)
__output__-- Generating done (0.0s)
__output__-- Build files have been written to: .../chapter-1/build

        
    

To use the default build system, Unix Makefiles, you can just omit the -G Ninja from the command line.

Now that your project is configured, build it with:

    

        
        cd build/
ninja
__output__[2/2] Linking CXX executable howdy

        
    

Execute the howdy application with:

    

        
        ./howdy
__output__Hello, World !

        
    

Depending on your platform, the application might be named slightly differently. On Windows, it would be howdy.exe, so to execute it you would instead have to type:

    

        
        .\howdy.exe
__output__Hello, World !

        
    
Note

You can remove the build directory at any point without any fear of losing important data, as you can always recreate those files with the configure-and-build steps described above.

Add the Matrix library foundations

You will now add the Matrix library foundations: a header file, source code, build instructions, and another example program using the library to check that everything is working.

The simplest function you can add at this stage is one that will return the library version.

Add the Matrix.h header file, declaring the Version object and the getVersion function and save the file as include/Matrix/Matrix.h:

    

        
        
            #pragma once

namespace MatComp {

/// The Version struct is used to carry around the major, minor and patch level.
struct Version {
    unsigned major; //< The major version level.
    unsigned minor; //< The minor version level.
    unsigned patch; //< The patch level.
};

/// Get the Matrix library version information.
const Version &getVersion();

} // namespace MatComp
        
    

With those declarations in place, create and add the following lines to lib/Matrix/Matrix.cpp to provide an implementation to getVersion:

    

        
        
            #include "Matrix/Matrix.h"

namespace {
const MatComp::Version version = {.major = 0, .minor = 1, .patch = 0};
}

namespace MatComp {

const Version &getVersion() { return version; }

} // namespace MatComp
        
    

Now, you can create a program that will make use of the getVersion function. Use your editor to save the code below as src/getVersion.cpp:

    

        
        
            #include "Matrix/Matrix.h"

#include <cstdlib>
#include <iostream>

using namespace std;
using namespace MatComp;

int main(int argc, char *argv[]) {
    const Version &version = getVersion();
    cout << "Using Matrix version: " << version.major << '.' << version.minor
         << '.' << version.patch << '\n';

    return EXIT_SUCCESS;
}
        
    

Finally, add the instructions below in the top-level CMakeLists.txt:

    

        
        
            # Set the minimum CMake version we require. In our case, it is intentionnally
# very old as we are not making use of recent CMake features.
cmake_minimum_required(VERSION 3.5)

# Give a name to our project ('Matrix') and inform CMake about the language used.
project(Matrix LANGUAGES CXX)

# Add 'howdy', a standalone executable with no dependency to any library that
# has to be built from the sources in 'src/howdy.cpp'.
add_executable(howdy src/howdy.cpp)

# Add our 'Matrix' library, that is built as a static library, from source file
# 'lib/Matrix/Matrix.cpp'. CMake is instruction that C++17 is used, and that
# the library headers can be found in ${CMAKE_SOURCE_DIR}/include.
add_library(Matrix STATIC lib/Matrix/Matrix.cpp)
target_compile_features(Matrix PUBLIC cxx_std_17)
target_include_directories(Matrix
  PUBLIC ${CMAKE_SOURCE_DIR}/include)

# Add 'matrix-getVersion', an executable that depends on the Matrix library,
# that has to be built from source file 'src/getVersion.cpp'.
add_executable(matrix-getVersion src/getVersion.cpp)
target_link_libraries(matrix-getVersion Matrix)
        
    

The add_library instructs CMake how to build the Matrix library. The target_include_directories specifies where the Matrix library header is located, and the target_compile_features specifies that C++17 is the version of the C++ language that is used by the Matrix library. The matrix-getVersion executable is compiled from the src/getVersion.cpp source file with the add_executable command and has to be linked with our Matrix library with the target_link_library command.

Now build the program with:

    

        
        ninja
__output__[6/6] Linking CXX executable matrix-getVersion

        
    

and run it with:

    

        
        ./matrix-getVersion
__output__Using Matrix version: 0.1.0

        
    

Congratulations, you have constructed a library and a program to test it!

What have you achieved so far ?

At this stage, your Matrix project has the following directory structure, which is slightly different depending on whether it is Windows or Linux:

    

        
        
            Matrix/
├── CMakeLists.txt
├── build/
│   ├── CMakeCache.txt
│   ...
│   ├── howdy*             <- The howdy executable program
│   ├── libMatrix.a        <- The Matrix library
│   └── matrix-getVersion* <- The getVersion executable program
├── include/
│   └── Matrix/
│       └── Matrix.h
├── lib/
│   └── Matrix/
│       └── Matrix.cpp
└── src/
    ├── getVersion.cpp
    └── howdy.cpp
        
    

You have created the foundation for developing and evolving your Matrix library in a platform-agnostic way, meaning that it can be easily developed and used on macOS, Linux, and Windows. This was done almost effortlessly thanks to CMake, which shields developers from all the platform-specific details such as how to invoke the compiler, build libraries, and link with those libraries on each of those platforms.

In addition to concealing the platform-specific details, CMake also does not restrict project developers to one specific development environment. You can use your favorite editor or IDE. For example, Visual Studio Code can work seamlessly with CMake with plugins, and CMake can generate project files for several popular IDEs, such as Xcode, Sublime Text, Eclipse, CodeBlocks, and CodeLite. You can run cmake --help to get a list of supported generators (in CMake terminology) for your platform.

Back
Next