2525DOWNLOAD_CACHE = {}
2626
2727
28+ DEFAULT_SITE_DIRS = ["Lib\\ site-packages" , "Scripts" ]
29+
2830def _multihash (file , hashes ):
2931 import hashlib
3032 LOGGER .debug ("Calculating hashes: %s" , ", " .join (hashes ))
@@ -346,10 +348,21 @@ def _cleanup_arp_entries(cmd, install_shortcut_pairs):
346348 cleanup ([i for i , s in install_shortcut_pairs ], cmd .tags )
347349
348350
351+ def _create_entrypoints (cmd , install , shortcut ):
352+ from .entrypointutils import scan_and_create
353+ scan_and_create (cmd , install , shortcut )
354+
355+
356+ def _cleanup_entrypoints (cmd , install_shortcut_pairs ):
357+ from .entrypointutils import cleanup
358+ cleanup (cmd , install_shortcut_pairs )
359+
360+
349361SHORTCUT_HANDLERS = {
350362 "pep514" : (_create_shortcut_pep514 , _cleanup_shortcut_pep514 ),
351363 "start" : (_create_start_shortcut , _cleanup_start_shortcut ),
352364 "uninstall" : (_create_arp_entry , _cleanup_arp_entries ),
365+ "site-dirs" : (_create_entrypoints , _cleanup_entrypoints ),
353366}
354367
355368
@@ -396,6 +409,16 @@ def update_all_shortcuts(cmd):
396409 create (cmd , i , s )
397410 shortcut_written .setdefault (s ["kind" ], []).append ((i , s ))
398411
412+ # Earlier releases may not have site_dirs. If not, assume
413+ if ("site-dirs" in (cmd .enable_shortcut_kinds or ("site-dirs" ,)) and
414+ "site-dirs" not in (cmd .disable_shortcut_kinds or ()) and
415+ all (s ["kind" ] != "site-dirs" for s in i .get ("shortcuts" , ()))):
416+
417+ create , cleanup = SHORTCUT_HANDLERS ["site-dirs" ]
418+ s = dict (kind = "site-dirs" , dirs = DEFAULT_SITE_DIRS )
419+ create (cmd , i , s )
420+ shortcut_written .setdefault ("site-dirs" , []).append ((i , s ))
421+
399422 if cmd .global_dir and cmd .global_dir .is_dir () and cmd .launcher_exe :
400423 for target in cmd .global_dir .glob ("*.exe.__target__" ):
401424 alias = target .with_suffix ("" )
@@ -522,15 +545,7 @@ def _download_one(cmd, source, install, download_dir, *, must_copy=False):
522545 return package
523546
524547
525- def _should_preserve_on_upgrade (cmd , root , path ):
526- if path .match ("site-packages" ):
527- return True
528- if path .parent == root and path .match ("Scripts" ):
529- return True
530- return False
531-
532-
533- def _preserve_site (cmd , root ):
548+ def _preserve_site (cmd , root , install ):
534549 if not root .is_dir ():
535550 return None
536551 if not cmd .preserve_site_on_upgrade :
@@ -542,39 +557,47 @@ def _preserve_site(cmd, root):
542557 if cmd .repair :
543558 LOGGER .verbose ("Not preserving site directory because of --repair" )
544559 return None
560+
545561 state = []
546562 i = 0
547- dirs = [root ]
563+
564+ site_dirs = DEFAULT_SITE_DIRS
565+ for s in install .get ("shortcuts" , ()):
566+ if s ["kind" ] == "site-dirs" :
567+ site_dirs = s .get ("dirs" , ())
568+ break
569+
548570 target_root = root .with_name (f"_{ root .name } " )
549571 target_root .mkdir (parents = True , exist_ok = True )
550- while dirs :
551- if _should_preserve_on_upgrade (cmd , root , dirs [0 ]):
552- while True :
553- target = target_root / str (i )
554- i += 1
555- try :
556- unlink (target )
557- break
558- except FileNotFoundError :
559- break
560- except OSError :
561- LOGGER .verbose ("Failed to remove %s." , target )
562- try :
563- LOGGER .info ("Preserving %s during update." , dirs [0 ].relative_to (root ))
564- except ValueError :
565- # Just in case a directory goes weird, so we don't break
566- LOGGER .verbose (exc_info = True )
567- LOGGER .verbose ("Moving %s to %s" , dirs [0 ], target )
572+
573+ for dirname in site_dirs :
574+ d = root / dirname
575+ if not d .is_dir ():
576+ continue
577+
578+ while True :
579+ target = target_root / str (i )
580+ i += 1
568581 try :
569- dirs [0 ].rename (target )
582+ unlink (target )
583+ break
584+ except FileNotFoundError :
585+ break
570586 except OSError :
571- LOGGER .warn ("Failed to preserve %s during update." , dirs [0 ])
572- LOGGER .verbose ("TRACEBACK" , exc_info = True )
573- else :
574- state .append ((dirs [0 ], target ))
587+ LOGGER .verbose ("Failed to remove %s." , target )
588+ try :
589+ LOGGER .info ("Preserving %s during update." , d .relative_to (root ))
590+ except ValueError :
591+ # Just in case a directory goes weird, so we don't break
592+ LOGGER .verbose (exc_info = True )
593+ LOGGER .verbose ("Moving %s to %s" , d , target )
594+ try :
595+ d .rename (target )
596+ except OSError :
597+ LOGGER .warn ("Failed to preserve %s during update." , d )
598+ LOGGER .verbose ("TRACEBACK" , exc_info = True )
575599 else :
576- dirs .extend (d for d in dirs [0 ].iterdir () if d .is_dir ())
577- dirs .pop (0 )
600+ state .append ((d , target ))
578601 # Append None, target_root last to clean up after restore is done
579602 state .append ((None , target_root ))
580603 return state
@@ -634,7 +657,7 @@ def _install_one(cmd, source, install, *, target=None):
634657
635658 dest = target or (cmd .install_dir / install ["id" ])
636659
637- preserved_site = _preserve_site (cmd , dest )
660+ preserved_site = _preserve_site (cmd , dest , install )
638661
639662 LOGGER .verbose ("Extracting %s to %s" , package , dest )
640663 if not cmd .repair :
0 commit comments