Make files not war что такое утилита GNU make, зачем ее использовать и как это делать правильно


Оглавление (нажмите, чтобы открыть):

Зачем использовать GNU make, gcc в windows и какой способ правильный?

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

1) Во-первых, для чего ее использовать?
1.1 Существуют открытые исходники проектов с файлами типа «Makefile». Вообщем-то, тут, наверное, нужно выделить только кросс-платформенные проекты. Исходники для *nix систем вобщем случае не будут компилироваться на Windows или если скомпилируются — приложение не запустится. Верно?
1.2 Другие причины использования?

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

3) Существует ли способ задать компилятору gcc папку для результатов компиляции (объектных файлов и экзешников) в Windows? На линуксе для этого обычно имеются предопределенные каталоги. Имел неприятный случай, когда объектный фал создался прямо в каталоге с gcc — я после этого выставил запреты на запись, удаление файлов в этой папке.

1.1. платформа задается настройками компилятора(в мейкфайле или из командной строки) в общем случае ему пофик под чем и под что собирать, были бы доступны соответвующие либы. сборка проекта на сервере под другой осью/с другой аппаратной платформой — вполне нормальное дело
1.2. не вдаваясь в срач по компиляторам — gcc открытый набор компиляторов под кучу платформ с высоким соответствием стандартам и прочая.

2.
MinGW(компиляторы и прочая для разработки) — все уже собрано и готово
Cygwin(posix среда для винды) — там и баш и все прочее.
но конкретно баш тебе не нужен. у винды тоже есть консоль и программы все так же благополучно используют аргументы командной строки.

3. опять же через командную строку/мейкфайл

Denadan
> MinGW(компиляторы и прочая для разработки) — все уже собрано и готово
а как в нем с файлами Makefile работать — через mingw32-make?

Забудь про мейкфайлы. Используй cmake.

Ghost2
> graveman
>
> Забудь про мейкфайлы. Используй cmake.
А если мне нужны Lua, ODE, png-либы?

CMake.
Либы подключаются или вкладываются исходники в проект, всё работает.

kvakvs
> CMake.
> Либы подключаются или вкладываются исходники в проект, всё работает.
Но там же Makefile со своим синтаксисом, а не CMakefile, CMake работает с файлами makefile?

graveman
> Но там же Makefile со своим синтаксисом, а не CMakefile, CMake работает с
> файлами makefile?
CMake создаёт Makefile под текущую машину, архитектуру и набор либ и конпеляторов.
Если винда то вполне может создать проект под студию например или коде::блокс или десяток других вариантов под разные ИДЕ.

kvakvs
> CMake создаёт Makefile под текущую машину, архитектуру и набор либ и
> конпеляторов.
Я имею ввиду, если есть уже существующий makefile, который должен быть собран при помощи gnu make.

> А если мне нужны Lua, ODE, png-либы?
Lua, это конечно исключение. Но обычно мэйкфалы никто руками не пишет.
Те же libpng и ODE используют autotools.

libpng использует cmake.

kvakvs
> CMake создаёт Makefile под текущую машину, архитектуру и набор либ и
> конпеляторов.
CMake создаёт сборку на основе файла CMakeLists.txt, в исходниках Lua есть только Makefile, который можно собрать только в MinGW 🙂

Про Lua: а если не mak-ать его, а добавить в проект в виде исходников? Слишком «не тру»? Всё-равно при сборке перекомпиляций не будет, только перелинковка, ну и кучка лишнего промежуточного барахла.

KoMaTo3
> Про Lua: а если не mak-ать его, а добавить в проект в виде исходников? Слишком
> «не тру»? Всё-равно при сборке перекомпиляций не будет, только перелинковка, ну
> и кучка лишнего промежуточного барахла.
Почему бы тогда не форкнуть и добавить CMakeLists.txt, а форк использовать как сабмодуль в проекте?

KoMaTo3
> Про Lua: а если не mak-ать его, а добавить в проект в виде исходников? Слишком
> «не тру»?
Ну это надо разбираться в его makefile для выяснения зависимостей или ты предлагаешь тупо добавить все файлы и нажать билд?

Я собрал Lua при помощи порта GNU make и Mingw. Мне интересно как другие работают именно с makefile (не с CMakefile).

Make files not war: что такое утилита GNU make, зачем ее использовать и как это делать правильно

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

Система make родилась в мире UNIX и постепенно переползла и на Windows вместе с портами GNU-компиляторов (gcc). Если открыть пример готового Makefile, то он поначалу может показаться полной абракадаброй, поскольку содержимое файла подчиняется заранее заданному набору правил, которые необходимо предварительно изучить. Для простоты рассмотрим пример работы с проектом AVR и компилятором gcc.

Немного о структуре файла.

— Комментарии, как это принято в UNIX скриптах, начинаются с символа # и продолжаются до конца строки.
— Обычно в файле содержатся метки, идентифицирующие «цели» (targets, см. далее). Метка начинается с начала строки и оканчивается двоеточием :. После двоеточия могут идти так называемые зависимости, dependencies (сразу непонятно, что это такое и для чего надо, но дальше по ходу дела станет яснее). Обычно это имена файлов, либо ссылки на цели.
— Если в качестве dependencies указана последовательность целей, то они будут выполняться друг за другом.
— Если в качестве dependencies указаны имена файлов (обычно объектных), то утилита make может проверить — нужно их компилировать, или нет (мне непонятно, как она проверяет, однако это работает). Например, если компилируется несколько исходных файлов в несколько объектных, то некоторые исходные файлы не требуется каждый раз перекомпилировать заново, если они не изменялись. Для больших проектов это важно, поскольку существенно экономит время сборки программы (в нашем случае — получение двоичной прошивки для AVR).
— Для упрощения содержимого Makefile и для удобства используются переменные. Пример задания переменной (здесь в переменную записана командная строка вызова программатора):

После задания переменной на неё можно ссылаться так:

Отладка работы утилиты make

Советы и рекомендации о том, как заставить make работать на вас, а не против вас

Сборка большинства программ для UNIX® и Linux® производится путем запуска утилиты make . Эта утилита считывает файл (обычно носящий имя «makefile» или «Makefile», здесь он называется просто «makefile»), в котором содержатся инструкции, и выполняет различные действия, необходимые для сборки программы. Во многих случаях сам makefile полностью генерируется специальной программой; например, для разработки процедур сборки используются программы autoconf/automake . В иных программах может потребоваться изменение файла makefile напрямую, и, конечно же, вам придется написать такой файл при разработке новой программы.

Фраза «утилита make» является ошибочной. Существует как минимум три различных наиболее распространенных варианта этой утилиты: GNU make, System V make и Berkeley make. Каждый из них вырос из базовой спецификации времен начала становления UNIX и в каждом из них добавлены новые возможности. Это привело к сложной ситуации: некоторые наиболее часто используемые функции, например, включение в makefile других файлов по ссылке, перестали быть портируемыми! Единственным решением является написание программы, которая создает файлы makefile. Поскольку утилита GNU make бесплатна и широко распространена, некоторые разработчики просто кодируют для нее; кроме того, некоторые проекты, берущие свои корни из BSD, потребуют от вас использования Berkeley make (которая также бесплатна).

Менее распространена, но всё ещё значима утилита smake Йорга Шиллинга (Jörg Schilling), а также отсутствующий пятый член семейства, первоначальная утилита make, определяющая комплекс основных функций, общих для всех остальных утилит. Несмотря на то, что утилита smake не используется по умолчанию на всех системах, это хорошая реализация make, и она является предпочтительной в некоторых программах (особенно в написанных Шиллингом).

Давайте рассмотрим некоторые из наиболее распространенных проблем, с которыми вы можете встретиться при работе с файлами makefile.

Определение файла makefile

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

Листинг 1. Вид правила зависимости

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

Листинг 2. Сообщение об ошибке Berkeley make

Утилита GNU make, хотя и не может обработать файл, дает более полезный совет:

Листинг 3. Сообщение об ошибке GNU make

Следует обратить внимание, что зависимости и команды необязательны; необходимо указывать лишь цель и двоеточие. Итак, мы поговорили о синтаксисе. Какова же семантика? Семантика такова, что если вы желаете провести сборку цели , будет проведена проверка всех зависимостей. Фактически, утилита будет рекурсивно пытаться собрать их; если у зависимости, в свою очередь, есть ещё одна зависимость, она будет обработана до того, как будет продолжена обработка правила. Если цель target : существует и ее дата не является более ранней, чем даты всех объектов, перечисленных в пункте зависимости , ничего не выполняется. Если target не существует или одна или несколько зависимостей обновились, утилита make выполнит команды instructions . Зависимости обрабатываются в порядке указания. Если зависимости не указаны, команды будут выполнены в безусловном порядке. Зависимости также называются источниками.

Если в командной строке указана цель (например, make foo ), то утилита make будет пытаться построить эту цель. В противном случае она будет пытаться построить первую цель, указанную в файле. Некоторые разработчики берут за правило указывать первую цель следующим образом:

Листинг 4. Наиболее часто используемое условное указание первой цели

Некоторые предполагают, что утилита make использует это правило потому, что оно называется «default» («по умолчанию»). Это не так; оно используется потому, что это первое правило в файле. Вы можете дать ему любое название, какой только пожелаете, но название «default» хорошо тем, что оно несет в себе смысловую нагрузку для читающего код. Помните о том, что создаваемый вами файл makefile будут читать не только программы make, но и люди.

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

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

Листинг 5. Пример фиктивной цели

Это дает утилите make следующие инструкции — если она желает собрать цель all , сначала необходимо убедиться в том, что существуют актуальные версии «hello», «goodbye» и «fibonacci». Тогда. ничего не делать. Никаких команд не дается. После выполнения этого правила не создается файла с названием «all». Это фиктивная цель. В некоторых версиях утилиты make используется технический термин «фиктивная» («phony») цель.

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

Листинг 6. Разумное использование фиктивной цели

Здесь указывается порядок операций процесса сборки.

Специальные цели и источники

Определен ряд специальных целей, оказывающих особое влияние на утилиту make и предоставляющих средства настройки. Точный перечень изменяется от одной реализации к другой; наиболее часто используется цель .SUFFIXES , «источником» для которой является ряд шаблонов, добавляемых к списку найденных расширений (суффиксов) файлов. Специальные цели не учитываются в обычном правиле, гласящем, что make по умолчанию строит первую цель в файле makefile.

В некоторых версиях утилита make позволяет определять специальные цели наряду с зависимостями для данной цели, например, .IGNORE, что обозначает, что ошибки команды, используемой для сборки этой цели, будут проигнорированы, как если бы перед ними стоял знак комментария. Эти флаги не всегда портируемы, но знать их для отладки файла makefile необходимо.


Общие правила

В make существуют неявные правила выполнения общих преобразований на основе расширений файлов. Например, при отсутствии файла makefile создайте файл с названием «hello.c» и выполните команду make hello :

Листинг 7. Пример неявных правил для файлов C

В файлах makefile для более крупных программ можно просто указать перечень необходимых модулей объектов (hello.o, world.o и так далее), после чего указать правило преобразования файлов .c в файлы .o:

Листинг 8. Правило преобразования файлов .c в файлы .o

На самом деле в большинстве утилит make уже есть правило, очень похожее на это; если вы затребуете у make построить файл file.o и в наличии будет соответствующий файл file.c, утилита сработает как положено. Сочетание «$ $CFLAGS заменяется на «FLAGS». Для того, чтобы указать переменную make, заключите её наименование в скобки: $(CFLAGS) . В противном случае вы получите переменную $C , за которой будет следовать текст FLAGS .

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

  • $ — Источник, из которого должна быть изготовлена цель
  • $* — Базовое имя цели (без расширения и имени каталога)
  • $@ — Полное имя цели

Авторы Berkeley make выводят эти переменные из обращения, но пока они всё ещё портируемы. Во всяком случае, портируемы отчасти; точные определения в различных реализациях утилиты make могут различаться. Любая сложная программа, написанная с их помощью, скорее всего, будет ограничена конкретной реализацией.

Сценарии командного процессора

Иногда бывает желательно выполнять задачи определенного рода за пределами портируемости утилиты make. Традиционным решением является написание встроенного сценария командного процессора (shell), поскольку make выполняется именно через него. Рассмотрим, как это работает.

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

Листинг 9. Разрыв строк в сценарии командного процессора

Здесь показаны три вещи, которые всегда необходимо помнить. Во-первых, используйте точки с запятой и обратные слэши. Во-вторых, для создания переменных используйте $(VARIABLE) . В-третьих, используйте $$ для передачи сценарию знака доллара. Вот и всё! Это действительно просто.

Префиксы

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

Если вы не желаете, чтобы команда выводилась на экран, начните ее со знака @. Этот вариант чаще всего используется для вывода сообщений:

Листинг 10. Подавление вывода

Без знака @ эти инструкции приведут к выводу следующей информации:

Листинг 11. Команды без знака @

Несмотря на то, что символ @ фактически не влияет на действия, выполняемые утилитой make, он остается очень популярной функцией.

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

Некоторые нужные всем действия невозможно сделать портируемыми. Однако для решения этих проблем существует несколько обходных путей.

Включение файлов

Одной из наиболее удручающих проблем совместимости долгое время является обработка включений в файлы makefile. Ранние реализации утилиты make, в отличие от современных, не всегда предоставляли пути для выполнения этой операции. Синтаксис GNU make — простая команда include file . Традиционный синтаксис Berkeley — .include «file» . На сегодняшний день по крайней мере одна реализация Berkeley также поддерживает нотацию GNU, однако это делают не все. Портируемое решение, найденное autoconf и Imake , состоит в простом включении определения каждой переменной, которая, по вашему мнению, может вам понадобиться.

Некоторые программы просто ставят условием использование GNU make. Другим необходима Berkeley make. Третьим — smake. Если вам очень нужно включать файлы, нет ничего немыслимого в том, чтобы просто указать, с помощью какой утилиты make будет строиться ваше дерево файлов. (Из трех портируемых утилит с открытым исходным кодом я предпочитаю Berkeley make.)

Получение переменных во вложенных сборках

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

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

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

Некоторые авторы пропагандируют более простое решение: вообще не использовать утилиту make рекурсивно. Для большинства проектов это подходящее решение, которое может значительно упростить (и ускорить!) компиляцию. Вероятно, каноническим источником можно считать статью Питера Миллера (Peter Miller), «Рекурсивное использование Make вредно» (см. Ссылки).

Что делать в случае возникновения проблем

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

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

Если не удалось найти программу, проверьте, установлена ли она. Если это так, проверьте пути; у некоторых разработчиков есть привычка указывать в файлах makefile абсолютные пути, что может привести к возникновению ошибок на других системах. Если вы производите установку в папку /opt, а в makefile указаны ссылки на /usr/local/bin, сборка завершится ошибкой. Исправьте путь.

Проверьте ваши часы; более того, посмотрите на даты создания файлов в дереве сборки и сравните их с датами создания других файлов вашей системы и вашими часами. Поведение утилиты make при подаче на вход хронологически противоречивых данных может варьировать от безобидного до сюрреалистического. Если у вас есть проблемы с часами (например, у некоторых «новых» файлов установлена дата создания в 1970 году), вам придется исправить их. Вам поможет утилита «touch». Сообщения, которые вы получаете при разнице во времени, бывают весьма неочевидными.

Если сообщение об ошибке уведомляет вас о синтаксической ошибке или о том, что не установлено или установлено неправильно множество переменных, попробуйте использовать другую версию make. Например, некоторые программы выдают загадочные ошибки при работе с gmake, но прекрасно собираются с помощью smake. Совсем таинственные ошибки могут означать, что вы используете с GNU make файл makefile, подготовленный для Berkeley, или наоборот. Программы, изначально написанные для Linux, часто предполагают наличие GNU make и выдают ошибку в самых неожиданных местах, если встречают что-либо иное, даже если в документации об этом не сказано ни слова.

В этом случае будут крайне полезны отладочные флаги. Используя в GNU make флаг -d , вы получите ОГРОМНЫЙ объем информации, в том числе полезной. В Berkeley make флаг -d имеет несколько дополнительных флагов; -d A представляет полное множество, можно использовать отдельные подмножества; например, -d vx выдаст отладочную информацию о назначении переменных ( v ), при этом все команды будут выполняться с ключом sh -x , чтобы командный процессор выводил все команды точно так, как он их получает. Отладочный флаг -n приводит к выводу утилитой make перечня действий, которые она считает необходимым выполнить; он не всегда верен, но часто наводит на мысли о том, где именно кроется ошибка.

Мастер Йода рекомендует:  Аутсорс или продуктовая компания где программисту лучше работать Отвечают эксперты

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

Документация

Как часто бывает, основная документация к GNU make, к сожалению, не доступна в формате man; вместо этого вам придется использовать систему info, и у вас не получится просто набрать man make и посмотреть интересующую вас информацию. Как бы то ни было, документация достаточно полна.

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

Развивающееся со временем расхождение между версиями BSD привело к возникновению едва уловимых отличий в реализации утилиты make. Из трех этих версий наиболее активно поддерживается на других системах утилита make из NetBSD; система NetBSD pkgsrc используется на других платформах и существенно опирается на реализацию make NetBSD.

Make files not war: что такое утилита GNU make, зачем ее использовать и как это делать правильно

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

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

3.1. Правила

Основным «строительным элементом» make-файла являются правила (rules). В общем виде правило выглядит так:

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

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

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

Подобного рода цели называются псевдоцели (pseudotargets) или абстрактные цели (phony targets).

Зависимость (dependency)- это некие «исходные данные», необходимые для достижения указанной в правиле цели. Можно сказать что зависимость — это «предварительное условие» для достижения цели. Зависимость может представлять собой имя файла. Этот файл должен существовать, для того чтобы можно было достичь указанной цели. В следующем правиле:


файлы main.o, Editor.o и TextLine.o являются зависимостями. Эти файлы должны существовать для того, чтобы стало возможным достижение цели — построение файла iEdit.

Зависимость также может быть именем некоторого действия. Это действие должно быть предварительно выполнено перед достижением указанной в правиле цели. В следующем примере зависимость clean_obj является именем действия (удалить объектные файлы программы):

Для того чтобы цель clean_all была достигнута, нужно сначала выполнить действие (достигнуть цели) clean_obj.

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

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

должна начинаться с символа табуляции.

3.2. Алгоритм работы make

Типичный make-файл проекта содержит несколько правил. Каждое из правил имеет некоторую цель и некоторые зависимости. Смыслом работы make является достижение цели, которую она выбрала в качестве главной цели (default goal). Если главная цель является именем действия (то есть абстрактной целью), то смысл работы make заключается в выполнении соответствующего действия. Если же главная цель является именем файла, то программа make должна построить самую «свежую» версию указанного файла.

3.2.1 Выбор главной цели

Главная цель может быть прямо указана в командной строке при запуске make. В следующем примере make будет стремиться достичь цели iEdit (получить новую версию файла iEdit):

А в этом примере make должна достичь цели clean (очистить директорию от объектных файлов проекта):

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

из четырех перечисленных в make-файле целей (iEdit, main.o, Editor.o, TextLine.o, clean) по умолчанию в качестве главной будет выбрана цель iEdit. Схематично, «верхний уровень» алгоритма работы make можно представить так:

3.2.2 Достижение цели

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

3.2.3 Обработка правил

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

3.2.4 Обработка зависимостей

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

3.2.5 Обработка команд

На стадии обработки команд решается вопрос — нужно ли выполнять описанные в правиле команды или нет. Считается, что нужно выполнять команды если:

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

В противном случае (если ни одно из вышеприведенных условий не выполняется) описанные в правиле команды не выполняются. Алгоритм принятия решения о выполнении команд схематично можно представить так:

3.3. Абстрактные цели и имена файлов

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

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

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

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

3.4. Пример работы make

Рассмотрим, как утилита make будет обрабатывать такой make-файл:

Предположим, что в директории с проектом находятся следующие файлы:

  • main.cpp
  • Editor.cpp
  • TextLine.cpp

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

Цель не указана в командной строке, поэтому запускается алгоритм выбора цели (функция ВыбратьГлавнуюЦель). Главной целью становится файл iEdit (первая цель из первого правила).

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

Сначала поочередно обрабатываются описанные в правиле зависимости (функция ОбработатьЗависимости). Первая зависимость — объектный файл main.o. Поскольку в make-файле есть правило с такой целью (функция ЕстьТакаяЦель возвращает true), то для цели main.o запускается процедура ДостичьЦели.

Функция ДостичьЦели ищет правило, где описана цель main.o. Эта цель описана во втором правиле make-файла. Для этого правила запускается функция ОбработатьПравило.

Функция ОбработатьПравило запускает процесс обработки зависимостей (функция ОбработатьЗависимости). Во втором правиле указана единственная зависимость — main.cpp. Такой цели в make-файле не существует, поэтому считается, что зависимость main.cpp является именем файла. Далее, проверяется наличие этого файла на диске (функция ПроверитьНаличиеФайла) — такой файл существует. На этом процесс обработки зависимостей завершается.

После обработки зависимостей, функция ОбработатьПравило принимает решение о том, нужно ли выполнять указанные в правиле команды (функция НужноВыполнятьКоманды). Цели правила (файла main.o) не существует, поэтому команды нужно выполнять. Функция ВыполнитьКоманды запускает указанную в правиле команду (компилятор GCC), в результате чего создается файл main.o.

Цель main.o достигнута (объектный файл main.o построен). Теперь make возвращается к обработке остальных зависимостей первого правила. Зависимости Editor.o и TextLine.o обрабатываются аналогично. Для них выполняются те же действия, что и для зависимости main.o.

После того, как все зависимости (main.o, Editor.o и TextLine.o) обработаны, решается вопрос о необходимости выполнения указанных в правиле команд (функция НужноВыполнятьКоманды).

Поскольку цель (iEdit) является именем файла, который в данный момент не существует, то принимается решение выполнить описанную в правиле команду (функция ВыполнитьКоманды).

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

3.5. Еще один пример работы make

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

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

Поскольку в правиле не указано каких-либо зависимостей, make сразу переходит к этапу обработки указанных в правиле команд. Цель является именем действия, поэтому команды нужно выполнять.

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

3.6. Переменные

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

присваивает переменной obj_list значение «main.o Editor.o TextLine.o» (без кавычек). Пробелы между символом ‘=’ и началом первого слова игнорируются. Следующие за последним словом пробелы также игнорируются. Значение переменной можно использовать с помощью конструкции:

Например, при обработке такого make-файла:

на экран будет выведена строка:

Переменные могут не только содержать текстовые строки, но и «ссылаться» на другие переменные. Например, в результате обработки make-файла:

на экран будет выведено:


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

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

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

Автоматические переменные — это переменные со специальными именами, которые «автоматически» принимают определенные значения перед выполнением описанных в правиле команд. Автоматические переменные можно использовать для «упрощения» записи правил. Такое, например, правило:

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

Здесь $^ и $@ являются автоматическими переменными. Переменная $^ означает «список зависимостей». В данном случае при вызове компилятора GCC она будет ссылаться на строку «main.o Editor.o TextLine.o». Переменная $@ означает «имя цели» и будет в этом примере ссылаться на имя «iEdit».

Иногда использование автоматических переменных совершенно необходимо — например, в шаблонных правилах (о них пойдет речь в следующей главе).

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

Шаблонные правила (implicit rules или pattern rules) — это правила, которые могут быть применены к целой группе файлов. В этом их отличие от обычных правил — описывающих отношения между конкретными файлами.

Традиционные реализации make поддерживают так называемую «суффиксную» форму записи шаблонных правил:

Например, следующее правило говорит о том, что все файлы с расширением «o» зависят от соответствующих файлов с расширением «cpp»:

Обратите внимание на использование автоматической переменной $^ для передачи компилятору имени файла-зависимости. Поскольку шаблонное правило может применяться к разным файлам, использование автоматических переменных — это единственный способ узнать для каких файлов сейчас задействуется правило.

Шаблонные правила позволяют упростить make-файл и сделать его более универсальным. Рассмотрим простой проектный файл:

Все исходные тексты программы обрабатываются одинаково — для них вызывается компилятор GCC. С использованием шаблонных правил, этот пример можно переписать так:

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

Makefile: правильная пересборка .c файлов в случае изменения .h файлов:

src1.c
src1.h
src2.c
src3.c
src4.c
src4.h

Стоит обратить внимание, что НЕ для всех .c файлов есть свои .h файлы.

Текущий Мakefile работает, но с оговоркой: при изменении любого .h файла пересобираются ВСЕ .c файлы:

Если раскрыть содержимое переменной $(HDRS), то автоматическая сборка будет выглядеть так:

Именно поэтому, если изменился только один файл src4.h, то будут пересобраны все *.c файлы.

В другом случае, попытка сделать так, как указано в примере ниже, приведёт к ошибке, потому, что файлы src2.h src3.h не существуют:

Будьте любезны, подскажите, пожалуйста, как сделать правильный Makefile, чтобы при изменении src4.h пересобирался только src4.с, но не остальные.

Все мои самостоятельные попытки найти ответ на этот вопрос успехом не увенчались.

makefile Начало работы с makefile

замечания

Makefile — это текстовый файл, который контролирует работу программы make . Программа make обычно используется для управления созданием программ из их исходных файлов, но ее можно более широко использовать для обработки любого процесса, в котором файлы (или целевые объекты ) необходимо восстановить после того, как другие файлы (или предпосылки ) были изменены. Файл makefile описывает взаимосвязь между целями и предварительными условиями, а также указывает команды, необходимые для обновления цели, когда одно или несколько предварительных условий были изменены. Единственный способ make «устаревшим» — это сравнить время модификации целевых файлов и их предварительные условия.

Makefiles несколько уникальны несколькими способами, которые могут быть запутаны изначально.

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

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

Версии

название Также известен как Первоначальная версия Версия Дата выхода
POSIX сделать 1992 Издание IEEE Std 1003.1-2008, 2020 2020-09-30
NetBSD BУбедитесь , 1988 20200926 2020-09-26
GNU make gmake 1988 4.2.1 2020-06-10
SunPro сделать dmake 2006 2015-07-13
MSVS nmake 2003 2015p3 2020-06-27

Основной Makefile

Подумайте о написании «привет мир!». программа в c. Допустим, наш исходный код находится в файле source.c, теперь, чтобы запустить нашу программу, нам нужно ее скомпилировать, как правило, на Linux (с использованием gcc) нам нужно будет ввести $> gcc source.c -o output где вывод это имя исполняемого файла. Для базовой программы это работает хорошо, но по мере того, как программы становятся более сложными, наша команда компиляции также может усложняться. Здесь появляется Makefile , make- файлы позволяют нам выписывать довольно сложный набор правил для того, как скомпилировать программу, а затем просто скомпилировать ее, набрав make в командной строке. Например, вот пример Makefile для примера hello wold выше.

Основной Makefile

Давайте сделаем базовый Makefile и сохраним его в нашей системе в том же каталоге, что и наш исходный код с именем Makefile . Обратите внимание, что этот файл должен быть назван Makefile, однако Capitol M является необязательным. Тем не менее, относительно условно использовать капитолий М.

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

Переменные в Make-файлах

В зависимости от проекта вы можете ввести некоторые переменные в ваш файл make. Вот пример Makefile с присутствующими переменными.

Теперь рассмотрим, что здесь произошло. В первой строке мы объявили переменную с именем CFLAGS, которая содержит несколько общих флагов, которые вы, возможно, захотите передать компилятору, обратите внимание, что вы можете хранить столько флагов, сколько хотите в этой переменной. Затем мы имеем ту же строку, что и перед тем, как сообщать make, чтобы проверить source.c, чтобы увидеть, было ли это изменено совсем недавно, чем вывод, если это так, что оно запускает правило компиляции. Правило нашей компиляции в основном такое же, как и раньше, но оно было сокращено с помощью переменных, переменная $ встроена в make (называемая автоматической переменной, см. Https://www.gnu.org/software/make/manual/ html_node / Automatic-Variables.html ), и это всегда означает источник, поэтому в данном случае source.c . $(CFLAGS) — это наша переменная, которую мы определили ранее, но обратите внимание, что нам пришлось поставить переменную в круглую скобку с $ front, как этот $(someVariable) . Это синтаксис для указания Make, чтобы расширить переменную до того, что вы набрали ранее. Наконец, мы имеем символ $ @, еще раз это переменная, встроенная в make, и она просто обозначает цель этапа компиляции, поэтому в этом случае это означает вывод .

чистый

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

Как вы можете видеть, мы просто добавили еще одно правило в наш Makefile и одну дополнительную переменную, которая содержит все наши цели. Это довольно распространенное правило в make-файлах, поскольку оно позволяет быстро удалить все созданные вами двоичные файлы, просто набрав $> make clean . Набрав make clean, вы сообщите программе make о запуске чистого правила, а затем make запустит команду rm, чтобы удалить все ваши цели.

Я надеюсь, что этот краткий обзор использования make поможет вам ускорить ваш рабочий процесс, Makefiles может стать очень сложным, но с этими идеями вы сможете начать использовать make и лучше понять, что происходит в других программистах Makefiles . Для получения дополнительной информации об использовании сделать отличный ресурс https://www.gnu.org/software/make/manual/ .

Определение правил

Быстрый старт

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

Правила следуют синтаксису ниже: (Обратите внимание, что команды, следующие за правилом, отступаются от вкладки )

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

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

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

Правила шаблонов

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

Представьте, что мы хотим создать цели foo.o и bar.o , скомпилировав C-скрипты, foo.c и bar.c , соответственно. Это можно сделать, используя обычные правила:


где автоматическая переменная $ — это имя первого предпосылки и $@ имя цели (полный список автоматических переменных можно найти здесь ).

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

Неявные правила

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

Пример правила шаблона, который мы видели в предыдущем разделе, на самом деле не должен быть объявлен в Makefile, поскольку make имеет неявное правило для компиляции C. Таким образом, в следующем правиле предварительные условия foo.o и bar.o будут построены с использованием неявного правила для компиляции C, прежде чем строить foo .

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

Общее правило для gzip файла

если каталог содержит 2 файла:

и makefile содержат следующий текст

то вы можете получить example.txt.gz , введя в оболочку

Makefile , состоит только из одного правила , которое инструктирует сделать , как создать файл, имя которого оканчивается .gz , если есть файл с таким же именем , но .gz суффиксом.

makefile Hello World

Примечание: [TAB] следует заменить фактической вкладкой, stackoverflow заменяет вкладки пробелами, а пробелы не используются так же, как вкладки в make-файле.

Вводный курс по Makefile: компилируем статические файлы для сайта

January 27, 2020

Из этой статьи вы узнаете как устроен Makefile и как практически использовать программу make.

А конкретно, вы узнаете:

  • Как практически задавать правила.
  • Какие могут быть подводные камни.
  • Как подойти к выбору числа процессов.
  • Как пакетно сжать .png, .jpg, .js.
  • Как скопилировать все .less в .css одним махом.
  • Как проверить что нет явных ошибок в PHP и JS.

В статье используется GNU Make версии 4.2.

Введение

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

Примерный вид простейшего Makefile:

Запустим команду make с этим Makefile в каталоге (не забыв заменить пробелы на табы), и с каким-то текстовым файлом source.txt, мы получим в этом же каталоге файл report.txt, в котором будет записано число строк в файле source.txt.

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

Исходные данные не поменялись, все верно. Делать в самом деле нечего!

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

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

Пример сложней

Например, такой Makefile найдёт все файлы .txt в текущем каталоге, посчитает в них строки, запишет результат в соответствующие файлы .wc :

Что здесь делается:

  1. Сначала найдем все исходные файлы .txt.
  2. Затем заменим расширение найденых файлов на .wc.
  3. Обозначаем в правиле all что нам нужны все файлы .wc.
  4. В шаблонном правиле показываем какой командой-рецептом получить .wc из соответствующего .txt.
  • В самой команде $ будет заменено исходным файлом, а вместо $@ будет подставлен целевой файл. Все виды подстановок.
  • Команда с @ в начале не будет показана в выводе.

Запустим make с выполнением задач в восемь потоков:

Команда make нашла все файлы и сделала всё, что было нужно.

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

Как видите, даже на такой примитивной задаче как подсчет строк в файлах можно получить прирост в скорости за счет запуска в несколько потоков. Также можно заметить что на очень простых задачах выгода от запуска в несколько процессов не всегда пропорциональна числу процессов (много съедает запуск параллельных задач).

Выбор числа потоков

Не существует какого-то конкретного и однозначного правила, из которого бы следовало что во всех случаях make нужно запускать с числом потоков (упомянутый выше ключ -j или —jobs ), например, равным числу ядер процессора. Впрочем, даже такое грубое правило можно использовать как отправную точку.

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

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

Дальше мы рассмотрим особенности выполнения в несколько потоков на практических задачах.

Долой переменные

Тот же Makefile можно еще больше упростить, отказавшись от переменных.

Конечно, в таком виде Makefile не приобретает в понятности: важно это или нет, решать вам.

Виртуальные цели

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

Если мы создадим файл test в текущем каталоге, то make откажется что-либо делать.

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

С таким указанием существование или отсутствие файла test ни на что не влияет:

Пробелы или табуляция?

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


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

Если нужно, заменить начальные пробелы (по четыре) можно так:

Практическая задача

Достаточно с введением! Рассмотрим практическую задачу: для всевозможные ресурсов сайта необходимо получить уменьшенную или скомпилированную версию. В частности:

  1. Для .jpg нужно получить .min.jpg, уменьшенные согласно рекомендаций Google.
  2. Аналогично, для .png нужно получить .min.png.
  3. Для всех .js нужно получить сжатые и сокращённые .min.js, по ходу проверив на очевидные ошибки.
  4. Для всех .less нужно получить готовые к использованию .css.
  5. Для всех .php, измененный за последнее время, нужно проверить синтаксис.
  6. И так далее, и тому подобное по потребностям.

Задача будет выполняться на сервере CI или при пуше через Git при деплое.

Реквизиты

Установим все требуемые программы:

Для работы всех рецептов ниже может потребоваться установить более свежие версии этих пакетов из testing или из backports. Например, так:

Смотрите по обстоятельствам.

Подготовка

Начнём с заголовка для Makefile. У нас он будет выглядеть так:

Здесь мы в переменной NPROC записываем доступное число ядер из команды nproc, научаем make использовать все доступные ядра без необходимости прописывать их явно в командной строке, назначаем полный баш вместо ущербного /bin/sh , используемого по умолчанию. Два доллара ( $$ ) нужно использовать для запуска команд и обращения к переменным шелла, в отличии от получения значений встроенных переменных.

Число процессов указываем в переменной чтобы можно было переназначить её при запуске make. Это нужно когда, например, вы запускаете задачу на CI сервере с количеством памяти, недостаточным для выполнения в максимальном числе потоков. Например, так:

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

В инструкциях ниже исходим из того что все статические файлы лежат в стандартном каталоге htdocs внутри текущего каталога www , в котором уже лежит наш Makefile. Зададим это умолчание в переменной в начале файла:

Там же, в начале Makefile, в правиле all будут перечислены все отдельные подправила в качестве зависимостей:

Их же мы указываем как виртуальные.

Пережмём PNG

Начнём с простого: с пережатия .png файлов. Такое сжатие делается прямолинейно и без особых подводных каменей.

Что здесь происходит:

  1. Для правила minify-png в качестве зависимостей укажем все найденные файлы .png, не включая уже уменьшенные .min.png. При этом сделаем подстановку, заменив исходное расширение .png на целевое .min.png.
  2. Пережмем файл, удаляя цветовую информация для прозрачных пикелей, перезаписывая файлы, используя больше итераций. Вывод команды сжатия не показываем.
  3. Покажем размеры исходного и уменьшенного файла.

Вот так просто мы ужали наши файлы с 1,4 Мб до 904 Кб, и всё это без потери качества.

Почему Zopfli? Zopfli vs OptiPNG

Программа OptiPNG — другой популярный вариант уменьшить картинки. Правила для работы с ней:

Путём несложного эксперимента можно убедиться что Zopfli сжимает лучше. На моих тестовых картинках OptiPNG показала результат примерно на 170 Кб более худший, чем Zopfli. Картинки у OptiPNG получились примерно на 20% больше, чем у Zopfli.

Уменьшаем JPG

Пережатие .jpg подразумевает потерю качества. По рекомендациям главного поисковика следует использовать качество 85, но это может приводить к появлению неприятных артефактов. Исключить их можно пережимая файлы с качеством 91.

  1. Для правила minify-jpg делаем всё то же, что для minify-png , лишь только не рассматривая файлы меньше 30 Кб. Их мы будем оптимизировать вручную.
  2. Запускаем команду convert из состава ImageMagick с рекомендуемыми параметрами, но делаем это в один поток при помощи команды flock , для учёта блокировки используя сам Makefile. Это нужно потому что команда convert сама умеет использовать все процессорные ресурсы: запуск нескольких команд параллельно не только не ускоряет работу, но и может привести к ошибкам.
  3. Показываем размеры старого и нового файла.

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

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

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

Потому время ожидания для flock указываем соответственно максимальному ожидаемому времени для выполнения всех-всех операций сжатия. В нашем случае мы считаем что всё-всё должно сжаться за 600 секунд или 10 минут.

Наши файлы в целом уменьшились с 748 Кб до 496 Кб заведомо без видимой потери качества.

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

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

JavaScript

Скрипты будем уменьшать с помощью Closure Compiler, используя наиболее безопасный метод оптимизации.

По процедуре ничего нового, кроме как что команду closure-compiler мы запускаем в один поток потому потому что та требовательна к объёму оперативной памати. Запустив несколько экземпляров closure-compiler параллельно можно легко получить ошибку аллокации памяти или даже призвать OOM Killer.

Объём скриптов легко сократился с 88 Кб до 48 Кб.

Кроме оптимизации файлов Closure Compiler проверяет их на корректность. Если у вас есть ошибки, то вы узнаете об этом. Например, так:

Выполнение других правил не будет продолжено. Сама программа make завершится с кодом ошибки и ваша система CI сообщит о проблеме в обычном отчете.

Проверка JavaScript кода на ошибки

В случае CI описанная в прошлом абзаце ситуация — штатная и естественная. Ведь именно для этого нужен CI, чтобы ловить ошибки. Но если отдельного этапа CI нет, то это проблема: вся процедура деплоя через Git будет сломана. Нужен какой-то способ проверить скрипты до деплоя. Встречайте правило js-lint :

Если у вас десяток или под сотню .js файлов, то ждать пока они все последовательно проверятся — это мучение. Одним вариантом было бы сделать какой-то реперный файл, от которого make будет смотреть нужно ли проверять этот .js или нет. Мы выбрали другой вариант: исходя из 250 Мб на один процесс closure-compiler посчитаем сколько их одновременно можно запустить в расчете на свободную оперативную память.

В самом правиле мы находим все интересующие нас файлы, отбиваем их нулём ( \0 ) и передаём в xargs , какая программа запускает параллельную обработку всех файлов согласно ранее определенного максимального количества процессов.

Это правило не следует добавлять в число целей, требуемых по умолчанию внутри цели all . Лучше будет сделать отдельную цель test , в требованиях к которой указать эту проверку и другие.

Для компиляции LESS файлов используем штатную утилиту.

Тут всё просто и понятно. Документация на ключ для получения относительных путей до картинок при импорте других .less файлов. В более новых версиях lessc может потребоваться использовать другие ключи для сжатия итоговых файлов.


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

PHP не предъявляет особых требований к памяти, потому используем все доступные процессоры, число которых берем из ранее определенной переменной NPROC .

Правило очистки

Обычно ожидается что если какие-то правила в Makefile что-то создают, то для них есть правило, которое удаляет результаты. Обычно это правило называется clean.

Итоговый файл

Со всеми правилами должен получиться подобный файл:

Сначала очистим все файлы, появившиеся ранее по мере тестирования:

Пересоздадим всё уменьшенные версии с самого начала:

Можно заметить что make выполняет все правила в перемешку, показывая результаты по ходу выполнения. Изменить такое поведение можно ключом —output-sync или -O . В этом случае строки в выводе будут групироваться по правилам. С другой стороны эта опция ломает определение запуска из терминала, а значит вы останетесь без цвета в выводе из тех программ, что показывают цвет при работе в терминале.

Что дальше?

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

В случае nginx эту проблему решить очень просто:

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

Если добавить отладочный заголовок:

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

Конец

Надеюсь, у вас всё получилось. Об опечатках сообщайте.

Нашли, что можно исправить или улучшить? Нашли ошибку? Напишите!

Зачем использовать GNU make, gcc в windows и какой способ правильный?

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

1) Во-первых, для чего ее использовать?
1.1 Существуют открытые исходники проектов с файлами типа «Makefile». Вообщем-то, тут, наверное, нужно выделить только кросс-платформенные проекты. Исходники для *nix систем вобщем случае не будут компилироваться на Windows или если скомпилируются — приложение не запустится. Верно?
1.2 Другие причины использования?

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

3) Существует ли способ задать компилятору gcc папку для результатов компиляции (объектных файлов и экзешников) в Windows? На линуксе для этого обычно имеются предопределенные каталоги. Имел неприятный случай, когда объектный фал создался прямо в каталоге с gcc — я после этого выставил запреты на запись, удаление файлов в этой папке.

1.1. платформа задается настройками компилятора(в мейкфайле или из командной строки) в общем случае ему пофик под чем и под что собирать, были бы доступны соответвующие либы. сборка проекта на сервере под другой осью/с другой аппаратной платформой — вполне нормальное дело
1.2. не вдаваясь в срач по компиляторам — gcc открытый набор компиляторов под кучу платформ с высоким соответствием стандартам и прочая.

2.
MinGW(компиляторы и прочая для разработки) — все уже собрано и готово
Cygwin(posix среда для винды) — там и баш и все прочее.
но конкретно баш тебе не нужен. у винды тоже есть консоль и программы все так же благополучно используют аргументы командной строки.

3. опять же через командную строку/мейкфайл

Denadan
> MinGW(компиляторы и прочая для разработки) — все уже собрано и готово
а как в нем с файлами Makefile работать — через mingw32-make?

Забудь про мейкфайлы. Используй cmake.

Ghost2
> graveman
>
> Забудь про мейкфайлы. Используй cmake.
А если мне нужны Lua, ODE, png-либы?

CMake.
Либы подключаются или вкладываются исходники в проект, всё работает.

kvakvs
> CMake.
> Либы подключаются или вкладываются исходники в проект, всё работает.
Но там же Makefile со своим синтаксисом, а не CMakefile, CMake работает с файлами makefile?

graveman
> Но там же Makefile со своим синтаксисом, а не CMakefile, CMake работает с
> файлами makefile?
CMake создаёт Makefile под текущую машину, архитектуру и набор либ и конпеляторов.
Если винда то вполне может создать проект под студию например или коде::блокс или десяток других вариантов под разные ИДЕ.

kvakvs
> CMake создаёт Makefile под текущую машину, архитектуру и набор либ и
> конпеляторов.
Я имею ввиду, если есть уже существующий makefile, который должен быть собран при помощи gnu make.

> А если мне нужны Lua, ODE, png-либы?
Lua, это конечно исключение. Но обычно мэйкфалы никто руками не пишет.
Те же libpng и ODE используют autotools.

libpng использует cmake.

kvakvs
> CMake создаёт Makefile под текущую машину, архитектуру и набор либ и
> конпеляторов.
CMake создаёт сборку на основе файла CMakeLists.txt, в исходниках Lua есть только Makefile, который можно собрать только в MinGW 🙂

Про Lua: а если не mak-ать его, а добавить в проект в виде исходников? Слишком «не тру»? Всё-равно при сборке перекомпиляций не будет, только перелинковка, ну и кучка лишнего промежуточного барахла.

KoMaTo3
> Про Lua: а если не mak-ать его, а добавить в проект в виде исходников? Слишком
> «не тру»? Всё-равно при сборке перекомпиляций не будет, только перелинковка, ну
> и кучка лишнего промежуточного барахла.
Почему бы тогда не форкнуть и добавить CMakeLists.txt, а форк использовать как сабмодуль в проекте?

KoMaTo3
> Про Lua: а если не mak-ать его, а добавить в проект в виде исходников? Слишком
> «не тру»?
Ну это надо разбираться в его makefile для выяснения зависимостей или ты предлагаешь тупо добавить все файлы и нажать билд?

Я собрал Lua при помощи порта GNU make и Mingw. Мне интересно как другие работают именно с makefile (не с CMakefile).

Сборка программы с помощью GNU Make

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 выполнит все этапы компиляции исходных файлов программы и компоновку исполняемого файла earth. Обратите внимание, что в командной строке gcc указываются только .c файлы и никогда не указываются .h файлы.

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

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

Обратите внимание, что явно задавать имя выходного файла необязательно. Оно будет получено из имени компилируемого файла заменой суффикса .c на суффикс .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 https://ant.apache.org/, scons https://www.scons.org/ и др

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Во-вторых, можно избавиться от дублирования имен файлов сначала в зависимостях, а потом в выполняемых командах. Для этого могут быть использованы специальные переменные $^, $ include deps.make

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

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

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

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

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

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

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

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

Смотреть что такое «Make» в других словарях. Эффективное использование GNU Make

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

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

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

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

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

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

То есть, правило 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 станет выполнять самое первое правило, если цель выполнения не была явно указана при вызове:


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

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 $ 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.

Переменные

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

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

  • 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)

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

См. также

Ссылки

  • Руководство по 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. .] 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

Мастер Йода рекомендует:  Как найти прямоугольники на изображении без OpenCV
Добавить комментарий