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

"Detected a distutils installed project" is fatal in pip 8 #3384

Closed
rbtcollins opened this issue Jan 20, 2016 · 29 comments
Closed

"Detected a distutils installed project" is fatal in pip 8 #3384

rbtcollins opened this issue Jan 20, 2016 · 29 comments
Labels
auto-locked Outdated issues that have been locked by automation
Milestone

Comments

@rbtcollins
Copy link

However distributions still ship a bunch of distutils projects - e.g. six, urllib3, jsonpointer, chardet, libvirt-python, pyOpenSSL, PyYAML, netaddr

So this is likely to affect many users.

A variation of this, where a virtualenv is being used also occurs - this shouldn't have been warning ever, since the virtualenv means we won't uninstall, we'll just shadow it. See #3404 for the bug for that.

We could:

  • suck it up and point folk at the distros for a while (5 years, plus or minus) until the current binary packages without the file record are going
  • advise folk to use --ignore-installed (which will cause lots of possibly undesired upgrades / network traffic), but may work
  • revert it and release 8.1
  • provide an option to make it a warning
@dstufft
Copy link
Member

dstufft commented Jan 20, 2016

Just a bit of background here:

The fundamental problem is that "pure" distutils projects (ones that import distutils rather than setuptools, and which were not installed in a way to force setuptools to be injected, as pip does) when installed do not include all of the relevant metadata. They include information like "foobar v1.0" was installed but they do not include /usr/lib/python3.5/site-packages/foobar/__init__.py belongs to "foobar v1.0". This means that whenever we uninstall these, we don't actually uninstall any files we simply remove the record that something has been installed and then overwrite files.

This generally "works", however it can lead to really weird, strange errors where you end if with foobar.py AND foobar/__init__.py (if for example, foobar switched form a module to a package) or files are left behind that should have been deleted, confusing import mechanisms. So, previously we lied and said we uninstalled something when we actually didn't, then we at least told you that were were lying to you, now finally, we've stopped lying to you and started to refuse to do something we don't have the information to actually do.

Long term projects should stop importing from distutils and switch instead to just unconditionally importing from setuptools. This is the recommended way to function and in 2016 you're almost always guaranteed to have setuptools available on an end users's machine. We may elect to work around it and push the date for this back further, but realistically things need to stop relying on plain distutils.

@petecheslock
Copy link

Hit this issue with installing elasticsearch-curator with this error

Detected a distutils installed project ('urllib3') which we cannot uninstall. The metadata provided by distutils does not contain a list of files which have been installed, so pip does not know which files to uninstall.

Worked around it by pip install -I pip==7.1.2

@Ivoz
Copy link
Contributor

Ivoz commented Jan 20, 2016

+1 on not lying to people that we know how to uninstall something when we really don't - literally the reason everyone stopped using easy_install, right?

@dstufft
Copy link
Member

dstufft commented Jan 20, 2016

Adding to 8.0.1 milestone, we should at least figure out what our answer is going to be before we release 8.0.1.

@dstufft dstufft added this to the 8.0.1 milestone Jan 20, 2016
@pfmoore
Copy link
Member

pfmoore commented Jan 20, 2016

Can we not as a minimum add something to the message saying "If you wish to uninstall FOO you need to manually identify and delete the files associated with the project from site-packages."? That seems like a better workaround than having people downgrade pip...

@ianw
Copy link
Contributor

ianw commented Jan 20, 2016

So I have no doubt everyone understands the issue; but I am seeing something that's easier to reproduce than nebulous devstack jobs. On Ubuntu Trusty the problem is I think coming out of #1570 and that argparse is kind of in the virtualenv and kind of not (it's filtered, i think?)

ubuntu@trusty:/tmp$ virtualenv test
New python executable in test/bin/python
Installing setuptools, pip...done.
ubuntu@trusty:/tmp$ . test/bin/activate
(test)ubuntu@trusty:/tmp$ pip install argparse
Requirement already satisfied (use --upgrade to upgrade): argparse in /usr/lib/python2.7
Cleaning up...
(test)ubuntu@trusty:/tmp$ pip install --upgrade argparse
Downloading/unpacking argparse from https://pypi.python.org/packages/2.7/a/argparse/argparse-1.4.0-py2.py3-none-any.whl#md5=c37216a954c8669054e2b2c54853dd49
  Downloading argparse-1.4.0-py2.py3-none-any.whl
Installing collected packages: argparse
  Found existing installation: argparse 1.2.1
    Not uninstalling argparse at /usr/lib/python2.7, outside environment /tmp/test
Successfully installed argparse
Cleaning up...

this has now turned into an error

(test)ubuntu@trusty:/tmp$ pip install --upgrade pip
Downloading/unpacking pip from https://pypi.python.org/packages/py2.py3/p/pip/pip-8.0.0-py2.py3-none-any.whl#md5=7b1da5eba510e1631791dcf300657916
  Downloading pip-8.0.0-py2.py3-none-any.whl (1.2MB): 1.2MB downloaded
Installing collected packages: pip
  Found existing installation: pip 1.5.4
    Uninstalling pip:
      Successfully uninstalled pip
Successfully installed pip
Cleaning up...
(test)ubuntu@trusty:/tmp$ pip install -U argparse
Collecting argparse
  Downloading argparse-1.4.0-py2.py3-none-any.whl
Installing collected packages: argparse
  Found existing installation: argparse 1.2.1
Detected a distutils installed project ('argparse') which we cannot uninstall. The metadata provided by distutils does not contain a list of files which have been installed, so pip does not know which files to uninstall.

So it seems "outside environment" has been caught along with "can't know what files to delete"

@xavfernandez
Copy link
Member

I was hit by the same argparse issue. I guess the dist_is_local step should happen before.

@dstufft
Copy link
Member

dstufft commented Jan 20, 2016

The argparse issue seems like a legit bug in that it shouldn't be getting caught by this error yea?

@daviddyball
Copy link

@dstufft, is it not possible to add a CLI argument called --IGNORE-DISTUTILS-ERRORS-YES-I-UNDERSTAND-THIS-IS-NOT-SUPPORTED until everyone switched to setuptools in their installation scripts?

@dstufft
Copy link
Member

dstufft commented Jan 20, 2016

@daviddyball Yes, and that's a possibility for what we'll do here. Today I'll be poking at the issue and we'll ultimately end up with a decision of some kind for how we proceed.

@wichert
Copy link

wichert commented Jan 20, 2016

We hit the exact same issue as @ianw, only with requests instead of argparse.

Isn't the issue here pip trying to uninstall a non-local package? Even if that had all the necessary metadata pip would never be able to do that, so the only sensible thing it can do is suck it up and not error out.

@sigmavirus24
Copy link
Member

@wichert is requests already installed by the distro when you try to install requests again?

@wichert
Copy link

wichert commented Jan 20, 2016

Because our requirements.txt pins a specific version so we have the same version everywhere.

@dstufft
Copy link
Member

dstufft commented Jan 20, 2016

Also, folks should file issues with the relevant projects to unconditionally use setuptools in their setup.py. It's 2016 and distutils isn't a reasonable thing to use by itself anymore.

@pfmoore
Copy link
Member

pfmoore commented Jan 20, 2016

so the only sensible thing it can do is suck it up and not error out.

But if pip can't uninstall the old version, when it does the install it could create a corrupt installation. Is it really any better to ignore the error and (potentially) leave the user with an unusable package?

IMO, there isn't really any good answer here.

As far as I can see, this only affects people with packages installed that were not installed using pip. I guess that mostly, these are distro-installed packages, and as such, shouldn't people be using their distro package manager to update these, rather than pip?

@wichert
Copy link

wichert commented Jan 20, 2016

The chances of being able to use the distro package manager to get a version of pip, virtualenv, requests, Pillow, etc. to anything more recent than 2 years old is effectively zero. The target audience here are people using something like a Debian or Ubuntu LTS release: they will contain software that is a few years old, and will only receive patches for security fixes.

Now your next question is probably going to be: why don't use use a virtualenv that does not expose system packages? The answer to that is: because there are packages that are not installable by pip. For example Python modules for complex C++ libraries that can't use setuptools for compilation (our situation), or because compilation requires more memory then a machine might have available (happens very often with numpy/scipy on VMs).

@dstufft
Copy link
Member

dstufft commented Jan 20, 2016

Specifically, it's packages installed that were installed using distutils rather than setuptools. Which means all of the following were true:

  • Was not installed using pip.
  • Was not installed using easy_install.
  • Project either conditionally or unconditionally imports from distutils in setup.py, preferring distutils or setuptools was not installed.

My estimation is that the vast bulk of these are going to be OS packages where either downstream unconditionally uses distutils, or supports both setuptools and distutils and the OS packager chose to use distutils over setuptools.

@pfmoore
Copy link
Member

pfmoore commented Jan 20, 2016

OK, so those scenarios are not ones I'm likely to be affected by, so I don't really have a way of evaluating the options. Are they that common (given that many people will just use a virtualenv)? And are none of the following viable approaches?

  • User manually goes in and deletes the offending package's files (before you say "how do I know which they are?", that's the reason pip can't do it, so you're on your own there...)
  • You use --ignore-installed to force an install of the specific package (and you then take responsibility for ensuring that the resulting installation is OK)

I'm basically -1 on letting pip do an install that we know is risky, without the user being told about the risks and consequences. It'd be too easy for us to end up dealing with "pip broke my system" tickets. Presumably people have been seeing the warning about this since it was introduced early last year? And presumably the behaviour was deprecated because it had caused issues prior to that?

@wichert
Copy link

wichert commented Jan 20, 2016

As I explained we do use a virtualenv, so using virtualenv is not a possible solution :)

The user can't manually delete the offending package, since that would require root access, and is likely to break the OS.

At least as far as we are concerned we just want pip to not try to uninstall something that is not local to the virtualenv. I don't particular mind if that is controlled by a flag, although from a user perspective I probably prefer that pip gives a very clear warning but defaults to skipping the uninstall attempt.

@pfmoore
Copy link
Member

pfmoore commented Jan 20, 2016

I'm clearly missing something here. My apologies.

You're using a virtualenv. OK. Presumably with use_system_site_packages, as otherwise how come pip's seeing the system package? These are packages that can't be installed with pip, I think you said? If so, why is pip trying to install (upgrade) them? If the user's asking pip to install something that you've confirmed can't be installed with pip, then not being able to uninstall the old version is surely the least of your worries?

@wichert
Copy link

wichert commented Jan 20, 2016

@pfmoore We have packages that can't be installed using pip, but those are not the ones pip is trying to upgrade.

We use a virtualenv with system site packages so we can use packages that can not be installed using pip. That also pulls in a set of other packages that are installed by the OS such as requests, pillow and psycopg2. As far as we are concerted that is a side-effect if virtualenv had an option to only selectively expose system packages we would use that to exclude those.

We also use exact version pins for all packages in requirements.txt to guarantee that we use the exact same version of all dependencies everywhere. That will include packages that are provided by the OS on some machines for various reasons – for example Ubuntu ships with a bunch of Python packages installed that OSX/MacPorts does not install. When we point pip to that requirements.txt it can try to upgrade packages installed by the OS. That has always worked, but results in a fatal error with pip 8.

I suspect the easiest way to reproduce this is to grab an Ubuntu 14.04 LTS box and do this:

$ sudo apt-get install python-requests  # This will install requests 0.4 or 2.2.1-1ubuntu0.3 (if you run trusty-updates)
$ virtualenv --system-site-packages tst
$ cd tst
$ bin/pip install --upgrade requests==2.9.1

This works fine with pip 7. On pip 8 it will abort with an error.

@pfmoore
Copy link
Member

pfmoore commented Jan 20, 2016

Ah, OK I see now. Sorry for taking so long to understand. Agreed that the "real" solution would be for virtualenv to have an "expose only the following system packages" option.

Maybe what we should do is downgrade the error to a warning if pip is planning to install the new version to a different location than the detected one. (We can't ignore it totally as (a) we don't know which one will take precedence, and (b) there's still the risk of partial shadowing).

Installing a package like this, shadowing another installation, would still be an unsupported arrangement, but at least people who understand the risks and want to (or have to) take them, can do so.

@dstufft
Copy link
Member

dstufft commented Jan 20, 2016

I think @wichert is hitting something different than the underlying issue here, it just so happened that promoting this to an error exposed it. Right now our uninstallation path looks like:

  1. Figure out what kind of installation it is (distutils (aka broken), setuptools, egg, etc).
    • If this is a distutils installation, this is the point we raise an error because we can't exactly do the next step without the metadata that is missing from these steps.
  2. Locate all of the paths associated with a particular project, and add them to a list of paths to remove.
  3. Determine if we're running inside of a virtual environment, and if we are if the item we're attempting to uninstall is "local" to that virtual environment, or if it comes from the system and remove files based on that.
    • If it comes from the system, we simply skip out here and pretend we uninstalled it. This means pip uninstall is broken, but upgrades will work because pip will shadow the system installed item with one installed inside of the virtual environment.
    • If it is inside of the virtual environment, we queue up a bunch of paths to remove, delete them, and then continue on with whatever the next task is.

This should be easy to support by splitting the third step up so that the first thing we do is check if the item we're attempting to install is "local" to any currently running virtual environments and simply skipping out earlier than we are currently. The goal here was to fix a slightly different problem, where you're not shadowing via sys.path, but you're literally overwriting files to the same disk location, or you're removing the .egg-info but leaving the package otherwise installed. Those cases don't happen in the specific one that @wichert is having.

@pfmoore
Copy link
Member

pfmoore commented Jan 20, 2016

Agreed. What we do in step 3a doesn't care if the existing install is broken, so we can (actual technical details permitting!) do that up front.

@dstufft
Copy link
Member

dstufft commented Jan 20, 2016

Argparse is another thing that people are running into this issue on, because argparse in the standard library includes a .egg-info file. However the standard library argparse is always going to shadow any installed argparse (because of sys.path ordering) so installing argparse on Python 2.7+ and 3.3+ is pointless unless you munge sys.path to put it first.

What do we think about making installing argparse a no-op on 2.7+, 3.3+ and just hard failing if someone attempts to "install" a version specifier of it that doesn't include what is shipped in the standard library?

@coderanger
Copy link

Checking in, standard Ubuntu 14.04 image includes gross versions of both requests and six, so attempting to install either system-wide will fail.

coderanger added a commit to poise/poise-python that referenced this issue Jan 20, 2016
clayg added a commit to NVIDIA/vagrant-swift-all-in-one that referenced this issue Jan 21, 2016
zmc added a commit to ceph/teuthology that referenced this issue Jan 21, 2016
See pypa/pip#3384

Signed-off-by: Zack Cerza <zack@redhat.com>
@dstufft
Copy link
Member

dstufft commented Jan 21, 2016

This should be fixed now in develop.

@dstufft dstufft closed this as completed Jan 21, 2016
openstack-gerrit pushed a commit to openstack/openstack that referenced this issue Jan 21, 2016
Project: openstack/diskimage-builder  d79ecfd07664bdcb56711d84fbc691b95d39d961

Use pip 7 for ironic

pip8 cannot install the ironic requirements due to
pypa/pip#3384.

Change-Id: Ic0c70baca83b91cb55ddf4f787c4ff25aaefc062
openstack-gerrit pushed a commit to openstack/diskimage-builder that referenced this issue Jan 21, 2016
pip8 cannot install the ironic requirements due to
pypa/pip#3384.

Change-Id: Ic0c70baca83b91cb55ddf4f787c4ff25aaefc062
zmc added a commit to ceph/teuthology that referenced this issue Jan 22, 2016
See pypa/pip#3384

Signed-off-by: Zack Cerza <zack@redhat.com>
openstack-gerrit pushed a commit to openstack/openstack that referenced this issue Jan 23, 2016
Project: openstack/tempest  e9e9cfc5f2b6a3acc30d831151659b0b164503a0

Cap Pip<8 due to pip bug

Due to pypa/pip#3384 in pip v8, this change
puts a cap on pip, in a similar manner to
https://review.openstack.org/#/c/269954/

Change-Id: I20b8b597e8806a7ee21d947eccc98e4ae7e00c3b
Closes-Bug: 1536290
openstack-gerrit pushed a commit to openstack/tempest that referenced this issue Jan 23, 2016
Due to pypa/pip#3384 in pip v8, this change
puts a cap on pip, in a similar manner to
https://review.openstack.org/#/c/269954/

Change-Id: I20b8b597e8806a7ee21d947eccc98e4ae7e00c3b
Closes-Bug: 1536290
openstack-gerrit pushed a commit to openstack/rally that referenced this issue Jan 26, 2016
The pip issue pypa/pip#3384 is already been fixed after pip 8.0.1
released. Since the pip 8.0 version breaks things, just skip it
in version pinning.

TrivialFix

Change-Id: Ida3e7cac5ecfd2ee7266e53650bf1732d5f9d6a0
clayg added a commit to NVIDIA/vagrant-swift-all-in-one that referenced this issue Jan 26, 2016
openstack-gerrit pushed a commit to openstack/bifrost that referenced this issue Jan 27, 2016
The pip issue pypa/pip#3384 is already been fixed after pip 8.0.1
released.

TrivialFix

Change-Id: Ib5bdff63a46ade8da31ddf4516affad6cf1a8029
openstack-gerrit pushed a commit to openstack/openstack that referenced this issue Jan 29, 2016
Project: openstack-dev/devstack  428c35bade09ea814d8ce119498d3beb947f2ee2

Remove the pip version pinning to < 8

The pip issue pypa/pip#3384 has already been fixed after pip 8.0.1
released. But leave the facility to easy flip this on in the future.

TrivialFix

Change-Id: I49658ce4056c773943321270defd461bbf3e9fb9
openstack-gerrit pushed a commit to openstack/openstack that referenced this issue Jan 29, 2016
Project: openstack-dev/devstack  428c35bade09ea814d8ce119498d3beb947f2ee2

Remove the pip version pinning to < 8

The pip issue pypa/pip#3384 has already been fixed after pip 8.0.1
released. But leave the facility to easy flip this on in the future.

TrivialFix

Change-Id: I49658ce4056c773943321270defd461bbf3e9fb9
openstack-gerrit pushed a commit to openstack/devstack that referenced this issue Jan 29, 2016
The pip issue pypa/pip#3384 has already been fixed after pip 8.0.1
released. But leave the facility to easy flip this on in the future.

TrivialFix

Change-Id: I49658ce4056c773943321270defd461bbf3e9fb9
epwn pushed a commit to epwn/ursula that referenced this issue Feb 1, 2016
Now that pypa/pip#3384 has been resolved we
can use the latest version.
davidcusatis pushed a commit to davidcusatis/ursula that referenced this issue Feb 23, 2016
Let's install a specific version of pip instead of latest until
pypa/pip#3384 gets addressed
epwn pushed a commit to epwn/ursula that referenced this issue Mar 18, 2016
Now that pypa/pip#3384 has been resolved we
can use the latest version.
pingle15 pushed a commit to pingle15/ursula that referenced this issue May 17, 2016
Now that pypa/pip#3384 has been resolved we
can use the latest version.
@lock lock bot added the auto-locked Outdated issues that have been locked by automation label Jun 4, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Jun 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
auto-locked Outdated issues that have been locked by automation
Projects
None yet
Development

No branches or pull requests