Skip to content

Commit dd3de21

Browse files
authored
Fix .desktop files installed by PEX scies. (#3099)
Fix `.desktop` files installed by `--scie-icon` / `--scie-desktop-file` PEX scies to be more robust. They now work even if the original PEX scie they were installed by is (re)moved as well as properly handling a `SCIE_BASE` with spaces in the path.
1 parent d1afccd commit dd3de21

File tree

7 files changed

+137
-37
lines changed

7 files changed

+137
-37
lines changed

CHANGES.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Release Notes
22

3+
## 2.88.1
4+
5+
This release fixes `.desktop` files installed by `--scie-icon` and `--scie-desktop-file` PEX scies
6+
to be more robust. They now work even if the original PEX scie they were installed by is (re)moved
7+
as well as properly handling a `SCIE_BASE` with spaces in the path.
8+
9+
* Fix `.desktop` files installed by PEX scies. (#3099)
10+
311
## 2.88.0
412

513
This release adds support for `--pip-version 26.0.1`.

pex/scie/configure-binding.py

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,35 @@ def _desktop_install_path(app_name):
6767
def desktop_install(
6868
app_name, # type: str
6969
desktop_file, # type: str
70+
scie_jump, # type: str
71+
scie_lift, # type: str
72+
scie_exe, # type: str
7073
icon=None, # type: Optional[str]
7174
):
7275
# type: (...) -> None
7376

7477
desktop_install_path = _desktop_install_path(app_name)
75-
os.makedirs(os.path.dirname(desktop_install_path))
78+
79+
try:
80+
os.makedirs(os.path.dirname(desktop_install_path))
81+
except OSError as e:
82+
if e.errno != errno.EEXIST:
83+
raise
84+
85+
def maybe_quote(value):
86+
# type: (str) -> str
87+
if " " in value:
88+
return '"{value}"'.format(value=value)
89+
return value
90+
7691
with open(desktop_file) as in_fp, open(desktop_install_path, "w") as out_fp:
77-
fmt_dict = dict(name=app_name, exe=os.environ["SCIE"])
92+
fmt_dict = dict(
93+
name=app_name,
94+
exe=scie_exe,
95+
default_exec="{scie_jump} --launch={scie_lift}".format(
96+
scie_jump=maybe_quote(scie_jump), scie_lift=maybe_quote(scie_lift)
97+
),
98+
)
7899
if icon:
79100
fmt_dict.update(icon=icon)
80101
out_fp.write(in_fp.read().format(**fmt_dict))
@@ -101,6 +122,9 @@ def desktop_uninstall(app_name):
101122
def prompt_desktop_install(
102123
app_name, # type: str
103124
desktop_file, # type: str
125+
scie_jump, # type: str
126+
scie_lift, # type: str
127+
scie_exe, # type: str
104128
icon=None, # type: Optional[str]
105129
):
106130
# type: (...) -> None
@@ -115,7 +139,14 @@ def prompt_desktop_install(
115139
message="Install a desktop entry?",
116140
detail="This will make it easier to launch {app_name}.".format(app_name=app_name),
117141
):
118-
desktop_install(app_name=app_name, desktop_file=desktop_file, icon=icon)
142+
desktop_install(
143+
app_name=app_name,
144+
desktop_file=desktop_file,
145+
scie_jump=scie_jump,
146+
scie_lift=scie_lift,
147+
scie_exe=scie_exe,
148+
icon=icon,
149+
)
119150

120151

121152
if __name__ == "__main__":
@@ -127,6 +158,9 @@ def prompt_desktop_install(
127158
)
128159
parser.add_argument("--venv-bin-dir", help="The platform-specific venv bin dir name.")
129160
parser.add_argument("--desktop-file", help="An optional application .desktop file.")
161+
parser.add_argument("--scie-name", help="The name of the scie.")
162+
parser.add_argument("--scie-jump", help="The path of this scie's scie-jump tip extracted.")
163+
parser.add_argument("--scie-lift", help="The path of this scie's lift manifest extracted.")
130164
parser.add_argument("--icon", help="An optional application icon file.")
131165
parser.add_argument(
132166
"--no-prompt-desktop-install",
@@ -154,22 +188,32 @@ def prompt_desktop_install(
154188
)
155189

156190
if options.desktop_file:
157-
scie_name = os.path.basename(os.environ["SCIE_ARGV0"])
191+
exe = os.environ["SCIE"]
158192
override_install_desktop_file = os.environ.get("CONFIGURE_DESKTOP_INSTALL", "").lower()
159193
if override_install_desktop_file == "prompt" or (
160194
not override_install_desktop_file and options.prompt_desktop_install
161195
):
162196
prompt_desktop_install(
163-
app_name=scie_name, desktop_file=options.desktop_file, icon=options.icon
197+
app_name=options.scie_name,
198+
desktop_file=options.desktop_file,
199+
scie_jump=options.scie_jump,
200+
scie_lift=options.scie_lift,
201+
scie_exe=exe,
202+
icon=options.icon,
164203
)
165204
elif override_install_desktop_file in ("1", "true") or (
166205
not override_install_desktop_file and not options.prompt_desktop_install
167206
):
168207
desktop_install(
169-
app_name=scie_name, desktop_file=options.desktop_file, icon=options.icon
208+
app_name=options.scie_name,
209+
desktop_file=options.desktop_file,
210+
scie_jump=options.scie_jump,
211+
scie_lift=options.scie_lift,
212+
scie_exe=exe,
213+
icon=options.icon,
170214
)
171215
elif override_install_desktop_file == "uninstall":
172-
desktop_uninstall(app_name=scie_name)
216+
desktop_uninstall(app_name=options.scie_name)
173217

174218
write_bindings(
175219
env_file=os.environ["SCIE_BINDING_ENV"],

pex/scie/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ def generate_desktop_file(
374374
parser.ensure_set("Version", default="1.0")
375375
parser.ensure_set("Type", default="Application")
376376
parser.ensure_set("Name", default=app_name)
377-
parser.ensure_set("Exec", default="{exe}")
377+
parser.ensure_set("Exec", default="{default_exec}")
378378
if self.icon:
379379
parser.ensure_set("Icon", default="{icon}")
380380

pex/scie/science.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def qualified_binary_name(self, binary_name):
6969

7070

7171
SCIENCE_RELEASES_URL = "https://github.com/a-scie/lift/releases"
72-
MIN_SCIENCE_VERSION = Version("0.17.2")
72+
MIN_SCIENCE_VERSION = Version("0.18.1")
7373
SCIENCE_REQUIREMENT = SpecifierSet("~={min_version}".format(min_version=MIN_SCIENCE_VERSION))
7474

7575

@@ -84,7 +84,7 @@ def _science_binary_url(suffix=""):
8484

8585

8686
PTEX_VERSION = "1.7.0"
87-
SCIE_JUMP_VERSION = "1.9.2"
87+
SCIE_JUMP_VERSION = "1.11.2"
8888

8989

9090
class Filenames(Enum["Filenames.Value"]):
@@ -125,7 +125,7 @@ def _is_free_threaded_pex(pex_info):
125125

126126
def create_manifests(
127127
configuration, # type: ScieConfiguration
128-
name, # type: str
128+
app_name, # type: str
129129
pex, # type: PEX
130130
use_platform_suffix=None, # type: Optional[bool]
131131
):
@@ -332,7 +332,7 @@ def create_cmd(named_entry_point):
332332
)
333333

334334
lift_template = {
335-
"name": name,
335+
"name": app_name,
336336
"load_dotenv": configuration.options.load_dotenv,
337337
"scie_jump": scie_jump_config,
338338
} # type: Dict[str, Any]
@@ -410,7 +410,7 @@ def configuration_binding(
410410

411411
manifest_path = os.path.join(
412412
safe_mkdtemp(),
413-
interpreter.platform.qualified_file_name("{name}-lift.toml".format(name=name)),
413+
interpreter.platform.qualified_file_name("{name}-lift.toml".format(name=app_name)),
414414
)
415415

416416
version_str = interpreter.version_str
@@ -450,7 +450,16 @@ def configuration_binding(
450450
)
451451
if interpreter.platform.os is Os.LINUX and configuration.options.desktop_app:
452452
extra_configure_binding_args.extend(
453-
("--desktop-file", Filenames.DESKTOP_FILE.placeholder)
453+
(
454+
"--desktop-file",
455+
Filenames.DESKTOP_FILE.placeholder,
456+
"--scie-name",
457+
app_name,
458+
"--scie-jump",
459+
"{scie.jump}",
460+
"--scie-lift",
461+
"{scie.lift}",
462+
)
454463
)
455464
if not configuration.options.desktop_app.prompt_install:
456465
extra_configure_binding_args.append("--no-prompt-desktop-install")

pex/version.py

Lines changed: 1 addition & 1 deletion
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.88.0"
4+
__version__ = "2.88.1"

tests/integration/scie/test_pex_scie.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ def test_specified_science_binary(tmpdir):
350350

351351
local_science_binary = os.path.join(str(tmpdir), "science")
352352
with open(local_science_binary, "wb") as write_fp, URLFetcher().get_body_stream(
353-
"https://github.com/a-scie/lift/releases/download/v0.17.2/{binary}".format(
353+
"https://github.com/a-scie/lift/releases/download/v0.18.1/{binary}".format(
354354
binary=SysPlatform.CURRENT.qualified_binary_name("science")
355355
)
356356
) as read_fp:
@@ -394,7 +394,7 @@ def test_specified_science_binary(tmpdir):
394394
cached_science_binaries
395395
), "Expected the local science binary to be used but not cached."
396396
assert (
397-
"0.17.2"
397+
"0.18.1"
398398
== subprocess.check_output(args=[local_science_binary, "--version"]).decode("utf-8").strip()
399399
)
400400

tests/integration/test_scie_desktop_install.py

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

44
from __future__ import absolute_import
55

6-
import json
76
import os
87
import subprocess
98
from textwrap import dedent
@@ -179,7 +178,9 @@ def test_desktop_file(tmpdir):
179178
dedent(
180179
"""\
181180
[Test]
182-
Data={{"name": "{name}", "exe": "{exe}", "icon": "{icon}"}}
181+
name={name}
182+
exe={exe}
183+
icon={icon}
183184
184185
[Desktop Entry]
185186
Name=Slartibartfast
@@ -188,9 +189,9 @@ def test_desktop_file(tmpdir):
188189
)
189190
)
190191

191-
icon = tmpdir.join("file.ico")
192+
icon_path = tmpdir.join("file.ico")
192193
icon_contents = "1/137"
193-
with open(icon, "w") as fp:
194+
with open(icon_path, "w") as fp:
194195
fp.write(icon_contents)
195196

196197
run_pex_command(
@@ -206,29 +207,67 @@ def test_desktop_file(tmpdir):
206207
"eager",
207208
"--scie-only",
208209
"--scie-icon",
209-
icon,
210+
icon_path,
210211
"--scie-desktop-file",
211212
desktop_file,
212213
"--no-scie-prompt-desktop-install",
213214
]
214215
).assert_success()
215216

216-
xdg_data_home = safe_mkdir(tmpdir.join("XDG_DATA_HOME"))
217-
scie_base = tmpdir.join("nce")
218-
env = make_env(SCIE_BASE=scie_base, XDG_DATA_HOME=xdg_data_home)
219-
assert b"| Moo! |" in subprocess.check_output(args=[scie, "Moo!"], env=env)
217+
def assert_desktop_entry(
218+
scie_base, # type: str
219+
xdg_data_home, # type: str
220+
):
221+
env = make_env(SCIE_BASE=scie_base, XDG_DATA_HOME=xdg_data_home)
222+
assert b"| Moo! |" in subprocess.check_output(args=[scie, "Moo!"], env=env)
220223

221-
parser = DesktopFileParser.create(os.path.join(xdg_data_home, "applications", "cowsay.desktop"))
222-
assert "Slartibartfast" == parser.get("Desktop Entry", "Name")
223-
assert "DoesNotExist.png" == parser.get("Desktop Entry", "Icon")
224+
parser = DesktopFileParser.create(
225+
os.path.join(xdg_data_home, "applications", "cowsay.desktop")
226+
)
227+
assert "Slartibartfast" == parser.get("Desktop Entry", "Name")
228+
assert "DoesNotExist.png" == parser.get("Desktop Entry", "Icon")
224229

225-
data = json.loads(parser.get("Test", "Data"))
226-
assert parser.get("Desktop Entry", "Exec") == data.pop("exe")
227-
assert "cowsay" == data.pop("name")
230+
executable = parser.get("Desktop Entry", "Exec")
231+
assert executable != scie
232+
assert scie == parser.get("Test", "exe")
228233

229-
icon = data.pop("icon")
230-
assert scie_base == commonpath((scie_base, icon))
231-
with open(icon) as fp:
232-
assert icon_contents == fp.read()
234+
def assert_quoting(string):
235+
# type: (str) -> str
233236

234-
assert not data
237+
if " " not in string:
238+
return string
239+
240+
prefix, unquoted, suffix = string.split('"')
241+
assert not prefix, "Expected {string} to be quoted but found prefix {prefix}.".format(
242+
string=string, prefix=prefix
243+
)
244+
assert not suffix, "Expected {string} to be quoted but found prefix {suffix}.".format(
245+
string=string, suffix=suffix
246+
)
247+
return unquoted
248+
249+
exe, arg = executable.split()
250+
exe = assert_quoting(exe)
251+
252+
option, value = arg.split("=")
253+
assert "--launch" == option
254+
value = assert_quoting(value)
255+
256+
assert b"| Moo? |" in subprocess.check_output(
257+
args=[exe, "{option}={value}".format(option=option, value=value)] + ["Moo?"], env=env
258+
)
259+
260+
assert "cowsay" == parser.get("Test", "name")
261+
262+
icon = parser.get("Test", "icon")
263+
assert scie_base == commonpath((scie_base, icon))
264+
with open(icon) as fp:
265+
assert icon_contents == fp.read()
266+
267+
assert_desktop_entry(
268+
scie_base=tmpdir.join("nce-no-space"),
269+
xdg_data_home=safe_mkdir(tmpdir.join("XDG_DATA_HOME")),
270+
)
271+
assert_desktop_entry(
272+
scie_base=tmpdir.join("nce-space"), xdg_data_home=safe_mkdir(tmpdir.join("XDG DATA HOME"))
273+
)

0 commit comments

Comments
 (0)