Как известно любая большая программа на Си содержит много программных компонентов и, как следствие, много настроек: констант, макросов, конфигурационных структур и прочего. Всё это можно назвать одним словом: конфиги.

Все передают конфиги по-разному. Это один из религиозных аспектов в программировании микроконтроллеров.

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)


  1. Apoheliy
    03.12.2023 17:24
    +4

    Для задачи как бы напрашивается вариант с валидацией конфиги в момент коммита/мёрджа/др.: если строчки не отсортированы или есть дубликаты, то в хранилище кода (и в сборке) такой файл не появится.

    Расскажете, чем Вам не зашло такое решение? Пробовались ли другие варианты?


    1. aabzel Автор
      03.12.2023 17:24

      Это слишком сложно.


      1. randomsimplenumber
        03.12.2023 17:24

        Зато надежно и делается 1 раз. devops так бы и сделал; написал 3 строчки в gitlab-ci.yml и ушел пить смузи


  1. randomsimplenumber
    03.12.2023 17:24
    +1

    Няп проблему, в вашей системе могут существовать невалидные конфиги. Причем признак невалидности не сортировка а наличие дубликатов. Ну так и проверяйте на дубликаты; 3-строчник на python в помощь ;)


  1. iig
    03.12.2023 17:24

    Не понял, зачем сортировать конфиг во премя сборки. Ну, переставили вы в нем строки. Компилятору все равно. Ну, убрали полные дубликаты VAR=VALUE. Компилятору все равно. VAR=VALUE2 и VAR=VALUE1 поменяли порядок. Компилятору опять все равно, но сборка на сортированном конфиге отличается от сборки на несортированном. Я бы назвал это утонченной диверсией ;)


    1. aabzel Автор
      03.12.2023 17:24

      Сортировка конфига нужна для минимального diff (a) при сравнении конфигов в программе winmerge


      1. iig
        03.12.2023 17:24

        А как связана сборка и winmerge?


        1. aabzel Автор
          03.12.2023 17:24
          -1

          Связана тем, что конфиги надо часто сравнивать в утилите winmerge ?


          1. iig
            03.12.2023 17:24
            -1

            конфиги надо часто сравнивать в утилите winmerge

            Допустим. Для того, чтобы сравнить 2 конфига - нужно сначала прогнать 2 сборки - я правильно понял? ;) А если добавить слово часто - нужно сделать много сборок, правильно?