Как известно любая большая программа на Си содержит много программных компонентов и, как следствие, много настроек: констант, макросов, конфигурационных структур и прочего. Всё это можно назвать одним словом: конфиги.
Все передают конфиги по-разному. Это один из религиозных аспектов в программировании микроконтроллеров.
1--Junior разработчики прописываю (про)hard(коженные) константы в каждом файле проекта или пихают всё в config.h, который потом вручную подключают #include(ом) во все *.с файлы,
2--Middle программисты передают конфиги через переменные окружения, которые определяют в скриптах сборки (Make, Cmake и т.п.).
3--Senior(ы) вообще передают конфиги через Device Tree и механизм Kconfig.
Тут мы не будет рассуждать как лучше и правильнее передавать конфиги в сборки. Поговорим лишь о том как поступать во втором случае, когда конфиги прописаны как переменные окружения в отдельном make файле по имени config.mk.
Терминология
Конфиг - набор определенных переменных окружения, которые используются при сборке программы (например на Си).
Цель - последовательность процессов, которые запускает утилита make, чтобы получить какой-то файл или просто выполнить действие.
Сборка - это папка с настройками конкретного проекта программы и артефакты. Обычно сборка это комплект следующих файлов: Makefile, config.mk, version.h. Если Вы работаете в Eclipse, то к файлам сборки также относятся настройки текстового редактора Eclipse для этой конкретной сборки. Это файлы *.project *.cproject. Всё перечисленное следует подвергать версионному контролю (например в GIT). Сборка порождает артефакты. Это файлы *.hex, *.bin, *.map, *.elf. Хорошей практикой считается, когда в папке со сборкой даже нет ни одного *.с файлика! Всё, что нужно сборка берёт из общей пере используемой кодовой базы, которая лежит в другой папке.
Пролог
У нас в репозитории есть много сборок (порядка 350) на все случаи жизни, где конфиги передаются прямо через переменные окружения прописанные в make скриптах.
У каждой сборки есть файл config.mk в котором перечислены программные компоненты из которых должна собираться эта конкретная сборка. Содержимое этого файла обычно выглядит так.
#Do not include .mk files here. That is general global configuration
ADC=Y
ADC_ISR=Y
ADT=Y
ALLOCATOR=Y
ARRAY=Y
ASICS=Y
....
TASK=Y
TBFP=Y
TERMINAL=Y
TIME=Y
UART0=Y
UART2=Y
UNIT_TEST=Y
UTILS=Y
UWB=Y
Это просто атомарные строчки. Тут происходит определение переменных окружения. Декларативно перечисляется из чего собирать прошивку. У другой сборки есть свой такой же декларативный config.mk файл и свой собственный набор переменных окружения.
В чем проблема?
1--Проблема в том, что, когда сборок становится слишком много, то их приходится создавать по образу и подобию других сборок. Потом часто возникает потребность сравнить конфиги двух разных сборок и выясняется, что конфиги сильно отличаются.
Причем они отличаются не потому, что состоят из разных программных компонентов, а потому, что строчки в конфиг файлах перечислены в случайном порядке, перемешаны (не отсортированы). Как известно, правда в том, что минимальный diff будет у двух отсортированных файлов.
2--При командной разработке разные разработчики не глядя на существующий конфиг повторно добавляют новые строчки переменных окружения, которые дублируют старые переменные окружения. Иногда они добавляют вперед и их переменная окружения не применяется. Переменная окружения перетирается предшественником. Если же добавляют переменную окружения в конец файла кофига, то она обнуляет предыдущую переменную окружения. В общем это порождает анархию и взаимную ненависть.
Когда конфиг отсортирован, то его проще просматривать и искать в нем что-то. Также легче найти место для вставки новой переменной окружения методом визуального бинарного поиска.
В связи с этим возникают две задачи:
1--Удалить повторяющиеся переменные окружения.
2--Отсортировать переменные окружения по алфавиту внутри файла config.mk.
Как отсортировать конфиги?
1--Понятное дело, что вручную никто не хочет сортировать 150 строчек в файле. Дело в том что очень мало программистов вообще знают сколько букв содержится в английском алфавите и еще меньше людей могут сходу взять карандаш и правильно написать порядок букв английского алфавита от A до Z. И это нормально! Надо просто автоматизировать процесс сортировки файла.
2--Можно написать отдельный *.bat файл для сортировки когфигов культовой консольной утилитой sort.exe. Утилиту sort можно установить из пакета CygWin. Однако вам придется при добавлении каждой новой сборки также прописывать новую строчку в этом *.bat файле сортировки. По факту оказалось, что людям просто лень этим заниматься. И это тоже нормально!
3--Самое правильное решение - это встроить сортировку конфигов в сам процесс сборки артефактов. К счастью, мы традиционно используем систему сборки GNU Make и тут это сделать очень просто. Для этого надо всего лишь определить ещё одну крохотную цель sort_config.
SORTER_TOOL=C:/cygwin64/bin/sort.exe
$(info Sort Program config)
$(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))
PROJECT_DIR=$(MK_PATH_WIN)
CONFIG_FILE=$(PROJECT_DIR)/config.mk
$(info CONFIG_FILE=$(CONFIG_FILE))
#CONFIG_FILE:=$(subst /cygdrive/c/,C:/, $(CONFIG_FILE))
#$(info CONFIG_FILE=$(CONFIG_FILE))
sort_config: $(CONFIG_FILE)
$(info SortConfig...)
$(SORTER_TOOL) -u $(CONFIG_FILE) -o $(CONFIG_FILE)
Тут опция -u означает, что утилита sort будет удалять повторения, а конструкция $(CONFIG_FILE) -o $(CONFIG_FILE) означает, что имя файла на выходе будет совпадать с именем файла на входе. Получается сортировка in place.
Затем надо открыть основной rule.mk и встроить туда новую цель sort_config. Перед сборкой сорцов отсортировать конфиг
EXTRA_TARGETS += sort_config
EXTRA_TARGETS += $(DEPENDENCY_GRAPH)
EXTRA_TARGETS += $(STATIC_ANALYSIS_TARGET)
EXTRA_TARGETS += $(CLI_COMMAMD_LIST_GENERATE)
EXTRA_TARGETS += $(PREPROCESS_CODE_BASE)
EXTRA_TARGETS += generate_definitions
# default action: build all
all: $(EXTRA_TARGETS) $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
generate_definitions:
cpp $(CPP_FLAGS) $(WORKSPACE_LOC)empty_sourse.c -dM -E> c_defines_generated.h
Достоинство rule.mk в том, что он общий на все 150-350 сборок в нашем репозитории. И нам не нужно ничего больше прописывать в настройках каждого проекта, как если бы мы программировали микроконтроллер в пресловутом IAR или Keil.
Теперь если Вы откроете папку сборки в консоли и напишите make all, Вы через минуту получите не только артефакты, но и аккуратный причёсанный конфиг!
Сортировка нужна не только в сортировке конфигов. Автоматический форматировщик отступов в Си коде clang-format.exe, например, еще сортирует #include по алфавиту.
Общая канва в том, что в программировании надо сортировать всё, что перечисляется и где порядок не имеет существенного значения. Это перечисление прототипов фунцнкий, перечисление констант emumeratios, перечисление include(ов), перечисление конфигов и т.п.
Итоги
Сортировка всегда хороша, когда от нее есть польза, например как тут это дает сокращение времени поиска сравнения конфигов и исключает ошибки на фазе прописывания этих самых конфигов.
При этом сортировка это признак наличия какого-то разума и культуры. Например посвящённые страны сортируют даже бытовой мусор.
Как видите, использование сборки из-под скриптов позволяет Вам помимо самой сборки артефактов еще и выполнять всяческую инфраструктурную DevOps(овую) работу. Это особенно просто и легко делается в GNU Make.
GNU Make вообще всё равно с каким языком программирования работать. Более того, GNU Make всё равно даже какие консольные утилиты запускать.
Links
https://ru.wikipedia.org/wiki/Sort
Генерация зависимостей внутри программы
https://habr.com/ru/articles/765424/
Сборка firmware для CC2652 из Makefile
https://habr.com/ru/articles/726352/
Почему Важно Собирать Код из Скриптов
https://habr.com/ru/articles/723054/
Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB
https://habr.com/ru/articles/673522/
Комментарии (9)
randomsimplenumber
03.12.2023 17:24+1Няп проблему, в вашей системе могут существовать невалидные конфиги. Причем признак невалидности не сортировка а наличие дубликатов. Ну так и проверяйте на дубликаты; 3-строчник на python в помощь ;)
iig
03.12.2023 17:24Не понял, зачем сортировать конфиг во премя сборки. Ну, переставили вы в нем строки. Компилятору все равно. Ну, убрали полные дубликаты VAR=VALUE. Компилятору все равно. VAR=VALUE2 и VAR=VALUE1 поменяли порядок. Компилятору опять все равно, но сборка на сортированном конфиге отличается от сборки на несортированном. Я бы назвал это утонченной диверсией ;)
aabzel Автор
03.12.2023 17:24Сортировка конфига нужна для минимального diff (a) при сравнении конфигов в программе winmerge
iig
03.12.2023 17:24А как связана сборка и winmerge?
aabzel Автор
03.12.2023 17:24-1Связана тем, что конфиги надо часто сравнивать в утилите winmerge ?
iig
03.12.2023 17:24-1конфиги надо часто сравнивать в утилите winmerge
Допустим. Для того, чтобы сравнить 2 конфига - нужно сначала прогнать 2 сборки - я правильно понял? ;) А если добавить слово часто - нужно сделать много сборок, правильно?
Apoheliy
Для задачи как бы напрашивается вариант с валидацией конфиги в момент коммита/мёрджа/др.: если строчки не отсортированы или есть дубликаты, то в хранилище кода (и в сборке) такой файл не появится.
Расскажете, чем Вам не зашло такое решение? Пробовались ли другие варианты?
aabzel Автор
Это слишком сложно.
randomsimplenumber
Зато надежно и делается 1 раз. devops так бы и сделал; написал 3 строчки в gitlab-ci.yml и ушел пить смузи