#!/usr/bin/env python import glob import os import shutil import sys import platform from distutils import log from setuptools import setup from distutils.util import get_platform from distutils.command.build import build from distutils.command.sdist import sdist from setuptools.command.bdist_egg import bdist_egg SYSTEM = sys.platform # adapted from commit e504b81 of Nguyen Tan Cong # Reference: https://docs.python.org/2/library/platform.html#cross-platform IS_64BITS = sys.maxsize > 2**32 # 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, 'capstone', 'lib') HEADERS_DIR = os.path.join(ROOT_DIR, 'capstone', 'include') SRC_DIR = os.path.join(ROOT_DIR, 'src') BUILD_DIR = SRC_DIR if os.path.exists(SRC_DIR) else os.path.join(ROOT_DIR, '../..') # Parse version from pkgconfig.mk VERSION_DATA = {} with open(os.path.join(BUILD_DIR, 'pkgconfig.mk')) as fp: lines = fp.readlines() for line in lines: line = line.strip() if len(line) == 0: continue if line.startswith('#'): continue if '=' not in line: continue k, v = line.split('=', 1) k = k.strip() v = v.strip() if len(k) == 0 or len(v) == 0: continue VERSION_DATA[k] = v if 'PKG_MAJOR' not in VERSION_DATA or \ 'PKG_MINOR' not in VERSION_DATA or \ 'PKG_EXTRA' not in VERSION_DATA: raise Exception("Malformed pkgconfig.mk") if 'PKG_TAG' in VERSION_DATA: VERSION = '{PKG_MAJOR}.{PKG_MINOR}.{PKG_EXTRA}.{PKG_TAG}'.format(**VERSION_DATA) else: VERSION = '{PKG_MAJOR}.{PKG_MINOR}.{PKG_EXTRA}'.format(**VERSION_DATA) if SYSTEM == 'darwin': VERSIONED_LIBRARY_FILE = "libcapstone.{PKG_MAJOR}.dylib".format(**VERSION_DATA) LIBRARY_FILE = "libcapstone.dylib" STATIC_LIBRARY_FILE = 'libcapstone.a' elif SYSTEM in ('win32', 'cygwin'): VERSIONED_LIBRARY_FILE = "capstone.dll" LIBRARY_FILE = "capstone.dll" STATIC_LIBRARY_FILE = None else: VERSIONED_LIBRARY_FILE = "libcapstone.so.{PKG_MAJOR}".format(**VERSION_DATA) LIBRARY_FILE = "libcapstone.so" STATIC_LIBRARY_FILE = 'libcapstone.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. This rearranges the source files under the python distribution directory. """ src = [] try: shutil.rmtree("src/") except (IOError, OSError): pass shutil.copytree(os.path.join(BUILD_DIR, "arch"), os.path.join(SRC_DIR, "arch")) shutil.copytree(os.path.join(BUILD_DIR, "include"), os.path.join(SRC_DIR, "include")) src.extend(glob.glob(os.path.join(BUILD_DIR, "*.[ch]"))) src.extend(glob.glob(os.path.join(BUILD_DIR, "*.mk"))) src.extend(glob.glob(os.path.join(BUILD_DIR, "Makefile"))) src.extend(glob.glob(os.path.join(BUILD_DIR, "LICENSE*"))) src.extend(glob.glob(os.path.join(BUILD_DIR, "README"))) src.extend(glob.glob(os.path.join(BUILD_DIR, "*.TXT"))) src.extend(glob.glob(os.path.join(BUILD_DIR, "RELEASE_NOTES"))) src.extend(glob.glob(os.path.join(BUILD_DIR, "make.sh"))) src.extend(glob.glob(os.path.join(BUILD_DIR, "CMakeLists.txt"))) src.extend(glob.glob(os.path.join(BUILD_DIR, "pkgconfig.mk"))) for filename in src: outpath = os.path.join(SRC_DIR, os.path.basename(filename)) log.info("%s -> %s" % (filename, outpath)) shutil.copy(filename, outpath) def build_libraries(): """ Prepare the capstone directory for a binary distribution or installation. Builds shared libraries and copies header files. 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) # copy public headers shutil.copytree(os.path.join(BUILD_DIR, 'include', 'capstone'), os.path.join(HEADERS_DIR, 'capstone')) # if prebuilt libraries are available, use those and cancel build if os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE)) and \ (not STATIC_LIBRARY_FILE or os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', STATIC_LIBRARY_FILE))): shutil.copy(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE), LIBS_DIR) if STATIC_LIBRARY_FILE is not None: shutil.copy(os.path.join(ROOT_DIR, 'prebuilt', STATIC_LIBRARY_FILE), LIBS_DIR) return os.chdir(BUILD_DIR) # platform description refers at https://docs.python.org/2/library/sys.html#sys.platform if SYSTEM == "win32": # Windows build: this process requires few things: # - CMake + MSVC installed # - Run this command in an environment setup for MSVC if not os.path.exists("build"): os.mkdir("build") os.chdir("build") # Do not build tests & static library os.system('cmake -DCMAKE_BUILD_TYPE=RELEASE -DCAPSTONE_BUILD_TESTS=0 -DCAPSTONE_BUILD_STATIC=0 -G "NMake Makefiles" ..') os.system("nmake") else: # Unix incl. cygwin os.system("CAPSTONE_BUILD_CORE_ONLY=yes bash ./make.sh") shutil.copy(VERSIONED_LIBRARY_FILE, os.path.join(LIBS_DIR, LIBRARY_FILE)) # only copy static library if it exists (it's a build option) if STATIC_LIBRARY_FILE and os.path.exists(STATIC_LIBRARY_FILE): shutil.copy(STATIC_LIBRARY_FILE, LIBS_DIR) os.chdir(cwd) class custom_sdist(sdist): def run(self): clean_bins() copy_sources() return sdist.run(self) class custom_build(build): def run(self): if 'LIBCAPSTONE_PATH' in os.environ: log.info('Skipping building C extensions since LIBCAPSTONE_PATH is set') else: log.info('Building C extensions') build_libraries() return build.run(self) class custom_bdist_egg(bdist_egg): def run(self): self.run_command('build') return bdist_egg.run(self) def dummy_src(): return [] cmdclass = {} cmdclass['build'] = custom_build cmdclass['sdist'] = custom_sdist cmdclass['bdist_egg'] = custom_bdist_egg try: from setuptools.command.develop import develop class custom_develop(develop): def run(self): log.info("Building C extensions") build_libraries() return develop.run(self) cmdclass['develop'] = custom_develop except ImportError: print("Proper 'develop' support unavailable.") 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()) else: # https://www.python.org/dev/peps/pep-0425/ sys.argv.insert(idx + 1, name.replace('.', '_').replace('-', '_')) long_desc = ''' Capstone is a disassembly framework with the target of becoming the ultimate disasm engine for binary analysis and reversing in the security community. Created by Nguyen Anh Quynh, then developed and maintained by a small community, Capstone offers some unparalleled features: - Support multiple hardware architectures: ARM, ARM64 (ARMv8), Mips, PPC, Sparc, SystemZ, XCore and X86 (including X86_64). - Having clean/simple/lightweight/intuitive architecture-neutral API. - Provide details on disassembled instruction (called "decomposer" by others). - Provide semantics of the disassembled instruction, such as list of implicit registers read & written. - Implemented in pure C language, with lightweight wrappers for C++, C#, Go, Java, NodeJS, Ocaml, Python, Ruby & Vala ready (available in main code, or provided externally by the community). - Native support for all popular platforms: Windows, Mac OSX, iOS, Android, Linux, *BSD, Solaris, etc. - Thread-safe by design. - Special support for embedding into firmware or OS kernel. - High performance & suitable for malware analysis (capable of handling various X86 malware tricks). - Distributed under the open source BSD license. Further information is available at http://www.capstone-engine.org [License] This project is released under the BSD license. If you redistribute the binary or source code of Capstone, please attach file LICENSE.TXT with your products. ''' setup( provides=['capstone'], packages=['capstone'], name='capstone', version=VERSION, author='Nguyen Anh Quynh', author_email='aquynh@gmail.com', description='Capstone disassembly engine', long_description=long_desc, long_description_content_type="text/markdown", url='https://www.capstone-engine.org', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Topic :: Software Development :: Build Tools', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', ], requires=['ctypes'], cmdclass=cmdclass, zip_safe=True, include_package_data=True, is_pure=False, package_data={ "capstone": ["lib/*", "include/capstone/*"], } )