.. _build-system-cpp: ================ C++ Build System ================ .. tip:: You can :download:`download the entire example project in one archive `. Required Software ================= In order to use the described build system for C++ you need the following software on your computer: * C++ compiler like gcc or visual c++ * `CMake`_ * `Boost`_ libraries * `Doxygen`_ * :ref:`RSB ` as an exemplary dependency Brief Overview ============== We will demonstrate how to set up a C++ project using the build tool `CMake`_, which seems to be one of the most-used choices in the recent years for C++. `CMake`_ is based on a declarative scripting language, which makes it flexible to use and well-understandable for programmers accustomed to C-like programming languages. The most important files for CMake are called :file:`CMakeLists.txt`. These files contain the scripting commands to set up your build system. In this tutorial we will build a shared library with some reusable functions. The library will be installed in a way that :term:`downstream` projects can use it. Additionally, we will build an executable which uses the shared library. This executable will be installed so that it can be called from the command line. We assume basic knowledge of C++ and the general way how C++ code can be deployed. That means executable and shared and static libraries as well as the use of header files. Additionally, we assume some basic knowledge of `CMake`_. In cases you have never used `CMake`_ before, please read the first part of `How CMake simplifies the build process `_, which explains how to use the `CMake`_ command line and some very basics of the langauge. .. note:: The presented build system uses several `CMake`_ macros provided by the RSC library, which is a dependency of :ref:`RSB `. Hence, RSC is available for all projects using :ref:`RSB `. In cases where you want to create a project without :ref:`RSB ` dependency and you want to avoid the dependency on RSC you are free to copy and modify the RSC `CMake`_ macros according to the license terms of RSC. Folder Layout ============= The folder layout of our tutorial project will look as follows. All RSX projects use this structure. * **build-system-essentials-cpp**: top-level folder of our project * :file:`CMakeLists.txt`: Main `CMake`_ declarations and dependency handling. * :file:`README.txt`, :file:`COPYING.txt`, ...: Files with additional textual information like license, contact information etc. * :file:`build-system-essentials-cpp-config.cmake.in`: `CMake`_ configuration file for exposing shared libraries to :term:`downstream` projects. * :file:`build-system-essentials-version.cmake.in`: `CMake`_ configuration file for exposing the version of shared libraries to :term:`downstream` projects. * :file:`build-system-essentials-build-tree-settings.cmake.in`: `CMake`_ configuration to expose shared libraries to :term:`downstream` projects from the build tree of our own project. * **src**: Contains the source code of our project laid out with one directory corresponding to each C++ namespace. Header files (``h`` extension) and implementation files (``cpp`` extension) are mixed inside this tree. * :file:`CMakeLists.txt` `CMake`_ declarations to build the shared libraries and end executables of the project. * **build-system-essentials** * **library**: This subdirectory contains code from which a shared library will be created. * :file:`MagicNumberTransformer.h`: Example header file; part of the shared library. * :file:`MagicNumberTransformer.cpp`: Example implementation file; part of the shared library. * :file:`binary.cpp`: Example implementation file from which the executable program will be built. * **test**: Contains the unit tests for the project in a parallel hierarchy to the **src** folder * :file:`CMakeLists.txt` `CMake`_ declarations to build the unit tests of the project. Declaring a CMake Project and Finding Upstream Dependencies =========================================================== .. seealso:: `CMake online documentation `_ Comprehensive documentation of all CMake commands as well as links to tutorials and examples manpage :manpage:`cmake(1)` Manpage of CMake with the same content as the manual on the website We start by declaring the basic project in `CMake`_ in the top-level :file:`CMakeLists.txt`. The first two lines here are canonical and make explicit the expected version of CMake and declare the project name: .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::cmake-project-declaration :end-before: mark-end::cmake-project-declaration :linenos: The next thing we define is an explicit user option, whether to build the unit tests or not. Further options might be added here: .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::user-options :end-before: mark-end::user-options :linenos: .. note:: User options in `CMake`_ can only be boolean flags. If you need other data types, e.g. to gather a file system path, you can use variables stored in the `CMake`_ cache. Please refer to the `CMake`_ documentation how to use them. For all binaries we are going to create in our own project we want to set a fixed rpath (TODO link), which defines were the dynamic linker will automatically search for shared libraries when executing the binary. The setting here searches relative to the location of the binary in the filesystem. This setting usually does not cause any trouble but can help very often if software is installed in a single prefix (the UNIX way): .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::rpath :end-before: mark-end::rpath :linenos: One important thing to handle in the build system is finding :term:`upstream` dependencies of your own project. We recommend to perform the handling of these dependencies consistently in the main :file:`CMakeLists.txt` to easily have an overview of required software packages. In the case of the example project we search for :ref:`RSB ` and its :term:`upstream` dependency RSC: .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::upstream-dependencies :end-before: mark-end::upstream-dependencies :linenos: RSC and :ref:`RSB ` are essential dependencies, hence they are marked ``REQUIRED`` and the `CMake`_ configuration fails if either of them cannot be found. With ``INCLUDE_DIRECTORIES`` the header locations of RSC and :ref:`RSB ` are added to the compiler include paths. The last line makes the `CMake`_ modules provided by RSC available to our own build system. .. note:: By using ``BEFORE`` in the ``INCLUDE_DIRECTORIES`` command the include directories are passed to the compiler before any others and we make sure that the dependencies we find are used before any potentially matching ones that are accidentally on the compiler command line. ``SYSTEM`` tells the compiler that these are headers from our system installation and hence cannot be changed by us easily. Therefore, the compiler will not emit warnings for these headers and our build log will be much cleaner. .. note:: To learn more about how `CMake`_ actually finds dependencies, please refer to the `official documentation of find_package `_ and the wiki article `CMake:How To Find Libraries `_. Now that the RSC `CMake`_ macros are made available we include some of them for our own use: .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::include-cmake-code :end-before: mark-end::include-cmake-code :linenos: In detail these are: * ``InstallFilesRecursive``: Used to install the header files of our own project * ``DefineProjectVersion``: Used to define version variable in `CMake`_ including information from version control systems like SVN or `GIT`_ * ``GenerateDoxygen``: Used to generate `doxygen`_ source code documentation * ``PedanticCompilerWarnings``: Install some compiler flags which increase the compilation warnings to a reasonable level Using the ``DefineProjectVersion`` macro from RSC we next define the version of our project and a utility variable to attach a version to the generated shared library of our project. .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::version-variables :end-before: mark-end::version-variables :linenos: After the ``DEFINE_PROJECT_VERSION_CALL`` several variables like ``ESSENTIALS_VERSION_MAJOR`` or ``ESSENTIALS_VERSION_MINOR`` are available for further use. Please refer to the documentation of the macro for further details. Finally, to conclude with the preamble of the top-level `CMake`_ file, we define the names of targets (that means libraries and binaries built by our project). These names are required several times throughout the remaining `CMake`_ logic. .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::define-target-names :end-before: mark-end::define-target-names :linenos: The first two lines define the names of the shared library and binary built by our project. The last line defines the name of a target which `CMake`_ uses to expose the own code to :term:`downstream` projects which want to use the shared library of our own project. .. note:: For exposing our own library to possible :term:`downstream` projects we recommend the use of `CMake`_'s "export" features. These features are very versatile but currently lack a good documentation. Some further hints besides what will be explained in this tutorial can be found in the `CMake`_ manual and on these websites: * `CMake/Tutorials/Exporting and Importing Targets `_ Building Targets ================ Now that all necessary libraries have been found and useful variables like version and target names have been defined, we can actually declare the targets to be built by the project. As these definitions usually take some space and we decided to organize the source code in separate folders, it is useful to factor them out to specific `CMake`_ listings in these folders. In case of the usual production source code this is easily done with the following directive: .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::include-src-directory :end-before: mark-end::include-src-directory :linenos: For the unit test, some more work is necessary. First, we gave the user the option to decide whether the tests shall be built or not. Additionally, if the test shall be built, we need to make available the unit testing framework we are going to use. For this purpose we recommend `Google Test`_ and `Google Mock`_, which provide a very good feature set. As `Google Mock`_ already contains `Google Test`_, we will only talk about `Google Mock`_ in the remainder of this document. RSC has specific support to include these framework: .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::include-test-directory :end-before: mark-end::include-test-directory :linenos: In line 1 we first enable `CMake`_'s testing features. In cases where the unit tests shall be built, line 3 triggers a download of the `Google Mock`_ sources using the RSC macro. As this might fail, we need to test for success afterwards. Finally, `Google Mock`_ requires thread support. Hence, we search for the correct threading library like pthreads in line 5 and afterwards finally include the test sub-tree. We will now look in detail how the targets in the ``src`` directory are build. Therefore we continue with looking at the definitions in :file:`src/CMakeLists.txt`. The first thing we are going to build in `CMake`_ is the shared library: .. literalinclude:: example-projects/build-system-essentials-cpp/src/CMakeLists.txt :language: cmake :start-after: mark-start::declare-library :end-before: mark-end::declare-library :linenos: First we define two variables containing respectively the source files and the header files for our projects. Afterwards, we declare a shared library using ``ADD_LIBRARY``. Please note that we also the header files should be added to the target, even if they do not need to be compiled. With the last line, we declare the version of the shared library. For all these definitions we have used the variables declared in the main `CMake`_ listing. As our shared library does not have any external dependencies, we do not need to link it against any other libraries. The next step is to define the executable we are going to build: .. literalinclude:: example-projects/build-system-essentials-cpp/src/CMakeLists.txt :language: cmake :start-after: mark-start::declare-executable :end-before: mark-end::declare-executable :linenos: The executable is declared using ``ADD_EXECUTABLE``. If there were additional headers files only meant for this executable, they should also be added to this target. In our case, the executable uses the library we have built just before and :ref:`RSB ` etc. Therefore, we have to link it against these libraries using the ``TARGET_LINK_LIBRARIES`` function. Finally, the executable as well as the shared library have to be installed to the file system so that they can be used (by :term:`downstream` projects). .. literalinclude:: example-projects/build-system-essentials-cpp/src/CMakeLists.txt :language: cmake :start-after: mark-start::installation :end-before: mark-end::installation :linenos: The main command to install things in `CMake`_ is ``INSTALL``. It has several signatures depending on what is going to be installed. In this case, we first install the two targets. For this purpose we have to tell `CMake`_ where to install these target with the various ``XXX DESTINATION`` declarations. Binaries will be installed to ``RUNTIME DESTINATION`` and shared libraries to ``LIBRARY DESTINATION``. On Windows, shared libraries are split into a dll and lib file. While the lib file will go to ``LIBRARY DESTINATION`` comparable to Linux, the dll will be installed to ``RUNTIME DESTINATION``. Hence, you should at least mention these two destinations in each ``INSTALL`` call. ``ARCHIVE DESTINATION`` would be used for static libraries. .. note:: The given paths in the ``DESTINATION`` variables are always relative to ``CMAKE_INSTALL_PREFIX``. So there is no need to construct such a thing manually or explicitly mention ``CMAKE_INSTALL_PREFIX``. .. attention:: It is important to include the ``EXPORT`` statement in the ``INSTALL`` call to make the targets available for :term:`downstream` projects using the aforementioned export mechanism. Finally, the headers need to be installed so that :term:`downstream` projects can be compiled against our own library. Our headers are arranged in a deeper directory hierarchy but `CMake`_ does not natively come with a function to preserve this hierarchy during installation. Therefore, we use the RSC macro ``INSTALL_FILES_RECURSIVE``. Building Unit Tests =================== The unit tests are separated in a different folder (``test``) and test files are organized in parallel hierarchy to the production source code. Build definitions for the tests can be found in the respective `CMake`_ listing :file:`test/CMakeLists.txt`. For integrating the unit testing with e.g. continuous integration server it is a good idea to have machine-readable output from the unit tests. For this purpose we start with defining a location for this output, in our case XML documents: .. literalinclude:: example-projects/build-system-essentials-cpp/test/CMakeLists.txt :language: cmake :start-after: mark-start::test-output-folder :end-before: mark-end::test-output-folder :linenos: For compiling the unit tests, we need to have our unit testing framework `Google Mock`_ in the include path as well as our own production source code that we are actually testing: .. literalinclude:: example-projects/build-system-essentials-cpp/test/CMakeLists.txt :language: cmake :start-after: mark-start::test-includes :end-before: mark-end::test-includes :linenos: Finally we can declare our unit test: .. literalinclude:: example-projects/build-system-essentials-cpp/test/CMakeLists.txt :language: cmake :start-after: mark-start::test-declaration :end-before: mark-end::test-declaration :linenos: We first define a name for the generated executable which runs the unit tests in ``TEST_NAME``. Afterwards, we build an executable with our test code and link it. The test executable needs to be linked against our own library and against `Google Mock`_. As `Google Mock`_ requires a threading library, it also needs to be linked against this library, which is declared in ``CMAKE_THREAD_LIBS_INIT`` from the ``find_package`` call in the top-level :file:`CMakeLists.txt`. Finally, we register the unit test in `CMake`_'s own testing framework CTest, which has the benefit that we can easily call ``make test`` to trigger the tests. For this purpose, `CMake`_ also needs to know that the unit test needs to be called with a special command line argument to generate the desired XML output. Exposing the Project to Downstream Projects =========================================== In order to make the project usable we for :term:`downstream` projects we need to generate and install a few more files. This generation is done again in the top-level :file:`CMakeLists.txt`: .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::cmake-config-files :end-before: mark-end::cmake-config-files :linenos: The three generated files are necessary to work with the export import mechanism for `CMake`_ targets and the configuration we are using in this example project closely resembles the descriptions in the wiki article `CMake/Tutorials/Exporting and Importing Targets`_. In case you are adapting the build structure of your project significantly, you should check all three files for changed variable or target names. Roughly described, :file:`build-system-essentials-config.cmake.in` is the file which ``find_package`` searches for the find our project. :file:`build-system-essentials-config-version.cmake.in` implements the version check for ``find_package`` in case someone requests a specific version of our library. Finally, :file:`build-system-essentials-build-tree-settings.cmake.in` declares special settings, in cases a :term:`downstream` project uses our code directly from our build tree, instead of an installed version. This is handy, because it avoids the installation step in cases where you are working constantly on this project and the :term:`downstream` project in parallel. In a last step, the `CMake`_ export mechanism requires the installation of another file, which is automatically generated by `CMake`_ on the call to ``EXPORT``. Finally, we also want to generate an API documentation for the project so that :term:`downstream` developers have a reference to work with. This is easily accomplished with the macro provided by RSC: .. literalinclude:: example-projects/build-system-essentials-cpp/CMakeLists.txt :language: cmake :start-after: mark-start::documentation :end-before: mark-end::documentation :linenos: How to Build and Use the Project ================================ You should perform `out-of-source builds `_: .. code-block:: sh cd PATH/build-system-essentials/cpp # go to a directory different from the project's root folder, e.g. mkdir build cd build # let CMake do the job cmake -DCMAKE_BUILD_TYPE=debug -DCMAKE_INSTALL_PREFIX=/your/prefix .. # build make # run tests make test # or to get output for non-failing tests ctest -V # build documentation make doc # install make install # clean generated files make clean .. note:: In case one of the dependencies like RSC is not found, you can point `cmake`_ to the installation location of these by adding ``-DRSC_DIR=path/to/RSCConfig.cmake`` to the `cmake`_ call. Further Readings ================ This tutorial should have shown you how to construct a reliable build system using `cmake`_. However, there are many more valuable techniques and important practices you should follow when modifying the build system. We recommend the following articles and blog postings for further information: * `CMake wiki `_ * `Out-of-Source Builds mit CMake (German) `_ * `Using Dependency Tracking in Jenkins with CMake-based C++ Projects `_ * `Gcov Coverage Reports in Jenkins `_ * `Relative RPath Settings with CMake `_