Passing arguments to "make run"
Asked Answered
D

16

525

I use Makefiles.

I have a target called run which runs the build target. Simplified, it looks like the following:

prog: ....
  ...

run: prog
  ./prog

Is there any way to pass arguments? So that

make run asdf --> ./prog asdf
make run the dog kicked the cat --> ./prog the dog kicked the cat
Dunseath answered 6/2, 2010 at 20:16 Comment(1)
Similar: How to pass argument to Makefile from command line?Olibanum
R
430

I don't know a way to do what you want exactly, but a workaround might be:

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

Then:

make ARGS="asdf" run
# or
make run ARGS="asdf"
Ripleigh answered 6/2, 2010 at 20:26 Comment(8)
@Rob: $() is more portable, it works in Nmake as well as make.Cicatrix
@John, it may be more portable with newer implementations of make but it is sometimes not backwards compatible. Surely even MS didn't break existing syntax.Noto
@Rob: Nmake has never supported ${} for macro expansion, and it appears to be an archaic form now in make. $() is recommended by every online tutorial I've looked at. $() is also more consistent with other tools such as bash.Cicatrix
Maybe it is archaic. I've always used ${}, but the manual for GNU Make states "To substitute a variable's value, write a dollar sign followed by the name of the variable in parentheses or braces: either $(foo)' or ${foo}' is a valid reference to the variable `foo'." and proceeds to give examples where only $() is used. Ah well.Ripleigh
cheers John and calmh, I went back and saw that the suggestion came from my copy of the first edition OReilly book "Managing Projects with Make". The author states the rule about archive substitution using ()'s and macros able to do both but suggests using {}'s to distinguish. But.... The new edition now retitled as "Managing Projects with GNU Make" uses ()'s throughout. Go figure.... Guess I'll have to modernise! (-: I'm still amazed that MS NMake barfs at {}'s though.Noto
I wonder if there is a way to set a default value for such an argument?Gosling
@Gosling It sure is - there is an example at the the question hereCelebes
I'd be surprised if anyone could write portable makefiles of any complexity that work with both nmake and POSIX make. nmake is not really very close to POSIX compliant so I don't see much point in restricting use to $() to try to gain portability to nmake. All POSIX implementations of make, since the original in the 1970's, support both $() and ${} and they are exactly identical in every way. There is not and has never been any recommendation that one is better than the other. Typically you choose ${} to disambiguate if the content contains () chars.Primo
C
309

This question is almost three years old, but anyway...

If you're using GNU make, this is easy to do. The only problem is that make will interpret non-option arguments in the command line as targets. The solution is to turn them into do-nothing targets, so make won't complain:

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

Running this gives:

$ make run foo bar baz
prog foo bar baz
Camenae answered 27/12, 2012 at 21:54 Comment(13)
This is great except it doesn't seem to work for arguments beginning with a dash: prog foo bar --bazSushi
It does work in that case too, but you have to tell make not to interpret --baz as a command line option: make -- prog foo bar --baz. The -- means "everything after this is an argument, not an option".Camenae
Turning existing targets into "do-nothing targets" will still run any dependencies of the original targets, i.e. if there is a "test: foo" target already, "make run pip install test" will still run the "foo" target.Ledda
Good point blueyed ! But there is a solution for that: replace the 'eval' line by $(eval $(RUN_ARGS):dummy;@:), whith no dummy target defined.Beckon
Another issue: it won't handle args with spaces, e.g. make prog -- vim -c 'echom foo' will result in 4 arguments, not just 3. The workaround is to escape the space: make prog -- vim -c echom\ foo.Ledda
But it's not possible (or at least nice) to pass in quotes, as in make prog -- vim -c echom\ '"foo"'.Ledda
If arguments contain spaces, escaping them doesn't work for me. Instead I need to use double and single quotes like "'double and single quote'"Greenhead
This is a great way to forward all arguments provided to 'make' to another program. First of all, the $(eval line isn't needed. Secondly, add a do-nothing rule. .PHONY: run run: @some-command $(RUN_ARGS) %: @: Greenhead
@Camenae Is it possible to have multiple targets in ifeq? Something along these statement. ifeq (target1|target2,$(firstword $(MAKECMDGOALS)))Reviewer
@LukaszDynowski yes, you could replace that by something like ifneq ($(findstring $(firstword $(MAKECMDGOALS)),$(TARGETS)),).Camenae
One thing I don't understand is the @: I get the $(RUN_ARGS):; which just creates an empty target, but what is the need for the @:?Corunna
I don't understant this line: $(eval $(RUN_ARGS):;@:), can someone kindly explain it to me?Feint
Note: When I copied this example and tried it, it did not work for me at first try, as the copy replaced tabs with spaces. so I got " **** missing separator " error. make sure you have tabs at start of each recipeHaileyhailfellowwellmet
I
92

TL;DR don't try to do this

$ make run arg

instead create script build_and_run_prog.sh:

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

and do this:

$ ./build_and_run_prog.sh arg

Read on for some explanation of why this is the most reasonable choice and why the other alternatives are best avoided


Answer to the stated question: how to pass arguments to a make target

you can use a variable in the recipe

run: prog
    ./prog $(var)

then pass a variable assignment as an argument to make

$ make run var=arg

this will execute ./prog arg.

this is the most correct and straightforward way to "pass arguments to a recipe". see the gnu make manual on overriding variables

but while it can be used to run a program with arguments it is certainly not meant to be used that way.

Let me elaborate on some problems

what you want to do is run prog with argument arg. but instead of writing:

$ ./prog arg

you need to write:

$ make run var=arg

this gets even more awkward when trying to pass multiple arguments or arguments containing spaces.

instead of writing

$ ./prog foo "bar baz"

you need to write

$ make run var="foo bar\ baz"

or

$ make run var="foo \"bar baz\""

i hope you see how this will get quite awkward for anything but the simplest arguments.

also note that you should not put $(var) in quotes in the makefile:

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

because then prog will always get just one argument.


Answer to the assumed intention behind your question: You want to run prog with some arguments but have it rebuild before running if necessary.

Create a script which rebuilds if necessary then runs prog with args

build_and_run_prog.sh:

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

This script makes the intention very clear. It uses make to do what it is good for: building/compiling. It uses a shell script to do what it is good for: batch processing.

Plus you can do whatever else you might need with the full flexibility and expressiveness of a shell script without all the caveats of a makefile.

Also the calling syntax is now practically identical:

$ ./build_and_run_prog.sh foo "bar baz"

compared to:

$ ./prog foo "bar baz"

contrast to

$ make run var="foo bar\ baz"

Background explanation of how make handles arguments:

Make is not designed to pass arguments to a target. All arguments on the command line are interpreted either as a goal (a.k.a. target), as an option, or as a variable assignment.

so if you run this:

$ make run foo --wat var=arg

make will interpret run and foo as goals (targets) to update according to their recipes. --wat as an option for make. And var=arg as a variable assignment for make.

i hope you can see the only method you have to pass information from the command line to use inside a recipe (without hacks) is via variable assignment.

for more details see the gnu manual on how to run make


For completeness here are some of the hacks to "pass arguments to make run".

Method 1:

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

%:
    @true

super short explanation: filter out current goal from list of goals. then pass list of goals as arguments to prog. also create a catch all target (%) which does nothing to silently ignore all the other "goals".

this will allow you to write something like this

$ make run arg1 arg2

problems of method 1:

  • Arguments that start with a dash will be interpreted by make and not passed as a goal.

      $ make run --foo --bar
    

    workaround

      $ make run -- --foo --bar
    
  • Arguments with an equal sign will be interpreted by make and not passed

      $ make run foo=bar
    

    no workaround

  • Arguments with spaces is awkward

      $ make run foo "bar\ baz"
    

    no workaround

  • If an argument happens to be run (equal to the target) it will also be removed

      $ make run foo bar run
    

    will run ./prog foo bar instead of ./prog foo bar run

    workaround possible with method 2

  • If an argument is a legitimate target it will also be run.

      $ make run foo bar clean
    

    will run ./prog foo bar clean but also the recipe for the target clean (assuming it exists).

    workaround possible with method 2

  • When you mistype a legitimate target it will be silently ignored because of the catch all target.

      $ make celan
    

    will just silently ignore celan.

    workaround is to make everything verbose. so you see what happens. but that creates a lot of noise for the legitimate output.

Method 2:

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

run:
    ./prog $(runargs)

super short explanation: if the target is run then process the goal list and save in variable. also create do nothing targets for the remaining "goals" using eval. later when running prog pass the prepared variable as arguments.

problems of method 2:

  • If an argument has same name as an existing target then make will print a warning that it is being overwritten.

    no workaround that I know of

  • Arguments with an equal sign will still be interpreted by make and not passed

    no workaround

  • Arguments with spaces is still awkward

    no workaround

  • Arguments with space breaks eval trying to create do nothing targets.

    workaround: create the global catch all target doing nothing as above. with the problem as above that it will again silently ignore mistyped legitimate targets.

  • it uses eval to modify the makefile at runtime. how much worse can you go in terms of readability and debugability and the Principle of least astonishment.

    workaround: don't!

I have only tested using gnu make. other makes may have different behaviour.


gnu make manual

https://www.gnu.org/software/make/manual/html_node/index.html

Inculcate answered 10/7, 2017 at 2:51 Comment(0)
C
87

for standard make you can pass arguments by defining macros like this

make run arg1=asdf

then use them like this

run: ./prog $(arg1)
   etc

References for make Microsoft's NMake

Cicatrix answered 6/2, 2010 at 20:26 Comment(4)
What's a macro? Isn't that what people call environment variables? What's the difference between a macro and an environment variable?Amberambergris
@Amberambergris - According to "define macro" on Google, a macro is "a single instruction that expands automatically into a set of instructions to perform a particular task." The top result on a search for "what is an environment variable" is this: "An environment variable is a dynamic-named value that can affect the way running processes will behave on a computer. They are part of the environment in which a process runs."Corrode
Every time I hear "macro" it remembers me of microsoft excel "You want to enable macros?" and I still don't have a clear understanding of what that mean. I try to avoid this word, doesn't "variable" is apropriate enough?Amberambergris
@KcFnMi, your intuition is correct, this is not a macro in the typical usage of the word. The confusion might arise because it is somewhat similar to a subset of C "macros", as in #define ARG1 asdf. Although I would call this a compile-time constant, a "pound define", or something similar because it isn't really a macro (since it only expands to a single constant as opposed to a statement/set of statements).Infundibulum
O
49

You can pass the variable to the Makefile like below:

run:
    @echo ./prog $$FOO

Usage:

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

or:

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

Alternatively use the solution provided by Beta:

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

%: - rule which match any task name; @: - empty recipe = do nothing

Usage:

$ make run the dog kicked the cat
./prog the dog kicked the cat
Olibanum answered 9/9, 2015 at 23:44 Comment(1)
Very elegant solution! Thanks!!Underglaze
B
26

Here's another solution that could help with some of these use cases:

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

In other words, pick some prefix (test- in this case), and then pass the target name directly to the program/runner. I guess this is mostly useful if there is some runner script involved that can unwrap the target name into something useful for the underlying program.

Botts answered 29/7, 2015 at 11:11 Comment(1)
You can also use $* to pass in just the part of the target that matched the %.Norenenorfleet
Q
11

No. Looking at the syntax from the man page for GNU make

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

you can specify multiple targets, hence 'no' (at least no in the exact way you specified).

Quirinal answered 6/2, 2010 at 20:32 Comment(0)
E
9

You can explicitly extract each n-th argument in the command line. To do this, you can use variable MAKECMDGOALS, it holds the list of command line arguments given to 'make', which it interprets as a list of targets. If you want to extract n-th argument, you can use that variable combined with the "word" function, for instance, if you want the second argument, you can store it in a variable as follows:

second_argument := $(word 2, $(MAKECMDGOALS) )
Ergener answered 3/2, 2016 at 20:31 Comment(3)
This also runs the make command for that argument. make: *** No rule to make target 'arg'. Stop.Scriabin
@Scriabin You can add a "catch all" target the supress the error: ``` %: @echo Done ```Demodulate
See also the "wordlist" function if you want a range of words instead : gnu.org/software/make/manual/html_node/Text-Functions.htmlSse
G
8

run: ./prog looks a bit strange, as right part should be a prerequisite, so run: prog looks better.

I would suggest simply:

.PHONY: run

run:
    prog $(arg1)

and I would like to add, that arguments can be passed:

  1. as argument: make arg1="asdf" run
  2. or be defined as environment: arg1="asdf" make run
Grassquit answered 6/2, 2010 at 21:25 Comment(3)
Side question, I see .PHONY: {command} everywhere, what does it mean?Skillful
@DylanPierce Check this answer. In two words: the leftside is a target, typically a file. .PHONY disables the timestamp checking for the file, as otherwise if you create an empty run file, the target commands will not execute.Grassquit
Thanks @dma_k, I didn't realize Makefiles command were so file based, I thought they were implicitly actionsSkillful
D
5

Not too proud of this, but I didn't want to pass in environment variables so I inverted the way to run a canned command:

run:
    @echo command-you-want

this will print the command you want to run, so just evaluate it in a subshell:

$(make run) args to my command
Deemphasize answered 28/7, 2016 at 15:21 Comment(2)
looking back at this answer two years later -- why was I ever so stubborn that I didn't want to use environment variables and why did I think inlining the generation of another command was better?Deemphasize
my comment on your "looking back" above: in my use case, I definitely don't want to consider env vars, as I use make deploy version=<version> to deploy microservices into the Cloud. I wouldn't want anyone to overwrite a previous working version by mistake, discovering too late that they had to edit an env var...Hypothermal
S
2

Here is my example. Note that I am writing under Windows 7, using mingw32-make.exe that comes with Dev-Cpp. (I have c:\Windows\System32\make.bat, so the command is still called "make".)

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

Usage for regular cleaning:

make clean

Usage for cleaning and creating a backup in mydir/:

make clean backup=mydir
Sanford answered 3/7, 2013 at 19:50 Comment(0)
C
1

Building a bit on idelic answer.
You can create a generic "function" to fetch parameters using the following:

define fetch_parameter
    $(eval target_name:= $(firstword $(MAKECMDGOALS)))
    $(eval varname := $(target_name)_value)
    $(eval $(varname) := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)))
    $(eval $($(varname))::;@:)
endef

You can then user it in your target like so:

my-target: res := $(call fetch_parameter)
my-target: ## Example target. Usage: make my-target <value>
    echo The value: $($@_value)

This way you can just use res := $(call fetch_parameter) on any target you want to retrieve values from.

Note: I've added an extra : on this bit of code $(eval $($(varname))::;@:) because if you have more than one target with a call to fetch_parameter it will also be triggered.
So if you have:

my-target: res := $(call fetch_parameter)
my-target: ## Example target. Usage: make my-target <value>
    echo The value: $($@_value)

my-target2: res := $(call fetch_parameter)
my-target2: ## Example target. Usage: make my-target2 <value>
    echo The value: $($@_value)

and you call make my-target2 hello, the way Make works, both $(call fetch_parameter) will be triggered causing the creation of 2 bogus hello targets, but with the extra : (hello::) Make won't complain that you are overriding a target.

Corunna answered 1/10, 2022 at 10:16 Comment(0)
T
0

I found a way to get the arguments with an equal sign =! The answer is especially an addition to @lesmana 's answer (as it is the most complete and explained one here), but it would be too big to write it as a comment. Again, I repeat his message: TL;DR don't try to do this!

I needed a way to treat my argument --xyz-enabled=false (since the default is true), which we all know by now that this is not a make target and thus not part of $(MAKECMDGOALS).

While looking into all variables of make by echoing the $(.VARIABLES) i got these interesting outputs:

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

This allows us to go two ways: either getting all starting with a -- (if that applies to your case), or look into the GNU make specific (probably not intended for us to use) variable -*-command-variables-*-. ** See footer for additional options ** In my case this variable held:

--xyz-enabled=false

With this variable we can combine it with the already existing solution with $(MAKECMDGOALS) and thus by defining:

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

and using it with (explicitly mixing up order of arguments):

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

returned:

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

As you can see, we loose the total order of the args. The part with the "assignment"-args seem to have been reversed, the order of the "target"-args are kept. I placed the "assignment"-args in the beginning, hopefully your program doesn't care where the argument is placed.


following make variables looks promising as well:

MAKEFLAGS =  -- isit=alwaysreverse? --foo=bar over=9000 --xyz-enabled=false
MAKEOVERRIDES = isit=alwaysreverse? --foo=bar over=9000 --xyz-enabled=false
Tomlinson answered 28/4, 2020 at 13:31 Comment(1)
alas this variable appears to have disappeared in latest gnu Make (4.4)Align
A
0

It's been for a while, but I'll provide mine version which I use in production.

I'll hope that someone will find it useful.

Example:

.PHONY: greeting
greeting:
    echo $(if $s,$s,)

Command:

make greeting s="hello world"

Output:

hello world

Anesthesiology answered 1/9, 2022 at 16:52 Comment(0)
E
0

My usual Makefile

SHELL := /bin/bash 
args := $(filter-out $@,$(MAKECMDGOALS))
Command := $(word 1,$(args))
arg1 := $(word 2,$(args))
arg2 := $(word 3,$(args))
arg3 := $(word 4,$(args))
Eichhorn answered 12/12, 2023 at 9:21 Comment(0)
S
-3

Another trick I use is the -n flag, which tells make to do a dry run. For example,

$ 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
Subalpine answered 28/2, 2019 at 18:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.