Passing arguments to "make run"
MakefileMakefile Problem Overview
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
Makefile Solutions
Solution 1 - Makefile
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"
Solution 2 - Makefile
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
Solution 3 - Makefile
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 http://www.opussoftware.com/quickref/MakeCmdLine.htm">make</a> Microsoft's NMakehttps://docs.microsoft.com/en-us/cpp/build/reference/nmake-reference?view=msvc-160">NMake</a>
Solution 4 - Makefile
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 targetclean
(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.
Solution 5 - Makefile
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
Solution 6 - Makefile
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.
Solution 7 - Makefile
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).
Solution 8 - Makefile
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:
- as argument:
make arg1="asdf" run
- or be defined as environment:
arg1="asdf" make run
Solution 9 - Makefile
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) )
Solution 10 - Makefile
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
Solution 11 - Makefile
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
Solution 12 - Makefile
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
Solution 13 - Makefile
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