Passing arguments to "make run"

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 
1

13 Answers

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" 
8

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 
10

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

4

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.

But beware of pitfalls. I will elaborate about the pitfalls of this method and others further below.


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. 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 more details see the gnu manual on goals (targets):

and the terminology.


Why I recommend against variable assignment

$ make run var=arg 

and the variable in the recipe

run: prog ./prog $(var) 

This is the most "correct" and straightforward way to pass arguments to a recipe. but while it can be used to run a program with arguments it is certainly not designed to be used that way. See the gnu manual on overriding

In my opinion this has one big disadvantage: what you want to do is run prog with argument arg. but instead of writing:

$ ./prog arg 

you are writing:

$ make run var=arg 

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

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

compare to:

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

for the record this is what my prog looks like:

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

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:

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

All this is why I recommend against this route.


For completeness here are some other methods to "pass arguments to make run".

Method 1:

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

filter out current goal from list of goals. create catch all target (%) which does nothing to silently ignore the other goals.

Method 2:

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

if the target is run then remove the first goal and create do nothing targets for the remaining goals using eval.

both will allow you to write something like this

$ make run arg1 arg2 

I recommend reading the gnu manual on make for further details.

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.

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.

0

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 
0

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.

1

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

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

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
3

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

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 

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 

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like