Нативный cpp + Docker + VSCode = Записать один раз и выполнить в любом месте
Вы когда-нибудь чувствовали необходимость использования нескольких ОС на вашей машине для разработки? Если вы разработчик на C++ и ваше приложение поддерживается на нескольких платформах, вы должны клонировать/синхронизировать код в нескольких ОС и собирать его на нескольких машинах. Проблема такого подхода заключается в том, что при этом тратятся ресурсы, возможно, несколько аппаратных машин.
Двойная загрузка или виртуальные машины – вот некоторые из вариантов, которые могут помочь решить проблему нерационального использования ресурсов. Но у них есть и другие проблемы, связанные с ними.
Один из подходов, который потребляет минимум ресурсов и не предполагает клонирования/синхронизации кода несколько раз для нескольких ОС, – это использование контейнеров docker.
Давайте посмотрим, как с помощью Docker и VScode можно добиться виртуального Write Once Run Anywhere для кодовой базы C++.
Необходимые условия:
- Docker
- VSCode
Мы начнем с супер простой программы Hello World на C++. Структура проекта выглядит следующим образом:
Код в файле main.cpp следующий:
#include <iostream>
auto main() -> int {
std::cout << "Hello World!" << std::endl;
return 0;
}
В этом руководстве я покажу вам, как собирать и выполнять код на C++ в GNU Debian, независимо от того, какая операционная система установлена на вашей машине. В моем случае операционная система моей хост-машины – macOS. Аналогичный процесс можно проделать, если вы хотите собрать код на Си++ на любой другой операционной системе (Ubuntu и т.д.).
Есть две задачи, которые мы хотим решить:
- Сборка и создание исполняемого файла
- Запуск/отладка исполняемого файла
Давайте начнем с первой задачи.
Сборка и создание исполняемого файла:
Во-первых, в корневом каталоге необходимо создать Dockefile, содержащий следующее содержимое:
FROM gcc:12.2.0
ENV TZ=Asia/Kolkata \
DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install vim gdb gdbserver curl sudo wget openssh-server dos2unix file zip flex
# configure SSH for communication with Visual Studio
RUN mkdir -p /var/run/sshd
RUN echo 'root:root' | chpasswd \
&& sed -i -E 's/#\s*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config && \
sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
RUN echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config && \
echo 'PermitEmptyPasswords yes' >> /etc/ssh/sshd_config && \
echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config && \
ssh-keygen -A
# For gdbserver
EXPOSE 2000
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
В качестве базового образа я выбрал gcc. Он основан на GNU Debian с уже установленным gcc. Можно использовать Ubuntu и установить gcc через менеджер пакетов Ubuntu.
Важно видеть, что этот Dockerfile запускает ssh-сервер внутри контейнера. Это полезно для второй задачи, где мы хотим отладить код.
Обратите внимание, что мы не будем копировать код внутри контейнера docker. Мы смонтируем каталог исходного кода на хост-машине внутри контейнера.
Чтобы собрать образ docker из приведенного выше Dockerfile, выполните следующую команду:
docker build -t gcc-12.2-hello-world .
Здесь точка (. ) обозначает текущий каталог.
После того как образ docker собран и виден на вкладке Images в приложении Docker Desktop, следующим шагом будет сборка исходного кода.
Предположим, что команда сборки: g++ -std=c++14 -g ./src/main.cpp -o ./bin/hello_world
Есть два способа сделать это:
Через Docker CLI
Создайте новый каталог с именем bin внутри корневого каталога.
Выполните следующую команду:
docker run --rm -v <BASE_PATH_ON_YOUR_MACHINE>/native_docker_debugging:/native_docker_debugging -w /native_docker_debugging gcc-12.2-hello-world g++ -std=c++14 -g ./src/main.cpp -o ./bin/hello_world
Если вы используете другие инструменты сборки, такие как make, CMake или Ninja, замените команду сборки, начинающуюся с g++
, на команду выбранной вами системы сборки.
Запуск контейнера и сборка кода с помощью ssh:
Выполните следующую команду:
docker run -d -v <BASE_PATH_ON_YOUR_MACHINE>/native_docker_debugging:/native_docker_debugging -p 2222:22 -p 2000:2000 --privileged --security-opt seccomp:unconfined --name gcc-12.2-run-hello-world gcc-12.2-hello-world
Он запустит контейнер с именем gcc-12.2-run-hello-world
. Он должен быть виден в разделе Containers приложения Docker Desktop.
Вы можете войти в контейнер по ssh, используя следующую команду:
ssh -p 2222 root@localhost
Если он запрашивает пароль, то это root . Это запустит новый терминал.
Поскольку исходный код находится в каталоге /native_docker_debugging, перейдите в этот каталог и запустите свою обычную команду сборки. Например, g++ -std=c++14 -g ./src/main.cpp -o ./bin/hello_world .
Исполняемый файл можно запустить из того же открытого терминала.
./bin/hello_world
Теперь, когда исполняемый файл создан и успешно запущен, перейдем ко второй задаче.
Отладка двоичного файла/исполняемого файла
На данный момент структура проекта выглядит следующим образом:
Если вы собрали исполняемый файл, используя раздел Запуск контейнера и сборка кода с помощью ssh
, нет необходимости запускать контейнер с помощью следующего шага. Контейнер должен быть уже запущен.
Выполните следующую команду для запуска контейнера:
docker run -d -v <BASE_PATH_ON_YOUR_MACHINE>/native_docker_debugging:/native_docker_debugging -p 2222:22 -p 2000:2000 --privileged --security-opt seccomp:unconfined --name gcc-12.2-run-hello-world gcc-12.2-hello-world
VSCode играет важную роль в следующих шагах.
В этом руководстве показана отладка с помощью соединения между gdb на хост-машине и gdbserver внутри контейнера. Поэтому необходимо установить gdb на хост-машине, чтобы его можно было подключить к gdbserver, запущенному внутри контейнера.
Создайте каталог с именем .vscode внутри корневого каталога. Внутри .vscode создайте файл с именем launch.json. Содержимое файла launch.json должно выглядеть следующим образом:
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Remote Launch",
"type": "cppdbg",
"request": "launch",
"program": "/native_docker_debugging/bin/hello_world",
"args": [],
"stopAtEntry": true,
"cwd": "/native_docker_debugging",
"externalConsole": true,
"sourceFileMap": {
"/native_docker_debugging": "<BASE_PATH_ON_YOUR_MACHINE>/native_docker_debugging",
},
"pipeTransport": {
"debuggerPath": "/usr/bin/gdb",
"pipeProgram": "/usr/local/bin/sshpass",
"pipeArgs": [
"-p",
"root",
"ssh",
"-p",
"2222",
"root@localhost",
],
"pipeCwd": ""
},
"MIMode": "gdb"
}
]
}
Замените на абсолютный путь к native_docker_debugging
.
В VSCode перейдите на вкладку Run (Шаг 1) на левой панели навигации. Выберите (gdb) Remote Launch в выпадающем списке (Шаг 2) и нажмите на кнопку Play (Шаг 3).
Теперь вы можете отлаживать код через VSCode.
Окончательная структура проекта выглядит следующим образом:
Вот и все! Спасибо что читаете!