Экскурс в Makefile

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

Для чего нужен make

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

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

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

Кстати, не обязательно компилировать. Ту же логику можно приспособить к любому процессу, когда из одних файлов получаются другие. Например, когда из *.h файлов получаются *.cpp файлы с помощью moc.

Правила в Makefile

Зависимости в Makefile задаются с помощью правил (rules), которые связывают цели (targets) с их зависимостями (prerequisites), а также определяют действия, которые нужно выполнить для обновления целей. В простейшем случае правило выглядит следующим образом:

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

Если не вдаваться в синтаксические детали, облегчающие написание Makefile (переменные, неявные действия и т.п.), то Makefile представляет собой набор такого рода правил. Эти правила не должны быть (и редко бывают) независимыми друг от друга. Цель в одном правиле спокойно может быть зависимостью в другом правиле. Определения того, какие правила выполнять и в каком порядке — задача make. В общем случае гарантируется, что для любого правила сначала отработают правила для зависимостей, если они указаны как цель в другом правиле.

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

Цели и зависимости не обязаны быть файлами

Самый простой пример — это традиционная цель clean, которая служит для удаления всех промежуточных файлов, получающихся при компиляции:

При выполнении действий этого правила файл clean не создается. В подобных случаях make понимает, что он имеет дело с целью, которая не является файлом, и считает ее «пустышкой» (phony). Такая цель всегда считается «изменившейся» — до тех пор, пока правило цели не будет выполнено. Это значит, что если make доберется по зависимостям до обработки pnony цели, то ее действия обязательно выполнятся (только один раз, конечно). Есть тут, правда, один неприятный момент.

Предположим, правило clean еще не отрабатывало. Тогда при выполнении правила text.txt вначале выполнится clean, верно? Да, но только в том случае, если файл clean реально не существует. Если же он вдруг существует, то он наверняка старый (т.к. правило clean его не обновляет), и поэтому ни правило clean, ни, в данном случае, правило text.txt не отработают вообще.

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

GNU make поддерживает специальный способ указать, что некая цель есть «пустышка». Для таких целей наличие файла с тем же именем не проверяется вообще.

Порядок выполнения зависимостей

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

Но есть тут подводный камень. Конкретная реализация make может обрабатывать Makefile в режиме параллельной обработки (загружая сразу все процессоры, что, естественно, ускоряет дело). В этом случае может получиться так, что, скажем, вторая зависимость отработает раньше первой. Поэтому использовать порядок обработки зависимостей для, по сути, неявного определения зависимости между ними (если для нас важен порядок зависимостей, значит они не независимы друг от друга) усиленно не рекомендуется. Лучше эту зависимость задать явно; такой вариант будет работать с любым режимом работы любой реализации make.

Цикл: qmake

Добавить комментарий