Генерация Зависимостей Внутри Программы

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Бывают ситуации, когда к разработке firmware надо подключить новых людей. Как объяснить им архитектуру нынешнего ПО? В программировании микроконтроллеров программы часто строятся иерархично. То есть один программный компонент вызывает функции из другого программного компонента. Например драйверу чтения SD-карты нужны функции от драйвера SPI, драйвера GPIO, компонента CRC7, CRC16. Как бы представить эту взаимосвязь для каждой конкретной сборке прошивки? Очевидно, что надо нарисовать граф. То есть картинку, где стрелочки и прямоугольники покажут как всё связано. И тут на помощь приходит язык разметки графов Graphviz.

Для кода на языке Graphviz можно сделать полуавтоматический кодо генератор. Надо обязать программиста, чтобы в папке каждого программного компонента был крохотный файлик *.gvi для явного указания высокоуровневых зависимостей. Смотришь в *.c код, примерно видишь, что там подключается #include “*.h“, что вызывается в самих функциях и отражаешь это в *.gvi файле рядом. Вот примерное содержимое для pdm.gvi. PDM - это программный компонент для записи звука из MEMS микрофонов.

GPIO->PDM
NVIC->PDM
REG->PDM
DFT->PDM
RAM->PDM
AUDIO->PDM
DMA->PDM

В этом *.gvi файле надо вручную прописать какие у данного программного компонента есть зависимости от других программных компонентов. Сделать это можно на простеньком текстовом языке Graphviz. По факту, всё что понадобится из синтаксиса языка Graphviz - это оператор стрелка “->”

Также нужен корневой файл main.gvi в который будет всё вставляться утилитой препроцессором (cpp.exe).

strict digraph graphname {
    rankdir=LR;
    splines=ortho
    node [shape="box"];

#ifdef HAS_BSP
    #include "bsp.gvi"
#endif    

#ifdef HAS_THIRD_PARTY
    #include "third_party.gvi"
#endif    

#ifdef HAS_PROTOCOLS
    #include "protocols.gvi"
#endif    

#ifdef HAS_ADT
    #include "adt.gvi"
#endif

#ifdef HAS_ASICS
    #include "asics.gvi"
#endif

#ifdef HAS_MCU
    #include "mcu.gvi"
#endif

#ifdef HAS_COMMON
    #include "common.gvi"
#endif

    #include "components.gvi"

#ifdef HAS_UTILS
    #include "utils.gvi"
#endif

#ifdef HAS_CORE
    #include "core.gvi"
#endif

#ifdef HAS_DRIVERS
    #include "drivers.gvi"
#endif

#ifdef HAS_INTERFACES
    #include "interfaces.gvi"
#endif
}

Также нужен makefile скрипт, который будет собирать все отдельные файлы с зависимостями в один единый файл на языке Graphviz. План такой. Надо организовать вот такой программный конвейер.

Заметьте, тут работает самый обыкновенный препроцессор из программ на Си (утилита cpp). Препроцессору всё равно с каким кодом работать. Препроцессор просто вставляет и заменяет куски текста.

Вот сам generate_dependencies.mk скрипт, который определяет ToolChain для построения изображения в привычном *.pdf файле.

$(info Generate Dependencies)

CC_DOT="C:/Program Files/Graphviz/bin/dot.exe"
RENDER="C:/Program Files/Google/Chrome/Application/chrome.exe"

MK_PATH_WIN := $(subst /cygdrive/c/,C:/, $(MK_PATH))
ARTEFACTS_DIR=$(MK_PATH_WIN)$(BUILD_DIR)
$(info ARTEFACTS_DIR=$(ARTEFACTS_DIR))

SOURCES_DOT=$(WORKSPACE_LOC)main.gvi
$(info SOURCES_DOT=$(SOURCES_DOT))

SOURCES_DOT:=$(subst /cygdrive/c/,C:/, $(SOURCES_DOT))
$(info SOURCES_DOT=$(SOURCES_DOT))

SOURCES_DOT_RES += $(ARTEFACTS_DIR)/$(TARGET)_dep.gv
$(info SOURCES_DOT_RES=$(SOURCES_DOT_RES))

ART_SVG = $(ARTEFACTS_DIR)/$(TARGET)_res.svg
ART_PDF = $(ARTEFACTS_DIR)/$(TARGET)_res.pdf

$(info ART_SVG=$(ART_SVG) )
$(info ART_PDF=$(ART_PDF) )

CPP_GV_OPT += -undef
CPP_GV_OPT += -P
CPP_GV_OPT += -E
CPP_GV_OPT += -nostdinc

CPP_GV_OPT += $(OPT)

DOT_OPT +=-Tsvg
LAYOUT_ENGINE = -Kdot

preproc_graphviz:$(SOURCES_DOT) 
	$(info Preproc...)
	mkdir $(ARTEFACTS_DIR)
	cpp $(SOURCES_DOT)  $(CPP_GV_OPT) $(INCDIR) -E -o $(SOURCES_DOT_RES)

generate_dep_pdf: preproc_graphviz
	$(info route graph...)
	$(CC_DOT) -V
	$(CC_DOT) -Tpdf $(LAYOUT_ENGINE) $(SOURCES_DOT_RES) -o $(ARTEFACTS_DIR)/$(TARGET).pdf
  
generate_dep_svg: preproc_graphviz
	$(info route graph...)
	$(CC_DOT) -V
	$(CC_DOT) $(DOT_OPT) $(SOURCES_DOT_RES) -o $(ARTEFACTS_DIR)/$(TARGET).svg

generate_dep:  generate_dep_svg generate_dep_pdf
	$(info All)

print_dep: generate_dep
	$(info print_svg)
	$(RENDER) -open $(ARTEFACTS_DIR)/$(TARGET).svg
	$(RENDER) -open $(ARTEFACTS_DIR)/$(TARGET).pdf


Скрипт generate_dependencies.mk следует условно подключить к основному скрипту сборки проекта rules.mk

ifeq ($(DEPENDENCIES_GRAPHVIZ), Y)
    include $(WORKSPACE_LOC)/generate_dependencies.mk
endif

далее в основном make файле определить переменную окружения DEPENDENCIES_GRAPHVIZ=Y

MK_PATH:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
#@echo $(error MK_PATH=$(MK_PATH))
WORKSPACE_LOC:=$(MK_PATH)../../

INCDIR += -I$(MK_PATH)
INCDIR += -I$(WORKSPACE_LOC)

DEBUG=Y
TARGET=board_name_build_name
DEPENDENCIES_GRAPHVIZ=Y

include $(MK_PATH)config.mk

ifeq ($(CLI),Y)
    include $(MK_PATH)cli_config.mk
endif

ifeq ($(DIAG),Y)
    include $(MK_PATH)diag_config.mk
endif

ifeq ($(UNIT_TEST),Y)
    include $(MK_PATH)test_config.mk
endif

include $(WORKSPACE_LOC)code_base.mk
include $(WORKSPACE_LOC)rules.mk
 

Теперь достаточно просто открыть консоль и набрать make all и вместе с артефактами с прошивкой у Вас рядом появится и файлы документации с изображением зависимостей. Скрипт сборки сгенерирует вот такой финальный Graphviz код

strict digraph graphname {
    rankdir=LR;
    splines=ortho
    node [shape="box"];
REG->ADC
NVIC->ADC
REG->FLASH
REG->GPIO
REG->TIMER
TIMER->TIME
REG->I2S
NVIC->I2S
SW_DAC->I2S
NVIC->I2C
GPIO->I2C
REG->I2C
FLASH->NVS
GPIO->PDM
NVIC->PDM
REG->PDM
DFT->PDM
RAM->PDM
AUDIO->PDM
DMA->PDM
GPIO->SPI
NVIC->SPI
SYSTICK->TIME
GPIO->UART
UART->LOG
LOG->CLI
    CLI->PROTOCOL
CRC8->TBFP
RAM->ARRAY
SPI->DW1000
GPIO->DW1000
TIME->DW1000
DW1000->DWM1000
CRC7->SD_CARD
CRC16->SD_CARD
SPI->SD_CARD
GPIO->SD_CARD
TIME->SD_CARD
DW1000->DECADRIVER
TIME->DECADRIVER
GPIP->DECADRIVER
SPI->DECADRIVER
I2S->MAX98357
GPIO->MAX98357
SD_DAC->MAX98357
NVIC->CORTEX_M33
SYSTICK->CORTEX_M33
}

В качеств примера у Вас получится примерно вот такой граф зависимостей.

Для расширения детализации дерева зависимостей надо просто добавлять новые *.gvi файлы. Их будет много (десятки) но они простые как правило по 3-6 строчек в каждом. В каждой папке с кодом должен лежать один *.gvi файл.

Вот, например, граф зависимости программных компонентов для прошивки хранителя паролей Pas~ r1.1

Достоинства графа зависимостей

1--Хорошая документация поможет быстро ввести в курс дела новых людей.

2--Граф зависимостей позволит выявить паразитные зависимости и оптимизировать архитектуру программы.

3--Автогенератор зависимостей легко встраивается в сборку, если система сборки предварительно написана на make скриптах, так как утилита make она, в сущности, всеядная. Утилите make как и cpp всё равно для какого языка программирования Вы её вызвали. Make - это просто дирижёр программного конвейера.

Недостатки графа зависимостей

1--Надо писать makefile надо освоить спецификацию GNU make (т.е. просмотреть по диагонали 200 страниц). Если Вы всё еще в 2023м собираете прошивки из GUI-IDE, то могу вам только посоветовать позвонить в техподдержку вашей IDE.

2--Надо вручную прописать *.gvi файл для каждого программного компонента.

Вывод

Как видите, сборка из скриптов позволяет Вам помимо получения бинарных артефактов (*.bin, *.hex, *.map, *.elf) также авто генерировать всяческую документацию. Например такую полезную схему как дерево зависимостей между программными компонентами. Это является хорошей причиной, чтобы собирать прошивки не из GUI-IDE, а из самописных скриптов.

Словарь

Акроним

Расшифровка

GVI

Graphviz Include

Links

https://dreampuf.github.io/GraphvizOnline/

Синергия Graphviz и препроцессора C/C++ https://habr.com/ru/articles/499170/

Почему Важно Собирать Код из Скриптов https://habr.com/ru/articles/723054/

Тандем Cpp/Dot для Описания Сложных ToolСhain(ов) https://habr.com/ru/articles/688542/

Язык Graphviz для Автогенерации Блок-Схем Сложных Электронных Цепей
https://habr.com/ru/articles/682346/

Задача про две ёмкости для жидкости https://habr.com/ru/articles/662561/

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы составляете дерево зависимоcтей для своих программ?
66.67% да 2
33.33% нет 1
Проголосовали 3 пользователя. Воздержавшихся нет.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы делаете автоматическую генерацию документации для программ?
33.33% да 1
66.67% нет 2
Проголосовали 3 пользователя. Воздержавшихся нет.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы вообще пишите документацию для программ?
100% да 3
0% нет 0
Проголосовали 3 пользователя. Воздержавшихся нет.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы использовали язык Graphviz для чего-либо?
66.67% да 2
33.33% нет 1
Проголосовали 3 пользователя. Воздержавшихся нет.
Источник: https://habr.com/ru/articles/765424/


Интересные статьи

Интересные статьи

В текущих проектах я применяю подход Behavior Driven Development (BDD) для написания end-to-end тестов. Хотя раньше я скептически относился к Given-When-Then синтаксису, теперь часто его использу...
Привет, Хабр!Сегодня я хотел бы рассказать о генерации идеального лабиринта - алгоритмом Эллера. Статья подойдёт всем любителям алгоритмов.
Привет, Хаброжители! Парадигма внедрения зависимостей (DI) в течение минувшего десятилетия де-факто стала одной из доминирующих на платформе .NET и теперь обязательна к изучению для всех...
Преподавательский состав магистерской программы JetBrains и ИТМО «Разработка программного обеспечения» на четверть (!) состоит из выпускников этой же программы. Пожалуй, ...
Коммуникационные тренды: взгляд из-за рубежа, 22.10.2019 Дайджест подготовлен iMARS Communications. «В эпоху digital мир вынужден адаптироваться к жизни в цифровой среде. С каждым днем по...