В период с 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/723054/
Добавить комментарий