Passando argumentos para "Make Run"

354

Eu uso Makefiles.

Eu tenho um destino chamado runque executa o destino da compilação. Simplificado, parece com o seguinte:

prog: ....
  ...

run: prog
  ./prog

Existe alguma maneira de passar argumentos? De modo a

make run asdf --> ./prog asdf
make run the dog kicked the cat --> ./prog the dog kicked the cat

Obrigado!

anon
fonte

Respostas:

264

Não sei como fazer exatamente o que você deseja, mas uma solução alternativa pode ser:

run: ./prog
    ./prog $(ARGS)

Então:

make ARGS="asdf" run
# or
make run ARGS="asdf"
Jakob Borg
fonte
27
@Rob: $ () é mais portátil, funciona tanto em Nmake quanto em make.
John Knoeller
7
@ Rob: Nmake nunca suportou $ {} para expansão de macro e parece ser uma forma arcaica agora em construção. O $ () é recomendado por todos os tutoriais online que eu analisei. $ () também é mais consistente com outras ferramentas, como o bash.
John Knoeller
10
Talvez seja arcaico. Eu sempre usei $ {}, mas o manual do GNU Make afirma "Para substituir o valor de uma variável, escreva um cifrão seguido pelo nome da variável entre parênteses ou chaves: $(foo)' or $ {foo} 'é uma referência válida para a variável `foo '." e continua dando exemplos onde apenas $ () é usado. Ah bem.
Jakob Borg
7
Felicidades John e Calma, voltei e vi que a sugestão veio da minha cópia da primeira edição do livro OReilly "Gerenciando Projetos com Make". O autor declara a regra sobre a substituição de arquivos usando () e macros capazes de fazer as duas coisas, mas sugere o uso de {} para distinguir. Mas .... A nova edição agora intitulada "Gerenciando Projetos com o GNU Make" usa () por toda parte. Vai entender ... Acho que vou ter que me modernizar! (-: Ainda estou surpreso que o MS NMake vomite na casa de {}.
Rob Wells
11
@xealits Com certeza é - há um exemplo na questão aqui
helvete
198

Esta pergunta tem quase três anos, mas mesmo assim ...

Se você estiver usando o GNU make, isso é fácil. O único problema é que makeinterpretará argumentos não opcionais na linha de comando como destinos. A solução é transformá-los em alvos do tipo "não faça nada", para makenão reclamar:

# If the first argument is "run"...
ifeq (run,$(firstword $(MAKECMDGOALS)))
  # use the rest as arguments for "run"
  RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
  # ...and turn them into do-nothing targets
  $(eval $(RUN_ARGS):;@:)
endif

prog: # ...
    # ...

.PHONY: run
run : prog
    @echo prog $(RUN_ARGS)

A execução disso fornece:

$ make run foo bar baz
prog foo bar baz
Idelic
fonte
2
Isso é ótimo, exceto que não parece trabalho para argumentos começando com um traço:prog foo bar --baz
ingydotnet
22
Ele faz o trabalho nesse caso também, mas você tem que dizer makenão interpretar --bazcomo uma opção de linha de comando: make -- prog foo bar --baz. O --meio "tudo depois disso é um argumento, não uma opção".
Idelic 6/11
Como eu definiria um valor padrão para RUN_ARGSusar isso?
Bouke
Talvez adicionar um elseramo ao ifeqe definir RUN_ARGSlá?
Idelic
11
Bom ponto azulado! Mas existe uma solução para isso: substitua a linha 'eval' por $(eval $(RUN_ARGS):dummy;@:), com nenhum alvo fictício definido.
Lucas Cimon 29/11
52

para make padrão, você pode passar argumentos definindo macros como esta

make run arg1=asdf

então use-os assim

run: ./prog $(arg1)
   etc

Referências para make NMake da Microsoft

John Knoeller
fonte
34

Você pode passar a variável para o Makefile como abaixo:

run:
    @echo ./prog $$FOO

Uso:

$ make run FOO="the dog kicked the cat"
./prog the dog kicked the cat

ou:

$ FOO="the dog kicked the cat" make run
./prog the dog kicked the cat

Como alternativa, use a solução fornecida pela Beta :

run:
    @echo ./prog $(filter-out $@,$(MAKECMDGOALS))
%:
    @:

%:- regra que corresponde a qualquer nome de tarefa; @:- receita vazia = não fazer nada

Uso:

$ make run the dog kicked the cat
./prog the dog kicked the cat
kenorb
fonte
22

TL; DR não tente fazer isso

$ make run arg

em vez disso, crie um script:

#! /bin/sh
# rebuild prog if necessary
make prog
# run prog with some arguments
./prog "$@"

e faça o seguinte:

$ ./buildandrunprog.sh arg

responda à pergunta declarada:

você pode usar uma variável na receita

run: prog
    ./prog $(var)

depois passe uma atribuição de variável como argumento para fazer

$ make run var=arg

isso será executado ./prog arg.

mas cuidado com as armadilhas. vou elaborar sobre as armadilhas deste método e outros métodos mais adiante.


responda à intenção assumida por trás da pergunta:

a suposição: você deseja executar progcom alguns argumentos, mas reconstrua-o antes de executar, se necessário.

a resposta: crie um script que reconstrua, se necessário, e execute prog com args

#! /bin/sh
# rebuild prog if necessary
make prog
# run prog with some arguments
./prog "$@"

esse script deixa a intenção muito clara. usa make para fazer o que é bom para: construir. Ele usa um script de shell para fazer o que é bom: processamento em lote.

Além disso, você pode fazer o que precisar com toda a flexibilidade e expressividade de um script de shell sem todas as ressalvas de um makefile.

também a sintaxe de chamada agora é praticamente idêntica:

$ ./buildandrunprog.sh foo "bar baz"

comparado a:

$ ./prog foo "bar baz"

contrasta com

$ make run var="foo bar\ baz"

fundo:

make não foi projetado para passar argumentos para um destino. todos os argumentos na linha de comando são interpretados como um objetivo (também conhecido como destino), como uma opção ou como uma atribuição de variável.

então, se você executar isso:

$ make run foo --wat var=arg

make interpretará rune foocomo objetivos (metas) a serem atualizados de acordo com suas receitas. --watcomo uma opção para make. evar=arg como uma atribuição de variável.

para mais detalhes veja: https://www.gnu.org/software/make/manual/html_node/Goals.html#Goals

para obter a terminologia, consulte: https://www.gnu.org/software/make/manual/html_node/Rule-Introduction.html#Rule-Introduction


sobre o método de atribuição de variáveis ​​e por que eu recomendo isso

$ make run var=arg

e a variável na receita

run: prog
    ./prog $(var)

essa é a maneira mais "correta" e direta de passar argumentos para uma receita. mas, embora possa ser usado para executar um programa com argumentos, certamente não foi projetado para ser usado dessa maneira. consulte https://www.gnu.org/software/make/manual/html_node/Overriding.html#Overriding

na minha opinião, isso tem uma grande desvantagem: o que você quer fazer é executar progcom argumento arg. mas em vez de escrever:

$ ./prog arg

tu estás a escrever:

$ make run var=arg

isso fica ainda mais estranho ao tentar passar vários argumentos ou argumentos que contêm espaços:

$ make run var="foo bar\ baz"
./prog foo bar\ baz
argcount: 2
arg: foo
arg: bar baz

comparado a:

$ ./prog foo "bar baz"
argcount: 2
arg: foo
arg: bar baz

para o registro é assim que a minha progaparência:

#! /bin/sh
echo "argcount: $#"
for arg in "$@"; do
  echo "arg: $arg"
done

Observe também que você não deve colocar $(var)aspas no makefile:

run: prog
    ./prog "$(var)"

porque progsempre terá apenas um argumento:

$ make run var="foo bar\ baz"
./prog "foo bar\ baz"
argcount: 1
arg: foo bar\ baz

é por isso que eu recomendo contra essa rota.


para completar, aqui estão alguns outros métodos para "passar argumentos para executar".

Método 1:

run: prog
    ./prog $(filter-out $@, $(MAKECMDGOALS))

%:
    @true

explicação super curta: filtre a meta atual da lista de metas. create catch all target ( %) que não faz nada para ignorar silenciosamente os outros objetivos.

método 2:

ifeq (run, $(firstword $(MAKECMDGOALS)))
  runargs := $(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))
  $(eval $(runargs):;@true)
endif

run:
    ./prog $(runargs)

explicação super curta: se o alvo for run, remova o primeiro objetivo e crie não faça nada para os demais objetivos usando eval.

ambos permitem que você escreva algo parecido com isto

$ make run arg1 arg2

para uma explicação mais aprofundada, estude o manual do make: https://www.gnu.org/software/make/manual/html_node/index.html

problemas do método 1:

  • argumentos que começam com um traço serão interpretados por make e não passados ​​como um objetivo.

    $ make run --foo --bar
    

    Gambiarra

    $ make run -- --foo --bar
    
  • argumentos com um sinal de igual serão interpretados por make e não passados

    $ make run foo=bar
    

    nenhuma solução alternativa

  • argumentos com espaços é estranho

    $ make run foo "bar\ baz"
    

    nenhuma solução alternativa

  • se um argumento for run(igual ao alvo), ele também será removido

    $ make run foo bar run
    

    será executado em ./prog foo barvez de./prog foo bar run

    solução possível com o método 2

  • se um argumento é um alvo legítimo, ele também será executado.

    $ make run foo bar clean
    

    será executado, ./prog foo bar cleanmas também a receita para o destino clean(supondo que ele exista).

    solução possível com o método 2

  • quando você digitar incorretamente um destino legítimo, ele será ignorado silenciosamente por causa da captura de todos os alvos.

    $ make celan
    

    apenas ignorará silenciosamente celan.

    solução alternativa é tornar tudo detalhado. para você ver o que acontece. mas isso cria muito ruído para a saída legítima.

problemas do método 2:

  • se um argumento tiver o mesmo nome que um destino existente, o make imprimirá um aviso de que está sendo substituído.

    nenhuma solução alternativa que eu conheço

  • argumentos com um sinal de igual ainda serão interpretados por make e não serão passados

    nenhuma solução alternativa

  • argumentos com espaços ainda é estranho

    nenhuma solução alternativa

  • argumentos com quebras de espaço evaltentando criar alvos não fazem nada.

    solução alternativa: crie a captura global de todos os destinos, sem fazer nada como acima. com o problema acima, ele novamente ignorará silenciosamente alvos legítimos incorretos.

  • Ele usa evalpara modificar o makefile em tempo de execução. quanto pior você pode ir em termos de legibilidade e depuração e do Princípio de menor espanto .

    solução alternativa: não faça isso !! 1 em vez disso, escreva um script de shell que execute make e, em seguida, execute prog.

Eu só testei usando o gnu make. outras marcas podem ter um comportamento diferente.


TL; DR não tente fazer isso

$ make run arg

em vez disso, crie um script:

#! /bin/sh
# rebuild prog if necessary
make prog
# run prog with some arguments
./prog "$@"

e faça o seguinte:

$ ./buildandrunprog.sh arg
lesmana
fonte
12

Aqui está outra solução que pode ajudar com alguns desses casos de uso:

test-%:
    $(PYTHON) run-tests.py $@

Em outras palavras, escolha um prefixo ( test-nesse caso) e depois passe o nome do destino diretamente para o programa / corredor. Eu acho que isso é útil principalmente se houver algum script de corredor envolvido que possa desembrulhar o nome do destino em algo útil para o programa subjacente.

djc
fonte
2
Você também pode usar $*para passar apenas a parte do destino que correspondeu ao %.
Malvineous
9

Não. Examinar a sintaxe da página de manual do GNU make

make [-f makefile] [opções] ... [destinos] ...

você pode especificar vários destinos, daí 'não' (pelo menos não da maneira exata que você especificou).

ziya
fonte
4

Você pode extrair explicitamente cada n-ésimo argumento na linha de comando. Para fazer isso, você pode usar a variável MAKECMDGOALS, que contém a lista de argumentos de linha de comando fornecidos para 'make', que interpreta como uma lista de destinos. Se você deseja extrair o n-ésimo argumento, pode usar essa variável combinada com a função "word"; por exemplo, se desejar o segundo argumento, pode armazená-lo em uma variável da seguinte maneira:

second_argument := $(word 2, $(MAKECMDGOALS) )
user5122888
fonte
Isso também executa o comando make para esse argumento. make: *** No rule to make target 'arg'. Stop.
ThomasReggi
2

anon , run: ./progparece um pouco estranho, pois a parte certa deve ser um alvo, então run: progfica melhor.

Eu sugeriria simplesmente:

.PHONY: run

run:
        prog $(arg1)

e gostaria de acrescentar que argumentos podem ser passados:

  1. como argumento: make arg1="asdf" run
  2. ou ser definido como ambiente: arg1="asdf" make run
dma_k
fonte
2

Aqui está o meu exemplo. Observe que estou escrevendo no Windows 7, usando o mingw32-make.exe que acompanha o Dev-Cpp. (Eu tenho c: \ Windows \ System32 \ make.bat, então o comando ainda é chamado de "make".)

clean:
    $(RM) $(OBJ) $(BIN) 
    @echo off
    if "${backup}" NEQ "" ( mkdir ${backup} 2> nul && copy * ${backup} )

Uso para limpeza regular:

make clean

Uso para limpar e criar um backup no mydir /:

make clean backup=mydir
osa
fonte
2

Não tenho muito orgulho disso, mas como não queria passar variáveis ​​de ambiente, inverti a maneira de executar um comando fixo:

run:
    @echo command-you-want

isso imprimirá o comando que você deseja executar, então apenas avalie-o em uma subshell:

$(make run) args to my command
Conrad.Dean
fonte
4
olhando para essa resposta dois anos depois - por que eu era tão teimosa que não queria usar variáveis ​​de ambiente e por que achei melhor incluir a geração de outro comando?
Conrad.Dean
0

Eu encontrei uma maneira de obter os argumentos com um sinal de igual (=)! A resposta é especialmente uma adição à resposta do @lesmana (como a mais completa e explicada aqui), mas seria muito grande para ser escrita como um comentário. Mais uma vez, repito sua mensagem: TL; DR não tente fazer isso!

Eu precisava de uma maneira de tratar meu argumento --xyz-enabled=false(já que o padrão é verdadeiro), que todos sabemos até agora que este não é um objetivo de criação e, portanto, não faz parte do$(MAKECMDGOALS) .

Ao olhar para todas as variáveis ​​de make , ecoando o $(.VARIABLES)eu tenho essas saídas interessantes:

[...] -*-command-variables-*- --xyz-enabled [...]

Isso nos permite seguir dois caminhos: começar tudo com uma --(se isso se aplica ao seu caso) ou olhar para o GNU fazer variável específica (provavelmente não pretendida para nós usarmos)-*-command-variables-*- . ** Veja o rodapé para opções adicionais ** No meu caso, essa variável contém:

--xyz-enabled=false

Com esta variável, podemos combiná-la com a solução já existente $(MAKECMDGOALS)e, assim, definindo:

# the other technique to invalidate other targets is still required, see linked post
run:
    @echo ./prog $(-*-command-variables-*-) $(filter-out $@,$(MAKECMDGOALS))`

e usá-lo com (misturando explicitamente a ordem dos argumentos):

make run -- config --xyz-enabled=false over=9000 --foo=bar show  isit=alwaysreversed? --help

devolvida:

./prog isit=alwaysreversed? --foo=bar over=9000 --xyz-enabled=false config show --help

Como você pode ver, perdemos a ordem total dos argumentos. A parte com as "atribuições" -args parece ter sido revertida, a ordem dos "destino" -args é mantida. Eu coloquei a "atribuição" -args no começo, espero que seu programa não se importe onde o argumento é colocado.


Atualização: a seguir, as variáveis ​​também parecem promissoras:

MAKEFLAGS =  -- isit=alwaysreverse? --foo=bar over=9000 --xyz-enabled=false
MAKEOVERRIDES = isit=alwaysreverse? --foo=bar over=9000 --xyz-enabled=false
Dionísio
fonte
-2

Outro truque que uso é a -nbandeira, que diz makepara fazer uma corrida a seco. Por exemplo,

$ make install -n 
# Outputs the string: helm install stable/airflow --name airflow -f values.yaml
$ eval $(make install -n) --dry-run --debug
# Runs: helm install stable/airflow --name airflow -f values.yaml --dry-run --debug
santon
fonte