(codename: HADOUKEN)
- HADOUKEN: NETTSI C++ Project Development Environment
- Table of contents
- Preface
- Author
- Getting started
- Hadouken script commands
- Updating hadouken
- Hadouken user's manual
- Development environment container
- Hey, wait a second, what about git?
- Tool integration modules
- Core modules
- MakeCompilationUnit
- MakeTarget
- EXCLUDE_*(optional)
- EXCLUDE_SOURCES(optional)
- EXCLUDE_HEADERS(optional)
- Notes about regex filter
- ARGUMENTS (optional)
- WITH_COVERAGE (optional)
- WITH_INSTALL (optional)
- COVERAGE_TARGETS (optional)
- COVERAGE_LCOV_FILTER_PATTERN (optional)
- COVERAGE_GCOVR_FILTER_PATTERN (optional)
- ADD_TEST_PARAMETERS (optional)
- GTEST_DISCOVER_PARAMETERS (optional)
- EXPOSE_PROJECT_METADATA (optional)
- PROJECT_METADATA_PREFIX (optional)
- NO_AUTO_COMPILATION_UNIT (optional)
- WORKING_DIRECTORY (optional)
- Excluding targets from build
- MakeComponent
- TargetProperties
- EnvironmentUtilities
- TargetUtilities
- Git
- hdk_git_get_branch_name(OUTPUT_VARIABLE DIRECTORY <dir>)
- hdk_git_get_head_commit_hash(OUTPUT_VARIABLE DIRECTORY <dir>)
- hdk_git_is_worktree_dirty(OUTPUT_VARIABLE DIRECTORY <dir>)
- hdk_git_get_config(OUTPUT_VARIABLE DIRECTORY <dir> CONFIG_KEY <key_to_be_retrieved>)
- hdk_git_get_tag(OUTPUT_VARIABLE DIRECTORY <dir> COMMIT <commit_hash> [optional])
- hdk_target_needs_git_lfs_files(TARGET <target> LFS_ROOT <lfs_root> LFS_FILE_LIST <lfs_files...>)
- hdk_git_metadata_as_compile_defn(PREFIX <prefix> [optional] DIRECTORY <directory> [optional])
- hdk_git_print_status()
- Conan
- HardwareConcurrency
- Build variant determination and compiler diagnostic flags (warnings)
- Finders
- Feature check
- Internal modules
- Closing words
- References
- Acknowledgements
- License
Hadouken
contains common boilerplate code between all C++ projects being developed/will be developed in NETTSI.
This guide will navigate you through the hadouken
's all features and how to use it.
Mustafa Kemal GILOR, Chief Software Architect ([email protected])
Hadouken is a CMake- Bash shell script project which is created to resolve three most common problems in C++ projects, eg. tool integration, dependency resolution and environment consistency.
The name hadouken
comes from the Street Fighter hero, Ryu's iconic fight move. It's simple, powerful and super effective against the common enemies of C++ projects. Just hadouken away all of your problems! ;)
Hadouken is very easy to install. You can integrate it with your project in matter of seconds. Pre-requirements for the hadouken are minimal. In order to be able to use hadouken
in a project, the project has to be version controlled in Git
, and the project must be a CMake
project. There are no requirements other than that.
You're lucky, I've prepared a video guide illustrating this. Check out Video tutorial: How to install hadouken on new projects? to watch it.
Hadouken is designed as to be a git submodule. First, you need to add it as a submodule to your existing git project. In order to do that, issue following command at your git project root:
Add submodule (via SSH)
git submodule add -b master [email protected]:nettsi/hadouken.git .hadouken
Add submodule (via HTTP)
git submodule add -b master https://github.com/nettsi/hadouken.git .hadouken
Add submodule (via SSH)
git submodule add -b master [email protected]:foss-projects/hadouken.git .hadouken
Add submodule (via HTTP)
git submodule add -b master https://gitlab.nettsi.com/foss-projects/hadouken.git .hadouken
This will add hadouken
project to your project, to .hadouken
folder. master
branch will be tracked by default. After this, run following command from terminal in your project root. The name for the submodule has to be .hadouken
. After adding the submodule, invoke the following command at project root to setup hadouken
to your project:
bash .hadouken/script/setup.sh
If your project root does not contain any CMake projects, Hadouken
will offer you to quick-start a new CMake
project. If you choose 'yes', Hadouken will ask you for a project name and automatically generate CMake files and an example C++ application.
Hadouken parent directory does not contain a CMakeLists.txt
Do you want to quick-start a new cmake project to parent folder [Yy/Nn]? y
Project name:
The script will offer you to install tools required and recommended by Hadouken. These tools are:
- Docker
- Visual Studio Code
- Visual Studio Code Extensions
Automatic installation of these tools are tested in Debian 10 and Ubuntu 18.10. Fedora and CentOS are also supported, but not tested (yet). You can always install these tools manually if you choose to do so.
After running the script, your project root should have the following symbolic links, mapped to the boilerplate content:
# Visual Studio Code - Remote Containers .devcontainer
.devcontainer -------> .hadouken/.devcontainer
# Visual Studio Code settings
.vscode -------------> .hadouken/dotfiles/.vscode
# Git vcs ignored settings
.gitignore ----------> .hadouken/dotfiles/.gitignore
# Git settings
.gitconfig ----------> .hadouken/dotfiles/.gitconfig
# Clang-format style file
.clang-format -------> .hadouken/dotfiles/.clang-format
# Clang-tidy style file
.clang-tidy ---------> .hadouken/dotfiles/.clang-tidy
# Lcov report generation settings file
.lcovrc -------------> .hadouken/dotfiles/.lcovrc
# Cppcheck settings
.cppcheck-suppress --> .hadouken/dotfiles/.cppcheck-suppress
# Hadouken shell script
hadouken ------------> .hadouken/script/hadouken.sh
The following hidden file(s) will be added to your project root:
.hadouken.docker-compose.extend.yml # docker-compose extension file
.hadouken-bootstrap.sh # container post-installation script
If any of the file(s) specified above already exist on project root, they will not be overridden.
There are some commands defined to configure and build the project on command line. Note that these commands currently not being run in development environment container. This functionality will be implemented soon.
-b|--build build project
| build previously configured project. Any extra arguments will be forwarded to CMake.
| example: `hadouken --build` # build via CMake.
-x|--clean clean project
| remove PROJECT_ROOT/build directory.
| example: `hadouken --clean` # removes PROJECT_ROOT/build directory.
| example: `hadouken --clean --deep` # runs git clean -fxd on project root.
-c|--configure
| configure project located in PROJECT_ROOT to PROJECT_ROOT/build directory with cmake
| any following arguments will be forwarded to CMake.
| example: `hadouken --configure --target=Debug`
-i|--install install project
| install previosuly built project. Any extra arguments will be forwarded to CMake.
| example: `hadouken --install` # install via CMake.
-p|--pack pack project (using cpack)
| package previously build project. Any extra arguments will be forwarded to CMake.
| example: `hadouken --pack` # pack via CMake.
-t|--test run unit tests for project (using ctest)
| run unit tests of previously build project. Any extra arguments will be forwarded to CTest.
| example: `hadouken --test` # pack via CMake.
-a|--all clean->configure->build->pack project
-u|--upgrade upgrade hadouken to latest release
| this basically runs submodule update, reduced to a command for ease of use.
| example: `hadouken --upgrade` # upgrade hadouken to latest master release.
You can print this information any time by invoking ./hadouken --help
command.
Hadouken project will be separately maintained, and there will be updates. In order to update the hadouken to latest stable release, issue the following command from your project's root directory:
./hadouken --upgrade
This will upgrade the hadouken submodule from remote master
branch. If you have done changes to your local copy of hadouken, this command will respect to that. In order to force the upgrade, add --force
parameter to upgrade command.
./hadouken --upgrade --force
In this section we will cover what hadouken offers and how to use them.
Hadouken
offers a docker based development environment container, which features all neccessary utilities to satisfy the prerequisites of features offered by Hadouken
.
The container is designed to be used with Visual Studio Code - Remote - Containers
extension. If you are not an Visual Studio Code user, you can still use the container manually.
While running on VSCode with Remote - Containers
extension, the extension will automatically recognize the Dockerfile
and devcontainer.json
files located in .devcontainer
folder.
If that is not the case, you can still open your project in development container by pressing F1 (or Ctrl + Shift + P) and selecting Remote-Containers: Reopen in Container
menu item.
This will open the project in development container. In first time, the extension will automatically build the docker image from the Dockerfile
and then install a VSCode Server equipped with extensions specified in devcontainer.json
file. This may take a while.
To go back to local environment, select Remote-Containers: Reopen Locally
menu item.
Development environment is based on Debian Sid, and contains the following tools and utilities;
- Version Control System (git)
- GCC Toolchain (gcc-10, gdb)
- LLVM Toolchain (llvm-10, lldb-10, clang-10, clangd)
- Build System Utilities (make, ninja-build, autoconf, automake, libtool, m4, cmake)
- Static Analysis, Formatting, Linting, Diagnotics (clang-format-10 clang-tidy-10 iwyu cppcheck valgrind ccache)
- Unit Testing, Benchmarking (libgtest-dev, libgmock-dev)
- Code Coverage Analysis (lcov, gcovr)
- Documentation (doxygen, doxygen-doc, doxygen-latex, doxygen-doxyparse, graphviz)
It is possible to run post-install commands in development environment container. To do that, edit the script file named .hadouken.bootstrap.sh
at project root directory. This script file will be run when development environment container is built for first time.
You can use
apt
orpip
to install the packages you desire. As a rule of thumb, always check whether the third party dependency is available as conan package. If so, use conan to satisfy your project's dependency.
# Project requires boost, so install it.
apt install libboost-all-dev
If you already built the development environment container, the container is needed to be rebuilt in order to reflect the changes. To do that, Press F1 and type Remote-Containers: Rebuild Container
and press Enter. This will re-create the container from scratch.
You can extend base docker-compose.yml
file to add more functionality, and even more images.
I asked the similar question myself and was going to implement a mechanism that exposes local git configuration and credentials to docker image, but realized that VSCode remote containers extension already does that. Your local git configuration is copied to the container as-is. If you are using http authentication, you can still proceed to authenticate using it. For authenticating with ssh keys, there is an extra step to take. Because of the security reasons, the extension does not copy the ssh keys to the container. Instead, it uses ssh-agent running on the host machine when ssh authentication is needed. So, if you want to be able to push to the remote repository from container shell, you have to spawn an instance of ssh-agent and add your relevant ssh private keys to it.
Before starting the container, run the following commands in a shell;
eval "$(ssh-agent -s)" # Start ssh-agent, -s option prints the options used by ssh agent to the stdout, eval sets them to the current shell.
ssh-add ~/.ssh/id_rsa # Add a ssh private key to the agent. The container can only use the keys added to the agent.
After that, you can work with git
as if you are on your local machine. While using ssh-agent, please bear in mind that all added ssh keys can be used by remote hosts which you ssh
'ed into. I strongly encourage you to read ssh-agent's man page before using it: https://linux.die.net/man/1/ssh-agent
If you want to spawn ssh-agent automatically on startup, you can add these lines to your
~/.bash_profile
script:
if [ -z "$SSH_AUTH_SOCK" ]
then
# Check for a currently running instance of the agent
RUNNING_AGENT="`ps -ax | grep 'ssh-agent -s' | grep -v grep | wc -l | tr -d '[:space:]'`"
if [ "$RUNNING_AGENT" = "0" ]
then
# Launch a new instance of the agent
ssh-agent -s &> .ssh/ssh-agent
fi
eval `cat .ssh/ssh-agent`
# You might add your ssh keys automatically here, but it is not recommended
# for security reasons.
fi
Hadouken provides several modules to help with external tool integration and ease of use. All tools are included in devenv
container, so skip any installation related stuff if you are using the devenv
container.
<project_name>
is capitalized and non-alphanumeric characters replaced (with an underscore) version of top level project name. The term top level
here refers to the cmake project declaration prior to hadouken module inclusion.
Controlled by <project_name>_TOOLCONF_USE_CLANG_FORMAT
option.
Locate clang-format*
in environment, if available. The status will be printed to the stdout.
This feature requires at least clang-format version 10 to be present in environment.
project(my-awesome-project) # Declare a new project
set(my-awesome-project_TOOLCONF_USE_CLANG_FORMAT TRUE) # Locate clang-format and use it if available.
include(.hadouken/hadouken.cmake) # Use hadouken
# ClangFormat module will try to locate the clang-format executable.
# If clang-format executable is found, each target created via `make_target` function call will have an additional
# target suffixed with `.format`, which will format the target's source code when invoked.
# This `.format' target will use `.clang-format` file located in project root, which will be a symbolic link to
# hadouken project's `.clang-format` file when not specified explicitly. Hadouken's `.clang-format` file will
# format the source code according to NETTSI C++ Code Standards.
You can install clang-format
by getting the desired revision of llvm project (clang-format is a part of it) https://github.com/llvm/llvm-project or you can get from you package manager.
Ubuntu/Debian
sudo apt-get update
sudo apt-get install clang-format
Controlled by <project_name>_TOOLCONF_USE_CLANG_TIDY
option.
Locate clang-tidy*
in environment, if available. The status will be printed to the stdout.
This feature requires at least clang-format version 10 to be present in environment.
project(my-awesome-project) # Declare a new project
set(my-awesome-project_TOOLCONF_USE_CLANG_TIDY TRUE) # Locate clang-format and use it if available.
include(.hadouken/hadouken.cmake) # Use hadouken
# ClangTidy module will try to locate the clang-format executable.
# If clang-tidy executable is found, each target created via `make_target` function call will have an additional
# target suffixed with `.tidy`, which will tidy/lint the target's source code when invoked.
# This `.tidy' target will use `.clang-tidy` file located in project root, which will be a symbolic link to
# hadouken project's `.clang-tidy` file when not specified explicitly. Hadouken's `.clang-tidy` file will
# tidy/lint the source code according to NETTSI C++ Code Standards.
You can install clang-tidy
by getting the desired revision of llvm project (clang-tidy is a part of it) https://github.com/llvm/llvm-project or you can get from you package manager.
Ubuntu/Debian
sudo apt-get update
sudo apt-get install clang-tidy
If you are using
vscode
for development, you can install thevscode-clangd
extension in order to be able to apply quick fixes suggested by theclang-tidy
.vscode-clangd
requires clang language serverclangd
to be availabe in your environment. You will need to installclangd
separately.For distros with apt based package managers:
sudo apt-get install clangd
Controlled by <project_name>_TOOLCONF_USE_CPPCHECK
option.
Locate cppcheck
in environment, if available. The status will be printed to the stdout.
As CMake officially supports cppcheck, 'CMAKE_CXX_CPPCHECK' variable will be set accordingly and CMake will trigger cppcheck on every source file build.
project(my-awesome-project) # Declare a new project
set(my-awesome-project_TOOLCONF_USE_CPPCHECK TRUE) # Locate cppcheck and use it if available.
include(.hadouken/hadouken.cmake) # Use hadouken
# No further action is required.
You can install cppcheck
either by downloading it from the official site http://cppcheck.sourceforge.net/#download or you can get from you package manager.
Ubuntu/Debian
sudo apt-get update
sudo apt-get install cppcheck
Controlled by <project_name>_TOOLCONF_USE_GCOV
, <project_name>_TOOLCONF_USE_LCOV
, <project_name>_TOOLCONF_USE_GCOVR
, <project_name>_TOOLCONF_USE_LLVM_COV
options.
Also to specify paths and names for customize search in your environment you can use these options:
<project_name>_TOOLCONF_GCOV_PATHS
<project_name>_TOOLCONF_GCOV_NAMES
<project_name>_TOOLCONF_LLVM_COV_PATHS
<project_name>_TOOLCONF_LLVM_COV_NAMES
Example usage:
project(my-awesome-project) # Declare a new project
set(my-awesome-project_TOOLCONF_USE_GCOV TRUE) # Locate gcov and use it if available.
set(my-awesome-project_TOOLCONF_GCOV_NAMES "gcov-tool" "gcov-new" "gcov-test") # Specify search names for gcov tool.
set(my-awesome-project_TOOLCONF_GCOV_PATHS "/usr/bin" "~/Desktop/toolchain") # Specify search names for gcov tool.
set(my-awesome-project_TOOLCONF_USE_GCOVR TRUE) # Locate gcovr and use it if available.
include(.hadouken/hadouken.cmake) # Use hadouken
make_target(TYPE UNIT_TEST WITH_COVERAGE)
# Creates an unit test named my-awesome-project and my-awesome-project.cov coverage target.
Locate gcov & lcov
in environment, if available. The status will be printed to stdout.
Code coverage is enabled per target basis. To enable code coverage target creation for a target, pass WITH_COVERAGE
argument to make_target
function. WITH_COVERAGE
will cause make_target
to create a new target named <target_name>.lcov
(when lcov enabled and present) and <target_name>.gcovr.xml, <target_name>.gcovr.html
(when gcovr enabled and present) which runs code coverage analysis and generates code coverage report when run.
project(my-awesome-project) # Declare a new project
set(my-awesome-project_TOOLCONF_USE_GCOV TRUE) # Locate gcov and use it if available.
set(my-awesome-project_TOOLCONF_USE_LCOV TRUE) # Locate lcov and use it if available.
set(my-awesome-project_TOOLCONF_USE_GCOVR TRUE) # Locate gcovr and use it if available.
include(.hadouken/hadouken.cmake) # Use hadouken
make_target(TYPE UNIT_TEST WITH_COVERAGE)
# Creates an unit test named my-awesome-project and my-awesome-project.cov coverage target.
Controlled by <project_name>_TOOLCONF_USE_COVERAGE
option.
Automatically detects your compiler and select the appropriate coverage tool (gcov
or llvm-cov
) for you.
Locate gcov
or llvm-cov
in environment, if available. The status will be printed to stdout.
Code coverage is enabled per target basis. To enable code coverage target creation for a target, pass WITH_COVERAGE
argument to make_target
function. WITH_COVERAGE
will cause make_target
to create a new target named <target_name>.lcov
(when lcov enabled and present) and <target_name>.gcovr.xml, <target_name>.gcovr.html
(when gcovr enabled and present) which runs code coverage analysis and generates code coverage report when run.
project(my-awesome-project) # Declare a new project
set(my-awesome-project_TOOLCONF_USE_COVERAGE TRUE) # Locate your coverage tool in your environment (for clang llvm-cov) (for gcc gcov) and use it if available.
set(my-awesome-project_TOOLCONF_USE_GCOVR TRUE) # Locate gcovr and use it if available.
include(.hadouken/hadouken.cmake) # Use hadouken
make_target(TYPE UNIT_TEST WITH_COVERAGE)
# Creates an unit test named my-awesome-project and my-awesome-project.cov coverage target.
Note: When you want to use <project_name>_TOOLCONF_USE_COVERAGE
option, you have to delete or comment out <project_name>_TOOLCONF_USE_GCOV
and <project_name>_TOOLCONF_USE_LLVM_COV
options from your CMakeLists.txt.
In this scenario, we have the following library and the corresponding unit test code:
lib.hpp
namespace hdktest{
namespace lib{
class library{
public:
library();
~library();
bool should_return_true(int val);
};
}
}
lib.cpp
namespace hdktest::lib{
library::library(){
std::cout << "construct" << std::endl;
}
library::~library(){
std::cout << "destruct" << std::endl;
}
bool library::should_return_true(int val){
switch(val){
case 1:
return false;
case 2:
return true;
case 3:
return false;
}
return true;
}
}
Library's CMakeLists.txt:
project(hdktest.lib)
make_target(TYPE STATIC)
add_subdirectory(test)
ut_lib.cpp
TEST(test,test){
hdktest::lib::library library;
EXPECT_TRUE(true);
}
TEST(test, param1){
hdktest::lib::library library;
EXPECT_FALSE(library.should_return_true(1));
}
TEST(test, param2){
hdktest::lib::library library;
EXPECT_TRUE(library.should_return_true(2));
}
TEST(test, test_param3_Test){
hdktest::lib::library library;
EXPECT_FALSE(library.should_return_true(3));
}
Unit test's CMakeLists.txt:
project(hdktest.lib.test)
make_target(
TYPE UNIT_TEST
SOURCES ut_lib.cpp
LINK hdktest.lib
WITH_COVERAGE
COVERAGE_TARGETS hdktest.lib
NO_AUTO_COMPILATION_UNIT
)
gcov
, lcov
, llvm-cov
and gcovr
integrations are also enabled by specifying following options:
SET(HDKTEST_TOOLCONF_USE_GCOV TRUE CACHE BOOL "Enable/disable gcov integration" FORCE)
SET(HDKTEST_TOOLCONF_USE_LCOV TRUE CACHE BOOL "Enable/disable lcov integration" FORCE)
SET(HDKTEST_TOOLCONF_USE_GCOVR TRUE CACHE BOOL "Enable/disable gcovr integration" FORCE)
SET(HDKTEST_TOOLCONF_USE_LLVM_COV TRUE CACHE BOOL "Enable/disable llvm-cov integration" FORCE)
Also there is one more option to enable coverage (gcov
, llvm-cov
):
SET(HDKTEST_TOOLCONF_USE_COVERAGE TRUE CACHE BOOL "Enable/disable coverage integration" FORCE)
Note: This option overrides HDKTEST_TOOLCONF_USE_GCOV
option and HDKTEST_TOOLCONF_USE_LLVM_COV
option, cause this option will select coverage tool as automatically depends on your compiler.
As we specified WITH_COVERAGE
option and enabling coverage tools, following additional targets will be available for build (which will trigger code coverage report generation):
- hdktest.lib.test.gcovr.html
- hdktest.lib.test.gcovr.xml
- hdktest.lib.test.lcov
By building hdktest.lib.test.lcov
target, a line coverage report will be generated in build folder.
Directories shown in the report are src
(source code of unit under test) and test
(the unit test code). In the report, we can see the unit test is covered %93.3 of the unit under test. Let's dive into src
to investigate situation more detailed.
Here we can see unit under test consists of only lib.cpp
file. By clicking lib.cpp
we can check which lines of lib.cpp
is not covered by the unit test.
The blue lines are the lines which are executed by the unit test. The Line data column shows how many times the corresponding line is executed. The red lines are the lines which have not been executed by the unit test. By looking this line coverage result, we can clearly see that the unit test constructed & destructed an instance of library object 8 times, and should_return_true
function has been called 6 times. In these calls, case 1 is taken 2 times, case 2 is taken 2 times and case 3 is taken 2 times respectively. We can conclude that our unit test did not supply an argument which is not handled by the switch case. To confirm that, let's take a look to the unit test code.
Our unit test has 4 cases, but none of them calls should_return_true
with a value which is outside of switch case's defined range of values. Let's fix that by adding a new unit test case, and check coverage report again.
TEST(test, test_param4_Test){
hdktest::lib::library library;
EXPECT_TRUE(library.should_return_true(4));
}
As you can see above, adding the missing case indeed fixed our coverage rate.
You can install gcov, lcov, gcovr
either by downloading it from their respective official sites http://cppcheck.sourceforge.net/#download or you can get from you package manager.
Ubuntu/Debian
sudo apt-get update
sudo apt-get install gcov lcov gcovr
Example code coverage output of an unit test is shown below(gcovr/html):
Controlled by <project_name>_TOOLCONF_COVERAGE_HTML_TITLE
variable.
Example:
project(my-awesome-project)
set(my-awesome-project_TOOLCONF_COVERAGE_HTML_TITLE "My custom html title on coverage")
Controlled by <project_name>_TOOLCONF_USE_GOOGLE_TEST
variable.
Locate google test & google mock in local conan cache, if available. The status will be printed to stdout.
Helper will create a target named <project_name>.hadouken_autotargets.test
which includes google test & google mock header and libraries. You can link against this target in order to create an unit test.
If you are using make_target
with type UNIT_TEST
, the created target will be automatically linked against the test target.
project(my-awesome-project)
set(my-awesome-project_TOOLCONF_USE_GOOGLE_TEST TRUE)
include(.hadouken/hadouken.cmake) # Use hadouken
# A target named `my-awesome-project.hadouken_autotargets.test` will be created if google test & google mock
# are found in environment. Failure to do so results in CMake configure step failure.
Recommended usage:
# Targets created with type UNIT_TEST will be automatically linked against googletest/googlemock
# when <project_name>_TOOLCONF_USE_GOOGLE_TEST is enabled. `ut_component1.cpp` will be able to
# consume googletest/googlemock without any further action.
make_target(
TYPE UNIT_TEST
SOURCES ut_component1.cpp
NO_AUTO_COMPILATION_UNIT
)
GoogleTest/GoogleMock are automatically installed via Conan when enabled. You can control the package name, version and conan profile via predefined variables. Defaults will be used unless specified.
set(my-awesome-project_HADOUKEN_CONAN_GOOGLE_TEST_PKG_NAME "gtest") # Change conan package name for google test
set(my-awesome-project_HADOUKEN_CONAN_GOOGLE_TEST_PKG_VERSION "1.12.0") # Change conan package version for google test
set(my-awesome-project_HADOUKEN_CONAN_PROFILE_FILE "/workspace/.conan/profiles/gcc") # Change conan profile
# ... on configuration:
# [cmake] -- Starting tool configuration for Google Test (`gtest/1.12.0`)
# [cmake] -- Resolving conan dependency `gtest/1.12.0`
# [cmake] -- Executing conan for `gtest/1.12.0`... (please be patient)
# [cmake] -- Using user-defined conan profile file `/workspace/.conan/profiles/gcc`
# [cmake] -- Conan execution done for `gtest/1.12.0`
# [cmake] -- Auto-created `ncf.hadouken_autotargets.test` target
Controlled by <project_name>_TOOLCONF_USE_GOOGLE_BENCH
variable.
Locate google benchmark in local conan cache, if available. The status will be printed to stdout.
Helper will create a target named <project_name>.hadouken_autotargets.bench
which includes google benchmark header and libraries. You can link against this target in order to create a microbenchmark.
If you are using make_target
, the created target will be automatically linked against the benchmark target.
project(my-awesome-project)
set(my-awesome-project_TOOLCONF_USE_GOOGLE_BENCH TRUE)
include(.hadouken/hadouken.cmake) # Use hadouken
# A target named `my-awesome-project.hadouken_autotargets.bench` will be created if google benchmark
# is successfully resolved via Conan. Failure to do so results in CMake configure step failure.
Recommended usage:
# Targets created with type BENCHMARK will be automatically linked against google benchmark
# when <project_name>_TOOLCONF_USE_GOOGLE_BENCH is enabled. `bm_component1.cpp` will be able to
# consume google benchmark without any further action.
make_target(
TYPE BENCHMARK
SOURCES bm_component1.cpp
NO_AUTO_COMPILATION_UNIT
)
Google Benchmark is automatically installed via Conan when enabled. You can control the package name, version and conan profile via predefined variables. Defaults will be used unless specified.
set(my-awesome-project_HADOUKEN_CONAN_GOOGLE_BENCHMARK_PKG_NAME "benchmark") # Change conan package name for google test
set(my-awesome-project_HADOUKEN_CONAN_GOOGLE_BENCHMARK_PKG_VERSION "1.5.4") # Change conan package version for google test
set(my-awesome-project_HADOUKEN_CONAN_PROFILE_FILE "/workspace/.conan/profiles/gcc") # Change conan profile
# ... on configuration:
# [cmake] -- Starting tool configuration for Google Benchmark (`benchmark/1.5.4`)
# [cmake] -- Resolving conan dependency `benchmark/1.5.4`
# [cmake] -- Executing conan for `benchmark/1.5.4`... (please be patient)
# [cmake] -- Using user-defined conan profile file `/workspace/.conan/profiles/gcc`
# [cmake] -- Conan execution done for `benchmark/1.5.4`
# [cmake] -- Auto-created `ncf.hadouken_autotargets.test` target
Controlled by <project_name>_TOOLCONF_USE_IWYU
option.
Locate include what you use
in environment, if available. The status will be printed to stdout.
As CMake officially supports iwyu
, CMAKE_CXX_INCLUDE_WHAT_YOU_USE
variable will be set accordingly and CMake will trigger iwyu on every source file build.
project(my-awesome-project) # Declare a new project
set(my-awesome-project_TOOLCONF_USE_IWYU TRUE) # Locate iwyu and use it if available.
include(.hadouken/hadouken.cmake) # Use hadouken
# No further action is required.
You can install iwyu
either by downloading it from the official site https://include-what-you-use.org/downloads/ or you can get from you package manager.
Ubuntu/Debian
sudo apt-get update
sudo apt-get install iwyu
iwyu
functions on specific clang version, so you also need to install that version. To determine which version is required, issue following command from the terminal;
iwyu --version
// Example output:
// include-what-you-use 0.12 based on clang version 9.0.1-2
In the scenario above, we would need to install clang-9
.
Controlled by <project_name>_TOOLCONF_USE_LWYU
option.
Link what you use requires no external tools. Standard toolchain linker will be used to perform this functionality.
As CMake officially supports lwyu
, CMAKE_LINK_WHAT_YOU_USE
variable will be set accordingly and CMake will trigger lwyu on every link action.
project(my-awesome-project) # Declare a new project
set(my-awesome-project_TOOLCONF_USE_LWYU TRUE) # Locate iwyu and use it if available.
include(.hadouken/hadouken.cmake) # Use hadouken
# No further action is required.
No extra tools required for this functionality besides the toolchain linker you are using.
Controlled by <project_name>_TOOLCONF_USE_CCACHE
option.
Locate ccache
in environment, if available. The status will be printed to the stdout.
CCache speeds up compilation times by caching the compilation artifacts, and reusing them on scenarios when re-compilation would produce the exact same artifact.
project(my-awesome-project) # Declare a new project
set(my-awesome-project_TOOLCONF_USE_CCACHE TRUE) # Locate ccache and use it if available.
include(.hadouken/hadouken.cmake) # Use hadouken
# No further action is required.
You can install ccache either by downloading it from the official site https://ccache.dev/download.html or you can get from you package manager.
Ubuntu/Debian
sudo apt-get update
sudo apt-get install ccache
Core modules are wrappers which are used commonly in CMake based projects.
CMake module which provides a function to gather the files which will be compiled for the project. Requires project folder to be structured in include/
src/
form. To gather compilation unit of a project, simply invoke hdk_make_compilation_unit
function at project scope. This will define 3 CMake variables in project scope.
${HEADERS} # Content of the include/ folder. Paths are relative to include/ folder.
${SOURCES} # Content of the src/ folder. Paths are relative to src/ folder.
${COMPILATION_UNIT} # Content of the both include/ and src/ folders.
CMake module which provides functions to create several CMake target types with less typing. To create a target using MakeTarget, simply invoke make_target
function with desired arguments. The make_target
function operates on projects, so it must be invoked after project()
CMake command is invoked for the desired project.
All arguments are optional, except the TYPE
argument.
The created target's compilation unit will be automatically gathered using AutoCompilationUnit module, so the project must be structured accordingly. Otherwise, you have to specify source and header files manually by populating SOURCES
and HEADERS
arguments.
make_target(TYPE <EXECUTABLE|STATIC|SHARED|UNIT_TEST|INTERFACE|BENCHMARK>
[LINK [<target_name|library_name> ...]]
[COMPILE_OPTIONS [<options> ...]]
[COMPILE_DEFINITIONS[<definitions> ...]]
[DEPENDS [<target_name> ...]]
[SUFFIX <name_suffix>]
[NAME <desired_name>]
[OUTPUT_NAME <desired_output_name>]
[PREFIX <name_prefix>]
[INCLUDES [<include_path> ...]]
[PARTOF [<target_name> ...]]
[SOURCES [<source_path> ...]]
[HEADERS [<header_path> ...]]
[WITH_COVERAGE]
[WITH_INSTALL]
[ARGUMENTS] [<argument-1> <argument-2> ...<argument-n>]
[COVERAGE_TARGETS [<target_name> ...]]
[COVERAGE_LCOV_FILTER_PATTERN <lcov-pattern>]
[COVERAGE_GCOVR_FILTER_PATTERN <gcovr-pattern>]
[EXPOSE_PROJECT_METADATA]
[PROJECT_METADATA_PREFIX <prefix>]
[NO_AUTO_COMPILATION_UNIT]
[WORKING_DIRECTORY <working_directory>]
[AUTO_SUFFIX]
)
Type of the target to be created. Possible values are;
Executable target (add_executable)
Example:
project(proj.executable VERSION 0.1.0)
make_target(
# Specify type as EXECUTABLE file
TYPE EXECUTABLE
# Link `proj.component-q.static` target to the executable
LINK proj.component-q.static
)
Static library target (add_library(STATIC))
Example:
project(proj.component-q VERSION 0.1.0.0 LANGUAGES CXX)
make_target(
# Specify type as STATIC library
TYPE STATIC
# Append .static suffix to the target name
SUFFIX .static
# Link `proj.component-x` and `proj-component-y` to target
# as PUBLIC (propogates to other targets using this target)
LINK PUBLIC proj.component-x proj.component-y
)
Shared library target (add_library(SHARED))
Example:
project(proj.component-q VERSION 0.1.0.0 LANGUAGES CXX)
make_target(
# Specify type as SHARED library
TYPE SHARED
# Append .shared suffix to the target name
SUFFIX .shared
# Link `proj.component-x` and `proj-component-y` to target
# as PUBLIC (propogates to other targets using this target)
LINK PUBLIC proj.component-x proj.component-y
)
Unit test target. Performs additional steps to make unit test discoverable by CTest. Also, target is automagically linked to google test
and google mock
libraries for convenience.
Example:
project(proj.component-x.test VERSION 0.1.0 LANGUAGES CXX)
# Create an unit test target with the name of
# `project.component-x.test.feature-1`
make_target(
# Create an unit test target
TYPE UNIT_TEST
# Specify the source file(s) of the target
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ut_feature_1.cpp
# Link the subject which will be tested
LINK proj.component-x
# Append `.feature-1` suffix to the created target
SUFFIX .feature-1
# Disable auto source/header file gathering for this target
NO_AUTO_COMPILATION_UNIT
)
# Create an unit test target with the name of
# `project.component-x.test.feature-2`
make_target(
# Create an unit test target
TYPE UNIT_TEST
# Specify the source file(s) of the target
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ut_feature_2.cpp
# Link the subject which will be tested
LINK proj.component-x
# Append `.feature-2` suffix to the created target
SUFFIX .feature-2
# Disable auto source/header file gathering for this target
NO_AUTO_COMPILATION_UNIT
)
Header-only library target (add_library(INTERFACE))
Benchmark target. Similar to UNIT_TEST target, target is automagically linked to google benchmark
library for convenience.
Example:
project(proj.component-x.bench VERSION 0.1.0 LANGUAGES CXX)
# Create an unit test target with the name of
# `project.component-x.bench`
make_target(
# Create a benchmark target
TYPE BENCHMARK
# Specify the source file(s) of the target
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/bm_component_x.cpp
# Link the subject which will be tested
LINK proj.component-x
# Disable auto source/header file gathering for this target
NO_AUTO_COMPILATION_UNIT
)
The list of libraries/targets to link to the created target. The names which are target names are resolved by CMake and the actual .so/.a files will be linked according to the target type. Link target's public/interface include path also will be appended to the created target, so the public interface of the target can be used in created target's source files by using angle brackets (<>
).
Example:
make_target(TYPE EXECUTABLE LINK pthread my-awesome-target)
The list of parameters to pass to the compiler when compiling the source code of the created target.
Example:
make_target(TYPE STATIC COMPILE_OPTIONS -O3)
The list of macro definitions to pass to the compiler when compiling the source code of the created target.
Example:
make_target(TYPE STATIC COMPILE_DEFINITIONS USING_HADOUKEN=1)
The list of dependencies of created target. In order to satisfy dependency, the specified targets will be processed before the created target is processed.
Example:
project(proj.application VERSION 0.1.0 LANGUAGES CXX)
# This command would create an executable target with the name of
# `proj.application` without the explicit name specification.
# When `NAME` paramater is available, the value of `NAME` parameter
# will be used as target name instead.
make_target(
# Create a shared library target
TYPE EXECUTABLE
# This will ensure `proj.deps.boost` target is configured
# before configuring the `proj.application` target.
# This is useful for externally configured projects, which are
# declared as custom targets via `add_custom_target`(*). Since custom
# targets cannot be linked to a target, we somehow need to indicate
# that `proj.deps.boost` should be configured before the target.
# Because, in order to be able to link libboost_system.a and libboost_thread.a
# libraries, they must have been already compiled before the compilation of proj.application begins.
# Keep in mind that a `LINK` ed CMake target are also implicitly
# dependency of the target, so there is no need to specify that
# target in DEPENDS section again.
DEPENDS proj.deps.boost proj.deps.spdlog
# Explicitly specify the name
NAME my_awesome_application
# Specify include paths of linked libraries
INCLUDES /usr/lib/boost/include /usr/lib/spdlog/include
# Link libraries
LINK libboost_system.a libboost_thread.a libspdlog.a
)
# The target will be named as `my_awesome_application`.
# (*) Proper way of declaring an external library as a CMake target is,
# to use IMPORTED targets. See:
# <https://cmake.org/cmake/help/latest/command/add_library.html#imported-libraries>
# for details
The user-defined name for the created target. By default, name will be automatically determined from previous project call. If you want to give a different name, you can give by specifying this argument.
Example:
project(proj.application VERSION 0.1.0 LANGUAGES CXX)
# This command would create an executable target with the name of
# `proj.application` without the explicit name specification.
# When `NAME` paramater is available, the value of `NAME` parameter
# will be used as target name instead.
make_target(
# Create a shared library target
TYPE EXECUTABLE
# Explicitly specify the name
NAME my_awesome_application
# Specify include paths of linked libraries
INCLUDES /usr/lib/boost/include /usr/lib/spdlog/include
# Link libraries
LINK libboost_system.a libboost_thread.a libspdlog.a
)
# The target will be named as `my_awesome_application`.
The user-defined output name for the created target. By default, name will be automatically determined from the target name. If you want to give a different name for the output (e.g. binaries), you can give by specifying this argument.
There are some special keywords enclosed in angle brackets (<>
), which can be used in OUTPUT_NAME
:
- <PROJECT_NAME> : Name of the current project
- <TARGET_NAME> : Name of the current target
- <TARGET_TYPE> : Type of the current target (lowercase)
Example:
project(proj.application VERSION 0.1.0 LANGUAGES CXX)
# This command would create an executable target with the name of
# `proj.application` without the explicit name specification.
# When `NAME` paramater is available, the value of `NAME` parameter
# will be used as target name instead.
make_target(
# Create a shared library target
TYPE EXECUTABLE
# Explicitly specify the name
NAME my_awesome_application
# Explicitly specify output name
OUTPUT_NAME mapp
# Specify include paths of linked libraries
INCLUDES /usr/lib/boost/include /usr/lib/spdlog/include
# Link libraries
LINK libboost_system.a libboost_thread.a libspdlog.a
)
# The output binary name will be mapp.exe
Another example:
# This command would create a project named proj.component1 and
# two separate targets with name of proj.component1.static and
# proj.component1.shared respectively.
# Their output name will be libproj.component1.a and libproj.component1.so
# since <PROJECT_NAME> is specified as output name.
make_component(
proj.component1
TARGET TYPE STATIC
LINK PRIVATE proj.component2.static
TARGET TYPE SHARED
LINK PRIVATE proj.component2.shared
ALL_LINK PUBLIC proj.component3
proj.component4
PRIVATE proj.component5
ALL_AUTO_SUFFIX
ALL_WITH_INSTALL
ALL_OUTPUT_NAME <PROJECT_NAME>
)
The prefix to be prepended to the created target name.
project(my-awesome-component)
make_target(TYPE EXECUTABLE PREFIX test.)
# The target name will be `test.my-awesome-component`
The suffix to be appended to the created target name. Usually used to distinguish between subtypes of the same target, or target with different configurations.
project(my-awesome-component)
make_target(TYPE SHARED SUFFIX .shared)
# The target name will be `my-awesome-component.shared`
make_target(TYPE SHARED SUFFIX .shared.pic COMPILE_OPTIONS -fPIC)
# The target name will be `my-awesome-component.shared.pic
make_target(TYPE STATIC SUFFIX .static)
# The target name will be `my-awesome-component.static`
Creates a suffix from target type and appends to the created target name. This option does not override the explicitly given suffix, but it has precedence over the explicit suffix.
project(my-awesome-component)
make_target(TYPE SHARED AUTO_SUFFIX)
# The target name will be `my-awesome-component.shared`
make_target(TYPE SHARED AUTO_SUFFIX SUFFIX .pic COMPILE_OPTIONS -fPIC)
# The target name will be `my-awesome-component.shared.pic
make_target(TYPE STATIC AUTO_SUFFIX)
# The target name will be `my-awesome-component.static`
List of include paths to be added to the include directory of the created target. Useful when LINK
ed library is not a CMake target.
Example:
project(proj.component-z VERSION 0.1.0 LANGUAGES CXX)
# Create a shared library target with name of
# `proj.component-z`
make_target(
# Create a shared library target
TYPE SHARED
# Specify include paths of linked libraries
INCLUDES /usr/lib/boost/include /usr/lib/spdlog/include
# Link libraries
LINK libboost_system.a libboost_thread.a libspdlog.a
)
Reverse of DEPENDS, makes specified argument (expected to be a target) to be dependent to the created target.
Example:
project(my-awesome-component)
make_target(TYPE SHARED PARTOF special-component)
# special-component now depends on my-awesome-component
Extra source files to be appended to created target's sources.
Example:
project(proj.component-y VERSION 0.1.0 LANGUAGES CXX)
# Create a static library target with name of
# `proj.component-y`
make_target(
# Create a static library target
TYPE STATIC
# Specify additional source code files for the target
SOURCES /home/extra/extra_file_1.cpp /home/extra/extra_file_2.cpp
# Link dependencies
LINK proj.component-x
)
# Without `NO_AUTO_COMPILATION_UNIT` parameter is specified,
# auto source gathering will still take place. Specified `SOURCES`
# will be appended to auto-gathered source file list.
Extra header files to be appended to created target's headers.
Example:
project(proj.component-y VERSION 0.1.0 LANGUAGES CXX)
# Create a static library target with name of
# `proj.component-y`
make_target(
# Create a static library target
TYPE STATIC
# Specify additional source code files for the target
HEADERS /home/extra/include/extras/extra_1.hpp /home/extra/include/extras/extra_2.hpp
# Link dependencies
LINK proj.component-x
)
# Without `NO_AUTO_COMPILATION_UNIT` parameter is specified,
# auto header gathering will still take place. Specified `HEADERS`
# will be appended to auto-gathered header file list.
Exclusion filters for auto compilation unit gathering.
A regular expression filter to exclude source files matching to the given pattern on auto compilation unit creation.
project(proj.component-y VERSION 0.1.0 LANGUAGES CXX)
# Create a static library target with name of
# `proj.component-y`
make_target(
# Create a static library target
TYPE STATIC
EXCLUDE_SOURCES ".*test\\.cpp$"
# Link dependencies
LINK proj.component-x
)
# This target will have all files in src/ folder in its' compilation
# unit except test.cpp
A regular expression filter to exclude header files matching to the given pattern on auto compilation unit creation.
project(proj.component-y VERSION 0.1.0 LANGUAGES CXX)
# Create a static library target with name of
# `proj.component-y`
make_target(
# Create a static library target
TYPE STATIC
EXCLUDE_HEADERS ".*[test\\.hpp|foo\\.hpp]$"
# Link dependencies
LINK proj.component-x
)
# This target will have all files in include/ folder in its' compilation
# unit except test.hpp and foo.hpp
From : https://cmake.org/cmake/help/v3.6/command/string.html#regex-specification
The following characters have special meaning in regular expressions:
^ Matches at beginning of input
$ Matches at end of input
. Matches any single character
[ ] Matches any character(s) inside the brackets
[^ ] Matches any character(s) not inside the brackets
- Inside brackets, specifies an inclusive range between
characters on either side e.g. [a-f] is [abcdef]
To match a literal - using brackets, make it the first
or the last character e.g. [+*/-] matches basic
mathematical operators.
* Matches preceding pattern zero or more times
+ Matches preceding pattern one or more times
? Matches preceding pattern zero or once only
| Matches a pattern on either side of the |
() Saves a matched subexpression, which can be referenced
in the REGEX REPLACE operation. Additionally it is saved
by all regular expression-related commands, including
e.g. if( MATCHES ), in the variables CMAKE_MATCH_(0..9).
Important note: You have to double-escape the literal dot to put a dot into the expression.
Arguments to be forwarded to the created target. ARGUMENTS
will only make sense for targets which invoke an executable or command. The ARGUMENTS
parameter is only defined for the types specified below:
- UNIT_TEST (ARGUMENTS will be appended to the add_test COMMAND part)
Example:
project(proj.component-x.test VERSION 0.1.0 LANGUAGES CXX)
# Create an unit test target with the name of
# `project.component-x.test.component.x`
make_target(
# Create an unit test target
TYPE UNIT_TEST
# Specify the source file(s) of the target
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ut_component_x.cpp
# Link the subject which will be tested
LINK proj.component-x
# Append `.component-x` suffix to the created target
SUFFIX .component-x
# Enable code coverage instrumentation
WITH_COVERAGE
# Arguments which will be passed to the add_test COMMAND part.
ARGUMENTS --gtest_shuffle --gtest_output=xml:${PROJECT_BINARY_DIR}/ut_reports/${PROJECT_NAME}.xml
# Disable auto source/header file gathering for this target
NO_AUTO_COMPILATION_UNIT
)
Generate code coverage report for the project. Can be used for UNIT_TEST
and BENCHMARK
target types.
Example:
project(proj.component-x.test VERSION 0.1.0 LANGUAGES CXX)
# Create an unit test target with the name of
# `project.component-x.test.component.x`
make_target(
# Create an unit test target
TYPE UNIT_TEST
# Specify the source file(s) of the target
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ut_component_x.cpp
# Link the subject which will be tested
LINK proj.component-x
# Append `.component-x` suffix to the created target
SUFFIX .component-x
# Enable code coverage instrumentation
WITH_COVERAGE
# Disable auto source/header file gathering for this target
NO_AUTO_COMPILATION_UNIT
)
Automatically generate install step for the target.
Example:
project(proj VERSION 0.1.0 LANGUAGES CXX)
# Create a static library target with the name of
# `project.component-x`
make_target(
# Create an unit test target
TYPE STATIC
# Append `.component-x` suffix to the created target
SUFFIX .component-x
# Create installation step for project
WITH_INSTALL
)
Requires WITH_COVERAGE
to be set first. Injects required instrumentation parameters (-fprofile-arcs -ftest-coverage) and link libraries (-lgcov) to the specified targets. Without this, coverage reports may or may not include the desired target's coverage rate.
project(proj.component-y.test VERSION 0.1.0 LANGUAGES CXX)
make_target(
TYPE UNIT_TEST
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ut_component_y.cpp
LINK proj.component-y
SUFFIX .component-y
WITH_COVERAGE
COVERAGE_TARGETS proj.component-y
NO_AUTO_COMPILATION_UNIT
)
# -fprofile-arcs and -ftest-coverage will be injected to
# proj.component-y's compilation flags via
# target_compile options and also -lgcov will be added to
# target's link libraries via target_link_libraries.
Requires WITH_COVERAGE
to be set first. The argument will be forwarded to lcov
's --extract
parameter, which allows to filter out coverage results.
Example:
project(hdktest.lib.test)
make_target(
TYPE UNIT_TEST
SOURCES ut_lib.cpp
LINK hdktest.lib
WITH_COVERAGE
COVERAGE_TARGETS hdktest.lib
COVERAGE_LCOV_FILTER_PATTERN "my_unit*.cpp"
NO_AUTO_COMPILATION_UNIT
)
# Only files matching the pattern will be included on lcov report
Requires WITH_COVERAGE
to be set first. The argument will be forwarded to gcovr
's --f
parameter, which allows to filter out coverage results.
Example:
project(hdktest.lib.test)
make_target(
TYPE UNIT_TEST
SOURCES ut_lib.cpp
LINK hdktest.lib
WITH_COVERAGE
COVERAGE_TARGETS hdktest.lib
COVERAGE_GCOVR_FILTER_PATTERN "${PROJECT_SOURCE_DIR}/.*cpp"
NO_AUTO_COMPILATION_UNIT
)
# Only files matching the pattern will be included on gcovr report
# Be aware that GCOVR's filter is a regex pattern. Use forward slash `/` as
# path separator.
Additional parameter list to pass into add_test()
calls made by hadouken for UNIT_TEST target type. Only valid for UNIT_TEST targets.
Example:
make_target(
TYPE UNIT_TEST
ADD_TEST_PARAMETERS WORKING_DIRECTORY /home/user/bin
# change working directory of the test executable
)
See https://cmake.org/cmake/help/latest/command/add_test.html for all possible add_test()
parameters.
Additional parameter list to pass into gtest_discover_tests()
calls made by hadouken for UNIT_TEST target type. Only valid for UNIT_TEST targets.
Example:
make_target(
TYPE UNIT_TEST
GTEST_DISCOVER_PARAMETERS PROPERTIES RUN_SERIIAL TRUE
# prohibit all resulting tests of `gtest_discover_test()` from running in parallel.
)
See https://cmake.org/cmake/help/git-stage/module/GoogleTest.html#command:gtest_discover_tests for all possible gtest_discover_test()
parameters.
Defines the following C/C++ preprocessor macros for the target created.
<prefix>MODULE_NAME
<prefix>MODULE_DESC
<prefix>MODULE_VERSION_MAJOR
<prefix>MODULE_VERSION_MINOR
<prefix>MODULE_VERSION_PATCH
<prefix>MODULE_VERSION_TWEAK
Defined macros will only be accessible (private) for the source/header files of the project.
<prefix>
can be specified by supplying PROJECT_METADATA_PREFIX
argument.
Example:
project(hdktest.exe VERSION 1.0.3.3 DESCRIPTION "Very interesting project")
make_target(
TYPE EXECUTABLE
SOURCES main.cpp
NO_AUTO_COMPILATION_UNIT
EXPOSE_PROJECT_METADATA
PROJECT_METADATA_PREFIX HDKTEST_EXE_
)
main.cpp:
#include <cstdio>
auto main(void) -> int{
std::printf("%s\n", HDKTEST_EXE_MODULE_NAME);
std::printf("%s\n", HDKTEST_EXE_MODULE_DESC);
std::printf("%s.%s.%s\n", HDKTEST_EXE_MODULE_VERSION_MAJOR, HDKTEST_EXE_MODULE_VERSION_MINOR, HDKTEST_EXE_MODULE_VERSION_PATCH, HDKTEST_EXE_MODULE_VERSION_TWEAK);
}
// Expected output of the code above:
/*
hdktest.exe
Very interesting project
1.0.3.3
*/
The information is gathered from the project()
info.
Requires EXPOSE_PROJECT_METADATA
to be set.
Prefix to prepend created project metadata macros.
Disable automatic compilation unit gathering from include/
and src/
directories.
Specify working directory for the created target. It is only valid for the following target types:
- UNIT_TEST (when executed by CTest)
project(hdktest.lib.test)
make_target(
TYPE UNIT_TEST
SOURCES ut_lib.cpp
LINK hdktest.lib
WITH_COVERAGE
COVERAGE_TARGETS hdktest.lib
COVERAGE_GCOVR_FILTER_PATTERN "${PROJECT_SOURCE_DIR}/.*cpp"
NO_AUTO_COMPILATION_UNIT
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} # Set working directory to ${PROJECT_SOURCE_DIR}
)
All targets created via hadouken can be excluded from build based on target type, target name and project name. The options to control target creation are:
- <ROOT_PROJECT_NAME_UPPER_SANITIZED>DISABLE<TARGET_TYPE>_TARGETS
- <ROOT_PROJECT_NAME_UPPER_SANITIZED>WITHOUT<PROJECT_NAME>
- <ROOT_PROJECT_NAME_UPPER_SANITIZED>WITHOUT<TARGET_NAME>
These options must be set on top level CMakeLists file of the project before including the main hadouken include file (.hadouken/hadouken.cmake)
<ROOT_PROJECT_NAME_UPPER_SANITIZED> is the root project name with all capital, any non a-zA-Z0-9
character is replaced with an underscore _
(eg. hdk.test.x = HDK_TEST_X).
Example (target type):
project(proj)
OPTION(PROJ_DISABLE_STATIC_TARGETS "Enable/disable project-wide static library target creation" ON )
OPTION(PROJ_DISABLE_SHARED_TARGETS "Enable/disable project-wide benchmark target creation" OFF )
# Include hadouken
include(.hadouken/hadouken.cmake)
# Will not create a target since `PROJ_DISABLE_STATIC_TARGETS` option is enabled
make_target(TYPE STATIC)
# Will create a target since `PROJ_DISABLE_SHARED_TARGETS' option is disabled
make_target(TYPE SHARED)
Example (Project name):
project(proj)
OPTION(PROJ_WITHOUT_PROJ_COMPONENT1 "Enable/disable proj.component1 project" ON )
# Include hadouken
include(.hadouken/hadouken.cmake)
project(proj.component1)
# Will not create a target since `PROJ_WITHOUT_PROJ_COMPONENT1` option is enabled
make_target(TYPE STATIC)
project(proj.component2)
# Will create a target since `PROJ_DISABLE_SHARED_TARGETS' option is disabled
make_target(TYPE SHARED)
Example (Target name):
project(proj)
OPTION(PROJ_WITHOUT_PROJ_COMPONENT1_STATIC "Enable/disable proj.component1.static target" ON )
OPTION(PROJ_WITHOUT_PROJ_COMPONENT1_SHARED "Enable/disable proj.component1.shared target" OFF )
# Include hadouken
include(.hadouken/hadouken.cmake)
project(proj.component1)
# Will not create a target since `PROJ_WITHOUT_PROJ_COMPONENT1` option is enabled
make_target(TYPE STATIC AUTO_SUFFIX)
# Will create a target since `PROJ_DISABLE_SHARED_TARGETS' option is disabled
make_target(TYPE SHARED AUTO_SUFFIX)
CMake module which allows to define multiple targets with single call. It is a syntatic sugar for multiple make_target
calls. It aims to reduce redundant writing.
make_component
automatically defines a new project, then iterates over all TARGET
's specified in the call, defining a target by calling make_target
for each of them. It also forwards the common arguments prefixed with ALL_
to the make_target
call. All make_target
arguments can be used with ALL_
prefix. It will be applied to all targets defined in make_component
call.
make_component(<COMPONENT_NAME>
TARGET <TARGET_ARGS...>
ALL_<TARGET_ARG>...
)
Example usage:
make_component(
proj.component1
VERSION 0.1.0
TARGET TYPE STATIC
LINK PRIVATE proj.component4.static
TARGET TYPE SHARED
LINK PUBLIC proj.component4.shared
ALL_LINK PUBLIC proj.component2
proj.component3
PRIVATE boost::boost
ALL_WITH_INSTALL
ALL_AUTO_SUFFIX
)
The usage illustrated above is exactly same functionality-wise with:
project(proj.component1 VERSION 0.1.0)
make_target(
TYPE STATIC
AUTO_SUFFIX
LINK PUBLIC proj.component2
proj.component3
PRIVATE boost::boost
proj.component4.static
WITH_INSTALL
)
make_target(
TYPE SHARED
AUTO_SUFFIX
LINK PUBLIC proj.component2
proj.component3
PRIVATE boost::boost
proj.component4.shared
WITH_INSTALL
)
Important notes:
- The targets will be defined in their appearance order in the
make_component
call. - Defined project's version, description, homepage url and languages will be inherited from parent project, if not explicitly given.
CMake module which provides information about a target's properties.
Prints all properties of the target specified. Useful for debug purposes.
Example:
project(my_lib)
make_target(TYPE STATIC)
# Print properties of the target
hdk_print_target_properties(my_lib)
Possible output:
[cmake] -- --------------------------------------------------------
[cmake] -- Printing properties of target `my_lib`
[cmake] -- --------------------------------------------------------
[cmake] -- my_lib AUTOGEN_ORIGIN_DEPENDS = ON
[cmake] -- my_lib AUTOMOC_COMPILER_PREDEFINES = ON
[cmake] -- my_lib AUTOMOC_MACRO_NAMES = Q_OBJECT
[cmake] -- my_lib AUTOMOC_PATH_PREFIX = OFF
[cmake] -- my_lib BINARY_DIR = /workspace/build
[cmake] -- my_lib BUILD_WITH_INSTALL_RPATH = OFF
[cmake] -- my_lib IMPORTED = FALSE
[cmake] -- my_lib IMPORTED_GLOBAL = FALSE
[cmake] -- my_lib INCLUDE_DIRECTORIES = /workspace/include/
[cmake] -- my_lib INSTALL_RPATH =
[cmake] -- my_lib INSTALL_RPATH_USE_LINK_PATH = OFF
[cmake] -- my_lib INTERFACE_INCLUDE_DIRECTORIES = /workspace/include/
[cmake] -- my_lib INTERFACE_LINK_LIBRARIES = gcov
[cmake] -- my_lib LINK_LIBRARIES = gcov
[cmake] -- my_lib NAME = my_lib
[cmake] -- my_lib PCH_WARN_INVALID = ON
[cmake] -- my_lib RULE_LAUNCH_COMPILE = /usr/bin/ccache
[cmake] -- my_lib SKIP_BUILD_RPATH = OFF
[cmake] -- my_lib SOURCES = /workspace/source.cpp
[cmake] -- my_lib SOURCE_DIR = /workspace
[cmake] -- my_lib TYPE = STATIC_LIBRARY
[cmake] -- my_lib UNITY_BUILD_BATCH_SIZE = 8
[cmake] -- my_lib UNITY_BUILD_MODE = BATCH
[cmake] -- my_lib VISIBILITY_INLINES_HIDDEN = OFF
There is also a flag which enables property printing for all targets:
project(proj)
OPTION(PROJ_DEBUG_TARGET_PRINT_PROPERTIES "Enable/disable project-wide target property printing " ON )
CMake module which contains utility functions for interacting with the environment.
Reads a dotenv (.env) file, and declares a CMake variable for each environment variable in it.
Example:
.env file content (test.env):
# this is a comment an will be skipped
HADOUKEN="is great"
VERY="nice"
hdk_read_environment_file("test.env", PRE_ _SUF)
# Now the environment variables in .env file are declared as
# `PRE_HADOUKEN_SUF` and `PRE_VERY_SUF`, containing the values specified
# in the file.
CMake module which contains utility functions for interacting with CMake targets.
Copy the artifact produced by <target>
to <destination_path>
on <step>
. Step can be PRE_BUILD, PRE_LINK or POST_BUILD
.
Example:
project(my_executable)
make_target(TYPE EXECUTABLE)
hdk_copy_target_artifact_to(
STEP POST_BUILD
TARGET_NAME my_executable
DESTINATION /tmp
)
# Executable produced by my_executable target will be copied to /tmp
# after target `my_executable` gets built.
This module has several functions to invoke git functionality from CMake.
A function to retrieve active branch name for the project. Sets OUTPUT_VARIABLE
variable to current branch name.
Example:
hdk_git_get_branch_name(CURRENT_BRANCH DIRECTORY ${PROJECT_SOURCE_DIR})
# ${CURRENT_BRANCH} now contains the branch name of the git repository in ${PROJECT_SOURCE_DIR}
A function to retrieve commit hash of the head for the git repository located in specified directory. Sets OUTPUT_VARIABLE
variable to head commit hash of git repository located at <dir>
.
Example:
hdk_git_get_head_commit_hash(HEAD_COMMIT DIRECTORY ${PROJECT_SOURCE_DIR})
# ${HEAD_COMMIT} now contains the head commit hash of the git repository in ${PROJECT_SOURCE_DIR}
A function to retrieve whether current work tree for the git repository located in specified directory is dirty. Sets OUTPUT_VARIABLE
variable to dirtiness status of git repository located at <dir>
.
Example:
hdk_git_is_worktree_dirty(IS_DIRTY DIRECTORY ${PROJECT_SOURCE_DIR})
# ${IS_DIRTY} now contains whether worktree of the git repository in ${PROJECT_SOURCE_DIR} is dirty or not.
A function to retrieve a git configuration by its' key name. Sets OUTPUT_VARIABLE
variable to value of the configuration value specified by CONFIG_KEY
in git repository located at <dir>
.
Example:
hdk_git_get_config(USER_MAIL DIRECTORY ${PROJECT_SOURCE_DIR} CONFIG_KEY user.email)
# ${USER_MAIL} now contains the user.email configuration value of the git repository located in ${PROJECT_SOURCE_DIR} folder.
A function to retrieve all tags pointing to <commit_hash>
in git repository located at <dir>
. Commit hash is defaulted to HEAD if not specified. Sets OUTPUT_VARIABLE
to found tags.
hdk_git_get_tag(TAGS DIRECTORY ${PROJECT_SOURCE_DIR})
# ${TAGS} now contains the all tags pointing to head in git repository located at ${PROJECT_SOURCE_DIR} folder.
A function to create a dependency to <target>
target which fetches all git lfs files specified in <lfs_files>
from the git lfs repository at <lfs_root>
on CMake configuration.
Example:
project(example.unit_test)
# This unit test needs `${PROJECT_SOURCE_DIR}/data/json-files/geo-location.json`
# `${PROJECT_SOURCE_DIR}/data/bin-files/archive.bin` files to be present in order
# to run.
make_target(
TYPE UNIT_TEST
SOURCES ${test_sources} ut_example.cpp
)
# This will cause `${PROJECT_SOURCE_DIR}/data/json-files/geo-location.json` and
# `${PROJECT_SOURCE_DIR}/data/bin-files/archive.bin` to be fetched from git large
# file storage on CMake configure step.
# This ensures the files needed by the unit test are present when unit test is
# being build and being run.
hdk_target_needs_git_lfs_files(
# LFS file dependent target
example.unit_test
# LFS repository path
${PROJECT_SOURCE_DIR}/data
# LFS file list (semicolon separated, one file per line)
[=[
json-files/geo-location.json;
bin-files/archive.bin
]=]
)
A function to export some important version control (git) variables as compile time definitions (preprocessor macros). Exported variables will be available to the all compiled code under the project. Variables are gathered from git repository located at <directory>
. When no explicit directory is given as parameter, DIRECTORY will be defaulted to ${CMAKE_CURRENT_SOURCE_DIR}
.
Exported variables are as follows:
<prefix>
GIT_BRANCH_NAME<prefix>
GIT_COMMIT_ID<prefix>
GIT_WORKTREE_DIRTY<prefix>
GIT_AUTHOR_NAME (current git author name)<prefix>
GIT_AUTHOR_EMAIL (current git author e-mail )
Example:
hdk_git_metadata_as_compile_defn(PREFIX MYPROJ_)
Later on, in code
#include <cstdio>
auto main(void) -> int {
std::printf("%s\n", MYPROJ_GIT_BRANCH_NAME);
std::printf("%s\n", MYPROJ_GIT_COMMIT_ID);
std::printf("%s\n", MYPROJ_GIT_WORKTREE_DIRTY);
std::printf("%s\n", MYPROJ_GIT_AUTHOR_NAME);
std::printf("%s\n", MYPROJ_GIT_AUTHOR_EMAIL);
}
Print branch, commit hash and dirtiness status of the git repository specified in DIRECTORY argument.
hdk_git_print_status()
# Possible output to stdout might be:
# VCS Status
# Branch: master
# tag: v.0.14.0
# Commit: 8f14b151ec1aba749f5ac579b6f4f8209738dee5
# Dirty: false
Official conan module provided by JFrog. Provides conan_cmake_run
function, which allows conan to be run from CMake.
cmake_minimum_required(VERSION 3.16)
project(dummy)
include(.hadouken/hadouken.cmake)
# Fetch boost 1.70.0 from conan repositories.
# Build any dependencies which are missing on repository as pre-built
# Create cmake targets for resolved dependencies
# Created dependencies can be accessed from CONAN_PKG:: target. (eg. CONAN_PKG::boost)
conan_cmake_run(REQUIRES boost/1.70.0
BASIC_SETUP CMAKE_TARGETS
BUILD missing)
A CMake module to retrieve logical core count of the machine. Sets ${HARDWARE_CONCURRENCY}
CMake variable.
This modules are designed for determining the common and compiler specific compilation parameters according to the build variant. The base functionality is provided by BuildVariant.cmake
module, which determines the compiler (using CMakeDetermine[C/CXX]Compiler CMake modules), sets the build variant if not set by the user (default is RelWithDebInfo), adjusts build variant specific compilation parameters, then includes the compiler specific module. Compiler-specific modules are suffixed with toolchain/compiler name and contains the distinct compilation flags for that compiler. These modules are used to enable some useful warnings, and also turn warnings into errors in release builds.
Warnings are important for keeping a bug-free and clean code base. Therefore, hadouken by default passes "-Wall" on debug and "-Wall -Werror" on release configuration to the compiler. On top of that, hadouken will pass additional useful warning flags depending on compiler version. These flags are contained in compiler specific diagnostic modules.
Hadouken currently supports GCC
and Clang
specific diagnostics flag modules. These modules enables several important compiler warning flags. Diagnostic flag modules are compiler version aware, therefore the compiler warning flags which requires compiler version higher than current compiler version will not be enabled on configuration.
See DiagnosticFlags_GCC.cmake
and DiagnosticFlags_Clang.cmake
for inspecting which version-specific, extra warning flags are being passed to the compiler.
Finders are located in cmake/modules/find
folder. This folder is designated for CMake finders, which allows user to call find_program
and locate executable/library/header files in environment. CMake has a quite exhaustive list of finder modules, but still lacks some. This folder will contain the finder modules which CMake does not contain.
The list of finder modules are provided below;
- FindGMock.cmake (google mock)
Feature check modules are located in cmake/modules/check
folder. They're driven by FeatureCheck.cmake
file. The purpose of a feature check module is, to determine whether a feature, a library, a header (etc..) is available in compilation environment or not. This is done by compiling a small C++ code regarding the subject. If the file compiles successfully, the subject is said to be available in the environment. Compilation is done by invoking a special CMake function named check_cxx_source_compiles
. This function takes a source code as an argument, tries to compile it, and sets a user-named variable to indicate compilation status. An example feature check module is shown below;
set(CMAKE_REQUIRED_QUIET true) # Do not print anything to stdout regarding feature check
set(CMAKE_REQUIRED_LIBRARIES "-lpthread") # Feature check dependencies (linkage against pthread is required for this scenario)
# A code which uses some of the `pthread` functions in order to determine
# whether pthread is available on the compilation environment
check_cxx_source_compiles("
#include <pthread.h>
extern \"C\" void* thread_proc(void* arg)
{
return arg;
}
int main(void)
{
pthread_mutex_t mut;
int result = pthread_mutex_init(&mut, 0);
if(0 == result)
{
result |= pthread_mutex_lock(&mut);
result |= pthread_mutex_unlock(&mut);
result |= pthread_mutex_trylock(&mut);
result |= pthread_mutex_unlock(&mut);
result |= pthread_mutex_destroy(&mut);
pthread_t t;
int r = pthread_create(&t, 0, &thread_proc, 0);
// result |= r;
if(r == 0)
{
//
// If we can create a thread, then we must be able to join to it:
//
void* arg;
r = pthread_join(t, &arg);
result |= r;
}
}
return result;
}
"
${HDK_ROOT_PROJECT_NAME_UPPER}_FEATURE_HAS_PTHREADS
)
# <project_name>_FEATURE_HAS_PTHREADS is set to true or false after this, indicating the compilation status.
The result variable has a specific naming convention too. FeatureCheck.cmake
expects the result variable to be defined in a specific way: (the actual check code is if (NOT ${HDK_ROOT_PROJECT_NAME_UPPER}_HAS_${ARG_AS_UPPER})
)
${HDK_ROOT_PROJECT_NAME_UPPER}
is a CMake variable defined by hadouken which contains the top level project name, converted to uppercase and all non-alphanumeric characters replaced with an underscore (_). The glue _HAS_
have to come after this. The last word should match with the feature check module (capitalized).
Feature check modules have a specific naming convention. Every feature check module file name has to start with Check
. The letters between Check
and .cmake
determines the feature check module name. The user will use this name to invoke feature check functions.
To perform a feature check, invoke feature_check_assert with desired feature names.
Example:
project(my-awesome-project) # Declare a new project
include(.hadouken/hadouken.cmake) # Use hadouken
# Check if pthreads is available on compilation environment.
# CMake configuration will result in failure if code in feature check macro fails to compile.
feature_check_assert(Pthreads)
You can use Pthreads as a reference when creating new feature check modules.
Hadouken uses some CMake modules for internal usage only.
This is the CMake module which prints the hadouken's banner to the terminal when invoked.
CMake module which contains some utility functions which are responsible for printing hadouken's log output.
Hadouken will continue to evolve, and you may contribute to it. Don't hesitate to issue a PR.
VSCode Remote Containers Documentation: https://code.visualstudio.com/docs/remote/containers
- vscode developers: for creating such a flexible, versatile code editor and a huge ecosystem. https://github.com/microsoft/vscode
- vscode-remote-containers team: https://github.com/microsoft/vscode-dev-containers
- cmake-conan developers: https://github.com/conan-io/cmake-conan
This project is licensed under Apache 2.0 license. See LICENSE file for license information.