如何在GNU Make中更改当前目录

12

我想将源代码目录与目标文件目录分开。似乎从Makefile更改当前工作目录是最简单的解决方案。

显式指定目标路径存在以下缺点:

  1. 在Makefile中需要针对每个目标引用添加变量,导致代码冗余。
  2. 构建特定中间目标需要更复杂的命令行(不利于调试)。

请参阅Pauls的规则#3

如果在当前工作目录中构建目标,则最简单。

关于VPATH——我也同意要求开发人员“在运行 make 命令之前切换到目标目录是很麻烦的”。

5个回答

10

将目标文件建立在单独的目录中是一种常见的make实践,GNU make可以方便地支持此操作而无需更改目录或调用辅助工具。以下是一个常规示例:

Makefile

srcs := main.c foo.c
blddir := bld
objs := $(addprefix $(blddir)/,$(srcs:.c=.o))
exe := $(blddir)/prog

.PHONY: all clean

all: $(exe)

$(blddir):
    mkdir -p $@

$(blddir)/%.o: %.c | $(blddir)
    $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<

$(exe) : $(objs)
    $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)

clean:
    rm -fr $(blddir)

这段代码的执行过程如下:

$ make
mkdir -p bld
cc   -c -o bld/main.o main.c
cc   -c -o bld/foo.o foo.c
cc -o bld/prog bld/main.o bld/foo.o

说明:

有时候会有强制性的原因需要更改make的工作目录,但仅仅是将生成的文件放在另一个目录中并不是这样做的一个好理由。


是的,这种方法“每个目标的每个引用都以路径名为前缀”,它的缺点在提到的论文中有所描述 - 我已经更新了答案。 - ruvim
1
这是我在搜索“如何在Make中更改当前目录”时得到的第二个结果。在您的回答中,您指出有人可能有合法的理由来更改当前目录。您能否在最后添加一小段代码片段,以供那些希望出于问题之外的原因更改当前目录的人参考? - Steve Cox

9
在我使用的面向mingw64(windows)构建的GNU Make程序中,

GNU Make 4.2.1 Built for x86_64-w64-mingw32

我可以使用以下命令来运行此目标,
debug:
    cd $(PREFIX) && $(GDB) kiwigb.exe

结果是目录更改是暂时的,但一切正常。


这不是问题的解决方案。您没有消除到目标的显式路径。 - ruvim
1
我不确定你所说的“显式路径到目标”的意思。我认为这是问题的解决方案,因为它允许您使用cd命令并运行命令。你说过,
从Makefile更改当前工作目录似乎是最简单的解决方案。
- user2262111
好的,看起来“目标”和“目标路径”超出了你的知识范围,但是通过cd命令,你可以在子shell中更改当前目录,而不是在Make中(请参见主题)。因此,这不是一个解决方案。 - ruvim

5

已知方法概述

Paul D. Smith 在 "多架构构建" 论文中对各种分离源目录和目标目录的方法进行了优秀的研究。以下方法被描述(包括它们的缺点):

  • 源代码复制
  • 显式路径(每个目标的引用都以路径名为前缀)
  • VPATH(从目标目录调用构建)
  • 高级 VPATH(自动递归调用)

另一种方法

然而,我找到了更简单的解决方案——使用更小的样板文件且不需要递归调用make。对于带有Guile支持的GNU Make,我们可以使用Guile chdir函数来从Makefile更改当前工作目录。此外,我们还可以在此之前通过mkdir创建目录。
data ?= ./data/

# Create $(data) directory if it is not exist (just for example)
$(guile (if (not (access? "$(data)" F_OK)) (mkdir "$(data)") ))

# Set the new correct value of CURDIR (before changing directory)
CURDIR := $(abspath $(data))

# Change the current directory to $(data)
$(guile (chdir "$(data)"))

# Another way of updating CURDIR
#  — via sub-shell call after changing directory
# CURDIR := $(shell pwd)


# Don't try to recreate Makefile file
# that is disappeared now from the current directory
Makefile : ;

$(info     CURDIR = $(CURDIR)     )
$(info        PWD = $(shell pwd)  )

最终的样板文件以更改当前目录

假设: data 变量在上下文中可用,$(data) 目录的父目录是可访问的,路径可以是相对的。

srcdir := $(realpath $(dir $(lastword $(MAKEFILE_LIST))))
ifeq (,$(filter guile,$(.FEATURES)))
  $(warning Guile is required to change the current directory.)
  $(error Your Make version $(MAKE_VERSION) is not built with support for Guile)
endif
$(MAKEFILE_LIST): ;
$(guile (if (not (file-exists? "$(data)")) (mkdir "$(data)") ))
ORIGCURDIR  := $(CURDIR)
CURDIR      := $(realpath $(data))
$(guile (chdir "$(data)"))
ifneq ($(CURDIR),$(realpath .))
  $(error Cannot change the current directory)
endif
$(warning CURDIR is changed to "$(data)")

记住,include指令中的相对路径默认是从当前目录计算的,因此它取决于位置——是在这个样板之前使用还是之后。

NB: 规则中不应使用$(data);可以使用$(srcdir)来指定相对于此Makefile文件位置的文件。

发现问题

此方法已在GNU Make 4.0和4.2.1中进行了测试。

观察到一个小问题。更改当前目录后,abspath函数会错误地继续根据旧的CURDIR解析相对路径;realpath则正常工作。

此外,此方法可能存在其他尚未知的缺点。


2
主要缺点是它需要您拥有使用Guile支持构建的GNU make版本,这在GNU make 4.0之前并不存在,并且仍然是可选的,因此并非所有构建都可能具备该功能。 - MadScientist
更准确地说,使用此方法需要具备支持Guile的Make,因此与其他方法的缺点相比,它是一种限制。 ;) - ruvim

4

正确的方法是打开一个 man 页面:

man make

通过输入/-C 并按下Enter,查找-C。你会看到如下结果:

   -C dir, --directory=dir
        Change  to  directory dir before reading the makefiles or doing
        anything else.  If multiple -C options are specified,  each  is
        interpreted relative to the previous one: -C / -C etc is equiv‐
        alent to -C /etc.  This is typically used with recursive  invo‐
        cations of make.

那么您可以使用它:

make -C <desired directory> ...

2
请参考Paul D. Smith的原始问题描述。通过-C命令行选项指定目标目录与使用cdpushd更改目录后再运行make一样麻烦。而且你仍然需要将源Makefile放置到目标目录中。 - ruvim

0
BUILD_DIR:=build

SRC_P1:=hello.c bye.c
HDR_P1:=hello.h
OBJ_P1:=$(addprefix $(BUILD_DIR)/,$(SRC_P1:.c=.o))
P1:=$(BUILD_DIR)/hello

CFLAGS=-I.
LDFLAGS=-L.

$(P1): $(BUILD_DIR) $(OBJ_P1) 
  $(LINK.c) $(OUTPUT_OPTION) $(OBJ_P1)

$(OBJ_P1): $(HDR_P1)

$(BUILD_DIR)/%.o: %.c
  $(COMPILE.c) $(OUTPUT_OPTION) $<

$(BUILD_DIR):
  mkdir $@

.PHONY: clean
clean:
  rm -fr $(BUILD_DIR)

这与上面的答案非常相似,不同之处在于可以定义CFLAGS和LDFLAGS而无需在配方部分中显式包含它们。规则$(BUILD_DIR)/%.o: %.c非常有趣,因为它仅适用于定义的对象集,而不是当前目录中的每个.c文件。


是的,这个答案与另一个答案太相似了——没有发布的理由。CFLAGSLDFLAGS与原始问题无关,这是GNU Make的C编译器特定功能 - ruvim
我认为这是问题中存在的问题,即减少冗余代码和命令复杂性。因此,我添加了这一点。 - Jorge Opaso Pazos

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