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

PYTHONPATH can break editable packages #1005

Open
asmodehn opened this issue Apr 4, 2017 · 6 comments
Open

PYTHONPATH can break editable packages #1005

asmodehn opened this issue Apr 4, 2017 · 6 comments
Labels
Needs Triage Issues that need to be evaluated for severity and status.

Comments

@asmodehn
Copy link

asmodehn commented Apr 4, 2017

I haven't traced the issue down to the setuptools details yet but here is the symptom :
pypa/pip#4261

TL;DR if a path is present in PYTHONPATH when installing (--editable -> setup.py develop ) a package , that path is not added to easy-install.pth.

We probably need a more detailed (and setuptools focused) report.

@jaraco
Copy link
Member

jaraco commented Apr 8, 2017

You've demonstrated the unexpected behavior in the upstream ticket, but let's see if we can demonstrate the issue in a more distilled fashion using setuptools only.

In my initial attempt, I was unsuccessful:

$ mkdir issue-1005
$ cd issue-1005 
$ python -m venv .env             
$ . .env/bin/activate
(.env) $ cat > setup.py
import setuptools
setuptools.setup(name='foo', version='1.0', packages=['foo_pkg'])
(.env) $ mkdir foo_pkg
(.env) $ touch foo_pkg/__init__.py
(.env) $ PYTHONPATH=$(pwd) python setup.py develop -q
running develop
running egg_info
creating foo.egg-info
writing foo.egg-info/PKG-INFO
writing dependency_links to foo.egg-info/dependency_links.txt
writing top-level names to foo.egg-info/top_level.txt
writing manifest file 'foo.egg-info/SOURCES.txt'
reading manifest file 'foo.egg-info/SOURCES.txt'
writing manifest file 'foo.egg-info/SOURCES.txt'
running build_ext
Creating /Users/jaraco/issue-1005/.env/lib/python3.6/site-packages/foo.egg-link (link to .)
Adding foo 1.0 to easy-install.pth file

Installed /Users/jaraco/issue-1005
Processing dependencies for foo==1.0
Finished processing dependencies for foo==1.0
(.env) $ cat .env/lib/python3.6/site-packages/easy-install.pth 
/Users/jaraco/issue-1005

Even when I use pip on Flask, I don't have the issue:

(.env) $ git clone -q https://github.com/mitsuhiko/flask.git
(.env) $ cat .env/lib/python3.6/site-packages/easy-install.pth
(.env) $ PYTHONPATH=$(pwd)/flask pip install -v --no-deps -e flask
Obtaining file:///Users/jaraco/issue-1005/flask
  Running setup.py (path:/Users/jaraco/issue-1005/flask/setup.py) egg_info for package from file:///Users/jaraco/issue-1005/flask
    Running command python setup.py egg_info
    running egg_info
    writing Flask.egg-info/PKG-INFO
    writing dependency_links to Flask.egg-info/dependency_links.txt
    writing entry points to Flask.egg-info/entry_points.txt
    writing requirements to Flask.egg-info/requires.txt
    writing top-level names to Flask.egg-info/top_level.txt
    /Users/jaraco/issue-1005/.env/lib/python3.6/site-packages/setuptools/dist.py:331: UserWarning: Normalizing '0.13-dev' to '0.13.dev0'
      normalized_version,
    warning: manifest_maker: standard file '-c' not found

    reading manifest file 'Flask.egg-info/SOURCES.txt'
    reading manifest template 'MANIFEST.in'
    warning: no previously-included files matching '*.py[co]' found anywhere in distribution
    no previously-included directories found matching 'docs/_build'
    no previously-included directories found matching 'docs/_themes'
    writing manifest file 'Flask.egg-info/SOURCES.txt'
  Source in ./flask has version 0.13.dev0, which satisfies requirement Flask==0.13.dev0 from file:///Users/jaraco/issue-1005/flask
Installing collected packages: Flask
  Found existing installation: Flask 0.13.dev0
    Not uninstalling flask at /Users/jaraco/issue-1005/flask, outside environment /Users/jaraco/issue-1005/.env
  Running setup.py develop for Flask
    Running command /Users/jaraco/issue-1005/.env/bin/python3 -c "import setuptools, tokenize;__file__='/Users/jaraco/issue-1005/flask/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" develop --no-deps
    running develop
    running egg_info
    writing Flask.egg-info/PKG-INFO
    writing dependency_links to Flask.egg-info/dependency_links.txt
    writing entry points to Flask.egg-info/entry_points.txt
    writing requirements to Flask.egg-info/requires.txt
    writing top-level names to Flask.egg-info/top_level.txt
    /Users/jaraco/issue-1005/.env/lib/python3.6/site-packages/setuptools/dist.py:331: UserWarning: Normalizing '0.13-dev' to '0.13.dev0'
      normalized_version,
    warning: manifest_maker: standard file '-c' not found

    reading manifest file 'Flask.egg-info/SOURCES.txt'
    reading manifest template 'MANIFEST.in'
    warning: no previously-included files matching '*.py[co]' found anywhere in distribution
    no previously-included directories found matching 'docs/_build'
    no previously-included directories found matching 'docs/_themes'
    writing manifest file 'Flask.egg-info/SOURCES.txt'
    running build_ext
    Creating /Users/jaraco/issue-1005/.env/lib/python3.6/site-packages/Flask.egg-link (link to .)
    Adding Flask 0.13.dev0 to easy-install.pth file
    Installing flask script to /Users/jaraco/issue-1005/.env/bin

    Installed /Users/jaraco/issue-1005/flask
Successfully installed Flask
Cleaning up...
(.env) $ cat .env/lib/python3.6/site-packages/easy-install.pth    
/Users/jaraco/issue-1005/flask

So on first blush, I'm unable to replicate your issue.

But more importantly, as reported, I'm not even sure this is an issue we wish to tackle. It doesn't strike me as unreasonable that if the installer is in an environment where the package is already on sys.path that it would choose not to add the redundant .pth entry.

So to proceed, we need two things: first, an example that can be readily replicated on late versions of Python and Setuptools (without pip); and second, a justification for why the use-case is valid (i.e. an answer to why would anyone want to set PYTHONPATH and setup.py develop the package?).

@asmodehn
Copy link
Author

asmodehn commented Apr 10, 2017 via email

@asmodehn
Copy link
Author

Firstly, I was able to reproduce the behavior you had, but with python2.7 :

alexv@AlexV-Linux:~$ mkvirtualenv issue-1005
New python executable in /home/alexv/.virtualenvs/issue-1005/bin/python
Installing setuptools, pip, wheel...done.
(issue-1005) alexv@AlexV-Linux:~$ git clone -q https://github.com/mitsuhiko/flask.git
(issue-1005) alexv@AlexV-Linux:~$ PYTHONPATH=$(pwd)/flask pip install -v --no-deps -e flask
Obtaining file:///home/alexv/flask
  Running setup.py (path:/home/alexv/flask/setup.py) egg_info for package from file:///home/alexv/flask
    Running command python setup.py egg_info
    /home/alexv/.virtualenvs/issue-1005/local/lib/python2.7/site-packages/setuptools/dist.py:334: UserWarning: Normalizing '0.13-dev' to '0.13.dev0'
      normalized_version,
    running egg_info
    creating Flask.egg-info
    writing requirements to Flask.egg-info/requires.txt
    writing Flask.egg-info/PKG-INFO
    writing top-level names to Flask.egg-info/top_level.txt
    writing dependency_links to Flask.egg-info/dependency_links.txt
    writing entry points to Flask.egg-info/entry_points.txt
    writing manifest file 'Flask.egg-info/SOURCES.txt'
    reading manifest file 'Flask.egg-info/SOURCES.txt'
    reading manifest template 'MANIFEST.in'
    warning: no previously-included files matching '*.py[co]' found anywhere in distribution
    no previously-included directories found matching 'docs/_build'
    no previously-included directories found matching 'docs/_themes'
    writing manifest file 'Flask.egg-info/SOURCES.txt'
  Source in ./flask has version 0.13.dev0, which satisfies requirement Flask==0.13.dev0 from file:///home/alexv/flask
Installing collected packages: Flask
  Running setup.py develop for Flask
    Running command /home/alexv/.virtualenvs/issue-1005/bin/python -c "import setuptools, tokenize;__file__='/home/alexv/flask/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" develop --no-deps
    /home/alexv/.virtualenvs/issue-1005/local/lib/python2.7/site-packages/setuptools/dist.py:334: UserWarning: Normalizing '0.13-dev' to '0.13.dev0'
      normalized_version,
    running develop
    running egg_info
    writing requirements to Flask.egg-info/requires.txt
    writing Flask.egg-info/PKG-INFO
    writing top-level names to Flask.egg-info/top_level.txt
    writing dependency_links to Flask.egg-info/dependency_links.txt
    writing entry points to Flask.egg-info/entry_points.txt
    reading manifest file 'Flask.egg-info/SOURCES.txt'
    reading manifest template 'MANIFEST.in'
    warning: no previously-included files matching '*.py[co]' found anywhere in distribution
    no previously-included directories found matching 'docs/_build'
    no previously-included directories found matching 'docs/_themes'
    writing manifest file 'Flask.egg-info/SOURCES.txt'
    running build_ext
    Creating /home/alexv/.virtualenvs/issue-1005/lib/python2.7/site-packages/Flask.egg-link (link to .)
    Adding Flask 0.13.dev0 to easy-install.pth file
    Installing flask script to /home/alexv/.virtualenvs/issue-1005/bin

    Installed /home/alexv/flask
Successfully installed Flask
Cleaning up...
(issue-1005) alexv@AlexV-Linux:~$ cat ~/.virtualenvs/issue-1005/
bin/                include/            lib/                local/              pip-selfcheck.json  
(issue-1005) alexv@AlexV-Linux:~$ cat ~/.virtualenvs/issue-1005/lib/python2.7/
_abcoll.py                   copy_reg.py                  genericpath.py               locale.pyc                   posixpath.py                 site.pyc                     sre_parse.pyc                UserDict.py                  
_abcoll.pyc                  copy_reg.pyc                 genericpath.pyc              no-global-site-packages.txt  posixpath.pyc                sre_compile.py               sre.py                       UserDict.pyc                 
abc.py                       distutils/                   lib-dynload/                 ntpath.py                    re.py                        sre_compile.pyc              stat.py                      warnings.py                  
abc.pyc                      encodings/                   linecache.py                 orig-prefix.txt              re.pyc                       sre_constants.py             stat.pyc                     warnings.pyc                 
codecs.py                    fnmatch.py                   linecache.pyc                os.py                        site-packages/               sre_constants.pyc            types.py                     _weakrefset.py               
codecs.pyc                   fnmatch.pyc                  locale.py                    os.pyc                       site.py                      sre_parse.py                 types.pyc                    _weakrefset.pyc              
(issue-1005) alexv@AlexV-Linux:~$ cat ~/.virtualenvs/issue-1005/lib/python2.7/site-packages/easy-install.pth 
/home/alexv/flask

But this is already the case in the upstream issue. The problem is that if, just after, I do :

(issue-1005) alexv@AlexV-Linux:~$ git clone -q https://github.com/kennethreitz/requests
(issue-1005) alexv@AlexV-Linux:~$ PYTHONPATH=$(pwd)/flask pip install -v --no-deps -e requests
Obtaining file:///home/alexv/requests
  Running setup.py (path:/home/alexv/requests/setup.py) egg_info for package from file:///home/alexv/requests
    Running command python setup.py egg_info
    running egg_info
    creating requests.egg-info
    writing requirements to requests.egg-info/requires.txt
    writing requests.egg-info/PKG-INFO
    writing top-level names to requests.egg-info/top_level.txt
    writing dependency_links to requests.egg-info/dependency_links.txt
    writing manifest file 'requests.egg-info/SOURCES.txt'
    reading manifest file 'requests.egg-info/SOURCES.txt'
    reading manifest template 'MANIFEST.in'
    warning: no files found matching 'test_requests.py'
    writing manifest file 'requests.egg-info/SOURCES.txt'
  Source in ./requests has version 2.13.0, which satisfies requirement requests==2.13.0 from file:///home/alexv/requests
Installing collected packages: requests
  Running setup.py develop for requests
    Running command /home/alexv/.virtualenvs/issue-1005/bin/python -c "import setuptools, tokenize;__file__='/home/alexv/requests/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" develop --no-deps
    running develop
    running egg_info
    writing requirements to requests.egg-info/requires.txt
    writing requests.egg-info/PKG-INFO
    writing top-level names to requests.egg-info/top_level.txt
    writing dependency_links to requests.egg-info/dependency_links.txt
    reading manifest file 'requests.egg-info/SOURCES.txt'
    reading manifest template 'MANIFEST.in'
    warning: no files found matching 'test_requests.py'
    writing manifest file 'requests.egg-info/SOURCES.txt'
    running build_ext
    Creating /home/alexv/.virtualenvs/issue-1005/lib/python2.7/site-packages/requests.egg-link (link to .)
    Adding requests 2.13.0 to easy-install.pth file

    Installed /home/alexv/requests
Successfully installed requests
Cleaning up...
(issue-1005) alexv@AlexV-Linux:~$ cat ~/.virtualenvs/issue-1005/lib/python2.7/site-packages/easy-install.pth 
/home/alexv/requests

Flask is now gone...

In your setuptools example, you have only one directory with one package.
To reproduce, I think we need two directories and two packages :

alexv@AlexV-Linux:~$ mkdir issue-1005
alexv@AlexV-Linux:~$ cd issue-1005/
alexv@AlexV-Linux:~/issue-1005$ mkdir foo_repo
alexv@AlexV-Linux:~/issue-1005$ mkdir bar_repo
alexv@AlexV-Linux:~/issue-1005$ python -m virtualenv .env
New python executable in /home/alexv/issue-1005/.env/bin/python
Installing setuptools, pip, wheel...done.
alexv@AlexV-Linux:~/issue-1005$ . .env/bin/activate
(.env) alexv@AlexV-Linux:~/issue-1005$ cat > foo_repo/setup.py
import setuptools
setuptools.setup(name='foo', version='1.0', packages=['foo_pkg'])
(.env) alexv@AlexV-Linux:~/issue-1005$ cat > bar_repo/setup.py
import setuptools
setuptools.setup(name='bar', version='1.0', packages=['bar_pkg'])    
(.env) alexv@AlexV-Linux:~/issue-1005$ mkdir foo_repo/foo_pkg
(.env) alexv@AlexV-Linux:~/issue-1005$ mkdir bar_repo/bar_pkg
(.env) alexv@AlexV-Linux:~/issue-1005$ touch foo_repo/foo_pkg/__init__.py
(.env) alexv@AlexV-Linux:~/issue-1005$ touch bar_repo/bar_pkg/__init__.py
(.env) alexv@AlexV-Linux:~/issue-1005$ PYTHONPATH=$(pwd)/foo_repo python foo_repo/setup.py develop -q
running develop
running egg_info
creating foo.egg-info
writing foo.egg-info/PKG-INFO
writing top-level names to foo.egg-info/top_level.txt
writing dependency_links to foo.egg-info/dependency_links.txt
writing manifest file 'foo.egg-info/SOURCES.txt'
error: package directory 'foo_pkg' does not exist
(.env) alexv@AlexV-Linux:~/issue-1005$ cd foo_repo/
(.env) alexv@AlexV-Linux:~/issue-1005/foo_repo$ PYTHONPATH=$(pwd) python setup.py develop -q
running develop
running egg_info
creating foo.egg-info
writing foo.egg-info/PKG-INFO
writing top-level names to foo.egg-info/top_level.txt
writing dependency_links to foo.egg-info/dependency_links.txt
writing manifest file 'foo.egg-info/SOURCES.txt'
reading manifest file 'foo.egg-info/SOURCES.txt'
writing manifest file 'foo.egg-info/SOURCES.txt'
running build_ext
Creating /home/alexv/issue-1005/.env/lib/python2.7/site-packages/foo.egg-link (link to .)
Adding foo 1.0 to easy-install.pth file

Installed /home/alexv/issue-1005/foo_repo
Processing dependencies for foo==1.0
Finished processing dependencies for foo==1.0
(.env) alexv@AlexV-Linux:~/issue-1005/foo_repo$ cat ../.env/lib/python2.7/site-packages/easy-install.pth 
/home/alexv/issue-1005/foo_repo
(.env) alexv@AlexV-Linux:~/issue-1005/foo_repo$ cd ../bar_repo/
(.env) alexv@AlexV-Linux:~/issue-1005/bar_repo$ PYTHONPATH=/home/alexv/issue-1005/foo_repo python setup.py develop -q
running develop
running egg_info
creating bar.egg-info
writing bar.egg-info/PKG-INFO
writing top-level names to bar.egg-info/top_level.txt
writing dependency_links to bar.egg-info/dependency_links.txt
writing manifest file 'bar.egg-info/SOURCES.txt'
reading manifest file 'bar.egg-info/SOURCES.txt'
writing manifest file 'bar.egg-info/SOURCES.txt'
running build_ext
Creating /home/alexv/issue-1005/.env/lib/python2.7/site-packages/bar.egg-link (link to .)
Adding bar 1.0 to easy-install.pth file

Installed /home/alexv/issue-1005/bar_repo
Processing dependencies for bar==1.0
Finished processing dependencies for bar==1.0
(.env) alexv@AlexV-Linux:~/issue-1005/bar_repo$ cat ../.env/lib/python2.7/site-packages/easy-install.pth 
/home/alexv/issue-1005/bar_repo

So here we can see the path /home/alexv/issue-1005/foo_repo that was in the PYTHONPATH has been removed from the easy-install.pth file when installing bar_pkg via python setup.py develop.

@jaraco
Copy link
Member

jaraco commented Apr 10, 2017

Aha. That helps. So it's the easy-install.pth cleanup that's apparently implicated, probably in easy_install.easy_install.update_pth, and that function relies on state from other functions, so it's not a simple routine to read.

I don't know when I'll have time to dig deeper, so if you have the time and drive, the next step will be to trace the code during the install of this second package and see if you can detect where the foo_repo is getting removed from the path.

@asmodehn
Copy link
Author

I m on my first adventure inside setuptools code, so I m not sure I understand much yet...

I tried to debug with PyCharm, but Pycharm also modify the PYTHONPATH with the current projects path, depending what you launch and the order of the projects in the UI, so it s a bit tricky to find... Maybe they were attempting to work around the problem we are having...
Anyway using pdb (maybe setuptools.sandbox ?) might be needed to get the exact behavior but I m not familiar with all this.

In case it is useful I started https://github.com/asmodehn/setuptools-1005
With two project foo and bar, what seems to happen as far as I understand is, tracing the relevant steps :

I am not sure what is the intent behind removing, from the easy-install.pth file, all the paths that are in PYTHONPATH... Removing the currently installing package makes sense, but all the ones in PYTHONPATH ?

@pganssle pganssle added the Needs Triage Issues that need to be evaluated for severity and status. label Oct 19, 2018
@hauntsaninja
Copy link
Contributor

Hello! I ran into this as well. As others have pointed out, the cleanup function here is a little over-eager.

It's easy to repro as well.

$ python3 -m venv env
$ source env/bin/activate
$ python -m pip install --upgrade setuptools pip
$ vim setup.py
$ cat setup.py 
from setuptools import setup

setup(name="repro", version="1")

$ # easy-install.pth doesn't exist
$ cat $(python -c 'import site; print(site.getsitepackages()[0])')/easy-install.pth
$ python setup.py develop
$ # now easy-install.pth exists, contains the path to here
$ cat $(python -c 'import site; print(site.getsitepackages()[0])')/easy-install.pth

$ # add some random other directory that exists to easy-install.pth
$ echo $HOME >> $(python -c 'import site; print(site.getsitepackages()[0])')/easy-install.pth
$ python setup.py develop
$ # easy-install.pth contents are unaffected
$ cat $(python -c 'import site; print(site.getsitepackages()[0])')/easy-install.pth

$ # but do the same thing with PYTHONPATH
$ PYTHONPATH=$HOME python setup.py develop
$ # oops, random other stuff is removed from easy-install.pth based on PYTHONPATH
$ cat $(python -c 'import site; print(site.getsitepackages()[0])')/easy-install.pth

Do you agree that this is a bug / would you accept a PR fixing this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Triage Issues that need to be evaluated for severity and status.
Projects
None yet
Development

No branches or pull requests

4 participants