在Makefile中设置PATH时出现问题

13

我对make和Makefile不太熟悉,但是我正在尝试为我的下一个项目创建一个Makefile,并遇到了PATH问题。 我一直收到错误消息:“找不到文件或目录”。

我创建了一个简单的目标,名为test,可以使用mocha运行所有测试。

Mocha作为本地节点模块安装,因此其可执行文件可以在./node_modules/.bin/mocha找到。根据这个make教程的描述,我正在更改我的PATH,以便可以将其称为mocha而不是键入完整路径,但某些事情似乎没有起作用。

以下是我到目前为止的内容:

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

test:
    which mocha
    mocha

.PHONY: test

当我运行 make test 命令时,我得到以下输出:

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

从输出结果可以看出,which mocha 正确地打印了 mocha 可执行文件的路径,但是当我简单地运行 mocha 时,它找不到。

我做错了什么?是否有关于变量作用域或Makefile中持久性的更大的问题我忽略了?

附注:如果重要的话,我正在使用Mac和随XCode开发者工具一起提供的make版本。 这是我运行 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

1
这很奇怪... 如果您删除行 export PATH := node_modules/.bin:$(PATH) 并像这样运行 make:$ PATH=node_modules/.bin:$PATH make test,会发生什么?它能正常工作吗? - Junior Dussouillez
没错,如果我这样做的话就可以正常工作。 - Philip Walton
5个回答

13
在 OSX 中,您还需要设置SHELL:set SHELL
PATH  := node_modules/.bin:$(PATH)
SHELL := /bin/bash

11

我无法重现你的结果(使用GNU make 3.81在GNU / Linux系统上)。看起来可能有两种情况之一,要么苹果系统的工作方式不同,要么苹果对他们所提供的GNU make版本进行了某种修补,导致出现了这个问题。

GNU make有两种运行命令的方式:正常方式会调用shell并传递命令给shell执行;而“快速路径”则是当make发现命令足够简单时直接执行,而不调用shell,因此要比正常方式更快。但由于它是直接被调用的,所以它从GNU make本身而不是从shell继承了PATH设置。

似乎由于苹果提供的GNU make版本没有正确设置直接由GNU make通过“快速路径”运行的命令的环境变量,导致该版本无法正常使用。

我模糊地记得在GNU make邮件列表中讨论过类似的问题,但花了一些时间搜索后没找到任何相关信息。

你可以通过添加一些shell特殊字符(如globbing、;、pipes等)来强制使命令使用慢速路径来“解决”这个问题。或者你可以使用完全限定的程序路径。

或者,你可以获取GNU make的源代码并自行构建;如果这种差异是苹果的“修补”导致的,那么自行构建应该能够更好地解决问题。或者安装来自Homebrew或Ports的GNU make,这将为你提供一个功能更强大的新版本。


我觉得这非常奇怪。如果你有兴趣,可以尝试安装纯正的GNU make,可以通过自己构建(很容易)或者通过MacPorts或Brew来实现。我在想苹果是否可能已经“友好地”修补了他们的GNU make版本,从而导致了一些问题。 - MadScientist
2
另一个需要检查的地方是更改您调用 mocha 的方式,使其类似于 : ; mocha。 这样做将绕过 GNU make 的快速处理并强制其调用 shell。 如果这能够正常运行,那么我会更加怀疑 Apple 版本的 GNU make 存在一些奇怪的问题。 - MadScientist
嗯,是的,使用 : ; mocha 运作得非常好。另外,mocha可以接受文件列表,但如果您不提供任何文件,它会默认为 ./test/* 。如果我只运行 mocha 而不是传入任何参数,它也可以正常工作。不知怎么的,一个单一的命令会让它感到困惑…… - Philip Walton
1
这不是一个单一的命令,而是一个_简单_的命令。为了提高效率,GNU make有一个“快速路径”,它检查要调用的命令,如果其中没有“特殊字符”(这被定义为需要shell处理的字符),则直接调用该命令,而不是启动shell。当您使用./test/*时,您正在使用一个特殊字符(*,用于globbing),它需要shell,就像我使用;一样。不知何故,苹果公司的GNU make版本没有设置本地的PATH环境变量,它只在子进程中设置PATH - MadScientist
1
如果能更容易地找到这个答案就太好了。我为了弄清楚为什么导出的“PATH”对某些配方不起作用而对其他配方起作用而苦苦挣扎了一段时间。在OSX上使用GNU Make 3.81可以轻松重现这个问题。在命令中添加一个“;”是一个快速的解决方法。 - Tim Schaub
显示剩余3条评论

3
我正在使用Ubuntu 16.04.4上的GNU make版本4.1。
我遇到了类似的问题,但是建议的解决方案都不适用于我。
在稍后进行变量设置时,PATH设置似乎没有生效(至少在这个版本的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)

我采用的解决方案是将早期的PATH设置显式地注入到特定的子Shell中,以便在稍后分配make变量时使用,例如:
VW = $(shell env PATH=$(PATH) which vw)

以下是一个Makefile的示例,它展示了可以工作的情况以及不能工作的情况:
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)'"

问题复现和解决方案:

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

$ make
# -- Doesn't work:
VW_NOT_OK=''
# -- Works:
VW_OK='/usr/local/bin/vw'

你的问题不同,因为你使用的是shell函数,而不是原始问题中的配方。shell函数的环境处理与配方不同。 - MadScientist
@MadScientist 我被迫使用 $(shell ...) 的原因是我尝试的其他方法都没有起作用。全局设置 PATH 似乎会影响 Makefile 命令/规则,但在后续变量设置中似乎没有生效:VARNAME = ...,其中设置取决于 $(PATH) 才能正常工作(就像我对 which 的调用一样)。请注意,我还写道“我遇到了类似的问题”,而不是完全相同的问题。 - arielf
“变量名称设置”不会将PATH用于任何事情,那么PATH的设置有什么影响呢?当然,$(which foo)是空字符串:which不是有效的make函数,因此您只是扩展了一个名为“which foo”的变量,它没有值,因此扩展为空字符串。除了在配方中之外,从makefile运行shell命令的唯一方法是使用$(shell ...)函数。$(which foo)扩展名为“which foo”的变量;$(shell which foo)运行shell命令“which foo”并扩展为其输出。 - MadScientist
谢谢您的解释。现在原因很清楚了! - arielf

2
这应该可以工作:
PATH  := $(PATH):$(PWD)/node_modules/.bin
SHELL := env PATH=$(PATH) /bin/bash

1
如果PATH的值周围没有双引号,它几乎可以工作;在Makefile分配中,引号没有特殊含义,因此它们只是被添加到PATH的前面和后面,有效地掩盖了第一个和最后一个值(除非有一个名为node_modules/.bin"的目录)。 - Steven

0

很抱歉你不能这样做 尝试使用“本地”变量代替

NODE_MODULES := node_modules/.bin

test:
    @ $(NODE_MODULES)/mocha

根据我阅读的文章,你是可以这样做的。而且由于 which mocha 能够正常工作,看起来它确实对那一行起了作用。另外,如果我打印出 PATH 变量,它确实包含了 node_modules 部分,尽管可能只在 make 子进程中有效,我不确定。 - Philip Walton
4
链接的SO问题与此问题无关。尽管子进程无法修改其父进程的环境变量,但这并不是此处发生的情况。那个问题询问如何使一个makefile修改“它的调用者”的环境变量。然而,这个问题完全不同:它想要修改“它的子进程”的环境变量,这是完全合理和支持的。 - MadScientist
1
如果你认为这可能会误导未来的读者,我已经看到并准备将其删除。 - G.G.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接