From 3054f63af63c98d816674da468f3e93043011b6a Mon Sep 17 00:00:00 2001 From: Bartosz Sokorski Date: Wed, 25 Sep 2024 11:53:53 +0200 Subject: [PATCH] Small refactor to installer.scripts --- src/installer/scripts.py | 89 ++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 50 deletions(-) diff --git a/src/installer/scripts.py b/src/installer/scripts.py index 0c7b7ea0..e45e27fe 100644 --- a/src/installer/scripts.py +++ b/src/installer/scripts.py @@ -3,20 +3,10 @@ import io import os import shlex -import sys import zipfile from dataclasses import dataclass, field -from types import ModuleType -from typing import TYPE_CHECKING, Mapping, Optional, Tuple, Union - -if sys.version_info >= (3, 9): # pragma: no cover - from importlib.resources import files - - def read_binary(package: Union[str, ModuleType], file_path: str) -> bytes: - return (files(package) / file_path).read_bytes() - -else: # pragma: no cover - from importlib.resources import read_binary +from importlib.resources import read_binary +from typing import TYPE_CHECKING, Mapping, Optional, Tuple from installer import _scripts @@ -52,41 +42,6 @@ def read_binary(package: Union[str, ModuleType], file_path: str) -> bytes: """ -def _is_executable_simple(executable: bytes) -> bool: - if b" " in executable: - return False - shebang_length = len(executable) + 3 # Prefix #! and newline after. - # According to distlib, Darwin can handle up to 512 characters. But I want - # to avoid platform sniffing to make this as platform agnostic as possible. - # The "complex" script isn't that bad anyway. - return shebang_length <= 127 - - -def _build_shebang(executable: str, forlauncher: bool) -> bytes: - """Build a shebang line. - - The non-launcher cases are taken directly from distlib's implementation, - which tries its best to account for command length, spaces in path, etc. - - https://bitbucket.org/pypa/distlib/src/58cd5c6/distlib/scripts.py#lines-124 - """ - executable_bytes = executable.encode("utf-8") - if forlauncher: # The launcher can just use the command as-is. - return b"#!" + executable_bytes - if _is_executable_simple(executable_bytes): - return b"#!" + executable_bytes - - # Shebang support for an executable with a space in it is under-specified - # and platform-dependent, so we use a clever hack to generate a script to - # run in ``/bin/sh`` that should work on all reasonably modern platforms. - # Read the following message to understand how the hack works: - # https://github.com/pypa/installer/pull/4#issuecomment-623668717 - - quoted = shlex.quote(executable).encode("utf-8") - # I don't understand a lick what this is trying to do. - return b"#!/bin/sh\n'''exec' " + quoted + b' "$0" "$@"\n' + b"' '''" - - class InvalidScript(ValueError): """Raised if the user provides incorrect script section or kind.""" @@ -146,7 +101,7 @@ def generate(self, executable: str, kind: "LauncherKind") -> Tuple[str, bytes]: """ launcher = self._get_launcher_data(kind) executable = self._get_alternate_executable(executable, kind) - shebang = _build_shebang(executable, forlauncher=bool(launcher)) + shebang = self._build_shebang(executable, forlauncher=bool(launcher)) code = _SCRIPT_TEMPLATE.format( module=self.module, import_name=self.attr.split(".")[0], @@ -154,11 +109,45 @@ def generate(self, executable: str, kind: "LauncherKind") -> Tuple[str, bytes]: ).encode("utf-8") if launcher is None: - return (self.name, shebang + b"\n" + code) + return self.name, shebang + b"\n" + code stream = io.BytesIO() with zipfile.ZipFile(stream, "w") as zf: zf.writestr("__main__.py", code) name = f"{self.name}.exe" data = launcher + shebang + b"\n" + stream.getvalue() - return (name, data) + return name, data + + @staticmethod + def _is_executable_simple(executable: bytes) -> bool: + if b" " in executable: + return False + shebang_length = len(executable) + 3 # Prefix #! and newline after. + # According to distlib, Darwin can handle up to 512 characters. But I want + # to avoid platform sniffing to make this as platform-agnostic as possible. + # The "complex" script isn't that bad anyway. + return shebang_length <= 127 + + def _build_shebang(self, executable: str, forlauncher: bool) -> bytes: + """Build a shebang line. + + The non-launcher cases are taken directly from distlib's implementation, + which tries its best to account for command length, spaces in path, etc. + + https://bitbucket.org/pypa/distlib/src/58cd5c6/distlib/scripts.py#lines-124 + """ + executable_bytes = executable.encode("utf-8") + if forlauncher: # The launcher can just use the command as-is. + return b"#!" + executable_bytes + if self._is_executable_simple(executable_bytes): + return b"#!" + executable_bytes + + # Shebang support for an executable with a space in it is under-specified + # and platform-dependent, so we use a clever hack to generate a script to + # run in ``/bin/sh`` that should work on all reasonably modern platforms. + # Read the following message to understand how the hack works: + # https://github.com/pypa/installer/pull/4#issuecomment-623668717 + + quoted = shlex.quote(executable).encode("utf-8") + # I don't understand a lick what this is trying to do. + return b"#!/bin/sh\n'''exec' " + quoted + b' "$0" "$@"\n' + b"' '''"