Sprawdź, czy istnieje program z pliku Makefile

115

Jak mogę sprawdzić, czy program można wywołać z pliku Makefile?

(Oznacza to, że program powinien istnieć w ścieżce lub być w inny sposób wywoływalny).

Można go użyć na przykład do sprawdzenia, dla którego kompilatora jest zainstalowany.

Np. Coś w rodzaju tego pytania , ale bez zakładania, że ​​podstawowa powłoka jest kompatybilna z POSIX.

Prof. Falken
źródło
1
Czy możesz wywołać powłokę zgodną z POSIX?
reinierpost
Prawdopodobnie nie, myślę, że mógłbym żądać, aby ktoś tam był, ale byłoby znacznie łatwiej, gdybym nie musiał.
Prof. Falken,
W międzyczasie rozwiązałem to, dodając program do projektu, który jest budowany jako pierwszy i którego jedynym celem jest sprawdzenie tego innego programu ... :-)
Prof. Falken
2
Tradycyjnym sposobem obejścia tego problemu jest posiadanie automakeskryptu, który sprawdza różne wymagania wstępne i wypisuje odpowiedni plik Makefile.
tripleee

Odpowiedzi:

84

Czasami potrzebujesz Makefile, aby móc działać na różnych docelowych systemach operacyjnych i chcesz, aby kompilacja zakończyła się niepowodzeniem, jeśli nie ma wymaganego pliku wykonywalnego, PATHzamiast działać przez możliwie długi czas, zanim ulegnie awarii.

Doskonałe rozwiązanie dostarczone przez inżyniera wymaga wyznaczenia celu . Jeśli jednak masz wiele plików wykonywalnych do przetestowania, a Twój plik Makefile ma wiele niezależnych celów, z których każdy wymaga testów, to każdy cel wymaga celu testowego jako zależności. To powoduje dużo dodatkowego pisania, a także czas przetwarzania, gdy tworzysz więcej niż jeden cel naraz.

Rozwiązanie dostarczone przez 0xf może testować plik wykonywalny bez tworzenia celu. Oszczędza to dużo czasu na pisanie i wykonywanie, gdy istnieje wiele celów, które można zbudować oddzielnie lub razem.

Moim ulepszeniem tego ostatniego rozwiązania jest użycie whichpliku wykonywalnego ( wherew systemie Windows), zamiast polegać na --versionopcji w każdym pliku wykonywalnym, bezpośrednio w ifeqdyrektywie GNU Make , zamiast definiować nową zmienną i używać GNU Make errorfunkcji, aby zatrzymać kompilację, jeśli nie ma wymaganego pliku wykonywalnego ${PATH}. Na przykład, aby przetestować lzopplik wykonywalny:

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

Jeśli masz kilka plików wykonywalnych do sprawdzenia, możesz chcieć użyć foreachfunkcji z whichplikiem wykonywalnym:

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

Zwróć uwagę na użycie :=operatora przypisania, który jest wymagany w celu wymuszenia natychmiastowej oceny wyrażenia RHS. Jeśli twój plik Makefile zmieni PATH, to zamiast ostatniej linii powyżej będziesz potrzebować:

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

Powinno to dać wynik podobny do:

ads$ make
Makefile:5: *** "No dudu in PATH.  Stop.
Jonathan Ben-Avraham
źródło
2
Jeśli zrobisz EXECUTABLES wszystkie zmienne (np. LS CC LD) i użyjesz $ ($ (exec)), możesz je przekazać, aby bezproblemowo tworzyć ze środowiska lub innych plików makefile. Przydatne podczas kompilacji krzyżowej.
rickfoosusa
1
Robię dławiki przy „,” podczas uruchamiania „ifeq (, $ (shell, który lzop))” = /
mentatkgs
2
W systemie Windows użyj where my_exe 2>NULzamiast which.
Aaron Campbell
2
Uważaj na wcięcia TABS z ifeq, który jest składnią reguły! musi być BEZ ZAKŁADKI. Ciężko mi z tym. stackoverflow.com/a/21226973/2118777
Pipo,
2
Jeśli używasz Bash, nie musisz wykonywać połączenia zewnętrznego which. command -vZamiast tego użyj wbudowanego . Więcej info .
kurczyński
48

Zmieszałem rozwiązania z @kenorb i @ 0xF i otrzymałem to:

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 

Działa pięknie, ponieważ "polecenie -v" nie drukuje niczego, jeśli plik wykonywalny nie jest dostępny, więc zmienna DOT nigdy nie jest definiowana i możesz ją po prostu sprawdzić, kiedy chcesz w swoim kodzie. W tym przykładzie zgłaszam błąd, ale jeśli chcesz, możesz zrobić coś bardziej użytecznego.

Jeśli zmienna jest dostępna, "polecenie -v" wykonuje niedrogą operację drukowania ścieżki poleceń, definiując zmienną DOT.

mentatkgs
źródło
5
Przynajmniej w niektórych systemach (np. Ubuntu 14.04) $(shell command -v dot)nie udaje się make: command: Command not found. Aby rozwiązać ten problem, przekierowywać wyjście stderr do / dev / null: $(shell command -v dot 2> /dev/null). Wyjaśnienie
J0HN
3
commandczy bash jest wbudowany, dla większej przenośności, rozważ użycie which?
Julien Palard
10
@JulienPalard Myślę, że się mylisz: commandnarzędzie jest wymagane przez posix (łącznie z -vopcją). Z drugiej strony whichnie ma standardowego zachowania (o którym wiem) i czasami jest po prostu bezużyteczne
Gyom
3
command -vwymaga środowiska powłoki podobnego do POSIX. To nie zadziała w systemie Windows bez emulacji cygwin lub podobnej.
dolmen
10
Powodem, dla którego commandczęsto nie działa, nie jest jego brak , ale szczególna optymalizacja, którą wykonuje GNU Make: jeśli polecenie jest „wystarczająco proste”, ominie powłokę; niestety oznacza to, że wbudowane czasami nie będą działać, chyba że trochę zmienisz polecenie.
Rufflewind
33

czy to właśnie zrobiłeś?

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

kredyt dla mojego współpracownika.

engineerchuan
źródło
Nie, skompilowałem program w jednym celu i uruchomiłem go w innym.
Prof. Falken
21

Użyj tej shellfunkcji, aby wywołać swój program w taki sposób, że wypisze coś na standardowe wyjście. Na przykład pass --version.

GNU Make ignoruje kod zakończenia polecenia przekazanego do shell. Aby uniknąć potencjalnego komunikatu „nie znaleziono polecenia”, przekieruj standardowy błąd do /dev/null.

Następnie można sprawdzić wyniki za pomocą ifdef, ifndef, $(if)itd.

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

Jako bonus, dane wyjściowe (takie jak wersja programu) mogą być przydatne w innych częściach pliku Makefile.

0xF
źródło
to daje błąd *** missing separator. Stop.Jeśli dodam karty we wszystkich wierszach po all:otrzymaniu błędumake: ifdef: Command not found
Matthias 009
także: ifdefoceni prawdę, nawet jeśli your_programnie istnieje gnu.org/software/make/manual/make.html#Conditional-Syntax
Matthias 009
1
Nie należy dodawać do zakładek ifdef, else, endiflinie. Tylko upewnij się, że @echolinie zaczynają się od tabulatorów.
0xF
Przetestowałem swoje rozwiązanie w systemie Windows i Linux. Dokumentacja, którą utworzyłeś, stwierdza, że ifdefsprawdza niepustą wartość zmiennej. Jeśli zmienna nie jest pusta, do czego się to oblicza?
0xF
1
@ Matthias009 kiedy testuję GNU Make v4.2.1, użycie ifdeflub ifndef(jak w zaakceptowanej odpowiedzi) działa tylko wtedy, gdy oceniana zmienna jest natychmiast ustawiana ( :=) zamiast leniwie set ( =). Jednak wadą używania natychmiast ustawianych zmiennych jest to, że są one oceniane w czasie deklaracji, podczas gdy zmienne ustawiane leniwie są oceniane, gdy są wywoływane. Oznacza to, że wykonywałbyś polecenia dla zmiennych :=nawet wtedy, gdy Make uruchamia tylko reguły, które nigdy nie używają tych zmiennych! Możesz tego uniknąć, używając =withifneq ($(MY_PROG),)
Dennis
9

Wyczyszczono niektóre z istniejących rozwiązań tutaj ...

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 ...)Można wykluczyć, jeśli chcesz to być ciszej.

To szybko się nie powiedzie . Nie jest wymagany cel.

mpen
źródło
8

Moje rozwiązanie obejmuje mały skrypt pomocniczy 1, który umieszcza plik flag, jeśli istnieją wszystkie wymagane polecenia. Ma to tę zaletę, że sprawdzenie wymaganych poleceń jest wykonywane tylko raz i nie przy każdym makewywołaniu.

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 Więcej o tej command -vtechnice można znaleźć tutaj .

Pływ
źródło
2
Właśnie go przetestowałem i działa, a ponieważ nie podałeś żadnej wskazówki, co nie działa dla Ciebie (skrypt bash lub kod Makefile) ani żadnych komunikatów o błędach, nie mogę pomóc Ci znaleźć problemu.
Przepływ
Na pierwszym ifstwierdzeniu pojawia się komunikat „nieoczekiwany koniec pliku” .
Adam Grant
6

Dla mnie wszystkie powyższe odpowiedzi oparte są na Linuksie i nie działają z Windows. Jestem nowy, więc moje podejście może nie być idealne. Ale kompletny przykład, który działa dla mnie zarówno w systemie Linux, jak i Windows, jest następujący:

# 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

opcjonalnie, gdy potrzebuję znaleźć więcej narzędzi, których mogę użyć:

EXECUTABLES = ls dd 
K := $(foreach myTestCommand,$(EXECUTABLES),\
        $(if $(shell ${WHICH} $(myTestCommand) 2>${DEVNUL} ),\
            $(myTestCommand) found,\
            $(error "No $(myTestCommand) in PATH)))
$(info ${K})        
Vit Bernatik
źródło
Nigdy nie myślałem, że ktoś użyje GNU Make z przerażającym cmd.exe… „powłoką”.
Johan Boulé,
4

Możesz użyć poleceń wbudowanych w bash, takich jak type foolub command -v foo, jak poniżej:

SHELL := /bin/bash
all: check

check:
        @type foo

Gdzie foojest twój program / polecenie. Przekieruj do, > /dev/nulljeśli chcesz, aby było cicho.

kenorb
źródło
Tak, ale chodziło o to, że chciałem, aby działał, gdy miałem tylko GNU, ale nie zainstalowałem basha.
Prof. Falken
3

Załóżmy, że masz różne cele i konstruktorów, każdy wymaga innego zestawu narzędzi. Ustaw listę takich narzędzi i potraktuj je jako cel, aby wymusić sprawdzenie ich dostępności

Na przykład:

make_tools := gcc md5sum gzip

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

file.txt.gz: file.txt gzip
    gzip -c file.txt > file.txt.gz 
lkanab
źródło
3

Osobiście określam requirecel, który wyprzedza wszystkie inne. Ten cel po prostu uruchamia polecenia wersji wszystkich wymagań pojedynczo i wyświetla odpowiednie komunikaty o błędach, jeśli polecenie jest niepoprawne.

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.

Wynik poniższego skryptu to

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
źródło
1

Rozwiązany przez skompilowanie specjalnego małego programu w innym docelowym pliku makefile, którego jedynym celem jest sprawdzenie wszelkich elementów środowiska wykonawczego, których szukałem.

Następnie nazwałem ten program kolejnym celem makefile.

To było coś takiego, jeśli dobrze pamiętam:

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

checker: checker.c
    cc -o checker checker.c
Prof. Falken
źródło
Dziękuję Ci. Ale to by przerwać, prawda? Czy można zapobiec przerywaniu programu make? Próbuję pominąć krok kompilacji, jeśli wymagane narzędzie nie jest dostępne.
Marc-Christian Schulze
@ coding.mof redagowane - tak było bardziej. Program sprawdził coś w czasie wykonywania i podał informacje zgodnie z resztą kompilacji.
Prof. Falken
1

Rozwiązania sprawdzające STDERRwyjście programu --versionnie działają w przypadku programów, które wypisują swoją wersję STDOUTzamiast STDERR. Zamiast sprawdzać ich wyjście do STDERRlub STDOUT, sprawdź kod powrotu programu. Jeśli program nie istnieje, jego kod zakończenia będzie zawsze różny od zera.

#!/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}
użytkownik
źródło