Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
numba
GitHub Repository: numba/llvmlite
Path: blob/main/ffi/build.py
1154 views
1
#!/usr/bin/env python
2
"""
3
Build script for the shared library providing the C ABI bridge to LLVM.
4
"""
5
6
from __future__ import print_function
7
8
import functools
9
import os
10
import subprocess
11
import shutil
12
import sys
13
import tempfile
14
import warnings
15
16
17
here_dir = os.path.abspath(os.path.dirname(__file__))
18
build_dir = os.path.join(here_dir, 'build')
19
target_dir = os.path.join(os.path.dirname(here_dir), 'llvmlite', 'binding')
20
21
22
is_64bit = sys.maxsize >= 2**32
23
24
25
def env_var_options_to_cmake_options():
26
# This is not ideal, it exists to put env var options into CMake. Whilst it
27
# is possible to pass options through setuptools/distutils etc, this is not
28
# ideal either esp as the packaging system could do with an overhaul to meet
29
# modern standards. Also, adding these as options to the CMake configuration
30
# opposed to getting CMake to parse the env vars means that in the future it
31
# is easier to extract the `ffi` library as a `libllvmlite` package such
32
# that it can be built against multiple LLVM versions using a CMake
33
# driven toolchain.
34
cmake_options = []
35
36
env_vars = {"LLVMLITE_PACKAGE_FORMAT": ("conda", "wheel"),
37
"LLVMLITE_USE_RTTI": ("ON", "OFF", ""),
38
"LLVMLITE_CXX_STATIC_LINK": bool,
39
"LLVMLITE_SHARED": bool,
40
"LLVMLITE_LTO": bool,
41
"LLVMLITE_SKIP_LLVM_VERSION_CHECK": bool,}
42
43
for env_var in env_vars.keys():
44
env_value = os.environ.get(env_var, None)
45
if env_value is not None:
46
expected_value = env_vars[env_var]
47
if expected_value is bool:
48
if env_value.lower() in ("true", "on", "yes", "1", "y"):
49
cmake_options.append(f"-D{env_var}=ON")
50
elif env_value.lower() in ("false", "off", "no", "0", "n", ""):
51
cmake_options.append(f"-D{env_var}=OFF")
52
else:
53
msg = ("Unexpected value found for build configuration "
54
f"environment variable '{env_var}={env_value}', "
55
"expected a boolean or unset.")
56
raise ValueError(msg)
57
else:
58
if env_value in expected_value:
59
cmake_options.append(f"-D{env_var}={env_value}")
60
elif env_value == "":
61
# empty is fine
62
pass
63
else:
64
msg = ("Unexpected value found for build configuration "
65
f"environment variable '{env_var}={env_value}', "
66
f"expected one of {expected_value} or unset.")
67
raise ValueError(msg)
68
69
# Are there any `LLVMLITE_` prefixed env vars which are unused? (perhaps a
70
# spelling mistake/typo)
71
llvmlite_env_vars = set(k for k in os.environ.keys()
72
if k.startswith("LLVMLITE_"))
73
unknown_env_vars = llvmlite_env_vars - set(env_vars.keys())
74
if unknown_env_vars:
75
msg = "Unknown LLVMLITE_ prefixed environment variables found:\n"
76
msg += "\n".join([f"- {x}" for x in unknown_env_vars])
77
msg += "\nIs this intended?"
78
warnings.warn(msg)
79
80
return cmake_options
81
82
83
@functools.cache
84
def check_cmake():
85
try:
86
subprocess.run(("cmake", ), check=True, stdout=subprocess.PIPE,
87
stderr=subprocess.PIPE, timeout=60)
88
except subprocess.CalledProcessError as e:
89
msg = ("llvmlite needs working CMake tools to build. There was an "
90
"issue when performing a test run of the 'cmake' binary.\n"
91
f"STDOUT: {e.stdout.decode('UTF-8')}\n"
92
f"STDERR: {e.stderr.decode('UTF-8')}\n"
93
"See the traceback for details.")
94
raise RuntimeError(msg) from e
95
except FileNotFoundError as e:
96
msg = ("llvmlite needs CMake tools to build. It appears that the "
97
"'cmake' tool is either not installed or not found on the path. "
98
"Please add CMake tools to the build environment and path, they "
99
"are available from many package managers.")
100
raise FileNotFoundError(msg) from e
101
# Timeout etc not handled, there's little advice that can be given and the
102
# traceback is self explanatory.
103
104
105
def try_cmake(cmake_dir, build_dir, generator, arch=None, toolkit=None):
106
# first check that CMake tools are present and run ok.
107
check_cmake()
108
old_dir = os.getcwd()
109
args = ['cmake', '-G', generator]
110
if arch is not None:
111
args += ['-A', arch]
112
if toolkit is not None:
113
args += ['-T', toolkit]
114
args.append(cmake_dir)
115
cmake_options = env_var_options_to_cmake_options()
116
args += cmake_options
117
# Handle conda build/conda-style toolchain. These toolchains ship an `ar`
118
# and a `ranlib` under a different name, and then export these as cmake
119
# compatible `-D` defines in the `CMAKE_ARGS` env var. OSX and Linux both
120
# have this, the following fetches this variable from the environment and
121
# wires it through to cmake.
122
CMAKE_ARGS = [x for x in os.environ.get("CMAKE_ARGS", "").split(' ') if x]
123
args += CMAKE_ARGS
124
try:
125
os.chdir(build_dir)
126
print('Running:', ' '.join(args))
127
subprocess.check_call(args)
128
finally:
129
os.chdir(old_dir)
130
131
132
def find_windows_generator():
133
"""
134
Find a suitable cmake "generator" under Windows.
135
"""
136
# XXX this assumes we will find a generator that's the same, or
137
# compatible with, the one which was used to compile LLVM... cmake
138
# seems a bit lacking here.
139
cmake_dir = os.path.join(here_dir, 'dummy')
140
# LLVM 9.0 and later needs VS 2017 minimum.
141
generators = []
142
env_generator = os.environ.get("CMAKE_GENERATOR", None)
143
if env_generator is not None:
144
env_arch = os.environ.get("CMAKE_GENERATOR_ARCH", None)
145
env_toolkit = os.environ.get("CMAKE_GENERATOR_TOOLKIT", None)
146
generators.append(
147
(env_generator, env_arch, env_toolkit)
148
)
149
150
generators.extend([
151
# use VS2022 first
152
('Visual Studio 17 2022', ('x64' if is_64bit else 'Win32'), 'v143'),
153
# try VS2019 next
154
('Visual Studio 16 2019', ('x64' if is_64bit else 'Win32'), 'v142'),
155
# # This is the generator configuration for VS2017
156
# ('Visual Studio 15 2017' + (' Win64' if is_64bit else ''), None, None)
157
])
158
for generator in generators:
159
build_dir = tempfile.mkdtemp()
160
print("Trying generator %r" % (generator,))
161
try:
162
try_cmake(cmake_dir, build_dir, *generator)
163
except subprocess.CalledProcessError:
164
continue
165
else:
166
# Success
167
return generator
168
finally:
169
shutil.rmtree(build_dir)
170
raise RuntimeError("No compatible CMake generator could be found.")
171
172
173
def main_windows():
174
generator = find_windows_generator()
175
config = 'Release'
176
if not os.path.exists(build_dir):
177
os.mkdir(build_dir)
178
# Run configuration step
179
try_cmake(here_dir, build_dir, *generator)
180
subprocess.check_call(['cmake', '--build', build_dir, '--config', config])
181
shutil.copy(os.path.join(build_dir, config, 'llvmlite.dll'), target_dir)
182
183
184
def main_posix(library_ext):
185
generator = 'Unix Makefiles'
186
config = 'Release'
187
if not os.path.exists(build_dir):
188
os.mkdir(build_dir)
189
try_cmake(here_dir, build_dir, generator)
190
cmd = ['cmake', '--build', build_dir, "--parallel", '--config', config]
191
subprocess.check_call(cmd)
192
shutil.copy(os.path.join(build_dir, 'libllvmlite' + library_ext),
193
target_dir)
194
195
196
def main():
197
ELF_systems = ('linux', 'gnu', 'freebsd', 'openbsd', 'netbsd')
198
if sys.platform == 'win32':
199
main_windows()
200
elif sys.platform.startswith(ELF_systems):
201
main_posix('.so')
202
elif sys.platform == 'darwin':
203
main_posix('.dylib')
204
else:
205
raise RuntimeError("unsupported platform: %r" % (sys.platform,))
206
207
208
if __name__ == "__main__":
209
main()
210
211