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

Release 21.1.2 #10012

Merged
merged 14 commits into from
May 23, 2021
3 changes: 3 additions & 0 deletions AUTHORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ AraHaan
Arindam Choudhury
Armin Ronacher
Artem
Arun Babu Neelicattu
Ashley Manton
Ashwin Ramaswami
atse
Expand Down Expand Up @@ -182,6 +183,7 @@ Desetude
Devesh Kumar Singh
Diego Caraballo
DiegoCaraballo
Dimitri Merejkowsky
Dmitry Gladkov
Domen Kožar
Dominic Davis-Foster
Expand Down Expand Up @@ -408,6 +410,7 @@ Nathaniel J. Smith
Nehal J Wani
Neil Botelho
Nguyễn Gia Phong
Nicholas Serra
Nick Coghlan
Nick Stenning
Nick Timkovich
Expand Down
17 changes: 16 additions & 1 deletion NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@

.. towncrier release notes start

21.1.2 (2021-05-23)
===================

Bug Fixes
---------

- New resolver: Correctly exclude an already installed package if its version is
known to be incompatible to stop the dependency resolution process with a clear
error message. (`#9841 <https://github.com/pypa/pip/issues/9841>`_)
- Allow ZIP to archive files with timestamps earlier than 1980. (`#9910 <https://github.com/pypa/pip/issues/9910>`_)
- Emit clearer error message when a project root does not contain either
``pyproject.toml``, ``setup.py`` or ``setup.cfg``. (`#9944 <https://github.com/pypa/pip/issues/9944>`_)
- Fix detection of existing standalone pip instance for PEP 517 builds. (`#9953 <https://github.com/pypa/pip/issues/9953>`_)


21.1.1 (2021-04-30)
===================

Expand All @@ -18,7 +33,7 @@ Deprecations and Removals
- Temporarily set the new "Value for ... does not match" location warnings level
to *DEBUG*, to hide them from casual users. This prepares pip 21.1 for CPython
inclusion, while pip maintainers digest the first intake of location mismatch
issues for the ``distutils``-``sysconfig`` trasition. (`#9912 <https://github.com/pypa/pip/issues/9912>`_)
issues for the ``distutils``-``sysconfig`` transition. (`#9912 <https://github.com/pypa/pip/issues/9912>`_)

Bug Fixes
---------
Expand Down
11 changes: 7 additions & 4 deletions src/pip/_internal/build_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,18 @@ def _create_standalone_pip() -> Iterator[str]:
"""
source = pathlib.Path(pip_location).resolve().parent

# Return the current instance if it is already a zip file. This can happen
# if a PEP 517 requirement is an sdist itself.
if not source.is_dir() and source.parent.name == "__env_pip__.zip":
# Return the current instance if `source` is not a directory. We can't build
# a zip from this, and it likely means the instance is already standalone.
if not source.is_dir():
yield str(source)
return

with TempDirectory(kind="standalone-pip") as tmp_dir:
pip_zip = os.path.join(tmp_dir.path, "__env_pip__.zip")
with zipfile.ZipFile(pip_zip, "w") as zf:
kwargs = {}
if sys.version_info >= (3, 8):
kwargs["strict_timestamps"] = False
with zipfile.ZipFile(pip_zip, "w", **kwargs) as zf:
for child in source.rglob("*"):
zf.write(child, child.relative_to(source.parent).as_posix())
yield os.path.join(pip_zip, "pip")
Expand Down
19 changes: 19 additions & 0 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,13 +509,32 @@ def load_pyproject_toml(self):
self.unpacked_source_directory, backend, backend_path=backend_path,
)

def _check_setup_py_or_cfg_exists(self) -> bool:
"""Check if the requirement actually has a setuptools build file.

If setup.py does not exist, we also check setup.cfg in the same
directory and allow the directory if that exists.
"""
if os.path.exists(self.setup_py_path):
return True
stem, ext = os.path.splitext(self.setup_py_path)
if ext == ".py" and os.path.exists(f"{stem}.cfg"):
return True
return False

def _generate_metadata(self):
# type: () -> str
"""Invokes metadata generator functions, with the required arguments.
"""
if not self.use_pep517:
assert self.unpacked_source_directory

if not self._check_setup_py_or_cfg_exists():
raise InstallationError(
f'File "setup.py" or "setup.cfg" not found for legacy '
f'project {self}.'
)

return generate_metadata_legacy(
build_env=self.build_env,
setup_py_path=self.setup_py_path,
Expand Down
37 changes: 24 additions & 13 deletions src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,18 +240,29 @@ def _iter_found_candidates(
hashes &= ireq.hashes(trust_internet=False)
extras |= frozenset(ireq.extras)

# Get the installed version, if it matches, unless the user
# specified `--force-reinstall`, when we want the version from
# the index instead.
installed_candidate = None
if not self._force_reinstall and name in self._installed_dists:
installed_dist = self._installed_dists[name]
if specifier.contains(installed_dist.version, prereleases=True):
installed_candidate = self._make_candidate_from_dist(
dist=installed_dist,
extras=extras,
template=template,
)
def _get_installed_candidate() -> Optional[Candidate]:
"""Get the candidate for the currently-installed version."""
# If --force-reinstall is set, we want the version from the index
# instead, so we "pretend" there is nothing installed.
if self._force_reinstall:
return None
try:
installed_dist = self._installed_dists[name]
except KeyError:
return None
# Don't use the installed distribution if its version does not fit
# the current dependency graph.
if not specifier.contains(installed_dist.version, prereleases=True):
return None
candidate = self._make_candidate_from_dist(
dist=installed_dist,
extras=extras,
template=template,
)
# The candidate is a known incompatiblity. Don't use it.
if id(candidate) in incompatible_ids:
return None
return candidate

def iter_index_candidate_infos():
# type: () -> Iterator[IndexCandidateInfo]
Expand Down Expand Up @@ -283,7 +294,7 @@ def iter_index_candidate_infos():

return FoundCandidates(
iter_index_candidate_infos,
installed_candidate,
_get_installed_candidate(),
prefers_installed,
incompatible_ids,
)
Expand Down
16 changes: 6 additions & 10 deletions src/pip/_internal/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,18 +269,14 @@ def tabulate(rows):
return table, sizes


def is_installable_dir(path):
# type: (str) -> bool
"""Is path is a directory containing setup.py or pyproject.toml?"""
def is_installable_dir(path: str) -> bool:
"""Is path is a directory containing pyproject.toml, setup.cfg or setup.py?"""
if not os.path.isdir(path):
return False
setup_py = os.path.join(path, "setup.py")
if os.path.isfile(setup_py):
return True
pyproject_toml = os.path.join(path, "pyproject.toml")
if os.path.isfile(pyproject_toml):
return True
return False
return any(
os.path.isfile(os.path.join(path, signifier))
for signifier in ("pyproject.toml", "setup.cfg", "setup.py")
)


def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE):
Expand Down
30 changes: 30 additions & 0 deletions tests/functional/test_new_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -1827,3 +1827,33 @@ def test_new_resolver_direct_url_with_extras(tmp_path, script):
assert not get_created_direct_url(result, "pkg1")
assert get_created_direct_url(result, "pkg2")
assert not get_created_direct_url(result, "pkg3")


def test_new_resolver_modifies_installed_incompatible(script):
create_basic_wheel_for_package(script, name="a", version="1")
create_basic_wheel_for_package(script, name="a", version="2")
create_basic_wheel_for_package(script, name="a", version="3")
create_basic_wheel_for_package(script, name="b", version="1", depends=["a==1"])
create_basic_wheel_for_package(script, name="b", version="2", depends=["a==2"])
create_basic_wheel_for_package(script, name="c", version="1", depends=["a!=1"])
create_basic_wheel_for_package(script, name="c", version="2", depends=["a!=1"])
create_basic_wheel_for_package(script, name="d", version="1", depends=["b", "c"])

script.pip(
"install",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"b==1",
)

# d-1 depends on b and c. b-1 is already installed and therefore first
# pinned, but later found to be incompatible since the "a==1" dependency
# makes all c versions impossible to satisfy. The resolver should be able to
# discard b-1 and backtrack, so b-2 is selected instead.
script.pip(
"install",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"d==1",
)
assert_installed(script, d="1", c="2", b="2", a="2")
4 changes: 2 additions & 2 deletions tools/news/template.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% set underline = "=" %}
{{ top_line }}
Copy link
Member

Choose a reason for hiding this comment

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

@pradyunsg @sbidoul FYI this change has completely removed the draft release title. See: https://pip.pypa.io/en/stable/news/

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, that is interesting. I wanted to fix the release process which produced a duplicate title.
From a quick look at the towncrier source code I could not find a way to make it work consistently in draft and non draft mode when title_format is set in [tools.towncrier]. There must be something I misunderstand in towncrier... or it is bug, idk. Or perhaps the part of our toolchain that uses it in draft mode (where is that?) could generate the title?

Copy link
Member

Choose a reason for hiding this comment

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

Yea, well, I prefer that to a duplicated heading in the release docs. :)

Copy link
Member

Choose a reason for hiding this comment

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

@sbidoul this is probably a towncrier bug because the behavior changed with the latest release.

Copy link
Member

Choose a reason for hiding this comment

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

The Towncrier regression makes normal and draft runs produce different outputs: twisted/towncrier#105. Possible upstream fix: twisted/towncrier#303.

{{ underline * top_line|length }}

{{ underline * ((top_line)|length) }}
{% for section in sections %}
{% set underline = "-" %}
{% if section %}
Expand Down