pip: `pip install .` leaves a build directory that is causing issues for some tools
Environment
- pip version: 20.1
- Python version: 3.6+
- OS: Any
Description Build-in-place leaves a build directory in the working directory, which can sometimes confuse tools like pytest when they discover pyc files inside.
Expected behavior
How to Reproduce
pip install .
pytest
Output
pytest
================================================================================== test session starts ==================================================================================
platform linux -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/omry/dev/hydra, inifile: pytest.ini
plugins: snail-0.1.0
collected 899 items / 1 error / 898 selected
======================================================================================== ERRORS =========================================================================================
_______________________________________________________________ ERROR collecting build/lib/hydra/test_utils/test_utils.py _______________________________________________________________
import file mismatch:
imported module 'hydra.test_utils.test_utils' has this __file__ attribute:
/home/omry/dev/hydra/hydra/test_utils/test_utils.py
which is not the same as the test file we want to collect:
/home/omry/dev/hydra/build/lib/hydra/test_utils/test_utils.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules
================================================================================ short test summary info ================================================================================
ERROR build/lib/hydra/test_utils/test_utils.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=================================================================================== 1 error in 0.64s ==
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 5
- Comments: 31 (20 by maintainers)
It’s worth noting that this affects more than pytest – git, docker, linters, IDEs, other test runners, etc. Of course they all have options to ignore a directory, but it’s a hassle. It would be better if there was an option (or default) for pip to clean up the build directory after installing.
I’d suggest filing an issue w/ pytest, to ask for a change to the discovery logic – ignore the
build
directory by default.I will say, I think that most or all of the breakages we ran into came up during the PEP 517 as things that were likely to be possible sources of breakages (for example). Maybe we need to do a better job of summarizing these discussions or when making changes related to past discussions going back and reviewing past discussions or something.
I’ve long believed that beta releases for tools like pip are of marginal benefit. When we’ve tried to do them in the past we’ve rarely caught many if any issues, even show stopping ones, until a final release was cut. That’s why I stopped doing them when I was still releasing pip and instead tried to be available to quickly react to broken releases. Attempting to phase large scale changes in over time is also a good pattern (with many ways to handle it like dark reads, opt in or opt out flags, etc).
I don’t think “blame” is the most useful thing to focus on. One could easily argue that everything was working fine until pip broke backwards compatibility by no longer ensuring builds were done in an ephemeral location. Unfortunately Python’s packaging has hit Hyrum’s Law to a pretty large degree, so it’s super common that anytime we make any changes to long standing behavior we’re going to be breaking compatibility with something. The only real solution to it is to be more deliberate and careful with rollback plans and provide ways to work round etc the more risky a change is, but to fundamentally accept that these things are going to happen as well.
I’m really curious how folks here, think we should communicate around potentially disruptive stuff like this. We had a beta period for getting feedback on this change, that was publicized an order of magnitude more than we’ve ever done in the past, and we still got no reports from users that would even hint at how disruptive this could be to their workflows.
We’ve been bitten by these things in the past (like back in 2018, with pip 10.0) and if you have any thoughts or feedback on how we can do communication around changes, and get feedback on those changes, please chime in at #7628. Everything from “here’s where I go to look for information, to I don’t expect pip to ever change, to I don’t look for information ever” is useful input to us.
A personal note
I still believe that none of the issues that have been flagged here are “pip’s fault” – they’re occurring because other tools (setuptools, pytest etc) are making incorrect assumptions about what the contents of a package directory are, and what they can do with that directory. Obviously, practicality beats purity here, and reverting this change and figuring out a significantly smoother approach is the right thing to do. pip isn’t generating the
build/
directory, setuptools is. Has setuptools always done that, heck yes.But like, why would tooling not ignore a
build
directory, that has been used for years by setuptools – which has been the de-facto standard for years. Are people not puttingbuild/
in their.gitignore
anymore? – GitHub’s default certainly has it. Is someone’s “Python IDE” or “Python test runner” not configured to ignorebuild/
? How is that a bug with pip, and not their tooling? Are users not willing to add a line to their docker containers, to do the right thing, instead of pushing more complexity into “digital infrastructure” tooling? How is that OK?This feels a lot like #5599 – Linux distros made an incorrect assumption, and a change in pip exposed that. What happens next? Lots of users reporting errors to pip, instead of their distro, and many Linux distros still don’t fix that assumption). I’m starting to really dislike not being able to make improvements to pip because “other stuff” is making assumptions about how pip’s internal processes work.
Presumably the workaround is simply to use the
--ignore=build
flag to pytest?Thanks for all the reports folks! For anyone who hasn’t been following this closely, a bunch of related discussion has taken place at https://github.com/pypa/pip/issues/7555#issuecomment-625362521.
If you’re wondering why this change was made: pip’s copy-to-a-temporary-directory-and-build-there logic has been a source of a lot of bugs (performance issues, correctness issues and more) over the years. We made this change to improve the user experience in this area, to solve those issues by removing that band-aid solution for something much simpler and less fragile. With hindsight, I think we pulled the band-aid out a bit too early/quickly. Although we expected there to be some workflows that’d break, we’d expected the breakages would occur in more niche usecases, and not be as broadly affecting as they have been.
In summary, this change is being lot more disruptive than we had initially anticipated and we’ll be reverting the “build in place” behavior change in a pip 20.1.1 release. We’ll be pursuing a different avenues to push build tools to stop making the assumption that we’ve broken with this change (that they can put whatever crap I want in the package’s root directory), likely by collaborating on mechanisms like the one proposed in https://discuss.python.org/t/2303/.
That’s a hard one. For some changes you can add a deprecation warning for a while when user is doing something that are not (longer) supposed to be doing. The problem with this one is that there is no way to gradually warn about it.
One possibility is something along the lines of asking people to explicitly opt into the new flow by introducing a command line flag / config file option. This can be accompanied by an active hint to the user when they run without making a choice.
The warning might look like:
I found another problem caused by this issue: it breaks manylinux builds which are based on the official manylinux demo project. It creates wheels which can’t be installed because of ABI incompatibility!
The demo iterates over many different pythons and runs
pip wheel .
repeatedly. The problem arises when the following happens:it runs
/opt/python/cp27-cp27m/bin/pip wheel .
: this creates abuild
directory in the cwdit runs
/opt/python/cp27-cp27mu/bin/pip wheel .
: now it finds thebuild
dir of the previous step but somehow it fails to recognize that it was created by an ABI-incompatible version of python (m
vsmu
): the result is that it creates a binary wheel which can’t be installed on the python that you just use to compile it!For a minimal example, look at this gist: it runs
pip wheel
twice, once withcp27-cp27m
and one withcp27-cp27mu
, then it tries to install/import the extension it just built. You can run it by doing this:This example (and all the travis-based projects which are based on manylinux-demo) still works with
quay.io/pypa/manylinux1_x86_64:2020-04-25-37c204c
which is the last to includepip 20.0.2
but it’s broken by all subsequent docker images which includepip 20.1
.If you manually downgrade to
pip==20.0.2
or remove thebuild
directory between the builds, then the example works again.I expect that many projects will start to automatically upload broken wheels as soon as they trigger a new travis-based version release, so it’s probably better to fix it soon.
Hmm, so I suppose the solution in Dockerfiles is
pip install . && rm -r build
Is there an option to pip to use the temporary location as in 20.0.2?
So, still curious… what is the point of
-b
? It seems like it does not alter any behavior of the permanent ‘build/‘ artifact. Could it be repurposed to specify the name of that dir?What is the expected behavior of the
--build
flag in this situation?@hsharrison, that should work for you, assuming you’re using
setuptools
as the build backend. However, it’s worth noting that different build backends may generate different junks in different places and I thinkpip
will wait until there’s a better standard specifying the expected behavior.@omry Your item 1. in the proposal is exactly what the thread @McSinyx mentioned is about.
@omry, I’ve just found out about this and hope you like it too: https://discuss.python.org/t/proposal-adding-a-persistent-cache-directory-to-pep-517-hooks/2303
In-place builds are new in 20.1, the reason 20.0.2 did not create a
build
directory is because the build was done in a temporary location.