#!/usr/bin/env python3
import sys
# quick version check
if sys.version_info[0] == 2 or (sys.version_info[0] == 3 and sys.version_info[1] < 4):
print("Sorry, the Overviewer requires at least Python 3.4 to run.")
sys.exit(1)
from setuptools import setup
from setuptools.extension import Extension
from distutils.command.clean import clean
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
import os.path
import glob
import platform
import time
import traceback
from overviewer_core import util
try:
import py2exe
except ImportError:
py2exe = None
try:
import py2app
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]
if curdir:
os.chdir(curdir)
# now, setup the keyword arguments for setup
# (because we don't know until runtime if py2exe/py2app is available)
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
#
# top-level files that should be included as documentation
doc_files = ['COPYING.txt',
'README.rst',
'CONTRIBUTORS.rst',
'sample_config.py']
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
ret = []
for dirpath, dirnames, filenames in os.walk(src):
current_dest = os.path.relpath(dirpath, src)
if current_dest == '.':
current_dest = dest
else:
current_dest = os.path.join(dest, current_dest)
current_sources = map(lambda p: os.path.join(dirpath, p), filenames)
ret.append((current_dest, current_sources))
return ret
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
#
# py2exe options
#
if py2exe is not None:
setup_kwargs['comments'] = 'http://overviewer.org'
# py2exe likes a very particular type of version number:
setup_kwargs['version'] = setup_kwargs['version'].replace('-', '.')
setup_kwargs['console'] = ['overviewer.py', 'contribManager.py']
setup_kwargs['data_files'] = [('', doc_files)]
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']}
#
# py2app options
#
if py2app is not None:
setup_kwargs['app'] = ['overviewer.py']
setup_kwargs['options']['py2app'] = {'argv_emulation': False}
setup_kwargs['setup_requires'] = ['py2app']
#
# script, package, and data
#
setup_kwargs['packages'] = ['overviewer_core', 'overviewer_core/aux_files']
setup_kwargs['scripts'] = ['overviewer.py']
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)]
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
numpy_include = get_numpy_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(SRC_PATH + 'primitives/*.c'):
name = os.path.split(name)[-1]
name = os.path.splitext(name)[0]
primitives.append(name)
c_overviewer_files = ['main.c', 'composite.c', 'iterate.c', 'endian.c', 'rendermodes.c', 'block_class.c']
c_overviewer_files += ['primitives/%s.c' % (mode) for mode in primitives]
c_overviewer_files += ['Draw.c']
c_overviewer_includes = ['overviewer.h', 'rendermodes.h']
c_overviewer_files = ['overviewer_core/src/' + s for s in c_overviewer_files]
c_overviewer_includes = ['overviewer_core/src/' + s for s in c_overviewer_includes]
setup_kwargs['ext_modules'].append(Extension('overviewer_core.c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include] + pil_include, 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}
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):
def run(self):
# do the normal cleanup
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')
versionpath = os.path.join("overviewer_core", "overviewer_version.py")
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:
log.warning("'%s' could not be cleaned -- permission denied",
fname)
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.warning("Would remove %s", os.path.join(root,f))
else:
os.remove(os.path.join(root, f))
def generate_version_py():
try:
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')
def generate_primitives_h():
prims = [p.lower().replace('-', '_') for p in primitives]
out = ['/* this file is auto-generated by setup.py */']
for p in prims:
out.append('extern RenderPrimitiveInterface primitive_{0};'.format(p))
out.append('static RenderPrimitiveInterface *render_primitives[] = {')
for p in prims:
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))
class CustomBuild(build):
def run(self):
generate_version_py()
generate_primitives_h()
try:
build.run(self)
print('\nBuild Complete')
except Exception:
traceback.print_exc(limit=1)
print("\nFailed to build Overviewer!")
print("Please review the errors printed above and the build instructions")
print("at . If you are")
print("still having build problems, file an incident on the github tracker")
print("or find us in IRC.")
sys.exit(1)
class CustomBuildExt(build_ext):
def build_extensions(self):
c = self.compiler.compiler_type
if c == "msvc":
# customize the build options for this compilier
for e in self.extensions:
e.extra_link_args.append("/MANIFEST")
e.extra_link_args.append("/DWINVER=0x060")
e.extra_link_args.append("/D_WIN32_WINNT=0x060")
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
e.extra_compile_args.append("-Wno-unused-function") # quell some annoying warnings
e.extra_compile_args.append("-Wdeclaration-after-statement")
e.extra_compile_args.append("-Werror=declaration-after-statement")
# 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)