From 9a11bc05f3af568d67b840d0bd6c49a6f35159d5 Mon Sep 17 00:00:00 2001 From: deathaxe Date: Sat, 4 Apr 2026 12:45:20 +0200 Subject: [PATCH] Fix removal of locked libraries on Windows This commit moves locked files (e.g. DLLs, pyd) to Data/Trash directory, to defer removal until next restart of ST, while enabling placing new file at the same location upon library upgrade. Technically, charset_normalizer failed to upgrade with WinError 5 due to a .pyd file in Lib/ directory being locked by Windows OS. The strategy is already used by `delete_directory()` function. --- package_control/library.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/package_control/library.py b/package_control/library.py index d97aa882..db56b50d 100644 --- a/package_control/library.py +++ b/package_control/library.py @@ -2,6 +2,9 @@ import re import shutil +from datetime import datetime +from hashlib import sha1 + import sublime from . import sys_path @@ -401,6 +404,26 @@ def remove(installed_library): cache_dir = sys_path.python_libs_cache_path(python_version) cache_ext = ".cpython-{}.opt-1.pyc".format(python_version.replace(".", "")) + # use timestamp as session id, in case library is installed/removed + # multiple times to avoid naming conflicts, when moving to trash. + session_id = str(datetime.now()) + + # Especially on Windows, files may be locked and therefore can't be removed, + # while loaded. They can however be renamed, thus moving them to trash directory is + # possible in order to simulate deletion for the sense of managing packages/libraries. + trash_dir = sys_path.trash_path() + os.makedirs(trash_dir, exist_ok=True) + + def remove_file(path): + try: + os.remove(path) + except OSError: + trash_path = os.path.join( + trash_dir, + sha1((session_id + path).encode('utf-8')).hexdigest().lower() + ) + os.rename(path, trash_path) + for rel_path in dist_info.top_level_paths(): # Remove the .dist-info dir last so we have info for clean-up in case # we hit an error along the way @@ -417,12 +440,12 @@ def remove(installed_library): delete_directory(os.path.join(cache_dir, rel_path)) elif os.path.isfile(abs_path): - os.remove(abs_path) + remove_file(abs_path) # remove bytecode cache if cache_dir and abs_path.endswith(".py"): try: - os.remove(os.path.join(cache_dir, rel_path[:-3] + cache_ext)) + remove_file(os.path.join(cache_dir, rel_path[:-3] + cache_ext)) except OSError: pass