使用GNU Make链接时参数列表太长

5
我有一个相当大的项目(4272个.o文件),但我无法通过GNU Make进行链接。我遇到了“make:/bin/sh:参数列表太长”的错误。 这是一个使用qmake生成makefile的Qt 5项目。
我知道有很多关于此问题的问题,但我不知道如何将任何解决方案应用到我的问题上。我也不完全确定为什么在链接步骤中遇到此错误。我收到的错误消息是:“make:/bin/sh:参数列表太长”。
用于连接我的项目的makefile条目如下:
build/debug/my_target/my_target:  $(OBJECTS)  
    @test -d build/debug/my_target/ || mkdir -p build/debug/my_target/
    $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJCOMP) $(LIBS)

它会扩展成类似于这样的内容:

@echo linking /build/debug/my_target/my_target && clang++ -ccc-gcc-name g++ -lc++ -L/path/to/licensing/lib -Wl,-rpath,/path/to/qt/lib -Wl,-rpath-link,/path/to/qt/lib -o build/debug/my_target/my_target build/debug/my_target/obj/object1.o build/debug/my_target/obj/object2.o ... build/debug/my_target/obj/object4272.o  ... [ a bunch of moc_X.o ] ... [ a bunch of libs ] -lGL -lpthread -no-pie

这段话有点长,但是接下来的内容有些奇怪:当我把扩展命令放在@echo linking build/debug/my_target/my_target &&后面,并将其放入一个shell脚本中运行时,它可以正常工作。该shell脚本包含202,420个字符(包括#!/bin/sh行)。此外,如果我去掉命令中的@echo ... &&部分,我就可以运行make并进行链接。
另一个解决方法:如果我手动编辑我的makefile文件,使链接命令包含build/debug/my_target/*.o而不是$(OBJECTS),它就可以正常工作:
build/debug/my_target/my_target:  $(OBJECTS)  
    @test -d build/debug/my_target/ || mkdir -p build/debug/my_target/
    $(LINK) $(LFLAGS) -o $(TARGET) build/debug/my_target/*.o $(OBJCOMP) $(LIBS)

我不认为我能让qmake做到这一点,所以除非我能找到另一个解决方案,否则我就被困在手动编辑我的makefile中了。
类似问题的答案似乎侧重于换行符及其在makefile中的处理方式。我的shell脚本只有两行(一个在“#!/bin/sh”之后,一个在实际命令之后)。此外,人们提出的一个解决方案(例如这个)使用for循环来逐个运行每个参数上的命令。我不确定我如何在这里应用它,因为(我认为)我需要所有这些目标文件在我的链接器命令中。
@echo如何导致超过最大参数长度?
我最初提出的问题并不真正相关: (注意:最初发布该问题时,链接命令开头缺少@echo。那似乎是“发生这种情况”的答案,因此我实际上不需要知道第二个问题的答案,在任何情况下,第一个评论已经回答了这个问题。)
我的系统的各种细节可能与此相关: - 我正在运行一个相当新的Arch Linux系统,内核版本为5.8.10 - ARG_MAX值为2097152,xargs --show-limits的输出为:
Your environment variables take up 2343 bytes
POSIX upper limit on argument length (this system): 2092761
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2090418
Size of command buffer we are actually using: 131072
Maximum parallelism (--max-procs must be no greater): 2147483647
  • ulimit -s 输出:8192(我尝试将其设置为更大的值,例如 ulimit -s 65536,但没有成功,这也许并不奇怪,因为 ARG_MAX 似乎比链接器命令大得多)。
  • GNU Make 版本是4.3。
  • clang/clang++ 版本是10.0.1。
  • Qt 版本是5.15.1(我相当确定这与问题无关,我们刚刚将项目从5.9.6切换过来,那时我也有同样的问题)。

3
你可以遵循规则“编写脚本并运行它”,或者逐个将这些对象文件绑定到静态库中,然后再链接。但如果在$(LINK) ...命令前加上echo会发生什么呢?你会得到相同的错误吗?这至少能告诉你是shell还是编译器在抱怨。 - Beta
嗯,是的,但是你的问题让我意识到问题所在,而且我在链接命令中漏掉了一些关键内容:它前面有@echo。去掉这个就解决了,但我不明白为什么会这样。我会相应地编辑问题。 - rainbowgoblin
Linux的ARG_MAX适用于命令行和环境变量的总和;由make设置的变量可能会超过限制。可以通过文件传递GCC参数,例如echo arg1 arg2 ... >args; gcc @args,这将绕过环境限制,但我不知道如何让qmake使用它来生成Makefile。你可以尝试在一个名字较短的目录中构建吗? - ephemient
@ephemient 因为它们被作为一个“单一”参数传递给sh -c,所以make命令的限制要比ARG_MAX小得多,因为Linux还对来自argv或envp的单个字符串的长度施加了更低的限制(<128k IIRC)。 - user10678532
@user414777 我并不是真的想在 Makefile 中使用 echo ...,我同意那样做是毫无意义的。(尽管我可以理解我的话可能会被误解。)我忘记了 MAX_ARG_STRLEN,它是你所说的单个参数限制,为 131072。无论如何,问题在于要找出如何让 qmake 处理这个限制。 - ephemient
奇怪的是,在Ubuntu 18.04虚拟机上编译这个项目时我没有遇到任何问题。这两个系统之间有很多配置差异,我不知道该如何开始比较它们,但是PAGE_SIZE(我理解它决定了MAX_ARG_STRLEN)在两种情况下都是相同的,都是4096。 - rainbowgoblin
2个回答

5

仅供参考,删除 echo 解决问题的原因是(这也是我要建议的):当您删除特殊 shell 运算符 && 并只有一个简单的命令调用而没有像多个命令、特殊引用、通配符等 shell 特性时,make 就会使用“快速路径”来调用您的命令。

也就是说,如果 make 能够确定 shell 对您的命令除了运行之外不会执行任何特殊操作,则它会跳过调用 shell 直接运行您的命令。

在这种情况下,您将不会遇到单参数限制,因为它不使用 /bin/sh -c '...' 形式。

当然,这可能有点神奇和不灵活,因为您必须小心确保链接行中永远不包含特殊的 shell 操作。但如果您可以确保这一点,那么它应该可以解决您的问题。


1
“特殊的 shell 操作” 也包括单引号或双引号。 - user10678532
实际上单引号没问题:make 可以理解它们的工作原理,并且仍然会为它们执行快速路径。我有点惊讶双引号会自动被淘汰,因为 GNU make 中有一些代码尝试检测双引号字符串是否包含特殊字符。可能值得探究一下。 - MadScientist
1
请注意,当前的“特殊字符”集合可以在src/job.c文件中找到;对于POSIX系统,它是:#;"*?[]&|<>(){}$^~!加上反引号。 - MadScientist
还有,显然,快速路径只在你没有将“SHELL”变量重置为非POSIX shell时才可用。 - MadScientist
实际上单引号没问题。是我的错,我在测试时可能搞混了什么。 - user10678532

3
为什么会发生这种情况?我可以在shell脚本中运行的命令,在make中为什么会遇到错误?
因为make通过将shell命令(recipe)作为单个参数传递给/bin/sh -c来运行它们,这不仅会超出操作系统对命令行参数和环境变量的限制,而且还会超出Linux对来自命令行或环境的单个字符串施加的更低限制,通常是128k字节。
如果没有办法将我的命令作为迭代系列的较短命令运行,那么该怎么办?
如@ephemient所建议的那样,可以使用gcc或ld的@arglist参数(指示它从文件中获取其参数),并使用GNU make的file函数创建该arglist文件,由于该文件是内部的,因此不会遇到任何OS限制。

可能需要对qmake进行上游更改,以便在生成的Makefile中使用@arglist,或将其构建拆分为较小的(静态)库,然后将它们链接在一起。否则,这可能是我们能得到的最接近答案的东西。 - ephemient

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