понедельник, 8 июня 2009 г.

GNU make для IAR EW

Ура! Сегодня соединились настроение и свободное время, чтобы поковырять компилятор IAR на предмет его использования из GNU make.

Вот за что люблю make, так это за то, что с его помощью решаются самые неожиданные задачи. И уж тем более, задача автоматизации компиляции ;)
Пропуская описание многочасового процесса чтения доки на компоненты IAR EW, сразу скажу, что у меня всё получилось (ну кто бы сомневался :).
Самые большие неудобства (и, соответственно, самые активные пляски с бубном) возникли из-за наличия пробелов в имени директории инсталляции IAR. Итак, ближе к телу.
Создаем makefile, прописываем исходные файли и #include-директории тестового проекта и параметры платформы:
project = test
test_include =
test_sources = $(wildcard *.c)

# IAR language extension
enable_iar_extensions = 1
# use hardware multiplier, varinats: 16,16s,32
# use_hwmul = 32
# the location of the hardware multiplier in hex (defaults to 0130)
# hwmul_location =
# use new runtime, runtime variants: normal, full
use_dlib = normal
# cpu type (case-sensitive)
cpu = MSP430F5438
# CPU core variant: 430 or 430X
cpu_core = 430X
# data model for 430X: small, medium, large
data_model = medium
# the size of the double floating point type (32 or 64)
# dfloat_size = 32
# enable generation of position independent code (yes or no)
# enable_pic = no
# stack and heap sizes
# stack_size = 160
# heap_size = 160
# heap20_size = 160
NOTE: Закомментированные опции не используются в данном примере чтобы оставить код более простым и легко обозримым. В полной реализации я уложился в триста с небольшим строк.
В качестве runtime-библиотеки выбираем рекомендованную DLib (CLib признана устаревшей и оставлена для совместимости). Юмор с переходом от CLib к DLib, по-моему, весьма оригинален, так что ему будет уделено несколько дополнительных строчек в makefile.
Для используемой платформы на MSP430F5438 выбираются вариант ядра 430X и модель данных medium. На основе этих данных далее вычисляется имя dlib-конфигурации (в данном случае испольуется её версия normal).

Для сборки в Windows определяем используемые команды и некоторые дополнительные функции:
ECHO   = echo
RM = erase /F /S >nul
RMDIR = rmdir
RMTREE = rmdir /Q /S
MKDIR = mkdir

fix_pathname = $(subst /,\,$(1))
mk_dir = cmd /c if not exist $(1) $(MKDIR) $(1)
rm_dir = cmd /c if exist $(1) $(RMTREE) $(1)

host_exe_ext = .exe
nonfs_char="^"

# service functions
empty=
space=$(empty) $(empty)
encode_path = $(subst $(space),$(nonfs_char),$(1))
decode_path = $(subst /,\,$(subst $(nonfs_char),$(space),$(1)))
Так как make плохо переварирвает пробелы в именах файлов и, как было сказано ранее, IAR по умолчанию ставится в такие директории, используется экранирование пробелов. Собственно, функции encode_path и decode_path и выполняют замену пробелов и восстановление исходного вида.

Теперь самое время сказать о необходимых телодвижениях.
Скрипт сборки подразумевает, что каталог инсталляции IAR хранится в переменной TOOLKIT_DIR. Поэтому следует добавить её определение в переменные среды или указать непосредственно перед вызовом make.
ARCH ?= 430
ifndef TOOLKIT_DIR
$(error TOOLKIT_DIR not defined!)
endif
Защищаемся на случай, если TOOLKIT_DIR не определёна и вводим переменную архитектуры - ARCH.
В соответствии с определённым TOOLKIT_DIR, определяем все зависящие переменные:
# IAR paths
IAR_DIR = $(call encode_path,$(TOOLKIT_DIR))/$(ARCH)
IAR_BIN = $(IAR_DIR)/bin
IAR_INCLUDE = $(IAR_DIR)/inc
IAR_LIB = $(IAR_DIR)/lib
IAR_CFG = $(IAR_DIR)/config

# IAR binaries
CC = $(call decode_path,"$(IAR_BIN)/icc$(ARCH)$(host_exe_ext)")
AR = $(call decode_path,"$(IAR_BIN)/xar$(host_exe_ext)")
AS = $(call decode_path,"$(IAR_BIN)/a$(ARCH)$(host_exe_ext)")
LD = $(call decode_path,"$(IAR_BIN)/xlink$(host_exe_ext)")
CXX = $(CC)
Пути к стандартным библиотекам и расширения файлов:
# include path for old C runtime
clib_include = $(IAR_INCLUDE)/clib
clib_lib = $(IAR_LIB)/clib
# include path for C/C++ runtime
dlib_include = $(IAR_INCLUDE)/dlib
dlib_lib = $(IAR_LIB)/dlib

# FIXME: ARCH-specific file extensions
obj_ext = .r43
exe_ext = .d43

c_ext = .c
cc_ext = .cc
asm_ext = .s43
Для разделения исходных текстов и результатов компиляции назначаем отдельный путь для сборки:
# root of project build tree
build_dir = build.$(build_type).$(ARCH)
Выполняем проверку переданных параметров, например, вот так:
# check and parse CPU core variant
cpu_core_list = 430 430X
ifdef cpu_core
cpu_core := $(strip $(cpu_core))
ifeq ($(filter $(cpu_core),$(cpu_core_list)),)
$(error Invalid CPU core variant: $(cpu_core). Available: $(cpu_core_list))
endif
else
cpu_core = $(firstword $(cpu_core_list))
endif
(Остальные подобные проверки опущены для простоты).
Проверяем корректность назначенной C-runtime и формируем имя кофигурации dlib (clib аналогично, чуть проще):
# check C/C++ runtime variant
ifdef use_dlib
use_dlib_list = none normal full
use_dlib := $(strip $(use_dlib))
ifeq ($(filter $(use_dlib),$(use_dlib_list)),)
$(error Invalid runtime configuration: $(use_dlib). Available: $(use_dlib_list))
endif
ifeq ($(use_dlib),none)
use_dlib =
else
# build name of dlib configuration
# add cpu core variant
dlib_config = dl
ifeq ($(cpu_core),430)
dlib_config += 430
else
dlib_config += 430x
# add data model for 430x
ifeq ($(data_model),small)
dlib_config += s
endif
ifeq ($(data_model),medium)
dlib_config += m
endif
ifeq ($(data_model),large)
dlib_config += l
endif
endif # cpu_core
# add double float size
ifeq ($(dfloat_size),32)
dlib_config += f
else
dlib_config += d
endif
# add dlib model suffix
ifeq ($(use_dlib),normal)
dlib_config += n
else
dlib_config += f
endif
# add PIC suffix
ifneq ($(enable_pic),)
dlib_config += p
endif
dlib_config := $(subst $(space),,$(dlib_config))
# check for library config exist (special decoding for function 'wilcard')
wilcard_dlib_config := $(subst $(space),\$(space),$(call decode_path,$(dlib_lib)/$(dlib_config).h))
ifeq ($(wildcard $(wilcard_dlib_config)),)
$(error DLib configuration "$(wilcard_dlib_config)" not exist)
endif
endif
endif
Интересным моментом является проверка существования файла сформированной конфигурации: для получения строки, пригодной для wildcard, используется экранирование пробелов символом '\'. Конечно, можно проверку опустить и предоставить компилятору сказать об этом.

Формирование корректного имени xcl-файла и определения типа процессора, вычисление списка include-директорий:
# linker CPU-specific command file
xcl_file = lnk$(subst MSP,,$(cpu)).xcl
# CPU-specific defines
defines = __$(cpu)__

sys_include = $(IAR_INCLUDE) $(if $(use_dlib),$(dlib_include),$(clib_include))
includes = $(sys_include) $($(project)_include)
Для проекта формируем имена объектных файлов и целевого файла:
$(project) = $(build_dir)/$(project)$(exe_ext)
objs = $(patsubst %,$(build_dir)/%$(obj_ext),$(basename $(sources)))
Флаги для компилятора и линкера:
IAR_CFLAGS = $(foreach dir,$(includes),-I $(call decode_path,"$(dir)")) \
$(foreach def,$(defines),-D$(def)) \
--core $(cpu_core) \
$(if $(enable_iar_extensions),-e) \
$(if $(dlib_config),--dlib_config $(call decode_path,"$(dlib_lib)/$(dlib_config).h")) \
$(if $(data_model),--data_model $(data_model)) \
-Ol \
--debug \
--no_wrap_diagnostics --only_stdout --silent

IAR_CXXFLAGS = --eec++ $(IAR_CFLAGS)

IAR_LDFLAGS = \
-D_STACK_SIZE=$(stack_size) -D_DATA16_HEAP_SIZE=$(heap_size) -D_DATA20_HEAP_SIZE=$(heap20_size) \
-S -L -xs -xn -xe -r -s __program_start \
-f $(call decode_path,"$(IAR_CFG)/$(xcl_file)") \
-C $(if $(dlib_config),$(call decode_path,"$(dlib_lib)/$(dlib_config)$(obj_ext)"))
Для простоты показана сборка отладочной версии. Важно не забыть корректное значение для --dlib_config и указать линкеру размеры стека/хипа и точки старта.

Наконец, собственно правила сборки. Они достаточно просты:
.PHONY: all clean

all: $($(project))
@$(ECHO) Done.

clean:
@$(ECHO) (RM) $(build_dir)/
-@$(call rm_dir,$(build_dir))

$(build_dir)/%$(obj_ext): %.c
@$(call mk_dir,$(call decode_path,"$(@D)"))
@$(ECHO) (CC) $(<F)
@$(CC) $(IAR_CFLAGS) --output $@ $^

$(build_dir)/%$(obj_ext): %.cc
@$(call mk_dir,$(call decode_path,"$(@D)"))
@$(ECHO) (CXX) $(<F)
@$(CC) $(IAR_CXXFLAGS) --output $@ $^

$($(project)): $(objs)
@$(ECHO) (LD) $(@F)
@$(LD) $(IAR_LDFLAGS) -o $@ $^

На этом приготовления закончены и можно выполнять сборку:
c:\home\mike\iar-test>make clean all
(RM) build.debug.430/
(CC) main.c
(LD) test.d43
Done.
c:\home\mike\iar-test>

Создаем в IAR IDE проект "Externally built executable", включаем туда получившийся test.d43, удаляем readme.txt (предварительно его прочитав), выставляем настройки в разделах "General options" (пункт "Device") и "Debugger" и запускам отладку!

Комментариев нет:

Отправить комментарий