Home | History | Annotate | Download | only in libear
      1 # -*- coding: utf-8 -*-
      2 #                     The LLVM Compiler Infrastructure
      3 #
      4 # This file is distributed under the University of Illinois Open Source
      5 # License. See LICENSE.TXT for details.
      6 """ This module compiles the intercept library. """
      7 
      8 import sys
      9 import os
     10 import os.path
     11 import re
     12 import tempfile
     13 import shutil
     14 import contextlib
     15 import logging
     16 
     17 __all__ = ['build_libear']
     18 
     19 
     20 def build_libear(compiler, dst_dir):
     21     """ Returns the full path to the 'libear' library. """
     22 
     23     try:
     24         src_dir = os.path.dirname(os.path.realpath(__file__))
     25         toolset = make_toolset(src_dir)
     26         toolset.set_compiler(compiler)
     27         toolset.set_language_standard('c99')
     28         toolset.add_definitions(['-D_GNU_SOURCE'])
     29 
     30         configure = do_configure(toolset)
     31         configure.check_function_exists('execve', 'HAVE_EXECVE')
     32         configure.check_function_exists('execv', 'HAVE_EXECV')
     33         configure.check_function_exists('execvpe', 'HAVE_EXECVPE')
     34         configure.check_function_exists('execvp', 'HAVE_EXECVP')
     35         configure.check_function_exists('execvP', 'HAVE_EXECVP2')
     36         configure.check_function_exists('exect', 'HAVE_EXECT')
     37         configure.check_function_exists('execl', 'HAVE_EXECL')
     38         configure.check_function_exists('execlp', 'HAVE_EXECLP')
     39         configure.check_function_exists('execle', 'HAVE_EXECLE')
     40         configure.check_function_exists('posix_spawn', 'HAVE_POSIX_SPAWN')
     41         configure.check_function_exists('posix_spawnp', 'HAVE_POSIX_SPAWNP')
     42         configure.check_symbol_exists('_NSGetEnviron', 'crt_externs.h',
     43                                       'HAVE_NSGETENVIRON')
     44         configure.write_by_template(
     45             os.path.join(src_dir, 'config.h.in'),
     46             os.path.join(dst_dir, 'config.h'))
     47 
     48         target = create_shared_library('ear', toolset)
     49         target.add_include(dst_dir)
     50         target.add_sources('ear.c')
     51         target.link_against(toolset.dl_libraries())
     52         target.link_against(['pthread'])
     53         target.build_release(dst_dir)
     54 
     55         return os.path.join(dst_dir, target.name)
     56 
     57     except Exception:
     58         logging.info("Could not build interception library.", exc_info=True)
     59         return None
     60 
     61 
     62 def execute(cmd, *args, **kwargs):
     63     """ Make subprocess execution silent. """
     64 
     65     import subprocess
     66     kwargs.update({'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT})
     67     return subprocess.check_call(cmd, *args, **kwargs)
     68 
     69 
     70 @contextlib.contextmanager
     71 def TemporaryDirectory(**kwargs):
     72     name = tempfile.mkdtemp(**kwargs)
     73     try:
     74         yield name
     75     finally:
     76         shutil.rmtree(name)
     77 
     78 
     79 class Toolset(object):
     80     """ Abstract class to represent different toolset. """
     81 
     82     def __init__(self, src_dir):
     83         self.src_dir = src_dir
     84         self.compiler = None
     85         self.c_flags = []
     86 
     87     def set_compiler(self, compiler):
     88         """ part of public interface """
     89         self.compiler = compiler
     90 
     91     def set_language_standard(self, standard):
     92         """ part of public interface """
     93         self.c_flags.append('-std=' + standard)
     94 
     95     def add_definitions(self, defines):
     96         """ part of public interface """
     97         self.c_flags.extend(defines)
     98 
     99     def dl_libraries(self):
    100         raise NotImplementedError()
    101 
    102     def shared_library_name(self, name):
    103         raise NotImplementedError()
    104 
    105     def shared_library_c_flags(self, release):
    106         extra = ['-DNDEBUG', '-O3'] if release else []
    107         return extra + ['-fPIC'] + self.c_flags
    108 
    109     def shared_library_ld_flags(self, release, name):
    110         raise NotImplementedError()
    111 
    112 
    113 class DarwinToolset(Toolset):
    114     def __init__(self, src_dir):
    115         Toolset.__init__(self, src_dir)
    116 
    117     def dl_libraries(self):
    118         return []
    119 
    120     def shared_library_name(self, name):
    121         return 'lib' + name + '.dylib'
    122 
    123     def shared_library_ld_flags(self, release, name):
    124         extra = ['-dead_strip'] if release else []
    125         return extra + ['-dynamiclib', '-install_name', '@rpath/' + name]
    126 
    127 
    128 class UnixToolset(Toolset):
    129     def __init__(self, src_dir):
    130         Toolset.__init__(self, src_dir)
    131 
    132     def dl_libraries(self):
    133         return []
    134 
    135     def shared_library_name(self, name):
    136         return 'lib' + name + '.so'
    137 
    138     def shared_library_ld_flags(self, release, name):
    139         extra = [] if release else []
    140         return extra + ['-shared', '-Wl,-soname,' + name]
    141 
    142 
    143 class LinuxToolset(UnixToolset):
    144     def __init__(self, src_dir):
    145         UnixToolset.__init__(self, src_dir)
    146 
    147     def dl_libraries(self):
    148         return ['dl']
    149 
    150 
    151 def make_toolset(src_dir):
    152     platform = sys.platform
    153     if platform in {'win32', 'cygwin'}:
    154         raise RuntimeError('not implemented on this platform')
    155     elif platform == 'darwin':
    156         return DarwinToolset(src_dir)
    157     elif platform in {'linux', 'linux2'}:
    158         return LinuxToolset(src_dir)
    159     else:
    160         return UnixToolset(src_dir)
    161 
    162 
    163 class Configure(object):
    164     def __init__(self, toolset):
    165         self.ctx = toolset
    166         self.results = {'APPLE': sys.platform == 'darwin'}
    167 
    168     def _try_to_compile_and_link(self, source):
    169         try:
    170             with TemporaryDirectory() as work_dir:
    171                 src_file = 'check.c'
    172                 with open(os.path.join(work_dir, src_file), 'w') as handle:
    173                     handle.write(source)
    174 
    175                 execute([self.ctx.compiler, src_file] + self.ctx.c_flags,
    176                         cwd=work_dir)
    177                 return True
    178         except Exception:
    179             return False
    180 
    181     def check_function_exists(self, function, name):
    182         template = "int FUNCTION(); int main() { return FUNCTION(); }"
    183         source = template.replace("FUNCTION", function)
    184 
    185         logging.debug('Checking function %s', function)
    186         found = self._try_to_compile_and_link(source)
    187         logging.debug('Checking function %s -- %s', function,
    188                       'found' if found else 'not found')
    189         self.results.update({name: found})
    190 
    191     def check_symbol_exists(self, symbol, include, name):
    192         template = """#include <INCLUDE>
    193                       int main() { return ((int*)(&SYMBOL))[0]; }"""
    194         source = template.replace('INCLUDE', include).replace("SYMBOL", symbol)
    195 
    196         logging.debug('Checking symbol %s', symbol)
    197         found = self._try_to_compile_and_link(source)
    198         logging.debug('Checking symbol %s -- %s', symbol,
    199                       'found' if found else 'not found')
    200         self.results.update({name: found})
    201 
    202     def write_by_template(self, template, output):
    203         def transform(line, definitions):
    204 
    205             pattern = re.compile(r'^#cmakedefine\s+(\S+)')
    206             m = pattern.match(line)
    207             if m:
    208                 key = m.group(1)
    209                 if key not in definitions or not definitions[key]:
    210                     return '/* #undef {} */\n'.format(key)
    211                 else:
    212                     return '#define {}\n'.format(key)
    213             return line
    214 
    215         with open(template, 'r') as src_handle:
    216             logging.debug('Writing config to %s', output)
    217             with open(output, 'w') as dst_handle:
    218                 for line in src_handle:
    219                     dst_handle.write(transform(line, self.results))
    220 
    221 
    222 def do_configure(toolset):
    223     return Configure(toolset)
    224 
    225 
    226 class SharedLibrary(object):
    227     def __init__(self, name, toolset):
    228         self.name = toolset.shared_library_name(name)
    229         self.ctx = toolset
    230         self.inc = []
    231         self.src = []
    232         self.lib = []
    233 
    234     def add_include(self, directory):
    235         self.inc.extend(['-I', directory])
    236 
    237     def add_sources(self, source):
    238         self.src.append(source)
    239 
    240     def link_against(self, libraries):
    241         self.lib.extend(['-l' + lib for lib in libraries])
    242 
    243     def build_release(self, directory):
    244         for src in self.src:
    245             logging.debug('Compiling %s', src)
    246             execute(
    247                 [self.ctx.compiler, '-c', os.path.join(self.ctx.src_dir, src),
    248                  '-o', src + '.o'] + self.inc +
    249                 self.ctx.shared_library_c_flags(True),
    250                 cwd=directory)
    251         logging.debug('Linking %s', self.name)
    252         execute(
    253             [self.ctx.compiler] + [src + '.o' for src in self.src] +
    254             ['-o', self.name] + self.lib +
    255             self.ctx.shared_library_ld_flags(True, self.name),
    256             cwd=directory)
    257 
    258 
    259 def create_shared_library(name, toolset):
    260     return SharedLibrary(name, toolset)
    261