Verifique se existe um programa de um Makefile


115

Como posso verificar se um programa pode ser chamado de um Makefile?

(Ou seja, o programa deve existir no caminho ou pode ser chamado de outra forma.)

Pode ser usado para verificar qual compilador está instalado, por exemplo.

Por exemplo, algo como esta questão , mas sem assumir que o shell subjacente é compatível com POSIX.


1
Você pode invocar um shell compatível com POSIX?
— reinierpost de

Provavelmente não, acho que poderia exigir que alguém estivesse lá, mas seria muito mais fácil se não precisasse.
— Prof. Falken

Nesse ínterim, resolvi isso adicionando um programa ao projeto, que é construído primeiro, e cujo único propósito é verificar esse outro programa ... :-)
— Prof. Falken

2
A solução alternativa tradicional é ter um automakescript que verifica vários pré-requisitos e escreve um adequado Makefile.
— tripleee

Respostas:


84

Às vezes, você precisa de um Makefile para ser executado em diferentes sistemas operacionais de destino e deseja que a compilação falhe mais cedo se um executável necessário não estiver disponível, em PATHvez de ser executado por um possivelmente longo tempo antes de falhar.

A excelente solução fornecida por engineerchuan requer fazer um alvo . No entanto, se você tiver muitos executáveis ​​para testar e seu Makefile tiver muitos destinos independentes, cada um dos quais requer os testes, então cada destino requer o destino de teste como uma dependência. Isso gera uma grande quantidade de digitação extra, bem como tempo de processamento, quando você cria mais de um destino por vez.

A solução fornecida por 0xf pode testar um executável sem criar um alvo. Isso economiza muito tempo de digitação e execução quando há vários destinos que podem ser construídos separadamente ou juntos.

Meu aprimoramento para a última solução é usar o whichexecutável ( whereno Windows), ao invés de contar com a existência de uma --versionopção em cada executável, diretamente na ifeqdiretiva GNU Make , ao invés de definir uma nova variável, e usar o GNU Make errorpara interromper a construção se um executável necessário não estiver disponível ${PATH}. Por exemplo, para testar o lzopexecutável:

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

Se você tiver vários executáveis ​​para verificar, convém usar uma foreachfunção com o whichexecutável:

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

Observe o uso do :=operador de atribuição que é necessário para forçar a avaliação imediata da expressão RHS. Se o seu Makefile mudar o PATH, então, em vez da última linha acima, você precisará:

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

Isso deve fornecer uma saída semelhante a:

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

2
Se você tornar EXECUTABLES todas as variáveis, (isto é, LS CC LD), e usar $ ($ (exec)), você pode passá-las para fazer perfeitamente a partir do ambiente ou outros makefiles. Útil durante a compilação cruzada.
— rickfoosusa

1
Meu make engasga no "," ao executar "ifeq (, $ (shell which lzop))" = /
— mentatkgs

2
No Windows, use em where my_exe 2>NULvez de which.
— Aaron Campbell

2
Cuidado com as indentações do TABS com ifeq, que é uma sintaxe de regra! deve ser SEM TAB. Tive dificuldade com isso. stackoverflow.com/a/21226973/2118777
— Pipo

2
Se estiver usando Bash, não há necessidade de fazer uma chamada externa para which. Em command -vvez disso, use o integrado . Mais informações .
— kurczynski

48

Eu misturei as soluções de @kenorb e @ 0xF e consegui isso:

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 perfeitamente porque "command -v" não imprime nada se o executável não estiver disponível, então a variável DOT nunca é definida e você pode apenas checá-la sempre que quiser em seu código. Neste exemplo, estou gerando um erro, mas você poderia fazer algo mais útil, se quisesse.

Se a variável estiver disponível, "command -v" realiza a operação barata de imprimir o caminho do comando, definindo a variável DOT.


5
Pelo menos em alguns sistemas (por exemplo, Ubuntu 14.04), $(shell command -v dot)falha com make: command: Command not found. Para corrigir isso, a saída stderr redirecionamento para / dev / null: $(shell command -v dot 2> /dev/null). Explicação
— J0HN

3
commandé um bash embutido, para mais portabilidade, considere usar which?
— Julien Palard

10
@JulienPalard Acredito que você esteja enganado: o commandutilitário é exigido por posix (incluindo a -vopção) Por outro lado whichnão tem bahaviour padronizado (que eu saiba) e às vezes é totalmente inutilizável
— Gyom

3
command -vrequer um ambiente de shell semelhante ao POSIX. Isso não funcionará no Windows sem cygwin ou emulação semelhante.
— Dolmen de

10
A razão pela qual commandmuitas vezes não funciona não é por causa de sua ausência , mas por causa de uma otimização peculiar que o GNU Make faz: se um comando for "simples o suficiente", ele contornará o shell; infelizmente isso significa que os embutidos às vezes não funcionarão a menos que você altere um pouco o comando.
— Rufflewind de

33

é isso que você fez?

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

crédito ao meu colega de trabalho.


Não, compilei um programa em um destino e o executei em outro.
— Prof. Falken

21

Use a shellfunção para chamar seu programa de forma que imprima algo na saída padrão. Por exemplo, passe --version.

GNU Make ignora o status de saída do comando passado para shell. Para evitar a mensagem potencial "comando não encontrado", redirecione o erro padrão para /dev/null.

Depois, você pode verificar o 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 um bônus, a saída (como a versão do programa) pode ser útil em outras partes do seu Makefile.


isso dá um erro *** missing separator. Stop.Se eu adicionar abas em todas as linhas depois de all:obter o erromake: ifdef: Command not found
— Matthias 009

também: ifdefserá avaliado como verdadeiro mesmo se your_programnão existir gnu.org/software/make/manual/make.html#Conditional-Syntax
— Matthias 009

1
Não adicionar guias para o ifdef, else, endiflinhas. Apenas certifique-se de que as @echolinhas comecem com tabulações.
— 0xF

Testei minha solução em Windows e Linux. A documentação que você vinculou afirma que ifdefverifica o valor da variável não vazia. Se sua variável não estiver vazia, o que ela avalia?
— 0xF

1
@ Matthias009 quando eu testo o GNU Make v4.2.1, usando ifdefou ifndef(como na resposta aceita) só funciona se a variável sendo avaliada for imediatamente definida ( :=) em vez de preguiçosamente definida ( =). No entanto, uma desvantagem de usar variáveis ​​definidas imediatamente é que elas são avaliadas no momento da declaração, enquanto as variáveis ​​definidas preguiçosamente são avaliadas quando são chamadas. Isso significa que você estaria executando comandos para variáveis :=mesmo quando o Make estivesse executando apenas regras que nunca usam essas variáveis! Você pode evitar isso usando um =comifneq ($(MY_PROG),)
— Dennis

9

Limpei algumas das soluções existentes aqui ...

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

O $(info ...)que você pode excluir se você quer que isso seja mais silencioso.

Isso vai falhar rapidamente . Nenhum alvo necessário.


8

Minha solução envolve um pequeno script auxiliar 1 que coloca um arquivo de sinalização se todos os comandos necessários existirem. Isso tem a vantagem de que a verificação dos comandos necessários é feita apenas uma vez e não a cada makeinvocação.

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 Mais sobre a command -vtécnica pode ser encontrado aqui .


2
Acabei de testar e funciona e, como você não forneceu nenhuma dica sobre o que não está funcionando para você (o script bash ou o código Makefile) ou qualquer mensagem de erro, não posso ajudá-lo a encontrar o problema.
— Fluxo

Estou recebendo "final de arquivo inesperado" na primeira ifdeclaração.
— Adam Grant

6

Para mim, todas as respostas acima são baseadas no Linux e não funcionam com o Windows. Eu sou novo para fazer, então minha abordagem pode não ser ideal. Mas um exemplo completo que funciona para mim tanto no Linux quanto no Windows é 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, quando preciso detectar mais ferramentas que posso usar:

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

Nunca pensei que alguém usaria o GNU Make com o temido cmd.exe ... "shell".
— Johan Boulé

4

Você pode usar comandos bash construídos como type fooou command -v foo, conforme abaixo:

SHELL := /bin/bash
all: check

check:
        @type foo

Onde fooestá seu programa / comando. Redirecione para > /dev/nullse desejar silencioso.


Sim, mas eu queria que funcionasse quando eu tinha apenas o GNU make, mas nenhum bash instalado.
— Prof. Falken

3

Suponha que você tenha alvos e construtores diferentes, cada um requer outro conjunto de ferramentas. Defina uma lista dessas ferramentas e considere-as como alvo para forçar a verificação de sua disponibilidade

Por exemplo:

make_tools := gcc md5sum gzip

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

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

3

Estou definindo pessoalmente um requirealvo que corre antes de todos os outros. Este destino simplesmente executa os comandos de versão de todos os requisitos, um por vez, e imprime as mensagens de erro apropriadas se o comando for invá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.

A saída do script abaixo é

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

Resolvido ao compilar um pequeno programa especial em outro destino makefile, cujo único propósito é verificar qualquer coisa de tempo de execução que eu estava procurando.

Então, chamei este programa em outro destino makefile.

Foi algo assim, se bem me lembro:

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

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

Obrigado. Mas isso faria abortar, certo? É possível evitar que o make aborte? Estou tentando pular uma etapa de construção se a ferramenta necessária não estiver disponível.
— Marc-Christian Schulze

@ coding.mof editado - era mais assim. O programa verificou algo no tempo de execução e forneceu informações de acordo com o resto da compilação.
— Prof. Falken

1

A verificação de soluções para STDERRsaída de --versionnão funciona para programas que imprimem suas versões em em STDOUTvez de STDERR. Em vez de verificar sua saída para STDERRou STDOUT, verifique o código de retorno do programa. Se o programa não existir, seu código de saída sempre será diferente de 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}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.