例如,我的 makefile 中有这样的内容:
all:
cd some_directory
但是当我输入 make
时,我只看到了“cd some_directory”,就像在 echo
命令中一样。
make
的经验,我从不想像这样更改目录。也许您应该尝试另一种解决方案?
cd dir; cmd file
几乎总是可以更有用地表示为 cmd dir/file
。
.
值。确实,大多数工具的设计方式都不需要更改密码。但这并不总是正确的,我认为将其称为“错误”以相信您的目录可能很重要并不是一个好主意。
zip
命令将在压缩存档的结构中包含整个给定的搜索路径,这可能是不可取的。
它实际上是在执行命令,将目录更改为 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 总是回显它执行的命令(除非明确禁止),即使它没有输出,这就是您所看到的。
从 GNU make 3.82(2010 年 7 月)开始,您可以使用 .ONESHELL
特殊目标在 shell 的单个实例中运行所有配方(粗体强调我的):
新的特殊目标: .ONESHELL 指示 make 调用 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"
命令未与 &&
链接,因此如果您想在第一个失败的命令处停止,您还应该将 -e
标志添加到您的 .SHELLFLAGS
:
.SHELLFLAGS += -e
-o pipefail
标志可能也很有趣:
如果设置,则管道的返回值是以非零状态退出的最后一个(最右边)命令的值,如果管道中的所有命令成功退出,则返回零。默认情况下禁用此选项。
pwd
本身和 `pwd`
(带有反引号)一样有效,但 $(shell pwd)
和 $(PWD)
在执行 cd
命令之前仍会返回目录,因此您不能直接使用它们。
pwd
和 `pwd`
是由 shell 本身执行的。
SHELLFLAGS
设置为 -e -c
,shell 将在第一个失败的命令处退出。
这是处理目录和制作的一个可爱技巧。而不是使用多行字符串或“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
-jn
),这确实是 make 的重点。
一旦它到达那里,你希望它做什么?每个命令都在一个子shell中执行,所以子shell改变了目录,但最终的结果是下一个命令仍然在当前目录中。
使用 GNU make,您可以执行以下操作:
BIN=/bin
foo:
$(shell cd $(BIN); ls)
cd $(BIN); ls
或 cd $(BIN) && ls
时的 $(shell ...)
(正如@andrewdotn 指出的那样)就足够了。
这是我使用的模式:
.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 .
我对这种模式的动机是:
上面的解决方案简单易读(虽然很冗长)
我阅读了经典论文“Recursive Make Considered Harmful”,它阻止了我使用 $(MAKE) -C some_dir all
我不想只使用一行代码(用分号或 && 标点符号),因为它的可读性较差,而且我担心在编辑 make 配方时会打错字。
我不想使用 .ONESHELL 特殊目标,因为:这是一个全局选项,使用 .ONESHELL 会影响 makefile 中的所有配方,即使前面的行之一以非零值失败,也会导致执行配方的所有行退出状态。像调用 set -e 这样的变通方法是可能的,但是必须为 makefile 中的每个配方实施这种变通方法。
这是一个全局选项,会影响 makefile 中的所有配方
使用 .ONESHELL 会导致执行配方的所有行,即使前面的行之一以非零退出状态失败。像调用 set -e 这样的变通方法是可能的,但是必须为 makefile 中的每个配方实施这种变通方法。
更改目录
foo:
$(MAKE) -C mydir
multi:
$(MAKE) -C / -C my-custom-dir ## Equivalent to /my-custom-dir
像这样:
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
祝你好运!
$(shell cd ....)
在最初解析 Makefile 时执行,而不是在运行此特定配方时执行。
$(shell)
仅在 make 决定构建 target 时展开。如果 make 永远不需要配方,它就不会扩展它。
&&
加入,因为如果目录不存在并且cd
失败,则使用;
,shell 将继续运行当前目录中的其余命令,这可能会导致编译时出现神秘的“未找到文件”消息、调用 make 时出现无限循环或clean:: cd dir; rm -rf *
等规则的灾难。 2. 调用 sub-makes 时,调用$(MAKE)
而不是make
,以便 options will be passed on correctly。%-recursive:
with body:@T="$@";$(MAKE) -C some_dir $${T%-*}
(我通常也有一个for循环,循环子目录列表,$${T%-*}
是一个bash扩展,它删除目标名称的-recursive
部分),然后为每个目标定义明确的速记(和 .PHONY)目标,例如all: all-recursive
、check: check-recursive
、clean: clean-recursive
。