From 4f0fd085c59ec40e532b897a78f4c4a86e1d42bb Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 25 Nov 2016 13:31:20 +0000 Subject: [PATCH 01/41] Vendor pytoml==0.1.11 This looks like the best maintained TOML parser for Python at present; it should be easy to switch if another one turns out to be preferable. --- pip/_vendor/pytoml/__init__.py | 3 + pip/_vendor/pytoml/core.py | 13 ++ pip/_vendor/pytoml/parser.py | 366 +++++++++++++++++++++++++++++++++ pip/_vendor/pytoml/writer.py | 121 +++++++++++ pip/_vendor/vendor.txt | 1 + 5 files changed, 504 insertions(+) create mode 100644 pip/_vendor/pytoml/__init__.py create mode 100644 pip/_vendor/pytoml/core.py create mode 100644 pip/_vendor/pytoml/parser.py create mode 100644 pip/_vendor/pytoml/writer.py diff --git a/pip/_vendor/pytoml/__init__.py b/pip/_vendor/pytoml/__init__.py new file mode 100644 index 00000000000..222a1967fde --- /dev/null +++ b/pip/_vendor/pytoml/__init__.py @@ -0,0 +1,3 @@ +from .core import TomlError +from .parser import load, loads +from .writer import dump, dumps diff --git a/pip/_vendor/pytoml/core.py b/pip/_vendor/pytoml/core.py new file mode 100644 index 00000000000..0fcada48c64 --- /dev/null +++ b/pip/_vendor/pytoml/core.py @@ -0,0 +1,13 @@ +class TomlError(RuntimeError): + def __init__(self, message, line, col, filename): + RuntimeError.__init__(self, message, line, col, filename) + self.message = message + self.line = line + self.col = col + self.filename = filename + + def __str__(self): + return '{}({}, {}): {}'.format(self.filename, self.line, self.col, self.message) + + def __repr__(self): + return 'TomlError({!r}, {!r}, {!r}, {!r})'.format(self.message, self.line, self.col, self.filename) diff --git a/pip/_vendor/pytoml/parser.py b/pip/_vendor/pytoml/parser.py new file mode 100644 index 00000000000..d4c4ac187bf --- /dev/null +++ b/pip/_vendor/pytoml/parser.py @@ -0,0 +1,366 @@ +import string, re, sys, datetime +from .core import TomlError + +if sys.version_info[0] == 2: + _chr = unichr +else: + _chr = chr + +def load(fin, translate=lambda t, x, v: v): + return loads(fin.read(), translate=translate, filename=fin.name) + +def loads(s, filename='', translate=lambda t, x, v: v): + if isinstance(s, bytes): + s = s.decode('utf-8') + + s = s.replace('\r\n', '\n') + + root = {} + tables = {} + scope = root + + src = _Source(s, filename=filename) + ast = _p_toml(src) + + def error(msg): + raise TomlError(msg, pos[0], pos[1], filename) + + def process_value(v): + kind, text, value, pos = v + if kind == 'str' and value.startswith('\n'): + value = value[1:] + if kind == 'array': + if value and any(k != value[0][0] for k, t, v, p in value[1:]): + error('array-type-mismatch') + value = [process_value(item) for item in value] + elif kind == 'table': + value = dict([(k, process_value(value[k])) for k in value]) + return translate(kind, text, value) + + for kind, value, pos in ast: + if kind == 'kv': + k, v = value + if k in scope: + error('duplicate_keys. Key "{0}" was used more than once.'.format(k)) + scope[k] = process_value(v) + else: + is_table_array = (kind == 'table_array') + cur = tables + for name in value[:-1]: + if isinstance(cur.get(name), list): + d, cur = cur[name][-1] + else: + d, cur = cur.setdefault(name, (None, {})) + + scope = {} + name = value[-1] + if name not in cur: + if is_table_array: + cur[name] = [(scope, {})] + else: + cur[name] = (scope, {}) + elif isinstance(cur[name], list): + if not is_table_array: + error('table_type_mismatch') + cur[name].append((scope, {})) + else: + if is_table_array: + error('table_type_mismatch') + old_scope, next_table = cur[name] + if old_scope is not None: + error('duplicate_tables') + cur[name] = (scope, next_table) + + def merge_tables(scope, tables): + if scope is None: + scope = {} + for k in tables: + if k in scope: + error('key_table_conflict') + v = tables[k] + if isinstance(v, list): + scope[k] = [merge_tables(sc, tbl) for sc, tbl in v] + else: + scope[k] = merge_tables(v[0], v[1]) + return scope + + return merge_tables(root, tables) + +class _Source: + def __init__(self, s, filename=None): + self.s = s + self._pos = (1, 1) + self._last = None + self._filename = filename + self.backtrack_stack = [] + + def last(self): + return self._last + + def pos(self): + return self._pos + + def fail(self): + return self._expect(None) + + def consume_dot(self): + if self.s: + self._last = self.s[0] + self.s = self[1:] + self._advance(self._last) + return self._last + return None + + def expect_dot(self): + return self._expect(self.consume_dot()) + + def consume_eof(self): + if not self.s: + self._last = '' + return True + return False + + def expect_eof(self): + return self._expect(self.consume_eof()) + + def consume(self, s): + if self.s.startswith(s): + self.s = self.s[len(s):] + self._last = s + self._advance(s) + return True + return False + + def expect(self, s): + return self._expect(self.consume(s)) + + def consume_re(self, re): + m = re.match(self.s) + if m: + self.s = self.s[len(m.group(0)):] + self._last = m + self._advance(m.group(0)) + return m + return None + + def expect_re(self, re): + return self._expect(self.consume_re(re)) + + def __enter__(self): + self.backtrack_stack.append((self.s, self._pos)) + + def __exit__(self, type, value, traceback): + if type is None: + self.backtrack_stack.pop() + else: + self.s, self._pos = self.backtrack_stack.pop() + return type == TomlError + + def commit(self): + self.backtrack_stack[-1] = (self.s, self._pos) + + def _expect(self, r): + if not r: + raise TomlError('msg', self._pos[0], self._pos[1], self._filename) + return r + + def _advance(self, s): + suffix_pos = s.rfind('\n') + if suffix_pos == -1: + self._pos = (self._pos[0], self._pos[1] + len(s)) + else: + self._pos = (self._pos[0] + s.count('\n'), len(s) - suffix_pos) + +_ews_re = re.compile(r'(?:[ \t]|#[^\n]*\n|#[^\n]*\Z|\n)*') +def _p_ews(s): + s.expect_re(_ews_re) + +_ws_re = re.compile(r'[ \t]*') +def _p_ws(s): + s.expect_re(_ws_re) + +_escapes = { 'b': '\b', 'n': '\n', 'r': '\r', 't': '\t', '"': '"', '\'': '\'', + '\\': '\\', '/': '/', 'f': '\f' } + +_basicstr_re = re.compile(r'[^"\\\000-\037]*') +_short_uni_re = re.compile(r'u([0-9a-fA-F]{4})') +_long_uni_re = re.compile(r'U([0-9a-fA-F]{8})') +_escapes_re = re.compile('[bnrt"\'\\\\/f]') +_newline_esc_re = re.compile('\n[ \t\n]*') +def _p_basicstr_content(s, content=_basicstr_re): + res = [] + while True: + res.append(s.expect_re(content).group(0)) + if not s.consume('\\'): + break + if s.consume_re(_newline_esc_re): + pass + elif s.consume_re(_short_uni_re) or s.consume_re(_long_uni_re): + res.append(_chr(int(s.last().group(1), 16))) + else: + s.expect_re(_escapes_re) + res.append(_escapes[s.last().group(0)]) + return ''.join(res) + +_key_re = re.compile(r'[0-9a-zA-Z-_]+') +def _p_key(s): + with s: + s.expect('"') + r = _p_basicstr_content(s, _basicstr_re) + s.expect('"') + return r + return s.expect_re(_key_re).group(0) + +_float_re = re.compile(r'[+-]?(?:0|[1-9](?:_?\d)*)(?:\.\d(?:_?\d)*)?(?:[eE][+-]?(?:\d(?:_?\d)*))?') +_datetime_re = re.compile(r'(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(?:Z|([+-]\d{2}):(\d{2}))') + +_basicstr_ml_re = re.compile(r'(?:(?:|"|"")[^"\\\000-\011\013-\037])*') +_litstr_re = re.compile(r"[^'\000-\037]*") +_litstr_ml_re = re.compile(r"(?:(?:|'|'')(?:[^'\000-\011\013-\037]))*") +def _p_value(s): + pos = s.pos() + + if s.consume('true'): + return 'bool', s.last(), True, pos + if s.consume('false'): + return 'bool', s.last(), False, pos + + if s.consume('"'): + if s.consume('""'): + r = _p_basicstr_content(s, _basicstr_ml_re) + s.expect('"""') + else: + r = _p_basicstr_content(s, _basicstr_re) + s.expect('"') + return 'str', r, r, pos + + if s.consume('\''): + if s.consume('\'\''): + r = s.expect_re(_litstr_ml_re).group(0) + s.expect('\'\'\'') + else: + r = s.expect_re(_litstr_re).group(0) + s.expect('\'') + return 'str', r, r, pos + + if s.consume_re(_datetime_re): + m = s.last() + s0 = m.group(0) + r = map(int, m.groups()[:6]) + if m.group(7): + micro = float(m.group(7)) + else: + micro = 0 + + if m.group(8): + g = int(m.group(8), 10) * 60 + int(m.group(9), 10) + tz = _TimeZone(datetime.timedelta(0, g * 60)) + else: + tz = _TimeZone(datetime.timedelta(0, 0)) + + y, m, d, H, M, S = r + dt = datetime.datetime(y, m, d, H, M, S, int(micro * 1000000), tz) + return 'datetime', s0, dt, pos + + if s.consume_re(_float_re): + m = s.last().group(0) + r = m.replace('_','') + if '.' in m or 'e' in m or 'E' in m: + return 'float', m, float(r), pos + else: + return 'int', m, int(r, 10), pos + + if s.consume('['): + items = [] + with s: + while True: + _p_ews(s) + items.append(_p_value(s)) + s.commit() + _p_ews(s) + s.expect(',') + s.commit() + _p_ews(s) + s.expect(']') + return 'array', None, items, pos + + if s.consume('{'): + _p_ws(s) + items = {} + if not s.consume('}'): + k = _p_key(s) + _p_ws(s) + s.expect('=') + _p_ws(s) + items[k] = _p_value(s) + _p_ws(s) + while s.consume(','): + _p_ws(s) + k = _p_key(s) + _p_ws(s) + s.expect('=') + _p_ws(s) + items[k] = _p_value(s) + _p_ws(s) + s.expect('}') + return 'table', None, items, pos + + s.fail() + +def _p_stmt(s): + pos = s.pos() + if s.consume( '['): + is_array = s.consume('[') + _p_ws(s) + keys = [_p_key(s)] + _p_ws(s) + while s.consume('.'): + _p_ws(s) + keys.append(_p_key(s)) + _p_ws(s) + s.expect(']') + if is_array: + s.expect(']') + return 'table_array' if is_array else 'table', keys, pos + + key = _p_key(s) + _p_ws(s) + s.expect('=') + _p_ws(s) + value = _p_value(s) + return 'kv', (key, value), pos + +_stmtsep_re = re.compile(r'(?:[ \t]*(?:#[^\n]*)?\n)+[ \t]*') +def _p_toml(s): + stmts = [] + _p_ews(s) + with s: + stmts.append(_p_stmt(s)) + while True: + s.commit() + s.expect_re(_stmtsep_re) + stmts.append(_p_stmt(s)) + _p_ews(s) + s.expect_eof() + return stmts + +class _TimeZone(datetime.tzinfo): + def __init__(self, offset): + self._offset = offset + + def utcoffset(self, dt): + return self._offset + + def dst(self, dt): + return None + + def tzname(self, dt): + m = self._offset.total_seconds() // 60 + if m < 0: + res = '-' + m = -m + else: + res = '+' + h = m // 60 + m = m - h * 60 + return '{}{:.02}{:.02}'.format(res, h, m) diff --git a/pip/_vendor/pytoml/writer.py b/pip/_vendor/pytoml/writer.py new file mode 100644 index 00000000000..e2ad8ab2fe1 --- /dev/null +++ b/pip/_vendor/pytoml/writer.py @@ -0,0 +1,121 @@ +from __future__ import unicode_literals +import io, datetime, sys + +if sys.version_info[0] == 3: + long = int + unicode = str + + +def dumps(obj, sort_keys=False): + fout = io.StringIO() + dump(fout, obj, sort_keys=sort_keys) + return fout.getvalue() + + +_escapes = {'\n': 'n', '\r': 'r', '\\': '\\', '\t': 't', '\b': 'b', '\f': 'f', '"': '"'} + + +def _escape_string(s): + res = [] + start = 0 + + def flush(): + if start != i: + res.append(s[start:i]) + return i + 1 + + i = 0 + while i < len(s): + c = s[i] + if c in '"\\\n\r\t\b\f': + start = flush() + res.append('\\' + _escapes[c]) + elif ord(c) < 0x20: + start = flush() + res.append('\\u%04x' % ord(c)) + i += 1 + + flush() + return '"' + ''.join(res) + '"' + + +def _escape_id(s): + if any(not c.isalnum() and c not in '-_' for c in s): + return _escape_string(s) + return s + + +def _format_list(v): + return '[{0}]'.format(', '.join(_format_value(obj) for obj in v)) + +# Formula from: +# https://docs.python.org/2/library/datetime.html#datetime.timedelta.total_seconds +# Once support for py26 is dropped, this can be replaced by td.total_seconds() +def _total_seconds(td): + return ((td.microseconds + + (td.seconds + td.days * 24 * 3600) * 10**6) / 10.0**6) + +def _format_value(v): + if isinstance(v, bool): + return 'true' if v else 'false' + if isinstance(v, int) or isinstance(v, long): + return unicode(v) + if isinstance(v, float): + return '{0:.17f}'.format(v) + elif isinstance(v, unicode) or isinstance(v, bytes): + return _escape_string(v) + elif isinstance(v, datetime.datetime): + offs = v.utcoffset() + offs = _total_seconds(offs) // 60 if offs is not None else 0 + + if offs == 0: + suffix = 'Z' + else: + if offs > 0: + suffix = '+' + else: + suffix = '-' + offs = -offs + suffix = '{0}{1:.02}{2:.02}'.format(suffix, offs // 60, offs % 60) + + if v.microsecond: + return v.strftime('%Y-%m-%dT%H:%M:%S.%f') + suffix + else: + return v.strftime('%Y-%m-%dT%H:%M:%S') + suffix + elif isinstance(v, list): + return _format_list(v) + else: + raise RuntimeError(v) + + +def dump(fout, obj, sort_keys=False): + tables = [((), obj, False)] + + while tables: + name, table, is_array = tables.pop() + if name: + section_name = '.'.join(_escape_id(c) for c in name) + if is_array: + fout.write('[[{0}]]\n'.format(section_name)) + else: + fout.write('[{0}]\n'.format(section_name)) + + table_keys = sorted(table.keys()) if sort_keys else table.keys() + new_tables = [] + for k in table_keys: + v = table[k] + if isinstance(v, dict): + new_tables.append((name + (k,), v, False)) + elif isinstance(v, list) and v and all(isinstance(o, dict) for o in v): + new_tables.extend((name + (k,), d, True) for d in v) + elif v is None: + # based on mojombo's comment: https://github.com/toml-lang/toml/issues/146#issuecomment-25019344 + fout.write( + '#{} = null # To use: uncomment and replace null with value\n'.format(_escape_id(k))) + else: + fout.write('{0} = {1}\n'.format(_escape_id(k), _format_value(v))) + + tables.extend(reversed(new_tables)) + + if tables: + fout.write('\n') diff --git a/pip/_vendor/vendor.txt b/pip/_vendor/vendor.txt index 9b8709c67fa..06e19911124 100644 --- a/pip/_vendor/vendor.txt +++ b/pip/_vendor/vendor.txt @@ -11,6 +11,7 @@ progress==1.2 ipaddress==1.0.18 # Only needed on 2.6 and 2.7 packaging==16.8 pyparsing==2.2.0 +pytoml==0.1.11 retrying==1.3.3 requests==2.13.0 setuptools==34.3.3 From c24f432c3354f861f70ea54d2039efdcf57a8eca Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 25 Nov 2016 14:14:03 +0000 Subject: [PATCH 02/41] Add property to locate pyproject_toml in downloaded source package --- pip/req/req_install.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pip/req/req_install.py b/pip/req/req_install.py index 0f935ffaf66..1a8aabee816 100644 --- a/pip/req/req_install.py +++ b/pip/req/req_install.py @@ -433,6 +433,18 @@ def setup_py(self): return setup_py + @property + def pyproject_toml(self): + assert self.source_dir, "No source dir for %s" % self + + pp_toml = os.path.join(self.setup_py_dir, 'pyproject.toml') + + # Python2 __file__ should not be unicode + if six.PY2 and isinstance(pp_toml, six.text_type): + pp_toml = pp_toml.encode(sys.getfilesystemencoding()) + + return pp_toml + def run_egg_info(self): assert self.source_dir if self.name: From 779bbaa328ba6e9697d7a326243e3453a706e9e4 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 29 Nov 2016 13:30:15 +0000 Subject: [PATCH 03/41] Temporary prefix for installing build dependencies --- pip/wheel.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/pip/wheel.py b/pip/wheel.py index 1a366047800..45ba1b82ec8 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -14,6 +14,7 @@ import shutil import stat import sys +import sysconfig import tempfile import warnings @@ -631,6 +632,55 @@ def supported(self, tags=None): return bool(set(tags).intersection(self.file_tags)) +class BuildEnvironment(object): + """Context manager to install build deps in a simple temporary environment + """ + def __init__(self): + self.prefix = tempfile.mkdtemp('pip-build-env-') + + def __enter__(self): + self.save_path = os.environ.get('PATH', None) + self.save_pythonpath = os.environ.get('PYTHONPATH', None) + + install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix' + install_dirs = sysconfig.get_paths(install_scheme, vars={ + 'base': self.prefix, + 'platbase': self.prefix, + }) + + scripts = install_dirs['scripts'] + if self.save_path: + os.environ['PATH'] = scripts + os.pathsep + self.save_path + else: + os.environ['PATH'] = scripts + os.pathsep + os.defpath + + if install_dirs['purelib'] == install_dirs['platlib']: + lib_dirs = install_dirs['purelib'] + else: + lib_dirs = install_dirs['purelib'] + os.pathsep + install_dirs['platlib'] + if self.save_pythonpath: + os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + self.save_pythonpath + else: + os.environ['PYTHONPATH'] = lib_dirs + + return self.prefix + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.save_path is None: + os.environ.pop('PATH', None) + else: + os.environ['PATH'] = self.save_path + + if self.save_pythonpath is None: + os.environ.pop('PYTHONPATH', None) + else: + os.environ['PYTHONPATH'] = self.save_pythonpath + + rmtree(self.prefix) + + return False # Do not suppress exceptions + + class WheelBuilder(object): """Build wheels from a RequirementSet.""" From b31c87f1df8ea2e20b49469e41fbfded524ec861 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 29 Nov 2016 13:41:39 +0000 Subject: [PATCH 04/41] Install build requirements for projects using PEP 518 --- pip/wheel.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pip/wheel.py b/pip/wheel.py index 45ba1b82ec8..1b717a66e83 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -39,6 +39,7 @@ from pip._vendor.distlib.scripts import ScriptMaker from pip._vendor import pkg_resources from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor import pytoml wheel_ext = '.whl' @@ -693,11 +694,43 @@ def __init__(self, requirement_set, finder, build_options=None, self.build_options = build_options or [] self.global_options = global_options or [] + def _find_build_reqs(self, req): + """Get a list of the packages required to build the project, if any. + + Build requirements can be specified in a pyproject.toml, as described + in PEP 518 + """ + if os.path.isfile(req.pyproject_toml): + with open(req.pyproject_toml) as f: + pp_toml = pytoml.load(f) + return pp_toml.get('build-system', {}).get('requires', []) + + return [] # No pyproject.toml + + def _install_build_reqs(self, reqs, prefix): + args = [sys.executable, '-m', 'pip', '--prefix', prefix] + list(reqs) + with open_spinner("Installing build dependencies") as spinner: + call_subprocess(args, show_stdout=False, spinner=spinner) + def _build_one(self, req, output_dir, python_tag=None): """Build one wheel. :return: The filename of the built wheel, or None if the build failed. """ + build_reqs = self._find_build_reqs(req) + + if build_reqs: + # Install build deps into temporary prefix (PEP 518) + with BuildEnvironment() as prefix: + self._install_build_reqs(build_reqs, prefix) + return self._build_one_inside_env(req, output_dir, + python_tag=python_tag) + else: + # Old style build, in the current environment + return self._build_one_inside_env(req, output_dir, + python_tag=python_tag) + + def _build_one_inside_env(self, req, output_dir, python_tag=None): tempd = tempfile.mkdtemp('pip-wheel-') try: if self.__build_one(req, tempd, python_tag=python_tag): From 92bfdf702f2cc867f27e98d3eae1e930bdd25edc Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 29 Nov 2016 13:52:43 +0000 Subject: [PATCH 05/41] Isolate wheel builds when using PEP 518 build dependencies --- pip/wheel.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index 1b717a66e83..9daa10070fd 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -724,16 +724,18 @@ def _build_one(self, req, output_dir, python_tag=None): with BuildEnvironment() as prefix: self._install_build_reqs(build_reqs, prefix) return self._build_one_inside_env(req, output_dir, - python_tag=python_tag) + python_tag=python_tag, isolate=True) else: # Old style build, in the current environment return self._build_one_inside_env(req, output_dir, python_tag=python_tag) - def _build_one_inside_env(self, req, output_dir, python_tag=None): + def _build_one_inside_env(self, req, output_dir, python_tag=None, + isolate=False): tempd = tempfile.mkdtemp('pip-wheel-') try: - if self.__build_one(req, tempd, python_tag=python_tag): + if self.__build_one(req, tempd, python_tag=python_tag, + isolate=isolate): try: wheel_name = os.listdir(tempd)[0] wheel_path = os.path.join(output_dir, wheel_name) @@ -748,14 +750,17 @@ def _build_one_inside_env(self, req, output_dir, python_tag=None): finally: rmtree(tempd) - def _base_setup_args(self, req): + def _base_setup_args(self, req, isolate=False): + flags = '-u' + if isolate: + flags += 'S' return [ - sys.executable, "-u", '-c', + sys.executable, flags, '-c', SETUPTOOLS_SHIM % req.setup_py ] + list(self.global_options) - def __build_one(self, req, tempd, python_tag=None): - base_args = self._base_setup_args(req) + def __build_one(self, req, tempd, python_tag=None, isolate=False): + base_args = self._base_setup_args(req, isolate=isolate) spin_message = 'Running setup.py bdist_wheel for %s' % (req.name,) with open_spinner(spin_message) as spinner: @@ -766,8 +771,13 @@ def __build_one(self, req, tempd, python_tag=None): if python_tag is not None: wheel_args += ["--python-tag", python_tag] + env = {} + if isolate: + env['PYTHONNOUSERSITE'] = '1' + try: call_subprocess(wheel_args, cwd=req.setup_py_dir, + extra_environ=env, show_stdout=False, spinner=spinner) return True except: From 3da2cef247176865b85f7409d15667ed27a2ca89 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 29 Nov 2016 18:09:21 +0000 Subject: [PATCH 06/41] Hopefully satisfy pep8 --- pip/wheel.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index 9daa10070fd..fef0648e3f2 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -658,9 +658,11 @@ def __enter__(self): if install_dirs['purelib'] == install_dirs['platlib']: lib_dirs = install_dirs['purelib'] else: - lib_dirs = install_dirs['purelib'] + os.pathsep + install_dirs['platlib'] + lib_dirs = install_dirs['purelib'] + os.pathsep + \ + install_dirs['platlib'] if self.save_pythonpath: - os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + self.save_pythonpath + os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \ + self.save_pythonpath else: os.environ['PYTHONPATH'] = lib_dirs @@ -724,7 +726,8 @@ def _build_one(self, req, output_dir, python_tag=None): with BuildEnvironment() as prefix: self._install_build_reqs(build_reqs, prefix) return self._build_one_inside_env(req, output_dir, - python_tag=python_tag, isolate=True) + python_tag=python_tag, + isolate=True) else: # Old style build, in the current environment return self._build_one_inside_env(req, output_dir, From 32eebbfaf97733a64e55e212fbba3ee71a9a4980 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 29 Nov 2016 18:33:30 +0000 Subject: [PATCH 07/41] Compatibility fallback for absent sysconfig module on Python 2.6 --- pip/wheel.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index fef0648e3f2..2404731031f 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -14,7 +14,6 @@ import shutil import stat import sys -import sysconfig import tempfile import warnings @@ -41,6 +40,31 @@ from pip._vendor.packaging.utils import canonicalize_name from pip._vendor import pytoml +try: + from sysconfig import get_paths +except ImportError: + # This section is for compatibility with Python 2.6, and can be removed once + # Python 2.7 is the minimum supported version. + sysconfig = None + + def get_paths(install_scheme, vars): + prefix = vars['base'] + if os.name == 'nt': + return { + 'purelib': '{0}/Lib/site-packages'.format(prefix), + 'platlib': '{0}/Lib/site-packages'.format(prefix), + 'scripts': '{0}/Scripts'.format(prefix), + } + else: + py_version_short = '{0}.{1}'.format(*sys.version_info[:2]) + return { + 'purelib': '{0}/lib/python{1}/site-packages'.format( + prefix, py_version_short), + 'platlib': '{0}/lib/python{1}/site-packages'.format( + prefix, py_version_short), + 'scripts': '{0}/bin'.format(prefix), + } + wheel_ext = '.whl' @@ -644,7 +668,7 @@ def __enter__(self): self.save_pythonpath = os.environ.get('PYTHONPATH', None) install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix' - install_dirs = sysconfig.get_paths(install_scheme, vars={ + install_dirs = get_paths(install_scheme, vars={ 'base': self.prefix, 'platbase': self.prefix, }) From 191d68001ee0c88d193ac45f91d773d00035e11b Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 2 Dec 2016 15:13:52 +0000 Subject: [PATCH 08/41] Let's see if that keeps PEP8 happy If anything, the indentation changes make the code less readable. But the hobgoblin says this is the right way to do it... --- pip/wheel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index 2404731031f..b93f77ecb93 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -43,8 +43,8 @@ try: from sysconfig import get_paths except ImportError: - # This section is for compatibility with Python 2.6, and can be removed once - # Python 2.7 is the minimum supported version. + # This section is for compatibility with Python 2.6, and can be removed + # once Python 2.7 is the minimum supported version. sysconfig = None def get_paths(install_scheme, vars): @@ -683,10 +683,10 @@ def __enter__(self): lib_dirs = install_dirs['purelib'] else: lib_dirs = install_dirs['purelib'] + os.pathsep + \ - install_dirs['platlib'] + install_dirs['platlib'] if self.save_pythonpath: os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \ - self.save_pythonpath + self.save_pythonpath else: os.environ['PYTHONPATH'] = lib_dirs From b4f65bfc7cb5c3e8eaf1d28682acb99ab227b776 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 2 Dec 2016 16:37:55 +0000 Subject: [PATCH 09/41] Try adding smoketest of pep518 build dependency installation --- tests/data/packages/pep518-3.0.tar.gz | Bin 0 -> 713 bytes tests/functional/test_wheel.py | 13 +++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 tests/data/packages/pep518-3.0.tar.gz diff --git a/tests/data/packages/pep518-3.0.tar.gz b/tests/data/packages/pep518-3.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..f133bc01819813bf2964adffd0a44e3d6880552c GIT binary patch literal 713 zcmV;)0yh00iwFoPqd`~#|72-%bT4paa5XVFEi*1KE_7jX0PUGuZ<|06g?Z*zY}JP( zQf!vxRz>nd)w|N#QLVHORfRTY?V@lKELQ#RyFjrWw@w<>U`0CL3ovVZ(absQj5Cor z=8J(fq=wcNRN;9}bu>MPZXa7T)1r)8%(SbxY1^)?5l8ERvXDF{M3ekBDpyO9?|I!r z>+KI|k57bblm9}8p~!#dd=ra>A13qkyDs^{zbV5g zAR|&=1Y8PrPV0+enfSR_hDDerbx_y9(`QTn&FN4T#Gon&aT4!ipI zlqEp_edxay9rkk2zws{?-jV*z9sOI(v7!GyfbyaLJ^F9=eTSL<+_(RC*^d4x=0830 zNk|@W$p>FVUX6Axh&iPCn8)IRGzForS5%Sc(=v{EzPcdSsWuE#$u51h1i5% zmhwlM?_Li?%)@9C{TR+fQf%G!S`@Q9%w+vpZSX4Ma-Qb#CIZcNr2n=7JoslkH2-DH z-qOEW&3{ew|NZd%nNb$GaT6wnNR}j9DXAv Date: Fri, 2 Dec 2016 17:21:35 +0000 Subject: [PATCH 10/41] Try to fix pep518 test --- tests/data/packages/pep518-3.0.tar.gz | Bin 713 -> 831 bytes tests/functional/test_wheel.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data/packages/pep518-3.0.tar.gz b/tests/data/packages/pep518-3.0.tar.gz index f133bc01819813bf2964adffd0a44e3d6880552c..88e67a6101444616680e173ebafad5bc8c9d7a4e 100644 GIT binary patch literal 831 zcmV-F1Hk+riwFoOtwC4<|72-%bT4paa5XVFEi*1KE_7jX0PUGuZ<{a_$9d*cc-=!= zsR7$SJXOj=wRKz7Ev=Syd#EZzgOhpXHZaln_A^j&+mfzQpz3yh55X4*iO%^SA0H8q_u5`Lm?|x@jnf=#>s(?o|Jei~o!g3*!G1+kbR% z_38EL$dEtf4&r}*bNr`l&Hj{N2JycWKAsHE-ky#=8zRz^B1z-#d@Kzahk-uN2ku)* zp|88h<@vkb+3@X!zQuQboAY_XBcDg(!VN@pm;Lc387FSQXFRB8GZ7{MH(j+-3F*4( zr$FRxUH>a4XjA`cby(7Wztlgo3FyBIYGpw#2l_YvCQi4cf3l{3)xe>CKbM~GNw4>Xr)6p97!d>OLoejVsCEc_KBJsOALU`_ zrNuG28lDd?z7F;Gamag#H|2|=lU#m_)AfFjhn@%)qaVbWN7>S2Z+JFNMIx(rHG`Ld zCns?lE=EAJE$P46!pyVOycLnjqZvvHCDllu zq;ZHUe~@t;WJqli;#AfnM`$7<-%Y&nol?vQ)uCgsul#F1 z*gQE{`JRKr`{_ea_!H^BF0yE?=>Ku`UxzUa_1~wl|L%)y51#*(^&g6uAJqH&P>9Ic)Gf-wF+ z{qL;a`&j+2)_<%0pTlg>e-}Xg7XSbN0000000000000000000000000005pZzW}-4 J?&1JY002&qt8@SW literal 713 zcmV;)0yh00iwFoPqd`~#|72-%bT4paa5XVFEi*1KE_7jX0PUGuZ<|06g?Z*zY}JP( zQf!vxRz>nd)w|N#QLVHORfRTY?V@lKELQ#RyFjrWw@w<>U`0CL3ovVZ(absQj5Cor z=8J(fq=wcNRN;9}bu>MPZXa7T)1r)8%(SbxY1^)?5l8ERvXDF{M3ekBDpyO9?|I!r z>+KI|k57bblm9}8p~!#dd=ra>A13qkyDs^{zbV5g zAR|&=1Y8PrPV0+enfSR_hDDerbx_y9(`QTn&FN4T#Gon&aT4!ipI zlqEp_edxay9rkk2zws{?-jV*z9sOI(v7!GyfbyaLJ^F9=eTSL<+_(RC*^d4x=0830 zNk|@W$p>FVUX6Axh&iPCn8)IRGzForS5%Sc(=v{EzPcdSsWuE#$u51h1i5% zmhwlM?_Li?%)@9C{TR+fQf%G!S`@Q9%w+vpZSX4Ma-Qb#CIZcNr2n=7JoslkH2-DH z-qOEW&3{ew|NZd%nNb$GaT6wnNR}j9DXAv Date: Fri, 9 Dec 2016 14:27:47 +0100 Subject: [PATCH 11/41] Correct install command for build deps --- pip/wheel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pip/wheel.py b/pip/wheel.py index b93f77ecb93..be48310b6a1 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -734,7 +734,8 @@ def _find_build_reqs(self, req): return [] # No pyproject.toml def _install_build_reqs(self, reqs, prefix): - args = [sys.executable, '-m', 'pip', '--prefix', prefix] + list(reqs) + args = [sys.executable, '-m', 'pip', 'install', '--prefix', prefix] \ + + list(reqs) with open_spinner("Installing build dependencies") as spinner: call_subprocess(args, show_stdout=False, spinner=spinner) From 8a9d0493e79267bcb36c9eef95d8bbc9da5b5fb4 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 9 Dec 2016 14:05:17 +0000 Subject: [PATCH 12/41] Use finder to find build deps to install --- pip/wheel.py | 4 +++- tests/functional/test_wheel.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index be48310b6a1..ab6084160b5 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -734,8 +734,10 @@ def _find_build_reqs(self, req): return [] # No pyproject.toml def _install_build_reqs(self, reqs, prefix): + urls = [self.finder.find_requirement(r, upgrade=False).url + for r in reqs] args = [sys.executable, '-m', 'pip', 'install', '--prefix', prefix] \ - + list(reqs) + + list(urls) with open_spinner("Installing build dependencies") as spinner: call_subprocess(args, show_stdout=False, spinner=spinner) diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py index 6b5e8b97f0e..758128cf5a2 100644 --- a/tests/functional/test_wheel.py +++ b/tests/functional/test_wheel.py @@ -208,5 +208,5 @@ def test_pip_wheel_with_pep518_build_reqs(script, data): wheel_file_name = 'pep518-3.0-py%s-none-any.whl' % pyversion[0] wheel_file_path = script.scratch / wheel_file_name assert wheel_file_path in result.files_created, result.stdout - assert "Successfully built simple" in result.stdout, result.stdout + assert "Successfully built pep518" in result.stdout, result.stdout assert "Installing build dependencies" in result.stdout, result.stdout From 3a68e97aebfcebcbc1746bcde4a78f360e2a50ba Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 19 Dec 2016 15:22:52 +0000 Subject: [PATCH 13/41] Wrap each build req in an InstallRequirement --- pip/wheel.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pip/wheel.py b/pip/wheel.py index ab6084160b5..aaf6d5d5c9a 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -29,6 +29,7 @@ InstallationError, InvalidWheelFilename, UnsupportedWheel) from pip.locations import distutils_scheme, PIP_DELETE_MARKER_FILENAME from pip import pep425tags +from pip.req.req_install import InstallRequirement from pip.utils import ( call_subprocess, ensure_dir, captured_stdout, rmtree, read_chunks, ) @@ -734,7 +735,8 @@ def _find_build_reqs(self, req): return [] # No pyproject.toml def _install_build_reqs(self, reqs, prefix): - urls = [self.finder.find_requirement(r, upgrade=False).url + urls = [self.finder.find_requirement(InstallRequirement.from_line(r), + upgrade=False).url for r in reqs] args = [sys.executable, '-m', 'pip', 'install', '--prefix', prefix] \ + list(urls) From d0a9b4bc69a5f04acebefa0bb9fb3e32892580c0 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 19 Dec 2016 15:45:36 +0000 Subject: [PATCH 14/41] Make import local to avoid circular import --- pip/wheel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pip/wheel.py b/pip/wheel.py index aaf6d5d5c9a..28033d8ba38 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -29,7 +29,6 @@ InstallationError, InvalidWheelFilename, UnsupportedWheel) from pip.locations import distutils_scheme, PIP_DELETE_MARKER_FILENAME from pip import pep425tags -from pip.req.req_install import InstallRequirement from pip.utils import ( call_subprocess, ensure_dir, captured_stdout, rmtree, read_chunks, ) @@ -735,6 +734,8 @@ def _find_build_reqs(self, req): return [] # No pyproject.toml def _install_build_reqs(self, reqs, prefix): + # Local install to avoid circular import (wheel <-> req_install) + from pip.req.req_install import InstallRequirement urls = [self.finder.find_requirement(InstallRequirement.from_line(r), upgrade=False).url for r in reqs] From b17095730114d03008ff4abc3f55cb75110d2456 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 19 Dec 2016 16:40:22 +0000 Subject: [PATCH 15/41] Disable isolation so packages can use setuptools --- pip/wheel.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index 28033d8ba38..33a00203c82 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -785,8 +785,10 @@ def _build_one_inside_env(self, req, output_dir, python_tag=None, def _base_setup_args(self, req, isolate=False): flags = '-u' - if isolate: - flags += 'S' + # The install process needs to be able to import setuptools from where + # it's installed as a dependency of pip, so skipping isolation for now. + # if isolate: + # flags += 'S' return [ sys.executable, flags, '-c', SETUPTOOLS_SHIM % req.setup_py From ea108f834b2f47d2e2e1503454b3c01d0d765574 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 19 Dec 2016 17:46:02 +0000 Subject: [PATCH 16/41] Use pip.__main__ to invoke pip on Python 2.6 --- pip/wheel.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pip/wheel.py b/pip/wheel.py index 33a00203c82..193e284f5cb 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -739,7 +739,12 @@ def _install_build_reqs(self, reqs, prefix): urls = [self.finder.find_requirement(InstallRequirement.from_line(r), upgrade=False).url for r in reqs] - args = [sys.executable, '-m', 'pip', 'install', '--prefix', prefix] \ + + if sys.version_info >= (2, 7): + mpip = 'pip' + else: + mpip = 'pip.__main__' # Python 2.6 can't execute a package with -m + args = [sys.executable, '-m', mpip, 'install', '--prefix', prefix] \ + list(urls) with open_spinner("Installing build dependencies") as spinner: call_subprocess(args, show_stdout=False, spinner=spinner) From 653e2918464c43b6085eee86a2400f6d469b8ec0 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 18 Jan 2017 10:11:05 +0000 Subject: [PATCH 17/41] Add source of sample distribution for tests --- tests/data/src/pep518-3.0/MANIFEST.in | 1 + tests/data/src/pep518-3.0/pyproject.toml | 2 ++ tests/data/src/pep518-3.0/setup.cfg | 5 +++++ tests/data/src/pep518-3.0/setup.py | 7 +++++++ tests/data/src/pep518-3.0/simple/__init__.py | 1 + 5 files changed, 16 insertions(+) create mode 100644 tests/data/src/pep518-3.0/MANIFEST.in create mode 100644 tests/data/src/pep518-3.0/pyproject.toml create mode 100644 tests/data/src/pep518-3.0/setup.cfg create mode 100644 tests/data/src/pep518-3.0/setup.py create mode 100644 tests/data/src/pep518-3.0/simple/__init__.py diff --git a/tests/data/src/pep518-3.0/MANIFEST.in b/tests/data/src/pep518-3.0/MANIFEST.in new file mode 100644 index 00000000000..bec201fc83b --- /dev/null +++ b/tests/data/src/pep518-3.0/MANIFEST.in @@ -0,0 +1 @@ +include pyproject.toml diff --git a/tests/data/src/pep518-3.0/pyproject.toml b/tests/data/src/pep518-3.0/pyproject.toml new file mode 100644 index 00000000000..ee8a38c7675 --- /dev/null +++ b/tests/data/src/pep518-3.0/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires=["simple==3.0"] diff --git a/tests/data/src/pep518-3.0/setup.cfg b/tests/data/src/pep518-3.0/setup.cfg new file mode 100644 index 00000000000..861a9f55426 --- /dev/null +++ b/tests/data/src/pep518-3.0/setup.cfg @@ -0,0 +1,5 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff --git a/tests/data/src/pep518-3.0/setup.py b/tests/data/src/pep518-3.0/setup.py new file mode 100644 index 00000000000..7ff29e276df --- /dev/null +++ b/tests/data/src/pep518-3.0/setup.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +from setuptools import setup, find_packages + +setup(name='pep518', + version='3.0', + packages=find_packages() + ) diff --git a/tests/data/src/pep518-3.0/simple/__init__.py b/tests/data/src/pep518-3.0/simple/__init__.py new file mode 100644 index 00000000000..7986d11379a --- /dev/null +++ b/tests/data/src/pep518-3.0/simple/__init__.py @@ -0,0 +1 @@ +#dummy From cfbc4d9feaddd07d33106f1e46dbbf3bfb4298ec Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 18 Jan 2017 10:20:03 +0000 Subject: [PATCH 18/41] Use no_clean option for wheel build environment --- pip/commands/wheel.py | 1 + pip/wheel.py | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pip/commands/wheel.py b/pip/commands/wheel.py index a15bcd899fe..97c24404a83 100644 --- a/pip/commands/wheel.py +++ b/pip/commands/wheel.py @@ -165,6 +165,7 @@ def run(self, options, args): finder, build_options=options.build_options or [], global_options=options.global_options or [], + no_clean=options.no_clean, ) if not wb.build(): raise CommandError( diff --git a/pip/wheel.py b/pip/wheel.py index 193e284f5cb..e0686d404a7 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -660,8 +660,9 @@ def supported(self, tags=None): class BuildEnvironment(object): """Context manager to install build deps in a simple temporary environment """ - def __init__(self): + def __init__(self, no_clean=False): self.prefix = tempfile.mkdtemp('pip-build-env-') + self.no_clean = no_clean def __enter__(self): self.save_path = os.environ.get('PATH', None) @@ -703,7 +704,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): else: os.environ['PYTHONPATH'] = self.save_pythonpath - rmtree(self.prefix) + if not self.no_clean: + rmtree(self.prefix) return False # Do not suppress exceptions @@ -712,13 +714,14 @@ class WheelBuilder(object): """Build wheels from a RequirementSet.""" def __init__(self, requirement_set, finder, build_options=None, - global_options=None): + global_options=None, no_clean=False): self.requirement_set = requirement_set self.finder = finder self._cache_root = requirement_set._wheel_cache._cache_dir self._wheel_dir = requirement_set.wheel_download_dir self.build_options = build_options or [] self.global_options = global_options or [] + self.no_clean = no_clean def _find_build_reqs(self, req): """Get a list of the packages required to build the project, if any. @@ -758,7 +761,7 @@ def _build_one(self, req, output_dir, python_tag=None): if build_reqs: # Install build deps into temporary prefix (PEP 518) - with BuildEnvironment() as prefix: + with BuildEnvironment(no_clean=self.no_clean) as prefix: self._install_build_reqs(build_reqs, prefix) return self._build_one_inside_env(req, output_dir, python_tag=python_tag, From a8cdd4ab7f360129f2856118dc0d1ea168d9bf4f Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 26 Jan 2017 11:04:10 +0000 Subject: [PATCH 19/41] Fix thinko --- pip/wheel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/wheel.py b/pip/wheel.py index e0686d404a7..e58926f4e3d 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -737,7 +737,7 @@ def _find_build_reqs(self, req): return [] # No pyproject.toml def _install_build_reqs(self, reqs, prefix): - # Local install to avoid circular import (wheel <-> req_install) + # Local import to avoid circular import (wheel <-> req_install) from pip.req.req_install import InstallRequirement urls = [self.finder.find_requirement(InstallRequirement.from_line(r), upgrade=False).url From 094b44191a98b30bff98b6817dcd731ebbcefd01 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sat, 1 Apr 2017 19:28:14 +0100 Subject: [PATCH 20/41] Python 2.6 is no longer supported :-) Also corrected a comment --- pip/wheel.py | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index e58926f4e3d..b2e6f5d7329 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -40,30 +40,7 @@ from pip._vendor.packaging.utils import canonicalize_name from pip._vendor import pytoml -try: - from sysconfig import get_paths -except ImportError: - # This section is for compatibility with Python 2.6, and can be removed - # once Python 2.7 is the minimum supported version. - sysconfig = None - - def get_paths(install_scheme, vars): - prefix = vars['base'] - if os.name == 'nt': - return { - 'purelib': '{0}/Lib/site-packages'.format(prefix), - 'platlib': '{0}/Lib/site-packages'.format(prefix), - 'scripts': '{0}/Scripts'.format(prefix), - } - else: - py_version_short = '{0}.{1}'.format(*sys.version_info[:2]) - return { - 'purelib': '{0}/lib/python{1}/site-packages'.format( - prefix, py_version_short), - 'platlib': '{0}/lib/python{1}/site-packages'.format( - prefix, py_version_short), - 'scripts': '{0}/bin'.format(prefix), - } +from sysconfig import get_paths wheel_ext = '.whl' @@ -743,11 +720,7 @@ def _install_build_reqs(self, reqs, prefix): upgrade=False).url for r in reqs] - if sys.version_info >= (2, 7): - mpip = 'pip' - else: - mpip = 'pip.__main__' # Python 2.6 can't execute a package with -m - args = [sys.executable, '-m', mpip, 'install', '--prefix', prefix] \ + args = [sys.executable, '-m', 'pip', 'install', '--prefix', prefix] \ + list(urls) with open_spinner("Installing build dependencies") as spinner: call_subprocess(args, show_stdout=False, spinner=spinner) @@ -767,7 +740,9 @@ def _build_one(self, req, output_dir, python_tag=None): python_tag=python_tag, isolate=True) else: - # Old style build, in the current environment + # Build in the current environment, with no isolation. + # Used for sdists without a pyproject.toml, as well as build systems + # that don't require any external dependencies. return self._build_one_inside_env(req, output_dir, python_tag=python_tag) From b0b6ec1c7ac5581953573307721e380696bada82 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sat, 1 Apr 2017 19:35:01 +0100 Subject: [PATCH 21/41] Add news file --- news/3691.feature | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 news/3691.feature diff --git a/news/3691.feature b/news/3691.feature new file mode 100644 index 00000000000..24eebbcc0fa --- /dev/null +++ b/news/3691.feature @@ -0,0 +1,4 @@ +Support for packages specifying build dependencies in pyproject.toml (see `PEP +518 `__). Packages which specify +one or more build dependencies this way will be built into wheels in an +isolated environment with those dependencies installed. From a50c4ef2ac0f7c20f115cb77d03b238b7f0e6230 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sat, 1 Apr 2017 19:36:36 +0100 Subject: [PATCH 22/41] Fix code style --- pip/wheel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index b2e6f5d7329..49a78f8ac2e 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -741,8 +741,8 @@ def _build_one(self, req, output_dir, python_tag=None): isolate=True) else: # Build in the current environment, with no isolation. - # Used for sdists without a pyproject.toml, as well as build systems - # that don't require any external dependencies. + # Used for sdists without a pyproject.toml, as well as build + # systems that don't require any external dependencies. return self._build_one_inside_env(req, output_dir, python_tag=python_tag) From c9d83b9dd659fc3efeae0fc08bede1436dbf9e93 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 12 Apr 2017 16:55:57 +0100 Subject: [PATCH 23/41] Let context exit implicitly return None, as requested --- pip/wheel.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index 49a78f8ac2e..33caf351fcf 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -684,8 +684,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): if not self.no_clean: rmtree(self.prefix) - return False # Do not suppress exceptions - class WheelBuilder(object): """Build wheels from a RequirementSet.""" From 618720de899cae6531f5f6093a531af3d8f4732b Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 12 Apr 2017 17:05:43 +0100 Subject: [PATCH 24/41] build-system.requires = [] -> ["setuptools", "wheel"] --- pip/wheel.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index 33caf351fcf..ced902a3834 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -702,14 +702,22 @@ def _find_build_reqs(self, req): """Get a list of the packages required to build the project, if any. Build requirements can be specified in a pyproject.toml, as described - in PEP 518 + in PEP 518. If this file exists but doesn't specify any build + requirements, pip will default to installing setuptools and wheel. If + pyproject.toml is not present, the build will take place in the current + environment. """ if os.path.isfile(req.pyproject_toml): with open(req.pyproject_toml) as f: pp_toml = pytoml.load(f) - return pp_toml.get('build-system', {}).get('requires', []) - - return [] # No pyproject.toml + bsr = pp_toml.get('build-system', {}).get('requires', []) + if not bsr: + # These are required for the most common way to build a wheel, + # so we'll install them if no build-system reqs are specified. + bsr = ['setuptools', 'wheel'] + return bsr + + return [] # No pyproject.toml - will result in a non-isolated build. def _install_build_reqs(self, reqs, prefix): # Local import to avoid circular import (wheel <-> req_install) From 9a79fb5ac439dd7dfc3bcfa0b298c943d577ee97 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 12 Apr 2017 17:41:23 +0100 Subject: [PATCH 25/41] Allow build-system requirements to be empty --- pip/wheel.py | 46 ++++++++--------------- tests/data/packages/pep518-3.0.tar.gz | Bin 831 -> 819 bytes tests/data/src/pep518-3.0/pyproject.toml | 2 +- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index ced902a3834..74253c7b4db 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -699,25 +699,21 @@ def __init__(self, requirement_set, finder, build_options=None, self.no_clean = no_clean def _find_build_reqs(self, req): - """Get a list of the packages required to build the project, if any. + """Get a list of the packages required to build the project, if any, + and a flag indicating whether pyproject.toml is present, indicating + that the build should be isolated. Build requirements can be specified in a pyproject.toml, as described - in PEP 518. If this file exists but doesn't specify any build - requirements, pip will default to installing setuptools and wheel. If - pyproject.toml is not present, the build will take place in the current - environment. + in PEP 518. If this file exists but doesn't specify build + requirements, pip will default to installing setuptools and wheel. """ if os.path.isfile(req.pyproject_toml): with open(req.pyproject_toml) as f: pp_toml = pytoml.load(f) - bsr = pp_toml.get('build-system', {}).get('requires', []) - if not bsr: - # These are required for the most common way to build a wheel, - # so we'll install them if no build-system reqs are specified. - bsr = ['setuptools', 'wheel'] - return bsr + return pp_toml.get('build-system', {})\ + .get('requires', ['setuptools', 'wheel']), True - return [] # No pyproject.toml - will result in a non-isolated build. + return ['setuptools', 'wheel'], False def _install_build_reqs(self, reqs, prefix): # Local import to avoid circular import (wheel <-> req_install) @@ -736,21 +732,13 @@ def _build_one(self, req, output_dir, python_tag=None): :return: The filename of the built wheel, or None if the build failed. """ - build_reqs = self._find_build_reqs(req) - - if build_reqs: - # Install build deps into temporary prefix (PEP 518) - with BuildEnvironment(no_clean=self.no_clean) as prefix: - self._install_build_reqs(build_reqs, prefix) - return self._build_one_inside_env(req, output_dir, - python_tag=python_tag, - isolate=True) - else: - # Build in the current environment, with no isolation. - # Used for sdists without a pyproject.toml, as well as build - # systems that don't require any external dependencies. + build_reqs, isolate = self._find_build_reqs(req) + # Install build deps into temporary prefix (PEP 518) + with BuildEnvironment(no_clean=self.no_clean) as prefix: + self._install_build_reqs(build_reqs, prefix) return self._build_one_inside_env(req, output_dir, - python_tag=python_tag) + python_tag=python_tag, + isolate=True) def _build_one_inside_env(self, req, output_dir, python_tag=None, isolate=False): @@ -774,10 +762,8 @@ def _build_one_inside_env(self, req, output_dir, python_tag=None, def _base_setup_args(self, req, isolate=False): flags = '-u' - # The install process needs to be able to import setuptools from where - # it's installed as a dependency of pip, so skipping isolation for now. - # if isolate: - # flags += 'S' + if isolate: + flags += 'S' return [ sys.executable, flags, '-c', SETUPTOOLS_SHIM % req.setup_py diff --git a/tests/data/packages/pep518-3.0.tar.gz b/tests/data/packages/pep518-3.0.tar.gz index 88e67a6101444616680e173ebafad5bc8c9d7a4e..b80d4e71089681865d750c0b365f6b78c6d7e4e6 100644 GIT binary patch literal 819 zcmV-31I+v%iwFo2SngN?|72-%bT4paa5XVFEi*1KE_7jX0PUIEPTN2bhJEd)*rpeN zMB?=+u8?wpT4)h%Kuu{cB4n9(6SnwNd!5#A-;JG+1Eqiz8`1vXjlHvW4x0IAXJ-?Z z*!r8c*`b%nko{O}(oS3jf#6kWD5okH+z(D!hq% zmGi(SJyH&QPcV5;%Rx4c+>}ju#^b0ostS1dtmwbG?Z|*=&jLc*s{Wsr{~bfuk^i6B z|AVuO&u@DSBS87k|2_I& z6nC}ve~hlNp?_*p8~Sgg{%6ATJ>j*#vUJwkIUst6s(mkH2c!xJ^>#)Ei8{!`&`XO0 za?wBSpMC4AAL5X;6K}$nro&uZ$LZ#{%|ed{OY28IVo|p8+B=qwQl5zNyB5atz!T#* z4VM;ZwkG{Ac6nR>jtBK`Sabbb@-cJP|21S9fc_ic^(!sUQtgUI8jGeRDWp^*b)3c_ zDdR!JagY(YNr+QXS@y}8N4}eQqZ_IEOs%>DyOB&8y`3tYcJ@_LGObcaZ%4-0t#R>Y zZ|!q-_vX`kQ1~8q_u5`Lm?|x@jnf=#>s(?o|Jei~o!g3*!G1+kbR% z_38EL$dEtf4&r}*bNr`l&Hj{N2JycWKAsHE-ky#=8zRz^B1z-#d@Kzahk-uN2ku)* zp|88h<@vkb+3@X!zQuQboAY_XBcDg(!VN@pm;Lc387FSQXFRB8GZ7{MH(j+-3F*4( zr$FRxUH>a4XjA`cby(7Wztlgo3FyBIYGpw#2l_YvCQi4cf3l{3)xe>CKbM~GNw4>Xr)6p97!d>OLoejVsCEc_KBJsOALU`_ zrNuG28lDd?z7F;Gamag#H|2|=lU#m_)AfFjhn@%)qaVbWN7>S2Z+JFNMIx(rHG`Ld zCns?lE=EAJE$P46!pyVOycLnjqZvvHCDllu zq;ZHUe~@t;WJqli;#AfnM`$7<-%Y&nol?vQ)uCgsul#F1 z*gQE{`JRKr`{_ea_!H^BF0yE?=>Ku`UxzUa_1~wl|L%)y51#*(^&g6uAJqH&P>9Ic)Gf-wF+ z{qL;a`&j+2)_<%0pTlg>e-}Xg7XSbN0000000000000000000000000005pZzW}-4 J?&1JY002&qt8@SW diff --git a/tests/data/src/pep518-3.0/pyproject.toml b/tests/data/src/pep518-3.0/pyproject.toml index ee8a38c7675..5e3bb233223 100644 --- a/tests/data/src/pep518-3.0/pyproject.toml +++ b/tests/data/src/pep518-3.0/pyproject.toml @@ -1,2 +1,2 @@ [build-system] -requires=["simple==3.0"] +requires=["simple==3.0", "setuptools", "wheel"] From 7561bf201fd95dd3ff78ec031b3fff3e8d291b2c Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 13 Apr 2017 10:51:41 +0100 Subject: [PATCH 26/41] Re-disable isolation for wheel builds --- pip/wheel.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index 74253c7b4db..12983de232a 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -762,8 +762,11 @@ def _build_one_inside_env(self, req, output_dir, python_tag=None, def _base_setup_args(self, req, isolate=False): flags = '-u' - if isolate: - flags += 'S' + # The -S flag currently breaks Python in virtualenvs, because it relies + # on site.py to find parts of the standard library outside the env. So + # isolation is disabled for now. + # if isolate: + # flags += 'S' return [ sys.executable, flags, '-c', SETUPTOOLS_SHIM % req.setup_py From 99e00fb8fb2f9aaf87dea25289621ed95f37c844 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 13 Apr 2017 10:55:15 +0100 Subject: [PATCH 27/41] Add package of 'wheel' in test packages directory Tests now need this to install wheel in build environment --- .../packages/wheel-0.29.0-py2.py3-none-any.whl | Bin 0 -> 66878 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/data/packages/wheel-0.29.0-py2.py3-none-any.whl diff --git a/tests/data/packages/wheel-0.29.0-py2.py3-none-any.whl b/tests/data/packages/wheel-0.29.0-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..506d5e520f918acc9c1a36014cca1f4b953c594c GIT binary patch literal 66878 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 D73`%$ literal 0 HcmV?d00001 From 034efe5f2bb1af46ac05b7d24eef952b9cc10ef4 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 13 Apr 2017 10:56:41 +0100 Subject: [PATCH 28/41] Let pip look in packages directory for 'wheel -e' test --- tests/functional/test_wheel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py index 758128cf5a2..2e021dc4dc4 100644 --- a/tests/functional/test_wheel.py +++ b/tests/functional/test_wheel.py @@ -103,7 +103,7 @@ def test_pip_wheel_builds_editable(script, data): script.pip('install', 'wheel') editable_path = os.path.join(data.src, 'simplewheel-1.0') result = script.pip( - 'wheel', '--no-index', '-e', editable_path + 'wheel', '--no-index', '-f', data.find_links, '-e', editable_path ) wheel_file_name = 'simplewheel-1.0-py%s-none-any.whl' % pyversion[0] wheel_file_path = script.scratch / wheel_file_name From a9c4081a9691145b1ab365f81bcc61a296deb937 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 13 Apr 2017 11:02:40 +0100 Subject: [PATCH 29/41] Warning message if package doesn't build-dep on setuptools --- pip/wheel.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pip/wheel.py b/pip/wheel.py index 12983de232a..0b1222af0af 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -733,6 +733,11 @@ def _build_one(self, req, output_dir, python_tag=None): :return: The filename of the built wheel, or None if the build failed. """ build_reqs, isolate = self._find_build_reqs(req) + if 'setuptools' not in build_reqs: + logger.warning( + "This version of pip does not implement PEP 516, so " + "it cannot build a wheel without setuptools. You may need to " + "upgrade to a newer version of pip.") # Install build deps into temporary prefix (PEP 518) with BuildEnvironment(no_clean=self.no_clean) as prefix: self._install_build_reqs(build_reqs, prefix) From b1d0e6b87d6f7455988ac45e83b60c58933b3d5b Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 13 Apr 2017 13:15:14 +0100 Subject: [PATCH 30/41] Avoid uninstalling setuptools when installing build dependencies --- pip/wheel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pip/wheel.py b/pip/wheel.py index 0b1222af0af..6e2fd7ab3bd 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -722,8 +722,8 @@ def _install_build_reqs(self, reqs, prefix): upgrade=False).url for r in reqs] - args = [sys.executable, '-m', 'pip', 'install', '--prefix', prefix] \ - + list(urls) + args = [sys.executable, '-m', 'pip', 'install', '--ignore-installed', + '--prefix', prefix] + list(urls) with open_spinner("Installing build dependencies") as spinner: call_subprocess(args, show_stdout=False, spinner=spinner) From c9df9a7bb764322232a39c9494059c64643d9081 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 13 Apr 2017 14:09:16 +0100 Subject: [PATCH 31/41] Include newer setuptools for tests Old version of setuptools it was using had a bug on Python 3.6 https://github.com/pypa/setuptools/issues/378 --- .../setuptools-34.4.1-py2.py3-none-any.whl | Bin 0 -> 390260 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/data/packages/setuptools-34.4.1-py2.py3-none-any.whl diff --git a/tests/data/packages/setuptools-34.4.1-py2.py3-none-any.whl b/tests/data/packages/setuptools-34.4.1-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..a1e62e24c8dddd63388558bd20c33ad36d9f5c4f GIT binary patch literal 390260 zcmV(@K-RxdO9KQH000080NQ|xN?cF6MQH#40Db@f01p5F0A*owd0%O6b97;BY%Xwl zEzB_vgFp;K(SA>1xq&nsAQEMYv|M0hkwsZ)Z6nqcxjhgu)im!j^RyeqH`7bn?`