Controlla se esiste un programma da un Makefile


115

Come posso verificare se un programma è richiamabile da un Makefile?

(Cioè, il programma dovrebbe esistere nel percorso o altrimenti essere richiamabile.)

Potrebbe essere usato per verificare quale compilatore è installato, ad esempio.

Ad esempio, qualcosa di simile a questa domanda , ma senza presumere che la shell sottostante sia compatibile con POSIX.


1
Puoi invocare una shell compatibile con POSIX?
reinierpost

Probabilmente no, immagino che potrei chiedere che uno sia lì, ma sarebbe molto più facile se non dovessi.
Prof.Falken

Nel frattempo, l'ho risolto aggiungendo un programma al progetto, che viene costruito per primo, e il cui unico scopo è controllare l'altro programma ... :-)
Prof.Falken

2
La soluzione tradizionale consiste nell'avere uno automakescript che controlli i vari prerequisiti e scriva un file Makefile.
tripleee il

Risposte:


84

A volte è necessario un Makefile per essere in grado di funzionare su diversi sistemi operativi di destinazione e si desidera che la compilazione fallisca in anticipo se un eseguibile richiesto non è presente PATHpiuttosto che eseguire per un tempo possibilmente lungo prima di fallire.

L'eccellente soluzione fornita da engineerchuan richiede la creazione di un obiettivo . Tuttavia, se hai molti eseguibili da testare e il tuo Makefile ha molti target indipendenti, ognuno dei quali richiede i test, allora ogni target richiede il target di test come dipendenza. Ciò rende molto più digitazione e tempo di elaborazione quando crei più di un obiettivo alla volta.

La soluzione fornita da 0xf può testare un eseguibile senza creare una destinazione. Ciò consente di risparmiare molto tempo di digitazione e di esecuzione quando ci sono più target che possono essere costruiti separatamente o insieme.

Il mio miglioramento a quest'ultima soluzione consiste nell'usare l' whicheseguibile ( wherein Windows), piuttosto che fare affidamento sul fatto che ci sia --versionun'opzione in ogni eseguibile, direttamente nella ifeqdirettiva GNU Make , piuttosto che definire una nuova variabile e usare GNU Make errorfunzione per interrompere la compilazione se un eseguibile richiesto non è presente ${PATH}. Ad esempio, per verificare l' lzopeseguibile:

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

Se devi controllare diversi eseguibili, potresti voler utilizzare una foreachfunzione con l' whicheseguibile:

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

Notare l'uso :=dell'operatore di assegnazione necessario per forzare la valutazione immediata dell'espressione RHS. Se il tuo Makefile cambia il PATH, allora invece dell'ultima riga sopra avrai bisogno di:

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

Questo dovrebbe darti un output simile a:

ads$ make
Makefile:5: *** "No dudu in PATH.  Stop.

2
Se crei ESECUTABILI tutte le variabili, (ad esempio LS CC LD), e usi $ ($ (exec)), puoi passarle per renderle senza problemi dall'ambiente o da altri makefile. Utile durante la compilazione incrociata.
rickfoosusa

1
Il mio make chokes su "," quando si esegue "ifeq (, $ (shell which lzop))" = /
mentatkgs

2
Su Windows, usa where my_exe 2>NULinvece di which.
Aaron Campbell

2
Attenzione ai rientri TABS con ifeq che è una sintassi delle regole! deve essere SENZA TAB. È stato un periodo difficile con quello. stackoverflow.com/a/21226973/2118777
Pipo

2
Se stai utilizzando Bash, non è necessario effettuare una chiamata esterna a which. Usa invece il built-in command -v. Maggiori info .
kurczynski

48

Ho mescolato le soluzioni di @kenorb e @ 0xF e ho ottenuto questo:

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 

Funziona magnificamente perché "comando -v" non stampa nulla se l'eseguibile non è disponibile, quindi la variabile DOT non viene mai definita e puoi semplicemente controllarla ogni volta che vuoi nel tuo codice. In questo esempio sto lanciando un errore, ma potresti fare qualcosa di più utile se lo desideri.

Se la variabile è disponibile, "comando -v" esegue l'operazione economica di stampare il percorso del comando, definendo la variabile DOT.


5
Almeno in alcuni sistemi (ad esempio Ubuntu 14.04), $(shell command -v dot)fallisce con make: command: Command not found. Per risolvere questo problema, l'output stderr reindirizzamento a / dev / null: $(shell command -v dot 2> /dev/null). Spiegazione
J0HN

3
commandè un bash builtin, per una maggiore portabilità, considerare l'utilizzo which?
Julien Palard

10
@JulienPalard Credo che ti sbagli: l' commandutilità è richiesta da posix (inclusa l' -vopzione) D'altra parte whichnon ha un comportamento standardizzato (che io sappia) ed è a volte del tutto inutilizzabile
Gyom

3
command -vrichiede un ambiente shell simile a POSIX. Questo non funzionerà su Windows senza cygwin o emulazione simile.
dolmen

10
Il motivo per cui commandspesso non funziona non è a causa della sua assenza , ma a causa di una particolare ottimizzazione che fa GNU Make: se un comando è "abbastanza semplice" bypasserà la shell; questo purtroppo significa che i built-in a volte non funzioneranno a meno che non si maneggi un po 'il comando.
Rufflewind

33

è questo quello che hai fatto?

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

credito al mio collega.


No, ho compilato un programma in un target e l'ho eseguito in un altro.
Prof.Falken

21

Usa la shellfunzione per chiamare il tuo programma in modo che stampi qualcosa sullo standard output. Ad esempio, passa --version.

GNU Make ignora lo stato di uscita del comando passato a shell. Per evitare il potenziale messaggio "comando non trovato", reindirizzare l'errore standard a /dev/null.

Poi si può verificare il risultato utilizzando 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

Come bonus, l'output (come la versione del programma) potrebbe essere utile in altre parti del tuo Makefile.


questo dà errore *** missing separator. Stop.Se aggiungo tabulazioni a tutte le righe dopo all:aver ricevuto un erroremake: ifdef: Command not found
Matthias 009

inoltre: ifdefvaluterà true anche se your_programnon esiste gnu.org/software/make/manual/make.html#Conditional-Syntax
Matthias 009

1
Non aggiungere schede per il ifdef, else, endiflinee. Assicurati solo che le @echorighe inizino con le tabulazioni.
0xF

Ho testato la mia soluzione su Windows e Linux. La documentazione che hai collegato afferma che ifdefverifica il valore della variabile non vuota. Se la tua variabile non è vuota, cosa valuta?
0xF

1
@ Matthias009 quando provo GNU Make v4.2.1, usando ifdefo ifndef(come nella risposta accettata) funziona solo se la variabile da valutare è immediatamente set ( :=) invece di pigramente set ( =). Tuttavia, uno svantaggio dell'utilizzo di variabili impostate immediatamente è che vengono valutate al momento della dichiarazione, mentre le variabili impostate pigramente vengono valutate quando vengono chiamate. Ciò significa che eseguirai comandi per variabili con :=anche quando Make esegue solo regole che non usano mai quelle variabili! Puoi evitarlo usando un =conifneq ($(MY_PROG),)
Dennis

9

Ripulito alcune delle soluzioni esistenti qui ...

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

La $(info ...)si può escludere, se si desidera che questo sia più tranquillo.

Questo fallirà velocemente . Nessun obiettivo richiesto.


8

La mia soluzione prevede un piccolo script di supporto 1 che inserisce un file flag se esistono tutti i comandi richiesti. Ciò ha il vantaggio che il controllo dei comandi richiesti viene eseguito solo una volta e non a ogni makechiamata.

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 Ulteriori informazioni sulla command -vtecnica possono essere trovate qui .


2
L'ho appena testato e funziona e poiché non hai fornito alcun suggerimento su ciò che non funziona per te (lo script bash o il codice Makefile) o eventuali messaggi di errore, non posso aiutarti a trovare il problema.
Flusso

Ricevo "fine file inaspettato" nella prima ifistruzione.
Adam Grant,

6

Per me tutte le risposte di cui sopra sono basate su Linux e non funzionano con Windows. Sono nuovo, quindi il mio approccio potrebbe non essere l'ideale. Ma l'esempio completo che funziona per me sia su Linux che su Windows è questo:

# 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

opzionalmente quando ho bisogno di rilevare più strumenti che posso usare:

EXECUTABLES = ls dd 
K := $(foreach myTestCommand,$(EXECUTABLES),\
        $(if $(shell ${WHICH} $(myTestCommand) 2>${DEVNUL} ),\
            $(myTestCommand) found,\
            $(error "No $(myTestCommand) in PATH)))
$(info ${K})        

Non avrei mai pensato che qualcuno avrebbe usato GNU Make con il temuto cmd.exe ... "shell".
Johan Boulé

4

Puoi usare comandi costruiti bash come type fooo command -v foo, come di seguito:

SHELL := /bin/bash
all: check

check:
        @type foo

Dov'è il footuo programma / comando. Reindirizza a > /dev/nullse vuoi che sia silenzioso.


Sì, ma il fatto è che volevo che funzionasse quando avevo solo GNU make ma non era installato bash.
Prof.Falken

3

Supponiamo di avere obiettivi e costruttori diversi, ognuno richiede un altro set di strumenti. Impostare un elenco di tali strumenti e considerarli come obiettivi per forzare il controllo della loro disponibilità

Per esempio:

make_tools := gcc md5sum gzip

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

file.txt.gz: file.txt gzip
    gzip -c file.txt > file.txt.gz 

3

Definisco personalmente un requireobiettivo che corre prima di tutti gli altri. Questa destinazione esegue semplicemente i comandi di versione di tutti i requisiti uno alla volta e stampa i messaggi di errore appropriati se il comando non è valido.

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.

L'output dello script seguente è

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

Risolto compilando un programmino speciale in un altro target di makefile, il cui unico scopo è quello di controllare qualsiasi cosa di runtime stavo cercando.

Quindi, ho chiamato questo programma in un altro target del makefile.

Se ricordo bene, era qualcosa del genere:

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

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

Grazie. Ma questo farebbe abortire, giusto? È possibile evitare che la marca si interrompa? Sto cercando di saltare una fase di creazione se lo strumento richiesto non è disponibile ..
Marc-Christian Schulze

@ coding.mof modificato - era più così. Il programma ha controllato qualcosa nel runtime e ha fornito le informazioni di conseguenza al resto della compilazione.
Prof. Falken

1

Le soluzioni che controllano l' STDERRoutput di --versionnon funzionano per i programmi che stampano la loro versione STDOUTinvece di STDERR. Invece di controllare il loro output su STDERRo STDOUT, controlla il codice di ritorno del programma. Se il programma non esiste, il suo codice di uscita sarà sempre diverso da zero.

#!/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}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.