# -*- coding: utf-8 -*- """ Vowpal Wabbit python setup module """ import distutils.dir_util import os import platform import sys from codecs import open from distutils.command.clean import clean as _clean from setuptools import setup, Extension, find_packages, Distribution as _distribution from setuptools.command.build_ext import build_ext as _build_ext from setuptools.command.sdist import sdist as _sdist from setuptools.command.test import test as _test from setuptools.command.install_lib import install_lib as _install_lib from shutil import rmtree import multiprocessing system = platform.system() version_info = sys.version_info here = os.path.abspath(os.path.dirname(__file__)) pkg_path = os.path.join(here, 'python') class Distribution(_distribution): global_options = _distribution.global_options global_options += [ ('enable-boost-cmake', None, 'Enable boost-cmake'), ('cmake-options=', None, 'Additional semicolon-separated cmake setup options list'), ] if system == 'Windows': global_options += [ ('vcpkg-root=', None, 'Path to vcpkg root. For Windows only'), ] def __init__(self, attrs=None): self.vcpkg_root = None self.enable_boost_cmake = None self.cmake_options = None _distribution.__init__(self, attrs) class CMakeExtension(Extension): def __init__(self, name): # don't invoke the original build_ext for this special extension Extension.__init__(self, name, sources=[]) def get_ext_filename_without_platform_suffix(filename): from distutils.sysconfig import get_config_var ext_suffix = get_config_var('EXT_SUFFIX') name, ext = os.path.splitext(filename) if not ext_suffix: return filename if ext_suffix == ext: return filename ext_suffix = ext_suffix.replace(ext, '') idx = name.find(ext_suffix) if idx == -1: return filename else: return name[:idx] + ext class BuildPyLibVWBindingsModule(_build_ext): def get_ext_filename(self, ext_name): # don't append the extension suffix to the binary name # see https://stackoverflow.com/questions/38523941/change-cythons-naming-rules-for-so-files/40193040#40193040 filename = _build_ext.get_ext_filename(self, ext_name) return get_ext_filename_without_platform_suffix(filename) def run(self): for ext in self.extensions: self.build_cmake(ext) _build_ext.run(self) def build_cmake(self, ext): # Make build directory distutils.dir_util.mkpath(self.build_temp) # Ensure lib output directory is made lib_output_dir = os.path.join(here, os.path.dirname(self.get_ext_fullpath(ext.name))) distutils.dir_util.mkpath(lib_output_dir) # example of cmake args config = 'Debug' if self.debug else 'Release' cmake_args = [ '-DCMAKE_BUILD_TYPE=' + config, '-DPY_VERSION=' + '{v[0]}.{v[1]}'.format(v=version_info), '-DBUILD_PYTHON=On', '-DBUILD_TESTS=Off', '-DWARNINGS=Off' ] if self.distribution.enable_boost_cmake is None: # Add this flag as default since testing indicates its safe. # But add a way to disable it in case it becomes a problem cmake_args += [ '-DBoost_NO_BOOST_CMAKE=ON' ] if self.distribution.cmake_options is not None: argslist = self.distribution.cmake_options.split(';') cmake_args += argslist if 'CONDA_PREFIX' in os.environ and not 'BOOST_ROOT' in os.environ: cmake_args.append('-DBOOST_ROOT={}'.format(os.environ['CONDA_PREFIX'])) # example of build args build_args = [ '--config', config ] if system == 'Windows': cmake_args += [ '-DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG=' + str(lib_output_dir), '-DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE=' + str(lib_output_dir), '-G', "Visual Studio 15 2017 Win64" ] build_args += [ '--target', 'pylibvw' ] if self.distribution.vcpkg_root is not None: # add the vcpkg toolchain if its provided abs_vcpkg_path = os.path.abspath(self.distribution.vcpkg_root) vcpkg_toolchain = os.path.join( abs_vcpkg_path, 'scripts', 'buildsystems', 'vcpkg.cmake' ) cmake_args += ['-DCMAKE_TOOLCHAIN_FILE=' + vcpkg_toolchain] else: cmake_args += [ '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + str(lib_output_dir), ] build_args += [ '--', '-j{}'.format(multiprocessing.cpu_count()), # Build the pylibvw target "pylibvw" ] os.chdir(str(self.build_temp)) self.spawn(['cmake'] + cmake_args + [str(here)]) if not self.dry_run: self.spawn(['cmake', '--build', '.'] + build_args) os.chdir(str(here)) class Clean(_clean): """ Clean up after building python package directories """ def run(self): rmtree(os.path.join(here, 'dist'), ignore_errors=True) rmtree(os.path.join(here, 'build'), ignore_errors=True) rmtree(os.path.join(here, 'vowpalwabbit.egg-info'), ignore_errors=True) _clean.run(self) class Sdist(_sdist): def run(self): _sdist.run(self) class InstallLib(_install_lib): def build(self): _install_lib.build(self) class Tox(_test): """ Run tox tests with 'python setup.py test' """ tox_args = None test_args = None test_suite = None user_options = [('tox-args=', 'a', "Arguments to pass to tox")] def initialize_options(self): _test.initialize_options(self) self.tox_args = None def finalize_options(self): _test.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): # import here, cause outside the eggs aren't loaded import tox import shlex args = self.tox_args if args: args = shlex.split(self.tox_args) errno = tox.cmdline(args=args) sys.exit(errno) # Get the long description from the README file with open(os.path.join(pkg_path, 'README.rst'), encoding='utf-8') as f: long_description = f.read() # Get the current version for the python package from the configure.ac file config_path = os.path.join(here, 'version.txt') with open(config_path, encoding='utf-8') as f: version = f.readline().strip() setup( name='vowpalwabbit', version=version, description='Vowpal Wabbit Python package', long_description=long_description, url='https://github.com/JohnLangford/vowpal_wabbit', author='Scott Graham', author_email='scott.d.graham@gmail.com', license='BSD 3-Clause License', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Science/Research', 'Topic :: Scientific/Engineering', 'Topic :: Scientific/Engineering :: Information Analysis', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], keywords='fast machine learning online classification regression', package_dir={'' : os.path.relpath(pkg_path)}, packages=find_packages(where=pkg_path), platforms='any', zip_safe=False, include_package_data=True, ext_modules=[CMakeExtension('pylibvw')], distclass=Distribution, cmdclass={ 'build_ext': BuildPyLibVWBindingsModule, 'clean': Clean, 'sdist': Sdist, 'test': Tox, 'install_lib': InstallLib }, # tox.ini handles additional test dependencies tests_require=['tox'] )