Когда я освоил make, моя жизнь существенно облегчилась. Я полагал, что люди либо знают make лучше меня, либо не знают и не хотят знать, но ошибался. Участвуя в сложном проекте, я написал makefile для одной из компонент вместо скрипта, который собирал ее, и понял, что многие классные специалисты, компилирующие сложные проекты, make могут не знать — он просто не попался им на пути.
make — это утилита для сборки проектов, хотя ее можно применять для других целей. Суть в целях. Целью может быть файл, а еще может быть абстрактная цель. Цели могут зависеть одна от другой; запрещены только циклические зависимости. Абстрактная цель всегда не выполнена; цель-файл не выполнена, если файл старше чем те, от которых он зависит.
Цели описываются в Makefile (или makefile), утилита make читает его и выполняет невыполненные цели. Выполнение цели — это выполнение некоторых команд, указанных в описании цели.
Это теория. Теперь пример. Допустим, у нас есть файлы книги в ТеХ: book.tex, 1.tex, 2.tex, 3.tex, book.bib. Мы создаем цель book:
book: book.tex 1.tex 2.tex 3.tex book.bib
<Tab> pdflatex book.tex
<Tab> -bibtex8 book.aux
<Tab> pdflatex book.tex
<Tab> pdflatex book.tex
Поясним. book — это цель. После двоеточия — зависимости. Ниже команды, перед ними должен стоять символ табуляции — ну, так повелось. Знак минус перед bibtex8 означает, что ошибочное завершение команды не должно останавливать сборку. Бибтех ругается, если нет, например, года издания или автора — библиография будет неправильная. Но книга-то соберется.
Вместо book.tex в командах можно писать $< — первая зависимость.
UPDATE. Цель может быть файлом, а может быть абстрактной, вроде all, clean или book. Если такого файла нет, цель считается абстрактной. Но если файл такой появится, будут проблемы. Поэтому в большом проекте такие цели лучше отметить как абстрактные с помощью ключа .PHONY:
.PHONY: all clean book
make без аргументов выполняет цель all (и все, от которых она зависит). Можно указать цель явно: make love (только цель love надо определить).
Меня поправили, что make без аргументов выполняет первую цель в файле (хотя можно указать цель по умолчанию). По традиции all и идет первой.
Сказанного уже хватит, чтобы начать пользоваться. Целей может быть много: можно собрать книгу по-быстрому, без библиографии; можно предварительно удалить вспомогательные файлы; можно сделать цель backup и сохранить текст в архив. Можно отдельной целью записать файлы book.aux и book.bbl — тогда бибтех будет срабатывать только тогда, когда менялся bib-файл.
Идем дальше. В проекте на Си или Фортране обычно много файлов, и компилировать их все надо редко. Файл компилируется в объектный, и потом им можно пользоваться для сборки проекта. В Фортране это ключ -c. Поэтому очень удобно собирать только измененные файлы, и весь проект. Можно его сразу даже и запустить.
Важно: make не копает глубоко и смотрит только на дату и время изменения файла. Изменился файл — значит, пересоберем его и те, которые от него зависят.
У меня обычно есть цели all (традиционная), go (all + запустить), continue (запустить с контрольной точки).
Можно задать неявное правило, чтобы не описывать однотипные действия для разных файлов:
%.o: %.f90
<Tab> fortran -c -o $@ $^
Здесь описаны сразу все цели-файлы вида *.o, которые зависят от соответствующих фортрановских файлов и обрабатываются однотипной командой. Черную магию с долларами — сейчас объясню!
Ничто не мешает задать цель несколько раз, что удобно для указания зависимостей. В неявном правиле объектник зависит от исходника, и все; а отдельно можно указать зависимости без команд:
earth.o: atmo.o ocean.o sealife.o landlife.o
ocean.o: sealife.o
Теперь первым скомпилируется sealife (и если его не трогать, он больше компилироваться не будет), потом ocean, потом earth (до него, не важно, когда, будут собраны еще два).
Переменные. В файле можно определить переменные: NAME=myprog.x Пробелы считаются. Переменная может быть пустой или содержать некий текст, например:
MODULES=earth.o atmo.o ocean.o sealife.o landlife.o
Доступ к переменной — через доллар:
$(NAME): $(MODULES)
<Tab>fortran -o $(NAME) $(MODULES)
Если переменная не определена в файле, она ищется среди переменных окружения. Если не нашлась, считается пустой. Очень удобно.
Есть много деталей, на которых не останавливаюсь! Например, когда подставлять значения переменных, через которые выражены новые переменные — при определении или при использовании. Это редко нужно.
Есть специальные переменные. Они удобны, но в неявных правилах просто необходимы! В их числе:
$@ — имя цели. Удобно, чтобы компилятор создал именно такой файл.
@^ — список зависимостей. Удобно, чтобы передать их компилятору.
@< — первая зависимость. Удобно, если создается объектник, и прочие зависимости нужны только для отслеживания, пора ли компилировать файл.
Есть еще функции для преобразования текста, например, чтобы отцепить расширение и прицепить другое.
Правильно все выразить через переменные. Компилятор, ключи, пути, имя проекта, детали запуска. Тогда будет что-то вроде:
NAME=xyz_$(CPUS)
$(NAME): $(MODULES)
<Tab>$(FC) $(OPTIONS) -c -o $@ $(IPATH) $(LPATH) $^
all: $(NAME)
go: all
<Tab> $(LAUNCHER) $(CPUS) $(TIME) $(DIR) $(NAME)
В makefile, в принципе, можно занести любые команды! Узнали, создали цель, забыли. Потом make printer и все будет.
Переменные окружения доступны как обычные переменные: $(PATH), например. Это очень важно, когда нужно линковать библиотеки: пути к ним обычно и указаны в переменных окружения.
Комментарии пишите! После решетки # . Ею же можно закомментировать что-то, если надо: OPTIONS=# -check-bounds
Про make есть куча материалов. Главное, знать, что это есть и оно Вам по силам! Удачи, коллеги.