Home | History | Annotate | Download | only in macholib
      1 ######################################################################
      2 #  This file should be kept compatible with Python 2.3, see PEP 291. #
      3 ######################################################################
      4 """
      5 dyld emulation
      6 """
      7 
      8 import os
      9 from framework import framework_info
     10 from dylib import dylib_info
     11 from itertools import *
     12 
     13 __all__ = [
     14     'dyld_find', 'framework_find',
     15     'framework_info', 'dylib_info',
     16 ]
     17 
     18 # These are the defaults as per man dyld(1)
     19 #
     20 DEFAULT_FRAMEWORK_FALLBACK = [
     21     os.path.expanduser("~/Library/Frameworks"),
     22     "/Library/Frameworks",
     23     "/Network/Library/Frameworks",
     24     "/System/Library/Frameworks",
     25 ]
     26 
     27 DEFAULT_LIBRARY_FALLBACK = [
     28     os.path.expanduser("~/lib"),
     29     "/usr/local/lib",
     30     "/lib",
     31     "/usr/lib",
     32 ]
     33 
     34 def ensure_utf8(s):
     35     """Not all of PyObjC and Python understand unicode paths very well yet"""
     36     if isinstance(s, unicode):
     37         return s.encode('utf8')
     38     return s
     39 
     40 def dyld_env(env, var):
     41     if env is None:
     42         env = os.environ
     43     rval = env.get(var)
     44     if rval is None:
     45         return []
     46     return rval.split(':')
     47 
     48 def dyld_image_suffix(env=None):
     49     if env is None:
     50         env = os.environ
     51     return env.get('DYLD_IMAGE_SUFFIX')
     52 
     53 def dyld_framework_path(env=None):
     54     return dyld_env(env, 'DYLD_FRAMEWORK_PATH')
     55 
     56 def dyld_library_path(env=None):
     57     return dyld_env(env, 'DYLD_LIBRARY_PATH')
     58 
     59 def dyld_fallback_framework_path(env=None):
     60     return dyld_env(env, 'DYLD_FALLBACK_FRAMEWORK_PATH')
     61 
     62 def dyld_fallback_library_path(env=None):
     63     return dyld_env(env, 'DYLD_FALLBACK_LIBRARY_PATH')
     64 
     65 def dyld_image_suffix_search(iterator, env=None):
     66     """For a potential path iterator, add DYLD_IMAGE_SUFFIX semantics"""
     67     suffix = dyld_image_suffix(env)
     68     if suffix is None:
     69         return iterator
     70     def _inject(iterator=iterator, suffix=suffix):
     71         for path in iterator:
     72             if path.endswith('.dylib'):
     73                 yield path[:-len('.dylib')] + suffix + '.dylib'
     74             else:
     75                 yield path + suffix
     76             yield path
     77     return _inject()
     78 
     79 def dyld_override_search(name, env=None):
     80     # If DYLD_FRAMEWORK_PATH is set and this dylib_name is a
     81     # framework name, use the first file that exists in the framework
     82     # path if any.  If there is none go on to search the DYLD_LIBRARY_PATH
     83     # if any.
     84 
     85     framework = framework_info(name)
     86 
     87     if framework is not None:
     88         for path in dyld_framework_path(env):
     89             yield os.path.join(path, framework['name'])
     90 
     91     # If DYLD_LIBRARY_PATH is set then use the first file that exists
     92     # in the path.  If none use the original name.
     93     for path in dyld_library_path(env):
     94         yield os.path.join(path, os.path.basename(name))
     95 
     96 def dyld_executable_path_search(name, executable_path=None):
     97     # If we haven't done any searching and found a library and the
     98     # dylib_name starts with "@executable_path/" then construct the
     99     # library name.
    100     if name.startswith('@executable_path/') and executable_path is not None:
    101         yield os.path.join(executable_path, name[len('@executable_path/'):])
    102 
    103 def dyld_default_search(name, env=None):
    104     yield name
    105 
    106     framework = framework_info(name)
    107 
    108     if framework is not None:
    109         fallback_framework_path = dyld_fallback_framework_path(env)
    110         for path in fallback_framework_path:
    111             yield os.path.join(path, framework['name'])
    112 
    113     fallback_library_path = dyld_fallback_library_path(env)
    114     for path in fallback_library_path:
    115         yield os.path.join(path, os.path.basename(name))
    116 
    117     if framework is not None and not fallback_framework_path:
    118         for path in DEFAULT_FRAMEWORK_FALLBACK:
    119             yield os.path.join(path, framework['name'])
    120 
    121     if not fallback_library_path:
    122         for path in DEFAULT_LIBRARY_FALLBACK:
    123             yield os.path.join(path, os.path.basename(name))
    124 
    125 def dyld_find(name, executable_path=None, env=None):
    126     """
    127     Find a library or framework using dyld semantics
    128     """
    129     name = ensure_utf8(name)
    130     executable_path = ensure_utf8(executable_path)
    131     for path in dyld_image_suffix_search(chain(
    132                 dyld_override_search(name, env),
    133                 dyld_executable_path_search(name, executable_path),
    134                 dyld_default_search(name, env),
    135             ), env):
    136         if os.path.isfile(path):
    137             return path
    138     raise ValueError("dylib %s could not be found" % (name,))
    139 
    140 def framework_find(fn, executable_path=None, env=None):
    141     """
    142     Find a framework using dyld semantics in a very loose manner.
    143 
    144     Will take input such as:
    145         Python
    146         Python.framework
    147         Python.framework/Versions/Current
    148     """
    149     try:
    150         return dyld_find(fn, executable_path=executable_path, env=env)
    151     except ValueError, e:
    152         pass
    153     fmwk_index = fn.rfind('.framework')
    154     if fmwk_index == -1:
    155         fmwk_index = len(fn)
    156         fn += '.framework'
    157     fn = os.path.join(fn, os.path.basename(fn[:fmwk_index]))
    158     try:
    159         return dyld_find(fn, executable_path=executable_path, env=env)
    160     except ValueError:
    161         raise e
    162 
    163 def test_dyld_find():
    164     env = {}
    165     assert dyld_find('libSystem.dylib') == '/usr/lib/libSystem.dylib'
    166     assert dyld_find('System.framework/System') == '/System/Library/Frameworks/System.framework/System'
    167 
    168 if __name__ == '__main__':
    169     test_dyld_find()
    170