CMake: use a custom linker

CmakeLinker

Cmake Problem Overview


I want to setup a custom toolchain with CMake. I've set the compiler but I don't know how to set the linker. This error is reported because CMake try to use the compiler to link:

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

Here there is a snippet of my toolchain file

# 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)

I've tried to force the compiler but the linker problem will not be solved.

Cmake Solutions


Solution 1 - Cmake

The link command line is set in Modules/CMake{C,CXX,Fortran}Information.cmake and defaults to using the compiler, not CMAKE_LINKER (see source code). This can be changed by replacing the rule that builds the link command line, which lives in variables CMAKE_CXX_LINK_EXECUTABLE (and friends). NB that variable does not indicate the path to the linker executable; it says how to link an executable!

One approach is to set that rule to use the linker, e.g.

cmake -DCMAKE_LINKER=/path/to/linker -DCMAKE_CXX_LINK_EXECUTABLE="<CMAKE_LINKER> <FLAGS> <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>"

See also this post from CMake mailing list and this one - this also makes a natural place to prepend a linker modifier to another linker.

Solution 2 - Cmake

As Mabraham points out, CMake calls the compiler to do the linking. So, by far the simplest solution to this is to LET IT, and instead tell the compiler to run a different linker when called.

Which, as noted in this other answer — but now it's even a documented option in gcc --help=common — is as easy as:

cmake -DCMAKE_CXX_FLAGS="-fuse-ld=lld"

g++ or clang++ will get passed the -fuse-ld=lld1 flag on every call, and when they do any linking they'll use the specified command instead of the built-in default. Easy-peasy, and CMake need not concern itself with such things at all.

(BTW, the option is parsed (-f) (use-ld) (=) (lld), there's no "fuse" option to gcc.)

Notes
  1. When using Clang, lld can be replaced with whatever other linker command you want to use, like ld.exe, ld.gold, mingw32/bin/ld.exe, etc.

    GCC isn't as flexible, its -fuse-ld only accepts one of three possible arguments: lld, bfd, or gold. It will invoke the first matching ld.foo executable it finds on the PATH. (Thanks to bviktor for pointing out GCC's limitations for alternate linker selection.)

Solution 3 - Cmake

Set the variable ${CMAKE_LINKER} either in CMakeCache.txt or after ccmake . under advanced options.

Solution 4 - Cmake

I have to use CMAKE_CXX_LINK_EXECUTABLE, CMAKE_C_LINK_EXECUTABLE variable:

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

Solution 5 - Cmake

CMake only gives you direct control over the compiler for each language. To call the linker, it goes through the configured compiler. This means that there is no universal way to set the linker in CMake, you must configure your compiler to use the linker you intend.

Such flags need to be set before CMake's compiler detection routines run because it will try to compile a test binary. The best way to do this is by creating a toolchain file. The best way to set these flags in the toolchain file is like so:

# 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")

These three variables control the (default) set of linker flags for executables, loadable modules, and shared libraries, respectively. There is no need to handle CMAKE_STATIC_LINKER_FLAGS_INIT (for static libraries) here because the archiver is invoked, rather than the linker.

You can then set the toolchain file when you first run CMake by setting -DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain.cmake at the command line. As of CMake 3.21, you will be able to pass --toolchain /path/to/toolchain.cmake instead (which is entirely equivalent, but a little less typing).

Solution 6 - Cmake

I had success with doing

add_link_options("-fuse-ld=lld")

It is a variation on the previous answers here. The difference is the CMake command I use to set the flag.

Adding it to CMAKE_CXX_FLAGS has the disadvantage of then also having to add -Wno-unused-command-line-argument as the flags get also added to compilation commands, not only to linking ones.

The disadvantage of CMAKE_SHARED_LINKER_FLAGS is that you have to add it multiple times, to _SHARED_, _EXE_, and maybe I forgot something.

Solution 7 - Cmake

For completeness, another full-proof option is to just link /usr/bin/ld to ld.gold by running

sudo ln -sf /usr/bin/x86_64-linux-gnu-ld.gold /usr/bin/ld

as suggested here

Solution 8 - Cmake

There is another way to do it, gcc has a "-fuse-ld" option, you can set LINKER_FLAGS in CMakeLists.txt like these:

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

then the custom specified linker should be invoked.

Solution 9 - Cmake

Here is a CMake function which sets linker based on some predefined arbitrary rules (Clang -> lld-version or lld, GCC -> gold).

The important parts:

  1. Search for lld-version which matches the Clang compiler version (ex. lld-13 if Clang 13.x.x is used), falls back to lld if not found
    add_link_options("-fuse-ld=lld-${CLANG_VERSION_MAJOR}")
  1. Use all system threads when linker is set to gold:
    add_link_options("-fuse-ld=gold;LINKER:--threads,--thread-count=${HOST_PROC_COUNT}")

The example is a bit too long because of comments, logs and custom logic, but it is self-contained and could be useful staring point for beginners.

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)

Tested on:

  • 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 (compatible with GNU linkers)
  • Ubuntu LLD 13.0.1 (compatible with GNU linkers)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBreezeightView Question on Stackoverflow
Solution 1 - CmakemabrahamView Answer on Stackoverflow
Solution 2 - CmakeFeRDView Answer on Stackoverflow
Solution 3 - CmakeGunther PiezView Answer on Stackoverflow
Solution 4 - CmakeBreezeightView Answer on Stackoverflow
Solution 5 - CmakeAlex ReinkingView Answer on Stackoverflow
Solution 6 - Cmakeuser7610View Answer on Stackoverflow
Solution 7 - CmakeVikView Answer on Stackoverflow
Solution 8 - CmakebuffyView Answer on Stackoverflow
Solution 9 - CmakeMartinBGView Answer on Stackoverflow