Время на прочтение
4 мин
Количество просмотров 640K
Не очень строгий перевод материала mrbook.org/tutorials/make Мне в свое время очень не хватило подобной методички для понимания базовых вещей о make. Думаю, будет хоть кому-нибудь интересно. Хотя эта технология и отмирает, но все равно используется в очень многих проектах.
Кармы на хаб «Переводы» не хватило, как только появится возможность — добавлю и туда.
Добавил в Переводы. Если есть ошибки в оформлении, то прошу указать на них. Буду исправлять.
Статья будет интересная прежде всего изучающим программирование на C/C++ в UNIX-подобных системах от самых корней, без использования IDE.
Компилировать проект ручками — занятие весьма утомительное, особенно когда исходных файлов становится больше одного, и для каждого из них надо каждый раз набивать команды компиляции и линковки. Но не все так плохо. Сейчас мы будем учиться создавать и использовать Мейкфайлы. Makefile — это набор инструкций для программы make, которая помогает собирать программный проект буквально в одно касание.
Для практики понадобится создать микроскопический проект а-ля Hello World из четырех файлов в одном каталоге:
main.cpp
#include <iostream>
#include "functions.h"
using namespace std;
int main(){
print_hello();
cout << endl;
cout << "The factorial of 5 is " << factorial(5) << endl;
return 0;
}
hello.cpp
#include <iostream>
#include "functions.h"
using namespace std;
void print_hello(){
cout << "Hello World!";
}
factorial.cpp
#include "functions.h"
int factorial(int n){
if(n!=1){
return(n * factorial(n-1));
}
else return 1;
}
functions.h
void print_hello();
int factorial(int n);
Все скопом можно скачать отсюда
Автор использовал язык C++, знать который совсем не обязательно, и компилятор g++ из gcc. Любой другой компилятор скорее всего тоже подойдет. Файлы слегка подправлены, чтобы собирались gcc 4.7.1
Программа make
Если запустить
make
то программа попытается найти файл с именем по умолчание Makefile
в текущем каталоге и выполнить инструкции из него. Если в текущем каталоге есть несколько мейкфайлов, то можно указать на нужный вот таким образом:
make -f MyMakefile
Есть еще множество других параметров, нам пока не нужных. О них можно узнать в ман-странице.
Процесс сборки
Компилятор берет файлы с исходным кодом и получает из них объектные файлы. Затем линковщик берет объектные файлы и получает из них исполняемый файл. Сборка = компиляция + линковка.
Компиляция руками
Самый простой способ собрать программу:
g++ main.cpp hello.cpp factorial.cpp -o hello
Каждый раз набирать такое неудобно, поэтому будем автоматизировать.
Самый простой Мейкфайл
В нем должны быть такие части:
цель: зависимости
[tab] команда
Для нашего примера мейкфайл будет выглядеть так:
all:
g++ main.cpp hello.cpp factorial.cpp -o hello
Обратите внимание, что строка с командой должна начинаться с табуляции! Сохраните это под именем Makefile-1
в каталоге с проектом и запустите сборку командой make -f Makefile-1
В первом примере цель называется all
. Это цель по умолчанию для мейкфайла, которая будет выполняться, если никакая другая цель не указана явно. Также у этой цели в этом примере нет никаких зависимостей, так что make сразу приступает к выполнению нужной команды. А команда в свою очередь запускает компилятор.
Использование зависимостей
Использовать несколько целей в одном мейкфайле полезно для больших проектов. Это связано с тем, что при изменении одного файла не понадобится пересобирать весь проект, а можно будет обойтись пересборкой только измененной части. Пример:
all: hello
hello: main.o factorial.o hello.o
g++ main.o factorial.o hello.o -o hello
main.o: main.cpp
g++ -c main.cpp
factorial.o: factorial.cpp
g++ -c factorial.cpp
hello.o: hello.cpp
g++ -c hello.cpp
clean:
rm -rf *.o hello
Это надо сохранить под именем Makefile-2
все в том же каталоге
Теперь у цели all
есть только зависимость, но нет команды. В этом случае make при вызове последовательно выполнит все указанные в файле зависимости этой цели.
Еще добавилась новая цель clean
. Она традиционно используется для быстрой очистки всех результатов сборки проекта. Очистка запускается так: make -f Makefile-2 clean
Использование переменных и комментариев
Переменные широко используются в мейкфайлах. Например, это удобный способ учесть возможность того, что проект будут собирать другим компилятором или с другими опциями.
# Это комментарий, который говорит, что переменная CC указывает компилятор, используемый для сборки
CC=g++
#Это еще один комментарий. Он поясняет, что в переменной CFLAGS лежат флаги, которые передаются компилятору
CFLAGS=-c -Wall
all: hello
hello: main.o factorial.o hello.o
$(CC) main.o factorial.o hello.o -o hello
main.o: main.cpp
$(CC) $(CFLAGS) main.cpp
factorial.o: factorial.cpp
$(CC) $(CFLAGS) factorial.cpp
hello.o: hello.cpp
$(CC) $(CFLAGS) hello.cpp
clean:
rm -rf *.o hello
Это Makefile-3
Переменные — очень удобная штука. Для их использования надо просто присвоить им значение до момента их использования. После этого можно подставлять их значение в нужное место вот таким способом: $(VAR)
Что делать дальше
После этого краткого инструктажа уже можно пробовать создавать простые мейкфайлы самостоятельно. Дальше надо читать серьезные учебники и руководства. Как финальный аккорд можно попробовать самостоятельно разобрать и осознать такой универсальный мейкфайл, который можно в два касания адаптировать под практически любой проект:
CC=g++
CFLAGS=-c -Wall
LDFLAGS=
SOURCES=main.cpp hello.cpp factorial.cpp
OBJECTS=$(SOURCES:.cpp=.o)
EXECUTABLE=hello
all: $(SOURCES) $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS)
$(CC) $(LDFLAGS) $(OBJECTS) -o $@
.cpp.o:
$(CC) $(CFLAGS) $< -o $@
Makefile-4
Успехов!
В этой статье мы поговорим о некоторых тонкостях работы с утилитой GNU make
, а также научимся писать простые и аккуратные make-файлы. Последнее особенно важно — make-файлы выглядят сложно и нечитабельно, если им не уделить должного внимания. Это обеспечивает make
плохую репутацию, хотя на самом деле инструмент крайне удобный, особенно для автоматизации сборки.
В большинстве проектов, будь то разработка ПО, написание книги или публикация записи в блоге, в какой-то момент приходится генерировать «конечные продукты» из вручную написанных исходных файлов, то есть осуществлять сборку.
Суть автоматической сборки состоит в том, чтобы взять за основу генерацию какого-то одного элемента, вычленить правила, по которым она происходит, и применить их к большему количеству элементов такого же типа, обновляя только изменяемые параметры.
Это может быть сделано (и часто делается) под определенную задачу с помощью кастомных скриптов, но мы в данной статье рассмотрим, как можно удобно автоматизировать сборку, используя утилиту make
и её встроенные правила.
Почему стоит использовать утилиту make
- она работает;
- легко настраивается как для новых, так и для существующих проектов;
- в большинстве ОС она предустановлена, если нет — её легко скачать;
- она крошечная и содержит мало зависимостей;
- make-файлы всё-таки могут быть короткими, ёмкими и красивыми;
- она не использует загадочные папки типа
working
илиresource
; - да и вообще темной магией не занимается — всё на виду.
Создадим файл и назовем его makefile
или Makefile
. Содержание стандартного make-файла можно описать так: «если любой из файлов-пререквизитов был изменен, то целевой файл должен быть обновлен». Суть make
в том, что нам нужно по определенным правилам произвести какие-то действия с пререквизитами, чтобы получить некую цель.
Правила, которые и составляют основу make-файла, по сути являются командами и могут быть сколь угодно сложными, а также могут содержать в себе вызов других инструментов, таких как компиляторы и парсеры.
Базовый синтаксис для определения цели (в файле makefile
):
цель: реквизит1 реквизит2 ...
команда1
команда2
...
<пустая строка>
Важно Индентация производится с помощью табуляции, а не пробелов.
В командах описывается, что make
должна сделать, чтобы создать целевой файл. Они исполняются, когда мы делаем запрос на создание или обновление целевого файла и make
заключает, что пререквизиты изменились или целевой файл еще не существует.
Часто нам нужно обрабатывать несколько файлов одного вида по одним и тем же правилам. Например, при создании страниц HTML на основе размеченного текста. Это делается с помощью шаблонных правил, наличие которых является отличительной чертой make
в сравнении с обычной сборкой через командную строку.
Шаблонные правила работают на основе сопоставления расширений файлов. Например, make
знает, как создавать объектные файлы *.o
из исходных C-файлов *.c
, компилируя их и передавая компилятору флаг -c
. В make
есть несколько встроенных шаблонных правил, самые известные из которых используются для компиляции кода на C и C++.
Теперь мы можем упростить make-файл, избегая написания команд в случаях, когда make
сама знает, что делать. Главное, не забывать ставить пустую строку после цели — make
это нужно, чтобы определять, где кончается одна цель и начинается другая.
В большинстве случаев мы можем даже опустить пререквизиты: внутренние правила make
подразумевают, что для того, чтобы, например, собрать somefile.o
по принципу Исходник на C → Объектный файл, нам нужен somefile.c
.
Будьте аккуратны: когда вы предлагаете make
свой список команд, она будет ориентироваться только на ваш код и в данном случае не будет искать шаблонные правила для сборки цели.
Вызываем make
Запустим make
в текущей директории:
make
Если make-файл в ней уже есть, будет создана (собрана) первая цель, которую make
сможет найти. Если make-файла нет (или он есть, но в нем нет целей), make
об этом сообщит.
Чтобы обратиться к конкретной цели, запустите:
make [цель]
Здесь цель
— это название цели (без квадратных скобок).
make
может догадаться, что делать, используя встроенные правила, без дополнительной информации от пользователя. Попробуйте создать файл test.c
в пустой папке и запустить make
. test.make
скомпилирует test.c
, используя встроенное шаблонное правило, и соберет цель с названием test
. Это сработает даже через несколько шагов генерации промежуточных файлов.
Специальные цели
В большинстве make-файлов можно найти цели, называемые специальными. Вот самые распространенные:
all
— собрать весь проект целиком;clean
— удалить все сгенерированные артефакты;install
— установить сгенерированные файлы в систему;release
илиdist
— для подготовки дистрибутивов (модули и тарболы).
Они не обязательно должны присутствовать в make-файле, но большинство сборочных процессов странно представить без хотя бы первых трех.
При сборке этих целей не будут созданы файлы с именами, например, all
или clean
. make
обычно решает, запускать ли какие-либо процессы, основываясь на данных о том, нужно ли изменять целевой файл. Создание файлов с подобными именами не даст make
произвести никаких изменений с целями.
Для предотвращения этого GNU make
позволяет помечать такие цели как «фиктивные» (phony), чтобы запускать их в любом случае. Сделать это можно, добавив необходимые цели в качестве пререквизитов во внутреннюю цель .PHONY
следующим образом:
.PHONY: all clean run
Цель all
обычно просто содержит главный исполняемый файл в пререквизите — иногда с дополнительными командами для пост-обработки генерируемых файлов. Так как мы в большинстве случаев хотим, чтобы цель all
исполнялась по умолчанию, она должна стоять первой в файле.
Для цели clean
стоит добавить список команд, удаляющих все генерируемые файлы. Это может быть легко сделано с использованием переменных, о которых мы поговорим в следующем разделе.
Переменные и функции
Как и в большинстве языков программирования, make-файлы можно сделать более читабельными, используя переменные. make
импортирует свою среду, позволяя нам определять переменные внутри файла через внешние источники.
Основные операции
Определять переменные и ссылаться на них можно следующим образом:
NAME = value
FOO = baz
bar = $(FOO) frob
Ссылаться на переменные можно через $(NAME)
или ${NAME}
. Если опустить скобки, make
сочтет за имя переменной только первый символ. Присоединение осуществляется при помощи оператора +=
. Можно также задать условные переменные с помощью ?=
(если им еще не присвоены значения).
Наконец, большинство реализаций make
позволяют нам задать выходную переменную с помощью оператора !=
при порождении одного подпроцесса за операцию.
Передача аргументов встроенным шаблонным правилам
Ранее мы видели некоторые шаблонные правила в действии, правда, во всех случаях они запускали одни и те же базовые команды. Конечно, требования к сборке не всегда одинаковы, и инструменты могут запрашивать разные флаги для разных задач.
Например, какому-то ПО нужно обеспечить соединение с разными библиотеками, или заданные оптимизации различаются для сборки отладки и итоговой сборки.
Поэтому встроенные правила в make
включают в себя несколько распространённых переменных в важных местах команд. Мы можем установить их по желанию из make-файла или внешней среды.
Это позволяет, например, запускать один и тот же make-файл с разными компиляторами, снабдив make
необходимым именем бинарного файла для выполнения. Так задаётся переменная среды компилятора C:
CC=clang make
Вот некоторые из самых известных переменных, которые вы могли видеть, если когда-нибудь заглядывали в make-файл:
$(CC)
/$(CXX)
— бинарные файлы для компиляторов C и C++, которыеmake
использует для сборки;$(CFLAGS)
/$(CXXFLAGS)
— флаги, передаваемые компиляторам;$(LDLIBS)
— присоединяемые библиотеки.
Программные переменные
make
хранит некоторые распространённые программы в переменных. В основном это делается для того, чтобы при необходимости их можно было перезаписать.
Самая важная из них — $(MAKE)
, которая должна использоваться при рекурсивном вызове make
из make-файла. Она принимает во внимание аргументы командной строки из исходного вызова.
В цели clean
, главная задача которой — удаление файлов, безопаснее использовать переменную $(RM)
вместо прямого вызова rm
.
Функции нескольких переменных
GNU make
определяет некоторые очень полезные методы, большинство которых работает со словами, то есть с разделенными пробелами строками символов. Это облегчает работу с переменными, содержащими в себе списки файлов.
Функции вызываются таким же образом, как вызывалась бы переменная с таким же именем, но с добавлением аргументов перед закрывающей скобкой.
Вот некоторые наиболее интересные методы:
$(wildcard шаблон)
возвращает список с названиями файлов, соответствующих шаблону, которые в том числе могут представлять собой относительный путь. Список внутри разделен с помощью пробелов, что проблематично для работы с файлами, содержащими пробелы в названии. Лучше всего избегать таких файлов при работе сmake
. Шаблон может содержать универсальный символ*
;$(patsubst шаблон поиска, шаблон замены, список слов)
заменяет все слова в списке, которые соответствуют шаблону поиска в соответствии с шаблоном замены. Оба шаблона используют%
в качестве символа;$(filter-out шаблон поиска, список слов)
возвращает список всех слов, отфильтрованных по шаблону поиска;$(notdir список слов)
возвращает список слов, где имя каждой записи сокращается до основного (то есть если имя содержит название директории, то оно отфильтровывается);$(shell команда)
запускает команду в подпроцессоре и перехватывает стандартный вывод подобно оператору!=
. Оболочка для выполнения команды определяется переменной$(SHELL)
.
Подробное описание функций можно найти в официальной документации.
Продвинутое использование переменных
Отсылки к переменным можно делать в любом контексте внутри make-файла. Можно даже соорудить имя исполняемого файла внутри списка команд с помощью соединения нескольких переменных. Это позволяет использовать переменные в качестве целей или пререквизитов и создавать простые конструкции типа:
OBJECTS = $(patsubst %.c,%.o,$(wildcard *.c))
all: $(OBJECTS)
Данный make-файл создает список всех исходных C-файлов в директории, заменяет суффикс .c
на .o
, используя функцию $(patsubst ...)
, и потом использует этот список файлов в качестве пререквизитов к цели all
. При запуске make
станет собирать цель all
, потому что она определена первой. Так как цель зависит от нескольких объектных файлов, которые могут ещё не существовать или должны быть обновлены, а make
знает, как их сделать из исходных C-файлов, все запрашиваемые файлы также будут собраны.
Это становится мощным инструментом в сочетании с шаблонными правилами — мы можем автоматически собирать пререквизиты, которые косвенно используются в качестве новых целей.
Замена суффиксов
Для совместимости с другими реализациями make
обеспечивает альтернативный синтаксис при вызове функции $(patsubst ...)
, называемый «ссылка с заменой» и позволяющий заменить некоторые суффиксы в списке слов на другие.
Make-файл из предыдущего примера можно преобразовать следующим образом:
FILES != echo *.c
OBJS = $(FILES:.c=.o)
all: $(OBJS)
Важно Вместо функции $(wildcard ...)
используется оператор !=
.
Целезависимые переменные
Это ещё одна интересная особенность, использование которой ведёт к сокращению кода: она позволяет устанавливать переменным разные значения в зависимости от текущей цели.
FOO = bar
target1: FOO = frob
target2: FOO += baz
В этом примере мы бы установили $(FOO)
значение bar
глобально, значение frob
для цели один и значение bar baz
для цели два.
Можно использовать любые необходимые операторы присваивания, что позволяет, например, создавать цели с разными наборами флагов для компилятора, просто присвоив переменной $(CFLAGS)
разные значения.
Интеграция с внешними процессами
В связи с тем, что дистрибутивы Linux в большинстве случаев поставляются с системой управления пакетами и многие пакеты создаются из проектов, использующих make
для сборки, были приняты некоторые общие правила использования переменных.
В основном они касаются установки целей проекта, так как бинарные пакеты часто состоят из результатов запуска make install
, упакованных в распространённом формате. Важно запомнить следующие переменные:
$(DESTDIR)
должна быть пустой по умолчанию и никогда не должна задаваться из make-файла (режим чтения). Используется составителями пакета для вставки пути доступа перед устанавливаемыми файлами;$(PREFIX)
— значение этой переменной в вашем make-файле должно соответствовать/usr/local
или другому заданному вами пути. Она позволяет пользователю пакета задать желаемую директорию для установки. Задавайте значение этой переменной, только если оно не было передано окружением (используя оператор?=
).
Определение шаблонных правил
Синтаксис для написания шаблонных правил во многом похож на обычный синтаксис целей. Основное отличие состоит в использовании шаблонного символа %
в основе цели (её имени до расширения), который и будет применяться для поиска соответствий.
Шаблон также может быть использован в списке пререквизитов, где он будет заменён на основу для формирования неявно определённых пререквизитов. (Список пререквизитов в случае чего можно расширить и явно определёнными.)
Также можно перезаписать любое из встроенных правил с помощью определения правила с такой же целью и пререквизитами.
Так как шаблонное правило должно быть написано таким образом, чтобы отличаться от реальных имён файлов, с которыми оно вызывается, make
предоставляет специальные переменные для определения шаблонных правил.
Динамические переменные
Все имена динамических переменных состоят из одного знака, поэтому скобки для ссылки на них не нужны. Таких переменных довольно много, рассмотрим наиболее необходимые:
$@
— полное название цели;$<
— имя первого пререквизита (в том числе косвенно сгенерированного поиском по шаблону);$^
— разделенный пробелами список всех пререквизитов (версия GNU).
Шаблонное правило, конвертирующее размеченные файлы в HTML с использованием markdown
, получает такой вид:
%.htm : %.md
markdown $^ > $@
Итоги
Ранее мы разобрали некоторые из наиболее трудных аспектов в контексте make-файлов. Перед вами относительно сложный, но тем не менее полезный make-файл, использующийся для статей на сайте автора:
.PHONY: all clean
ARTICLES = $(patsubst %.md,%.htm,$(wildcard *.md))
%.htm : %.md index.htm
./generate_article.py $< > $@
all: $(ARTICLES)
clean:
$(RM) $(ARTICLES)
Скрипт generate_article.py
реализует минималистичный шаблонизатор, используя index.htm
в качестве базы для вставки HTML, сгенерированного из входных файлов. Присутствие шаблонизатора в пререквизитах шаблонного правила обеспечивает, что изменения в шаблонизаторе вызовут изменения всех файлов, относящихся к статье.
Для дальнейшего изучения make
рекомендуем ознакомиться с официальным руководством.
По материалам статьи «Make Files Not War»
Для автоматической генерации зависимостей от файлов заголовков в языках C и C++ можно использовать команду gcc -M file.c или gcc -MM file.c. Второй вариант отличается тем, что не генерирует зависимости от системных заголовочных файлов.
Правила написания Makefile
- В качестве ЦЕЛИ или ЗАВИСИМОСТИ может использоваться список файлов через пробел или шаблон в стиле shell.
- Команды в правилах начинаются со знака табуляции.
- Порядок правил кроме первого несущественен.
- По умолчанию главной целью make является первая цель первого правила в первом make-файле. Цель можно явно задать при запуске make.
- Цель, начинающаяся с точки, не используется как цель по умолчанию, если она не содержит один или более символов / т.е. определяет путь к файлу; кроме того, по умолчанию не используются цели, определяющие шаблонные правила.
- Шаблоны интерпретируются в момент выполнения правила, при присваивании переменным интерпретация шаблона не происходит.
Автоматические переменные
В КОМАНДАХ можно использовать автоматические переменные. Эти переменные заново вычисляются для каждого выполняемого правила на основе цели и зависимостей правила.
Автоматическая переменная | Назначение |
$@ | Имя файла цели правила. В шаблонном правиле с несколькими целями,имя той цели, которая вызвала выполнение команд правила. |
$< | Имя первой зависимости. Если цель получила свои команды из неявного правила, то это будет первая зависимость, добавленная неявным правилом. |
$? | Имена всех зависимостей, которые являются более новыми, чем цель, с пробелами между ними. |
$^ | Имена всех зависимостей, с пробелами между ними. Если Вы для цели неоднократно укажете одну и ту же зависимость, значение переменной ‘$^‘ будет содержать только одну копию ее имени. |
$+ | Эта переменная аналогична переменной ‘$^‘, только зависимости, указанные неоднократно дублируются в том порядке, в котором они указаны в make-файле. Это в первую очередь полезно для использования в командах компоновки, где является существенным повторение имен библиотек в определенном порядке |
$* | База с которой сопоставляется неявное правило (см. ниже). В шаблонном правиле база представляет собой часть имени файла, которая сопоставляется символу ‘%‘ в шаблоне цели. Если целью является файл ‘dir/a.foo.b‘, а шаблон цели — ‘a.%.b‘, то базой будет ‘dir/foo‘. База полезна для создания имен файлов, связанных с правилом. В явных правилах база не определена как имя файла без расширения,если такое расширение можно выделить. Не рекомендуется использовать эту переменную в явных правилах |
Неявные правила
Неявные правила определены для многих языков программирования и применяются в соответствии с расширением исходного файла. По умолчанию в gnu make список расширений такой : .out, .a, .ln, .o, .c, .cc, .C, cpp, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch, .web, .sh, .elc, .el. При использовании неявных правил используются переменные, переопределяя которые можно управлять процессом преобразования файлов, например, указывать нестандартный компилятор или передавать ему опции.
Исходный файл | Порожденный файл | Команда |
Компиляция C-программ ‘file.c’ | ‘file.o’ | $(CC) -c $(CPPFLAGS) $(CFLAGS) file.c |
Компиляция программ на языке C++ ‘file.cc’ или ‘file.C’ |
‘file.o’ | $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) file .cc |
Компиляция программ на Фортране ‘file.f’ |
‘file.o’ | $(FC) -c $(FFLAGS) file .f |
- Введение
- Что такое
make
и Makefile - Продвинутое использование
- Заключение
Введение
В жизни многих разработчиков найдётся история про первый рабочий день с новым проектом. После клонирования основного репозитория проекта наступает этап, когда приходится вводить множество команд с определёнными флагами и в заданной последовательности. Без описания команд, в большинстве случаев, невозможно понять что происходит, например:
# Bash
touch ~/.bash_history
ufw allow 3035/tcp || echo 'cant configure ufw'
ufw allow http || echo 'cant configure ufw'
docker run
-v /root/:/root/
-v /etc:/etc
-v /var/run/docker.sock:/var/run/docker.sock
-v /var/tmp:/var/tmp
-v /tmp:/tmp
-v $PWD:/app
--network host
-w /app
--env-file .env
ansible ansible-playbook ansible/development.yml -i ansible/development --limit=localhost -vv
grep -qxF 'fs.inotify.max_user_watches=524288' /etc/sysctl.conf || echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf || echo 'cant set max_user_watches' && sysctl -p
sudo systemctl daemon-reload && sudo systemctl restart docker
Эти команды являются лишь частью того, что необходимо выполнить при разворачивании проекта. В приведённом примере видно, что команды сами по себе длинные, содержат много флагов, а значит, их трудно не только запомнить, но и вводить вручную. Постоянно вести документацию становится сложнее с ростом проекта, она неизбежно устаревает, а порог входа для новичков становится выше, ведь уже никто не в состоянии вспомнить всех деталей проекта. Некоторые такие команды необходимо использовать каждый день, и даже не один раз в день.
Со временем становится понятно, что нужен инструмент, способный объединить в себе подобные команды, предоставить к ним удобные шорткаты (более короткие и простые команды) и обеспечить самодокументацию проекта. Именно таким инструментом стал Makefile и утилита make
. Этот гайд расскажет, как использование этих инструментов позволит свести процесс разворачивания проекта к нескольким коротким и понятным командам:
# Bash
make setup
make start
make test
Что такое make
и Makefile
Makefile — это файл, который хранится вместе с кодом в репозитории. Его обычно помещают в корень проекта. Он выступает и как документация, и как исполняемый код. Мейкфайл скрывает за собой детали реализации и раскладывает «по полочкам» команды, а утилита make
запускает их из того мейкфайла, который находится в текущей директории.
Изначально make
предназначалась для автоматизации сборки исполняемых программ и библиотек из исходного кода. Она поставлялась по умолчанию в большинство *nix дистрибутивов, что и привело к её широкому распространению и повсеместному использованию. Позже оказалось что данный инструмент удобно использовать и при разработке любых других проектов, потому что процесс в большинстве своём сводится к тем же задачам — автоматизация и сборка приложений.
Применение мейка в проектах стало стандартом для многих разработчиков, включая крупные проекты. Примеры мейкфайла можно найти у таких проектов, как Kubernetes, Babel, Ansible и, конечно же, повсеместно на Хекслете.
Синтаксис Makefile
make
запускает цели из Makefile, которые состоят из команд:
# Makefile
цель1: # имя цели, поддерживается kebab-case и snake_case
команда1 # для отступа используется табуляция, это важная деталь
команда2 # команды будут выполняться последовательно и только в случае успеха предыдущей
Но недостаточно просто начать использовать мейкфайл в проекте. Чтобы получить эффект от его внедрения, понадобится поработать над разделением команд на цели, а целям дать семантически подходящие имена. Поначалу, перенос команд в Makefile может привести к свалке всех команд в одну цель с «размытым» названием:
# Makefile
up: # разворачивание и запуск
cp -n .env.example .env
touch database/database.sqlite
composer install
npm install
php artisan key:generate
php artisan migrate --seed
heroku local -f Procfile.dev # запуск проекта
Здесь происходит сразу несколько действий: создание файла с переменными окружения, подготовка базы данных, генерация ключей, установка зависимостей и запуск проекта. Это невозможно понять из комментариев и названия цели, поэтому будет правильно разделить эти независимые команды на разные цели:
# Makefile
env-prepare: # создать .env-файл для секретов
cp -n .env.example .env
sqlite-prepare: # подготовить локальную БД
touch database/database.sqlite
install: # установить зависимости
composer install
npm install
key: # сгенерировать ключи
php artisan key:generate
db-prepare: # загрузить данные в БД
php artisan migrate --seed
start: # запустить приложение
heroku local -f Procfile.dev
Теперь, когда команды разбиты на цели, можно отдельно установить зависимости командой make install
или запустить приложение через make start
. Но остальные цели нужны только при первом разворачивании проекта и выполнять их нужно в определённой последовательности. Говоря языком мейкфайла, цель имеет пререквизиты:
# Makefile
цель1: цель2 # такой синтаксис указывает на зависимость задач — цель1 зависит от цель2
команда2 # команда2 выполнится только в случае успеха команды из цель2
цель2:
команда1
Задачи будут выполняться только в указанной последовательности и только в случае успеха предыдущей задачи. Значит, можно добавить цель setup
, чтобы объединить в себе все необходимые действия:
# Makefile
setup: env-prepare sqlite-prepare install key db-prepare # можно ссылаться на цели, описанные ниже
env-prepare:
cp -n .env.example .env
sqlite-prepare:
touch database/database.sqlite
install:
composer install
npm install
key:
php artisan key:generate
db-prepare:
php artisan migrate --seed
start:
heroku local -f Procfile.dev
Теперь развернуть и запустить проект достаточно двумя командами:
# Bash
make setup # выполнит последовательно: env-prepare sqlite-prepare install key db-prepare
make start
Благодаря проделанной работе Makefile, команды проекта вместе с флагами сведены в Makefile. Он обеспечивает правильный порядок выполнения и не важно, какие при этом задействованы языки и технологии.
Продвинутое использование
Фальшивая цель
Использование make
в проекте однажды может привести к появлению ошибки make: <имя-цели> is up to date.
, хотя всё написано правильно. Зачастую, её появление связано с наличием каталога или файла, совпадающего с именем цели. Например:
# Makefile
test: # цель в мейкфайле
php artisan test
# Bash
$ ls
Makefile
test # в файловой системе находится каталог с именем, как у цели в мейкфайле
$ make test # попытка запустить тесты
make: `test` is up to date.
Как уже говорилось ранее, изначально make
предназначалась для сборок из исходного кода. Поэтому она ищет каталог или файл с указанным именем, и пытается собрать из него проект. Чтобы изменить это поведение, необходимо в конце мейкфайла добавить .PHONY
указатель на цель:
# Makefile
test:
php artisan test
.PHONY: test
# Bash
$ make test
✓ All tests passed!
Последовательный запуск команд и игнорирование ошибок
Запуск команд можно производить по одной: make setup
, make start
, make test
или указывать цепочкой через пробел: make setup start test
. Последний способ работает как зависимость между задачами, но без описания её в мейкфайле. Сложности могут возникнуть, если одна из команд возвращает ошибку, которую нужно игнорировать. В примерах ранее такой командой было создание .env-файла при разворачивании проекта:
# Makefile
env-prepare:
cp -n .env.example .env # если файл уже создан, то повторный запуск этой команды вернёт ошибку
Самый простой (но не единственный) способ «заглушить» ошибку — это сделать логическое ИЛИ прямо в мейкфайле:
# Makefile
env-prepare:
cp -n .env.example .env || true # теперь любой исход выполнения команды будет считаться успешным
Добавлять такие хаки стоит с осторожностью, чтобы не «выстрелить себе в ногу» в более сложных случаях.
Переменные
Зачастую в команды подставляют параметры для конфигурации, указания путей, переменные окружения и make
тоже позволяет этим управлять. Переменные можно прописать прямо в команде внутри мейкфайла и передавать их при вызове:
# Makefile
say:
echo "Hello, $(HELLO)!"
# Bash
$ make say HELLO=World
echo "Hello, World!"
Hello, World!
$ make say HELLO=Kitty
echo "Hello, Kitty!"
Hello, Kitty!
Переменные могут быть необязательными и содержать значение по умолчанию. Обычно их объявляют в начале мейкфайла.
# Makefile
HELLO?=World # знак вопроса указывает, что переменная опциональна. Значение после присвоения можно не указывать.
say:
echo "Hello, $(HELLO)!"
# Bash
$ make say
echo "Hello, World!"
Hello, World!
$ make say HELLO=Kitty
echo "Hello, Kitty!"
Hello, Kitty!
Некоторые переменные в Makefile имеют названия отличные от системных. Например, $PWD
называется $CURDIR
в мейкфайле:
# Makefile
project-env-generate:
docker run --rm -e RUNNER_PLAYBOOK=ansible/development.yml
-v $(CURDIR)/ansible/development:/runner/inventory # $(CURDIR) - то же самое, что $PWD в терминале
-v $(CURDIR):/runner/project
ansible/ansible-runner
Заключение
В рамках данного гайда было рассказано об основных возможностях Makefile и утилиты make
. Более плотное знакомство с данным инструментом откроет множество других его полезных возможностей: условия, циклы, подключение файлов. В компаниях, где имеется множество проектов, написанных разными командами в разное время, мейкфайл станет отличным подспорьем в стандартизации типовых команд: setup start test deploy ...
.
Возможность описывать в мейкфале последовательно многострочные команды позволяет использовать его как «универсальный клей» между менеджерами языков и другими утилитами. Широкая распространённость этого инструмента и общая простота позволяют внедрить его в свой проект достаточно легко, без необходимости доработок. Но мейкфайл может быть по-настоящему большим и сложным, это можно увидеть на примере реальных проектов:
- Codebattle
- Babel
- Kubernetes
Дополнительные материалы
- Утилита make: полезный универсальный инструмент программиста — видео-версия данного гайда.
Мейкфайлы, использованные при составлении гайда:
- Hexlet SICP
- Hexlet Basics
Introduction | |
Установка | |
Проверить версию | |
Для чего используются Makefiles | |
Формат | |
.PHONY: | |
Посмотреть цели Make-файла | |
Пример из C++ | |
Переменные | |
Docker из Makefile | |
Параметризация Make | |
BUILD_ID | |
USER_ID | |
Альтернативы | |
$$: Вызов bash команд (например whoami) | |
—: Игнорировать ошибки | |
Цель из других целей | |
Несколько make-файлов в одной директории | |
Связанные статьи |
Установить make
sudo apt install make
или для rpm
sudo yum install make
Так как make входит в состав build-essentials можно установить вместе с этим пакетом
sudo apt install build-essentials
Проверить версию make
/usr/bin/make —version
GNU Make 4.2.1
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Для чего используются Makefiles
Make-файлы используются, чтобы помочь решить, какие части большой
программы должны быть перекомпилированы.
В подавляющем большинстве случаев компилируются файлы
C
или
C++
.
Другие языки обычно имеют свои собственные инструменты, которые служат той же цели, что и Make.
Его можно использовать и за пределами программ, когда вам нужна серия инструкций для запуска
в зависимости от того, какие файлы изменились.
В этой статье вы узнаете про использование компиляции C/C++.
Вот пример графика зависимостей, который вы можете построить с помощью Make.
Если какие-либо зависимости файла изменятся, то файл будет перекомпилирован:
wikipedia.org
Формат
Makefile состоит из правил (rules).
Первым указывается название цели (target), затем зависимости (prerequisites)
и действие (recipe — набор действий/команд), которое нужно выполнить.
Зависимости нужны не всегда и указываются по необходимости. Для простоты на первом этапе можно
думать о зависимостях как о файлах, которые нужно проверить: если ни один не изменился — заново
компилировать не нужно.
Отступы по умолчанию нужно ставить табуляцией. Если хотите поменять на другой символ — задайте
.RECIPEPREFIX
target: prerequisites
recipe
На русский обычно переводят так
цель: зависимости
команды
Типичное применение: какая-то зависимость изменилась → выполнятеся действие в результате которого
создаётся таргет файл.
output: main.o message.o
g++ main.o message.o -o output
clean:
rm *.o output
Как и в статье
Configure, make, install
в примере выше используются стандартные цели (target)
Про опции -o и -c
читайте статью
«Компиляция в C++
Опция | Назначение |
---|---|
-c | Указывает компилятору не делать линковку и создавать .o файлы для каждого исходника |
-o filename | Меняет название output файла со стадартного на указанный |
-S | Directs the compiler to produce an assembly source file but not to assemble the program. |
Дополнительная информация (на
английском
):
gnu.org: Rule-Introduction
Если файл вам не нужен, например, вы просто хотите выполнить какие-то команды — можно
использовать .PHONY
.PHONY
.PHONY: site
site:
echo "HeiHei.ru"
Если теперь выполнить
make site
echo «HeiHei.ru»
HeiHei.ru
Удалите site из первой строки, а всё остальное не трогайте
make site
echo «HeiHei.ru»
HeiHei.ru
Вроде бы ничего не изменилось, но теперь создайте файл
site
рядом с
Makefile
touch site
make site
make: ‘site’ is up to date.
Так как таргет теперь реальный — make не нашёл изменений и ничего не сделал. Из-за такого простого
совпадения имени цели (target) и какого-то файла в директории может перестать работать скрипт.
Для защиты от таких неприятностей и применяют PHONY
Также PHONY удобен тем, что можно перечислить все цели в самом начале файла.
Если не злоупотреблять этой возможностью — можно улучшить читаемость кода, особенно в небольших файлах.
Посмотреть цели Make-файла
Если вы создали Make-файл с большим количеством PHONY целей и забыли название нужно — не обязательно продираться через весь файл
Чтобы получить списко всех целей воспользуйтесь
grep
и выполните
cat GNUmakefile | grep PHONY:
Пример из C++
Рассмотрим пример из статьи о
заголовочных файлах .h
Есть три файла
ls
Functions.cpp Functions.h Main.cpp
Main.cpp
#include <iostream>
#include "Functions.h"
int main() {
double b = add(1.3, 4.5);
cout << "1.3 + 4.5 is " << b << "n";
return 0;
}
Functions.cpp
double add(double x, double y)
{
return x + y;
}
Functions.h
#pragma once
double add(double x, double y);
Если один из этих файлов изменился — нужно перекомпилировать проект. Для начала будем пользоваться командой
g++ -o output Main.cpp Functions.cpp
Эта команда сначала вызывает компиляцию, затем линковку
Создайте
Makefile
и откройте его в текстовом редакторе. Например, в
Vim
touch Makefile
vi Makefile
Makefile
будет выглядеть следующим образом
output: Main.cpp Functions.cpp Functions.h
g++ -o output Main.cpp Functions.cpp
Теперь для компиляции достаточно выполнить
make output
Или просто
make
В результате появится исполняемый файл
output
В этот пример можно добавить ещё два шага: отдельно следить за компиляцией и убираться после работы.
Если вам не понятно что происходит в этом файле — изучите статью
«Компиляция в C++
.PHONY: clean
output: Main.o Functions.o
g++ Main.o Functions.o -o output
Main.o: Main.cpp
g++ -c Main.cpp
Functions.o: Functions.cpp
g++ -c Functions.cpp
clean:
rm *.o output
To запустить скрипт, достаточно выполнить
make
g++ -c Main.cpp
g++ -c Functions.cpp
g++ -o output Main.o Functions.o
Если нужно скомпилировать Main execute
make Main.o
g++ -c Main.cpp
ls
Появится файл
Main.o
но не появятся остальные (Functions.o, output)
Functions.cpp Functions.h Main.cpp Main.o Makefile
На примере команды make Main.o можно понять почему в Make-файлах используется термин цели (target)
make
Main.o
говорит — создай файл
Main.o
а инструкция в Makefile определяет правило по которому это нужно сделать.
Если теперь выполнить make
Main.o
не будет перекомпилироваться. Будут выполнены только последние два шага.
g++ -c Functions.cpp
g++ -o output Main.o Functions.o
Выполните make если ещё не выполняли и не делайте после этого clean
Добавим ещё одну функцию в наш проект. Нужно указать её в файлах Functions.*
Вызывать пока не будет, поэтому
Main.cpp
остаётся без изменений
Functions.cpp
bool test(bool x)
{
return x;
}
Functions.h
bool test(bool x);
make
g++ -c Functions.cpp
g++ -o output Main.o Functions.o
Обратите внимание:
Main.cpp
не был перекомпилирован так как в нём нет изменений.
Таже посмотрите на время изменения файла
output
оно должно измениться.
Не вносите никаких изменений в файлы и execute
make
make: ‘output’ is up to date.
Перекомпиляция не нужна и поэтому не выполнена
Переменные
Подробнее про переменные в Makefile читайте в статье
Работа с переменными в GNUmakefile
В этом примере вы можете увидеть как названия файлов сохранены в переменную для сокращения кода.
.PHONY: clean
objects = Main.o Functions.o
output: $(objects)
g++ -o output $(objects)
Main.o: Main.cpp
g++ -c Main.cpp
Functions.o: Functions.cpp
g++ -c Functions.cpp
clean:
rm *.o output
Запустить Docker container из Makefile
.PHONY: docker
docker:
docker-compose -f docker/dev/docker-compose.yml build
Параметризация Make
?= позволяет переменным быть перезаписанными на существующие переменные окружения
:= перезаписывает значение переменной
PROJECT_NAME ?= myproject
ORG_NAME ?= heihei
REPO_NAME ?= myproject
#Filenames
DEV_COMPOSE_FILE := docker/dev/docker-compose.yml
REL_COMPOSE_FILE := docker/release/docker-compose.yml
.PHONY: test release
test:
docker-compose -f $(DEV_COMPOSE_FILE) build
docker-compose -f $(DEV_COMPOSE_FILE) up agent
docker-compose -f $(DEV_COMPOSE_FILE) up test
release:
docker-compose -f $(REL_COMPOSE_FILE) build
docker-compose -f $(REL_COMPOSE_FILE) up agent
docker-compose -f $(REL_COMPOSE_FILE) run --rm app manage.py collectstatic --noinput
docker-compose -f $(REL_COMPOSE_FILE) run --rm app manage.py migrate --noinput
docker-compose -f $(REL_COMPOSE_FILE) up test
clean:
docker-compose -f $(DEV_COMPOSE_FILE) kill
docker-compose -f $(DEV_COMPOSE_FILE) rm -f
docker-compose -f $(REL_COMPOSE_FILE) kill
docker-compose -f $(DEV_COMPOSE_FILE) rm -f
BUILD_ID
To добавить переменным уникальности используют BUILD_ID
# Docker Compose Project Names
REL_PROJECT := $(PROJECT_NAME)$(BUILD_ID)
DEV_PROJECT := $(REL_PROJECT)dev
USER_ID
To получить ID пользователя запустившего GNUmakefile
USER_ID = $(shell id -u ${USER})
Какие альтернативы Make существуют
Популярными альтернативными системами сборки C/C++ являются
SCons, CMake, Bazel и Ninja. Некоторые редакторы кода, такие как
Microsoft Visual Studio
, имеют свои собственные встроенные инструменты сборки.
Для
Java
есть Ant,
Maven
и Gradle.
Другие языки, такие как
Go
и Rust, имеют свои собственные инструменты сборки.
Интерпретируемые языки, такие как
Python
,
Ruby
и
JavaScript
, не требуют аналога для создания файлов.
Цель Makefile состоит в том, чтобы скомпилировать любые файлы, которые
должны быть скомпилированы, основываясь на том, какие файлы изменились.
Но когда файлы на интерпретируемых языках меняются, ничего не нужно перекомпилировать.
При запуске программы используется самая последняя версия файла.
Что означает cc -c
cc это C compiler
Существует несколько общедоступных компиляторов C
В этой статье использовался
gcc
-c это опция, которую разбирали
здесь
whoami
В обычном
Bash скрипте
достаточно написать $(whoami) и это будет равносильно подстановке вывода whoami
В Make файле это может не получиться. Есть два варианта решить проблему
`whoami`
И
$$(whoami)
Игнорировать ошибки
Если какая-то команда выполнена с ошибкой выполнение сценария прерывается.
Рассмотрим пример
RPM_DIR=/home/$$(whoami)/rpms/
.PHONY: clean-repo
clean-repo:
@sudo rm $(RPM_DIR)release/*
@sudo rm $(RPM_DIR)master/*
Если в …release/ пусто, то удалять в …master/ make уже не будет.
Вместо этого появится ошибка:
sudo rm /home/$(whoami)/rpms/release/*
rm: cannot remove ‘/home/andrei/rpms/release/*’: No such file or directory
make: *** [clean-repo] Error 1
Избежать этой проблемы можно поставив — перед командой
RPM_DIR=/home/$$(whoami)/rpms/
.PHONY: clean-repo
clean-repo:
@-sudo rm $(RPM_DIR)release/*
@-sudo rm $(RPM_DIR)master/*
[andrei@localhost ~]$ make clean-repo
rm: cannot remove ‘/home/andrei/rpms/release/*’: No such file or directory
make: [clean-repo] Error 1 (ignored)
make жалуется, но переходит ко второй команде и чистит директорию.
Цель из других целей
Если нужно запустить несколько целей сразу, можно вызывать из новой цели
all-targets: target1 target2 target3
Несколько make-файлов в одной директории
Если в одной директории находится два и более make-файлов с совпадающими целями, вызывать
из нужного файла помогает опция -f
Пример проекта
make
├── GNUmakefile.beget
└── GNUmakefile.heihei
# GNUmakefile.beget
.PHONY: url
url:
echo «https://beget.com»
# GNUmakefile.heihei
.PHONY: url
url:
echo «https://heihei.ru»
make -f GNUmakefile.beget url
echo «https://beget.com»
https://beget.com
make -f GNUmakefile.heihei url
echo «https://heihei.ru»
https://heihei.ru
make | |
Основы make | |
PHONY | |
CURDIR | |
shell | |
wget + make | |
Переменные в Make файлах | |
ifeq: Условные операторы | |
filter | |
-c: Компиляция | |
Linux | |
Bash | |
C | |
C++ | |
C++ Header файлы | |
Configure make install | |
DevOps | |
Docker | |
OpenBSD | |
Errors make |
One of the main reasons why Linux is popular among C/C++ programmers is the support provided by Linux to them. This includes the g++ compiler suite and its related tools used for program development such as the make
command.
In this tutorial, you will learn about the make
command in Linux, its use, the basis of the makefile, and how it is used with the make
command.
Table of Contents
- What is the make command?
- Makefile
- Structure of a Makefile
- File dependency
- Using make command to delete files
- The use of variables
- Remember before using the make command
- Advantages
- Conclusion
What is the make command?
Large and complex software applications consist of hundreds of source code and other files. Compiling and linking these files is not easy and can be erroneous. During the build process of these applications, several object files are also created. To manage these files and the entire software development project, the make
command is used in Linux.
Consider the case when the programmers have their projects in simple folders. They don’t have any IDE (Integrated Development Environment) such as Eclipse or Visual Studio available to compile and handle their project. The only available option is to use the terminal to compile the source code. Instead of remembering all the commands to compile their files manually, or keeping track of files that are changed and need to be recompiled, they can simply use the make
command to handle things for them automatically.
Instead of performing the compilation steps individually and remembering the file names and commands, the make
command can be used to automatically perform these tasks. In short, make
allows you to automatically build, compile, execute or even install complex software applications using a single command. Thus, it makes the life of a C/C++ programmer easier.
The basis of the make
command is a text file called Makefile. The Makefile contains the instructions regarding options passed to the compiler.
Makefile is usually created as a text file without any extension, like this
touch makefile
In this file, you can write a set of commands to perform various tasks. For example,
g++ main.cpp -o example_code ./example_code
You can consider Makefile as a simple bash script that contains commands to build, compile, and run your code files. Now if you enter the make command like this
make
it will execute the instructions (commands) written in the Makefile. In the example given above, it will first compile the main.cpp file and will create an executable file named ‘example_code’, then it will execute the ‘example_code’ file.
In this way, you can write multiple commands in the Makefile and execute them all using the simple make
command.
Wait, you might be thinking that a Makefile is just like a simple bash script, so what is the big deal? No, the make
command can do more than what you just thought, so keep on reading.
Structure of a Makefile
You can set up several targets in a Makefile, which you can then use with the make command. A target can be specified by writing its name followed by a colon (:
), for example,
all: g++ main.cpp -o example_code run: ./example_code
here ‘all’ and ‘run’ are targets. By default, the make
command will execute the instructions written after the first target (‘all’ in this case).
If you want to execute instructions written after a specific target, you have to specify its name, for example,
make run
This command will run the executable ‘example_code’, as mentioned after the target ‘run’ in the above example.
File dependency
The other important thing you can specify in a Makefile is file dependency. It means that Makefile can specify the code modules which are required to build the program. In addition, it can also specify the required source code files for a particular code module. Therefore, you can use a Makefile for dependency checking.
In a Makefile, you can specify a dependency after the target name is separated by a space, like this
main.o: main.cpp
The above line says that the object file ‘main.o’ has a dependency on ‘main.cpp’. In other words, to execute this target the ‘main.cpp’ must exist.
Using make command to delete files
You can use the make command to delete all object and executable files created during the build process. For that, you have to create a target in the Makefile similar to the example given below.
clean: 'rm -rf *o example_code'
this target when executed (using $ make clean
) will delete all object files and the executable named ‘example_code’.
The use of variables
You can declare and use variables in a Makefile to represent values that might change. For instance, you can represent the name of the compiler using a variable like this
CC=g++
Suppose you want to change the compiler (say to gcc), then you need to change only this value if you are using a variable. There would be no need to change it in all the occurrences in the Makefile because there the name of the variable is used.
$(CC) main.cpp
You can see a variable is specified with the help of a $ sign. You can also specify options using variables, like
FLAGS= -c -Wall
And then use them as shown below:
$(CC) $(FLAGS) main.cpp
Remember before using the make command
- The make command can be used with a simple Makefile or with complex Makefiles containing several macros or commands. The use of macros with the make command brings application portability. It means the application can be used on other operating systems.
- If no file is specified, the default Makefile will be used. If you want to use your Makefile then you have to specify its name using the
-f
option.
make -f MyFile
- The make command can also be used to install a program. For that you need to specify a target in the Makefile, mentioning the program and pathname in the install command, like the one shown below:
install: skel
install -g root -o root skel /usr/local/bin
Now, it is also possible to build and install a program in a single step (i.e., by executing the make
command). If you want only installation, you can use the following command.
make install
- If you change any element of a target (such as a source code file), the
make
command will rebuild the target automatically.
Advantages
Here are some advantages of the make command and the Makefile, which will show you, its importance.
- It makes the codes easier and clearer to read and removes errors from them.
- If you make any changes in the program files, you do not need to compile them again and again. Instead, the make command will automatically compile those files only where changes have been done.
- Makefile is also used to present a project in a more systematic, organized, and efficient way. You can divide a large application program into smaller parts and use the Makefile to handle these smaller parts in different ways.
- Make command allows us to compile multiple files at once so that all the files can be compiled in a single step which is time-efficient as well.
- In the case of compiling multiple files, there is no need to type the names of all the files at the command prompt. Remembering their names is difficult and typing their names can be an error-prone task. So, it is easy to write their names once in the Makefile and let the make command handle everything.
Conclusion
In this tutorial, you have studied the make command and Makefile in detail. The make command is used to manage large development projects comprising of tens of source code files. The Makefile is simply a text file that is being used by the make command to set up targets. It allows us to represent the whole project systematically and efficiently thereby making it easier and more readable to debug.
Introduction
The Linux make
command is a commonly used utility by sysadmins and developers. The command assists in the compilation process and is a must-have tool for building large applications. This utility cuts out repetition and speeds up compilation, saving time.
This article will show you how to use the Linux make
command with examples.
Prerequisites
- Access to the terminal.
- The
make
function installed. - A text editor.
Note: If make
is not available on the system, use sudo apt install make
.
How Does the make Command Work?
The make
command compiles different program pieces and builds a final executable. The purpose of make
is to automate file compilation, making the process simpler and less time-consuming.
The command works with any programming language as long as the compiler can be executed with a shell command.
Compiling is straightforward when working with a few files. Therefore, the process includes invoking the compiler and listing file names.
For example, to compile a C program from three files (file1.c, file2.c, file3.h):
Invoke the compiler with:
gcc file1.c file2.c file3.h
The gcc
command creates an a.out file, which is a standard compiled executable.
However, changing one of the source files requires recompiling everything, which is even more complicated when working with large apps. The make
command automates the process, allowing users to update only pieces that need to be changed without recompiling every file.
The make
command uses a user-generated file, Makefile, to compile program pieces. When executed for the first time, make
searches the Makefile for instructions, e.g., file descriptions and modification times. Based on the available data, make
decides which files need to be updated and issues the necessary commands.
What Are Makefiles?
A Makefile is a text file containing a set of rules that instructs make
how to build an application. A rule consists of three parts: the target, dependencies, and command(s).
The Makefile basic syntax is:
target: dependencies
<TAB> commands
Parts of the syntax are:
- Targets. Names of the files to be created or updated after executing
make
. - Dependencies. Names of the files (separated by spaces) from which the target is constructed.
- The commands. Rules describing how to create or update the target when dependencies change.
One Makefile has several sets of rules. The first rule is the default one and states how the final executable (the target) is to be made from not-yet-created object files (dependencies):
The syntax in this case is:
EXECUTABLE: Object file 1, Object file 2
<TAB> commands
Note: The commands in Makefiles always come after the TAB key. Otherwise, the command does not work.
After setting rule one, the user adds instructions on how to create object files:
The make
command works by compiling source files into object files and then compiling object files into the target file, the final executable. The Makefile syntax with the three rules mentioned above is:
EXECUTABLE: Object file 1, Object file 2
<TAB> commands
Object file 1: Source file 1, Source file 2
<TAB> commands
Object file 2: Source file 2
<TAB> commands
The basic make
syntax looks like this:
make [OPTIONS]
When executed without arguments, make
builds the first target from the Makefile.
Linux make Command Options
The make
command is widely used due to its effectiveness, variety, and the ability to perform specific actions. While the command prints result when run without options, adding arguments expands make
‘s usability.
Here are the most used options:
Command | Description |
---|---|
-B , --always-make |
Unconditionally compiles all targets. |
-d , --debug[=FLAGS] |
Prints the debugging information. |
-C dir , --directory=dir |
Changes the directory before executing the Makefile. |
-f file , --file=file , --Makefile=FILE |
Uses a specific file as a Makefile. |
-i , --ignore-errors |
Ignores all errors in commands. |
-I dir , --include-dir=dir |
Specifies a directory to search for the specified Makefile. |
-j [jobs] , --jobs[=jobs] |
Specifies the number of jobs to run simultaneously. |
-k , --keep-going |
Continues running make for as long as possible after getting an error. |
-l [load] , --load-average[=load] |
Specifies that no new task should be started if other tasks are in the queue. |
-n , --dry-run , --just-print , --recon |
Prints expected output without executing make. |
-o file , --old-file=file , --assume-old=file |
Ensures that make does not remake the file even if it is older than the dependencies. |
-p , --print-data-base |
Prints the database produced after reading the Makefile. |
-q , --question |
Activates the Question mode, in which make doesn’t run any commands but returns an exit status zero if the target is already compiled. |
-r , --no-builtin-rules |
Eliminates the built-in implicit rules. |
-s , --silent , --quiet |
Restricts printing the commands as they are executed. |
-S , --no-keep-going , --stop |
Stops «-k , --keep-going » command. |
-t , --touch |
Touches files instead of running the commands. |
--trace |
Traces each target’s disposition. |
-W file , --what-if=file , --new-file=file , --assume-new=file |
Ignores the fact that the target file has been modified. |
--warn-undefined-variables |
Warns that an unknown variable is referenced. |
Linux make Command Examples
The best way to understand make
and Makefiles is by running the command and different options on a simple program.
For example, to build an executable that prints the message «Learn about Makefiles», follow these steps:
1. Create a directory called Test.
2. Make three source files main.c, text.c, and text.h:
- main.c — is the file with the main function (
int main
) that calls a function from another file.
- text.c — is the file with the you want to print «Learn about Makefiles!».
- text.h — is the header file with declarations for the functions. The header is included in both c files with the
#include
argument, which has the same purpose as copy/pasting the header’s contents.
The following sections illustrate some common use cases of make
and Makefiles on the three files mentioned above.
Note: The following examples are written in C language.
Create a Program
There are two ways to create a program:
- Compiling files the standard way by invoking the gcc compiler. This method is suitable for smaller programs.
- Using make and Makefiles.
Create a Program with gcc Compiler
Use the gcc compiler for simple programs only. Otherwise, use Makefiles when working with a large number of files.
To create a program with the compiler:
1. Open the terminal and navigate to the directory containing the files.
2. Invoke the gcc compiler and type the name of both c files. The header doesn’t get compiled because it’s already included in c files.
gcc main.c text.c
3. List all the files in the current directory with the ls
command:
ls
The terminal shows that the new executable a.out file is created. The executable is also seen via a file explorer:
To test whether the compilation was successful, invoke the executable with:
./a.out
The terminal shows that the executable works properly.
Create a Program with Make and Makefiles
Compiling files with make
and Makefiles is simpler than using the compiler. Start by creating a new text document in the same directory and name it Makefile or makefile.
Open the file and use the basic Makefile syntax as the guideline:
1. Type the new executable’s name as the target, for example, my_app.
2. Add object files main.o and text.o as the dependencies. The make
command recompiles the target every time object files change.
3. Hit TAB and invoke the gcc
compiler for the object files:
<TAB> gcc main.o text.o
4. Add the -o
flag and name the target my_app.
After writing the first rule, the Makefile looks like this:
my_app: main.o text.o
<TAB> gcc main.o text.o -o my_app
Next, instruct the Makefile on how to create main.o:
1. Set main.o as the target.
2. Type in main.c as the dependency. main.c serves to create and update main.o.
3. Write the following command to update main.o every time main.c changes:
gcc -c main.c
4. Add the -c
flag to instruct the Makefile not to create a new executable but only to read the code and compile the object file.
main.o: main.c
<TAB> gcc -c main.c
To create text.o, set that file as the target, and add both text.c and text.h as dependencies. However, the gcc
command only compiles the text.c
file, since headers are never compiled:
text.o: text.c text.h
<TAB> gcc -c text.c
Save the Makefile and type make
in the terminal.
The make
command created two object files (main.o and text.o) and the executable (my_app).
To verify that make created new files, run ls
again:
The terminal shows that running the command created my_app. To run the my_app file, type:
./my_app
Update the Program
When one source file is changed, make
only updates object files depending on that source file. For instance, to change the text displayed when running the my_app from «Learn about Makefiles» to «Where am I?»:
1. Open text.c in the text editor:
2. Change the text to «Where am I?»:
3. Save the file, open the terminal, and run make
:
The make
command detected changes in text.c and recompiled only that file.
To verify the change, run the executable:
./my_app
Compile All Files
To compile all files and not only the changed files, use -B
or --always-make
options.
For example, to change the text in the text.c file back to «Learn about Makefiles» and save the file, enter:
make -B
The output shows that make
compiled all the files in the folder, even the ones that haven’t been changed.
Clean Object Files
When a user runs make
for the first time, the command creates object files and the executable. Therefore, to declutter the source folder and clean object files, add the clean
function to the Makefile:
clean:
<TAB> rm *.o my_app
The command consists of:
- The clean target with no dependencies — the target is always considered outdated and always executed.
- The
rm
command — removes specified objects. - The
*.o
part — matches files with the o extension and cleans object files and my_app.
To clean object files, run:
make clean
After running ls
again, the terminal shows that the object files and my_app have been removed.
Run make in Debug Mode
Run make
in debug mode to print additional info about the compiling process. Execute make
with the -d
option to display the debugging output:
make -d
Use Different File as the Makefile
By default, make
looks for a file called Makefile or makefile in the current directory. To use another file, run:
make -f [file_name]
For example, if a Makefile is named my_file, execute:
make -f my_file
Use Variables
Variables in Makefiles represent multiple file names, arguments, targets, dependencies, commands, source directories, or other items. Furthermore, a variable is defined by a name and represents a string of text called the variable’s value.
To define a variable, use =
. For example, substitute gcc
with a variable C.
C=gcc
my_app: main.o text.o
<TAB> $ (C) main.o text.o -o my_app
main.o:main.c
<TAB> $ (C) -c main.c
text.o: text.c text.h
<TAB> $ (C) -c text.c
When running make
in the terminal, the command reads the C
variable as gcc
:
Conclusion
After going through the examples in this tutorial, you know how to use the make
command in Linux and how it works.
Next, download the Linux commands cheat sheet to learn other important Linux commands.