CMake:使用自定义链接器

77

我想使用CMake设置自定义工具链,已经设置了编译器,但不知道如何设置链接器。由于CMake尝试使用编译器进行链接,所以会报告此错误:

The C compiler "xgcc.exe" is not able to compile a simple test program.

下面是我的工具链文件的一部分代码片段

# specify the cross compiler
INCLUDE(CMakeForceCompiler)
SET(CMAKE_C_COMPILER   xgcc.exe)
SET(CMAKE_CXX_COMPILER xgcc.exe)
#CMAKE_FORCE_C_COMPILER(xgcc.exe GNU)
#CMAKE_FORCE_CXX_COMPILER(xgcc.exe GNU)

我尝试过强制编译器,但链接器问题无法解决。

11个回答

47
链接命令行设置在Modules/CMake{C,CXX,Fortran}Information.cmake中,默认使用编译器而不是CMAKE_LINKER(参见源代码)。可以通过替换构建链接命令行的规则来更改此设置,该规则位于变量CMAKE_CXX_LINK_EXECUTABLE(以及相关变量)中。注意,该变量并不表示链接器可执行文件的路径;它表示如何链接可执行文件!
编辑:定制此行为的最佳方法可以在FeRD的答案中找到。
一种方法是将该规则设置为使用链接器,例如:
cmake -DCMAKE_LINKER=/path/to/linker -DCMAKE_CXX_LINK_EXECUTABLE="<CMAKE_LINKER> <FLAGS> <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>"

另请参阅CMake邮件列表中的此帖子这个帖子 - 这也是一个自然的地方,在另一个链接器之前添加链接器修饰符。

1
如何在 -DCMAKE_CXX_LINK_EXECUTABLE="<CMAKE_LINKER> <FLAGS> <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>" 中定义 OBJECTS - Ezio
3
这个规则没有具体说明任何内容。它是用于指导cmake构建可执行文件的,因此它会插入从您在CMakeLists.txt文件中添加到目标的源代码编译出来的对象。 - mabraham
或者,我如何更改像CMAKE_CXX_LINK_FLAGS、LINK_FLAGS这样的字符串的值? - Ezio
1
@mabraham 覆盖CMAKE_CXX_LINK_EXECUTABLE会让CMake混淆,在链接示例C应用程序的初始测试中,它不会提供对象文件。我得到了"The C compiler ... is not able to compile a simple test program. clang++: error: no input files"。也许这就是Ezio在他的第一条评论中所说的。CMake版本:3.12.4。另请参阅https://cmake.org/pipermail/cmake-developers/2014-June/022364.html。 - Zuzu Corneliu
1
这是设置CMAKE_CXX_LINK_EXECUTABLE默认值的CMake 源代码。虽然我不知道为什么它没有使用CMAKE_LINKER - cyfdecyf
显示剩余6条评论

37
正如Mabraham所指出的那样,CMake调用编译器进行链接。因此,迄今为止最简单的解决方案是让它自己处理,而是在调用时告诉编译器运行不同的链接器。
正如这个答案中所提到的那样 - 现在甚至在gcc --help=common中有了记录选项 - 这很容易:
cmake -DCMAKE_CXX_FLAGS="-fuse-ld=lld"

g++clang++在每次调用时都会传递-fuse-ld=lld1标志,当它们进行任何链接时,它们将使用指定的命令而不是内置默认值。非常简单,CMake根本不需要关心这些事情。

(顺便说一下,该选项被解析为(-f) (use-ld) (=) (lld),没有gcc的"fuse"选项。)

注释

  1. 使用 Clang 时,lld 可以被替换为你想要使用的其他链接器命令,例如 ld.exe, ld.gold, mingw32/bin/ld.exe 等。

    GCC 不如 Clang 灵活,它的 -fuse-ld 仅接受有限的一组可能参数。(在 GCC 12.2.1 中,它们列在 gcc --help=common 输出中,列表包括 bfd, gold, lldmold)。GCC 将调用在 PATH 上找到的匹配 ld.foo 的第一个可执行文件。(感谢 bviktor 指出了 GCC 替代链接器选择的限制。)


3
在所有呈现的“解决方案”中,这是唯一对我实际起作用的命令行解决方案(cmake 3.19.2)。 - sdenham
2
@0xC0000022L 我同意这并不是一个理想的解决方案,但对于任何像我一样找到这个问题并尝试使用 CMake 项目的人来说,因为该项目确实依赖于编译器驱动程序来调用链接器,这可能是所有在这里提出的解决方案中最好的一个。 - sdenham
1
@FeRD 不,我的意思是这个变量设置是错误的。它确实可以工作,不要误解我的意思。但是每个编译单元被编译时都会导致一个未使用命令行开关的警告。因为该开关只在链接步骤中才真正相关。所以虽然这可以工作,但很难称之为最佳实践。在GNUmakefile中,您通常不会使用CXXFLAGS来传递链接器标志。无论如何,我为此答案的一般实用性投了赞成票,尽管存在这个缺陷。 - 0xC0000022L
2
@FeRD 啊,但是有的。只是你必须为每种类型的构件链接单独进行设置。CMAKE_EXE_LINKER_FLAGSCMAKE_SHARED_LINKER_FLAGS就是其中的两个。 - 0xC0000022L
1
@bviktor 我的 Fedora 打包安装的 LLVM 11 提供了 RPM lld-11.0.0-1.fc33,其中包括链接器二进制文件 /usr/bin/lld。无论如何,如果这是可执行文件的名称,使用 -fuse-ld=lld-11 有什么问题呢?正如我在答案中所说,您可以指定任何链接器可执行文件。 - FeRD
显示剩余7条评论

10

我在进行时取得了成功

add_link_options("-fuse-ld=lld")

这是之前回答的一种变体。区别在于我使用的CMake命令来设置标志位不同。
将其添加到CMAKE_CXX_FLAGS中的缺点是,由于标志位也被添加到编译命令中,而不仅仅是链接命令,因此必须添加-Wno-unused-command-line-argumentCMAKE_SHARED_LINKER_FLAGS 的缺点是您需要多次添加它,例如_SHARED__EXE_,可能还有其他内容,我可能忘了。

这是正确的解决方案。没有冗长且容易出错的命令行。如上所建议的 cmake -DCMAKE_CXX_FLAGS="-fuse-ld=lld" 如果在使用 -Werror 进行编译时不会因为未使用该标志而引起错误,那么它将是一个很好的解决方案,但实际上不可行。 - swineone
为了完整起见,还有CMAKE_MODULE_LINKER_FLAGSCMAKE_STATIC_LINKER_FLAGS。当然,还有可能是单独的CMAKE_SHARED_LINKER_FLAGS_DEBUGCMAKE_SHARED_LINKER_FLAGS_RELEASE等等...虽然我不知道仅针对某些配置修改链接器会有什么优势。 - FeRD
1
这个答案取决于正确的编译器。对于GCC和Clang,设置链接器具有不同的语法。 - usr1234567
@usr1234567 一般来说,你是正确的。值得庆幸的是,在指定链接器方面,GCC和Clang之间存在一些收敛 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93645。这里有一个合理的方法示例,使用变量插值来维护不同编译器的分离标志,有时是必要的 https://github.com/apache/qpid-proton/blob/e47b0805abe5bd25a4f979142d620f498b296407/CMakeLists.txt#L71-L78。 - user7610

10
CMake只能直接控制每种语言的编译器。要调用链接器,它会通过配置的编译器进行。这意味着在CMake中没有通用的方法来设置链接器,您必须配置编译器以使用您打算使用的链接器。这些标志需要在CMake的编译器检测程序运行之前设置,因为它将尝试编译测试二进制文件。最好的方法是创建一个工具链文件。在工具链文件中设置这些标志的最佳方法如下:
# e.g. to use lld with Clang
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld")

这三个变量分别控制可执行文件、可加载模块和共享库的(默认)链接器标志。这里不需要处理CMAKE_STATIC_LINKER_FLAGS_INIT(用于静态库),因为使用的是存档程序而不是链接器。
您可以在首次运行CMake时通过在命令行设置-DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain.cmake来设置工具链文件。从CMake 3.21开始,您还可以传递--toolchain /path/to/toolchain.cmake(完全等效,但打字稍少)。

1
谢谢,还有解释说明,我正需要这个。 - undefined

6

要设置变量 ${CMAKE_LINKER},可以在CMakeCache.txt中设置,也可以在ccmake . 的高级选项下设置。


1
我可以在CMakeLists.txt中设置它吗? 我刚在cmake手册中查找了CMAKE_LINKER,但是我没有找到它。您确定变量的名称吗? - Breezeight
2
是的。在您的构建目录中调用 ccmake . 并按下 't'。向下滚动,直到找到 CMAKE_LINKER。 - Gunther Piez
这种使用编译器而不是链接器的行为似乎是有意为之的(但我不确定它是否是一项功能!)-请参阅我的答案。 - mabraham
2
我以前做的就是这样,现在却不起作用了。就好像cmake根本不考虑这个变量:将CMAKE_LINKER设置为完全无意义的值并没有改变错误(或命令行)的任何细微之处。CMake 3.14.5。 - ulidtko
1
@mabraham 一个例子是当编译标志需要链接运行时库(例如fsanitize)时,它可以成为一个特性。 - pooya13
显示剩余3条评论

6

我需要使用CMAKE_CXX_LINK_EXECUTABLE和CMAKE_C_LINK_EXECUTABLE变量:

SET(CMAKE_C_LINK_EXECUTABLE "c:\\MoSync\\bin\\pipe-tool.exe")

28
大多数情况下这样不起作用 - 那个变量确定了整个链接命令的规则,通常需要标记和参数来进行链接。 - mabraham

3
以下是一个CMake函数,它基于一些预定义的任意规则设置链接器(Clang -> lld-version或lld,GCC -> gold)。
重点如下:
1. 搜索与Clang编译器版本匹配的"lld-version"(例如,如果使用Clang 13.x.x,则匹配lld-13),如果未找到,则回退到"lld"。
请注意保留HTML标记。
    add_link_options("-fuse-ld=lld-${CLANG_VERSION_MAJOR}")
  1. 当链接器设置为gold时,请使用所有系统线程:
    add_link_options("-fuse-ld=gold;LINKER:--threads,--thread-count=${HOST_PROC_COUNT}")

由于注释、日志和自定义逻辑的存在,这个示例有点过长,但它是一个独立的实例,对初学者来说可能会有用。

function(select_best_linker) #lld for Clang and GNU gold for GCC
    if (UNIX AND NOT APPLE)
        include(ProcessorCount)
        ProcessorCount(HOST_PROC_COUNT)

        if(${CMAKE_CXX_COMPILER_ID} MATCHES Clang)

            # By default LLD uses all system threads.
            # This could be tweaked for versions 11+ (--threads=1), but cannot be disabled for older versions
            # add_link_options("-fuse-ld=lld-${CLANG_VERSION_MAJOR};LINKER:--threads=${HOST_PROC_COUNT}") #LLD>=11
            # add_link_options("-fuse-ld=lld;LINKER:--threads")#LLD <= 10 this is the default state

            string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION})
            list(GET VERSION_LIST 0 CLANG_VERSION_MAJOR) #extract major compiler version

            find_program(LLD_PROGRAM_MATCH_VER lld-${CLANG_VERSION_MAJOR}) #search for lld-13 when clang 13.x.x is used
            find_program(LLD_PROGRAM lld) #else find default lld

            if (LLD_PROGRAM_MATCH_VER) #lld matching compiler version
                message(STATUS "Set linker to LLD (multi-threaded): ${LLD_PROGRAM_MATCH_VER}")
                add_link_options("-fuse-ld=lld-${CLANG_VERSION_MAJOR}")
            elseif(LLD_PROGRAM) #default lld
                message(STATUS "Set linker to LLD (multi-threaded): ${LLD_PROGRAM}")
                add_link_options("-fuse-ld=lld")
            endif(LLD_PROGRAM_MATCH_VER)

        elseif(${CMAKE_CXX_COMPILER_ID} MATCHES GNU)

            find_program(GNU_GOLD_PROGRAM gold)
            if (GNU_GOLD_PROGRAM)
                message(STATUS "Set linker to GNU gold: ${GNU_GOLD_PROGRAM}, using threads: ${HOST_PROC_COUNT}")
                add_link_options("-fuse-ld=gold;LINKER:--threads,--thread-count=${HOST_PROC_COUNT}")
            endif(GNU_GOLD_PROGRAM)

        endif(${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
    endif(UNIX AND NOT APPLE)
endfunction(select_best_linker)

测试环境:

  • Ubuntu 20.04
  • CMake 3.16.3
  • GCC 9.4.0
  • Clang-12
  • Clang-13
  • GNU gold (GNU Binutils 2.37) 1.16
  • LLD 10.0.0 (与GNU链接器兼容)
  • Ubuntu LLD 13.0.1 (与GNU链接器兼容)

2
为了完整起见,另一个全面的选择是通过运行以下命令将/usr/bin/ld链接到ld.gold: sudo ln -sf /usr/bin/x86_64-linux-gnu-ld.gold /usr/bin/ld 如此建议在这里

1

还有一种方法可以做到这一点,gcc有一个 "-fuse-ld" 选项,您可以像下面这样在 CMakeLists.txt 中设置 LINKER_FLAGS:

set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")

然后应该调用自定义指定的链接器。

1
从(尚未发布的)版本3.29开始,CMake提供了一种设置链接器的方法。
  • 将变量CMAKE_LINKER_TYPE设置为预定义变量LLDGOLDMOLDBFDMSVC或更奇特的变量之一。有关更多详细信息,请参阅文档
  • 如果预设不足够,您可以使用链接器的路径或编译器的标志来设置CMAKE_<LANG>_USING_LINKER_<TYPE>。通过设置变量CMAKE_<LANG>_USING_LINKER_MODE来告诉CMake您的选择(文档)。

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