Makefile для самых маленьких
Не очень строгий перевод материала mrbook.org/tutorials/make Мне в свое время очень не хватило подобной методички для понимания базовых вещей о make. Думаю, будет хоть кому-нибудь интересно. Хотя эта технология и отмирает, но все равно используется в очень многих проектах. Кармы на хаб «Переводы» не хватило, как только появится возможность — добавлю и туда. Добавил в Переводы. Если есть ошибки в оформлении, то прошу указать на них. Буду исправлять.
Статья будет интересная прежде всего изучающим программирование на C/C++ в UNIX-подобных системах от самых корней, без использования IDE.
Компилировать проект ручками — занятие весьма утомительное, особенно когда исходных файлов становится больше одного, и для каждого из них надо каждый раз набивать команды компиляции и линковки. Но не все так плохо. Сейчас мы будем учиться создавать и использовать Мейкфайлы. Makefile — это набор инструкций для программы make, которая помогает собирать программный проект буквально в одно касание.
Для практики понадобится создать микроскопический проект а-ля Hello World из четырех файлов в одном каталоге:
Все скопом можно скачать отсюда
Автор использовал язык C++, знать который совсем не обязательно, и компилятор g++ из gcc. Любой другой компилятор скорее всего тоже подойдет. Файлы слегка подправлены, чтобы собирались gcc 4.7.1
Программа make
Процесс сборки
Компилятор берет файлы с исходным кодом и получает из них объектные файлы. Затем линковщик берет объектные файлы и получает из них исполняемый файл. Сборка = компиляция + линковка.
Компиляция руками
Самый простой Мейкфайл
В нем должны быть такие части:
Для нашего примера мейкфайл будет выглядеть так:
Использование зависимостей
Использовать несколько целей в одном мейкфайле полезно для больших проектов. Это связано с тем, что при изменении одного файла не понадобится пересобирать весь проект, а можно будет обойтись пересборкой только измененной части. Пример:
Это надо сохранить под именем Makefile-2 все в том же каталоге
Использование переменных и комментариев
Переменные широко используются в мейкфайлах. Например, это удобный способ учесть возможность того, что проект будут собирать другим компилятором или с другими опциями.
Что делать дальше
После этого краткого инструктажа уже можно пробовать создавать простые мейкфайлы самостоятельно. Дальше надо читать серьезные учебники и руководства. Как финальный аккорд можно попробовать самостоятельно разобрать и осознать такой универсальный мейкфайл, который можно в два касания адаптировать под практически любой проект:
Пример создания Makefile для Go-приложений
В этом руководстве мы рассмотрим, как разработчик Go может использовать Makefile при разработке собственных приложений.
Что такое Makefile-ы?
Makefile — невероятно полезный инструмент автоматизации, который можно использовать для запуска и сборки приложений не только на Go, но и на большинстве других языков программирования.
Его часто можно увидеть в корневом каталоге множества Go приложений на Github и Gitlab. Он широко используются в качестве инструмента для автоматизации задач, которые часто сопровождают разработчиков.
Если вы используете Go для создания веб-сервисов, то Makefile поможет решить следующие задачи:
Если мы вызовем команду make в этом каталоге, то получим следующий вывод:
Переменные окружения
Первое, чего мы хотим от Makefile — подключать переменные окружения, которые мы определили для проекта. Поэтому первая строчка будет выглядеть так:
Далее мы определяем имя проекта, папки/файлы Go, пути к pid…
В оставшейся части Makefile мы будем часто использовать переменную GOPATH. Все наши команды должны быть связаны с GOPATH конкретного проекта, иначе они не будут работать. Это обеспечивает чистую изоляцию наших проектов, но при этом усложняет работу. Чтобы упростить задачу, мы можем добавить команду exec, которая выполнит любую команду с нашим GOPATH.
Однако стоит помнить, что использовать exec нужно только в том случае, если требуется сделать то, что нельзя прописать в makefile.
Режим разработки
Режим разработки должен:
Описанный выше код решает следующие задачи:
Компиляция
Команда compile не просто вызывает go compile в фоновом режиме — она очищает вывод ошибок и печатает упрощенную версию.
Вот как выглядит вывод командной строки, когда мы внесли «ломающие» правки:
Запуск/остановка сервера
start-server запускает бинарник, скомпилированный в фоновом режиме, сохраняя свой PID во временный файл. stop-server читает PID и убивает процесс при необходимости.
Мониторинг изменений
Нам нужен файл-watcher для отслеживания изменений. Я перепробовал многие, но не смог найти подходящего, поэтому написал свой собственный инструмент для мониторинга файлов — yolo. Установите его с помощью команды:
После установки мы можем наблюдать за изменениями в каталоге проекта, исключая папки vendor и bin.
Теперь у нас есть команда watch, которая рекурсивно отслеживает изменения в каталоге проекта, за исключением каталога vendor. Мы можем просто передать любую команду в run.
Например, start вызывает make-start-server при изменении кода:
Мы можем использовать его для запуска тестов или проверки race conditions автоматически. Переменные окружения будут установлены при исполнении, поэтому вам не нужно беспокоиться о GOPATH:
Откройте localhost: 9001 в браузере и сразу же увидите результат работы:
Установка зависимостей
Когда мы вносим изменения в код, мы бы хотели, чтобы отсутствующие зависимости были загружены до компиляции. Команда install сделает эту работу за нас:
Мы будем автоматизировать вызов install при изменении файла перед компиляцией, поэтому зависимости будут установлены автоматически. Если вы хотите установить зависимость вручную, можете запустить:
Внутри эта команда будет преобразована в:
Как это работает? Смотрите следующий раздел, где мы добавляем обычные команды Go для реализации команд более высокого уровня.
Команды Go
Поскольку мы хотим установить GOPATH в каталог проекта, чтобы упростить управление зависимостями, которое до сих пор официально не решено в экосистеме Go, нам нужно обернуть все команды Go в Makefile.
Наконец, нам нужна команда help, чтобы увидеть список доступных команд. Мы можем автоматически генерировать красиво отформатированный вывод справки, используя команды sed и column:
Следующая команда сканирует Makefile на строки, начинающиеся с ##, и выводит их. Таким образом, вы можете просто комментировать определенные команды, и комментарии будут выводиться командой help.
Просто о make
Меня всегда привлекал минимализм. Идея о том, что одна вещь должна выполнять одну функцию, но при этом выполнять ее как можно лучше, вылилась в создание UNIX. И хотя UNIX давно уже нельзя назвать простой системой, да и минимализм в ней узреть не так то просто, ее можно считать наглядным примером количество- качественной трансформации множества простых и понятных вещей в одну весьма непростую и не прозрачную. В своем развитии make прошел примерно такой же путь: простота и ясность, с ростом масштабов, превратилась в жуткого монстра (вспомните свои ощущения, когда впервые открыли мэйкфайл).
Мое упорное игнорирование make в течении долгого времени, было обусловлено удобством используемых IDE, и нежеланием разбираться в этом ‘пережитке прошлого’ (по сути — ленью). Однако, все эти надоедливые кнопочки, менюшки ит.п. атрибуты всевозможных студий, заставили меня искать альтернативу тому методу работы, который я практиковал до сих пор. Нет, я не стал гуру make, но полученных мною знаний вполне достаточно для моих небольших проектов. Данная статья предназначена для тех, кто так же как и я еще совсем недавно, желают вырваться из уютного оконного рабства в аскетичный, но свободный мир шелла.
Make- основные сведения
make — утилита предназначенная для автоматизации преобразования файлов из одной формы в другую. Правила преобразования задаются в скрипте с именем Makefile, который должен находиться в корне рабочей директории проекта. Сам скрипт состоит из набора правил, которые в свою очередь описываются:
1) целями (то, что данное правило делает);
2) реквизитами (то, что необходимо для выполнения правила и получения целей);
3) командами (выполняющими данные преобразования).
В общем виде синтаксис makefile можно представить так:
То есть, правило make это ответы на три вопроса:
Несложно заметить что процессы трансляции и компиляции очень красиво ложатся на эту схему:
Простейший Makefile
Предположим, у нас имеется программа, состоящая всего из одного файла:
Для его компиляции достаточно очень простого мэйкфайла:
Компиляция из множества исходников
Предположим, что у нас имеется программа, состоящая из 2 файлов:
main.c
Makefile, выполняющий компиляцию этой программы может выглядеть так:
Он вполне работоспособен, однако имеет один значительный недостаток: какой — раскроем далее.
Инкрементная компиляция
Представим, что наша программа состоит из десятка- другого исходных файлов. Мы вносим изменения в один из них, и хотим ее пересобрать. Использование подхода описанного в предыдущем примере приведет к тому, что все без исключения исходные файлы будут снова скомпилированы, что негативно скажется на времени перекомпиляции. Решение — разделить компиляцию на два этапа: этап трансляции и этап линковки.
Теперь, после изменения одного из исходных файлов, достаточно произвести его трансляцию и линковку всех объектных файлов. При этом мы пропускаем этап трансляции не затронутых изменениями реквизитов, что сокращает время компиляции в целом. Такой подход называется инкрементной компиляцией. Для ее поддержки make сопоставляет время изменения целей и их реквизитов (используя данные файловой системы), благодаря чему самостоятельно решает какие правила следует выполнить, а какие можно просто проигнорировать:
Попробуйте собрать этот проект. Для его сборки необходимо явно указать цель, т.е. дать команду make hello.
После- измените любой из исходных файлов и соберите его снова. Обратите внимание на то, что во время второй компиляции, транслироваться будет только измененный файл.
После запуска make попытается сразу получить цель hello, но для ее создания необходимы файлы main.o и hello.o, которых пока еще нет. Поэтому выполнение правила будет отложено и make станет искать правила, описывающие получение недостающих реквизитов. Как только все реквизиты будут получены, make вернется к выполнению отложенной цели. Отсюда следует, что make выполняет правила рекурсивно.
Фиктивные цели
На самом деле, в качестве make целей могут выступать не только реальные файлы. Все, кому приходилось собирать программы из исходных кодов должны быть знакомы с двумя стандартными в мире UNIX командами:
Командой make производят компиляцию программы, командой make install — установку. Такой подход весьма удобен, поскольку все необходимое для сборки и развертывания приложения в целевой системе включено в один файл (забудем на время о скрипте configure). Обратите внимание на то, что в первом случае мы не указываем цель, а во втором целью является вовсе не создание файла install, а процесс установки приложения в систему. Проделывать такие фокусы нам позволяют так называемые фиктивные (phony) цели. Вот краткий список стандартных целей:
Теперь мы можем собрать нашу программу, произвести ее инсталлцию/деинсталляцию, а так же очистить рабочий каталог, используя для этого стандартные make цели.
Обратите внимание на то, что в цели all не указаны команды; все что ей нужно — получить реквизит hello. Зная о рекурсивной природе make, не сложно предположить как будет работать этот скрипт. Так же следует обратить особое внимание на то, что если файл hello уже имеется (остался после предыдущей компиляции) и его реквизиты не были изменены, то команда make ничего не станет пересобирать. Это классические грабли make. Так например, изменив заголовочный файл, случайно не включенный в список реквизитов, можно получить долгие часы головной боли. Поэтому, чтобы гарантированно полностью пересобрать проект, нужно предварительно очистить рабочий каталог:
Для выполнения целей install/uninstall вам потребуются использовать sudo.
Переменные
Все те, кто знакомы с правилом DRY (Don’t repeat yourself), наверняка уже заметили неладное, а именно — наш Makefile содержит большое число повторяющихся фрагментов, что может привести к путанице при последующих попытках его расширить или изменить. В императивных языках для этих целей у нас имеются переменные и константы; make тоже располагает подобными средствами. Переменные в make представляют собой именованные строки и определяются очень просто:
Существует негласное правило, согласно которому следует именовать переменные в верхнем регистре, например:
Ниже представлен мэйкфайл, использующий две переменные: TARGET — для определения имени целевой программы и PREFIX — для определения пути установки программы в систему.
Это уже посимпатичней. Думаю, теперь вышеприведенный пример для вас в особых комментариях не нуждается.
Автоматические переменные
Автоматические переменные предназначены для упрощения мейкфайлов, но на мой взгляд негативно сказываются на их читабельности. Как бы то ни было, я приведу здесь несколько наиболее часто используемых переменных, а что с ними делать (и делать ли вообще) решать вам:
Передача аргументов для «make run»
Я использую Makefile.
Сядьте обратно. Я знаю, что это гениально, но не нужно стоять овациями.
Теперь, мой вопрос: есть ли способ передать аргументы? Так что
ОТВЕТЫ
Ответ 1
Я не знаю, как сделать то, что вы хотите точно, но обходным решением может быть:
Ответ 2
Этот вопрос почти три года, но в любом случае.
Если вы используете GNU make, это легко сделать. Единственная проблема заключается в том, что make интерпретирует необязательные аргументы в командной строке как цели. Решение состоит в том, чтобы превратить их в цели без цели, поэтому make не будет жаловаться:
Ответ 3
для стандартного make вы можете передавать аргументы, определяя такие макросы как
тогда используйте их как
Ссылки для make Microsoft NMake
Ответ 4
Вы можете передать переменную в Makefile, как показано ниже:
В качестве альтернативы используйте решение Beta:
Ответ 5
TL; DR не пытайтесь сделать это
вместо этого создайте скрипт:
ответ на поставленный вопрос:
Вы можете использовать переменную в рецепте
затем передайте переменную в качестве аргумента, чтобы сделать
но остерегайтесь ловушек. я расскажу о подводных камнях этого метода и других методов далее.
ответ на предполагаемое намерение, стоящее за вопросом:
предположение: вы хотите запустить prog с некоторыми аргументами, но перестроить его перед запуском при необходимости.
ответ: создайте скрипт, который при необходимости пересобирает, затем запускает прогу с аргументами
этот сценарий делает намерение очень ясным. он использует make, чтобы делать то, для чего он хорош: building. он использует скрипт оболочки, чтобы делать то, для чего он хорош: пакетная обработка.
также синтаксис вызова теперь практически идентичен:
Кроме того, вы можете делать все, что вам может понадобиться, с полной гибкостью и выразительностью сценария оболочки без всех предостережений make файла.
make не предназначен для запуска цели и передачи аргументов этой цели. все аргументы в командной строке интерпретируются либо как цель (a.k.a. target), либо как опция, либо как присвоение переменной.
так что если вы запустите это:
о методе назначения переменных и почему я рекомендую против него
и переменная в рецепте
это самый «правильный» и простой способ передачи аргументов в рецепт. но хотя он может использоваться для запуска программы с аргументами, он определенно не предназначен для такого использования. см https://www.gnu.org/software/make/manual/html_node/Overriding.html#Overriding
это становится еще более неловким при попытке передать несколько аргументов или аргументов, содержащих пробелы:
для записи вот как выглядит мой prog :
тогда prog всегда получит только один аргумент:
для полноты здесь приведены некоторые другие методы «передачи аргументов для запуска».
супер краткое объяснение: отфильтруйте текущую цель из списка целей. create catch all target ( % ), который ничего не делает, чтобы беззвучно игнорировать другие цели.
оба позволят вам написать что-то вроде этого
аргументы, начинающиеся с тире, будут интерпретироваться make и не будут передаваться как цель.
аргументы со знаком равенства будут интерпретированы make и не будут переданы
нет обходного пути
аргументы с пробелами неудобны
нет обходного пути
если аргумент окажется run (равным цели), он также будет удален
Обходной путь возможен со способом 2
если аргумент является законной целью, он также будет запущен.
Обходной путь возможен со способом 2
когда вы наберете неверную легитимную цель, она будет игнорироваться из-за перехвата цели.
Обходной путь должен сделать все многословным. так что вы видите, что происходит. но это создает много шума для законного выхода.
если аргумент имеет то же имя, что и существующая цель, make выдаст предупреждение о его перезаписи.
нет обходного пути, который я знаю о
аргументы со знаком равенства будут по-прежнему интерпретироваться make и не передаваться
нет обходного пути
аргументы с пробелами все еще неудобны
нет обходного пути
Обходной путь: создайте глобальный перехват цели, ничего не делая, как описано выше. с проблемой, как указано выше, что он снова будет молча игнорировать опечатки законных целей.
он использует eval для изменения make файла во время выполнения. насколько хуже вы можете стать с точки зрения читабельности и отладки и принципа наименьшего удивления.
Я тестировал только с помощью GNU Make. другие марки могут иметь другое поведение.
TL; DR не пытайтесь сделать это
вместо этого создайте скрипт:
Ответ 6
Вот еще одно решение, которое может помочь в некоторых из этих случаев использования:
Другими словами, выберите в этом случае префикс ( test- ), а затем передайте целевое имя непосредственно программе/бегун. Я думаю, что это в основном полезно, если задействован какой-то бегун script, который может развернуть имя цели во что-то полезное для базовой программы.
Ответ 7
Нет. Глядя на синтаксис с man-страницы для GNU make
вы можете указать несколько целей, следовательно, «нет» (по крайней мере, не так точно, как вы указали).
Ответ 8
Вы можете явно извлечь каждый n-й аргумент в командной строке. Для этого вы можете использовать переменную MAKECMDGOALS, она содержит список аргументов командной строки, заданных для «make», который он интерпретирует как список целей. Если вы хотите извлечь n-й аргумент, вы можете использовать эту переменную в сочетании с функцией «word», например, если вы хотите использовать второй аргумент, вы можете сохранить его в переменной следующим образом:
Ответ 9
Я бы предложил просто:
и я хотел бы добавить, что аргументы могут быть переданы:
Ответ 10
Вот мой пример. Обратите внимание, что я пишу под Windows 7, используя mingw32-make.exe, который поставляется с Dev-Cpp. (У меня есть c:\Windows\System32\make.bat, поэтому команда по-прежнему называется «make».)
Использование для регулярной очистки:
Использование для очистки и создания резервной копии в mydir/:
Ответ 11
Не слишком горжусь этим, но я не хотел передавать переменные окружения, поэтому я перевернул способ запуска консервной команды:
это выведет команду, которую вы хотите запустить, поэтому просто оцените ее в подоболочке:
Ответ 12
Передача аргументов в «make run»
Я использую Makefiles.
У меня есть цель, run которая называется цель сборки. Упрощенно это выглядит следующим образом:
Есть ли способ передать аргументы? Так что
Я не знаю способ сделать то, что вы хотите точно, но обходной путь может быть:
Если вы используете GNU make, это легко сделать. Единственная проблема заключается в том, что make нецелевые аргументы в командной строке будут интерпретироваться как цели. Решение состоит в том, чтобы превратить их в цели бездействия, поэтому make не будем жаловаться:
для стандартного make вы можете передавать аргументы, определяя макросы следующим образом
затем используйте их как это
Рекомендации по изготовлению Microsoft NMake
Вы можете передать переменную в Makefile, как показано ниже:
В качестве альтернативы используйте решение, предоставленное Beta :
TL; DR не пытайтесь сделать это
вместо этого создайте скрипт:
ответ на поставленный вопрос:
Вы можете использовать переменную в рецепте
затем передать переменную в качестве аргумента, чтобы сделать
но остерегайтесь ловушек. я расскажу о подводных камнях этого метода и других методов далее.
ответ на предполагаемое намерение, стоящее за вопросом:
предположение: вы хотите запустить prog с некоторыми аргументами, но перезапустите его, если необходимо.
ответ: создайте скрипт, который при необходимости пересобирает, затем запускает прогу с аргументами
этот сценарий делает намерение очень ясным. он использует make, чтобы делать то, для чего он хорош: building. он использует скрипт оболочки, чтобы делать то, для чего он хорош: пакетная обработка.
Кроме того, вы можете делать все, что вам может понадобиться, с полной гибкостью и выразительностью сценария оболочки без всех предостережений make-файла.
также синтаксис вызова теперь практически идентичен:
make не предназначен для передачи аргументов цели. Все аргументы в командной строке интерпретируются либо как цель (или цель), либо как опция, либо как присвоение переменной.
так что если вы запустите это:
о методе назначения переменных и почему я рекомендую против него
и переменная в рецепте
это самый «правильный» и простой способ передачи аргументов в рецепт. но хотя он может использоваться для запуска программы с аргументами, он определенно не предназначен для такого использования. см. https://www.gnu.org/software/make/manual/html_node/Overriding.html#Overriding
это становится еще более неловким при попытке передать несколько аргументов или аргументов, содержащих пробелы:
для записи вот как prog выглядит мой :
потому что тогда prog всегда будет получен только один аргумент:
Вот почему я рекомендую против этого маршрута.
для полноты здесь приведены некоторые другие методы «передачи аргументов для запуска».
супер краткое объяснение: отфильтровать текущую цель из списка целей. create catch all target ( % ), который ничего не делает, чтобы молча игнорировать другие цели.
оба позволят вам написать что-то вроде этого
аргументы, начинающиеся с тире, будут интерпретироваться make и не будут передаваться как цель.
аргументы со знаком равенства будут интерпретированы make и не будут переданы
нет обходного пути
аргументы с пробелами неудобно
нет обходного пути
если аргумент окажется run (равен цели), он также будет удален
Обходной путь возможен со способом 2
если аргумент является законной целью, он также будет запущен.
Обходной путь возможен со способом 2
когда вы неправильно наберете легитимную цель, она будет тихо проигнорирована из-за того, что поймает всю цель.
если аргумент имеет то же имя, что и существующая цель, то make выдаст предупреждение о том, что он перезаписывается.
нет обходного пути, о котором я знаю
аргументы со знаком равенства будут по-прежнему интерпретироваться make и не передаваться
нет обходного пути
аргументы с пробелами все еще неудобны
нет обходного пути
Аргументы с пробелами, eval пытающимися создать ничего не значащие цели.
Обходной путь: создайте глобальный перехват цели, ничего не делая, как описано выше. с проблемой, как указано выше, что он снова будет молча игнорировать опечатки законных целей.
Я тестировал только с помощью GNU Make. другие марки могут иметь другое поведение.
TL; DR не пытайтесь сделать это
вместо этого создайте скрипт:
Вот еще одно решение, которое может помочь с некоторыми из этих случаев использования:
Другими словами, выберите некоторый префикс ( test- в этом случае), а затем передайте имя цели непосредственно программе / исполнителю. Я предполагаю, что это в основном полезно, если есть какой-то сценарий запуска, который может развернуть целевое имя во что-то полезное для основной программы.
Нет. Глядя на синтаксис из man-страницы для GNU make
Вы можете указать несколько целей, следовательно, «нет» (по крайней мере, нет точно, как вы указали).
Вы можете явно извлечь каждый n-й аргумент в командной строке. Для этого вы можете использовать переменную MAKECMDGOALS, в которой содержится список аргументов командной строки, заданных для make, который он интерпретирует как список целей. Если вы хотите извлечь n-й аргумент, вы можете использовать эту переменную в сочетании с функцией «word», например, если вам нужен второй аргумент, вы можете сохранить его в переменной следующим образом:
Я бы предложил просто:
и я хотел бы добавить, что аргументы могут быть переданы:
Вот мой пример. Обратите внимание, что я пишу под Windows 7, используя mingw32-make.exe, который поставляется с Dev-Cpp. (У меня есть c: \ Windows \ System32 \ make.bat, поэтому команда все еще называется «make».)
Использование для регулярной уборки:
Использование для очистки и создания резервной копии в mydir /:
Не слишком горжусь этим, но я не хотел передавать переменные окружения, поэтому я перевернул способ запуска постоянной команды:
это напечатает команду, которую вы хотите запустить, поэтому просто оцените ее в подоболочке:
Я нашел способ получить аргументы со знаком равенства (=)! Ответ является особенно дополнением к ответу @lesmana (поскольку он является наиболее полным и объясненным здесь), но он был бы слишком большим, чтобы написать его в качестве комментария. Я снова повторяю его сообщение: TL; DR, не пытайтесь это сделать!
и используя его с (явно смешивая порядок аргументов):
Как видите, мы теряем общий порядок аргументов. Часть с аргументами «назначение», кажется, поменялась местами, порядок «целевых» аргументов сохраняется. Я поместил вначале аргументы «присваивания», надеюсь, вашей программе все равно, где находится аргумент.
Обновление: следующие переменные выглядят многообещающе:



