Проверьте, существует ли программа из Makefile

115

Как я могу проверить, можно ли вызвать программу из Makefile?

(То есть программа должна существовать в пути или иным образом быть вызываемой.)

Его можно использовать, например, для проверки того, какой компилятор установлен.

Например, что-то вроде этого вопроса , но без предположения, что базовая оболочка совместима с POSIX.

Проф. Фалькен
источник
1
Можете ли вы вызвать POSIX-совместимую оболочку?
reinierpost
Наверное, нет, я бы мог потребовать, чтобы кто-то был там, но было бы намного проще, если бы мне не пришлось.
Проф. Фалькен
Тем временем я решил это, добавив в проект программу, которая создается первой и единственная цель которой - проверить наличие этой другой программы ... :-)
Проф. Фалькен
2
Традиционный обходной путь - создать automakeсценарий, который проверяет различные предварительные условия и записывает подходящий Makefile.
tripleee

Ответы:

84

Иногда вам нужен Makefile, чтобы иметь возможность работать в разных целевых ОС, и вы хотите, чтобы сборка завершилась сбоем раньше, если требуемый исполняемый файл отсутствует, PATHа не запускался в течение, возможно, долгого времени до сбоя.

Превосходное решение, предоставленное инженером, требует постановки цели . Однако, если у вас есть много исполняемых файлов для тестирования и ваш Makefile имеет много независимых целей, каждая из которых требует тестов, то каждая цель требует тестовой цели в качестве зависимости. Это требует много дополнительного набора текста, а также времени обработки, когда вы задаете более одной цели за раз.

Решение, предоставляемое 0xf, может тестировать исполняемый файл без создания цели. Это экономит много времени на ввод и выполнение, когда есть несколько целей, которые могут быть созданы либо по отдельности, либо вместе.

Мое улучшение последнего решения состоит в том, чтобы использовать whichисполняемый файл ( whereв Windows), а не полагаться на наличие --versionопции в каждом исполняемом файле непосредственно в ifeqдирективе GNU Make , а не определять новую переменную, и использовать GNU Make errorфункция, чтобы остановить сборку, если необходимый исполняемый файл отсутствует ${PATH}. Например, чтобы проверить lzopисполняемый файл:

 ifeq (, $(shell which lzop))
 $(error "No lzop in $(PATH), consider doing apt-get install lzop")
 endif

Если у вас есть несколько исполняемых файлов для проверки, вы можете использовать foreachфункцию с whichисполняемым файлом:

EXECUTABLES = ls dd dudu lxop
K := $(foreach exec,$(EXECUTABLES),\
        $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))

Обратите внимание на использование :=оператора присваивания, который требуется для немедленного вычисления выражения RHS. Если ваш Makefile изменит PATH, то вместо последней строки выше вам понадобится:

        $(if $(shell PATH=$(PATH) which $(exec)),some string,$(error "No $(exec) in PATH")))

Это должно дать вам результат, похожий на:

ads$ make
Makefile:5: *** "No dudu in PATH.  Stop.
Джонатан Бен-Авраам
источник
2
Если вы сделаете EXECUTABLES все переменные (например, LS CC LD) и используете $ ($ (exec)), вы можете передать их, чтобы без проблем сделать из среды или других make-файлов. Полезно при кросс-компиляции.
rickfoosusa
1
Мой make подавляется в "," при запуске "ifeq (, $ (shell which lzop))" = /
mentatkgs
2
В Windows используйте where my_exe 2>NULвместо which.
Аарон Кэмпбелл
2
Остерегайтесь отступов TABS с ifeq, что является синтаксисом правила! это должно быть БЕЗ ВКЛАДКИ. Было трудно с этим. stackoverflow.com/a/21226973/2118777
Pipo
2
Если вы используете Bash, нет необходимости делать внешний вызов which. command -vВместо этого используйте встроенный . Больше информации .
kurczynski
48

Я смешал решения из @kenorb и @ 0xF и получил следующее:

DOT := $(shell command -v dot 2> /dev/null)

all:
ifndef DOT
    $(error "dot is not available please install graphviz")
endif
    dot -Tpdf -o pres.pdf pres.dot 

Это прекрасно работает, потому что команда «-v» ничего не выводит, если исполняемый файл недоступен, поэтому переменная DOT никогда не определяется, и вы можете просто проверить ее, когда захотите, в своем коде. В этом примере я выдаю ошибку, но вы можете сделать что-нибудь более полезное, если хотите.

Если переменная доступна, «команда -v» выполняет недорогую операцию печати пути команды, определяя переменную DOT.

mentatkgs
источник
5
По крайней мере, в некоторых системах (например, Ubuntu 14.04) $(shell command -v dot)не работает make: command: Command not found. Чтобы это исправить, перенаправление вывода в STDERR / DEV / нуль: $(shell command -v dot 2> /dev/null). Объяснение
J0HN
3
commandэто встроенный bash, для большей переносимости рассмотрите возможность использования which?
Жюльен Палар,
10
@JulienPalard Я считаю, что вы ошибаетесь: commandутилита требуется для posix (включая -vопцию). С другой стороны, у whichнее нет стандартизированного поведения (о котором я знаю), и она иногда совершенно непригодна для использования
Gyom
3
command -vтребуется среда оболочки, подобная POSIX. Это не будет работать в Windows без эмуляции cygwin или аналогичной.
дольмен
10
Причина, по которой commandчасто не работает, не из-за ее отсутствия , а из-за особой оптимизации, которую делает GNU Make: если команда «достаточно проста», она будет обходить оболочку; это, к сожалению, означает, что встроенные программы иногда не работают, если вы немного не измените команду.
Rufflewind
33

это то, что ты сделал?

check: PYTHON-exists
PYTHON-exists: ; @which python > /dev/null
mytarget: check
.PHONY: check PYTHON-exists

кредит моему коллеге.

инженерчуань
источник
Нет, я скомпилировал программу для одной цели и запустил ее в другой.
Проф. Фалькен
21

Используйте эту shellфункцию для вызова вашей программы таким образом, чтобы она выводила что-то на стандартный вывод. Например, пройти --version.

GNU Make игнорирует статус выхода переданной команды shell. Чтобы избежать потенциального сообщения «команда не найдена», перенаправьте стандартную ошибку на /dev/null.

Затем вы можете проверить результат, используя ifdef, ifndefи $(if)т. Д.

YOUR_PROGRAM_VERSION := $(shell your_program --version 2>/dev/null)

all:
ifdef YOUR_PROGRAM_VERSION
    @echo "Found version $(YOUR_PROGRAM_VERSION)"
else
    @echo Not found
endif

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

0xF
источник
это дает ошибку, *** missing separator. Stop.если я добавляю табуляции во все строки после all:получения ошибкиmake: ifdef: Command not found
Маттиас 009,
также: ifdefбудет оценивать как истину, даже если your_programне существует gnu.org/software/make/manual/make.html#Conditional-Syntax
Matthias 009,
1
Не добавлять вкладки к ifdef, else, endifлиний. Просто убедитесь, что @echoстроки начинаются с табуляции.
0xF
Я тестировал свое решение в Windows и Linux. В связанной документации указано, что ifdefпроверяется непустое значение переменной. Если ваша переменная не пуста, что она оценивает?
0xF
1
@ Matthias009, когда я тестирую GNU Make v4.2.1, использование ifdefили ifndef(как в принятом ответе) работает только в том случае, если оцениваемая переменная немедленно устанавливается ( :=), а не лениво set ( =). Однако обратная сторона использования немедленно установленных переменных заключается в том, что они оцениваются во время объявления, тогда как переменные с отложенным набором вычисляются при их вызове. Это означает, что вы будете выполнять команды для переменных, :=даже если Make выполняет только правила, которые никогда не используют эти переменные! Вы можете избежать этого, используя =сifneq ($(MY_PROG),)
Деннис
9

Здесь убраны некоторые из существующих решений ...

REQUIRED_BINS := composer npm node php npm-shrinkwrap
$(foreach bin,$(REQUIRED_BINS),\
    $(if $(shell command -v $(bin) 2> /dev/null),$(info Found `$(bin)`),$(error Please install `$(bin)`)))

$(info ...)Можно исключить , если вы хотите , чтобы это было спокойнее.

Это быстро потерпит неудачу . Цель не требуется.

mpen
источник
8

Мое решение включает в себя небольшой вспомогательный сценарий 1, который помещает файл флага, если все необходимые команды существуют. Это имеет то преимущество, что проверка необходимых команд выполняется только один раз, а не при каждом makeвызове.

check_cmds.sh

#!/bin/bash

NEEDED_COMMANDS="jlex byaccj ant javac"

for cmd in ${NEEDED_COMMANDS} ; do
    if ! command -v ${cmd} &> /dev/null ; then
        echo Please install ${cmd}!
        exit 1
    fi
done

touch .cmd_ok

Makefile

.cmd_ok:
    ./check_cmds.sh

build: .cmd_ok target1 target2

1 Подробнее о command -vтехнике можно прочитать здесь .

поток
источник
2
Я только что протестировал его, и он работает, и поскольку вы не указали, что у вас не работает (сценарий bash или код Makefile) или какие-либо сообщения об ошибках, я не могу помочь вам найти проблему.
Flow
Я получаю «неожиданный конец файла» по первому ifутверждению.
Адам Грант,
6

Для меня все вышеперечисленные ответы основаны на Linux и не работают с окнами. Я новичок, поэтому мой подход может быть не идеальным. Но полный пример, который работает для меня как в Linux, так и в Windows, таков:

# detect what shell is used
ifeq ($(findstring cmd.exe,$(SHELL)),cmd.exe)
$(info "shell Windows cmd.exe")
DEVNUL := NUL
WHICH := where
else
$(info "shell Bash")
DEVNUL := /dev/null
WHICH := which
endif

# detect platform independently if gcc is installed
ifeq ($(shell ${WHICH} gcc 2>${DEVNUL}),)
$(error "gcc is not in your system PATH")
else
$(info "gcc found")
endif

при необходимости, когда мне нужно найти больше инструментов, я могу использовать:

EXECUTABLES = ls dd 
K := $(foreach myTestCommand,$(EXECUTABLES),\
        $(if $(shell ${WHICH} $(myTestCommand) 2>${DEVNUL} ),\
            $(myTestCommand) found,\
            $(error "No $(myTestCommand) in PATH)))
$(info ${K})        
Вит Бернатик
источник
Никогда не думал, что кто-то воспользуется GNU Make с ужасной «оболочкой» cmd.exe ...
Johan
4

Вы можете использовать встроенные команды bash, такие как type fooили command -v foo, как показано ниже:

SHELL := /bin/bash
all: check

check:
        @type foo

Где fooтвоя программа / команда. Перенаправьте на, > /dev/nullесли хотите, чтобы он был тихим.

Kenorb
источник
Да, но я хотел, чтобы он работал, когда у меня была только GNU make, но не было установлено bash.
Проф. Фалькен,
3

Предположим, у вас разные цели и строители, для каждого из которых требуется свой набор инструментов. Составьте список таких инструментов и рассмотрите их как цель для принудительной проверки их доступности.

Например:

make_tools := gcc md5sum gzip

$(make_tools):  
    @which $@ > /dev/null

file.txt.gz: file.txt gzip
    gzip -c file.txt > file.txt.gz 
лканаб
источник
3

Я лично определяю requireцель, которая выполняется раньше всех остальных. Эта цель просто запускает команды версии всех требований по одной и печатает соответствующие сообщения об ошибках, если команда недействительна.

all: require validate test etc

require:
    @echo "Checking the programs required for the build are installed..."
    @shellcheck --version >/dev/null 2>&1 || (echo "ERROR: shellcheck is required."; exit 1)
    @derplerp --version >/dev/null 2>&1 || (echo "ERROR: derplerp is required."; exit 1) 

# And the rest of your makefile below.

Результат нижеприведенного скрипта:

Checking the programs required for the build are installed...
ERROR: derplerp is required.
makefile:X: recipe for target 'prerequisites' failed
make: *** [prerequisites] Error 1
Руди Кершоу
источник
1

Решается путем компиляции специальной небольшой программы в другой целевой файл makefile, единственная цель которого - проверить все, что я искал во время выполнения.

Затем я вызвал эту программу в еще одном целевом файле makefile.

Это было примерно так, если я правильно помню:

real: checker real.c
    cc -o real real.c `./checker`

checker: checker.c
    cc -o checker checker.c
Проф. Фалькен
источник
Спасибо. Но это же прервется, правда? Можно ли предотвратить прерывание работы make? Я пытаюсь пропустить этап сборки, если необходимый инструмент недоступен ..
Марк-Кристиан Шульце,
@ coding.mof отредактировал - это было больше похоже. Программа проверила что-то во время выполнения и предоставила информацию в соответствии с остальной частью компиляции.
Проф. Фалькен
1

Проверка решений для STDERRвывода --versionне работает для программ, которые печатают свою версию STDOUTвместо STDERR. Вместо того, чтобы проверять их вывод в STDERRили STDOUT, проверьте код возврата программы. Если программа не существует, ее код выхода всегда будет отличным от нуля.

#!/usr/bin/make -f
# /programming/7123241/makefile-as-an-executable-script-with-shebang
ECHOCMD:=/bin/echo -e
SHELL := /bin/bash

RESULT := $(shell python --version >/dev/null 2>&1 || (echo "Your command failed with $$?"))

ifeq (,${RESULT})
    EXISTS := true
else
    EXISTS := false
endif

all:
    echo EXISTS: ${EXISTS}
    echo RESULT: ${RESULT}
пользователь
источник