"""
Build script for the shared library providing the C ABI bridge to LLVM.
"""
from __future__ import print_function
import functools
import os
import subprocess
import shutil
import sys
import tempfile
import warnings
here_dir = os.path.abspath(os.path.dirname(__file__))
build_dir = os.path.join(here_dir, 'build')
target_dir = os.path.join(os.path.dirname(here_dir), 'llvmlite', 'binding')
is_64bit = sys.maxsize >= 2**32
def env_var_options_to_cmake_options():
cmake_options = []
env_vars = {"LLVMLITE_PACKAGE_FORMAT": ("conda", "wheel"),
"LLVMLITE_USE_RTTI": ("ON", "OFF", ""),
"LLVMLITE_CXX_STATIC_LINK": bool,
"LLVMLITE_SHARED": bool,
"LLVMLITE_LTO": bool,
"LLVMLITE_SKIP_LLVM_VERSION_CHECK": bool,}
for env_var in env_vars.keys():
env_value = os.environ.get(env_var, None)
if env_value is not None:
expected_value = env_vars[env_var]
if expected_value is bool:
if env_value.lower() in ("true", "on", "yes", "1", "y"):
cmake_options.append(f"-D{env_var}=ON")
elif env_value.lower() in ("false", "off", "no", "0", "n", ""):
cmake_options.append(f"-D{env_var}=OFF")
else:
msg = ("Unexpected value found for build configuration "
f"environment variable '{env_var}={env_value}', "
"expected a boolean or unset.")
raise ValueError(msg)
else:
if env_value in expected_value:
cmake_options.append(f"-D{env_var}={env_value}")
elif env_value == "":
pass
else:
msg = ("Unexpected value found for build configuration "
f"environment variable '{env_var}={env_value}', "
f"expected one of {expected_value} or unset.")
raise ValueError(msg)
llvmlite_env_vars = set(k for k in os.environ.keys()
if k.startswith("LLVMLITE_"))
unknown_env_vars = llvmlite_env_vars - set(env_vars.keys())
if unknown_env_vars:
msg = "Unknown LLVMLITE_ prefixed environment variables found:\n"
msg += "\n".join([f"- {x}" for x in unknown_env_vars])
msg += "\nIs this intended?"
warnings.warn(msg)
return cmake_options
@functools.cache
def check_cmake():
try:
subprocess.run(("cmake", ), check=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, timeout=60)
except subprocess.CalledProcessError as e:
msg = ("llvmlite needs working CMake tools to build. There was an "
"issue when performing a test run of the 'cmake' binary.\n"
f"STDOUT: {e.stdout.decode('UTF-8')}\n"
f"STDERR: {e.stderr.decode('UTF-8')}\n"
"See the traceback for details.")
raise RuntimeError(msg) from e
except FileNotFoundError as e:
msg = ("llvmlite needs CMake tools to build. It appears that the "
"'cmake' tool is either not installed or not found on the path. "
"Please add CMake tools to the build environment and path, they "
"are available from many package managers.")
raise FileNotFoundError(msg) from e
def try_cmake(cmake_dir, build_dir, generator, arch=None, toolkit=None):
check_cmake()
old_dir = os.getcwd()
args = ['cmake', '-G', generator]
if arch is not None:
args += ['-A', arch]
if toolkit is not None:
args += ['-T', toolkit]
args.append(cmake_dir)
cmake_options = env_var_options_to_cmake_options()
args += cmake_options
CMAKE_ARGS = [x for x in os.environ.get("CMAKE_ARGS", "").split(' ') if x]
args += CMAKE_ARGS
try:
os.chdir(build_dir)
print('Running:', ' '.join(args))
subprocess.check_call(args)
finally:
os.chdir(old_dir)
def find_windows_generator():
"""
Find a suitable cmake "generator" under Windows.
"""
cmake_dir = os.path.join(here_dir, 'dummy')
generators = []
env_generator = os.environ.get("CMAKE_GENERATOR", None)
if env_generator is not None:
env_arch = os.environ.get("CMAKE_GENERATOR_ARCH", None)
env_toolkit = os.environ.get("CMAKE_GENERATOR_TOOLKIT", None)
generators.append(
(env_generator, env_arch, env_toolkit)
)
generators.extend([
('Visual Studio 17 2022', ('x64' if is_64bit else 'Win32'), 'v143'),
('Visual Studio 16 2019', ('x64' if is_64bit else 'Win32'), 'v142'),
])
for generator in generators:
build_dir = tempfile.mkdtemp()
print("Trying generator %r" % (generator,))
try:
try_cmake(cmake_dir, build_dir, *generator)
except subprocess.CalledProcessError:
continue
else:
return generator
finally:
shutil.rmtree(build_dir)
raise RuntimeError("No compatible CMake generator could be found.")
def main_windows():
generator = find_windows_generator()
config = 'Release'
if not os.path.exists(build_dir):
os.mkdir(build_dir)
try_cmake(here_dir, build_dir, *generator)
subprocess.check_call(['cmake', '--build', build_dir, '--config', config])
shutil.copy(os.path.join(build_dir, config, 'llvmlite.dll'), target_dir)
def main_posix(library_ext):
generator = 'Unix Makefiles'
config = 'Release'
if not os.path.exists(build_dir):
os.mkdir(build_dir)
try_cmake(here_dir, build_dir, generator)
cmd = ['cmake', '--build', build_dir, "--parallel", '--config', config]
subprocess.check_call(cmd)
shutil.copy(os.path.join(build_dir, 'libllvmlite' + library_ext),
target_dir)
def main():
ELF_systems = ('linux', 'gnu', 'freebsd', 'openbsd', 'netbsd')
if sys.platform == 'win32':
main_windows()
elif sys.platform.startswith(ELF_systems):
main_posix('.so')
elif sys.platform == 'darwin':
main_posix('.dylib')
else:
raise RuntimeError("unsupported platform: %r" % (sys.platform,))
if __name__ == "__main__":
main()