Como atribuir a saída de um comando a uma variável Makefile


225

Eu preciso executar algumas regras de criação condicionalmente, apenas se o Python instalado for maior que uma determinada versão (por exemplo, 2.5).

Eu pensei que poderia fazer algo como executar:

python -c 'import sys; print int(sys.version_info >= (2,5))'

e, em seguida, usando a saída ('1' se aprovado, '0' caso contrário) em uma ifeqdeclaração make.

Em um script simples do bash shell, é apenas:

MY_VAR=`python -c 'import sys; print int(sys.version_info >= (2,5))'`

mas isso não funciona em um Makefile.

Alguma sugestão? Eu poderia usar qualquer outra solução sensata para conseguir isso.


Estranhos ticks em volta do trabalho de comando para executar outros scripts para mim em um Makefile. Pode ser outra coisa.
Leif Gruenwoldt 13/01

Respostas:


346

Use o Make shellbuiltin como emMY_VAR=$(shell echo whatever)

me@Zack:~$make
MY_VAR IS whatever

me@Zack:~$ cat Makefile 
MY_VAR := $(shell echo whatever)

all:
    @echo MY_VAR IS $(MY_VAR)

34
shell não é um comando Make builtin padrão. Este é um built-in do GNU Make.
Dereckson

12
stackoverflow.com/a/2373111/12916 adiciona uma observação importante sobre como escapar $.
precisa

6
Este exemplo simples funciona. Também funciona com o pipeline de comandos do shell. Mas é essencial que você use $$ para representar $ no comando shell
Sergey P. aka azure

28
Enquanto a pergunta é levemente antiga, é melhor executar MY_VAR: = $ (shell ...); caso contrário, toda vez que MY_VAR for avaliada, ele executará $ (shell ...) novamente.
Russ Schultz

Eu tinha um espaço entre a Shell e os parênteses de abertura e apenas após remover o espaço fiz o meu texto de saída makefile
peterchaula

29

Embrulhar a tarefa em evalestá funcionando para mim.

# dependency on .PHONY prevents Make from 
# thinking there's `nothing to be done`
set_opts: .PHONY
  $(eval DOCKER_OPTS = -v $(shell mktemp -d -p /scratch):/output)

6
"Nota: o @true aqui impede Make de pensar que não há nada a ser feito." Hum, .PHONY always make these targetsé para isso.
underscore_d

Obrigado! Isso me ajuda a desvio "bash estranho" no makefile
Nam G VU

19

Aqui está um exemplo um pouco mais complicado com tubulação e atribuição de variáveis ​​na receita:

getpodname:
    # Getting pod name
    @eval $$(minikube docker-env) ;\
    $(eval PODNAME=$(shell sh -c "kubectl get pods | grep profile-posts-api | grep Running" | awk '{print $$1}'))
    echo $(PODNAME)

2
Caso isso seja útil, estou usando uma abordagem um pouco semelhante para obter o PODNAMEnome da implantação:$(eval PODNAME=$(shell sh -c "kubectl get pod -l app=sqlproxy -o jsonpath='{.items[0].metadata.name}'"))
Jan Richter

A sintaxe me confundiu por um segundo, até eu perceber que você estava usando o shell construído em eval (na linha docker-env) e a função make eval (na próxima linha).
Brian Gordon

17

Estou escrevendo uma resposta para aumentar a visibilidade da sintaxe real que resolve o problema. Infelizmente, o que alguém pode considerar trivial pode se tornar uma dor de cabeça muito significativa para quem procura uma resposta simples para uma pergunta razoável.

Coloque o seguinte no arquivo "Makefile".

MY_VAR := $(shell python -c 'import sys; print int(sys.version_info >= (2,5))')

all:
    @echo MY_VAR IS $(MY_VAR)

O comportamento que você gostaria de ver é o seguinte (supondo que você tenha instalado recentemente o python).

make
MY_VAR IS 1

Se você copiar e colar o texto acima no Makefile, conseguirá isso? Provavelmente não. Você provavelmente receberá um erro como o relatado aqui:

makefile: 4: *** separador ausente. Pare

Motivo: porque, embora eu tenha usado pessoalmente uma guia genuína, o Stack Overflow (tentando ser útil) converte minha guia em vários espaços. Você, cidadão frustrado da Internet, agora copia isso, pensando que agora tem o mesmo texto que eu usei. O comando make, agora lê os espaços e descobre que o comando "all" está formatado incorretamente. Portanto, copie o texto acima, cole-o e converta o espaço em branco antes de "@echo" em uma guia, e este exemplo deve finalmente funcionar para você.


Depende do editor em que você está colando. Apenas copiei e colei em um editor Eclipse Makefile e obtive uma guia à esquerda (conforme necessário).
Technophile

Oh, eu não pensei nisso. Atom aqui.
AlanSE

11

Cuidado com receitas como esta

target:
    MY_ID=$(GENERATE_ID);
    echo $MY_ID;

Faz duas coisas erradas. A primeira linha da receita é executada em uma instância de shell separada da segunda linha. Enquanto isso, a variável é perdida. A segunda coisa errada é que $não se escapa.

target:
    MY_ID=$(GENERATE_ID); \
    echo $$MY_ID;

Ambos os problemas foram corrigidos e a variável é utilizável. A barra invertida combina as duas linhas para serem executadas em um único shell; portanto, a configuração da variável e a leitura das palavras posteriores da variável funcionam.

Percebo que o post original dizia como obter os resultados de um comando shell em uma variável MAKE, e essa resposta mostra como obtê-lo em uma variável shell. Mas outros leitores podem se beneficiar.

Uma melhoria final, se o consumidor espera que uma "variável de ambiente" seja definida, você precisará exportá-la.

my_shell_script
    echo $MY_ID

precisaria disso no makefile

target:
    export MY_ID=$(GENERATE_ID); \
    ./my_shell_script;

Espero que ajude alguém. Em geral, deve-se evitar fazer qualquer trabalho real fora das receitas, porque se alguém usar o makefile com a opção '--dry-run', apenas para ver o que ele fará, não terá efeitos colaterais indesejáveis. Cada $(shell)chamada é avaliada em tempo de compilação e algum trabalho real pode ser feito acidentalmente. Melhor deixar o trabalho real, como gerar IDs, para dentro das receitas, quando possível.


9

Com o GNU Make, você pode usar shelle evalarmazenar, executar e atribuir saída de chamadas arbitrárias da linha de comando. A diferença entre o exemplo abaixo e o que :=é usado é a :=atribuição uma vez (quando encontrada) e para todos. Variáveis ​​recursivamente expandidas configuradas com =são um pouco mais "preguiçosas"; as referências a outras variáveis ​​permanecem até que a própria variável seja referenciada e a subsequente expansão recursiva ocorre sempre que a variável é referenciada , o que é desejável para criar "consistentes, exigíveis, trechos". Consulte o manual sobre configuração de variáveis para obter mais informações.

# Generate a random number.
# This is not run initially.
GENERATE_ID = $(shell od -vAn -N2 -tu2 < /dev/urandom)

# Generate a random number, and assign it to MY_ID
# This is not run initially.
SET_ID = $(eval MY_ID=$(GENERATE_ID))

# You can use .PHONY to tell make that we aren't building a target output file
.PHONY: mytarget
mytarget:
# This is empty when we begin
    @echo $(MY_ID)
# This recursively expands SET_ID, which calls the shell command and sets MY_ID
    $(SET_ID)
# This will now be a random number
    @echo $(MY_ID)
# Recursively expand SET_ID again, which calls the shell command (again) and sets MY_ID (again)
    $(SET_ID)
# This will now be a different random number
    @echo $(MY_ID)

Você tem a primeira boa explicação que eu já vi. Obrigado. Embora uma coisa a acrescentar é que, se $(SET_ID)vive dentro de uma ifcláusula que é falsa , ela ainda é chamada .
Roman

Ele ainda é chamado porque o if stmts é avaliado em tempo de compilação makefile e não em tempo de execução. Para instruções específicas em tempo de execução, coloque-as em receitas. Se forem condicionais, adicione if stmts, escritos em bash / shell, como parte da receita.
Juraj

0

No exemplo abaixo, eu armazenei o caminho da pasta Makefile LOCAL_PKG_DIRe, em seguida, uso a LOCAL_PKG_DIRvariável nos destinos.

Makefile:

LOCAL_PKG_DIR := $(shell eval pwd)

.PHONY: print
print:
    @echo $(LOCAL_PKG_DIR)

Saída terminal:

$ make print
/home/amrit/folder
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.