Перевірте, чи існує програма з Makefile


115

Як я можу перевірити, чи можна програму викликати з Makefile?

(Тобто програма повинна існувати на шляху або іншим чином може бути викликана.)

Він може використовуватися, наприклад, для перевірки встановлення компілятора.

Наприклад, щось подібне до цього питання , але не припускаючи, що основна оболонка сумісна з POSIX.


1
Чи можете ви викликати сумісну з POSIX оболонку?
reinierpost

Напевно, ні, я думаю, я міг би вимагати, щоб хтось там був, але було б набагато простіше, якби я цього не зробив.
Проф. Фолкен

Тим часом я вирішив це, додавши програму до проекту, який будується першим, і єдиною метою якого є перевірка на цю іншу програму ... :-)
Проф. Фолкен

2
Традиційним рішенням є створення automakeсценарію, який перевіряє різні передумови та виписує відповідне Makefile.
трійка

Відповіді:


83

Іноді вам потрібен 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)), ви можете передавати їх, щоб зробити це безперешкодно з оточення або інших файлів. Корисно під час перехресного складання.
rickfoosusa

1
Моя задихається на "," під час запуску "ifeq (, $ (оболонка, яка lzop))" = /
mentatkgs

2
У Windows використовуйте where my_exe 2>NULзамість which.
Аарон Кемпбелл

2
Остерігайтеся відступів TABS за допомогою ifeq, який є синтаксисом правил! вона повинна бути БЕЗ ТАБЛИЦІ. Мені було важко з цим. stackoverflow.com/a/21226973/2118777
Піпо

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 

Це прекрасно працює, оскільки "command -v" нічого не друкує, якщо виконуваний файл недоступний, тому змінна DOT ніколи не визначається, і ви можете просто перевірити її коли завгодно у своєму коді. У цьому прикладі я накидаю помилку, але ви могли б зробити щось більш корисне, якби хотіли.

Якщо змінна доступна, "команда -v" виконує недорогу операцію друку командного шляху, визначаючи змінну DOT.


5
Принаймні, в деяких системах (наприклад, Ubuntu 14.04) $(shell command -v dot)не вдається make: command: Command not found. Щоб виправити це, переспрямуйте висновок stderr на / dev / null : $(shell command -v dot 2> /dev/null). Пояснення
J0HN

3
commandце bash вбудований, для більшої портативності, подумайте про використання which?
Жульєн Палард

10
@JulienPalard Я вірю , що ти помилився: commandутиліта не вимагає POSIX (включаючи -vваріант) З іншого боку , whichвже не стандартизовані bahaviour (що я знаю) і іноді прямо непридатною
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.


це дає помилку *** 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(як у прийнятій відповіді), працює лише тоді, коли змінна, що оцінюється, негайно встановлюється ( :=) замість ліниво встановленого ( =). Однак недоліком використання негайно встановлених змінних є те, що вони оцінюються під час оголошення, тоді як ліниво задані змінні оцінюються при їх виклику. Це означає, що ви виконуєте команди для змінних, :=навіть коли Make працює лише правилами, які ніколи не використовують ці змінні! Ви можете уникнути цього, скориставшись =сifneq ($(MY_PROG),)
Dennis

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 ...)Можна виключити , якщо ви хочете , щоб це було спокійніше.

Це швидко вийде з ладу . Не потрібна ціль.


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

Makefilefile

.cmd_ok:
    ./check_cmds.sh

build: .cmd_ok target1 target2

1 Більше про command -vтехніку можна прочитати тут .


2
Я просто перевірив це, і він працює, і оскільки ви не надали підказки про те, що для вас не працює (скрипт bash або код Makefile), або будь-які повідомлення про помилки, я не можу допомогти вам знайти проблему.
Потік

Я отримую "несподіваний кінець файлу" на першому ifтвердженні.
Адам Грант

6

Для мене всі наведені вище відповіді базуються на Linux і не працюють з Windows. Я новий, щоб зробити так, що мій підхід може бути не ідеальним. Але повний приклад, який працює для мене як на 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 ... "оболонкою".
Йохан Буле

4

Ви можете використовувати вбудовані команди bash, такі як type fooабо command -v foo, як зазначено нижче:

SHELL := /bin/bash
all: check

check:
        @type foo

Де fooваша програма / команда. Перенаправляйте, > /dev/nullякщо ви хочете, щоб це мовчало.


Так, але річ у тому, що я хотів, щоб це спрацювало, коли я мав тільки 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.

Це було щось подібне, якщо я пригадую правильно:

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

checker: checker.c
    cc -o checker checker.c

Дякую. Але це скасує, чи не так? Чи можна запобігти зробити аборт? Я намагаюся пропустити крок збирання, якщо потрібний інструмент недоступний ..
Marc-Christian Schulze

@ 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}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.