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. """
16 __all__
= ["build_libear"]
19 def build_libear(compiler
, dst_dir
):
20 """Returns the full path to the 'libear' library."""
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"])
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"
44 configure
.write_by_template(
45 os
.path
.join(src_dir
, "config.h.in"), os
.path
.join(dst_dir
, "config.h")
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
)
55 return os
.path
.join(dst_dir
, target
.name
)
58 logging
.info("Could not build interception library.", exc_info
=True)
62 def execute(cmd
, *args
, **kwargs
):
63 """Make subprocess execution silent."""
67 kwargs
.update({"stdout": subprocess
.PIPE
, "stderr": subprocess
.STDOUT
})
68 return subprocess
.check_call(cmd
, *args
, **kwargs
)
71 @contextlib.contextmanager
72 def TemporaryDirectory(**kwargs
):
73 name
= tempfile
.mkdtemp(**kwargs
)
80 class Toolset(object):
81 """Abstract class to represent different toolset."""
83 def __init__(self
, src_dir
):
84 self
.src_dir
= src_dir
88 def set_compiler(self
, compiler
):
89 """part of public interface"""
90 self
.compiler
= compiler
92 def set_language_standard(self
, standard
):
93 """part of public interface"""
94 self
.c_flags
.append("-std=" + standard
)
96 def add_definitions(self
, defines
):
97 """part of public interface"""
98 self
.c_flags
.extend(defines
)
100 def dl_libraries(self
):
101 raise NotImplementedError()
103 def shared_library_name(self
, name
):
104 raise NotImplementedError()
106 def shared_library_c_flags(self
, release
):
107 extra
= ["-DNDEBUG", "-O3"] if release
else []
108 return extra
+ ["-fPIC"] + self
.c_flags
110 def shared_library_ld_flags(self
, release
, name
):
111 raise NotImplementedError()
114 class DarwinToolset(Toolset
):
115 def __init__(self
, src_dir
):
116 Toolset
.__init
__(self
, src_dir
)
118 def dl_libraries(self
):
121 def shared_library_name(self
, name
):
122 return "lib" + name
+ ".dylib"
124 def shared_library_ld_flags(self
, release
, name
):
125 extra
= ["-dead_strip"] if release
else []
126 return extra
+ ["-dynamiclib", "-install_name", "@rpath/" + name
]
129 class UnixToolset(Toolset
):
130 def __init__(self
, src_dir
):
131 Toolset
.__init
__(self
, src_dir
)
133 def dl_libraries(self
):
136 def shared_library_name(self
, name
):
137 return "lib" + name
+ ".so"
139 def shared_library_ld_flags(self
, release
, name
):
140 extra
= [] if release
else []
141 return extra
+ ["-shared", "-Wl,-soname," + name
]
144 class LinuxToolset(UnixToolset
):
145 def __init__(self
, src_dir
):
146 UnixToolset
.__init
__(self
, src_dir
)
148 def dl_libraries(self
):
152 def 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
)
161 return UnixToolset(src_dir
)
164 class Configure(object):
165 def __init__(self
, toolset
):
167 self
.results
= {"APPLE": sys
.platform
== "darwin"}
169 def _try_to_compile_and_link(self
, source
):
171 with
TemporaryDirectory() as work_dir
:
173 with
open(os
.path
.join(work_dir
, src_file
), "w") as handle
:
176 execute([self
.ctx
.compiler
, src_file
] + self
.ctx
.c_flags
, cwd
=work_dir
)
181 def check_function_exists(self
, function
, name
):
182 template
= "int FUNCTION(); int main() { return FUNCTION(); }"
183 source
= template
.replace("FUNCTION", function
)
185 logging
.debug("Checking function %s", function
)
186 found
= self
._try
_to
_compile
_and
_link
(source
)
188 "Checking function %s -- %s", function
, "found" if found
else "not found"
190 self
.results
.update({name
: found
})
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
)
197 logging
.debug("Checking symbol %s", symbol
)
198 found
= self
._try
_to
_compile
_and
_link
(source
)
200 "Checking symbol %s -- %s", symbol
, "found" if found
else "not found"
202 self
.results
.update({name
: found
})
204 def write_by_template(self
, template
, output
):
205 def transform(line
, definitions
):
207 pattern
= re
.compile(r
"^#cmakedefine\s+(\S+)")
208 m
= pattern
.match(line
)
211 if key
not in definitions
or not definitions
[key
]:
212 return "/* #undef {0} */{1}".format(key
, os
.linesep
)
214 return "#define {0}{1}".format(key
, os
.linesep
)
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
))
224 def do_configure(toolset
):
225 return Configure(toolset
)
228 class SharedLibrary(object):
229 def __init__(self
, name
, toolset
):
230 self
.name
= toolset
.shared_library_name(name
)
236 def add_include(self
, directory
):
237 self
.inc
.extend(["-I", directory
])
239 def add_sources(self
, source
):
240 self
.src
.append(source
)
242 def link_against(self
, libraries
):
243 self
.lib
.extend(["-l" + lib
for lib
in libraries
])
245 def build_release(self
, directory
):
247 logging
.debug("Compiling %s", src
)
252 os
.path
.join(self
.ctx
.src_dir
, src
),
257 + self
.ctx
.shared_library_c_flags(True),
260 logging
.debug("Linking %s", self
.name
)
263 + [src
+ ".o" for src
in self
.src
]
266 + self
.ctx
.shared_library_ld_flags(True, self
.name
),
271 def create_shared_library(name
, toolset
):
272 return SharedLibrary(name
, toolset
)