Почему важно собирать С-код из MakeFile(ов)

от автора

В период с 199x по 201x развелось очень много программистов-микроконтроллеров, которые никогда не вылазили из всяческих IDE (IAR, KEIL, Code Composer Studio, AtilocTrueStudio). Как по мне, дак это очень печально. Во многом потому, что специалист в Keil не сможет быстро понять как работать в IAR и наоборот. Миграция на другую IDE тоже вызывает большую трудность, так как это сводится к мышковозне в GUI. Каждая версия IAR не совместима с более новой версией IDE.

Дело в том, что GUI IDE появились в 199x…201x, когда не было расцвета DevOps(а), программист работал один и все действия выполнялись вручную мышкой. В то время работа в GUI казалась программистам-микроконтроллеров веселее, ведь в IDE много стразиков.

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

код отдельно, конфиги отдельно

и работа с IDE стала только тормозить процессы. Ведь конфиги хранятся в IDE-шной XML(ке). Приходилось дублировать конфиги платы для каждой сборки, что использовала эту плату. Пришлось дублировать код конфигов и этот процесс сопровождался ошибками. При масштабировании работы с IDE кодовая база превращалась в зоопарк в болоте.

Какие недостатки сборки исходников из-под IDE?

1—IDE отжирают много ресурсов компьютера, как RAM как и CPU, IDE же надо много оперативки, чтобы отрисовывать окошки со стразиками.

2—IDE монолитные и неделимые. Если вы захотите поменять препроцессор, компилятор или компоновщик, а остальные фазы ToolChain(а) оставить как есть, то ничего из этого не выйдет, так как капот IDE закрыт на замок.

3—IDE стоят дорого, порядка 3500 EUR на один компьютер

4—IDEшные xml очень слабо документированы или не документированы вовсе. У всех вендоров xml имеет свой особенный язык разметки. При внесении незначительных изменений появляется огромный git diff.

5—Затруднена сборка из консоли. В основном инициировать сборку в IDE можно мышкой или горячими клавишами.

6—Обратная несовместимость с новыми версиями IDE

7—В условиях технологического эмбарго законно купить IDE европейского вендора невозможно.

В общем распространение IDE это яркий пример известного ныне «технологического диктата» запада для стран второго и третьего мира.

Мы вам даём песочницу (IDE), а вы сидите там за бортиками и лепите свои куличики (прошивки).

Понятное дело, что в таких рамках на «сделать что-то серьезное» рассчитывать не приходится.

Пришлось думать. Хорошим решением оказалось сделать шаг назад в 197x 198x когда на компьютерах всё делали из консоли. Собирать сорцы из скриптов. Можно вообще bat файл написать и он в общем-то инициирует запуск нужных утилит, однако исторически С-код собирали утилитой make.

В чем достоинства сборки С-кода из make файлов?

1— Makefile это самый гибкий способ управлять модульностью. Можно буквально одной строчкой добавлять или исключать один конкретный программный компонент (десятки файлов) из десятков сборок. В случае же сборки из-под IDE вам бы пришлось вручную редактировать .xml для каждой сборки.

2—Сборку из Makefile очень легко автоматизировать. Достаточно в консоли выполнить make all и у вас инициируется процесс сборки.

3—После сборки из скриптов вы получите полный лог сборки, в то время как IDE обычно показывают последние 3-4 экрана сообщение компилятора.

4—В MakeFile очень просто менять компиляторы. Это буквально заменить одну строчку. С GCC на Clang или на GHS. Вот типичный основной makefile для любой сборки на ARM Cortex-Mxx

mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) $(info Build  $(mkfile_path) ) BUILD_DIR = build  #@echo $(error SOURCES_C= $(SOURCES_C)) INCDIR := $(subst /cygdrive/c/,C:/, $(INCDIR)) #@echo $(error INCDIR=$(INCDIR)) SOURCES_C := $(subst /cygdrive/c/,C:/, $(SOURCES_C)) #@echo $(error SOURCES_C=$(SOURCES_C)) SOURCES_ASM := $(subst /cygdrive/c/,C:/, $(SOURCES_ASM)) LIBS  := $(subst /cygdrive/c/,C:/, $(LIBS)) LDSCRIPT := $(subst /cygdrive/c/,C:/, $(LDSCRIPT)) #@echo $(error SOURCES_ASM=$(SOURCES_ASM))  # binaries PREFIX = arm-none-eabi- GCC_PATH="C:/Program Files (x86)/GNU Arm Embedded Toolchain/10 2021.10/bin" $(info GCC_PATH=$(GCC_PATH))  # The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx) # either it can be added to the PATH environment variable. ifdef GCC_PATH   CC = $(GCC_PATH)/$(PREFIX)gcc   AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp   CP = $(GCC_PATH)/$(PREFIX)objcopy   SZ = $(GCC_PATH)/$(PREFIX)size else   CC = $(PREFIX)gcc   AS = $(PREFIX)gcc -x assembler-with-cpp   CP = $(PREFIX)objcopy   SZ = $(PREFIX)size endif HEX = $(CP) -O ihex BIN = $(CP) -O binary -S   # float-abi ifeq ($(NRF5340), Y)     ifeq ($(CORE_NET), Y)         FLOAT-ABI = -mfloat-abi=soft         OPT += -fsingle-precision-constant     endif          ifeq ($(CORE_APP), Y)         FLOAT-ABI = -mfloat-abi=hard     endif else    FLOAT-ABI = -mfloat-abi=hard endif  # mcu MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)  # macros for gcc #CSTANDARD = -std=c11 CSTANDARD = -std=gnu99 # AS defines AS_DEFS =   # AS includes AS_INCLUDES =   ifeq ($(DEBUG), Y)     #@echo $(error DEBUG=$(DEBUG))     CFLAGS += -g3 -gdwarf-2 -ggdb     OPT += -O0  else     OPT += -Os endif OPT += -fmessage-length=0     OPT += -fsigned-char OPT += -fno-common OPT += -fstack-usage OPT += -finline-small-functions  #Perform dead code elimination OPT += -fdce  #Perform dead store elimination OPT += -fdse  # compile gcc flags ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections  CFLAGS += $(CSTANDARD) CFLAGS += -Wall #CFLAGS += -Wformat-overflow=1 CFLAGS += $(MCU) $(OPT) -fdata-sections -ffunction-sections $(INCDIR)    # Generate dependency information CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"  # LDFLAGS  # libraries LINKER_FLAGS += -Xlinker --gc-sections  ifeq ($(MBR), Y)     #@echo $(error MBR=$(MBR))     LIBS += -lnosys     LDFLAGS += -specs=nano.specs else     LINKER_FLAGS += -u _scanf_float     LINKER_FLAGS += -u _printf_float endif #LINKER_FLAGS += -lrdimon --specs=rdimon.specs  ifeq ($(LIBC), Y)     #@echo $(error LIBC=$(LIBC))     LIBS += -lc endif  ifeq ($(MATH), Y)     #@echo $(error MATH=$(MATH))     LIBS += -lm  endif   #@echo $(error LDSCRIPT=$(LDSCRIPT)) LIBDIR =   LDFLAGS += $(MCU) -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections $(LINKER_FLAGS)  # default action: build all all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin   # build the application # list of objects OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(SOURCES_C:.c=.o))) vpath %.c $(sort $(dir $(SOURCES_C))) # list of ASM program objects OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(SOURCES_ASM:.S=.o))) vpath %.S $(sort $(dir $(SOURCES_ASM)))  $(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)  $(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@  $(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR) $(AS) -c $(CFLAGS) $< -o $@  $(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile $(CC) $(OBJECTS) $(LDFLAGS) -o $@ $(SZ) $@  $(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR) $(HEX) $< $@  $(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR) $(BIN) $< $@  $(BUILD_DIR): mkdir $@  # clean up clean: -rm -fR $(BUILD_DIR)    # dependencies -include $(wildcard $(BUILD_DIR)/*.d)  # *** EOF ***

5—Когда вы собираете из Make вы можете не только собирать исходники, но и собирать документацию, строить графы зависимостей на dot, построить схему ToolChain(а). Вызвать Latex, Doxyden.

Утилите make всё равно какие консольные утилиты вызывать. Это универсальный способ определения программных конвейеров.

6—Для каждой сборки надо самим писать крохотный Makefile

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)  #@echo $(error SOURCES_C=$(SOURCES_C)) include $(MK_PATH)config.mk include $(MK_PATH)cli_config.mk include $(MK_PATH)diag_config.mk include $(MK_PATH)test_config.mk  include $(WORKSPACE_LOC)code_base.mk include $(WORKSPACE_LOC)rules.mk  

и конфиг для сборки.

 mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) $(info Build  $(mkfile_path) )  TARGET=pastilda_r1_1_generic #@echo $(error TARGET=$(TARGET)) AES256=Y ALLOCATOR=Y  ......  USB_HOST_HS=Y USB_HOST_PROC=Y UTILS=Y XML=Y 

Для каждого компонента *.mk файл. Язык make простой и это, в сущности, bash. Вот типичный *.mk файл для драйвера DW1000

ifneq ($(DWM1000_MK_INC),Y)     DWM1000_MK_INC=Y      DWM1000_DIR = $(DRIVERS_DIR)/dwm1000     mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))     $(info Build $(mkfile_path) )     $(info + DWM1000)      INCDIR += -I$(DWM1000_DIR)     OPT += -DHAS_DWM1000     OPT += -DHAS_DWM1000_PROC     OPT += -DHAS_UWB      DWM1000_RANGE_DIAG=Y     DWM1000_RANGE_COMMANDS=Y      DWM1000_OTP_COMMANDS=Y     DWM1000_OTP_DIAG=Y      SOURCES_C += $(DWM1000_DIR)/dwm1000_drv.c      include $(DWM1000_DIR)/otp/dwm1000_otp.mk     include $(DWM1000_DIR)/registers/dwm1000_registers.mk      ifeq ($(DWM1000_RANGE),Y)         include $(DWM1000_DIR)/range/dwm1000_range.mk     endif      ifeq ($(DIAG),Y)         ifeq ($(DWM1000_DIAG),Y)             $(info +DWM1000_DIAG)             OPT += -DHAS_DWM1000_DIAG             SOURCES_C += $(DWM1000_DIR)/dwm1000_diag.c         endif     endif      ifeq ($(CLI),Y)         ifeq ($(DWM1000_COMMANDS),Y)             $(info +DWM1000_COMMANDS)             OPT += -DHAS_DWM1000_COMMANDS             SOURCES_C += $(DWM1000_DIR)/dwm1000_commands.c         endif     endif endif 

7—Сборка из Make стимулирует придерживаться модульности, изоляции программных компонентов и прослеживанию зависимостей между компонентами. Если вы собираете из make, то очень вероятно, что у вас получится чистый, аккуратный репозиторий сам собой.

8—Makefile(лы) хороши тем, что можно добавить много проверок зависимостей и assert(ов) на фазе отработки Make-скриптов прямо в *.mk файлах еще до компиляции самого кода, даже до запуска препроцессора, так как язык программирования make поддерживает условные операторы и функции. Можно очень много ошибок отловить на этапе отработки утилиты make.

9—Язык make очень прост. Вся спека GNU Make это 226 страниц. Cтю Фельдман (автор make) просто гений.

10—Makefile(лы) прозрачные текстовые. Всегда видно, где опции препроцессора, где ключи для компилятора, а где для компоновщика. Всё, что нужно можно найти утилитой grep в той же консоли.

11—Конфига для сборки можно формировать как раз на стадии make файлов и передавать их как ключи для препроцессора. Таким образом конфиги будут видны в каждом *.с файле проекта и не надо вставлять #include(ы) c конфигами. Всё можно передать как опции утилите cpp (препроцессора).

Вывод

Make это как пуговицы. Старая, простая и очень полезная вещь. Собираете свои прошивки из make в этом нет ничего сложного.

Links

https://www.youtube.com/watch?v=vmuO4bHjTSo&t=7s

https://habr.com/ru/post/47513/

https://habr.com/ru/post/111691/

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете код с помощью утилиты Make?
61.9% да 13
38.1% нет 8
Проголосовал 21 пользователь. Воздержался 1 пользователь.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете прошивки из-под IDE?
50% да 9
50% нет 9
Проголосовали 18 пользователей. Воздержался 1 пользователь.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете код с помощью СMake?
55% да 11
45% нет 9
Проголосовали 20 пользователей. Воздержался 1 пользователь.

ссылка на оригинал статьи https://habr.com/ru/post/723054/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *