- C++/Qt DevOps Template Project
- Project Development Methods
This project is to test GitLab's CI/CD-pipeline with a hello-world and hello-world-qt C++ applications. One for console and one for GUI using the Qt-framework. Both applications are build for Linux x86_64, Linux aarch64 (cross-compiler) and Windows (cross-compiler). The project includes Catch2 and GoogleTest unittest-frameworks.
The application has a shared library and is build using CMake and presets and a build script to simplify pipeline configurations for building, testing and packaging.
The Gitlab-Runners use Docker containers for builds and the runner is also a container itself. Runners are using a self-hosted caching service/server (MinIO) for caching between jobs across different hosts machines/containers when build stages are separated in steps depending on the child pipeline configuration.
The used Docker containers are stored on a self-hosted Docker-repository and deployment on a self-hosted apt-repository and for Windows a raw-repository.
Links:
Repositories:
Building the project from the command line is available using the ./build.sh shell script natively or from
within the Docker container (used also for the CI/CD pipelines).
To execute this script indirectly in a Docker container using the correct image and
options the ./docker-build.sh shell script is devised and runs on Linux as well as Windows/Cygwin.
Next is a table of all combination possible using JetBrains product for product development.
Some of the next table columns and values explained:
- CLI: is Command Line Interface
- IDE: Integrated Development Environment
- CLion: JetBrains C++ IDE
- Gateway: JetBrains Gateway using CLion
- OS: Operating system
- Arch: Architecture of the build system.
- Unittest: Perform unitest (no debug) from the IDE.
- Debug: Debug from the IDE without additional configuration.
- Toolchain: The toolchain used.
- Coverage: Able to perform coverage and produce a report (target
document). - SSH: On a remote machine the
./docker-build.sh sshdis executed opening port3022.
| IDE | OS-ver | Arch | Toolchain | Target OS | Unittest | Debug | Coverage |
|---|---|---|---|---|---|---|---|
| CLion | Windows 10/11 | x86_64 | Cygwin + mingw-x86_64 | Windows 10/11 | yes | yes | maybe |
| CLion | Ubuntu 24.04 | x86_64 | gnu-x86_64 | Ubuntu 24.04 | yes | yes | yes |
| CLion | Ubuntu 24.04 | x86_64 | gnu-aarch64 | Ubuntu 24.04 | yes | yes | yes |
| CLion | Ubuntu 24.04 | x86_64 | mingw-x86_64 | Ubuntu 24.04 | yes | no* | yes |
| CLion | Linux | x86_64 | Docker/gnu-x86_64 | Ubuntu 24.04 | yes | no* | yes |
| CLion | Linux | x86_64 | Docker/mingw-x86_64 | Windows 10/11 | yes | no* | maybe |
| CLion | Linux | x86_64 | Docker/gnu-aarch64 | Ubuntu 24.04 | yes | no* | yes |
| Gateway/SSH | any | x86_64 | Docker/gnu-x86_64 | Ubuntu 24.04 | yes | yes | yes |
| Gateway/SSH | any | x86_64 | Docker/mingw-x86_64 | Windows 10/11 | yes | no* | maybe |
| Gateway/SSH | any | x86_64 | Docker/gnu-aarch64 | Ubuntu 24.04 | yes | yes | yes |
no*CLion debugging withing a Docker toolchain is possible starting upgdbserverand
configuring a CLion target for remotegdbusage and mapping source paths.
After cloning the Git repository is to be initialized using the script init-repo.sh facilitates retrieving submodules and URL when cloned from GitHub. Fixes the symlinks for Windows/Cygwin which requires Cygwin to be installed using one of the following scripts.
Cygwin automatic installation script/command using powershell.
powershell -Command "Invoke-Expression(Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/Scanframe/sf-cygwin-bin/master/install-cygwin.ps1' -UseBasicParsing).Content"powershell -Command "Invoke-Expression(Invoke-WebRequest -Uri 'https://git.scanframe.com/shared/bin-bash/-/raw/master/install-cygwin.ps1' -UseBasicParsing).Content"The project uses the CMake workflow which is a high-level preset that defines a sequence of configure, build, and test steps in a single preset. Introduced in CMake v3.24, workflow presets allow users to streamline multi-stage operations within a project, reducing manual commands. They reference 'configure', 'build', and 'test' presets, enabling consistent and repeatable workflows.
Builds on Linux architectures x86_64 and aarch64.
git clone "https://git.scanframe.com/shared/devops.git" trial-devops
./trial-devops/init-repo.sh
./trial-devops/build.sh --required lnx
./trial-devops/build.sh -w gnu-debugBuilds for Windows using the Windows mingw crosscompiler and Wine for testing.
git clone "https://git.scanframe.com/shared/devops.git" trial-devops
./trial-devops/init-repo.sh
./trial-devops/build.sh --required lnx
./trial-devops/build.sh --required win
./trial-devops/build.sh -w gw-debugBuilds native on Windows downloading a MinGW64 toolchain when requested.
git clone "https://git.scanframe.com/shared/devops.git" trial-devops
./trial-devops/init-repo.sh
./trial-devops/build.sh --required win
./trial-devops/build.sh -w mingw-debugPresuming Docker is already installed.
Builds targets Linux x86_64, Linux aarch64 and Windows x86_64.
git clone "https://git.scanframe.com/shared/devops.git" trial-devops
./trial-devops/init-repo.sh
./trial-devops/docker_build.sh -- -w gnu-debug
./trial-devops/docker_build.sh -- -w ga-debug
./trial-devops/docker_build.sh -- -w gw-debugThe Docker image used for the CI/CD-pipeline en also for compiling in CLion is
configured by the in the GitHub sf-docker-runner repository bash
script cpp-builder.sh and cpp-builder/cpp.Dockerfile.
The bash-script assembles all files needed to create self-hosted
Sonatype Nexus server.
Clone the repository, execute the script cpp-builder.sh and view its sub-commands.
It requires pre-compiled Qt libraries which is done using the build-qt-lib.sh shell script.
| Image | OS | Architecture | Qt |
|---|---|---|---|
nexus.scanframe.com/amd64/gnu-cpp:24.04 |
Ubuntu 24.04 | x86_64 | n/a |
nexus.scanframe.com/arm64/gnu-cpp:24.04 |
Ubuntu 24.04 | aarch64 | n/a |
nexus.scanframe.com/amd64/gnu-cpp:24.04-6.7.2 |
Ubuntu 24.04 | x86_64 | v6.7.2 |
nexus.scanframe.com/arm64/gnu-cpp:24.04-6.7.2 |
Ubuntu 24.04 | aarch64 | v6.7.2 |
nexus.scanframe.com/amd64/gnu-cpp:24.04-6.8.1 |
Ubuntu 24.04 | x86_64 | v6.8.1 |
nexus.scanframe.com/arm64/gnu-cpp:24.04-6.8.1 |
Ubuntu 24.04 | aarch64 | v6.8.1 |
The image without the Qt framework is also used to compile the Linux Qt framework for the image with a Qt framework.
The image contains all needed packages for all build targets, including documents), and each of them are listed here with their versions.
The application source is located in this repository.
The generic 'hello-world' console application in gen/main.cpp.
The Qt cross-platform 'hello-world-qt' GUI-application in qt/main.cpp.
The cross-platform 'hello-lib' shared/dynamic/library in hwl/src/main.cpp.
The CMake Linux package contains more than the cmake executable.
| App | Description |
|---|---|
| CMake | CMake is an open-source, cross-platform build system. It uses configuration files (CMakeLists.txt) to generate native build scripts for various platforms and compilers. CMake simplifies the build process by providing a consistent interface for managing complex build configurations. |
| CTest | CTest is a testing tool that integrates with CMake. It allows developers to define and run tests for their CMake-based projects. CTest can execute tests in parallel, generate test reports, and integrate with Continuous Integration (CI) systems for automated testing. |
| CPack | CPack is a packaging tool designed to create distribution packages for software projects built with CMake. It can generate package formats such as DEB, RPM, NSIS, and ZIP. CPack simplifies the process of creating installable packages for different operating systems and distributions. |
To allow reuse of scripts for the ease of usage a library sf-cmake is created and used as a Git-submodule.
To make it more challenging the Catch2 unit-test library is imported.
The test application sources are located in ./src/tests.
# FetchContent added in CMake 3.11, downloads during the configure step.
include(FetchContent)
# Import Catch2 library for testing.
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.1.1
)
# Adds Catch2::Catch2
FetchContent_MakeAvailable(Catch2)The cmake/lib/SfDoxygenConfig.cmake package adds a function Sf_AddDoxygenDocumentation() which in its turn adds a
manual target.
Look at Doxygen website for the syntax in C++ header comment blocks or Markdown files.
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 in directly calls
the clang-format.sh script
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 fails.
The ./build.sh script make a call to the CMake support library bash-script
Build.sh.
Executes CMake commands using the 'CMakePresets.json' and 'CMakeUserPresets.json' files
of which the first is mandatory to exist.
Usage: build.sh [<options>] [<presets> ...]
-h, --help : Shows this help.
-d, --debug : Debug: Show executed commands rather then executing them.
-i, --info : Return information on all available build, test and package presets.
-s, --submodule : Return branch information on all Git submodules of last commit.
-p, --package : Create packages using a preset.
--required <trg> : Install required packages using the package manager under Linux.
For Windows package managers apt-cyg (Cygwin) and WinGet are used.
Where <trg> is the targeted system to build for like 'lnx', 'win', 'arm' on Linux
and for Windows only 'win'.
-m, --make : Create build directory and makefiles only.
-f, --fresh : Configure a fresh build tree, removing any existing cache file.
-C, --wipe : Wipe clean build tree directory by removing all contents from the build directory.
-c, --clean : Cleans build targets first (adds build option '--clean-first')
-b, --build : Build target and make config when it does not exist.
-B, --build-only : Build target only and fail when the configuration does not exist.
-t, --test : Runs the ctest application using a test-preset.
-r, --regex : Regular expression on which test names are to be executed.
-w, --workflow : Runs the passed work flow presets.
-l, --list-only : Lists the ctest test defined application by the project and selected preset.
-n, --target : Overrides the build targets set in the preset by a single target.
--run -- <cmd> : Run a command with the modified PATH for (Windows).
Examples:
Get all project presets info: ./build.sh -i
Make/Build project: ./build.sh -b my-build-preset1 my-build-preset2
Test project: ./build.sh -t my-test-preset1 my-test-preset2
Make/Build/Test/Pack project: ./build.sh -w my-workflow-preset
To make it easy to run the same commands within the Docker builder image,
the docker-build.sh is provided which takes the same arguments as the build.sh script.
Same as 'build.sh' script but running from Docker image but allows Docker specific commands.
Usage: docker-build.sh <options> -- <build-options> [command] <args...>
Options:
-h, --help : Shows this help.
--qt-ver <version> : Qt version part forming the Docker image name which defaults to '6.8.1' but empty is possible.
-p, --platform <platform> : Platform part forming the Docker image which defaults to 'amd64' where available is 'amd64' and 'arm64'.
--no-build-dir : Docker project builds in a regular cmake-build directory as a native build would.
Commands:
pull : Pulls the docker image from the Docker registry.
run : Runs a command as user 'user' in the container using Docker command.
'run' or 'exec' depending on a running container in the background.
start : Starts/Detaches a container named 'cpp_builder' in the background.
attach : Attaches to the in the background running container named 'cpp_builder'.
status : Returns info of the running container 'cpp_builder' in the background.
stop : Stops the container named 'cpp_builder' running in the background.
kill : Kills the container named 'cpp_builder' running in the background.
versions : Shows versions of most installed applications within the container.
sshd : Starts sshd service on port 3022 to allow remote control.
When a the container is detached it executes the 'build.sh' script by attaching to the container which is much faster.
Examples:
Show the targets using the amd64 platform docker image and Qt version 6.8.1.
docker-build.sh --platform amd64 --qt-ver '6.8.1' -- --info
Show the uname information of the arm64 container without QT libraries.
docker-build.sh --platform arm64 --qt-ver '' -- run uname -a
The command ./docker-build.sh versions will show all installed tool versions of the docker image.
The CI/CD Pipeline configuration has a main main.gitlab-ci.yml file which triggers a
child-pipeline build-single.gitlab-ci.yml twice.
Respectively Linux and Windows but having different variable assignments passed from the main pipeline.
The coverage.gitlab-ci.yml is triggert once.
The SF_SIGNAL variable is set in GitLab for the project.
| Value | Description |
|---|---|
| skip | Do not trigger any pipelines. |
| test | Tests the caching and artifacts mechanism. |
| deploy | Allows testing manual deployment of packages where child pipelines are manual as well. |
| When left empty or not defined the pipeline runs normal. |
@startuml
<style>
FontName Arial
FontSize 13
root
{
Padding 0
Margin 0
HorizontalAlignment Left
}
frame {
' define a new style, using CSS class syntax
FontColor Black
LineColor Gray
' Transparency is also possible
'BackgroundColor #52A0DC55
BackgroundColor #F9F9F9-#E9E9E9
'[From top left to bottom right <&fullscreen-enter>]
RoundCorner 10
}
}
rectangle
{
.event
{
'Green gradient
BackgroundColor #77BC65-#069A2E
RoundCorner 10
}
.gitlab-ci
{
BackgroundColor #FFDE59-#B47804
}
}
arrow
{
LineColor darkred
}
}
</style>
skinparam TitleFontStyle Bold
skinparam TitleFontSize 20
skinparam RankSep 40
skinparam NodeSep 10
title "CI-Pipeline & Triggers"
frame "Pipeline" as pipeline {
left to right direction
frame "Push Events" as events {
rectangle "Merge Request" <<event>> as merge_event
rectangle "Protected Branch" <<event>> as protected_event
}
frame "GitLab-CI" as gitlab_ci {
rectangle "Child: GNU-Build" <<gitlab-ci>> as gnu_cmake
rectangle "Child: GW-Build" <<gitlab-ci>> as gw_cmake
rectangle "Child: GA-Build" <<gitlab-ci>> as ga_cmake
rectangle "Child: GNU-Coverage" <<gitlab-ci>> as gnu_coverage
rectangle "Main" <<gitlab-ci>> as main
}
'Connectors
protected_event -> main : trigger
merge_event --> main : trigger
main --> gnu_cmake : trigger
main --> gw_cmake : trigger
main --> ga_cmake : trigger
main --> gnu_coverage : trigger
}
@endumlThe Docker way is to use image minio-server and minio-mc respectively for service and control console.
For using Docker a script minio.sh is created to
simplify it in
the sf-docker-runner repository.
To install a MinIO service from scratch using a Debian package is described in
the wiki-page.
To configure an APT-repository on a Sonatype Nexus server is described in
this wiki-page.
For uploading files to a Nexus repository is the upload-nexus.sh script.
To run a GitLab-Runner service using Docker use image gitlab/gitlab-runner:latest.
For using Docker a script gitlab-runner.sh
is created.
The script sets all the needed Docker options required by the 'C++ Build Image' (gnu-cpp:dev) to
have fuse available for bindfs fuze-zip and mounting it in the
sf-docker-runner repository.
To develop using CLion natively requires installing required packages to be installed to perform the
CMake workflow. To install these package is done executing the build.sh with the --required option.
# Install packages for Linux builds.
./build.sh --required lnx
# Install packages for Windows builds.
./build.sh --required winFor CLion add a Docker toolchain where the image to use is amd64/gnu-cpp:24.04-6.8.1 when it
was build locally or for example nexus.scanframe.com/amd64/gnu-cpp:24.04-6.8.1
when it was build remote and uploaded to the self-hosted Nexus service.
The Docker Qt 6.8.1 toolchain Container Settings are as follows:
-u 0:0
-e DISPLAY
--net host
--cap-add SYS_ADMIN
--device /dev/fuse
--security-opt apparmor:unconfined
--rm
-v /home/<linux-username>/.Xauthority:/home/user/.Xauthority:ro
-v <project-dir>:/mnt/project
The volume mount for .Xauthority and DISPLAY environment variable is to allow Qt GUI applications to use the host's
X-server.
Option -u 0:0 is needed for the entry point to change the users user uid and gid to match those to the
owner of the mounted project directory or the passed environment variable -e LOCAL_USER=<local-uid>:<local-gid>.
This is to prevent inaccessible files by the host. Using the owner of the mounted directory is easier for
porting a project between users. Option --privileged can be used instead of options --cap-add SYS_ADMIN --device /dev/fuse --security-opt apparmor:unconfined when there is an issue.
JetBrains Gateway can access the Docker container running a sshd service which opens a port 3022
on the system it is started on which is accessible with user user and password user.
The Docker image can be started locally or remote even on a Raspberry Pi having an aarch64 architecture.
# Starts the container to run in the background.
./docker-build.sh sshd
# Stops the container running in the background.
./docker-build.sh stopA child pipeline (linux-gcov) cannot not report coverage to GitLab.
The workaround is storing the coverage report, which is a text file on the Nexus server in a temporary location,
in the child pipeline's job.
In the main pipeline a job (report-coverage) retrieves the report and
outputs it so GitLab picks it up from there using the coverage in job configuration (
coverage: /^\s*lines:\s*\d+.\d+\%/).
A GitLab issue 363557 has been created but is not resolved jet.