|
5 | 5 | from pip._internal.exceptions import ( |
6 | 6 | DistributionNotFound, |
7 | 7 | InstallationError, |
| 8 | + InstallationSubprocessError, |
| 9 | + MetadataInconsistent, |
8 | 10 | UnsupportedPythonVersion, |
9 | 11 | UnsupportedWheel, |
10 | 12 | ) |
|
33 | 35 | ExplicitRequirement, |
34 | 36 | RequiresPythonRequirement, |
35 | 37 | SpecifierRequirement, |
| 38 | + UnsatisfiableRequirement, |
36 | 39 | ) |
37 | 40 |
|
38 | 41 | if MYPY_CHECK_RUNNING: |
@@ -94,6 +97,7 @@ def __init__( |
94 | 97 | self._force_reinstall = force_reinstall |
95 | 98 | self._ignore_requires_python = ignore_requires_python |
96 | 99 |
|
| 100 | + self._build_failures = {} # type: Cache[InstallationError] |
97 | 101 | self._link_candidate_cache = {} # type: Cache[LinkCandidate] |
98 | 102 | self._editable_candidate_cache = {} # type: Cache[EditableCandidate] |
99 | 103 | self._installed_candidate_cache = { |
@@ -136,21 +140,40 @@ def _make_candidate_from_link( |
136 | 140 | name, # type: Optional[str] |
137 | 141 | version, # type: Optional[_BaseVersion] |
138 | 142 | ): |
139 | | - # type: (...) -> Candidate |
| 143 | + # type: (...) -> Optional[Candidate] |
140 | 144 | # TODO: Check already installed candidate, and use it if the link and |
141 | 145 | # editable flag match. |
| 146 | + |
| 147 | + if link in self._build_failures: |
| 148 | + # We already tried this candidate before, and it does not build. |
| 149 | + # Don't bother trying again. |
| 150 | + return None |
| 151 | + |
142 | 152 | if template.editable: |
143 | 153 | if link not in self._editable_candidate_cache: |
144 | | - self._editable_candidate_cache[link] = EditableCandidate( |
145 | | - link, template, factory=self, name=name, version=version, |
146 | | - ) |
| 154 | + try: |
| 155 | + self._editable_candidate_cache[link] = EditableCandidate( |
| 156 | + link, template, factory=self, |
| 157 | + name=name, version=version, |
| 158 | + ) |
| 159 | + except (InstallationSubprocessError, MetadataInconsistent) as e: |
| 160 | + logger.warning("Discarding %s. %s", link, e) |
| 161 | + self._build_failures[link] = e |
| 162 | + return None |
147 | 163 | base = self._editable_candidate_cache[link] # type: BaseCandidate |
148 | 164 | else: |
149 | 165 | if link not in self._link_candidate_cache: |
150 | | - self._link_candidate_cache[link] = LinkCandidate( |
151 | | - link, template, factory=self, name=name, version=version, |
152 | | - ) |
| 166 | + try: |
| 167 | + self._link_candidate_cache[link] = LinkCandidate( |
| 168 | + link, template, factory=self, |
| 169 | + name=name, version=version, |
| 170 | + ) |
| 171 | + except (InstallationSubprocessError, MetadataInconsistent) as e: |
| 172 | + logger.warning("Discarding %s. %s", link, e) |
| 173 | + self._build_failures[link] = e |
| 174 | + return None |
153 | 175 | base = self._link_candidate_cache[link] |
| 176 | + |
154 | 177 | if extras: |
155 | 178 | return ExtrasCandidate(base, extras) |
156 | 179 | return base |
@@ -210,13 +233,16 @@ def iter_index_candidates(): |
210 | 233 | for ican in reversed(icans): |
211 | 234 | if not all_yanked and ican.link.is_yanked: |
212 | 235 | continue |
213 | | - yield self._make_candidate_from_link( |
| 236 | + candidate = self._make_candidate_from_link( |
214 | 237 | link=ican.link, |
215 | 238 | extras=extras, |
216 | 239 | template=template, |
217 | 240 | name=name, |
218 | 241 | version=ican.version, |
219 | 242 | ) |
| 243 | + if candidate is None: |
| 244 | + continue |
| 245 | + yield candidate |
220 | 246 |
|
221 | 247 | return FoundCandidates( |
222 | 248 | iter_index_candidates, |
@@ -280,6 +306,16 @@ def make_requirement_from_install_req(self, ireq, requested_extras): |
280 | 306 | name=canonicalize_name(ireq.name) if ireq.name else None, |
281 | 307 | version=None, |
282 | 308 | ) |
| 309 | + if cand is None: |
| 310 | + # There's no way we can satisfy a URL requirement if the underlying |
| 311 | + # candidate fails to build. An unnamed URL must be user-supplied, so |
| 312 | + # we fail eagerly. If the URL is named, an unsatisfiable requirement |
| 313 | + # can make the resolver do the right thing, either backtrack (and |
| 314 | + # maybe find some other requirement that's buildable) or raise a |
| 315 | + # ResolutionImpossible eventually. |
| 316 | + if not ireq.name: |
| 317 | + raise self._build_failures[ireq.link] |
| 318 | + return UnsatisfiableRequirement(canonicalize_name(ireq.name)) |
283 | 319 | return self.make_requirement_from_candidate(cand) |
284 | 320 |
|
285 | 321 | def make_requirement_from_candidate(self, candidate): |
|
0 commit comments