Как стать автором
Обновить

Генерация зависимостей внутри программы

Уровень сложностиПростой
Время на прочтение7 мин
Количество просмотров4.3K

Бывают ситуации, когда к разработке firmware надо подключить новых людей. Как объяснить им архитектуру нынешнего ПО?

Глобально в каждом Firmware репозитории весь код можно отсортировать на такие мега-части как sensitivity, connectivity, control, computing , security и storage.

Код из каждой части может быть как-то связан с частями из других программных компонентов. В программировании микроконтроллеров программы по-хорошему должны строится иерархично. То есть один программный компонент вызывает функции из другого программного компонента.
Например драйверу чтения SD‑карты нужны функции от драйвера SPI, драйвера GPIO, компонента CRC7, CRC16 и т. п.

Как бы представить эту взаимосвязь для каждой конкретной сборки прошивки? Очевидно, что надо нарисовать граф. То есть, картинку, где стрелочки и прямоугольники покажут как всё взаимно связано. И тут-то на помощь нам как раз и приходит язык разметки графов Graphviz.

Для кода на языке Graphviz можно сделать полуавтоматический кодо генератор. Надо обязать программиста, чтобы в папке каждого программного компонента был крохотный файлик *.gvi для явного указания высокоуровневых зависимостей. Для простоты поддержки этот *.gvi файл надо называть так же как имя папки в которой лежит *.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). Препроцессору всё равно с каким кодом работать. Препроцессор просто вставляет и заменяет куски текста. Утилиту cpp.exe можно вообще порекомендовать писателям учебников или чертёжникам.

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

#CC=C:/cygwin64/bin/dot.exe
$(info Generate Dependencies)

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

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

$(info WORKSPACE_LOC=$(WORKSPACE_LOC))
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
#DOT_OPT +=-L10
#DOT_OPT +=-v 
#LAYOUT_ENGINE = -Kneato 
#LAYOUT_ENGINE = -Kfdp 
#LAYOUT_ENGINE = -Ksfdp
#LAYOUT_ENGINE = -Ktwopi
#LAYOUT_ENGINE = -Kosage
#LAYOUT_ENGINE = -Kpatchwork
LAYOUT_ENGINE = -Kdot
DEPENDENCY_GRAPH += generate_dep

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

#clean:
#	$(info clean)
#	rm $(MK_PATH)/artefacts/*.*


#$(ART_SVG):$(ART_SVG)

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

DEPENDENCY_GRAPH=

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

# default action: build all
all: generate_definitions $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin $(DEPENDENCY_GRAPH)

generate_definitions:
	cpp $(CPP_FLAGS) $(WORKSPACE_LOC)empty_sourse.c -dM -E> c_defines_generated.h

далее в основном 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.exe как и cpp.exe всё равно для какого языка программирования Вы её вызвали. Make.exe — это просто дирижёр программного конвейера.

  4. Для каждой отдельной сборки строится её собственный граф зависимостей. Лишний код из соседней папки не участвует.

  5. Граф строится автоматически по коду из *.gv файла. Мышка тут абсолютно не нужна.

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

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

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

  3. Автоматический трассировщик графа от Graphviz не всегда удачно разводит граф

пример не самой удачной разводки графа движком osage
пример не самой удачной разводки графа движком osage

Вывод

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

Стоит отметить, что получившееся дерево зависимостей программных компонентов поймут скорее только программисты-микроконтроллеров нежели схемотехники. Чтобы читать дерево зависимостей надо примерно знать основы Computer Science. Надо понимать, что такое AES256, BASE64, CRC, CLI, CSV, FIFO, LIFO, DFT, FFT, XLM и прочие акронимы.

А вообще как по мне, дак самая лучшая документация к коду - это модульные тесты. https://habr.com/ru/articles/698092/

В модульных тестах сразу видно как заполнять прототипы функций и что делать с результатом работы функции.

Еще голый код и утилита grep при определенной сноровке могут позволить извлечь полезные сведения о коде.

Вариантов много.

У меня есть еще один текст про документацию. Это схема ToolChain(а). Про это можно почитать тут https://habr.com/ru/articles/688542/ . Тоже весьма полезная вещь для понимания происходящего под капотом.

Словарь

Акроним

Расшифровка

GVI

Graphviz Include

Links

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы составляете дерево зависимоcтей для своих программ?
21.05% да8
78.95% нет30
Проголосовали 38 пользователей. Воздержались 7 пользователей.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы делаете автоматическую генерацию документации для программ?
41.46% да17
58.54% нет24
Проголосовал 41 пользователь. Воздержались 3 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы вообще пишите документацию для программ?
65.85% да27
34.15% нет14
Проголосовал 41 пользователь. Воздержались 3 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы использовали язык Graphviz для чего-либо?
47.62% да20
52.38% нет22
Проголосовали 42 пользователя. Воздержались 5 пользователей.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы поняли представленный механизм построения программных зависимостей?
75% да6
25% нет2
Проголосовали 8 пользователей. Воздержался 1 пользователь.
Теги:
Хабы:
Всего голосов 14: ↑10 и ↓4+11
Комментарии48

Публикации

Истории

Работа

DevOps инженер
51 вакансия
Программист С
32 вакансии

Ближайшие события

19 сентября
CDI Conf 2024
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн