6
votes

I am having a pretty strange linking error in a project that uses automake. What I do seems pretty simple from the manual, so I really wonder what I can doing wrong ...

My project has three folders :

  • src/common, in which I compile a number of C++ files into a libube-common.a static lib
  • src/engine, in which I compile a number of files into a libube-engine.a static lib
  • src/client, in which ... you guessed it, libue-client.a , and also one file ube.cpp that is my main

Each of the libraries is compiled with a Makefile.am like this :

noinst_LIBRARIES=libube-common.a
libube_common_a_SOURCES=gettext.h lua_helper.hpp \
 silent_ostream.hpp \
 logging.hpp logging.cpp \
 logger_interface.hpp \
     ... etc ...
AM_CPPFLAGS=-DSRCDIR=\"${srcdir}\" \
 -DLUADIR=\"${luadir}\" \
 -Wall -Werror \
 -I$(srcdir)/../../include \
 $(LUA_INCLUDE) \
 $(BOOST_CPPFLAGS)

This results in the various objects being built with a line like :

g++ -DHAVE_CONFIG_H -I. -I../../../../../src/common -I../..  -DSRCDIR=\"../../../../../src/common\" -DLUADIR=\"\" -Wall -Werror -I../../../../../src/common/../../include -I/usr/include/lua5.1 -I/usr/include   -g -O2 -MT logging.o -MD -MP -MF .deps/logging.Tpo -c -o logging.o ../../../../../src/common/logging.cpp

And all of them are put in the library with :

ar cru libube-common.a logging.o prefix_resource_resolver.o stat_file_checker.o 
ranlib libube-common.a

All of this seems good and well, I can even linked some little tests programs against the library (inside the same makefile)

Then, in the Makefile.am of my main program, I asked to link against the local libraries :

ube_LDADD=../common/libube-common.a \
      ../engine/libube-engine.a \
      libube-client.a \
          ... other libs ...

And that's where I get errors like this :

g++  -g -O2   -o ube ube.o ../common/libube-common.a ../engine/libube-engine.a libube-   client.a -L/usr/include/lua5.1/lib -llua5.1  -lm -ldl  -L/usr/lib -lSDL -lSDL_image -lpng -ltiff -ljpeg -lz -lSDL_ttf -lfreetype -lSDL_mixer -lSDL_mixer -lSDL_ttf -lSDL_image

libube-client.a(game_loop.o): In function `Logging::debug_ostream(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)':
(...) logging.hpp:54: undefined reference to `Logging::get_ostream(LogLevel::Level, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
(...)logging.hpp:54: undefined reference to `Logging::get_ostream(LogLevel::Level, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)'

At first I though it was because of some static symbols, but I also get the issue with non-static ones.

I checked the generated libs, and it seem to contain the symbol properly :

~/prj/ube/builds/linux/current/src/common$ nm -C libube-common.a  | grep logging.o -C 20

logging.o:
00000010 t global constructors keyed to _ZN7Logging15disable_loggingEv
00000000 V guard variable for Logging::get_instance()::s_local_instance
000000b0 T Logging::get_ostream(LogLevel::Level, std::string)
00000000 T Logging::disable_logging()
00000040 T Logging::is_category_enabled(LogLevel::Level, std::string&)
     U std::ios_base::Init::Init()
     U std::ios_base::Init::~Init()
00000000 b std::__ioinit
     U __cxa_atexit
     U __dso_handle
     U __gxx_personality_v0

The only fix is to explicitely link against my .o files (by adding them to the ube_LDADD line ... but that kinda defies the idea of using a library !!)

I seem to have been following the manual : http://www.gnu.org/software/hello/manual/automake/Linking.html#Linking

But ovbiously I messed up somewhere, so any idea is welcome !!

Thanks

PH


EDIT : The library in itself seems to work, it seems to be a linking issue. I can link my test cases programs agaist them. Here is what I do :

In folder src/common/tests, there is a main called common-tests.cpp that runs unit tests ; the common-tests bin is linked against the library libube-common.a (it only needs objects that are inside the lib, since those are unit tests)

# There is one program that aggreatates all tests cases
check_PROGRAMS = common-tests
common_tests_SOURCES= tests/common_tests.cpp \
tests/prefix_resource_resolver_test.cpp \
tests/mock_file_checker.hpp \
tests/stat_file_checker_test.cpp

# The program needs to be compiled against the local lib 
common_tests_LDADD=libube-common.a -L$(top_srcdir)/lib -lgtest -lgmock -llua -ldl

# This means common-tests is run when using 'make check'.
TESTS = common-tests

When running make check, the test program is compiled this way :

g++  -g -O2   -o common-tests common_tests.o prefix_resource_resolver_test.o stat_file_checker_test.o libube-common.a -L../../../../../lib -lgtest -lgmock -llua -ldl -lSDL_mixer -lSDL_ttf -lSDL_image 

And things work perfectly. The only difference I can see is that in this case the library is right next to the executable to link ... could this really make any difference ?

Also, I tried using options like -Wl,--whole-archive but it did not help (plus I don't know how to add them to the Automake-generated line ...)

1
Those same libraries work on your platform? You can link them to a small testcase?Potatoswatter
Normally, es, see my edits ...phtrivier

1 Answers

5
votes

This is most likely a library ordering issues - the more 'common' a library is, the later it should occur in the final link line. Specifically, GNU ld reads libraries for symbols exactly once, and then discards all other symbols from the library, before moving on to the next library instruction. There's various solutions (see the man page for 'ld') but the easiest one is to re-order the lines in your Makefile.am to put libube-common.a after the client and engine libs.

Note that Darwin ld does not have this behaviour, it preserves all library symbols by default (which potentially uses much more memory during linking).