Эффективное использование программы make. Сборка программы с помощью GNU Make

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

В этой заметке я попытаюсь рассказать как создать makefile.

По умолчанию правила сборки считываются из файла с именем Makefile.

Структуру Makefile можно представить так:

ЦЕЛЬ: ЗАВИСИМОСТЬ ДЕЙСТВИЕ

Но обычно используются более сложные правила, например:

ЦЕЛЬ: ЦЕЛЬ1 ЦЕЛЬ2 ДЕЙСТВИЕ ЦЕЛЬ1: ЗАВИСИМОСТЬ1 ДЕЙСТВИЕ1 ЦЕЛЬ2: ЗАВИСИМОСТЬ2 ДЕЙСТВИЕ2

ЦЕЛЬ — это то что мы получаем в результате ДЕЙСТВИЯ. Это может быть файл, директория или просто абстрактная ЦЕЛЬ не имеющая связи с каким-либо объектом на жестком диске. После имени цели ставится двоеточие. При запуске команды make без параметров выполнится первое найденное правило. Что бы выполнить другое правило надо указать его команде make

Make ЦЕЛЬ2

ЗАВИСИМОСТЬ — это то, от чего зависит наша ЦЕЛЬ. Это могут быть файлы, каталоги или другие ЦЕЛИ. Make сравнивает дату и время изменения ЦЕЛИ и объектов о которых зависит цель. Если объекты от которых зависит цель были изменены позже чем создана цель, то будет выполнено ДЕЙСТВИЕ. ДЕЙСТВИЕ так же выполняется если ЦЕЛЬ не является именем файла или директории.

ДЕЙСТВИЕ — это набор команд которые надо выполнить. Перед командами должен быть введен символ табуляции. Если вместо символа табуляции будут введены пробелы то при компиляции будет выведено сообщение об ошибке:

Makefile:13: *** пропущен разделитель. Останов.

Makefile:13: *** missing separator. Stop.

Пример Makefile:

all: test.elf test.elf: test1.o test2.o gcc -o test.elf test1.o test2.o test1.o test1.c gcc -c test1.c -o test1.o test2.o test2.c gcc -c test2.c -o test2.o

Рассмотри последний пример:
Первым выполняется all т.к. находится в начале Makefile. all зависит от test.elf и файла или директории с именем all не существует, поэтому всегда будет происходить проверка цели с именем test.elf.

test.elf зависит от test1.o и test2.o, по этому сначала будет проверена цель test1.o затем test2.o

При проверке цели test1.o сравниваются дата и время изменения файла test1.o и test1.c. Если файл test1.o не существует или файл test1.c был изменены позднее чем test1.o то будет выполнена команда gcc -с test1.c -o test1.o.

Аналогично будет проверена цель test2.o.

После этого сравниваются дата и время изменения файла test.elf и файлов test1.o test2.o. Если test1.o или test2.o новее то будет выполнена команда gcc -o test.elf test1.o test2.o

Таким образом отслеживаются изменения в файлах test1.с и test2.c.

P/S надеюсь данная заметка упростит создание makefile, но если есть какие-то вопросы — напишите в комментарии, постараюсь ответить.

Утилита автоматически определяет, какие части большой программы должны быть перекомпилированы и команды для их перекомпиляции. Наиболее часто make используется для компиляции C-программ и содержит особенности ориентированные именно на такие задачи, но можно использовать make с любым языком программирования. Более того, применение утилиты make не ограничивается программами. Можно использовать еe для описания любой задачи, где некоторые файлы должны автоматически порождаться из других всегда, когда те изменяются.

make-file

Прежде чем использовать make , необходимо создать файл, называемый make-файлом , который описывает отношения между файлами Вашей программы и содержит команды для обновления каждого файла. Обычно исполняемый файл зависит от объектных файлов, которые, в свою очередь, зависят от исходных файлов и файлов заголовков. Для имени make-файла рекомендуется название GNUmakefile , makefile или Makefile , причем поиск идет именно в указанном порядке. Если необходимо использовать нестандартное имя, то его можно передать явно через опцию -f .
Когда make-файл уже написан, достаточно выполнить в каталоге в котором он находится команду make . Простой make-файл состоит из правил(инструкций) следующего вида:


ПЕРЕМЕННАЯ = ЗНАЧЕНИЕ...
ЦЕЛЬ... : ЗАВИСИМОСТЬ...
КОМАНДА 1
КОМАНДА 2
ПЕРЕМЕННАЯ = ЗНАЧЕНИЕ...
ЦЕЛЬ... : ЗАВИСИМОСТЬ...
КОМАНДА 1
КОМАНДА 2

и т.д.

ЦЕЛЬ обычно представляет собой имя файла, генерируемого программой make ; примерами целей являются исполняемые или объектные файлы. Цель может также быть именем выполняемого действия, как, например, "clean ".
ЗАВИСИМОСТЬ - это файл, изменение которого служит признаком необходимости цели. Часто цель зависит от нескольких файлов.
КОМАНДА - это действие, которое выполняет make . Правило может иметь более чем одну команду - каждую на своей собственной строке. Важное замечание: необходимо начинать каждую строку, содержащую команды, с символа табуляции. Длинные строки разбиваются на несколько с использованием обратного слэша, за которым следует перевод строки. Знак диез # является началом комментария. Строка с # до конца игнорируется. Комментарии могут переноситься на несколько строк с помощью обратного слэша в конце строки.

Пример makefile

Использование действий по умолчанию


#default target - file edit
edit: main.o kbd.o command.o display.o \
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

Main.o: main.c defs.h
cc -c main.c
kbd.o: kbd.c defs.h command.h
cc -c kbd.c
command.o: command.c defs.h command.h
cc -c command.c
display.o: display.c defs.h buffer.h
cc -c display.c
insert.o: insert.c defs.h buffer.h
cc -c insert.c
search.o: search.c defs.h buffer.h
cc -c search.c
files.o: files.c defs.h buffer.h command.h
cc -c files.c
utils.o: utils.c defs.h
cc -c utils.c
clean:
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

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

Специальная цель .PHONY является встроенной в make и определяет свои зависимости как цели-имена, которым нет соответствия в виде файлов. Если данное правило пропустить, то создание в текущем каталоге файла с именем clean заблокирует выполнение make clean .
Использование правил по умолчанию позволяет изменить стиль записей зависимостей:

Квадратные скобки означают необязательность присутствия данной части.
Цель - имя цели, которую надо выполнить.
Переменная ="abc" -переопределение переменных. Значения переменных введенных в командной строке имеют больший приоритет, чем определения в make-файле.
Опции:
-f file - явное задание имени make-файла , если задание опущено, то ищются файлы GNUmakefile , makefile или Makefile
-n ; - имитация действий без реального выполнения, служит для отладки
-t - изменение времени модификации цели без реального выполнения
-q - проверка на необходимость обновления цели без реального выполнения

Утилита make предназначена для автоматизации сборки проектов. Если какие-либо файлы проекта могут быть сгенерированы из других файлов, утилита позволяет выполнить процесс построения наиболее оптимальным способом, по возможности минимизируя количество обрабатываемых файлов.

Исторически утилита предназначалась для сборки проектов на языке C в операционной системе Unix, однако может быть использоваться для работы с любыми проектами. Первая версия системы была создана в 1977 году.

На сегодняшний день наиболее распространены три варианта утилиты, объединенные общими принципами работы, но отличающиеся синтаксисом языка и возможностями:

    GNU make - самый распространенный и функциональный вариант

    BSD make (pmake) - используется в проектах BSD, по функциональности примерно соответствует GNU make

    nmake (Microsoft make) - работает под Windows, малофункционален, только базовые принципы make.

Мы работаем с GNU make. На BSD системах (в частности, FreeBSD, он может быть доступен как gmake, на Linux - просто make).

Основные принципы

Утилита make работает по правилам (rules) , записанным в специальном конфигурационном файле. Правила определяют цели (targets) , завимости между целями и набор команд для выполнения каждой цели.

Цели могут соответствовать определенным файлам. Кроме того, цели могут не соответствовать ни одному файлу и использоваться для группировки других целей или определенной последовательности команд. Такие цели называются phony targets.

Каждая цель может зависеть от выполнения других целей. Выполнение цели требует предварительного выполнения других целей, от которых она зависит.

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

Таким образом, makefile определяет граф зависимостей, по которому утилита make выполняет ту или иную цель, по возможности минимизируя количество операций сборки.

Запуск make

Несмотря на то, что для make можно указать произвольный файл правил, как правило используют стандартное имя Makefile . Поддерживаются также несколько альтернативных имен по умолчанию, но имеет смысл использовать наиболее распространенное.

Соответственно, команда

$ make

будет использовать файл Makefile , находящийся в текущем каталоге.

При запуске make можно указать цель, которая будет выполнена. Если цель не указана, используется цель по умолчанию, которая либо указана в файле правил явно, либо неявно используется первая определенная цель.

Явное указание цели выполняется инструкцией DEFAULT_GOAL в Makefile:

. DEFAULT_GOAL: all

Например, команда

$ make clean

вызовет обработку цели clean файла Makefile , находящегося в текущем каталоге.

Можно указать сразу несколько целей.

Выполнение целей может быть настроено с использованием переменных (о которых ниже). При запуске make можно указать значения переменных:

$ make build PREFIX =/ usr/ local

Значение переменной PREFIX будет доступно в правилах Makefile и может быть использовано при сборке.

Команда поддерживает также ряд дополнительных опций, из которых наиболее важные следующие:

    F - позволяет явно указать файл правил

    C - переходит в указанный каталог перед выполнением, может быть, например, использована для запуска make из внешнего каталога по отношению к каталогу проекта

    B - отключает проверку времени зависимых целей и принудительно выполняет их полностью

Базовый синтаксис make

Основная конструкция, используемая в файлах make , выглядит следующим образом:

Target: dep1 dep2 ... command1 command2 ...

    target - цель

    dep1 , dep2 - цели, от которых зависит цель target

    command1 , command2 - команды, выполняемые для достижения цели target

Например:

Style. css: src/ less/ app. less lessc src/ less/ app. less > style. css

Этот фрагмент определяет, что файл style.css зависит от файла src/less/app.less и для его сборки необходимо выполнить команду lessc src/less/app.less > style.css . Перегенерация файла style.css будет выполняться только в случае,если файл src/less/app.less новее, чем файл style.css (до тех пор, пока при запуске make не будет указан ключ -B).

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

В качестве команд обработки целей используются команды shell. Текст команды выводится, для того, чтобы он не выводился, команду необходимо начать с символа @ .

Каждая команда запускается в отдельном интерпретаторе shell, таким образом, команды не связаны друг с другом. Иначе говоря, одна строка команды - один shell. Это поведение может быть переопределено с помощью специальной цели.ONESHELL .

Если команду (или список зависимостей) необходимо записать в несколько строк, используют символ переноса \ .

PHONY targets

Цели, не соответствующие файлам, и предназначенные для выполнения набора команд или группировки завимостей, декларируются следующим образом:

.PHONY : clean clean: rm *. o temp

Деклараций.PHONY может быть несколько, обычно определяют одну и прописывают туда все соответствующие цели.

В нашем примере вызов make clean приведет к выполнению цели clean , которая безусловно выполнит удаление временных файлов.

В случае, если у phony target есть зависимость в виде другой phony target, то зависимость выполняется перед зависящей целью. Таким образом, мы получаем механизм, напоминающий подпрограммы. Например, мы можем определить цель all , собирающую все файлы проекта, и отдельные цели css , js и php , собирающие отдельной css -файлы, js -файлы и обрабатывающие php файлы.

Соответственно, в Makefile мы можем написать:

.PHONY : all css js php all: css js php css: www/ style. css ... тут команды js: www/ js/ app. js ... тут еще команды php: ... тут снова команды

В результате мы можем использовать make all для пересборки всех файлов и, скажем, make css для пересборки только CSS -файлов.

Переменные

В make-файле можно использовать переменные, хотя правильнее сказать, что можно использовать макросы.

Переменные определяются присваиванием в makefile или могут быть переданы извне.

Переменные - это макроопределения, причем вычисление переменной всегда выполняется в самый последний момент перед подстановкой. Макросы могут использовать везде в тексте makefile.

СС= gcc IDIR=../ include CFLAGS=- I$ (IDIR ) DEPS= hellomake. o hellofunc. p NAME= hellomake $ (NAME ) : $ (DEPS ) $ (CC ) - o $ (NAME ) $ (DEPS )

Подстановка выполняется конструкцией $(VAR) в отличие от shell, где используется $VAR .

Если в shell команде используется shell-переменная, необходимо квотить знак $ , дублируя его, например:

Printhome: echo $$ HOME

Помимо макропеременных существуют и более традиционные, в которых значение устанавливается сразу. Для работы с ними используется оператор:= . В наших условиях достаточно использовать обычные переменные.

Часто требуется определить переменную только в том случае, если она еще не была определена. Для этого используется оператор?= :

MYSQL_CHARSET ?= UTF8

Соответственно, если мы вызовем

make create-database

будет использована кодировка UTF8 , а в случае

Make create- database MYSQL_CHARSET= CP1251

будет использована CP1251 .

Если переменная содержит несколько строк, можно использовать синтаксис define:

Define MYSQL_APP_CONF_TEMPLATE [ mysql] host=$ (MYSQL_SERVER ) port=$ (MYSQL_PORT ) user=$ (MYSQL_USER ) password= "$(MYSQL_PASSWORD)" endef

Автоматические переменные

Make поддерживает набор автоматических переменных, облегчающих написание правил. Например, переменная $@ соответствую текущей цели (то, что слева от:), а переменная $^ - списку зависимостей (то, что справа от:). Таким образом, например, можно написать:

Www/ js/ script. js: src/ js/ jquery. js src/ js/ plugin1. js src/ js/ plugin2. js cat $^ > $@

В результате www/js/script.js будет результатом объединения трех js-файлов.

Полный список таких переменных приведен в документации, для нас наиболее интересны:

    $@ - имя цели

    $< - имя первой зависимости

    $? - имена всех зависимостей, которые новее чем цель

    $^ - имена всех зависимостей цели

С полным списком можно ознакомиться в документации: Automatic Variables .

Условное выполнение

В Makefile можно использовать условные выражения. Опять же, мы говорим о макрообработке make, соответственно, условные выражения работают на уровне makefile, а не на уровне команд. Обычно условные выражения используются для определения тех или иных целей в зависимости от значения переменных. Например:

ifdef $ (APP ) setup: ... else setup: @ echo "Error, applications is not defined" endif

В качестве условий можно проверять определенность переменной, а также ее значение:

Foo: $ (objects ) ifeq ($ (CC ) , gcc) $ (CC ) - o foo $ (objects ) $ (libs_for_gcc ) else $ (CC ) - o foo $ (objects ) $ (normal_libs ) endif

Полностью с возможностями условных выражений можно ознакомиться в документации: Conditional syntax .

Шаблонные правила

Шаблонные правила (pattern rules) позволяют указать правило преобразования одних файлов в другие на основании зависимостей между их именами. Например, мы можем указать правило для получения объектного файла из файла на языке C:

%. o: %. c $ (CC ) $< - o $@

Обратите внимание на использование переменной %< , которая в таких правилах используется для получения имени исходного файла.

Шаблоны не обязаны ограничиваться расширениями файлов. Если исходные и выходные файлы соответствуют друг другу и в их именах есть какая-либо зависимость, можно использовать pattern rule.

Включение других файлов make

Файл make может подключить другие файлы make оператором include:

include make1. mk make2. mk

Таким образом, из файлов можно строить модульную систему, часто имеет смысл выполнять include внутри условного оператора.

Функции

Make определяет большой набор функций, которые могут быть использованы в переменных (макросах). Вызов функции выполняется конструкцией:

$ (function arg1, arg2,... )

Функции позволяют обрабатывать строки, имена файлов, организовывать циклы по набору значений, организовывать условный вызов других функций.

Несколько примеров из hive. Получаем текущее время (обратите внимание на использование:= :

HIVE_TIME := $ (shell date +% Y/% m/% d\ % H:% M:% S)

Включение файла container.mk только в случае, если он существует:

include $ (shell find $ (HIVE_ETC_DIR ) - name container. mk)

Формирование имени MySQL базы по имени проекта:

MYSQL_DB ?= $ (subst -, _,$ (HIVE_NAME ) _$ (APP ) )

Добавление префиксов и суффиксов к именам файлов

$ (addprefix src/ less/,$ (addsuffix . less, app ui catalog) )

Подробнее о функциях можно прочитать в документации Functions .

Собственные функции

Можно создавать собственные параметризованные функции путем определения переменных, содержащих специальные переменные $1 , $2 , ..., соответствующие переданным аргументам. Вызов пользовательской функции производится специальным макросом call:

$ (call variable, param, param,... )

Очень тупой пример:

Hive_red = "\0 33 Specifies the number of jobs (commands) to run simultaneously. ...

Проверим преимущества, предоставляемые этой возможностью на практическом примере. В качестве эталона для сборки возьмём проект NTP-сервера, который собирается не очень долго, но и не слишком быстро:

$ pwd /usr/src/ntp-4.2.6p3

Сначала запустим сборку на 4-х ядерном процессоре Atom (не очень быстрая модель с частотой 1.66Ghz) но с очень быстрым твердотельным диском SSD:

$ cat /proc/cpuinfo | head -n10 processor: 0 vendor_id: GenuineIntel cpu family: 6 model: 28 model name: Intel(R) Atom(TM) CPU 330 @ 1.60GHz stepping: 2 cpu MHz: 1596.331 cache size: 512 KB $ make clean # запускаем сборку в четыре потока $ time make -j4 ... real 1m5.023s user 2m40.270s sys 0m16.809s $ make clean # запускаем сборку в стандартном режиме без параллелизма $ time make ... real 2m6.534s user 1m56.119s sys 0m12.193s $ make clean # запускаем сборку с автоматическим выбранным уровнем параллелизма $ time make -j ... real 1m5.708s user 2m43.230s sys 0m16.301s

Как можно заметить, использование параллелизма (явное или не явное) позволяет ускорить сборку почти в два раза – 1 минута против 2-ух. Выполним сборку этого же проекта на более быстром 2-х ядерном процессоре, но с достаточно медленным обычным диском HDD:

$ cat /proc/cpuinfo | head -n10 processor: 0 vendor_id: GenuineIntel cpu family: 6 model: 23 model name: Pentium(R) Dual-Core CPU E6600 @ 3.06GHz stepping: 10 cpu MHz: 3066.000 cache size: 2048 KB ... $ time make ... real 0m31.591s user 0m21.794s sys 0m4.303s $ time make -j2 ... real 0m23.629s user 0m21.013s sys 0m3.278s

Хотя итоговая скорость сборки и выросла в 3-4 раза, но улучшение от числа процессоров составляет только порядка 20%, так как «слабым звеном» здесь является медленный накопитель, допускающий задержку при записи большого числа мелких.obj файлов проекта.

Примечание : Хотелось бы напомнить, что не всякая сборка make , которая успешно выполняется на одном процессоре (как это имеет место по умолчанию или при указании -j1 ), будет также успешно выполняться при большем числе задействованных процессоров. Это связано с нарушениями синхронизации операций в случаях сложных сборок. Самым наглядным примером такой сборки, завершающейся с ошибкой в случае параллельного исполнения, является сборка ядра Linux для некоторых версий ядра. Возможность параллельного выполнения make нужно экспериментально проверять для собираемого проекта. Но в большинстве случаев это возможность может использоваться и позволяет в разы ускорить процесс сборки!

Если данный способ ускорения процесса сборки основан на том, что сейчас подавляющее большинство систем являются многопроцессорными (многоядерными), то следующий способ использует тот факт, что объём памяти RAM современных компьютеров (2-4-8 ГБ) значительно превышает объём памяти, необходимый для компиляции программного кода. В таком случае, компиляцию, основным сдерживающим фактором для которой является создание множества объектных файлов, можно перенести в область специального созданного диска (RAM диск, tmpfs ), расположенного в памяти:

$ free total used free shared buffers cached Mem: 4124164 1516980 2607184 0 248060 715964 -/+ buffers/cache: 552956 3571208 Swap: 4606972 0 4606972 $ df -m | grep tmp tmpfs 2014 1 2014 1% /dev/shm

Теперь можно временно перенести файлы собираемого проекта в tmpfs (мы по-прежнему используем NTP-сервер из предыдущего примера), в каталог /dev/shm :

$ pwd /dev/shm/ntp-4.2.6p3 $ make -j ... real 0m4.081s user 0m1.710s sys 0m1.149s

В данном случае одновременно используются оба способа повышения производительности, и улучшение относительно исходной компиляции достигает почти порядка. Правда, этот пример запускался на системе с медленным HDD, где параллельная сборка практически не давала выигрыша и требовала порядка 30 секунд.

Этот способ ускорения можно применить к сборке ядра Linux, для которого, как уже было сказано, параллельная сборка не работает. Чтобы воспользоваться преимуществами RAM-памяти, скопируем дерево исходных кодов ядра в каталог /dev/shm :

$ pwd /dev/shm/linux-2.6.35.i686 $ time make bzImage ... HOSTCC arch/x86/boot/tools/build BUILD arch/x86/boot/bzImage Root device is (8, 1) Setup is 13052 bytes (padded to 13312 bytes). System is 3604 kB CRC 418921f4 Kernel: arch/x86/boot/bzImage is ready (#1) real 9m23.986s user 7m4.826s sys 1m18.529s

Как видно, сборка ядра Linux заняла менее 10 минут, что является необычайно хорошим результатом.

В качестве вывода, можно посоветовать тщательно оптимизировать условия сборки проекта под используемое для этого оборудование, и, учитывая, что в процессе отладки сборка выполняется сотни раз, то можно сэкономить множество времени!

Сборка модулей ядра

Частным случаем сборки приложений является сборка модулей ядра Linux (драйверов). Начиная с версий ядра 2.6, для сборки модуля составляется Makefile , построенный на использовании макросов, и нам остаётся только записать (для файла собственного кода с именем mod_params.c ), следующий шаблон для сборки модулей:

Листинг 1. Makefile для сборки модулей ядра
CURRENT = $(shell uname -r) KDIR = /lib/modules/$(CURRENT)/build PWD = $(shell pwd) TARGET = mod_params obj-m:= $(TARGET).o default: $(MAKE) -C $(KDIR) M=$(PWD) modules ... $ make make -C /lib/modules/2.6.18-92.el5/build \ M=examples/modules-done_1/hello_printk modules make: Entering directory `/usr/src/kernels/2.6.18-92.el5-i686" CC [M] /examples/modules-done_1/hello_printk/hello_printk.o Building modules, stage 2. MODPOST CC /examples/modules-done_1/hello_printk/hello_printk.mod.o LD [M] examples/modules-done_1/hello_printk/hello_printk.ko make: Leaving directory `/usr/src/kernels/2.6.18-92.el5-i686" $ ls -l *.o *.ko -rw-rw-r-- 1 olej olej 74391 Мар 19 15:58 hello_printk.ko -rw-rw-r-- 1 olej olej 42180 Мар 19 15:58 hello_printk.mod.o -rw-rw-r-- 1 olej olej 33388 Мар 19 15:58 hello_printk.o $ file hello_printk.ko hello_printk.ko: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped $ /sbin/modinfo hello_printk.ko filename: hello_printk.ko author: Oleg Tsiliuric license: GPL srcversion: 83915F228EC39FFCBAF99FD depends: vermagic: 2.6.18-92.el5 SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1

Заключение

В статье были рассмотрены аспекты работы с утилитой make, которые не часто описываются в литературе, но могут оказаться крайне полезными в практической работе. Также мы завершили обсуждение вопросов, связанных с поставкой и сборкой программного обеспечения в ОС Linux.

В следующей статье мы начнём знакомство с библиотеками API, присутствующими в POSIX системах.

Происхождение

До создания make системы сборки (компиляции) ПО Unix обычно состояли из shell -скриптов сборки, сопровождавших исходный код программ.

make была создана Стюартом Фельдманом (Stuart Feldman ) в 1977 году в Bell Labs .

В настоящее время существует множество утилит для отслеживания зависимостей, но make - одна из самых широко распространённых, в первую очередь благодаря тому, что она включена в Unix , начиная с версии PWB/UNIX (for Programmer’s Workbench ), которая содержала инструменты для разработки программного обеспечения .

Современные версии

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

  • BSD make , основанная на работе Адама де Бура (Adam de Boor ) над версией make , с возможностью параллельной сборки; в той или иной форме перешла в FreeBSD , NetBSD и OpenBSD .
  • GNU make - входит в большинство дистрибутивов GNU/Linux и часто используется в сочетании с GNU build system .
$ make love Not war. $ uname -r 7.1-RELEASE-p3

Make-файл

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

make-файл состоит из правил и переменных. Правила имеют следующий синтаксис:

Цель1 цель2 ...: реквизит1 реквизит2 ... команда1 команда2 ...

Правило представляет собой набор команд, выполнение которых приведёт к сборке файлов-целей из файлов-реквизита .

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

Обычно цель представляет собой имя файла, который генерируется в результате работы указанных команд. Целью также может служить название некоторого действия, которое будет выполнено в результате выполнения команд (например, цель clean в make-файлах для компиляции программ обычно удаляет все файлы, созданные в процессе компиляции).

Строки, в которых записаны команды , должны начинаться с символа табуляции.

Рассмотрим несложную программу на Си. Пусть программа program состоит из пары файлов кода - main.c и lib.c, а также из одного заголовочного файла - defines.h, который подключён в оба файла кода. Поэтому, для создания program необходимо из пар (main.c defines.h) и (lib.c defines.h) создать объектные файлы main.o и lib.o, а затем слинковать их в program. При сборке вручную требуется дать следующие команды:

Cc -c main.c defines.h cc -c lib.c defines.h cc -o program main.o lib.o

Если в процессе разработки программы в файл defines.h будут внесены изменения, потребуется перекомпиляция обоих файлов и линковка, а если изменим lib.c, то повторную компиляцию main.о можно не выполнять.

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

  • собирает из этой информации правильную последовательность команд для получения требуемых результирующих файлов;
  • и инициирует создание требуемого файла только в случае, если такого файла не существует, или он старше, чем файлы от которых он зависит.

Если при запуске make явно не указать цель, то будет обрабатываться первая цель в make-файле, имя которой не начинается с символа «.».

Для программы program достаточно написать следующий make-файл:

Program: main.o lib.o cc -o program main.o lib.o main.o lib.o: defines.h

Стоит отметить ряд особенностей. В имени второй цели указаны два файла и для этой же цели не указана команда компиляции. Кроме того, нигде явно не указана зависимость объектных файлов от «*.c»-файлов. Дело в том, что программа make имеет предопределённые правила для получения файлов с определёнными расширениями. Так, для цели-объектного файла (расширение «.o») при обнаружении соответствующего файла с расширением «.c» будет вызван компилятор «сс -с» с указанием в параметрах этого «.c»-файла и всех файлов-зависимостей.

Синтаксис для определения переменных:

Переменная = значение

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

OBJ = main.o lib.o program: $(OBJ) cc -o program $(OBJ) $(OBJ): defines.h

Нужно отметить, что вычисление значение переменных происходит только в момент использования (используется так называемое ленивое вычисление). Например, при сборке цели all из следующего make-файла на экран будет выведена строка «Huh?».

Foo = $(bar) bar = $(ugh) ugh = Huh? all: echo $(foo)

Lib.o: lib.h

Таким образом, один целевой файл может указываться в нескольких целях. При этом полный список зависимостей для файла будет составлен из списков зависимостей всех целей, в которых он участвует, создание файла будет производиться только один раз.

См. также

Ссылки

  • Руководство по GNU make на gnu.org (англ.)
  • Руководство по GNU make (версия 3.79) (рус.)
  • Руководство по FreeBSD make (англ.)
  • Решение проблем, возникающих при выполнении команд./configure , make и make install
  • Применение GNU make (рус.)
  • Эффективное использование GNU Make (рус.)
  • Справка по составлению Makefile (рус.)

Wikimedia Foundation . 2010 .

  • Галлер, Лев Михайлович
  • Камаргу (лошадь)

Смотреть что такое "Make" в других словарях:

    make - make, v. t. d); p. pr. & vb. n. {making}.] 1. To cause to … The Collaborative International Dictionary of English

    make - make1 vt. made, making 1. to bring into being; specif., a) to form by shaping or… … English World dictionary

    Make - (engl. machen, erstellen) ist ein Computerprogramm, das Shellskript ähnlich Kommandos in Abhängigkeit von Bedingungen ausführt. Es wird hauptsächlich bei der Softwareentwicklung eingesetzt. Genutzt wird es beispielsweise, um in einem Projekt, das … Deutsch Wikipedia

    Make - Cet article a pour sujet le logiciel intitulé make. Pour une définition du mot « make », voir l’article make du Wiktionnaire. make est un logiciel traditionnel d UNIX. C est un « moteur de production »: il sert à appeler … Wikipédia en Français

    make - (engl. machen, erstellen) ist ein Computerprogramm, das Kommandos in Abhängigkeit von Bedingungen ausführt. Es wird hauptsächlich bei der Softwareentwicklung als Programmierwerkzeug eingesetzt. Genutzt wird es beispielsweise, um in Projekten, die … Deutsch Wikipedia