Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autosetuptools #1069

Closed
wants to merge 9 commits into from
7 changes: 6 additions & 1 deletion pip/basecommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from pip.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
from pip.status_codes import SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND
from pip.util import get_prog
from pip.req import NeedSetuptools


__all__ = ['Command']
Expand Down Expand Up @@ -136,12 +137,16 @@ def main(self, args, initial_options):
# and when it is done, isinstance is not needed anymore
if isinstance(status, int):
exit = status
except NeedSetuptools:
# XXX assume we are in the install command
self.run(options, ['setuptools'])
return self.run(options, args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think each pip version should set a setuptools requirement (based on our testing), not just "setuptools"
If setuptools is installed and doesn't meet the requirement, I think we should prompt them for what they want to do. "Proceed at risk, or Allow pip to re-install". they could set the permanent answer in their config or environment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea and would help me; I'm always getting older versions of setuptools by accident.

Except -1 on the interactive prompt.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case where we'd be upgrading, not sure we can just presume. they're just trying to install some package, and we go upgrade setuptools.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I prefer to exit with an error code and something like a "please upgrade setuptools or use --force" message if setuptools is installed but too old. We'll get there when we get there.

except (InstallationError, UninstallationError):
e = sys.exc_info()[1]
logger.fatal(str(e))
logger.info('Exception information:\n%s' % format_exc())
store_log = True
exit = ERROR
exit = ERROR
except BadCommand:
e = sys.exc_info()[1]
logger.fatal(str(e))
Expand Down
2 changes: 1 addition & 1 deletion pip/baseparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import sys
import optparse
import pkg_resources
import pip.pkg_resources as pkg_resources
import os
import textwrap
from distutils.util import strtobool
Expand Down
2 changes: 1 addition & 1 deletion pip/commands/freeze.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import re
import sys
import pkg_resources
import pip.pkg_resources as pkg_resources
import pip
from pip.req import InstallRequirement
from pip.log import logger
Expand Down
5 changes: 3 additions & 2 deletions pip/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import sys
import tempfile
import shutil
from pip.req import InstallRequirement, RequirementSet, parse_requirements
from pip.req import InstallRequirement, RequirementSet, parse_requirements,\
NeedSetuptools
from pip.log import logger
from pip.locations import src_prefix, virtualenv_no_global, distutils_scheme
from pip.basecommand import Command
Expand Down Expand Up @@ -249,7 +250,7 @@ def run(self, options, args):
logger.notify('Successfully downloaded %s' % downloaded)
elif self.bundle:
requirement_set.create_bundle(self.bundle_filename)
logger.notify('Created bundle in %s' % self.bundle_filename)
logger.notify('Created bundle in %s' % self.bundle_filename)
finally:
# Clean up
if (not options.no_clean) and ((not options.no_install) or options.download_dir):
Expand Down
2 changes: 1 addition & 1 deletion pip/commands/search.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sys
import textwrap
import pkg_resources
import pip.pkg_resources as pkg_resources
import pip.download
from pip.basecommand import Command, SUCCESS
from pip.util import get_terminal_size
Expand Down
2 changes: 1 addition & 1 deletion pip/commands/show.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
import pkg_resources
import pip.pkg_resources as pkg_resources
from pip.basecommand import Command
from pip.log import logger

Expand Down
15 changes: 9 additions & 6 deletions pip/commands/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from __future__ import absolute_import

import os
import sys

from pip.basecommand import Command
from pip.index import PackageFinder
from pip.log import logger
Expand Down Expand Up @@ -82,14 +82,17 @@ def __init__(self, *args, **kw):
self.parser.insert_option_group(0, cmd_opts)

def run(self, options, args):


missing_requirements = []
# confirm requirements
if not wheel_setuptools_support(check=True):
missing_requirements.append("'pip wheel' requires %s." % setuptools_requirement)
try:
import wheel.bdist_wheel
import wheel
except ImportError:
raise CommandError("'pip wheel' requires bdist_wheel from the 'wheel' distribution.")
if not wheel_setuptools_support():
raise CommandError("'pip wheel' requires %s." % setuptools_requirement)
missing_requirements.append("'pip wheel' requires bdist_wheel from the 'wheel' distribution.")
if missing_requirements:
raise CommandError("; ".join(missing_requirements))

index_urls = [options.index_url] + options.extra_index_urls
if options.no_index:
Expand Down
2 changes: 1 addition & 1 deletion pip/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import gzip
import mimetypes
import posixpath
import pkg_resources
import pip.pkg_resources as pkg_resources
import random
import socket
import ssl
Expand Down
2 changes: 2 additions & 0 deletions pip/pkg_resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from pip.vendor.pkg_resources import *
from pip.vendor.pkg_resources import PY_MAJOR
20 changes: 17 additions & 3 deletions pip/req.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from email.parser import FeedParser
import os
import imp
import pkg_resources
import pip.pkg_resources as pkg_resources
import re
import sys
import shutil
Expand Down Expand Up @@ -34,6 +34,8 @@
import pip.wheel
from pip.wheel import move_wheel_files

class NeedSetuptools(Exception): pass

class InstallRequirement(object):

def __init__(self, req, comes_from, source_dir=None, editable=False,
Expand Down Expand Up @@ -868,6 +870,11 @@ def __init__(self, build_dir, src_dir, download_dir, download_cache=None,
self.as_egg = as_egg
self.use_user_site = use_user_site
self.target_dir = target_dir #set from --target option

try:
self.has_setuptools = pkg_resources.get_distribution('setuptools')
except pkg_resources.DistributionNotFound:
self.has_setuptools = False

def __str__(self):
reqs = [req for req in self.requirements.values()
Expand Down Expand Up @@ -1040,6 +1047,10 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False):
if not os.path.exists(self.build_dir):
_make_build_dir(self.build_dir)
req_to_install.update_editable(not self.is_download)

if not self.has_setuptools and req_to_install.name != 'setuptools':
raise NeedSetuptools()

if self.is_download:
req_to_install.run_egg_info()
req_to_install.archive(self.download_dir)
Expand Down Expand Up @@ -1094,12 +1105,12 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False):
if unpack:
is_bundle = req_to_install.is_bundle
is_wheel = url and url.filename.endswith('.whl')
if is_bundle:
if is_bundle: # XXX remove
req_to_install.move_bundle_files(self.build_dir, self.src_dir)
for subreq in req_to_install.bundle_requirements():
reqs.append(subreq)
self.add_requirement(subreq)
elif is_wheel:
if is_wheel:
req_to_install.source_dir = location
req_to_install.url = url.url
dist = list(pkg_resources.find_distributions(location))[0]
Expand All @@ -1114,6 +1125,9 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False):
req_to_install)
reqs.append(subreq)
self.add_requirement(subreq)
# Currently anything below this line requires setuptools:
elif not self.has_setuptools and req_to_install.name != 'setuptools':
raise NeedSetuptools()
elif self.is_download:
req_to_install.source_dir = location
req_to_install.run_egg_info()
Expand Down
2 changes: 1 addition & 1 deletion pip/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import stat
import re
import posixpath
import pkg_resources
import pip.pkg_resources as pkg_resources
import zipfile
import tarfile
import subprocess
Expand Down
16 changes: 16 additions & 0 deletions pip/vendor/_markerlib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
try:
import ast
from .markers import default_environment, compile, interpret
except ImportError:
if 'ast' in globals():
raise
def default_environment():
return {}
def compile(marker):
def marker_fn(environment=None, override=None):
# 'empty markers are True' heuristic won't install extra deps.
return not marker.strip()
marker_fn.__doc__ = marker
return marker_fn
def interpret(marker, environment=None, override=None):
return compile(marker)()
115 changes: 115 additions & 0 deletions pip/vendor/_markerlib/markers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
"""Interpret PEP 345 environment markers.

EXPR [in|==|!=|not in] EXPR [or|and] ...

where EXPR belongs to any of those:

python_version = '%s.%s' % (sys.version_info[0], sys.version_info[1])
python_full_version = sys.version.split()[0]
os.name = os.name
sys.platform = sys.platform
platform.version = platform.version()
platform.machine = platform.machine()
platform.python_implementation = platform.python_implementation()
a free string, like '2.6', or 'win32'
"""

__all__ = ['default_environment', 'compile', 'interpret']

import ast
import os
import platform
import sys
import weakref

_builtin_compile = compile

try:
from platform import python_implementation
except ImportError:
if os.name == "java":
# Jython 2.5 has ast module, but not platform.python_implementation() function.
def python_implementation():
return "Jython"
else:
raise


# restricted set of variables
_VARS = {'sys.platform': sys.platform,
'python_version': '%s.%s' % sys.version_info[:2],
# FIXME parsing sys.platform is not reliable, but there is no other
# way to get e.g. 2.7.2+, and the PEP is defined with sys.version
'python_full_version': sys.version.split(' ', 1)[0],
'os.name': os.name,
'platform.version': platform.version(),
'platform.machine': platform.machine(),
'platform.python_implementation': python_implementation(),
'extra': None # wheel extension
}

def default_environment():
"""Return copy of default PEP 385 globals dictionary."""
return dict(_VARS)

class ASTWhitelist(ast.NodeTransformer):
def __init__(self, statement):
self.statement = statement # for error messages

ALLOWED = (ast.Compare, ast.BoolOp, ast.Attribute, ast.Name, ast.Load, ast.Str)
# Bool operations
ALLOWED += (ast.And, ast.Or)
# Comparison operations
ALLOWED += (ast.Eq, ast.Gt, ast.GtE, ast.In, ast.Is, ast.IsNot, ast.Lt, ast.LtE, ast.NotEq, ast.NotIn)

def visit(self, node):
"""Ensure statement only contains allowed nodes."""
if not isinstance(node, self.ALLOWED):
raise SyntaxError('Not allowed in environment markers.\n%s\n%s' %
(self.statement,
(' ' * node.col_offset) + '^'))
return ast.NodeTransformer.visit(self, node)

def visit_Attribute(self, node):
"""Flatten one level of attribute access."""
new_node = ast.Name("%s.%s" % (node.value.id, node.attr), node.ctx)
return ast.copy_location(new_node, node)

def parse_marker(marker):
tree = ast.parse(marker, mode='eval')
new_tree = ASTWhitelist(marker).generic_visit(tree)
return new_tree

def compile_marker(parsed_marker):
return _builtin_compile(parsed_marker, '<environment marker>', 'eval',
dont_inherit=True)

_cache = weakref.WeakValueDictionary()

def compile(marker):
"""Return compiled marker as a function accepting an environment dict."""
try:
return _cache[marker]
except KeyError:
pass
if not marker.strip():
def marker_fn(environment=None, override=None):
""""""
return True
else:
compiled_marker = compile_marker(parse_marker(marker))
def marker_fn(environment=None, override=None):
"""override updates environment"""
if override is None:
override = {}
if environment is None:
environment = default_environment()
environment.update(override)
return eval(compiled_marker, environment)
marker_fn.__doc__ = marker
_cache[marker] = marker_fn
return _cache[marker]

def interpret(marker, environment=None):
return compile(marker)(environment)
Loading