Search code examples
c++cmakec++14static-linkingclang++

Nested Static Linked Libraries and a Spooky Bug


(Platform: C++14, Clang 3.8, Ubuntu 16.04, CMake 3.5.1)

I have two static libraries (PawLIB, CalikoCat), and an executable that relies on OtherLibrary. Within CalkoCat is a single class, Dummy. (I'm just making sure everything links right before we start serious development on this library.)

The Dummy class currently relies on a function from PawLIB for testing purposes.

calikocat-source/include/calikocat/dummy.hpp

#include "pawlib/iochannel.hpp"

class Dummy
{
    public:
        Dummy(){}
        static void speak();
        ~Dummy(){}
};

calikocat-source/src/dummy.cpp

#include "otherlibrary/dummy.hpp"

void Dummy::speak()
{
    pawlib::ioc << "Hello, world!" << pawlib::io_end;
}

No problems. It compiles and links correctly with CMake.

NOTE: You may safely assume all the variables in both CMakeLists.txt files in this question are carefully tested and working. I've triple-checked each.

calikocat-source/CMakeLists.txt (snippet)

include_directories(include)

# Include headers of dependencies.
include_directories(${PAWLIB_DIR}/include)

add_library(${TARGET_NAME} STATIC
    include/calikocat/dummy.hpp
    src/dummy.cpp
)

# Link against dependencies.
target_link_libraries(${TARGET_NAME} ${PAWLIB_DIR}/lib/libpawlib.a)

Now, I like including tester applications with my libraries, and this is no exception. In the repo, I separate the library and tester code into calikocat-source and calikocat-tester directories. I use CMake for the tester as well...

calikocat-tester/CMakeLists.txt (snippet)

include_directories(include)

# Include headers of dependencies.
include_directories(${PAWLIB_DIR}/include)
include_directories(../calikocat-source/include)

add_executable(calikocat-tester
    main.cpp
)

# Link against dependencies.
target_link_libraries(${TARGET_NAME} ${CPGF_DIR}/lib/libcpgf.a)
target_link_libraries(${TARGET_NAME} ${PAWLIB_DIR}/lib/libpawlib.a)
target_link_libraries(${TARGET_NAME} ${CMAKE_HOME_DIRECTORY}/../calikocat-source/lib/$<CONFIG>/libcalikocat.a)

Whether this builds correctly depends entirely on the presence of a single line on main.cpp! Read below CAREFULLY.

calikocat-tester/main.cpp, WORKING VERSION

#include <calikocat/dummy.hpp>
#include <pawlib/iochannel.hpp>

int main()
{
    pawlib::ioc << "Hello, world!" << pawlib::io_end;
    Dummy::speak();

    return 0;
}

Compiling and running this version works as expected, printing Hello, world! out to the terminal twice.

calikocat-tester/main.cpp, NON-WORKING VERSION

#include <calikocat/dummy.hpp>
#include <pawlib/iochannel.hpp>

int main()
{
    //pawlib::ioc << "Hello, world!" << pawlib::io_end;
    Dummy::speak();

    return 0;
}

This version of the file, where I comment out the line using PawLIB directly, does NOT compile. Here's the error...

[100%] Linking CXX executable ../../bin/Debug/calikocat-tester
../../../calikocat-source/lib/Debug/libcalikocat.a(dummy.cpp.o): In function `Dummy::speak()':
/home/jason/Code/Repositories/calikocat/calikocat-source/src/dummy.cpp:4: undefined reference to `pawlib::ioc'
/home/jason/Code/Repositories/calikocat/calikocat-source/src/dummy.cpp:5: undefined reference to `pawlib::iochannel::operator<<(pawlib::ioformat::IOControl const&)'
../../../calikocat-source/lib/Debug/libcalikocat.a(dummy.cpp.o): In function `pawlib::iochannel::operator<<(char const*)':
/home/jason/Code/Repositories/calikocat/calikocat-source/../../pawlib/pawlib/include/pawlib/iochannel.hpp:521: undefined reference to `pawlib::iochannel::resolve_pointer(char const*)'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
CMakeFiles/calikocat-tester.dir/build.make:99: recipe for target '../../bin/Debug/calikocat-tester' failed

I am expecting it to compile, and when run, just print out Hello, world! to the terminal once.

What is going on here, and what am I missing?


Solution

  • You should try reversing these two lines:

    target_link_libraries(${TARGET_NAME} ${PAWLIB_DIR}/lib/libpawlib.a)
    target_link_libraries(${TARGET_NAME} ${CMAKE_HOME_DIRECTORY}/../calikocat-source/lib/$<CONFIG>/libcalikocat.a)
    

    Because libcalikocat.a is using symbols from libpawlib.a, it should appear first on the command line.