C++ dev container static code analysis, debugging, formatting and unit testing
At this point I went in guns blazing and wrote pretty much every integration by hand. This is something to address down the road. There are extensions that are meant to make life easier and I did not make great use of them.
Among the goals were such diverse elements as static code validation, debugging, formatting, unit testing and a fanatical devotion to the pope - wait - that is a different scatch.
static code validation
cppcheck
I tested a few known cppcheck errors things were straight forward. cppcheck was already installed.
cppcheck --relative-paths --template=gcc --language=c++ --enable=all --suppress=missingIncludeSystem --std=c++20 --force -ibuild .
The problemMatcher
was simply $gcc
with a slight twist of setting the owner so messages are reset properly.
"problemMatcher": {
"base": "$gcc",
"owner": "cppcheck"
}
clang-tidy
clang-tidy worked pretty much the same way in terms of setting up the task and the problemMatcher
.
This is the limited use command I selected to run:
clang-tidy -checks=*,-llvmlibc-restrict-system-libc-headers,-fuchsia-trailing-return,-llvmlibc-* -p=build src/main/main.cpp -header-filter=.*, -fix
- note that I am targetting the main file. I need to test this approach on a larger project. For now some messages are displayed and that was the low bar I was shooting for here.
Installing clang was a medium task and along with some other tools I did end up extending the docker file.
debugging
debugging is remarkebly easy using the C/C++ extension. It is basically the default launch process and requires no modification. The only thing to watch out for is that the project is built with the debug flag in mind.
"cmake .. -G \"Unix Makefiles\" -DCMAKE_BUILD_TYPE=Debug"
I ended up setting three launch configurations for my demo project. One for main, and one for each testing framework (boost test / gtest).
formatting
Using clang format requires processing one file at a time since applying fixes on multiple files at once can eaily backfire. So I ended up iterating through the whole project and processing all .cpp and .h files one at a time. I am pretty sure this will kill my compile time in larger projects and is something I will definitely revisit.
Here is the task command I ended up using:
{
"label": "clang-format-llvm",
"group": "build",
"type": "shell",
"command": "find src \\( -name '*.cpp' -o -name '*.h' \\) -exec clang-format -i -style=LLVM '{}' \\;",
},
unit testing
Some tings were easy some required changes in the setup.
Adding the dependency is as easy as adding two lines to the conanfile.
boost/1.76.0
gtest/1.10.0
compileing two stand alone executeables for these tests was also very easy as long as they did not import anything external.
Importing parts of the project required a cleaner cmake structure. I realize that there are some more things I need to do here and so I wont go into my current setup too much. Basically the root project contains four individual projects. Main and the two test projects are executeables and then there is one library core project that is imported by all three others. This is how I used to set up my projects in the past and this time around I found a lot of online references suggesting the same thing.
The problemMatcher
implementation is not very satisfying and I thing some kind of test explorer plugin would go a long way to help here.
I ran with these hotfix problem matchers that seem to do the trick for now.
{
"label": "boost_test",
"type": "shell",
"command": "./build/bin/boost_test",
"problemMatcher": {
"owner": "boost_test",
"fileLocation": [
"relative",
"/"
],
"pattern": {
"regexp": "^([^(]+?)\\((\\d+)\\):[^:]+:[^:]+:(.+)$",
"file": 1,
"line": 2,
"message": 3
}
}
},
{
"label": "google_test",
"type": "shell",
"command": "./build/bin/google_test",
"problemMatcher": {
"owner": "google_test",
"fileLocation": [
"relative",
"/"
],
"pattern": {
"regexp": "^(.workspaces.+?):(\\d+):\\s+([^[]+)$",
"file": 1,
"line": 2,
"message": 3
}
}
},
changed dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/base:ubuntu
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN sudo apt-get -y install software-properties-common
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CC86BB64
RUN add-apt-repository ppa:ubuntu-toolchain-r/test
RUN add-apt-repository -y ppa:rmescandon/yq
RUN apt-get update
RUN apt-get -y install build-essential cmake cppcheck valgrind clang lldb llvm gdb python3-pip gcc-11 g++-11 yq jq clang-format-11 clang-tidy-11
RUN apt-get autoremove -y
RUN apt-get clean -y
RUN rm -rf /var/lib/apt/lists/*
RUN update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-11 10
RUN update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-11 10
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 10
RUN update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 10
RUN pip3 install conan
ENV CONAN_V2_MODE=1
USER vscode
RUN conan config init
RUN yq w -i ~/.conan/settings.yml 'compiler.gcc.version[+]' '11'
what is next
I am not sure but I think I will look into style guides for project layout and common plugins next. I feel like there is some benefit to be had. I will also look into the standard layout produced by CLion and see if that CMake Project could be made mostly compatible with what I am running here.
a list of things I read or watched
cppcheck homepage, cppcheck manual, clang format blog, clang format styles 1, clang format, clang tidy, clang tidy fuchsia, clang tidy namespace, modern cmake, vscode debug, vscode launch, vscode tasks, clang format all, cmake speed, core guidelines, clang llvm sytem lib header restriction, clang install, clang example, cmake linking, vscode problem matchers, cmake project setup 1, cmake project setup 2, cppcheck in cmake, cppcheck problem matcher, clang llvm sytem lib header restriction fix, cmake project setup 3, cmake debug / release, clang format styles 2, clang tidy example, style guides, Jason Turner clang-tidy