Python bindings:

- Moved project package settings to the new TOML format
- Refactored setup.py to cleanup/improve the code and make it ready for cibuildwheel
- Updated README.md with the package long description part
- Removed setup.cfg since universal wheel building will be deprecated soon
This commit is contained in:
Antelox 2024-10-11 10:03:23 +02:00
parent 683b97497b
commit f90429db72
4 changed files with 103 additions and 132 deletions

View File

@ -1,3 +1,21 @@
# Unicorn
Unicorn is a lightweight, multi-platform, multi-architecture CPU emulator framework
based on [QEMU](http://qemu.org).
Unicorn offers some unparalleled features:
- Multi-architecture: ARM, ARM64 (ARMv8), M68K, MIPS, PowerPC, RISCV, SPARC, S390X, TriCore and X86 (16, 32, 64-bit)
- Clean/simple/lightweight/intuitive architecture-neutral API
- Implemented in pure C language, with bindings for Crystal, Clojure, Visual Basic, Perl, Rust, Ruby, Python, Java, .NET, Go, Delphi/Free Pascal, Haskell, Pharo, and Lua.
- Native support for Windows & *nix (with Mac OSX, Linux, *BSD & Solaris confirmed)
- High performance via Just-In-Time compilation
- Support for fine-grained instrumentation at various levels
- Thread-safety by design
- Distributed under free software license GPLv2
Further information is available at http://www.unicorn-engine.org
# Python Bindings for Unicorn
Originally written by Nguyen Anh Quynh, polished and redesigned by elicn, maintained by all community contributors.

View File

@ -0,0 +1,40 @@
[build-system]
requires = ["setuptools", "build", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "unicorn"
version = "2.1.1"
requires-python = ">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*, != 3.4.*, != 3.5.*, != 3.6.*"
authors = [
{ name = "Nguyen Anh Quynh", email = "quynh@gmail.com" },
]
description = "Unicorn CPU emulator engine"
readme = "README.md"
keywords = ["emulation", "qemu", "unicorn"]
classifiers = [
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
]
[project.urls]
Homepage = "http://www.unicorn-engine.org"
Repository = "https://github.com/unicorn-engine/unicorn"
"Bug Tracker" = "https://github.com/unicorn-engine/unicorn/issues"
Changelog = "https://github.com/unicorn-engine/unicorn/blob/master/ChangeLog"
[project.optional-dependencies]
test = [
"pytest",
"pytest-cov",
]
[tool.setuptools.packages.find]
include = ["unicorn*"]

View File

@ -1,2 +0,0 @@
[bdist_wheel]
universal=1

View File

@ -1,29 +1,19 @@
#!/usr/bin/env python
# Python binding for Unicorn engine. Nguyen Anh Quynh <aquynh@gmail.com>
from __future__ import print_function
import glob
import logging
import os
import subprocess
import shutil
import sys
import platform
import setuptools
import shutil
import subprocess
import sys
from setuptools import setup
from sysconfig import get_platform
from setuptools.command.build import build
from setuptools.command.sdist import sdist
from setuptools.command.bdist_egg import bdist_egg
log = logging.getLogger(__name__)
SYSTEM = sys.platform
# sys.maxint is 2**31 - 1 on both 32 and 64 bit mingw
IS_64BITS = platform.architecture()[0] == '64bit'
# are we building from the repository or from a source distribution?
ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
LIBS_DIR = os.path.join(ROOT_DIR, 'unicorn', 'lib')
@ -32,29 +22,28 @@ SRC_DIR = os.path.join(ROOT_DIR, 'src')
UC_DIR = SRC_DIR if os.path.exists(SRC_DIR) else os.path.join(ROOT_DIR, '../..')
BUILD_DIR = os.path.join(UC_DIR, 'build_python')
VERSION = "2.1.1"
if SYSTEM == 'darwin':
if sys.platform == 'darwin':
LIBRARY_FILE = "libunicorn.2.dylib"
STATIC_LIBRARY_FILE = "libunicorn.a"
elif SYSTEM in ('win32', 'cygwin'):
elif sys.platform in ('win32', 'cygwin'):
LIBRARY_FILE = "unicorn.dll"
STATIC_LIBRARY_FILE = "unicorn.lib"
else:
LIBRARY_FILE = "libunicorn.so.2"
STATIC_LIBRARY_FILE = "libunicorn.a"
def clean_bins():
shutil.rmtree(LIBS_DIR, ignore_errors=True)
shutil.rmtree(HEADERS_DIR, ignore_errors=True)
def copy_sources():
"""Copy the C sources into the source directory.
"""
Copy the C sources into the source directory.
This rearranges the source files under the python distribution
directory.
"""
src = []
shutil.rmtree(SRC_DIR, ignore_errors=True)
os.mkdir(SRC_DIR)
@ -67,12 +56,12 @@ def copy_sources():
shutil.copytree(os.path.join(ROOT_DIR, '../../glib_compat'), os.path.join(SRC_DIR, 'glib_compat/'))
try:
# remove site-specific configuration file
# might not exist
# remove site-specific configuration file, might not exist
os.remove(os.path.join(SRC_DIR, 'qemu/config-host.mak'))
except OSError:
pass
src = []
src.extend(glob.glob(os.path.join(ROOT_DIR, "../../*.[ch]")))
src.extend(glob.glob(os.path.join(ROOT_DIR, "../../*.mk")))
src.extend(glob.glob(os.path.join(ROOT_DIR, "../../cmake/*.cmake")))
@ -87,6 +76,7 @@ def copy_sources():
log.info("%s -> %s" % (filename, outpath))
shutil.copy(filename, outpath)
def build_libraries():
"""
Prepare the unicorn directory for a binary distribution or installation.
@ -94,7 +84,6 @@ def build_libraries():
Will use a src/ dir if one exists in the current directory, otherwise assumes it's in the repo
"""
cwd = os.getcwd()
clean_bins()
os.mkdir(HEADERS_DIR)
os.mkdir(LIBS_DIR)
@ -102,158 +91,84 @@ def build_libraries():
# copy public headers
shutil.copytree(os.path.join(UC_DIR, 'include', 'unicorn'), os.path.join(HEADERS_DIR, 'unicorn'))
# check if a prebuilt library exists
# if so, use it instead of building
# check if a prebuilt library exists and if so, use it instead of building
if os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE)):
shutil.copy(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE), LIBS_DIR)
if STATIC_LIBRARY_FILE is not None and os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', STATIC_LIBRARY_FILE)):
shutil.copy(os.path.join(ROOT_DIR, 'prebuilt', STATIC_LIBRARY_FILE), LIBS_DIR)
return
# otherwise, build!!
os.chdir(UC_DIR)
# otherwise, build
if not os.path.exists(BUILD_DIR):
os.mkdir(BUILD_DIR)
try:
subprocess.check_call(['msbuild', '/help'])
except:
has_msbuild = False
else:
has_msbuild = True
has_msbuild = shutil.which('msbuild') is not None
conf = 'Debug' if int(os.getenv('DEBUG', 0)) else 'Release'
if has_msbuild and SYSTEM == 'win32':
if has_msbuild and sys.platform == 'win32':
plat = 'Win32' if platform.architecture()[0] == '32bit' else 'x64'
conf = 'Debug' if os.getenv('DEBUG', '') else 'Release'
if not os.path.exists(BUILD_DIR):
os.mkdir(BUILD_DIR)
subprocess.check_call(['cmake', '-B', BUILD_DIR, '-G', "Visual Studio 16 2019", "-A", plat, "-DCMAKE_BUILD_TYPE=" + conf])
subprocess.check_call(['msbuild', 'unicorn.sln', '-m', '-p:Platform=' + plat, '-p:Configuration=' + conf], cwd=BUILD_DIR)
subprocess.check_call(['cmake', '-B', BUILD_DIR, '-G', "Visual Studio 16 2019", "-A", plat,
"-DCMAKE_BUILD_TYPE=" + conf], cwd=UC_DIR)
subprocess.check_call(['msbuild', 'unicorn.sln', '-m', '-p:Platform=' + plat, '-p:Configuration=' + conf],
cwd=BUILD_DIR)
obj_dir = os.path.join(BUILD_DIR, conf)
shutil.copy(os.path.join(obj_dir, LIBRARY_FILE), LIBS_DIR)
shutil.copy(os.path.join(BUILD_DIR, STATIC_LIBRARY_FILE), LIBS_DIR)
else:
# platform description refs at https://docs.python.org/2/library/sys.html#sys.platform
if not os.path.exists(BUILD_DIR):
os.mkdir(BUILD_DIR)
conf = 'Debug' if os.getenv('DEBUG', '') else 'Release'
cmake_args = ["cmake", '-B', BUILD_DIR, '-S', UC_DIR, "-DCMAKE_BUILD_TYPE=" + conf]
if os.getenv("TRACE", ""):
if os.getenv("TRACE"):
cmake_args += ["-DUNICORN_TRACER=on"]
subprocess.check_call(cmake_args)
os.chdir(BUILD_DIR)
subprocess.check_call(cmake_args, cwd=UC_DIR)
threads = os.getenv("THREADS", "4")
subprocess.check_call(["cmake", "--build", ".", "-j" + threads])
shutil.copy(LIBRARY_FILE, LIBS_DIR)
shutil.copy(STATIC_LIBRARY_FILE, LIBS_DIR)
subprocess.check_call(["cmake", "--build", ".", "-j" + threads], cwd=BUILD_DIR)
os.chdir(cwd)
shutil.copy(os.path.join(BUILD_DIR, LIBRARY_FILE), LIBS_DIR)
shutil.copy(os.path.join(BUILD_DIR, STATIC_LIBRARY_FILE), LIBS_DIR)
class custom_sdist(sdist):
class CustomSDist(sdist):
def run(self):
clean_bins()
copy_sources()
return sdist.run(self)
return super().run()
class custom_build(build):
class CustomBuild(build):
def run(self):
if 'LIBUNICORN_PATH' in os.environ:
log.info("Skipping building C extensions since LIBUNICORN_PATH is set")
else:
log.info("Building C extensions")
build_libraries()
return build.run(self)
return super().run()
class custom_bdist_egg(bdist_egg):
class CustomBDistEgg(bdist_egg):
def run(self):
self.run_command('build')
return bdist_egg.run(self)
return super().run()
def dummy_src():
return []
cmdclass = {}
cmdclass['build'] = custom_build
cmdclass['sdist'] = custom_sdist
cmdclass['bdist_egg'] = custom_bdist_egg
if 'bdist_wheel' in sys.argv and '--plat-name' not in sys.argv:
idx = sys.argv.index('bdist_wheel') + 1
sys.argv.insert(idx, '--plat-name')
name = get_platform()
if 'linux' in name:
# linux_* platform tags are disallowed because the python ecosystem is fubar
# linux builds should be built in the centos 5 vm for maximum compatibility
# see https://github.com/pypa/manylinux
# see also https://github.com/angr/angr-dev/blob/master/bdist.sh
sys.argv.insert(idx + 1, 'manylinux1_' + platform.machine())
elif 'mingw' in name:
if IS_64BITS:
sys.argv.insert(idx + 1, 'win_amd64')
else:
sys.argv.insert(idx + 1, 'win32')
else:
# https://www.python.org/dev/peps/pep-0425/
sys.argv.insert(idx + 1, name.replace('.', '_').replace('-', '_'))
cmdclass = {'build': CustomBuild, 'sdist': CustomSDist, 'bdist_egg': CustomBDistEgg}
try:
from setuptools.command.develop import develop
class custom_develop(develop):
class CustomDevelop(develop):
def run(self):
log.info("Building C extensions")
build_libraries()
return develop.run(self)
return super().run()
cmdclass['develop'] = custom_develop
cmdclass['develop'] = CustomDevelop
except ImportError:
print("Proper 'develop' support unavailable.")
def join_all(src, files):
return tuple(os.path.join(src, f) for f in files)
long_desc = '''
Unicorn is a lightweight, multi-platform, multi-architecture CPU emulator framework
based on [QEMU](http://qemu.org).
Unicorn offers some unparalleled features:
- Multi-architecture: ARM, ARM64 (ARMv8), M68K, MIPS, PowerPC, RISCV, SPARC, S390X, TriCore and X86 (16, 32, 64-bit)
- Clean/simple/lightweight/intuitive architecture-neutral API
- Implemented in pure C language, with bindings for Crystal, Clojure, Visual Basic, Perl, Rust, Ruby, Python, Java, .NET, Go, Delphi/Free Pascal, Haskell, Pharo, and Lua.
- Native support for Windows & *nix (with Mac OSX, Linux, *BSD & Solaris confirmed)
- High performance via Just-In-Time compilation
- Support for fine-grained instrumentation at various levels
- Thread-safety by design
- Distributed under free software license GPLv2
Further information is available at http://www.unicorn-engine.org
'''
setup(
provides=['unicorn'],
packages=setuptools.find_packages(include=["unicorn", "unicorn.*"]),
name='unicorn',
version=VERSION,
author='Nguyen Anh Quynh',
author_email='aquynh@gmail.com',
description='Unicorn CPU emulator engine',
long_description=long_desc,
long_description_content_type="text/markdown",
url='http://www.unicorn-engine.org',
classifiers=[
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
],
requires=['ctypes'],
cmdclass=cmdclass,
zip_safe=False,
include_package_data=True,
is_pure=False,
package_data={
'unicorn': ['unicorn/py.typed', 'lib/*', 'include/unicorn/*']
}
has_ext_modules=lambda: True, # It's not a Pure Python wheel
)