ChatGPT解决这个技术问题 Extra ChatGPT

如何在 makefile 中编写“cd”命令?

例如,我的 makefile 中有这样的内容:

all:
     cd some_directory

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

目前尚不清楚您想要做什么,但是根据我使用 make 的经验,我从不想像这样更改目录。也许您应该尝试另一种解决方案?
相信您的目录很重要是一个常见的新手错误。对于大多数事情,它不是; cd dir; cmd file 几乎总是可以更有用地表示为 cmd dir/file
认为您当前的工作目录无关紧要是一个常见的新手错误。许多程序,尤其是 shell 脚本,在编写时都考虑到了特定的 . 值。确实,大多数工具的设计方式都不需要更改密码。但这并不总是正确的,我认为将其称为“错误”以相信您的目录可能很重要并不是一个好主意。
提示:如果您的 cd 命令显示“没有这样的文件或目录”,即使(相对)目录确实存在,请检查您的 CDPATH 环境变量是否为空或包含“.”。 Make 执行带有“sh”的命令,如果设置了它,它只会通过 CDPATH 找到相对路径。这与 bash 形成对比,后者将尝试 .在咨询 CDPATH 之前。
补充一下@tripleee 所说的(十二年前,是的),有 个实例表明当前目录很重要。例如,在 MacOS 上,zip 命令将在压缩存档的结构中包含整个给定的搜索路径,这可能是不可取的。

f
falstro

它实际上是在执行命令,将目录更改为 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 总是回显它执行的命令(除非明确禁止),即使它没有输出,这就是您所看到的。


好吧,并非总是如此。要抑制回声,只需将 @ 放在行首。
@Beta:是的,破折号前缀也忽略了错误状态。也许我有点得意忘形,我想指出 make 确实会响应命令的事实,无论它是哪种命令。在这种情况下,它是一个没有输出的命令,这使得回显对于不熟悉 make 的人来说似乎更加陌生。
两点: 1. 命令真的应该由 && 加入,因为如果目录不存在并且 cd 失败,则使用 ;,shell 将继续运行当前目录中的其余命令,这可能会导致编译时出现神秘的“未找到文件”消息、调用 make 时出现无限循环或 clean:: cd dir; rm -rf * 等规则的灾难。 2. 调用 sub-makes 时,调用 $(MAKE) 而不是 make,以便 options will be passed on correctly
@perreal,我通常定义这样的模式规则:%-recursive: with body: @T="$@";$(MAKE) -C some_dir $${T%-*} (我通常也有一个for循环,循环子目录列表,$${T%-*} 是一个bash扩展,它删除目标名称的 -recursive 部分),然后为每个目标定义明确的速记(和 .PHONY)目标,例如 all: all-recursivecheck: check-recursiveclean: clean-recursive
@ChristianStewart,是的,正如评论 2 和 3 中提到的那样。
C
Chnossos

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 命令之前仍会返回目录,因此您不能直接使用它们。
是的,因为变量和函数扩展是在 make 执行命令之前完成的,而 pwd`pwd` 是由 shell 本身执行的。
不是每个人都使用遗留的 makefile,即使这样,这个答案也是关于知道这种可能性存在。
这可能是一个烦人/危险的选项,因为只有目标的最后一个命令会导致失败(任何早期的命令失败都将被忽略),并且可能没有人会与您一起工作。
SHELLFLAGS 设置为 -e -c,shell 将在第一个失败的命令处退出。
J
JoeS

这是处理目录和制作的一个可爱技巧。而不是使用多行字符串或“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

“可爱的”?更像是足够的绳索,可以射中自己的脚。
这个当前目录是为该规则中的命令设置,还是为所有随后执行的规则设置?此外,这种方法的一些变化是否可以在 Windows 下工作?
这当然会中断并行执行(-jn),这确实是 make 的重点。
这是一个糟糕的黑客行为。如果您不得不求助于这样的事情,那么您并没有使用 Makefile 来实现它们的目的。
我不会不同意这是一个糟糕的黑客,这是肯定的。但确实展示了你可以做的一些邪恶的事情。
N
Nadir SOUALEM

一旦它到达那里,你希望它做什么?每个命令都在一个子shell中执行,所以子shell改变了目录,但最终的结果是下一个命令仍然在当前目录中。

使用 GNU make,您可以执行以下操作:

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

为什么 cd $(BIN); lscd $(BIN) && ls 时的 $(shell ...) (正如@andrewdotn 指出的那样)就足够了。
J
Jasha

这是我使用的模式:

.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 中的每个配方实施这种变通方法。


j
jackotonye

更改目录

foo: 
    $(MAKE) -C mydir

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

R
Rubik's Cube

像这样:

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 时执行,而不是在运行此特定配方时执行。
@triplee 不完全——$(shell) 仅在 make 决定构建 target 时展开。如果 make 永远不需要配方,它就不会扩展它。