I have recently encountered what appears to be a bug in building shared libraries (.dylib files) from compiled C++ code (.o files) on Mavericks (OS X 10.9.4, Xcode 5.1). Key symbols from one particular implementation class seem to be exported in the .o file, but not from the .dylib file incorporating the .o.
Our codebase is broken up into modules. We make heavy use of the Curiously Recurring Template Pattern (CRTP). A sketch of the code is shown here:
#include <string>
class ExprVec;
class Operator
{
public:
virtual bool operator()(bool &result, ExprVec const &args) const = 0;
virtual bool operator()(int32_t &result, ExprVec const &args) const = 0;
virtual bool operator()(double &result, ExprVec const &args) const = 0;
virtual bool operator()(std::string &result, ExprVec const &args) const = 0;
};
template <class IMPL>
class OperatorShim : public Operator
{
public:
bool operator()(bool &result, ExprVec const &args) const
{
return static_cast<IMPL const *>(this)->calc(result, args);
}
bool operator()(int32_t &result, ExprVec const &args) const
{
return static_cast<IMPL const *>(this)->calc(result, args);
}
bool operator()(double &result, ExprVec const &args) const
{
return static_cast<IMPL const *>(this)->calc(result, args);
}
bool operator()(std::string &result, ExprVec const &args) const
{
return static_cast<IMPL const *>(this)->calc(result, args);
}
};
template <typename T>
class OperatorImpl : public OperatorShim<OperatorImpl<T> >
{
public:
// implemented by derived classes
virtual bool calc(T &result, ExprVec const &args) const = 0;
// default methods
template <typename U>
bool calc <U &result, ExprVec const &args) const
{
// throw wrong-type exception
return false;
}
};
template class OperatorImpl<bool>;
template class OperatorImpl<int32_t>;
template class OperatorImpl<double>;
template class OperatorImpl<std::string>;
In our actual codebase, the declarations are in a .hh file, and the definitions in a .cc file. The .o file from the .cc exports all the templatized calc() default methods - but the .dylib doesn't. These symbols are in the .dylib, but only as debugging symbols. I've verified this by using nm and c++filt to inspect the files.
This results in an error linking any application using this library, as the OperatorShim methods call the unexported OperatorImpl methods.
This only happens when building on Mac OS X 10.9.x with Xcode 5.1.x command line tools. 10.8.x and Xcode 5.0.x have no problem linking this library, nor do the GNU tools on Linux.
As a workaround, I've moved the method implementations back into the .hh file. Because they require several other include files, I would prefer to keep the implementations separate from the declarations.
You can reproduce the actual problem on OS X 10.9.x as follows:
export PLEXIL_HOME=$PWD
svn checkout -r3785 svn+ssh://svn.code.sf.net/p/plexil/code/branches/plexil-3-new-exp/src
cd src
./configure --disable-static --enable-module-tests --prefix=$PLEXIL_HOME
make
The files in question are .../src/expr/OperatorImpl.{hh,cc,o}, and .../src/expr/.libs/libPlexilExpr.0.dylib . The command that built the dylib file is:
libtool: link: g++ -dynamiclib -Wl,-undefined -Wl,dynamic_lookup -o .libs/libPlexilExpr.0.dylib .libs/libPlexilExpr_la-Alias.o .libs/libPlexilExpr_la-Array.o .libs/libPlexilExpr_la-ArrayImpl.o .libs/libPlexilExpr_la-ArithmeticFunctionFactory.o .libs/libPlexilExpr_la-ArithmeticOperators.o .libs/libPlexilExpr_la-ArrayOperators.o .libs/libPlexilExpr_la-ArrayReference.o .libs/libPlexilExpr_la-ArrayVariable.o .libs/libPlexilExpr_la-Assignable.o .libs/libPlexilExpr_la-AssignableImpl.o .libs/libPlexilExpr_la-BooleanOperators.o .libs/libPlexilExpr_la-CommandHandle.o .libs/libPlexilExpr_la-Comparisons.o .libs/libPlexilExpr_la-ConcreteExpressionFactory.o .libs/libPlexilExpr_la-Constant.o .libs/libPlexilExpr_la-Expression.o .libs/libPlexilExpr_la-ExpressionConstants.o .libs/libPlexilExpr_la-ExpressionFactories.o .libs/libPlexilExpr_la-ExpressionFactory.o .libs/libPlexilExpr_la-ExpressionImpl.o .libs/libPlexilExpr_la-ExpressionListener.o .libs/libPlexilExpr_la-ExprVec.o .libs/libPlexilExpr_la-Function.o .libs/libPlexilExpr_la-FunctionFactory.o .libs/libPlexilExpr_la-NodeConstants.o .libs/libPlexilExpr_la-NodeConstantExpressions.o .libs/libPlexilExpr_la-NotifierImpl.o .libs/libPlexilExpr_la-OperatorImpl.o .libs/libPlexilExpr_la-PlexilExpr.o .libs/libPlexilExpr_la-StringOperators.o .libs/libPlexilExpr_la-UserVariable.o .libs/libPlexilExpr_la-Value.o .libs/libPlexilExpr_la-ValueType.o -lm -lpthread -ldl -O2 -install_name /Users/chucko/src/plexil-3-new-exp/lib/libPlexilExpr.0.dylib -compatibility_version 1 -current_version 1.0 -Wl,-single_module
Is this the result of a coding error on my part? I tried a number of different approaches but couldn't get the problem to go away.