From df24405ae90c215a68e0320ab7d2280a17acf41a Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Fri, 10 Dec 2021 18:30:29 -0700 Subject: [PATCH] Allow changing the installation root. --- src/installer/destinations.py | 13 +++++++++++-- src/installer/utils.py | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/installer/destinations.py b/src/installer/destinations.py index 368d930e..dc7ee76c 100644 --- a/src/installer/destinations.py +++ b/src/installer/destinations.py @@ -2,6 +2,7 @@ import io import os +import sysconfig from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, Optional, Tuple, Union from installer.records import Hash, RecordEntry @@ -12,6 +13,7 @@ copyfileobj_with_hashing, fix_shebang, ) +from src.installer.utils import SCHEME_NAMES, change_root if TYPE_CHECKING: from installer.scripts import LauncherKind, ScriptSection @@ -89,10 +91,11 @@ class SchemeDictionaryDestination(WheelDestination): def __init__( self, - scheme_dict: Dict[str, str], + scheme_dict: Optional[Dict[str, str]], interpreter: str, script_kind: "LauncherKind", hash_algorithm: str = "sha256", + root: Optional["os.PathLike[str]"] = None, ) -> None: """Construct a ``SchemeDictionaryDestination`` object. @@ -103,10 +106,16 @@ def __init__( of :any:`hashlib.algorithms_available` (ideally from :any:`hashlib.algorithms_guaranteed`). """ - self.scheme_dict = scheme_dict + if scheme_dict is not None: + self.scheme_dict = scheme_dict + else: + self.scheme_dict = sysconfig.get_paths() self.interpreter = interpreter self.script_kind = script_kind self.hash_algorithm = hash_algorithm + if root is not None: + for scheme in SCHEME_NAMES: + self.scheme_dict[scheme] = change_root(root, self.scheme_dict[scheme]) def write_to_fs(self, scheme: Scheme, path: str, stream: BinaryIO) -> RecordEntry: """Write contents of ``stream`` to the correct location on the filesystem. diff --git a/src/installer/utils.py b/src/installer/utils.py index cedbe72b..927b14e9 100644 --- a/src/installer/utils.py +++ b/src/installer/utils.py @@ -229,3 +229,26 @@ def parse_entrypoints(text: str) -> Iterable[Tuple[str, str, str, "ScriptSection script_section = cast("ScriptSection", section[: -len("_scripts")]) yield name, module, attrs, script_section + + +def change_root(new_root: os.PathLike[str], pathname) -> str: + """Return 'pathname' with 'new_root' prepended. + + If 'pathname' is relative, this is equivalent to "os.path.join(new_root,pathname)". + Otherwise, it requires making 'pathname' relative and then joining the + two, which is tricky on DOS/Windows and Mac OS. + """ + if os.name == "posix": + if not os.path.isabs(pathname): + return os.path.join(new_root, pathname) + else: + return os.path.join(new_root, pathname[1:]) + + elif os.name == "nt": + (drive, path) = os.path.splitdrive(pathname) + if path[0] == "\\": + path = path[1:] + return os.path.join(new_root, path) + + else: + raise NotImplementedError("nothing known about platform '%s'" % os.name)