From 760cb66c14274232ee8ee5e5d69de9d7efdc8d9d Mon Sep 17 00:00:00 2001 From: Tom Solberg Date: Thu, 3 Nov 2022 09:47:21 +0100 Subject: [PATCH] fix: Do not add local package to lockfile (#1481) * Do not add local package to lockfile * cleanup * format * doctor the resolution instead of lockfile * update news * fix flake8 * fix imports --- news/1481.bugfix.md | 1 + src/pdm/installers/core.py | 5 ++++- src/pdm/models/repositories.py | 27 ++++++++++++++++++++++++++- src/pdm/resolver/core.py | 15 +++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 news/1481.bugfix.md diff --git a/news/1481.bugfix.md b/news/1481.bugfix.md new file mode 100644 index 0000000000..1204482b87 --- /dev/null +++ b/news/1481.bugfix.md @@ -0,0 +1 @@ +The local package is now treated specially during installation and locking. This means it will no longer be included in the lockfile, and should never be installed twice even when using nested extras. This will ensure the lockdown stays relevant when the version changes. diff --git a/src/pdm/installers/core.py b/src/pdm/installers/core.py index 159aa4c043..deb8eb5b02 100644 --- a/src/pdm/installers/core.py +++ b/src/pdm/installers/core.py @@ -22,7 +22,10 @@ def install_requirements( resolver = project.core.resolver_class(provider, reporter) resolve_max_rounds = int(project.config["strategy.resolve_max_rounds"]) resolved, _ = resolve( - resolver, reqs, environment.python_requires, max_rounds=resolve_max_rounds + resolver, + reqs, + environment.python_requires, + max_rounds=resolve_max_rounds, ) manager = InstallManager(environment, use_install_cache=use_install_cache) working_set = environment.get_working_set() diff --git a/src/pdm/models/repositories.py b/src/pdm/models/repositories.py index f04d4b83b9..0ec80f8076 100644 --- a/src/pdm/models/repositories.py +++ b/src/pdm/models/repositories.py @@ -436,8 +436,33 @@ def _identify_candidate(self, candidate: Candidate) -> tuple: def _get_dependencies_from_lockfile(self, candidate: Candidate) -> CandidateInfo: return self.candidate_info[self._identify_candidate(candidate)] + def _get_dependency_from_local_package(self, candidate: Candidate) -> CandidateInfo: + """Adds the local package as a candidate only if the candidate + name is the same as the local package.""" + if candidate.name != self.environment.project.name: + raise CandidateInfoNotFound(candidate) from None + + reqs = self.environment.project.meta.dependencies + if candidate.req.extras is not None: + reqs = sum( + ( + self.environment.project.meta.optional_dependencies[g] + for g in candidate.req.extras + ), + [], + ) + + return ( + reqs, + str(self.environment.python_requires), + self.environment.project.meta.description, + ) + def dependency_generators(self) -> Iterable[Callable[[Candidate], CandidateInfo]]: - return (self._get_dependencies_from_lockfile,) + return ( + self._get_dependency_from_local_package, + self._get_dependencies_from_lockfile, + ) def find_candidates( self, diff --git a/src/pdm/resolver/core.py b/src/pdm/resolver/core.py index 4fe8533382..d6a85741a2 100644 --- a/src/pdm/resolver/core.py +++ b/src/pdm/resolver/core.py @@ -3,8 +3,11 @@ from typing import TYPE_CHECKING, Dict, cast from pdm.models.candidates import Candidate +from pdm.models.repositories import BaseRepository +from pdm.models.requirements import strip_extras from pdm.resolver.providers import BaseProvider from pdm.resolver.python import PythonRequirement +from pdm.utils import normalize_name if TYPE_CHECKING: from resolvelib.resolvers import Resolver @@ -27,13 +30,22 @@ def resolve( """ requirements.append(PythonRequirement.from_pyspec_set(requires_python)) provider = cast(BaseProvider, resolver.provider) + repository = cast(BaseRepository, provider.repository) + result = resolver.resolve(requirements, max_rounds) mapping = cast(Dict[str, Candidate], result.mapping) mapping.pop("python", None) + + local_name = ( + normalize_name(repository.environment.project.meta.name) + if repository.environment.project.name + else None + ) for key, candidate in list(result.mapping.items()): if key is None: continue + # For source distribution whose name can only be determined after it is built, # the key in the resolution map should be updated. if key.startswith(":empty:"): @@ -41,4 +53,7 @@ def resolve( mapping[new_key] = mapping.pop(key) key = new_key + if strip_extras(key)[0] == local_name: + del mapping[key] + return mapping, provider.fetched_dependencies