182

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
Asclepius
  • 57,944
  • 17
  • 167
  • 143
Meng Lu
  • 13,726
  • 12
  • 39
  • 47
  • 4
    Similar: [Passing arguments to “make run”](http://stackoverflow.com/q/2214575/55075) – kenorb Sep 09 '15 at 23:33
  • 3
    Possible duplicate of [Passing arguments to "make run"](https://stackoverflow.com/questions/2214575/passing-arguments-to-make-run) – Lesmana Jul 08 '17 at 22:09

6 Answers6

284

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

Lesmana
  • 25,663
  • 9
  • 82
  • 87
Beta
  • 96,650
  • 16
  • 149
  • 150
  • 2
    `$(shell echo $(MAKECMDGOALS) | sed 's!^.* $@ !!')` to omit all targets before and just consider the following as arguments: `make target1 target2 action value1 value2` – Evgeniy Generalov May 14 '14 at 13:51
  • 3
    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 Sep 09 '14 at 17:38
  • 29
    @Jon: The manual is [here](http://www.gnu.org/software/make/manual/make.html). The part consisting of `%:` and `@:` is a [rule](http://www.gnu.org/software/make/manual/make.html#Rules). The target name `%` means that it is [a rule that matches anything](http://www.gnu.org/software/make/manual/make.html#Match_002dAnything-Rules); 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](http://www.gnu.org/software/make/manual/make.html#Rule-Syntax); the `:` means do nothing, and the `@` means do it silently. – Beta Sep 09 '14 at 22:48
  • 1
    Thanks. I had been reading that manual and I didn't consider at first, that `%:` was actually `% :`, a wildcard type target name. I don't see anything in that manual page regarding `@:` though... it does suggest that a "do-nothing" rule would simply have a `;` after the target specification, so, would it not be more accurate to write `% : ;` as the "wildcard do-nothing" rule? – Jon Sep 10 '14 at 13:55
  • 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 Aug 04 '15 at 16:18
  • fwiw, ":" is a synonym of "true" command, which is likely less cryptic. ":" isn't used often anymore, and brevity likely isn't worth it...? – patcon Mar 14 '16 at 04:45
  • @Beta using your solution (awesome, wow)--do you know how to suppress the output at the end of execution that says, `make: 'input1' is up to date` for the various inputs? – Chris Mar 29 '17 at 01:10
  • @Beta this got me 90% of the way but if my arg is "le creuset" it will split on the space despite being wrapped in ". Is there a known workaround here? – Sion Mar 06 '18 at 22:38
  • 2
    "you're breaking the basic pattern of how Make works" - what do you mean by this? And what is the "correct" pattern? – Big McLargeHuge Mar 15 '22 at 15:20
  • This works for me but it outputs `make: *** No rule to make target 'value'. Stop.`. How to prevent this? – abc123 May 02 '22 at 19:59
  • If the argument is something like `make foo bar -- --param` this works fine, it resolves to `bar --param` -- but if we try to do `make foo bar -- --param=10` to pass in a value, for some reason it is just `bar` with `--param=10` stripped out. Anyone have any ideas? – andrew.welch Dec 15 '22 at 21:18
  • Aha, got it -- if you require that flags be passed down as well, you'd want: `$(filter-out $@,$(MAKECMDGOALS)) $(MAKEFLAGS)` – andrew.welch Dec 15 '22 at 21:44
36

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

M3D
  • 673
  • 6
  • 11
  • 2
    It worked!! TO other people trying it out, make sure there is `tab` before `@echo`, not `space`. – Abdullah Al Maruf - Tuhin Feb 27 '19 at 15:56
  • This also works if the action (e.g., test:) is a dependency of the target specified on the command line. – SergiyKolesnikov Nov 25 '20 at 09:55
  • 2
    This will execute the target `hi` if this target exist in the Makefile. Any idea how to avoid this? – oz123 Jul 09 '21 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 Jul 16 '21 at 02:23
  • @M3D This only worked for me when I did: `make test args=hi` - am I being thick? – jtlz2 Jun 06 '23 at 19:45
24

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

kharandziuk
  • 12,020
  • 17
  • 63
  • 121
8

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}}...'
Alex Hirzel
  • 1,719
  • 2
  • 12
  • 18
3

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"

Lesmana
  • 25,663
  • 9
  • 82
  • 87