[Перевод] Кунг-фу стиля Linux: великая сила make

Со временем Linux (точнее — операционная система, которую обычно называют «Linux», представляющая собой ядро Linux и GNU-инструменты) стала гораздо сложнее, чем Unix — ОС, стоящая у истоков Linux. Это, конечно, неизбежно. Но это означает, что тем, кто пользуется Linux уже давно, нужно было постепенно расширять свои знания и навыки, осваивая новые возможности. А вот на тех, кто начинает работу в Linux в наши дни, сваливается необходимость освоить систему, так сказать, за один присест. Эту ситуацию хорошо иллюстрирует пример того, как в Linux обычно осуществляется сборка программ. Практически во всех проектах используется make — утилита, которая, запуская процессы компиляции кода, пытается делать только то, что нужно. Это было особенно важно в те времена, когда компьютеру с процессором, работающим на частоте в 100 МГц, и с медленным жёстким диском, нужно было потратить целый день на то, чтобы собрать какой-нибудь серьёзный проект. Программа make, судя по всему, устроена очень просто. Но сегодня у того, кто почитает типичный файл Makefile, может закружиться голова. А во многих проектах используются дополнительные абстракции, которые ещё сильнее всё запутывают.

aalyyf4m3bqgzx0yezkf0ukcryc.jpeg

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

Если есть IDE, собирающая проекты, то Makefile не нужен?


Кто-то может усмехнуться и сказать, что Makefile ему не нужен. Используемая им IDE (Integrated Development Environment, интегрированная среда разработки) сама собирает его проекты. Это, может, и так, но в недрах множества IDE, на самом деле, используются файлы Makefile. Даже в тех, в которых не предусмотрены механизмы экспорта таких файлов. Умение обращаться с файлами Makefile упрощает написание сборочных скриптов и работу с CI/CD-инструментами, которые обычно ожидают наличия таких файлов.

Единственным исключением является Java. В Java-разработке имеются собственные инструменты для сборки проектов, утилита make в этой среде распространена не так сильно, как в других. Но make можно использовать и тем, кто пишет на Java — вместе с инструментами командной строки вроде javac или gcj.

Простейший Makefile


Вот пример простейшего Makefile:

hello: hello.c


Вот и всё. Тут имеется правило, в котором сказано, что имеется файл hello, зависящий от файла hello.c. Если hello.c новее чем hello, то hello надо собрать. Так, а тут притормозим. Откуда программа знает о том, что ей делать? Обычно в Makefile включают инструкции по сборке целей (hello в нашем случае) на основе зависимостей. Но существуют и правила, применяемые по умолчанию.

Если создать вышеописанный файл Makefile в директории, в которой уже есть файл hello.c, и выполнить в этой директории команду make, можно будет увидеть, что выполнится такая команда:

cc hello.c -o hello


Это — совершенно нормально, так как в большинстве Linux-дистрибутивов cc указывает на компилятор C, используемый по умолчанию. Если снова запустить make, можно увидеть, что утилита ничего собирать не будет. Вместо этого она выдаст примерно такое сообщение:

make: 'hello' is up to date.


Для того чтобы снова собрать проект, нужно либо внести в hello.c изменения, либо обработать hello.c командой touch, либо удалить файл hello. Между прочим, если запустить make с опцией -n, то программа сообщит о том, что собирается делать, но при этом ничего делать не будет. Это может пригодиться в тех случаях, когда нужно разобраться с тем, к выполнению каких команд приведёт обработка некоего Makefile.

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

CC=gcc
CFLAGS=-g
hello : hello.c


При использовании такого Makefile вызов make приведёт к выполнению такой команды:

gcc -g hello.c -o hello


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

CC=gcc
CFLAGS=-g
# Закомментируйте следующую строку для отключения оптимизации
CFLAGS+=-O
hello : hello.c


На самом деле, неявно используемое правило выглядит так:

$(CC) $(CFLAGS) $(CPPFLAGS) hello.c -o hello


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

Собственные правила


Возможно, правила, используемые по умолчанию, вас не устраивают. Это — не проблема. Утилита make, правда, весьма внимательно относится к формату файлов Makefile. Так, если начать строку с символа Tab (не с нескольких пробелов, а именно с настоящего Tab), тогда то, что находится в строке, будет восприниматься как команда запуска некоего скрипта, а не как правило. И хотя то, что показано ниже, возможно, выглядит не особенно аккуратно, это — пример вполне работоспособного Makefile:

hello : hello.c
    gcc -g -O hello.c


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

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


Тут представлено правило, которое, в целом, соответствует правилу, применяемому по умолчанию. Символ % — это универсальный местозаполнитель, соответствующий любой последовательности символов. В переменную $< попадает имя первого (и, в данном случае, единственного) реквизита, которым является hello.c. Переменная $@ даёт нам имя цели (в данном примере — hello). Есть и многие другие особые переменные, но для начала вам достаточно знать об этих двух.

В Makefile могут быть многострочные скрипты (обрабатываемые системной командной оболочкой, используемой по умолчанию, хотя это можно и изменить). Главное требование — строки должны начинаться с Tab. В файле можно описывать множество реквизитов. Например:

hello : hello.c hello.h mylocallib.h


Правда, подобный код довольно тяжело поддерживать. При использовании C и C++ большинство компиляторов (включая gcc) могут создавать .d-файлы, которые способны автоматически сообщать make о том, от каких именно файлов зависит объект. Это, правда, выходит за рамки данного материала. Если вас это заинтересовало — взгляните на справку по gcc, и, в частности, на описание опции -MMD.

Объектные файлы


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

hello : hello.o mylib.o
hello.o : hello.c hello.h mylib.h
mylib.o : mylib.c mylib.h


Благодаря правилам, применяемым по умолчанию, это всё, что вам нужно. Утилита make достаточно интеллектуальна для того чтобы понять, что ей нужен файл hello.o, поэтому она найдёт правило для него. Конечно, после каждого из этих правил можно добавить строку (или строки) со скриптом, сделав это в том случае, если нужно контролировать то, что происходит в процессе сборки проекта.

По умолчанию make пытается собрать лишь первую обнаруженную цель. Иногда первой идёт фиктивная цель:

all : hello libtest config-editor


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

.PHONY: all


К фиктивным целям можно прикреплять действия. Например, в Makefile часто можно увидеть примерно такую конструкцию:

.PHONY: clean
clean: 
     rm *.o


Благодаря этому можно выполнить команду make clean для того чтобы удалить все объектные файлы. Возможно, подобную цель не будут делать первой, а такую команду не будут прикреплять к чему-то вроде цели all. Ещё одним распространённым приёмом является создание фиктивной цели, код, связанный с которой, прошивает программу на микроконтроллер. В результате, например, выполнив команду вроде make program можно записать программу на некое устройство.

Необычные способы использование make


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

Главное тут — это понимать то, что make стремится к построению дерева зависимостей, выясняя то, какие его части нуждаются в обработке, и запуская скрипты, связанные с этими частями. А то, что делается в этих скриптах, не обязательно должно приводить к сборке программ. Они могут копировать файлы (даже на удалённые системы, например, используя scp), они могут удалять файлы, или, в целом, выполнять любые действия, которые можно выполнить из командной строки.

Итоги


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

Используете ли вы make для решения каких-то особенных задач?

oug5kh6sjydt9llengsiebnp40w.png

3piw1j3wd_cgmzq9sefgferaumu.png

© Habrahabr.ru