Überprüfen Sie, ob ein Programm aus einem Makefile vorhanden ist

115

Wie kann ich überprüfen, ob ein Programm von einem Makefile aus aufgerufen werden kann?

(Das heißt, das Programm sollte im Pfad vorhanden sein oder auf andere Weise aufgerufen werden können.)

Hiermit kann beispielsweise überprüft werden, welcher Compiler installiert ist.

ZB so etwas wie diese Frage , aber ohne anzunehmen, dass die zugrunde liegende Shell POSIX-kompatibel ist.

Prof. Falken
quelle
1
Können Sie eine POSIX-kompatible Shell aufrufen ?
Reinierpost
Wahrscheinlich nicht, ich denke, ich könnte verlangen, dass einer da ist, aber es wäre viel einfacher, wenn ich nicht müsste.
Prof. Falken
In der Zwischenzeit habe ich es gelöst, indem ich dem zuerst erstellten Projekt ein Programm hinzugefügt habe, dessen einziger Zweck darin besteht, nach diesem anderen Programm zu suchen ... :-)
Prof. Falken
2
Die traditionelle Problemumgehung besteht darin, ein automakeSkript zu haben, das verschiedene Voraussetzungen überprüft und ein geeignetes Skript ausgibt Makefile.
Tripleee

Antworten:

83

Manchmal benötigen Sie ein Makefile, um auf verschiedenen Zielbetriebssystemen ausgeführt werden zu können, und Sie möchten, dass der Build frühzeitig fehlschlägt, wenn eine erforderliche ausführbare Datei nicht vorhanden ist, PATHanstatt möglicherweise lange vor dem Ausfall ausgeführt zu werden.

Die hervorragende Lösung von Engineerchuan erfordert die Festlegung eines Ziels . Wenn Sie jedoch viele ausführbare Dateien testen müssen und Ihr Makefile viele unabhängige Ziele hat, für die jeweils Tests erforderlich sind, benötigt jedes Ziel das Testziel als Abhängigkeit. Dies bedeutet viel zusätzliche Eingabe- und Verarbeitungszeit, wenn Sie mehr als ein Ziel gleichzeitig erstellen.

Die von 0xf bereitgestellte Lösung kann auf eine ausführbare Datei testen, ohne ein Ziel zu erstellen . Dies spart viel Tipp- und Ausführungszeit, wenn mehrere Ziele separat oder zusammen erstellt werden können.

Meine Verbesserung gegenüber der letztgenannten Lösung besteht darin, die whichausführbare Datei ( whereunter Windows) zu verwenden, anstatt sich darauf zu verlassen, dass --versionin jeder ausführbaren Datei direkt in der GNU Make- ifeqDirektive eine Option vorhanden ist , anstatt eine neue Variable zu definieren und die GNU Make zu verwenden errorFunktion zum Stoppen des Builds, wenn eine erforderliche ausführbare Datei nicht vorhanden ist ${PATH}. Zum Testen der lzopausführbaren Datei:

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

Wenn Sie mehrere ausführbare Dateien überprüfen müssen, möchten Sie möglicherweise eine foreachFunktion mit der whichausführbaren Datei verwenden:

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

Beachten Sie die Verwendung des :=Zuweisungsoperators, der erforderlich ist, um eine sofortige Auswertung des RHS-Ausdrucks zu erzwingen. Wenn Ihr Makefile das ändert PATH, benötigen Sie anstelle der letzten Zeile oben:

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

Dies sollte eine Ausgabe ähnlich der folgenden ergeben:

ads$ make
Makefile:5: *** "No dudu in PATH.  Stop.
Jonathan Ben-Avraham
quelle
2
Wenn Sie EXECUTABLES für alle Variablen (z. B. LS CC LD) erstellen und $ ($ (exec)) verwenden, können Sie diese übergeben, um sie nahtlos aus der Umgebung oder anderen Makefiles zu erstellen. Nützlich beim Cross-Compilieren.
Rickfoosusa
1
Ich mache Drosseln am ",", wenn "ifeq (, $ (shell which lzop))" = /
mentatkgs
2
Verwenden Sie unter Windows where my_exe 2>NULanstelle von which.
Aaron Campbell
2
Achten Sie auf TABS-Einrückungen mit ifeq, einer Regelsyntax! es muss OHNE TAB sein. Hatte es schwer damit. stackoverflow.com/a/21226973/2118777
Pipo
2
Wenn Sie verwenden Bash, müssen Sie keinen externen Anruf tätigen which. Verwenden Sie command -vstattdessen die integrierte Funktion. Weitere Infos .
Kurczynski
48

Ich habe die Lösungen von @kenorb und @ 0xF gemischt und Folgendes erhalten:

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 

Es funktioniert wunderbar, weil "Befehl -v" nichts druckt, wenn die ausführbare Datei nicht verfügbar ist, sodass die Variable DOT nie definiert wird und Sie sie einfach überprüfen können, wann immer Sie möchten. In diesem Beispiel wird ein Fehler ausgegeben, aber Sie könnten etwas Nützlicheres tun, wenn Sie möchten.

Wenn die Variable verfügbar ist, führt "Befehl -v" die kostengünstige Operation zum Drucken des Befehlspfads aus und definiert die DOT-Variable.

mentatkgs
quelle
5
Zumindest in einigen Systemen (dh Ubuntu 14.04) $(shell command -v dot)scheitert mit make: command: Command not found. Um dies zu beheben, leiten Sie die stderr-Ausgabe nach / dev / null um : $(shell command -v dot 2> /dev/null). Erklärung
J0HN
3
commandIst eine Bash-Funktion für mehr Portabilität in Betracht gezogen which?
Julien Palard
10
@ JulienPalard Ich glaube, Sie irren sich: Das commandDienstprogramm wird von posix (einschließlich der -vOption) benötigt. Andererseits whichhat es kein standardisiertes Verhalten (das ich kenne) und ist manchmal völlig unbrauchbar
Gyom
3
command -verfordert eine POSIX-ähnliche Shell-Umgebung. Dies funktioniert unter Windows ohne Cygwin oder eine ähnliche Emulation nicht.
Dolmen
10
Der Grund, warum commandoft nicht funktioniert, liegt nicht in seiner Abwesenheit , sondern in einer besonderen Optimierung , die GNU Make vornimmt: Wenn ein Befehl "einfach genug" ist, umgeht er die Shell; Dies bedeutet leider, dass integrierte Funktionen manchmal nur funktionieren, wenn Sie den Befehl ein wenig entstellen.
Rufflewind
33

hast du das getan

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

Gutschrift an meinen Kollegen.

Ingenieurchuan
quelle
Nein, ich habe ein Programm in einem Ziel kompiliert und in einem anderen ausgeführt.
Prof. Falken
21

Verwenden Sie die shellFunktion, um Ihr Programm so aufzurufen, dass es etwas in die Standardausgabe druckt. Zum Beispiel bestehen --version.

GNU Make ignoriert den Exit-Status des an übergebenen Befehls shell. Um die mögliche Meldung "Befehl nicht gefunden" zu vermeiden, leiten Sie den Standardfehler an um /dev/null.

Dann können Sie das Ergebnis überprüfen verwenden ifdef, ifndef, $(if)usw.

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

Als Bonus kann die Ausgabe (z. B. die Programmversion) in anderen Teilen Ihres Makefiles nützlich sein.

0xF
quelle
Dies gibt Fehler *** missing separator. Stop.Wenn ich Tabulatoren in allen Zeilen hinzufüge, nachdem all:ich Fehler erhaltemake: ifdef: Command not found
Matthias 009
auch: ifdefwird wahr auswerten, auch wenn your_programes nicht existiert gnu.org/software/make/manual/make.html#Conditional-Syntax
Matthias 009
1
Fügen Sie keine Tabs die ifdef, else, endifLinien. Stellen Sie einfach sicher, dass die @echoZeilen mit Tabulatoren beginnen.
0xF
Ich habe meine Lösung unter Windows und Linux getestet. In der von Ihnen verknüpften Dokumentation wird angegeben, dass ifdefnach nicht leeren Variablenwerten gesucht wird. Wenn Ihre Variable nicht leer ist, wie wird sie ausgewertet?
0xF
1
@ Matthias009 Wenn ich GNU Make v4.2.1 teste, funktioniert die Verwendung von ifdefoder ifndef(wie in der akzeptierten Antwort) nur, wenn die auszuwertende Variable sofort gesetzt wird ( :=) anstatt träge gesetzt wird ( =). Ein Nachteil der Verwendung von sofort gesetzten Variablen besteht jedoch darin, dass sie zur Deklarationszeit ausgewertet werden, während träge gesetzte Variablen beim Aufruf ausgewertet werden. Dies bedeutet, dass Sie Befehle für Variablen ausführen würden, :=selbst wenn Make nur Regeln ausführt , die diese Variablen niemals verwenden! Sie können dies vermeiden, indem Sie ein =withifneq ($(MY_PROG),)
Dennis
9

Einige der vorhandenen Lösungen hier wurden bereinigt ...

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

Das $(info ...)können Sie ausschließen , wenn Sie diese leiser sein wollen.

Dies wird schnell fehlschlagen . Kein Ziel erforderlich.

mpen
quelle
8

Meine Lösung beinhaltet ein kleines Hilfsskript 1 , das eine Flag-Datei platziert, wenn alle erforderlichen Befehle vorhanden sind. Dies hat den Vorteil, dass die Überprüfung auf die erforderlichen Befehle nur einmal und nicht bei jedem makeAufruf erfolgt.

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 Mehr zur command -vTechnik finden Sie hier .

Fließen
quelle
2
Ich habe es gerade getestet und es funktioniert und da Sie keinen Hinweis darauf gegeben haben, was für Sie nicht funktioniert (das Bash-Skript oder der Makefile-Code) oder Fehlermeldungen, kann ich Ihnen nicht helfen, das Problem zu finden.
Flow
Ich erhalte bei der ersten ifAnweisung ein "unerwartetes Dateiende" .
Adam Grant
6

Für mich basieren alle oben genannten Antworten auf Linux und funktionieren nicht mit Windows. Ich bin neu in der Entwicklung, daher ist mein Ansatz möglicherweise nicht ideal. Das vollständige Beispiel, das für mich sowohl unter Linux als auch unter Windows funktioniert, ist das folgende:

# 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

Optional kann ich Folgendes verwenden, wenn ich weitere Tools erkennen muss:

EXECUTABLES = ls dd 
K := $(foreach myTestCommand,$(EXECUTABLES),\
        $(if $(shell ${WHICH} $(myTestCommand) 2>${DEVNUL} ),\
            $(myTestCommand) found,\
            $(error "No $(myTestCommand) in PATH)))
$(info ${K})        
Vit Bernatik
quelle
Ich hätte nie gedacht, dass jemand GNU Make mit der gefürchteten cmd.exe ... "Shell" verwenden würde.
Johan Boulé
4

Sie können bash-erstellte Befehle wie type foooder command -v foowie folgt verwenden:

SHELL := /bin/bash
all: check

check:
        @type foo

Wo fooist dein Programm / Befehl? Weiterleiten an, > /dev/nullwenn Sie möchten, dass es stumm geschaltet wird.

Kenorb
quelle
Ja, aber die Sache ist, ich wollte, dass es funktioniert, wenn ich nur GNU machen, aber keine Bash installiert habe.
Prof. Falken
3

Angenommen, Sie haben unterschiedliche Ziele und Builder, für die jeweils andere Tools erforderlich sind. Legen Sie eine Liste solcher Tools fest und betrachten Sie sie als Ziel, um die Überprüfung ihrer Verfügbarkeit zu erzwingen

Beispielsweise:

make_tools := gcc md5sum gzip

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

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

Ich persönlich definiere ein requireZiel, das vor allen anderen läuft. Dieses Ziel führt einfach die Versionsbefehle aller Anforderungen einzeln aus und druckt die entsprechenden Fehlermeldungen, wenn der Befehl ungültig ist.

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.

Die Ausgabe des folgenden Skripts lautet

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

Gelöst durch Kompilieren eines speziellen kleinen Programms in einem anderen Makefile-Ziel, dessen einziger Zweck darin besteht, nach den gewünschten Laufzeitsachen zu suchen.

Dann habe ich dieses Programm in einem weiteren Makefile-Ziel aufgerufen.

Es war so etwas, wenn ich mich richtig erinnere:

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

checker: checker.c
    cc -o checker checker.c
Prof. Falken
quelle
Danke dir. Aber das würde abbrechen machen, oder? Kann verhindert werden, dass make abgebrochen wird? Ich versuche, einen Build-Schritt zu überspringen, wenn das erforderliche Tool nicht verfügbar ist.
Marc-Christian Schulze
@ coding.mof bearbeitet - es war eher so. Das Programm überprüfte etwas in der Laufzeit und gab dem Rest der Kompilierung entsprechende Informationen.
Prof. Falken
1

Die Lösungen, die auf STDERRAusgabe von prüfen, --versionfunktionieren nicht für Programme, die ihre Version STDOUTanstelle von drucken STDERR. Anstatt ihre Ausgabe an STDERRoder zu STDOUTüberprüfen, suchen Sie nach dem Programmrückgabecode. Wenn das Programm nicht existiert, ist sein Exit-Code immer ungleich Null.

#!/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}
Benutzer
quelle