Существует несколько способов, которыми в GNU make переменная может получить свое значение. Эти способы различаются тем, как они обрабатывают значения, которые им присваиваются в makefile, и как эти значения управляются, когда позже переменная используется или расширяется.
[Recursively Expanded Variable Assignment]
Первый способ это рекурсивно расширяемая переменная (recursively expanded variable). Переменные такого сорта определяются через ‘=’ или директивой define (см. описание определения многострочных переменных [7]). Указанное значение устанавливается дословно; если определение содержит ссылки на другие переменные, то эти ссылки расширяются всякий раз, когда эта переменная подставляется (в процессе расширения другой строки). Когда такое происходит, это называется рекурсивным расширением.
Например:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:;echo $(foo)
Здесь echo 'Huh?': '$(foo)' расширится в '$(bar)' что расширяется в '$(ugh)', что в конечном счете расширяется в 'Huh?'.
Этот вид переменной единственный, поддерживаемый большинством версий make. Он имеет свои преимущества и недостатки. Достоинство (как многие считают) в следующем:
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
Это будет делать то, что задумано: когда ‘CFLAGS’ расширен в источнике, он расширяется в '-Ifoo -Ibar -O'. Основным недостатком является то, что вы не можете добавить что-то в конце переменной, как в следующем примере:
Потому что это приведет к бесконечному циклу при расширении переменной (фактически это будет распознано, и выведено сообщение об ошибке).
Другой недостаток в том, что любые функции (см. [8]), на которые есть ссылка в определении, будут выполнены каждый раз, когда переменная расширяется. Это замедляет работу make; еще хуже, что wildcard и функции shell дают непредсказуемые результаты, потому что нет простого способа управлять, когда они будут вызываны, или насколько часто это произойдет.
[Simply Expanded Variable Assignment]
Чтобы избежать проблем неудобств рекурсивно расширяемых переменных, был введен другой их вариант: просто расширяемые переменнные (simply expanded variables).
Они определяются через ':=' или '::='. В GNU make эти формы эквивалентны; однако в стандарте POSIX описана только форма '::=' (поддержка '::=' была добавлена в стандарт для POSIX Issue 8).
Значение просто расширяемой переменной сканируется один раз, расширяя любые ссылки на другие переменные и функции, когда эта переменная определена. Как только расширение завершено, значение этой переменной больше никогда не расширяется: когда переменная используется, её значение копируется дословно в качестве расширения. Если значение содержит ссылки на переменные, то результат расширения будет содержать результат расширения на момент определения этой переменной. Поэтому
x := foo
y := $(x) bar
x := later
эквивалентно следующему:
Вот несколько более сложный пример, иллюстрирующий использование ':=' совместно с функцией shell (см. описание функции shell [6]). Этот пример также показывает использование переменной MAKELEVEL, которая изменяется, когда происходит переход с одного уровня на другой (см. [9] для информации по MAKELEVEL).
ifeq (0,${MAKELEVEL})
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif
Достоинство этого использования ':=' в том, что тогда типовой рецепт "пуска в директорию" выглядит так:
${subdirs}:
${MAKE} -C $@ all
Просто расширяемые переменные обычно делают сложное программирование makefile для make более предсказуемым, потому что эти переменные работают так же, как переменные в большинстве языков программирования. Они позволяют вам переопределить переменную, используя её собственное значение (или её значение, обработанное каким-либо способом одной из функций расширения), и использовать функции расширения гораздо более эффективно (см. [8]).
Вы может етакже использовать их для введения управляемого начального пробела в значения переменных. Начальные символы пробела отбрасываются из вашего ввода перед подстановкой ссылок на переменные и вызовами функций; это означает, что вы можете включить лидирующие пробелы в значение переменной, защищая их с ссылками на переменные, например так:
nullstring :=
space := $(nullstring) # конец строки
Здесь значение переменной space содержит ровно 1 пробел. Комментарий "# конец строки" здесь добавлен только для ясности. Поскольку завершающие символы пробела не вырезаются из значений переменных, простой пробел в конце строки будет давать тот же эффект (однако это будет трудно читать). Если вы поместите пробел в конец значения переменной, то хорошей идеей будет поместить такой комментарий в конец строки, чтобы пояснить свои намерения. И наоборот, если в конце строки не требуется вводить символы пробела, то вы должны помнить, что не нужно помещать случайный комментарий в конецт строки после пробела, как здесь:
dir := /foo/bar # директория для размещения лягушек
Здесь значение переменной dir будет "/foo/bar " (с 4 завершающими пробелами), что вероятно не то, что нужно было получить (представьте себе что-нибудь наподобие $(dir)/file в этом определении!).
[Immediately Expanded Variable Assignment]
Эта форма назначения позволяет немедленное расширение при назначении переменной (immediate expansion), однако в отличие от простого назначения (simple assignment) результирующая переменная рекурсивная: она будет заново расширена снова при каждом использовании. Чтобы избежать неожиданных результатов, после немедленного расширения значение будет автоматически закрываться в кавычки: все экземпляры $ в значении после расширения будут преобразованы в $$. Этот тип назначения использует оператор ':::='. Например,
var = first
OUT :::= $(var)
var = second
в результате присвоит переменной OUT текст 'first', в то время как здесь:
var = one$$two
OUT :::= $(var)
var = three$$four
результат для переменной OUT будет содержать текст 'one$$two'. Значение расширяеься при назначении переменной, поэтому результатом являетсмя расширение первого значения var, 'one$two'; затем значение повторно ообрабатывается перед присваиванием, давая конечный результат'one$$two'.
После этого переменная OUT считается рекурсивной, поэтому при её использовании она будет повторно расширена.
Это выглядит функционально эквивалентным операторам ':=' / '::=', но с некоторыми отличиями:
• Во-первых, после назначения переменная является нормальной рекурсивной переменной; при добавлении к ней '+=' значение в правой части не будет расширено немедленно. Если вы предпочитаете, чтобы оператор '+=' немедленно расширял правую сторону, вместо этого следует использовать назначеие ':=' / '::='.
• Во-вторых, эти переменные несколько менее эффективны, чем просто расширяемые переменные, поскольку они нуждаются в повторном расширении при использовании, а не в простом копировании. Однако поскольку все ссылки на переменные исключены, это расширение просто отменяет обработку значения, и не будет выполнять расширение переменных или запускать любые функции.
Вот другой пример:
var = one$$twoOUT :::= $(var)OUT += $(var)var = three$$four
После этого значение OUT это текст "one$$two $(var)". Когда эта переменная исопльзуется, она будет расширена, и результат получится "one$two three$four".
Этот стиль назначения эквивалентен традиционному BSD make оператору ':='; как вы можете видеть, это работает несколько иначе, чем GNU make оператор ':='. Оператор :::= был добавлен в спецификацию для POSIX Issue 8, чтобы обеспечить портируемость.
[Conditional Variable Assignment]
Существует другой опеартор назначения переменных '?='. Он называется условным оператором присваивания, потому что срабатывает только если переменная еще не была определена. Этот оператор:
полностью эквивалентен следующему (см. описание функции origin [5]):
ifeq ($(origin FOO), undefined)
FOO = bar
endif
Обратите внимание, что переменная, имеющая пустое значение, по-прежнему считается определенной, поэтому для неё '?=' не установит значение.