Skip to content

Commit 2a78d33

Browse files
committed
Improved coverage and handling of link fallbacks
1 parent 696c8d4 commit 2a78d33

File tree

2 files changed

+76
-16
lines changed

2 files changed

+76
-16
lines changed

src/manage/install_command.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -249,21 +249,14 @@ def _write_alias(cmd, install, alias, target):
249249
LOGGER.debug("Skipping %s alias because the launcher template was not found.", alias["name"])
250250
return
251251

252-
launcher_remap = cmd.scratch.setdefault("install_command._write_alias.launcher_remap", {})
253-
LOGGER.debug("remap %s", launcher_remap)
254-
try:
255-
launcher = launcher_remap[str(launcher)]
256-
except KeyError:
257-
pass
258-
259252
try:
260253
launcher_bytes = launcher.read_bytes()
261254
except OSError:
262255
warnings_shown = cmd.scratch.setdefault("install_command._write_alias.warnings_shown", set())
263256
if str(launcher) not in warnings_shown:
264257
LOGGER.warn("Failed to read launcher template at %s.", launcher)
265258
warnings_shown.add(str(launcher))
266-
LOGGER.debug(exc_info=True)
259+
LOGGER.debug("Failed to read %s", launcher, exc_info=True)
267260
return
268261

269262
existing_bytes = b''
@@ -275,7 +268,13 @@ def _write_alias(cmd, install, alias, target):
275268
except OSError:
276269
LOGGER.debug("Failed to read existing alias launcher.")
277270

278-
if existing_bytes != launcher_bytes:
271+
launcher_remap = cmd.scratch.setdefault("install_command._write_alias.launcher_remap", {})
272+
273+
if existing_bytes == launcher_bytes:
274+
# Valid existing launcher, so save its path in case we need it later
275+
# for a hard link.
276+
launcher_remap.setdefault(launcher.name, p)
277+
else:
279278
# First try and create a hard link
280279
unlink(p)
281280
try:
@@ -285,13 +284,21 @@ def _write_alias(cmd, install, alias, target):
285284
if ex.winerror != 17:
286285
# Report errors other than cross-drive links
287286
LOGGER.debug("Failed to create hard link for command.", exc_info=True)
288-
try:
289-
p.write_bytes(launcher_bytes)
290-
LOGGER.debug("Created %s as copy of %s", p.name, launcher.name)
291-
launcher_remap[str(launcher)] = p
292-
except OSError:
293-
LOGGER.error("Failed to create global command %s.", alias["name"])
294-
LOGGER.debug(exc_info=True)
287+
launcher2 = launcher_remap.get(launcher.name)
288+
if launcher2:
289+
try:
290+
os.link(launcher, p)
291+
except OSError:
292+
LOGGER.debug("Failed to create hard link from fallback launcher")
293+
launcher2 = None
294+
if not launcher2:
295+
try:
296+
p.write_bytes(launcher_bytes)
297+
LOGGER.debug("Created %s as copy of %s", p.name, launcher.name)
298+
launcher_remap[launcher.name] = p
299+
except OSError:
300+
LOGGER.error("Failed to create global command %s.", alias["name"])
301+
LOGGER.debug(exc_info=True)
295302

296303
p_target = p.with_name(p.name + ".__target__")
297304
try:

tests/test_install_command.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,59 @@ def test_write_alias_fallback_platform(alias_checker):
104104
alias_checker.check_w64(alias_checker.Cmd("-spam"), "1.0", "testB")
105105

106106

107+
def test_write_alias_launcher_missing(fake_config, assert_log, tmp_path):
108+
fake_config.launcher_exe = tmp_path / "non-existent.exe"
109+
fake_config.default_platform = '-32'
110+
fake_config.global_dir = tmp_path / "bin"
111+
IC._write_alias(
112+
fake_config,
113+
{"tag": "test"},
114+
{"name": "test.exe"},
115+
tmp_path / "target.exe",
116+
)
117+
assert_log(
118+
"Checking for launcher.*",
119+
"Checking for launcher.*",
120+
"Checking for launcher.*",
121+
"Create %s linking to %s",
122+
"Skipping %s alias because the launcher template was not found.",
123+
assert_log.end_of_log(),
124+
)
125+
126+
127+
def test_write_alias_launcher_unreadable(fake_config, assert_log, tmp_path):
128+
class FakeLauncherPath:
129+
stem = "test"
130+
suffix = ".exe"
131+
parent = tmp_path
132+
133+
@staticmethod
134+
def is_file():
135+
return True
136+
137+
@staticmethod
138+
def read_bytes():
139+
raise OSError("no reading for the test")
140+
141+
fake_config.scratch = {}
142+
fake_config.launcher_exe = FakeLauncherPath
143+
fake_config.default_platform = '-32'
144+
fake_config.global_dir = tmp_path / "bin"
145+
IC._write_alias(
146+
fake_config,
147+
{"tag": "test"},
148+
{"name": "test.exe"},
149+
tmp_path / "target.exe",
150+
)
151+
assert_log(
152+
"Checking for launcher.*",
153+
"Create %s linking to %s",
154+
"Failed to read launcher template at %s.",
155+
"Failed to read %s",
156+
assert_log.end_of_log(),
157+
)
158+
159+
107160
@pytest.mark.parametrize("default", [1, 0])
108161
def test_write_alias_default(alias_checker, monkeypatch, tmp_path, default):
109162
prefix = Path(tmp_path) / "runtime"

0 commit comments

Comments
 (0)