en / de
Expertisen
Methoden
Dienstleistungen
Referenzen
Jobs & Karriere
Firma
Technologie-Trends TechCast WebCast TechBlog News Events Academy

VSCode DevContainer im Embedded Software Umfeld

Heutige Embedded Softwareentwicklung beinhaltet Themen wie IoT, Multitouch-fähige Benutzeroberflächen, Updatefähigkeit und natürlich die traditionellen Gebiete, das Steuern und Überwachen von Systemen und ihren Prozessen.

Neben den oben genannten gibt es auch noch weitere Herausforderungen: die Produkte haben einen Lifecycle von bis zu 15 Jahren. Auch die dazugehörige Embedded Software muss in diesem Zeitraum wartbar sein und allenfalls weiterentwickelt werden können. Folglich muss auch die dazugehörige Entwicklungsumgebung über den ganzen Lifecycle zur Verfügung stehen.

Bis heute findet man häufig den Ansatz, die Entwicklungsumgebung in eine Virtuelle Maschine (VM) zu verpacken, da sich natürlich auch die für die Entwicklung benötigte Software und die Betriebssysteme weiterentwickeln. So kann es zum Beispiel sein, dass ein verwendeter Compiler oder eine IDE auf einem moderneren Betriebssystem ein anderes Verhalten zeigt oder gar nicht mehr unterstützt wird. Aber auch das Installieren und Konfigurieren einer Entwicklungsumgebung ist komplex und zeitaufwändig. Es sind Arbeiten, die jedes Teammitglied immer wieder durchführen muss.

In diesem Blogbeitrag möchte ich euch zeigen, diese Problemstellung mit VSCode DevContainer zu lösen. Nachstehend findet ihr ein Tutorial, in dem ich die verwendeten Tools kurz erkläre und die notwendigen Schritte zum Erstellen eines DevContainer zeige.

Die VSCode DevContainer Extension

Was sind VSCode DevContainer eigentlich? VSCode an sich ist sicherlich bestens bekannt. Es handelt sich um einen modernen, leichtgewichtigen Quelltext-Editor. Mit Hilfe von Erweiterungen (folgend als Extensions bezeichnet) erhält man die Möglichkeit VSCode zu einer vollwertigen IDE (Integrated Development Environment) auszubauen.

Die VSCode DevContainter Extension ist eine Erweiterung, die es einem auf einfache Weise ermöglicht, die Entwicklungsumgebung in einem Docker Container zu konfigurieren und auszuführen. Die generelle Architektur ist in der folgenden Abbildung dargestellt:

Architekturansicht der VSCode DevContainer Extension

Quelle: https://code.visualstudio.com/docs/devcontainers/containers

Das hat den Vorteil, dass alle Entwickler in einer vorkonfigurierten und konsistenten virtuellen Umgebung arbeiten können. Das beschleunigt den Onboarding Prozess und vereinfacht die Verwaltung und Wartung der Infrastruktur für Projekte mit unterschiedlichen Konfigurationen.

Verwendete Tools und Hardware

 

Installation und Konfiguration

Nun folgt die Installation der Tools, die Erläuterung der wichtigsten Konfigurationsschritte und natürlich die Inbetriebnahme aus Sicht eines Softwareentwicklers. Das verwendete Beispielprojekt kann man unter https://github.com/knsig/DevContainerEmbeddedTestApp einsehen.

Die Installation erfolgt in zwei Schritten. Im ersten Teil installieren wir die Tools, die auf jedem Host installiert und ausgeführt werden müssen. Sie bilden die Basis unseres Setups. Dabei handelt es sich um Docker, WSL, VSCode, STLink GDB Server und STLink Programmer.

Der zweite Teil der Installation beinhaltet das Definieren und Erzeugen des Docker-Container. In diesem Schritt wählen wir ein Docker-Image als Basis und installieren einige allgemeine Tools, die uns bei der Installation unterstützen, sowie die oben aufgeführten Tools: CMake, ninja-build, die Arm GNU Toochain und Git. Das muss nur einmal erstellt werden und kann anschliessend in einem Git-Repository abgelegt und beliebig geteilt werden.

Installation auf dem Arbeitsplatz

Da es im Internet bereits zahlreiche Anleitungen zur Installation von Docker, WSL, VSCode und VSCode Extensions gibt, erlaube ich mir, auf die offizielle Dokumentation der Hersteller zu verweisen:

Docker Engine: https://docs.docker.com/engine/install/

Installation WSL (für Windows-Benutzer): https://learn.microsoft.com/en-us/windows/wsl/install

Konfiguration WSL (für Windows-Benutzer): https://docs.docker.com/desktop/wsl/

VSCode: https://code.visualstudio.com/download

STLink GDB Server und STLink Programmer

Die beiden Programme verwende ich in meinem Setup, um das Discovery-Board zu programmieren und zu debuggen.

Der STLink GDB Server führen wir auf dem Host aus, damit wir eine Verbindung per USB zum Discovery-Board herstellen können. Weiter ist der Server die Gegenstelle für den GDB-Debugger der im Docker-Container für eine Debug-Session gestartet wird. Damit können wir nun im DevContainer mit GDB eine Debug-Session starten, die auf dem Discovery-Board ausgeführt wird.

Ich habe ein Windows System im Einsatz und die STM CubeIDE installiert. In diesem Fall findet man die beiden Anwendungen im Installationsverzeichnis der CubeIDE unter

c:\STM32CubeIDE_1.7.0\STM32CubeIDE\plugins\com.st.stm32cube.ide.mcu.externaltools.stlink-gdb-server.win32_2.0.0.202105311346\tools\bin

Wenn sich der Pfad in der PATH-Systemvariable befindet, dann startet man die Anwendung in einem Terminal wie folgt:

ST-LINK_gdbserver.exe -p 2000 -e -d -v -cp C:\ST\STM32CubeIDE_1.7.0\STM32CubeIDE\plugins\com.st.stm32cube.ide.mcu.externaltools.cubeprogrammer.win32_2.0.0.202105311346\tools\bin

Es können auch die Tools von Segger verwendet werden. Da Hardwaredebugging zum Alltag beim Entwickeln von Embedded Software gehört, gehe ich davon aus, dass ihr euch zurechtfindet und möchte nicht weiter auf die Installation eingehen.

Erstellen und Konfigurieren des DevContainers

Nun geht es weiter mit dem Einrichten der Entwicklungsumgebung an sich. Zuerst brauchen wir ein C/C++ Beispielprojekt. Ich habe es mir einfach gemacht und mithilfe der CubeIDE ein Beispielprojekt für das Discovery-Board generieren lassen. Anschliessend habe ich die nötigen Sourcefiles und Makefiles aus dem Eclipse-Workspace exportiert, in einen neuen Ordner abgelegt und den mit VSCode geöffnet.

Als nächstes müssen wir einige Verzeichnisse und Dateien erstellen:

Schliesslich sollte die Struktur ähnlich wie in der folgenden Abbildung aussehen:

VSCode Entwicklungsumgebung

Ordnerstruktur Beispielprojekt

Dockerfile

Nun definieren wir das Docker-Image, von dem aus dann der Docker-Container erzeugt wird.

# this points to the lts version
FROM ubuntu:latest

# install basics 
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update --fix-missing && apt-get install -y \
      unzip \
      libncurses5 \
      build-essential \
      wget \
      make \
      git \
      cppcheck \
      && rm -rf /var/lib/apt/lists/*

# add the arm toolchain
RUN wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.10/gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 \
      -O gcc-arm-none-eabi.tar.bz2 \
      && mkdir /opt/gcc-arm-none-eabi-10.3-2021.10 \
      && tar xjfv gcc-arm-none-eabi.tar.bz2 \
      -C /opt/gcc-arm-none-eabi-10.3-2021.10 --strip-components 1 \
      && rm gcc-arm-none-eabi.tar.bz2 \
      && ln -s /opt/gcc-arm-none-eabi-10.3-2021.10/bin/* /usr/local/bin

# add cmake
ARG CMAKE_VERSION=3.27.7
RUN wget https://github.com/Kitware/CMake/releases/download/v3.27.7/cmake-3.27.7-linux-x86_64.sh \
      -q -O /tmp/cmake-install.sh \
      && chmod u+x /tmp/cmake-install.sh \
      && mkdir /opt/cmake-3.27.7 \
      && ./tmp/cmake-install.sh --skip-license --prefix=/opt/cmake-3.27.7 \
      && rm /tmp/cmake-install.sh \
      && ln -s /opt/cmake-3.27.7/bin/* /usr/local/bin

# add ninja-build
RUN wget https://github.com/ninja-build/ninja/releases/download/v1.11.1/ninja-linux.zip \
      -O /tmp/ninja-build.zip \
      &&  mkdir /opt/ninja-build-1.11.1 \
      && unzip /tmp/ninja-build.zip -d /opt/ninja-build-1.11.1 \
      && rm /tmp/ninja-build.zip \
      && ln -s /opt/ninja-build-1.11.1/* /usr/local/bin

# add a vscode user
RUN useradd -m vscode && chsh -s /bin/bash vscode

Basierend auf einem aktuellen Ubuntu Image installieren wir zuerst einige Basisanwendungen wie unzip (Entpacken von komprimierten Daten), wget (Download von Inhalten aus dem Internet) und Git. Anschliessend wird die Arm GNU Toolchain heruntergeladen, entpackt, im Verzeichnis «/opt» abgelegt und Softlinks im Verzeichnis «/usr/local/bin» angelegt, um die Tools systemweit verfügbar zu machen. Die gleichen Schritte werden für «CMake» und «ninja-build» ausgeführt.

Zum Schluss wird der Benutzer «vscode» angelegt. Das ist wichtig für Linux-Benutzer, damit im Docker-Container mit der gleichen UID/GID gearbeitet werden kann, wie auf dem Host. Ist das nicht der Fall, kann der User auf dem Host möglicherweise die im Container erzeugten Files nicht löschen, weil der Container ohne Angabe eines Users mit Root-Rechten ausgeführt wird.

devcontainer.json

Fahren wir fort mit dem Inhalt von der Datei «devcontainer.json». Wie schon erwähnt beinhaltet diese Datei die Konfiguration für die DevContainer Extension.

{
  "name": "EmbSwDevContainer",
  "build": {
    "dockerfile": "Dockerfile"
  },

  "runArgs": [
    "--hostname=devcontainer",
    "--network=host"
  ],

  // Set *default* container specific settings.json values on container create.
  // "settings": {},

  // Add the IDs of extensions you want installed when the container is created.
  "customizations": {
    "vscode": {
      "extensions": [
        "mhutchie.git-graph",
    "ms-vscode.cpptools-extension-pack",
    "spmeesseman.vscode-taskexplorer",
    "mcu-debug.debug-tracker-vscode",
    "marus25.cortex-debug",
    "ms-vscode.cmake-tools",
    "twxs.cmake"
      ]
    }
  },

  // Use 'forwardPorts' to make a list of ports inside the container available locally.
  "forwardPorts": [
    61234
  ],

  // Use 'postCreateCommand' to run commands after the container is created.
  // "postCreateCommand": "uname -a",

  // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
  "remoteUser": "vscode",
  "containerUser": "vscode",
  "updateRemoteUserUID": true
}

Zu erwähnen ist, wenn unter «dockerfile» nur der Name angegeben wird, sucht die Extension im Verzeichnis «.devcontainer» danach. Es kann jedoch auch ein relativer Pfad angegeben werden, an dem das Dockerfile liegt.

Die Extensions, die im Container installiert und ausgeführt werden sollen, definieren wir unter «customizations». Wie ihr sicher bemerkt habt, habe ich in der Zwischenzeit noch ein paar mehr installiert, als nur die oben erwähnten.

Das Port-Forwarding benutzen wir für die Verbindung vom GDB-Debugger auf den STLink GDB Server.

launch.json

Die Einstellungen für die «cortex-debug» Extension, die uns beim Debuggen auf dem Discovery-Board unterstützt, werden in «launch.json» vorgenommen. Wichtige Einstellungen sind die Felder «type», «device», «executable», «loadFiles», «serverType» und «gdbTarget».

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Inside Docker - EmbeddedApplication",
      "type": "cortex-debug",
      "cwd": "${workspaceFolder}",
      "request": "launch",
      "device": "STM32F303VCT6",
      "svdFile": "${workspaceFolder}/STM32F303.svd",
      "executable": "${workspaceFolder}/build/EmbeddedApplication.elf",
      "loadFiles": [
        "${workspaceFolder}/build/EmbeddedApplication.elf"
      ],
      "servertype": "external",
      "gdbTarget": "10.35.0.31:2000",
      "postLaunchCommands":[
        "monitor reset"
      ],
      "runToEntryPoint": "main",
      "showDevDebugOutput": "raw", 
    },
  ]
}

settings.json

Zuletzt müssen wir den Pfad auf den GDB-Debugger für die «cortex-debug» Extension setzten, damit diese den Debugger auch findet. In meinem Fall benutze ich auch eine CMake Extension von Microsoft. Für diese habe ich auch zwei Einstellungen vorgenommen.

{
  "cmake.generator": "Ninja",
  "cmake.configureOnOpen": true,
  "cortex-debug.gdbPath": "/opt/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gdb",
  "cortex-debug.registerUseNaturalFormat": false,
  "cortex-debug.variableUseNaturalFormat": true,
}

 

Wenn ihr alle Dateien erstellt und eure Umgebung eingerichtet habt, vergesst nicht, alles in einem Git-Repository abzulegen und mit euren Arbeitskollegen zu teilen.

Arbeiten mit der DevContainer Entwicklungsumgebung

Geschafft! Wir haben alle notwendigen Installationen und Konfigurationen vorgenommen, um endlich unserer wirklichen Leidenschaft nachzugehen – dem Entwickeln von Embedded Software.

Ein neuer Projektmitarbeiter muss nun die Tools aus dem ersten Schritt bei sich installieren. Anschliessen kann er das Projekt-Repository klonen mit den Dateien und Konfigurationen aus dem zweiten Teil. Und nun ist er bereits bereit, den Container zu starten, um mit der Arbeit am Projekt zu beginnen.

Um den Container mit DevContainer zu starten, könnt ihr am einfachsten die Tastenkombination «CTRL + SHIFT + P» verwenden. Anschliessend könnt ihr oben in der Eingabe «DevContainer: Rebuild and Reopen in Container» eingeben und mit Enter bestätigen.

DevContainer starten

DevContainer starten

Der Container wird automatisch erzeugt und gestartet. All eure Befehle, die ihr eingebt, werden nun im Container ausgeführt. Ihr könnt wie gewohnt CMake ausführen, und eure Projekt Kompilieren und mit Git eueren Sourcecode verwalten.

Für den Start einer Debug-Session führt ihr zuerst auf eurem Host den STLink GDB Server aus. Den startet ihr wie weiter oben beschrieben. Wenn der STLink GDB Server gestartet ist und eine Verbindung zum Discovery-Board besteht, wechselt ihr zurück zu VSCode. In VSCode wechselt ihr in die «Run and Debug»-Ansicht und wählt oben die Debug-Session die wir in «launch.json» konfiguriert haben und klickt auf «Start Debugging».

Debug-Session starten

Debug-Session starten

[message_box title=»Windows Firewall» color=»yellow»]Falls Windows-Benutzer Probleme mit dem Debuggen auf dem Discovery-Board haben, müsst ihr die Firewall Regeln kontrollieren. Der Port, den wir für Verbindung zum STLink GDB Server (oder für das Tool, das ihr verwendet) verwenden, muss freigeschaltet sein.[/message_box]

Den Container stoppt ihr, indem ihr einfach VSCode schliesst oder wiederum die Tastenkombination «CTRL + SHIFT + P» benutzt und «DevContainer: Reopen Folder Locally» eingebt.

DevContainer stoppen

DevContainer stoppen

Fazit

Wir haben jetzt eine vollwertige Entwicklungsumgebung für Embedded Software, die virtuell in einem Docker-Container ausgeführt wird. Wenn man das Setup zum ersten Mal erstellt, gibt es sicherlich vieles neu zu Entdecken und es braucht ein wenig Gewohnheit, zwischen Host und Container zu unterscheiden. Die Konfiguration zum Debuggen mag auch ein wenig aufwendiger sein. Das Beispiel zeigt auch nur das absolute Minimum und kann beliebig mit euren favorisierten Extensions und Tools erweitert werden.

Abschliessend finde ich, dass das Arbeiten sehr angenehm ist. Das Setup können wir einfach zwischen Mitarbeitern teilen und die Wartung der Entwicklungsumgebung ist weniger aufwendig und um ein vielfaches übersichtlicher.

Kommentare

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Newsletter - aktuelle Angebote, exklusive Tipps und spannende Neuigkeiten

 Jetzt anmelden
NACH OBEN
Zur Webcast Übersicht