diff --git a/.travis.yml b/.travis.yml index a5eee75..9f240ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ python: - "3.7" - "3.8" env: - - MC_VERSION=1.16.1 + - MC_VERSION=1.16.1 PIL_INCLUDE_DIR=. before_install: - wget https://raw.githubusercontent.com/python-pillow/Pillow/master/src/libImaging/Imaging.h - wget https://raw.githubusercontent.com/python-pillow/Pillow/master/src/libImaging/ImagingUtils.h diff --git a/setup.py b/setup.py index 19a2934..65c3c8e 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import sys -import traceback # quick version check @@ -10,22 +9,25 @@ if sys.version_info[0] == 2 or (sys.version_info[0] == 3 and sys.version_info[1] sys.exit(1) -from distutils.core import setup -from distutils.extension import Extension -from distutils.command.build import build +from setuptools import setup +from setuptools.extension import Extension from distutils.command.clean import clean -from distutils.command.build_ext import build_ext +from distutils.command.build import build +from setuptools.command.build_ext import build_ext from distutils.command.sdist import sdist from distutils.cmd import Command from distutils.dir_util import remove_tree from distutils.sysconfig import get_python_inc from distutils import log -import os, os.path + +import os +import os.path import glob import platform import time -import overviewer_core.util as util -import numpy +import traceback + +from overviewer_core import util try: import py2exe @@ -34,10 +36,17 @@ except ImportError: try: import py2app - from setuptools.extension import Extension except ImportError: py2app = None + +BUILD_ERROR = """ +Failed to build Overviewer! +Please review the errors printed above and the build instructions at +. If you are still having +build problems, file an incident on the github tracker or find us in IRC. +""" + # make sure our current working directory is the same directory # setup.py is in curdir = os.path.split(sys.argv[0])[0] @@ -46,36 +55,43 @@ if curdir: # now, setup the keyword arguments for setup # (because we don't know until runtime if py2exe/py2app is available) -setup_kwargs = {} -setup_kwargs['ext_modules'] = [] -setup_kwargs['cmdclass'] = {} -setup_kwargs['options'] = {} +setup_kwargs = dict({ + 'name': 'Minecraft-Overviewer', + 'url': 'http://overviewer.org/', + 'version': util.findGitVersion(), + 'author': 'Andrew Brown', + 'author_email': 'brownan@gmail.com', + 'description': 'Generates large resolution images of a Minecraft map.', + 'long_description': open('README.rst', 'r').read(), + 'license': open('COPYING.txt', 'r').read(), + + 'ext_modules': [], + 'cmdclass': {}, + 'options': {}, + 'install_requires': ['numpy', 'pillow'] +}) # # metadata # -# Utility function to read the README file. -# Used for the long_description. It's nice, because now 1) we have a top level -# README file and 2) it's easier to type in the README file than to put a raw -# string in below ... -def read(fname): - return open(fname).read() - -setup_kwargs['name'] = 'Minecraft-Overviewer' -setup_kwargs['version'] = util.findGitVersion() -setup_kwargs['description'] = 'Generates large resolution images of a Minecraft map.' -setup_kwargs['url'] = 'http://overviewer.org/' -setup_kwargs['author'] = 'Andrew Brown' -setup_kwargs['author_email'] = 'brownan@gmail.com' -setup_kwargs['license'] = 'GNU General Public License v3' -setup_kwargs['long_description'] = read('README.rst') # top-level files that should be included as documentation -doc_files = ['COPYING.txt', 'README.rst', 'CONTRIBUTORS.rst', 'sample_config.py'] +doc_files = ['COPYING.txt', + 'README.rst', + 'CONTRIBUTORS.rst', + 'sample_config.py'] -# helper to create a 'data_files'-type sequence recursively for a given dir -def recursive_data_files(src, dest=None): + +def data_files(src, dest=None): + """helper to create a 'data_files'-type sequence recursively for a given + dir + + :param src: directory to include files from + :param dest: + :return: list + + """ if dest is None: dest = src @@ -92,15 +108,22 @@ def recursive_data_files(src, dest=None): ret.append((current_dest, current_sources)) return ret -# helper to create a 'package_data'-type sequence recursively for a given dir -def recursive_package_data(src, package_dir='overviewer_core'): + +def package_data(src, package_dir='overviewer_core'): + """Helper to create a 'package_data'-type sequence recursively for a given + dir + + :param src: source directory + :param package_dir: package directory + :return: list + + """ full_src = os.path.join(package_dir, src) ret = [] for dirpath, dirnames, filenames in os.walk(full_src, topdown=False): current_path = os.path.relpath(dirpath, package_dir) for filename in filenames: ret.append(os.path.join(current_path, filename)) - return ret # @@ -108,23 +131,30 @@ def recursive_package_data(src, package_dir='overviewer_core'): # if py2exe is not None: - setup_kwargs['comments'] = "http://overviewer.org" + setup_kwargs['comments'] = 'http://overviewer.org' # py2exe likes a very particular type of version number: - setup_kwargs['version'] = util.findGitVersion().replace("-",".") + setup_kwargs['version'] = setup_kwargs['version'].replace('-', '.') setup_kwargs['console'] = ['overviewer.py', 'contribManager.py'] setup_kwargs['data_files'] = [('', doc_files)] - setup_kwargs['data_files'] += recursive_data_files('overviewer_core/data/textures', 'textures') - setup_kwargs['data_files'] += recursive_data_files('overviewer_core/data/web_assets', 'web_assets') - setup_kwargs['data_files'] += recursive_data_files('overviewer_core/data/js_src', 'js_src') - setup_kwargs['data_files'] += recursive_data_files('contrib', 'contrib') + setup_kwargs['data_files'] += data_files('overviewer_core/data/textures', + 'textures') + setup_kwargs['data_files'] += data_files('overviewer_core/data/web_assets', + 'web_assets') + setup_kwargs['data_files'] += data_files('overviewer_core/data/js_src', + 'js_src') + setup_kwargs['data_files'] += data_files('contrib', 'contrib') setup_kwargs['zipfile'] = None if platform.system() == 'Windows' and '64bit' in platform.architecture(): b = 3 else: b = 1 - setup_kwargs['options']['py2exe'] = {'bundle_files' : b, 'excludes': 'Tkinter', 'includes': - ['fileinput', 'overviewer_core.items', 'overviewer_core.aux_files.genPOI']} + setup_kwargs['options']['py2exe'] = \ + {'bundle_files': b, + 'excludes': 'Tkinter', + 'includes': ['fileinput', + 'overviewer_core.items', + 'overviewer_core.aux_files.genPOI']} # # py2app options @@ -132,7 +162,7 @@ if py2exe is not None: if py2app is not None: setup_kwargs['app'] = ['overviewer.py'] - setup_kwargs['options']['py2app'] = {'argv_emulation' : False} + setup_kwargs['options']['py2app'] = {'argv_emulation': False} setup_kwargs['setup_requires'] = ['py2app'] # @@ -141,36 +171,60 @@ if py2app is not None: setup_kwargs['packages'] = ['overviewer_core', 'overviewer_core/aux_files'] setup_kwargs['scripts'] = ['overviewer.py'] -setup_kwargs['package_data'] = {'overviewer_core': recursive_package_data('data/textures') + recursive_package_data('data/web_assets') + recursive_package_data('data/js_src')} +setup_kwargs['package_data'] = \ + {'overviewer_core': (package_data('data/textures') + + package_data('data/web_assets') + + package_data('data/js_src'))} if py2exe is None: - setup_kwargs['data_files'] = [('share/doc/minecraft-overviewer', doc_files)] + setup_kwargs['data_files'] = [('share/doc/minecraft-overviewer', + doc_files)] -# -# c_overviewer extension -# +def get_numpy_include(): + """Obtain the numpy include directory. This logic works across numpy + versions + + :rtype: list + + """ + print(sys.path) + try: + import numpy + except ImportError: + if sys.argv[1] != 'develop': + print('numpy not found, please run "python setup.py develop"') + sys.exit(1) + return [] + try: + return numpy.get_include() + except AttributeError: + return numpy.get_numpy_include() # Third-party modules - we depend on numpy for everything -# Obtain the numpy include directory. This logic works across numpy versions. -try: - numpy_include = numpy.get_include() -except AttributeError: - numpy_include = numpy.get_numpy_include() +numpy_include = get_numpy_include() -try: - pil_include = os.environ['PIL_INCLUDE_DIR'].split(os.pathsep) -except Exception: - pil_include = [ os.path.join(get_python_inc(plat_specific=1), 'Imaging') ] - if not os.path.exists(pil_include[0]): - pil_include = [ ] +# Try and find the Imaging path automatically +pil_include = [] +imaging_path = os.environ.get('PIL_INCLUDE_DIR', + os.path.normpath('./Imaging/libImaging')) +if not os.path.exists(imaging_path): + imaging_path = os.path.join(get_python_inc(plat_specific=1), 'Imaging') +if os.path.exists(imaging_path): + pil_include.append(imaging_path) +if not pil_include: + print('ERROR: Could not find Python Imaging library') + print(BUILD_ERROR) + sys.exit(1) + +SRC_PATH = 'overviewer_core/src/' # used to figure out what files to compile # auto-created from files in primitives/, but we need the raw names so # we can use them later. primitives = [] -for name in glob.glob("overviewer_core/src/primitives/*.c"): +for name in glob.glob(SRC_PATH + 'primitives/*.c'): name = os.path.split(name)[-1] name = os.path.splitext(name)[0] primitives.append(name) @@ -190,6 +244,17 @@ setup_kwargs['ext_modules'].append(Extension('overviewer_core.c_overviewer', c_o # (NOT in build/) setup_kwargs['options']['build_ext'] = {'inplace' : 1} +include_dirs = ['.', numpy_include] + pil_include + +setup_kwargs['ext_modules'].append(Extension('overviewer_core.c_overviewer', + c_overviewer_files, + include_dirs=include_dirs, + depends=c_overviewer_includes, + extra_link_args=[])) + +# tell build_ext to build the extension in-place (NOT in build/) +setup_kwargs['options']['build_ext'] = {'inplace': 1} + # custom clean command to remove in-place extension # and the version file, primitives header class CustomClean(clean): @@ -229,49 +294,38 @@ class CustomClean(clean): def generate_version_py(): try: - outstr = "" - outstr += "VERSION=%r\n" % util.findGitVersion() - outstr += "HASH=%r\n" % util.findGitHash() - outstr += "BUILD_DATE=%r\n" % time.asctime() - outstr += "BUILD_PLATFORM=%r\n" % platform.processor() - outstr += "BUILD_OS=%r\n" % platform.platform() - f = open("overviewer_core/overviewer_version.py", "w") - f.write(outstr) - f.close() + with open('overviewer_core/overviewer_version.py', 'w') as f: + f.write('\n'.join(['VERSION=%r' % util.findGitVersion(), + 'HASH=%r' % util.findGitHash(), + 'BUILD_DATE=%r' % time.asctime(), + 'BUILD_PLATFORM=%r' % platform.processor(), + 'BUILD_OS=%r' % platform.platform()]) + '\n') except Exception: - print("WARNING: failed to build overviewer_version file") + print('WARNING: failed to build overviewer_version file') + def generate_primitives_h(): - global primitives prims = [p.lower().replace('-', '_') for p in primitives] - - outstr = "/* this file is auto-generated by setup.py */\n" + out = ['/* this file is auto-generated by setup.py */'] for p in prims: - outstr += "extern RenderPrimitiveInterface primitive_{0};\n".format(p) - outstr += "static RenderPrimitiveInterface *render_primitives[] = {\n" + out.append('extern RenderPrimitiveInterface primitive_{0};'.format(p)) + out.append('static RenderPrimitiveInterface *render_primitives[] = {') for p in prims: - outstr += " &primitive_{0},\n".format(p) - outstr += " NULL\n" - outstr += "};\n" + out.append(' &primitive_{0},'.format(p)) + out.append(' NULL') + out.append('};\n') + with open(SRC_PATH + 'primitives.h', 'w') as f: + f.write('\n'.join(out)) - with open("overviewer_core/src/primitives.h", "w") as f: - f.write(outstr) - -class CustomSDist(sdist): - def run(self): - # generate the version file - generate_version_py() - generate_primitives_h() - sdist.run(self) class CustomBuild(build): + def run(self): - # generate the version file + generate_version_py() + generate_primitives_h() try: - generate_version_py() - generate_primitives_h() build.run(self) - print("\nBuild Complete") + print('\nBuild Complete') except Exception: traceback.print_exc(limit=1) print("\nFailed to build Overviewer!") @@ -282,6 +336,7 @@ class CustomBuild(build): sys.exit(1) class CustomBuildExt(build_ext): + def build_extensions(self): c = self.compiler.compiler_type if c == "msvc": @@ -290,7 +345,7 @@ class CustomBuildExt(build_ext): e.extra_link_args.append("/MANIFEST") e.extra_link_args.append("/DWINVER=0x060") e.extra_link_args.append("/D_WIN32_WINNT=0x060") - if c == "unix": + elif c == "unix": # customize the build options for this compilier for e in self.extensions: e.extra_compile_args.append("-Wno-unused-variable") # quell some annoying warnings @@ -303,15 +358,56 @@ class CustomBuildExt(build_ext): # build in place, and in the build/ tree self.inplace = False build_ext.build_extensions(self) + self.inplace = True build_ext.build_extensions(self) +class CustomClean(clean): + """Custom clean command to remove in-place extension and the version file, + primitives header + + """ + def run(self): + clean.run(self) + # try to remove '_composite.{so,pyd,...}' extension, + # regardless of the current system's extension name convention + build_ext = self.get_finalized_command('build_ext') + ext_fname = build_ext.get_ext_filename('overviewer_core.c_overviewer') + primspath = os.path.join('overviewer_core', 'src', 'primitives.h') + + for fname in [ext_fname, primspath]: + if os.path.exists(fname): + try: + log.info("removing '%s'", fname) + if not self.dry_run: + os.remove(fname) + except OSError as error: + log.warn("'%s' could not be cleaned: %s", fname, error) + else: + log.debug("'%s' does not exist -- can't clean it", fname) + + # now try to purge all *.pyc files + for root, dirs, files in \ + os.walk(os.path.join(os.path.dirname(__file__), '.')): + for f in files: + if f.endswith('.pyc'): + if self.dry_run: + log.warn('Would remove %s', os.path.join(root, f)) + else: + os.remove(os.path.join(root, f)) + + +class CustomSDist(sdist): + def run(self): + generate_version_py() + generate_primitives_h() + sdist.run(self) + + setup_kwargs['cmdclass']['clean'] = CustomClean setup_kwargs['cmdclass']['sdist'] = CustomSDist setup_kwargs['cmdclass']['build'] = CustomBuild setup_kwargs['cmdclass']['build_ext'] = CustomBuildExt -### setup(**setup_kwargs) -