如何获取Makefile的当前相对目录?

316

我有几个类似于这样的应用程序特定目录中的Makefile:

/project1/apps/app_typeA/Makefile
/project1/apps/app_typeB/Makefile
/project1/apps/app_typeC/Makefile

每个Makefile在此路径的上一级中包含一个.inc文件:

/project1/apps/app_rules.inc

在 app_rules.inc 中,我正在设置构建时想要二进制文件放置的目标位置。我希望所有的二进制文件都在它们各自的 app_type 路径中:

/project1/bin/app_typeA/

我尝试使用$(CURDIR),就像这样:

OUTPUT_PATH = /project1/bin/$(CURDIR)

但实际上,我得到的二进制文件却被埋藏在整个路径名中,就像这样:(请注意冗余)

/project1/bin/projects/users/bob/project1/apps/app_typeA

我该怎么做才能获取"当前目录"的执行路径,以便我只能知道app_typeX,从而将二进制文件放入它们各自的类型文件夹中?

14个回答

411

shell函数

您可以使用shell函数:current_dir = $(shell pwd)。 或者结合notdirshell,如果您不需要绝对路径: current_dir = $(notdir $(shell pwd))

更新

给出的解决方案仅在从Makefile的当前目录运行make时有效。
如@Flimm所指出的那样:

请注意,这返回的是当前工作目录,而不是Makefile的父目录。
例如,如果您运行cd /; make -f /home/username/project/Makefile,则current_dir变量将为/,而不是/home/username/project/

下面的代码将适用于从任何目录调用的Makefiles:

mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path))))

6
不起作用,问题还是一样。pwd 命令会打印出完整路径名,而我只需要当前文件夹的名称。 - boltup_im_coding
8
需要立即扩展其值,因此应使用“:=”运算符。例如:mkfile_path :=,current_dir =(否则右侧的值可能在包含其他Makefile后MAKEFILE_LIST发生更改时被评估)。 - Tom Gruner
17
我知道这是一个老问题,但我刚遇到它并认为这可能有用。当makefile的相对路径中存在空格时,这种方法也不能正常工作。具体来说,在路径为“Sub Directory/Makefile”的makefile中使用 $(lastword "$(MAKEFILE_LIST)") 将会给你 "Directory/Makefile"。我认为这方面没有任何解决方法,因为带有两个makefile的 $(MAKEFILE_LIST) 会给出一个字符串 "Makefile One Makefile Two",无法处理空格。 - mrosales
7
那个 current_dir 对我没用,而 $(PWD) 可以,并且更简单。 - CaTx
11
“只有在从Makefile的当前目录运行make时才能使用该解决方案”是一个委婉的说法,意思是“并不真正起作用”。我会把最新的部分放在答案的顶部。 - Victor Sergienko
显示剩余16条评论

197

源自此处;

ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))

显示为:

$ cd /home/user/

$ make -f test/Makefile 
/home/user/test

$ cd test; make Makefile 
/home/user/test

5
个人认为最好使用内部的 dir 函数来代替 shell dirname,即使它返回以斜杠字符结尾的路径名。 - Serge Roussak
4
由于减少对外部工具的依赖,例如如果某个系统中有dirname命令的别名,会发生什么? - Serge Roussak
8
这个操作对我来说几乎起作用了,但是DIR前面有一个空格,所以稍作修改后就完美地运行了:DIR:=$(strip $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))) - Thorsten Lorenz
13
要引用当前的 Makefile,必须在任何 include 操作之前使用 $MAKEFILE_LIST(参见文档)。 - Brent Bradburn
4
应该使用引号——至少在dirname参数周围——以防路径中有空格。 - Brent Bradburn
显示剩余7条评论

74

如果您正在使用GNU make,$(CURDIR)实际上是一个内置变量。它是当前工作目录,通常是Makefile所在的位置,但并不总是

OUTPUT_PATH = /project1/bin/$(notdir $(CURDIR))

请参阅附录A快速参考,位于http://www.gnu.org/software/make/manual/make.html


24
从GNU Make手册(第51页)中翻译得知:当GNU Make启动时(在处理任何-C选项之后),它会将变量CURDIR设置为当前工作目录的路径名。这个路径名不是Makefile所在的位置,尽管它们可能相同。 - Simon Peverett
7
因为Simon Peverett在之前的评论中指出该答案不正确,技术性上存在问题,所以被踩了。 - Subfuzion
6
在括号中的表达式意思是“-C选项应用后的当前工作目录”。也就是说,$(CURDIR) 对于“make -C xxx”和“cd xxx; make”命令来说给出的结果是完全相同的。此外,它还会删除尾随的“/”。我在使用gmake v3.81时进行了尝试。但如果以“make -f xxx/Makefile”的形式调用“make”,你是正确的。 - TrueY
3
CURDIR 对我有用,而 PWD 没有。我执行了 mkdir -p a/s/d,然后在每个文件夹中都有一个 makefile,在执行 +$(MAKE) <subdir> 前打印了 PWDCURDIR - Mark K Cowan
这应该是正确的答案。无需重新发明轮子,Make 工具可以为您完成。 - user503582

36
THIS_DIR := $(dir $(abspath $(firstword $(MAKEFILE_LIST))))

17
除非您在路径中有空格。 - Craig Ringer
13
除非你在前一行中包含了其他的 makefile,否则不会执行该操作。 - robal
1
@robal 是的。这就是我刚遇到的问题。如果你只有两个Makefile(主要的加上一个包含的),那么这个方法非常好用。 - sherrellbc
这对我很有效(尚未测试目录中的空格,但我不希望遇到这种情况)。我在这段代码中遇到的主要问题是 $(dir) 保留了最后一个路径分隔符。在它之前添加 $(realpath) 可以解决这个问题。 - Guss
为了更详细地解释@robal所写的内容,你应该使用lastword而不是firstword。对于简单的Makefile来说,这个“数组”的大小为1,所以选择第一个元素等同于选择最后一个元素,但是为了获取包含该行代码的目录路径,你希望从这个以空格分隔的列表中选择最后一个名称。 - Yogev Neumann

23

简单、正确、现代的方式:

适用于 GNU make >= 3.81,该版本于2006年引入

ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
  1. MAKEFILE_LIST 随着包含文件的进入和退出而改变。最后一项是当前文件。

  2. lastword 提取最后一项(相对于pwd的Makefile名称)

  3. realpath 是内置于make中的,可以从文件系统根目录解析为规范路径

  4. dir 去掉文件名,只保留目录。

注意:生成的ROOT_DIR将包含尾部路径分隔符(例如在nix上为'/',在Windows上为'\')


2
这是我迄今为止找到的最佳解决方案。如果您使用include并且需要在每个Makefile中使用相对路径,则应将此代码放置在每个Makefile的开头。是的,它不能处理空格,但是使用MAKEFILE_LIST的任何答案都无法处理空格,因为make将空格用作MAKEFILE_LIST中的分隔符,结果您永远不知道这个空格是分隔符还是文件路径或文件名的一部分。这让我很难过。 - kdmitry
注意,关于上面的评论:而不是将此放在每个Makefile的开头,也可以放在包含的Makefile中,并且只列出一次。我想这取决于具体要做什么。但是假设有多个Makefile都使用类似include ../../make.inc或其他(带有不同级别的../)的东西,它可以为make.inc提供一个路径,该路径是其他内容的基础。 - undefined
1
漂亮而干净。我们不应该关心路径末尾的斜杠吗? - undefined
@HoseinRahnama 我认为至少值得一提,我会加上的。 - undefined

12

我喜欢所选择的答案,但我认为展示它的实际工作原理比解释更有帮助。

/tmp/makefile_path_test.sh

#!/bin/bash -eu

# Create a testing dir
temp_dir=/tmp/makefile_path_test
proj_dir=$temp_dir/dir1/dir2/dir3
mkdir -p $proj_dir

# Create the Makefile in $proj_dir
# (Because of this, $proj_dir is what $(path) should evaluate to.)
cat > $proj_dir/Makefile <<'EOF'
path := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
cwd  := $(shell pwd)

all:
    @echo "MAKEFILE_LIST: $(MAKEFILE_LIST)"
    @echo "         path: $(path)"
    @echo "          cwd: $(cwd)"
    @echo ""
EOF

# See/debug each command
set -x

# Test using the Makefile in the current directory
cd $proj_dir
make

# Test passing a Makefile
cd $temp_dir
make -f $proj_dir/Makefile

# Cleanup
rm -rf $temp_dir

输出:

+ cd /tmp/makefile_path_test/dir1/dir2/dir3
+ make
MAKEFILE_LIST:  Makefile
         path: /private/tmp/makefile_path_test/dir1/dir2/dir3
          cwd: /tmp/makefile_path_test/dir1/dir2/dir3

+ cd /tmp/makefile_path_test
+ make -f /tmp/makefile_path_test/dir1/dir2/dir3/Makefile
MAKEFILE_LIST:  /tmp/makefile_path_test/dir1/dir2/dir3/Makefile
         path: /tmp/makefile_path_test/dir1/dir2/dir3
          cwd: /tmp/makefile_path_test

+ rm -rf /tmp/makefile_path_test

注意: 函数$(patsubst %/,%,[path/goes/here/])用于去除路径末尾的斜杠。


8

我尝试了很多答案,但在我的AIX系统上使用GNU make 3.80时,我需要做一些传统的事情。

结果发现,直到3.81版本才添加了lastwordabspathrealpath。 :(

mkfile_path := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
mkfile_dir:=$(shell cd $(shell dirname $(mkfile_path)); pwd)
current_dir:=$(notdir $(mkfile_dir))

如其他人所说,这种方法不太优雅,因为它会两次调用shell,并且仍然存在空格问题。
但是由于我的路径中没有任何空格,所以无论我如何开始make,它都对我有效:
- make -f ../wherever/makefile - make -C ../wherever - make -C ~/wherever - cd ../wherever; make 所有这些都给我当前目录的"wherever"和"mkfile_dir"的绝对路径。

1
就我个人而言,我在AIX(5-6-7)上编译了GNU Make 3.82,并且没有遇到任何问题。 - Zsigmond Lőrinczy
1
是的,通过3.81版本,之前解决方案所需的函数已经实现。我的答案适用于3.80或更早版本的老系统。可悲的是,在工作中我不被允许向AIX系统添加或升级工具。 :( - Jesse Chisholm

2

以下是使用shell语法获取Makefile文件绝对路径的一行命令:

SHELL := /bin/bash
CWD := $(shell cd -P -- '$(shell dirname -- "$0")' && pwd -P)

这里是不基于 shell 的版本,基于 @0xff 的回答

CWD := $(abspath $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))))

通过打印来测试它,例如:

cwd:
        @echo $(CWD)

1
非常重要!上面的第二个示例需要使用“:=”赋值运算符,否则在复杂的makefile中可能会发生一些非常奇怪和愤怒的事情(相信我)。 - EMon
2
第一个问题是重复的错误,并且不适用于树外构建。 - Johan Boulé
不幸的是,CWD := $(shell cd -P -- '$(shell dirname -- "$0")' && pwd -P) 总是返回你执行 make 的目录,这可能与 Makefile 的目录不同。这种方法适用于 shell 脚本,但不适用于 make - kdmitry

1

使用{}代替()

cur_dir=${shell pwd}
parent_dir=${shell dirname ${shell pwd}}}

1
据我所知,这是唯一一个正确处理空格的答案:
space:= 
space+=

CURRENT_PATH := $(subst $(lastword $(notdir $(MAKEFILE_LIST))),,$(subst $(space),\$(space),$(shell realpath '$(strip $(MAKEFILE_LIST))')))

它的基本原理是通过将空格字符转义,用'\ '代替' ',使Make能够正确解析它,然后通过另一个替换从MAKEFILE_LIST中删除makefile的文件名,只留下包含makefile的目录。虽然不是世界上最紧凑的东西,但确实有效。您最终会得到像这样所有空格都被转义的内容:
$(info CURRENT_PATH = $(CURRENT_PATH))

CURRENT_PATH = /mnt/c/Users/foobar/gDrive/P\ roje\ cts/we\ b/sitecompiler/

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