• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8import sys
9import os
10import os.path
11import re
12import tempfile
13import shutil
14import contextlib
15import logging
16
17__all__ = ['build_libear']
18
19
20def 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
62def 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
71def TemporaryDirectory(**kwargs):
72    name = tempfile.mkdtemp(**kwargs)
73    try:
74        yield name
75    finally:
76        shutil.rmtree(name)
77
78
79class 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
113class 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
128class 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
143class 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
151def 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
163class 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
222def do_configure(toolset):
223    return Configure(toolset)
224
225
226class 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
259def create_shared_library(name, toolset):
260    return SharedLibrary(name, toolset)
261