To begin, make sure you have the following tools available on your development platform:
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.
When developing software, there are two separate, but linked, stages:
Configuring the project includes making decisions and preparatory work around:
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.Building the source code includes:
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 .
Organizing the files in a project is important because it allows you to:
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:
In this case, the project is built in the build/
directory.
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/
.
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;
}
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.
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 !
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.
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!
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.