Python: Make cython work with extreme prejudice
diff --git a/bindings/python/.gitignore b/bindings/python/.gitignore
index 0ca6529..61178e6 100644
--- a/bindings/python/.gitignore
+++ b/bindings/python/.gitignore
@@ -3,3 +3,7 @@
 src/
 capstone/lib
 capstone/include
+pyx/lib
+pyx/include
+pyx/*.c
+pyx/*.pyx
diff --git a/bindings/python/BUILDING.txt b/bindings/python/BUILDING.txt
index 157296d..dfbc9f2 100644
--- a/bindings/python/BUILDING.txt
+++ b/bindings/python/BUILDING.txt
@@ -11,6 +11,8 @@
 
 		$ sudo make install3
 
+   To control the install destination, set the DESTDIR environment variable.
+
 2. For better Python performance, install cython-based binding with:
 
 		$ sudo make install_cython
diff --git a/bindings/python/Makefile b/bindings/python/Makefile
index e3a559c..e10cbc8 100644
--- a/bindings/python/Makefile
+++ b/bindings/python/Makefile
@@ -1,14 +1,10 @@
-PYX_BUILDDIR=build_pyx
-
-.PHONY: gen_const install install3 install_cython clean
+.PHONY: gen_const install install3 install_cython sdist sdist3 bdist bdist3 clean check
 
 gen_const:
 	cd .. && python const_generator.py python
 
 install:
 	rm -rf src/
-	rm -rf prebuilt/win64/capstone.dll
-	rm -rf prebuilt/win32/capstone.dll
 	if test -n "${DESTDIR}"; then \
 		python setup.py build install --root="${DESTDIR}"; \
 	else \
@@ -17,8 +13,6 @@
 
 install3:
 	rm -rf src/
-	rm -rf prebuilt/win64/capstone.dll
-	rm -rf prebuilt/win32/capstone.dll
 	if test -n "${DESTDIR}"; then \
 		python3 setup.py build install --root="${DESTDIR}"; \
 	else \
@@ -27,72 +21,47 @@
 
 # NOTE: Newer cython can be installed by: sudo pip install --upgrade cython
 install_cython:
-	rm -rf src/ dist/
-	rm -rf prebuilt/win64/capstone.dll
-	rm -rf prebuilt/win32/capstone.dll
-	mkdir -p $(PYX_BUILDDIR)/pyx
-	cp setup_cython.py $(PYX_BUILDDIR)
-	cp pyx/ccapstone* $(PYX_BUILDDIR)/pyx/
-	cp capstone/__init__.py $(PYX_BUILDDIR)/pyx/__init__.py
-	cp capstone/arm.py $(PYX_BUILDDIR)/pyx/arm.pyx
-	cp capstone/arm_const.py $(PYX_BUILDDIR)/pyx/arm_const.pyx
-	cp capstone/arm64.py $(PYX_BUILDDIR)/pyx/arm64.pyx
-	cp capstone/arm64_const.py $(PYX_BUILDDIR)/pyx/arm64_const.pyx
-	cp capstone/mips.py $(PYX_BUILDDIR)/pyx/mips.pyx
-	cp capstone/mips_const.py $(PYX_BUILDDIR)/pyx/mips_const.pyx
-	cp capstone/ppc.py $(PYX_BUILDDIR)/pyx/ppc.pyx
-	cp capstone/ppc_const.py $(PYX_BUILDDIR)/pyx/ppc_const.pyx
-	cp capstone/sparc.py $(PYX_BUILDDIR)/pyx/sparc.pyx
-	cp capstone/sparc_const.py $(PYX_BUILDDIR)/pyx/sparc_const.pyx
-	cp capstone/systemz.py $(PYX_BUILDDIR)/pyx/systemz.pyx
-	cp capstone/sysz_const.py $(PYX_BUILDDIR)/pyx/sysz_const.pyx
-	cp capstone/x86.py $(PYX_BUILDDIR)/pyx/x86.pyx
-	cp capstone/x86_const.py $(PYX_BUILDDIR)/pyx/x86_const.pyx
-	cp capstone/xcore.py $(PYX_BUILDDIR)/pyx/xcore.pyx
-	cp capstone/xcore_const.py $(PYX_BUILDDIR)/pyx/xcore_const.pyx
-	cd $(PYX_BUILDDIR) && python setup_cython.py build -b ./tmp install --home=$(PYX_BUILDDIR)
-	mv $(PYX_BUILDDIR)/build/lib/python/capstone/* capstone
-	cd $(PYX_BUILDDIR) && python setup_cython.py build -b ./tmp install
+	rm -rf src/
+	if test -n "${DESTDIR}"; then \
+		python setup_cython.py build install --root="${DESTDIR}"; \
+	else \
+		python setup_cython.py build install; \
+	fi
+
+install3_cython:
+	rm -rf src/
+	if test -n "${DESTDIR}"; then \
+		python3 setup_cython.py build install --root="${DESTDIR}"; \
+	else \
+		python3 setup_cython.py build install; \
+	fi
 
 # build & upload PyPi package with source code of the core
 sdist:
 	rm -rf src/ dist/
-	rm -rf prebuilt/win64/capstone.dll
-	rm -rf prebuilt/win32/capstone.dll
-	cp README.pypi-src README
-	cp PKG-INFO.src PKG-INFO
 	python setup.py sdist register upload
 
 # build & upload PyPi package with source code of the core
 sdist3:
 	rm -rf src/ dist/
-	rm -rf prebuilt/win64/capstone.dll
-	rm -rf prebuilt/win32/capstone.dll
-	cp README.pypi-src README
-	cp PKG-INFO.src PKG-INFO
 	python3 setup.py sdist register upload
 
 # build & upload PyPi package with prebuilt core
-# NOTE: be sure to have precompiled core under prebuilt/win*/ beforehand
-sdist_win:
+bdist:
 	rm -rf src/ dist/
-	cp README.pypi-win README
-	cp PKG-INFO.win PKG-INFO
-	python setup.py sdist register upload
+	python setup.py bdist_wheel register upload
 
 # build & upload PyPi package with prebuilt core
-# NOTE: be sure to have precompiled core under prebuilt/win*/ beforehand
-sdist3_win:
+bdist3:
 	rm -rf src/ dist/
-	cp README.pypi-win README
-	cp PKG-INFO.win PKG-INFO
-	python3 setup.py sdist register upload
+	python3 setup.py bdist_wheel register upload
 
 clean:
-	rm -rf $(PYX_BUILDDIR) build/ src/ dist/ *.egg-info README
-	rm -rf capstone/lib capstone/include
-	rm -rf prebuilt/win64/capstone.dll
-	rm -rf prebuilt/win32/capstone.dll
+	rm -rf build/ src/ dist/ *.egg-info
+	rm -rf capstone/lib capstone/include pyx/lib pyx/include
+	rm -f pyx/*.c pyx/__init__.py
+	for f in capstone/*.py; do rm -f pyx/$$(basename $$f)x; done
+	rm -f MANIFEST
 
 
 TESTS = test_basic.py test_detail.py test_arm.py test_arm64.py test_m68k.py test_mips.py
diff --git a/bindings/python/README.txt b/bindings/python/README.txt
index 32a6a6e..fa498ae 100644
--- a/bindings/python/README.txt
+++ b/bindings/python/README.txt
@@ -1,7 +1,7 @@
 To install capstone, you should run `pip install capstone`.
 
 If you would like to build capstone with just the source distribution, without
-pip, just run `python setup.py install` in the same folder as this text file.
+pip, just run `python setup.py install` in the folder with setup.py in it.
 
 In order to use this source distribution, you will need an environment that can
 compile C code. On linux, this is usually easy, but on windows, this involves
diff --git a/bindings/python/setup.py b/bindings/python/setup.py
index 45dbd3e..6ee612a 100755
--- a/bindings/python/setup.py
+++ b/bindings/python/setup.py
@@ -3,7 +3,6 @@
 import glob
 import os
 import shutil
-import stat
 import sys
 import platform
 
@@ -55,18 +54,18 @@
         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"))
+    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, "*.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, "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")))
 
     for filename in src:
         outpath = os.path.join(SRC_DIR, os.path.basename(filename))
@@ -101,15 +100,12 @@
         os.system('cmake -DCMAKE_BUILD_TYPE=RELEASE -DCAPSTONE_BUILD_TESTS=0 -DCAPSTONE_BUILD_STATIC=0 -G "NMake Makefiles" ..')
         os.system("nmake")
     elif SYSTEM == "cygwin":
-        os.chmod("make.sh", stat.S_IREAD|stat.S_IEXEC)
-        if is_64bits:
-            os.system("CAPSTONE_BUILD_CORE_ONLY=yes ./make.sh cygwin-mingw64")
+        if IS_64BITS:
+            os.system("CAPSTONE_BUILD_CORE_ONLY=yes bash ./make.sh cygwin-mingw64")
         else:
-            os.system("CAPSTONE_BUILD_CORE_ONLY=yes ./make.sh cygwin-mingw32")
-
-        so = "capstone.dll"
+            os.system("CAPSTONE_BUILD_CORE_ONLY=yes bash ./make.sh cygwin-mingw32")
     else:   # Unix
-        os.system("CAPSTONE_BUILD_CORE_ONLY=yes ./make.sh")
+        os.system("CAPSTONE_BUILD_CORE_ONLY=yes bash ./make.sh")
 
     shutil.copy(LIBRARY_FILE, LIBS_DIR)
     if STATIC_LIBRARY_FILE: shutil.copy(STATIC_LIBRARY_FILE, LIBS_DIR)
@@ -153,7 +149,7 @@
 
     cmdclass['develop'] = custom_develop
 except ImportError:
-    print "Proper 'develop' support unavailable."
+    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
diff --git a/bindings/python/setup_cython.py b/bindings/python/setup_cython.py
index 585aea5..bf32dfe 100644
--- a/bindings/python/setup_cython.py
+++ b/bindings/python/setup_cython.py
@@ -1,33 +1,113 @@
 from distutils.core import setup
 from distutils.extension import Extension
-from distutils.command.install_lib import install_lib as _install
+from distutils.command.build import build
 from Cython.Distutils import build_ext
 
 VERSION = '4.0'
 
-compile_args = ['-O3', '-fomit-frame-pointer']
+SYSTEM = sys.platform
+VERSION = '3.0.4'
 
-ext_modules = [
-    Extension("capstone.ccapstone", ["pyx/ccapstone.pyx"], libraries=["capstone"], extra_compile_args=compile_args),
-    Extension("capstone.arm", ["pyx/arm.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.arm_const", ["pyx/arm_const.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.arm64", ["pyx/arm64.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.arm64_const", ["pyx/arm64_const.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.m68k", ["pyx/m68k.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.m68k_const", ["pyx/m68k_const.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.mips", ["pyx/mips.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.mips_const", ["pyx/mips_const.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.ppc", ["pyx/ppc.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.ppc_const", ["pyx/ppc_const.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.x86", ["pyx/x86.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.x86_const", ["pyx/x86_const.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.sparc", ["pyx/sparc.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.sparc_const", ["pyx/sparc_const.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.systemz", ["pyx/systemz.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.sysz_const", ["pyx/sysz_const.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.xcore", ["pyx/xcore.pyx"], extra_compile_args=compile_args),
-    Extension("capstone.xcore_const", ["pyx/xcore_const.pyx"], extra_compile_args=compile_args)
-]
+# 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, 'pyx', 'lib')
+HEADERS_DIR = os.path.join(ROOT_DIR, 'pyx', '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, '../..')
+PYPACKAGE_DIR = os.path.join(ROOT_DIR, 'capstone')
+CYPACKAGE_DIR = os.path.join(ROOT_DIR, 'pyx')
+
+if SYSTEM == 'darwin':
+    LIBRARY_FILE = "libcapstone.dylib"
+    STATIC_LIBRARY_FILE = 'libcapstone.a'
+elif SYSTEM in ('win32', 'cygwin'):
+    LIBRARY_FILE = "capstone.dll"
+    STATIC_LIBRARY_FILE = None
+else:
+    LIBRARY_FILE = "libcapstone.so"
+    STATIC_LIBRARY_FILE = 'libcapstone.a'
+
+compile_args = ['-O3', '-fomit-frame-pointer', '-I' + HEADERS_DIR]
+link_args = ['-L' + LIBS_DIR]
+
+ext_module_names = ['arm', 'arm_const', 'arm64', 'arm64_const', 'mips', 'mips_const', 'ppc', 'ppc_const', 'x86', 'x86_const', 'sparc', 'sparc_const', 'systemz', 'sysz_const', 'xcore', 'xcore_const']
+ext_modules = [Extension("capstone.ccapstone",
+                         ["pyx/ccapstone.pyx"],
+                         libraries=["capstone"],
+                         extra_compile_args=compile_args,
+                         extra_link_args=link_args)]
+ext_modules += [Extension("capstone.%s" % name,
+                          ["pyx/%s.pyx" % name],
+                          extra_compile_args=compile_args,
+                          extra_link_args=link_args)
+                for name in ext_module_names]
+
+def clean_bins():
+    shutil.rmtree(LIBS_DIR, ignore_errors=True)
+    shutil.rmtree(HEADERS_DIR, ignore_errors=True)
+
+def copy_pysources():
+    for fname in os.listdir(PYPACKAGE_DIR):
+        if not fname.endswith('.py'):
+            continue
+
+        if fname == '__init__.py':
+            shutil.copy(os.path.join(PYPACKAGE_DIR, fname), os.path.join(CYPACKAGE_DIR, fname))
+        else:
+            shutil.copy(os.path.join(PYPACKAGE_DIR, fname), os.path.join(CYPACKAGE_DIR, fname + 'x'))
+
+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'), os.path.join(HEADERS_DIR, 'capstone'))
+
+    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")
+    elif SYSTEM == "cygwin":
+        if IS_64BITS:
+            os.system("CAPSTONE_BUILD_CORE_ONLY=yes bash ./make.sh cygwin-mingw64")
+        else:
+            os.system("CAPSTONE_BUILD_CORE_ONLY=yes bash ./make.sh cygwin-mingw32")
+
+    else:   # Unix
+        os.system("CAPSTONE_BUILD_CORE_ONLY=yes bash ./make.sh")
+
+    shutil.copy(LIBRARY_FILE, LIBS_DIR)
+    if STATIC_LIBRARY_FILE: shutil.copy(STATIC_LIBRARY_FILE, LIBS_DIR)
+    os.chdir(cwd)
+
+
+class custom_build(build):
+    def run(self):
+        log.info('Copying python sources')
+        copy_pysources()
+        log.info('Building C extensions')
+        build_libraries()
+        return build.run(self)
 
 # clean package directory first
 #import os.path, shutil, sys
@@ -46,7 +126,7 @@
     packages     = ['capstone'],
     name         = 'capstone',
     version      = VERSION,
-    cmdclass     = {'build_ext': build_ext},
+    cmdclass     = {'build_ext': build_ext, 'build': custom_build},
     ext_modules  = ext_modules,
     author       = 'Nguyen Anh Quynh',
     author_email = 'aquynh@gmail.com',
@@ -56,4 +136,8 @@
                 'License :: OSI Approved :: BSD License',
                 'Programming Language :: Python :: 2',
                 ],
+    include_package_data=True,
+    package_data={
+        "capstone": ["lib/*", "include/capstone/*"],
+    }
 )