diff --git a/.devcontainer/.clang-format b/.devcontainer/.clang-format index d4c6dda..9a61cf3 100644 --- a/.devcontainer/.clang-format +++ b/.devcontainer/.clang-format @@ -1,6 +1,9 @@ --- Language: Cpp -# BasedOnStyle: Google +# Єдиний стиль форматування C++ прикладів у курсі. +# Файл копіюється в корінь devcontainer-а, щоб clang-format знаходив його +# незалежно від директорії, з якої запускається команда. +# BasedOnStyle: Google AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveMacros: false diff --git a/.devcontainer/.clang-tidy b/.devcontainer/.clang-tidy new file mode 100644 index 0000000..0aa217a --- /dev/null +++ b/.devcontainer/.clang-tidy @@ -0,0 +1,52 @@ +# clang-tidy використовується як static analysis шар для C++ коду. +# Набір перевірок навмисно широкий, але не всі warning-и є помилками: +# частину сигналу студенти мають навчитися читати поступово. +Checks: > + -*, + bugprone-*, + clang-analyzer-*, + cppcoreguidelines-*, + modernize-*, + performance-*, + readability-*, + -readability-avoid-const-params-in-decls, + -readability-function-cognitive-complexity, + misc-unused-using-decls, + +# Аналізувати лише код домашніх робіт і типові source/include директорії, +# а не системні headers або сторонні бібліотеки. +HeaderFilterRegex: '(^|/)(homework_[0-9]+|include|src)/' + +# Помилки analyzer/core-guidelines/modernize мають ламати перевірку. +# Readability і performance лишаються попередженнями, щоб не блокувати +# навчальні ітерації надто рано. +WarningsAsErrors: clang-analyzer-*, + cppcoreguidelines-*, + modernize-*, + +# Naming rules формують один стиль для прикладів курсу. +CheckOptions: + - key: readability-identifier-naming.FunctionCase + value: lower_case + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-naming.ConstantCase + value: CamelCase + - key: readability-identifier-naming.ConstantPrefix + value: k + - key: readability-identifier-naming.LocalVariableCase + value: lower_case + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.MemberCase + value: lower_case + - key: readability-identifier-naming.MemberSuffix + value: _ + - key: readability-identifier-naming.PrivateMemberCase + value: lower_case + - key: readability-identifier-naming.PrivateMemberSuffix + value: _ + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.StructCase + value: CamelCase diff --git a/.devcontainer/.clangd b/.devcontainer/.clangd index 8a3e3af..d417c24 100644 --- a/.devcontainer/.clangd +++ b/.devcontainer/.clangd @@ -1,4 +1,17 @@ +# clangd працює як language server у VS Code. +# Основна інформація про include paths і compile flags береться з +# compile_commands.json, який генерує root CMakeLists.txt. InlayHints: Enabled: Yes + # Імена параметрів у викликах не показуються, щоб не перевантажувати + # екран на простих прикладах. ParameterNames: No + # Типи, виведені через auto, корисно бачити під час навчання C++. DeducedTypes: Yes + +# clang-tidy конфіг лежить у repo/container, але diagnostics вимкнено до +# заняття 2.6, де static analysis буде окремою темою. +Diagnostics: + ClangTidy: + Remove: + - '*' diff --git a/.devcontainer/.cmake-format.json b/.devcontainer/.cmake-format.json index 6a97078..023c071 100644 --- a/.devcontainer/.cmake-format.json +++ b/.devcontainer/.cmake-format.json @@ -1,4 +1,9 @@ { + "_course_note": [ + "Конфіг cmake-format для CMakeLists.txt у курсі.", + "Файл копіюється в корінь devcontainer-а, щоб formatter знаходив", + "його незалежно від поточної директорії запуску." + ], "_help_parse": "Options affecting listfile parsing", "parse": { "_help_additional_commands": [ diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4c3ba82..57b4765 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -12,7 +12,7 @@ # docker inspect --format='{{index .RepoDigests 0}}' ubuntu:noble FROM ubuntu:noble@sha256:c4a8d5503dfb2a3eb8ab5f807da5bc69a85730fb49b5cfca2330194ebcc41c7b -# Noninteractive frontend, щоб apt не зависнув чекаючи відповіді на +# Неінтерактивний frontend, щоб apt не зависнув чекаючи відповіді на # питання (часовий пояс, клавіатура, kernel upgrade prompt). У Dockerfile # інтерактивного stdin немає - без цього build може зависнути назавжди. ARG DEBIAN_FRONTEND="noninteractive" @@ -39,10 +39,11 @@ RUN apt update && \ # запуску - тобто вони знайдуться незалежно від того, де всередині # контейнера змонтовано проєкт. COPY .clang-format /.clang-format +COPY .clang-tidy /.clang-tidy COPY .clangd /.clangd COPY .cmake-format.json /.cmake-format.json -# Environment: +# Змінні середовища: # - TZ: UTC як нейтральний дефолт. Логи завжди в UTC, без суперечок з # літнім часом різних регіонів. # - LC_ALL / LANG / LANGUAGE: en_US.UTF-8 щоб stdout/stderr коректно @@ -52,7 +53,7 @@ ENV LC_ALL=en_US.UTF-8 ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US:en -# Passwordless sudo для dev-контейнера: зручно для apt install на ходу, +# sudo без пароля для dev-контейнера: зручно для apt install на ходу, # debug tools, etc. У прод-образах ніколи - там контейнер має жорстко # визначений набір прав. RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers diff --git a/.devcontainer/apt-packages.in b/.devcontainer/apt-packages.in index 91a0a3a..e55eca6 100644 --- a/.devcontainer/apt-packages.in +++ b/.devcontainer/apt-packages.in @@ -1,3 +1,4 @@ +# Базові пакети для стабільної роботи Ubuntu image. ca-certificates curl locales @@ -6,9 +7,26 @@ tzdata ssh git +# C++ toolchain і language server для редактора. gcc clang clangd +clang-tidy + +# Runtime-бібліотеки Clang sanitizer-ів і symbolizer для читабельного ASan/UBSan output. +libclang-rt-18-dev +llvm-18 + +# Крос-тулчейн для ARM64-пристроїв: RPi4, Radxa ROCK 5B+, Jetson. +g++-aarch64-linux-gnu + +# Інструменти для заняття 2.4: локальний і remote debug, memory diagnostics. +gdb +gdbserver +gdb-multiarch +valgrind + +# Інструменти збірки і форматери, які використовуються в лекціях та CI. make cmake clang-format diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ad5594b..d88651b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,8 @@ { + // Назва, яка показується у VS Code Dev Containers UI. "name": "CPP MilTech", + // Image збирається з локального Dockerfile. Аргументи потрібні, щоб + // container user і workspace path збігалися з host-сесією. "build": { "dockerfile": "Dockerfile", "args": { @@ -7,23 +10,45 @@ "CONTAINER_USER": "${localEnv:USER}" } }, + // VS Code відкриває контейнер від імені того самого користувача, що й host. + // Це зменшує проблеми з ownership файлів у bind mount. "remoteUser": "${localEnv:USER}", + // Проєкт монтується у домашню директорію користувача всередині контейнера. + // Так шляхи виглядають однаково на Linux, macOS і Windows+WSL. "workspaceMount": "source=${localWorkspaceFolder},target=${localEnv:HOME}/${localWorkspaceFolderBasename},type=bind", "workspaceFolder": "${localEnv:HOME}/${localWorkspaceFolderBasename}", + // SSH ключі потрібні для git operations. bash_history винесено на host, + // щоб історія команд переживала rebuild контейнера. "mounts": [ "source=${localEnv:HOME}/.ssh,target=/home/${localEnv:USER}/.ssh,type=bind,readonly,consistency=cached", - "source=${localEnv:HOME}/.devcontainers/${localWorkspaceFolderBasename}/.bash_history,target=/home/${localEnv:USER}/.bash_history,type=bind,consistency=delegated", + "source=${localEnv:HOME}/.devcontainers/${localWorkspaceFolderBasename}/.bash_history,target=/home/${localEnv:USER}/.bash_history,type=bind,consistency=delegated" ], + // initialize створює host-side директорії для mounts до старту container-а. + // Запуск через bash робить команду стійкою до копіювання без executable bit. "initializeCommand": [ + "bash", ".devcontainer/scripts/initialize" ], + // host network спрощує SSH/debug сценарії для занять з target-пристроями. + // SYS_PTRACE і seccomp=unconfined потрібні для стабільної роботи GDB + // всередині контейнера, особливо коли debug-сесія attach-иться до процесу. + // ulimit core=-1 дозволяє процесам у контейнері створювати core dump. + // Сам шлях/формат core-файла все одно визначає host kernel через + // /proc/sys/kernel/core_pattern; у WSL це налаштовується окремо на host. "runArgs": [ "--network=host", + "--cap-add=SYS_PTRACE", + "--security-opt=seccomp=unconfined", + "--ulimit=core=-1", "--hostname=devcontainer", "--add-host=devcontainer:127.0.1.1" ], + // Усе всередині customizations.vscode застосовується саме до VS Code + // сесії в контейнері. Це не те саме, що .vscode/extensions.json на host. "customizations": { "vscode": { + // Extensions встановлюються в devcontainer, щоб tooling був однаковий + // у всіх студентів незалежно від host VS Code. "extensions": [ "josetr.cmake-language-support-vscode", "cheshirekow.cmake-format", @@ -31,13 +56,17 @@ "llvm-vs-code-extensions.vscode-clangd" ], "settings": { + // clangd читає build/debug/compile_commands.json з debug preset. + // clang-tidy конфіг лишається в репозиторії, але автоматичний запуск + // через clangd буде увімкнено пізніше, після заняття 2.6. "clangd.path": "/usr/bin/clangd", "clangd.arguments": [ - "--compile-commands-dir=ada_ws/build/x86_64/", - "--clang-tidy", + "--compile-commands-dir=build/debug", "--background-index" ], "clangd.restartAfterCrash": true, + // IntelliSense від Microsoft C++ extension вимкнено, щоб не було двох + // C++ language server-ів одночасно. Основний engine - clangd. "C_Cpp.intelliSenseEngine": "disabled", "[cpp]": { "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd", @@ -50,18 +79,21 @@ "[cmake]": { "editor.defaultFormatter": "cheshirekow.cmake-format" }, + // cmake-format читає конфіг, який Dockerfile копіює в /. "cmakeFormat.args": [ "--config-file=/.cmake-format.json" ], - "cmakeFormat.exePath": "/usr/bin/cmake-format" - } - }, - "terminal.integrated.shell.linux": "bash", - "terminal.integrated.profiles.linux": { - "bash (container default)": { - "path": "/bin/bash", - "overrideName": true + "cmakeFormat.exePath": "/usr/bin/cmake-format", + // Єдиний bash profile у container terminal. Історія команд монтується + // окремо через mounts вище. + "terminal.integrated.defaultProfile.linux": "bash (container default)", + "terminal.integrated.profiles.linux": { + "bash (container default)": { + "path": "/bin/bash", + "overrideName": true + } + } } } } -} +} \ No newline at end of file diff --git a/.devcontainer/scripts/initialize b/.devcontainer/scripts/initialize index 20a1d63..e484665 100755 --- a/.devcontainer/scripts/initialize +++ b/.devcontainer/scripts/initialize @@ -1,13 +1,13 @@ #!/usr/bin/env bash set -euo pipefail -# Runs on the host (macOS, Linux, WSL2) before the container starts. -# Creates a bash history file under the host's home so shell history -# persists across container rebuilds. +# Скрипт виконується на host-системі (macOS, Linux, WSL2) до старту +# контейнера. Тут створюються host-side файли, які потім монтуються +# у devcontainer. DEVCONTAINER_DATA_ON_HOST="${HOME}/.devcontainers/$(basename "${PWD}")" mkdir -p "${DEVCONTAINER_DATA_ON_HOST}" touch "${DEVCONTAINER_DATA_ON_HOST}/.bash_history" -# Ensure ~/.ssh exists so the bind-mount in devcontainer.json does not fail -# on fresh machines where the user never generated SSH keys yet. +# ~/.ssh має існувати до bind mount з devcontainer.json. На свіжих машинах +# SSH ключів може ще не бути, і без цієї директорії container start падає. [ -d "${HOME}/.ssh" ] || mkdir -m 700 "${HOME}/.ssh" diff --git a/.gitattributes b/.gitattributes index 795b063..7029cd4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,14 @@ +# Нормалізувати текстові файли до LF, щоб приклади однаково працювали на +# Linux, macOS і Windows+WSL. * text=auto eol=lf +# Shell scripts і devcontainer hooks завжди мають LF, інакше bash у Linux +# контейнері може падати на CRLF. *.sh text eol=lf .devcontainer/scripts/* text eol=lf Dockerfile text eol=lf +# Бінарні assets не проходять text normalization. *.png binary *.jpg binary *.jpeg binary diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9bb8d39..33032ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,9 +39,10 @@ jobs: # 4. Монтує checkout у workspaceFolder. # 5. Виконує runCmd від імені remoteUser. # - # -G Ninja: явно обираємо Ninja-генератор. ninja-build уже є у - # devcontainer-і, швидший за Make на проєктах з багатьма файлами - # (кращий паралелізм, менше overhead-у на запуск shell-команд). + # CMake preset debug тримає локальну і CI-збірку в одному місці: + # build/debug. Це також збігається з clangd compile_commands і + # VS Code debug launch config. ARM64 preset додатково перевіряє, + # що demo-код можна крос-компілювати для SBC target-а. - name: Build inside dev container uses: devcontainers/ci@v0.3 with: @@ -51,5 +52,7 @@ jobs: # на runner-і для runCmd. push: never runCmd: | - cmake -S . -B build -G Ninja - cmake --build build + cmake --preset debug + cmake --build --preset debug + cmake --preset aarch64-debug + cmake --build --preset aarch64-debug diff --git a/.gitignore b/.gitignore index b27363a..530bb39 100644 --- a/.gitignore +++ b/.gitignore @@ -1,79 +1,33 @@ -# Prerequisites -*.d +# Директорії збірки та локальний output мають відтворюватися з CMake. +build/ +build-*/ +out/ -# Compiled Object files -*.slo -*.lo +# CMake генерує ці файли при in-source або ручних локальних збірках. +CMakeCache.txt +CMakeFiles/ +CTestTestfile.cmake +Makefile +cmake_install.cmake +compile_commands.json +install_manifest.txt +_deps/ + +# Мінімальний набір compiled/debug артефактів для C++ прикладів. +*.d *.o *.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries +*.a *.so *.dylib *.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables *.exe *.out -*.app - -# Debug files *.dSYM/ -*.su -*.idb *.pdb +core +core.* -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -# CMake -CMakeLists.txt.user -CMakeCache.txt -CMakeFiles -CMakeScripts -Testing -Makefile -cmake_install.cmake -install_manifest.txt -compile_commands.json -CTestTestfile.cmake -_deps - -# Build directories -build/ -build-*/ -out/ -bin/ -obj/ - -# IDE / editor -.vs/ -.idea/ -*.swp -*.swo -*~ -.cache/ - -# OS +# Метадані операційної системи не є частиною навчального репозиторію. .DS_Store Thumbs.db diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e6f2364..b3594d1 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,8 @@ { + // Рекомендація тільки для host VS Code: цей extension потрібен на машині + // студента, щоб відкрити репозиторій у devcontainer. C++ інструменти + // встановлюється всередині контейнера через .devcontainer/devcontainer.json. "recommendations": [ "ms-vscode-remote.remote-containers" ] -} +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7e724e0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,56 @@ +{ + // Launch configurations працюють у devcontainer і використовують GDB з image. + // Для ДЗ 5 потрібно буде додати окрему конфігурацію за цим прикладом. + "version": "0.2.0", + "configurations": [ + { + // Локальний debug demo-коду заняття 2.4. Використовує bad input, + // щоб GDB зупинився на runtime crash. + "name": "Debug demo 2.4: debug_probe", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/debug/demos/lesson_2_4/debug_probe/debug_probe", + "args": [ + "${workspaceFolder}/demos/lesson_2_4/debug_probe/data/bad_missing_field.txt" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb", + "preLaunchTask": "CMake: configure and build", + "setupCommands": [ + { + "description": "Увімкнути pretty-printing для STL типів у GDB.", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, + { + // Локальний debug стартера ДЗ 4. preLaunchTask збирає debug preset. + "name": "Debug homework_04: ugv_odometry", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/debug/homework_04/ugv_odometry", + "args": [ + "${workspaceFolder}/homework_04/data/straight.txt" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb", + "preLaunchTask": "CMake: configure and build", + "setupCommands": [ + { + "description": "Увімкнути pretty-printing для STL типів у GDB.", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..a29b2ed --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,37 @@ +{ + // VS Code tasks виконуються з кореня workspace у devcontainer terminal. + // Тут лишаються тільки базові команди: configure/build і clean. + "version": "2.0.0", + "tasks": [ + { + // Default build task: Ctrl+Shift+B запускає debug preset, потім build. + "label": "CMake: configure and build", + "type": "shell", + "command": "cmake --preset debug && cmake --build --preset debug", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$gcc" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated", + "clear": true + } + }, + { + // Clean task навмисно простий: видаляється тільки локальна build директорія. + "label": "CMake: clean build directory", + "type": "shell", + "command": "rm -rf build", + "problemMatcher": [], + "presentation": { + "reveal": "always", + "panel": "dedicated", + "clear": true + } + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee00a7..7137ede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,68 @@ Усі помітні зміни в цьому репо фіксуються тут. Формат - [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), дати в ISO 8601. +## 2026-04-30 + +### Added + +- Block 2 / Lesson 2.4: `debug` CMake preset, VS Code launch config для + `homework_04` і build task, який використовує Debug-збірку через preset. +- Block 2 / Lessons 2.4-2.5: стартовий код `homework_05` для діагностики + телеметрії з C++ бібліотекою, виконуваним файлом, проблемними вхідними + файлами і навмисними помилками під час виконання. + `CMakeLists.txt` для `homework_05` не додано навмисно, це частина ДЗ 5. +- Block 2 / Lesson 2.4: demo `demos/lesson_2_4/debug_probe` для локального + консольного GDB, core dump, Valgrind і віддаленого GDB/gdbserver на + Raspberry Pi / ARM64-пристрої. +- Block 2 / Lesson 2.4: ARM64 cross-compilation preset `aarch64-debug` і + CMake toolchain `cmake/toolchains/aarch64-linux-gnu.cmake`. +- Block 2 / Lessons 2.3-2.4: VS Code tasks для CMake configure/build і + видалення локальної `build/` директорії. `CMake: configure and build` + зроблено default build task для `Ctrl+Shift+B`. + ([#10](https://github.com/robot-dreams-code/C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY/pull/10)) +- Block 2 / Lessons 2.3-2.6: clang-tidy конфіг, debug/diagnostics tools + (`gdb`, `gdbserver`, `gdb-multiarch`, `valgrind`) і налаштування clangd через + `build/compile_commands.json`. + ([#9](https://github.com/robot-dreams-code/C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY/pull/9)) + +### Changed + +- Block 2 / Lessons 2.1-2.6: оновлено setup docs під GitHub Template flow, + додано пояснювальні коментарі до student-facing tooling/config файлів і + зменшено `.gitignore` до мінімального C++/CMake набору. + ([#9](https://github.com/robot-dreams-code/C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY/pull/9)) +- Block 2 / Lesson 2.4: clangd тепер читає compile database з + `build/debug/compile_commands.json`, щоб відповідати `debug` preset. +- Block 2 / Lesson 2.4: README/preps і CI build flow переведено на + `cmake --preset debug` і `cmake --build --preset debug`. +- Block 2 / Lesson 2.4: CI build flow додатково перевіряє + `aarch64-debug` cross-build. +- Block 2 / Lesson 2.4: remote GDB інструкції уточнено: `satelite` лишається + SSH alias, а `target remote` використовує IP пристрою. +- Block 2 / Lesson 2.4: core dump інструкції доповнено fallback-сценарієм для + WSL/Docker, де core-файл може перехоплюватись системним handler-ом. +- Block 2 / Lesson 2.4: devcontainer runtime args доповнено `SYS_PTRACE`, + `seccomp=unconfined` і `core=-1` для GDB/core dump сценаріїв. +- Block 2 / Lesson 2.4: core dump README доповнено WSL host setup через + `kernel.core_pattern`, перевіркою `core.%e.%p` після rebuild/reopen + devcontainer і запуском GDB по фактичному `core*` файлу. +- Block 2 / Lesson 2.4: core dump README уточнює, що `ulimit -c unlimited` + потрібно виконати у поточній shell-сесії, якщо перевірка показує `0`. +- Block 2 / Lesson 2.4: devcontainer отримав `libclang-rt-18-dev` і + `llvm-18`, щоб ASan/UBSan збірка через Clang лінкувалась і показувала + читабельний stack trace з file:line. +- Block 2 / Lesson 2.4: `.gitignore` ігнорує локальні `core` / `core.*` + файли, які з'являються під час core dump demo. +- Block 2 / Lesson 2.4: clang-tidy diagnostics у clangd вимкнено до заняття + 2.6 через devcontainer settings і `.clangd`; конфіг і пакет clang-tidy + лишаються в repo/container для майбутнього ввімкнення. +- Block 2 / Lesson 2.4: `initializeCommand` запускає + `.devcontainer/scripts/initialize` через `bash`, щоб копіювання файлів без + executable bit не ламало старт devcontainer. +- Block 2 / Lesson 2.4: README/preps доповнено snapshot sync flow через + `git clone` + `tar`, `git status` і commit, щоб оновлення курс-репо не + залежали від ручного списку файлів. + ## 2026-04-25 ### Changed diff --git a/CMakeLists.txt b/CMakeLists.txt index 4025f7a..79f396b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,21 @@ cmake_minimum_required(VERSION 3.20) + +# Загальний CMake-проєкт для матеріалів блоку 2. +# Кожна homework_* або demos/* директорія підключається окремо. project(section2 LANGUAGES CXX) +# C++20 - базовий стандарт курсу. EXTENSIONS OFF прибирає GNU-розширення, +# щоб код лишався ближчим до переносимого ISO C++. set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +# clangd читає compile_commands.json і завдяки цьому бачить ті самі include +# paths, defines і flags, що й реальна CMake-збірка. +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + # Домашні роботи. По мірі появи додаємо кожну окремим add_subdirectory. add_subdirectory(homework_04) + +# Demo-код для заняття 2.4: локальне і віддалене відлагодження через GDB. +add_subdirectory(demos/lesson_2_4/debug_probe) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..f7f4f45 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,45 @@ +{ + "version": 2, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20, + "patch": 0 + }, + "configurePresets": [ + { + "name": "debug", + "displayName": "Debug", + "description": "Debug build для занять блоку 2: символи для GDB і зрозуміла структура build/debug.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "aarch64-debug", + "displayName": "ARM64 Debug", + "description": "Cross-compile debug build для Raspberry Pi / Radxa / Jetson через aarch64-linux-gnu toolchain.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/aarch64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/toolchains/aarch64-linux-gnu.cmake" + } + } + ], + "buildPresets": [ + { + "name": "debug", + "displayName": "Build Debug", + "description": "Зібрати debug preset після configure.", + "configurePreset": "debug" + }, + { + "name": "aarch64-debug", + "displayName": "Build ARM64 Debug", + "description": "Зібрати ARM64 debug preset після cross-configure.", + "configurePreset": "aarch64-debug" + } + ] +} diff --git a/README.md b/README.md index acbab52..acd8d37 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,19 @@ PR-и на вашій копії йдуть у ваш main. 3. **Зібрати весь репо всередині контейнера:** ```bash - cmake -S . -B build -G Ninja - cmake --build build + cmake --preset debug + cmake --build --preset debug ``` - Виконувані файли домашніх робіт з'являться в `build/homework_XX/`. + Виконувані файли домашніх робіт з'являться в `build/debug/homework_XX/`. + Demo-код занять лежить у відповідних піддиректоріях `build/debug/demos/`. + +4. **Зібрати ARM64 debug binary для Raspberry Pi / Radxa / Jetson:** + ```bash + cmake --preset aarch64-debug + cmake --build --preset aarch64-debug + ``` + ARM64 артефакти з'являться в `build/aarch64-debug/`. Цей preset + використовує toolchain `cmake/toolchains/aarch64-linux-gnu.cmake`. ## Доступ для перевірки @@ -57,20 +66,52 @@ formal review (approve / request changes) навіть на публічному ## Оновлення з курс-репо -Якщо у курс-репо з'являється щось нове (новий starter, фікс у devcontainer, -оновлена інструкція) - підтягти у свій репо можна через додатковий remote: +Зміни в курс-репо не синкаються автоматично у локальний репо. Якщо +щось важливе оновлюється - буде анонс у Slack-каналі курсу. + +Рекомендований спосіб підтягнути snapshot курс-репо без затирання ДЗ 4: ```bash -# одноразово: додати курс-репо як remote `course` -git remote add course https://github.com/robot-dreams-code/C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY.git - -# коли треба оновитись: -git fetch course -git merge course/main # підтягнути все -# або точково: -git cherry-pick # конкретний коміт +tmp_dir=$(mktemp -d) + +git clone --depth 1 \ + git@github.com:robot-dreams-code/C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY.git \ + "$tmp_dir/course" + +( + cd "$tmp_dir/course" + tar --exclude='./.git' --exclude='./homework_04' -cf - . +) | tar -xf - + +rm -rf "$tmp_dir" + +git status +git add . +git commit -m "chore: sync course repository updates" ``` +Команда копіює tooling/devcontainer/CMake/demo/homework_05 з актуального +курс-репо, але не затирає `homework_04/`. `git status` перед комітом потрібен +як контроль того, які файли зміняться у локальному репо. + +**Чому не git merge / cherry-pick:** курс-репо позначений як GitHub +Template. Копія через "Use this template" - це новий репо з єдиним +"Initial commit", без зв'язку з курс-репо в історії. Спільного предка +нема, тому git нічого не може злити автоматично. + +Чому Template, а не fork: fork залишає історію спільною, і merge для +нього працював би. Але PR на fork-у GitHub дефолтно цілить у upstream +(у курс-репо). Курс-репо має лишатися read-only, PR-и студентів - іти +у їхні репо. Template це знімає, ціна - нема прямого git-sync. + +`git merge course/main` падає з `refusing to merge unrelated histories`. +З `--allow-unrelated-histories` merge пройде, але вийде каша: дві +кореневі коміти і конфлікти на більшості файлів. Cherry-pick не падає +сам, але конфліктує на локально модифікованих файлах. + +Простіше - snapshot copy за командою вище. Без merge unrelated histories і +без ручного вибору окремих файлів. + ## Перед стартом Потрібно: @@ -95,6 +136,8 @@ git cherry-pick # конкретний коміт ├── .github/workflows/ # GitHub Actions: CI build через devcontainer ├── CHANGELOG.md # лог помітних змін по PR-ах ├── CMakeLists.txt # корневий CMakeLists, підтягує homework_XX через add_subdirectory +├── cmake/toolchains/ # CMake toolchain-файли для cross-compilation +├── demos/ # demo-код для занять ├── homework_XX/ # окрема домашка, кожна зі своїм CMakeLists.txt └── preps/ # інструкції зі сетапу за платформами ``` @@ -117,8 +160,11 @@ git cherry-pick # конкретний коміт Workflow `.github/workflows/build.yml` запускається на кожен PR (і на push у main). Він: 1. Будує devcontainer image з Dockerfile. -2. Виконує всередині `cmake -S . -B build -G Ninja && cmake --build build`. -3. Падає якщо хоч одна `homework_XX` не компілюється з +2. Виконує native debug build: + `cmake --preset debug && cmake --build --preset debug`. +3. Виконує ARM64 cross debug build: + `cmake --preset aarch64-debug && cmake --build --preset aarch64-debug`. +4. Падає якщо хоч одна `homework_XX` або demo target не компілюється з тулчейном з devcontainer-а (gcc-13, clang-18, C++20). Якщо CI червоний локально зібралося, але на CI ні - швидше за все diff --git a/cmake/toolchains/aarch64-linux-gnu.cmake b/cmake/toolchains/aarch64-linux-gnu.cmake new file mode 100644 index 0000000..04a9130 --- /dev/null +++ b/cmake/toolchains/aarch64-linux-gnu.cmake @@ -0,0 +1,15 @@ +# Toolchain для cross-compilation з Linux x86_64 devcontainer-а під ARM64 Linux. +# Підходить для Raspberry Pi 4, Radxa ROCK 5B+ і Jetson з 64-bit Linux userspace. + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR aarch64) + +# Компілятори з Debian/Ubuntu пакета g++-aarch64-linux-gnu. +set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) +set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) + +# Пошук headers/libraries йде у target sysroot, а host-програм - на host-і. +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/demos/lesson_2_4/debug_probe/CMakeLists.txt b/demos/lesson_2_4/debug_probe/CMakeLists.txt new file mode 100644 index 0000000..f141f45 --- /dev/null +++ b/demos/lesson_2_4/debug_probe/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.20) + +# Цей CMakeLists можна збирати двома способами: +# - як частину кореневого репозиторію через add_subdirectory(); +# - окремо на цільовому пристрої, якщо скопійовано лише цю demo-директорію. +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + project(debug_probe LANGUAGES CXX) + + # Standalone-збірка використовує той самий C++20, що й основний курс. + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) +endif() + +add_executable(debug_probe src/main.cpp) + +# Warning flags тримаємо на target-рівні, щоб не впливати на інші приклади. +target_compile_options( + debug_probe + PRIVATE $<$:-Wall -Wextra -Wpedantic>) diff --git a/demos/lesson_2_4/debug_probe/README.md b/demos/lesson_2_4/debug_probe/README.md new file mode 100644 index 0000000..b933406 --- /dev/null +++ b/demos/lesson_2_4/debug_probe/README.md @@ -0,0 +1,253 @@ +# Демо 2.4: debug_probe для локального і віддаленого GDB + +Цей приклад використовується на занятті 2.4 для показу одного циклу +відлагодження: + +```text +відтворити -> оглянути -> підтвердити -> виправити -> перевірити +``` + +Код дуже малий і має дві навмисні помилки під час виконання. Одна проявляється +як падіння на некоректних вхідних даних, друга добре видна через Valgrind на +коректних вхідних даних. Точні місця в коді не позначено, щоб демо проходило +через інструменти, а не через читання готової підказки. + +## Локальний запуск + +```bash +cmake --preset debug +cmake --build --preset debug + +./build/debug/demos/lesson_2_4/debug_probe/debug_probe \ + demos/lesson_2_4/debug_probe/data/good.txt +``` + +Очікуваний вивід: + +```text +seq 7 +battery_v 24.6 +satellites 12 +health ready +``` + +## Консольний GDB + +```bash +gdb ./build/debug/demos/lesson_2_4/debug_probe/debug_probe +``` + +Всередині GDB: + +```text +run demos/lesson_2_4/debug_probe/data/bad_missing_field.txt +bt +frame 0 +print text +up +print field_count +print fields[2] +quit +``` + +## Valgrind + +```bash +valgrind --leak-check=full \ + ./build/debug/demos/lesson_2_4/debug_probe/debug_probe \ + demos/lesson_2_4/debug_probe/data/good.txt +``` + +Очікуваний сигнал: `definitely lost` memory block. + +Проблемні вхідні дані також можна запускати через Valgrind, щоб побачити +некоректне читання: + +```bash +valgrind \ + ./build/debug/demos/lesson_2_4/debug_probe/debug_probe \ + demos/lesson_2_4/debug_probe/data/bad_missing_field.txt +``` + +## Крос-збірка для Raspberry Pi + +```bash +cmake --preset aarch64-debug +cmake --build --preset aarch64-debug +``` + +ARM64 виконуваний файл: + +```text +build/aarch64-debug/demos/lesson_2_4/debug_probe/debug_probe +``` + +## Віддалений запуск на Raspberry Pi + +На `satelite` потрібен `gdbserver`, але не компілятор: + +```bash +ssh satelite +sudo apt update +sudo apt install -y gdbserver +exit +``` + +Скопіювати виконуваний файл і дані: + +```bash +ssh satelite 'rm -rf /tmp/debug_probe' +ssh satelite 'mkdir -p /tmp/debug_probe/data' +scp build/aarch64-debug/demos/lesson_2_4/debug_probe/debug_probe \ + satelite:/tmp/debug_probe/ +scp demos/lesson_2_4/debug_probe/data/*.txt satelite:/tmp/debug_probe/data/ +``` + +Запустити на цільовому пристрої: + +```bash +ssh satelite +cd /tmp/debug_probe +chmod +x debug_probe +./debug_probe data/good.txt +gdbserver :1234 ./debug_probe data/bad_missing_field.txt +``` + +Підключитись з комп'ютера/devcontainer. Використовується локальний +крос-скомпільований виконуваний файл з debug-символами: + +```bash +gdb-multiarch build/aarch64-debug/demos/lesson_2_4/debug_probe/debug_probe +``` + +Всередині GDB: + +```text +target remote 192.168.1.12:1234 +continue +bt +frame 0 +print text +quit +``` + +`satelite` - це SSH alias. GDB підключається не через SSH config, а через +звичайний TCP host:port, тому для `target remote` потрібен IP або DNS-ім'я +пристрою. + +## Core dump + +Devcontainer для цього репозиторію запускається з runtime args для +відлагодження: + +```text +--cap-add=SYS_PTRACE +--security-opt=seccomp=unconfined +--ulimit=core=-1 +``` + +Після зміни цих параметрів контейнер потрібно перестворити. Вони дають GDB +потрібні права і прибирають ліміт на розмір core dump, але не змінюють +політику host kernel. + +На WSL host перед demo можна тимчасово задати простий шаблон core-файлів: + +```bash +sudo sysctl -w kernel.core_pattern='core.%e.%p' +``` + +Очікувана перевірка на WSL host: + +```bash +cat /proc/sys/kernel/core_pattern +# core.%e.%p +``` + +Після цього потрібно перестворити або заново відкрити devcontainer, щоб +застосувались `runArgs` з `.devcontainer/devcontainer.json`. Усередині +devcontainer перевірка має показати: + +```bash +ulimit -c +# unlimited + +cat /proc/sys/kernel/core_pattern +# core.%e.%p +``` + +Якщо `ulimit -c` показує `0`, увімкнути core dump для поточного термінала: + +```bash +ulimit -c unlimited +ulimit -c +``` + +Це налаштування діє тільки для поточної shell-сесії та процесів, запущених з неї. + +Після перезапуску WSL це налаштування може повернутись до стандартного +`wsl-capture-crash`. + +Локально: + +```bash +ulimit -c unlimited +./build/debug/demos/lesson_2_4/debug_probe/debug_probe \ + demos/lesson_2_4/debug_probe/data/bad_missing_field.txt + +CORE_FILE=$(ls -t core* | head -1) +gdb ./build/debug/demos/lesson_2_4/debug_probe/debug_probe "$CORE_FILE" +``` + +Якщо файл `core` не з'явився, перевірити політику системи: + +```bash +cat /proc/sys/kernel/core_pattern +``` + +На WSL/Docker crash може перехоплюватись системним handler-ом. Для стабільного +локального demo можна створити core-файл із GDB після зупинки на SIGSEGV: + +```bash +gdb ./build/debug/demos/lesson_2_4/debug_probe/debug_probe +run demos/lesson_2_4/debug_probe/data/bad_missing_field.txt +generate-core-file /tmp/debug_probe.core +quit + +gdb ./build/debug/demos/lesson_2_4/debug_probe/debug_probe /tmp/debug_probe.core +bt +``` + +На `satelite`: + +```bash +ssh satelite +cd /tmp/debug_probe +ulimit -c unlimited +./debug_probe data/bad_missing_field.txt +ls -lh core* +``` + +Якщо core забрав systemd-coredump: + +```bash +coredumpctl list debug_probe +coredumpctl dump debug_probe > /tmp/debug_probe/debug_probe.core +``` + +Скопіювати core на комп'ютер і відкрити через локальний ARM64 виконуваний файл +з debug-символами: + +```bash +scp 'satelite:/tmp/debug_probe/core*' /tmp/ +CORE_FILE=$(ls -t /tmp/core* | head -1) +gdb-multiarch build/aarch64-debug/demos/lesson_2_4/debug_probe/debug_probe \ + "$CORE_FILE" +``` + +Альтернативно, якщо core було експортовано через `coredumpctl dump`: + +```bash +scp satelite:/tmp/debug_probe/debug_probe.core /tmp/debug_probe.core +gdb-multiarch build/aarch64-debug/demos/lesson_2_4/debug_probe/debug_probe \ + /tmp/debug_probe.core +``` diff --git a/demos/lesson_2_4/debug_probe/data/bad_missing_field.txt b/demos/lesson_2_4/debug_probe/data/bad_missing_field.txt new file mode 100644 index 0000000..956941f --- /dev/null +++ b/demos/lesson_2_4/debug_probe/data/bad_missing_field.txt @@ -0,0 +1 @@ +7 24.6 diff --git a/demos/lesson_2_4/debug_probe/data/good.txt b/demos/lesson_2_4/debug_probe/data/good.txt new file mode 100644 index 0000000..269f2db --- /dev/null +++ b/demos/lesson_2_4/debug_probe/data/good.txt @@ -0,0 +1 @@ +7 24.6 12 diff --git a/demos/lesson_2_4/debug_probe/src/main.cpp b/demos/lesson_2_4/debug_probe/src/main.cpp new file mode 100644 index 0000000..1932527 --- /dev/null +++ b/demos/lesson_2_4/debug_probe/src/main.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include + +// Lecture demo notes: +// this file intentionally contains two runtime defects. The defects are related +// to malformed input and heap ownership. Exact locations are not marked on +// purpose. + +const int EXPECTED_FIELD_COUNT = 3; +const int MAX_LINE_LENGTH = 128; + +struct ProbeSample { + int seq; + double battery_v; + int satellites; +}; + +int split_line(char line[], char* fields[], int max_fields) { + int count = 0; + char* cursor = line; + + while (*cursor != '\0' && count < max_fields) { + while (*cursor == ' ' || *cursor == '\t' || *cursor == '\n' || *cursor == '\r') { + *cursor = '\0'; + ++cursor; + } + + if (*cursor == '\0') { + break; + } + + fields[count] = cursor; + ++count; + + while (*cursor != '\0' && *cursor != ' ' && *cursor != '\t' && *cursor != '\n' && + *cursor != '\r') { + ++cursor; + } + } + + return count; +} + +int parse_int(const char* text) { + // This keeps parsing visibly simple for a GDB demo. + if (text[0] == '+') { + ++text; + } + + return static_cast(std::strtol(text, nullptr, 10)); +} + +double parse_double(const char* text) { + // This mirrors parse_int so both functions are easy to inspect in GDB. + if (text[0] == '+') { + ++text; + } + + return std::strtod(text, nullptr); +} + +ProbeSample parse_sample(char line[]) { + char* fields[EXPECTED_FIELD_COUNT] = {}; + const int field_count = split_line(line, fields, EXPECTED_FIELD_COUNT); + (void)field_count; + + ProbeSample sample{}; + sample.seq = parse_int(fields[0]); + sample.battery_v = parse_double(fields[1]); + sample.satellites = parse_int(fields[2]); + return sample; +} + +char* health_label(const ProbeSample& sample) { + char* label = new char[16]; + + if (sample.battery_v < 21.0) { + std::strcpy(label, "battery_low"); + return label; + } + + if (sample.satellites < 4) { + std::strcpy(label, "gps_weak"); + return label; + } + + std::strcpy(label, "ready"); + return label; +} + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "usage: debug_probe \n"; + return 1; + } + + std::ifstream input{argv[1]}; + if (!input) { + std::cerr << "error: failed to open input file: " << argv[1] << '\n'; + return 2; + } + + char line[MAX_LINE_LENGTH]; + if (!input.getline(line, MAX_LINE_LENGTH)) { + std::cerr << "error: input file is empty\n"; + return 3; + } + + const ProbeSample sample = parse_sample(line); + char* health = health_label(sample); + + std::cout << "seq " << sample.seq << '\n'; + std::cout << "battery_v " << sample.battery_v << '\n'; + std::cout << "satellites " << sample.satellites << '\n'; + std::cout << "health " << health << '\n'; + + return 0; +} diff --git a/homework_04/CMakeLists.txt b/homework_04/CMakeLists.txt index b7c94c2..bc6a70a 100644 --- a/homework_04/CMakeLists.txt +++ b/homework_04/CMakeLists.txt @@ -1,9 +1,16 @@ cmake_minimum_required(VERSION 3.20) + +# Окремий CMake-проєкт для домашньої роботи 04. Його можна збирати як +# через root CMakeLists.txt, так і напряму з цієї директорії. project(ugv_odometry CXX) +# Код домашньої роботи збирається з тим самим стандартом, що й лекційні приклади. set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +# Один executable для задачі з UGV odometry. add_executable(ugv_odometry src/main.cpp) + +# Базові compiler warnings допомагають побачити підозрілі місця до runtime. target_compile_options(ugv_odometry PRIVATE -Wall -Wextra -pedantic) diff --git a/homework_05/README.md b/homework_05/README.md new file mode 100644 index 0000000..ff68cdd --- /dev/null +++ b/homework_05/README.md @@ -0,0 +1,17 @@ +# ДЗ 5: стартовий код + +Ця директорія містить стартовий код для ДЗ 5. У коді навмисно залишено +помилки під час виконання. Код компілюється, але падає або аварійно +завершується на проблемних вхідних файлах. + +`CMakeLists.txt` не додано навмисно. Його потрібно створити як частину ДЗ: + +- `telemetry` - бібліотека з логікою читання, парсингу і підсумку; +- `telemetry_check` - виконуваний файл; +- `telemetry_check` лінкується до `telemetry`; +- `homework_05` підключається у кореневому `CMakeLists.txt`. + +Після виправлення `good.txt` має друкувати підсумок, а проблемні вхідні файли +не мають призводити до аварійного падіння. Для некоректних вхідних даних +очікується коротке повідомлення про помилку у `stderr` і ненульовий код +завершення. diff --git a/homework_05/data/bad_invalid_number.txt b/homework_05/data/bad_invalid_number.txt new file mode 100644 index 0000000..a85629c --- /dev/null +++ b/homework_05/data/bad_invalid_number.txt @@ -0,0 +1,3 @@ +0 1 24.8 3.1 41.0 1 14 +100 2 24.7 not_a_number 41.4 1 14 +200 3 24.6 3.3 41.8 1 13 diff --git a/homework_05/data/bad_missing_field.txt b/homework_05/data/bad_missing_field.txt new file mode 100644 index 0000000..e688f46 --- /dev/null +++ b/homework_05/data/bad_missing_field.txt @@ -0,0 +1,3 @@ +0 1 24.8 3.1 41.0 1 14 +100 2 24.7 3.2 41.4 1 +200 3 24.6 3.3 41.8 1 13 diff --git a/homework_05/data/bad_zero_delta.txt b/homework_05/data/bad_zero_delta.txt new file mode 100644 index 0000000..2b8f828 --- /dev/null +++ b/homework_05/data/bad_zero_delta.txt @@ -0,0 +1,2 @@ +0 1 24.8 3.1 41.0 1 14 +0 2 24.7 3.2 41.4 1 14 diff --git a/homework_05/data/empty.txt b/homework_05/data/empty.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/homework_05/data/empty.txt @@ -0,0 +1 @@ + diff --git a/homework_05/data/good.txt b/homework_05/data/good.txt new file mode 100644 index 0000000..e097051 --- /dev/null +++ b/homework_05/data/good.txt @@ -0,0 +1,4 @@ +0 1 24.8 3.1 41.0 1 14 +100 2 24.7 3.2 41.4 1 14 +200 3 24.6 3.3 41.8 1 13 +300 4 24.5 3.4 42.2 1 13 diff --git a/homework_05/include/telemetry.hpp b/homework_05/include/telemetry.hpp new file mode 100644 index 0000000..0cdf002 --- /dev/null +++ b/homework_05/include/telemetry.hpp @@ -0,0 +1,35 @@ +#pragma once + +// Fixed-size storage keeps the starter close to the topics from block 1. +const int MAX_TELEMETRY_FRAMES = 128; + +// One telemetry sample from the input log. +struct Frame { + long timestamp_ms; + int seq; + double voltage_v; + double current_a; + double temperature_c; + int gps_fix; + int satellites; +}; + +// Aggregated values printed by the executable. +struct Summary { + int frames_total; + int frames_valid; + double voltage_min; + double voltage_max; + double temperature_avg; + int low_voltage_frames; + double frame_rate_hz; +}; + +// Reads frames from a whitespace-separated telemetry log. +int read_frames(const char* path, Frame frames[], int max_frames); + +// Calculates summary values for already parsed frames. +Summary summarize(const Frame frames[], int frame_count); + +// Prints summary in the stable homework output format. +void print_summary(const Summary& summary); diff --git a/homework_05/src/main.cpp b/homework_05/src/main.cpp new file mode 100644 index 0000000..43ec461 --- /dev/null +++ b/homework_05/src/main.cpp @@ -0,0 +1,19 @@ +#include "telemetry.hpp" + +#include + +int main(int argc, char** argv) { + // The executable expects exactly one telemetry log path. + if (argc != 2) { + std::cerr << "usage: telemetry_check \n"; + return 1; + } + + Frame frames[MAX_TELEMETRY_FRAMES]; + const int frame_count = read_frames(argv[1], frames, MAX_TELEMETRY_FRAMES); + + const Summary summary = summarize(frames, frame_count); + print_summary(summary); + + return 0; +} diff --git a/homework_05/src/telemetry.cpp b/homework_05/src/telemetry.cpp new file mode 100644 index 0000000..9ac87fc --- /dev/null +++ b/homework_05/src/telemetry.cpp @@ -0,0 +1,153 @@ +#include "telemetry.hpp" + +#include +#include +#include + +// Debugging exercise notes: +// this file intentionally contains four runtime defects. +// The defects are related to malformed input shape, invalid numeric values, +// unsafe time deltas, and empty logs. Exact locations are not marked on purpose. + +const int EXPECTED_FIELD_COUNT = 7; +const int MAX_LINE_LENGTH = 256; + +int split_line(char line[], char* fields[], int max_fields) { + int count = 0; + char* cursor = line; + + while (*cursor != '\0' && count < max_fields) { + while (*cursor == ' ' || *cursor == '\t' || *cursor == '\n' || *cursor == '\r') { + *cursor = '\0'; + ++cursor; + } + + if (*cursor == '\0') { + break; + } + + fields[count] = cursor; + ++count; + + while (*cursor != '\0' && *cursor != ' ' && *cursor != '\t' && *cursor != '\n' && + *cursor != '\r') { + ++cursor; + } + } + + return count; +} + +long parse_long(const char* text) { + char* end = nullptr; + const long value = std::strtol(text, &end, 10); + + if (end == text) { + std::abort(); + } + + return value; +} + +int parse_int(const char* text) { + return static_cast(parse_long(text)); +} + +double parse_double(const char* text) { + char* end = nullptr; + const double value = std::strtod(text, &end); + + if (end == text) { + std::abort(); + } + + return value; +} + +Frame parse_frame(char line[]) { + char* fields[EXPECTED_FIELD_COUNT] = {}; + const int field_count = split_line(line, fields, EXPECTED_FIELD_COUNT); + (void)field_count; + + Frame frame{}; + frame.timestamp_ms = parse_long(fields[0]); + frame.seq = parse_int(fields[1]); + frame.voltage_v = parse_double(fields[2]); + frame.current_a = parse_double(fields[3]); + frame.temperature_c = parse_double(fields[4]); + frame.gps_fix = parse_int(fields[5]); + frame.satellites = parse_int(fields[6]); + return frame; +} + +double compute_frame_rate_hz(const Frame frames[], int frame_count) { + const long elapsed_ms = frames[frame_count - 1].timestamp_ms - frames[0].timestamp_ms; + + return static_cast((frame_count - 1) * 1000 / elapsed_ms); +} + +int read_frames(const char* path, Frame frames[], int max_frames) { + std::ifstream input{path}; + if (!input) { + std::cerr << "error: failed to open input file: " << path << '\n'; + return 0; + } + + int frame_count = 0; + char line[MAX_LINE_LENGTH]; + + while (input.getline(line, MAX_LINE_LENGTH)) { + if (line[0] == '\0') { + continue; + } + + if (frame_count < max_frames) { + frames[frame_count] = parse_frame(line); + ++frame_count; + } + } + + return frame_count; +} + +Summary summarize(const Frame frames[], int frame_count) { + Summary summary{}; + summary.frames_total = frame_count; + summary.frames_valid = frame_count; + summary.voltage_min = frames[0].voltage_v; + summary.voltage_max = frames[0].voltage_v; + summary.low_voltage_frames = 0; + + double temperature_sum = 0.0; + + for (int i = 0; i < frame_count; ++i) { + if (frames[i].voltage_v < summary.voltage_min) { + summary.voltage_min = frames[i].voltage_v; + } + + if (frames[i].voltage_v > summary.voltage_max) { + summary.voltage_max = frames[i].voltage_v; + } + + temperature_sum += frames[i].temperature_c; + + if (frames[i].voltage_v < 22.0) { + ++summary.low_voltage_frames; + } + } + + const int temperature_tenths = static_cast(temperature_sum * 10.0) / frame_count; + summary.temperature_avg = static_cast(temperature_tenths) / 10.0; + summary.frame_rate_hz = compute_frame_rate_hz(frames, frame_count); + return summary; +} + +void print_summary(const Summary& summary) { + std::cout << "frames_total " << summary.frames_total << '\n'; + std::cout << "frames_valid " << summary.frames_valid << '\n'; + std::cout << "voltage_min " << summary.voltage_min << '\n'; + std::cout << "voltage_max " << summary.voltage_max << '\n'; + std::cout << "temperature_avg " << summary.temperature_avg << '\n'; + std::cout << "low_voltage_frames " << summary.low_voltage_frames << '\n'; + std::cout << "frame_rate_hz " << summary.frame_rate_hz << '\n'; +} diff --git a/preps/README.md b/preps/README.md index 573c3c6..8914ca9 100644 --- a/preps/README.md +++ b/preps/README.md @@ -5,17 +5,47 @@ ## Кроки 1. **Поставити Docker** - див. [linux.md](linux.md) / [windows.md](windows.md) / [macos.md](macos.md) для своєї ОС. -2. **Склонувати репо курсу** (гілка `block-2-lesson-1`). Команди в тому ж OS-файлі, секція "Як взяти код курсу". На Windows - клон йде **в WSL**, не на `C:\`. -3. **Відкрити у VS Code**: `code .` з Ubuntu-терміналу після клону (не з Windows Explorer - `devcontainer.json` цього проекту зламається без WSL-сесії). -4. **Встановити Dev Containers extension**: VS Code запропонує сам (із `.vscode/extensions.json`). Якщо ні - `Ctrl+Shift+X` (на macOS `Cmd+Shift+X`), пошук `Dev Containers`, Install. -5. **Reopen in Container**: VS Code запропонує - погодитись. Перший білд 3-5 хв. +2. **Створити власний репо з template** через `Use this template` на GitHub. +3. **Склонувати власний репо**. Команди в тому ж OS-файлі, секція "Як взяти код". На Windows - клон йде **в WSL**, не на `C:\`. +4. **Відкрити у VS Code**: `code .` з Ubuntu-терміналу після клону (не з Windows Explorer - `devcontainer.json` цього проекту зламається без WSL-сесії). +5. **Встановити Dev Containers extension**: VS Code запропонує сам (із `.vscode/extensions.json`). Якщо ні - `Ctrl+Shift+X` (на macOS `Cmd+Shift+X`), пошук `Dev Containers`, Install. +6. **Reopen in Container**: VS Code запропонує - погодитись. Перший білд 3-5 хв. ## Якщо вже клонували репо раніше -Якщо репо клоновано до нових оновлень (фікси для devcontainer) - підтягнути зміни. В Ubuntu/shell-терміналі: +Якщо репо клоновано до нових оновлень - дочекатися Slack-анонсу від лектора. +Курс-репо створене як GitHub Template, тому звичайний `git pull` з upstream +не працює надійно для студентських копій. + +Для підтягування snapshot-а курс-репо без затирання ДЗ 4: + +```bash +tmp_dir=$(mktemp -d) + +git clone --depth 1 \ + git@github.com:robot-dreams-code/C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY.git \ + "$tmp_dir/course" + +( + cd "$tmp_dir/course" + tar --exclude='./.git' --exclude='./homework_04' -cf - . +) | tar -xf - + +rm -rf "$tmp_dir" + +git status +git add . +git commit -m "chore: sync course repository updates" +``` + +`git status` перед комітом потрібен як перевірка, що `homework_04/` не +затерто, а оновлення tooling/devcontainer/CMake/demo/homework_05 підтягнуто. + +Якщо клонована саме власна копія репозиторію, локальні зміни з неї +підтягуються як звичайно: ```bash -cd ~/projects/C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY +cd ~/projects/cpp-miltech git pull ``` @@ -32,9 +62,11 @@ git pull Всередині контейнера (VS Code terminal): ```bash -cmake -S . -B build -cmake --build build -./build/app +cmake --preset debug +cmake --build --preset debug +./build/debug/homework_04/ugv_odometry homework_04/data/straight.txt ``` -Має вивести `Hello, section 2!`. Якщо не працює - в чат курсу. +Перші дві команди мають завершитись без помилки. Остання команда запускає +стартовий executable для ДЗ 4; у стартовій версії він може нічого не друкувати, +бо реалізація ще є частиною домашнього завдання. Якщо збірка не працює - в чат курсу. diff --git a/preps/devcontainers-cli.md b/preps/devcontainers-cli.md index 1754da9..4acfd70 100644 --- a/preps/devcontainers-cli.md +++ b/preps/devcontainers-cli.md @@ -26,8 +26,8 @@ devcontainer exec --workspace-folder . bash # зайти в контейне Одна команда без входу в shell: ```bash -devcontainer exec --workspace-folder . cmake -S . -B build -devcontainer exec --workspace-folder . cmake --build build +devcontainer exec --workspace-folder . cmake --preset debug +devcontainer exec --workspace-folder . cmake --build --preset debug ``` Після зміни `Dockerfile`: diff --git a/preps/linux.md b/preps/linux.md index 6ee61f6..f95ea89 100644 --- a/preps/linux.md +++ b/preps/linux.md @@ -22,7 +22,10 @@ sudo systemctl enable docker docker run hello-world ``` -## Як взяти код курсу +## Як взяти код + +Спочатку створити власний репозиторій через `Use this template` на сторінці +курс-репо в GitHub. Потім клонувати власний репозиторій: ```bash sudo apt update && sudo apt install -y git # або еквівалент вашого пакетного менеджера @@ -32,8 +35,8 @@ git config --global user.email "you@example.com" mkdir -p ~/projects cd ~/projects -git clone --branch block-2-lesson-1 https://github.com/robot-dreams-code/C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY.git -cd C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY +git clone https://github.com//cpp-miltech.git +cd cpp-miltech code . ``` diff --git a/preps/macos.md b/preps/macos.md index e6a11ff..f0d3808 100644 --- a/preps/macos.md +++ b/preps/macos.md @@ -36,7 +36,10 @@ brew services start colima На Apple Silicon (M1/M2/M3/M4) все запускається нативно як `arm64`. Якщо раптом попаде образ тільки `amd64` - `docker run --platform linux/amd64 ...` (повільніше, через qemu). -## Як взяти код курсу +## Як взяти код + +Спочатку створити власний репозиторій через `Use this template` на сторінці +курс-репо в GitHub. Потім клонувати власний репозиторій: ```bash git config --global user.name "Your Name" @@ -44,8 +47,8 @@ git config --global user.email "you@example.com" mkdir -p ~/projects cd ~/projects -git clone --branch block-2-lesson-1 https://github.com/robot-dreams-code/C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY.git -cd C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY +git clone https://github.com//cpp-miltech.git +cd cpp-miltech code . ``` diff --git a/preps/windows.md b/preps/windows.md index b1dcf63..210b7c2 100644 --- a/preps/windows.md +++ b/preps/windows.md @@ -46,9 +46,11 @@ docker run hello-world **Важливо:** код тримати в `~/projects/...` всередині WSL, не на `C:\`. Через `/mnt/c/` IO в рази повільніше, VS Code буде нестерпно гальмувати. -## Як взяти код курсу +## Як взяти код -Клон йде в WSL (не на `C:\`). В Ubuntu-терміналі: +Спочатку створити власний репозиторій через `Use this template` на сторінці +курс-репо в GitHub. Потім клонувати власний репозиторій у WSL (не на `C:\`). +В Ubuntu-терміналі: ```bash sudo apt update && sudo apt install -y git @@ -58,8 +60,8 @@ git config --global user.email "you@example.com" mkdir -p ~/projects cd ~/projects -git clone --branch block-2-lesson-1 https://github.com/robot-dreams-code/C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY.git -cd C-PLUS-PLUS-FOR-MILITARY-TECHNOLOGY +git clone https://github.com//cpp-miltech.git +cd cpp-miltech code . ```