Comment puis-je vérifier si un programme est appelable depuis un Makefile?
(C'est-à-dire que le programme doit exister dans le chemin ou être appelable.)
Il pourrait par exemple être utilisé pour vérifier quel compilateur est installé.
Par exemple. quelque chose comme cette question , mais sans supposer que le shell sous-jacent est compatible POSIX.
Parfois, vous avez besoin d’un Makefile pour pouvoir s’exécuter sur différents systèmes d’exploitation cibles et vous souhaitez que la construction échoue rapidement si un fichier exécutable requis n’est pas dans PATH
plutôt que pendant une période assez longue avant d’échouer.
L’excellente solution proposée par engineerchuan nécessite de créer un target. Cependant, si vous avez plusieurs exécutables à tester et que votre Makefile comporte plusieurs cibles indépendantes, chacune nécessitant des tests, chaque cible requiert alors la cible de test en tant que dépendance. Cela représente beaucoup de temps de frappe et de traitement lorsque vous créez plusieurs cibles à la fois.
La solution fournie par 0xf peut tester un exécutable sans créer de cible. Cela économise beaucoup de temps de frappe et d’exécution lorsque plusieurs cibles peuvent être construites séparément ou ensemble.
Mon amélioration par rapport à cette dernière solution consiste à utiliser l'exécutable which
(where
sous Windows), plutôt que de compter sur la présence d'une option --version
dans chaque exécutable, directement dans la directive GNU Make ifeq
, plutôt que de définir un nouveau variable, et d'utiliser la fonction GNU Make error
pour arrêter la construction si un exécutable requis n'est pas dans ${PATH}
. Par exemple, pour tester l'exécutable lzop
:
ifeq (, $(Shell which lzop))
$(error "No lzop in $(PATH), consider doing apt-get install lzop")
endif
Si vous avez plusieurs exécutables à vérifier, vous pouvez utiliser une fonction foreach
avec l'exécutable which
:
EXECUTABLES = ls dd dudu lxop
K := $(foreach exec,$(EXECUTABLES),\
$(if $(Shell which $(exec)),some string,$(error "No $(exec) in PATH")))
Notez l'utilisation de l'opérateur d'assignation :=
nécessaire pour forcer l'évaluation immédiate de l'expression RHS. Si votre Makefile change la PATH
, alors au lieu de la dernière ligne ci-dessus, vous aurez besoin de:
$(if $(Shell PATH=$(PATH) which $(exec)),some string,$(error "No $(exec) in PATH")))
Cela devrait vous donner une sortie similaire à:
ads$ make
Makefile:5: *** "No dudu in PATH. Stop.
J'ai mélangé les solutions de @kenorb et @ 0xF et j'ai obtenu ceci:
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
Cela fonctionne à merveille car "commande -v" n'imprime rien si l'exécutable n'est pas disponible, aussi la variable DOT n'est jamais définie et vous pouvez simplement la vérifier quand vous le souhaitez dans votre code. Dans cet exemple, une erreur est générée, mais vous pouvez faire quelque chose de plus utile si vous le souhaitez.
Si la variable est disponible, "commande -v" effectue l'opération peu coûteuse d'impression du chemin de commande, définissant la variable DOT.
c'est ce que tu as fait?
check: PYTHON-exists
PYTHON-exists: ; @which python > /dev/null
mytarget: check
.PHONY: check PYTHON-exists
crédit à mon collègue.
Utilisez la fonction Shell
pour appeler votre programme de telle sorte qu'il imprime quelque chose sur la sortie standard. Par exemple, passez --version
.
GNU Make ignore le statut de sortie de la commande transmise à Shell
. Pour éviter le message potentiel "commande introuvable", redirigez l'erreur standard vers /dev/null
.
Ensuite, vous pouvez vérifier le résultat en utilisant ifdef
, ifndef
, $(if)
etc.
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
En prime, la sortie (telle que la version du programme) pourrait être utile dans d'autres parties de votre Makefile.
Ma solution implique un petit script d'aide1 qui place un fichier de drapeau si toutes les commandes requises existent. Cela présente l'avantage que la vérification des commandes requises est effectuée une seule fois et non à chaque invocation 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 Plus d'informations sur la technique command -v
peuvent être trouvées ici .
Certaines des solutions existantes ont été nettoyées ici ...
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 ...)
que vous pouvez exclure si vous souhaitez que cela soit plus silencieux.
Cela va échouer vite . Aucun objectif requis.
Pour moi, toutes les réponses ci-dessus sont basées sur linux et ne fonctionnent pas avec Windows. Je suis nouveau pour faire alors mon approche peut ne pas être idéale. Mais un exemple complet qui fonctionne pour moi sous Linux et Windows est le suivant:
# 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
éventuellement lorsque je dois détecter plus d'outils que je peux utiliser:
EXECUTABLES = ls dd
K := $(foreach myTestCommand,$(EXECUTABLES),\
$(if $(Shell ${WHICH} $(myTestCommand) 2>${DEVNUL} ),\
$(myTestCommand) found,\
$(error "No $(myTestCommand) in PATH)))
$(info ${K})
Vous pouvez utiliser les commandes construites par bash telles que type foo
ou command -v foo
, comme ci-dessous:
Shell := /bin/bash
all: check
check:
@type foo
Où foo
est votre programme/commande. Rediriger vers > /dev/null
si vous le souhaitez en mode silencieux.
Résolu en compilant un petit programme spécial dans une autre cible de makefile, dont le seul but est de vérifier les éléments d'exécution que je cherchais.
Ensuite, j'ai appelé ce programme dans une autre cible de makefile.
C'était quelque chose comme ça si je me souviens bien:
real: checker real.c
cc -o real real.c `./checker`
checker: checker.c
cc -o checker checker.c
Supposons que vous avez des cibles et des générateurs différents, chacun nécessitant un autre ensemble d'outils . Définissez une liste de ces outils et considérez-les comme cible pour forcer la vérification de leur disponibilité
Par exemple:
make_tools := gcc md5sum gzip
$(make_tools):
@which $@ > /dev/null
file.txt.gz: file.txt gzip
gzip -c file.txt > file.txt.gz
Les solutions recherchant la sortie STDERR
de --version
ne fonctionnent pas pour les programmes imprimant leur version sur STDOUT
au lieu de STDERR
. Au lieu de vérifier leur sortie sur STDERR
ou STDOUT
, vérifiez le code de retour du programme. Si le programme n'existe pas, son code de sortie sera toujours différent de zéro.
#!/usr/bin/make -f
# https://stackoverflow.com/questions/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}