Passer des arguments pour «faire courir»

354

J'utilise des Makefiles.

J'ai une cible appelée runqui exécute la cible de génération. Simplifié, il ressemble à ceci:

prog: ....
  ...

run: prog
  ./prog

Existe-t-il un moyen de transmettre des arguments? Pour que

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

Merci!

anon
la source

Réponses:

264

Je ne sais pas comment faire exactement ce que vous voulez, mais une solution de contournement pourrait être:

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

Alors:

make ARGS="asdf" run
# or
make run ARGS="asdf"
Jakob Borg
la source
27
@Rob: $ () est plus portable, il fonctionne aussi bien dans Nmake que dans make.
John Knoeller
7
@Rob: Nmake n'a jamais supporté $ {} pour l'expansion des macros, et il semble que ce soit une forme archaïque maintenant dans make. $ () est recommandé par tous les tutoriels en ligne que j'ai consultés. $ () est également plus cohérent avec d'autres outils tels que bash.
John Knoeller
10
C'est peut-être archaïque. J'ai toujours utilisé $ {}, mais le manuel de GNU Make indique "Pour remplacer la valeur d'une variable, écrivez un signe dollar suivi du nom de la variable entre parenthèses ou accolades: soit $(foo)' or $ {foo} 'est une référence valide pour la variable `foo '." et donne des exemples où seul $ () est utilisé. Et bien.
Jakob Borg
7
cheers John et calmh, je suis retourné et j'ai vu que la suggestion venait de ma copie du livre OReilly de la première édition "Managing Projects with Make". L'auteur énonce la règle concernant la substitution d'archives à l'aide de () et de macros capables de faire les deux, mais suggère d'utiliser {} pour distinguer. Mais .... La nouvelle édition est désormais renommée comme "Gestion de projets avec GNU Make". Allez comprendre .... Je suppose que je vais devoir me moderniser! (-: Je suis toujours étonné que MS NMake aboie chez {}.
Rob Wells
1
@xealits C'est sûr - il y a un exemple à la question ici
helvete
198

Cette question a presque trois ans, mais de toute façon ...

Si vous utilisez GNU make, c'est facile à faire. Le seul problème est que makeles arguments sans option de la ligne de commande seront interprétés comme des cibles. La solution est de les transformer en cibles de ne rien faire, alors makene vous plaignez pas:

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

L'exécution de ceci donne:

$ make run foo bar baz
prog foo bar baz
Idélique
la source
2
C'est génial, sauf que cela ne semble pas fonctionner pour les arguments commençant par un tiret:prog foo bar --baz
ingydotnet
22
Il fonctionne dans ce cas aussi, mais vous devez dire makene pas interpréter --bazcomme une option de ligne de commande: make -- prog foo bar --baz. Le --signifie "tout ce qui suit est un argument, pas une option".
Idelic
Comment définir une valeur par défaut pour l' RUN_ARGSutiliser?
Bouke
Peut-être ajouter une elsebranche à la ifeqet RUN_ARGSy mettre ?
Idelic
1
Bon point bleui! Mais il existe une solution pour cela: remplacer la ligne «eval» par $(eval $(RUN_ARGS):dummy;@:), sans aucune cible fictive définie.
Lucas Cimon
52

pour la marque standard, vous pouvez passer des arguments en définissant des macros comme celle-ci

make run arg1=asdf

puis utilisez-les comme ça

run: ./prog $(arg1)
   etc

Références pour faire NMake de Microsoft

John Knoeller
la source
34

Vous pouvez passer la variable au Makefile comme ci-dessous:

run:
    @echo ./prog $$FOO

Usage:

$ 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

Vous pouvez également utiliser la solution fournie par Beta :

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

%:- règle qui correspond à n'importe quel nom de tâche; @:- recette vide = ne rien faire

Usage:

$ make run the dog kicked the cat
./prog the dog kicked the cat
kenorb
la source
23

TL; DR n'essayez pas de faire cela

$ make run arg

créez plutôt un script:

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

et faites ceci:

$ ./buildandrunprog.sh arg

réponse à la question posée:

vous pouvez utiliser une variable dans la recette

run: prog
    ./prog $(var)

puis passez une affectation de variable comme argument pour faire

$ make run var=arg

cela s'exécutera ./prog arg.

mais méfiez-vous des pièges. je développerai les pièges de cette méthode et d'autres méthodes plus bas.


réponse à l'intention supposée derrière la question:

l'hypothèse: vous voulez exécuter progavec quelques arguments mais le faire reconstruire avant de l'exécuter si nécessaire.

la réponse: créer un script qui reconstruit si nécessaire puis exécute prog avec args

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

ce script rend l'intention très claire. il utilise make pour faire ce pour quoi il est bon: construire. il utilise un script shell pour faire ce qu'il est bon: le traitement par lots.

De plus, vous pouvez faire tout ce dont vous pourriez avoir besoin avec toute la flexibilité et l'expressivité d'un script shell sans toutes les mises en garde d'un makefile.

la syntaxe d'appel est désormais pratiquement identique:

$ ./buildandrunprog.sh foo "bar baz"

comparer aux:

$ ./prog foo "bar baz"

contrairement à

$ make run var="foo bar\ baz"

Contexte:

make n'est pas conçu pour transmettre des arguments à une cible. tous les arguments de la ligne de commande sont interprétés soit comme un objectif (alias cible), comme une option, soit comme une affectation de variable.

donc si vous lancez ceci:

$ make run foo --wat var=arg

make interprétera runet foocomme objectifs (cibles) à mettre à jour en fonction de leurs recettes. --waten option pour faire. et var=argcomme une affectation variable.

pour plus de détails, voir: https://www.gnu.org/software/make/manual/html_node/Goals.html#Goals

pour la terminologie, voir: https://www.gnu.org/software/make/manual/html_node/Rule-Introduction.html#Rule-Introduction


sur la méthode d'affectation des variables et pourquoi je la déconseille

$ make run var=arg

et la variable dans la recette

run: prog
    ./prog $(var)

c'est le moyen le plus "correct" et le plus simple de passer des arguments à une recette. mais bien qu'il puisse être utilisé pour exécuter un programme avec des arguments, il n'est certainement pas conçu pour être utilisé de cette façon. voir https://www.gnu.org/software/make/manual/html_node/Overriding.html#Overriding

à mon avis, cela a un gros inconvénient: ce que vous voulez faire, c'est exécuter progavec argument arg. mais au lieu d'écrire:

$ ./prog arg

tu écris:

$ make run var=arg

cela devient encore plus gênant lorsque vous essayez de passer plusieurs arguments ou des arguments contenant des espaces:

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

comparer aux:

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

pour mémoire, voici à quoi je progressemble:

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

notez également que vous ne devez pas mettre $(var)de guillemets dans le makefile:

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

car alors, progil n'y aura toujours qu'un seul argument:

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

c'est pourquoi je déconseille cette route.


pour être complet, voici quelques autres méthodes pour "passer des arguments pour exécuter".

méthode 1:

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

%:
    @true

super courte explication: filtrer l'objectif actuel de la liste des objectifs. create catch all target ( %) qui ne fait rien pour ignorer silencieusement les autres objectifs.

méthode 2:

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

run:
    ./prog $(runargs)

super courte explication: si la cible est runalors supprimez le premier objectif et créez des cibles de ne rien faire pour les objectifs restants en utilisant eval.

les deux vous permettront d'écrire quelque chose comme ça

$ make run arg1 arg2

pour une explication plus approfondie, étudiez le manuel de make: https://www.gnu.org/software/make/manual/html_node/index.html

problèmes de méthode 1:

  • les arguments qui commencent par un tiret seront interprétés par make et ne seront pas transmis comme objectif.

    $ make run --foo --bar
    

    solution de contournement

    $ make run -- --foo --bar
    
  • les arguments avec un signe égal seront interprétés par make et ne seront pas passés

    $ make run foo=bar
    

    pas de solution

  • les arguments avec des espaces sont maladroits

    $ make run foo "bar\ baz"
    

    pas de solution

  • si un argument se trouve être run(égal à la cible), il sera également supprimé

    $ make run foo bar run
    

    fonctionnera ./prog foo barau lieu de./prog foo bar run

    solution possible avec la méthode 2

  • si un argument est une cible légitime, il sera également exécuté.

    $ make run foo bar clean
    

    s'exécutera ./prog foo bar cleanmais aussi la recette de la cible clean(en supposant qu'elle existe).

    solution possible avec la méthode 2

  • lorsque vous saisissez mal une cible légitime, elle est ignorée en silence à cause de la capture de toutes les cibles.

    $ make celan
    

    va simplement ignorer silencieusement celan .

    solution de contournement consiste à tout rendre verbeux. vous voyez donc ce qui se passe. mais cela crée beaucoup de bruit pour la sortie légitime.

problèmes de méthode 2:

  • si un argument a le même nom qu'une cible existante, make affichera un avertissement indiquant qu'il est écrasé.

    aucune solution de contournement que je connaisse

  • les arguments avec un signe égal seront toujours interprétés par make et ne seront pas passés

    pas de solution

  • les arguments avec des espaces sont toujours maladroits

    pas de solution

  • des arguments avec des sauts d'espace evalessayant de créer des cibles de ne rien faire.

    solution: créez la cible globale catch all sans rien faire comme ci-dessus. avec le problème ci-dessus, il ignorera à nouveau silencieusement les cibles légitimes mal tapées.

  • il utilise evalpour modifier le makefile lors de l'exécution. combien pire pouvez-vous aller en termes de lisibilité et de débogage et le principe du moindre étonnement .

    solution de contournement: ne faites pas ça !! 1 au lieu d'écrire un script shell qui exécute make puis s'exécute prog.

j'ai seulement testé en utilisant gnu make. d'autres marques peuvent avoir un comportement différent.


TL; DR n'essayez pas de faire cela

$ make run arg

créez plutôt un script:

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

et faites ceci:

$ ./buildandrunprog.sh arg
lesmana
la source
12

Voici une autre solution qui pourrait vous aider dans certains de ces cas d'utilisation:

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

En d'autres termes, choisissez un préfixe ( test-dans ce cas), puis passez le nom cible directement au programme / runner. Je suppose que cela est surtout utile s'il y a un script de coureur impliqué qui peut déballer le nom cible en quelque chose d'utile pour le programme sous-jacent.

djc
la source
2
Vous pouvez également utiliser $*pour passer uniquement la partie de la cible qui correspond au %.
Malvineous
9

Non. En regardant la syntaxe de la page de manuel de GNU make

make [-f makefile] [options] ... [cibles] ...

vous pouvez spécifier plusieurs cibles, d'où «non» (au moins non de la manière exacte que vous avez spécifiée).

ziya
la source
4

Vous pouvez extraire explicitement chaque nième argument de la ligne de commande. Pour ce faire, vous pouvez utiliser la variable MAKECMDGOALS, elle contient la liste des arguments de ligne de commande donnés à «make», qu'elle interprète comme une liste de cibles. Si vous voulez extraire le nième argument, vous pouvez utiliser cette variable combinée avec la fonction "word", par exemple, si vous voulez le deuxième argument, vous pouvez le stocker dans une variable comme suit:

second_argument := $(word 2, $(MAKECMDGOALS) )
user5122888
la source
Cela exécute également la commande make pour cet argument. make: *** No rule to make target 'arg'. Stop.
ThomasReggi
2

anon , run: ./progsemble un peu étrange, car la partie droite devrait être une cible, doncrun: prog semble mieux.

Je suggérerais simplement:

.PHONY: run

run:
        prog $(arg1)

et je voudrais ajouter que des arguments peuvent être transmis:

  1. comme argument: make arg1="asdf" run
  2. ou être défini comme environnement: arg1="asdf" make run
dma_k
la source
2

Voici mon exemple. Notez que j'écris sous Windows 7, en utilisant mingw32-make.exe fourni avec Dev-Cpp. (J'ai c: \ Windows \ System32 \ make.bat, donc la commande est toujours appelée "make".)

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

Utilisation pour un nettoyage régulier:

make clean

Utilisation pour nettoyer et créer une sauvegarde dans mydir /:

make clean backup=mydir
osa
la source
2

Pas trop fier de cela, mais je ne voulais pas passer dans les variables d'environnement alors j'ai inversé la façon d'exécuter une commande standard:

run:
    @echo command-you-want

cela affichera la commande que vous souhaitez exécuter, alors évaluez-la simplement dans un sous-shell:

$(make run) args to my command
Conrad.Dean
la source
4
en repensant à cette réponse deux ans plus tard - pourquoi étais-je si têtu que je ne voulais pas utiliser de variables d'environnement et pourquoi pensais-je qu'il était préférable d'inclure la génération d'une autre commande?
Conrad.Dean
0

J'ai trouvé un moyen d'obtenir les arguments avec un signe égal (=)! La réponse est surtout un ajout à la réponse de @lesmana (car c'est la plus complète et expliquée ici), mais ce serait trop gros pour l'écrire en commentaire. Encore une fois, je répète son message: TL; DR n'essayez pas de faire ça!

J'avais besoin d'un moyen de traiter mon argument --xyz-enabled=false(car la valeur par défaut est vraie), que nous savons tous maintenant que ce n'est pas une cible de fabrication et donc ne fait pas partie de$(MAKECMDGOALS) .

En examinant toutes les variables de make en faisant écho à la, $(.VARIABLES)j'ai obtenu ces sorties intéressantes:

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

Cela nous permet d'aller de deux façons: soit en commençant par une --(si cela s'applique à votre cas), soit en examinant la variable spécifique à GNU make (probablement pas destinée à être utilisée)-*-command-variables-*- . ** Voir le pied de page pour des options supplémentaires ** Dans mon cas, cette variable contenait:

--xyz-enabled=false

Avec cette variable nous pouvons la combiner avec la solution déjà existante avec $(MAKECMDGOALS)et donc en définissant:

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

et l'utiliser avec (mélanger explicitement l'ordre des arguments):

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

revenu:

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

Comme vous pouvez le voir, nous perdons l'ordre total des arguments. La partie avec les arguments "affectation" semble avoir été inversée, l'ordre des arguments "cible" est conservé. J'ai placé les "affectations" -args au début, j'espère que votre programme ne se soucie pas de l'emplacement de l'argument.


Mise à jour: les variables suivantes sont également prometteuses:

MAKEFLAGS =  -- isit=alwaysreverse? --foo=bar over=9000 --xyz-enabled=false
MAKEOVERRIDES = isit=alwaysreverse? --foo=bar over=9000 --xyz-enabled=false
Denys
la source
-2

Une autre astuce que j'utilise est le -ndrapeau, qui indique makede faire un run à sec. Par exemple,

$ 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
la source