pex 2.63.0__py2.py3-none-any.whl → 2.64.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pex might be problematic. Click here for more details.

Files changed (46) hide show
  1. pex/cli/commands/lock.py +15 -0
  2. pex/dist_metadata.py +36 -9
  3. pex/docs/html/_pagefind/fragment/{en_f2578bc.pf_fragment → en_3046a3a.pf_fragment} +0 -0
  4. pex/docs/html/_pagefind/fragment/en_3f5cca9.pf_fragment +0 -0
  5. pex/docs/html/_pagefind/fragment/en_5f2da5c.pf_fragment +0 -0
  6. pex/docs/html/_pagefind/fragment/en_7350892.pf_fragment +0 -0
  7. pex/docs/html/_pagefind/fragment/{en_66c5113.pf_fragment → en_ac9b982.pf_fragment} +0 -0
  8. pex/docs/html/_pagefind/fragment/en_d158da6.pf_fragment +0 -0
  9. pex/docs/html/_pagefind/fragment/en_e575d34.pf_fragment +0 -0
  10. pex/docs/html/_pagefind/fragment/en_fca878d.pf_fragment +0 -0
  11. pex/docs/html/_pagefind/index/en_23c894e.pf_index +0 -0
  12. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  13. pex/docs/html/_pagefind/pagefind.en_86ab41ad5d.pf_meta +0 -0
  14. pex/docs/html/_static/documentation_options.js +1 -1
  15. pex/docs/html/api/vars.html +5 -5
  16. pex/docs/html/buildingpex.html +5 -5
  17. pex/docs/html/genindex.html +5 -5
  18. pex/docs/html/index.html +5 -5
  19. pex/docs/html/recipes.html +5 -5
  20. pex/docs/html/scie.html +5 -5
  21. pex/docs/html/search.html +5 -5
  22. pex/docs/html/whatispex.html +5 -5
  23. pex/pip/tool.py +84 -11
  24. pex/pip/vcs.py +42 -25
  25. pex/resolve/lock_downloader.py +0 -1
  26. pex/resolve/locked_resolve.py +11 -11
  27. pex/resolve/locker.py +98 -18
  28. pex/resolve/lockfile/create.py +157 -27
  29. pex/resolve/lockfile/updater.py +11 -0
  30. pex/resolver.py +201 -4
  31. pex/version.py +1 -1
  32. {pex-2.63.0.dist-info → pex-2.64.0.dist-info}/METADATA +4 -4
  33. {pex-2.63.0.dist-info → pex-2.64.0.dist-info}/RECORD +38 -38
  34. pex/docs/html/_pagefind/fragment/en_6c6ecbd.pf_fragment +0 -0
  35. pex/docs/html/_pagefind/fragment/en_6c77f9b.pf_fragment +0 -0
  36. pex/docs/html/_pagefind/fragment/en_71b5a8a.pf_fragment +0 -0
  37. pex/docs/html/_pagefind/fragment/en_8762fc9.pf_fragment +0 -0
  38. pex/docs/html/_pagefind/fragment/en_a55bc27.pf_fragment +0 -0
  39. pex/docs/html/_pagefind/fragment/en_c87eb0d.pf_fragment +0 -0
  40. pex/docs/html/_pagefind/index/en_b6cc89e.pf_index +0 -0
  41. pex/docs/html/_pagefind/pagefind.en_5515c79d6d.pf_meta +0 -0
  42. {pex-2.63.0.dist-info → pex-2.64.0.dist-info}/WHEEL +0 -0
  43. {pex-2.63.0.dist-info → pex-2.64.0.dist-info}/entry_points.txt +0 -0
  44. {pex-2.63.0.dist-info → pex-2.64.0.dist-info}/licenses/LICENSE +0 -0
  45. {pex-2.63.0.dist-info → pex-2.64.0.dist-info}/pylock/pylock.toml +0 -0
  46. {pex-2.63.0.dist-info → pex-2.64.0.dist-info}/top_level.txt +0 -0
pex/pip/tool.py CHANGED
@@ -491,9 +491,7 @@ class Pip(object):
491
491
  # since Pip relies upon `shutil.move` which is only atomic when `os.rename` can be used.
492
492
  # See https://github.com/pex-tool/pex/issues/1776 for an example of the issues non-atomic
493
493
  # moves lead to in the `pip wheel` case.
494
- pip_tmpdir = os.path.join(self.cache_dir, ".tmp")
495
- safe_mkdir(pip_tmpdir)
496
- extra_env.update(TMPDIR=pip_tmpdir)
494
+ extra_env.update(TMPDIR=safe_mkdtemp(dir=safe_mkdir(self.cache_dir), prefix=".tmp."))
497
495
 
498
496
  with ENV.strip().patch(
499
497
  PEX_ROOT=ENV.PEX_ROOT,
@@ -584,6 +582,47 @@ class Pip(object):
584
582
  if not build_configuration.build_isolation:
585
583
  yield "--no-build-isolation"
586
584
 
585
+ def spawn_report(
586
+ self,
587
+ report_path, # type: str
588
+ requirements=None, # type: Optional[Iterable[str]]
589
+ requirement_files=None, # type: Optional[Iterable[str]]
590
+ constraint_files=None, # type: Optional[Iterable[str]]
591
+ allow_prereleases=False, # type: bool
592
+ transitive=True, # type: bool
593
+ target=None, # type: Optional[Target]
594
+ package_index_configuration=None, # type: Optional[PackageIndexConfiguration]
595
+ build_configuration=BuildConfiguration(), # type: BuildConfiguration
596
+ observer=None, # type: Optional[DownloadObserver]
597
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
598
+ universal_target=None, # type: Optional[UniversalTarget]
599
+ log=None, # type: Optional[str]
600
+ ):
601
+ # type: (...) -> Job
602
+ report_cmd = [
603
+ "install",
604
+ "--no-clean",
605
+ "--dry-run",
606
+ "--ignore-installed",
607
+ "--report",
608
+ report_path,
609
+ ]
610
+ return self._spawn_install_compatible_command(
611
+ cmd=report_cmd,
612
+ requirements=requirements,
613
+ requirement_files=requirement_files,
614
+ constraint_files=constraint_files,
615
+ allow_prereleases=allow_prereleases,
616
+ transitive=transitive,
617
+ target=target,
618
+ package_index_configuration=package_index_configuration,
619
+ build_configuration=build_configuration,
620
+ observer=observer,
621
+ dependency_configuration=dependency_configuration,
622
+ universal_target=universal_target,
623
+ log=log,
624
+ )
625
+
587
626
  def spawn_download_distributions(
588
627
  self,
589
628
  download_dir, # type: str
@@ -601,32 +640,66 @@ class Pip(object):
601
640
  log=None, # type: Optional[str]
602
641
  ):
603
642
  # type: (...) -> Job
604
- target = target or targets.current()
605
643
 
606
644
  download_cmd = ["download", "--dest", download_dir]
645
+ return self._spawn_install_compatible_command(
646
+ cmd=download_cmd,
647
+ requirements=requirements,
648
+ requirement_files=requirement_files,
649
+ constraint_files=constraint_files,
650
+ allow_prereleases=allow_prereleases,
651
+ transitive=transitive,
652
+ target=target,
653
+ package_index_configuration=package_index_configuration,
654
+ build_configuration=build_configuration,
655
+ observer=observer,
656
+ dependency_configuration=dependency_configuration,
657
+ universal_target=universal_target,
658
+ log=log,
659
+ )
660
+
661
+ def _spawn_install_compatible_command(
662
+ self,
663
+ cmd, # type: List[str]
664
+ requirements=None, # type: Optional[Iterable[str]]
665
+ requirement_files=None, # type: Optional[Iterable[str]]
666
+ constraint_files=None, # type: Optional[Iterable[str]]
667
+ allow_prereleases=False, # type: bool
668
+ transitive=True, # type: bool
669
+ target=None, # type: Optional[Target]
670
+ package_index_configuration=None, # type: Optional[PackageIndexConfiguration]
671
+ build_configuration=BuildConfiguration(), # type: BuildConfiguration
672
+ observer=None, # type: Optional[DownloadObserver]
673
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
674
+ universal_target=None, # type: Optional[UniversalTarget]
675
+ log=None, # type: Optional[str]
676
+ ):
677
+ # type: (...) -> Job
678
+ target = target or targets.current()
679
+
607
680
  extra_env = {} # type: Dict[str, str]
608
681
  pex_extra_sys_path = [] # type: List[str]
609
682
 
610
- download_cmd.extend(self._iter_build_configuration_options(build_configuration))
683
+ cmd.extend(self._iter_build_configuration_options(build_configuration))
611
684
  if not build_configuration.build_isolation:
612
685
  pex_extra_sys_path.extend(sys.path)
613
686
 
614
687
  if allow_prereleases:
615
- download_cmd.append("--pre")
688
+ cmd.append("--pre")
616
689
 
617
690
  if not transitive:
618
- download_cmd.append("--no-deps")
691
+ cmd.append("--no-deps")
619
692
 
620
693
  if requirement_files:
621
694
  for requirement_file in requirement_files:
622
- download_cmd.extend(["--requirement", requirement_file])
695
+ cmd.extend(["--requirement", requirement_file])
623
696
 
624
697
  if constraint_files:
625
698
  for constraint_file in constraint_files:
626
- download_cmd.extend(["--constraint", constraint_file])
699
+ cmd.extend(["--constraint", constraint_file])
627
700
 
628
701
  if requirements:
629
- download_cmd.extend(requirements)
702
+ cmd.extend(requirements)
630
703
 
631
704
  foreign_platform_observer = foreign_platform.patch(target)
632
705
  if (
@@ -722,7 +795,7 @@ class Pip(object):
722
795
  tailer.stop()
723
796
 
724
797
  command, process = self._spawn_pip_isolated(
725
- download_cmd,
798
+ cmd,
726
799
  package_index_configuration=package_index_configuration,
727
800
  interpreter=target.get_interpreter(),
728
801
  log=log,
pex/pip/vcs.py CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  from __future__ import absolute_import
5
5
 
6
+ import glob
6
7
  import os
7
8
  import re
8
9
 
@@ -60,7 +61,6 @@ def fingerprint_downloaded_vcs_archive(
60
61
  project_name, # type: str
61
62
  version, # type: str
62
63
  vcs, # type: VCS.Value
63
- subdirectory=None, # type: Optional[str]
64
64
  ):
65
65
  # type: (...) -> Tuple[Fingerprint, str]
66
66
 
@@ -70,7 +70,7 @@ def fingerprint_downloaded_vcs_archive(
70
70
  )
71
71
  )
72
72
  digest = Sha256()
73
- digest_vcs_archive(archive_path=archive_path, vcs=vcs, digest=digest, subdirectory=subdirectory)
73
+ digest_vcs_archive(archive_path=archive_path, vcs=vcs, digest=digest)
74
74
  return Fingerprint.from_digest(digest), archive_path
75
75
 
76
76
 
@@ -78,36 +78,53 @@ def digest_vcs_archive(
78
78
  archive_path, # type: str
79
79
  vcs, # type: VCS.Value
80
80
  digest, # type: HintedDigest
81
- subdirectory=None, # type: Optional[str]
82
81
  ):
83
82
  # type: (...) -> None
84
83
 
85
84
  # All VCS requirements are prepared as zip archives as encoded in:
86
- # `pip._internal.req.req_install.InstallRequirement.archive`.
85
+ # `pip._internal.req.req_install.InstallRequirement.archive` and the archive is already offset
86
+ # by a subdirectory (if any).
87
87
  with TRACER.timed(
88
88
  "Digesting {archive} {vcs} archive".format(archive=os.path.basename(archive_path), vcs=vcs)
89
89
  ), temporary_dir() as chroot, open_zip(archive_path) as archive:
90
+ # TODO(John Sirois): Consider implementing zip_hash to avoid the extractall.
90
91
  archive.extractall(chroot)
91
92
 
92
- # Ignore VCS control directories for the purposes of fingerprinting the version controlled
93
- # source tree. VCS control directories can contain non-reproducible content (Git at least
94
- # has files that contain timestamps).
95
- #
96
- # We cannot prune these directories from the source archive directly unfortunately since
97
- # some build processes use VCS version information to derive their version numbers (C.F.:
98
- # https://pypi.org/project/setuptools-scm/). As such, we'll get a stable fingerprint, but be
99
- # forced to re-build a wheel each time the VCS requirement is re-locked later, even when it
100
- # hashes the same.
101
- vcs_control_dir = ".{vcs}".format(vcs=vcs)
93
+ # The zip archives created by Pip have a single project name top-level directory housing
94
+ # the full clone. We look for that to get a consistent clone hash with a bare clone.
95
+ listing = glob.glob(os.path.join(chroot, "*"))
96
+ if len(listing) == 1 and os.path.isdir(listing[0]):
97
+ chroot = listing[0]
102
98
 
103
- # TODO(John Sirois): Consider implementing zip_hash to avoid the extractall.
104
- hashing.dir_hash(
105
- directory=os.path.join(chroot, subdirectory) if subdirectory else chroot,
106
- digest=digest,
107
- dir_filter=(
108
- lambda dir_path: (
109
- not is_pyc_dir(dir_path) and os.path.basename(dir_path) != vcs_control_dir
110
- )
111
- ),
112
- file_filter=lambda f: not is_pyc_file(f),
113
- )
99
+ digest_vcs_repo(repo_path=chroot, vcs=vcs, digest=digest)
100
+
101
+
102
+ def digest_vcs_repo(
103
+ repo_path, # type: str
104
+ vcs, # type: VCS.Value
105
+ digest, # type: HintedDigest
106
+ subdirectory=None, # type: Optional[str]
107
+ ):
108
+ # type: (...) -> None
109
+
110
+ # Ignore VCS control directories for the purposes of fingerprinting the version controlled
111
+ # source tree. VCS control directories can contain non-reproducible content (Git at least
112
+ # has files that contain timestamps).
113
+ #
114
+ # We cannot prune these directories from the source archive directly unfortunately since
115
+ # some build processes use VCS version information to derive their version numbers (C.F.:
116
+ # https://pypi.org/project/setuptools-scm/). As such, we'll get a stable fingerprint, but be
117
+ # forced to re-build a wheel each time the VCS requirement is re-locked later, even when it
118
+ # hashes the same.
119
+ vcs_control_dir = ".{vcs}".format(vcs=vcs)
120
+
121
+ hashing.dir_hash(
122
+ directory=os.path.join(repo_path, subdirectory) if subdirectory else repo_path,
123
+ digest=digest,
124
+ dir_filter=(
125
+ lambda dir_path: (
126
+ not is_pyc_dir(dir_path) and os.path.basename(dir_path) != vcs_control_dir
127
+ )
128
+ ),
129
+ file_filter=lambda f: not is_pyc_file(f),
130
+ )
@@ -157,7 +157,6 @@ class VCSArtifactDownloadManager(DownloadManager[VCSArtifact]):
157
157
  archive_path=local_distribution.path,
158
158
  vcs=artifact.vcs,
159
159
  digest=digest,
160
- subdirectory=artifact.subdirectory,
161
160
  )
162
161
  shutil.move(local_distribution.path, os.path.join(dest_dir, filename))
163
162
  return filename
@@ -12,7 +12,7 @@ from functools import total_ordering
12
12
  from pex.artifact_url import VCS, ArtifactURL, Fingerprint, VCSScheme
13
13
  from pex.common import pluralize
14
14
  from pex.dependency_configuration import DependencyConfiguration
15
- from pex.dist_metadata import DistMetadata, Requirement, is_sdist, is_wheel
15
+ from pex.dist_metadata import ProjectMetadata, Requirement, is_sdist, is_wheel
16
16
  from pex.enum import Enum
17
17
  from pex.exceptions import production_assert
18
18
  from pex.interpreter_constraints import InterpreterConstraint
@@ -660,7 +660,7 @@ class LockedResolve(object):
660
660
  def create(
661
661
  cls,
662
662
  resolved_requirements, # type: Iterable[ResolvedRequirement]
663
- dist_metadatas, # type: Iterable[DistMetadata]
663
+ project_metadatas, # type: Iterable[ProjectMetadata]
664
664
  fingerprinter, # type: Fingerprinter
665
665
  platform_tag=None, # type: Optional[tags.Tag]
666
666
  marker=None, # type: Optional[Marker]
@@ -703,21 +703,21 @@ class LockedResolve(object):
703
703
  editable=partial_artifact.editable,
704
704
  )
705
705
 
706
- dist_metadata_by_pin = {
706
+ project_metadata_by_pin = {
707
707
  Pin(dist_info.project_name, dist_info.version): dist_info
708
- for dist_info in dist_metadatas
708
+ for dist_info in project_metadatas
709
709
  }
710
710
  locked_requirements = []
711
711
  for resolved_requirement in resolved_requirements:
712
- distribution_metadata = dist_metadata_by_pin.get(resolved_requirement.pin)
713
- if distribution_metadata is None:
712
+ project_metadata = project_metadata_by_pin.get(resolved_requirement.pin)
713
+ if project_metadata is None:
714
714
  raise ValueError(
715
- "No distribution metadata found for {project}.\n"
716
- "Given distribution metadata for:\n"
715
+ "No project metadata found for {project}.\n"
716
+ "Given project metadata for:\n"
717
717
  "{projects}".format(
718
718
  project=resolved_requirement.pin.as_requirement(),
719
719
  projects="\n".join(
720
- sorted(str(pin.as_requirement()) for pin in dist_metadata_by_pin)
720
+ sorted(str(pin.as_requirement()) for pin in project_metadata_by_pin)
721
721
  ),
722
722
  )
723
723
  )
@@ -725,8 +725,8 @@ class LockedResolve(object):
725
725
  LockedRequirement.create(
726
726
  pin=resolved_requirement.pin,
727
727
  artifact=resolve_fingerprint(resolved_requirement.artifact),
728
- requires_dists=distribution_metadata.requires_dists,
729
- requires_python=distribution_metadata.requires_python,
728
+ requires_dists=project_metadata.requires_dists,
729
+ requires_python=project_metadata.requires_python,
730
730
  additional_artifacts=(
731
731
  resolve_fingerprint(artifact)
732
732
  for artifact in resolved_requirement.additional_artifacts
pex/resolve/locker.py CHANGED
@@ -16,11 +16,12 @@ from pex.dist_metadata import ProjectNameAndVersion, Requirement
16
16
  from pex.hashing import Sha256
17
17
  from pex.orderedset import OrderedSet
18
18
  from pex.pep_440 import Version
19
+ from pex.pep_503 import ProjectName
19
20
  from pex.pip import foreign_platform
20
21
  from pex.pip.download_observer import Patch, PatchSet
21
22
  from pex.pip.local_project import digest_local_project
22
23
  from pex.pip.log_analyzer import LogAnalyzer
23
- from pex.pip.vcs import fingerprint_downloaded_vcs_archive
24
+ from pex.pip.vcs import digest_vcs_repo, fingerprint_downloaded_vcs_archive
24
25
  from pex.pip.version import PipVersionValue
25
26
  from pex.requirements import LocalProjectRequirement, VCSRequirement
26
27
  from pex.resolve.locked_resolve import LockStyle
@@ -194,6 +195,7 @@ class AnalyzeError(Exception):
194
195
 
195
196
  @attr.s(frozen=True)
196
197
  class ArtifactBuildResult(object):
198
+ path = attr.ib() # type: str
197
199
  url = attr.ib() # type: ArtifactURL
198
200
  pin = attr.ib() # type: Pin
199
201
 
@@ -211,17 +213,18 @@ class ArtifactBuildObserver(object):
211
213
  # type: (str) -> Optional[ArtifactBuildResult]
212
214
 
213
215
  match = re.search(
214
- r"Source in .+ has version (?P<version>\S+), which satisfies requirement "
216
+ r"Source in (?P<path>.+) has version (?P<version>\S+), which satisfies requirement "
215
217
  r"(?P<requirement>.+) .*from {url}".format(url=re.escape(self._artifact_url.raw_url)),
216
218
  line,
217
219
  )
218
220
  if not match:
219
221
  return None
220
222
 
223
+ path = match.group("path")
221
224
  version = Version(match.group("version"))
222
225
  requirement = Requirement.parse(match.group("requirement"))
223
226
  pin = Pin(project_name=requirement.project_name, version=version)
224
- return ArtifactBuildResult(url=self._artifact_url, pin=pin)
227
+ return ArtifactBuildResult(path=path, url=self._artifact_url, pin=pin)
225
228
 
226
229
 
227
230
  class Locker(LogAnalyzer):
@@ -234,12 +237,14 @@ class Locker(LogAnalyzer):
234
237
  download_dir, # type: str
235
238
  fingerprint_service=None, # type: Optional[FingerprintService]
236
239
  pip_version=None, # type: Optional[PipVersionValue]
240
+ lock_is_via_pip_download=False, # type: bool
237
241
  ):
238
242
  # type: (...) -> None
239
243
 
240
244
  self._target = target
241
245
  self._vcs_url_manager = VCSURLManager.create(root_requirements)
242
246
  self._pip_version = pip_version
247
+ self._lock_is_via_pip_download = lock_is_via_pip_download
243
248
  self._resolver = resolver
244
249
  self._lock_style = lock_style
245
250
  self._download_dir = download_dir
@@ -357,19 +362,18 @@ class Locker(LogAnalyzer):
357
362
  return self.Continue()
358
363
 
359
364
  build_result = self._artifact_build_observer.build_result(line)
360
- if build_result:
365
+ source_fingerprint = None # type: Optional[Fingerprint]
366
+ verified = False
367
+ commit_id = None # type: Optional[str]
368
+ editable = False
369
+ if build_result and self._lock_is_via_pip_download:
361
370
  artifact_url = build_result.url
362
- source_fingerprint = None # type: Optional[Fingerprint]
363
- verified = False
364
- commit_id = None # type: Optional[str]
365
- editable = False
366
371
  if isinstance(artifact_url.scheme, VCSScheme):
367
372
  source_fingerprint, archive_path = fingerprint_downloaded_vcs_archive(
368
373
  download_dir=self._download_dir,
369
374
  project_name=str(build_result.pin.project_name),
370
375
  version=str(build_result.pin.version),
371
376
  vcs=artifact_url.scheme.vcs,
372
- subdirectory=artifact_url.subdirectory,
373
377
  )
374
378
  verified = True
375
379
  selected_path = os.path.basename(archive_path)
@@ -378,8 +382,8 @@ class Locker(LogAnalyzer):
378
382
  )
379
383
  self._selected_path_to_pin[selected_path] = build_result.pin
380
384
 
381
- vcs, _, vcs_url = build_result.url.raw_url.partition("+")
382
- if "@" in build_result.url.path:
385
+ vcs, _, vcs_url = artifact_url.raw_url.partition("+")
386
+ if "@" in artifact_url.path:
383
387
  vcs_url, _, _ = vcs_url.rpartition("@")
384
388
  commit_id = self._commit_ids.pop(vcs_url, None)
385
389
  elif isinstance(artifact_url.scheme, ArchiveScheme.Value):
@@ -425,6 +429,74 @@ class Locker(LogAnalyzer):
425
429
  additional_artifacts = self._links[build_result.pin]
426
430
  additional_artifacts.pop(artifact_url, None)
427
431
 
432
+ self._resolved_requirements[build_result.pin] = ResolvedRequirement(
433
+ pin=build_result.pin,
434
+ artifact=PartialArtifact(
435
+ url=artifact_url,
436
+ fingerprint=source_fingerprint,
437
+ verified=verified,
438
+ commit_id=commit_id,
439
+ editable=editable,
440
+ ),
441
+ additional_artifacts=tuple(additional_artifacts.values()),
442
+ )
443
+ elif build_result:
444
+ artifact_url = build_result.url
445
+ if isinstance(artifact_url.scheme, VCSScheme):
446
+ digest = Sha256()
447
+ digest_vcs_repo(
448
+ repo_path=build_result.path,
449
+ vcs=artifact_url.scheme.vcs,
450
+ digest=digest,
451
+ subdirectory=artifact_url.subdirectory,
452
+ )
453
+ source_fingerprint = Fingerprint.from_digest(digest)
454
+ verified = True # noqa
455
+ vcs, _, vcs_url = artifact_url.raw_url.partition("+")
456
+ if "@" in artifact_url.path:
457
+ vcs_url, _, _ = vcs_url.rpartition("@")
458
+ commit_id = self._commit_ids.pop(vcs_url, None)
459
+ elif isinstance(artifact_url.scheme, ArchiveScheme.Value):
460
+ source_archive_path = build_result.path
461
+ # If Pip resolves the artifact from its own cache, we will not find it in the
462
+ # download dir for this run; so guard against that. In this case the existing
463
+ # machinery that finalizes a locks missing fingerprints will download the
464
+ # artifact and hash it.
465
+ if os.path.isfile(source_archive_path):
466
+ digest = Sha256()
467
+ hashing.file_hash(source_archive_path, digest)
468
+ source_fingerprint = Fingerprint.from_digest(digest)
469
+ verified = True
470
+ elif "file" == artifact_url.scheme:
471
+ digest = Sha256()
472
+ if os.path.isfile(artifact_url.path):
473
+ hashing.file_hash(artifact_url.path, digest)
474
+ self._selected_path_to_pin[
475
+ os.path.basename(artifact_url.path)
476
+ ] = build_result.pin
477
+ else:
478
+ digest_local_project(
479
+ directory=artifact_url.path,
480
+ digest=digest,
481
+ pip_version=self._pip_version,
482
+ target=self._target,
483
+ resolver=self._resolver,
484
+ )
485
+ self._local_projects.add(artifact_url.path)
486
+ self._saved.add(build_result.pin)
487
+ editable = artifact_url.path in self._editable_projects
488
+ source_fingerprint = Fingerprint.from_digest(digest)
489
+ verified = True
490
+ else:
491
+ raise AnalyzeError(
492
+ "Unexpected scheme {scheme!r} for artifact at {url}".format(
493
+ scheme=artifact_url.scheme, url=artifact_url
494
+ )
495
+ )
496
+
497
+ additional_artifacts = self._links[build_result.pin]
498
+ additional_artifacts.pop(artifact_url, None)
499
+
428
500
  self._resolved_requirements[build_result.pin] = ResolvedRequirement(
429
501
  pin=build_result.pin,
430
502
  artifact=PartialArtifact(
@@ -506,13 +578,21 @@ class Locker(LogAnalyzer):
506
578
  )
507
579
  return self.Continue()
508
580
 
509
- match = re.search(r"Saved (?P<file_path>.+)$", line)
510
- if match:
511
- saved_path = match.group("file_path")
512
- build_result_pin = self._selected_path_to_pin.get(os.path.basename(saved_path))
513
- if build_result_pin:
514
- self._saved.add(build_result_pin)
515
- return self.Continue()
581
+ if self._lock_is_via_pip_download:
582
+ match = re.search(r"Saved (?P<file_path>.+)$", line)
583
+ if match:
584
+ saved_path = match.group("file_path")
585
+ build_result_pin = self._selected_path_to_pin.get(os.path.basename(saved_path))
586
+ if build_result_pin:
587
+ self._saved.add(build_result_pin)
588
+ return self.Continue()
589
+ else:
590
+ match = re.search(r"Would install (?P<pnavs>.+)$", line)
591
+ if match:
592
+ for pnav in match.group("pnavs").split():
593
+ project_name, _, version = pnav.rpartition("-")
594
+ self._saved.add(Pin(ProjectName(project_name), Version(version)))
595
+ return self.Continue()
516
596
 
517
597
  if self._lock_style in (LockStyle.SOURCES, LockStyle.UNIVERSAL):
518
598
  match = re.search(r"Found link (?P<url>\S+)(?: \(from .*\))?, version: ", line)