Skip to content

Commit 0bcefd9

Browse files
committed
Handle & test editable lock overrides.
1 parent 0e2f2d8 commit 0bcefd9

File tree

5 files changed

+168
-12
lines changed

5 files changed

+168
-12
lines changed

pex/cache/dirs.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
from __future__ import absolute_import
55

6+
import base64
67
import glob
8+
import hashlib
79
import os
810

911
from pex.common import pluralize, safe_rmtree
@@ -837,6 +839,7 @@ def create(
837839
fingerprint, # type: str
838840
pnav=None, # type: Optional[ProjectNameAndVersion]
839841
target=None, # type: Optional[Target]
842+
editable=False, # type: bool
840843
pex_root=ENV, # type: Union[str, Variables]
841844
):
842845
# type: (...) -> BuiltWheelDir
@@ -847,9 +850,15 @@ def create(
847850
if is_sdist(sdist):
848851
dist_type = "sdists"
849852
file_name = os.path.basename(sdist)
853+
dist_name = file_name
850854
else:
851-
dist_type = "local_projects"
855+
dist_type = "editable_projects" if editable else "local_projects"
852856
file_name = None
857+
dist_name = str(
858+
base64.urlsafe_b64encode(hashlib.sha1(sdist.encode("utf-8")).digest())
859+
.decode("utf-8")
860+
.rstrip("=")
861+
)
853862

854863
# For the purposes of building a wheel from source, the product should be uniqued by the
855864
# wheel name which is unique on the host os up to the python and abi tags. In other words,
@@ -871,9 +880,7 @@ def create(
871880
abi_tag=interpreter.identity.abi_tag,
872881
platform_tag=interpreter.identity.platform_tag,
873882
)
874-
sdist_dir = CacheDir.BUILT_WHEELS.path(
875-
dist_type, os.path.basename(sdist), pex_root=pex_root
876-
)
883+
sdist_dir = CacheDir.BUILT_WHEELS.path(dist_type, dist_name, pex_root=pex_root)
877884
dist_dir = os.path.join(sdist_dir, fingerprint, target_tags)
878885

879886
if is_sdist(sdist):

pex/resolve/locked_resolve.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,12 @@ def request_resolve(requests):
933933
if not target.requirement_applies(request.requirement, extras=request.extras):
934934
continue
935935
to_be_resolved.append(request)
936-
if request.overridden and request.editable and request.requirement.url:
936+
if (
937+
request.overridden
938+
and request.editable
939+
and build_configuration.honor_editable
940+
and request.requirement.url
941+
):
937942
overridden[request.project_name] = ArtifactURL.parse(request.requirement.url)
938943

939944
resolved = {} # type: Dict[ProjectName, Set[str]]

pex/resolve/lockfile/pep_751.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from __future__ import absolute_import
55

6+
import hashlib
67
import os
78
import sys
89
from collections import OrderedDict, defaultdict, deque
@@ -34,6 +35,7 @@
3435
LockedRequirement,
3536
LockedResolve,
3637
Resolved,
38+
UnFingerprintedArtifact,
3739
UnFingerprintedLocalProjectArtifact,
3840
UnFingerprintedVCSArtifact,
3941
VCSArtifact,
@@ -53,6 +55,7 @@
5355
from pex.toml import InlineTable, TomlDecodeError
5456
from pex.tracer import TRACER
5557
from pex.typing import TYPE_CHECKING, cast
58+
from pex.util import CacheHelper
5659

5760
if TYPE_CHECKING:
5861
from typing import (
@@ -1176,6 +1179,10 @@ class ResolveError(Exception):
11761179
pass
11771180

11781181

1182+
if TYPE_CHECKING:
1183+
_A = TypeVar("_A", bound=UnFingerprintedArtifact)
1184+
1185+
11791186
@attr.s(frozen=True)
11801187
class PackageEvaluator(object):
11811188
@classmethod
@@ -1331,6 +1338,46 @@ def select_best_fit_wheel(self, wheels):
13311338
def select_best_fit_artifact(self, package):
13321339
# type: (Package) -> Union[FileArtifact, UnFingerprintedLocalProjectArtifact, UnFingerprintedVCSArtifact]
13331340

1341+
requirement = (
1342+
self.requirements_by_project_name.get(package.project_name) or package.as_requirement()
1343+
)
1344+
override = self.dependency_configuration.overridden_by(requirement, target=self.target)
1345+
if override:
1346+
1347+
def create_invalid_override_error():
1348+
# type: () -> ResolveError
1349+
return ResolveError(
1350+
"Only local artifacts or projects can be used to override packages in a "
1351+
"pylock.toml. Given: {override}".format(override=override)
1352+
)
1353+
1354+
if not override.url:
1355+
raise create_invalid_override_error()
1356+
url = ArtifactURL.parse(override.url)
1357+
if url.scheme != "file":
1358+
raise create_invalid_override_error()
1359+
if os.path.isfile(url.path):
1360+
return FileArtifact(
1361+
filename=url.path,
1362+
fingerprint=(
1363+
url.fingerprint
1364+
or Fingerprint(
1365+
algorithm="sha256",
1366+
hash=CacheHelper.hash(url.path, hasher=hashlib.sha256),
1367+
)
1368+
),
1369+
url=url,
1370+
verified=True,
1371+
)
1372+
elif os.path.isdir(url.path):
1373+
return UnFingerprintedLocalProjectArtifact(
1374+
directory=url.path,
1375+
editable=self.build_configuration.honor_editable and override.editable,
1376+
url=url,
1377+
verified=True,
1378+
)
1379+
raise create_invalid_override_error()
1380+
13341381
if not package.has_wheel or not self.build_configuration.allow_wheel(package.project_name):
13351382
# A source distribution (sdist, directory, archive or vcs).
13361383
return package.artifact

pex/resolver.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -714,9 +714,15 @@ def prepare(self):
714714
"Unexpected archive type for sdist {project}".format(project=self.source_path)
715715
)
716716

717-
def result(self, source_path=None):
718-
# type: (Optional[str]) -> BuildResult
719-
return BuildResult.from_request(self, source_path=source_path)
717+
def result(
718+
self,
719+
source_path=None, # type: Optional[str]
720+
honor_editable=False, # type: bool
721+
):
722+
# type: (...) -> BuildResult
723+
return BuildResult.from_request(
724+
self, source_path=source_path, honor_editable=honor_editable
725+
)
720726

721727

722728
@attr.s(frozen=True)
@@ -726,13 +732,15 @@ def from_request(
726732
cls,
727733
build_request, # type: BuildRequest
728734
source_path=None, # type: Optional[str]
735+
honor_editable=False, # type: bool
729736
):
730737
# type: (...) -> BuildResult
731738
if build_request.fingerprint:
732739
built_wheel = BuiltWheelDir.create(
733740
sdist=source_path or build_request.source_path,
734741
fingerprint=build_request.fingerprint,
735742
target=build_request.target,
743+
editable=honor_editable and build_request.editable,
736744
)
737745
target_dir = built_wheel.dist_dir
738746
else:
@@ -1028,8 +1036,8 @@ def __init__(
10281036
self._pip_version = pip_version
10291037
self._resolver = resolver
10301038

1031-
@staticmethod
10321039
def _categorize_build_requests(
1040+
self,
10331041
build_requests, # type: Iterable[BuildRequest]
10341042
check_compatible=True, # type: bool
10351043
):
@@ -1039,7 +1047,9 @@ def _categorize_build_requests(
10391047
OrderedSet
10401048
) # type: DefaultDict[str, OrderedSet[InstallRequest]]
10411049
for build_request in build_requests:
1042-
build_result = build_request.result()
1050+
build_result = build_request.result(
1051+
honor_editable=self._build_configuration.honor_editable
1052+
)
10431053
if not build_result.is_built:
10441054
TRACER.log(
10451055
"Building {} to {}".format(build_request.source_path, build_result.dist_dir)
@@ -1059,7 +1069,9 @@ def _categorize_build_requests(
10591069
def _spawn_wheel_build(self, build_request):
10601070
# type: (BuildRequest) -> SpawnedJob[BuildResult]
10611071
source_path, editable = build_request.prepare()
1062-
build_result = build_request.result(source_path)
1072+
build_result = build_request.result(
1073+
source_path, honor_editable=self._build_configuration.honor_editable
1074+
)
10631075

10641076
def spawn_build_wheel():
10651077
# type: () -> SpawnedJob[BuildResult]

tests/integration/test_venv_editables.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def edit_local_project_all_caps(
9595

9696

9797
@skip_too_old_setuptools
98-
def test_venv_create(
98+
def test_venv_create_pip(
9999
tmpdir, # type: Tempdir
100100
local_project, # type: str
101101
):
@@ -115,6 +115,91 @@ def test_venv_create(
115115
assert b"foo" == subprocess.check_output(args=[local_project_script, "foo"])
116116

117117

118+
@pytest.fixture
119+
def pex_lock(
120+
tmpdir, # type: Tempdir
121+
local_project, # type: str
122+
):
123+
# type: (...) -> str
124+
pex_lock = tmpdir.join("lock.json")
125+
run_pex3("lock", "create", local_project, "--indent", "2", "-o", pex_lock).assert_success()
126+
return pex_lock
127+
128+
129+
@skip_too_old_setuptools
130+
def test_venv_create_pex_lock(
131+
tmpdir, # type: Tempdir
132+
pex_lock, # type: str
133+
local_project, # type: str
134+
):
135+
# type: (...) -> None
136+
137+
venv_dir = tmpdir.join("venv")
138+
run_pex3("venv", "create", "--lock", pex_lock, "-d", venv_dir).assert_success()
139+
local_project_script = Virtualenv(venv_dir).bin_path("local-project")
140+
141+
assert b"foo" == subprocess.check_output(args=[local_project_script, "foo"])
142+
edit_local_project_all_caps(local_project, all_caps=True)
143+
assert b"foo" == subprocess.check_output(args=[local_project_script, "foo"])
144+
145+
run_pex3(
146+
"venv",
147+
"create",
148+
"--override=-e local_project @ {local_project}".format(local_project=local_project),
149+
"--lock",
150+
pex_lock,
151+
"-d",
152+
venv_dir,
153+
"--force",
154+
).assert_success()
155+
assert b"FOO" == subprocess.check_output(args=[local_project_script, "foo"])
156+
edit_local_project_all_caps(local_project, all_caps=False)
157+
assert b"foo" == subprocess.check_output(args=[local_project_script, "foo"])
158+
159+
160+
@pytest.fixture
161+
def pylock(
162+
tmpdir, # type: Tempdir
163+
pex_lock, # type: str
164+
):
165+
# type: (...) -> str
166+
pylock = tmpdir.join("pylock.toml")
167+
# pep-751
168+
run_pex3("lock", "export", "--format", "pep-751", "-o", pylock, pex_lock).assert_success()
169+
return pylock
170+
171+
172+
@skip_too_old_setuptools
173+
def test_venv_create_pylock(
174+
tmpdir, # type: Tempdir
175+
pylock, # type: str
176+
local_project, # type: str
177+
):
178+
# type: (...) -> None
179+
180+
venv_dir = tmpdir.join("venv")
181+
run_pex3("venv", "create", "--pylock", pylock, "-d", venv_dir).assert_success()
182+
local_project_script = Virtualenv(venv_dir).bin_path("local-project")
183+
184+
assert b"foo" == subprocess.check_output(args=[local_project_script, "foo"])
185+
edit_local_project_all_caps(local_project, all_caps=True)
186+
assert b"foo" == subprocess.check_output(args=[local_project_script, "foo"])
187+
188+
run_pex3(
189+
"venv",
190+
"create",
191+
"--override=-e local_project @ file:{local_project}".format(local_project=local_project),
192+
"--pylock",
193+
pylock,
194+
"-d",
195+
venv_dir,
196+
"--force",
197+
).assert_success()
198+
assert b"FOO" == subprocess.check_output(args=[local_project_script, "foo"])
199+
edit_local_project_all_caps(local_project, all_caps=False)
200+
assert b"foo" == subprocess.check_output(args=[local_project_script, "foo"])
201+
202+
118203
@skip_too_old_setuptools
119204
def test_run(
120205
tmpdir, # type: Tempdir

0 commit comments

Comments
 (0)