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.

Prof. Falken
fonte
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.
Jonathan Ben-Avraham
fonte
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.

mentatkgs
fonte
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.

engenheiro
fonte
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.

0xF
fonte
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.

mpen
fonte
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 .

Fluxo
fonte
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})        
Vit Bernatik
fonte
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.

Kenorb
fonte
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 
lkanab
fonte
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
Rudi Kershaw
fonte
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
Prof. Falken
fonte
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}
do utilizador
fonte