212

I would like to change this Makefile:

SHELL := /bin/bash
PATH  := node_modules/.bin:$(PATH)

boot:
    @supervisor         \
      --harmony         \
      --watch etc,lib       \
      --extensions js,json      \
      --no-restart-on error     \
        lib

test:
    NODE_ENV=test mocha         \
      --harmony             \
      --reporter spec       \
        test

clean:
    @rm -rf node_modules

.PHONY: test clean

to:

SHELL := /bin/bash
PATH  := node_modules/.bin:$(PATH)

boot:
    @supervisor         \
      --harmony         \
      --watch etc,lib       \
      --extensions js,json      \
      --no-restart-on error     \
        lib

test: NODE_ENV=test
test:
    mocha                   \
      --harmony             \
      --reporter spec       \
        test

clean:
    @rm -rf node_modules

.PHONY: test clean

Unfortunately the second one does not work (the node process still runs with the default NODE_ENV.

What did I miss?

Victor Sergienko
  • 13,115
  • 3
  • 57
  • 91
bodokaiser
  • 15,122
  • 22
  • 97
  • 140
  • Your `Unfortunately` comment stems from a misunderstanding between an environment variable versus a `Makefile` variable. The best way to prove that an environment variable has been set, is to query this environment variable inside another program that `make` wil call. Only doing `echo $(BLAH)` is merely evaluating Makefile's key/value mechanism inside the Makefile. In python, you can `print(os.getenv("MURDOC"))` to truly query the environment variable. – daparic Aug 04 '20 at 12:28

4 Answers4

220

Make variables are not exported into the environment of processes make invokes... by default. However you can use make's export to force them to do so. Change:

test: NODE_ENV = test

to this:

test: export NODE_ENV = test

(assuming you have a sufficiently modern version of GNU make >= 3.77 ).

rado
  • 4,040
  • 3
  • 32
  • 26
MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • 5
    I have GNU make 3.81, and `all: <\n\t>export PROJ_ROOT=$(CURDIR)<\n\t>echo $(PROJ_ROOT)<\n>` outputs the correct expansion for the first row, but only `echo` for the second one. `PROJ_ROOT` is not set after running make. Spaces around `=` give "bad variable name" for export. Having the first row as prerequisite as in your example gives "commands commence before first target" – Gauthier Oct 30 '14 at 09:18
  • 11
    @Gauthier yes of course. That's not what I wrote. You added a <\n\t> after the `all:`, which is not in my example. My example is intended to be used as written: it's defining a target-specific variable, _NOT_ adding a command to the recipe. Also you cannot use a recipe and a target-specific variable on a target at the same time: you have to write the target twice. See the second example in the question, and ask a new question if this doesn't help explain it: there's not enough space or formatting in comments. – MadScientist Oct 30 '14 at 12:00
  • 2
    What about multiple variables? – holms Sep 03 '18 at 13:14
  • Each variable has to be set on its own separate line in the makefile. – MadScientist Sep 03 '18 at 14:29
  • 1
    That's fine but your added it to the head of target. Just listing them in context of target won't work? Because it doesn't work for me – holms Sep 11 '18 at 13:14
  • Sorry but I don't understand what you mean. Probably you should file a new question rather than asking in the comments here. – MadScientist Sep 11 '18 at 14:02
  • How modern is modern? – jjmerelo Nov 18 '18 at 17:28
  • 2
    Target-specific variables were added in GNU make 3.77. They are exportable as of GNU make 3.81. See http://git.savannah.gnu.org/cgit/make.git/tree/NEWS – MadScientist Nov 18 '18 at 17:38
198

As MadScientist pointed out, you can export individual variables with:

export MY_VAR = foo  # Available for all targets

Or export variables for a specific target (target-specific variables):

my-target: export MY_VAR_1 = foo
my-target: export MY_VAR_2 = bar
my-target: export MY_VAR_3 = baz

my-target: dependency_1 dependency_2
  echo do something

You can also specify the .EXPORT_ALL_VARIABLES target to—you guessed it!—EXPORT ALL THE THINGS!!!:

.EXPORT_ALL_VARIABLES:

MY_VAR_1 = foo
MY_VAR_2 = bar
MY_VAR_3 = baz

test:
  @echo $$MY_VAR_1 $$MY_VAR_2 $$MY_VAR_3

see .EXPORT_ALL_VARIABLES

Shammel Lee
  • 4,092
  • 1
  • 17
  • 20
  • 2
    Oddly enough I did test it earlier that showed it worked.. (not sure why now..) I can go back and delete the comment I guess.. – AnthonyC Aug 22 '18 at 05:53
  • 3
    @AnthonyC It works because there are two `MY_VAR`s: one is makefile variable accessed as `${MY_VAR}` and another one is bash exported variable, accessed as `$$MY_VAR` – Sergei Mar 19 '19 at 07:48
  • Useful. But cannot find a way to export only a set of variables. – Eric Chen Jun 27 '19 at 22:29
  • 2
    Note if the variable already exists in your makefile and you just want to export it to another command, you can use the syntax `export var ?=` without assigning a new value – smac89 May 01 '21 at 18:47
  • 3
    .EXPORT_ALL_VARIABLES is right answer – Ken May 05 '22 at 02:38
  • Does exporting in a target pass that variable to it's dependencies as well? In your example; does dependency_1 and dependency_2 have MY_VAR_1 etc. in their environment? – Jerinaw Mar 27 '23 at 17:53
  • `.EXPORT_ALL_VARIABLES` is "right answer" only if we ignore the fact the question most likely demonstrates a case of [XY problem](http://xyproblem.info) and, more importantly, that exporting _every environment and Make variable_ (yes, both kinds) to every process invoked by Make as part of `make`, is arguably a recipe (no pun intended) for disaster, disaster here loosely meaning subtly "broken" builds that cannot be easily proven (should `$FOOBAR` value be the same for every command?). There's a reason `.EXPORT_ALL_VARIABLES` isn't default. It's the right answer alright, to the wrong question. – Armen Michaeli Apr 25 '23 at 14:22
23

I only needed the environment variables locally to invoke my test command, here's an example setting multiple environment vars in a bash shell, and escaping the dollar sign in make.

SHELL := /bin/bash

.PHONY: test tests
test tests:
    PATH=./node_modules/.bin/:$$PATH \
    JSCOVERAGE=1 \
    nodeunit tests/
0 _
  • 10,524
  • 11
  • 77
  • 109
ThorSummoner
  • 16,657
  • 15
  • 135
  • 147
  • 8
    Please edit your answer to include some explanation. Code-only answers do very little to educate future SO readers. Your answer is in the moderation queue for being low-quality. – mickmackusa Apr 22 '17 at 02:36
  • ThorSummoner, this solution is not as flexible as the approach above. For example, one might wish to have one single rule for invoking a command and then several other rules which modify that behaviour by setting environment variables. Consider: test: cmd perf: export PERF="yes" perf: test If 'cmd' is complicated (and it usually is), then this approach is a lot easier to maintain. Your approach to setting the environment variable in the cmd rule makes this more difficult. – Keith Hanlan Jul 26 '17 at 15:10
  • This solution may not be as flexible indeed, but it is arguably "safer" by some definition since there's no "polluting" of environment indiscriminately, across recipes and shell invocations. In many cases using the shell's own `VAR=... command ...` syntax is exactly what you'd want and what is sufficient. Many environment variables aren't meant to be consumed everywhere and by everything -- some processes expect "their own" variables which only these processes ever would use. The setting of the variable is also at least as explicit as if it were a target-specific variable, so there's that too. – Armen Michaeli Apr 25 '23 at 14:13
4

I would re-write the original target test, taking care the needed variable is defined IN THE SAME SUB-PROCESS as the application to launch:

test:
    ( NODE_ENV=test mocha --harmony --reporter spec test )
slm
  • 15,396
  • 12
  • 109
  • 124
Guiyo M
  • 39
  • 3
  • 1
    The bracketing (`(` and `)`) here, which creates a sub-shell, serves no meaningful purpose -- normally Make invokes every logical line with own, quite bare (by intent) shell process anyway (so your sub-shell isn't shared with much else you would want to isolate it from) and setting of variables like `NODE_ENV=...` above, will accomplish desired effect much the same -- with or without the subshell -- meaning the only program that will see the `NODE_ENV` variable with value `test` is the `mocha` program Make will invoke as per above (again, with or without the program being run in a sub-shell). – Armen Michaeli Apr 25 '23 at 14:16