from __future__ import print_function, division import glob import os import io import re import shutil import sys import tarfile from contextlib import contextmanager from distutils.log import INFO, WARN, ERROR from distutils.spawn import find_executable from tempfile import mkdtemp from time import sleep from setuptools import find_packages, setup, Command from setuptools.command.install import install from setuptools.command.develop import develop try: from urllib.request import urlopen from urllib.error import URLError except ImportError: from urllib2 import urlopen, URLError KTOOLS_VERSION = '3.0.5' SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) def get_readme(): with io.open(os.path.join(SCRIPT_DIR, 'README.md'), encoding='utf-8') as readme: return readme.read() def get_install_requirements(): with io.open(os.path.join(SCRIPT_DIR, 'requirements-package.in'), encoding='utf-8') as reqs: return reqs.readlines() def get_version(): """ Return package version as listed in `__version__` in `init.py`. """ with io.open(os.path.join(SCRIPT_DIR, 'oasislmf', '__init__.py'), encoding='utf-8') as init_py: return re.search('__version__ = [\'"]([^\'"]+)[\'"]', init_py.read()).group(1) @contextmanager def temp_dir(): d = mkdtemp() yield d shutil.rmtree(d, ignore_errors=True) version = get_version() reqs = get_install_requirements() readme = get_readme() class InstallKtoolsMixin(object): def fetch_ktools_tar(self, location, attempts=3, timeout=5, cooldown=1): self.announce('Retrieving ktools {}'.format(KTOOLS_VERSION), INFO) last_error = None request = None for i in range(attempts): try: request = urlopen('https://github.com/OasisLMF/ktools/archive/v{}.tar.gz'.format(KTOOLS_VERSION), timeout=timeout * 1000) break except URLError as e: self.announce('Failed to get ktools tar (attempt {})'.format(i + 1), WARN) last_error = e sleep(cooldown) else: self.announce('Failed to get ktools tar after {} attempts'.format(attempts), ERROR) if last_error: raise last_error with open(location, 'wb') as f: f.write(request.read()) def unpack_tar(self, tar_location, extract_location): self.announce('Unpacking ktools', INFO) with tarfile.open(tar_location) as tar: if not os.path.exists(extract_location): os.makedirs(extract_location) tar.extractall(extract_location) def ktools_inpath(self): ktools_bin_subset = [ 'eve', 'getmodel', 'gulcalc', 'fmcalc', 'summarycalc', ] for ktools_bin in ktools_bin_subset: if find_executable(ktools_bin) is None: return False return True def build_ktools(self, extract_location): self.announce('Building ktools', INFO) build_dir = os.path.join(extract_location, 'ktools-{}'.format(KTOOLS_VERSION)) exit_code = os.system('cd {build_dir} && ./autogen.sh && ./configure && make && make check'.format(build_dir=build_dir)) if(exit_code is not 0): self.announce('Ktools build failed.\n', WARN) if (not self.ktools_inpath()): self.announce('Exisiting Ktools install not found.\n', WARN) return build_dir def add_ktools_to_path(self, build_dir): print('Installing ktools') if not os.path.exists(self.get_bin_dir()): os.makedirs(self.get_bin_dir()) for p in glob.glob(os.path.join(build_dir, 'src', '*', '*')): split = p.split(os.path.sep) # if the file name is the same as the directory we have found a # component executable if split[-1] == split[-2]: component_path = os.path.join(self.get_bin_dir(), split[-1]) shutil.copy(p, component_path) yield component_path def install_ktools(self): with temp_dir() as d: local_tar_path = os.path.join(d, 'ktools.tar.gz') local_extract_path = os.path.join(d, 'extracted') self.fetch_ktools_tar(local_tar_path) self.unpack_tar(local_tar_path, local_extract_path) build_dir = self.build_ktools(local_extract_path) self.ktools_components = list(self.add_ktools_to_path(build_dir)) class PostInstallKtools(InstallKtoolsMixin, install): command_name = 'install' user_options = install.user_options + [ ('ktools', None, 'Only install ktools components'), ] boolean_options = install.boolean_options + ['ktools'] def __init__(self, *args, **kwargs): self.ktools_components = [] install.__init__(self, *args, **kwargs) def run(self): self.install_ktools() install.run(self) def get_outputs(self): return install.get_outputs(self) + self.ktools_components def get_bin_dir(self): return self.install_scripts class PostDevelopKtools(InstallKtoolsMixin, develop): command_name = 'develop' user_options = develop.user_options boolean_options = develop.boolean_options def __init__(self, *args, **kwargs): self.ktools_components = [] develop.__init__(self, *args, **kwargs) def run(self): self.install_ktools() develop.run(self) def get_outputs(self): return develop.get_outputs(self) + self.ktools_components def get_bin_dir(self): return self.script_dir try: from wheel.bdist_wheel import bdist_wheel class BdistWheel(bdist_wheel): command_name = 'bdist_wheel' user_options = bdist_wheel.user_options def finalize_options(self): bdist_wheel.finalize_options(self) self.root_is_pure = False def get_tag(self): python, abi, plat = bdist_wheel.get_tag(self) python, abi = 'py2.py3', 'none' return python, abi, plat except ImportError: BdistWheel = None # allow setup.py to be run from any path os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) class Publish(Command): command_name = 'publish' user_options = [ ('wheel', None, 'Publish the wheel'), ('sdist', None, 'Publish the sdist tar'), ('no-clean', None, 'Don\'t clean the build artifacts'), ('sign', None, 'Sign the artifacts using GPG') ] boolean_options = ['wheel', 'sdist'] def initialize_options(self): self.wheel = False self.sdist = False self.no_clean = False self.sign = False def finalize_options(self): if not (self.wheel or self.sdist): self.announce('Either --wheel and/or --sdist must be provided', ERROR) sys.exit(1) def run(self): if os.system('pip freeze | grep twine'): self.announce('twine not installed.\nUse `pip install twine`.\nExiting.', WARN) sys.exit(1) if self.sdist: os.system('python setup.py sdist') if self.wheel: os.system('python setup.py bdist_wheel') if self.sign: for p in glob.glob('dist/*'): os.system('gpg --detach-sign -a {}'.format(p)) os.system('twine upload dist/*') print('You probably want to also tag the version now:') print(' git tag -a {v} -m \'version {v}\''.format(v=version)) print(' git push --tags') if not self.no_clean: shutil.rmtree('dist') shutil.rmtree('build') shutil.rmtree('oasislmf.egg-info') setup( name='oasislmf', version=version, packages=find_packages(exclude=('tests', 'tests.*', 'tests.*.*')), include_package_data=True, package_data={ '': [ 'requirements-package.in', 'LICENSE', ], 'oasislmf/_data/': ['*'] }, exclude_package_data={ '': ['__pycache__', '*.py[co]'], }, scripts=['bin/oasislmf'], license='BSD 3-Clause', description='Core loss modelling framework.', long_description=readme, long_description_content_type='text/markdown', url='https://github.com/OasisLMF/oasislmf', author='Oasis LMF', author_email="support@oasislmf.org", keywords='oasis lmf loss modeling framework', install_requires=reqs, classifiers=[ 'Development Status :: 4 - Beta', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], cmdclass={ 'install': PostInstallKtools, 'develop': PostDevelopKtools, 'bdist_wheel': BdistWheel, 'publish': Publish, }, )