Please note:The SCons wiki is now restored from the attack in March 2013. All old passwords have been invalidated. Please reset your password if you have an account. If you note missing pages, please report them to webmaster@scons.org. Also, new account creation is currently disabled due to an ongoing spam flood (2013/08/27).

MSVC compiler has a nice feature - if you build DLL, all symbols should be resolved. After googling for a few hours, I didn't find the necessary combination of GCC flags, to achieve same result for shared libraries. So I created a custom builder based on ldd and c++filt utilities, which dumps unresolved symbols to a text file.

It is not the perfect solution, but it is good enough for me and may be it will be useful for you too.

The builder code:

   1 import os
   2 import re
   3 import subprocess
   4 import SCons.Action
   5 import SCons.Builder
   6 import SCons.Scanner
   7 
   8 class dependencies_dumper_t:
   9     unresolved_lib_re = re.compile( r'^\s+(?P<lib>.*)\s\=\>\snot found.*' )
  10     undefined_symbol_re = re.compile( r'^\s*undefined symbol\:\s+(?P<symbol>.*)\s\(.*\)' )
  11 
  12     def __init__( self, target, source, env ):
  13         self.env = env
  14         self.target = target
  15         self.source = source
  16 
  17         self.new_os_env = os.environ.copy()
  18         self.new_os_env['LD_LIBRARY_PATH'] = env['ENV']['LD_LIBRARY_PATH']
  19         #self.new_os_env['PATH'] = env.get( 'PATH', os.environ.get( 'PATH', '' ) )
  20         #todo?: may be I need to add source file directory to LD_LIBRARY_PATH
  21 
  22     def write2log( self, msg ):
  23         print msg
  24 
  25     def __run_ldd( self ):
  26         try:
  27             tf = file( self.target[0].abspath, 'w+r' )
  28             args = [ 'ldd', '-r', self.source[0].abspath ]
  29             ldd_prs = subprocess.Popen( ' '.join( args )
  30                                         , shell=True
  31                                         , close_fds=True
  32                                         , bufsize=1024*50
  33                                         , stdout=tf
  34                                         , stderr=tf
  35                                         , cwd=os.path.dirname( self.source[0].abspath )
  36                                         , env=self.new_os_env)
  37 
  38             ldd_prs.wait()
  39 
  40             if 0 != ldd_prs.returncode:
  41                 raise RuntimeError( 'Error during execution of ldd process. Error code: %d'
  42                                     % ldd_prs.return_code )
  43             tf.seek(0)
  44             output = tf.read()
  45             tf.close()
  46             lines = output.split( '\n' )
  47             lines = map( lambda s: s.strip(), lines )
  48             return filter( None, lines )
  49         except Exception, err:
  50             print 'error executing ldd process: ', str(err)
  51             raise
  52 
  53     def __demangle_symbol( self, symbol ):
  54         try:
  55             tmp_f_name = os.path.join( self.env['AIS_B_CFG' ]['TEMP_PATH']
  56                                        , os.path.basename( self.source[0].abspath ) + '.cppfilt.txt' )
  57             tmp_f = file( self.env.File( tmp_f_name ).abspath, 'w+r' )
  58             args = [ 'c++filt', symbol ]
  59             prs = subprocess.Popen( ' '.join( args )
  60                                     , shell=True
  61                                     , close_fds=True
  62                                     , stdout=tmp_f
  63                                     , stderr=tmp_f )
  64 
  65             prs.wait()
  66 
  67             if 0 != prs.returncode:
  68                 raise RuntimeError( 'Error during execution of c++filt process. Error code: %d'
  69                                     % prs.return_code )
  70             tmp_f.seek(0)
  71             output = tmp_f.read().strip()
  72             tmp_f.close()
  73             return '%s { %s }' % ( output, symbol )
  74         except Exception, err:
  75             print 'error executing c++filt process: ', str(err)
  76             return symbol
  77 
  78     def __list_unknown( self, ldd_result, pattern, gname ):
  79         unknown = []
  80         for l in ldd_result:
  81             m = pattern.match( l )
  82             if not m:
  83                 continue
  84             unknown.append( m.group( gname ) )
  85         return unknown
  86 
  87     def dump(self):
  88         ldd_lines = self.__run_ldd()
  89         libs = self.__list_unknown( ldd_lines, self.unresolved_lib_re, 'lib' )
  90         symbols = self.__list_unknown( ldd_lines, self.undefined_symbol_re, 'symbol' )
  91         tf = file( self.target[0].abspath, 'w+' )
  92         if libs or symbols:
  93             tf.write( 'LD_LIBRARY_PATH:\n' )
  94             for path in self.new_os_env['LD_LIBRARY_PATH'].split(':'):
  95                 tf.write( '\t' + path + '\n' )
  96             if libs:
  97                 tf.write( 'unresolved libs: \n' )
  98                 for lib in libs:
  99                     tf.write( '\t' + lib + '\n' )
 100             if symbols:
 101                 tf.write( 'undefined symbols: \n' )
 102                 for symbol in symbols:
 103                     tf.write( '\t' + self.__demangle_symbol( symbol ) + '\n' )
 104         tf.close()
 105 
 106 def build_it( target, source, env ):
 107     """
 108     source - executable or shared object
 109     target - text file, which contains list of other shared libraries it
 110              depends on and list of undefined symbols. If there are no
 111              undefined symbols, the file will be empty
 112 
 113     LD_LIBRARY_PATH environment variable will be used to find out the location
 114     of other shared libraries
 115 
 116     PATH environment variable will be used to find out the location of 'ldd'
 117     executable
 118     """
 119     dependencies_dumper_t( target, source, env ).dump()
 120     return 0
 121 
 122 
 123 def register_builder( env, name ):
 124     """"shared library dependencies builder scanner"""
 125     builder = env.Builder( action = SCons.Action.Action(build_it, "dumping dependencies '$TARGET'")
 126                            #target_factory - is a factory function that the Builder will use to turn 
 127                            #any targets specified as strings into SCons Nodes
 128                            , target_factory=env.fs.File )
 129     env.Append(BUILDERS={name: builder })
 130 
 131 def exists(env):
 132     """the "combine" builder always exists"""
 133     return True 

Usage example:

   1    import dependencies_builder
   2 
   3    env = Environment( ... )
   4    dependencies_builder.register_builder( env, 'DumpSODependencies' )
   5 
   6 
   7    library = env.SharedLibrary( ... )
   8    installed_lib = env.Install( ..., library )
   9    library_dependencies = installed_lib[0].abspath + '.txt'
  10    dependencies = env.DumpSODependencies( library_dependencies, library )
  11    env.SideEffect('#serialize_dependencies', dependencies)
  12    env.Alias(targetname, library_dependencies)
  13    env.AlwaysBuild( library_dependencies )