From 1658aa32f7332c3757ff30e59d650e30a97f52ab Mon Sep 17 00:00:00 2001 From: Anthon van der Neut Date: Fri, 13 Jan 2017 09:36:12 +0100 Subject: update setup, remove transition dependency on ruamel.yaml --- .hgignore | 2 + Makefile | 3 + __init__.py | 38 ++--- setup.py | 514 +++++++++++++++++++++++++++++++++++++++++++++++------------- tox.ini | 5 +- 5 files changed, 432 insertions(+), 130 deletions(-) diff --git a/.hgignore b/.hgignore index dd78dbd..350f799 100644 --- a/.hgignore +++ b/.hgignore @@ -12,3 +12,5 @@ build *.egg-info .tox ruamel +.ruamel +.cache diff --git a/Makefile b/Makefile index 594e80a..12eb858 100644 --- a/Makefile +++ b/Makefile @@ -15,3 +15,6 @@ updatereadme: layout: pdf cp README.pdf /data0/tmp/pdf + +owl: sdist + make owl_copy owl_devpi diff --git a/__init__.py b/__init__.py index d9504cf..099145f 100644 --- a/__init__.py +++ b/__init__.py @@ -5,21 +5,19 @@ from __future__ import print_function # install_requires of ruamel.base is not really required but the old # ruamel.base installed __init__.py, and thus a new version should # be installed at some point - -null = None -_package_data = { # JSON - "full_package_name": "ruamel.std.argparse", - "version_info": [0, 6, 1], - "author": "Anthon van der Neut", - "author_email": "a.van.der.neut@ruamel.eu", - "description": "Enhancements to argparse: extra actions, subparser aliases, smart formatter, a decorator based wrapper", # NOQA - "entry_points": null, - "universal": 1, - "install_requires": { - "any": ["ruamel.base>=1.0.0"], - "py26": ["argparse"] # tox needs this so it is difficult to test - } -} # JSON +_package_data = dict( + full_package_name="ruamel.std.argparse", + version_info=(0, 7, 1), + author='Anthon van der Neut', + author_email='a.van.der.neut@ruamel.eu', + description="Enhancements to argparse: extra actions, subparser aliases, " + "smart formatter, a decorator based wrapper", + entry_points=None, + install_requires=dict( + any=[] + ), + universal=True, +) # < from ruamel.util.new import _convert_version @@ -47,8 +45,8 @@ __version__ = _convert_version(version_info) del _convert_version -import sys -import argparse +import sys # NOQA +import argparse # NOQA from argparse import ArgumentParser # NOQA PY3 = sys.version_info[0] == 3 @@ -58,6 +56,10 @@ if PY3: else: string_types = basestring, +store_true = 'store_true' +store_false = 'store_false' +append = 'append' + class SubParsersAction(argparse._SubParsersAction): """support aliases, based on differences of 3.3 and 2.7 @@ -176,7 +178,7 @@ class ProgramBase(object): aliases = kw.pop('aliases', 0) self._parser = argparse.ArgumentParser(*args, **kw) if aliases and sys.version_info < (3,): - self._parser.register('action', 'parsers', SubParsersAction) + self._parser.register('action', 'parsers', SubParsersAction) # NOQA self._program_base_initialising = True cls = self self._sub_parsers = None diff --git a/setup.py b/setup.py index 15518a7..7112227 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,20 @@ # # header # coding: utf-8 -from __future__ import print_function +from __future__ import print_function, absolute_import, division, unicode_literals + +# # __init__.py parser + +import sys +import os +import datetime +sys.path = [path for path in sys.path if path not in [os.getcwd(), '']] +import platform # NOQA +from _ast import * # NOQA +from ast import parse # NOQA + +from setuptools import setup, Extension, Distribution # NOQA +from setuptools.command import install_lib # NOQA if __name__ != '__main__': raise NotImplementedError('should never include setup.py') @@ -10,88 +23,157 @@ if __name__ != '__main__': full_package_name = None +if __name__ != '__main__': + raise NotImplementedError('should never include setup.py') -# parses python ( "= dict( )" ) or json ( "= { # JSON" ) +if sys.version_info < (3, ): + string_type = basestring +else: + string_type = str + + +if sys.version_info < (3, 4): + class Bytes(): + pass + + class NameConstant: + pass + +if sys.version_info < (3, ): + open_kw = dict() +else: + open_kw = dict(encoding='utf-8') + + +if sys.version_info < (2, 7) or platform.python_implementation() == 'Jython': + class Set(): + pass + + +def literal_eval(node_or_string): + """ + Safely evaluate an expression node or a string containing a Python + expression. The string or node provided may only consist of the following + Python literal structures: strings, bytes, numbers, tuples, lists, dicts, + sets, booleans, and None. + """ + _safe_names = {'None': None, 'True': True, 'False': False} + if isinstance(node_or_string, string_type): + node_or_string = parse(node_or_string, mode='eval') + if isinstance(node_or_string, Expression): + node_or_string = node_or_string.body + else: + raise TypeError("only string or AST nodes supported") + + def _convert(node): + if isinstance(node, (Str, Bytes)): + return node.s + elif isinstance(node, Num): + return node.n + elif isinstance(node, Tuple): + return tuple(map(_convert, node.elts)) + elif isinstance(node, List): + return list(map(_convert, node.elts)) + elif isinstance(node, Set): + return set(map(_convert, node.elts)) + elif isinstance(node, Dict): + return dict((_convert(k), _convert(v)) for k, v + in zip(node.keys, node.values)) + elif isinstance(node, NameConstant): + return node.value + elif sys.version_info < (3, 4) and isinstance(node, Name): + if node.id in _safe_names: + return _safe_names[node.id] + elif isinstance(node, UnaryOp) and \ + isinstance(node.op, (UAdd, USub)) and \ + isinstance(node.operand, (Num, UnaryOp, BinOp)): # NOQA + operand = _convert(node.operand) + if isinstance(node.op, UAdd): + return + operand + else: + return - operand + elif isinstance(node, BinOp) and \ + isinstance(node.op, (Add, Sub)) and \ + isinstance(node.right, (Num, UnaryOp, BinOp)) and \ + isinstance(node.left, (Num, UnaryOp, BinOp)): # NOQA + left = _convert(node.left) + right = _convert(node.right) + if isinstance(node.op, Add): + return left + right + else: + return left - right + elif isinstance(node, Call): + func_id = getattr(node.func, 'id', None) + if func_id == 'dict': + return dict((k.arg, _convert(k.value)) for k in node.keywords) + elif func_id == 'set': + return set(_convert(node.args[0])) + elif func_id == 'date': + return datetime.date(*[_convert(k) for k in node.args]) + elif func_id == 'datetime': + return datetime.datetime(*[_convert(k) for k in node.args]) + err = SyntaxError('malformed node or string: ' + repr(node)) + err.filename = '' + err.lineno = node.lineno + err.offset = node.col_offset + err.text = repr(node) + err.node = node + raise err + return _convert(node_or_string) + + +# parses python ( "= dict( )" ) or ( "= {" ) def _package_data(fn): data = {} - with open(fn) as fp: + with open(fn, **open_kw) as fp: parsing = False lines = [] for line in fp.readlines(): - if line.startswith('_package_data'): + if sys.version_info < (3,): + line = line.decode('utf-8') + if line.startswith(u'_package_data'): if 'dict(' in line: parsing = 'python' - elif '# JSON' in line: - parsing = 'json' - lines.append('{\n') + lines.append(u'dict(\n') + elif line.endswith(u'= {\n'): + parsing = 'python' + lines.append(u'{\n') else: raise NotImplementedError continue if not parsing: continue - if parsing == 'json': - x = line.rsplit("# ", 1) - if len(x) > 1: - if x[1].startswith('JSON'): - lines.append(x[0]+'\n') - import json - try: - data = json.loads(''.join(lines)) - except ValueError: - w = len(str(len(lines))) - for i, line in enumerate(lines): - print('{0:{1}}: {2}'.format( - i+1, w, line), end='') - raise - break - elif not x[0].strip(): - continue # empty line can have any comment - elif '"' not in x[1] and "'" not in x[1]: - # can't deal with quotes might be # in string - line = x[0] + '\n' - lines.append(line) - elif parsing == 'python': - if line.startswith(')'): + if parsing == 'python': + if line.startswith(u')') or line.startswith(u'}'): + lines.append(line) + try: + data = literal_eval(u''.join(lines)) + except SyntaxError as e: + context = 2 + from_line = e.lineno - (context + 1) + to_line = e.lineno + (context - 1) + w = len(str(to_line)) + for index, line in enumerate(lines): + if from_line <= index <= to_line: + print(u"{0:{1}}: {2}".format(index, w, line).encode('utf-8'), + end=u'') + if index == e.lineno - 1: + print(u"{0:{1}} {2}^--- {3}".format( + u' ', w, u' ' * e.offset, e.node)) + raise break - if '# NOQA' in line: - line = line.split('# NOQA', 1)[0].rstrip() - k, v = [x.strip() for x in line.split('=', 1)] - if v[-1] == ',': - v = v[:-1] - if v[0] in '\'"' and v[0] == v[-1]: - data[k] = v[1:-1] - elif v == 'None': - data[k] = None - elif v == 'True': - data[k] = True - elif v == 'False': - data[k] = False - elif v[0] == '(' and v[-1] == ')': - data[k] = tuple([x.strip()[1:-1] if x[0] in '\'"' else - int(x) for x in v[1:-1].split(', ')]) - elif v[0] == '[' and v[-1] == ']': - data[k] = [x.strip()[1:-1] if x[0] in '\'"' else int(x) - for x in v[1:-1].split(', ')] - else: - print('Unknown: >>>>> {0!r} {1!r}'.format(k, v)) + lines.append(line) else: raise NotImplementedError return data -pkg_data = _package_data('__init__.py') +# make sure you can run "python ../some/dir/setup.py install" +pkg_data = _package_data(__file__.replace('setup.py', '__init__.py')) exclude_files = [ 'setup.py', ] -# # imports -import os -import sys -import platform - -from setuptools import setup, Extension, Distribution # NOQA -from setuptools.command import install_lib - # # helper def _check_convert_version(tup): @@ -104,7 +186,7 @@ def _check_convert_version(tup): if isinstance(x, int): nr_digits += 1 if nr_digits > 2: - raise ValueError("too many consecutive digits " + ret_val) + raise ValueError("too many consecutive digits after " + ret_val) ret_val += next_sep + str(x) next_sep = '.' continue @@ -113,7 +195,7 @@ def _check_convert_version(tup): if first_letter in 'abcr': if post_dev: raise ValueError("release level specified after " - "post/dev:" + x) + "post/dev: " + x) nr_digits = 0 ret_val += 'rc' if first_letter == 'r' else first_letter elif first_letter in 'pd': @@ -122,6 +204,9 @@ def _check_convert_version(tup): ret_val += '.post' if first_letter == 'p' else '.dev' else: raise ValueError('First letter of "' + x + '" not recognised') + # .dev and .post need a number otherwise setuptools normalizes and complains + if nr_digits == 1 and post_dev: + ret_val += '0' return ret_val @@ -146,6 +231,74 @@ class MyInstallLib(install_lib.install_lib): return alt_files +class InMemoryZipFile(object): + def __init__(self, file_name=None): + try: + from cStringIO import StringIO + except ImportError: + from io import BytesIO as StringIO + import zipfile + self.zip_file = zipfile + # Create the in-memory file-like object + self._file_name = file_name + self.in_memory_data = StringIO() + # Create the in-memory zipfile + self.in_memory_zip = self.zip_file.ZipFile( + self.in_memory_data, "w", self.zip_file.ZIP_DEFLATED, False) + self.in_memory_zip.debug = 3 + + def append(self, filename_in_zip, file_contents): + '''Appends a file with name filename_in_zip and contents of + file_contents to the in-memory zip.''' + self.in_memory_zip.writestr(filename_in_zip, file_contents) + return self # so you can daisy-chain + + def write_to_file(self, filename): + '''Writes the in-memory zip to a file.''' + # Mark the files as having been created on Windows so that + # Unix permissions are not inferred as 0000 + for zfile in self.in_memory_zip.filelist: + zfile.create_system = 0 + self.in_memory_zip.close() + with open(filename, 'wb') as f: + f.write(self.in_memory_data.getvalue()) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self._file_name is None: + return + self.write_to_file(self._file_name) + + def delete_from_zip_file(self, pattern=None, file_names=None): + """ + zip_file can be a string or a zipfile.ZipFile object, the latter will be closed + any name in file_names is deleted, all file_names provided have to be in the ZIP + archive or else an IOError is raised + """ + if pattern and isinstance(pattern, string_type): + import re + pattern = re.compile(pattern) + if file_names: + if not isinstance(file_names, list): + file_names = [file_names] + else: + file_names = [] + with self.zip_file.ZipFile(self._file_name) as zf: + for l in zf.infolist(): + if l.filename in file_names: + file_names.remove(l.filename) + continue + if pattern and pattern.match(l.filename): + continue + self.append(l.filename, zf.read(l)) + if file_names: + raise IOError('[Errno 2] No such file{}: {}'.format( + '' if len(file_names) == 1 else 's', + ', '.join([repr(f) for f in file_names]))) + + class NameSpacePackager(object): def __init__(self, pkg_data): assert isinstance(pkg_data, dict) @@ -153,11 +306,23 @@ class NameSpacePackager(object): self.full_package_name = self.pn(self._pkg_data['full_package_name']) self._split = None self.depth = self.full_package_name.count('.') + self.nested = self._pkg_data.get('nested', False) self.command = None + self._pkg = [None, None] # required and pre-installable packages if sys.argv[0] == 'setup.py' and sys.argv[1] == 'install' and \ '--single-version-externally-managed' not in sys.argv: - print('error: have to install with "pip install ."') + if os.environ.get('READTHEDOCS', None) == 'True': + os.system('pip install .') + sys.exit(0) + print('error: you have to install with "pip install ."') sys.exit(1) + # If you only support an extension module on Linux, Windows thinks it + # is pure. That way you would get pure python .whl files that take + # precedence for downloading on Linux over source with compilable C + if self._pkg_data.get('universal'): + Distribution.is_pure = lambda *args: True + else: + Distribution.is_pure = lambda *args: False for x in sys.argv: if x[0] == '-' or x == 'setup.py': continue @@ -171,7 +336,11 @@ class NameSpacePackager(object): @property def split(self): - """split the full package name in list of compontents""" + """split the full package name in list of compontents traditionally + done by setuptools.find_packages. This routine skips any directories + with __init__.py that start with "_" or ".", or contain a + setup.py/tox.ini (indicating a subpackage) + """ if self._split is None: fpn = self.full_package_name.split('.') self._split = [] @@ -179,7 +348,7 @@ class NameSpacePackager(object): self._split.insert(0, '.'.join(fpn)) fpn = fpn[:-1] for d in os.listdir('.'): - if not os.path.isdir(d) or d == self._split[0] or d[0] == '_': + if not os.path.isdir(d) or d == self._split[0] or d[0] in '._': continue # prevent sub-packages in namespace from being included x = os.path.join(d, 'setup.py') @@ -191,37 +360,48 @@ class NameSpacePackager(object): x = os.path.join(d, '__init__.py') if os.path.exists(x): self._split.append(self.full_package_name + '.' + d) + if sys.version_info < (3, ): + self._split = [(y.encode('utf-8') if isinstance(y, unicode) else y) + for y in self._split] return self._split @property def namespace_packages(self): return self.split[:self.depth] + def namespace_directories(self, depth=None): + """return list of directories where the namespace should be created / + can be found + """ + res = [] + for index, d in enumerate(self.split[:depth]): + # toplevel gets a dot + if index > 0: + d = os.path.join(*d.split('.')) + res.append('.' + d) + return res + @property def package_dir(self): - return { + d = { # don't specify empty dir, clashes with package_data spec self.full_package_name: '.', - self.split[0]: self.split[0], } + if len(self.split) > 1: # only if package namespace + d[self.split[0]] = self.namespace_directories(1)[0] + return d def create_dirs(self): """create the directories necessary for namespace packaging""" - if not os.path.exists(self.split[0]): - for d in self.split[:self.depth]: - d = os.path.join(*d.split('.')) + directories = self.namespace_directories(self.depth) + if not directories: + return + if not os.path.exists(directories[0]): + for d in directories: os.mkdir(d) with open(os.path.join(d, '__init__.py'), 'w') as fp: fp.write('import pkg_resources\n' 'pkg_resources.declare_namespace(__name__)\n') - # not necessary if not using find_packages, recursive links - # are horrible anyway, slowing down pip - # os.symlink( - # # a.b gives a/b -> .. - # # a.b.c gives a/b/c -> ../.. - # os.path.join(*['..'] * self.depth), - # os.path.join(*self.split[self.depth].split('.')) - # ) def check(self): try: @@ -277,23 +457,48 @@ class NameSpacePackager(object): ' installs for package name {0}'.format(fn)) def entry_points(self, script_name=None, package_name=None): + """normally called without explicit script_name and package name + the default console_scripts entry depends on the existence of __main__.py: + if that file exists then the function main() in there is used, otherwise + the in __init__.py. + + the _package_data entry_points key/value pair can be explicitly specified + including a "=" character. If the entry is True or 1 the + scriptname is the last part of the full package path (split on '.') + if the ep entry is a simple string without "=", that is assumed to be + the name of the script. + """ + def pckg_entry_point(name): + return '{0}{1}:main'.format( + name, + '.__main__' if os.path.exists('__main__.py') else '', + ) + ep = self._pkg_data.get('entry_points', True) if ep is None: return None - if ep is not True: - return {'console_scripts': [ep]} + if ep not in [True, 1]: + if '=' in ep: + # full specification of the entry point like + # entry_points=['yaml = ruamel.yaml.cmd:main'], + return {'console_scripts': [ep]} + # assume that it is just the script name + script_name = ep if package_name is None: package_name = self.full_package_name if not script_name: script_name = package_name.split('.')[-1] return {'console_scripts': [ - '{0} = {1}:main'.format(script_name, package_name), + '{0} = {1}'.format(script_name, pckg_entry_point(package_name)), ]} @property def url(self): - return 'https://bitbucket.org/{0}/{1}'.format( - *self.full_package_name.split('.', 1)) + if self.full_package_name.startswith('ruamel.'): + sp = self.full_package_name.split('.', 1) + else: + sp = ['ruamel', self.full_package_name] + return 'https://bitbucket.org/{0}/{1}'.format(*sp) @property def author(self): @@ -305,12 +510,16 @@ class NameSpacePackager(object): @property def license(self): + """return the license field from _package_data, None means MIT""" lic = self._pkg_data.get('license') if lic is None: # lic_fn = os.path.join(os.path.dirname(__file__), 'LICENSE') # assert os.path.exists(lic_fn) return "MIT license" - return license + return lic + + def has_mit_lic(self): + return 'MIT' in self.license @property def description(self): @@ -319,10 +528,10 @@ class NameSpacePackager(object): @property def status(self): # αβ - status = self._pkg_data.get('status', u'β') - if status == u'α': + status = self._pkg_data.get('status', u'β').lower() + if status in [u'α', u'alpha']: return (3, 'Alpha') - elif status == u'β': + elif status in [u'β', u'beta']: return (4, 'Beta') elif u'stable' in status.lower(): return (5, 'Production/Stable') @@ -333,32 +542,73 @@ class NameSpacePackager(object): return [ 'Development Status :: {0} - {1}'.format(*self.status), 'Intended Audience :: Developers', - 'License :: ' + ('Other/Proprietary License' - if self.pn(self._pkg_data.get('license')) else - 'OSI Approved :: MIT License'), + 'License :: ' + ('OSI Approved :: MIT' if self.has_mit_lic() + else 'Other/Proprietary') + ' License', 'Operating System :: OS Independent', 'Programming Language :: Python', ] + [self.pn(x) for x in self._pkg_data.get('classifiers', [])] + @property + def keywords(self): + return self.pn(self._pkg_data.get('keywords')) + @property def install_requires(self): - ir = self._pkg_data.get('install_requires', []) + """list of packages required for installation""" + return self._analyse_packages[0] + + @property + def install_pre(self): + """list of packages required for installation""" + return self._analyse_packages[1] + + @property + def _analyse_packages(self): + """gather from configuration, names starting with * need + to be installed explicitly as they are not on PyPI + install_requires should be dict, with keys 'any', 'py27' etc + or a list (which is as if only 'any' was defined + """ + if self._pkg[0] is None: + self._pkg[0] = [] + self._pkg[1] = [] + + ir = self._pkg_data.get('install_requires') + if ir is None: + return self._pkg # these will be both empty at this point if isinstance(ir, list): - return ir + self._pkg[0] = ir + return self._pkg # 'any' for all builds, 'py27' etc for specifics versions - res = ir.get('any', []) + packages = ir.get('any', []) + if isinstance(packages, string_type): + packages = packages.split() # assume white space separated string + if self.nested: + # parent dir is also a package, make sure it is installed (need its .pth file) + parent_pkg = self.full_package_name.rsplit('.', 1)[0] + if parent_pkg not in packages: + packages.append(parent_pkg) implementation = platform.python_implementation() if implementation == 'CPython': pyver = 'py{0}{1}'.format(*sys.version_info) elif implementation == 'PyPy': pyver = 'pypy' if sys.version_info < (3, ) else 'pypy3' - res.extend(ir.get(pyver, [])) - return res + elif implementation == 'Jython': + pyver = 'jython' + packages.extend(ir.get(pyver, [])) + for p in packages: + # package name starting with * means use local source tree, non-published + # to PyPi or maybe not latest version on PyPI -> pre-install + if p[0] == '*': + p = p[1:] + self._pkg[1].append(p) + self._pkg[0].append(p) + return self._pkg @property def data_files(self): df = self._pkg_data.get('data_files', []) - if self._pkg_data.get('license') is None: + if self.has_mit_lic(): df.append('LICENSE') if not df: return None @@ -367,7 +617,7 @@ class NameSpacePackager(object): @property def package_data(self): df = self._pkg_data.get('data_files', []) - if self._pkg_data.get('license') is None: + if self.has_mit_lic(): # include the file df.append('LICENSE') # but don't install it @@ -384,8 +634,16 @@ class NameSpacePackager(object): return self._ext_modules if '--version' in sys.argv: return None - # if sys.platform == "win32": - # return None + if platform.python_implementation() == 'Jython': + return None + if sys.platform == "win32" and not self._pkg_data.get('win32bin'): + return None + try: + plat = sys.argv.index('--plat-name') + if 'win' in sys.argv[plat + 1]: + return None + except ValueError: + pass import tempfile import shutil from textwrap import dedent @@ -444,23 +702,20 @@ class NameSpacePackager(object): shutil.rmtree(tmp_dir) return self._ext_modules - # If you only support an extension module on Linux, Windows thinks it is - # pure. That way you would get pure python .whl files that take precedence - # for downloading on Linux over source with compilable C - # if '--universal' in sys.argv: - # Distribution.is_pure = lambda *args: True - # Distribution.is_pure = lambda *args: False def wheel(self, kw, setup): """temporary add setup.cfg if creating a wheel to include LICENSE file https://bitbucket.org/pypa/wheel/issues/47 """ - if 'bdist_wheel' not in sys.argv or not os.path.exists('LICENSE'): + if 'bdist_wheel' not in sys.argv: return file_name = 'setup.cfg' if os.path.exists(file_name): # add it if not in there? return with open(file_name, 'w') as fp: - fp.write('[metadata]\nlicense-file = LICENSE\n') + if os.path.exists('LICENSE'): + fp.write('[metadata]\nlicense-file = LICENSE\n') + else: + print("\n\n>>>>>> LICENSE file not found <<<<<\n\n") if self._pkg_data.get('universal'): fp.write('[bdist_wheel]\nuniversal = 1\n') try: @@ -474,6 +729,13 @@ class NameSpacePackager(object): # # call setup def main(): + dump_kw = '--dump-kw' + if dump_kw in sys.argv: + import wheel + import distutils + print('python: ', sys.version) + print('distutils:', distutils.__version__) + print('wheel: ', wheel.__version__) nsp = NameSpacePackager(pkg_data) nsp.check() nsp.create_dirs() @@ -492,18 +754,50 @@ def main(): install_requires=nsp.install_requires, license=nsp.license, classifiers=nsp.classifiers, + keywords=nsp.keywords, package_data=nsp.package_data, ext_modules=nsp.ext_modules, ) - if '--version' not in sys.argv or '--verbose' in sys.argv: + if '--version' not in sys.argv and ('--verbose' in sys.argv or dump_kw in sys.argv): for k in sorted(kw): v = kw[k] - print(k, '->', v) + print(' "{0}": "{1}",'.format(k, v)) + if dump_kw in sys.argv: + sys.argv.remove(dump_kw) with open('README.rst') as fp: kw['long_description'] = fp.read() if nsp.wheel(kw, setup): return + for x in ['-c', 'egg_info', '--egg-base', 'pip-egg-info']: + if x not in sys.argv: + break + else: + # we're doing a tox setup install any starred package by searching up the source tree + # until you match your/package/name for your.package.name + for p in nsp.install_pre: + import subprocess + # search other source + setup_path = os.path.join(*p.split('.') + ['setup.py']) + try_dir = os.path.dirname(sys.executable) + while len(try_dir) > 1: + full_path_setup_py = os.path.join(try_dir, setup_path) + if os.path.exists(full_path_setup_py): + pip = sys.executable.replace('python', 'pip') + cmd = [pip, 'install', os.path.dirname(full_path_setup_py)] + # with open('/var/tmp/notice', 'a') as fp: + # print('installing', cmd, file=fp) + subprocess.check_output(cmd) + break + try_dir = os.path.dirname(try_dir) setup(**kw) - + if nsp.nested and sys.argv[:2] == ['-c', 'bdist_wheel']: + d = sys.argv[sys.argv.index('-d')+1] + for x in os.listdir(d): + if x.endswith('.whl'): + # remove .pth file from the wheel + full_name = os.path.join(d, x) + with InMemoryZipFile(full_name) as imz: + imz.delete_from_zip_file(nsp.full_package_name + '.*.pth') + break main() diff --git a/tox.ini b/tox.ini index f0d6a5d..e1ba3bc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,12 @@ [tox] -envlist = pep8,py27,py34,py26,py33 +envlist = pep8,py35,py27,py34,pypy [testenv] commands = py.test _test deps = pytest - flake8 + flake8==2.5.5 [testenv:pep8] commands = @@ -14,4 +14,5 @@ commands = [flake8] show-source = True +max-line-length = 95 exclude = .hg,.git,.tox,dist,.cache,__pycache__,ruamel.zip2tar.egg-info -- cgit v1.2.1