diff --git a/.circleci/config.yml b/.circleci/config.yml index 195d032e7..8e5b238c4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,30 +44,14 @@ references: jobs: # x64 - manylinux-x64_cp27-cp27m: - <<: *x64_build_job manylinux-x64_cp27-cp27mu: <<: *x64_build_job - manylinux-x64_cp34-cp34m: - <<: *x64_build_job - manylinux-x64_cp35-cp35m: - <<: *x64_build_job - manylinux-x64_cp36-cp36m: - <<: *x64_build_job manylinux-x64_cp37-cp37m_upload-sdist: <<: *x64_build_job # x86 -# manylinux-x86_cp27-cp27m: -# <<: *x86_build_job # manylinux-x86_cp27-cp27mu: # <<: *x86_build_job -# manylinux-x86_cp34-cp34m: -# <<: *x86_build_job -# manylinux-x86_cp35-cp35m: -# <<: *x86_build_job -# manylinux-x86_cp36-cp36m: -# <<: *x86_build_job # manylinux-x86_cp37-cp37m: # <<: *x86_build_job @@ -103,47 +87,23 @@ workflows: build-test-deploy: jobs: # x64 - - manylinux-x64_cp27-cp27m: - <<: *no_filters - manylinux-x64_cp27-cp27mu: <<: *no_filters - - manylinux-x64_cp34-cp34m: - <<: *no_filters - - manylinux-x64_cp35-cp35m: - <<: *no_filters - - manylinux-x64_cp36-cp36m: - <<: *no_filters - manylinux-x64_cp37-cp37m_upload-sdist: <<: *no_filters # x86 -# - manylinux-x86_cp27-cp27m: -# <<: *no_filters # - manylinux-x86_cp27-cp27mu: # <<: *no_filters -# - manylinux-x86_cp34-cp34m: -# <<: *no_filters -# - manylinux-x86_cp35-cp35m: -# <<: *no_filters -# - manylinux-x86_cp36-cp36m: -# <<: *no_filters # - manylinux-x86_cp37-cp37m: # <<: *no_filters - deploy-master: requires: # x64 - - manylinux-x64_cp27-cp27m - manylinux-x64_cp27-cp27mu - - manylinux-x64_cp34-cp34m - - manylinux-x64_cp35-cp35m - - manylinux-x64_cp36-cp36m - manylinux-x64_cp37-cp37m_upload-sdist # x86 -# - manylinux-x86_cp27-cp27m # - manylinux-x86_cp27-cp27mu -# - manylinux-x86_cp34-cp34m -# - manylinux-x86_cp35-cp35m -# - manylinux-x86_cp36-cp36m # - manylinux-x86_cp37-cp37m filters: branches: @@ -151,18 +111,10 @@ workflows: - deploy-release: requires: # x64 - - manylinux-x64_cp27-cp27m - manylinux-x64_cp27-cp27mu - - manylinux-x64_cp34-cp34m - - manylinux-x64_cp35-cp35m - - manylinux-x64_cp36-cp36m - manylinux-x64_cp37-cp37m_upload-sdist # x86 -# - manylinux-x86_cp27-cp27m # - manylinux-x86_cp27-cp27mu -# - manylinux-x86_cp34-cp34m -# - manylinux-x86_cp35-cp35m -# - manylinux-x86_cp36-cp36m # - manylinux-x86_cp37-cp37m filters: tags: diff --git a/.travis.yml b/.travis.yml index c3e2293cb..b95c63629 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,21 +13,6 @@ matrix: env: - PYTHON_VERSION=3.7.2 - - os: osx - language: generic - env: - - PYTHON_VERSION=3.6.8 - - - os: osx - language: generic - env: - - PYTHON_VERSION=3.5.6 - - - os: osx - language: generic - env: - - PYTHON_VERSION=3.4.9 - - os: osx language: generic env: @@ -36,9 +21,6 @@ matrix: cache: directories: - $HOME/.pyenv/versions/3.7.2 - - $HOME/.pyenv/versions/3.6.8 - - $HOME/.pyenv/versions/3.5.6 - - $HOME/.pyenv/versions/3.4.9 - $HOME/.pyenv/versions/2.7.15 - $HOME/downloads diff --git a/appveyor.yml b/appveyor.yml index 6724108a7..704af62e9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,42 +20,6 @@ environment: PYTHON_ARCH: "64" BLOCK: "0" - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - PYTHON_DIR: "C:\\Python34" - PYTHON_VERSION: "3.4.x" - PYTHON_ARCH: "32" - BLOCK: "0" - - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - PYTHON_DIR: "C:\\Python34-x64" - PYTHON_VERSION: "3.4.x" - PYTHON_ARCH: "64" - BLOCK: "0" - - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - PYTHON_DIR: "C:\\Python35" - PYTHON_VERSION: "3.5.x" - PYTHON_ARCH: "32" - BLOCK: "0" - - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - PYTHON_DIR: "C:\\Python35-x64" - PYTHON_VERSION: "3.5.x" - PYTHON_ARCH: "64" - BLOCK: "0" - - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - PYTHON_DIR: "C:\\Python36" - PYTHON_VERSION: "3.6.x" - PYTHON_ARCH: "32" - BLOCK: "0" - - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - PYTHON_DIR: "C:\\Python36-x64" - PYTHON_VERSION: "3.6.x" - PYTHON_ARCH: "64" - BLOCK: "0" - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 PYTHON_DIR: "C:\\Python37" PYTHON_VERSION: "3.7.x" diff --git a/scikit-ci.yml b/scikit-ci.yml index a2fecfe8a..a16edc461 100644 --- a/scikit-ci.yml +++ b/scikit-ci.yml @@ -60,6 +60,14 @@ build: test: commands: + # Convert to generic platform wheel + - python: | + import glob, sys + sys.path.insert(0, "./scripts") + from convert_to_generic_platform_wheel import convert_to_generic_platform_wheel + wheels = glob.glob("dist/*.whl") + for wheel in wheels: + convert_to_generic_platform_wheel(wheel, remove_original=True) - python setup.py test after_test: diff --git a/scripts/convert_to_generic_platform_wheel.py b/scripts/convert_to_generic_platform_wheel.py new file mode 100644 index 000000000..234e31796 --- /dev/null +++ b/scripts/convert_to_generic_platform_wheel.py @@ -0,0 +1,171 @@ + +import argparse +import logging +import os +import sys + +from itertools import product +from os.path import (abspath, basename, dirname, isfile, join as pjoin, splitext) + +from auditwheel.tools import unique_by_index +from auditwheel.wheeltools import _dist_info_dir, InWheelCtx +from wheel.pkginfo import read_pkg_info, write_pkg_info +from wheel.install import WHEEL_INFO_RE + +logger = logging.getLogger(splitext(basename(__file__))[0]) + + +def _to_generic_pyver(pyver_tags): + """Convert from CPython implementation to generic python version tags + + Convert each CPython version tag to the equivalent generic tag. + + For example:: + + cp35 -> py3 + cp27 -> py2 + + See https://www.python.org/dev/peps/pep-0425/#python-tag + """ + return ['py%s' % tag[2] if tag.startswith('cp') else tag for tag in pyver_tags] + + +def _convert_to_generic_platform_wheel(wheel_ctx): + """Switch to generic python tags and remove ABI tags from a wheel + + Convert implementation specific python tags to their generic equivalent and + remove all ABI tags from wheel_ctx's filename and ``WHEEL`` file. + + Parameters + ---------- + wheel_ctx : InWheelCtx + An open wheel context + """ + + abi_tags = ['none'] + + info_fname = pjoin(_dist_info_dir(wheel_ctx.path), 'WHEEL') + info = read_pkg_info(info_fname) + + # Check what tags we have + if wheel_ctx.out_wheel is not None: + out_dir = dirname(wheel_ctx.out_wheel) + wheel_fname = basename(wheel_ctx.out_wheel) + else: + out_dir = '.' + wheel_fname = basename(wheel_ctx.in_wheel) + + # Update wheel filename + parsed_fname = WHEEL_INFO_RE(wheel_fname) + fparts = parsed_fname.groupdict() + original_platform_tags = fparts['plat'].split('.') + + original_abi_tags = fparts['abi'].split('.') + logger.debug('Previous ABI tags: %s', ', '.join(original_abi_tags)) + if abi_tags != original_abi_tags: + logger.debug('New ABI tags ....: %s', ', '.join(abi_tags)) + fparts['abi'] = '.'.join(abi_tags) + else: + logger.debug('No ABI tags change needed.') + + original_pyver_tags = fparts['pyver'].split('.') + logger.debug('Previous pyver tags: %s', ', '.join(original_pyver_tags)) + pyver_tags = _to_generic_pyver(original_pyver_tags) + if pyver_tags != original_pyver_tags: + logger.debug('New pyver tags ....: %s', ', '.join(pyver_tags)) + fparts['pyver'] = '.'.join(pyver_tags) + else: + logger.debug('No pyver change needed.') + + _, ext = splitext(wheel_fname) + fparts['ext'] = ext + out_wheel_fname = "{namever}-{pyver}-{abi}-{plat}{ext}".format(**fparts) + + logger.info('Previous filename: %s', wheel_fname) + if out_wheel_fname != wheel_fname: + logger.info('New filename ....: %s', out_wheel_fname) + else: + logger.info('No filename change needed.') + + out_wheel = pjoin(out_dir, out_wheel_fname) + + # Update wheel tags + in_info_tags = [tag for name, tag in info.items() if name == 'Tag'] + logger.info('Previous WHEEL info tags: %s', ', '.join(in_info_tags)) + + # Python version, C-API version combinations + pyc_apis = [] + for tag in in_info_tags: + py_ver = '.'.join(_to_generic_pyver(tag.split('-')[0].split('.'))) + abi = 'none' + pyc_apis.append('-'.join([py_ver, abi])) + # unique Python version, C-API version combinations + pyc_apis = unique_by_index(pyc_apis) + + # Set tags for each Python version, C-API combination + updated_tags = ['-'.join(tup) for tup in product(pyc_apis, original_platform_tags)] + + if updated_tags != in_info_tags: + del info['Tag'] + for tag in updated_tags: + info.add_header('Tag', tag) + + logger.info('New WHEEL info tags ....: %s', ', '.join(info.get_all('Tag'))) + write_pkg_info(info_fname, info) + else: + logger.info('No WHEEL info change needed.') + return out_wheel + + +def convert_to_generic_platform_wheel(wheel_path, out_dir='./dist/', remove_original=False, verbose=0): + logging.disable(logging.NOTSET) + if verbose >= 1: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + + wheel_fname = basename(wheel_path) + out_dir = abspath(out_dir) + + with InWheelCtx(wheel_path) as ctx: + ctx.out_wheel = pjoin(out_dir, wheel_fname) + ctx.out_wheel = _convert_to_generic_platform_wheel(ctx) + + if remove_original: + logger.info('Removed original wheel %s' % wheel_path) + os.remove(wheel_path) + + +def main(): + p = argparse.ArgumentParser(description='Convert wheel to be independent of python implementation and ABI') + p.set_defaults(prog=basename(sys.argv[0])) + p.add_argument("-v", + "--verbose", + action='count', + dest='verbose', + default=0, + help='Give more output. Option is additive') + + p.add_argument('WHEEL_FILE', help='Path to wheel file.') + p.add_argument('-w', + '--wheel-dir', + dest='WHEEL_DIR', + type=abspath, + help='Directory to store updated wheels (default: "dist/")', + default='dist/') + p.add_argument("-r", + "--remove-original", + dest='remove_original', + action='store_true', + help='Remove original wheel') + + args = p.parse_args() + + if not isfile(args.WHEEL_FILE): + p.error('cannot access %s. No such file' % args.WHEEL_FILE) + + convert_to_generic_platform_wheel(args.WHEEL_FILE, args.WHEEL_DIR, args.remove_original, args.verbose) + + +if __name__ == '__main__': + main()