Как назначить вывод команды для переменной Makefile

225

Мне нужно выполнить некоторые правила make условно, только если установленный Python больше определенной версии (скажем, 2.5).

Я думал, что могу сделать что-то вроде выполнения:

python -c 'import sys; print int(sys.version_info >= (2,5))'

а затем используя вывод («1», если в порядке, «0» в противном случае) в операторе ifeqmake.

В простом сценарии оболочки bash это просто:

MY_VAR=`python -c 'import sys; print int(sys.version_info >= (2,5))'`

но это не работает в Makefile.

Какие-либо предложения? Я мог бы использовать любой другой разумный обходной путь для достижения этой цели.

Фортран
источник
Странная спина вокруг командной работы для выполнения других скриптов для меня в Makefile. Может быть что-то еще.
Лейф Грюнвольдт

Ответы:

346

Используйте shellвстроенный Make, как вMY_VAR=$(shell echo whatever)

me@Zack:~$make
MY_VAR IS whatever

me@Zack:~$ cat Makefile 
MY_VAR := $(shell echo whatever)

all:
    @echo MY_VAR IS $(MY_VAR)
Аркаитц Хименес
источник
34
shell не является стандартной встроенной командой Make. Это встроенная GNU Make.
Дерексон
12
stackoverflow.com/a/2373111/12916 добавляет важное примечание о побеге $.
Джесси Глик
6
Этот простой пример работает. Он также работает с конвейером команд оболочки. Но важно, чтобы вы использовали $$ для представления $ в команде оболочки
Сергей П. aka azure
28
Хотя вопрос слегка устарел, лучше всего сделать MY_VAR: = $ (shell ...), в противном случае каждый раз, когда вычисляется MY_VAR, он снова выполняет $ (shell ...).
Расс Шульц
У меня был пробел между shell и открывающими скобками, и только после удаления пробела мой makefile
вывел
29

Упаковка задания в evalработает для меня.

# dependency on .PHONY prevents Make from 
# thinking there's `nothing to be done`
set_opts: .PHONY
  $(eval DOCKER_OPTS = -v $(shell mktemp -d -p /scratch):/output)
Дейв
источник
6
«Примечание: @true здесь не позволяет Make думать, что ничего не поделаешь». Хм, вот для чего .PHONY always make these targets.
underscore_d
Спасибо! Это помогает мне обойти «странный удар» в makefile
Nam G VU
19

Вот немного более сложный пример с конвейером и назначением переменной внутри рецепта:

getpodname:
    # Getting pod name
    @eval $$(minikube docker-env) ;\
    $(eval PODNAME=$(shell sh -c "kubectl get pods | grep profile-posts-api | grep Running" | awk '{print $$1}'))
    echo $(PODNAME)
Франческо Касула
источник
2
В случае, если это когда-нибудь пригодится, я использую немного похожий подход для определения PODNAMEимени развертывания:$(eval PODNAME=$(shell sh -c "kubectl get pod -l app=sqlproxy -o jsonpath='{.items[0].metadata.name}'"))
Ян Рихтер
Синтаксис смутил меня на секунду, пока я не понял, что вы используете встроенную оболочку eval (в строке docker-env) и функцию make eval (в следующей строке).
Брайан Гордон
17

Я пишу ответ, чтобы улучшить видимость фактического синтаксиса, который решает проблему. К сожалению, то, что кто-то может считать тривиальным, может стать очень серьезной головной болью для человека, который ищет простой ответ на разумный вопрос.

Поместите следующее в файл «Makefile».

MY_VAR := $(shell python -c 'import sys; print int(sys.version_info >= (2,5))')

all:
    @echo MY_VAR IS $(MY_VAR)

Поведение, которое вы хотели бы видеть, следующее (при условии, что у вас недавно установлен Python).

make
MY_VAR IS 1

Если вы скопируете и вставите вышеуказанный текст в Makefile, получите ли вы это? Возможно нет. Вы, вероятно, получите сообщение об ошибке, подобное тому, что сообщается здесь:

makefile: 4: *** отсутствует разделитель. Стоп

Почему: потому что, хотя я лично использовал настоящую вкладку, Переполнение стека (пытаясь быть полезным) преобразует мою вкладку в несколько пробелов. Вы, разочарованный гражданин Интернета, теперь копируете это, думая, что теперь у вас есть тот же текст, который я использовал. Команда make теперь читает пробелы и обнаруживает, что команда «all» неправильно отформатирована. Поэтому скопируйте приведенный выше текст, вставьте его, а затем преобразуйте пробел перед «@echo» во вкладку, и этот пример, наконец, должен, надеюсь, сработать для вас.

AlanSE
источник
Зависит от того, в какой редактор вы вставляете. Я просто скопировал и вставил в редактор Eclipse Makefile, и получил ведущую вкладку (при необходимости).
Технофил
О, я не думал об этом. Атом здесь.
AlanSE
11

Остерегайтесь таких рецептов

target:
    MY_ID=$(GENERATE_ID);
    echo $MY_ID;

Он делает две вещи неправильно. Первая строка в рецепте выполняется в отдельном экземпляре оболочки от второй строки. Переменная тем временем теряется. Во-вторых, неправильно то, что $не удалось избежать.

target:
    MY_ID=$(GENERATE_ID); \
    echo $$MY_ID;

Обе проблемы были исправлены, и переменная может использоваться. Обратная косая черта объединяет обе строки в одну оболочку, поэтому установка переменной и чтение послесловия переменной работает.

Я понимаю, что в оригинальном сообщении говорилось, как получить результаты команды оболочки в переменную MAKE, и этот ответ показывает, как получить ее в переменную оболочки. Но другие читатели могут извлечь выгоду.

Одно из последних улучшений: если потребитель ожидает, что будет установлена ​​«переменная среды», вам придется ее экспортировать.

my_shell_script
    echo $MY_ID

понадобится это в make-файле

target:
    export MY_ID=$(GENERATE_ID); \
    ./my_shell_script;

Надеюсь, что это помогает кому-то. В целом, следует избегать какой-либо реальной работы вне рецептов, потому что если кто-то использует make-файл с опцией --dry-run, чтобы только ВИДЕТЬ, что он будет делать, у него не будет никаких нежелательных побочных эффектов. Каждый $(shell)вызов оценивается во время компиляции, и некоторая реальная работа может быть выполнена случайно. Лучше оставить реальную работу, такую ​​как создание идентификаторов, внутри рецептов, когда это возможно.

Юрай
источник
9

С GNU Make вы можете использовать shellи evalдля хранения, запуска и назначения вывода из произвольных вызовов командной строки. Разница между примере ниже , и те , использование которых :=представляет собой :=назначение происходит один раз (когда он встречается) и для всех. Рекурсивно расширенные переменные, установленные с =помощью, немного более «ленивы»; ссылки на другие переменные остаются до тех пор, пока не будет указана ссылка на саму переменную, и последующее рекурсивное расширение будет происходить каждый раз при обращении к переменной , что желательно для создания «согласованных, вызываемых фрагментов». См. Руководство по настройке переменных для получения дополнительной информации.

# Generate a random number.
# This is not run initially.
GENERATE_ID = $(shell od -vAn -N2 -tu2 < /dev/urandom)

# Generate a random number, and assign it to MY_ID
# This is not run initially.
SET_ID = $(eval MY_ID=$(GENERATE_ID))

# You can use .PHONY to tell make that we aren't building a target output file
.PHONY: mytarget
mytarget:
# This is empty when we begin
    @echo $(MY_ID)
# This recursively expands SET_ID, which calls the shell command and sets MY_ID
    $(SET_ID)
# This will now be a random number
    @echo $(MY_ID)
# Recursively expand SET_ID again, which calls the shell command (again) and sets MY_ID (again)
    $(SET_ID)
# This will now be a different random number
    @echo $(MY_ID)
Митчелл Трейси
источник
У вас есть первое приятное объяснение, которое я когда-либо встречал. Спасибо. Хотя одна вещь , чтобы добавить, что если $(SET_ID)жизнь внутри из ifпункта , что является ложным , то она по - прежнему называется .
Роман
Он по-прежнему вызывается, потому что stmts if вычисляются во время компиляции make-файла, а не во время выполнения. Для конкретных инструкций во время выполнения, поместите их в рецепты. Если они условные, добавьте в рецепт stmts, написанные в bash / shell.
Юрай
0

В приведенном ниже примере я сохранил путь к папке Makefile LOCAL_PKG_DIRи затем использовал LOCAL_PKG_DIRпеременную в целях.

Makefile:

LOCAL_PKG_DIR := $(shell eval pwd)

.PHONY: print
print:
    @echo $(LOCAL_PKG_DIR)

Терминальный выход:

$ make print
/home/amrit/folder
Амритпал Сингх
источник