Skip to content

Commit 696c8d4

Browse files
committed
Avoid recreating unchanged global aliases, and use hard links where possible.
Fixes #184
1 parent f378d2a commit 696c8d4

File tree

3 files changed

+73
-11
lines changed

3 files changed

+73
-11
lines changed

src/manage/commands.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,10 @@ class BaseCommand:
351351
show_help = False
352352

353353
def __init__(self, args, root=None):
354+
# Storage for command-specific data per-command execution.
355+
# All data should use a unique key.
356+
self.scratch = {}
357+
354358
cmd_args = {
355359
k: v for k, v in
356360
[*CLI_SCHEMA.items(), *CLI_SCHEMA.get(self.CMD, {}).items()]

src/manage/install_command.py

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,12 @@ def _if_exists(launcher, plat):
225225

226226
def _write_alias(cmd, install, alias, target):
227227
p = (cmd.global_dir / alias["name"])
228+
target = Path(target)
228229
ensure_tree(p)
229-
unlink(p)
230230
launcher = cmd.launcher_exe
231231
if alias.get("windowed"):
232232
launcher = cmd.launcherw_exe or launcher
233+
233234
plat = install["tag"].rpartition("-")[-1]
234235
if plat:
235236
LOGGER.debug("Checking for launcher for platform -%s", plat)
@@ -247,8 +248,61 @@ def _write_alias(cmd, install, alias, target):
247248
else:
248249
LOGGER.debug("Skipping %s alias because the launcher template was not found.", alias["name"])
249250
return
250-
p.write_bytes(launcher.read_bytes())
251-
p.with_name(p.name + ".__target__").write_text(str(target), encoding="utf-8")
251+
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+
259+
try:
260+
launcher_bytes = launcher.read_bytes()
261+
except OSError:
262+
warnings_shown = cmd.scratch.setdefault("install_command._write_alias.warnings_shown", set())
263+
if str(launcher) not in warnings_shown:
264+
LOGGER.warn("Failed to read launcher template at %s.", launcher)
265+
warnings_shown.add(str(launcher))
266+
LOGGER.debug(exc_info=True)
267+
return
268+
269+
existing_bytes = b''
270+
try:
271+
with open(p, 'rb') as f:
272+
existing_bytes = f.read(len(launcher_bytes) + 1)
273+
except FileNotFoundError:
274+
pass
275+
except OSError:
276+
LOGGER.debug("Failed to read existing alias launcher.")
277+
278+
if existing_bytes != launcher_bytes:
279+
# First try and create a hard link
280+
unlink(p)
281+
try:
282+
os.link(launcher, p)
283+
LOGGER.debug("Created %s as hard link to %s", p.name, launcher.name)
284+
except OSError as ex:
285+
if ex.winerror != 17:
286+
# Report errors other than cross-drive links
287+
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)
295+
296+
p_target = p.with_name(p.name + ".__target__")
297+
try:
298+
if target.match(p_target.read_text(encoding="utf-8")):
299+
return
300+
except FileNotFoundError:
301+
pass
302+
except (OSError, UnicodeDecodeError):
303+
LOGGER.debug("Failed to read existing target path.", exc_info=True)
304+
305+
p_target.write_text(str(target), encoding="utf-8")
252306

253307

254308
def _create_shortcut_pep514(cmd, install, shortcut):

tests/test_install_command.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Cmd:
2222
default_platform = "-64"
2323

2424
def __init__(self, platform=None):
25+
self.scratch = {}
2526
if platform:
2627
self.default_platform = platform
2728

@@ -81,19 +82,19 @@ def check_warm64(self, cmd, tag, name):
8182

8283

8384
def test_write_alias_tag_with_platform(alias_checker):
84-
alias_checker.check_32(alias_checker.Cmd, "1.0-32", "testA")
85-
alias_checker.check_w32(alias_checker.Cmd, "1.0-32", "testB")
86-
alias_checker.check_64(alias_checker.Cmd, "1.0-64", "testC")
87-
alias_checker.check_w64(alias_checker.Cmd, "1.0-64", "testD")
88-
alias_checker.check_arm64(alias_checker.Cmd, "1.0-arm64", "testE")
89-
alias_checker.check_warm64(alias_checker.Cmd, "1.0-arm64", "testF")
85+
alias_checker.check_32(alias_checker.Cmd(), "1.0-32", "testA")
86+
alias_checker.check_w32(alias_checker.Cmd(), "1.0-32", "testB")
87+
alias_checker.check_64(alias_checker.Cmd(), "1.0-64", "testC")
88+
alias_checker.check_w64(alias_checker.Cmd(), "1.0-64", "testD")
89+
alias_checker.check_arm64(alias_checker.Cmd(), "1.0-arm64", "testE")
90+
alias_checker.check_warm64(alias_checker.Cmd(), "1.0-arm64", "testF")
9091

9192

9293
def test_write_alias_default_platform(alias_checker):
9394
alias_checker.check_32(alias_checker.Cmd("-32"), "1.0", "testA")
9495
alias_checker.check_w32(alias_checker.Cmd("-32"), "1.0", "testB")
95-
alias_checker.check_64(alias_checker.Cmd, "1.0", "testC")
96-
alias_checker.check_w64(alias_checker.Cmd, "1.0", "testD")
96+
alias_checker.check_64(alias_checker.Cmd(), "1.0", "testC")
97+
alias_checker.check_w64(alias_checker.Cmd(), "1.0", "testD")
9798
alias_checker.check_arm64(alias_checker.Cmd("-arm64"), "1.0", "testE")
9899
alias_checker.check_warm64(alias_checker.Cmd("-arm64"), "1.0", "testF")
99100

@@ -110,6 +111,7 @@ def test_write_alias_default(alias_checker, monkeypatch, tmp_path, default):
110111
class Cmd:
111112
global_dir = Path(tmp_path) / "bin"
112113
launcher_exe = None
114+
scratch = {}
113115
def get_installs(self):
114116
return [
115117
{
@@ -146,6 +148,7 @@ def write_alias(*a):
146148

147149
def test_print_cli_shortcuts(patched_installs, assert_log, monkeypatch, tmp_path):
148150
class Cmd:
151+
scratch = {}
149152
global_dir = Path(tmp_path)
150153
def get_installs(self):
151154
return installs.get_installs(None)
@@ -163,6 +166,7 @@ def get_installs(self):
163166

164167
def test_print_path_warning(patched_installs, assert_log, tmp_path):
165168
class Cmd:
169+
scratch = {}
166170
global_dir = Path(tmp_path)
167171
def get_installs(self):
168172
return installs.get_installs(None)

0 commit comments

Comments
 (0)