Skip to content

Commit b16741e

Browse files
authored
Fix PEX scie contents when split. (#2713)
The embedded PEX now has the executable bit set and it retains the name requested by the user in nearly all cases.
1 parent df155aa commit b16741e

File tree

4 files changed

+95
-39
lines changed

4 files changed

+95
-39
lines changed

CHANGES.md

+17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Release Notes
22

3+
## 2.33.3
4+
5+
This release fixes Pex Zip64 support such that PEX zips do not use Zip64 extensions unless needed.
6+
Previously, any zip between ~2GB and ~4GB that actually fell under Zip64 limits would still use
7+
Zip64 extensions. This prevented the file from being bootable under Python before the 3.13 release
8+
since the `zipimporter` was not fixed to support ZIp64 extensions until then.
9+
10+
The `--scie-only` option is fixed for the case when the `-o` / `--output-file` name does not end in
11+
`.pex`. Previously there would be no scie (or PEX) output at all!
12+
13+
Finally, this release fixes PEX scies such that, when split, the embedded PEX is both executable and
14+
retains the expected name as provided by `-o` / `--output-file`.
15+
16+
* Enable true Zip64 support. (#2714)
17+
* Fix `--scie-only` for `-o` not ending in `.pex`. (#2715)
18+
* Fix PEX scie contents when split. (#2713)
19+
320
## 2.33.2
421

522
This release fixes PEXes build with root requirements like `foo[bar] foo[baz]` (vs. `foo[bar,baz]`,

pex/scie/science.py

+43-36
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from pex.common import pluralize, safe_mkdtemp, safe_open
1717
from pex.compatibility import shlex_quote
1818
from pex.dist_metadata import NamedEntryPoint, parse_entry_point
19+
from pex.enum import Enum
1920
from pex.exceptions import production_assert
2021
from pex.executables import chmod_plus_x
2122
from pex.fetcher import URLFetcher
@@ -66,7 +67,7 @@ def qualified_binary_name(self, binary_name):
6667

6768

6869
SCIENCE_RELEASES_URL = "https://github.com/a-scie/lift/releases"
69-
MIN_SCIENCE_VERSION = Version("0.10.1")
70+
MIN_SCIENCE_VERSION = Version("0.12.1")
7071
SCIENCE_REQUIREMENT = SpecifierSet("~={min_version}".format(min_version=MIN_SCIENCE_VERSION))
7172

7273

@@ -80,43 +81,35 @@ def _science_binary_url(suffix=""):
8081
)
8182

8283

83-
PTEX_VERSION = "1.5.0"
84-
SCIE_JUMP_VERSION = "1.5.0"
84+
PTEX_VERSION = "1.5.1"
85+
SCIE_JUMP_VERSION = "1.6.1"
8586

8687

87-
@attr.s(frozen=True)
88-
class Filename(object):
89-
name = attr.ib() # type: str
88+
class Filenames(Enum["Filenames.Value"]):
89+
class Filename(Enum.Value):
90+
def __init__(self, value):
91+
# type: (str) -> None
92+
Enum.Value.__init__(self, value)
93+
self.name = value
9094

91-
@property
92-
def placeholder(self):
93-
# type: () -> str
94-
return "{{{name}}}".format(name=self.name)
95+
@property
96+
def placeholder(self):
97+
# type: () -> str
98+
return "{{{name}}}".format(name=self.name)
9599

100+
PTEX = Filename("ptex")
101+
PEX = Filename("pex")
102+
CONFIGURE_BINDING = Filename("configure-binding.py")
96103

97-
@attr.s(frozen=True)
98-
class Filenames(object):
99-
@classmethod
100-
def avoid_collisions_with(cls, scie_name):
101-
# type: (str) -> Filenames
102-
return cls(
103-
pex=Filename("_pex" if scie_name == "pex" else "pex"),
104-
configure_binding=Filename(
105-
"_configure-binding.py"
106-
if scie_name == "configure-binding.py"
107-
else "configure-binding.py"
108-
),
109-
)
110104

111-
pex = attr.ib() # type: Filename
112-
configure_binding = attr.ib() # type: Filename
105+
Filenames.seal()
113106

114107

115108
def create_manifests(
116109
configuration, # type: ScieConfiguration
117110
name, # type: str
118111
pex, # type: PEX
119-
filenames, # type: Filenames
112+
use_platform_suffix=None, # type: Optional[bool]
120113
):
121114
# type: (...) -> Iterator[Manifest]
122115

@@ -208,15 +201,27 @@ def create_cmd(named_entry_point):
208201
"args": ["{scie.bindings.configure:PEX}"],
209202
}
210203

204+
# Try to give the PEX the extracted filename expected by the user. This should work in almost
205+
# all cases save for the Pex PEX.
206+
pex_name = os.path.basename(pex.path())
207+
if pex_name not in frozenset(filename.value for filename in Filenames.values()):
208+
pex_key = Filenames.PEX.name # type: Optional[str]
209+
else:
210+
pex_name = Filenames.PEX.name
211+
pex_key = None
212+
211213
lift = {
212214
"name": name,
213215
"ptex": {
214-
"id": "ptex",
216+
"id": Filenames.PTEX.name,
215217
"version": PTEX_VERSION,
216218
"argv1": "{scie.env.PEX_BOOTSTRAP_URLS={scie.lift}}",
217219
},
218220
"scie_jump": {"version": SCIE_JUMP_VERSION},
219-
"files": [{"name": filenames.configure_binding.name}, {"name": filenames.pex.name}],
221+
"files": [
222+
{"name": Filenames.CONFIGURE_BINDING.name},
223+
dict(name=pex_name, is_executable=True, **({"key": pex_key} if pex_key else {})),
224+
],
220225
} # type: Dict[str, Any]
221226

222227
configure_binding = {
@@ -235,7 +240,7 @@ def create_cmd(named_entry_point):
235240
"exe": "#{python-distribution:python}",
236241
}
237242

238-
configure_binding_args = [filenames.pex.placeholder, filenames.configure_binding.placeholder]
243+
configure_binding_args = [Filenames.PEX.placeholder, Filenames.CONFIGURE_BINDING.placeholder]
239244
for interpreter in configuration.interpreters:
240245
manifest_path = os.path.join(
241246
safe_mkdtemp(),
@@ -266,20 +271,24 @@ def create_cmd(named_entry_point):
266271
else:
267272
extra_configure_binding_args = ["--installed-pex-dir"]
268273
if pex.layout is Layout.LOOSE:
269-
extra_configure_binding_args.append(filenames.pex.placeholder)
274+
extra_configure_binding_args.append(Filenames.PEX.placeholder)
270275
else:
271276
production_assert(pex_info.pex_hash is not None)
272277
pex_hash = cast(str, pex_info.pex_hash)
273278
extra_configure_binding_args.append(
274279
UnzipDir.create(pex_hash, pex_root=pex_root).path
275280
)
276281

282+
if use_platform_suffix is True or (
283+
use_platform_suffix is None and interpreter.platform is not SysPlatform.CURRENT
284+
):
285+
lift["platforms"] = [interpreter.platform.value]
286+
277287
with safe_open(manifest_path, "wb") as fp:
278288
toml.dump(
279289
{
280290
"lift": dict(
281291
lift,
282-
platforms=[interpreter.platform.value],
283292
interpreters=[interpreter_config],
284293
commands=list(create_commands(interpreter.platform)),
285294
bindings=[
@@ -453,10 +462,8 @@ def build(
453462
elif len(configuration.interpreters) > 1:
454463
use_platform_suffix = True
455464

456-
filenames = Filenames.avoid_collisions_with(name)
457-
458465
errors = OrderedDict() # type: OrderedDict[Manifest, str]
459-
for manifest in create_manifests(configuration, name, pex, filenames):
466+
for manifest in create_manifests(configuration, name, pex, use_platform_suffix):
460467
args = [science, "--cache-dir", _science_dir(env, "cache")]
461468
if env.PEX_VERBOSE:
462469
args.append("-{verbosity}".format(verbosity="v" * env.PEX_VERBOSE))
@@ -467,10 +474,10 @@ def build(
467474
[
468475
"lift",
469476
"--file",
470-
"{name}={pex_file}".format(name=filenames.pex.name, pex_file=pex_file),
477+
"{name}={pex_file}".format(name=Filenames.PEX.name, pex_file=pex_file),
471478
"--file",
472479
"{name}={configure_binding}".format(
473-
name=filenames.configure_binding.name,
480+
name=Filenames.CONFIGURE_BINDING.name,
474481
configure_binding=os.path.join(
475482
os.path.dirname(__file__), "configure-binding.py"
476483
),

pex/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Copyright 2015 Pex project contributors.
22
# Licensed under the Apache License, Version 2.0 (see LICENSE).
33

4-
__version__ = "2.33.2"
4+
__version__ = "2.33.3"

tests/integration/scie/test_pex_scie.py

+34-2
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ def test_specified_science_binary(tmpdir):
313313

314314
local_science_binary = os.path.join(str(tmpdir), "science")
315315
with open(local_science_binary, "wb") as write_fp, URLFetcher().get_body_stream(
316-
"https://github.com/a-scie/lift/releases/download/v0.10.1/{binary}".format(
316+
"https://github.com/a-scie/lift/releases/download/v0.12.1/{binary}".format(
317317
binary=SysPlatform.CURRENT.qualified_binary_name("science")
318318
)
319319
) as read_fp:
@@ -357,7 +357,7 @@ def test_specified_science_binary(tmpdir):
357357
cached_science_binaries
358358
), "Expected the local science binary to be used but not cached."
359359
assert (
360-
"0.10.1"
360+
"0.12.1"
361361
== subprocess.check_output(args=[local_science_binary, "--version"]).decode("utf-8").strip()
362362
)
363363

@@ -1172,3 +1172,35 @@ def assert_scie_only(output_file):
11721172

11731173
assert_scie_only(cowsay_pex)
11741174
assert_scie_only(cowsay_scie)
1175+
1176+
1177+
@skip_if_no_provider
1178+
def test_scie_split_contents(tmpdir):
1179+
# type: (Tempdir) -> None
1180+
1181+
pex = tmpdir.join("cowsay.pex")
1182+
run_pex_command(
1183+
args=["cowsay<6", "-c", "cowsay", "-o", pex, "--scie", "lazy", "--scie-only"]
1184+
).assert_success()
1185+
1186+
assert not os.path.exists(pex)
1187+
1188+
scie, _ = os.path.splitext(pex)
1189+
assert is_exe(scie)
1190+
assert b"| Moo! |" in subprocess.check_output(args=[scie, "Moo!"])
1191+
1192+
split_dir = tmpdir.join("split")
1193+
subprocess.check_call(args=[scie, split_dir], env=make_env(SCIE="split"))
1194+
1195+
# TODO(John Sirois): When the 1.6.0 scie-jump is released, replace this check with:
1196+
# + Add is_exe(...) check.
1197+
# + Change is_script(...) check below to check_executable=True.
1198+
# + Execute the split PEX directly instead of via sys.executable below.
1199+
with open(os.path.join(split_dir, "lift.json")) as fp:
1200+
assert {file["name"]: file for file in json.load(fp)["scie"]["lift"]["files"]}[
1201+
"cowsay.pex"
1202+
]["executable"]
1203+
1204+
split_pex = os.path.join(split_dir, "cowsay.pex")
1205+
assert is_script(split_pex, check_executable=False)
1206+
assert b"| Moo! |" in subprocess.check_output(args=[sys.executable, split_pex, "Moo!"])

0 commit comments

Comments
 (0)