Статья
Версия для печати
Обсудить на форуме
«Hello World!» в embedded-исполнении. Часть 5

© Dale, 08.11.2011 — 10.11.2011.

Начало: часть 1, часть 2, часть 3, часть 4.

Оглавление


Генеральная уборка

Как отличить настоящего мастера от неумехи, который лишь старается выглядеть мастером? По инструментам? Нет, среди моих знакомых есть несколько, которые питают страсть к коллекционированию дорогих и действительно хороших инструментов, но ни разу не пустили их в дело, наслаждаясь самим фактом обладания красивой игрушкой. По словам и амбициям? Тем более не вариант. По конечному результату? В принципе верно, но откровенная халтура может быть прекрасно оформлена внешне, и чтобы понять это, потребуется некоторое время.
Если есть возможность, взгляните на рабочее место мастера. Если на верстаке вперемешку разбросаны инструменты, готовые детали, заготовки, обрезки, чертежи, справочники и много всякой прочей всячины, образуя миниатюрную модель первозданного хаоса, в котором невозможно что-то быстро найти, и не оставляя места для работы, вывод один — о качестве здесь не может быть и речи. Конечно же, и при работе мастера рабочее место неизбежно захламляется, без этого никак; однако мастер не дает беспорядку распространяться бесконтрольно, приводя рабочее место в порядок после каждой операции.

Лирическое отступление
Давным-давно, когда двигатели внутреннего сгорания были еще карбюраторными, мне посчастливилось видеть работу одного карбюраторщика, настоящего артиста своего дела. Меня просто поразила стерильная чистота его рабочего места, хотя карбюратор видавшего виды авто — отнюдь не самая чистая штука. А уж обилию и состоянию его инструментов мог бы позавидовать, пожалуй, иной нейрохирург (равно как и умению с ними обращаться). Стоит ли говорить, что автомобиль преображался после посещения мастера, его невозможно было узнать — он просто радовался дороге, как застоявшийся в стойле конь.
К сожалению, это был единственный случай столь виртуозного мастерства в долгой практике моего вынужденного знакомства с автосервисами.

Пришла пора и нам навести порядок в нашем проекте перед тем, как перейти к следующему этапу. Мне внушает нешуточные опасения состояние нашего make-файла для модуля Application: он не столь велик, но очень плохо читается и, главное, изобилует повторениями. А повторения — это зло, поскольку они препятствуют модификациям и вынуждают оставлять все как есть в опасении, что после редактирования станет только хуже.

Исходное состояние

Итак, посмотрим, что же мы имеем на данный момент:

Код: (Text) "Makefile"
# Подставьте здесь команду для вызова вашего компилятора C
CC=mingw32-gcc

CPPFLAGS=-I include -I ..\Led\include -I ..\Timer\include

.DEFAULT : obj\PC\Application.o
obj\PC\Application.o : src\Application.c include\Application.h ..\Led\include\Led.h
        $(COMPILE.c) -o obj\PC\Application.o src\Application.c

.PHONY : clean
clean :
        del /q obj\PC\*.o

.PHONY : test
test : TestApplication_Runner.exe      

TestApplication_Runner.exe : obj\PC\TestApplication_Runner.o obj\PC\TestApplication.o \
obj\PC\Application.o ..\Blinker\obj\PC\unity.o ..\Blinker\obj\PC\cmock.o ..\Led\obj\PC\MockLed.o \
..\Timer\obj\PC\MockTimer.o
        $(LINK.c) -o TestApplication_Runner.exe obj\PC\TestApplication_Runner.o obj\PC\TestApplication.o \
obj\PC\Application.o ..\Blinker\obj\PC\unity.o ..\Blinker\obj\PC\cmock.o ..\Led\obj\PC\MockLed.o \
..\Timer\obj\PC\MockTimer.o

obj\PC\TestApplication_Runner.o : test\TestApplication_Runner.c
        $(COMPILE.c) -o obj\PC\TestApplication_Runner.o -I ..\vendor\unity\src -I ..\vendor\cmock\src \
-I ..\Led\mocks -I ..\Timer\mocks test\TestApplication_Runner.c

obj\PC\TestApplication.o : test\TestApplication.c
        $(COMPILE.c) -o obj\PC\TestApplication.o -I ..\vendor\unity\src -I ..\Led\mocks \
-I ..\Timer\mocks test\TestApplication.c

..\Blinker\obj\PC\unity.o : ..\vendor\unity\src\unity.c
        $(COMPILE.c) -o ..\Blinker\obj\PC\unity.o -I ..\vendor\unity\src ..\vendor\unity\src\unity.c

..\Blinker\obj\PC\cmock.o : ..\vendor\cmock\src\cmock.c
        $(COMPILE.c) -o ..\Blinker\obj\PC\cmock.o -I ..\vendor\cmock\src -I ..\vendor\unity\src \
..\vendor\cmock\src\cmock.c

..\Led\obj\PC\MockLed.o : ..\Led\mocks\MockLed.c
        $(COMPILE.c) -o ..\Led\obj\PC\MockLed.o -I ..\vendor\unity\src -I ..\vendor\cmock\src -I \
..\Led\include ..\Led\mocks\MockLed.c

..\Timer\obj\PC\MockTimer.o : ..\Timer\mocks\MockTimer.c
        $(COMPILE.c) -o ..\Timer\obj\PC\MockTimer.o -I ..\vendor\unity\src -I ..\vendor\cmock\src \
-I ..\Timer\include ..\Timer\mocks\MockTimer.c

test\TestApplication_Runner.c : test\TestApplication.c
        ruby ..\vendor\unity\auto\generate_test_runner.rb test/TestApplication.c

Все вносимые изменения нам следует проверить, а для этого нужно будет неоднократно запустить сборку проекта. Однако утилита Make не любит напрасный труд и не будет запускаться без крайней на то необходимости. Чтобы заставить ее трудиться, нам придется каждый раз полностью вычищать рабочие директории проекта перед сборкой. Для этого прежде всего наведем порядок в процедуре очистки.

Совершенствуем очистку проекта

Итак, приступим:

> make clean
del /q obj\PC\*.o

Тот ли это эффект, которого мы добивались? Лишь отчасти. Мы вычистили поддиректорию obj\PC от объектных файлов; однако это не единственные артефакты, создаваемые в процессе сборки. Внимательно просмотрев Makefile, мы видим, что объектные файлы создаются также в директориях Blinker, Led и Timer. Их наш Makefile не чистит. (Попутно заметим, что директория Blinker, пожалуй, не лучшее место для размещения технологических объектных файлов продуктов от сторонних поставщиков; однако пока отложим этот вопрос).
Мы могли бы добавить команды удаления объектных файлов в наш Makefile. Однако это не лучшая идея. Вскорости мы доберемся до модулей Led и Timer (и вполне возможно, что появятся и другие модули) со своими собственными make-файлами, и процедуру очистки придется реализовывать повторно. Сделаем это сразу.
Сначала создадим Makefile для модуля Led:

Код: (Text) "Makefile"
.PHONY : clean
clean :
        del /q obj\PC\*.o

Убедимся, что он работает, вызвав команду make clean. Действительно, объектные файлы в поддиректории Led\obj\PC исчезли бесследно. А теперь заставим  Makefile модуля Application делать то же самое. Для этого нам потребуется рекурсивный вызов Make. С учетом того, что очистка модуля Led нами уже была реализована, это будет выглядеть так:

Код: (Text) "Makefile"
.PHONY : clean
clean :
        del /q obj\PC\*.o
        $(MAKE) -C ..\Led clean

Повторим то же самое для модулей Timer и Blinker (а заодно добавим очистку TestApplication_Runner.exe, о которой в суматохе как-то позабыли).

Код: (Text) "Makefile"
.PHONY : clean
clean :
        del /q obj\PC\*.o
        del /q TestApplication_Runner.exe
        $(MAKE) -C ..\Led clean
        $(MAKE) -C ..\Timer clean
        $(MAKE) -C ..\Blinker clean

Переменные Make

С очисткой более-менее разобрались (еще не совсем, но на данный момент этого будет достаточно). Теперь обратим внимание на безобразную в своей громоздкости конструкцию:

Код: (Text) "Makefile"
TestApplication_Runner.exe : obj\PC\TestApplication_Runner.o obj\PC\TestApplication.o obj\PC\Application.o \
..\Blinker\obj\PC\unity.o ..\Blinker\obj\PC\cmock.o ..\Led\obj\PC\MockLed.o ..\Timer\obj\PC\MockTimer.o
        $(LINK.c) -o TestApplication_Runner.exe obj\PC\TestApplication_Runner.o obj\PC\TestApplication.o \
obj\PC\Application.o ..\Blinker\obj\PC\unity.o ..\Blinker\obj\PC\cmock.o ..\Led\obj\PC\MockLed.o \
..\Timer\obj\PC\MockTimer.o

Команда компоновки фактически представляет собой цель, после которой перечислены пререквизиты. Если понадобится добавить (или изменить) какой-либо пререквизит, это придется сделать дважды. Это безобразие нужно немедленно прекращать.
При написании make-файлов можно использовать автоматические переменные, позволяющие избежать подобного дублирования. В данном случае нам будут полезны две такие переменные:

  • $@ — соответствует цели правила;
  • @^ — соответствует списку пререквизитов, разделенных пробелами.

Используя эти переменные, перепишем наше правило:

Код: (Text) "Makefile"
TestApplication_Runner.exe : obj\PC\TestApplication_Runner.o obj\PC\TestApplication.o obj\PC\Application.o \
..\Blinker\obj\PC\unity.o ..\Blinker\obj\PC\cmock.o ..\Led\obj\PC\MockLed.o ..\Timer\obj\PC\MockTimer.o
        $(LINK.c) -o $@ $^

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

Код: (Text) "Makefile"
# Подставьте здесь команду для вызова вашего компилятора C
CC=mingw32-gcc

.DEFAULT : obj\PC\Application.o
obj\PC\Application.o : src\Application.c include\Application.h ..\Led\include\Led.h
        $(COMPILE.c) -o $@ -I ..\Led\include -I ..\Timer\include -I include $<

.PHONY : clean
clean :
        del /q obj\PC\*.o
        del /q TestApplication_Runner.exe
        $(MAKE) -C ..\Led clean
        $(MAKE) -C ..\Timer clean
        $(MAKE) -C ..\Blinker clean

.PHONY : test
test : TestApplication_Runner.exe      

TestApplication_Runner.exe : obj\PC\TestApplication_Runner.o obj\PC\TestApplication.o \
obj\PC\Application.o ..\Blinker\obj\PC\unity.o ..\Blinker\obj\PC\cmock.o ..\Led\obj\PC\MockLed.o \
..\Timer\obj\PC\MockTimer.o
        $(LINK.c) -o $@ $^

obj\PC\TestApplication_Runner.o : test\TestApplication_Runner.c
        $(COMPILE.c) -o obj\PC\TestApplication_Runner.o -I ..\vendor\unity\src -I ..\vendor\cmock\src \
-I ..\Led\mocks -I ..\Timer\mocks -I ..\Led\include -I ..\Timer\include -I include $<

obj\PC\TestApplication.o : test\TestApplication.c
        $(COMPILE.c) -o $@ -I ..\vendor\unity\src -I ..\Led\mocks -I ..\Timer\mocks -I ..\Led\include \
-I ..\Timer\include -I include $<

..\Blinker\obj\PC\unity.o : ..\vendor\unity\src\unity.c
        $(COMPILE.c) -o $@ -I ..\vendor\unity\src $<

..\Blinker\obj\PC\cmock.o : ..\vendor\cmock\src\cmock.c
        $(COMPILE.c) -o $@ -I ..\vendor\cmock\src -I ..\vendor\unity\src $<

..\Led\obj\PC\MockLed.o : ..\Led\mocks\MockLed.c
        $(COMPILE.c) -o $@ -I ..\vendor\unity\src -I ..\vendor\cmock\src -I ..\Led\include $<

..\Timer\obj\PC\MockTimer.o : ..\Timer\mocks\MockTimer.c
        $(COMPILE.c) -o $@ -I ..\vendor\unity\src -I ..\vendor\cmock\src -I ..\Timer\include $<

test\TestApplication_Runner.c : test\TestApplication.c
        ruby ..\vendor\unity\auto\generate_test_runner.rb test/TestApplication.c

(Я задействовал еще одну автоматическую переменную $<; она соответствует первому пререквизиту из списка).
Наш Makefile изрядно сбросил вес. Однако есть еще над чем поработать.
Прежде всего бросаются в глаза многократные повторы достаточно длинных путей ..\vendor\unity\src и ..\vendor\cmock\src. Этих повторов можно избежать, определив соответствующие переменные:

Код: (Text) "Makefile"
# Подставьте здесь команду для вызова вашего компилятора C
CC=mingw32-gcc

UNITY_SRC=..\vendor\unity\src
CMOCK_SRC=..\vendor\cmock\src

.DEFAULT : obj\PC\Application.o
obj\PC\Application.o : src\Application.c include\Application.h ..\Led\include\Led.h
        $(COMPILE.c) -o $@ -I ..\Led\include -I ..\Timer\include -I include $<

.PHONY : clean
clean :
        del /q obj\PC\*.o
        del /q TestApplication_Runner.exe
        $(MAKE) -C ..\Led clean
        $(MAKE) -C ..\Timer clean
        $(MAKE) -C ..\Blinker clean

.PHONY : test
test : TestApplication_Runner.exe      

TestApplication_Runner.exe : obj\PC\TestApplication_Runner.o obj\PC\TestApplication.o \
obj\PC\Application.o ..\Blinker\obj\PC\unity.o ..\Blinker\obj\PC\cmock.o ..\Led\obj\PC\MockLed.o \
..\Timer\obj\PC\MockTimer.o
        $(LINK.c) -o $@ $^

obj\PC\TestApplication_Runner.o : test\TestApplication_Runner.c
        $(COMPILE.c) -o obj\PC\TestApplication_Runner.o -I $(UNITY_SRC) -I $(CMOCK_SRC) -I ..\Led\mocks \
-I ..\Timer\mocks -I ..\Led\include -I ..\Timer\include -I include $<

obj\PC\TestApplication.o : test\TestApplication.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I ..\Led\mocks -I ..\Timer\mocks -I ..\Led\include \
-I ..\Timer\include -I include $<

..\Blinker\obj\PC\unity.o : ..\vendor\unity\src\unity.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) $<

..\Blinker\obj\PC\cmock.o : ..\vendor\cmock\src\cmock.c
        $(COMPILE.c) -o $@ -I $(CMOCK_SRC) -I $(UNITY_SRC) $<

..\Led\obj\PC\MockLed.o : ..\Led\mocks\MockLed.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(CMOCK_SRC) -I ..\Led\include $<

..\Timer\obj\PC\MockTimer.o : ..\Timer\mocks\MockTimer.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(CMOCK_SRC) -I ..\Timer\include $<

test\TestApplication_Runner.c : test\TestApplication.c
        ruby ..\vendor\unity\auto\generate_test_runner.rb test/TestApplication.c

Аналогичным образом избавляемся от остальных повторов:

Код: (Text) "Makefile"
# Подставьте здесь команду для вызова вашего компилятора C
CC=mingw32-gcc

UNITY_SRC=..\vendor\unity\src
CMOCK_SRC=..\vendor\cmock\src
LED_HDR=..\Led\include
TIMER_HDR=..\Timer\include
LED_MOCKS=..\Led\mocks
TIMER_MOCKS=..\Timer\mocks
OBJ_PC=obj\PC

.DEFAULT : $(OBJ_PC)\Application.o
$(OBJ_PC)\Application.o : src\Application.c include\Application.h $(LED_HDR)\Led.h
        $(COMPILE.c) -o $@ -I $(LED_HDR) -I $(TIMER_HDR) -I include $<

.PHONY : clean
clean :
        del /q obj\PC\*.o
        del /q TestApplication_Runner.exe
        $(MAKE) -C ..\Led clean
        $(MAKE) -C ..\Timer clean
        $(MAKE) -C ..\Blinker clean

.PHONY : test
test : TestApplication_Runner.exe      

TestApplication_Runner.exe : $(OBJ_PC)\TestApplication_Runner.o $(OBJ_PC)\TestApplication.o \
$(OBJ_PC)\Application.o ..\Blinker\$(OBJ_PC)\unity.o ..\Blinker\$(OBJ_PC)\cmock.o \
..\Led\$(OBJ_PC)\MockLed.o ..\Timer\$(OBJ_PC)\MockTimer.o
        $(LINK.c) -o $@ $^

$(OBJ_PC)\TestApplication_Runner.o : test\TestApplication_Runner.c
        $(COMPILE.c) -o $(OBJ_PC)\TestApplication_Runner.o -I $(UNITY_SRC) -I $(CMOCK_SRC) \
-I $(LED_MOCKS) -I $(TIMER_MOCKS) -I $(LED_HDR) -I $(TIMER_HDR) -I include $<

$(OBJ_PC)\TestApplication.o : test\TestApplication.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(LED_MOCKS) -I $(TIMER_MOCKS) -I $(LED_HDR) \
-I $(TIMER_HDR) -I include $<

..\Blinker\$(OBJ_PC)\unity.o : $(UNITY_SRC)\unity.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) $<

..\Blinker\$(OBJ_PC)\cmock.o : $(CMOCK_SRC)\cmock.c
        $(COMPILE.c) -o $@ -I $(CMOCK_SRC) -I $(UNITY_SRC) $<

..\Led\$(OBJ_PC)\MockLed.o : $(LED_MOCKS)\MockLed.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(CMOCK_SRC) -I $(LED_HDR) $<

..\Timer\$(OBJ_PC)\MockTimer.o : $(TIMER_MOCKS)\MockTimer.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(CMOCK_SRC) -I $(TIMER_HDR) $<

test\TestApplication_Runner.c : test\TestApplication.c
        ruby ..\vendor\unity\auto\generate_test_runner.rb test/TestApplication.c

Распределение обязанностей по модулям

Теперь Makefile читается гораздо лучше, чем в начале нашей чистки. Однако мне очень не нравится то, что Makefile модуля Application самым бесцеремонным образом вмешивается во внутренние дела других модулей, строя их объектные файлы. Это сильно подрывает принципы модульности нашего проекта: модуль не должен знать деталей строения другого модуля и пользоваться лишь его открытыми сервисами. То же касается и построения модуля: вместо того, чтобы вторгаться в частную жизнь другого модуля, следует лишь попросить его выполнить необходимую работу по построению необходимых объектных файлов.
Начнем с модуля Led; вынесем построение подставного объекта MockLed.o в его собственный make-файл:

Код: (Text) "Makefile"
.PHONY : mocks
mocks : $(OBJ_PC)\MockLed.o

$(OBJ_PC)\MockLed.o : $(LED_MOCKS)\MockLed.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(CMOCK_SRC) -I $(LED_HDR) $<

Разумеется, это не работает, ведь мы ссылаемся на значения переменных, которые определены в другом файле.
Можно было бы скопировать определения из модуля Application в модуль Led; однако это было бы полной капитуляцией в войне с избыточностью и дублированием, которую мы сами начали. Мы поступим по-другому: вынесем определения общих для всех модулей переменных в файл, который разместим в корневой для всех модулей директории Software, и будем включать его во все make-файлы директивой include.

Код: (Text) Common.mk
# Подставьте здесь команду для вызова вашего компилятора C
CC=mingw32-gcc

UNITY_SRC=..\vendor\unity\src
CMOCK_SRC=..\vendor\cmock\src
LED_HDR=..\Led\include
TIMER_HDR=..\Timer\include
LED_MOCKS=..\Led\mocks
TIMER_MOCKS=..\Timer\mocks
OBJ_PC=obj\PC

Код: (Text) "Makefile"
include ..\Common.mk

.PHONY : clean
clean :
        del /q $(OBJ_PC)\*.o


.PHONY : mocks
mocks : $(OBJ_PC)\MockLed.o

$(OBJ_PC)\MockLed.o : $(LED_MOCKS)\MockLed.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(CMOCK_SRC) -I $(LED_HDR) $<

Теперь другое дело: подставной объект MockLed.o успешно собран. Модифицируем соответствующее правило в make-файле для Application:

Код: (Text) "Makefile"
TestApplication_Runner.exe : $(OBJ_PC)\TestApplication_Runner.o $(OBJ_PC)\TestApplication.o \
$(OBJ_PC)\Application.o ..\Blinker\$(OBJ_PC)\unity.o ..\Blinker\$(OBJ_PC)\cmock.o \
..\Timer\$(OBJ_PC)\MockTimer.o
        $(MAKE) -C ..\Led mocks
        $(LINK.c) -o $@ $^ ..\Led\$(OBJ_PC)\MockLed.o

Сборка проходит удачно. Сделаем то же самое и для модуля Timer.

Код: (Text) "Makefile"
include ..\Common.mk

.DEFAULT : $(OBJ_PC)\Application.o
$(OBJ_PC)\Application.o : src\Application.c include\Application.h $(LED_HDR)\Led.h
        $(COMPILE.c) -o $@ -I $(LED_HDR) -I $(TIMER_HDR) -I include $<

.PHONY : clean
clean :
        del /q obj\PC\*.o
        del /q TestApplication_Runner.exe
        $(MAKE) -C ..\Led clean
        $(MAKE) -C ..\Timer clean
        $(MAKE) -C ..\Blinker clean

.PHONY : test
test : TestApplication_Runner.exe      

TestApplication_Runner.exe : $(OBJ_PC)\TestApplication_Runner.o $(OBJ_PC)\TestApplication.o \
$(OBJ_PC)\Application.o ..\Blinker\$(OBJ_PC)\unity.o ..\Blinker\$(OBJ_PC)\cmock.o
        $(MAKE) -C ..\Led mocks
        $(MAKE) -C ..\Timer mocks
        $(LINK.c) -o $@ $^ ..\Led\$(OBJ_PC)\MockLed.o ..\Timer\$(OBJ_PC)\MockTimer.o

$(OBJ_PC)\TestApplication_Runner.o : test\TestApplication_Runner.c
        $(COMPILE.c) -o $(OBJ_PC)\TestApplication_Runner.o -I $(UNITY_SRC) -I $(CMOCK_SRC) \
-I $(LED_MOCKS) -I $(TIMER_MOCKS) -I $(LED_HDR) -I $(TIMER_HDR) -I include $<

$(OBJ_PC)\TestApplication.o : test\TestApplication.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(LED_MOCKS) -I $(TIMER_MOCKS) -I $(LED_HDR) \
-I $(TIMER_HDR) -I include $<

..\Blinker\$(OBJ_PC)\unity.o : $(UNITY_SRC)\unity.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) $<

..\Blinker\$(OBJ_PC)\cmock.o : $(CMOCK_SRC)\cmock.c
        $(COMPILE.c) -o $@ -I $(CMOCK_SRC) -I $(UNITY_SRC) $<

test\TestApplication_Runner.c : test\TestApplication.c
        ruby ..\vendor\unity\auto\generate_test_runner.rb test/TestApplication.c

Самое подходящее время найти более подходящее место для технологических объектных модулей unity.o и cmock.o, которые нашли временное пристанище в модуле Blinker, но выглядят там явно чужеродными элементами. Определим их на постоянное жительство в vendor\obj\PC. Для получения этих модулей создадим соответствующий make-файл в директории vendor.

Код: (Text) "Makefile"
include ..\Common.mk

.PHONY : clean
clean :
        del /q $(OBJ_PC)\*.o


.PHONY : aux
aux : $(OBJ_PC)\unity.o $(OBJ_PC)\cmock.o

$(OBJ_PC)\unity.o : $(UNITY_SRC)\unity.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) $<

$(OBJ_PC)\cmock.o : $(CMOCK_SRC)\cmock.c
        $(COMPILE.c) -o $@ -I $(CMOCK_SRC) -I $(UNITY_SRC) $<

Подведем промежуточный итог. Мы начинали с одного make-файла, неряшливостью напоминающего воронье гнездо (хотя и работоспособного). После ряда преобразований мы получили такой набор:

Код: (Text) "Software\Common.mk"
# Подставьте здесь команду для вызова вашего компилятора C
CC=mingw32-gcc

UNITY_SRC=..\vendor\unity\src
CMOCK_SRC=..\vendor\cmock\src
LED_HDR=..\Led\include
TIMER_HDR=..\Timer\include
LED_MOCKS=..\Led\mocks
TIMER_MOCKS=..\Timer\mocks
OBJ_PC=obj\PC

Код: (Text) "Application\Makefike"
include ..\Common.mk

.DEFAULT : $(OBJ_PC)\Application.o
$(OBJ_PC)\Application.o : src\Application.c include\Application.h $(LED_HDR)\Led.h
        $(COMPILE.c) -o $@ -I $(LED_HDR) -I $(TIMER_HDR) -I include $<

.PHONY : clean
clean :
        del /q obj\PC\*.o
        del /q TestApplication_Runner.exe
        $(MAKE) -C ..\Led clean
        $(MAKE) -C ..\Timer clean
        $(MAKE) -C ..\Blinker clean
        $(MAKE) -C ..\vendor clean

.PHONY : test
test : TestApplication_Runner.exe      

TestApplication_Runner.exe : $(OBJ_PC)\TestApplication_Runner.o $(OBJ_PC)\TestApplication.o \
$(OBJ_PC)\Application.o
        $(MAKE) -C ..\Led mocks
        $(MAKE) -C ..\Timer mocks
        $(MAKE) -C ..\vendor aux
        $(LINK.c) -o $@ $^ ..\Led\$(OBJ_PC)\MockLed.o ..\Timer\$(OBJ_PC)\MockTimer.o \
..\vendor\$(OBJ_PC)\unity.o ..\vendor\$(OBJ_PC)\cmock.o

$(OBJ_PC)\TestApplication_Runner.o : test\TestApplication_Runner.c
        $(COMPILE.c) -o $(OBJ_PC)\TestApplication_Runner.o -I $(UNITY_SRC) -I $(CMOCK_SRC) \
-I $(LED_MOCKS) -I $(TIMER_MOCKS) -I $(LED_HDR) -I $(TIMER_HDR) -I include $<

$(OBJ_PC)\TestApplication.o : test\TestApplication.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(LED_MOCKS) -I $(TIMER_MOCKS) -I $(LED_HDR) \
-I $(TIMER_HDR) -I include $<

test\TestApplication_Runner.c : test\TestApplication.c
        ruby ..\vendor\unity\auto\generate_test_runner.rb test/TestApplication.c

Код: (Text) "Led\Makefile"
include ..\Common.mk

.PHONY : clean
clean :
        del /q $(OBJ_PC)\*.o


.PHONY : mocks
mocks : $(OBJ_PC)\MockLed.o

$(OBJ_PC)\MockLed.o : $(LED_MOCKS)\MockLed.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(CMOCK_SRC) -I $(LED_HDR) $<

Код: (Text) "Timer\Makefile"
include ..\Common.mk

.PHONY : clean
clean :
        del /q $(OBJ_PC)\*.o


.PHONY : mocks
mocks : $(OBJ_PC)\MockTimer.o

$(OBJ_PC)\MockTimer.o : $(TIMER_MOCKS)\MockTimer.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(CMOCK_SRC) -I $(TIMER_HDR) $<

Код: (Text) "vendor\Makefile"
include ..\Common.mk

.PHONY : clean
clean :
        del /q $(OBJ_PC)\*.o


.PHONY : aux
aux : $(OBJ_PC)\unity.o $(OBJ_PC)\cmock.o

$(OBJ_PC)\unity.o : $(UNITY_SRC)\unity.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) $<

$(OBJ_PC)\cmock.o : $(CMOCK_SRC)\cmock.c
        $(COMPILE.c) -o $@ -I $(CMOCK_SRC) -I $(UNITY_SRC) $<

Ищем потерянные зависимости

Теперь, когда make-файлы стали небольшими и аккуратными, стало реально привести их в полный порядок. Обратим теперь внимание на упущения, на которые раньше приходилось закрывать глаза, пока не решены более насущные проблемы.
Возьмем, к примеру, модуль Led. Откуда берется объектный модуль MockLed.o? Разумеется, компиляцией MockLed.с. А откуда взялся MockLed.с, ведь мы не писали его сами? Он был сгенерирован специальными инструментальными средствами по интерфейсу Led.h.
Что будет, если интерфейс модуля Led изменится? Ровным счетом ничего. Подставной объект для модуля не будет перегенерирован, поскольку мы не предусмотрели для этого никаких средств. Убедимся в этом воочию.

> touch include\Led.h
> make mocks
make.EXE: Nothing to be done for `mocks'.

Нужно принимать соответствующие меры:

Код:
$(LED_MOCKS)\MockLed.c : $(LED_HDR)\Led.h
ruby ..\vendor\cmock\lib\cmock.rb -o..\enforce_strict_ordering.yml $(LED_HDR)\Led.h

> touch include\Led.h
> make mocks
ruby ..\vendor\cmock\lib\cmock.rb -o..\enforce_strict_ordering.yml ..\Led\include\Led.h
Creating mock for Led...
mingw32-gcc    -c -o obj\PC\MockLed.o -I ..\vendor\unity\src -I ..\vendor\cmock\src -I ..\Led\include
 ..\Led\mocks\MockLed.c

То же самое повторяем и для модуля Timer.
Необходимо также учесть зависимость Application и TestApplication от интерфейсов Led и Timer (как прямую, так и косвенную, через подставные объекты). Это дает такую картину:

Код: (Text) "Application\Makefile"
include ..\Common.mk

.DEFAULT : $(OBJ_PC)\Application.o
$(OBJ_PC)\Application.o : src\Application.c include\Application.h $(LED_HDR)\Led.h $(TIMER_HDR)\Timer.h
        $(COMPILE.c) -o $@ -I $(LED_HDR) -I $(TIMER_HDR) -I include $<

.PHONY : clean
clean :
        del /q obj\PC\*.o
        del /q TestApplication_Runner.exe
        $(MAKE) -C ..\Led clean
        $(MAKE) -C ..\Timer clean
        $(MAKE) -C ..\Blinker clean
        $(MAKE) -C ..\vendor clean

.PHONY : test
test : TestApplication_Runner.exe      

TestApplication_Runner.exe : $(OBJ_PC)\TestApplication_Runner.o $(OBJ_PC)\TestApplication.o \
$(OBJ_PC)\Application.o
        $(MAKE) -C ..\Led mocks
        $(MAKE) -C ..\Timer mocks
        $(MAKE) -C ..\vendor aux
        $(LINK.c) -o $@ $^ ..\Led\$(OBJ_PC)\MockLed.o ..\Timer\$(OBJ_PC)\MockTimer.o \
..\vendor\$(OBJ_PC)\unity.o ..\vendor\$(OBJ_PC)\cmock.o

$(OBJ_PC)\TestApplication_Runner.o : test\TestApplication_Runner.c
        $(MAKE) -C ..\Led mocks
        $(MAKE) -C ..\Timer mocks
        $(COMPILE.c) -o $(OBJ_PC)\TestApplication_Runner.o -I $(UNITY_SRC) -I $(CMOCK_SRC) \
-I $(LED_MOCKS) -I $(TIMER_MOCKS) -I $(LED_HDR) -I $(TIMER_HDR) -I include $<

$(OBJ_PC)\TestApplication.o : test\TestApplication.c include\Application.h $(LED_HDR)\Led.h \
$(TIMER_HDR)\Timer.h
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(LED_MOCKS) -I $(TIMER_MOCKS) -I $(LED_HDR) \
-I $(TIMER_HDR) -I include $<

test\TestApplication_Runner.c : test\TestApplication.c
        ruby ..\vendor\unity\auto\generate_test_runner.rb test/TestApplication.c \
..\enforce_strict_ordering.yml

Код: (Text) "Led\Makefile"
include ..\Common.mk

.PHONY : clean
clean :
        del /q $(OBJ_PC)\*.o
        del /q $(LED_MOCKS)\MockLed.*


.PHONY : mocks
mocks : $(OBJ_PC)\MockLed.o

$(OBJ_PC)\MockLed.o : $(LED_MOCKS)\MockLed.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(CMOCK_SRC) -I $(LED_HDR) $<

$(LED_MOCKS)\MockLed.c $(LED_MOCKS)\MockLed.h : $(LED_HDR)\Led.h
        ruby ..\vendor\cmock\lib\cmock.rb -o..\enforce_strict_ordering.yml $(LED_HDR)\Led.h

Код: (Text) "Timer\Makefile"
include ..\Common.mk

.PHONY : clean
clean :
        del /q $(OBJ_PC)\*.o
        del /q $(TIMER_MOCKS)\MockTimer.*


.PHONY : mocks
mocks : $(OBJ_PC)\MockTimer.o

$(OBJ_PC)\MockTimer.o : $(TIMER_MOCKS)\MockTimer.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) -I $(CMOCK_SRC) -I $(TIMER_HDR) $<

$(TIMER_MOCKS)\MockTimer.c $(TIMER_MOCKS)\MockTimer.h : $(TIMER_HDR)\Timer.h
        ruby ..\vendor\cmock\lib\cmock.rb -o..\enforce_strict_ordering.yml $(TIMER_HDR)\Timer.h

Код: (Text) "vendor\Makefile"
include ..\Common.mk

.PHONY : clean
clean :
        del /q $(OBJ_PC)\*.o


.PHONY : aux
aux : $(OBJ_PC)\unity.o $(OBJ_PC)\cmock.o

$(OBJ_PC)\unity.o : $(UNITY_SRC)\unity.c
        $(COMPILE.c) -o $@ -I $(UNITY_SRC) $<

$(OBJ_PC)\cmock.o : $(CMOCK_SRC)\cmock.c
        $(COMPILE.c) -o $@ -I $(CMOCK_SRC) -I $(UNITY_SRC) $<

Код: (Text) "Software\Common.mk"
# Подставьте здесь команду для вызова вашего компилятора C
CC=mingw32-gcc

UNITY_SRC=..\vendor\unity\src
CMOCK_SRC=..\vendor\cmock\src
LED_HDR=..\Led\include
TIMER_HDR=..\Timer\include
LED_MOCKS=..\Led\mocks
TIMER_MOCKS=..\Timer\mocks
OBJ_PC=obj\PC

На этом столь утомительную, но столь же необходимую работу по приведению make-файлов проекта в порядок завершаем.
Текущее состояние проекта можно загрузить из приложения.



Продолжение.
Версия для печати
Обсудить на форуме