CMake:将 add_library 与许多源一起使用并在 target_link_libraries 中使用时出现链接错误

CMake: Linking error when using add_library with many sources and using it in target_link_libraries

完整列表在最后,这里是具体部分:

add_library(common common.h utils.h utils.cc)


add_executable(type_test type_test.cc)
target_link_libraries(type_test
        ${GTEST_BOTH_LIBRARIES}
        ast
        common
        compilation_context
        easyloggingpp
        functions
        lexer
        parser
        ${CMAKE_THREAD_LIBS_INIT})
add_test(type_test COMMAND out/type_test)

现在当我 运行 cmake . --GNinja 和以后 ninja type_test 时,我得到:


    [0/1] Re-running CMake...
    -- Found LLVM 6.0.1
    -- Using LLVMConfig.cmake in: /usr/lib/llvm-6.0/cmake
    -- Configuring done
    -- Generating done
    -- Build files have been written to: /home/gru/Code/schwifty
    [1/1] Linking CXX executable out/type_test
    FAILED: out/type_test 
    : && /usr/bin/clang++  -DELPP_FEATURE_CRASH_LOG   CMakeFiles/type_test.dir/type_test.cc.o  -o out/type_test  /usr/local/lib/libgtest.a /usr/local/lib/libgtest_main.a out/libast.a out/libcommon.a out/libcompilation_context.a out/libeasyloggingpp.a out/libfunctions.a out/liblexer.a out/libparser.a -lpthread && :
    /usr/bin/ld: out/libcompilation_context.a(type.cc.o): in function `schwifty::Types::parse_type_string_internal(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)':
    type.cc:(.text+0x18cb): undefined reference to `schwifty::utils::startswith(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
    /usr/bin/ld: type.cc:(.text+0x1c04): undefined reference to `schwifty::utils::startswith(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    ninja: build stopped: subcommand failed.

schwifty::utils::startswith肯定是在utils.h中声明的,在utils.cc中实现的。

此处提供完整清单:


    cmake_minimum_required(VERSION 3.10)
    project(schwifty)

    set(CMAKE_CXX_STANDARD 14)

    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DELPP_FEATURE_CRASH_LOG")

    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out)
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out)

    find_package(PythonInterp 3.6 REQUIRED)

    file(MAKE_DIRECTORY downloads external)

    #
    # Easylogging++
    #
    if (EXISTS "external/easyloggingpp")
    else ()
        file(MAKE_DIRECTORY external/easyloggingpp)
        file(DOWNLOAD https://github.com/muflihun/easyloggingpp/archive/v9.96.4.zip
                downloads/easyloggingpp.zip)
        execute_process(COMMAND unzip downloads/easyloggingpp.zip -d downloads)
        file(GLOB easyloggingpp_files
                downloads/easyloggingpp-9.96.4/src/easylogging++.*)
        file(COPY ${easyloggingpp_files} DESTINATION external/easyloggingpp)
    endif ()

    include_directories(external/easyloggingpp)
    add_library(easyloggingpp external/easyloggingpp/easylogging++.cc)

    #
    # Local lib targets
    #

    add_library(ast ast.h ast.cc)

    add_library(ast_compare_visitor ast_compare_visitor.h ast_compare_visitor.cc)

    add_library(classes classes.h classes.cc)

    add_library(codegen
            codegen.h
            codegen.cc
            codegen_common.h
            codegen_common.cc
            expression_type_visitor.cc
            expression_type_visitor.h)

    add_library(common common.h utils.h utils.cc)

    add_library(compilation_context
            compilation_context.h
            compilation_context.cc
            enum.h
            enum.cc
            errors.h
            errors.cc
            operators.h
            operators.cc
            type.h
            type.cc)

    add_library(functions functions.h functions.cc)

    add_library(jit jit.cc jit.h)

    add_library(lexer lexer.cc lexer.h lexer_common.cc lexer_common.h)

    add_library(parser parser.h parser.cc)

    add_library(runtime runtime.cc runtime.h)

    add_library(type_inference
            type_inference.h
            type_inference.cc
            symbol_visitor.cc
            symbol_visitor.h
            type_inference_visitor.cc
            type_inference_visitor.h)

    #
    # External lib targets
    #

    find_package(LLVM REQUIRED CONFIG)

    message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
    message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")

    include_directories(${LLVM_INCLUDE_DIRS})
    add_definitions(${LLVM_DEFINITIONS})

    llvm_map_components_to_libnames(llvm_libs all)

    find_package(FMT REQUIRED CONFIG)

    #
    # Schwifty main executable
    #

    add_executable(schwifty schwifty.cc)
    target_link_libraries(schwifty
            ${llvm_libs}
            ast
            classes
            codegen
            common
            compilation_context
            easyloggingpp
            fmt::fmt
            functions
            lexer
            parser
            runtime
            type_inference)

    #
    # Testing
    #

    enable_testing()
    find_package(GTest REQUIRED)

    find_package(Threads)

    include_directories(${GTEST_INCLUDE_DIRS})

    add_executable(codegen_test codegen_test.cc)
    target_link_libraries(codegen_test
            ${GTEST_BOTH_LIBRARIES}
            ${llvm_libs}
            easyloggingpp
            ast
            classes
            codegen
            common
            compilation_context
            fmt::fmt
            functions
            jit
            lexer
            parser
            runtime
            type_inference
            ${CMAKE_THREAD_LIBS_INIT})
    add_test(codegen_test COMMAND out/codegen_test)

    add_executable(lexer_test lexer_test.cc)
    target_link_libraries(lexer_test
            ${GTEST_BOTH_LIBRARIES}
            ast
            common
            compilation_context
            easyloggingpp
            functions
            lexer
            parser
            fmt::fmt
            ${CMAKE_THREAD_LIBS_INIT})
    add_test(lexer_test COMMAND out/lexer_test)

    add_executable(parser_test parser_test.cc)
    target_link_libraries(parser_test
            ${GTEST_BOTH_LIBRARIES}
            ast
            ast_compare_visitor
            compilation_context
            common
            easyloggingpp
            functions
            lexer
            parser
            fmt::fmt
            ${CMAKE_THREAD_LIBS_INIT})
    add_test(parser_test COMMAND out/parser_test)

    add_executable(type_test type_test.cc)
    target_link_libraries(type_test
            ${GTEST_BOTH_LIBRARIES}
            ast
            common
            compilation_context
            easyloggingpp
            functions
            lexer
            parser
            ${CMAKE_THREAD_LIBS_INIT})
    add_test(type_test COMMAND out/type_test)

    add_executable(type_inference_test type_inference_test.cc)
    target_link_libraries(type_inference_test
            ${GTEST_BOTH_LIBRARIES}
            easyloggingpp
            ast
            classes
            common
            compilation_context
            functions
            fmt::fmt
            lexer
            parser
            runtime
            type_inference
            ${CMAKE_THREAD_LIBS_INIT})
    add_test(type_inference_test COMMAND ./out/type_inference_test)

    add_test(NAME end_to_end_tests
            WORKING_DIRECTORY ${CTEST_SOURCE_DIRECTORY}
            COMMAND ${PYTHON_EXECUTABLE} end_to_end_tests.py)

此处提供所有代码:https://bitbucket.org/gruszczy/schwifty/src/default/

使用 bfd ld 的静态库的链接顺序很重要。 (不确定黄金)。 libcommon.a 在 libcompilation_context.a 出现之前被读取、处理和删除,这需要在 libcommon.a 中声明的符号。

解决这个问题的老办法是 ld c.o -lcommon -lcompilation_context -lcommoncmakeLINK_INTERFACE_MULTIPLICITY 应该(我认为)可以解决您的问题。

您似乎没有遵循现代 CMake 最佳实践。您的库不会 link 任何东西,只有您的可执行文件可以。手动编译时,这就是它的做法,因为静态库不会 link 到其他静态库,只有可执行文件才可以。然而,在 CMake 中,世界有点不同。

你应该总是link你使用的库,你直接使用的,不是间接使用。对于可执行文件和库目标。 CMake 中的目标形成库和可执行文件的层次结构。 CMake 会相应地跟踪需要什么和 links。例如,如果您的可执行文件 exe 需要库 liba,而库 liba 本身需要库 libb,您不应该 link 您的可执行文件同时用于 libalibb,而是 link libalibb,并且只有 link 你的可执行文件到 liba。 CMake 将完成剩下的工作并解析层次结构以形成正确的 linking 命令。