130

I want to write a Makefile which would run tests. Test are in a directory './tests' and executable files to be tested are in the directory './bin'.

When I run the tests, they don't see the exec files, as the directory ./bin is not in the $PATH.

When I do something like this:

EXPORT PATH=bin:$PATH make test 

everything works. However I need to change the $PATH in the Makefile.

Simple Makefile content:

test all: PATH=bin:${PATH} @echo $(PATH) x 

It prints the path correctly, however it doesn't find the file x.

When I do this manually:

$ export PATH=bin:$PATH $ x 

everything is OK then.

How could I change the $PATH in the Makefile?

4
  • Can you not just call the tests from the executable directory like ../test/test_to_run? Sorry if I have misunderstood the question. Commented Jan 20, 2012 at 12:02
  • I want this file to be visible to the tests normally. I don't want to play with the directories, as I refactoring that would be a nighmare. Commented Jan 20, 2012 at 12:12
  • The only way you can come close to this is to have the makefile write out a shell script containing the variable decls and then have the parent shell source that script with .. This is probably impractical however. Commented Jan 20, 2012 at 13:03
  • I believe that unix.stackexchange.com/questions/11530/… is a very different (stupid) question, unlike yours. Commented Feb 13, 2015 at 11:22

6 Answers 6

145

Did you try export directive of Make itself (assuming that you use GNU Make)?

export PATH := bin:$(PATH) test all: x 

Also, there is a bug in you example:

test all: PATH=bin:${PATH} @echo $(PATH) x 

First, the value being echoed is an expansion of PATH variable performed by Make, not the shell. If it prints the expected value then, I guess, you've set PATH variable somewhere earlier in your Makefile, or in a shell that invoked Make. To prevent such behavior you should escape dollars:

test all: PATH=bin:$$PATH @echo $$PATH x 

Second, in any case this won't work because Make executes each line of the recipe in a separate shell. This can be changed by writing the recipe in a single line:

test all: export PATH=bin:$$PATH; echo $$PATH; x 
Sign up to request clarification or add additional context in comments.

2 Comments

$(PATH) will get set to the value of PATH of the shell invoking make. As per the manual, "Every environment variable that make sees when it starts up is transformed into a make variable with the same name and value."
The export directive worked for me (thanks!), but only after I installed GNU Make 4.3. In version 3.81 (the default on macOS Catalina), the updated PATH is correctly reflected in variables (echo $(PATH)) and within commands' environments (env, which python, bash -c python), but does not seem to be used when locating the executable for the command: the command python still runs the Python executable on the original PATH.
40

By design make parser executes lines in a separate shell invocations, that's why changing variable (e.g. PATH) in one line, the change may not be applied for the next lines (see this post).

One way to workaround this problem, is to convert multiple commands into a single line (separated by ;), or use One Shell special target (.ONESHELL, as of GNU Make 3.82).

Alternatively you can provide PATH variable at the time when shell is invoked. For example:

PATH := $(PATH):$(PWD)/bin:/my/other/path SHELL := env PATH=$(PATH) /bin/bash 

5 Comments

This is the best, because it works even with $(shell) invocations! :D Example: pastebin.com/Pii8pmkD
On dev.azure I get: env: ‘env’: No such file or directory; Works locally ;/
Does not work on Debian 10 with make 4.2: make: env PATH=<path> /bin/sh: Command not found. Using export works. Make sure to use $(HOME) and not ~ for a path in the home dir.
@kenorb looking for this export SHELL := env VAR="xxx" /bin/bash, worked ! thanks
This does not work for me. See my answer for why.
28

Path changes appear to be persistent if you set the SHELL variable in your makefile first:

SHELL := /bin/bash PATH := bin:$(PATH) test all: x 

I don't know if this is desired behavior or not.

2 Comments

Yeah, this does indeed do what the OP wanted...but is it a feature or a bug? Even after reading the make manual's SHELL section I'm not sure.
This doesn’t work for me either. which and env now pick up the new PATH, but directly executing a binary is still not found in the modified PATH, only in the original one.
4

What I usually do is supply the path to the executable explicitly:

EXE=./bin/ ... test all: $(EXE)x 

I also use this technique to run non-native binaries under an emulator like QEMU if I'm cross compiling:

EXE = qemu-mips ./bin/ 

If make is using the sh shell, this should work:

test all: PATH=bin:$PATH x 

2 Comments

Yep, cool soulution, however I've got my tests written in perl and I need to call the exe from the perl script, not directly from makefile. I've got to rethink the whole testing of this stuff :)
Gotcha. How about setting PATH on the command line itself? See the edit above.
4

In many versions of Gnu Make, such as v3.81 which is shipped by Apple on macOS, changes to the PATH variable inside the Makefile affect the shells make launches, but do not affect make itself. It is not clear if this is a bug or a feature.

For example, with this Makefile:

SHELL := /bin/sh TESTDIR = /tmp/testdir export PATH := $(TESTDIR):$(PATH) .PHONY: all prepare internal shell clean all: prepare shell internal clean prepare: @mkdir -p $(TESTDIR) @cp /bin/date $(TESTDIR)/mydate @chmod +x $(TESTDIR)/mydate # This recipe forces `make` to invoke a shell by using command substitution # or boolean operation shell: prepare @echo "In target: shell" @printf "mydate is %s\n" "$$(mydate -I -u)" @: && mydate -I -u @echo "End target: shell" # This is invoked directly by `make` and does not pick up the change in PATH internal: prepare @echo "In target: internal" mydate -I -u @echo "End target: internal" clean: rm -rf $(TESTDIR) 

You will find that make shell works, but make internal does not.

The fact that the difference is between whether a shell is launched or not can lead to some very confusing behavior, as you can now see.

$ make In target: shell mydate is 2024-02-02 2024-02-02 End target: shell In target: internal mydate -I -u make: mydate: No such file or directory make: *** [internal] Error 1 

Comments

-3

To set the PATH variable, within the Makefile only, use something like:

PATH := $(PATH):/my/dir test: @echo my new PATH = $(PATH) 

1 Comment

It doesn't work the way I want. I've updated the question withe examples of what I want to achieve.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.