I'm having an issue with make and PATH that I don't understand. Here's a self-contained demo of the issue where make fails unexpectedly:
#!/bin/bash
if [ -d ~/makeTest ]; then
rm -r ~/makeTest ;
fi
mkdir ~/makeTest
cd ~/makeTest
mkdir bin
echo "#!/bin/bash
echo Hello world!" > bin/install.sh
chmod +x bin/install.sh
echo ".PHONY: install
export PATH := \$(HOME)/makeTest/bin:\$(PATH)
install:
@which install.sh
@install.sh" > Makefile
make install
(View on GitHub.)
To summarize, this script produces a bin folder containing an install.sh script and a Makefile with the following contents:
.PHONY: install
export PATH := $(HOME)/makeTest/bin:$(PATH)
install:
@which install.sh
@install.sh
make install produces the output:
/Users/Suriname0/makeTest/bin/install.sh
make: install.sh: No such file or directory
make: *** [install] Error 1
Why does which correctly see the install.sh script on PATH but actually executing the command fails with "No such file or directory"? It's easy to verify that PATH is updated correctly: adding @echo $(PATH) produces the expected (updated) PATH.
Thank you for any help understanding this PATH issue!
Might be related to other weird PATH issues. On Mac OS.
The problem is a bug or limitation of Gnu Make 3.81, which is the version that Apple ships with its OS (/usr/bin/make). The behavior was changed in version 3.82, and it appears (in my limited testing) that version 3.82 and later execute the OP's script without error. However, 3.81 was the last version to be distributed under GPLv2, and Apple has shown a firm resistance to shipping anything licensed under GPLv3 (such as make 3.82 or bash v4), so I would never expect Apple to ever ship an updated version of Gnu Make. If they were to ship a newer make utility, I would expect them to use a different fork of Make altogether, as they did by replacing bash with zsh as their default terminal shell.
In Gnu Make 3.81 (Apple's make), changes to PATH affect the shell, but do not affect make itself. If a recipe is a straightforward invocation of a command, make does not launch a shell and have the shell look for it and launch it, make launches the command itself, searching for it using the $PATH it was launched with. In this scenario, your changes to PATH are ignored.
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
shell: prepare
@echo "In target: shell"
@printf "mydate is %s\n" "$$(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.
$ make
In target: shell
mydate is 2024-02-02
End target: shell
In target: internal
mydate -I -u
make: mydate: No such file or directory
make: *** [internal] Error 1
The solution, if you want to be compatible with the default version of make you will find on Apple computers, is to force make to launch a shell. That can be as simple as chaining lines together using ; \ or using the : no op with &&:
install-workaround-1:
@which install.sh; \
install.sh
install-workaround-2:
@: && install.sh
This works perfectly for me:
$ cat bin/hello
#!/bin/sh
echo hello, world
$ cat Makefile
export PATH := $(PWD)/bin:$(PATH)
$(info $(PATH))
all:
@hello
$ make
/home/kaz/junk/makepath/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/kaz/bin:/home/kaz/bin
hello, world
So the export PATH := assignment is clearly taking effect, allowing the recipe command hello to be found in that directory.
This is Ubuntu 18, with:
$ make --version
GNU Make 4.1
Built for i686-pc-linux-gnu
Copyright (C) 1988-2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
There is a discrepancy in the question in that the script which generates the Makefile uses := but the snippet shown uses =. That is actually not possible; GNU Make will diagnose if a = variable refers to itself recursively:
Makefile:1: *** Recursive variable 'PATH' references itself (eventually). Stop.
If I comment out the PATH assignment so that hello is not found, the error looks like this:
$ make
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/kaz/bin:/home/kaz/bin
make: hello: Command not found
Makefile:6: recipe for target 'all' failed
make: *** [all] Error 127
I also tried reproducing it via an almost identical script, which generates the directory with the script and the Makefile and immediately calls make install. I never see the error; hello world is always printed:
#!/bin/bash
if [ -d mktest ]; then
rm -r mktest ;
fi
mkdir mktest
cd mktest
mkdir bin
echo "#!/bin/bash
echo Hello world!" > bin/install.sh
chmod +x bin/install.sh
echo ".PHONY: install
export PATH := \$(PWD)/bin:\$(PATH)
install:
@which install.sh
@install.sh" > Makefile
make install
I would recommend using a system call tracing utility like strace to capture how install.sh is being accessed and what system call executed by what process is failing.
It's possible this is a problem in your specific version of GNU Make. For instance, the following bug affected a newer version that I'm testing with:
https://savannah.gnu.org/bugs/?56834
When this was fixed, a test case was added to make sure that a PATH set in the Makefile takes effect.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With