From 34374b00c2c320d27273f6be69dfe7bd571367fb Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 14 Jan 2020 13:42:18 +0200 Subject: [PATCH] Replace custom distutils hacks with a simple Extension --- .gitignore | 5 + MANIFEST.in | 2 +- python/distorm3/__init__.py | 54 +++---- python/python_module_init.c | 30 ++++ setup.cfg | 3 - setup.py | 294 +----------------------------------- 6 files changed, 65 insertions(+), 323 deletions(-) create mode 100644 .gitignore create mode 100644 python/python_module_init.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6729dea --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.egg-info +*.py[cod] +*.so +build +dist diff --git a/MANIFEST.in b/MANIFEST.in index 3b97656..372e4c5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include COPYING setup.cfg setup.py include make\win32\cdistorm.vcxproj make\win32\cdistorm.vcxproj.filters make\win32\distorm.sln make\win32\resource.h make\win32\Resource.rc recursive-include src *.c *.h -recursive-include include *.c *.h +recursive-include include *.h recursive-include . *.py diff --git a/python/distorm3/__init__.py b/python/distorm3/__init__.py index 1b1d5a2..7d8706b 100755 --- a/python/distorm3/__init__.py +++ b/python/distorm3/__init__.py @@ -26,8 +26,7 @@ ] from ctypes import * -from os.path import split, join -from os import name as os_name +import os import sys if sys.version_info[0] >= 3: @@ -36,33 +35,25 @@ #============================================================================== # Load the diStorm DLL -# Guess the DLL filename and load the library. -_distorm_path = None -try: - _distorm_path = split(__file__)[0] -except NameError: - # In some containers __file__ isn't set. - pass -if hasattr(sys, "oxidized"): - _distorm_path = "" -if hasattr(sys, '_MEIPASS'): - _distorm_path = sys._MEIPASS -potential_libs = ['libdistorm3.so', 'libdistorm3.dylib'] -if os_name == 'nt': - potential_libs = ['distorm3.dll', 'libdistorm3.dll'] -lib_was_found = False -for i in potential_libs: - try: - _distorm_file = join(_distorm_path, i) - _distorm = cdll.LoadLibrary(_distorm_file) - lib_was_found = True - break - except OSError: - pass - -if lib_was_found == False: +def _load_distorm(): + if sys.version_info[0] == 3: + try: + import _distorm3 + return cdll.LoadLibrary(_distorm3.__spec__.origin) + except ImportError: + pass + + dll_ext = ('.dll' if sys.platform == 'win32' else '.so') + libnames = ['_distorm3' + dll_ext] + for dir in sys.path: + for name in libnames: + _distorm_file = os.path.join(dir, name) + if os.path.isfile(_distorm_file): + return cdll.LoadLibrary(_distorm_file) raise ImportError("Error loading the diStorm dynamic library (or cannot load library into process).") +_distorm = _load_distorm() + # Get the decode C function (try 64 bits version first, only then 32 bits). SUPPORT_64BIT_OFFSET = False try: @@ -71,12 +62,9 @@ internal_format = _distorm.distorm_format64 SUPPORT_64BIT_OFFSET = True except AttributeError: - try: - internal_decode = _distorm.distorm_decode32 - internal_decompose = _distorm.distorm_decompose32 - internal_format = _distorm.distorm_format32 - except AttributeError: - raise ImportError("Error loading distorm") + internal_decode = _distorm.distorm_decode32 + internal_decompose = _distorm.distorm_decompose32 + internal_format = _distorm.distorm_format32 #============================================================================== # diStorm C interface diff --git a/python/python_module_init.c b/python/python_module_init.c new file mode 100644 index 0000000..51998f6 --- /dev/null +++ b/python/python_module_init.c @@ -0,0 +1,30 @@ +#define PY_SSIZE_T_CLEAN +#include + +#if PY_MAJOR_VERSION == 2 +PyMODINIT_FUNC +init_distorm3(void) +{ + (void) Py_InitModule("_distorm3", NULL); +} +#else +static struct PyModuleDef _distorm3_module = { + PyModuleDef_HEAD_INIT, + "_distorm3", + NULL, + -1, + NULL, +}; + +PyMODINIT_FUNC +PyInit__distorm3(void) +{ + PyObject *m; + + m = PyModule_Create(&_distorm3_module); + if (m == NULL) + return NULL; + + return m; +} +#endif diff --git a/setup.cfg b/setup.cfg index c026412..ecf2af7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[wheel] -universal = 1 - [install] force=1 compile=1 diff --git a/setup.py b/setup.py index bea7857..89d6584 100755 --- a/setup.py +++ b/setup.py @@ -7,251 +7,11 @@ # Licensed under BSD. # -__revision__ = "$Id: setup.py 603 2010-01-31 00:11:05Z qvasimodo $" - -import re import os import os.path -import platform -import string -import shutil -import sys -import subprocess as sp -import struct - from glob import glob -from shutil import ignore_patterns -from setuptools import dist -from distutils import log -from distutils.command.build import build -from distutils.command.build_clib import build_clib -from distutils.command.clean import clean -from setuptools.command.install import install -from distutils.command.install_lib import install_lib -from distutils.command.sdist import sdist -from distutils.core import setup, Extension -from distutils.errors import DistutilsSetupError - -def quick_get_vcvars(): - try: - vswhere = os.fsdecode(sp.check_output(r"echo %ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe", shell=1).strip()) - print("vswhere: {}".format(vswhere)) - installationPath = os.fsdecode(sp.check_output(r"{} -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath".format(vswhere)).strip()) - print("installationPath: {}".format(installationPath)) - vcvars = installationPath + r"\vc\Auxiliary\Build\vcvarsall.bat" - print("vcvars: {}".format(vcvars)) - if os.path.exists(vcvars): - return vcvars - except sp.CalledProcessError: - log.info('quick_get_vcvars() failed to locate vcvarsall.bat path') - -def scanfor_vc_all(): - fname = "vcvarsall.bat" - startDir = "C:\\Program Files (x86)\\Microsoft Visual Studio\\" - print("searching for %s" % fname) - for dirpath, dirnames, filenames in os.walk(startDir): - for f in filenames: - if f == fname: - return os.path.join(dirpath, f) - -def compile_vc(solution_path, config, platform): - match_vs = re.compile('vs(\d+)comntools$', re.I).match - compilers = [ - m.group(1, 0) for m in (match_vs(k) for k in os.environ.keys()) - if m is not None - ] - msbuild = [ - 'msbuild', - '/p:Configuration=%s' % config, - '/p:Platform=%s' % platform, - solution_path - ] - # Try a super-quick method to find vcvarsall.bat - try: - bat = quick_get_vcvars() - if bat is not None: - log.info('Compiling with %s' % bat) - sp.check_call(['call', bat, 'x86_amd64' if platform=='x64' else 'x86', '&&'] + msbuild, shell = True) - return - except sp.CalledProcessError: - log.info('compilation with vswhere failed') - - # Else, proceed with the previous scheme... - for ver, var in sorted(compilers, key = lambda v: -int(v[0])): - bat = os.path.join(os.environ[var], r'..\..\vc\vcvarsall.bat') - try: - log.info('Compiling with %s: %s', var, ' '.join(msbuild)) - sp.check_call(['call', bat, '&&'] + msbuild, shell = True) - return - except sp.CalledProcessError: - log.info('compilation with %s failed', var) - # Try brute force find the batch file for VS env - try: - bat = scanfor_vc_all() - log.info('Compiling with %s' % bat) - sp.check_call(['call', bat, 'x86_amd64' if platform=='x64' else 'x86', '&&'] + msbuild, shell = True) - return - except sp.CalledProcessError: - log.info('compilation failed') - - raise DistutilsSetupError( - 'Failed to compile "%s" with any available compiler' % solution_path - ) - -def get_sources(): - """Returns a list of C source files that should be compiled to - create the libdistorm3 library. - """ - return sorted(glob('src/*.c')) - -class custom_build(build): - """Customized build command""" - def run(self): - log.info('running custom_build') - if 'windows' in platform.system().lower(): - bits = 'win32' # x86 by default - bitsize = 8 * struct.calcsize("P") - if bitsize == 32: - bits = 'win32' - elif bitsize == 64: - bits = 'x64' - # If win32/x64 is specified in command line, change it here - for i in sys.argv: - if i.find("--plat-name=win-amd64") != -1: - bits = 'x64' - if i.find("--plat-name=win32") != -1: - bits = 'win32' - compile_vc('make/win32/distorm.sln', 'dll', bits) - self.copy_file('distorm3.dll', 'python/distorm3') - build.run(self) - -class custom_build_clib(build_clib): - """Customized build_clib command - - This custom_build_clib will create dynamically linked libraries rather - than statically linked libraries. In addition, it places the compiled - libraries alongside the python packages, to facilitate the use of ctypes. - """ - - def finalize_options(self): - # We want build-clib to default to build-lib as defined by the - # "build" command. This is so the compiled library will be put - # in the right place along side the python code. - self.set_undefined_options('build', - ('build_lib', 'build_clib'), - ('build_temp', 'build_temp'), - ('compiler', 'compiler'), - ('debug', 'debug'), - ('force', 'force')) - - self.libraries = self.distribution.libraries - if self.libraries: # In Python 3.0 they have a bug in check_library_list, comment it out then. - self.check_library_list(self.libraries) - - if self.include_dirs is None: - self.include_dirs = self.distribution.include_dirs or [] - if type(self.include_dirs) in (bytes, str): - self.include_dirs = string.split(self.include_dirs, - os.pathsep) - - def get_source_files_for_lib(self, lib_name, build_info): - sources = build_info.get('sources', []) - if hasattr(sources, '__call__'): - sources = sources() - if (sources is None or - type(sources) not in (list, tuple) or - len(sources) == 0): - raise DistutilsSetupError( - "in 'libraries' option (library '%s'), 'sources' must be " - "present and must be a list of source filenames" % lib_name - ) - return sources +from setuptools import Extension, setup - def get_source_files(self): - self.check_library_list(self.libraries) - filenames = [] - for (lib_name, build_info) in self.libraries: - sources = self.get_source_files_for_lib(lib_name, build_info) - filenames.extend(sources) - return filenames - - def run(self): - log.info('running custom_build_clib') - build_clib.run(self) - - def build_libraries(self, libraries): - for (lib_name, build_info) in libraries: - sources = self.get_source_files_for_lib(lib_name, build_info) - sources = list(sources) - - log.info("building '%s' library", lib_name) - - # First, compile the source code to object files in the - # library directory. - macros = build_info.get('macros') - include_dirs = build_info.get('include_dirs') - objects = self.compiler.compile(sources, - output_dir=self.build_temp, - macros=macros, - include_dirs=include_dirs, - extra_postargs=build_info.get('extra_compile_args', []), - debug=self.debug) - - # Then link the object files and put the result in the - # package build directory. - package = build_info.get('package', '') - self.compiler.link_shared_lib( - objects, lib_name, - output_dir=os.path.join(self.build_clib, package), - extra_postargs=build_info.get('extra_link_args', []), - debug=self.debug,) - - -class custom_clean(clean): - """Customized clean command - - Customized clean command removes .pyc files from the project, - as well as build and dist directories.""" - def run(self): - log.info('running custom_clean') - # Remove .pyc files - if hasattr(os, 'walk'): - for root, dirs, files in os.walk('.'): - for f in files: - if f.endswith('.pyc'): - log.info("removing '%s'" % f) - try: - os.unlink(f) - except: - pass - - # Remove generated directories - for dir in ['build', 'dist', 'make/win32/.vs', 'make/win32/dll', 'make/win32/x64', 'python/distorm3.egg-info']: - if os.path.exists(dir): - log.info("removing '%s' (and everything under it)"%dir) - try: - shutil.rmtree(dir, ignore_errors=True) - except: - pass - - clean.run(self) - -class custom_sdist(sdist): - """Customized sdist command""" - def run(self): - log.info('running custom_sdist') - sdist.run(self) - -class BinaryDistribution(dist.Distribution): - def is_pure(self): - return False - def has_ext_modules(self): - return True - -class custom_install(install): - def finalize_options(self): - install.finalize_options(self) - self.install_lib = self.install_platlib def main(): # Just in case we are being called from a different directory @@ -259,44 +19,13 @@ def main(): if cwd: os.chdir(cwd) - # Get the target platform - system = platform.system().lower() - - # Setup the extension module - # Setup the library - ext_modules = None - libraries = None package_data = [] - if 'windows' in system: - package_data = ['distorm3.dll'] - elif 'darwin' in system or 'macosx' in system: - libraries = [( - 'distorm3', dict( - package='distorm3', - sources=get_sources, - include_dirs=['src', 'include'], - extra_compile_args=['-arch', 'i386', '-arch', 'x86_64', '-O2', - '-Wall', '-fPIC', '-DSUPPORT_64BIT_OFFSET', - '-DDISTORM_DYNAMIC']))] - elif 'cygwin' in system: - libraries = [( - 'distorm3', dict( - package='distorm3', - sources=get_sources, - include_dirs=['src', 'include'], - extra_compile_args=['-fPIC', '-O2', '-Wall', - '-DSUPPORT_64BIT_OFFSET', - '-DDISTORM_STATIC']))] - else: - libraries = [( - 'distorm3', dict( - package='distorm3', - sources=get_sources, - include_dirs=['src', 'include'], - extra_link_args=['-Wl,-soname,libdistorm3.so.3'], - extra_compile_args=['-fPIC', '-O2', '-Wall', - '-DSUPPORT_64BIT_OFFSET', - '-DDISTORM_STATIC']))] + distorm_module = Extension( + "_distorm3", + sources=sorted(glob('src/*.c')) + ["python/python_module_init.c"], + include_dirs=['src', 'include'], + define_macros=[('SUPPORT_64BIT_OFFSET', None), ('DISTORM_DYNAMIC', None)], + ) options = { @@ -305,15 +34,8 @@ def main(): 'provides' : ['distorm3'], 'packages' : ['distorm3'], 'package_dir' : { '' : 'python' }, - 'cmdclass' : { 'build' : custom_build, - 'build_clib' : custom_build_clib, - 'clean' : custom_clean, - 'sdist' : custom_sdist, - 'install' : custom_install }, - 'libraries' : libraries, + 'ext_modules' : [distorm_module], 'package_data' : {'distorm3': package_data}, - 'distclass' : BinaryDistribution, - # Metadata 'name' : 'distorm3', 'version' : '3.4.1',