miasm/setup.py

343 lines
12 KiB
Python

from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext
from setuptools.command.build_py import build_py
from setuptools.command.sdist import sdist
import atexit
import fnmatch
import os
import platform
import re
import subprocess
import sysconfig
import tempfile
from shutil import copy2, copyfile, rmtree, which
is_win = platform.system() == "Windows"
is_mac = platform.system() == "Darwin"
is_64bit = platform.architecture()[0] == "64bit"
require_jit = os.environ.get("MIASM_REQUIRE_JIT") == "1"
if is_win:
import winreg
BUILD_WARNINGS = []
def set_extension_compile_args(extension):
rel_lib_path = extension.name.replace(".", "/")
abs_lib_path = os.path.join(sysconfig.get_path("platlib"), rel_lib_path)
lib_name = abs_lib_path + ".so"
extension.extra_link_args = ["-Wl,-install_name," + lib_name]
def win_get_llvm_reg():
REG_PATH = "SOFTWARE\\LLVM\\LLVM"
try:
return winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
REG_PATH,
0,
winreg.KEY_READ | winreg.KEY_WOW64_32KEY,
)
except FileNotFoundError:
pass
return winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, REG_PATH, 0, winreg.KEY_READ)
def win_find_clang_path():
try:
with win_get_llvm_reg() as rkey:
return winreg.QueryValueEx(rkey, None)[0]
except FileNotFoundError:
# Visual Studio ships with an optional Clang distribution; detect that
# when the standalone LLVM registry key is not present.
clang_cl = which("clang-cl")
if clang_cl is None:
return None
return os.path.abspath(os.path.join(os.path.dirname(clang_cl), "..", ".."))
def win_get_clang_version(clang_path):
try:
clang = os.path.join(clang_path, "bin", "clang.exe")
stdout = subprocess.check_output('"{}" --version'.format(clang))
version = stdout.splitlines(False)[0].decode()
match = re.search(r"version (\d+\.\d+\.\d+)", version)
if match is None:
return None
return [int(part) for part in match.group(1).split(".")]
except FileNotFoundError:
return None
def win_use_clang():
# To force setuptools to use clang-cl, copy the LLVM tools into a
# temporary directory as cl.exe/link.exe and put that directory first in
# PATH. Using the build directory would avoid a tempdir, but setuptools
# does not expose a reliable build path before build_ext starts.
clang_path = win_find_clang_path()
if clang_path is None:
return False
clang_version = win_get_clang_version(clang_path)
if clang_version is None:
return False
tmpdir = tempfile.mkdtemp(prefix="llvm")
try:
copyfile(os.path.join(clang_path, "bin", "clang-cl.exe"), os.path.join(tmpdir, "cl.exe"))
# When forcing clang, put lld-link.exe first as link.exe so setuptools
# uses the LLVM-compatible linker. LLVM >= 14.0.0 is required because
# earlier versions do not support MSVC's /LTCG flag and fail during
# linking.
if clang_version[0] < 14:
rmtree(tmpdir)
return False
copyfile(os.path.join(clang_path, "bin", "lld-link.exe"), os.path.join(tmpdir, "link.exe"))
except FileNotFoundError:
rmtree(tmpdir)
return False
# Add the temporary directory at the front of PATH and clean it up when the
# build process exits.
os.environ["PATH"] = "%s;%s" % (tmpdir, os.environ["PATH"])
atexit.register(lambda dir_: rmtree(dir_), tmpdir)
print(
"Found Clang {}.{}.{}: {}".format(
clang_version[0], clang_version[1], clang_version[2], clang_path
)
)
return True
def make_ext_modules(optional):
vm_common = [
"miasm/jitter/vm_mngr.c",
"miasm/jitter/vm_mngr_py.c",
"miasm/jitter/bn.c",
]
core_common = [
"miasm/jitter/JitCore.c",
*vm_common,
"miasm/jitter/op_semantics.c",
]
return [
Extension("miasm.jitter.VmMngr", vm_common, optional=optional),
Extension(
"miasm.jitter.arch.JitCore_x86",
[*core_common, "miasm/jitter/arch/JitCore_x86.c"],
optional=optional,
),
Extension(
"miasm.jitter.arch.JitCore_arm",
[*core_common, "miasm/jitter/arch/JitCore_arm.c"],
optional=optional,
),
Extension(
"miasm.jitter.arch.JitCore_aarch64",
[*core_common, "miasm/jitter/arch/JitCore_aarch64.c"],
optional=optional,
),
Extension(
"miasm.jitter.arch.JitCore_msp430",
[*core_common, "miasm/jitter/arch/JitCore_msp430.c"],
optional=optional,
),
Extension(
"miasm.jitter.arch.JitCore_mep",
[
"miasm/jitter/JitCore.c",
*vm_common,
"miasm/jitter/arch/JitCore_mep.c",
],
optional=optional,
),
Extension(
"miasm.jitter.arch.JitCore_mips32",
[*core_common, "miasm/jitter/arch/JitCore_mips32.c"],
optional=optional,
),
Extension(
"miasm.jitter.arch.JitCore_ppc32",
[*core_common, "miasm/jitter/arch/JitCore_ppc32.c"],
optional=optional,
),
Extension(
"miasm.jitter.arch.JitCore_m68k",
[*core_common, "miasm/jitter/arch/JitCore_m68k.c"],
optional=optional,
),
Extension(
"miasm.jitter.Jitllvm",
[
"miasm/jitter/Jitllvm.c",
"miasm/jitter/bn.c",
"miasm/runtime/udivmodti4.c",
"miasm/runtime/divti3.c",
"miasm/runtime/udivti3.c",
],
optional=optional,
),
Extension(
"miasm.jitter.Jitgcc",
["miasm/jitter/Jitgcc.c", "miasm/jitter/bn.c"],
optional=optional,
),
]
def configured_ext_modules():
build_extensions = True
win_force_clang = False
if is_win:
if is_64bit or which("cl") is None:
# 64-bit builds require clang for uint128_t support. In 32-bit mode
# the ABI does not use uint128_t, so MSVC is fine; still try clang
# there if cl.exe is missing from PATH.
win_force_clang = win_use_clang()
if is_64bit and not win_force_clang:
BUILD_WARNINGS.append(
"Could not find a suitable Clang/LLVM installation. "
"You can download LLVM from https://releases.llvm.org"
)
BUILD_WARNINGS.append(
"Alternatively you can select the 'C++ Clang-cl build tools' "
"in the Visual Studio Installer"
)
build_extensions = False
cl = which("cl")
link = which("link")
if cl is None or link is None:
BUILD_WARNINGS.append(
"Could not find cl.exe and/or link.exe in the PATH, try building "
"miasm from a Visual Studio command prompt"
)
BUILD_WARNINGS.append("More information at: https://wiki.python.org/moin/WindowsCompilers")
build_extensions = False
else:
print("Found cl.exe: {}".format(cl))
print("Found link.exe: {}".format(link))
if not build_extensions:
message = "miasm jit extensions will not be compiled, details:"
if require_jit:
print("ERROR: " + message)
else:
print("WARNING: " + message)
for warning in BUILD_WARNINGS:
print(" " + warning)
if require_jit:
raise RuntimeError("Unable to build miasm native extensions")
return []
ext_modules = make_ext_modules(optional=not require_jit)
if is_win:
# Force setuptools to use the compiler/linker already selected in PATH.
# https://docs.python.org/3/distutils/apiref.html#module-distutils.msvccompiler
os.environ["MSSdk"] = "1"
os.environ["DISTUTILS_USE_SDK"] = "1"
extra_compile_args = ["-D_CRT_SECURE_NO_WARNINGS"]
if win_force_clang:
march = "-m64" if is_64bit else "-m32"
extra_compile_args += [
march,
"-Wno-unused-command-line-argument",
"-Wno-visibility",
"-Wno-dll-attribute-on-redeclaration",
"-Wno-tautological-compare",
"-Wno-unused-but-set-variable",
]
for extension in ext_modules:
extension.extra_compile_args = extra_compile_args
elif is_mac:
for extension in ext_modules:
set_extension_compile_args(extension)
cfg_vars = sysconfig.get_config_vars()
ldshared = cfg_vars.get("LDSHARED")
if ldshared:
cfg_vars["LDSHARED"] = ldshared.replace("-bundle", "-dynamiclib")
return ext_modules
def write_version_file(root, version):
version_file = os.path.join(root, "miasm", "VERSION")
os.makedirs(os.path.dirname(version_file), exist_ok=True)
with open(version_file, "w", encoding="utf-8") as fdesc:
fdesc.write(version)
class MiasmBuildPy(build_py):
def run(self):
super().run()
write_version_file(self.build_lib, self.distribution.get_version())
class MiasmSdist(sdist):
def make_release_tree(self, base_dir, files):
super().make_release_tree(base_dir, files)
write_version_file(base_dir, self.distribution.get_version())
class MiasmBuildExt(build_ext):
def build_extensions(self):
if is_mac:
linker_so = getattr(self.compiler, "linker_so", None)
if isinstance(linker_so, list):
self.compiler.linker_so = [
"-dynamiclib" if arg == "-bundle" else arg
for arg in linker_so
]
super().build_extensions()
def run(self):
super().run()
if is_win and self.extensions:
self.copy_windows_import_libs()
def copy_windows_import_libs(self):
build_roots = {
os.path.abspath("build"),
os.path.abspath(os.path.dirname(self.build_lib)),
os.path.abspath(os.path.dirname(self.build_temp)),
}
libs = []
for build_root in build_roots:
if not os.path.isdir(build_root):
continue
for root, _, files in os.walk(build_root):
for filename in files:
if filename.endswith(".lib"):
libs.append(os.path.join(root, filename))
for lib in libs:
filename = os.path.basename(lib)
dst_dir = os.path.join(self.build_lib, "miasm", "jitter")
# Windows import libraries are named after the built extension,
# e.g. VmMngr.cp313-win_amd64.lib.
if not any(
fnmatch.fnmatch(filename, pattern)
for pattern in ["VmMngr.*lib", "Jitgcc.*lib", "Jitllvm.*lib"]
):
dst_dir = os.path.join(dst_dir, "arch")
os.makedirs(dst_dir, exist_ok=True)
dst = os.path.join(dst_dir, filename)
if os.path.abspath(lib) == os.path.abspath(dst):
continue
if not os.path.isfile(dst):
print("Copying", lib, "to", dst)
copy2(lib, dst)
setup(
ext_modules=configured_ext_modules(),
cmdclass={
"build_ext": MiasmBuildExt,
"build_py": MiasmBuildPy,
"sdist": MiasmSdist,
},
)