web-dev-qa-db-fra.com

Vérifier si un programme existe depuis un Makefile

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.

70
Prof. Falken

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

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.

36
mentatkgs

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.

31
engineerchuan

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.

17
0xF

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 .

8
Flow

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.

7
mpen

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})        
4
Vit Bernatik

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

foo est votre programme/commande. Rediriger vers > /dev/null si vous le souhaitez en mode silencieux.

2
kenorb

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
1
Prof. Falken

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 
1
lkanab

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}
1
user