Что делает команда make в linux. Make-файлы. Самый простой Мейкфайл

Для Windows 20.03.2019

У вас, вероятно, появился вопрос: можно ли не компилировать эти файлы по отдельности, а собрать сразу всю программу одной командой? Можно.

gcc calculate.c main.c -o kalkul -lm

Вы скажете, что это удобно? Удобно для нашей программы, потому что она состоит всего из двух c-файлов. Однако профессиональная программа может состоять из нескольких десятков таких файлов. Каждый раз набирать названия их всех в одной строке было бы делом чрезмерно утомительным. Но есть возможность решить эту проблему. Названия всех исходных файлов и все команды для сборки программы можно поместить в отдельный текстовый файл. А потом считывать их оттуда одной короткой командой.

Давайте создадим такой текстовый файл и воспользуемся им. В каталоге проекта kalkul2 удалите все файлы, кроме calculate.h, calculate.c, main.c. Затем создайте в этом же каталоге новый файл, назовите его Makefile (без расширений). Поместите туда следующий текст.

Kalkul: calculate.o main.o gcc calculate.o main.o -o kalkul -lm calculate.o: calculate.c calculate.h gcc -c calculate.c main.o: main.c calculate.h gcc -c main.c clean: rm -f kalkul calculate.o main.o install: cp kalkul /usr/local/bin/kalkul uninstall: rm -f /usr/local/bin/kalkul

Обратите внимание на строки, введённые с отступом от левого края. Этот отступ получен с помощью клавиши Tab. Только так его и надо делать! Если будете использовать клавишу «Пробел», команды не будут исполняться.

Затем дадим команду, состоящую всего из одного слова:

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

Makefile является списком правил. Каждое правило начинается с указателя, называемого «Цель». После него стоит двоеточие, а далее через пробел указываются зависимости. В нашем случае ясно, что конечный файл kalkul зависит от объектных файлов calculate.o и main.o. Поэтому они должны быть собраны прежде сборки kalkul. После зависимостей пишутся команды. Каждая команда должна находиться на отдельной строке, и отделяться от начала строки клавишей Tab. Структура правила Makefile может быть очень сложной. Там могут присутствовать переменные, конструкции ветвления, цикла. Этот вопрос требует отдельного подробного изучения.

Если мы посмотрим на три первых правила, то они нам хорошо понятны. Там те же самые команды, которыми мы уже пользовались. А что же означают правила clean, install и uninstall?

В правиле clean стоит команда rm, удаляющая исполняемый и объектные файлы. Флаг -f означает, что, если удаляемый файл отсутствует, программа должна это проигнорировать, не выдавая никаких сообщений. Итак, правило clean предназначено для «очистки» проекта, приведения его к такому состоянию, в каком он был до команды make.

Запустите

Появились объектные файлы и исполняемый. Теперь

Объектные и исполняемый файлы исчезли. Остались только c-файлы, h-файл и сам Makefile. То есть, проект «очистился» от результатов команды make.

Правило install помещает исполняемый файл в каталог /usr/local/bin - стандартный каталог размещения пользовательских программ. Это значит, что её можно будет вызывать из любого места простым набором её имени. Но помещать что-либо в этот каталог можно только, зайдя в систему под «суперпользователем». Для этого надо дать команду su и набрать пароль «суперпользователя». В противном случае система укажет, что вам отказано в доступе. Выход из «суперпользователя» осуществляется командой exit. Итак,

Теперь вы можете запустить это программу просто, введя имя программы, без прописывания пути.

Можете открыть каталог /usr/local/bin. Там должен появиться файл с названием kalkul.

Давайте теперь «уберём за собой», не будем засорять систему.

Посмотритекаталог /usr/local/bin. Файл kalkul исчез. Итак, правило uninstall удаляет программу из системного каталога.

Дмитрий Пантелеичев (dimanix2006 at rambler dot ru) - Make-файлы
Linux: Полное руководство Колисниченко Денис Николаевич

21.2. Сборочная утилита make

21.2. Сборочная утилита make

Если вы уже собирали прикладную программу из исходных кодов, то обратили внимание на стандартную последовательность команд: make; make install .

Без утилиты make не обходится создание ни одного серьезного проекта. Эта утилита управляет сборкой большого проекта, состоящего из десятков и сотен файлов. Программа make может работать не только с компилятором gcc, но и с любым компилятором для любого языка программирования, способным запускаться из командной строки.

Директивы утилиты make служат для определения зависимостей между файлами проекта и находятся в файле по имени Makefile, расположенном в каталоге сборки.

Разберемся, как пишутся make-файлы. Общий формат make-файла выглядит так:

цель1: список_необходимых_файлов

последовательность_команд

цельN: список_необходимых_файлов

последовательностъ_команд

Цель - это метка для некоторой последовательности команд (например, install) или результирующий файл, который нужно «построить» - скомпилировать или скомпоновать.

Цели должны отделяться друг от друга хотя бы одной пустой строкой. Список необходимых файлов - это перечень файлов или других целей, которые нужны для достижения данной цели; он может быть и пустым.

Последовательность команд - это команды, которые нужно выполнить для достижения цели. Последовательность команд должна отделяться от начала строки символом табуляции, иначе вы получите ошибку «missing separator» (нет разделителя).

Make-файл может содержать комментарии - они начинаются символом #.

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

PATH=/usr/include /usr/src/linux/include

MODFLAGS:= -O3 -Wall -DLINUX -I$(PATH)

$(CC) $(MODFLAGS) -c proga.c

Чтобы обратиться к макроопределению в команде или в другом макроопределении, нужно использовать конструкцию $(имя). Макроопределение может включать в себя другое, ранее определенное, макроопределение.

Формат запуска утилиты make:

make [-f файл] [ключи] [цель]

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

Примечание

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

Работа программы make заканчивается, когда достигнута цель, указанная в командной строке. Обычно это цель all , собирающая все результирующие файлы проекта. Другими распространенными целями являются install (установить собранную программу) и clean (удалить ненужные файлы, созданные в процессе сборки).

В листинге 21.2 представлен make-файл, собирающий небольшой проект из двух программ client и server , каждая из которых компилируется из одного файла исходного кода.

Листинг 21.2. Примерный make-файл

all: client server

client: client.с

$(CC) client.с -о client

server: server.с

$(CC) server.с -о server

Обычно при вызове утилиты make не нужно задавать никаких ключей. Но иногда использование ключей бывает очень кстати (таблица 21.1).

Ключи команды make Таблица 21.1

Ключ Назначение
-C каталог Перейти в указанный каталог перед началом работы
-d Вывод отладочной информации
-e Приоритет переменным окружения. Если у нас установлена переменная окружения CC и в Makefile есть переменная с таким же именем, то будет использована переменная окружения
-f файл Использовать указанный файл вместо Makefile
-i Игнорировать ошибки компилятора
-I каталог В указанном каталоге будет производиться поиск файлов, включаемых в Makefile
-j n Запускать не более n команд одновременно
-k Продолжить работу после ошибки, если это возможно
-n Вывести команды, которые должны были выполниться, но не выполнять их
-о файл Пропустить данный файл, даже если в Makefile указано, что он должен быть создан заново
-r Не использовать встроенные правила
-s Не выводить команды перед их выполнением
-w Вывод текущего каталога до и после выполнения команды
Из книги Выпуски 2004 года автора Голубицкий Сергей Михайлович

Из книги Linux From Scratch автора Бикманс Герард

автора Реймонд Эрик Стивен

Установка Make-3.79.1 Приблизительное время компиляции: 0.22 SBU Необходимое дисковое пространство: 6 MBИнсталляция MakeДля инсталляции Make выполните:./configure –prefix=/usr && make && make install && chgrp root /usr/bin/make && chmod 755 /usr/bin/makeПояснения командПо умолчанию, /usr/bin/make принадлежит к той

Из книги Искусство программирования для Unix автора Реймонд Эрик Стивен

Make Официальная ссылкаMake (3.79.1): ftp://ftp.gnu.org/gnu/make/Содержимое MakeПоследняя проверка: версия 3.79.1.ПрограммыmakeОписанияmakemake автоматически определяет, какие части объемной программы нуждаются в рекомпиляции, и выполняет все необходимые для этого команды.Зависимости MakeПоследняя

Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К.

Из книги C++. Сборник рецептов автора Диггинс Кристофер

Из книги автора

Из книги автора

15.4.4.2. Imake Утилита Imake была написана в попытке автоматизировать создание make-файлов для системы X Window. Она надстраивается на makedepend для решения как проблемы вывода зависимостей, так и проблемы переносимости.Imake-система эффективно заменяет традиционные make-файлы 1make-файлами,

Из книги автора

15.8.1. Emacs и make Например, утилиту make можно запустить из Emacs с помощью команды ESC-x compile , Данная команда запускает make(1) в текущем каталоге, собирая вывод в буфер Emacs.Сама по себе данная операция не была бы очень полезной, но Emacs-режим make распознает формат сообщений об ошибках

Из книги автора

15.4. Утилита make: автоматизация процедур Сами по себе исходные коды программ не делают приложения. Также важен способ их компоновки и упаковки для распространения. Операционная система Unix предоставляет инструментальное средство для частичной автоматизации данных

Из книги автора

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

Из книги автора

15.4.2. Утилита make в разработке не на C/C++ Программа make может оказаться полезной не только для программ на C/C++. Языки сценариев, подобные описанным в главе 14, могут не требовать традиционных этапов компиляции и компоновки, однако часто существуют другие виды зависимостей, с

Из книги автора

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

Из книги автора

15.8.1. Emacs и make Например, утилиту make можно запустить из Emacs с помощью команды ESC-x compile . Данная команда запускает make(1) в текущем каталоге, собирая вывод в буфер Emacs.Сама по себе данная операция не была бы очень полезной, но Emacs-режим make распознает формат сообщений об ошибках

Из книги автора

Из книги автора

1.14. Получение GNU make ПроблемаВы хотите получить и установить утилиту GNU make, используемую для сборки библиотек и исполняемых файлов из исходного кода.РешениеРешение зависит от вашей операционной системы.WindowsХотя в некоторых источниках можно получить готовые бинарные

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

Мое упорное игнорирование make в течении долгого времени, было обусловлено удобством используемых IDE, и нежеланием разбираться в этом "пережитке прошлого" (по сути - ленью). Однако, все эти надоедливые кнопочки, менюшки ит.п. атрибуты всевозможных студий, заставили меня искать альтернативу тому методу работы, который я практиковал до сих пор. Нет, я не стал гуру make, но полученных мною знаний вполне достаточно для моих небольших проектов. Данная статья предназначена для тех, кто так же как и я еще совсем недавно, желают вырваться из уютного оконного рабства в аскетичный, но свободный мир шелла.

Make- основные сведения

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

1) целями (то, что данное правило делает);
2) реквизитами (то, что необходимо для выполнения правила и получения целей);
3) командами (выполняющими данные преобразования).

В общем виде синтаксис makefile можно представить так:

# Индентация осуществляется исключительно при помощи символов табуляции, # каждой команде должен предшествовать отступ <цели>: <реквизиты> <команда #1> ... <команда #n>

То есть, правило make это ответы на три вопроса:

{Из чего делаем? (реквизиты)} ---> [Как делаем? (команды)] ---> {Что делаем? (цели)}
Несложно заметить что процессы трансляции и компиляции очень красиво ложатся на эту схему:

{исходные файлы} ---> [трансляция] ---> {объектные файлы}
{объектные файлы} ---> [линковка] ---> {исполнимые файлы}

Простейший Makefile

Предположим, у нас имеется программа, состоящая всего из одного файла:

/* * main.c */ #include int main() { printf("Hello World!\n"); return 0; }
Для его компиляции достаточно очень простого мэйкфайла:

Hello: main.c gcc -o hello main.c
Данный Makefile состоит из одного правила, которое в свою очередь состоит из цели - «hello», реквизита - «main.c», и команды - «gcc -o hello main.c». Теперь, для компиляции достаточно дать команду make в рабочем каталоге. По умолчанию make станет выполнять самое первое правило, если цель выполнения не была явно указана при вызове:

$ make <цель>

Компиляция из множества исходников

Предположим, что у нас имеется программа, состоящая из 2 файлов:
main.c
/* * main.c */ int main() { hello(); return 0; }
и hello.c
/* * hello.c */ #include void hello() { printf("Hello World!\n"); }
Makefile, выполняющий компиляцию этой программы может выглядеть так:

Hello: main.c hello.c gcc -o hello main.c hello.c
Он вполне работоспособен, однако имеет один значительный недостаток: какой - раскроем далее.

Инкрементная компиляция

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

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

Main.o: main.c gcc -c -o main.o main.c hello.o: hello.c gcc -c -o hello.o hello.c hello: main.o hello.o gcc -o hello main.o hello.o
Попробуйте собрать этот проект. Для его сборки необходимо явно указать цель, т.е. дать команду make hello.
После- измените любой из исходных файлов и соберите его снова. Обратите внимание на то, что во время второй компиляции, транслироваться будет только измененный файл.

После запуска make попытается сразу получить цель hello, но для ее создания необходимы файлы main.o и hello.o, которых пока еще нет. Поэтому выполнение правила будет отложено и make станет искать правила, описывающие получение недостающих реквизитов. Как только все реквизиты будут получены, make вернется к выполнению отложенной цели. Отсюда следует, что make выполняет правила рекурсивно.

Фиктивные цели

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

$ make $ make install
Командой make производят компиляцию программы, командой make install - установку. Такой подход весьма удобен, поскольку все необходимое для сборки и развертывания приложения в целевой системе включено в один файл (забудем на время о скрипте configure). Обратите внимание на то, что в первом случае мы не указываем цель, а во втором целью является вовсе не создание файла install, а процесс установки приложения в систему. Проделывать такие фокусы нам позволяют так называемые фиктивные (phony) цели. Вот краткий список стандартных целей:

  • all - является стандартной целью по умолчанию. При вызове make ее можно явно не указывать.
  • clean - очистить каталог от всех файлов полученных в результате компиляции.
  • install - произвести инсталляцию
  • uninstall - и деинсталляцию соответственно.
Для того чтобы make не искал файлы с такими именами, их следует определить в Makefile, при помощи директивы.PHONY. Далее показан пример Makefile с целями all, clean, install и uninstall:

PHONY: all clean install uninstall all: hello clean: rm -rf hello *.o main.o: main.c gcc -c -o main.o main.c hello.o: hello.c gcc -c -o hello.o hello.c hello: main.o hello.o gcc -o hello main.o hello.o install: install ./hello /usr/local/bin uninstall: rm -rf /usr/local/bin/hello
Теперь мы можем собрать нашу программу, произвести ее инсталлцию/деинсталляцию, а так же очистить рабочий каталог, используя для этого стандартные make цели.

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

$ make clean $ make
Для выполнения целей install/uninstall вам потребуются использовать sudo.

Переменные

Все те, кто знакомы с правилом DRY (Don"t repeat yourself), наверняка уже заметили неладное, а именно - наш Makefile содержит большое число повторяющихся фрагментов, что может привести к путанице при последующих попытках его расширить или изменить. В императивных языках для этих целей у нас имеются переменные и константы; make тоже располагает подобными средствами. Переменные в make представляют собой именованные строки и определяются очень просто:

=
Существует негласное правило, согласно которому следует именовать переменные в верхнем регистре, например:

SRC = main.c hello.c
Так мы определили список исходных файлов. Для использования значения переменной ее следует разименовать при помощи конструкции $(); например так:

Gcc -o hello $(SRC)
Ниже представлен мэйкфайл, использующий две переменные: TARGET - для определения имени целевой программы и PREFIX - для определения пути установки программы в систему.

TARGET = hello PREFIX = /usr/local/bin .PHONY: all clean install uninstall all: $(TARGET) clean: rm -rf $(TARGET) *.o main.o: main.c gcc -c -o main.o main.c hello.o: hello.c gcc -c -o hello.o hello.c $(TARGET): main.o hello.o gcc -o $(TARGET) main.o hello.o install: install $(TARGET) $(PREFIX) uninstall: rm -rf $(PREFIX)/$(TARGET)
Это уже посимпатичней. Думаю, теперь вышеприведенный пример для вас в особых комментариях не нуждается.

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

Автоматические переменные предназначены для упрощения мейкфайлов, но на мой взгляд негативно сказываются на их читабельности. Как бы то ни было, я приведу здесь несколько наиболее часто используемых переменных, а что с ними делать (и делать ли вообще) решать вам:
  • $@ Имя цели обрабатываемого правила
  • $< Имя первой зависимости обрабатываемого правила
  • $^ Список всех зависимостей обрабатываемого правила
Если кто либо хочет произвести полную обфускацию своих скриптов - черпать вдохновение можете здесь:

Утилита автоматически определяет, какие части большой программы должны быть перекомпилированы и команды для их перекомпиляции. Наиболее часто 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 - проверка на необходимость обновления цели без реального выполнения

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

Утилита make

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

Утилита make доступна для разных ОС, и из-за особенностей выполнения наряду с «родной» реализацией во многих ОС присутствует GNU реализация gmake , и поведение этих реализаций в некоторых ОС, например, Solaris может существенно отличаться. Поэтому в сценариях сборки рекомендуется указывать имя конкретной утилиты. В ОС Linux эти два имени являются синонимами, реализованными через символическую ссылку, как показано ниже:

$ ls -l /usr/bin/*make lrwxrwxrwx 1 root root 4 Окт 28 2008 /usr/bin/gmake -> make -rwxr-xr-x 1 root root 162652 Май 25 2008 /usr/bin/make ... $ make --version GNU Make 3.81 ...

По умолчанию имя файла сценария сборки - Makefile . Утилита make обеспечивает полную сборку указанной цели , присутствующей в сценарии, например:

$ make $ make clean

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

$ make -f Makefile.my

Простейший файл Makefile состоит из синтаксических конструкций двух типов: целей и макроопределений. Описание цели состоит из трех частей: имени цели, списка зависимостей и списка команд интерпретатора оболочки, требуемых для построения цели. Имя цели - непустой список файлов, которые предполагается создать. Список зависимостей - список файлов, в зависимости от которых строится цель. Имя цели и список зависимостей составляют заголовок цели, записываются в одну строку и разделяются двоеточием (":"). Список команд записывается со следующей строки, причем все команды начинаются с обязательного символа табуляции . Многие текстовые редакторы могут быть настроены таким образом, чтобы заменять символы табуляции пробелами. Этот факт стоит учесть и проверить, что редактор, в котором редактируется Makefile , не замещает табуляции пробелами, так как подобная проблема встречается довольно часто. Любая строка в последовательности списка команд, не начинающаяся с табуляции (ещё одна команда) или символа "# " (комментарий) - считается завершением текущей цели и началом новой.

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

$ make -p >make.suffix make: *** Не заданы цели и не найден make-файл. Останов. $ cat make.suffix # GNU Make 3.81 # Copyright (C) 2006 Free Software Foundation, Inc. ... # База данных Make, напечатана Thu Apr 14 14:48:51 2011 ... CC = cc LD = ld AR = ar CXX = g++ COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c COMPILE.C = $(COMPILE.cc) ... SUFFIXES:= .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l .s .S .mod .sym \ .def .h .info .dvi .tex .texinfo .texi .txinfo .w .ch... # Implicit Rules ... %.o: %.c # команды, которые следует выполнить (встроенные): $(COMPILE.c) $(OUTPUT_OPTION) $< ...

Значения всех этих переменных: CC , LD , AR , EXTRA_CFLAGS , ... могут использоваться файлом сценария как неявные определения со значениями по умолчанию. Кроме этого, можно определить и собственные правила обработки по умолчанию для выбранных суффиксов (расширений файловых имён), как это показано на примере выше для исходных файлов кода на языке С: %.c.

Большинство интегрированных сред разработки (IDE) или пакетов для создания переносимых инсталляций (например, automake или autoconf) ставят своей задачей создание файла Makefile для утилиты make .

Как ускорить сборку make

Сборка простых проектов происходит достаточно быстро, но с учётом роста проекта по ходу его развития, время сборки, основная часть которого затрачивается на компиляцию, может значительно вырасти. Хорошо известным примером такого рода является сборка ядра Linux, которая, в зависимости от типа оборудования, может занимать от нескольких десятков минут до часов процессорного времени. Усугубляет ситуацию и то, что при работе над проектом (доработка кода, отладка, поиск ошибок, тестирование, и т.д.) может понадобиться выполнять пересборку проекта по несколько десятков раз в день. Поэтому возможности, позволяющие ускорить этот процесс, приобретают действительную актуальность.

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

$ man make ... -j , --jobs[=jobs] 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 системах.



Рекомендуем почитать

Наверх