Compruebe si existe un programa a partir de un Makefile

115

¿Cómo puedo comprobar si se puede llamar a un programa desde un Makefile?

(Es decir, el programa debe existir en la ruta o ser invocable).

Podría usarse para verificar qué compilador está instalado, por ejemplo.

Por ejemplo, algo como esta pregunta , pero sin asumir que el shell subyacente es compatible con POSIX.

Prof. Falken
fuente
1
¿Puede invocar un shell compatible con POSIX?
reinierpost
Probablemente no, supongo que podría exigir que uno esté allí, pero sería mucho más fácil si no tuviera que hacerlo.
Prof. Falken
Mientras tanto, lo resolví agregando un programa al proyecto, que se construye primero, y cuyo único propósito es verificar ese otro programa ... :-)
Prof. Falken
2
La solución tradicional es tener un automakescript que verifique varios requisitos previos y escriba un archivo Makefile.
tripleee

Respuestas:

83

A veces, necesita un archivo Makefile para poder ejecutarse en diferentes sistemas operativos de destino y desea que la compilación falle antes si un ejecutable requerido no está en PATHlugar de ejecutarse durante un tiempo posiblemente largo antes de fallar.

La excelente solución proporcionada por Engineerchuan requiere hacer un objetivo . Sin embargo, si tiene muchos ejecutables para probar y su Makefile tiene muchos objetivos independientes, cada uno de los cuales requiere las pruebas, entonces cada objetivo requiere el objetivo de prueba como una dependencia. Eso genera una gran cantidad de escritura adicional, así como tiempo de procesamiento cuando crea más de un objetivo a la vez.

La solución proporcionada por 0xf puede probar un ejecutable sin hacer un objetivo. Eso ahorra mucho tiempo de escritura y ejecución cuando hay varios objetivos que se pueden construir por separado o juntos.

Mi mejora con respecto a la última solución es usar el whichejecutable ( whereen Windows), en lugar de confiar en que haya una --versionopción en cada ejecutable, directamente en la ifeqdirectiva GNU Make , en lugar de definir una nueva variable y usar GNU Make errorfunción para detener la compilación si un ejecutable requerido no está en ${PATH}. Por ejemplo, para probar el lzopejecutable:

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

Si tiene varios ejecutables para verificar, entonces es posible que desee usar una foreachfunción con el whichejecutable:

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

Tenga en cuenta el uso del :=operador de asignación que se requiere para forzar la evaluación inmediata de la expresión RHS. Si su Makefile cambia PATH, entonces en lugar de la última línea de arriba necesitará:

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

Esto debería darle un resultado similar a:

ads$ make
Makefile:5: *** "No dudu in PATH.  Stop.
Jonathan Ben-Avraham
fuente
2
Si crea EJECUTABLES todas las variables, (es decir, LS CC LD), y usa $ ($ (exec)), puede pasarlas para hacerlas sin problemas desde el entorno u otros archivos MAKE. Útil en la compilación cruzada.
rickfoosusa
1
Mi make se ahoga en "," al ejecutar "ifeq (, $ (shell which lzop))" = /
mentatkgs
2
En Windows, use en where my_exe 2>NULlugar de which.
Aaron Campbell
2
¡Tenga cuidado con las sangrías TABS con ifeq, que es una sintaxis de regla! debe ser SIN TAB. Lo pasé mal con eso. stackoverflow.com/a/21226973/2118777
Pipo
2
Si está utilizando Bash, no es necesario realizar una llamada externa a which. Utilice el integrado en su command -vlugar. Más info .
kurczynski
48

Mezclé las soluciones de @kenorb y @ 0xF y obtuve esto:

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 

Funciona de maravilla porque "command -v" no imprime nada si el ejecutable no está disponible, por lo que la variable DOT nunca se define y puede verificarla cuando lo desee en su código. En este ejemplo, arrojo un error, pero podría hacer algo más útil si quisiera.

Si la variable está disponible, "comando -v" realiza la operación económica de imprimir la ruta del comando, definiendo la variable DOT.

mentatkgs
fuente
5
Al menos en algunos sistemas (es decir, Ubuntu 14.04), $(shell command -v dot)falla con make: command: Command not found. Para solucionar este problema, la salida stderr redirección a / dev / null: $(shell command -v dot 2> /dev/null). Explicación
J0HN
3
commandes un bash incorporado, para mayor portabilidad, considere usar which?
Julien Palard
10
@JulienPalard Creo que está equivocado: posixcommand requiere la utilidad (incluida la opción) Por otro lado, no tiene un comportamiento estandarizado (que yo sepa) y, a veces, es completamente inutilizable-vwhich
Gyom
3
command -vrequiere un entorno de shell similar a POSIX. Esto no funcionará en Windows sin cygwin o una emulación similar.
dolmen
10
La razón por la que a commandmenudo no funciona no es por su ausencia , sino por una optimización peculiar que hace GNU Make: si un comando es "suficientemente simple", saltará el shell; desafortunadamente, esto significa que los elementos integrados a veces no funcionarán a menos que modifique un poco el comando.
Rufflewind
33

¿Es esto lo que hiciste?

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

crédito a mi compañero de trabajo.

ingeniero
fuente
No, compilé un programa en un objetivo y lo ejecuté en otro.
Prof. Falken
21

Utilice la shellfunción para llamar a su programa de manera que imprima algo en la salida estándar. Por ejemplo, pase --version.

GNU Make ignora el estado de salida del comando pasado shell. Para evitar el mensaje potencial "comando no encontrado", redirija el error estándar a /dev/null.

A continuación puede consultar el resultado utilizando 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

Como beneficio adicional, la salida (como la versión del programa) puede ser útil en otras partes de su Makefile.

0xF
fuente
esto da un error *** missing separator. Stop.Si agrego pestañas en todas las líneas después de all:recibir el errormake: ifdef: Command not found
Matthias 009
también: ifdefevaluará verdadero incluso si your_programno existe gnu.org/software/make/manual/make.html#Conditional-Syntax
Matthias 009
1
No añadir pestañas a la ifdef, else, endiflíneas. Solo asegúrate de que las @echolíneas comiencen con tabulaciones.
0xF
Probé mi solución en Windows y Linux. La documentación que vinculó indica que ifdefverifica el valor de la variable no vacía. Si su variable no está vacía, ¿a qué se evalúa?
0xF
1
@ Matthias009 cuando pruebo GNU Make v4.2.1, usar ifdefo ifndef(como en la respuesta aceptada) solo funciona si la variable que se evalúa se establece inmediatamente ( :=) en lugar de establecer perezosamente ( =). Sin embargo, una desventaja de usar variables establecidas inmediatamente es que se evalúan en el momento de la declaración, mientras que las variables establecidas de forma perezosa se evalúan cuando se llaman. Esto significa que estaría ejecutando comandos para variables con :=incluso cuando Make solo ejecuta reglas que nunca usan esas variables. Puede evitar esto usando un =conifneq ($(MY_PROG),)
Dennis
9

Limpió algunas de las soluciones existentes aquí ...

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)`)))

El $(info ...)puede excluir si quieres que esto sea más silencioso.

Esto fallará rápidamente . No se requiere objetivo.

mpen
fuente
8

Mi solución implica un pequeño script auxiliar 1 que coloca un archivo de bandera si existen todos los comandos necesarios. Esto tiene la ventaja de que la verificación de los comandos requeridos solo se realiza una vez y no en cada makeinvocación.

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

1command -v Puede encontrar más información sobre la técnica aquí .

Fluir
fuente
2
Lo acabo de probar y funciona y, dado que no proporcionó ninguna pista sobre lo que no funciona para usted (el script bash o el código Makefile) o cualquier mensaje de error, no puedo ayudarlo a encontrar el problema.
Flujo
Recibo un "final inesperado del archivo" en la primera ifdeclaración.
Adam Grant
6

Para mí, todas las respuestas anteriores se basan en Linux y no funcionan con Windows. Soy nuevo en hacer, por lo que mi enfoque puede no ser el ideal. Pero el ejemplo completo que funciona para mí tanto en Linux como en Windows es este:

# 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

opcionalmente, cuando necesito detectar más herramientas que puedo usar:

EXECUTABLES = ls dd 
K := $(foreach myTestCommand,$(EXECUTABLES),\
        $(if $(shell ${WHICH} $(myTestCommand) 2>${DEVNUL} ),\
            $(myTestCommand) found,\
            $(error "No $(myTestCommand) in PATH)))
$(info ${K})        
Vit Bernatik
fuente
Nunca pensé que alguien usaría GNU Make con el temido cmd.exe ... "shell".
Johan Boulé
4

Puede usar comandos integrados en bash como type fooo command -v foo, como se muestra a continuación:

SHELL := /bin/bash
all: check

check:
        @type foo

¿Dónde fooestá tu programa / comando? Redirigir a > /dev/nullsi lo desea en silencio.

Kenorb
fuente
Sí, pero la cosa es que quería que funcionara cuando solo tenía GNU make pero no bash instalado.
Prof. Falken
3

Suponga que tiene diferentes objetivos y constructores, cada uno requiere otro conjunto de herramientas. Establezca una lista de tales herramientas y considérelas como objetivo para forzar la verificación de su disponibilidad.

Por ejemplo:

make_tools := gcc md5sum gzip

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

file.txt.gz: file.txt gzip
    gzip -c file.txt > file.txt.gz 
lkanab
fuente
3

Personalmente estoy definiendo un requireobjetivo que se ejecuta antes que todos los demás. Este objetivo simplemente ejecuta los comandos de versión de todos los requisitos uno a la vez e imprime los mensajes de error apropiados si el comando no es válido.

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 salida del siguiente script es

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

Resuelto compilando un pequeño programa especial en otro destino de archivo MAKE, cuyo único propósito es verificar cualquier material de tiempo de ejecución que esté buscando.

Luego, llamé a este programa en otro destino de archivo MAKE.

Era algo como esto si no recuerdo mal:

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

checker: checker.c
    cc -o checker checker.c
Prof. Falken
fuente
Gracias. Pero esto abortaría, ¿verdad? ¿Es posible evitar que make aborte? Estoy tratando de omitir un paso de compilación si la herramienta requerida no está disponible ..
Marc-Christian Schulze
@ coding.mof editado - era más así. El programa verificó algo en el tiempo de ejecución y proporcionó información de acuerdo con el resto de la compilación.
Prof. Falken
1

Las soluciones que comprueban la STDERRsalida de --versionno funcionan para programas que imprimen su versión en STDOUTlugar de STDERR. En lugar de verificar su salida a STDERRo STDOUT, verifique el código de retorno del programa. Si el programa no existe, su código de salida siempre será distinto de cero.

#!/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}
usuario
fuente