Makefile with source get error `No such file or directory`

Viewed 14433

I have a really simple Makefile that's doing source to set ENV variables. But it doesn't work if I call it from Makefile

I get this error

make dev
source ./bin/authenticate.sh
make: source: No such file or directory
make: *** [dev] Error 1

The script exists.

If I run this in command-line it works.

source ./bin/authenticate.sh
Works!

This is my Makefile

test:
    pytest -s

dev:
    source ./bin/authenticate.sh

I'm using OSX. I'm not sure if this would make a difference.

2 Answers

tldr; lose the source, use the dot (.)

Longer explanation follows...

It doesn't work because make is looking for a source program in the $PATH. This fails because source is a (non-POSIX) shell builtin, not an executable program on any conventional UNIX-like system.

I was able to replicate failure shown in the question; the following (much abridged) output was generated on Ubuntu 16.04 with GNU Make v4.1:

$ strace -f -s65536 -- make dev 2>&1 | grep 'authenticate'
read(3, "test:\n\tpytest -s\n\ndev:\n\tsource ./bin/authenticate.sh\n", 4096) = 53
write(1, "source ./bin/authenticate.sh\n", 29source ./bin/authenticate.sh
[pid 32100] execve("/usr/local/sbin/source", ["source", "./bin/authenticate.sh"], [/* 82 vars */]) = -1 ENOENT (No such file or directory)
[pid 32100] execve("/usr/local/bin/source", ["source", "./bin/authenticate.sh"], [/* 82 vars */]) = -1 ENOENT (No such file or directory)
[pid 32100] execve("/usr/sbin/source", ["source", "./bin/authenticate.sh"], [/* 82 vars */]) = -1 ENOENT (No such file or directory)
[pid 32100] execve("/usr/bin/source", ["source", "./bin/authenticate.sh"], [/* 82 vars */]) = -1 ENOENT (No such file or directory)
[pid 32100] execve("/sbin/source", ["source", "./bin/authenticate.sh"], [/* 82 vars */]) = -1 ENOENT (No such file or directory)
[pid 32100] execve("/bin/source", ["source", "./bin/authenticate.sh"], [/* 82 vars */]) = -1 ENOENT (No such file or directory)

You can see that make made six failed attempts to find the source program; i.e., one for each component of my $PATH.

If source is changed to ., make changes its strategy; instead of trying to find and execute a program, it just passes the rule body to the system shell:

$ strace -f -s65536 -- make dev 2>&1 | grep 'authenticate'
read(3, "test:\n\tpytest -s\n\ndev:\n\t. ./bin/authenticate.sh\n", 4096) = 48
write(1, ". ./bin/authenticate.sh\n", 24. ./bin/authenticate.sh
[pid 32122] execve("/bin/sh", ["/bin/sh", "-c", ". ./bin/authenticate.sh"], [/* 82 vars */]) = 0
[pid 32122] open("./bin/authenticate.sh", O_RDONLY) = 3

The kind of shell used by make determines which shell builtins get expanded within each Makefile rule. You can tell make to use a shell of your choosing like so:

$ cat Makefile
SHELL = /bin/bash
test:
        pytest -s

dev:
        source ./bin/authenticate.sh

Since bash defines the builtin source as a synonym for ., the rule using source now succeeds:

$ strace -f -s65536 -- make dev 2>&1 | grep 'authenticate'
read(3, "SHELL = /bin/bash\ntest:\n\tpytest -s\n\ndev:\n\tsource ./bin/authenticate.sh\n", 4096) = 71
write(1, "source ./bin/authenticate.sh\n", 29source ./bin/authenticate.sh
[pid 32573] execve("/bin/bash", ["/bin/bash", "-c", "source ./bin/authenticate.sh"], [/* 82 vars */]) = 0
[pid 32573] open("./bin/authenticate.sh", O_RDONLY) = 3

References:

do cd bin && source authenticate.sh it works

Related