This repository makes using CMake in C++ projects easier and features:
- Allows building from Linux for Linux & Windows and from Windows only for Windows itself. Building from Windows requires Cygwin to be installed.
- Shell script to make, build, test and package using CMake also used by ci-pipelines.
- Locating the installed Qt library or downloading them.
- Downloading build tools for Windows when needed.
- Adding version and description to Windows DLL's EXE's as resource from the project (Linux: so-files are versioned only).
- Generating/compiling Doxygen documentation from the source with PlantUML plugin installed (downloaded by version number).
- Find the newest installed GCC compiler when more are installed and a cross-Windows compiler when used.
- Creating installable packages for Windows (NSIS, zip) and Linux (deb, rpm).
- Uploading to a Nexus APT repository of Debian packages or raw upload for Windows packages.
- Upload of generated HTML coverage reports of which a message containing a link is written to the GitLab merge request.
- Version bump script to determine the next version based on which (merge-)commit is released when using conventional commit messages.
- Script to build using a Docker container using a special image containing all tools needed.
Add the submodule from the private GitLab or the GitHub mirror as cmake/lib in your fresh git repository:
# Initialize the local repository.
git init --initial-branch=main
git submodule add --branch main -- "https://git.scanframe.com/library/cmake-lib.git" cmake/lib
git submodule add --branch main -- "https://github.com/Scanframe/sf-cmake.git" cmake/libInitialize the repository running the project setup script.
cmake/lib/bin/project-setup.shFor building the sample project for debugging execute the ./build.sh or ./docker-build.sh shell script.
The latter allows using a Docker container for building and running the project.
Execute any of the scripts without any options to figure out what to do.
Some
# Show help.
./build.sh -h
# Show target information using the preset in json file.
./build.sh -i
# Make the build project.
./build.sh -m gnu-debug
# Compile all default projects.
./build.sh -b gnu-debug
# Compile the non-default DoxGen documentation project.
./build.sh -b gnu-debug -n document
# Opens the Chrome browser in application mode with the generated pages.
bin/man/open.shA project directory tree could look like this.
<project-root>
├── .gitlab
├── bin
│ ├── gcov
│ ├── lnx64
│ │ └── lib
│ ├── pkg
│ ├── man
│ └── win64
│ └── lib
├── cmake
│ ├── cpack
│ └── lib (Repository location)
├── cmake-build
│ ├── gnu-debug (Linux GNU)
│ ├── gw-debug (Linux MinGW)
│ └── mingw-debug (Windows MinGW)
├── doc
├── lib
│ └── qt
└── src
└── tests
| Path | Description |
|---|---|
| .gitlab | GitLab CI/CD pipeline scripts. |
| bin | Root for compiled results from builds. |
| bin/gcov | Coverage report files from unittests. |
| bin/lnx64 | Binaries from Linux 64-bit builds. |
| bin/lnx64/lib | Dynamic libraries from Linux 64-bit builds. |
| bin/win64 | Binaries from Windows 64-bit builds. |
| bin/win64/lib | Dynamic libraries from Windows 64-bit builds. |
| bin/pkg | Packages from all builds. |
| bin/man | Doxygen generated documentation builds. |
| cmake/cpack | CPack files for packing the application and libraries. |
| cmake/lib | Obligatory Location of this 'cmake-lib' git-submodule. |
| cmake-build | CMake binary root directory. |
| doc | Doxygen document project source. |
| lib | Downloaded or symlinks to libraries. |
| lib/qt | Linux Qt library directory or symlink. |
| src | Application source files. |
| src/test | Test application source files. |
The directory bin and holds a placeholder file named __output__ to find the designated bin build
output directory for subprojects. The reason for building only subprojects instead of all is to speed
up debugging by compiling only the dynamic loaded library separately.
When directories are empty but needed then add a file called __placeholder__ so is not ignoring them.
To set up a project from scratch, some files, directories or templates need to be copied or symlinked as shown in the next table.
| Method | Source | Destination |
|---|---|---|
| Copy | tpl/root/CMakeLists.tpl.cmake | /CMakeLists.txt |
| Copy | tpl/root/CMakePresets.json | /CMakePresets.json |
| Copy | tpl/root/CMakeUserPresets.json | /CMakeUserPresets.json |
| Copy | tpl/gitlab-ci/* | /.gitlab |
| Symlink | bin/build.sh | /build.sh |
| Symlink | bin/docker-build.sh | /docker-build.sh |
| Symlink | bin/tools.sh | /tools.sh |
| Symlink | bin/version-bump.sh | /version-bump.sh |
| Symlink | bin/check-format.sh | /check-format.sh |
To get a head start, look into the tpl/root directory for files that will give a head start getting a project going.
Instead of installing Qt with the "Qt Maintenance Tool" this CMake command will download the library
in lib/Qt, lib/QtWin or lib/QtW64 depending on the host and target OS.
The tools for building on a Windows OS can also be installed using the symlink ./tools in the project
root created from cmake/lib/bin/tools.sh. A file .tools-dir-<hostname> is created where the tools
are installed and used by the ./build.sh to add it to the PATH when executing CMake commands.
find_package(SfQtLibrary 6.7.2 CONFIG REQUIRED)For generating documentation from the code using Doxygen the doc subdirectory
is to be added in the main CMakeLists.txt.
# Add Doxygen document project.
add_subdirectory(doc)The doc directory CMakeLists.txt looks like this where header files are added to the config file.
# Required first entry checking the cmake version.
cmake_minimum_required(VERSION 3.18)
# Set the global project name.
project("document")
# Add doxygen project when SfDoxygen was found.
# On Windows this is only possible when doxygen is installed in Cygwin.
find_package(SfDoxygen QUIET)
if (SfDoxygen_FOUND)
# Get the markdown files in this project directory including the README.md.
file(GLOB _SourceList RELATIVE "${CMAKE_CURRENT_BINARY_DIR}" "*.md" "../*.md")
# Get all the header files from the application.
file(GLOB_RECURSE _SourceListTmp RELATIVE "${CMAKE_CURRENT_BINARY_DIR}" "../src/*.h" "../src/*.md")
# Remove unwanted header file(s) ending on 'Private.h'.
list(FILTER _SourcesListTmp EXCLUDE REGEX ".*Private\\.h$")
# Append the list with headers.
list(APPEND _SourceList ${_SourceListTmp})
# Adds the actual manual target and the last argument is the optional PlantUML jar version to download use.
Sf_AddDoxygenDocumentation("${PROJECT_NAME}" "${PROJECT_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}/../bin/man" "${_SourceList}" "v1.2023.0")
endif ()Look at the Doxygen website for the syntax in C++ header comment blocks or Markdown files.
To create a version tag with this library, there are two options.
Create a release tag like v1.2.3 or a release candidate tag like 1.2.3-rc.4.
The CMake coding picks this up using function Sf_GetGitTagVersion returns the version depending on the result of the next Git-command.
# Only annotated tags so no '--tags' option.
git describe --dirty --match "v*.*.*"Possible results from this command are:
v1.2.3
v1.2.3-dirty
v1.2.3-rc.4-dirty
v1.2.3-rc.4
v1.2.3-45-g914edbb-dirty
v1.2.3-rc.4-56-g914edbb-dirty
The CMake function Sf_GetGitTagVersion creates a version list from the result.
Sf_GetGitTagVersion(_Versions "${CMAKE_CURRENT_LIST_DIR}")
list(GET _Versions 0 SF_GIT_TAG_VERSION)
list(GET _Versions 1 SF_GIT_TAG_RC)
list(GET _Versions 2 SF_GIT_TAG_COMMITS)For example when the result is v1.2.3-rc.4-56-g914edbb-dirty.
| Index | Description | Value |
|---|---|---|
| 0 | Main version number | 1.2.3 |
| 1 | Optional release candidate number | 4 |
| 2 | Commit count since the tag | 56 |
Index positions 1 and 2 are empty when not applicable.
For this item a separate page is created so see: Semantic Versioning
There are three Nexus apt-repositories that can be described to:
| Name | Usage |
|---|---|
stable |
Actual releases. |
staging |
Release candidates. |
develop |
Development and testing. |
To have the latest release, subscribe only to stable.
To have update when a release candidate (RC) becomes available subscribe additionally to staging.
When developing and testing debian packages subscribe additionally to develop.
Debian packages are deployed/uploaded to the appropriate apt-repository depending on if it:
- MR: Originates from a merge-request.
- PRB: Originates from a push to the release branch which is mainly
main. - RC: Is a Release Candidate.
- CMT: Has commits since tag was created.
| MR | RC | PRB | CMT | Destination |
|---|---|---|---|---|
| No | No | Yes | No | stable |
| No | No | Yes | Yes | staging |
| No | No | No | * | n/a |
| No | Yes | Yes | * | staging |
| No | Yes | No | * | develop |
| Yes | * | * | * | develop |
Windows ZIP and installer EXE files are uploaded to a
dist/<destination>directory.
The tools for this are gcov and gcovr of
The functions needed to perform coverage are located in SfBaseConfig.cmake.
| Function | Description |
|---|---|
| Sf_AddTargetForCoverage | Sets compiler and linker options for the target depending on the target type. |
| Sf_AddAsCoverageTest | Adds a test to the list which is used as a dependency for the test generating the report. |
| Sf_AddTestCoverageReport | Adds the test generating the report calling the script coverage-report.sh. |
To enable format check before a commit modify or add the script
.git/hooks/pre-commit with the following content.
It calls the check-format.sh script, which indirectly calls the
clang-format.sh` from the CMake support library.
It also checks if it is a commit to the main or master branch and prevents it.
#!/bin/bash
# Redirect output to stderr.
exec 1>&2
# Get the branch name.
branch="$(git rev-parse --abbrev-ref HEAD)"
# Check if it is 'main' and prevent a commit on it.
if [[ "${branch}" == "main" || "${branch}" == "master" ]]; then
echo "You can't commit directly to the '${branch}' branch!"
exit 1
fi
# When the file 'check-format.sh' exists call it to check if the formatting is correct.
if [[ -f check-format.sh ]]; then
if ! ./check-format.sh; then
echo "Source is not formatted correctly!"
exit 1
fi
fiThis same script is used in the main pipeline configuration script
main.gitlab-ci.yml in the job named 'check-env'.
So when the format is incorrect, the pipeline will fail.