img

Что такое make-файл и как он работает?

 

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

Open Data Policy

Если при обновлении некоторых файлов вы хотите запускать или обновлять весь программный модуль, то для вас может оказаться полезной утилита make. Для нее требуется файл Makefile (или makefile), который определяет набор задач для выполнения. Вы могли уже использовать make, когда компилировали программу из исходного кода. Большая часть проектов с открытым исходным кодом использует make для того, чтобы скомпилировать конечный исполняемый бинарный файл, который в последствии можно будет установить с помощью команды make install.

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

Простые примеры

Давайте начнем с того, что напечатаем в терминале простое «Hello World!». Создайте пустой каталог myproject, который будет содержать файл Makefile, внутри которого будет записано следующее:

say_hello:


        echo "Hello World"

А теперь запустите файл, набрав make внутри каталога myproject. Результат будет следующий:

$ make
echo "Hello World"


Hello World

В приведенном выше примере say_hello выступает в роли имени функции, подобно любому другому языку программирования. Это называется целью (target)Предварительные условия (prerequisites) или зависимости (dependencies) соответствуют цели. Для простоты мы не определили никаких предварительных условий в данном примере. Команда echo "Hello World" называется рецептом (recipe)Рецепт использует предварительные условия для формирования цели. Цель, предварительные условия и рецепты вместе формируют правило (rule)

Подытожим сказанное. Ниже представлен синтаксис обычного правила:

target: prerequisites
<TAB> recipe

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

final_target: sub_target final_target.c


        Recipe_to_create_final_target



sub_target: sub_target.c


        Recipe_to_create_sub_target

Цель – это не обязательно файл; это может быть просто название рецепта, как в нашем примере. Мы это называем «абстрактными целями».

Возвращаясь к примеру выше, при выполнении make отображалась целая команда echo "Hello World", а только потом уже фактический вывод команды. Зачастую нам такое не нужно. Чтобы сама команда не отображалась, ввод команды echo нужно начать с @:

say_hello:


        @echo "Hello World"

Теперь попробуйте запустить make снова. В выводе останется только следующее:

$ make


Hello World

Давайте добавим несколько абстрактных целей (generate и clean) в Makefile:

say_hello:


        @echo "Hello World"



generate:


        @echo "Creating empty text files..."


        touch file-{1..10}.txt



clean:


        @echo "Cleaning up..."


        rm *.txt

Если мы попробуем запустить make после того, как внесли в него изменения, то увидим, что выполнится только цель say_hello. Это происходит по той причине, что только первая цель в make-файле является целью по умолчанию. То, что называется целью по умолчанию (default goal), послужило причиной для того, чтобы в качестве первой цели в большинстве проектов использовалась цель allall ответственна за то, чтобы вызывать другие цели. При этом такое развитие событий можно переиграть с помощью специальной абстрактной цели под названием .DEFAULT_GOAL.

Давайте добавим ее в начало нашего make-файла:

.DEFAULT_GOAL := generate

Это запустит цель generate в качестве цели по умолчанию:

$ make


Creating empty text files...
touch file-{1..10}.txt

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

Давайте добавим абстрактную цель all и уберем .DEFAULT_GOAL:

all: say_hello generate



say_hello:


        @echo "Hello World"



generate:


        @echo "Creating empty text files..."


        touch file-{1..10}.txt



clean:


        @echo "Cleaning up..."


        rm *.txt

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

.PHONY: all say_hello generate clean



all: say_hello generate



say_hello:


        @echo "Hello World"



generate:


        @echo "Creating empty text files..."


        touch file-{1..10}.txt



clean:


        @echo "Cleaning up..."


        rm *.txt
make вызовет say_hello и generate:
$ make


Hello World


Creating empty text files...
touch file-{1..10}.txt

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

$ make clean


Cleaning up...
rm *.txt

Теперь, когда вы имеете представление о том, как работает простой make-файл и как его можно написать, давайте рассмотрим несколько более сложных примеров.

Усложненные примеры

Переменные

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

Самый простой способ определить переменную в make-файле – использовать оператор =. Например, вот так выглядит присваивание команды gcc переменной CC:

CC = gcc

Также она называется рекурсивной расширенной переменной (recursive expanded variable), и ее можно использовать в правиле:

hello: hello.c


    ${CC} hello.c -o hello

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

gcc hello.c -o hello

И ${CC}, и $(CC)являются допустимыми ссылками для вызова gcc. Но если кто-то попробует переприсвоить переменную самой себе, то получится бесконечный цикл. Давайте проверим:

CC = gcc


CC = ${CC}



all:


    @echo ${CC}

При запуске make получим следующее:

$ make


Makefile:8: *** Recursive variable 'CC' references itself (eventually).  Stop.

Чтобы избежать такой ситуации, можно воспользоваться оператором := (это будет называться простой расширенной переменной (simply expanded variable)). И у нас не должно возникнуть проблем при запуске следующего make-файла:

CC := gcc


CC := ${CC}



all:


    @echo ${CC}

Шаблоны и функции

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

# Usage:


# make        # compile all binary


# make clean  # remove ALL binaries and objects



.PHONY = all clean



CC = gcc                        # compiler to use



LINKERFLAG = -lm



SRCS := $(wildcard *.c)


BINS := $(SRCS:%.c=%)



all: ${BINS}



%: %.o


        @echo "Checking.."


        ${CC} ${LINKERFLAG} $< -o $@



%.o: %.c


        @echo "Creating object.."


        ${CC} -c $<



clean:


        @echo "Cleaning up..."


        rm -rvf *.o ${BINS}
  • Строки начинаются с #. Это комментарии.
  • Строка .PHONY = all clean определяет абстрактные цели all и clean
  • Переменная LINKERFLAG определяет флаги, которые будут использоваться в рецепте вместе с gcc.
  • SRCS := $(wildcard *.c)$(wildcard pattern) - это одна из функций для файловых имен. В данной случае, все файлы с расширением .c будут хранится в переменной SRCS
  • BINS := $(SRCS:%.c=%) : это называется ссылкой с заменой. В данной случае, если SRCS будет иметь значение 'foo.c bar.c', то значение для BINS будет следующее: 'foo bar'
  • Строка all: ${BINS}: абстрактная цель all вызывает значения в ${BINS} как отдельные цели. 
  • Правило:
%: %.o


  @echo "Checking.."


${CC} ${LINKERFLAG} $&lt; -o $@

Чтобы понять это правило, давайте взглянем на пример. Предположим, что foo - это одно из значений ${BINS}. Отождествим % с foo (% можно отождествить с любым именем цели). Ниже приведено правило в полной форме:

foo: foo.o


  @echo "Checking.."


  gcc -lm foo.o -o foo

Как показано выше, % заменяется foo$< заменяется foo.o$< выполняет соответствие по образцу с предварительными условиями, а $@ отождествляется с целью. Это правило будет вызвано для каждого значения в ${BINS}

  • Правило:
%.o: %.c


  @echo "Creating object.."


${CC} -c $<

Каждое предварительное условие в предыдущем правиле считается целью для этого правила. Ниже приведено правило в полной форме:

foo.o: foo.c


  @echo "Creating object.."


  gcc -c foo.c
  • И наконец, мы удаляем все двоичные и объектные файлы в цели clean

Ниже приведен переписанный make-файл, который мы рассматривали выше, из расчета, что он находится в каталоге с единственным файлом foo.c:

# Usage:


# make        # compile all binary


# make clean  # remove ALL binaries and objects



.PHONY = all clean



CC = gcc                        # compiler to use



LINKERFLAG = -lm



SRCS := foo.c


BINS := foo



all: foo



foo: foo.o


        @echo "Checking.."


        gcc -lm foo.o -o foo



foo.o: foo.c


        @echo "Creating object.."


        gcc -c foo.c



clean:


        @echo "Cleaning up..."


        rm -rvf foo.o foo
Ссылка
скопирована
Системное администрирование
Скидка 10%
Администратор Linux
Стань Linux администратором и сделай весомый шаг в сторону карьеры в DevOps. Самые важные знания от сертифицированного и практикующего тренера с 20 летним стажем
Получи бесплатный
вводный урок!
Пожалуйста, укажите корректный e-mail
отправили вводный урок на твой e-mail!
Получи все материалы в telegram и ускорь обучение!
img
Еще по теме:
img
  Вы можете более эффективно запускать и компилировать свои программы с помощью этого удобного средства автоматизации. Есл
img
Дотфайлы (dotfiles) – это немаловажные файлы, которые могут сыграть ключевую роль в вашей карьере разработчика программного обес
img
Linux Jargon Buster: что такое менеджер пакетов в Linux? Как это работает? То, что позволяет отличать дистрибутивы Linux друг от
img
Виртуализация серверов – это разделение одного физического сервера на несколько виртуальных серверов, каждый из которых работает
img
На базе нашего опыта и статей мы сделалем еще один полезный документ: руководство администратора по Linux/Unix системам. В докум
img
Удаленный доступ к системам давно стал необходимостью, и сейчас с трудом можно представить, что было бы, если бы мы не могли упр
Комментарии
ВЕСЕННИЕ СКИДКИ
40%
50%
60%
До конца акции: 30 дней 24 : 59 : 59