CPP / C++ - CMake Building System
Table of Contents
- 1. CMake Building System
- 1.1. Overview
- 1.2. Cmake Videos
- 1.3. Notable Projects Using CMake
- 1.4. IDE with CMake support
- 1.5. Minimal CMake Project Workflow
- 1.6. Build a project from command line - detailed
- 1.7. Sample Project with multiple targets
- 1.8. Sample Project with multiple sub-projects
- 1.9. [DRAFT] Enabling and disabling preprocessor macros
- 1.10. Consuming or Dependencies
- 1.11. Show system information
- 1.12. Show all modules
- 1.13. Microsft Windows Specific
- 1.14. Snippets
- 1.14.1. Place build-tree object-code in source directory
- 1.14.2. Set common file extension for all executables
- 1.14.3. Glob files from a directory or sub-directories
- 1.14.4. Copy some target binaries to a directory
- 1.14.5. Create target to run compiled executable
- 1.14.6. Issue an error if condition is not met
- 1.14.7. Copy a target file whenever it is recompiled
- 1.14.8. Cross compiling for Android
- 1.14.9. Detect Operating System
- 1.14.10. Detect whether current build is 32 bits or 64 bits
- 1.14.11. Detect Compiler
- 1.14.12. Debug Information for diagnosing problems
- 1.15. Force Settings
- 1.16. Useful CMake Modules
- 1.17. Cross references
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
- See: https://cmake.org/success/
- kitware/VTK - Visualization Toolkit repository
- "VTK is an open-source software system for image processing, 3D graphics, volume rendering and visualization. VTK includes many advanced algorithms (e.g., surface reconstruction, implicit modeling, decimation) and rendering techniques (e.g., hardware-accelerated volume rendering, LOD control)."
- Kitware/ParaView
- https://www.paraview.org/
- "ParaView is an open-source, multi-platform data analysis and visualization application. ParaView users can quickly build visualizations to analyze their data using qualitative and quantitative techniques. The data exploration can be done interactively in 3D or programmatically using ParaView’s batch processing capabilities. ParaView was developed to analyze extremely large datasets using distributed memory computing resources. It can be run on supercomputers to analyze datasets of petascale size as well as on laptops for smaller data, has become an integral tool in many national laboratories, universities and industry, and has won several awards related to high performance computation."
- KDE Project (Note: KDE is a framework and desktop environment built on top of QT Framework)
- Qt5 GUI Application Framework
- One of the most well-known C++ libraries. Qt works on Microsoft Windows, Apple's MacOSX, Linux-x11, Linux-framebuffer (embedded systems) and many other operating systems.
- https://github.com/qt/qtwayland
- https://github.com/qt/qtapplicationmanager
- https://github.com/qt/qtwinextras
- https://github.com/qt/qtserialbus
- https://github.com/qt/qtdeclarative
- https://github.com/qt/qtsensors
- https://github.com/qt/qtremoteobjects
- LLVM Project (LLVM compiler framework and CLang Compiler)
- "The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. Note: the repository does not accept github pull requests at this moment."
- CERN's Root
- "The official repository for ROOT: analyzing, storing and visualizing big data, scientifically."
- React-OS
- Open source clone of Windows NT operating system.
- Boost Library
- Most famous C++ framework. Boost used its own building system Bjam, but newer versions are using CMake.
- Notepad Plus Plus - NPP
- Fast lightweight and customizable Windows-only text editor.
- https://notepad-plus-plus.org/
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]
- Note: Best replacement for CodeBlocks IDE with CMake support.
- Features:
- Just-work solution, zero time spending on configuration
- Despite the name, can be used with any type of C++ project.
- Good Integration with debugger
- Cross-platform and ligthweight
- Fast and accurate code completion
- Basic refactoring support
- Outstanding CMake support
- QT Widgets and QTQuick QML GUI builder, aka drag-and-drop GUI designer.
- Supported Building Systems:
- CMake
- QBS
- QMake
- GNU Autotools - provided by plugin
- Download: https://www.qt.io/offline-installers
- Source Code: https://github.com/qt-creator/qt-creator
- Additional:
- 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:
- ms-vscode.cpptools [ESSENTIAL]
- C/C++ IntelliSense, debugging, and code browsing.
- ms-vscode.cmake-tools [ESSENTIAL]
- CMake support
- twxs.cmake [ESSENTIAL]
- CMake language support
- ms-vscode-remote.remote-containers [ESSENTIAL]
- Development in containers, specially Docker containers.
- ms-azuretools.vscode-docker [ESSENTIAL]
- Allows managing Docker containers.
- vscode-shader
- Shader language support for VScode. Supports GLSL (OpenGL shading language - extension *.glsl); HLSL (DirectX shading language) and Cg.
- danielpinto8zz6.c-cpp-compile-run [ESSENTIAL]
- Makes possible compiling and running single C++-files.
- ms-vscode.cpptools-themes
- C/C++ Extension UI Themes
- CoenraadS.bracket-pair-colorizer-2
- Set different colors for matching brackets, parenthesis, square brackets, curly brackets which helps code readability.
- tonka3000.qtvsctools
- Integration with QT Tools.
- hars.CppSnippets
- Many basic recurring C++ snippets for class, enum, enum class and control structures.
- lfs.vscode-emacs-friendly
- Emulates Emacs keybinds, for instance, replaces Ctrl-P with Meta-X for lauching commands.
- ms-vscode.cpptools [ESSENTIAL]
- 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."
- Linux Kernel Programming IDE (LinK+ IDE)
- 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."
- GNU MCU Eclipse and GNU ARM → GNU MCU Eclipse!
- Articles about Eclipse IDE:
- List of Eclipse-based software - Wikipedia
- Going to Mars: Building a DIY Eclipse IDE for ARM Embedded Microcontrollers - DZone IoT
- Buildroot Eclipse Bundle : A powerful IDE for Embedded Linux developers | EclipseCon 2013
- Extend your embedded ARM C/C++ IDE with additional features more power to developers!
- Configure Eclipse - Getting Started with Toradex
- Using Eclipse to develop for Embedded Linux on a Windows Host [White Paper]
- Eclipse in Embedded
- Developer/Eclipse/Information – RTEMS Project
- Debugging u-boot using Eclipse - Big Lake Software
- Build and debug u-boot for the MX6sx in eclipse ( KDS 3.2 )
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
- Include directories containing header files. Similar to command line option -I with GCC.
# 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:
- target_compile_features
- CMAKE_CXX_KNOWN_FEATURES - Arguments of command target_compile_features
- See: https://github.com/Kitware/CMake/tree/master/Tests/CompileFeatures
# 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.7.3 Further Reading
General
- Ben Morgan - The University of Warwick - C++11/14/1z in CMake
- Documentation of Compatibility
- Manpage - Cmake compile features
- https://community.kde.org/Guidelines_and_HOWTOs/CMake/Library
Install Dependencies - Shared Libraries:
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.
- Note: The default behavior of CMake install target on Linux is to
install libraries and executables in a system-wide mode to the
paths
$ 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:
- https://cmake.org/cmake/help/latest/module/ExternalProject.html
- https://blog.kitware.com/cmake-superbuilds-git-submodules/
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:
- GLFW Official Web Site
- Documentation:
- GLFW Repository
- GLWF Releases
- Glut Teapot
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
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; }
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:
- https://developer.android.com/ndk/guides/cmake
- http://www.ccp4.ac.uk/dist/checkout/cmake-3.1.3/Help/manual/cmake-toolchains.7.rst
- https://itk.org/Wiki/ITK/Cross_Compiling
- https://cristianadam.eu/20181202/a-better-qnx-cmake-toolchain-file/
- https://stackoverflow.com/questions/12844772/how-to-cross-compile-cmake-for-arm-with-cmake
- https://stackoverflow.com/questions/5098360/cmake-specifying-build-toolchain
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
- 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()
- 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
- 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()
- Detect compiler using CMAKE_CXX_COMPILER_ID variable
The variable CMAKE_CXX_COMPILER_ID can be used for checking the current C++ compiler.
- See: Documentation
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
- Cotire [BEST]
- CMake module for speeding compilation by automating the usage of pre-compiled headers in a non-intrusive way that requires few modifications in the source code.
- Note: The process of using pre-compiled headers is usually cumbersome and compiler-specific. This CMake module (script) automates this configuration.
- Repository: https://github.com/sakra/cotire
- ruslo/poly
- wrench Collection of CMake toolchain files and scripts for cross-platform build and CI testing (GCC, Visual Studio, iOS, Android, Clang analyzer, sanitizers etc.) http://goo.gl/5JpHX
- Contains CMAKE Toolchain files for iOS, Android, Raspberry Pi, Windows …
- https://github.com/ruslo/polly
- cmake-precompile-headers
- Precompiled header setup for CMake. Supported CMake generators: Visual Studio; NMake Makefiles; Unix Makefiles (GCC); MinGW Makefiles; MSYS Makefiles and Ninja.
- https://github.com/larsch/cmake-precompiled-header
- UCM
- Useful cmake macros that help with: compiler/linker flags, collecting sources, PCHs, Unity builds and other stuff.
- Repository: https://github.com/onqtam/ucm
- Dockcross
- Cross compiling toolchains in Docker images
- Repository: https://github.com/dockcross/dockcross
- clang-tidy-project-cmake
- Add clang-tidy checks to a target usingCMake
- Repository: https://github.com/polysquare/clang-tidy-target-cmake
- FindLAPACK
- CMake FindLAPACK.cmake that works with Intel MKL, Atlas, OpenBLAS, Netlib, LAPACK95 for C / C++ / Fortran
- Repository: https://github.com/scivision/lapack-cmake
- android-cmake
- "CMake is great, and so is Android. This is a collection of CMake scripts that may be useful to the Android NDK community. It is based on experience from porting OpenCV library to Android: http://opencv.org/platforms/android.html Main goal is to share these scripts so that devs that use CMake as their build system may easily compile native code for Android."
- Repository: https://github.com/taka-no-me/android-cmake
- cmake-avr
- cmake-avr - a cmake toolchain for AVR projects
- https://github.com/mkleemann/cmake-avr
- arduino-cmake
- Arduino CMake Build system
- https://github.com/francoiscampbell/arduino-cmake
- Hunder Package Manager - A CMake module (aka script) that works as
package manager.
- Repository: https://github.com/ruslo/hunter
1.16.2 Collected CMake Modules
Collected CMake modules/scripts from other projects.
1.17 Cross references
1.17.1 Common Variables
- 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}")
- 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.)- Documentation: CXX_STANDARD — CMake 3.13.2 Documentation
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) ... ... .... ....
- 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.
- 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