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