Поговорим на тему сборки проекта. В данной статье рассмотрим как писать простенькие Make-файлы, для сборки проектов на языках C или C++. Конкретно мы будем компилировать проект на gcc стандарта C99.
Что такое и зачем нужен make? Это специальная утилита для автоматической компиляции кода в объектные модули и сборки из данных модулей исполняемого файла. Используется много где в операционных системах *nix, как я понимаю. Но конкретно мы будем работать на Linux, в библиотеках которых данная утилита крайне широко распространена.
Суть в том, что в процессе разработки ПО появляется необходимость тестирования. Но компилировать постоянно весь проект (а если он очень большой и над ним работает много программистов) не очень удобно. Поэтому make снова в этой ситуации нам поможет. То есть, не удалять результаты объектных файлов прошлых компиляций, а перекомпилировать лишь нужный модуль, что явно быстрее, чем весь проект.
Для начала рассмотрим простой пример сборки. Допустим, у нас есть небольшая программа, которая пишет 5 раз фразу "Hello make" в консоль. Напишем Makefile для нашей простой программы с таким содержанием:
1 2 3 4 5 6 7 8 |
all: BinDir gcc -std=c99 -Wall main.c -o ./bin/helloMake BinDir: mkdir bin clean: rm -r bin |
В первой строке нашего Makefile написана цель сборки "all:". Названия целей можно писать любое. Но, обычно для имени конечной цели (главная сборка проекта) пишут именно "all". Я читал, что при вызове make без явного указания цели, то автоматически выполняется цель "all" (но мои эксперименты показали, что в Makefile без цели "all" выполняется первая цель в списке). Через двоеточие указываются зависимости для цели. То есть, прежде чем выполнится цель "all", сперва будут выполняться ее зависимости, в нашем случае это "BinDir". На второй строке идет команда для выполнения. Команды обязательно отделяются табуляцией! Таков синтаксис, без табуляции команды будут игнорироваться утилитой и ничего не выполнится. В качестве команд можно писать все, что необходимо. В нашем случае, мы просто компилируем нашу программу в директорию ./bin (которая создается в цели "BinDir") с помощью компилятора gcc. Последняя цель "clean" чистит результаты сборки проекта - то есть мы просто удалили каталог проекта bin. Это запись является традиционная для Makefile'ов, поэтому писать подобные цели - хороший тон.
Вызов утилиты происходит в каталоге, где находится Makefile с помощью команды в консоли make [цель]. Указание цели при вызове make может быть пустой, как я уже выше писал, тогда просто выполнится первая цель в списке. Процесс сборки и выполнения показан на картинке ниже.
Для более сложной задачи, допустим, у нас есть дистрибутив Ubuntu 14.04, есть небольшой проект на C99 - надо написать Makefile. Пускай сам проект лежит в директории /home/tetraquark/Bomberta. Тут находится файл конфига приложения - settings и сам файл Makefile для утилиты make. Все заголовочные файлы лежат тут: /home/tetraquark/Bomberta/include, файлы с исходным кодом тут: /home/tetraquark/Bomberta/src. В Makefile'ах есть возможность использовать переменные и всю мощь bash скриптов. Вообще, стоит поискать и посмотреть примеры реализаций в разных библиотеках под Linux на C, в которых чаще всего используется именно make. Например, zlib, ncurses и другие. Вот так выглядит реализация Makefile для нашего проекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# Название компилятора CC = gcc # Место расположения каталога проекта, где лежит Makefile PROJECTPATH = /home/tetraquark/Bomberta # Путь до исходников SRCDIR = $(PROJECTPATH)/src # Флаги для компилятора CFLAGS = -std=c99 # Подключаемые библиотеки LIBS = -lncurses -lform # Каталог, куда будут сохраняться результаты сборки BINDIR = $(PROJECTPATH)/bin all: Bindir Setting Menu.o EventListener.o Render.o Client.o $(CC) $(CFLAGS) $(SRCDIR)/main.c $(BINDIR)/Menu.o $(BINDIR)/EventListener.o $(BINDIR)/Render.o $(BINDIR)/Client.o -o $(BINDIR)/main $(LIBS) Client.o: Bindir $(CC) $(CFLAGS) -c $(SRCDIR)/Client.c -o $(BINDIR)/Client.o -lsocket EventListener.o: Bindir $(CC) $(CFLAGS) -c $(SRCDIR)/EventListener.c -o $(BINDIR)/EventListener.o Render.o: Bindir $(CC) $(CFLAGS) -c $(SRCDIR)/Render.c -o $(BINDIR)/Render.o Menu.o: Bindir $(CC) $(CFLAGS) -c $(SRCDIR)/Menu.c -o $(BINDIR)/Menu.o Setting: Bindir cp settings $(BINDIR)/settings Bindir: mkdir $(BINDIR) clean: rm -r $(BINDIR) |
Это не самая лучшая и удобная реализация, можно придумать и додумать что-то свое под нужды проекта. Следующий шаг автоматизации сборки - написание скрипта, например, на bash, который исходя из входных настроек будет генерировать нужный Makefile с нужными параметрами. Такие скрипты также создаются разработчиками, чаще всего они имеют название "Configure".