Compare commits

...

5 Commits

Author SHA1 Message Date
Bart Beumer 427f11bbdf WIP 2025-07-27 10:22:40 +00:00
Bart Beumer a745b47cf1 WIP 2025-07-27 10:16:59 +00:00
Bart Beumer ea55d3e6fd WIP 2025-07-27 10:01:54 +00:00
Bart Beumer 8e657df097 WIP 2025-07-27 09:42:05 +00:00
Bart Beumer 0e95d60bcd Add presentation 2025-07-27 08:32:28 +00:00
27 changed files with 870 additions and 14 deletions

37
.clang-format Normal file
View File

@ -0,0 +1,37 @@
---
AlignAfterOpenBracket: Align
AllowAllArgumentsOnNextLine: 'false'
AllowAllConstructorInitializersOnNextLine: 'false'
AllowAllParametersOfDeclarationOnNextLine: 'false'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: None
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakTemplateDeclarations: 'Yes'
BinPackArguments: 'false'
BinPackParameters: 'false'
BreakBeforeBraces: Allman
BreakConstructorInitializers: BeforeComma
ColumnLimit: '120'
Cpp11BracedListStyle: 'true'
FixNamespaceComments: 'true'
IncludeBlocks: Merge
IndentCaseLabels: 'true'
IndentWidth: '4'
Language: Cpp
MaxEmptyLinesToKeep: '2'
NamespaceIndentation: Inner
PointerAlignment: Left
SortUsingDeclarations: 'true'
SpaceAfterTemplateKeyword: 'false'
SpacesInAngles: 'false'
SpacesInCStyleCastParentheses: 'false'
SpacesInContainerLiterals: 'false'
SpacesInSquareBrackets: 'false'
Standard: Cpp11
TabWidth: '4'
UseTab: Never
...

View File

@ -0,0 +1,7 @@
[requires]
boost/1.84.0
gtest/1.14.0
[generators]
CMakeDeps
CMakeToolchain

View File

@ -0,0 +1,19 @@
import os
from conan import ConanFile
from conan.tools.files import copy
from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps
class HelloConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "boost/1.84.0", "gtest/1.14.0", "libmagic/5.45"
generators = "CMakeDeps"
build_policy = "*"
def generate(self):
# We need to find the folder of libmagic and supply it to cmake so that
# we can deploy the magic file.
libmagic = self.dependencies["libmagic"]
tc = CMakeToolchain(self)
tc.variables["CONAN_LIBMAGIC_PACKAGE_FOLDER"] = libmagic.package_folder
tc.generate()

View File

@ -0,0 +1,462 @@
\documentclass[aspectratio=169]{beamer}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{graphics}
\usepackage{graphicx}
\usepackage{verbatim}
\usepackage{plantuml}
\usepackage{listings}
\usepackage{csquotes}
\usepackage{multicol}
\usepackage{menukeys}
% Style related code
%
\setbeamerfont{footnote}{size=\tiny}
% Code block style
%
\lstset{
basicstyle=\footnotesize\ttfamily,
columns=fullflexible,
frame=single,
numbers=left
}
\title{Workshop Modern C++ build environment}
\author{Bart Beumer (bart.beumer@alten.nl)}
\institute{Alten}
\date{2025-07-29}
\begin{document}
\begin{frame}
\titlepage
\end{frame}
\begin{frame}{Rationale}
Using modern tools in combination with C \& C++ we can describe the build environment, 3rd party libraries and executables to build.
We will be able to create a highly reproducable build environment regardless of the OS running on the developer PC (as long as it can run docker).
\vspace{5mm}
This workshop aims to demonstrate how to create this build environment, how to use the tools, introduce ourselves to this way of working using included demonstration projects.
\end{frame}
\begin{frame}{Tools}
We will be using a combination of tools, each having their own purpose.
\begin{itemize}
\item Dev containers \footnote{\url{https://containers.dev/}}
\begin{quote}
A development container (or dev container for short) allows you to use a container as a full-featured development environment.
\end{quote}
\item Conan \footnote{\url{https://conan.io/faq}}
Open source, decentralized and multi-platform package manager.
\item CMake \footnote{\url{https://cmake.org/about/}}
\begin{quote}
... an open source, cross-platform family of tools designed to build, test, and package software.
\end{quote}
\end{itemize}
\end{frame}
\begin{frame}{Dev containers}
Solves the problem of having an encapsulated environment containing all tools, files, configuration that makes up the development environment.
\vspace{5mm}
Supported by multiple tools like:
\begin{itemize}
\item Visual Studio Code \footnote{\url{https://containers.dev/supporting}}
\item IntelliJ IDEA \footnote{\url{https://blog.jetbrains.com/idea/2024/07/using-dev-containers-in-jetbrains-ides-part-1/}}
\item GitHub codespaces \footnote{\url{https://docs.github.com/en/codespaces/about-codespaces/what-are-codespaces}}
\end{itemize}
\end{frame}
\begin{frame}{Dev containers}
We need the following files:
\begin{itemize}
\item Dockerfile
\item devcontainer.json
\end{itemize}
\end{frame}
\begin{frame}{Dev containers}
Using docker compose we create the entire container.
\lstinputlisting[keepspaces,firstline=1, firstnumber=1]{../.devcontainer/Dockerfile}
\end{frame}
\begin{frame}{Dev containers}
A devcontainer.json contains metadata used by tools.
\lstinputlisting[firstline=1, firstnumber=1]{../.devcontainer/devcontainer.json}
\end{frame}
\begin{frame}{Dev containers}
We will experiment with these files during the workshop.
\end{frame}
\begin{frame}{Conan}
\begin{quotation}
Conan is a package manager for C and C++ which aims to solve some very common and difficult challenges.\footnote{\url{https::/conan.io/faq}}
\end{quotation}
\begin{itemize}
\item Conveniently depend on 3rd party packages and use them.
\item Automates the process of downloading, building \& deploy for further use.
\item Provide a mechanism to use libraries.
\end{itemize}
\end{frame}
\begin{frame}{Conan}
A simple text file can be used to configure dependencies on packages.
\lstinputlisting[language=make]{example_conan.txt}
\end{frame}
\begin{frame}{Conan}
We will experiment with these files during the workshop.
\end{frame}
\begin{frame}{CMake}
\begin{quote}
CMake is an open source, cross-platform family of tools designed to build, test, and package software. CMake gives you control of the software compilation process using simple independent configuration files. Unlike many cross-platform systems, CMake is designed to be used in conjunction with the native build environment.
\footnote{\url{https://cmake.org/about/}}
\end{quote}
\begin{itemize}
\item Good documentation and tutorials provided.\footnote{\url{https://cmake.org/cmake/help/latest/guide/tutorial/index.html}}
\item Tools run locally on a system.
\end{itemize}
\end{frame}
\begin{frame}{CMake}
Using files we can describe libraries, executables, the (external) dependencies they have.
\lstinputlisting[language=make]{../src/hello_world/CMakeLists.txt}
\end{frame}
\begin{frame}{CMake}
We will experiment with these files during the workshop.
\end{frame}
\begin{frame}{Workshop}
\begin{multicols}{2}
Lets start with opening our IDE and our project folder.
\includegraphics[width=0.45\textwidth]{vscode_empty}
Quickly after opening the folder we get the following question:
\includegraphics[width=0.45\textwidth]{vscode_folder_contains_devcontainer}
VS Code will build the container if needed and start using it.
\end{multicols}
\end{frame}
\begin{frame}{Workshop}
The dev container has been built, now we need to let conan work and fetch and build dependencies.
\vspace{5mm}
\keys{CTRL + SHIFT + P} ``conan install''
\vspace{5mm}
\includegraphics[width=0.75\textwidth]{vscode_conan_install}
\end{frame}
\begin{frame}{Workshop}
\begin{multicols}{2}
We can now use CMake to build our software.
\includegraphics[height=0.65\textheight]{vscode_conan_done_cmake}
\begin{itemize}
\item configure
\item build
\item debug/launch
\end{itemize}
\end{multicols}
\end{frame}
\begin{frame}{Workshop}
Continuing with theory
\end{frame}
\begin{frame}[fragile]{Conan: important commands}
\begin{verbatim}
pip install conan
\end{verbatim}
We use the python package manager to install conan. (part of Dockerfile)
\begin{verbatim}
conan profile detect
\end{verbatim}
We let conan detect compilers and tools for which it can use. (part of Dockerfile)
\begin{verbatim}
conan install
\end{verbatim}
While in the project working directory, download packages, compile them, generate CMake environment. (was run using vs code)
After that we can use CMake as we would normally.
\end{frame}
\begin{frame}{CMake}
\end{frame}
\begin{frame}{CMake: The basics}
\lstinputlisting[language=make]{../src/hello_world/CMakeLists.txt}
\end{frame}
\begin{frame}{CMake: The basics}
\lstinputlisting[language=make,firstline=1,lastline=2]{../src/hello_world/CMakeLists.txt}
\begin{itemize}
\item Sets the minimum required version of cmake for a project.
\item Sets behavior of CMake as it was at a certain version (set policies)\footnote{\url{https://cmake.org/cmake/help/latest/command/cmake_minimum_required.html}}
\end{itemize}
\vspace{5mm}
A mechanism to provide backwards compatibility while allowing future versions to change behavior of commands.
\end{frame}
\begin{frame}[fragile]{CMake: The basics}
\lstinputlisting[language=make,firstline=2, firstnumber=2, lastline=9]{../src/hello_world/CMakeLists.txt}
\begin{itemize}
\item ``project''
Used to set the PROJECT\_NAME variable.
\item ``add\_executable''
Adding executable by specifying the executable name and the source files.
\end{itemize}
\end{frame}
\begin{frame}[fragile]{CMake: The basics}
\lstinputlisting[language=make,firstline=9, firstnumber=9, lastline=18]{../src/hello_world/CMakeLists.txt}
\begin{itemize}
\item ``set\_property''
Enables us to set properties like language standard to use (C++, CUDA, ...)
\item ``target\_link\_libraries''
Specify library dependencies.
\end{itemize}
\end{frame}
\begin{frame}{CMake: The basics, library}
\lstinputlisting[language=make]{../src/log/CMakeLists.txt}
\end{frame}
\begin{frame}{CMake: The basics, library}
\lstinputlisting[language=make,firstline=4,firstnumber=4,lastline=9]{../src/log/CMakeLists.txt}
Similar to ``add\_executable'' we can create a library, specifying the name of the library and the sources that form it.
\end{frame}
\begin{frame}{CMake: The basics, library}
\lstinputlisting[language=make,firstline=15,firstnumber=15,lastline=18]{../src/log/CMakeLists.txt}
Add include directories, look for header files.
Applicable for building the given target
Applicable for building targets depending on this target
\end{frame}
\begin{frame}{Using 3rd-party packages}
\end{frame}
\begin{frame}{Conan: 3rd-party packages}
\lstinputlisting[language=make]{example_conan.txt}
\begin{itemize}
\item ``[requires]''
Declare the libraries that we want to use in our project.
\item ``[generators]''
Tell Conan to generate files for given build system
\end{itemize}
\end{frame}
\begin{frame}{Conan: 3rd-party packages the python way}
\lstinputlisting[language=make]{example_conan_py.txt}
\end{frame}
\begin{frame}{CMake: Using 3rd-party packages}
\lstinputlisting[language=make]{../src/hello_world_boost/CMakeLists.txt}
\end{frame}
\begin{frame}{CMake, 3rd-party packages}
\lstinputlisting[language=make, firstline=2,firstnumber=2,lastline=4]{../src/hello_world_boost/CMakeLists.txt}
Find a package (usually provided by something external to the project), and load its package-specific details.\footnote{\url{https://cmake.org/cmake/help/latest/command/find_package.html}}
\lstinputlisting[language=make, firstline=17,firstnumber=17,lastline=20]{../src/hello_world_boost/CMakeLists.txt}
Using exposed target names we can use those packages.
\begin{itemize}
\item Affects libraries linked to target.
\item Affects include paths to look for headers.
\end{itemize}
\end{frame}
\begin{frame}{VERIFIED UNTIL HERE}
\end{frame}
\begin{frame}{CWorkshop time!}
\begin{itemize}
\item Open VS Code, build example applications
\item Create your own application, using 3rd-party library of your choice (fmt,gtest)
\end{itemize}
\end{frame}
\begin{frame}[fragile]{CMake: Integrate testing}
\begin{verbatim}
enable_testing()
\end{verbatim}
Use enable\_testing() at the top level CMakeLists.txt to enable cmake to generate things needed to support testing\footnote{\url{https://cmake.org/cmake/help/latest/command/enable_testing.html}}.
\begin{verbatim}
add_test()
\end{verbatim}
Use add\_test() inside your CMakeLists.txt to register the test\footnote{\url{https://cmake.org/cmake/help/latest/command/add_test.html}}.
\begin{verbatim}
ctest
\end{verbatim}
Commandline application to run all registered tests\footnote{\url{https://cmake.org/cmake/help/latest/manual/ctest.1.html}}.
\end{frame}
\begin{frame}{CWorkshop time!}
\begin{itemize}
\item Open VS Code, build example applications
\item Create your own unittest, add testing in CMake
\end{itemize}
\end{frame}
\begin{frame}{C++23 Modules is supported!}
For C++23 we have a feature called ``modules''. This feature requires support not only by compilers, but also by build tools. The latest version of CMake in combination with Ninja does this.
Included in provided material is an example
FURTHER DEV NEEDED
\end{frame}
\begin{frame}{END OF THE PRESENTATION}
\end{frame}
\begin{frame}{CMake, where to find more info}
\begin{itemize}
\item https://cmake.org/
\item https://cmake.org/cmake/help/latest/
\end{itemize}
\end{frame}
\begin{frame}{Conan, in the background}
\end{frame}
\begin{frame}{Workshop}
\end{frame}
\begin{frame}{Uniform reproducable environment}
There are a lot of factors that influence your C++ development environment
\begin{itemize}
\item The OS being used.
\item Environment variables.
\item Installed compilers.
\item Libraries and tools.
\end{itemize}
Setting up a developer environment (1st day at new assignment) can involve a lot of steps!
Dev containes is a small facility built on top of docker that allows all of the above factors to be contained and easilly created
\end{frame}
\begin{frame}{CMake, common variables}
\begin{tabular}{| c | c |}
\hline
PROJECT\_NAME & Name of the project given to the project command. \\
\hline
CMAKE\_CURRENT\_SOURCE\_DIR & Path to source directory currently being processed. \\
\hline
CMAKE\_CURRENT\_BINARY\_DIR & Path to binary directory currently being processed. \\
\hline
CMAKE\_COMMAND & Path to the cmake executable. \\
\hline
\end{tabular}
Variables can be used in CMake to navigate directories
\footnote{\url{https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html}}
\end{frame}
\begin{frame}{CMake, creating modules}
\begin{columns}
\begin{column}{0.45\textwidth}
\lstinputlisting[language=make]{../src/math/CMakeLists.txt}
\end{column}
\begin{column}{0.45\textwidth}
Requires recent CMake and Ninja packages.
\end{column}
\end{columns}
\end{frame}
\end{document}

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,3 +1,6 @@
cmake_minimum_required(VERSION 3.20)
add_subdirectory(example_boost)
add_subdirectory(hello_world)
add_subdirectory(hello_world_awesome)
add_subdirectory(awesome_log)

View File

@ -0,0 +1,48 @@
cmake_minimum_required(VERSION 3.20)
project(awesome_log)
add_library(
${PROJECT_NAME}
./src/console_writer.cpp
./src/log.cpp
./src/null_writer.cpp
./src/scoped_log.cpp
)
set_property(
TARGET ${PROJECT_NAME}
PROPERTY CXX_STANDARD 20
)
target_include_directories(
${PROJECT_NAME}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
)
find_package(GTest REQUIRED)
project(awesome_log_test)
add_executable(
${PROJECT_NAME}
./test/test_scoped_log.cpp
)
set_property(
TARGET ${PROJECT_NAME}
PROPERTY CXX_STANDARD 20
)
target_link_libraries(
${PROJECT_NAME}
PUBLIC
awesome_log
GTest::gtest_main
GTest::gmock
)
add_test(
NAME ${PROJECT_NAME}
COMMAND ${PROJECT_NAME}
)

View File

@ -0,0 +1,14 @@
#pragma once
#include "writer_interface.hpp"
namespace awesome_log
{
class console_writer : public writer_interface
{
public:
~console_writer() override;
void log(std::string_view msg) override;
};
} // namespace awesome_log

View File

@ -0,0 +1,12 @@
#pragma once
#include <memory>
namespace awesome_log
{
class writer_interface;
writer_interface& get_writer();
void set_writer(const std::shared_ptr<writer_interface>& writer);
} // namespace awesome_log

View File

@ -0,0 +1,14 @@
#pragma once
#include "writer_interface.hpp"
namespace awesome_log
{
class null_writer : public writer_interface
{
public:
~null_writer() override;
void log(std::string_view msg) override;
};
} // namespace awesome_log

View File

@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <string_view>
namespace awesome_log
{
class scoped_log
{
public:
scoped_log(std::string_view msg);
~scoped_log();
scoped_log() = delete;
// No copying or moving allowed.
scoped_log(const scoped_log&) = delete;
scoped_log(scoped_log&&) = delete;
scoped_log& operator=(const scoped_log&) = delete;
scoped_log& operator=(scoped_log&&) = delete;
private:
std::string m_msg;
int m_uncaught_exceptions;
};
} // namespace awesome_log

View File

@ -0,0 +1,15 @@
#pragma once
#include <string_view>
namespace awesome_log
{
class writer_interface
{
public:
virtual ~writer_interface() = default;
virtual void log(std::string_view msg) = 0;
};
} // namespace awesome_log

View File

@ -0,0 +1,12 @@
#include <awesome_log/console_writer.hpp>
#include <iostream>
using namespace awesome_log;
console_writer::~console_writer() = default;
void console_writer::log(std::string_view msg)
{
std::cout << msg << std::endl;
}

View File

@ -0,0 +1,23 @@
#include <awesome_log/log.hpp>
#include <awesome_log/null_writer.hpp>
using awesome_log::writer_interface;
namespace
{
std::shared_ptr<writer_interface> g_instance;
}
writer_interface& awesome_log::get_writer()
{
if (!g_instance)
{
g_instance = std::make_shared<null_writer>();
}
return *g_instance;
}
void awesome_log::set_writer(const std::shared_ptr<writer_interface>& writer)
{
g_instance = writer;
}

View File

@ -0,0 +1,10 @@
#include <awesome_log/null_writer.hpp>
using namespace awesome_log;
null_writer::~null_writer() = default;
void null_writer::log(std::string_view)
{
}

View File

@ -0,0 +1,32 @@
#include <awesome_log/log.hpp>
#include <awesome_log/scoped_log.hpp>
#include <awesome_log/writer_interface.hpp>
#include <exception>
#include <sstream>
using namespace awesome_log;
scoped_log::scoped_log(std::string_view msg)
: m_msg(msg)
, m_uncaught_exceptions(std::uncaught_exceptions())
{
std::stringstream ss;
ss << "> " << m_msg;
get_writer().log(ss.str());
}
scoped_log::~scoped_log()
{
bool is_stack_unwinding = (m_uncaught_exceptions != std::uncaught_exceptions());
std::stringstream ss;
ss << "< " << m_msg;
if (is_stack_unwinding)
{
ss << " (stack unwinding)";
}
get_writer().log(ss.str());
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <awesome_log/writer_interface.hpp>
#include <gmock/gmock.h>
class mock_writer : public awesome_log::writer_interface
{
public:
~mock_writer() override = default;
MOCK_METHOD(void, log, (std::string_view), (override));
};

View File

@ -0,0 +1,47 @@
#include "mock_writer.hpp"
#include <awesome_log/log.hpp>
#include <awesome_log/scoped_log.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <memory>
class test_scoped_log : public ::testing::Test
{
public:
void SetUp() override
{
m_writer = std::make_shared<mock_writer>();
awesome_log::set_writer(m_writer);
}
void TearDown() override { awesome_log::set_writer(std::shared_ptr<awesome_log::writer_interface>()); }
protected:
std::shared_ptr<mock_writer> m_writer;
};
TEST_F(test_scoped_log, log_normal_flow)
{
EXPECT_CALL(*m_writer, log("> log"));
EXPECT_CALL(*m_writer, log("< log"));
{
awesome_log::scoped_log a("log");
}
}
TEST_F(test_scoped_log, log_stack_unwind)
{
EXPECT_CALL(*m_writer, log("> log"));
EXPECT_CALL(*m_writer, log("< log (stack unwinding)"));
try
{
awesome_log::scoped_log a("log");
throw std::runtime_error("");
}
catch (const std::exception& e)
{
// Not doing anything
}
}

View File

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.20)
find_package(Boost 1.88.0 REQUIRED COMPONENTS serialization)
project(hello_world_boost)
project(example_boost)
add_executable(
${PROJECT_NAME}

View File

@ -1,12 +1,12 @@
#include <sstream>
#include <iostream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <iostream>
#include <sstream>
struct position
{
double x;
double y;
int x;
int y;
template<class Archive>
void serialize(Archive& ar, const unsigned int /*version*/)
@ -15,17 +15,13 @@ struct position
ar & y;
}
bool operator==(const position& rhs) const
{
return x == rhs.x
&& y == rhs.y;
}
bool operator==(const position& rhs) const { return x == rhs.x && y == rhs.y; }
};
int main(int argc, char* argv[])
{
position myposition{.x=123.0, .y=456.0};
position myposition{.x = 123, .y = 456};
std::stringstream ss;
{
@ -44,5 +40,4 @@ int main(int argc, char* argv[])
{
std::cout << "positions are the same" << std::endl;
}
}

View File

@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.20)
project(hello_world)
add_executable(
${PROJECT_NAME}
src/main.cpp
)
set_property(
TARGET ${PROJECT_NAME}
PROPERTY CXX_STANDARD 20
)

View File

@ -0,0 +1,7 @@
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "Hello world" << std::endl;
}

View File

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.20)
project(hello_world_awesome)
add_executable(
${PROJECT_NAME}
src/main.cpp
)
target_link_libraries(
${PROJECT_NAME}
awesome_log
)
set_property(
TARGET ${PROJECT_NAME}
PROPERTY CXX_STANDARD 20
)

View File

@ -0,0 +1,28 @@
#include <awesome_log/console_writer.hpp>
#include <awesome_log/log.hpp>
#include <awesome_log/scoped_log.hpp>
#include <iostream>
#include <memory>
using awesome_log::scoped_log;
int main(int argc, char* argv[])
{
awesome_log::set_writer(std::make_shared<awesome_log::console_writer>());
{
scoped_log scoped("main.cpp, printing text");
std::cout << "Hello world" << std::endl;
}
try
{
scoped_log scoped("main.cpp, playing with exceptions");
throw std::runtime_error("Let's play!");
// ... unreachable code goes here.
}
catch (const std::exception& e)
{
std::cout << "Caught an exception, as expected in this example" << std::endl;
}
}