Periksa apakah program ada dari Makefile


115

Bagaimana cara memeriksa apakah suatu program dapat dipanggil dari Makefile?

(Artinya, program harus ada di jalur atau bisa dipanggil.)

Ini bisa digunakan untuk memeriksa kompiler mana yang diinstal, misalnya.

Misalnya pertanyaan seperti ini , tetapi tanpa mengasumsikan shell yang mendasarinya kompatibel dengan POSIX.


1
Bisakah Anda meminta shell yang kompatibel dengan POSIX?
reinierpost

Mungkin tidak, saya rasa saya bisa meminta seseorang untuk berada di sana, tetapi akan jauh lebih mudah jika saya tidak perlu.
Prof. Falken

Sementara itu, saya menyelesaikannya dengan menambahkan program ke proyek, yang dibangun terlebih dahulu, dan yang satu-satunya tujuan adalah untuk memeriksa program lain itu ... :-)
Prof. Falken

2
Solusi tradisionalnya adalah memiliki automakeskrip yang memeriksa berbagai prasyarat dan menulis yang sesuai Makefile.
tripleee pada

Jawaban:


84

Terkadang Anda memerlukan Makefile agar dapat berjalan di OS target yang berbeda dan Anda ingin build tersebut gagal lebih awal jika executable yang diperlukan tidak ada PATHdaripada dijalankan dalam waktu yang mungkin lama sebelum gagal.

Solusi terbaik yang diberikan oleh engineerchuan membutuhkan pembuatan target . Namun, jika Anda memiliki banyak executable untuk diuji dan Makefile Anda memiliki banyak target independen, yang masing-masing memerlukan pengujian, maka setiap target memerlukan target pengujian sebagai dependensi. Itu menghasilkan banyak pengetikan tambahan serta waktu pemrosesan ketika Anda membuat lebih dari satu target pada satu waktu.

Solusi yang disediakan oleh 0xf dapat menguji executable tanpa membuat target. Itu menghemat banyak waktu pengetikan dan eksekusi ketika ada beberapa target yang dapat dibangun secara terpisah atau bersama-sama.

Perbaikan saya untuk solusi yang terakhir adalah dengan menggunakan yang whichdapat dieksekusi ( wheredi Windows), daripada mengandalkan adanya --versionopsi di setiap yang dapat dieksekusi, langsung di ifeqdirektif GNU Make , daripada untuk menentukan variabel baru, dan untuk menggunakan GNU Make errorberfungsi untuk menghentikan build jika executable yang diperlukan tidak ada ${PATH}. Misalnya, untuk menguji yang lzopdapat dieksekusi:

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

Jika Anda memiliki beberapa executable untuk diperiksa, Anda mungkin ingin menggunakan foreachfungsi dengan whichexecutable:

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

Perhatikan penggunaan :=operator penugasan yang diperlukan untuk memaksa evaluasi langsung dari ekspresi RHS. Jika Makefile Anda mengubah PATH, maka alih-alih baris terakhir di atas Anda perlu:

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

Ini akan memberi Anda hasil yang mirip dengan:

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

2
Jika Anda membuat EXECUTABLES semua variabel, (mis. LS CC LD), dan menggunakan $ ($ (exec)), Anda dapat meneruskannya untuk membuat secara mulus dari lingkungan atau makefile lain. Berguna saat kompilasi silang.
rickfoosusa

1
Saya membuat tersedak pada "," saat menjalankan "ifeq (, $ (shell which lzop))" = /
mentatkgs

2
Di Windows, gunakan where my_exe 2>NULsebagai pengganti which.
Aaron Campbell

2
Waspadalah terhadap indentasi TABS dengan ifeq yang merupakan sintaks aturan! itu harus TANPA TAB. Mengalami kesulitan dengan itu. stackoverflow.com/a/21226973/2118777
Pipo

2
Jika Anda menggunakan Bash, tidak perlu melakukan panggilan eksternal ke which. Gunakan built-in command -vsebagai gantinya. Info lebih lanjut .
kurczynski

48

Saya mencampur solusi dari @kenorb dan @ 0xF dan mendapatkan ini:

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 

Ia bekerja dengan baik karena "perintah -v" tidak mencetak apa pun jika yang dapat dieksekusi tidak tersedia, sehingga variabel DOT tidak pernah didefinisikan dan Anda dapat memeriksanya kapan pun Anda inginkan dalam kode Anda. Dalam contoh ini saya memberikan kesalahan, tetapi Anda dapat melakukan sesuatu yang lebih berguna jika Anda mau.

Jika variabel tersedia, "perintah -v" melakukan operasi pencetakan jalur perintah yang tidak mahal, dengan mendefinisikan variabel DOT.


5
Setidaknya di beberapa sistem (yaitu Ubuntu 14.04), $(shell command -v dot)gagal dengan make: command: Command not found. Untuk mengatasinya, redirect stderr output ke / dev / null: $(shell command -v dot 2> /dev/null). Penjelasan
J0HN

3
commandyang dimaksud dengan bash builtin, untuk lebih mudah dibawa, pertimbangkan untuk menggunakan which?
Julien Palard

10
@JulienPalard Saya yakin Anda salah: commandutilitas diperlukan oleh posix (termasuk -vopsi) Di sisi lain whichtidak memiliki bahasa standar (yang saya tahu) dan kadang-kadang langsung tidak dapat digunakan
Gyom

3
command -vmembutuhkan lingkungan shell seperti POSIX. Ini tidak akan berfungsi di Windows tanpa cygwin atau emulasi serupa.
dolmen

10
Alasan mengapa commandsering kali tidak berfungsi bukan karena ketidakhadirannya , tetapi karena pengoptimalan khusus yang dilakukan GNU Make: jika sebuah perintah "cukup sederhana" ia akan melewati shell; ini sayangnya berarti built-in terkadang tidak berfungsi kecuali Anda sedikit mengacaukan perintah.
Rufflewind

33

apakah ini yang kamu lakukan?

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

kredit untuk rekan kerja saya.


Tidak, saya menyusun program di satu target dan menjalankannya di target lain.
Prof. Falken

21

Gunakan shellfungsi untuk memanggil program Anda sedemikian rupa sehingga mencetak sesuatu ke keluaran standar. Misalnya, lulus --version.

GNU Make mengabaikan status keluar dari perintah yang diteruskan ke shell. Untuk menghindari potensi pesan "perintah tidak ditemukan", alihkan kesalahan standar ke /dev/null.

Maka Anda dapat memeriksa hasilnya menggunakan ifdef, ifndef, $(if)dll

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

Sebagai bonus, output (seperti versi program) mungkin berguna di bagian lain Makefile Anda.


ini memberikan kesalahan *** missing separator. Stop.Jika saya menambahkan tab di semua baris setelah all:saya mendapatkan kesalahanmake: ifdef: Command not found
Matthias 009

juga: ifdefakan dievaluasi benar meskipun your_programtidak ada gnu.org/software/make/manual/make.html#Conditional-Syntax
Matthias 009

1
Jangan menambahkan tab ke ifdef, else, endifgaris. Pastikan saja @echogarisnya dimulai dengan tab.
0xF

Saya menguji solusi saya di Windows dan Linux. Dokumentasi yang Anda tautkan menyatakan bahwa ifdefmemeriksa nilai variabel yang tidak kosong. Jika variabel Anda tidak kosong, apa yang dievaluasi?
0xF

1
@ Matthias009 ketika saya menguji GNU Make v4.2.1, menggunakan ifdefatau ifndef(seperti pada jawaban yang diterima) hanya berfungsi jika variabel yang dievaluasi segera disetel ( :=) alih-alih set malas ( =). Namun, kelemahan dari penggunaan variabel segera ditetapkan adalah bahwa mereka dievaluasi pada waktu deklarasi, sedangkan variabel set malas dievaluasi saat dipanggil. Ini berarti Anda akan menjalankan perintah untuk variabel :=bahkan ketika Make hanya menjalankan aturan yang tidak pernah menggunakan variabel tersebut! Anda dapat menghindari ini dengan menggunakan =denganifneq ($(MY_PROG),)
Dennis

9

Membersihkan beberapa solusi yang ada di sini ...

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

The $(info ...)Anda dapat mengecualikan jika Anda ingin ini menjadi lebih tenang.

Ini akan gagal dengan cepat . Tidak ada target yang dibutuhkan.


8

Solusi saya melibatkan skrip pembantu kecil 1 yang menempatkan file bendera jika semua perintah yang diperlukan ada. Ini datang dengan keuntungan bahwa pemeriksaan untuk perintah yang diperlukan hanya dilakukan sekali dan tidak pada setiap makepemanggilan.

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 Lebih lanjut tentang command -vteknik dapat ditemukan di sini .


2
Saya baru saja mengujinya dan berhasil dan karena Anda tidak memberikan petunjuk tentang apa yang tidak berfungsi untuk Anda (skrip bash atau kode Makefile) atau pesan kesalahan apa pun, saya tidak dapat membantu Anda menemukan masalahnya.
Aliran

Saya mendapatkan "akhir file yang tidak terduga" pada ifpernyataan pertama .
Adam Grant

6

Bagi saya semua jawaban di atas didasarkan pada linux dan tidak berfungsi dengan windows. Saya baru dalam membuat jadi pendekatan saya mungkin tidak ideal. Tetapi contoh lengkap yang berfungsi untuk saya di linux dan windows adalah ini:

# 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

opsional ketika saya perlu mendeteksi lebih banyak alat yang dapat saya gunakan:

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

Saya tidak pernah berpikir seseorang akan menggunakan GNU Make dengan cmd.exe yang ditakuti ... "shell".
Johan Boulé

4

Anda dapat menggunakan perintah bash built seperti type fooatau command -v foo, seperti di bawah ini:

SHELL := /bin/bash
all: check

check:
        @type foo

Di mana fooprogram / perintah Anda. Alihkan ke > /dev/nulljika Anda ingin senyap.


Ya, tetapi masalahnya adalah saya ingin ini berfungsi ketika saya hanya memiliki GNU make tetapi tidak ada bash yang diinstal.
Prof. Falken

3

Asumsikan Anda memiliki target dan pembangun yang berbeda, masing-masing membutuhkan seperangkat alat yang lain. Tetapkan daftar alat tersebut dan anggap mereka sebagai target untuk memaksa memeriksa ketersediaannya

Sebagai contoh:

make_tools := gcc md5sum gzip

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

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

3

Saya secara pribadi mendefinisikan requiretarget yang berjalan sebelum yang lainnya. Target ini hanya menjalankan perintah versi dari semua persyaratan satu per satu dan mencetak pesan kesalahan yang sesuai jika perintah tersebut tidak valid.

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.

Output dari skrip di bawah ini adalah

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

Dipecahkan dengan menyusun program kecil khusus di target makefile lain, yang tujuan utamanya adalah untuk memeriksa hal-hal runtime yang saya cari.

Kemudian, saya memanggil program ini dengan target makefile lainnya.

Itu adalah sesuatu seperti ini jika saya mengingatnya dengan benar:

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

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

Terima kasih. Tapi ini akan membuat gagal, kan? Apakah mungkin mencegah make dari menggugurkan? Saya mencoba untuk melewati langkah pembuatan jika alat yang diperlukan tidak tersedia ..
Marc-Christian Schulze

@ coding.mof diedit - lebih seperti itu. Program tersebut memeriksa sesuatu pada waktu proses dan memberikan info yang sesuai dengan kompilasi lainnya.
Prof. Falken

1

Solusi yang memeriksa STDERRkeluaran --versiontidak berfungsi untuk program yang mencetak versinya STDOUTsebagai pengganti STDERR. Alih-alih memeriksa outputnya ke STDERRatau STDOUT, periksa kode pengembalian program. Jika program tidak ada, kode keluarnya akan selalu bukan nol.

#!/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}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.