Home | History | Annotate | Download | only in python
      1 #!/usr/bin/env python
      2 
      3 import glob
      4 import os
      5 import shutil
      6 import sys
      7 import platform
      8 
      9 from distutils import log
     10 from setuptools import setup
     11 from distutils.util import get_platform
     12 from distutils.command.build import build
     13 from distutils.command.sdist import sdist
     14 from setuptools.command.bdist_egg import bdist_egg
     15 
     16 SYSTEM = sys.platform
     17 
     18 # adapted from commit e504b81 of Nguyen Tan Cong
     19 # Reference: https://docs.python.org/2/library/platform.html#cross-platform
     20 IS_64BITS = sys.maxsize > 2**32
     21 
     22 # are we building from the repository or from a source distribution?
     23 ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
     24 LIBS_DIR = os.path.join(ROOT_DIR, 'capstone', 'lib')
     25 HEADERS_DIR = os.path.join(ROOT_DIR, 'capstone', 'include')
     26 SRC_DIR = os.path.join(ROOT_DIR, 'src')
     27 BUILD_DIR = SRC_DIR if os.path.exists(SRC_DIR) else os.path.join(ROOT_DIR, '../..')
     28 
     29 # Parse version from pkgconfig.mk
     30 VERSION_DATA = {}
     31 with open(os.path.join(BUILD_DIR, 'pkgconfig.mk')) as fp:
     32     lines = fp.readlines()
     33     for line in lines:
     34         line = line.strip()
     35         if len(line) == 0:
     36             continue
     37         if line.startswith('#'):
     38             continue
     39         if '=' not in line:
     40             continue
     41 
     42         k, v = line.split('=', 1)
     43         k = k.strip()
     44         v = v.strip()
     45         if len(k) == 0 or len(v) == 0:
     46             continue
     47         VERSION_DATA[k] = v
     48 
     49 if 'PKG_MAJOR' not in VERSION_DATA or \
     50         'PKG_MINOR' not in VERSION_DATA or \
     51         'PKG_EXTRA' not in VERSION_DATA:
     52     raise Exception("Malformed pkgconfig.mk")
     53 
     54 if 'PKG_TAG' in VERSION_DATA:
     55     VERSION = '{PKG_MAJOR}.{PKG_MINOR}.{PKG_EXTRA}.{PKG_TAG}'.format(**VERSION_DATA)
     56 else:
     57     VERSION = '{PKG_MAJOR}.{PKG_MINOR}.{PKG_EXTRA}'.format(**VERSION_DATA)
     58 
     59 if SYSTEM == 'darwin':
     60     LIBRARY_FILE = "libcapstone.dylib"
     61     STATIC_LIBRARY_FILE = 'libcapstone.a'
     62 elif SYSTEM in ('win32', 'cygwin'):
     63     LIBRARY_FILE = "capstone.dll"
     64     STATIC_LIBRARY_FILE = None
     65 else:
     66     LIBRARY_FILE = "libcapstone.so"
     67     STATIC_LIBRARY_FILE = 'libcapstone.a'
     68 
     69 def clean_bins():
     70     shutil.rmtree(LIBS_DIR, ignore_errors=True)
     71     shutil.rmtree(HEADERS_DIR, ignore_errors=True)
     72 
     73 def copy_sources():
     74     """Copy the C sources into the source directory.
     75     This rearranges the source files under the python distribution
     76     directory.
     77     """
     78     src = []
     79 
     80     try:
     81         shutil.rmtree("src/")
     82     except (IOError, OSError):
     83         pass
     84 
     85     shutil.copytree(os.path.join(BUILD_DIR, "arch"), os.path.join(SRC_DIR, "arch"))
     86     shutil.copytree(os.path.join(BUILD_DIR, "include"), os.path.join(SRC_DIR, "include"))
     87 
     88     src.extend(glob.glob(os.path.join(BUILD_DIR, "*.[ch]")))
     89     src.extend(glob.glob(os.path.join(BUILD_DIR, "*.mk")))
     90 
     91     src.extend(glob.glob(os.path.join(BUILD_DIR, "Makefile")))
     92     src.extend(glob.glob(os.path.join(BUILD_DIR, "LICENSE*")))
     93     src.extend(glob.glob(os.path.join(BUILD_DIR, "README")))
     94     src.extend(glob.glob(os.path.join(BUILD_DIR, "*.TXT")))
     95     src.extend(glob.glob(os.path.join(BUILD_DIR, "RELEASE_NOTES")))
     96     src.extend(glob.glob(os.path.join(BUILD_DIR, "make.sh")))
     97     src.extend(glob.glob(os.path.join(BUILD_DIR, "CMakeLists.txt")))
     98     src.extend(glob.glob(os.path.join(BUILD_DIR, "pkgconfig.mk")))
     99 
    100     for filename in src:
    101         outpath = os.path.join(SRC_DIR, os.path.basename(filename))
    102         log.info("%s -> %s" % (filename, outpath))
    103         shutil.copy(filename, outpath)
    104 
    105 def build_libraries():
    106     """
    107     Prepare the capstone directory for a binary distribution or installation.
    108     Builds shared libraries and copies header files.
    109 
    110     Will use a src/ dir if one exists in the current directory, otherwise assumes it's in the repo
    111     """
    112     cwd = os.getcwd()
    113     clean_bins()
    114     os.mkdir(HEADERS_DIR)
    115     os.mkdir(LIBS_DIR)
    116 
    117     # copy public headers
    118     shutil.copytree(os.path.join(BUILD_DIR, 'include'), os.path.join(HEADERS_DIR, 'capstone'))
    119 
    120     # if prebuilt libraries are available, use those and cancel build
    121     if os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE)) and \
    122             (not STATIC_LIBRARY_FILE or os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', STATIC_LIBRARY_FILE))):
    123         shutil.copy(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE), LIBS_DIR)
    124         if STATIC_LIBRARY_FILE is not None:
    125             shutil.copy(os.path.join(ROOT_DIR, 'prebuilt', STATIC_LIBRARY_FILE), LIBS_DIR)
    126         return
    127 
    128     os.chdir(BUILD_DIR)
    129 
    130     # platform description refers at https://docs.python.org/2/library/sys.html#sys.platform
    131     if SYSTEM == "win32":
    132         # Windows build: this process requires few things:
    133         #    - CMake + MSVC installed
    134         #    - Run this command in an environment setup for MSVC
    135         if not os.path.exists("build"): os.mkdir("build")
    136         os.chdir("build")
    137         # Do not build tests & static library
    138         os.system('cmake -DCMAKE_BUILD_TYPE=RELEASE -DCAPSTONE_BUILD_TESTS=0 -DCAPSTONE_BUILD_STATIC=0 -G "NMake Makefiles" ..')
    139         os.system("nmake")
    140     else:   # Unix incl. cygwin
    141         os.system("CAPSTONE_BUILD_CORE_ONLY=yes bash ./make.sh")
    142 
    143     shutil.copy(LIBRARY_FILE, LIBS_DIR)
    144     if STATIC_LIBRARY_FILE: shutil.copy(STATIC_LIBRARY_FILE, LIBS_DIR)
    145     os.chdir(cwd)
    146 
    147 
    148 class custom_sdist(sdist):
    149     def run(self):
    150         clean_bins()
    151         copy_sources()
    152         return sdist.run(self)
    153 
    154 
    155 class custom_build(build):
    156     def run(self):
    157         log.info('Building C extensions')
    158         build_libraries()
    159         return build.run(self)
    160 
    161 
    162 class custom_bdist_egg(bdist_egg):
    163     def run(self):
    164         self.run_command('build')
    165         return bdist_egg.run(self)
    166 
    167 def dummy_src():
    168     return []
    169 
    170 cmdclass = {}
    171 cmdclass['build'] = custom_build
    172 cmdclass['sdist'] = custom_sdist
    173 cmdclass['bdist_egg'] = custom_bdist_egg
    174 
    175 try:
    176     from setuptools.command.develop import develop
    177     class custom_develop(develop):
    178         def run(self):
    179             log.info("Building C extensions")
    180             build_libraries()
    181             return develop.run(self)
    182 
    183     cmdclass['develop'] = custom_develop
    184 except ImportError:
    185     print("Proper 'develop' support unavailable.")
    186 
    187 if 'bdist_wheel' in sys.argv and '--plat-name' not in sys.argv:
    188     idx = sys.argv.index('bdist_wheel') + 1
    189     sys.argv.insert(idx, '--plat-name')
    190     name = get_platform()
    191     if 'linux' in name:
    192         # linux_* platform tags are disallowed because the python ecosystem is fubar
    193         # linux builds should be built in the centos 5 vm for maximum compatibility
    194         # see https://github.com/pypa/manylinux
    195         # see also https://github.com/angr/angr-dev/blob/master/bdist.sh
    196         sys.argv.insert(idx + 1, 'manylinux1_' + platform.machine())
    197     else:
    198         # https://www.python.org/dev/peps/pep-0425/
    199         sys.argv.insert(idx + 1, name.replace('.', '_').replace('-', '_'))
    200 
    201 setup(
    202     provides=['capstone'],
    203     packages=['capstone'],
    204     name='capstone',
    205     version=VERSION,
    206     author='Nguyen Anh Quynh',
    207     author_email='aquynh (at] gmail.com',
    208     description='Capstone disassembly engine',
    209     url='http://www.capstone-engine.org',
    210     classifiers=[
    211         'License :: OSI Approved :: BSD License',
    212         'Programming Language :: Python :: 2',
    213         'Programming Language :: Python :: 3',
    214     ],
    215     requires=['ctypes'],
    216     cmdclass=cmdclass,
    217     zip_safe=True,
    218     include_package_data=True,
    219     package_data={
    220         "capstone": ["lib/*", "include/capstone/*"],
    221     }
    222 )
    223