196

How to pass argument to Makefile from command line?

I understand I can do

$ make action VAR="value"
$ value

with Makefile

VAR = "default"
action:
    @echo $(VAR)

How do I get the following behavior?

$ make action value
value

How about

$make action value1 value2
value1 value2
2

7 Answers 7

292

You probably shouldn't do this; you're breaking the basic pattern of how Make works. But here it is:

action:
        @echo action $(filter-out $@,$(MAKECMDGOALS))

%:      # thanks to chakrit
    @:    # thanks to William Pursell

EDIT:
To explain the first command,

$(MAKECMDGOALS) is the list of "targets" spelled out on the command line, e.g. "action value1 value2".

$@ is an automatic variable for the name of the target of the rule, in this case "action".

filter-out is a function that removes some elements from a list. So $(filter-out bar, foo bar baz) returns foo baz (it can be more subtle, but we don't need subtlety here).

Put these together and $(filter-out $@,$(MAKECMDGOALS)) returns the list of targets specified on the command line other than "action", which might be "value1 value2".

13
  • 3
    $(shell echo $(MAKECMDGOALS) | sed 's!^.* $@ !!') to omit all targets before and just consider the following as arguments: make target1 target2 action value1 value2 Commented May 14, 2014 at 13:51
  • 4
    Pardon my ignorance. I've tried googling %: and @: and cannot find info on what those "directives" (or whatever they're called) do. Could you please explain?
    – Jon
    Commented Sep 9, 2014 at 17:38
  • 31
    @Jon: The manual is here. The part consisting of %: and @: is a rule. The target name % means that it is a rule that matches anything; that is, if Make can't find any other way to build the thing you tell it to build, it will execute that rule. The @: is a recipe; the : means do nothing, and the @ means do it silently.
    – Beta
    Commented Sep 9, 2014 at 22:48
  • 2
    filter-out doesn't work when the action is a dependency of the target specified on the command line, because $@ will be set to the dependency's name, not the original argument called on the command line. Instead, I assign MAKECMDGOALS to a shell array and then remove the first element: @ args=($(MAKECMDGOALS)); args=("$${args[@]:1}")
    – Gingi
    Commented Aug 4, 2015 at 16:18
  • 3
    "you're breaking the basic pattern of how Make works" - what do you mean by this? And what is the "correct" pattern? Commented Mar 15, 2022 at 15:20
42

Here is a generic working solution based on @Beta's

I'm using GNU Make 4.1 with SHELL=/bin/bash atop my Makefile, so YMMV!

This allows us to accept extra arguments (by doing nothing when we get a job that doesn't match, rather than throwing an error).

%:
    @:

And this is a macro which gets the args for us:

args = `arg="$(filter-out $@,$(MAKECMDGOALS))" && echo $${arg:-${1}}`

Here is a job which might call this one:

test:
    @echo $(call args,defaultstring)

The result would be:

$ make test
defaultstring
$ make test hi
hi

Note! You might be better off using a "Taskfile", which is a bash pattern that works similarly to make, only without the nuances of Maketools. See https://github.com/adriancooney/Taskfile

5
  • 2
    It worked!! TO other people trying it out, make sure there is tab before @echo, not space. Commented Feb 27, 2019 at 15:56
  • This also works if the action (e.g., test:) is a dependency of the target specified on the command line. Commented Nov 25, 2020 at 9:55
  • 2
    This will execute the target hi if this target exist in the Makefile. Any idea how to avoid this?
    – oz123
    Commented Jul 9, 2021 at 23:38
  • @oz123 You might want to look into using environment variables, rather than positional arguments. You could also look at using a scripting language for this. As mentioned, bash has some useful patterns for this.
    – M3D
    Commented Jul 16, 2021 at 2:23
  • @M3D This only worked for me when I did: make test args=hi - am I being thick?
    – jtlz2
    Commented Jun 6, 2023 at 19:45
27

Much easier aproach. Consider a task:

provision:
        ansible-playbook -vvvv \
        -i .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory \
        --private-key=.vagrant/machines/default/virtualbox/private_key \
        --start-at-task="$(AT)" \
        -u vagrant playbook.yml

Now when I want to call it I just run something like:

AT="build assets" make provision

or just:

make provision in this case AT is an empty string

12

Few years later, want to suggest just for this: https://github.com/casey/just

action v1 v2=default:
    @echo 'take action on {{v1}} and {{v2}}...'
6

You will be better of defining variables and calling your make instead of using parameters:

Makefile

action: ## My action helper
    @echo $$VAR_NAME

Terminal

> VAR_NAME="Hello World" make action
Hello World
2

don't try to do this

$ make action value1 value2

instead create script:

#! /bin/sh
# rebuild if necessary
make
# do action with arguments
action "$@"

and do this:

$ ./buildthenaction.sh value1 value2

for more explanation why do this and caveats of makefile hackery read my answer to another very similar but seemingly not duplicate question: Passing arguments to "make run"

0

Instead of hacking your way to pass arguments because Make won't support this feature by default, a more recommended approach is to use environment variables.

Instead of doing this,

$ make action VAR="value"
$ value

Do this,

$ export VAR=value && make action
$ value

action:
    @echo $(VAR)

Not the answer you're looking for? Browse other questions tagged or ask your own question.