13

I'm new to make and Makefiles, but I'm trying to create one for my next project and I'm running into PATH issues. I keep getting the error: "No such file or directory"

I've created a simple target called test that runs all my tests using mocha.

Mocha is installed as a local node module, so its executable can be found at ./node_modules/.bin/mocha. I'm altering my PATH as described in this make tutorial so I can refer to it as mocha instead of typing the full path, but something doesn't seem to be working.

Here's what I have so far:

export PATH := node_modules/.bin:$(PATH)

test:
    which mocha
    mocha

.PHONY: test

When I run make test I get the following output:

which mocha
node_modules/.bin/mocha
mocha
make: mocha: No such file or directory
make: *** [test] Error 1

As you can see from the output, which mocha is correctly printing the path to the mocha executable, but when I simply run mocha, it can't find it.

What am I doing wrong? Is there bigger picture about variable scope or persistence in Makefiles that I'm missing?

P.S. If it's important, I'm using a Mac and the version of make that comes with the XCode developer tools. This is what I get when I run make -v

GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for i386-apple-darwin11.3.0
Philip Walton
  • 29,693
  • 16
  • 60
  • 84

5 Answers5

13

In OSX you also need to also set SHELL:

PATH  := node_modules/.bin:$(PATH)
SHELL := /bin/bash
Community
  • 1
  • 1
tom
  • 718
  • 8
  • 15
11

I can't reproduce your results (using GNU make 3.81 on a GNU/Linux system). It appears that there may be some difference in either the way the Apple system works, or that Apple has made some kind of patch to the GNU make version they ship that is causing this problem.

GNU make has two ways of running recipes: the normal way, where it invokes a shell and passes the recipe to the shell to be run, and the "fast path", where, if make sees that the command is "simple enough" (that is if it contains no shell special characters), it will chop up the command into words and directly execute the command without invoking the shell. The latter is much faster, but since it's being invoked directly it inherits the PATH setting from GNU make itself not from the shell.

It appears that for some reason the version of GNU make shipped by Apple is not working properly in that it's not setting the environment correctly for commands which are run directly by GNU make, via the "fast path".

I have a very vague recollection of something like this being discussed on the GNU make mailing lists, but after spending some time searching I wasn't able to come up with anything.

You can "fix" this problem by forcing your command to use the slow path, by introducing some shell special characters (globbing, ;, pipes, etc.). Or you can use a fully-qualified path to the program.

Or, you can go get the source code for GNU make and build it yourself; if this difference is a result of a "fix" by Apple that should make it work better. Or install GNU make from homebrew or ports, which will also get you a newer version with more features, I expect.

MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • Yes, it does work if I use the full path, which is the weird part. And yes it does work if I put that code into a file and run it. Perhaps it's a mac thing if you can get it working on Linux. – Philip Walton Feb 11 '14 at 19:00
  • I find this extremely odd. If you have the inclination you might try installing straight GNU make, either by building it yourself (it's easy) or maybe through MacPorts or Brew. I'm wondering if Apple might have "helpfully" patched their version of GNU make in some way that is breaking things. – MadScientist Feb 11 '14 at 20:01
  • 2
    Another thing to check is change your invocation of `mocha` to look like this: `: ; mocha`. Doing this will circumvent GNU make's fast-path processing and force it to invoke a shell. If this works then I'm even more suspicious that something is bizarre with the Apple version of GNU make. – MadScientist Feb 11 '14 at 20:03
  • Hmmmm, yes, using `: ; mocha` worked just fine. Also, mocha accepts a list of files but if you supply none, it defaults to `./test/*`. If instead of just running `mocha` I run `mocha ./test/*` it also works. Somehow it's being confused by having a single command... – Philip Walton Feb 11 '14 at 20:06
  • 1
    It's not a single command. It's a _simple_ command. For efficiency GNU make has a "fast path" where it examines the command to be invoked and if there are no "special characters" in it (this is defined as characters that need the shell to handle them) then it invokes the command directly rather than starting a shell. When you use `./test/*` you're using a special character (`*`, for globbing) that requires the shell, just like when I used `;`. Somehow Apple's version of GNU make is not setting the local `PATH` environment variable, it's only setting `PATH` in subprocesses. – MadScientist Feb 11 '14 at 20:12
  • Interesting, well, I guess that answers that. It's not a problem to use the full path, I was mainly just curious as to *why* it wasn't working. – Philip Walton Feb 11 '14 at 20:20
  • if you sumbit an additional answer with the content of your last comment I will accept it. I hesitate to accept this answer because you have to read to the comments before getting any real information. – Philip Walton Feb 11 '14 at 22:42
  • 1
    It would be great if this answer were easier to find. I struggled for a while to figure out why the exported `PATH` didn't apply to some recipes while it did to others. It is trivial to reproduce this on OSX with GNU Make 3.81. Adding a `;` to commands is a quick workaround. – Tim Schaub Nov 09 '14 at 20:59
3

I'm using GNU make version 4.1 on Ubuntu 16.04.4

I had a similar issue and none of the suggested solutions worked for me.

Settings to PATH do not seem to be in effect when later variable settings take place (at least in this version of make).

export PATH := /usr/local/bin:/bin:/usr/bin
# Path setting is not in effect here, 'which' returns an empty string
# even when 'vw' is installed in /usr/local/bin/vw 
VW = $(which vw)

The solution that worked for me was to inject the earlier PATH setting explicitly into the particular sub-shell used to assign a later make variable, like this:

VW = $(shell env PATH=$(PATH) which vw)

The following Makefile example demonstrates the case that works vs the one that doesn't:

SHELL := /bin/bash
export PATH := /usr/local/bin:/bin:/usr/bin

# Expecting 'vw' to be found, due to PATH setting above
#       ('vw' is in /usr/local/bin/vw)

VW_NOT_OK = $(which vw)
VW_OK = $(shell env PATH=$(PATH) which vw)

all:
        # -- Doesn't work:
        @echo "VW_NOT_OK='$(VW_NOT_OK)'"
        # -- Works:
        @echo "VW_OK='$(VW_OK)'"

Reproducing the problem and solution:

$ make --version| head -1
GNU Make 4.1

$ make
# -- Doesn't work:
VW_NOT_OK=''
# -- Works:
VW_OK='/usr/local/bin/vw'
arielf
  • 5,802
  • 1
  • 36
  • 48
  • Your problem is different, because you're using the `shell` function, not a recipe as in the original question. The handling of the environment for the `shell` function is not the same as for recipes. – MadScientist Feb 29 '20 at 13:37
  • @MadScientist the reason I was forced to use `$(shell ...)` was that nothing else I tried worked. Setting `PATH` globally seems to affect Makefile commands/rules but it doesn't seem be in effect in later variable settings: `VARNAME = ...` where the setting depends on `$(PATH)` in order to work (like in my call to `which`). Note that I also wrote "I had a similar issue" not an identical one. – arielf Apr 13 '20 at 04:54
  • "Variable name settings" don't use PATH for anything, so how does the setting of PATH matter? Certainly `$(which foo)` is the empty string: `which` is not a valid make function so you're just expanding a variable named, literally, `which foo`, that has no value and so expands to the empty string. The _ONLY_ way to run a shell command from a makefile, other than in a recipe, is by using the `$(shell ...)` function. `$(which foo)` expands the variable named `which foo`; `$(shell which foo)` runs the shell command `which foo` and expands to its output. – MadScientist Apr 13 '20 at 14:11
  • Thanks for the explanation. The reason is clear now! – arielf Apr 14 '20 at 00:23
2

This should work:

PATH  := $(PATH):$(PWD)/node_modules/.bin
SHELL := env PATH=$(PATH) /bin/bash
kenorb
  • 155,785
  • 88
  • 678
  • 743
  • 1
    It would almost work if it weren't for the double quotes around the value for `PATH`; quotes have no special meaning in Makefile assignments, so they are simply prepended and appended to the `PATH`, effectively masking the first and last values (unless there is a directory called `node_modules/.bin"` – Steven Sep 04 '17 at 13:10
0

I am afraid that you can't do that Try with "local" variables instead

NODE_MODULES := node_modules/.bin

test:
    @ $(NODE_MODULES)/mocha
Community
  • 1
  • 1
G.G.
  • 634
  • 1
  • 5
  • 10
  • Well, according to the article I read, you can do that. And since `which mocha` is working, it appears that it *did* work for that line. Also, if I print out the PATH variable, it does include the node_modules bit, though perhaps that's only scoped to the make sub-process, I don't know. – Philip Walton Feb 11 '14 at 18:56
  • 4
    The linked SO question is not relevant to this one. It's true that a sub-process cannot modify its parent's environment but that's not what's happening here. That question is asking how to have a makefile modify _its caller's_ environment. This question is entirely different: it wants to modify _its children's_ environment, which is perfectly reasonable and supported. – MadScientist Feb 11 '14 at 19:59
  • 1
    I see and I am ready to remove it if you think that it could be misleading for future readers – G.G. Feb 11 '14 at 20:03