From 2ea60f4e89f58295f00fe4e7d2189b86ef3374f1 Mon Sep 17 00:00:00 2001 From: Balogun Feranmi Date: Wed, 21 Oct 2026 23:28:55 +0100 Subject: [PATCH] Add dedicated C++ devcontainer Provide a separately selectable C++ development container alongside the default Python container (issue #1272). The container is based on Ubuntu 26.04 and mirrors the Linux CI toolchain after #1631: gcc-16/g++-16, clang-22, clang-tidy-18, CMake, Ninja, gdb and the C++ dependencies from the build guide (boost, eigen3, nlohmann-json, msgpack-cxx, doctest). It reuses the gcc-debug/clang-debug CMake presets and the existing VS Code debug configurations. cpp_build is kept in a per-checkout Docker volume. CMake caches contain absolute paths, so sharing that directory with the host made presets fail with a CMakeCache.txt directory mismatch (reported for clang-debug). The clang-format extension uses the version pinned in uv.lock, which is the same version CI runs through pre-commit, and the documentation now covers formatting, REUSE, pre-commit and compiler verification. Signed-off-by: Balogun Feranmi --- .devcontainer/cpp/Dockerfile | 39 +++++++ .devcontainer/cpp/devcontainer.json | 60 ++++++++++ .devcontainer/cpp/devcontainer.json.license | 3 + .gitattributes | 1 + docs/advanced_documentation/build-guide.md | 2 + .../devcontainer-setup.md | 108 +++++++++++++++++- 6 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 .devcontainer/cpp/Dockerfile create mode 100644 .devcontainer/cpp/devcontainer.json create mode 100644 .devcontainer/cpp/devcontainer.json.license diff --git a/.devcontainer/cpp/Dockerfile b/.devcontainer/cpp/Dockerfile new file mode 100644 index 0000000000..d87e86b19d --- /dev/null +++ b/.devcontainer/cpp/Dockerfile @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: Contributors to the Power Grid Model project +# +# SPDX-License-Identifier: MPL-2.0 + +# Dedicated C++ development container for power-grid-model. +# Matches the Linux CI toolchain (Ubuntu 26.04, GCC/G++ 16, Clang 22) and the +# C++ dependencies listed in docs/advanced_documentation/build-guide.md. +# clang-tidy remains on version 18 on purpose: CI does the same (see PR #1631). +FROM mcr.microsoft.com/devcontainers/base:resolute + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + gcc-16 g++-16 \ + clang-22 lld-22 libclang-rt-22-dev clang-tidy-18 \ + cmake ninja-build gdb \ + libboost-dev libeigen3-dev nlohmann-json3-dev libmsgpack-cxx-dev doctest-dev \ + gcovr lcov pkg-config git curl ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# The CMake presets resolve `gcc`/`g++`/`clang`/`clang++` via PATH +# (same approach as CI, which symlinks the versioned binaries). +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-16 100 \ + && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-16 100 \ + && update-alternatives --install /usr/bin/clang clang /usr/bin/clang-22 100 \ + && update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-22 100 \ + && update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-18 100 + +# uv provides the Python runtime for the repository's uv-managed tooling. +# pre-commit, REUSE and clang-format are installed from uv.lock by the +# postCreateCommand, so they stay in sync with CI instead of being duplicated +# as global tools here. +ENV UV_INSTALL_DIR=/usr/local/bin +RUN curl -LsSf https://astral.sh/uv/install.sh | sh + +ENV CC=gcc CXX=g++ +ENV DEBIAN_FRONTEND=dialog diff --git a/.devcontainer/cpp/devcontainer.json b/.devcontainer/cpp/devcontainer.json new file mode 100644 index 0000000000..0a0b38a2a7 --- /dev/null +++ b/.devcontainer/cpp/devcontainer.json @@ -0,0 +1,60 @@ +{ + // Dedicated C++ development container (issue #1272). + // The default Python container in .devcontainer/devcontainer.json is unchanged; + // VS Code offers both configurations when reopening in a container. + "name": "power-grid-model (C++)", + "build": { + "dockerfile": "Dockerfile", + "context": "." + }, + + "workspaceFolder": "/power-grid-model", + "workspaceMount": "source=${localWorkspaceFolder},target=/power-grid-model,type=bind", + + // Keep CMake build trees inside the container. Sharing cpp_build/ with the + // host leaves absolute paths from the host in CMakeCache.txt, which makes + // presets such as clang-debug fail with "CMakeCache.txt directory ... is + // different than the directory ... where CMakeCache.txt was created". + // ${devcontainerId} keeps the volume per checkout, so parallel worktrees do + // not share a cache either. + "mounts": ["type=volume,source=power-grid-model-cpp-build-${devcontainerId},target=/power-grid-model/cpp_build"], + + "remoteUser": "vscode", + + "remoteEnv": { + "CC": "gcc", + "CXX": "g++" + }, + + "postCreateCommand": "sudo chown -R vscode:vscode cpp_build && uv sync --frozen --group lint --group test && uv run --frozen pre-commit install && gcc --version && g++ --version && clang --version && clang-tidy --version && uv run --frozen clang-format --version && cmake --version && ninja --version && cmake --list-presets", + + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "matepek.vscode-catch2-test-adapter", + "jeff-hykin.better-cpp-syntax", + "tamasfe.even-better-toml", + "xaver.clang-format" + ], + "settings": { + // NOTE: there is no supported setting that pre-selects a CMake configure + // preset. After the container is created, run `CMake: Select Configure + // Preset` in VS Code and choose `gcc-debug` (`clang-debug` works too). + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", + // Use the clang-format version pinned in uv.lock (the same one CI uses + // through `uv run --frozen clang-format`) instead of a system package. + "clang-format.executable": "/power-grid-model/.venv/bin/clang-format", + "clang-format.style": "file", + "[c]": { + "editor.defaultFormatter": "xaver.clang-format" + }, + "[cpp]": { + "editor.defaultFormatter": "xaver.clang-format" + }, + "editor.formatOnSave": true + } + } + } +} diff --git a/.devcontainer/cpp/devcontainer.json.license b/.devcontainer/cpp/devcontainer.json.license new file mode 100644 index 0000000000..7601059167 --- /dev/null +++ b/.devcontainer/cpp/devcontainer.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 diff --git a/.gitattributes b/.gitattributes index e246d4d0a9..ea364ce7a5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,3 +16,4 @@ *.h text eol=lf *.cpp text eol=lf *.hpp text eol=lf +Dockerfile text eol=lf diff --git a/docs/advanced_documentation/build-guide.md b/docs/advanced_documentation/build-guide.md index 4e09c916ff..2c77200ee1 100644 --- a/docs/advanced_documentation/build-guide.md +++ b/docs/advanced_documentation/build-guide.md @@ -27,6 +27,8 @@ In this repository there are three builds: If you just want to develop the _Python_ side of `power-grid-model`, you can use the [provided development container](./devcontainer-setup.md). It automatically sets up the entire environment so you can start developing immediately. +For _C++_ development there is a separate [C++ development container](./devcontainer-setup.md#c-development-container) +with the required toolchain, dependencies and IDE debug support. ``` ```{contents} diff --git a/docs/advanced_documentation/devcontainer-setup.md b/docs/advanced_documentation/devcontainer-setup.md index 5a49e3dbba..318723566f 100644 --- a/docs/advanced_documentation/devcontainer-setup.md +++ b/docs/advanced_documentation/devcontainer-setup.md @@ -6,8 +6,13 @@ SPDX-License-Identifier: MPL-2.0 # Development Container Setup Guide -This document explains how you can use our development container (dev container) setup to contribute to the _Python_ -side of `power-grid-model`. +This document explains how you can use our development container (dev container) setups to contribute to +`power-grid-model`. +There are two separately selectable configurations: + +* The default container (`.devcontainer/devcontainer.json`) for the _Python_ side of `power-grid-model`. +* A dedicated container (`.devcontainer/cpp/devcontainer.json`) for _C++_ development (see + [C++ Development Container](#c-development-container)). ```{note} A [development container](https://containers.dev/) is a pre-configured @@ -124,3 +129,102 @@ Note that the _Professional_ edition is required to have native development cont * Open the [welcome screen](https://www.jetbrains.com/help/pycharm/welcome-screen.html) of PyCharm. * Click `Remote Development`, choose `Dev Containers` and click the `Create Dev Containers` button. * Select your local path to the cloned repository and choose your container engine. + +## C++ Development Container + +The dedicated C++ container (`.devcontainer/cpp/devcontainer.json`) is based on Ubuntu 26.04 and mirrors the CI +toolchain: `gcc-16`/`g++-16`, `clang-22`, `clang-tidy-18` (kept at 18 in CI as well), `CMake` (>= 3.23), `Ninja`, +`gdb`, and the C++ dependencies from the [build guide](./build-guide.md) (`boost`, `eigen3`, `nlohmann-json`, +`msgpack-cxx`, `doctest`). +It also installs `uv`, which provides the `clang-format`, `pre-commit` and `reuse` versions pinned in `uv.lock`, so +the formatting and licence checks use the same versions as CI. +The Python container remains the default and is unchanged; combined Python/C++ and docs-building environments are +out of scope. + +### Selecting the C++ container in VS Code + +* Install the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + extension and open the cloned `power-grid-model` folder. +* Click the green `Remote Indicator` in the bottom-left corner or open the Command Palette + (`Ctrl+Shift+P` / `Cmd+Shift+P`), then select `Dev Containers: Reopen in Container`. +* When prompted, pick the `power-grid-model (C++)` configuration (`.devcontainer/cpp/devcontainer.json`). + +### Configure and build (CMake preset) + +No configure preset is pre-selected: `cmake.configurePreset` is not a setting the CMake Tools extension +provides, so the container cannot activate `gcc-debug` for you on first open. In VS Code, open the Command +Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`), run `CMake: Select Configure Preset`, and choose `gcc-debug` +(`clang-debug` remains available as the Clang alternative). +From a VS Code terminal inside the container, or any terminal with the environment set up: + +```shell +cmake --preset gcc-debug +cmake --build --preset gcc-debug +``` + +Alternatively, use the CMake Tools extension preset picker, or the shortcut `./build.sh -p gcc-debug`. + +Each preset records absolute paths in its CMake cache. The container therefore keeps `cpp_build/` in a Docker +volume instead of sharing it with the host, so a build tree created on the host can no longer collide with a build +tree created in the container (this was the cause of the `clang-debug` `CMakeCache.txt` path error). The volume +name includes the dev container ID, so separate checkouts and worktrees get their own build tree. +To start from a clean build tree, remove the volume and rebuild: + +```shell +docker volume ls --filter name=power-grid-model-cpp-build +docker volume rm # or: podman volume rm +``` + +### Verify the selected compiler + +Both presets set their compiler explicitly, so switching presets is enough: + +```shell +cmake --preset gcc-debug +grep -E '^CMAKE_(C|CXX)_COMPILER:' cpp_build/gcc-debug/CMakeCache.txt # /usr/bin/gcc, /usr/bin/g++ +cmake --preset clang-debug +grep -E '^CMAKE_(C|CXX)_COMPILER:' cpp_build/clang-debug/CMakeCache.txt # /usr/bin/clang, /usr/bin/clang++ +``` + +### Run the C++ tests + +```shell +ctest --preset gcc-debug --output-on-failure +``` + +The `doctest` suites are also discoverable in the VS Code Test Explorer via the Catch2 test adapter. + +### Format, check licences and run pre-commit + +The container installs the `Clang-Format` VS Code extension (`xaver.clang-format`) and points it at +`/power-grid-model/.venv/bin/clang-format`, the version pinned in `uv.lock` (the same one CI uses), with +`-style=file`, so saving a C/C++ file applies the repository `.clang-format` configuration. `pre-commit` is +installed as a Git hook by the container setup. All hooks and tools run through the project environment, which the +`postCreateCommand` created with `uv sync --frozen --group lint --group test`: + +```shell +uv run --frozen pre-commit run --all-files # all hooks +uv run --frozen pre-commit run clang-format --files path/to/file.cpp +uv run --frozen pre-commit run reuse --all-files # REUSE licence compliance +uv run --frozen clang-format -style=file -i path/to/file.cpp +uv run --frozen gersemi -i --warnings-as-errors cmake/ # CMake formatting +``` + +Run `uv run --frozen pre-commit run --all-files` to check a single hook across the repository. The +`reuse`, `ruff`, `mypy`, `clang-format`, `gersemi` and `pytest` hook IDs are defined in `.pre-commit-config.yaml`. +If the environment is missing (for example after a rebuild without the setup command), recreate it with +`uv sync --frozen --group lint --group test`. + +### Debug a C++ test in the IDE + +* Build with a Debug preset as above so binaries carry symbols. +* Set a breakpoint, then start the preconfigured `Debug validation test [Unix]` launch configuration + (`.vscode/launch.json`, `gdb`-based), or right-click an individual test in the Test Explorer and debug it. +* The launch configuration resolves the test binary via `${command:cmake.buildDirectory}` + (e.g. `cpp_build/gcc-debug/bin/power_grid_model_validation_tests`). + +### Returning to the Python container + +Reopen the folder in the default container (`Dev Containers: Reopen in Container`, then pick the +`power-grid-model` configuration), or `Dev Containers: Reopen Folder Locally` first and then reopen in the +Python container.