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

24.06.2019

Меня всегда привлекал минимализм. Идея о том, что одна вещь должна выполнять одну функцию, но при этом выполнять ее как можно лучше, вылилась в создание 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 предназначена для отслеживания зависимостей одних файлов от других, выявления "устаревших" файлов при помощи сравнения времен модификации файлов и выполнения команд для "обновления" устаревших файлов. Позволяет автоматизировать процессы трансляции, компоновки, запуска модульных тестов и развертывания системы за счет описания соответствующих сценариев на специальном языке

Сценарии Make описываются в т.н. файле проекта. Проектом называется совокупность файлов, зависящих друг от друга. Файл описания проекта перечисляет зависимости между файлами и задает команды для обновления зависимых файлов. Имя файла описания проекта задается опцией –f командной строки программы make и по умолчанию предполагается равным Makefile или makefile . Если имя файла проекта явно не задано, при запуске утилита ищет в текущем каталоге файл с указанными выше именами, и, если такой файл существует, выполняет команды из него.

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

Обычно программы на языках Си или Си++ представляют собой совокупность нескольких.c (.cpp) файлов с реализациями функций и.h файлов с прототипами функций и определениями типов данных. Как правило, каждому.c файлу соответствует.h файл с тем же именем.

Предположим, что разрабатываемая программа называется earth и состоит из файлов arthur.c, arthur.h, trillian.c, trillian.h, prosser.c, prosser.h.

Разработка программы ведется в POSIX-среде с использованием компилятора GCC.

Простейший способ скомпилировать программу - указать все исходные.c файлы в командной строке gcc:

Gcc arthur.c trillian.c prosser.c -o earth

Компилятор gcc выполнит все этапы компиляции исходных файлов программы и компоновку исполняемого файла earth. Обратите внимание, что в командной строке gcc указываются только.c файлы и никогда не указываются.h файлы.

Компиляция и компоновка при помощи перечисления всех исходных файлов в аргументах командной строки GCC допустима лишь для совсем простых программ. С ростом числа исходных файлов ситуация очень быстро становится неуправляемой. Кроме того, каждый раз все исходные файлы будут компилироваться от начала до конца, что в случае больших проектов занимает много времени. Поэтому обычно компиляция программы выолняется в два этапа: компиляция объектных файлов и компоновка исполняемой программы из объектных файлов. Каждому.c файлу теперь соответствует объектный файл, имя которого в POSIX-системах имеет суффикс.o. Таким образом, в рассматриваемом случае программа earth компонуется из объектных файлов arthur.o, trillian.o и prosser.o следующей командой:

Gcc arthur.o trillian.o prosser.o -o earth

Каждый объектный файл должен быть получен из соответствующего исходного файла следующей командой:

Gcc -c arthur.c

Обратите внимание, что явно задавать имя выходного файла необязательно. Оно будет получено из имени компилируемого файла заменой суффикса.c на суффикс.o. Итак, для компиляции программы earth теперь необходимо выполнить четыре команды:

Gcc -c arthur.c gcc -c trillian.c gcc -c prosser.c gcc arthur.o trillian.o prosser.o -o earth

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

  • если изменение внесено в один файл, например, в файл prosser.c, нет необходимости перекомпилировать файлы trillian.o или arthur.o; достаточно перекомпилировать файл prosser.o, а затем выполнить компоновку программы earth;
  • компиляция объектных файлов arthur.o, trillian.o и prosser.o не зависит друг от друга, поэтому может выполняться параллельно на многопроцессорном (многоядерном) компьютере.

В случае нескольких исходных.c и.h файлов и соответствующих промежуточных.o файлов отслеживать, какой файл нуждается в перекомпиляции, становится сложно, и здесь на помощь приходит программа make. По описанию файлов и команд для компиляции программа makе определяет, какие файлы нуждаются в перекомпиляции, и может выполнять перекомпиляцию независимых файлов параллельно.

Файл A зависит от файла B, если для получения файла A необходимо выполнить некоторую команду над файлом B. Можно сказать, что в программе существует зависимость файла A от файла B. В нашем случае файл arthur.o зависит от файла arthur.c, а файл earth зависит от файлов arthur.o, trillian.o и prosser.o. Можно сказать, что файл earth транзитивно зависит от файла arthur.c. Зависимость файла A от файла B называется удовлетворенной , если:

  • все зависимости файла B от других файлов удовлетворены;
  • файл A существует в файловой системе;
  • файл A имеет дату последней модификации не раньше даты последней модификации файла B.

Если все зависимости файла A удовлетворены, то файл A не нуждается в перекомпиляции. В противном случае сначала удовлетворяются все зависимости файла B, а затем выполняется команда перекомпиляции файла A.

Например, если программа earth компилируется в первый раз, то в файловой системе не существует ни файла earth, ни объектных файлов arthur.o, trillian.o, prosser.o. Это значит, что зависимости файла earth от объектных файлов, а также зависимости объектных файлов от.c файлов не удовлетворены, то есть все они должны быть перекомпилированы. В результате в файловой системе появятся файлы arthur.o, trillian.o, prosser.o, даты последней модификации которых будут больше дат последней модификации соответствующих.c файлов (в предположении, что часы на компьютере идут правильно, и что в файловой системе нет файлов "из будущего"). Затем будет создан файл earth, дата последней модификации которого будет больше даты последней модификации объектных файлов.

В получившейся конфигурации все зависимости всех файлов друг от друга удовлетворены, и поэтому для компиляции программы earth не нужно выполнять никаких команд

Предположим теперь, что в процессе разработки был изменен файл prosser.c. Его время последнего изменения теперь больше времени последнего изменения файла prosser.o. Зависимость prosser.o от prosser.c становится неудовлетворенной, и, как следствие, зависимость earth от prosser.o также становится неудовлетворенной. Чтобы удовлетворить зависимости необходимо перекомпилировать файл prosser.o, а затем файл earth. Файлы arthur.o и trillian.o можно не трогать, так как зависимости этих файлов от соответствующих.c файлов удовлетворены. Такова общая идея работы программы make и, на самом деле, всех программ управления сборкой проекта: ant http://ant.apache.org/ , scons http://www.scons.org/ и др

Хотя утилита make присутствует во всех системах программирования, вид управляющего файла или набор опций командной строки могут сильно различаться. Далее будет рассматриваться командный язык и опции командной строки программы GNU make. В дистрибутивах операционной системы Linux программа называется make. В BSD, как правило, программа GNU make доступна под именем gmake.

Файл описания проекта может содержать описания переменных, описания зависимостей и описания команд, которые используются для компиляции. Каждый элемент файла описания проекта должен, как правило, располагаться на отдельной строке. Для размещения элемента описания проекта на нескольких строках используется символ продолжения \ точно так же, как в директивах препроцессора языка Си.

Определения переменных записываются следующим образом:

<имя> = <определение>

Использование переменной записывается в одной из двух форм:

$(<имя>) или ${<имя>} - Эти формы равнозначны.

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

Зависимости между компонентами определяются следующим образом:

<цель> : <цель1> <цель2> ... <цельN>

Где <цель> - имя цели, которое может быть либо именем файла, либо некоторым именем, обозначающим действие, которому не соответствует никакой файл, например clean. Список целей в правой части задает цели, от которых зависит <цель> .

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

Команды для перекомпиляции цели записываются после описания зависимости. Каждая команда должна начинаться с символа табуляции (\t). Если ни одной команды для перекомпиляции цели не задано, будут использоваться стандартные правила, если таковые имеются. Для определения, каким стандартным правилом необходимо воспользоваться, обычно используются суффиксы имен файлов. Если ни одна команда для перекомпиляции цели не задана и стандартное правило не найдено, программа make завершается с ошибкой.

Для программы earth простейший пример файла Makefile для компиляции проекта может иметь вид:

Earth: arthur.o trillian.o prosser.o gcc arthur.o trillian.o prosser.o -o earth arthur.o: arthur.c gcc -c arthur.c trillian.o: trillian.c gcc -c trillian.c prosser.o: prosser.c gcc -c prosser.c

Однако, в этом описании зависимостей не учтены.h файлы. Например, если файл arthur.h подключается в файлах arthur.c и trillian.c, то изменение файла arthur.h должно приводить к перекомпиляции как arthur.c, так и trillian.c. Получается, что.o файлы зависят не только от.c файлов, но и от.h файлов, которые включаются данными.c файлами непосредственно или косвенно. С учетом этого файл Makefile может приобрести следующий вид:

Earth: arthur.o trillian.o prosser.o gcc arthur.o trillian.o prosser.o -o earth arthur.o: arthur.c arthur.h gcc -c arthur.c trillian.o: trillian.c trillian.h arthur.h gcc -c trillian.c prosser.o: prosser.c prosser.h arthur.h gcc -c prosser.c

Первой в списке зависимостей обычно записывается «главная» зависимость, а затем записываются все остальные файлы-зависимости.

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

Make prosser.o

будет при необходимости перекомпилирован только файл prosser.o и те файлы, от которых он зависит, все прочие файлы затронуты не будут. Если в командной строке имя цели не указано, берется первая цель в файле. В нашем случае это будет цель earth.

Если придерживаться хорошего стиля написания Makefile, то каждый Makefile должен содержать как минимум два правила: all – основное правило, которое соответствует основному предназначению файла, и правило clean, которое предназначено для удаления всех рабочих файлов, создаваемых в процессе компиляции. В случае программы earth рабочими файлами можно считать сам исполняемый файл программы earth, а также все объектные файлы.

С учетом этих дополнений файл Makefile примет вид:

All: earth earth: arthur.o trillian.o prosser.o gcc arthur.o trillian.o prosser.o -o earth arthur.o: arthur.c arthur.h gcc -c arthur.c trillian.o: trillian.c trillian.h arthur.h gcc -c trillian.c prosser.o: prosser.c prosser.h arthur.h gcc -c prosser.c clean: rm -f earth *.o

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

Make clean

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

Во-первых, можно параметризовать название используемого компилятора, а также предоставить возможность управлять параметрами командной строки компилятора. Для задания компилятора можно определить переменную CC , для задания опций командной командной строки компиляции объектных файлов - переменную CFLAGS , а для задания опций командной строки компоновки выходной программы - переменную LDFLAGS .

Получим следующий файл:

CC = gcc CFLAGS = -Wall -O2 LDFLAGS = -s all: earth earth: arthur.o trillian.o prosser.o $(CC) $(LDFLAGS) arthur.o trillian.o prosser.o -o earth arthur.o: arthur.c arthur.h $(CC) $(CFLAGS) -c arthur.c trillian.o: trillian.c trillian.h arthur.h $(CC) $(CFLAGS) -c trillian.c prosser.o: prosser.c prosser.h arthur.h $(CC) $(CFLAGS) -c prosser.c clean: rm -f earth *.o

Теперь можно изменить используемый компилятор, не только отредактировав Makefile, но и из командной строки. Например, запуск программы make в виде

Make CC=icc

Позволит для компиляции программы использовать не gcc, а Intel компилятор Си. Аналогично запуск

Make CFLAGS="-g" LDFLAGS="-g"

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

Во-вторых, можно избавиться от дублирования имен файлов сначала в зависимостях, а потом в выполняемых командах. Для этого могут быть использованы специальные переменные $^ , $< и $@ . Переменная $@ раскрывается в имя цели, стоящей в левой части правила. Переменная $< раскрывается в имя первой зависимости в правой части правила. Переменная $^ раскрывается в список всех зависимостей в правой части. Правило для компиляции файла arthur.o приобретет следующий вид:

Arthur.o: arthur.c arthur.h $(CC) $(CFLAGS) -c $<

Именно такое правило для компиляции.o файлов из.c файлов уже встроено в make, поэтому строку компиляции можно просто удалить. Останется следующий Makefile:

CC = gcc CFLAGS = -Wall -O2 LDFLAGS = -s all: earth earth: arthur.o trillian.o prosser.o $(CC) $(LDFLAGS) $^ -o $@ arthur.o: arthur.c arthur.h trillian.o: trillian.c trillian.h arthur.h prosser.o: prosser.c prosser.h arthur.h clean: rm -f earth *.o

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

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

Тем не менее, в этом файле проекта осталось слабое место. Оно связано с тем, что зависимости объектных файлов включают в себя помимо.c файлов и.h файлы, подключаемые.c файлами непосредственно или транзитивно. Представим себе, что в файл prosser.c была добавлена директива

#include "trillian.h"

Но Makefile не был соответствующим образом изменен. Теперь может получиться так, что в файле trillian.h будет изменена некоторая структура данных, но файл prosser.o не будет перекомпилирован и код модуля prosser.o будет продолжать работать со старой версией структуры данных, в то время как остальная программа - с новой версией структуры данных. Такое расхождение в описании данных в рамках одной программы может привести к "загадочным" ошибкам при ее работе.

Хотелось бы каким-либо образом строить списки зависимостей объектных файлов от.c и.h файлов автоматически. Для этого мы воспользуемся специальными опциями компилятора gcc и расширенными возможностями GNU make.

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

include deps.make

Для генерации файла deps.make с зависимостями воспользуемся опцией -MM компилятора gcc:

Deps.make: arthur.c trillian.c prosser.c arthur.h trillian.h prosser.h gcc -MM arthur.c trillian.c prosser.c > deps.make

Файл deps.make зависит от всех.c и.h файлов, из которых собирается программа. Может показаться, что это правило не будет работать, так как в Makefile необходимо включить файл deps.make, для генерации которого необходимо выполнить Makefile, то есть возникает циклическая зависимость, однако GNU make умеет корректно обрабатывать такие ситуации.

Для того, чтобы не выписывать списки.c и.h файлов несколько раз, в начале Makefile можно определить переменные:

CFILES = arthur.c trillian.c prosser.c HFILES = arthur.h trillian.h prosser.h

Более того, список объектных файлов можно получать из списка.c файлов заменой суффикса.c на.o:

OBJECTS = $(CFILES:.c=.o)

В итоге получили следующий Makefile:

CC = gcc CFLAGS = -Wall -O2 LDFLAGS = -s CFILES = arthur.c trillian.c prosser.c HFILES = arthur.h trillian.h prosser.h OBJECTS = $(CFILES:.c=.o) TARGET = earth all: $(TARGET) earth: $(OBJECTS) $(CC) $(LDFLAGS) $^ -o $@ include deps.make deps.make: $(CFILES) $(HFILES) gcc -MM $(CFILES) > deps.make clean: rm -f $(TARGET) *.o

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

Пример файла C++ проекта:

CXX = g++ LDFLAGS = CXXFLAGS = -Wall -O2 -g CXXFILES = main.cpp fn.cpp HFILES = fn.h OBJECTS = $(CXXFILES:.cpp=.o) TARGET = proga all: $(TARGET) proga: $(OBJECTS) $(CXX) $(LDFLAGS) $^ -o $@ include deps.make deps.make: $(CXXFILES) $(HFILES) $(CXX) -MM $(CXXFILES) > deps.make clean: rm -f proga *.o

Для просмотра результирующих значений переменных полезно просматривать вывод команды: make -p

Что такое Makefile? Makefile - это сценарий для утилиты make . Эта утилита помогает автоматизировать процесс компиляции проекта (проектов):

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

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

make -f prjmake.nmk

Makefile состоит из нескольких основных частей:

  • правила;
  • директивы;
  • переменные;
  • комментарии.
Начнем рассмотрение в обратном порядке списка - от простого к сложному.

Комментарии.
Комментарий обозначается символом "#". Если нужно использовать символ "#" в другом контексте, то необходимо добавить обратный слэш: "\#".

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

LIB = lib1.o lib2.o lib3.o

Название переменной задается в верхнем регистре (по негласному соглашению). Для получения параметров переменной используется смпользуются скобки и символ "
$(PATH)

Значение переменной может вычисляться рекурсивно:

PATH=$(PATH)/trunc

Либо можно использовать другую форму записи:

PATH+=/trunc

Некоторые переменные являются стандартыми константами, для них нельзя вычислять значение рекусивно. Такими перменными являются: CC (имя компилятора С), СХХ (имя компилятора С++), CFLAGS (параметры С компилятора), CXXFLAGS (параметры С++ компилятора).

Помимо переменных, определенных "писателем" файла, Make-файл предполагает использование автоматических переменных, значения которых вычисляются в контексте использования таких переменных (вычисление основывается на цели и зависимости правила - о целях и зависимостях ниже):

  • $@ - имя цели;
  • $$@ - имя цели, если его необходимо ввести в строке описания зависимости справа от двоеточия;
  • $? - имена всех зависимостей (с пробелами) которые новее, чем цель;
  • $^ - имена всез зависимостей с пробелами;
  • $* - имя текущего предусловия за вычетом суффикса;
  • $% - имя соответствующего.о файла, если текущей целью является файл библиотеки;
  • $** - только для nmake - внутри правила обозначаются все зависимости, оказавшиеся справа от двоеточия;
  • D - часть имени внутренних макроопределений, описывающая директорию файла (допустимые варианты применения: $(@D), $$(@D), $(
  • F - часть имени внутренних макоопределений, описывающая собственно имя файла (допустимые варианты прменения: $(@F), $$(@F), $(
  • B - часть имени внутренних макроопределений, описывающая базовое имя файла (без диска, директориии и расширения);
  • R - часть имени внутренних макроопределений, описывающая полный путь к файлу за вычетом расширения.
Директивы.
Директива - это команда для make с указанием, что делать в процессе чтения make-файла. Может выполнять такие действия:
  • подключать другие make-файлы;
  • принимать решение какие части файла использовать, а какие игнорировать (в зависимости от значения переменных);
  • определение значений переменных.
Поключать другие make-файлы можно директивой include. Она указвает утилите make , что нужно приостановить чтение текущего файла и прочитать указанные файлы:

include filenames...

Если фала не существует - выводиться ошибка. Для отключения сообщени об ошибке, к директиве добавлятеся префикс "-":

Include file1

Решение о том, какие части make-файла использовать, а какие игнорировать, примается на основе условий, синтаксис которого имеет вид:

conditional-directive
text-if-true
else
text-if-false
endif

Например:

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
Определить значение перенной можно при помощи дериктив define... endef:

define two-lines
echo foo
echo $(bar)
endef
В этом случает значение переменной two-lines = echo foo; echo $(bar)

Правила.
Правила объясняют make , что и как нужно пересобирать. В общем виде структура выглядит так:

targets: prerequisites
command
...

Здесь targets - имена файлов-результатов, разделенные пробелами; так же может быть действие не связанное с процессом компиляции, например, clean. prerequisites - зависимости, то, от чего зависит создание targets. command - комманда, которая выполняется для получения targets; может быть не одна; перед командой обязательно ставиться табуляция.

Существуют несколько фиктивных целей:

  • all - выполнение работы по достижению всех частных целей, перечисленных ниже;
  • build - компиляция и сборка всех устаренших (невыполненных) целей/файлов;
  • clean - удаление всех файлов, кроме исходных;
  • docs - подготовка документации и размещение таких файлов в соответствующих системных директориях;
  • examples - компиляция и сборка примеров;
  • install - работа, по размещению всех готовых частей проекта в соответствующих директориях.
В именах файлов могут использоваться wildcards:

objects = *.o

Пример использования правил:

# задаем цель - исполняемый файл mytest, зависимый от lib.o
mytest: lib.o
# задаем комманду, результатом которой будет mytest
$(CXX) mytest.cpp $^ -o $@

# задаем цель - файл lib.o, зависимый от libimpl.h и libimpl.с
lib.o: libimpl*
# задаем комманду, результатом которой будет lib.o
$(CXX) -c $^ -o $@

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

# все o-файлы зависят от cpp-файлов
.cpp.o:
gcc -c $^

Если бы в предыдущем примере mytest зависел от нескольких o-файлов, то шаблонное правило можно было бы записать так:

mytest: lib.o map.o vector.o
gcc $^ -o $@

Cpp.o:
gcc -c $^

Некоторый ключи компилятора:
-I"path/to/include" - директория со списком хидеров.
-Wall-Werror - вывод варнингов.
-O1, -O2, -O3 - оптимизация.

Некоторые ключи сборки:
-llibrary - указывает линковшику использовать библиотеку library при сборке программы.
-s - не включает симольные таблицы и информацию о размещении функций в испольняемый файл. Использование этого ключа позволяет существенно ужать исполняемые файлы.
-L"path/to/libs" - директория с библиотеками.
-static - статическая компоновка библиотек.

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

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-файлы

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

До создания 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

Похожие статьи