Ваш make-файл должен иметь список объектов, которые нужно связать вместе, например:
OBJ_FILES = inner_definitions.o inner_class_1.o inner_class_2.o \
outer_class.o executable.o
executable : $(OBJ_FILES)
gcc $^ -o $@
Кто-то должен написать это; GCC не может сделать это для вас, Make не может сделать это для вас. Не каждый разработчик проекта должен знать, как создать этот список, а только тот, кто пишет эту часть make-файла. Все остальные будут использовать этот make-файл, и разработчик, который добавляет новую зависимость (например, inner_class_3
), может добавить ее в список.
И если ваш make-файл потерян в огне, и единственный разработчик, который знает все зависимости, поражен шиной, действительно нетрудно восстановить список: когда вы пытаетесь создать executable
, компоновщик жалуется что foo::bar()
не определено, вы осматриваетесь и обнаруживаете, что foo::bar()
определено в inner_class_2.cpp
, вы добавляете inner_class_2.o
в список. Повторяйте, пока компоновщик не перестанет жаловаться.
P.S. Как только все будет в порядке, вы можете значительно упростить остальную часть make-файла:
%.o: %.cpp %.h
gcc -c $< -o $@
inner_class_1.o inner_class_2.o : inner_definitions.h
outer_class.o : inner_class_1.h inner_class_2.h
executable.o : outer_class.h
EDIT:
- Метод, который я предложил, не требует перечисления всех объектных файлов, которые могут быть созданы, только тех, которые действительно необходимы для создания `исполняемого файла`; Я вывел список из вашего вопроса.
- Передача дополнительных объектных файлов компоновщику не имеет значения для конечного исполняемого файла, но приводит к ненужной перестройке. Например, предположим, что вы добавили `alien.o` в` OBJ_FILES`. Затем, если вы измените `alien.cpp` и запустите` make исполняемый файл`, он восстановит `alien.o` и` исполняемый файл`, даже если в этом нет особой необходимости. Исправление (благодаря slowdog): ненужные объектные файлы попадают в конечный исполняемый файл как мертвый код - но я все еще прав насчет ненужной перестройки.
- Организация объектных файлов в архивах и общих библиотеках часто удобна, но здесь ничего не меняется.
- Я не знаю надежного способа автоматического построения списка объектов, то есть способа, который мог бы иметь дело с проблемными случаями, например, когда одна и та же функция определена в двух разных исходных файлах. Это может стать реальной проблемой, если участвуют юнит-тесты. Но вы можете сделать это в вашем make-файле , если вы следуете простому соглашению об именах.
- Хитрость для этого в вашем make-файле довольно продвинутая. Честно говоря, я думаю, что вам будет лучше сделать это простым способом, пока вы не освоитесь с инструментами.
EDIT:
Хорошо, вот схема продвинутой техники.
Сначала рассмотрим все включенные заголовочные файлы. Было бы неплохо, чтобы Make обрабатывал зависимости, а не вставлял их вручную, как в приведенном выше make-файле. И это простая задача: если X.cpp #include Y.h (напрямую или через цепочку заголовочных файлов #included), то X.o будет зависеть от Y.h. Это уже было разработано как «Advanced Auto-Dependency Generation» . Но если вы следуете строгому соглашению об именах, вы можете сделать еще один шаг: если все, что объявлено, но не определено в Xh, определено в X.cpp, то, следуя тому же дереву операторов #include, мы сможем создать список необходимых объектных файлов, которые затем будут зависимостями executable
.
Это действительно много, чтобы поглотить сразу, поэтому я не буду пытаться проработать пример. Я предлагаю вам просмотреть документ и посмотреть, как он может генерировать зависимости Y.h, затем попытаться применить его к примеру make-файла, а затем подумать, что должен делать «шаг вперед».
Позже вы можете применить его к тестовому устройству, где объектные файлы, например, outer_class.o
, stub_inner_class_1.o
, stub_inner_class_2.o
и test_outer_class.o
.