CPP / C++ - CMake Building System

Table of Contents

1 CMake Building System

1.1 Overview

Motivation

  • Easier cross-platform compilation and portability
    • A single CMake building-script to rule all building systems.
  • No reliance on a single IDE or building system
    • CMake can generate projects and building scripts for many IDEs and also several IDEs are providing support for CMake without requiring generating a project to the IDE.
  • Human-readable project file with a declarative language
    • The project file is just a CMakeLists.txt human-readable text with a simple and declarative language, not an obfuscated or cryptic XML file, a verbose and fragile Json or a non-portable Makefile or Visual Studio Solutions.
  • Easier to consume libraries and other projects
    • CMake makes consuming external and internal dependencies easier than other building systems such as GNU autotools and GNU Makefile. All what it is needed to add a external library to a project is to copy the library directory to the project and add the line add_subdirectory(<LIBRARY_DIRECTORY>). Another advantage is that the library does not need to be installed in the system.
  • All configuration managed by code rather than GUI and XML
    • Almost all configuration of a CMake project are managed by CMake language code, rather than by GUIs graphical user interfaces like Visual Studio Projects with MSBUild. This approach allows more customization, scalability and also faster changes. Another benefits are the portability among IDEs with CMake support. This text-centric approach also frees users from learning IDE specific details or configuration GUIs.
  • Network effect (Network Externality)
    • Network effect can be described as phenomenon where additional users increases the value of products and services to all users.
    • Many C++ libraries are being distributed with CMake; many open source projects are adopting CMake; most C++ package managers support CMake and IDEs are starting to provide CMake support.

CMake can be used in following ways:

  • Build a software directly from command line in a similar way to Unix's make - $ make and $ make install.
  • Generate IDE-specific or preferred project files in IDEs that does not supports CMake, for instance cmake can generate projects from the file CMakeLists.txt for the IDEs and text editors: Microsoft Visual Studio, CodeBlocks, Kate projects, Sublime and Eclipse CDT.
  • Manage a project in IDEs which supports CMake. In this case, it is not necessary to generate IDE-specific project files.

Programming Languages supported by CMake

  • ASM - Assembly
  • C
  • C++
  • Fortran
  • CSharp C#
  • CUDA
  • CXX
  • Java
  • RC - Windows Resource Compiler
  • Swift

Capabilities:

  • Generate IDE-specific project files.
  • Generate Visual Studio Solutions (projects)
  • Generate Makefiles (GNU Make) or NMake (Windows make) for direct compilation.
  • Run targets like GNU make
  • Build a software or library directly from command line.

Possibilities

A CMake project can contain one or more target (taks), similar to Makefile rules:

  • Multiples targets for build executables - the same CMake file can be used to build multiple applications.
  • Target to build shared library
  • Target to build static library
  • Target to install aplications or libraries
  • Custom targets

CMake Project Examples

  • winapi snippets
    • Code snippets and examples for Windows API in modern C++ managed by CMake.
  • shared library
    • Example containibng: Cross platform shared library exporting functions, classes, polymorphic classes and C-interfaces (wrapper functions with C-linkage). It also contains a C++ client-code and Python wrapper which uses Python ctype FFI foreign function interface.
  • example-pybind11-vcpkg
    • This example project contains a Python3 native module written in C++ with the help of CMake and Pybind11, vcpkg (C++) Package manager.

1.2 Cmake Videos

  • Florent Castelli: Introduction to CMake
    • "CMake is a tool designed to help you build and test your software. It is now more popular than ever and is now supported by some major IDEs and libraries, including Android Studio, CLion, QtCreator or Visual Studio. Let's cover the basics, understand how CMake works and how to write modern and extensible cross-platform build scripts with CMake. We'll cover some examples on how to enable some modern C++ tooling through CMake, improving code quality and build times."
  • C++Now 2017: Daniel Pfeifer “Effective CMake"
    • "While CMake has become the de-facto standard buildsystem for C++, it's siblings CTest and CPack are less well known. This talk gives a lightspeed introduction into these three tools and then focuses on best practices on building, testing, and packaging. We will learn how to manage dependencies and export our libraries in such a way that they can be easily used by our clients. We will crosscompile for different platforms and run our tests in an emulator. We will analyze code coverage and perform static and dynamic code analysis."
  • Deep CMake for Library Authors - Craig Scott - CppCon 2019
    • "This talk presents a road map for C++ library authors grappling with cross-platform aspects of library development and deployment. It highlights key CMake features that every cross-platform library project should be using and digs deeper into the platform-specific quirks and conventions behind them. The material presented will give library authors more robust control over their API, smoother integration with major platforms and packaging systems, and more convenient inclusion by other projects. The presentation will firstly examine how symbol visibility, library versioning and API evolution can be handled coherently across all major platforms and compilers. CMake provides dedicated features for these that are easy to use, but with the deeper understanding provided by this talk, library authors will be able to make these areas work together more seamlessly and avoid future maintenance and compatibility issues. We will then explore how platform and vendor differences affect the installed directory layout for projects with libraries. CMake features for transparently handling the different conventions and policies will be presented, including recent CMake improvements which simplify this task. The importance of RPATH/RUNPATH functionality for improved runtime robustness and ease of use will also be explained, along with some associated support CMake provides. Along the way, the talk will mention a number of specific things that CMake library projects should do or avoid to make themselves easy for other projects to consume. This will include versioning support for CMake config package files, guidance on defining install components and accounting for the different ways that projects may incorporate yours into their build."
  • C++Now 2018: Mateusz Pusz “Git, CMake, Conan: How to Ship and Reuse our C++ Projects”
    • "The purpose of that presentation is to solve the problems of build system and packaging that we have with large, multi-platform, C++ projects with many open source dependencies. Git and CMake are already established standards in our community. However, it is not clear how to use them in an efficient way. As a result, many C++ projects have problems with either importing other dependencies or making themselves easy to import by others. The talk will describe how Conan package manager - a new contender on the market may address those use cases."
  • Let's cmakeify the C++ standard library - Jussi Pakkanen - CppCon2019
  • Modern Cmake: An introduction
  • CMake/CPack/CTest/CDash Open Source Tools to Build Test and Deploy C++ Software
    • "CMake/CPack/CTest/CDash Open Source Tools to Build Test and Deploy C++ Software, presented by Bill Hoffman. CMake has been in development since 1999, and has been used on several large open source projects such as ITK, VTK, ParaView, VXL, Trilinos and CMake itself. Further, KDE, one of the largest OSS projects has adopted CMake, demonstrating that CMake is capable of successfully supporting complex and large software systems. Hence CMake usage is growing rapidly with thousands of daily downloads and inclusion in several Linux distributions. Unlike many build systems, CMake is designed to be used in conjunction with native build tools enabling developers to use makefiles, Kdevelop projects, Xcode projects, and even MS Visual Studio projects. A simple input language (included in a CMakeLists.txt file) is used to specify which files to build and what types of system introspection tests need to be performed to build the software. A persistent cache file is used to store the system information and avoid the need for user-defined environment variables. In addition to building software, CMake provides a testing client (CTest) that integrates with the web-based CDash testing server. This server creates dashboards that build a snapshot of the software at a given time. This is critical to cross-platform development since often a change on one platform fails to compile on another one. The testing system provides for nightly builds which use a copy of the software at a specific time each night, experimental tests that can be used to share build results with other developers before committing source code, and continuous build results that test the build each time files are committed to the source control system. Once the software is built and tested, the CPack tool can be used to package the software. CPack works similar to CMake in that it generates package information for native packaging tools. NSIS, RPM, OSX packages self extracting tar.gz, tar.gz, tar.zip can all be created. CPack information is included as a simple extension to the CMake build files."

1.3 Notable Projects Using CMake

1.4 IDE with CMake support

1.4.1 Overview

  • Note: List of IDEs that can use CMake as project configuration file where the user does not need to generate IDE specific project file such as Visual Studio Solution or CodeBlocks projects.

1.4.2 Lightweight IDE - Integrated Development Environments

  • QTCreator (QT Company) [BEST]
  • KDevelop IDE [BEST]
    • Note: KDevelop allows managing project with CMake, visualization of all CMake targets, run individual targets with mouse click, run install target and so on.
    • Note: On Windows, it doesn't support Visual Studio / MSVC projects with MSVC (VC++) "visual" C++ compiler.
  • VScode - Visual Studio Code (Microsft MSFT) [BEST]
    • Note: Lightweight editor built on top of Electron framework using web technologies with extensions, aka plugin, support.
    • Note: Some of the following features are provided by VScode extensions.
    • Features:
      • Command-based like Emacs, but VScode uses Ctrl + P for launching commands, intead of Emacs' Alt + x or Meta-X keybindings.
      • Configurable-like Emacs, but VScode uses JSon for configuration files and Javascript for extensions.
      • Syntax highlight for many languages, including C++ and CMake scripting language.
      • Code folding
      • CMake building system support
      • CMake support: Automatic toolchain (compilers, linker and so on) scanning for CMake.
      • CMake support: Also supports CMake utility targets, that are not associated to any source file.
      • Code Navigation: symbol panel and click-at symbol to open the file where it is defined.
      • Cross Platform: works on Windows, Linux and Mac OSX.
      • Built-in terminal emulator panel.
      • Can open and run projects from any programming languages in Docker containers which allows creating reproducible development environments.
    • VScode Extensions:
  • Jucipp / Gitlab
    • "A lightweight & cross-platform IDE supporting the most recent C++ standards. Current IDEs struggle with C++ support due to the complexity of the programming language. juCI++, however, is designed especially towards libclang with speed, stability, and ease of use in mind."
    • Supported build systems: CMake ad Meson
  • GNome Builder IDE
    • "Builder aims to be an IDE for writing GNOME-based software. We believe this focus will help us build something great for our community. If you would like to help in this effort, join our IRC channel and we will help you find something to work on. Builder is primarily written in C and Python. Some aspects of Builder may be written in another language when it makes sense. Builder is developed in conjunction with GNOME releases. This means that we often contribute to, and rely on, features being developed in other GNOME modules such as Gtk."
    • Supported building systems:
      • GNU Autotools
      • CMake
      • Meson
    • https://builder.readthedocs.io/en/latest/
    • https://github.com/GNOME/gnome-builder
  • CodeLite IDE
    • "CodeLite is an open source, free, cross platform IDE specialized in C, C++, PHP and JavaScript (mainly for backend developers using Node.js) programming languages which runs best on all major Platforms ( OSX, Windows and Linux )"

1.4.3 Full-fledge IDE - Integrated Development Environments

Large IDEs (Integrated Development Enviroment) are best suitable for navigating and editing large codebases. The killer features of IDEs are: more accurate code completion; side-panel with list of functions, classes and so on; refactoring menu which allows making changes on multiple files in a single step; project templates; generation of boilerplate code such as: getters, setters and header-source files pair ; integrated debug and many other amenities that enhances the productivity.

  • Microsft Visual Studio [Windows-only] [BEST]
  • JetBrains - Clion IDE [BEST]
  • Eclipse CEvelop (Modified Eclipse IDE for C and C++ Development)
    • Many embedded systems vendors provide IDEs or development environment based on modified Eclipse IDEs.
    • Note: It does not support CMake directly, it requires some the installation of some plugins:
    • Plugin: cmake-eclipse-helper
    • Plugin: cmake4eclipse
    • Plugin: cmake-editor
  • Other Eclipse-based IDEs and Frameworks:
    • Linux Kernel Programming IDE (LinK+ IDE)
      • "LinK+ IDE is a simple IDE for Linux Kernel Developers and Lovers. It is based on Eclipse IDE customized for Linux kernel programming. It reduces the development time and executes code in an elegant fashion. This IDE supports Linux kernel configuration, compilation & emulation, system call development and device driver development. LinK+ IDE includes various templates in the category of character, block and network device driver subsystems for device driver development. It includes various bus infrastructure templates and also provides Linux device driver code completion, code assistance & code navigation for developers."
    • Eclipse IDE for C/C++ Linux Developers
      • "This package augments the C/C++ package specifically for Linux developers. It includes GNU Autotools integration with the CDT and plugins to interact with native Linux tools such as GCov, GProf, OProfile, and Valgrind. Visualization and analysis plugins for Linux tracing tools LTTng and SystemTap are also present. For Linux distribution packagers, an RPM .spec editor with rpmlint integration is available. Note that this package includes some incubating components, as indicated by features with "(Incubation)" following their name."
    • MODembed (Microchip PIC 8-bits, 16 Bits Microcontrollers and MSP430 core from Texas Instruments)
      • "MODembed is an experimental IDE aiming to provide a more advanced toolchain for embedded software development. It provides a model-based approach for defining the whole compilation process."
    • somnium-drt-cortex-m-ide
      • "SOMNIUM DRT Cortex-M IDE is a professional C and C++ embedded software development environment for ARM Cortex-M devices, which is built on the Eclipse platform. DRT is fully compatible with the GNU toolchain and other third party software. DRT provides industry-leading levels of optimization, support for the latest C and C++ language standards and unique debugging tools, including live expression view without breakpointing, trace and fault diagnosis."
    • The Arduino Eclipse IDE named Sloeber (Product)
      • "This product allows you to create, compile and upload sketches with nearly the same simplicity as the Arduino IDE but with all full functional features one would expect from a professional IDE and includes a serial monitor and a graphical plotter. This is a free and open-source plugin that is a professional and complete alternative to the Arduino IDE. This is an add-on to the CDT plugin and as such has all C/C++ development features plus a toolbar with the Arduino buttons from the Arduino IDE and an Arduino menu for importing libraries and more."
    • OpenCL Development Tool
      • Note: OpenCL (Kronos Group) is a vendor-agnostic C-extension language for general purpose computing on GPUs.
      • "OpenCL Development Tool is an IDE based on Eclipse CDT for developers writing kernels and host stubs in C, C++ and OpenCL language. The tool provides wizards creating OpenCL projects and kernels by one click. The editor provides syntax checking, error highlighting and offers solutions for typical errors. Also provides cheat sheets for best practices."
  • EClipse tooling and Convenience for Embedded Systems
    • GNU MCU Eclipse and GNU ARM → GNU MCU Eclipse!
      • "The GNU MCU Eclipse (formerly GNU ARM Eclipse) is an open source project that includes a family of Eclipse plug-ins and tools for multi-platform embedded ARM & RISC-V development, based on GNU toolchains."
    • EmbSysRegView - View Registers of Embedded Devices
      • "EMBedded SYStems REGister VIEW is an Eclipse Plugin which is designed for monitoring and modifying memory values of embedded devices. Therefore it offers a structured display of the special functions registers (SFR). While debugging, the register values are presented in the Hexadecimal (HEX) and Binary (Bin) column of the view. Modifications of peripheral registers values are possible via: Editing in the Binary column via BitButtons; Editing in the Hex column; Choosing an interpretation from an ComboBox in the Hex column (not supported by all chip xml files); The EmbSysRegView works with any 32 bit target that stores data in little endian byte order and maps its register into memory adress space. In other words, if you find your registers in the CDT Memory View, the RegisterView will also work with that device assumin that a chip xml description is available."
    • Embedded Reservation Space
      • "This Embedded Reservation space (ERS) plugin will help embedded programmer to know the embedded resource that are utilized by team members. This information (usage of embedded resources) will be shared between team member using ERS plugin. ERS plugin contains two plugins one is server and other is client plugin. Server plugin will be installed by team leader and client plugin will be installed by team members. Team leader enter the embedded resources that are used by different team members. The team members access the information using client plugin."
    • impulse Embedded Extension (Support for Serial RS232 and JTag)
      • "An embedded system is a computer system with a dedicated function within a larger mechanical or electrical system, often with real-time computing constraints. This extension contains a serial line (RS232) adapter, a J-Link adapter, a CDT memory adapter, a Serial Wire Viewer (SWV) reader, a SystemView reader and a binary log format reader from the ukos project. impulse is a powerful waveform viewer and analyser tool, helping engineers to comfortably understand and debug complex semiconductor and multi-core software systems."
    • code-confidence-tools-ecos [EVALUATION VERSION] (Note: ECos is an RTOS used in automotive industry)
      • "The Code Confidence™ Tools provide the most productive environment for eCos RTOS application development. All components are fully integrated with the Eclipse™ C/C++ Development Tooling (CDT), creating a seamless development experience from the initial installation of eCos(R) source code to the final delivery of your fully debugged eCos application. Product features include project creation wizards, an advanced configuration editor, C/C++ editor integration, performance-optimized GNU toolchains, support for profiling, code coverage analysis, and kernel-aware debug views which simplify and accelerate eCos application debugging. The tools incorporate ThreadSpy™ technology for the thread-aware debugging of eCos applications using JTAG, BDM, SWD and other on-chip debug hardware. All debugging features are integrated with the Eclipse Debugger Services Framework (DSF) which is optimized for use with embedded systems."
    • Code Confidence Tools for FreeRTOS [EVALUATION VERSION]
      • "The Code Confidence™ Tools provide the most productive debug environment for embedded application engineers working with the FreeRTOS™ kernel. All components are fully integrated with the award-winning Eclipse™ Platform (IDE) and C/C++ Development Tooling (CDT), providing a seamless debugging experience. Product features include full support for the debugging of individual FreeRTOS tasks at both source code and machine assembly levels. Code Confidence ThreadSpy™ technology provides per-task backtrace, and enables inspection of the local variables within each stack frame of each task. All debugging features are fully integrated with the high-performance Debugger Services Framework (DSF), enabling the use of per-task breakpoints, watchpoints and single-stepping while allowing the inspection of call stacks, source code variables, processor registers and memory regions. An evaluation version is available for download."
  • Articles about Eclipse IDE:

1.5 Minimal CMake Project Workflow

This section contains a minimal CMake project that generates a single executable which uses OpenGL libraries. All sources at: gist

A CMake project contains at least the file CMakeLists.txt (Cmake building definitions akin to Makefile) and one or more sources - files: *.c, *.cpp, *.cxx, *.hpp.

Workflow:

  • Build and install the application or library directly from command line.
  • Generate IDE-specific building scripts if the IDE does not support opening CMake projects. Example: CMake can generate Makefiles for EClipse CDT or Visual Studio solution for old Visual Studio versions which doesn't support CMake.
  • Open and manage the project directly if the IDE or editor supports CMake. In Visual Studio, QTCreator or KDevelop, to open a CMake project, it is just necessary to open the directory containing the CMakeLists.txt.

File: CMakeLists.text

cmake_minimum_required(VERSION 3.9)

# Proeject name - should not contain whitespace 
project(OpengGL_CPP)

#========== Global Configurations =============#
#----------------------------------------------#

# Set the C++ standard for all targets (It sets the flags
# (-std=c++11, -std=c++14 ...) on Clang or GCC. and /std=c++17 on MSVC
# OPTIONAL:
#---------------------------------
set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)

#========== Targets Configurations ============#

# Build an executable (Unix-like OSes generates ./openglDemo1, on
# Windows ./opengDemo1.exe)
# ..........................................
add_executable(openglDemo1 opengl1.cpp)

# Set executable dependency libraries
# Equivalent to pass flags -lGL, -lGLU and -lglut 
target_link_libraries(openglDemo1 GL GLU glut)

# Add extension .bin to executable name to make it easier
# to identify that the binary file is an executable.
# So, it renames the executable  'openglDemo1' to 'openglDemo1.bin'
if(UNIX)
  set_target_properties(openglDemo1 PROPERTIES SUFFIX ".bin")
endif()

# Add target to run executable 
add_custom_target(run-ex1
    COMMAND openglDemo1 
    DEPENDS openglDemo1 
    WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
)

Without all the comments and the custom target, the CMakeLists.txt file would become:

cmake_minimum_required(VERSION 3.9)
project(OpengGL_CPP)
set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)

#========== Targets Configurations ============#
add_executable(openglDemo1 opengl1.cpp)
target_link_libraries(openglDemo1 GL GLU glut)

Building on Command Line:

1 Install OpenGL development libraries (Fedora Linux):

$ sudo dnf install freeglut-devel.x86_64
# Optional: Faster and better building system than GNU Make
$ sudo dnf install ninja-build.x86_64 

2 Clone sample project:

$ git clone https://gist.github.com/caiorss/fd9e61887e9b2575f2275a80af44fac2 cmake-opengl
Cloning into 'cmake-opengl'...
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (4/4), done.

$ cd cmake-opengl/

$ ls
CMakeLists.txt  opengl1.cpp

3 Build with Makefile:

  • cmake -H<SOURCE> -B<BUILD> [-G <Build-Systems>]
    • <SOURCE> - Directory where is CMakeLists.txt (In this case (.) current directory)
    • <BUILD> - Directory containing compilation output and the building scripts generated by CMake for some building systems. In this exaple: ./cache
    • -G <Build-Systems> -> [Optional] Specifies the building systems which CMake will generate the scripts for. On Linux, the default building system is Makefiles and on Windows it is MSBuild.
# Generate => Building scripts for Makefile on ./cache directory 
$ cmake -H. -Bcache 

-- The C compiler identification is GNU 8.2.1
-- The CXX compiler identification is GNU 8.2.1
... ...   ... ...   ... ...   ... ...   ... ... 
-- Generating done
-- Build files have been written to: /home/archbox/root-scripts/cmake-opengl/cache

Build the executable running the building script (Makefile) from CMake:

  • Note: It will build all CMake targets, all executables and libraries.
$ cmake --build cache

/usr/bin/cmake -H/home/archbox/root-scripts/cmake-opengl -B/home/archbox/root-scripts/cmake-opengl/cache --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /home/archbox/root-scripts/cmake-opengl/cache/CMakeFiles /home/archbox/root-scripts/cmake-opengl/cache/CMakeFiles/progress.marks
 .. ...  .. ...  .. ...  .. ...  .. ...  .. ... 
ir/build
gmake[2]: Entering directory '/home/archbox/root-scripts/cmake-opengl/cache'
[ 50%] Building CXX object CMakeFiles/openglDemo1.dir/opengl1.cpp.o
/usr/lib64/ccache/c++    -std=gnu++1z -o CMakeFiles/openglDemo1.dir/opengl1.cpp.o -c /home/archbox/root-scripts/cmake-opengl/opengl1.cpp
[100%] Linking CXX executable openglDemo1.bin

Build a single target:

$ cmake --build cache --target openglDemo1

Inspect executable:

$ file cache/openglDemo1.bin 

 cache/openglDemo1.bin: ELF 64-bit LSB
 executable, x86-64, version 1 (SYSV), dynamically linked, interpreter
 /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0,
 BuildID[sha1]=269d06a0054677d70ecf0258268565535e3dfbd4, not stripped

Execute a custom target, for instance the custom target run-ex1 which runs the executable (aking to $ make run).

$ cmake --build cache --target run-ex1

  ... ... .... 
Scanning dependencies of target run-ex1
gmake[3]: Leaving directory '/home/archbox/root-scripts/cmake-opengl/cache'
/usr/bin/gmake -f CMakeFiles/run-ex1.dir/build.make CMakeFiles/run-ex1.dir/build
gmake[3]: Entering directory '/home/archbox/root-scripts/cmake-opengl/cache'
./openglDemo1.bin
[INFO] Starting OpenGL main loop.
[INFO] Running loop.
[INFO] Running loop.

4 Build with Ninja.

  • The compilation can be faster and less verbose with Ninja-build building systems.
# Remove cache directory 
rm -rf cache

# Generate building scripts for Ninja building system in directory ./cache 
$ cmake -H. -Bcache -G Ninja
-- The C compiler identification is GNU 8.2.1
 ... .... ... .... ... .... ... ....
-- Generating done
-- Build files have been written to: /home/archbox/root-scripts/cmake-opengl/cache

# Build a single target 
$ cmake --build cache --target openglDemo1
[2/2] Linking CXX executable openglDemo1.bin

# Build all targets 
$ cmake --build cache --target all
ninja: no work to do.

# Run target: run-ex1 (akin to $ make run)
$ cmake --build cache --target run-ex1
[0/1] cd /home/archbox/root-scripts/cm...pts/cmake-opengl/cache/openglDemo1.bin

5 Open project with IDE or editor that doesn't support CMake, for instance: Code Blocks.

  • The following command generates a blocks project file (OpengGL_CPP.cbp) and a GNU make building (Makefile) script. Then, the project can be opened from Code Blocks by selecting the file OpenGL_CPP.cpb which is a CodeBlocks project file.
$ cmake -H. -Bcblock -G "CodeBlocks - Unix Makefiles"

-- The C compiler identification is GNU 8.2.1
 ... ... ... ... ... ... .... 
-- Configuring done
-- Generating done
-- Build files have been written to: /home/archbox/root-scripts/cmake-opengl/cblock

$ tree cblock -L 1
cblock
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
├── Makefile
└── OpengGL_CPP.cbp

1 directory, 4 files

1.6 Build a project from command line - detailed

A project using Cmake as building system contains a build script at top level directory named CMakeLists.txt. To build a project with CMake, it is necessary to consider that CMake, is not a building system and does not build anything. Actually, Cmake generates building scripts for the platform preferred building scripts. For instance, on Linux, it can generate Makefile, Eclipse Makefiles; on Windows it can generate MSBuild scripts, NMake (Windows SDK's make) and so on.

  • Check all possible building system for which CMake can generate build scripts.
    • Command: $ cmake -G
# Running on Windows 10 
$ cmake -G
CMake Error: No generator specified for -G

Generators
  Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 12 2013 [arch] = Generates Visual Studio 2013 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 11 2012 [arch] = Generates Visual Studio 2012 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 10 2010 [arch] = Generates Visual Studio 2010 project files.
                                 Optional [arch] can be "Win64" or "IA64".
  Visual Studio 9 2008 [arch]  = Generates Visual Studio 2008 project files.
                                 Optional [arch] can be "Win64" or "IA64".
  Borland Makefiles            = Generates Borland makefiles.
  NMake Makefiles              = Generates NMake makefiles.
  NMake Makefiles JOM          = Generates JOM makefiles.
  Green Hills MULTI            = Generates Green Hills MULTI files
                                 (experimental, work-in-progress).
  MSYS Makefiles               = Generates MSYS makefiles.
  MinGW Makefiles              = Generates a make file for use with
                                 mingw32-make.
  Unix Makefiles               = Generates standard UNIX makefiles.
  Ninja                        = Generates build.ninja files.
  Watcom WMake                 = Generates Watcom WMake makefiles.
  CodeBlocks - MinGW Makefiles = Generates CodeBlocks project files.
  CodeBlocks - NMake Makefiles = Generates CodeBlocks project files.
  CodeBlocks - NMake Makefiles JOM
                               = Generates CodeBlocks project files.
  CodeBlocks - Ninja           = Generates CodeBlocks project files.
  CodeBlocks - Unix Makefiles  = Generates CodeBlocks project files.
  CodeLite - MinGW Makefiles   = Generates CodeLite project files.
  CodeLite - NMake Makefiles   = Generates CodeLite project files.
  CodeLite - Ninja             = Generates CodeLite project files.
  CodeLite - Unix Makefiles    = Generates CodeLite project files.
  Sublime Text 2 - MinGW Makefiles
                               = Generates Sublime Text 2 project files.
  Sublime Text 2 - NMake Makefiles
                               = Generates Sublime Text 2 project files.
  Sublime Text 2 - Ninja       = Generates Sublime Text 2 project files.
  Sublime Text 2 - Unix Makefiles
                               = Generates Sublime Text 2 project files.
  Kate - MinGW Makefiles       = Generates Kate project files.
  Kate - NMake Makefiles       = Generates Kate project files.
  Kate - Ninja                 = Generates Kate project files.
  Kate - Unix Makefiles        = Generates Kate project files.
  Eclipse CDT4 - NMake Makefiles
                               = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - MinGW Makefiles
                               = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Ninja         = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.

STEP 1: Generate Building Script

Project directory listing:

$ tree .
.
├── build.bat
├── client1.cpp
├── CMakeLists.txt
├── Makefile
├── pywrapper.py
├── testlib.cpp
└── testlib.hpp

Generate building scripts for platform-preferred building system:

  • $ cmake -H<SOURCE> -B<BUILD>
    • <SOURCE> => Source directory where all *.cpp, *.hpp code. In this case it is (.) dot, current directory. If the directory was ./src, the command would be -Hsrc or -H./src
    • <BUILD> => Compilation output, directory where are all building scripts.

Command output on Windows 10 - MSVC 2017. Note: It will set VC++ for 32 bits target, not 64 bits.

$ cmake -H. -Bbuild
-- Building for: Visual Studio 15 2017
-- The C compiler identification is MSVC 19.16.27025.1
-- The CXX compiler identification is MSVC 19.16.27025.1  
... ...   ... ...   ... ...   ... ...   ... ... 
-- Configuring done
-- Generating done
-- Build files have been written to: C:/Users/archbox/Desktop/experiments/dll/build

Command output on Linux:

$ cmake -H. -Bbuild
-- The C compiler identification is GNU 8.2.1
-- The CXX compiler identification is GNU 8.2.1
-- Check for working C compiler: /usr/lib64/ccache/cc
-- Check for working C compiler: /usr/lib64/ccache/cc -- works
... ...   ... ...   ... ...   ... ...   ... ... 
 [INFO] Libtest location is at: libtest_location-NOTFOUND
-- Configuring done
-- Generating done
-- Build files have been written to: /home/archbox/root-scripts/gist/build

# List ./build directory 
$ tree -L 1 build
build
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
└── Makefile

1 directory, 3 files

Generate building script for specific building system:

  • $ cmake -H<SOURCE> -B<BUILD> -G <BUILD-SYSTEM>
    • <SOURCE> => Source directory where all *.cpp, *.hpp code. In this case it is (.) dot, current directory. If the directory was ./src, the command would be -Hsrc or -H./src
    • <BUILD> => Compilation output, directory where are all building scripts.
    • <BUILD-SYSTEM> => All available build system can be listed with $ cmake -G.
# Generate MSbuild building scripts for 32 bits target 
 $ cmake -H. -Bbuild -G "Visual Studio 15 2017"

 # Generate MSbuild building scripts for 64 bits target 
 $ cmake -H. -Bbuild -G "Visual Studio 15 2017 Win64"

Generate building script with debug symbols enabled:

It automatically adds the debugging flags (/Zi) for VC++ Microsoft Visual C++ Compiler which necessary for debugging a program with GDB, LLDB or WinDBG.

  • Add the flag -DCMAKE_BUILD_TYPE=DEBUG
# Example 1:
$ cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=DEBUG

# Example 2:
$ cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=RELEASE

# Example 3: 
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=DEBUG -G "Visual Studio 15 2017 Win64"

# Example 4: 
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=DEBUG -G "Visual Studio 15 2017 Win64"

# Example 5: 
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=RELEASE -G "Visual Studio 15 2017 Win64"

STEP 2: Compile the project invoking the generated building scripts

Invoke the building scripts indirectly using CMake:

  • $ cmake –build <BUILD>
    • <BUILD> Is the build directory generated at step 1.
$ cmake --build build
# OR
$ cmake --build build --config Debug 
# OR
$ cmake --build build --config Release

Invoke the building script directly, call the building tool:

  • This building method depends on the current building system.

For MSBuild, the compilation becomes:

$ MSBuild build\testlib.vcxproj

Microsoft (R) Build Engine version 15.5.180.51428 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 12/21/2018 12:49:38 PM.
Project "C:\Users\archbox\Desktop\experiments\dll\build\testlib.vcxproj" on node 1 (default targets).
Project "C:\Users\archbox\Desktop\experiments\dll\build\testlib.vcxproj" (1) is building "C:\Users\archbox\ Desktop\experiments\dll\build\ZERO_CHECK.vcxproj" (2) on node 1 (default targets).
... ... ... ... ... ... ... ... ... ... ... ... ... ... 
testlib.vcxproj -> C:\Users\archbox\Desktop\experiments\dll\build\Debug\testlib.dll
FinalizeBuildStatus:
  Deleting file "testlib.dir\Debug\testlib.tlog\unsuccessfulbuild".
  Touching "testlib.dir\Debug\testlib.tlog\testlib.lastbuildstate".
Done Building Project "C:\Users\archbox\Desktop\experiments\dll\build\testlib.vcxproj" (default targets).

For Linux or Unix GNU-Make, the compilation becomes:

 $ cd build

 $ make
 /usr/bin/cmake -H/home/archbox/root-scripts/gist -B/home/archbox/root-scripts/gist/build --check-build-system CMakeFiles/Makefile.cmake 0
 /usr/bin/cmake -E cmake_progress_start /home/archbox/root-scripts/gist/build/CMakeFiles /home/archbox/root-scripts/gist/build/CMakeFiles/progress.marks
 make -f CMakeFiles/Makefile2 all
 make[1]: Entering directory '/home/archbox/root-scripts/gist/build'
 make -f CMakeFiles/testlib.dir/build.make CMakeFiles/testlib.dir/depend
 make[2]: Entering directory '/home/archbox/root-scripts/gist/build'
 ... ... ... ... ... ... ... ... ... ... ... ... 
 [100%] Built target client1-executable
 make[1]: Leaving directory '/home/archbox/root-scripts/gist/build'
 /usr/bin/cmake -E cmake_progress_start /home/archbox/root-scripts/gist/build/CMakeFiles 0  


# Built executables: 
#--------------------------------
$ file libtestlib.so 
libtestlib.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically 
linked, BuildID[sha1]=ce79ed7580a16cbc27bf113a2967eb1d66f496a3, not stripped

$ file client1-executable.bin 
client1-executable.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
for GNU/Linux 3.2.0, BuildID[sha1]=8103d822a6d58497b63346dfcc1f7375599114f7, not stripped

Or just in a single step:

$ cd build && make && cd ..

1.7 Sample Project with multiple targets

1.7.1 Adding and setting multiple targets

This section provides shows how to set up a project with additional configuration and multiple targets which means a project with multiple executables, shared libraries or static libraries.

Set up executable target

  • Executable target defintion => Builds a *.exe on Windows.
add_executable(SampleApp main.cpp source1.cpp source2.cpp)

Header files can also be added to the target in order to make them easier to access in an IDE such as Visual Studio.

add_executable( SampleApp 
                main.cpp 
                class1.cpp class2.hpp 
                class1.cpp lcass2.hpp
              )

Set target defines #define or -D<DEFINITION>=VALUE or /D<DEFINTION>=<VALUE>

  • Compiler defintions. => Defines.
    • It is the same as compiling with GCC with command line option -D{DEFINITION}={VALUE} or compiling with MSVC VC++ with the command line option /D{DEFINITION}={VALUE} which becomes #define {DEFINITION} {VALUE} in the code.
    • Docs: target_compile_definitions — CMake 3.13.2 Documentation
 target_compile_definitions(SampleApp PRIVATE  
    VERBOSITY=100  # Same as #define VERBOSITY 100 
    Optimize       # Same as -DOptimize or #define Optimize
    foo=bar        # Same as -Dfoo=bar on (GGCC or Clang) or /Dfoo=bar on MSVC
   )
# In a single line 
 target_compile_definitions(SampleApp PRIVATE VERBOSITY=100 Optimize foo=bar)

Set target include directories

# Similar to: g++ main.cpp src1.cpp ... -o app.bin -Isrc/include1 -I/home/user/include
target_include_directories(SampleApp PUBLIC src/include1 /home/usr/include)

Set directories where shared or static libraries will be searched

Set the directories where shared or static libraries will be searched. Note: this command sets the search path for shared libraries for all targets.

link_directories(  
  # C:\Users\DummyUser\libs
  "C:\\Users\\DummyUser\\libs" # Windows paths with backward slash must be escaped with '\'
  "C:/libs/Graphics"           # Windows paths with forward slash 
  "/home/DummyUser/libs" 
)

The command shown in the previous code is equivalent to compiling a target with GCC as:

$ g++ file1.cpp -c -o file1.o -LC:\Users\DummyUser\libs -LC:/libs/Graphics/ -L/home/DummyUser/libs

Shared Librarie files extensions:

  • *.dll files on Windows for all compilers.
  • *.so files on Unix-like OSes
  • *.dylib or *.so on MacOSX

Static librarie file extension:

  • *.lib files for MSVC, VC++
  • *.a for GCC, GCC/Mingw or Clang++.

Set linking libraries

  • Libraries that the executable will link against.
    • It can be: shared libraries installed on the system or either shared or static libraries in the current project.
    • Note: Project libraries are also targets.
# Project shared library SampleSharedLib.so (Linux, Unix) or 
# SampleSharedLib.dll on Windows. 
add_library( SampleSharedLib SHARED  
             tools.cpp      tools.hpp 
             CShell.cpp     CShell.hpp 
             FileSystem.cpp FileSystem.hpp
           )
# Configuration of target SampleSharedLib
target_include_directories(SampleSharedLib PRIVATE src/lib/include1)

# Project static library 
add_library(StaticLibrary STATIC lib1.cpp lib2.cpp lib3.cpp)

target_link_libraries(SampleApp  
        GL blas opencv ole32 user32  # System libraries  /usr/lib/*.so files on Linux 
                                     # or *.lib files for MSVC VC++/Windows compiler and DLLs 
                                     # at C:\Windows\System32
        SampleSharedLib              # Project shared library 
        StaticLibrary                # Project static library 
)

Set compiler flags

  • Target compilar flags.
    • Note: The compilation flags are compiler-specific, the following flags only works for GCC and Clang, but they don't work for MSVC (VC++ Compiler) that uses forward slashes for compiler options instead of dashes.
if(NOT MSVC) 
   # Only for GCC or Clang 
   # (g) => Add debugging symbols 
   target_compile_options(SampleApp -g -Wall -Wpendantic -Wextra )
else()
   # MSVC (VC++) => "visual" C++
   target_compile_options(SampleApp /Zi /Wall /Yd)         
endif()

Set target compiler extensions and C++ standard

Documentation:

# Equivalent to compiling the target usign GCC with -std=c++14 (C++14 ISO Standard)
# Or /std:c++14 in MSVC VC++ 
target_compile_features(SomeExecutableTarget PRIVATE cxx_std_14)

# Equivalent to compiling the target usign GCC with -std=c++17 (C++17 ISO Standard)
# Or /std:c++17 in MSVC VC++ 
target_compile_features(SomeExecutableTarget PRIVATE cxx_std_17)

# Equivalent to compiling the target usign GCC with -std=c++20 - Experimental C++20
target_compile_features(SomeExecutableTarget PRIVATE cxx_std_20)

Set target Properties

  • Set target properties:
set_target_properties(SampleApp PROPERTIES 
    {PROPERTY1} {VALUE1}
    {PROPERTY2} {VALUE2}
    ... .... ... ... 
    {PROPERTYN} {VALUEN}
 )

Example 1: Add file extension .bin to Unix executable (note: executables of Unix-like operating system do not have extension *.exe like in Microsft Windows):

if(UNIX)
   # When it is compiled, instead of generating the executable named as SampleApp, 
   # the program will be named as "SampleApp.bin". 
   set_target_properties(SampleApp PROPERTIES SUFFIX ".bin")
 endif()

Example 2: Modify multiple properties, set the shared library file name to AddinExtesion.xll.

add_library(WrapperLibrary SHARED src1.cpp src2.cpp ... srcN.cpp)
# ... omit configuration ... # 

set_target_properties(WrapperLibrary PROPERTIES
  # On Unix, it would add "lib" to beggining of the file name.
  # If prefix was not set to empty, the output file name would be
  # 'libAddinExtesion.xll'
  PREFIX       ""  

  # Change file name without changing prefix or extension (suffix)
  OUTPUT_NAME  "AddinExtension"

  # Change file extension 
  SUFFIX      ".xll"    

  # Makes all symbols hidden by default (compiles with by compiling
  # with flag -fvisibility=hidden on Unix-like Oses).  This setting
  # mimics the behavior of MSVC compiler extension
  # __declspec(dllexport)/__declspec(dllimport)
  CXX_VISIBILITY_PRESET hidden
)

The target properties could also be set individually:

add_library(WrapperLibrary SHARED src1.cpp src2.cpp ... srcN.cpp)
# ... omit configuration ... # 
set_target_properties(WrapperLibrary PROPERTIES PREFIX      "")
set_target_properties(WrapperLibrary PROPERTIES OUTPUT_NAME "AddinExtension")
set_target_properties(WrapperLibrary PROPERTIES SUFFIX      ".xll")  

Add more targets

A CMake project can have multiple targets, generating executables, static libraries or shared libraries.

add_executable(DocGenerator src/docgen/main.cpp src/docgen/src2.cpp ...)
# ... configuration of DocGenerator

add_executable(Display src/docgen/main.cpp src/docge/src2.cpp ...)

add_library(libCommon SHARED src/lib/src1.cpp src/lib/src2.cpp ...)

# .... configure targes .... #

To avoid

  • Commands which affects all targets: for instance, include_directories(dir1 dir2 …) will add those include directories to all targets regardless if they need them or not. It is better to use the command target_include_directories as it affects just a single target. The word 'should not' does not means 'never'. If the intent is really set the same include directories for all targets, then the command include_directories should be used.
  • Non portable compiler settings. Example: Compiler flags such as -D{KEY}=VALUE, used by GCC and Clang, or /D{KEY}=VALUE, used by MSVC (VC++) are non portable accross different compilers. To overcome this, CMake provides many commands with an unified interface to many compilers and build-systems, such as target_compile_definitions that allows setting defines in a compiler agnostic manner.

1.7.2 Example

Project directory structure:

  • C:\User\Dummy\Documents\SampleProject (Project Root Directory)
    • CMakeLists.txt
    • ./bin => Directory where the compiled binaries will be installed.
    • ./src - Directory
      • src/main.cpp
      • src/execMain.cpp
      • src/source1.cpp
      • src/source1.hpp
      • src/source2.cpp
      • src/source2.hpp
      • src/source3.cpp
      • … … ..
    • ./lib - directory
      • lib/lib1.cpp
      • lib/lib2.cpp
      • lib/lib3.cpp

File: CMakeLists.txt file for this hypothetical project.

Global configuration:

  • Note: Global settings such add_definitions and include_directories should be avoided.
 # ========= Global Configuration  ===============#
 # The global configuration is shared by all targets. 
 cmake_minimum_required(VERSION 3.9)

 project( SuperCADTool
          # Note: the following fields are optional 
          DESCRIPTION "Another CAD Computer Aided Design APP"
          VERSION     1.0 
          LANGUAGE    CXX 
        )

 set(CMAKE_CXX_STANDARD 17)     
 set(CMAKE_VERBOSE_MAKEFILE ON)

# AVOID! 
# Global compile definition
# Add compiled definition -DQT_QML_DEBUG for all target.s 
add_definitions(-DQT_QML_DEBUG)

# AVOID!
# Global includes added to all targets 
include_directories("C:\\boost\\v1.67\\include"  "/home/someone/includes")

# AVOID! 
# Global linking directories which contains static (*.a) or shared
# libraries (*.so or *.dll files)
link_directories(  
   # C:\Users\DummyUser\libs
   "C:\\Users\\DummyUser\\libs" # Windows paths with backward slash must be escaped with '\'
   "C:/libs/Graphics"           # Windows paths with forward slash 
   "/home/DummyUser/libs" 
 )

Target Configurations:

 # ======= Configuration per target =============#

 # Target: SampleApp executable 
 #---------------------------------------------------------
 add_executable(SampleApp src/main.cpp src/source1.cpp src/source2.cpp) 
 target_compile_definitions(SampleApp PRIVATE VERBOSITY=100 Optimize foo=bar)
 target_include_directories(SampleApp PUBLIC src/include1 /home/usr/include)

 target_link_libraries(SampleApp  
         GL blas opencv ole32 user32  # System libraries  /usr/lib/*.so files on Linux 
                                      # or *.lib files for MSVC VC++/Windows compiler and DLLs 
                                      # at C:\Windows\System32
         SampleSharedLib              # Project shared library 
         StaticLibrary                # Project static library 
 )

# Target: Executable2 - Add header files to make them visible in the IDE 
#-----------------------------------------------------------------
add_executable(Executable2 src/execMain.cpp 
                           src/source1.cpp src/source1.hpp 
                           src/source2.cpp src/source2.hpp 
               ) 

# Target: SampleShared - Shared library dll, so 
#-------------------------------------------------------------------
add_library(SampleSharedLib SHARED  lib/lib1.cpp lib/lib2.cpp lib/lib3.cpp)
target_include_directories(SampleSharedLib PRIVATE src/lib/include1)

Intallation commands:

# Install binaries locally during development.
# The binaries are installed in project's "${ROOT DIRECTORY}./bin" 
#----------------------------------------------------------------------

# During the development install ./bin dir.  in project's root directory 
# To install using CMAKE_INSTALL_PREFIX path, set this variable to 'OFF'
set(INSTALL_LOCAL ON) 

if(INSTALL_LOCAL ON)
  # Overrides the default default install location that would be 
  # ${CMAKE_INSTALL_PREFIX}/bin as ${CMAKE_CURRENT_LIST_DIR}/bin 
  #
  # Note: CMAKE_CURRENT_LIST_DIR is the directory where is CMakeLists.txt 
  install(TARGETS SampleApp Executable2 SampleSharedLib DESTINATION  ${CMAKE_CURRENT_LIST_DIR}/bin)
else()
  # Install binary to directory probvided by the variable CMAKE_INSTALL_PREFIX 
  install(TARGETS SampleApp Executable2 SampleSharedLib DESTINATION  bin)
endif()

Compilation:

Once the project file CMakeLists.txt is opened in any supported IDE or an text editor such as QTCreator, the IDE will show all available targets and provide menus, context menus or buttons where is possible to select all targets for building the entire project; build individual targets; debug an specific executable targets and run the install target that copies the compiled files to the directory stored in the variable CMAKE_INSTALL_PREFIX.

Running targets manually from command line without any IDE:

Step 1. Configuration - run the command from the project's top level directory.

  • Configure with install path CMAKE_INSTALL_PREFIX set to default sytem location. (/usr/local on Linux). To install the binaries to the this location, set the variable INSTALL_LOCAL in the code to OFF.
# Use Ninja build system 
$ cmake -Bcache -H. -G Ninja 

# Use Makefile 
$ cmake -Bcache -H. -G Ninja 
  • Configure base install path (CMAKE_INSTALL_PREFIX) to /home/dummy/Documents/myapp, assuming that INSTALL_LOCAL is set to OFF.
$ cmake -Bcache -H. -G Ninja -DCMAKE_INSTALL_PREFIX=/home/dummy/Documents/myapp 

Step 2. Run targets.

  • Run all targetes building the whole project
$ cmake --build cache --target  
  • Run only the target SampleApp that builds the SampleApp executable:
$ cmake --build cache --target SampleApp
  • Run only the target Executable2 that builds the Excutable2 executable
$ cmake --build cache --target Executable2 
  • Run the target 'install' that will install all binaries specified in target install to ./bin directory if the option INSTALL_LOCAL is set in the code (ON). To install the standard location specified by ${CMAKE_INSTALL_PREFIX}, set INSTALL_LOCAL in the code to OFF.
$ cmake --build cache --target install 

1.8 Sample Project with multiple sub-projects

1.8.1 Overview

The following code is a cross-platform CMake project, with sub-projects, containing 5 targets: a install target; a shared and static libraries and two executables. The aim of this experiment is to asses how a installation of a CMake project with multiple sub-projects behaves on different operating systems.

Repository:

Directory structure

 $ >> tree . 
.
├── app1.cpp
├── app2.cpp
├── build_msvc.bat
├── CMakeLists.txt
├── lib_tool
│   ├── CMakeLists.txt
│   ├── tool.cpp
│   └── tool.hpp
├── lib_util
│   ├── CMakeLists.txt
│   ├── util.cpp
│   └── util.hpp
├── LICENSE.txt
└── Makefile

2 directories, 12 files

Top-Level Files

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
project(CMake_With_Makefile)

#========== Global Configurations =============#
#----------------------------------------------#

set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)

# Export ALL DLLs symbols on Windows without __declspec(xxxx) annotations.
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS true)

function(ADD_RUN_TARGET tname target)
    add_custom_target( ${tname}
        COMMAND ${target}
        DEPENDS ${target}
        WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
    )
endfunction()

# Fix DLL not found problem on Windows 
# Note: Windows shared libraries do not have RPATH or RUNPATH
# DLLs are searched first at current directories and then in directories 
# listed in $PATH variable.
if(WIN32)
    set(LIBRARY_OUTPUT_PATH    "${CMAKE_BINARY_DIR}")
    set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}")
endif()


    #=============================================#
    #  T A R G E T S  C O N F I G U R A T I O N   #  
    #=============================================#


add_subdirectory(lib_util)
add_subdirectory(lib_tool)

       add_executable( app1 app1.cpp )
target_link_libraries( app1 lib::util lib::tool )


if(_WIN32)
    set_target_properties( lib::util PROPERTIES 
                                     OUTPUT_NAME Util 
                                     CLEAN_DIRECTORY_OUTPUT 1 )  
endif()

# Set RPATH in the executable for making it relocatable on Unix.
# It allows installing the application on any directory or 
# running the application from any directory without 
# 'symbol not found or library not found' errros. 
#
# Note: The variable $ORIGIN is the executable absolute directory
if(UNIX AND NOT APPLE)
    set_target_properties( app1 PROPERTIES INSTALL_RPATH "$ORIGIN:$ORIGIN/../lib")                       
elseif(APPLE)
    # For MacOSX (MacO - Binary Files)
    set_target_properties( app1 PROPERTIES INSTALL_RPATH "@executable_path/../lib")                       
endif()

       add_executable( app2 app2.cpp )
target_link_libraries( app2 lib::util lib::tool)
# set_target_properties( app2 PROPERTIES INSTALL_RPATH "$ORIGIN/../lib")                       


ADD_RUN_TARGET(run-app1 app1)
ADD_RUN_TARGET(run-app2 app2)

    #===========================================#
    #  I N S T A L  L    T A R G E T            #
    #===========================================#


install( TARGETS  app1 app2 util tool 
         # Executables and DLLs (Windows Only)
         RUNTIME  DESTINATION ${CMAKE_INSTALL_BINDIR}            
         # Shared libraries: *.so or *.dylib 
         LIBRARY  DESTINATION ${CMAKE_INSTALL_LIBDIR}          
         # Static libraries: *.a 
         ARCHIVE  DESTINATION ${CMAKE_INSTALL_LIBDIR}

)

install(FILES LICENSE.txt DESTINATION data)

File: Makefile

  • Makefile for performing project commands.
# GNU Make file script for easier compilation from
# command line

all: help 

help:
        @echo "Build options "
        @echo " $ make debug                 => Debug build at _debug directory "
        @echo " $ make release               => Release build at _release directory "   
        @echo " $ make install prefix=<PATH> => Build in release mode and install app. at prefix path. "                

debug:
        @echo " =>> DEBUG build "
        # Only configs if '_debug' directory does not exist yet.
        [ ! -f _debug ] && cmake --config Debug -B_debug -H.    
        cmake --build _debug --target

release:
        @echo " =>> RELEASE build " && sleep 1 
        [ ! -f _release ] && cmake --config Release -B_release -H. 
        cmake --build _release --target

install:
        @echo " =>> INSTALL release" && sleep 1 
        cmake --config Release -B_install -H. -DCMAKE_INSTALL_PREFIX=${prefix}
        cmake --build _install --target install

run-app1:
        @echo " =>> Run app1 " && sleep 1
        [ ! -f _debug ] && cmake --config Debug -B_debug -H.    
        cmake --build _debug --target run-app1 

clean:
        rm -rfv ./_debug ./_release ./_install  

clean-debug:
        rm -rf -v ./_debug 

clean-release:
        rm -rf -v ./_release 

File: app1.cpp / target app1

#include <iostream>

#include <util.hpp>
#include <tool.hpp>

 int main(int argc, char** argv)
 {
    std::cout << " [INFO] Application 1 Called Ok." << std::endl;

    util::util_function(100);
    tool::too_lib_function();

   #ifdef _WIN32
      std::cout << " Type RETURN to exit " << "\n";
      std::cin.get();
   #endif   

    return 0;
 }

File: app2.cpp / target app2

#include <iostream>

#include <util.hpp>
#include <tool.hpp> 

int main(int argc, char** argv)
{
    std::cout << " [INFO] Application 2 Called Ok." << std::endl;

    util::util_function(20);
    tool::too_lib_function();

    #ifdef _WIN32
       std::cout << " Type RETURN to exit " << "\n";
       std::cin.get();
    #endif 

    return 0;
}

Sub-project lib_util (target lib::util)

File: lib_util/CMakeLists.txt

               add_library( tool STATIC tool.cpp tool.hpp )
target_include_directories( tool PUBLIC . )

add_library( lib::tool ALIAS tool )

File: lib_util/util.hpp

#ifndef _UTIL_HPP_
#define _UTIL_HPP_

namespace util {
    void util_function(int n);
}

#endif 

File: lib_util/util.cpp

#include "util.hpp"

#include <iostream> 

namespace util {

    void util_function(int n)
    {
        std::cout << " [TRACE] util_function() called with n = " << n << '\n';
    }
}

Sub-project lib_tool (target lib::tool)

File: lib_tool/CMakeLists.txt

               add_library ( util SHARED util.cpp util.hpp)
target_include_directories ( util PUBLIC . )
               add_library ( lib::util ALIAS util )

File: lib_tool/tool.hpp

#ifndef _TOOL_HPP_
#define _TOOL_HPP_

namespace tool {
    void too_lib_function();
};

#endif 

File: lib_tool/tool.cpp

#include "tool.hpp"

#include <iostream> 

namespace tool {
    void too_lib_function()
    {
        std::cout << " [TRACE] Called tool_lib_function() OK. " << "\n";
    }
};

1.8.2 Building and installing on Linux

Clone the repository:

$ git clone https://github.com/caiorss/cmake-cpp-nested cnest 
$ cd cnest

Run debug build

 $ >> make debug 
 =>> DEBUG build 
# Only configs if '_debug' directory does not exist yet.
[ ! -f _debug ] && cmake --config Debug -B_debug -H.    
-- The C compiler identification is GNU 10.1.1
-- The CXX compiler identification is GNU 10.1.1
-- Check for working C compiler: /usr/bin/cc
 ... ... ... 

 $ >> ls _debug/
app1*  CMakeCache.txt  cmake_install.cmake  lib_util/
app2*  CMakeFiles/     lib_tool/            Makefile

 # Test executable app1 
 $ >> _debug/app1
 [INFO] Application 2 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK. 

 # Test executable app2 
 ~/t/cnest
 $ >> _debug/app2
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 20
 [TRACE] Called tool_lib_function() OK. 

Building and Installing application locally

  • Building and installing application to directory $HOME/Applications/mycmake-app
$ cmake --config Release -H. -B_install -DCMAKE_INSTALL_PREFIX=$HOME/Applications/mycmake-app
$ cmake --build _install --target install 
  • Building and installing application to directory $HOME/Applications/mycmake-app using the Makefile.
$ make install prefix=$HOME/Applications/mycmake-app
  • Checking installation directory:
 $ >> tree ~/Applications/mycmake-app
/home/user/Applications/mycmake-app
├── bin
│   ├── app1
│   └── app2
├── data
│   └── LICENSE.txt
└── lib
    ├── libtool.a
    └── libutil.so

3 directories, 5 files
  • Testing applications:
 # Attempt to run executable app1 
 $ >> ~/Applications/mycmake-app/bin/app1 
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK. 


 # Attempt to run executable app2   
 $ >> ~/Applications/mycmake-app/bin/app2
 [INFO] Application 2 Called Ok.
/home/mxpkf8/Applications/mycmake-app/bin/app2: symbol lookup error: /home/mxpkf8/Applications/mycmake-app/bin/app2: undefined symbol: _ZN4util13util_functionEi

 $ >> env LD_LIBRARY_PATH=$HOME/Applications/mycmake-app/lib  ~/Applications/mycmake-app/bin/app2
 [INFO] Application 2 Called Ok.
 [TRACE] util_function() called with n = 20
 [TRACE] Called tool_lib_function() OK. 
  • Running from applications from any directory [SOLUTION 1].
# [FAILURE] Attempt to run app1 from any directory 
$ app1
bash: app1: command not found

# Solution: Add the install directory to $PATH environment variable
# either in the shell or in the configuration files ~/.bashrc or ~/.profile
$ export PATH=$HOME/Applications/mycmake-app/bin:$PATH

$ app1
 [INFO] Application 2 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK. 

$ app2
 [INFO] Application 1 Called Ok.
app2: symbol lookup error: app2: undefined symbol: _ZN4util13util_functionEi

# Not recommended 
$ export LD_LIBRARY_PATH=$HOME/Applications/mycmake-app/lib:$LD_LIBARY_PATH

$ app2
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 20
 [TRACE] Called tool_lib_function() OK. 
  • Running from applications from any directory [SOLUTION 2].
    • Create symbolic links from the install directory to any directory listed in $PATH variable such as /usr/local/bin
$ ln -s ~/Applications/mycmake-app/bin/app1 /usr/local/bin/app1
$ ln -s ~/Applications/mycmake-app/bin/app2 /usr/local/bin/app2  
  • Checking shared libraries dependencies
$ >> ldd  ~/Applications/mycmake-app/bin/app1
linux-vdso.so.1 (0x00007ffc945f6000)
libutil.so => /home/user/Applications/mycmake-app/bin/../lib/libutil.so (0x00007f6f95035000)
libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f6f94e23000)
libm.so.6 => /lib64/libm.so.6 (0x00007f6f94cdd000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f6f94cc2000)
libc.so.6 => /lib64/libc.so.6 (0x00007f6f94af8000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6f9503c000)

Build and Install application globally (system-wide install)

  • Run a docker-image containing building tools for simulating a virtual machine.
$ docker run -it --rm -v $PWD:/cwd -w /cwd --entrypoint=bash linux-build
  • The following commands are performed within the docker image:
    • Note: The default behavior of CMake install target on Linux is to install libraries and executables in a system-wide mode to the paths /usr/local/lib and /usr/local/bin/. The shortcoming of installing in this way are: different versions of the same applications cannot coexist; possible dependency hell if installed application libraries overrides existing ones already being used by other applications.
$ cmake --config Release -H. -B_install 

$ cmake --build _install --target install
    ... ... ... ... ... ... ... ... 
    ... ... ... ... ... 
 Install the project...
 /hbb/bin/cmake -P cmake_install.cmake
 -- Install configuration: ""
 -- Installing: /usr/local/bin/app1
 -- Set runtime path of "/usr/local/bin/app1" to "$ORIGIN:$ORIGIN/../lib"
 -- Installing: /usr/local/bin/app2
 -- Set runtime path of "/usr/local/bin/app2" to ""
 -- Installing: /usr/local/lib/libutil.so
 -- Installing: /usr/local/lib/libtool.a
 -- Installing: /usr/local/data/LICENSE.txt
  • Testing executables:
$ app1
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK. 

$ app2
 [INFO] Application 2 Called Ok.
app2: symbol lookup error: app2: undefined symbol: _ZN4util13util_functionEi
[root@5bf932c0a153 cwd]# 
  • Checking executables:
 $ which app1
 /usr/local/bin/app1

 $ which app2
 /usr/local/bin/app2

$ ldd $(which app1)
   linux-vdso.so.1 =>  (0x00007ffe2752b000)
   libutil.so => /usr/local/bin/../lib/libutil.so (0x00007f1530013000)
   libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007f152fd0d000)
   libm.so.6 => /lib64/libm.so.6 (0x00007f152fa89000)
   libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f152f873000)
   libc.so.6 => /lib64/libc.so.6 (0x00007f152f4df000)
   /lib64/ld-linux-x86-64.so.2 (0x00007f1530215000)


$ ldd $(which app2)
   linux-vdso.so.1 =>  (0x00007ffdad757000)
   libutil.so => /usr/lib64/libutil.so (0x00007fed49570000)
   libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007fed4926a000)
   libm.so.6 => /lib64/libm.so.6 (0x00007fed48fe6000)
   libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fed48dd0000)
   libc.so.6 => /lib64/libc.so.6 (0x00007fed48a3c000)
   /lib64/ld-linux-x86-64.so.2 (0x00007fed49773000)

1.8.3 Building and installing on Windows with MSVC

Building and installing locally (non sytem wide mode)

  • Clone repository:
$ git clone https://github.com/caiorss/cmake-cpp-nested cnest 
$ cd cnest
  • Configure:
    • Note: If the option -G is omitted, CMake will chose the newest version of MSVC installed on the current machine.
    • Note: Visual Sudio Compilers MSVC older than 2019 versions does not use the command line switch -A for specifying the architecture. For instance, for MSVC 2017, the command line switch should be:
      • -G "Visual Sudio 15 2017" for x86 (IA32 - 32 bits)
      • -G "Visual Sudio 15 2017 Win64" for x86-64 64 bits (AMD64 or IA64)
      • -G "Visual Sudio 15 2017 ARM" for ARM architecture
# For Visual Studio MSVC 2019 / x86-64 (AMD64 or IA64 - 64 bits)
$ cmake --Config Release -H. -B_install1 -G "Visual Studio 16 2019" -A x64 -DCMAKE_INSTALL_PREFIX=%USERPROFILE%\Desktop\mycmake-app

# For Visual Studio MSVC 2017 / x86-64 (64 bits)
$ cmake --Config Release -H. -B_install1 -G "Visual Studio 16 2017 Win64" -DCMAKE_INSTALL_PREFIX=%USERPROFILE%\Desktop\mycmake-app

# For Visual Studio MSVC 2017 x86 - 32 bits 
$ cmake --Config Release -H. -B_install1 -G "Visual Studio 16 2017" -DCMAKE_INSTALL_PREFIX=%USERPROFILE%\Desktop\mycmake-app
  • Build all targets:
$ cmake --build _install1 --target
  • Show contents of build directory
    • Note: ls => is Unix tool ported for Windows, provided by bash and git on Windows.
$ ls _install1

ALL_BUILD.vcxproj          CMake_With_Makefile.sln  run-app1.vcxproj
ALL_BUILD.vcxproj.filters  CMakeCache.txt           run-app1.vcxproj.filters
app1.dir/                  CMakeFiles/              run-app2.vcxproj
app1.vcxproj               Debug/                   run-app2.vcxproj.filters
app1.vcxproj.filters       INSTALL.vcxproj          x64/
app2.dir/                  INSTALL.vcxproj.filters  ZERO_CHECK.vcxproj
app2.vcxproj               install_manifest.txt     ZERO_CHECK.vcxproj.filters
app2.vcxproj.filters       lib_tool/
cmake_install.cmake        lib_util/

$ ls _install1/Debug
app1.exe*  app1.pdb   app2.ilk  tool.lib  util.dll*  util.ilk  util.pdb
app1.ilk   app2.exe*  app2.pdb  tool.pdb  util.exp   util.lib

# ----- Check file types ----------------------------------------------#

$ file _install1/Debug/app1.exe
_install1/Debug/app1.exe: PE32+ executable (console) x86-64, for MS Windows

$ file _install1/Debug/app1.ilk
_install1/Debug/app1.ilk: data

$ file _install1/Debug/app1.pdb
_install1/Debug/app1.pdb: MSVC program database ver 7.00, 4096*267 bytes

$ file _install1/Debug/app1.lib
_install1/Debug/app1.lib: cannot open `_install1/Debug/app1.lib' (No such file or directory)

$ file _install1/Debug/tool.lib
_install1/Debug/tool.lib: current ar archive

$ file _install1/Debug/util.exp
_install1/Debug/util.exp: Intel amd64 COFF object file, not stripped, 2 sections, symbol offset=0xb38, 38 symbols
  • Testing executable targets:
$ _install1\Debug\app1.exe
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit

$ _install1\Debug\app2.exe
 [INFO] Application 2 Called Ok.
 [TRACE] util_function() called with n = 20
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit
  • Peform installation target:
$ cmake --build _install1 --target install
Microsoft (R) Build Engine version 16.8.2+25e4d540b for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

  tool.vcxproj -> Z:\cnest\_install1\Debug\tool.lib
  Auto build dll exports
  util.vcxproj -> Z:\cnest\_install1\Debug\util.dll
  app1.vcxproj -> Z:\cnest\_install1\Debug\app1.exe
  app2.vcxproj -> Z:\cnest\_install1\Debug\app2.exe
  -- Install configuration: "Debug"
  -- Installing: C:/Users/h03fx/Desktop/mycmake-app/bin/app1.exe
  -- Installing: C:/Users/h03fx/Desktop/mycmake-app/bin/app2.exe
  -- Installing: C:/Users/h03fx/Desktop/mycmake-app/lib/util.lib
  -- Installing: C:/Users/h03fx/Desktop/mycmake-app/bin/util.dll
  -- Installing: C:/Users/h03fx/Desktop/mycmake-app/lib/tool.lib
  -- Installing: C:/Users/h03fx/Desktop/mycmake-app/data/LICENSE.txt
  • Listing installation directory.
 $ >> tree mycmake-app 
mycmake-app
├── bin
│   ├── app1.exe
│   ├── app2.exe
│   └── util.dll
├── data
│   └── LICENSE.txt
└── lib
    ├── tool.lib
    └── util.lib

3 directories, 6 files
  • Testing executables.
$ %USERPROFILE%\Desktop\mycmake-app\bin\app1
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit


$ %USERPROFILE%\Desktop\mycmake-app\bin\app2
 [INFO] Application 2 Called Ok.
 [TRACE] util_function() called with n = 20
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit


$ %USERPROFILE%\Desktop\mycmake-app\bin\app1.exe
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit

Building and installing globally (sytem wide mode)

  • Building and installing with default generator
    • Note: This step requires running a terminal emulator with administrative privilege.
$ cmake --Config Release -H. -B_install2 -G "Visual Studio 16 2019" -A x64

$ cmake --build _install2 --target install
Microsoft (R) Build Engine version 16.6.0+5ff7b0c9e for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

  tool.vcxproj -> C:\Users\mxpkf8\Desktop\cnest\_install2\Debug\tool.lib
  Auto build dll exports
  util.vcxproj -> C:\Users\mxpkf8\Desktop\cnest\_install2\Debug\util.dll
  app1.vcxproj -> C:\Users\mxpkf8\Desktop\cnest\_install2\Debug\app1.exe
  app2.vcxproj -> C:\Users\mxpkf8\Desktop\cnest\_install2\Debug\app2.exe
  -- Install configuration: "Debug"
  -- Installing: C:/Program Files (x86)/CMake_With_Makefile/bin/app1.exe
  -- Installing: C:/Program Files (x86)/CMake_With_Makefile/bin/app2.exe
  -- Installing: C:/Program Files (x86)/CMake_With_Makefile/lib/util.lib
  -- Installing: C:/Program Files (x86)/CMake_With_Makefile/bin/util.dll
  -- Installing: C:/Program Files (x86)/CMake_With_Makefile/lib/tool.lib
  -- Installing: C:/Program Files (x86)/CMake_With_Makefile/data/LICENSE.txt
  • Running:
$ "C:/Program Files/CMake_With_Makefile/bin/app1.exe"
[INFO] Application 1 Called Ok.
[TRACE] util_function() called with n = 100
[TRACE] Called tool_lib_function() OK.
Type RETURN to exit

$ "C:/Program Files/CMake_With_Makefile/bin/app2.exe"
 [INFO] Application 2 Called Ok.
 [TRACE] util_function() called with n = 20
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit

$ "%PROGRAMFILES%"\CMake_With_Makefile\bin\app1.exe
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit


1.8.4 Building and installing on Windows with MinGW

Clone the repository:

$ git clone https://github.com/caiorss/cmake-cpp-nested cnest 
$ cd cnest

Build and install in a non-system wide way

  • Perform configuration step:
$ cmake --config Debug -H. -B_buildmg -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=%USERPROFILE%\Desktop\cnest-mingw-install
  • Perform build step:
$ cmake --build _buildmg --target
  • Check build directory:
$ ls _buildmg
app1.exe  cmake_install.cmake  CMakeFiles  lib_util   libutil.dll    Makefile
app2.exe  CMakeCache.txt       lib_tool    libtool.a  libutil.dll.a
  • Test executables:
$ _buildmg/app1.exe
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit

$ _buildmg/app1.exe
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit
  • Run install target:
$ cmake --build _buildmg --target install
  ...   ...   ...   ...   ...   ... 
  ...   ...   ...   ...   ...   ... 

 Install the project...
C:\Users\myuser\scoop\apps\cmake\3.19.2\bin\cmake.exe -P cmake_install.cmake
-- Install configuration: ""
-- Installing: C:/Users/myuser/Desktop/cnest-mingw-install/bin/app1.exe
-- Installing: C:/Users/myuser/Desktop/cnest-mingw-install/bin/app2.exe
-- Installing: C:/Users/myuser/Desktop/cnest-mingw-install/lib/libutil.dll.a
-- Installing: C:/Users/myuser/Desktop/cnest-mingw-install/bin/libutil.dll
-- Installing: C:/Users/myuser/Desktop/cnest-mingw-install/lib/libtool.a
-- Installing: C:/Users/myuser/Desktop/cnest-mingw-install/data/LICENSE.txt
  • Run executables from install directory:
$ %USERPROFILE%\Desktop\cnest-mingw-install\bin\app1.exe
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit

$ %USERPROFILE%\Desktop\cnest-mingw-install\bin\app2.exe
 [INFO] Application 2 Called Ok.
 [TRACE] util_function() called with n = 20
 [TRACE] Called tool_lib_function() OK.
 Type RETURN to exit

1.8.5 Building and installing on Mac OSX

  • Clone repository:
$ git clone https://github.com/caiorss/cmake-cpp-nested cnest 
$ cd cnest

Building installing (non-system wide way)

Configure:

$ cmake --config Release -H. -B_build -DCMAKE_INSTALL_PREFIX=$HOME/Desktop/myappx

-- The C compiler identification is AppleClang 11.0.3.11030032
-- The CXX compiler identification is AppleClang 11.0.3.11030032
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc - works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ - works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done

-- Generating done
-- Build files have been written to: /Volumes/data/cnest/_build

Build:

$ cmake --build _build --target all 

View build-tree:

$ tree -L 2 _build
_build
├── CMakeCache.txt
 ... ... ... ... ... 
 ... ... ... ... ... 
├── Makefile
├── app1
├── app2
├── cmake_install.cmake
├── lib_tool
│   ├── CMakeFiles
│   ├── Makefile
│   ├── cmake_install.cmake
│   └── libtool.a
└── lib_util
    ├── CMakeFiles
    ├── Makefile
    ├── cmake_install.cmake
    └── libutil.dylib

Show format of binary files:

$ file _build/app1
_build/app1: Mach-O 64-bit executable x86_64

$ file _build/lib_tool/libtool.a
_build/lib_tool/libtool.a: current ar archive random library

$ file _build/lib_util/libutil.dylib
_build/lib_util/libutil.dylib: Mach-O 64-bit dynamically linked shared library x86_64

Test applications:

$ _build/app1
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK. 

$ _build/app2
 [INFO] Application 2 Called Ok.
 [TRACE] util_function() called with n = 20
 [TRACE] Called tool_lib_function() OK.  

Install:

$ cmake --build _build --target install
  ... ... ... ... ... ... ... ... ...
  ... ...  ... .... ... ... ... ... .. 
/Library/Developer/CommandLineTools/usr/bin/make  -f CMakeFiles/Makefile2 preinstall
make[1]: Nothing to be done for `preinstall'.
Install the project...
/usr/local/Cellar/cmake/3.17.3/bin/cmake -P cmake_install.cmake
-- Install configuration: ""
-- Installing: /Users/unix/Desktop/myappx/bin/app1
-- Installing: /Users/unix/Desktop/myappx/bin/app2
-- Installing: /Users/unix/Desktop/myappx/lib/libutil.dylib
-- Installing: /Users/unix/Desktop/myappx/lib/libtool.a
-- Installing: /Users/unix/Desktop/myappx/data/LICENSE.txt

Show installation directory:

$ tree $HOME/Desktop/myappx
/Users/unix/Desktop/myappx
├── bin
│   ├── app1
│   └── app2
├── data
│   └── LICENSE.txt
└── lib
    ├── libtool.a
    └── libutil.dylib

3 directories, 5 files

Test applications from install directory:

$ /Users/unix/Desktop/myappx/bin/app1
 [INFO] Application 1 Called Ok.
 [TRACE] util_function() called with n = 100
 [TRACE] Called tool_lib_function() OK. 


$ /Users/unix/Desktop/myappx/bin/app2
 [INFO] Application 2 Called Ok.
dyld: lazy symbol binding failed: Symbol not found: __ZN4util13util_functionEi
  Referenced from: /Users/unix/Desktop/myappx/bin/app2
  Expected in: /usr/lib/libutil.dylib

dyld: Symbol not found: __ZN4util13util_functionEi
  Referenced from: /Users/unix/Desktop/myappx/bin/app2
  Expected in: /usr/lib/libutil.dylib


$ env DYLD_LIBRARY_PATH=/Users/unix/Desktop/myappx/lib   /Users/unix/Desktop/myappx/bin/app2
 [INFO] Application 2 Called Ok.
 [TRACE] util_function() called with n = 20
 [TRACE] Called tool_lib_function() OK. 

Analysze depdencies of an executable:

$ otool -l  /Users/unix/Desktop/myappx/bin/app1

/Users/unix/Desktop/myappx/bin/app1:
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777223          3  0x00           2    19       1584 0x00218085
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 72
  segname __PAGEZERO
   vmaddr 0x0000000000000000
   vmsize 0x0000000100000000
 .... .. .   .... .. .   .... .. .    .... .. .   .... .. .   .... .. .
 .... .. .   .... .. .   .... .. .    .... .. .   .... .. .   .... .. .
Load command 17
      cmd LC_DATA_IN_CODE
  cmdsize 16
  dataoff 22088
 datasize 0
Load command 18
          cmd LC_RPATH
      cmdsize 40
         path @executable_path/../lib (offset 12)

Install in system-wide way

  • DO NOT ATTEMPT => It results in error as the command attempts to install the applications to /bin directory which is a read-only file system.
  • Unlike other unix-like operating systems which installs everything scattered across the system (executables to /bin and libraries to /lib), in Mac-OSX, the most used approach for deploying applications is to distribute applications as App bundles, which are directories terminated in .app suffix containing all executables, shared libraries dependencies (*.dylib), assets and metadata. The advantage of the app-bundle method of deployment are: applications are self-contained; they can be installed by just dragging and dropping them to the /Applications directory; they can be removed just by deleting a d single directory; different versions of the same application can be installed without disrupting each other or any other system library.
# Error: attemtps to install to read-only file system 
$ cmake --config Release -H. -B_build2 -DCMAKE_INSTALL_PREFIX=
$ cmake --build _build2 --target install

1.9 [DRAFT] Enabling and disabling preprocessor macros

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
project(MacroCmake)

set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON )

option(ENABLE_LOGGING "Enable compilation with logging" OFF)

#============== Set targets =============================#

            add_executable( macro-test macro-test.cpp    )
target_compile_definitions( macro-test PRIVATE
                                       CODE_VERSION="\\"0.25\\""
                           )

IF(ENABLE_LOGGING)
  target_compile_definitions( macro-test PRIVATE LOGGING)
ENDIF()

File: macro-test.cpp

#include <iostream>

#ifndef CODE_VERSION
  #define CODE_VERSION "0.1" 
#endif 

int main(int argc, char** argv)
{

  if(argc < 2)
  {
      std::fprintf(stderr,  " Product version: %s \n", CODE_VERSION);
      std::fprintf(stderr, " Usage: %s <NUMBER> \n", argv[0]);
      return EXIT_FAILURE;
  }

  int n    = std::stoi(argv[1]);
  int prod = 1;

  for(int i = 1; i <= n; i++){
    prod *= i;

    #if LOGGING 
      std::fprintf(stderr, " => i = %d ; prod = %d \n", i, prod); 
    #endif 
  }

  std::printf(" => product = %d \n", prod); 

  // Same as: return 0;
  return EXIT_SUCCESS;
}

Building

Build without enabling logging.

$ >> cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug  
$ >> cmake --build _build --target all

$ >> _build/macro-test 
Product version: "0.25" 
Usage: _build/macro-test <NUMBER> 

$ >> _build/macro-test 6
=> product = 720 

Build enabling logging.

$ >> cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug -DENABLE_LOGGING=ON
$ >> cmake --build _build --target all

$ >> _build/macro-test 6
=> i = 1 ; prod = 1 
=> i = 2 ; prod = 2 
=> i = 3 ; prod = 6 
=> i = 4 ; prod = 24 
=> i = 5 ; prod = 120 
=> i = 6 ; prod = 720 
=> product = 720 

Build defining macros on command line: (Note: this procedure is not portable, it will not work with MSVC - visual C++ compiler as the MSVC macros uses '/D' slash instead of '-D' dash for setting macros.)

$ >> cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS='-DLOGGING -DCODE_VERSION=\"3.0\"'
$ >> cmake --build _build --target all

$ >> _build/macro-test
Product version: 3.0 
Usage: _build/macro-test <NUMBER> 

$ >> _build/macro-test 4
=> i = 1 ; prod = 1 
=> i = 2 ; prod = 2 
=> i = 3 ; prod = 6 
=> i = 4 ; prod = 24 
=> product = 24 

1.10 Consuming or Dependencies

1.10.1 Overview

Note:

  • object-code => Here means any compiled machine code, any file with the extensions: *.exe, *.o, *.obj, *.so, *.dylib

CMake can consume libraries or external dependencies in many different ways:

  • Library Installation with building system
    • Installing the libraries with CMake, GNU Make, GNU autotools or any building systems and consuming them via find_package(LIB) and target_link_libraries( ….).
    • Disadvantages:
      • As C++ compilers do not have a standard and stable ABI - Application Binary Interface, the library needs to be installed for every different compiler and different version of the same compiler, otherwise it will not be possible to build an application that links against a library built with different compiler.
      • Installing multiple versions of the same library may result in dependency hell breaking dynamic-linked applications depending on it.
  • Library installation from operating system's package manager
    • Installing libraries with a package manager such as Linux Fedora (dnf), Ubuntu (apt-get or Mac OSX (brew) and consuming them via find_package(LIB) and target_link_libraries(…).
    • Disadvantages:
      • The installation is not reproducible in other platforms and often, it is not possible to install multiple versions of the same library and it may not be available in all package managers.
  • CMake add_subdirectory(lib/<LIBRARY_DIR>)
    • The library directory containing the source code is added to the project either via CMake ExternalProject, manual copy or via git add submodule. The library is consumed in the project by adding a line: add_subdirectory(lib/<LIBRARY_DIR>) to the file CMaklists.txt. And the applications links against the library with target_link_libraries(app lib::libname).
    • Advantages:
      • No ABI issues as the library is built with the same compiler as the application.
      • Better for static linking.
      • More simple as it does not require the library maintainer to provide a CMake module for find_package.
      • Better reproducibility and easier compilation on different operating systems and also easier cross-compilation for embedded targets.
      • No dependency hell: As multiple versions of the library can be used in different projects without disrupting each other.
    • Disadvantages:
      • Longer compile-time
      • More disk space spending
      • The library is recompiled for every project.
  • C++-specific Package Manager with CMake Integration
    • Package managers automates the process of downloading libraries' source code and compiling them alongside the project. It is no possible to generalize C++ package managers as there are many differences among them. In the case of Conan package manager, it builds and installs the library object-code and header files to the Conan's cache directory where the library can be reused on many projects and recompiled on-demand if the compiler used to build the project is not ABI-compatible with the compiler used to build the libraries object codes.
    • Note: there are other package managers built as CMake scripts, but they fall short when dealing with libraries using other building systems such as GNU autotools or GNU Makefiles.
    • Advantages:
      • Automates the process of getting and installing libraries.
      • Saves disk space.
      • Shorter compile-time as library-object codes can be reused in other projects.
      • No ABI issues
      • No dependency hell
      • Better for static linking and cross-compilation for embedded systems.
      • Reproducible on many different operating systems.
      • Makes easier to install big libraries, frameworks or libraries with many dependencies such as Boost and Poco libraries which depends on openssl which is problematic to build on Windows.
    • Disadvantages:
      • conan is not mainstream and there are still many competing package managers.
      • conan requires a Python installation, although it is not a big issue since there are many tools using Python as an embedded scripting language such GNU Debugger GDB or Windows Debugger.

1.10.2 CMake add_subdirectory

External libraries or sub-projects can be consumed with CMake command add_subdirectory(<PATH-TO/LIB-DIRECTORY>) which builds the project and the library source code <LIB-DIRECTORY> containing a CMakeLists.txt. The benefits of this approach is that libraries are:

  • Libraries does not need to be installed in the system before usage.
  • No ABI incompatibility issue as the compiler used for building the application and library are the same.
  • Better tracking of dependencies and no dependency hell as different versions of the same library can be used in many project without disrupting each other.

Drawbacks:

  • Higher disk space usage as every project needs a copy of the library sources.
  • Libraries need to use CMake as building system and contain a CMakeLists.txt file at root directory, otherwise, it will not be possible to use add_subdirectory command.
  • Possible complexity of git add submodule.

Libraries used:

Creating project

(1) Create project and git repository

$ git init .
Initialized empty Git repository in /home/user/projects/cmake-experiment/.git/

$ git add CMakeLists.txt main.cpp 

$ git commit -m started
[master (root-commit) 8e60c5c] started
 2 files changed, 16 insertions(+)
 create mode 100644 CMakeLists.txt
 create mode 100644 main.cpp

(2) Add external libraries to the project via git submodule or by copying them manually.

$ git submodule add  https://github.com/gabime/spdlog extern/spdlog
$ git submodule add https://github.com/cpputest/cpputest extern/cpputest

(3) Show current directory:

$ tree -L 2 .
.
├── CMakeLists.txt
├── CMakeLists.txt.user
├── extern
│   ├── cpputest
│   └── spdlog
└── main.cpp

3 directories, 3 files

File: CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(cmake-experiment)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

add_subdirectory(extern/spdlog)
add_subdirectory(extern/cpputest)

add_executable(app main.cpp)
target_link_libraries(app spdlog::spdlog CppUTest)
  • The command add_subdirectory adds the sources of the library spdlog to the current build by processing the file extern/spdlog/CMakeLists.txt.
  • Libraries can be made easier to use and consume by listing in their documentation the targets they define which client codes can link against with add_library. If the those targets are not mentioned the user has to inspect all libraries CMakeLists.txt files which can be cumbersome in libraries with many nested directories.

Finding targets defined by spdlog and CppUtest libraries:

  • The file extern/spdlog/CMakeLists.txt contains the following lines that defines library targets which the top-level CMakeLists.txt file can consume:
add_library(spdlog INTERFACE)
add_library(spdlog::spdlog ALIAS spdlog)
  • The file extern/CppUtest/CMakeLists.txt contains the following lines which builds the extern/CppUtest/src/CppUtest/CMakeLists.txt as a sub-project.
include_directories(${CppUTestRootDirectory}/include)
add_subdirectory(src/CppUTest)
  • The file extern/CppUtest/src/CppUtest/CMakeLists.txt contains the following lines which defines the target CppUtest which the top level CMakeLists.txt that has add_subdirectory(extern/cpputest) can link against:
add_library( CppUTest
            CommandLineArguments.cpp
            MemoryLeakWarningPlugin.cpp
            TestHarness_c.cpp
            ... ... ... ... 
           )

File: main.cpp

#include <iostream>
#include <spdlog/spdlog.h>
#include <CppUTest/CommandLineTestRunner.h>
#include <CppUTest/Utest.h>
#include <CppUTest/TestHarness.h>

double func(double x)
{
    return 3 * x * x - 21.0 * x + 36.0;
}

TEST_GROUP(MainTest)
{
};

TEST(MainTest, functionPoints)
{
    spdlog::info("Running functionPointsTest.");

    DOUBLES_EQUAL(0.0, func(4.0), 0.001);
    DOUBLES_EQUAL(0.0, func(3.0), 0.001);
    DOUBLES_EQUAL(0.0, func(3.0), 0.001);
    DOUBLES_EQUAL(10.0, func(126.0), 0.001);

    // Supposed to fail
    DOUBLES_EQUAL(1.0, func(18.0), 0.001);

    spdlog::info("Ending functionPointsTest.");
};

int main(int argc, char** argv)
{
    SPDLOG_TRACE("Entering function");
    spdlog::info("Starting server OK.");
    spdlog::error("Server crashed. ");
    spdlog::info("Received request from 192.168.10.1 at port 9090");
    spdlog::info("Sending response to client 192.168.100.1 connection ID 10");
    SPDLOG_TRACE("Exit application");

    std::cout << "========== Running Tests ============" << std::endl;

    return RUN_ALL_TESTS(argc, argv);
}

Build on Command Line

$ cmake -H. -B_build -GNinja -DCMAKE_BUILD_TYPE=Debug
 ... ... ... ... ... ... 
-------------------------------------------------------

-- Configuring done
-- Generating done
-- Build files have been written to: /home/archbox/projects/cmake-experiment/_build

$ cmake --build _build --target all

Running

$ _build/app 
[2019-07-11 17:24:59.450] [info] Starting server OK.
[2019-07-11 17:24:59.450] [error] Server crashed. 
[2019-07-11 17:24:59.450] [info] Received request from 192.168.10.1 at port 9090
[2019-07-11 17:24:59.451] [info] Sending response to client 192.168.100.1 connection ID 10
========== Running Tests ============
[2019-07-11 17:24:59.451] [info] Running functionPointsTest.

../main.cpp:23: error: Failure in TEST(MainTest, functionPoints)
        expected <10>
        but was  <45018> threshold used was <0.001>

.
Errors (1 failures, 1 tests, 1 ran, 4 checks, 0 ignored, 0 filtered out, 1 ms)

1.10.3 FetchContent module (Approach 1)

The CMake module FetchContent can fetch dependencies at configuration time from many possible sources such as GIT remote repositories, http servers (URLs) and compressed files such as *.tar, *.tgz, *.zip and so on.

Documentation:

See also:

Example:

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(cmake-experiment)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

# See documentation at: https://cmake.org/cmake/help/latest/module/FetchContent.html
include(FetchContent)

#========== Fetch cpputest ============================#

FetchContent_Declare(
    cpputest
    GIT_REPOSITORY  "https://github.com/cpputest/cpputest.git"
    GIT_TAG         "v3.8"
)
FetchContent_GetProperties(cpputest)
if(NOT cpputest_POPULATED)
    FetchContent_Populate(cpputest)
    message(" [INFO] cpputest_SOURCE_DIR = ${cpputest_SOURCE_DIR} ")
    message(" [INFO] cpputest_BINARY_DIR = ${cpputest_BINARY_DIR} ")

    # Optional => From: https://github.com/cpputest/cpputest/blob/master/CMakeLists.txt
    set(TEST OFF)
    set(TEST_DETAILED OFF)

    add_subdirectory(${cpputest_SOURCE_DIR} ${cpputest_BINARY_DIR})

    # Note: this path was found by trial and error, it is library-specific
    include_directories(${cpputest_SOURCE_DIR}/include)
endif()

#===== Fetch spdlog as archive ====================#

FetchContent_Declare(
    spdlog
    URL       "https://github.com/gabime/spdlog/archive/v1.3.1.zip"
)
FetchContent_GetProperties(spdlog)
if(NOT spdlog_POPULATED)
    FetchContent_Populate(spdlog)
    # message(" [INFO] spdlog_SOURCE_DIR = ${spdlog_SOURCE_DIR} ")
    # message(" [INFO] spdlog_BINARY_DIR = ${spdlog_BINARY_DIR} ")

    # Optional => Set library build options
    # Build options at: https://github.com/gabime/spdlog/blob/v1.x/CMakeLists.txt
    set(SPDLOG_BUILD_EXAMPLES OFF)
    set(SPDLOG_BUILD_BENCH    OFF)
    set(SPDLOG_BUILD_TESTS    OFF)

    add_subdirectory(${spdlog_SOURCE_DIR} ${spdlog_BINARY_DIR})
endif()

# ========= Target Settings ==========================#
add_executable(app main.cpp)
target_link_libraries(app  CppUTest spdlog::spdlog)

File: main.cpp

#include <iostream>
#include <spdlog/spdlog.h>
#include <CppUTest/CommandLineTestRunner.h>
#include <CppUTest/Utest.h>
#include <CppUTest/TestHarness.h>

double func(double x)
{
    return 3 * x * x - 21.0 * x + 36.0;
}

TEST_GROUP(MainTest)
{
};

TEST(MainTest, functionPoints)
{
    spdlog::info("Running functionPointsTest.");

    DOUBLES_EQUAL(0.0, func(4.0), 0.001);
    DOUBLES_EQUAL(0.0, func(3.0), 0.001);
    DOUBLES_EQUAL(0.0, func(3.0), 0.001);
    DOUBLES_EQUAL(10.0, func(126.0), 0.001);

    // Supposed to fail
    DOUBLES_EQUAL(1.0, func(18.0), 0.001);

    spdlog::info("Ending functionPointsTest.");
};

int main(int argc, char** argv)
{
    SPDLOG_TRACE("Entering function");
    spdlog::info("Starting server OK.");
    spdlog::error("Server crashed. ");
    spdlog::info("Received request from 192.168.10.1 at port 9090");
    spdlog::info("Sending response to client 192.168.100.1 connection ID 10");
    SPDLOG_TRACE("Exit application");

    std::cout << "========== Running Tests ============" << std::endl;

    return RUN_ALL_TESTS(argc, argv);
}

Building and running from command line:

Configuration step

$ cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug
-- The C compiler identification is GNU 8.3.1
-- The CXX compiler identification is GNU 8.3.1
-- Check for working C compiler: /usr/lib64/ccache/cc
-- Check for working C compiler: /usr/lib64/ccache/cc -- works

 ... .... ... ... ... ... ... ... ... ... 

-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/projects/cmake-experiment/build

Building step

$ cmake --build build --target

Check build directory:

$ tree -L 2 build
build
├── app
├── CMakeCache.txt
├── CMakeFiles
│   ├── 3.14.3
│   ├── app.dir
│   ├── cmake.check_cache
│   ├── CMakeDirectoryInformation.cmake
│   ├── CMakeError.log
│   ├── CMakeOutput.log
│   ├── CMakeRuleHashes.txt
│   ├── CMakeTmp
│   ├── feature_tests.bin
│   ├── feature_tests.c
│   ├── feature_tests.cxx
│   ├── Makefile2
│   ├── Makefile.cmake
│   ├── progress.marks
│   └── TargetDirectories.txt
├── cmake_install.cmake
├── _deps
│   ├── cpputest-build
│   ├── cpputest-src
│   ├── cpputest-subbuild
│   ├── spdlog-build
│   ├── spdlog-src
│   └── spdlog-subbuild
├── Makefile
├── spdlogConfigVersion.cmake
└── spdlog.pc

11 directories, 18 files

Running:

$ build/app 
[2019-07-14 01:16:58.305] [info] Starting server OK.
[2019-07-14 01:16:58.305] [error] Server crashed. 
[2019-07-14 01:16:58.305] [info] Received request from 192.168.10.1 at port 9090
[2019-07-14 01:16:58.305] [info] Sending response to client 192.168.100.1 connection ID 10
========== Running Tests ============
[2019-07-14 01:16:58.305] [info] Running functionPointsTest.

/home/archbox/projects/cmake-experiment/main.cpp:23: error: Failure in TEST(MainTest, functionPoints)
        expected <10>
        but was  <45018> threshold used was <0.001>

.
Errors (1 failures, 1 tests, 1 ran, 4 checks, 0 ignored, 0 filtered out, 0 ms)

1.10.4 FetchContent module (Approach 2)

The usage of the CMake module FetchContent can be simplified by defining functions and macros encapsulating its functionalities.

Libraries Used:

Example:

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(cmake-experiment)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

# See documentation at: https://cmake.org/cmake/help/latest/module/FetchContent.html

#========== Macros for automating Library Fetching =============#
include(FetchContent)

# Download library from Github and add it as sub-project
#
macro(Download_Library_Git  NAME TAG REPOSITORY_URL)
    FetchContent_Declare(
        ${NAME}
        GIT_REPOSITORY  ${REPOSITORY_URL}
        GIT_TAG         ${TAG}
    )
    FetchContent_GetProperties(${NAME})
    if(NOT cpputest_POPULATED)
        FetchContent_Populate(${NAME})
        message("${NAME}_SOURCE_DIR} = ${${NAME}_SOURCE_DIR}")
        add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR})
    endif()
endmacro()

macro(Download_Library_Github LIBRARY_NAME TAG USER PROJECT )
    Download_Library_Git(
        ${LIBRARY_NAME}
        ${TAG}
        "https://github.com/${USER}/${PROJECT}.git"
        )
endmacro()

# Download library archive (zip, *.tar.gz, ...) from URL
macro(Download_Library_Url NAME URL)
    FetchContent_Declare(${NAME} URL  ${URL})
    FetchContent_GetProperties(${NAME})
    if(NOT ${NAME}_POPULATED)
        FetchContent_Populate(${NAME})
        add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR})
    endif()
endmacro()

# Add path header files to the include path of all targets.
# => NAME => Library name added with any Download_Library command.
# => RELATIVE_PATH => Path to header files relative to library root directory
#
function(include_headers_directory NAME REALATIVE_PATH)
    include_directories("${${NAME}_SOURCE_DIR}/${RELATIVE_PATH}")
endfunction()

#============== Library Download =========================#

Download_Library_Url(
    spdlog
    "https://github.com/gabime/spdlog/archive/v1.3.1.zip"
    )

Download_Library_Github(
    cppcodec  # Library name - arbitrary
    v0.2      # Git tag
    tplgy     # Github user name
    cppcodec  # Name of project that will be cloned.
              # Note: It is does not always have the same name as the library.
    )

# Only use it if CMake cannot find the headers or the library is not
# set properly.
include_headers_directory(cppcodec ./cppcodec)

# ========= Targets Settings ==========================#

add_executable(mainapp main.cpp)
target_link_libraries(mainapp cppcodec spdlog::spdlog)

File: main.cpp

#include <iostream>
#include <spdlog/spdlog.h>

#include <cppcodec/base32_crockford.hpp>
#include <cppcodec/base64_rfc4648.hpp>


int main(int argc, char** argv)
{
    SPDLOG_TRACE("Entering function");

    spdlog::info("Starting server OK.");

    spdlog::info("Received request from 192.168.10.1 at port 9090");
    spdlog::info("Sending response to client 192.168.100.1 connection ID 10");

    using base32 = cppcodec::base32_crockford;
    using base64 = cppcodec::base64_rfc4648;

    std::vector<uint8_t> decoded = base64::decode("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
    std::cout << "decoded size (\"any carnal pleasure\"): " << decoded.size() << '\n';
    std::cout << base32::encode(decoded) << std::endl; // "C5Q7J833C5S6WRBC41R6RSB1EDTQ4S8"


    SPDLOG_TRACE("Exit application");

    return 0;
}

Running:

$ cd proejct_directory 
$ cmake -B_build -H. -DCMAKE_BUILD_TYPE=Debug
$ cmake --build _build --target all

# Run 
$ _build/mainapp 
[2019-07-19 17:46:56.306] [info] Starting server OK.
[2019-07-19 17:46:56.306] [info] Received request from 192.168.10.1 at port 9090
[2019-07-19 17:46:56.306] [info] Sending response to client 192.168.100.1 connection ID 10
decoded size ("any carnal pleasure"): 19
C5Q7J833C5S6WRBC41R6RSB1EDTQ4S8

1.10.5 FetchContent_makeAvailable (Approach 3)   opengl glfw graphics

This example demonstrates how to use FetchContent for building an OpenGL application that uses the GLFW library downloaded from github releases URL. The commands FetchContent_Declare and FetchContent_MakeAvaiable can be used with any other library as long as it has CMakeLists.txt in its root directory.

Key CMake Commands Uses:

  • FetchContent_MakeAvailable
  • FetchContent_Declare

See:

Install Development Dependencies for Ubuntu Linux

$ sudo apt-get install -y freeglut3-dev
$ sudo apt-get install -y libxi-dev
$ sudo apt-get install -y libxcursor-dev
$ sudo apt-get install -y libxrandr-dev
$ sudo apt-get install -y libxinerama-dev

Sample Project

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(GLFW_project)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_VERBOSE_MAKEFILE ON)

#================ GLFW Settings  ===============#

include(FetchContent)

# Set GLFW Options before FectchContent_MakeAvailable 
set( GLFW_BUILD_EXAMPLES OFF CACHE BOOL  "GLFW lib only" )
set( GLFW_BUILD_TESTS OFF CACHE BOOL     "GLFW lib only" )
set( GLFW_BUILD_DOCS OFF CACHE BOOL      "GLFW lib only" )
set( GLFW_BUILD_INSTALL OFF CACHE BOOL   "GLFW lib only" )

FetchContent_Declare(
  glfwlib
  URL   https://github.com/glfw/glfw/releases/download/3.3.2/glfw-3.3.2.zip
)

FetchContent_MakeAvailable(glfwlib)

#========= Find Packages =========================#

find_package(OpenGL REQUIRED)

#========== Target Configurations ================#

add_executable(app1 main.cpp)
target_link_libraries(app1 glfw OpenGL::GL glut GLU)

File: main.cpp

#include <GLFW/glfw3.h>
#include <GL/glu.h>
#include <GL/glut.h>

int main(int argc, char** argv)
{
    GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return -1;

    glutInit(&argc, argv);

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    glClearColor(0.0f, 0.5f, 0.6f, 1.0f);


    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window))
    {
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

        glRotated(15.0, 1.0, 1.0, 1.0);
        ::glutWireTeapot(0.65);

        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

Building from Command Line

Configuration step

  • $ cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug
$ cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug 
-- The C compiler identification is GNU 8.3.1
-- The CXX compiler identification is GNU 8.3.1
    ... ... ... .... ... 

-- Found OpenGL: /usr/lib64/libOpenGL.so   
-- Configuring done
-- Generating done
-- Build files have been written to: /home/archbox/projects/glfw-learning/_build

Building step

  • $ cmake –build _build –target all
$ cmake --build _build --target all 

... ... ... ... ... ... 
... ... ... ... ... ... 

Scanning dependencies of target glfw
gmake[2]: Leaving directory '/home/archbox/projects/glfw-learning/_build'
/usr/bin/gmake -f _deps/glfwlib-build/src/CMakeFiles/glfw.dir/build.make _deps/glfwlib-build/src/CMakeFiles/glfw.dir/build
gmake[2]: Entering directory '/home/archbox/projects/glfw-learning/_build'
[  5%] Building C object _deps/glfwlib-build/src/CMakeFiles/glfw.dir/context.c.o

 ... ... ... ... ... .. 
... ... ... ... ... ... 

[ 10%] Building C object _deps/glfwlib-build/src/CMakeFiles/glfw.dir/init.c.o

... ... ... ... ... ... 
... ... ... ... ... ... 

[ 89%] Linking C static library libglfw3.a

[100%] Linking CXX executable app1

Running

$ _build/app1 

opengl_glut_teapot1.png

Figure 1: Graphics Output

1.10.6 CPM.cmake module [best]   cmake best dependency

The CMake module CPM.cmake is a wrapper around CMake's FetchContent which simplifies downloading libraries and dependencies sub-projects. It can download libraries from GIT repositories, HTTP servers and FTP servers. The downside of this dependency management that it is only suitable for ligthweight libraries.

This dependency approach is suitable not for large libraries or frameworks with significant compile time, such as Boost libraries, QT Framework or Poco libraries, as a downloaded library will have to be recompiled for every new project. For this case, the best solution is to use a package manage such as Conan or Vcpkg which can reuse binary artifacts across many projects.

Official Repository:

Examples:

Sample Project

In this sample project, the CMakeLists.txt automatically downloads and installs CPM.cmake in the current project binary directory ${CMAKE_BINARY_DIR}. The module CPM.cmake provide the function 'CPMAddPackage' which is used for fetching the librares spdlog and cppcodec from HTTP server and GIT repository.

Gist:

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
project(Simple_Cmake_Project)

#========== Global Configurations =============#
#----------------------------------------------#

set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# ------------ Download CPM CMake Script ----------------#

## Automatically donwload and use module CPM.cmake
file(DOWNLOAD https://raw.githubusercontent.com/TheLartians/CPM.cmake/v0.26.2/cmake/CPM.cmake
                 "${CMAKE_BINARY_DIR}/CPM.cmake")
include("${CMAKE_BINARY_DIR}/CPM.cmake")

#----------- Add dependencies --------------------------#

CPMAddPackage(
    NAME spdlog
    URL  "https://github.com/gabime/spdlog/archive/v1.3.1.zip"

    # -->> Only enable this, if the library does not use CMake.
    #      The library uses only Makefile, GNU autotools and so on.  
    #      In other words, does not have a CMakeLists.txt at root directory.
    # DOWNLOAD_ONLY YES
)

CPMAddPackage(
    NAME               cppcodec 
    GITHUB_REPOSITORY  tplgy/cppcodec 
    GIT_TAG            v0.2
    #VERSION            0.2 

    OPTIONS 
        "BUILD_TESTING OFF"
)
include_directories( ${cppcodec_SOURCE_DIR}/ )

message([TRACE] " spdlog source = ${spdlog_SOURCE_DIR} ")
message([TRACE] " cppcodec source = ${cppcodec_SOURCE_DIR} ")

#----------- Set targets -------------------------------#

add_executable(app1 app1.cpp)
target_link_libraries(app1 cppcodec spdlog::spdlog)

File: app1.cpp

#include <iostream>
#include <spdlog/spdlog.h>

#include <cppcodec/base32_crockford.hpp>
#include <cppcodec/base64_rfc4648.hpp>


int main(int argc, char** argv)
{
    SPDLOG_TRACE("Entering function");

    spdlog::info("Starting server OK.");

    spdlog::info("Received request from 192.168.10.1 at port 9090");
    spdlog::info("Sending response to client 192.168.100.1 connection ID 10");

    using base32 = cppcodec::base32_crockford;
    using base64 = cppcodec::base64_rfc4648;

    std::string secret = "SGVsbG8gd29ybGQgQ01ha2UgQ1BNIHdpdGggYmFzZTY0IGVuY29uZGluZy4=";

    std::vector<uint8_t> decoded = base64::decode(secret);
    std::string text(decoded.begin(), decoded.end());

    std::cout << " =>> Decoded text = " << text << std::endl; 

    SPDLOG_TRACE("Exit application");

    return 0;
}

Building:

# Download code 
 $ git clone https://gist.github.com/caiorss/76b8540d8a358ab0c740709962e1e511 demo-cpm 
 $ cd demo-cpm

# [1] Configuration step 
 $ cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug
-- The C compiler identification is GNU 10.1.1
-- The CXX compiler identification is GNU 10.1.1
-- Check for working C compiler: /usr/bin/cc
  ... ....  ... ....  ... .... 
 ... ....  ... ....  ... .... 
-- CPM: adding package spdlog@ ()
-- Build type: Debug
-- CPM: adding package cppcodec@0.2 (v0.2)
[TRACE] spdlog source = /home/myuser/temp-projects/cmake-cpm/_build/_deps/spdlog-src 
[TRACE] cppcodec source = /home/myuser/temp-projects/cmake-cpm/_build/_deps/cppcodec-src 
-- Found PkgConfig: /usr/bin/pkg-config (found version "1.6.3") 
-- Checking for module 'catch2'
--   Package 'catch2', required by 'virtual:world', not found
-- Did NOT find system Catch2, instead using bundled version
-- Configuring done
-- Generating done
-- Build files have been written to: /home/myuser/temp-projects/cmake-cpm/_build

# [2] Building step 
$ cmake --build _build --target all

Running:

 $ _build/app1 
[2020-06-01 02:34:11.610] [info] Starting server OK.
[2020-06-01 02:34:11.610] [info] Received request from 192.168.10.1 at port 9090
[2020-06-01 02:34:11.610] [info] Sending response to client 192.168.100.1 connection ID 10
 =>> Decoded text = Hello world CMake CPM with base64 enconding

1.11 Show system information

cmake --sytem-information 

Output on Linux:

Avoid ctest truncation of output: CTEST_FULL_OUTPUT
========================================================
=== MAIN VARIABLES
========================================================
CMAKE_STATIC_LIBRARY_PREFIX == "lib"
CMAKE_STATIC_LIBRARY_SUFFIX == ".a"
CMAKE_SHARED_LIBRARY_PREFIX == "lib"
CMAKE_SHARED_LIBRARY_SUFFIX == ".so"
CMAKE_SHARED_MODULE_PREFIX == "lib"
CMAKE_SHARED_MODULE_SUFFIX == ".so"


CMAKE_DL_LIBS == "dl"
CMAKE_LIBRARY_PATH_FLAG == "-L"
CMAKE_LINK_LIBRARY_FLAG == "-l"
CMAKE_SKIP_RPATH == "NO"
CMAKE_SYSTEM_INFO_FILE == "Platform/Linux"
CMAKE_SYSTEM_NAME == "Linux"
CMAKE_SYSTEM == "Linux-4.18.18-200.fc28.x86_64"
CMAKE_CXX_COMPILER == "/usr/lib64/ccache/c++"
CMAKE_C_COMPILER == "/usr/lib64/ccache/cc"
CMAKE_COMPILER_IS_GNUCC == "1"
CMAKE_COMPILER_IS_GNUCXX == "1"

// C shared library flag
CMAKE_SHARED_LIBRARY_C_FLAGS == "-fPIC"
CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS == "-shared"
CMAKE_SHARED_LIBRARY_LINK_FLAGS == ""
... ...    ... ...    ... ...    ... ...    ... ... 

1.12 Show all modules

$ cmake --help-module-list

Output:

AddFileDependencies
AndroidTestUtilities
BundleUtilities
CMakeAddFortranSubdirectory
CMakeBackwardCompatibilityCXX
CMakeDependentOption
CMakeDetermineVSServicePack
CMakeExpandImportedTargets
CMakeFindDependencyMacro
CMakeFindFrameworks
CMakeFindPackageMode
CMakeForceCompiler
CMakeGraphVizOptions

... ... ... ... ... ... 

FindLAPACK
FindLATEX
FindLTTngUST
FindLibArchive
FindLibLZMA
FindLibXml2
FindLibXslt
FindLua
FindLua50
FindLua51
FindMFC
FindMPEG
FindMPEG2
FindMPI
FindMatlab
FindMotif
FindOpenACC

... ... ... ... ... ... ... ... 

1.13 Microsft Windows Specific

1.13.1 Statically link against MINGW runtime

Linking error reporting missing shared libraries is a common issue that may happen when distributing C or C+++ applications built with MINGW to other Windows machines without MINGW runtime installed. The solution for this issue is to static link the executable against MINGW runtime dependencies as shown in the next sample project.

Project Files

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(mingw-statically-linked)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_VERBOSE_MAKEFILE ON)


# ---------- T A R G E T S  ----------------------------#

# Executable linked dynamically against MinGW libraries
# It may fail to execute if the deployment machine
# does not have MINGW runtime libraries installed. 
       add_executable( winapp-dynam windows-application.cpp)
target_link_libraries( winapp-dynam stdc++fs )

# Executable statically linked against MingW runtinme  dependencies
# - which allows deploying it on any Windows machine.
#
# find_package( Threads REQUIRED)
add_executable( winapp-static windows-application.cpp)
target_link_libraries( winapp-static stdc++fs )

IF(MINGW)
   target_link_options( winapp-static PRIVATE                                 
                         -static-libgcc
                         -static-libstdc++
                         -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive
                        )
ENDIF()       


File: windows-application.cpp

#include <iostream>
#include <filesystem>

#include <windows.h>

namespace fs = std::filesystem;

void list_directory(std::string const& path);


int main()
{

    std::cerr << " [TRACE] Display root directory of current machine" << '\n';
    /* List root directory or C:\\ */
    list_directory("C:\\");

    std::fprintf(stderr, " [TRACE] Hello world MS-Windows.");
    OutputDebugStringA("Output of applications built for Window Subsystem");

    /*  Show messagebox window */
    DWORD const infoboxOptions  = MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND;
    MessageBoxA( nullptr, "Messagebox text", "MessageBox title", infoboxOptions);

    OutputDebugStringA("Exit application");

    std::cout << " => Type RETURN to exit" << std::endl;    
    std::cin.get();
    return 0;
}

void list_directory(std::string const& path)
{
  for(auto const& entry : fs::directory_iterator(path) )
  {
    auto p = entry.path();
    std::cout << " => Path = " << p.filename() << std::endl;          
  }
}

Building

Build using MinGW on Windows.

$ cmake -H. -B_build_mingw -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles"
$ cmake --build _build_mingw --target

# View build directory: 
$ ls _build_mingw\
cmake_install.cmake  CMakeFiles/  winapp-dynam.exe*
CMakeCache.txt       Makefile     winapp-static.exe*

Build cross compiling with Dockercross (see: dockcross ), a docker image for cross compiling for several platforms. Note: This docker image only statically links MINGW runtime libraries regardless of CMake settings.

$ docker run -it --rm -v $PWD:/cwd -w /cwd --entrypoint=bash dockcross/windows-static-x64
$ cmake -H. -B_build_cross_compile -DCMAKE_BUILD_TYPE=Debug
$ cmake --build _build_cross_compile --target

# View build directory 
[root:/cwd/cmake-mingw-project] # ls _build_cross_compile/
CMakeCache.txt  Makefile             winapp-dynam.exe
CMakeFiles      cmake_install.cmake  winapp-static.exe

Attempt to run the executable winapp-dynam.exe

# Execute on Windows 
$ _build_mingw\winapp-dynam.exe

An attempt to run the executable winapp-dynam.exe in a machine without MINGW installed or without any MINGW runtime dependencies results in the following error messages which are locale-specific (user language specific).

  • Error message 1 (Machine with English Locale) : "The code execution cannot proceed because libgcc_s_seh-1.dll was not found. Reinstalling the program may fix this problem".
  • Error message 2 (Machine with English Locale) : "The code execution cannot proceed because libstdc++-6.dll was not found. Reinstalling the program may fix this problem".
  • Error message 1 (Machine with Portuguese Locale) : "A execução do código não pode continuar porque libgcc_s_seh-1.dll não foi encontrado. Reinstalando o programa para corrigir o problema."
  • Error message 2 (Machine with Portuguese Locale) : "A execução do código não pode continuar porque libstdc++-6 não foi encontrado. Reinstalando o programa para corrigir o problema."

If an attempt to run this application using Linux's wine is made, the next error messages appears:

 $ >> wine _build_mingw/winapp-dynam.exe  
002b:err:module:import_dll Library libgcc_s_seh-1.dll (which is needed by L"Z:\\home\\user\\Documents\\cmake-mingw-project\\_build_mingw\\winapp-dynam.exe") not found
002b:err:module:import_dll Library libstdc++-6.dll (which is needed by L"Z:\\home\\user\\Documents\\cmake-mingw-project\\_build_mingw\\winapp-dynam.exe") not found
002b:err:module:LdrInitializeThunk Importing dlls for L"Z:\\home\\user\\Documents\\cmake-mingw-project\\_build_mingw\\winapp-dynam.exe" failed, status c0000135

Attempt to run the executable winapp-static.exe

The second executable works without any issues in another Windows machine without MINGW runtime libraries. This attempt works because the executable was statically linked against all MINGW runtime libraries.

  • Running under Windows 10
$ _build_mingw\winapp-static.exe

 [TRACE] Display root directory of current machine
 => Path = "$Recycle.Bin"
 => Path = "Config.Msi"
 => Path = "Documents and Settings"
 => Path = "hiberfil.sys"
 => Path = "pagefile.sys"
 => Path = "PerfLogs"
 => Path = "Program Files"
 => Path = "Program Files (x86)"
 => Path = "ProgramData"
 => Path = "Recovery"
 => Path = "swapfile.sys"
 => Path = "System Volume Information"
 => Path = "Temp"
 => Path = "Users"
 => Path = "Windows"
 [TRACE] Hello world MS-Windows. => Type RETURN to exit
  • Running under Wine on Linux
$ >> wine _build_mingw/winapp-static.exe
[TRACE] Display root directory of current machine
=> Path = "Program Files"
=> Path = "Program Files (x86)"
=> Path = "ProgramData"
=> Path = "users"
=> Path = "windows"
[TRACE] Hello world MS-Windows. => Type RETURN to exit

1.13.2 Executable Target with resource icon

This section provides an example about how to add a resource icon to a Windows executable target.

Files:

Project Item Description  
Application ProgramWithICON.exe  
Sources: src/main.cpp src/src1.cpp src/src2.cpp  
Icon src/programImage.ico  
Resource script src/icon.rc  

Resource file: src/icon.rc

// Icon Resource => Compiled by Resource compiler
id ICON "programImage.ico"

CMakeLists.txt

 # .... Omit beginning and global configuration .... #
  add_executable(ProgramWithICON src/main.cpp src/src1.cpp src/src2.cpp)
 if(WIN32)
    target_sources(ProgramWithICON src/icon.rc)
 endif()

 target_include_directories(ProgramWithICON  ./includes)
# .... Omit additional configuration ... #

1.13.3 Build executable for console or window subsystem

The following CMakeLists.txt file defines two executables targets built with the same source file main.cpp. The target app_console_subsystem.exe is compiled for the Windows' console subsystem and the target app_window_subsystem.exe for the window subsystem.

Applications compiled for the console subsystem opens a terminal when clicked and prints the std::cout and std::cerr output to it while applications built for the window subsystem do not open any terminal and do not print any std::cout or std::cerr output.

An window-subsystem application can perform logging by using the Windows API function OutputDebugString, writing to a file or redirecting the std::cout and std::cerr streams to file stream object such as std::ofstream.

File: CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(cppwindows)

#=======================================

# Copy target file to current directory whenerver it is rebuilt
function(copy_after_build TARGET_NAME )
    # Note: CMAKE_CURRENT_LIST_DIR is the directory where is this
    # CMakeLists.txt file.
    set(DESTDIR ${CMAKE_CURRENT_LIST_DIR}/bin/)
    file(MAKE_DIRECTORY ${DESTDIR})

    # Copy binary file to <CMakeLists.txt didctory>./bin
    # after target is compiled.
    add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
      COMMAND ${CMAKE_COMMAND} -E copy
                               $<TARGET_FILE:${TARGET_NAME}> ${DESTDIR}
                               )
endfunction()

#----------------------------------------

# Build the executable: app_console_subsystem.exe which opens a
# terminal when clicked
add_executable(app_console_subsystem main.cpp)
copy_after_build(app_console_subsystem)

# Builds the executable: app_window_subsystem.exe
# which does not open a terminal when clicked, it only shows
# a message box
add_executable(app_window_subsystem WIN32 main.cpp)
copy_after_build(app_window_subsystem)

File: main.cpp

#include <iostream>
#include <windows.h>

int main()
{
    std::cout << "Hello World Windows Console and Window subsystem!" << std::endl;
    OutputDebugStringA("Output of applications built for Window Subsystem");

    // Show messagebox window
    DWORD const infoboxOptions  = MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND;
    MessageBoxA( nullptr, "Messagebox text", "MessageBox title", infoboxOptions);

    std::cout << "Exit application " << std::endl;
    OutputDebugStringA("Exit application");

    std::cin.get();
    std::cout << " => Enter RETURN to exit" << std::endl;
    return 0;
}

cmake_console_subsystem1.png

Figure 2: Application app_console_subsystem.exe (Console subsystem)

cmake_window_subsystem1.png

Figure 3: Application app_window_subsystem.exe (Window subsystem)

Note: Another way to make the executable to build for the window-subsystem is by using the following CMake Lines:

add_executable(my-target src1.cpp src1.hpp src2.cpp src2.hpp ... )

IF(WIN32)
   set_property(TARGET my-target PROPERTY WIN32_EXECUTALE true)
ENDIF()

1.13.4 Set target subsystem: console/window

Set target subsystem.

if(WIN32)
   set_target_properties(WindowApplicationExample PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
   set_target_properties(WindowApplicationExample PROPERTIES COMPILE_DEFINITIONS_DEBUG "_CONSOLE")
   set_target_properties(WindowApplicationExample PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
   set_target_properties(WindowApplicationExample PROPERTIES COMPILE_DEFINITIONS_RELWITHDEBINFO "_CONSOLE")
   set_target_properties(WindowApplicationExample PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
   set_target_properties(WindowApplicationExample PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif(WIN32)

Taken from:

1.13.5 Export all shared library symbols

CMake has a handy option CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS that can export all DLL shared library symbols without any __declspec(dllexport) or __declspec(dllimport) annotation.

Example:

File: main.cpp (executable main.exe)

cmake_minimum_required(VERSION 3.5)
project(testbed_project)

#=========== Global Settings ==============================#
# ... omit other settings ... 

# Export ALL DLLs symbols on Windows without __declspec(xxxx) annotations.
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS true)

#=============== Targets-specific settings ====================#

# Target: libutil (Library)
add_library(utils SHARED utils.cpp utils.hpp)

# Target: app1 => Executable (Dynamically linked)
add_executable(main main1.cpp)
target_link_libraries(main utils)

File: utils.hpp (Shared library utils.dll)

#ifndef _UTILS_HPP_
#define _UTILS_HPP_

#include <string>

namespace  utils {

     class Dummy{
     private:
          std::string m_name = "<Untitled>";
     public:
         Dummy();
         Dummy(std::string const& name);
         void  showName() const;
     };
}
#endif 

File: utils.cpp (Shared library utils.dll)

#include <iostream> 
#include "utils.hpp"

namespace utils{
      Dummy::Dummy() { }
      Dummy::Dummy(std::string const& name): m_name(name) { }

      void Dummy::showName() const
      {
          std::cout << " [TRACE] Object name is : " << m_name << "\n";
      }
}

Without the CMake option CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS, the class Dummy would need the __declspec annotation and the code would be:

class __declspec(dllexport) Dummy{
private:
     std::string m_name = "<Untitled>";
public:
    Dummy();
    Dummy(std::string const& name);
    void  showName() const;
};

To make a symbol private (visible only the utils.cpp compilation-unit) annotate it with the static keyword or place the symbols in a anonymous namespace, example:

 static void internal_function(int) {
   // ... code here ... 
 }

 static class InternalClass {
   // ... code here ... 
 }

 static struct MyStruct{
    double x; 
    double y;
 }; 

// ---- OR make the symbols private with anonymous namespace  ------// 

namespace {

   void internal_function(int) {
     // ... code here ... 
   }

   class InternalClass {
     // ... code here ... 
   }

   static struct MyStruct{
      double x; 
      double y;
   }; 

}

1.14 Snippets

1.14.1 Place build-tree object-code in source directory

The following sample project shows how to place build-tree object-codes (executables and libraries) in the source directory (project directory) for making easier to find them wihout browsing the build tree.

The sample project is comprised of two executables app1 and app2 linked against a shared library libutils.

Sample Project

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(testbed_project)

#=========== Global Settings =================#

set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) 

#  => Place executable(s) in ${SOURCE DIRECTORY}/out/bin 
#  => Place libraries in ${SOURCE DIRECTORY}/out/lib 
#
#  => Note: ${SOURCE DIRECTORY} is the directory where is this CMakeLists.txt 
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/out/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/out/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/out/bin)

# Set relative rpath to shared library in executable object code
# See:
#  => https://cmake.org/cmake/help/latest/prop_tgt/BUILD_RPATH_USE_ORIGIN.html
#  => https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling
if(UNIX)
  SET(CMAKE_INSTALL_RPATH "$ORIGIN/../lib:$ORIGIN/")
endif()

# Export ALL DLLs symbols on Windows without __declspec(xxxx) annotations.
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS true)

#=============== Targets-specific settings ====================#

# Target: libutil (Library)
add_library(utils SHARED utils.cpp utils.hpp)
add_library(utils_static STATIC utils.cpp utils.hpp)

# Target: app1 => Executable (Dynamically linked)
add_executable(app1 main1.cpp)
target_link_libraries(app1 utils)

# Target: app2 => Executable (Dynamically linked)
add_executable(app2 main2.cpp)
target_link_libraries(app2 utils)

# Target: app1_static => Executable (static linked)
add_executable(app1_static main1.cpp)
target_link_libraries(app1_static utils_static)

# Target: app1_static => Executable (static linked)
add_executable(app2_static main1.cpp)
target_link_libraries(app2_static utils_static)


#============== Target: Install ===============================#
# Equivalent to: $ make install 

install(TARGETS app1 app2 app1_static app2_static utils utils_static  
  RUNTIME  DESTINATION bin # Executable: *.exe, 
  LIBRARY  DESTINATION lib # Shared libraries: *.dll, *.so and so on 
  ARCHIVE  DESTINATION lib # Static libraries: *.a 
)

install(FILES README.txt DESTINATION ./)

File: README.txt

======= Application User Guide ==========

  + app1 => Application for creating PCB - Printed Circuit Board
  + app2 => App. for communicating with remote link

  <INSERT TEXT HERE ....>  

File: utils.hpp (CMake targets: utils and utils_static)

#ifndef _UTILS_HPP_
#define _UTILS_HPP_

#include <string>

namespace  utils {

     class Dummy{
     private:
          std::string m_name = "<Untitled>";
     public:
          Dummy();
          Dummy(std::string const& name);
          void  showName() const;
     };
}

#endif 

File: utils.hpp (CMake targets: utils and utils_static)

#include <iostream> 

#include "utils.hpp"

namespace utils{

     Dummy::Dummy() { }
     Dummy::Dummy(std::string const& name): m_name(name) { }

     void Dummy::showName() const
     {
         std::cout << " [TRACE] Object name is : " << m_name << "\n";
     }

}

File: main1.cpp (CMake targets: app1 and app1_static )

#include <iostream>
#include "utils.hpp"

using utils::Dummy;

int main()
{
    std::puts(" [TRACE] I am the application 1. Ok.");
    Dummy dummy1("I_am_dummy_object_A");
    dummy1.showName();

    return 0;
}

File: main2.cpp (CMake targets: app2 and app2_static )

#include <iostream>
#include "utils.hpp"

int main()
{
    std::puts(" [TRACE] I am the application 2. Ok.");
    utils::Dummy dummy2("I_am_dummy_object_B");
    dummy2.showName();

    return 0;
}

Build and running on Linux

Configure and build:

#  + (-B/tmp/_build2) => Place build tree at /tmp/_build2
#  + Install binaries (object-code) at ~/Desktop/appinstall
$ cmake -H. -B/tmp/_build2 -DCMAKE_INSTALL_PREFIX=~/Desktop/appinstall
$ cmake --build /tmp/build2 --target 

Check binaries in the source directory (project directory):

# List source/project directory 
$ ls .
out/  CMakeLists.txt  main1.cpp  main2.cpp  README.txt  utils.cpp  utils.hpp


$ tree .
.
├── CMakeLists.txt
├── main1.cpp
├── main2.cpp
├── out
│   ├── bin
│   │   ├── app1
│   │   ├── app1_static
│   │   ├── app2
│   │   └── app2_static
│   └── lib
│       ├── libutils.so
│       └── libutils_static.a
├── README.txt
├── utils.cpp
└── utils.hpp

3 directories, 12 files

Test executables:

$ out/bin/app1
 [TRACE] I am the application 1. Ok.
 [TRACE] Object name is : I_am_dummy_object_A

$ out/bin/app2
 [TRACE] I am the application 2. Ok.
 [TRACE] Object name is : I_am_dummy_object_B

$ out/bin/app1_static 
 [TRACE] I am the application 1. Ok.
 [TRACE] Object name is : I_am_dummy_object_A

$ out/bin/app2_static 
 [TRACE] I am the application 1. Ok.
 [TRACE] Object name is : I_am_dummy_object_A


# Check app1 dependencies 
$ ldd out/bin/app1
        linux-vdso.so.1 (0x00007ffff7ffa000)
        libutils.so => /home/archbox/shared/demo-cmake-proj2/out/lib/libutils.so (0x00007ffff7bd0000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007ffff783e000)
        libm.so.6 => /lib64/libm.so.6 (0x00007ffff74aa000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007ffff7292000)
        libc.so.6 => /lib64/libc.so.6 (0x00007ffff6ed4000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd6000)

# Check app1_static dependencies 
$ ldd out/bin/app2_static 
        linux-vdso.so.1 (0x00007ffff7ffa000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007ffff7a44000)
        libm.so.6 => /lib64/libm.so.6 (0x00007ffff76b0000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007ffff7498000)
        libc.so.6 => /lib64/libc.so.6 (0x00007ffff70da000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd6000)

Build and install (execute CMake's intall target):

$ cmake --build /tmp/_build2/ --target install

Check intalled files:

$ ls ~/Desktop/appinstall/
bin/  lib/  README.txt

$ tree ~/Desktop/appinstall/
├── bin
│   ├── app1
│   ├── app1_static
│   ├── app2
│   └── app2_static
├── lib
│   ├── libutils.so
│   └── libutils_static.a
└── README.txt

$ ~/Desktop/appinstall/bin/app1
 [TRACE] I am the application 1. Ok.
 [TRACE] Object name is : I_am_dummy_object_A

$ ~/Desktop/appinstall/bin/app2
 [TRACE] I am the application 2. Ok.
 [TRACE] Object name is : I_am_dummy_object_B

$ ~/Desktop/appinstall/bin/app1_static 
 [TRACE] I am the application 1. Ok.
 [TRACE] Object name is : I_am_dummy_object_A

Check dependencies of app1:

$ ldd ~/Desktop/appinstall/bin/app1
        linux-vdso.so.1 (0x00007ffff7ffa000)
        libutils.so => /home/archbox/Desktop/appinstall/bin/../lib/libutils.so (0x00007ffff7bd0000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007ffff783e000)
        libm.so.6 => /lib64/libm.so.6 (0x00007ffff74aa000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007ffff7292000)
        libc.so.6 => /lib64/libc.so.6 (0x00007ffff6ed4000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd6000)

Build and running on Windows 10 with MSVC 2017

$ cmake -H. -B..\_build_proj -G "Visual Studio 15 2017 Win64" -DCMAKE_INSTALL_RPEFIX=%USERPROFILE%\appinstall
$ cmake --build ..\_build_proj --target 

List directory:

λ ls
CMakeLists.txt  README.txt  main1.cpp  main2.cpp  out/  utils.cpp  utils.hpp

List binaries in the source directory:

λ ls out\bin\Debug\
app1.exe*  app1.pdb          app1_static.ilk  app2.exe*  app2.pdb          app2_static.ilk  utils.dll*  utils.pdb
app1.ilk   app1_static.exe*  app1_static.pdb  app2.ilk   app2_static.exe*  app2_static.pdb  utils.ilk

Test executables:

λ out\bin\Debug\app1.exe
 [TRACE] I am the application 1. Ok.
 [TRACE] Object name is : I_am_dummy_object_A

λ out\bin\Debug\app2.exe
 [TRACE] I am the application 2. Ok.
 [TRACE] Object name is : I_am_dummy_object_B

λ out\bin\Debug\app1_static.exe
 [TRACE] I am the application 1. Ok.
 [TRACE] Object name is : I_am_dummy_object_A

Install:

λ cmake --build ..\_build_proj --target install

Check installation:

λ ls C:\\Users\archbox\appinstall\
README.txt  bin/  lib/

λ ls %USERPROFILE%\appinstall/bin
app1.exe*  app1_static.exe*  app2.exe*  app2_static.exe*  utils.dll*

λ ls %USERPROFILE%\appinstall/lib
utils.lib  utils_static.lib

λ type %USERPROFILE%\appinstall\README.txt
======= Application User Guide ==========

  + app1 => Application for creating PCB - Printed Circuit Board
  + app2 => App. for communicating with remote link

  <INSERT TEXT HERE ....>

1.14.2 Set common file extension for all executables

Unlike Windows, native executables files on Unix-like operating systems don't have a file extension such as .exe which makes harder for automation tools, such as Jenkins, distinguish them form other files.

  • The following CMakeListst.txt builds two executables app1 and app2. The executable names on Windows are app1.exe and app2.exe. On Unix-like operating systems, their name become app1.bin and app2.bin. Without the line with CMAKE_EXECUTABLE_SUFFIX their name on Unix-like operating systems would be app1 and app2.

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
project(ProjectWithUnixExtension)

#----------------------------------------------#

set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Add file extension .bin to all executable files on Unix (Linux, OSX, BSD, ...)
if(UNIX)
  set(CMAKE_EXECUTABLE_SUFFIX ".bin")
endif()

#========== Targets Configurations ============#
add_executable(app1  main1.cpp)
add_executable(app2  main1.cpp src1.cpp src2.cpp src3.cpp)

1.14.3 Glob files from a directory or sub-directories

Globing allows adding files of a given cmake target without specifying all of them explicitly. Although, it is not a good practice, it can be useful for migrating projects using other building systems to CMake.

Example: sample project that used GNU autotools or Makefiles.

  • ./path/to/project <DIRECTORY>
    • CMakeLists.txt
    • main/ <DIR> =>> Executable Target: main -> ./main (Unix) or main.exe (Windows)
      • main.cpp
      • class1.cpp
      • class1.hpp
      • class2.cpp
      • class2.hpp
    • mylib <DIR> =>> Shared Library Target: mylib1 libmylib1.so (Unix), libmylib1.dylib (OSX) or mylib1.dll (Windows)
      • class1A.cpp
      • class1A.hpp
      • class2A.cpp
      • class2A.hpp
      • …. … ..
      • ./directory/ <DIR>
        • file1A.cpp
        • file1A.hpp
        • file1A.cpp
        • file1A.hpp
        • … … …

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.0)
project(cmake-glob)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

  #----------------------------------------------#
  #    mylib  => Library target                  #
  #----------------------------------------------#

# Assign all *.cpp files paths in the directory ./mylib 
# to the variable 'mylib_sources'. It allso includes the 
# subdirectories of ./mylib. 
file(GLOB_RECURSE mylib_sources "mylib/*.cpp")
file(GLOB_RECURSE mylib_headers "mylib/*.hpp")

add_library(mylib SHARED ${lib_sources} ${lib_headers} )
target_include_directories(mylib PUBLIC ./lib)

  #----------------------------------------------#
  #     main  => Executable target               #
  #----------------------------------------------#

# --> Assign the path of all *.cpp files in the directory 
#  ./main to the variable 'main_sources'
#  note: it does not include subdirectories. 
file(GLOB main_sources "main/*.cpp")
file(GLOB main_headers "main/*.hpp")

       add_executable( main ${main_sources} ${main_headers})
target_link_libraries( main mylib glut gl ... )

Highlights

  • The following lines sets the variable main_sources to the following list of strings containing the file names: main/main.cpp, main/class1.cpp, main/class2.cpp and so on.
  • GLOB scans all files in a directory with a given pattern, but does not scan in sub-directories.
file(GLOB main_sources "main/*.cpp")
  • The next lien sets the variable mylib_headers to mylib a list of strings contains the file names: mylib/class1A.cpp mylib/class2A.cpp … mylib/directory/file1A.cpp, mylib/directory/file2A.cpp and so on.
  • GLOB_RECURSE scans all files in a directory and subdirectories with a given pattern.
file(GLOB_RECURSE mylib_sources "mylib/*.cpp") 

1.14.4 Copy some target binaries to a directory

This piece of code copies the target binaries (compiled code) to a directory relative to project's root directory where is CMakeLists.txt

Targets:

# TARGET: testlib Shared library => Builds liblinalg.so on Unix or
# linalg.dll on Windows.
add_library(linalg SHARED  src/testlib.cpp)

# TARGET: app Executable => Builds 'app' on Unix-like Oses and
# unix.exe on Windows.
add_executable(app src/main/main-app.cpp src/main/file1.cpp src/main/file2.cpp)

Copy target files to top level directory where is CMakeLists.txt

# If not set the install directory, attemp set the install directory
# CMAKE_INSTALL_PREFIX to the directory ./bin 
if(NOT DEFINED CMAKE_INSTALL_PREFIX OR CMAKE_INSTALL_PREFIX MATCHES "")
  set(CMAKE_INSTALL_PREFIX  "./bin")
endif()

message(" [INFO] CMakeLists.txt is in the directory ${CMAKE_CURRENT_LIST_DIR}")

# Copy targets to ./ - Directory where is this file CMakeLists.txt (project top level dir)
install(TARGETS app linalg DESTINATION  ${CMAKE_CURRENT_LIST_DIR})

This target can be run with:

  • STEP 1: Set CMake building system or the project type with -G.
$ cd project-directory 

#===> Set build system 
# -H<SOURCE> -B<COMPILATION-OUTPUT> -G <BUILDING-SYSTEM>
$ cmake -H. -G "Unix Makefiles" -Bbuild 
  • STEP 2: Run targets
#===> Run all targets [all default.]
$ cmake --build build --target all 

#===> Run target install and copy the binaries to ./bin 
$ cmake --build build --target install 

1.14.5 Create target to run compiled executable

The piece following piece of code runs an executable target after it is built in a similar way to a command $ make run.

cmake_minimum_required(VERSION 3.9)

 ... ...  ... ...  ... ...

# TARGET: myapplication 
add_executable(myapplication src/main.cpp src/code1.cpp code2.cpp ...)

# TARGET: run -> Runs the executable myapplication 
# On Unix-like Oses, it runs  /<build-directory>/myapplication 
# On Windows: it runs  <build-directory>/myapplication.exe 
add_custom_target(run
    COMMAND myapplication
    DEPENDS myapplication
    WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
)

After the building system is set and output directory ./build, run:

#===> Run all targets [all default.]
$ cmake --build build --target run

1.14.6 Issue an error if condition is not met

In this piece of code, if the compiler is not MSVC (vc++, aka cl.exe), CMake stops the generation of the building script showing an error message to the user.

... ... 

if(MSVC)
   set_target_properties(WindowApp1 PROPERTIES LINK_FLAGS "/entry:mainCRTStartup")
else() 
   message(FATAL_ERROR " [ERROR] Linker flags of target WindowApp1 not set for this compiler")
endif()
... ... 

1.14.7 Copy a target file whenever it is recompiled

Example: Copy a shared library to project root directory ./ whenever it is rebuilt.

  • Set the shared library target. (libtestlib.so on Unix or testlib.dll on Windows)
add_library( testlib  SHARED  ./testlib.cpp)
set_target_properties(testlib PROPERTIES CXX_VISIBILITY_PRESET hidden)
  • Automatically copy to root directory the generated shared library binary to root directory on every recompilation of the target testlib.
add_custom_command(TARGET testlib POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy 
  $<TARGET_FILE:testlib>                     # Source    
  ${CMAKE_CURRENT_SOURCE_DIR}/libtest.so     # Destination, file or directory 
  # OR: ${CMAKE_CURRENT_SOURCE_DIR}/         # Directory destination. 
)

Test this post-build target:

# Generate cache directory containing building scripts or project files 
# specifics for some building systems
$ cmake -H. -Bcache -G "CHOOSE SYSTEM>"

# Run all targets  [Default]
$ cmake --build cache --target
$ cmake --build cache --target all 

# Run target 'testlib'. 
$ cmake --build cache --target testlib 

After the target is recompiled, the shared library is copied to the current directory (project top-level directory ./).

References:

1.14.8 Cross compiling for Android

See:

File: application1.cpp

#include <iostream>

int main(int argc, char** argv)
{

  std::cout << " [INFO] Hello world from application1" << std::endl;
  ::system("whoami");

  return 0;
}

File: CmakeLists.txt

cmake_minimum_required(VERSION 3.9)
project(Simple_Cmake_Project)

#========== Global Configurations =============#
#----------------------------------------------#
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

#========== Targets Configurations ============#
add_executable(app1 application1.cpp)

Building:

STEP 1: Set CMake Configuration.

# Using Makefile (Linux)   
$ cmake -H. -B_build -G Ninja -DCMAKE_BUILD_TYPE=Debug \
   -DCMAKE_TOOLCHAIN_FILE=$HOME/Android/Sdk/ndk-bundle/build/cmake/android.toolchain.cmake \
   -DANDROID_ABI=armeabi-v7a \
   -DANDROID_NATIVE_API_LEVEL=21

# OR Using Ninja build generator 
$ cmake -H. -B_build -G Ninja -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_TOOLCHAIN_FILE=$HOME/Android/Sdk/ndk-bundle/build/cmake/android.toolchain.cmake \
  -DANDROID_ABI=armeabi-v7a \
  -DANDROID_NATIVE_API_LEVEL=21

STEP 2: Build

$ cmake --build _build --target all
[4/4] Linking CXX executable app1

# Check executable 
$ file _build/app1
_build/app1: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, 
interpreter /system/, BuildID[sha1]=8d6b159e0e65604fe1f65bca5ad9eef36aa4ab26, with debug_info, not stripped

# Attempt to run => Fails because the current machine (host) is Linux
# x86_64 - 64 bits, not ARM 32 bits
$ _build/app1
bash: _build/app1: cannot execute binary file: Exec format error

STEP 3: Deploy with ADB - Android Debug Bridge

$ adb push _build/app1 /data/local/tmp
_build/app1: 1 file pushed. 18.6 MB/s (2848980 bytes in 0.146s)

STEP 4: Run application on Android.

  • Run in adb shell
$ adb shell

on5xelte:/ $ /data/local/tmp/app1
 [INFO] Hello world from application1
shell
  • Run with adb directly
$ adb shell /data/local/tmp/app1
 [INFO] Hello world from application1
shell

1.14.9 Detect Operating System

  1. Detect operating system with predefined variables

    Check whether current OS is Microsft Windows NT:

    if(WIN32)
      # actions ... 
      message("Compiling for Windows NT")
    else()
      # else actions .... ...
    endif()
    

    Check whether current OS is a Unix-like operating system (Linux, BSD, MacOSX, Android and so on):

    if(UNIX)
      # actions ... 
      message("Running on Unix-like OS")
    endif()  
    

    Check if current OS is MacOSX:

    if(APPLE)
      # actions ... 
      message("Running on Unix-like OS")
    endif()  
    

    Check if current OS is Linux:

    if(LINUX)
      # actions ... 
      message("Running on Unix-like OS")
    endif()  
    

    Check if current OS is UNIX, but not MacOSX:

    if(UNIX AND NOT APPLE)
      # actions ... 
      message("Running on Unix-like OS")
    endif()  
    
  2. Detect operating system with variable CMAKE_SYSTEM_NAME

    Example:

    if(${CMAKE_SYSTEM_NAME} matches "linux")
      message(" [INFO] compiling for Linux")
    elseif(${CMAKE_SYSTEM_NAME} matches "Windows")
      message(" [INFO] compiling for Windows")
    elseif(${CMAKE_SYSTEM_NAME} matches "darwin")
      message(" [INFO] compiling for Apple MacOSX")
    elseif(${CMAKE_SYSTEM_NAME} matches "Android")
      message(" [INFO] compiling for Android OS.")
    else()
      message( " [WARNING] Operating system not identified.")
    endif()
    

    Possible Values of Variable CMAKE_SYSTEM_NAME

    if(${CMAKE_SYSTEM_NAME} matches <<MATCHING-STRING>)
      message(" [INFO] compiling for <<MATCHIGN-STTRING> ")
    endif()
    
    Value of <<MATCHING-STRING> Operating System  
    for CMAKE_SYSTEM_NAME    
    Windows Windows  
    Darwin MacOSX  
    BSD Free BSD, OpenBDS, NetBSD …  
    FreeBSD    
    Linux Linux  
    Android Android  

1.14.10 Detect whether current build is 32 bits or 64 bits

Check whether current configuration is for 64 bits build:

if(CMAKE_SIZEOF_VOID_P EQUAL 8)
   message(" [INFO] 64 bits build.")
else()
   message(" [INFO] 32 bits build.")
endif()

1.14.11 Detect Compiler

  1. Detect compiler using predefined variables

    MSVC

    Check if current compiler is MSVC - Microsft - Visual C/C++ Compiler (Visual C++, vc++ for short):

    if(MSVC)
      message(STATUS << "Running MSVC")
    endif()
    

    MSVC for 32 bits target

    Detect MSVC set up for 32 bits target (buids 32 bits binaries):

    if(MSVC AND NOT CMAKE_CL_64)
      message(" [INFO] Building for 32 bits Windows target.")
    endif()
    

    MSVC for 64 bits target

    if(MSVC AND CMAKE_CL_64)
      message(" [INFO] Building for 64 bits Windows target.")
    endif()
    

    Mingw

    Check if current compiler is GCC/Mingw, GCC compiler ported to Windows.

    if(MSVC)
      message("Running Mingw/GCC maybe on Windows")
    endif()
    
  2. Detect compiler using CMAKE_CXX_COMPILER_ID variable

    The variable CMAKE_CXX_COMPILER_ID can be used for checking the current C++ compiler.

    if (${CMAKE_CXX_COMPILER_ID} matches GNU)
      message(STATUS " [INFO] Using GCC - GNU C/C++ Compiler or MingW/GCC.")
    elseif(${CMAKE_CXX_COMPILER_ID} matches CLANG) 
      message(STATUS " [INFO] Using the super amazing, the finest C++ compiler: Clang++/LLVM.")
    elseif(${CMAKE_CXX_COMPILER_ID} matches MSVC)
      message(STATUS " [INFO] Using MSVC Windows 'Visual!!' C++ compiler")
    else()
      message( FATAL_ERROR " [ERROR] I cannot identify CMake Configuration not set for this compiler yet.")
    endif()
    

    All possible values of CMAKE_CXX_COMPILER_ID:

    Value of CMAKE_CXX_COMPILER_ID Compiler
    Most used compilers  
    GNU GNU Compiler Collection (gcc.gnu.org)
    Clang LLVM Clang (clang.llvm.org)
    MSVC Microsoft Visual Studio (microsoft.com)
    Intel Intel Compiler (intel.com)
    PGI The Portland Group (pgroup.com)
    G95 G95 Fortran (g95.org)
    Embarcadero, Borland Embarcadero (embarcadero.com)
    ARMCC ARM Compiler (arm.com)
       
    Other compilers  
    Absoft Absoft Fortran (absoft.com)
    ADSP Analog VisualDSP++ (analog.com)
    AppleClang Apple Clang (apple.com)
    Bruce Bruce C Compiler
    CCur Concurrent Fortran (ccur.com)
    Cray Cray Compiler (cray.com)
    HP Hewlett-Packard Compiler (hp.com)
    IAR IAR Systems (iar.com)
    MIPSpro SGI MIPSpro (sgi.com)
    NVIDIA NVIDIA CUDA Compiler (nvidia.com)
    OpenWatcom Open Watcom (openwatcom.org)
    Flang Flang Fortran Compiler
    PathScale PathScale (pathscale.com)
    SDCC Small Device C Compiler (sdcc.sourceforge.net)
    SunPro Oracle Solaris Studio (oracle.com)
    TI Texas Instruments (ti.com)
    TinyCC Tiny C Compiler (tinycc.org)
    XL, VisualAge, zOS IBM XL (ibm.com)

1.14.12 Debug Information for diagnosing problems

Set Build Type

Informs whether the current build type is Release or Debug. Relase has optmization flags enabled and debugging symbols disabled. Debug building type has optmization flags and debugging symbols flags disabled.

The build type is:

  • Release when :
    • $ cmake –build <BUILD-DIRECTORY> –config Release –target
    • $ cmake –build <BUILD-DIRECTORY> –config Release –target install
  • Debug when using (Default):
    • $ cmake –build <BUILD-DIRECTORY> –config Debug –target
    • $ cmake –build <BUILD-DIRECTORY> –config Debug –target Application1
    • $ cmake –build <BUILD-DIRECTORY> –config Debug –target install

Set Installation Directory

The installation directory can be set with the option -DCMAKE_INSTALL_PREFIX, examples:

# Configuration 
$ cmake -Bcache -DCMAKE_INSTALL_PREFIX=C:\\Users\Dummy\apps 

# Execute install target 
$ cmake --build cache --config Debug --target install 

Useful Debugging Commands

message(" [INFO] Build type = ${CMAKE_BUILD_TYPE}")

Target word size 32 bits (x86) or 64 bits (x86_64):

if(CMAKE_SIZEOF_VOID_P EQUAL 8)
   message(" [INFO] Target = 64 bits build.")
else()
   message(" [INFO] Target = 32 bits build.")
endif()

Compiler:

message(" [INFO] Compiler ID = ${CMAKE_CXX_COMPILER_ID}")

Current Platform:

message(" [INFO] System Name = ${CMAKE_SYSTEM_NAME} ")

Binary directory <BUILD-DIRECTORY>

message(" [INFO] CMake binary dir = ${CMAKE_BINARY_DIR}")

Installation directory specifies the directory where the binaries will be installed by the target install. It can be specified during configuration phase by using:

  • –DCMAKE_INSTALL_PREFIX=C:\Users\Dummy\app
message(" [INFO] Install path CMAKE_INSTALL_PREFIX = ${CMAKE_INSTALL_PREFIX} ")

Putting it all together:

message(" [INFO]  System Name  - CMAKE_SYSTEM_NAME     = ${CMAKE_SYSTEM_NAME}")
message(" [INFO]  Build type   - CMAKE_BUILD_TYPE      = ${CMAKE_BUILD_TYPE}")
message(" [INFO]  Compiler ID  - CMAKE_CXX_COMPILER_ID = ${CMAKE_CXX_COMPILER_ID}")
message(" [INFO]  Install path - CMAKE_INSTALL_PREFIX  = ${CMAKE_INSTALL_PREFIX} ")
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
   message(" [INFO] Target = 64 bits build.")
else()
   message(" [INFO] Target = 32 bits build.")
endif()
message(" [INFO] CMake binary dir = ${CMAKE_BINARY_DIR}")

1.15 Force Settings

1.15.1 Force generation of debugging symbols

This snippet forces the generation of debugging symbols regardless of the build type. It is a useful setting when building from command line.

Add the following line to the CMake file:

set(CMAKE_BUILD_TYPE Debug) # Force debug build

After the line is added, the file CMakeLists.txt becomes:

#========= Global Configuration ===============# 
cmake_minimum_required(VERSION 3.9)
project(Demo_cpp_plugin)
set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)

# Force debug build
set(CMAKE_BUILD_TYPE Debug) # Force debug build

#========== Targets Configurations ============#

add_executable(app ... )
.. ... ... ... 

1.15.2 Force Compiler to Clang

Despite C++ language remarkable improvements, C++ compiler error messages of most compilers still cryptic and hard to grasp. Even for silly syntax mistakes, C++ compilers can fill the screen with error messages providing little context about the compiler error.

Such cryptic errors can be better diagnosed by switching the compiler to Clang that provides much better error messages that other compilers. The compiler can be temporarily switched by using the IDE, by setting environment variables or by setting CMake variables to force using an specific compiler.

Example: CMakeLists.txt forcing compiler to Clang.

cmake_minimum_required(VERSION 3.9)
project(NumericalMethods)
set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)

set(FORCE_CLANG ON)
# Force compiler to Clang++
if(FORCE_CLANG)
    set(CMAKE_C_COMPILER clang)
    set(CMAKE_CXX_COMPILER clang++)
endif()

#========== Targets Configurations ============#

# TARGET: rootFinding1.cpp 
add_executable(rootFinding1 rootFinding1.cpp)

Explanation:

This next line sets the variable FORCE_CLANG:

set(FORCE_CLANG ON)   # Force using clang compiler 
set(FORCE_CLANG OFF)  # Use default compiler 

When the variable FORCE_CLANG is set, the commands within the if-endif block set the C and C++ compiler to Clang and Clang++.

# Force compiler to Clang++
if(FORCE_CLANG)
    set(CMAKE_C_COMPILER clang)
    set(CMAKE_CXX_COMPILER clang++)
endif()

1.16 Useful CMake Modules

1.16.1 CMake Module Projects

1.17 Cross references

1.17.1 Common Variables

  1. CMAKE_CURRENT_LIST_DIR - Directory where is CMakeLists.txt

    Prints the directory where CMAkeLists.

    message(" [INFO] CMakeLists.txt is in the directory ${CMAKE_CURRENT_LIST_DIR}")
    
  2. CMAKE_CXX_STANDARD - Set C++ Standard

    The variable CMAKE_CXX_STANDARD sets the project current C++ standard. This variable can be set to 98 (for C++98); 11 (C++11); 14 (C++14); 17 (C++17) and 20 (C++20 - experimental yet.)

    Example: Set project to compile with C++11 ISO standard.

    set(CMAKE_CXX_STANDARD 11)
    

    Usage: (File CMAKELists.txt)

    cmake_minimum_required(VERSION 3.9)
    project(MY_PROJECT_NAME)
    
    set(CMAKE_CXX_STANDARD 17)
    ... ... .... .... 
    
  3. CMAKE_BUILD_TYPE - Set default building type to DEBUG

    In the debug building type optimization flags are disabled and flags for generating debugging symbols are enabled, for GGC or GCC/Mingw and Clang the (-g) is automatically added and for MSVC, aka VC++ (cl.exe), the flag /Zi is also passed to the compiler. The default building type can be set to DEBUG with the following code.

    # Set default building type to debug if it was not set
    # in command line with -DCMAKE_BUILD_TYPE=release
    if(NOT DEFINED CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE MATCHES "")
      set(CMAKE_BUILD_TYPE  debug)
    endif()
    

    The building type can be changed by running cmake with the flag (-DCMAKE_BUILD_TYPE=release). Example:

    $ cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=DEBUG -G "Visual Studio 15 2017 Win64"
    

    Note: CMake is not case sensitive, so it does not matter using 'release' or RELEASE; if or IF; endif or ENDIF and so on.

  4. CMAKE_CXX_FLAGS - Compiler flags

    The globals compiler flags used to build all targets can be extended by setting the variable CMAKE_CXX_FLAGS. However, as the compiler flags depends on the compiler, the custom settings may not be portable if one uses a different compiler, for instance the (-g) flag used to generate debug symbols with GCC or Clang does not work with MSVC (visual c++ compiler, vc++) as it uses the flag /Zi for generating debugging symbols.

    Example 1:

    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}"  "-std=c++17 -g -Wall -Wextra -pendantic -O0" )
    

    Example 2 (better):

    if(NOT MSVC)
       set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}"  "-std=c++17 -g -Wall -Wextra -pendantic -O0" )
    endif()
    

1.17.2 CMake Map

  • Global configuration/settings
    • add_definitions
    • set(<VARIABLE> <VALUE>)
    • add_subdirectory
  • Target defintions
    • add_executable => Create target for building executable.
    • add_library => Create target for building static or shared library.
    • add_custom_target
  • Target Configuration:
    • set_target_properties(<TARGET> PROPERTIES <KEY> <VALUE> [<KEY <VALUE>] …)
    • set_target_properties(testlib PROPERTIES CXX_VISIBILITY_PRESET hidden)
      • Make symbols of shared or static library hidden by default, only explicitly annotated symbols are visible. It avoids name clashing which can lead to undefined behavior.
    • target_link_libraries(<executable-target> PUBLIC testlib)
  • Auxiliary targets:
    • install => Install packages to some directoriy.
  • Find Commands:
    • find_path
    • find_package
    • find_library
    • include_directory
  • Global Variables:
    • set(CMAKE_CXX_STANDARD 14)
    • set(CMAKE_VERBOSE_MAKEFILE ON)
    • set(CMAKE_BUILD_TYPE debug)

Typical CMake Variables:

Variable Compiler flags Description
  affected  
Misc    
PROJECT_NAME - Sets project name.
     
Compiler    
     
CMAKE_CXX_STANDARD -std=c++11 Set the C++ standard used for compoling all sources
CMAKE_CXX_FLAGS - Additional compiler flags for all targets.
CMAKE_BUILD_TYPE   Set whether build type is release or debug
     
Location    
CMAKE_BINARY_DIR    
CMAKE_CURRENT_LIST_DIR   Get location of current CMakeLists.txt file.
CMAKE_INSTALL_PREFIX   Location where targets will be installed (target install)
CMAKE_CURRENT_BINARY_DIR    
     

Notes:

  • The default value of the variable CMAKE_INSTALL_PREFIX are:
    • Windows: C:/Program Files/${PROJECT_NAME}
    • Unix-like OS: /usr/local

Created: 2021-09-27 Mon 19:27

Validate