Определить переменную make во время выполнения правила

209

В моем GNUmakefile я хотел бы иметь правило, которое использует временный каталог. Например:

out.tar: TMP := $(shell mktemp -d)
        echo hi $(TMP)/hi.txt
        tar -C $(TMP) cf $@ .
        rm -rf $(TMP)

Как написано выше, указанное выше правило создает временный каталог во время его анализа . Это означает, что, даже если я не разглядываю .tar все время, создается много временных каталогов. Я бы хотел, чтобы мой / tmp был завален неиспользуемыми временными каталогами.

Есть ли способ заставить переменную быть определенной только при запуске правила, а не всякий раз, когда оно определено?

Моя основная мысль - сбросить mktemp и tar в сценарий оболочки, но это выглядит несколько неприглядно.

Эмиль Сит
источник

Ответы:

323

В вашем примере, TMPпеременная устанавливается (и временный каталог создан) всякий раз , когда правила для out.tarоцениваются. Чтобы создать каталог только тогда, когда out.tarон действительно запущен, вам нужно переместить создание каталога вниз по шагам:

out.tar : 
    $(eval TMP := $(shell mktemp -d))
    @echo hi $(TMP)/hi.txt
    tar -C $(TMP) cf $@ .
    rm -rf $(TMP)

Функция eval оценивает строку, как если бы она была набрана в make-файле вручную. В этом случае он устанавливает TMPпеременную в результате shellвызова функции.

изменить (в ответ на комментарии):

Чтобы создать уникальную переменную, вы можете сделать следующее:

out.tar : 
    $(eval $@_TMP := $(shell mktemp -d))
    @echo hi $($@_TMP)/hi.txt
    tar -C $($@_TMP) cf $@ .
    rm -rf $($@_TMP)

Это добавило бы имя цели (в данном случае out.tar) к переменной, создав переменную с именем out.tar_TMP. Надеюсь, этого достаточно для предотвращения конфликтов.

e.James
источник
2
Приведу несколько разъяснений ... это не приведет TMP к этой цели, не так ли? Так что, если есть другие правила, которые имеют собственное использование $ (TMP) (возможно, параллельно с -j), могут быть конфликты? Кроме того, @echo даже необходимо? Кажется, вы могли бы просто оставить это.
Эмиль Сит
3
Кажется, делает свое дело (хотя и немного непрозрачно для среднего не-гуру гуру :-) Спасибо!
Эмиль Сит
29
Будьте осторожны с этим решением! $(eval $@_TMP := $(shell mktemp -d))произойдет, когда Makefile будет сначала оценен не в порядке процедур правил. Другими словами, $(eval ...)происходит раньше, чем вы думаете. Хотя это может быть хорошо для этого примера, этот метод вызовет проблемы для некоторых последовательных операций.
JamesThomasMoon1979
1
@ JamesThomasMoon1979 Знаете ли вы, как преодолеть эти ограничения, например, оценить некоторые переменные, которые должны быть результатом шагов, выполненных ранее в правиле?
Вадим Котов
2
Отвечая на мой собственный вопрос: обходной путь для меня - создавать файлы во время выполнения первого правила и пытаться оценить их в первом шаге второго правила.
Вадим Котов
63

Относительно простой способ сделать это - написать всю последовательность в виде сценария оболочки.

out.tar:
   set -e ;\
   TMP=$$(mktemp -d) ;\
   echo hi $$TMP/hi.txt ;\
   tar -C $$TMP cf $@ . ;\
   rm -rf $$TMP ;\

Я объединил некоторые связанные советы здесь: https://stackoverflow.com/a/29085684/86967

nobar
источник
3
Это, безусловно , самый простой и поэтому лучший ответ (избегая @и evalи делать ту же работу). Обратите внимание, что в выводе вы видите $TMP(например tar -C $TMP ...), хотя значение правильно передается команде.
Карл Рихтер
Вот как это обычно делается; Я надеюсь, что люди видят этот ответ, потому что на практике никто не проходит через все усилия принятого.
PMOS
Что делать, если вы используете специальные команды оболочки? В этом случае команды оболочки должны быть на том же языке оболочки, что и вызывающий make, верно? Я видел какой-то make-файл с Шебангом.
ptitpion
@ptitpion: Вы также SHELL := /bin/bashможете включить в свой make-файл функции, специфичные для BASH.
Нобар
31

Другая возможность - использовать отдельные строки для установки переменных Make при запуске правила.

Например, вот make-файл с двумя правилами. Если правило срабатывает, оно создает временный каталог и устанавливает для TMP имя временного каталога.

PHONY = ruleA ruleB display

all: ruleA

ruleA: TMP = $(shell mktemp -d testruleA_XXXX)
ruleA: display

ruleB: TMP = $(shell mktemp -d testruleB_XXXX)
ruleB: display

display:
    echo ${TMP}

Запуск кода дает ожидаемый результат:

$ ls
Makefile
$ make ruleB
echo testruleB_Y4Ow
testruleB_Y4Ow
$ ls
Makefile  testruleB_Y4Ow
user3124434
источник
Напомним, что GNU make имеет синтаксис для предварительных условий только для заказа, которые могут быть необходимы для дополнения этого подхода.
анол
11
Будьте осторожны с этим решением! ruleA: TMP = $(shell mktemp -d testruleA_XXXX)и ruleB: TMP = $(shell mktemp -d testruleB_XXXX)произойдет при первой оценке Makefile. Другими словами, ruleA: TMP = $(shell ...происходит раньше, чем вы думаете. Хотя это может работать в данном конкретном случае, этот метод вызовет проблемы для некоторых последовательных операций.
JamesThomasMoon1979
1

Мне не нравятся ответы «Не», но ... нет.

makeПеременные являются глобальными и должны оцениваться на этапе «синтаксического анализа» make-файла, а не на этапе выполнения.

В этом случае, пока переменная локальна для одной цели, следуйте ответу @ nobar и сделайте ее переменной оболочки.

Целевые переменные также считаются вредными для других реализаций make: kati , Mozilla pymake . Из-за них цель может быть построена по-разному, в зависимости от того, является ли она построенной автономно, или как зависимость родительской цели с переменной, специфичной для цели. И вы не будете знать, как это было , потому что вы не знаете, что уже построено.

Виктор Сергиенко
источник