我怎么写'cd'命令在一个makefile?

例如,我在makefile中有这样的东西:

all:
cd some_directory

但是当我输入make时,我只看到'cd some_directory',就像在echo命令中一样。

296892 次浏览

它实际上是在执行命令,将目录更改为some_directory,然而,这是在子进程shell中执行的,并且不会影响make或您正在使用的shell。

如果你想在some_directory您还需要添加一个分号并附加其他命令中执行更多的任务。注意,你不能使用新行,因为它们被make解释为规则的结束,所以你使用的任何新行都需要用反斜杠转义。

例如:

all:
cd some_dir; echo "I'm in some_dir"; \
gcc -Wall -o myTest myTest.c

还要注意,即使添加了反斜杠和换行符,每个命令之间也必须有分号。这是因为shell将整个字符串解析为一行。正如注释中所指出的,您应该使用'&&'来连接命令,这意味着只有在前面的命令成功时才执行这些命令。

all:
cd some_dir && echo "I'm in some_dir" && \
gcc -Wall -o myTest myTest.c

这在做破坏性工作时尤其重要,比如清理,因为如果cd因为任何原因失败,你就会破坏错误的东西。

不过,一种常见的用法是在子目录中调用make,您可能想研究一下这个子目录。这里有一个命令行选项,所以你不必自己调用cd,所以你的规则看起来像这样

all:
$(MAKE) -C some_dir all

它将转换为some_dir并在该目录中执行Makefile,目标为“all”。作为最佳实践,使用$(MAKE)而不是直接调用make,因为它会注意调用正确的make实例(例如,如果你为构建环境使用一个特殊的make版本),以及在使用某些开关(如-t)运行时提供略微不同的行为。

为了记录,make 总是回显它执行的命令(除非显式抑制),即使它没有输出,这就是你所看到的。

你希望它到达那里后做什么?每个命令都在子shell中执行,因此子shell会更改目录,但最终结果是下一个命令仍然在当前目录中。

使用GNU make,你可以做如下的事情:

BIN=/bin
foo:
$(shell cd $(BIN); ls)

这里有一个处理目录和制作的可爱技巧。不要在每个命令上使用多行字符串或“cd;”,而是定义一个简单的chdir函数,如下所示:

CHDIR_SHELL := $(SHELL)
define chdir
$(eval _D=$(firstword $(1) $(@D)))
$(info $(MAKE): cd $(_D)) $(eval SHELL = cd $(_D); $(CHDIR_SHELL))
endef

然后你所要做的就是在你的规则中这样调用它:

all:
$(call chdir,some_dir)
echo "I'm now always in some_dir"
gcc -Wall -o myTest myTest.c

你甚至可以这样做:

some_dir/myTest:
$(call chdir)
echo "I'm now always in some_dir"
gcc -Wall -o myTest myTest.c

GNU make 3.82(2010年7月)开始,你可以使用.ONESHELL特殊目标在shell的单个实例化中运行所有食谱(粗体强调):

  • 新的特殊目标:.ONESHELL指示使调用shell的单个实例并为其提供整个食谱,而不管它包含多少行。
.ONESHELL: # Applies to every targets in the file!


all:
cd ~/some_dir
pwd # Prints ~/some_dir if cd succeeded


another_rule:
cd ~/some_dir
pwd # Prints ~/some_dir if cd succeeded

注意,这相当于手动运行

$(SHELL) $(.SHELLFLAGS) "cd ~/some_dir; pwd"
# Which gets replaced to this, most of the time:
/bin/sh -c "cd ~/some_dir; pwd"

命令没有与&&链接,所以如果你想在第一个失败的命令处停止,你还应该在你的.SHELLFLAGS中添加-e标志:

.SHELLFLAGS += -e

另外,-o pipefail标志可能也会感兴趣:

如果设置了该值,则管道的返回值为最后一个(最右边)以非零状态退出的命令的值,如果管道中的所有命令都成功退出,则为零。默认情况下,该选项是禁用的。

是这样的:

target:
$(shell cd ....); \
# ... commands execution in this directory
# ... no need to go back (using "cd -" or so)
# ... next target will be automatically in prev dir

好运!

更改dir

foo:
$(MAKE) -C mydir


multi:
$(MAKE) -C / -C my-custom-dir   ## Equivalent to /my-custom-dir

下面是我使用的模式:

.PHONY: test_py_utils
PY_UTILS_DIR = py_utils
test_py_utils:
cd $(PY_UTILS_DIR) && black .
cd $(PY_UTILS_DIR) && isort .
cd $(PY_UTILS_DIR) && mypy .
cd $(PY_UTILS_DIR) && pytest -sl .
cd $(PY_UTILS_DIR) && flake8 .


我使用这种模式的动机是:

  • 上面的解决方案简单易读(尽管冗长)
  • 我读了经典的论文“递归认定有害”;,它不鼓励我使用$(MAKE) -C some_dir all
  • 我不想只使用一行代码(以分号或&&分隔),因为它可读性较差,而且我担心在编辑make配方时会出现拼写错误。
  • 我不想使用.ONESHELL特殊目标,因为:
    • 这是一个全局选项,影响makefile中的所有食谱
    • 使用.ONESHELL会导致配方的所有行都被执行,即使前面的某行已经失败,退出状态为非零。像调用set -e这样的解决方法是可能的,但这些解决方法必须为makefile中的每个食谱实现。