Как написать makefile для linux

Время на прочтение
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 $&lt; &gt; $@

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.

Если какие-либо зависимости файла изменятся, то файл будет перекомпилирован:

Граф зависимостей для компиляции изображение с сайта www.andreyolegovich.ru

Граф зависимостей


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
  1. What is the make command?
  2. Makefile
  3. Structure of a Makefile
    1. File dependency
    2. Using make command to delete files
    3. The use of variables
  4. Remember before using the make command
  5. Advantages
  6. 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.

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

How Does the make Command Work?

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.

a.out File Created in the Compile Folder

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

Makefile Target and Dependencies

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

Makefile Executable and Object Files

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:

Makefile Executable and Object Files and Source 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:

Linux make Command Examples
  • main.c — is the file with the main function (int main) that calls a function from another file.
main.c File
  • text.c — is the file with the you want to print «Learn about Makefiles!».
text.c File
  • 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.
text.h File

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
gcc Terminal Output

3. List all the files in the current directory with the ls command:

ls
gcc and ls Terminal Output

The terminal shows that the new executable a.out file is created. The executable is also seen via a file explorer:

a.out File Created in the Test Folder

To test whether the compilation was successful, invoke the executable with:

./a.out
Invoking the a.out Executable Terminal Output

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.

Makefile in the Test Folder

Open the file and use the basic Makefile syntax as the guideline:

Makefile Syntax

1. Type the new executable’s name as the target, for example, my_app.

Makefile Target

2. Add object files main.o and text.o as the dependencies. The make command recompiles the target every time object files change.

Makefile Dependencies

3. Hit TAB and invoke the gcc compiler for the object files:

<TAB> gcc main.o text.o
Makefile Commands

4. Add the -o flag and name the target my_app.

Makefile -o Flag

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
Makefile With the First Rule Completed

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
Makefile for Object File 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
Makefile for Object File text.c

Save the Makefile and type make in the terminal.

make Command Terminal Output

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:

make and ls Commands Terminal Output

The terminal shows that running the command created my_app. To run the my_app file, type:

./my_app
Executing my_app Terminal Output

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:

Changing the text.c Message

2. Change the text to «Where am I?»:

Changed the text.c File Message

3. Save the file, open the terminal, and run make:

Make Command After the Changes Terminal Output

The make command detected changes in text.c and recompiled only that file.

To verify the change, run the executable:

./my_app
ls Command to Verify Changes

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
make -B Terminal Output

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
Makefile and Clean Command

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
make clean and ls Terminal Output

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
make -d Terminal Output

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
make -f Terminal Output

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
make Command Variables

When running make in the terminal, the command reads the C variable as gcc:

Make Command with Variables Terminal Output

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.

Понравилась статья? Поделить с друзьями:
  • Как написать lua скрипты
  • Как написать lsi текст правильно
  • Как написать list c
  • Как написать letter of application
  • Как написать kpi для сотрудников