Vérifier si un programme existe à partir d'un Makefile

115

Comment puis-je vérifier si un programme peut être appelé à partir d'un Makefile?

(Autrement dit, le programme doit exister dans le chemin ou être appelable d'une autre manière.)

Il pourrait être utilisé pour vérifier pour quel compilateur est installé, par exemple.

Par exemple, quelque chose comme cette question , mais sans supposer que le shell sous-jacent est compatible POSIX.

Prof. Falken
la source
1
Pouvez-vous invoquer un shell compatible POSIX?
reinierpost
Probablement pas, je suppose que je pourrais en demander un, mais ce serait beaucoup plus facile si je n'avais pas à le faire.
Prof. Falken
En attendant, je l'ai résolu en ajoutant un programme au projet, qui est construit en premier, et dont le seul but est de vérifier cet autre programme ... :-)
Prof. Falken
2
La solution de contournement traditionnelle consiste à avoir un automakescript qui vérifie les différents prérequis et écrit un fichier approprié Makefile.
tripleee

Réponses:

84

Parfois, vous avez besoin d'un Makefile pour pouvoir s'exécuter sur différents OS cibles et vous voulez que la construction échoue tôt si un exécutable requis n'est pas dans PATHplutôt que de s'exécuter pendant une période éventuellement longue avant d'échouer.

L'excellente solution fournie par engineerchuan nécessite de faire une cible . Cependant, si vous avez de nombreux exécutables à tester et que votre Makefile a de nombreuses cibles indépendantes, dont chacune nécessite les tests, alors chaque cible nécessite la cible de test en tant que dépendance. Cela entraîne beaucoup de frappe supplémentaire ainsi que du temps de traitement lorsque vous créez plus d'une cible à la fois.

La solution fournie par 0xf peut tester un exécutable sans faire de cible. Cela économise beaucoup de temps de frappe et d'exécution lorsqu'il y a plusieurs cibles qui peuvent être construites séparément ou ensemble.

Mon amélioration de cette dernière solution est d'utiliser l' whichexécutable ( wheresous Windows), plutôt que de compter sur l'existence d'une --versionoption dans chaque exécutable, directement dans la ifeqdirective GNU Make , plutôt que de définir une nouvelle variable, et d'utiliser le GNU Make errorpour arrêter la construction si un exécutable requis n'est pas dans ${PATH}. Par exemple, pour tester l' lzopexécutable:

 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 foreachfonction avec l' whichexécutable:

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'affectation qui est requis pour forcer l'évaluation immédiate de l'expression RHS. Si votre Makefile change le 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.
Jonathan Ben-Avraham
la source
2
Si vous créez des EXECUTABLES toutes les variables, (ie LS CC LD), et utilisez $ ($ (exec)), vous pouvez les passer pour créer de manière transparente à partir de l'environnement ou d'autres makefiles. Utile lors de la compilation croisée.
rickfoosusa
1
Mon make s'étouffe au "," lors de l'exécution de "ifeq (, $ (shell which lzop))" = /
mentatkgs
2
Sous Windows, utilisez à la where my_exe 2>NULplace de which.
Aaron Campbell
2
Attention aux indentations TABS avec ifeq qui est une syntaxe de règle! il doit être SANS TAB. J'ai eu du mal avec ça. stackoverflow.com/a/21226973/2118777
Pipo
2
Si vous utilisez Bash, il n'est pas nécessaire de passer un appel externe vers which. Utilisez command -vplutôt la fonction intégrée . Plus d'infos .
kurczynski
48

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'affiche rien si l'exécutable n'est pas disponible, donc 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, je lance une erreur, 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.

mentatkgs
la source
5
Au moins dans certains systèmes (par exemple Ubuntu 14.04), $(shell command -v dot)échoue avec make: command: Command not found. Pour résoudre ce problème, redirect stderr vers / dev / null: $(shell command -v dot 2> /dev/null). Explication
J0HN
3
commandest un bash intégré, pour plus de portabilité, envisagez d'utiliser which?
Julien Palard
10
@JulienPalard Je crois que vous vous trompez: l' commandutilitaire est requis par posix (y compris l' -voption) Par contre whichn'a pas de comportement standardisé (que je connaisse) et est parfois carrément inutilisable
Gyom
3
command -vnécessite un environnement shell de type POSIX. Cela ne fonctionnera pas sous Windows sans cygwin ou une émulation similaire.
dolmen
10
La raison pour laquelle commandsouvent ne fonctionne pas n'est pas à cause de son absence , mais à cause d'une optimisation particulière que fait GNU Make: si une commande est "assez simple", elle contournera le shell; cela signifie malheureusement que les fonctions intégrées ne fonctionneront parfois pas à moins que vous ne modifiiez un peu la commande.
Rufflewind
33

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.

ingénieur
la source
Non, j'ai compilé un programme dans une cible et je l'ai exécuté dans une autre.
Prof. Falken
21

Utilisez la shellfonction pour appeler votre programme de manière à ce qu'il imprime quelque chose sur la sortie standard. Par exemple, passez--version .

GNU Make ignore l'état de sortie de la commande passée à 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) peut être utile dans d'autres parties de votre Makefile.

0xF
la source
cela donne une erreur *** missing separator. Stop.Si j'ajoute des onglets à toutes les lignes après all:une erreurmake: ifdef: Command not found
Matthias 009
aussi: ifdefévaluera vrai même s'il your_programn'existe pas gnu.org/software/make/manual/make.html#Conditional-Syntax
Matthias 009
1
Ne pas ajouter des onglets à ifdef, else, endiflignes. Assurez-vous simplement que les @echolignes commencent par des onglets.
0xF
J'ai testé ma solution sous Windows et Linux. La documentation que vous avez liée indique que la ifdefvaleur de la variable n'est pas vide. Si votre variable n'est pas vide, à quoi s'évalue-t-elle?
0xF
1
@ Matthias009 lorsque je teste GNU Make v4.2.1, l'utilisation de ifdefou ifndef(comme dans la réponse acceptée) ne fonctionne que si la variable évaluée est immédiatement set ( :=) au lieu de paresseusement set ( =). Cependant, un inconvénient de l'utilisation de variables définies immédiatement est qu'elles sont évaluées au moment de la déclaration, tandis que les variables définies paresseusement sont évaluées lorsqu'elles sont appelées. Cela signifie que vous exécuteriez des commandes pour des variables :=même si Make n'exécute que des règles qui n'utilisent jamais ces variables! Vous pouvez éviter cela en utilisant un =avecifneq ($(MY_PROG),)
Dennis
9

Nettoyé certaines des solutions existantes 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)`)))

le $(info ...) vous pouvez exclure si vous voulez que ce soit plus calme.

Cela échouera rapidement . Aucune cible requise.

mpen
la source
8

Ma solution implique un petit script d'aide 1 qui place un fichier indicateur si toutes les commandes requises existent. Cela présente l'avantage que la vérification des commandes requises n'est effectuée qu'une seule fois et non toutes lesmake appel.

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 Pour en savoir plus sur la command -vtechnique, cliquez ici .

Couler
la source
2
Je viens de le tester et cela fonctionne et comme vous n'avez fourni aucune indication sur ce qui ne fonctionne pas pour vous (le script bash ou le code Makefile) ou sur les messages d'erreur, je ne peux pas vous aider à trouver le problème.
Débit
J'obtiens une "fin de fichier inattendue" sur la première ifinstruction.
Adam Grant
6

Pour moi, toutes les réponses ci-dessus sont basées sur Linux et ne fonctionnent pas avec Windows. Je suis nouveau à faire, donc mon approche n'est peut-être pas idéale. Mais un exemple complet qui fonctionne pour moi à la fois 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 j'ai besoin de 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})        
Vit Bernatik
la source
Je n'ai jamais pensé que quelqu'un utiliserait GNU Make avec le redoutable cmd.exe ... "shell".
Johan Boulé
4

Vous pouvez utiliser des commandes intégrées bash telles que type fooou command -v foo, comme ci-dessous:

SHELL := /bin/bash
all: check

check:
        @type foo

fooest votre programme / commande. Redirigez vers > /dev/nullsi vous le souhaitez silencieux.

Kenorb
la source
Oui, mais le fait est que je voulais que cela fonctionne alors que je n'avais que GNU make mais pas de bash installé.
Prof. Falken
3

Supposons que vous ayez des cibles et des constructeurs différents, chacun nécessitant un autre ensemble d'outils. Définissez une liste de ces outils et considérez-les comme une 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 
lkanab
la source
3

Je définis personnellement un requireobjectif qui passe avant tous les autres. Cette cible exécute simplement les commandes de version de toutes les exigences une par une et imprime les messages d'erreur appropriés si la commande n'est pas valide.

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.

La sortie du script ci-dessous est

Checking the programs required for the build are installed...
ERROR: derplerp is required.
makefile:X: recipe for target 'prerequisites' failed
make: *** [prerequisites] Error 1
Rudi Kershaw
la source
1

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 recherchais.

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
Prof. Falken
la source
Je vous remercie. Mais cela ferait échouer, non? Est-il possible d'empêcher l'abandon de make? J'essaye de sauter une étape de construction si l'outil requis n'est pas disponible.
Marc-Christian Schulze
@ coding.mof édité - c'était plus comme ça. Le programme a vérifié quelque chose dans le runtime et a donné des informations en conséquence au reste de la compilation.
Prof. Falken
1

Les solutions vérifiant la STDERRsortie de --versionne fonctionnent pas pour les programmes qui impriment leur version à la STDOUTplace de STDERR. Au lieu de vérifier leur sortie vers STDERRou 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
# /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}
utilisateur
la source